Start working on better tab completion
Code is not used anywhere yet, but the end goal is: * Replace the bash-style algorithm with a less kludgy one * Add the ability to customize tab completion method (will also include default zsh-style completion) * Abstract tab completion so it can be shared for chat and emote names as available options
This commit is contained in:
parent
34ca5e12af
commit
dfdc07cbfa
73
test/www/tabcomplete.js
Normal file
73
test/www/tabcomplete.js
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
const assert = require('assert');
|
||||||
|
global.CyTube = {};
|
||||||
|
require('../../www/js/tabcomplete');
|
||||||
|
|
||||||
|
const testcases = [
|
||||||
|
{
|
||||||
|
input: 'and his name is j',
|
||||||
|
position: 17,
|
||||||
|
options: ['johncena', 'johnstamos', 'johto'],
|
||||||
|
output: {
|
||||||
|
text: 'and his name is joh',
|
||||||
|
newPosition: 19
|
||||||
|
},
|
||||||
|
description: 'completes the longest unique substring'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: 'and his name is johnc',
|
||||||
|
position: 21,
|
||||||
|
options: ['johncena', 'johnstamos', 'johto'],
|
||||||
|
output: {
|
||||||
|
text: 'and his name is johncena ',
|
||||||
|
newPosition: 25
|
||||||
|
},
|
||||||
|
description: 'completes a unique match'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: 'and his name is johnc',
|
||||||
|
position: 21,
|
||||||
|
options: ['asdf'],
|
||||||
|
output: {
|
||||||
|
text: 'and his name is johnc',
|
||||||
|
newPosition: 21
|
||||||
|
},
|
||||||
|
description: 'does not complete when there is no match'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: 'and his name is johnc',
|
||||||
|
position: 21,
|
||||||
|
options: [],
|
||||||
|
output: {
|
||||||
|
text: 'and his name is johnc',
|
||||||
|
newPosition: 21
|
||||||
|
},
|
||||||
|
description: 'does not complete when there are no options'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: ' ',
|
||||||
|
position: 1,
|
||||||
|
options: ['abc', 'def', 'ghi'],
|
||||||
|
output: {
|
||||||
|
text: ' ',
|
||||||
|
newPosition: 1
|
||||||
|
},
|
||||||
|
description: 'does not complete when the input is empty'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
describe('CyTube.tabCompletionMethods', () => {
|
||||||
|
describe('#Longest unique prefix', () => {
|
||||||
|
testcases.forEach(test => {
|
||||||
|
it(test.description, () => {
|
||||||
|
assert.deepEqual(test.output,
|
||||||
|
CyTube.tabCompleteMethods['Longest unique prefix'](
|
||||||
|
test.input,
|
||||||
|
test.position,
|
||||||
|
test.options,
|
||||||
|
{}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -68,6 +68,7 @@ var NO_STORAGE = typeof localStorage == "undefined" || localStorage === null;
|
||||||
var SOCKETIO_CONNECT_ERROR_COUNT = 0;
|
var SOCKETIO_CONNECT_ERROR_COUNT = 0;
|
||||||
var HAS_CONNECTED_BEFORE = false;
|
var HAS_CONNECTED_BEFORE = false;
|
||||||
var IMAGE_MATCH = /<img\s[^>]*?src\s*=\s*['\"]([^'\"]*?)['\"][^>]*?>/gi;
|
var IMAGE_MATCH = /<img\s[^>]*?src\s*=\s*['\"]([^'\"]*?)['\"][^>]*?>/gi;
|
||||||
|
var CyTube = {};
|
||||||
|
|
||||||
function getOpt(k) {
|
function getOpt(k) {
|
||||||
var v = NO_STORAGE ? readCookie(k) : localStorage.getItem(k);
|
var v = NO_STORAGE ? readCookie(k) : localStorage.getItem(k);
|
||||||
|
|
|
||||||
82
www/js/tabcomplete.js
Normal file
82
www/js/tabcomplete.js
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
CyTube.tabCompleteMethods = {};
|
||||||
|
|
||||||
|
// Bash-style 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) {
|
||||||
|
var lower = input.toLowerCase();
|
||||||
|
// First, backtrack to the nearest whitespace to find the
|
||||||
|
// incomplete string that should be completed.
|
||||||
|
var start;
|
||||||
|
var incomplete = '';
|
||||||
|
for (start = position - 1; start >= 0; start--) {
|
||||||
|
if (/\s/.test(lower[start])) {
|
||||||
|
start++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
incomplete = lower[start] + incomplete;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nothing to complete
|
||||||
|
if (!incomplete.length) {
|
||||||
|
return {
|
||||||
|
text: input,
|
||||||
|
newPosition: position
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var matches = options.filter(function (option) {
|
||||||
|
return option.toLowerCase().indexOf(incomplete) === 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
var completed;
|
||||||
|
var isFullMatch = false;
|
||||||
|
if (matches.length === 0) {
|
||||||
|
return {
|
||||||
|
text: input,
|
||||||
|
newPosition: position
|
||||||
|
};
|
||||||
|
} else if (matches.length === 1) {
|
||||||
|
// Unique match
|
||||||
|
completed = matches[0];
|
||||||
|
isFullMatch = true;
|
||||||
|
} else {
|
||||||
|
// There is not a unique match, find the longest possible prefix
|
||||||
|
// that results in a unique completion
|
||||||
|
// Do this by comparing each match to the next and trimming to the
|
||||||
|
// first index where they differ.
|
||||||
|
var currentPrefix = null;
|
||||||
|
for (var i = 0; i < matches.length - 1; i++) {
|
||||||
|
var first = matches[i];
|
||||||
|
var second = matches[i+1];
|
||||||
|
var nextPrefix = '';
|
||||||
|
for (var j = 0; (currentPrefix === null || j < currentPrefix.length)
|
||||||
|
&& j < first.length
|
||||||
|
&& j < second.length; j++) {
|
||||||
|
if (first[j].toLowerCase() === second[j].toLowerCase()) {
|
||||||
|
nextPrefix += first[j];
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentPrefix === null || nextPrefix.length < currentPrefix.length) {
|
||||||
|
currentPrefix = nextPrefix;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
completed = currentPrefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
var space = isFullMatch ? ' ' : '';
|
||||||
|
return {
|
||||||
|
text: input.substring(0, start) + completed + space + input.substring(position),
|
||||||
|
newPosition: start + completed.length + space.length
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Zsh-style completion.
|
||||||
|
// Always complete a full option, and cycle through available options on successive tabs
|
||||||
|
CyTube.tabCompleteMethods['Cycle options'] = function (input, position, options, context) {
|
||||||
|
|
||||||
|
};
|
||||||
Loading…
Reference in a new issue