diff options
| author | Alon Levy <alon@pobox.com> | 2014-12-16 17:37:42 +0200 |
|---|---|---|
| committer | Alon Levy <alon@pobox.com> | 2014-12-16 17:37:42 +0200 |
| commit | ade19de410e0a789a921ec1666b278b2a64176d6 (patch) | |
| tree | c4c54b106de93609ca7bb70a20fd4be696715dbe /src/client/view/completer.js | |
| parent | c7d026b1d504323a8c61d51b726e5e8d2337fc4b (diff) | |
moving files around after repository merger
Diffstat (limited to 'src/client/view/completer.js')
| -rw-r--r-- | src/client/view/completer.js | 229 |
1 files changed, 229 insertions, 0 deletions
diff --git a/src/client/view/completer.js b/src/client/view/completer.js new file mode 100644 index 00000000..d2593321 --- /dev/null +++ b/src/client/view/completer.js @@ -0,0 +1,229 @@ +define( +['jquery', 'Bacon'], +function($, Bacon) { + +function unquoted(name) +{ + var start = 0, + end = name.length; + + if (name.length >= 1) { + if (name.charAt(0) == '"') { + start = 1; + if (name.length > 1 && name.charAt(name.length - 1) == '"') { + end = name.length - 1; + } + } + return name.substring(start, end); + } + return name; +} + +function setCaret(e, num) +{ + e.selectionStart = e.selectionEnd = num; +} + +var completer = (function (input_element, dropdown, base_config) { + var config = get_config(base_config), + dropdown_raw = dropdown[0], + options_bus = new Bacon.Bus(), + options = [], + selected_index = -1, + input_element_raw = input_element[0], + completion_start = 0, + completion_end = 0, + minimum_length = 1; + + // turn off the browser's autocomplete + input_element.attr('autocomplete', 'off'); + + //$('.ui-autocomplete').css('width', '10px'); + options_bus.onValue(function update_options(new_options) { + options = new_options; + }); + + input_element.keyup(function(e) { + var ret = undefined; + switch (e.keyCode) { + case 38: //UP + prev_option(); + ret = false; + break; + case 40: //DOWN + next_option(); + ret = false; + break; + case 27: // Escape + hide(); + ret = false; + break; + default: + // This catches cursor move due to keyboard events. no event for cursor movement itself + // below we catch cursor moves due to mouse click + oninput(input_element_raw.value, input_element_raw.selectionStart); + } + return ret; + }); + input_element.keydown(function(e) { + switch (e.keyCode) { + case 38: + case 40: + return false; + case 9: // Tab + if (config.hideOnTab) { + hide(); + } + break; + } + }); + + function get_config(base) { + return { + triggerStart: base && base.triggerStart || '#', + triggerEnd: base && base.triggerEnd || ' ', + hideOnTab: base && base.hasOwnProperty('hideOnTab') ? base.hideOnTab : true, + }; + } + + function completions(text) + { + var ret = [], + noquotes = unquoted(text.toLowerCase()); + + for (var name in options) { + if (name.toLowerCase().indexOf(noquotes) === 0) { + ret.push(name); + } + } + return ret; + } + + function show() { + if (dropdown.children().length > 0) { + dropdown.show(); + } + } + function hide() + { + dropdown.hide(); + } + + /*** + * #this is a # + * ^ + * + * #this is a #t + * ^ + * + * #this and #that then #he + * ^ + */ + function oninput(text, cursor) { + var hash = text.slice(0, cursor).lastIndexOf(config.triggerStart); + // TODO check if current completion has been invalidated + _invalidateSelection(); + hide(); + dropdown_raw.innerHTML = ""; // remove all elements + if (hash == -1 && config.triggerStart != ' ') { // space matches start of string too + return; + } + var space = text.slice(hash + 1).indexOf(config.triggerEnd); + space = space == -1 ? text.length : space; + if (space < cursor) { + return; + } + completion_start = hash + 1; + completion_end = space; + var string = text.slice(completion_start, completion_end); + if (string.length < minimum_length) { + return; + } + completions(string).forEach(function(name) { + var suggestion = $('<div class="suggestion-item">' + name + '</div>'); + suggestion.on('click', function(e) { + _applySuggestion(name); + input_element.focus(); + }); + dropdown.append(suggestion); + }); + show(); + } + + function _invalidateSelection() { + update_highlighting(-1); + } + + function _move_option(change, default_value) { + var next, + n = dropdown.children().length; + + if (n == 0) { + return; + } + show(); + if (selected_index == -1) { + next = default_value; + } else { + next = (selected_index + change) % n; + } + update_highlighting(next); + } + function next_option() { + _move_option(1, 0); + } + function prev_option() { + _move_option(dropdown.children().length - 1, dropdown.children().length - 1); + } + function _get_option(index) { + if (dropdown.children().length <= index) { + console.log('error: dropdown does not contain index ' + index + + ', it has ' + dropdown.children().length + ' elements'); + return ''; + } + var e = dropdown.children()[index], + s = e.innerText || e.textContent; + if (s.indexOf(' ') != -1) { + return '"' + s + '"'; + } + return s; + } + function _choice(i) { + return dropdown.children().eq(i); + } + function update_highlighting(new_index) { + if (selected_index != -1) { + _choice(selected_index).removeClass('selected'); + } + if (new_index != -1) { + _choice(new_index).addClass('selected'); + } + selected_index = new_index; + } + function _applySuggestion(str) { + var cur = input_element.val(), + start = cur.slice(0, completion_start) + str + ' '; + input_element.val(start + cur.slice(completion_end)); + setCaret(input_element, start.length); + oninput('', 0); + } + function handleEnter() { + if (selected_index == -1) { + return false; + } + _applySuggestion(_get_option(selected_index)); + return true; + } + + return { + options: options_bus, + oninput: oninput, + next_option: next_option, + prev_option: prev_option, + handleEnter: handleEnter, + }; +}); + +return completer; + +}); |
