From 2d0b980c65966e45260ba341e176b9cde0c57399 Mon Sep 17 00:00:00 2001 From: DanyaPostfactum Date: Thu, 22 Aug 2013 11:38:57 +1000 Subject: [PATCH 1/2] Fix two broken javascript snippets --- lib/ace/snippets/javascript.snippets | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/ace/snippets/javascript.snippets b/lib/ace/snippets/javascript.snippets index 287862eb..83b15aab 100644 --- a/lib/ace/snippets/javascript.snippets +++ b/lib/ace/snippets/javascript.snippets @@ -10,7 +10,7 @@ snippet fun } # Anonymous Function regex /((=)\s*|(:)\s*|(\()|\b)/f/(\))?/ -name f +snippet f function${M1?: ${1:functionName}}($2) { ${0:$TM_SELECTED_TEXT} }${M2?;}${M3?,}${M4?)} @@ -149,7 +149,7 @@ snippet sing return instance; } # class -name class +snippet class regex /^\s*/clas{0,2}/ var ${1:class} = function(${20}) { $40$0 From 9475d7738ba2786aa71b99c2c6519572e0ef8359 Mon Sep 17 00:00:00 2001 From: DanyaPostfactum Date: Thu, 22 Aug 2013 11:43:11 +1000 Subject: [PATCH 2/2] Add fuzzy matching for completions --- lib/ace/autocomplete.js | 44 +++++++++++++++++++++++--- lib/ace/autocomplete/popup.js | 19 +++++++---- lib/ace/autocomplete/text_completer.js | 13 +------- lib/ace/ext/language_tools.js | 14 +++----- 4 files changed, 59 insertions(+), 31 deletions(-) diff --git a/lib/ace/autocomplete.js b/lib/ace/autocomplete.js index f0252680..8279b727 100644 --- a/lib/ace/autocomplete.js +++ b/lib/ace/autocomplete.js @@ -165,6 +165,41 @@ var Autocomplete = function() { "PageDown": function(editor) { editor.completer.popup.gotoPageUp(); } }; + this.filterCompletions = function(items, needle) { + var results = []; + var upper = needle.toUpperCase(); + var lower = needle.toLowerCase(); + loop: for (var i = 0, item; item = items[i]; i++) { + var caption = item.value || item.caption; + var lastIndex = -1; + var matchMask = 0; + var penalty = 0; + var index, distance; + // caption char iteration is faster in Chrome but slower in Firefox, so lets use indexOf + for (var j = 0; j < needle.length; j++) { + // TODO add penalty on case mismatch + var i1 = caption.indexOf(lower[j], lastIndex + 1); + var i2 = caption.indexOf(upper[j], lastIndex + 1); + index = (i1 >= 0) ? ((i2 < 0 || i1 < i2) ? i1 : i2) : i2; + if (index < 0) + continue loop; + distance = index - lastIndex - 1; + if (distance > 0) { + // first char mismatch should be more sensitive + if (lastIndex == -1) + penalty += 10; + penalty += distance; + } + matchMask = matchMask | (1 << index); + lastIndex = index; + } + item.matchMask = matchMask; + item.score = (item.score || 0) - penalty; + results.push(item); + } + return results; + }; + this.gatherCompletions = function(editor, callback) { var session = editor.getSession(); var pos = editor.getCursorPosition(); @@ -180,9 +215,6 @@ var Autocomplete = function() { next(); }); }, function() { - matches.sort(function(a, b) { - return b.score - a.score; - }); callback(null, { prefix: prefix, matches: matches @@ -220,10 +252,14 @@ var Autocomplete = function() { // if (matches.length == 1) // return this.insertMatch(matches[0]); + + matches = this.filterCompletions(matches, results.prefix); + matches = matches.sort(function(a, b) { + return b.score - a.score; + }); this.completions = new FilteredList(matches); this.completions.setFilter(results.prefix); this.openPopup(this.editor, keepPopupPosition); - this.popup.setHighlight(results.prefix); }.bind(this)); }; diff --git a/lib/ace/autocomplete/popup.js b/lib/ace/autocomplete/popup.js index ece43d6a..2d0bba1a 100644 --- a/lib/ace/autocomplete/popup.js +++ b/lib/ace/autocomplete/popup.js @@ -131,7 +131,19 @@ var AcePopup = function(parentNode) { if (!data.caption) data.caption = data.value; - tokens.push({type: data.className || "", value: data.caption}); + var last = -1; + var flag, c; + for (var i = 0; i < data.caption.length; i++) { + c = data.caption[i]; + flag = data.matchMask & (1 << i) ? 1 : 0; + if (last !== flag) { + tokens.push({type: data.className || "" + ( flag ? ".bold" : ""), value: c}); + last = flag; + } else { + tokens[tokens.length - 1].value += c; + } + } + if (data.meta) { var maxW = popup.renderer.$size.scrollerWidth / popup.renderer.layerConfig.characterWidth; if (data.meta.length + data.caption.length < maxW - 2) @@ -167,11 +179,6 @@ var AcePopup = function(parentNode) { popup.moveCursorTo(line, 0 || 0); }; - popup.setHighlight = function(re) { - popup.session.highlight(re); - popup.session._emit("changeFrontMarker"); - }; - popup.hide = function() { this.container.style.display = "none"; this._signal("hide"); diff --git a/lib/ace/autocomplete/text_completer.js b/lib/ace/autocomplete/text_completer.js index 8e4bc019..87a25fb9 100644 --- a/lib/ace/autocomplete/text_completer.js +++ b/lib/ace/autocomplete/text_completer.js @@ -38,17 +38,6 @@ define(function(require, exports, module) { return textBefore.split(splitRegex).length - 1; } - // NOTE: Naive implementation O(n), can be O(log n) with binary search - function filterPrefix(prefix, words) { - var results = []; - for (var i = 0; i < words.length; i++) { - if (words[i].lastIndexOf(prefix, 0) === 0) { - results.push(words[i]); - } - } - return results; - } - /** * Does a distance analysis of the word `prefix` at position `pos` in `doc`. * @return Map @@ -76,7 +65,7 @@ define(function(require, exports, module) { exports.getCompletions = function(editor, session, pos, prefix, callback) { var wordScore = wordDistance(session, pos, prefix); - var wordList = filterPrefix(prefix, Object.keys(wordScore)); + var wordList = Object.keys(wordScore); callback(null, wordList.map(function(word) { return { name: word, diff --git a/lib/ace/ext/language_tools.js b/lib/ace/ext/language_tools.js index 0a9a3fd0..7e9680b5 100644 --- a/lib/ace/ext/language_tools.js +++ b/lib/ace/ext/language_tools.js @@ -39,9 +39,6 @@ var textCompleter = require("../autocomplete/text_completer"); var keyWordCompleter = { getCompletions: function(editor, session, pos, prefix, callback) { var keywords = session.$mode.$keywordList || []; - keywords = keywords.filter(function(w) { - return w.lastIndexOf(prefix, 0) == 0; - }); callback(null, keywords.map(function(word) { return { name: word, @@ -62,12 +59,11 @@ var snippetCompleter = { var snippets = snippetMap[scope] || []; for (var i = snippets.length; i--;) { var s = snippets[i]; - if (s.tabTrigger && s.tabTrigger.indexOf(prefix) === 0) - completions.push({ - caption: s.tabTrigger, - snippet: s.content, - meta: "snippet" - }); + completions.push({ + caption: s.tabTrigger, + snippet: s.content, + meta: "snippet" + }); } }, this); callback(null, completions);