Integrate new tab completion methods

There is now an option to choose which tab completion method to use.
Also, emotes can be tab completed.
This commit is contained in:
Calvin Montgomery 2017-01-10 22:26:46 -08:00
parent e1ad7c63af
commit 27e168ba8b
8 changed files with 109 additions and 9 deletions

View file

@ -2,7 +2,7 @@
"author": "Calvin Montgomery", "author": "Calvin Montgomery",
"name": "CyTube", "name": "CyTube",
"description": "Online media synchronizer and chat", "description": "Online media synchronizer and chat",
"version": "3.27.1", "version": "3.28.0",
"repository": { "repository": {
"url": "http://github.com/calzoneman/sync" "url": "http://github.com/calzoneman/sync"
}, },

View file

@ -240,6 +240,7 @@ html(lang="en")
script(id="socketio-js", src=sioSource) script(id="socketio-js", src=sioSource)
script(src="/js/data.js") script(src="/js/data.js")
script(src="/js/util.js") script(src="/js/util.js")
script(src="/js/tabcomplete.js")
script(src="/js/player.js") script(src="/js/player.js")
script(src="/js/paginator.js") script(src="/js/paginator.js")
script(src="/js/ui.js") script(src="/js/ui.js")

View file

@ -114,6 +114,12 @@ mixin us-chat
+rcheckbox("us-sendbtn", "Add a send button to chat") +rcheckbox("us-sendbtn", "Add a send button to chat")
+rcheckbox("us-no-emotes", "Disable chat emotes") +rcheckbox("us-no-emotes", "Disable chat emotes")
+rcheckbox("us-strip-image", "Remove images from chat") +rcheckbox("us-strip-image", "Remove images from chat")
.form-group
label.control-label.col-sm-4(for="#us-chat-tab-method") Tab completion method
.col-sm-8
select#us-chat-tab-method.form-control
option(value="Cycle options") Cycle options
option(value="Longest unique match") Longest unique match
mixin us-mod mixin us-mod
#us-mod.tab-pane #us-mod.tab-pane

View file

@ -4,7 +4,7 @@ require('../../www/js/tabcomplete');
describe('CyTube.tabCompletionMethods', () => { describe('CyTube.tabCompletionMethods', () => {
describe('"Longest unique prefix"', () => { describe('"Longest unique match"', () => {
const testcases = [ const testcases = [
{ {
input: 'and his name is j', input: 'and his name is j',
@ -26,6 +26,16 @@ describe('CyTube.tabCompletionMethods', () => {
}, },
description: 'completes a unique match' description: 'completes a unique match'
}, },
{
input: 'johnc',
position: 5,
options: ['johncena', 'johnstamos', 'johto'],
output: {
text: 'johncena ',
newPosition: 9
},
description: 'completes a unique match at the beginning of the string'
},
{ {
input: 'and his name is johnc', input: 'and his name is johnc',
position: 21, position: 21,
@ -70,7 +80,7 @@ describe('CyTube.tabCompletionMethods', () => {
testcases.forEach(test => { testcases.forEach(test => {
it(test.description, () => { it(test.description, () => {
assert.deepEqual( assert.deepEqual(
CyTube.tabCompleteMethods['Longest unique prefix']( CyTube.tabCompleteMethods['Longest unique match'](
test.input, test.input,
test.position, test.position,
test.options, test.options,
@ -108,6 +118,30 @@ describe('CyTube.tabCompletionMethods', () => {
], ],
description: 'cycles through options correctly' description: 'cycles through options correctly'
}, },
{
input: 'c',
position: 1,
options: ['COBOL', 'Carlos', 'carl', 'john', 'joseph', ''],
outputs: [
{
text: 'carl ',
newPosition: 5
},
{
text: 'Carlos ',
newPosition: 7
},
{
text: 'COBOL ',
newPosition: 6
},
{
text: 'carl ',
newPosition: 5
}
],
description: 'cycles through options correctly at the beginning of the string'
},
{ {
input: 'hey ', input: 'hey ',
position: 5, position: 5,

View file

@ -123,7 +123,8 @@ var USEROPTS = {
show_shadowchat : getOrDefault("show_shadowchat", false), show_shadowchat : getOrDefault("show_shadowchat", false),
emotelist_sort : getOrDefault("emotelist_sort", true), emotelist_sort : getOrDefault("emotelist_sort", true),
no_emotes : getOrDefault("no_emotes", false), no_emotes : getOrDefault("no_emotes", false),
strip_image : getOrDefault("strip_image", false) strip_image : getOrDefault("strip_image", false),
chat_tab_method : getOrDefault("chat_tab_method", "Cycle options")
}; };
/* Backwards compatibility check */ /* Backwards compatibility check */

View file

@ -2,7 +2,7 @@ CyTube.tabCompleteMethods = {};
// Bash-style completion // Bash-style completion
// Only completes as far as it is possible to maintain uniqueness of the completion. // Only completes as far as it is possible to maintain uniqueness of the completion.
CyTube.tabCompleteMethods['Longest unique prefix'] = function (input, position, options, context) { CyTube.tabCompleteMethods['Longest unique match'] = function (input, position, options, context) {
var lower = input.toLowerCase(); var lower = input.toLowerCase();
// First, backtrack to the nearest whitespace to find the // First, backtrack to the nearest whitespace to find the
// incomplete string that should be completed. // incomplete string that should be completed.
@ -10,12 +10,12 @@ CyTube.tabCompleteMethods['Longest unique prefix'] = function (input, position,
var incomplete = ''; var incomplete = '';
for (start = position - 1; start >= 0; start--) { for (start = position - 1; start >= 0; start--) {
if (/\s/.test(lower[start])) { if (/\s/.test(lower[start])) {
start++;
break; break;
} }
incomplete = lower[start] + incomplete; incomplete = lower[start] + incomplete;
} }
start++;
// Nothing to complete // Nothing to complete
if (!incomplete.length) { if (!incomplete.length) {
@ -101,12 +101,12 @@ CyTube.tabCompleteMethods['Cycle options'] = function (input, position, options,
var incomplete = ''; var incomplete = '';
for (start = position - 1; start >= 0; start--) { for (start = position - 1; start >= 0; start--) {
if (/\s/.test(lower[start])) { if (/\s/.test(lower[start])) {
start++;
break; break;
} }
incomplete = lower[start] + incomplete; incomplete = lower[start] + incomplete;
} }
start++;
// Nothing to complete // Nothing to complete
if (!incomplete.length) { if (!incomplete.length) {

View file

@ -111,7 +111,11 @@ $("#guestname").keydown(function (ev) {
} }
}); });
function chatTabComplete() {
/**
* TODO: Remove this post-deployment
*/
function oldChatTabComplete() {
var words = $("#chatline").val().split(" "); var words = $("#chatline").val().split(" ");
var current = words[words.length - 1].toLowerCase(); var current = words[words.length - 1].toLowerCase();
if (!current.match(/^[\w-]{1,20}$/)) { if (!current.match(/^[\w-]{1,20}$/)) {
@ -186,6 +190,54 @@ function chatTabComplete() {
$("#chatline").val(words.join(" ")); $("#chatline").val(words.join(" "));
} }
CyTube.chatTabCompleteData = {
context: {}
};
function chatTabComplete() {
if (!CyTube.tabCompleteMethods) {
oldChatTabComplete();
return;
}
var chatline = document.getElementById("chatline");
var currentText = chatline.value;
var currentPosition = chatline.selectionEnd;
if (typeof currentPosition !== 'number' || !chatline.setSelectionRange) {
// Bail, we're on IE8 or something similarly dysfunctional
return;
}
var firstWord = !/\s/.test(currentText.trim());
var options = [];
var userlistElems = document.getElementById("userlist").children;
for (var i = 0; i < userlistElems.length; i++) {
var username = userlistElems[i].children[1].textContent;
if (firstWord) {
username += ':';
}
options.push(username);
}
CHANNEL.emotes.forEach(function (emote) {
options.push(emote.name);
});
var method = USEROPTS.chat_tab_method;
if (!CyTube.tabCompleteMethods[method]) {
console.error("Unknown chat tab completion method '" + method + "', using default");
method = "Cycle options";
}
var result = CyTube.tabCompleteMethods[method](
currentText,
currentPosition,
options,
CyTube.chatTabCompleteData.context
);
chatline.value = result.text;
chatline.setSelectionRange(result.newPosition, result.newPosition);
}
$("#chatline").keydown(function(ev) { $("#chatline").keydown(function(ev) {
// Enter/return // Enter/return
if(ev.keyCode == 13) { if(ev.keyCode == 13) {
@ -218,7 +270,11 @@ $("#chatline").keydown(function(ev) {
return; return;
} }
else if(ev.keyCode == 9) { // Tab completion else if(ev.keyCode == 9) { // Tab completion
chatTabComplete(); try {
chatTabComplete();
} catch (error) {
console.error(error);
}
ev.preventDefault(); ev.preventDefault();
return false; return false;
} }

View file

@ -643,6 +643,7 @@ function showUserOptions() {
$("#us-sendbtn").prop("checked", USEROPTS.chatbtn); $("#us-sendbtn").prop("checked", USEROPTS.chatbtn);
$("#us-no-emotes").prop("checked", USEROPTS.no_emotes); $("#us-no-emotes").prop("checked", USEROPTS.no_emotes);
$("#us-strip-image").prop("checked", USEROPTS.strip_image); $("#us-strip-image").prop("checked", USEROPTS.strip_image);
$("#us-chat-tab-method").val(USEROPTS.chat_tab_method);
$("#us-modflair").prop("checked", USEROPTS.modhat); $("#us-modflair").prop("checked", USEROPTS.modhat);
$("#us-shadowchat").prop("checked", USEROPTS.show_shadowchat); $("#us-shadowchat").prop("checked", USEROPTS.show_shadowchat);
@ -677,6 +678,7 @@ function saveUserOptions() {
USEROPTS.chatbtn = $("#us-sendbtn").prop("checked"); USEROPTS.chatbtn = $("#us-sendbtn").prop("checked");
USEROPTS.no_emotes = $("#us-no-emotes").prop("checked"); USEROPTS.no_emotes = $("#us-no-emotes").prop("checked");
USEROPTS.strip_image = $("#us-strip-image").prop("checked"); USEROPTS.strip_image = $("#us-strip-image").prop("checked");
USEROPTS.chat_tab_method = $("#us-chat-tab-method").val();
if (CLIENT.rank >= 2) { if (CLIENT.rank >= 2) {
USEROPTS.modhat = $("#us-modflair").prop("checked"); USEROPTS.modhat = $("#us-modflair").prop("checked");