Skip to content

Commit

Permalink
update autocomplete menu + unit tests #511
Browse files Browse the repository at this point in the history
  • Loading branch information
jcubic committed Aug 29, 2019
1 parent 84b0a96 commit d1bfd1a
Show file tree
Hide file tree
Showing 2 changed files with 129 additions and 11 deletions.
80 changes: 79 additions & 1 deletion __tests__/terminal.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ require('../js/jquery.terminal-src')(global.$);
require('../js/unix_formatting')(global.$);
require('../js/pipe')(global.$);
require('../js/echo_newline')(global.$);
//require('../js/autocomplete_menu')(global.$);
require('../js/autocomplete_menu')(global.$);

jest.setTimeout(10000);

Expand Down Expand Up @@ -2266,6 +2266,84 @@ describe('extensions', function() {
expect(term.get_prompt()).toEqual(prompt);
});
});
describe('autocomplete_menu', function() {
function completion(term) {
return find_menu(term).find('li').map(function() {
return a0($(this).text());
}).get();
}
function find_menu(term) {
return term.find('.cmd-cursor-line .cursor-wrapper .cmd-cursor + ul');
}
function menu_visible(term) {
var menu = find_menu(term);
expect(menu.length).toEqual(1);
expect(menu.is(':visible')).toBeTruthy();
}
function complete(term, text) {
term.focus().insert(text);
shortcut(false, false, false, 9, 'tab');
return delay(5);
}
it('should display menu from function with Promise', async function() {
var term = $('<div/>').terminal($.noop, {
autocompleteMenu: true,
completion: function(string) {
if (!string.match(/_/) && string.length > 3) {
return Promise.resolve([string + '_foo', string + '_bar']);
}
}
});
await complete(term, 'hello');
menu_visible(term);
expect(term.get_command()).toEqual('hello_');
expect(completion(term)).toEqual(['foo', 'bar']);
term.destroy();
});
it('should display menu from array', async function() {
var term = $('<div/>').terminal($.noop, {
autocompleteMenu: true,
completion: ['hello_foo', 'hello_bar']
});
await complete(term, 'hello');
menu_visible(term);
expect(term.get_command()).toEqual('hello_');
expect(completion(term)).toEqual(['foo', 'bar']);
term.destroy();
});
it('should display menu from Promise<array>', async function() {
var term = $('<div/>').terminal($.noop, {
autocompleteMenu: true,
completion: async function() {
await delay(10);
return ['hello_foo', 'hello_bar'];
}
});
complete(term, 'hello');
await delay(100);
menu_visible(term);
expect(term.get_command()).toEqual('hello_');
expect(completion(term)).toEqual(['foo', 'bar']);
term.destroy();
});
it('should display menu with one element', async function() {
var term = $('<div/>').terminal($.noop, {
autocompleteMenu: true,
completion: ['hello_foo', 'hello_bar']
});
await complete(term, 'hello');
enter_text('f');
await delay(5);
menu_visible(term);
expect(term.get_command()).toEqual('hello_f');
expect(completion(term)).toEqual(['oo']);
shortcut(false, false, false, 9, 'tab');
await delay(5);
expect(completion(term)).toEqual([]);
expect(term.get_command()).toEqual('hello_foo');
term.destroy();
});
});
});
describe('sub plugins', function() {
describe('text_length', function() {
Expand Down
60 changes: 50 additions & 10 deletions js/autocomplete_menu.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
* Released under the MIT license
*
*/
/* global define, global, require, module, setTimeout */
/* global define, global, require, module, setTimeout, clearTimeout */
(function(factory) {
var root = typeof window !== 'undefined' ? window : global;
if (typeof define === 'function' && define.amd) {
Expand Down Expand Up @@ -52,20 +52,47 @@
})(function($) {
var jquery_terminal = $.fn.terminal;
$.fn.terminal = function(interpreter, options) {
return jquery_terminal.call(this, interpreter, autocomplete_menu(options));
function init(node) {
return jquery_terminal.call(node, interpreter, autocomplete_menu(options));
}
if (this.length > 1) {
return this.each(init.bind(null, this));
} else {
return init(this);
}
};
// -----------------------------------------------------------------------------------
// :: cancableble task for usage in comletion menu to ignore previous async completion
// -----------------------------------------------------------------------------------
function Task(fn) {
this._fn = fn;
}
Task.prototype.invoke = function() {
if (!this._cancel) {
this._fn.apply(null, arguments);
}
};
Task.prototype.cancel = function() {
this._cancel = true;
};
// -----------------------------------------------------------------------------------
// :: function return patched terminal settings
// -----------------------------------------------------------------------------------
function autocomplete_menu(options) {
if (options && !options.autocompleteMenu) {
return options;
}
var settings = options || {};
function complete_menu(term, e, list) {
var last_task;
// -------------------------------------------------------------------------------
// :: function that do actuall matching and displaying of completion menu
// -------------------------------------------------------------------------------
function complete_menu(term, e, word, list) {
var matched = [];
var word = term.before_cursor(true);
var regex = new RegExp('^' + $.terminal.escape_regex(word));
for (var i = list.length; i--;) {
if (regex.test(list[i])) {
matched.push(list[i]);
matched.unshift(list[i]);
}
}
if (e.which === 9) {
Expand Down Expand Up @@ -93,6 +120,7 @@
delete settings.completion;
settings.onInit = function(term) {
onInit.call(this, term);
// init html menu element
var wrapper = this.cmd().find('.cmd-cursor').
wrap('<span/>').parent().addClass('cursor-wrapper');
ul = $('<ul></ul>').appendTo(wrapper);
Expand All @@ -101,21 +129,33 @@
ul.empty();
});
};
var timer;
settings.keydown = function(e, term) {
// setTimeout because terminal is adding characters in keypress
// we use keydown because we need to prevent default action
// for tab and still execute custom code
setTimeout(function() {
clearTimeout(timer);
timer = setTimeout(function() {
ul.empty();
var word = term.before_cursor(true);
if (last_task) {
last_task.cancel(); // ignore previous completion task
}
// we save task in closure for callbacks and promise::then
var task = last_task = new Task(complete_menu);
if (typeof completion === 'function') {
var ret = completion.call(term);
var ret = completion.call(term, word, function(list) {
task.invoke(term, e, word, list);
});
if (ret && typeof ret.then === 'function') {
ret.then(complete_menu.bind(null, term, e));
ret.then(function(completion) {
task.invoke(term, e, word, completion);
});
} else if (ret instanceof Array) {
complete_menu(term, e, ret);
task.invoke(term, e, word, ret);
}
} else if (completion instanceof Array) {
complete_menu(term, e, completion);
task.invoke(term, e, word, completion);
}
}, 0);
var ret = keydown.call(this, e, term);
Expand Down

0 comments on commit d1bfd1a

Please sign in to comment.