From b811f1dc3fba71f25bedb8342b7ef55cdc262397 Mon Sep 17 00:00:00 2001 From: Garen Torikian Date: Fri, 4 Jan 2013 15:07:23 -0800 Subject: [PATCH] Fix rerender on text insertion --- lib/ace/autocomplete.js | 36 +++--- lib/ace/autocomplete/autocomplete_worker.js | 75 +++---------- lib/ace/autocomplete/complete_util.js | 117 +++++++++++++++----- lib/ace/autocomplete/text_completer.js | 54 ++++++--- 4 files changed, 167 insertions(+), 115 deletions(-) diff --git a/lib/ace/autocomplete.js b/lib/ace/autocomplete.js index c91cb7c1..705fa978 100644 --- a/lib/ace/autocomplete.js +++ b/lib/ace/autocomplete.js @@ -163,6 +163,8 @@ var Autocomplete = function() { el.style.left = pos.left + "px"; el.style.display = ""; + + renderer.updateText(); }; this.detach = function() { @@ -173,10 +175,15 @@ var Autocomplete = function() { if (this.popup) this.popup.container.style.display = "none"; + + this.editor.Autocomplete.activated = false; }; this.changeListener = function(e) { - //console.log(e) + if (this.editor.Autocomplete.activated) + Autocomplete.startCommand.exec(this.editor); + else + this.detach(); }; this.blurListener = function() { @@ -215,6 +222,8 @@ var Autocomplete = function() { }; this.insertMatch = function(row) { + this.detach(); + if (row == undefined) row = this.popup.getRow(); var text = this.completions.filtered[row]; @@ -224,7 +233,6 @@ var Autocomplete = function() { // should be good enough, otherwise we can use getDocument().removeInLine this.editor.removeWordLeft(); this.editor.insert(text); - this.detach(); }; this.commands = { @@ -237,16 +245,7 @@ var Autocomplete = function() { "space": function(editor) { editor.Autocomplete.detach(); editor.insert(" ");}, "Return": function(editor) { editor.Autocomplete.insertMatch(); }, "Shift-Return": function(editor) { editor.Autocomplete.insertMatch(true); }, - "Tab": function(editor) { editor.Autocomplete.insertMatch(); }, - "backspace": function(editor) { - var doc = editor.session.getDocument(), - cursor = editor.getCursorPosition(); - - editor.Autocomplete.detach(); - // delete one char, and reevaluate - editor.remove("left"); - editor.Autocomplete.complete(editor); - } + "Tab": function(editor) { editor.Autocomplete.insertMatch(); } }; this.complete = function(editor) { @@ -266,15 +265,19 @@ var Autocomplete = function() { editor.on("blur", this.$blurListener); editor.on("mousedown", this.$mousedownListener); - worker.attachToDocument(editor.session.getDocument(), {cursor: editor.getCursorPosition(), keywords: editor.session.getMode().getKeywords(true)}, true); + worker.attachToDocument(editor.session.getDocument(), {cursor: editor.getCursorPosition(), keywords: editor.session.getMode().getKeywords()}, true); worker.on("complete", function(data) { var matches = data.data.matches; - _self.completions = new FilteredList(matches); - _self.completions.setFilter("a"); if (matches.length) { - _self.openPopup(editor); } + _self.completions = new FilteredList(matches); + _self.completions.setFilter("a"); + _self.openPopup(editor); + } + else { + _self.detach(); + } }); }; @@ -377,6 +380,7 @@ Autocomplete.startCommand = { if (!editor.Autocomplete) editor.Autocomplete = new Autocomplete(); editor.Autocomplete.complete(editor); + editor.Autocomplete.activated = true; }, bindKey: "Ctrl-Space|Shift-Space|Alt-Space" } diff --git a/lib/ace/autocomplete/autocomplete_worker.js b/lib/ace/autocomplete/autocomplete_worker.js index cbcbbc05..39a3ba89 100644 --- a/lib/ace/autocomplete/autocomplete_worker.js +++ b/lib/ace/autocomplete/autocomplete_worker.js @@ -45,29 +45,6 @@ var AutocompleteWorker = exports.AutocompleteWorker = function(sender) { oop.inherits(AutocompleteWorker, Mirror); (function() { - // For code completion - function removeDuplicateMatches(matches) { - // First sort - matches.sort(function(a, b) { - if (a.name < b.name) - return 1; - else if (a.name > b.name) - return -1; - else - return 0; - }); - - for(var i = 1; i < matches.length; ){ - if (matches[i - 1] == matches[i]){ - matches.splice(i, 1); - } else { - i++; - } - } - - return matches; - }; - this.onUpdate = function() { var _self = this; @@ -75,43 +52,25 @@ oop.inherits(AutocompleteWorker, Mirror); var pos = this.data.cursor; var currentPos = { line: pos.row, col: pos.column }; - var matches = []; completer.complete(_self.doc, this.data.cursor, this.data.keywords, function(identifier, completions) { - if (completions) - matches = matches.concat(completions); - removeDuplicateMatches(matches); - // Sort by priority, score - matches.sort(function(a, b) { - if (a.priority < b.priority) - return 1; - else if (a.priority > b.priority) - return -1; - else if (a.score < b.score) - return 1; - else if (a.score > b.score) - return -1; - else if (a.id && a.id === b.id) { - if (a.isFunction) - return -1; - else if (b.isFunction) - return 1; - } - if (a.name < b.name) - return -1; - else if(a.name > b.name) - return 1; - else - return 0; - }); - _self.sender.emit("complete", { - startRow: pos.row, - startColumn: pos.column - identifier.length, - endRow: pos.row, - endColumn: Infinity, - matches: matches, - line: _self.doc.getLine(pos.row) - }); + if (!identifier) { + _self.sender.emit("complete", { + matches: [] + }); + } + + else { + _self.sender.emit("complete", { + startRow: pos.row, + startColumn: pos.column - identifier.length, + endRow: pos.row, + endColumn: Infinity, + matches: completions, + line: _self.doc.getLine(pos.row) + }); + } + return; }); }; diff --git a/lib/ace/autocomplete/complete_util.js b/lib/ace/autocomplete/complete_util.js index b094fb74..ad62393e 100644 --- a/lib/ace/autocomplete/complete_util.js +++ b/lib/ace/autocomplete/complete_util.js @@ -1,3 +1,33 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Distributed under the BSD license: + * + * Copyright (c) 2012, Ajax.org B.V. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Ajax.org B.V. nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * ***** END LICENSE BLOCK ***** */ + define(function(require, exports, module) { var ID_REGEX = /[a-zA-Z_0-9\$]/; @@ -26,36 +56,71 @@ function retrieveFollowingIdentifier(text, pos, regex) { return buf; } -function prefixBinarySearch(items, prefix) { - var startIndex = 0; - var stopIndex = items.length - 1; - var middle = Math.floor((stopIndex + startIndex) / 2); - - while (stopIndex > startIndex && middle >= 0 && items[middle].indexOf(prefix) !== 0) { - if (prefix < items[middle]) { - stopIndex = middle - 1; - } - else if (prefix > items[middle]) { - startIndex = middle + 1; - } - middle = Math.floor((stopIndex + stopIndex) / 2); - } - - // Look back to make sure we haven't skipped any - while (middle > 0 && items[middle-1].indexOf(prefix) === 0) - middle--; - return middle >= 0 ? middle : 0; // ensure we're not returning a negative index -} +// filched from jQuery +function grep( elems, callback, inv ) { + var retVal, + ret = [], + i = 0, + length = elems.length; + inv = !!inv; + + // Go through the array, only saving the items + // that pass the validator function + for ( ; i < length; i++ ) { + retVal = !!callback( elems[ i ], i ); + if ( inv !== retVal ) { + ret.push( elems[ i ] ); + } + } + + return ret; +}; + +function sortByScore(items, identDict) { + + return items.sort(function(a, b) { + var scoreA = identDict[a], + scoreB = identDict[b]; + + if (a < b) + return 1; + else if (a > b) + return -1; + else + return 0; + }); +}; + +function findCompletions(prefix, identDict, allIdentifiers) { + var _self = this, + fuzzyMatcher = function (prefix, item) { + return ~item.toLowerCase().indexOf(prefix.toLowerCase()); + }; + + var matches = grep(allIdentifiers, function (item) { + return fuzzyMatcher(prefix, item); + }); + + matches = sortByScore(matches, identDict); -function findCompletions(prefix, allIdentifiers) { - allIdentifiers.sort(); - var startIdx = prefixBinarySearch(allIdentifiers, prefix); - var matches = []; - for (var i = startIdx; i < allIdentifiers.length && allIdentifiers[i].indexOf(prefix) === 0; i++) - matches.push(allIdentifiers[i]); return matches; } +exports.removeDuplicateWords = function(matches) { + // First, sort + matches = matches.sort(); + + for (var i = 1; i < matches.length; ){ + if (matches[i - 1] == matches[i]){ + matches.splice(i, 1); + } else { + i++; + } + } + + return matches; +}; + exports.retrievePrecedingIdentifier = retrievePrecedingIdentifier; exports.retrieveFollowingIdentifier = retrieveFollowingIdentifier; exports.findCompletions = findCompletions; diff --git a/lib/ace/autocomplete/text_completer.js b/lib/ace/autocomplete/text_completer.js index 44a72c9b..d8d5673f 100644 --- a/lib/ace/autocomplete/text_completer.js +++ b/lib/ace/autocomplete/text_completer.js @@ -1,3 +1,33 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Distributed under the BSD license: + * + * Copyright (c) 2012, Ajax.org B.V. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Ajax.org B.V. nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * ***** END LICENSE BLOCK ***** */ + define(function(require, exports, module) { var completeUtil = require("./complete_util"); var SPLIT_REGEX = /[^a-zA-Z_0-9\$]+/; @@ -10,7 +40,7 @@ define(function(require, exports, module) { var text = doc.getValue().trim(); // Determine cursor's word index - var textBefore = doc.getLines(0, pos.row-1).join("\n") + "\n"; + var textBefore = doc.getLines(0, pos.row - 1).join("\n") + "\n"; var currentLine = doc.getLine(pos.row); textBefore += currentLine.substr(0, pos.column); var prefixPosition = textBefore.trim().split(SPLIT_REGEX).length - 1; @@ -27,11 +57,11 @@ define(function(require, exports, module) { if (ident.length === 0) continue; var distance = Math.max(prefixPosition, i) - Math.min(prefixPosition, i); - // Score substracted from 100000 to force descending ordering + // Score substracted from MAX to force descending ordering if (Object.prototype.hasOwnProperty.call(identDict, ident)) - identDict[ident] = Math.max(MAX_SCORE-distance, identDict[ident]); + identDict[ident] = Math.max(MAX_SCORE - distance, identDict[ident]); else - identDict[ident] = MAX_SCORE-distance; + identDict[ident] = MAX_SCORE - distance; } @@ -42,14 +72,6 @@ define(function(require, exports, module) { return identDict; } - function analyze(doc, pos, keywords) { - var line = doc.getLine(pos.row); - var identifier = completeUtil.retrievePrecedingIdentifier(line, pos.column); - - var analysisCache = wordDistanceAnalyzer(doc, pos, identifier, keywords); - return analysisCache; - } - completer.complete = function(doc, pos, keywords, callback) { var line = doc.getLine(pos.row); var identifier = completeUtil.retrievePrecedingIdentifier(line, pos.column); @@ -58,15 +80,17 @@ define(function(require, exports, module) { if (identifier === "") return callback(null); - var identDict = analyze(doc, pos, keywords); + var identDict = wordDistanceAnalyzer(doc, pos, identifier, keywords); var allIdentifiers = []; for (var ident in identDict) { allIdentifiers.push(ident); } - // find matches based on text in doc - var matches = completeUtil.findCompletions(identifier, allIdentifiers); + allIdentifiers = completeUtil.removeDuplicateWords(allIdentifiers); + + // find fuzzy matches based on text in doc, as well as mode keywords + var matches = completeUtil.findCompletions(identifier, identDict, allIdentifiers); callback(identifier, matches); };