From 2bf673c41de533f4a0a616c23c1a3289b4218df5 Mon Sep 17 00:00:00 2001 From: Aaron O'Mullan Date: Mon, 27 Jan 2014 18:21:42 +0100 Subject: [PATCH 01/19] Add auto magic complete (complete while typing) --- lib/ace/ext/language_tools.js | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/lib/ace/ext/language_tools.js b/lib/ace/ext/language_tools.js index e0ed0ec7..e7cb1fba 100644 --- a/lib/ace/ext/language_tools.js +++ b/lib/ace/ext/language_tools.js @@ -34,6 +34,7 @@ define(function(require, exports, module) { var snippetManager = require("../snippets").snippetManager; var Autocomplete = require("../autocomplete").Autocomplete; var config = require("../config"); +var util = require("../autocomplete/util"); var textCompleter = require("../autocomplete/text_completer"); var keyWordCompleter = { @@ -114,6 +115,30 @@ var loadSnippetFile = function(id) { }); }; +var onChangeAutocomplete = function(e, editor) { + var session = editor.getSession(); + var pos = editor.getCursorPosition(); + var line = session.getLine(pos.row); + + // Append added text to the line + if(e.data.action === 'insertText') { + line += e.data.text; + pos.column += e.data.text.length; + } + + // The prefix to autocomplete for + var prefix = util.retrievePrecedingIdentifier(line, pos.column); + + // Only autocomplete if there's a prefix that can be matched + if(prefix !== '') { + Autocomplete.startCommand.exec(editor); + } else if(editor.completer && editor.completer.activated) { + // When the prefix is empty + // close the autocomplete dialog + editor.completer.detach(); + } +}; + var Editor = require("../editor").Editor; require("../config").defineOptions(Editor.prototype, "editor", { enableBasicAutocompletion: { @@ -121,7 +146,11 @@ require("../config").defineOptions(Editor.prototype, "editor", { if (val) { this.completers = completers; this.commands.addCommand(Autocomplete.startCommand); + + // On each change automatically trigger the autocomplete + this.on('change', onChangeAutocomplete); } else { + this.removeListener('change', onChangeAutocomplete); this.commands.removeCommand(Autocomplete.startCommand); } }, From 07c99e05414698373349116780fd15e2cd955d93 Mon Sep 17 00:00:00 2001 From: Aaron O'Mullan Date: Tue, 28 Jan 2014 00:52:55 +0100 Subject: [PATCH 02/19] Fix: skip auto magic completion when pasting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This can some times cause confusing behavior. A paste is any text insertion of more than one character at a time … --- lib/ace/ext/language_tools.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/lib/ace/ext/language_tools.js b/lib/ace/ext/language_tools.js index e7cb1fba..0e12e4df 100644 --- a/lib/ace/ext/language_tools.js +++ b/lib/ace/ext/language_tools.js @@ -120,6 +120,20 @@ var onChangeAutocomplete = function(e, editor) { var pos = editor.getCursorPosition(); var line = session.getLine(pos.row); + // Detect paste (poor man's paste detection) + var pasting = ( + ( + e.data.action === 'insertText' && + e.data.text.length > 1 + ) || + e.data.action === 'insertLines' + ); + + // we don't want to autocomplete on paste events + if(pasting) { + return; + } + // Append added text to the line if(e.data.action === 'insertText') { line += e.data.text; From afd9c99da25f224006b19e67f19dacf47ddd1ae0 Mon Sep 17 00:00:00 2001 From: Aaron O'Mullan Date: Tue, 28 Jan 2014 01:24:08 +0100 Subject: [PATCH 03/19] Don't attach/detach autocomplete on change if already attached/detached --- lib/ace/ext/language_tools.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/ace/ext/language_tools.js b/lib/ace/ext/language_tools.js index 0e12e4df..d0d31a01 100644 --- a/lib/ace/ext/language_tools.js +++ b/lib/ace/ext/language_tools.js @@ -143,10 +143,12 @@ var onChangeAutocomplete = function(e, editor) { // The prefix to autocomplete for var prefix = util.retrievePrecedingIdentifier(line, pos.column); + var hasCompleter = (editor.completer && editor.completer.activated); + // Only autocomplete if there's a prefix that can be matched - if(prefix !== '') { + if(prefix !== '' && !(hasCompleter)) { Autocomplete.startCommand.exec(editor); - } else if(editor.completer && editor.completer.activated) { + } else if(prefix === '' && hasCompleter) { // When the prefix is empty // close the autocomplete dialog editor.completer.detach(); From af1aab40f52aa536b5aa6785a571b40320c8bb91 Mon Sep 17 00:00:00 2001 From: Aaron O'Mullan Date: Tue, 28 Jan 2014 01:43:12 +0100 Subject: [PATCH 04/19] Enable auto magic complete only when typing We only want to auto magically complete when the user is typing text, not when text is pasted or being removed. --- lib/ace/ext/language_tools.js | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/lib/ace/ext/language_tools.js b/lib/ace/ext/language_tools.js index d0d31a01..b739eabe 100644 --- a/lib/ace/ext/language_tools.js +++ b/lib/ace/ext/language_tools.js @@ -121,24 +121,22 @@ var onChangeAutocomplete = function(e, editor) { var line = session.getLine(pos.row); // Detect paste (poor man's paste detection) - var pasting = ( + var typing = !( ( e.data.action === 'insertText' && e.data.text.length > 1 ) || - e.data.action === 'insertLines' + e.data.action !== 'insertText' ); // we don't want to autocomplete on paste events - if(pasting) { + if(!typing) { return; } // Append added text to the line - if(e.data.action === 'insertText') { - line += e.data.text; - pos.column += e.data.text.length; - } + line += e.data.text; + pos.column += e.data.text.length; // The prefix to autocomplete for var prefix = util.retrievePrecedingIdentifier(line, pos.column); From c4012ebd461b5809f50273eaa7da7e59776438e1 Mon Sep 17 00:00:00 2001 From: Aaron O'Mullan Date: Tue, 28 Jan 2014 02:14:23 +0100 Subject: [PATCH 05/19] Don't autoInsert with auto magic completer This caused completions to be inserted without ever being shown, if they were the only completion for the prefix entered. --- lib/ace/ext/language_tools.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/lib/ace/ext/language_tools.js b/lib/ace/ext/language_tools.js index b739eabe..b505fcfa 100644 --- a/lib/ace/ext/language_tools.js +++ b/lib/ace/ext/language_tools.js @@ -145,7 +145,18 @@ var onChangeAutocomplete = function(e, editor) { // Only autocomplete if there's a prefix that can be matched if(prefix !== '' && !(hasCompleter)) { - Autocomplete.startCommand.exec(editor); + if (!editor.completer) { + // Create new autocompleter + editor.completer = new Autocomplete(); + + // Disable autoInsert + editor.completer.autoInsert = false; + } + + editor.completer.showPopup(editor); + // needed for firefox on mac + editor.completer.cancelContextMenu(); + } else if(prefix === '' && hasCompleter) { // When the prefix is empty // close the autocomplete dialog From 98dc63cce4606b517bd64edb623b3f2a11659cec Mon Sep 17 00:00:00 2001 From: Aaron O'Mullan Date: Tue, 28 Jan 2014 11:37:43 +0100 Subject: [PATCH 06/19] Close auto magic complete when prefix is empty While removing text --- lib/ace/ext/language_tools.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/ace/ext/language_tools.js b/lib/ace/ext/language_tools.js index b505fcfa..67e35dc7 100644 --- a/lib/ace/ext/language_tools.js +++ b/lib/ace/ext/language_tools.js @@ -119,6 +119,7 @@ var onChangeAutocomplete = function(e, editor) { var session = editor.getSession(); var pos = editor.getCursorPosition(); var line = session.getLine(pos.row); + var hasCompleter = (editor.completer && editor.completer.activated); // Detect paste (poor man's paste detection) var typing = !( @@ -129,6 +130,15 @@ var onChangeAutocomplete = function(e, editor) { e.data.action !== 'insertText' ); + // We don't want to autocomplete with no prefix + if( + e.data.action === 'removeText' && + util.retrievePrecedingIdentifier(line, pos.column) === '' + ) { + if(hasCompleter) editor.completer.detach(); + return; + } + // we don't want to autocomplete on paste events if(!typing) { return; @@ -141,8 +151,6 @@ var onChangeAutocomplete = function(e, editor) { // The prefix to autocomplete for var prefix = util.retrievePrecedingIdentifier(line, pos.column); - var hasCompleter = (editor.completer && editor.completer.activated); - // Only autocomplete if there's a prefix that can be matched if(prefix !== '' && !(hasCompleter)) { if (!editor.completer) { From f9fcf49c66642fc52087a07db1a87612c62c4ab0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samy=20Pess=C3=A9?= Date: Tue, 28 Jan 2014 15:47:12 +0100 Subject: [PATCH 07/19] Apply editor theme to autocomplete popup --- lib/ace/autocomplete.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/ace/autocomplete.js b/lib/ace/autocomplete.js index 50154c01..d4491f6f 100644 --- a/lib/ace/autocomplete.js +++ b/lib/ace/autocomplete.js @@ -71,6 +71,7 @@ var Autocomplete = function() { var renderer = editor.renderer; if (!keepPopupPosition) { this.popup.setRow(0); + this.popup.setTheme(editor.getTheme()); this.popup.setFontSize(editor.getFontSize()); var lineHeight = renderer.layerConfig.lineHeight; From df99195544b897dfd49786259bc444ada615e081 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samy=20Pess=C3=A9?= Date: Tue, 28 Jan 2014 15:49:53 +0100 Subject: [PATCH 08/19] Set default theme for autocomplete popup --- lib/ace/autocomplete/popup.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/ace/autocomplete/popup.js b/lib/ace/autocomplete/popup.js index ca40cad1..cc4618b6 100644 --- a/lib/ace/autocomplete/popup.js +++ b/lib/ace/autocomplete/popup.js @@ -291,16 +291,16 @@ var AcePopup = function(parentNode) { }; dom.importCssString("\ -.ace_autocomplete.ace-tm .ace_marker-layer .ace_active-line {\ +.ace_editor.ace_autocomplete .ace_marker-layer .ace_active-line {\ background-color: #CAD6FA;\ z-index: 1;\ }\ -.ace_autocomplete.ace-tm .ace_line-hover {\ +.ace_editor.ace_autocomplete .ace_line-hover {\ border: 1px solid #abbffe;\ margin-top: -1px;\ background: rgba(233,233,253,0.4);\ }\ -.ace_autocomplete .ace_line-hover {\ +.ace_editor.ace_autocomplete .ace_line-hover {\ position: absolute;\ z-index: 2;\ }\ @@ -312,11 +312,11 @@ dom.importCssString("\ text-align: right;\ z-index: -1;\ }\ -.ace_autocomplete .ace_completion-highlight{\ +.ace_editor.ace_autocomplete .ace_completion-highlight{\ color: #000;\ text-shadow: 0 0 0.01em;\ }\ -.ace_autocomplete {\ +.ace_editor.ace_autocomplete {\ width: 280px;\ z-index: 200000;\ background: #fbfbfb;\ From 110eb26f96fa865ccd5cfbd9bbf92086572c29df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samy=20Pess=C3=A9?= Date: Fri, 31 Jan 2014 15:18:45 +0100 Subject: [PATCH 09/19] Use event afterExec instead of change for live autocomplete --- lib/ace/ext/language_tools.js | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/lib/ace/ext/language_tools.js b/lib/ace/ext/language_tools.js index 67e35dc7..deb1973d 100644 --- a/lib/ace/ext/language_tools.js +++ b/lib/ace/ext/language_tools.js @@ -115,24 +115,27 @@ var loadSnippetFile = function(id) { }); }; -var onChangeAutocomplete = function(e, editor) { +var onChangeAutocomplete = function(e) { + var editor = e.editor; var session = editor.getSession(); var pos = editor.getCursorPosition(); var line = session.getLine(pos.row); var hasCompleter = (editor.completer && editor.completer.activated); + var text = e.args || ""; + // Detect paste (poor man's paste detection) var typing = !( ( - e.data.action === 'insertText' && - e.data.text.length > 1 + e.command.name === "insertstring" && + text.length > 1 ) || - e.data.action !== 'insertText' + e.command.name !== "insertstring" ); // We don't want to autocomplete with no prefix if( - e.data.action === 'removeText' && + e.command.name === 'backspace' && util.retrievePrecedingIdentifier(line, pos.column) === '' ) { if(hasCompleter) editor.completer.detach(); @@ -145,8 +148,8 @@ var onChangeAutocomplete = function(e, editor) { } // Append added text to the line - line += e.data.text; - pos.column += e.data.text.length; + line += text; + pos.column += text.length; // The prefix to autocomplete for var prefix = util.retrievePrecedingIdentifier(line, pos.column); @@ -181,7 +184,7 @@ require("../config").defineOptions(Editor.prototype, "editor", { this.commands.addCommand(Autocomplete.startCommand); // On each change automatically trigger the autocomplete - this.on('change', onChangeAutocomplete); + this.commands.on('afterExec', onChangeAutocomplete); } else { this.removeListener('change', onChangeAutocomplete); this.commands.removeCommand(Autocomplete.startCommand); From 09b9348852de8d0fe010639a1a4834657b740465 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samy=20Pess=C3=A9?= Date: Fri, 31 Jan 2014 22:19:32 +0100 Subject: [PATCH 10/19] Improve autocomplete support for async completers --- lib/ace/autocomplete.js | 41 ++++++++++++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/lib/ace/autocomplete.js b/lib/ace/autocomplete.js index d4491f6f..de30a740 100644 --- a/lib/ace/autocomplete.js +++ b/lib/ace/autocomplete.js @@ -60,6 +60,7 @@ var Autocomplete = function() { this.insertMatch(); e.stop(); }.bind(this)); + this.gatherCompletionsId = 0; }; this.openPopup = function(editor, prefix, keepPopupPosition) { @@ -146,6 +147,8 @@ var Autocomplete = function() { data = this.popup.getData(this.popup.getRow()); if (!data) return false; + this.gatherCompletionsId = this.gatherCompletionsId + 1; + if (data.completer && data.completer.insertMatch) { data.completer.insertMatch(this.editor); } else { @@ -191,16 +194,14 @@ var Autocomplete = function() { this.base.column -= prefix.length; var matches = []; - util.parForEach(editor.completers, function(completer, next) { + editor.completers.forEach(function(completer) { completer.getCompletions(editor, session, pos, prefix, function(err, results) { if (!err) matches = matches.concat(results); - next(); - }); - }, function() { - callback(null, { - prefix: prefix, - matches: matches + callback(null, { + prefix: prefix, + matches: matches + }); }); }); return true; @@ -229,6 +230,7 @@ var Autocomplete = function() { }; this.updateCompletions = function(keepPopupPosition) { + var that = this; if (keepPopupPosition && this.base && this.completions) { var pos = this.editor.getCursorPosition(); var prefix = this.editor.session.getTextRange({start: this.base, end: pos}); @@ -240,22 +242,39 @@ var Autocomplete = function() { this.openPopup(this.editor, prefix, keepPopupPosition); return; } + + // Save current gatherCompletions session, session is close when a match is insert + var _id = this.gatherCompletionsId; this.gatherCompletions(this.editor, function(err, results) { + // Calcul prefix + var session = that.editor.getSession(); + var pos = that.editor.getCursorPosition(); + var line = session.getLine(pos.row); + var prefix = util.retrievePrecedingIdentifier(line, pos.column); + + // Results matches var matches = results && results.matches; - if (!matches || !matches.length) - return this.detach(); // TODO reenable this when we have proper change tracking // if (matches.length == 1) // return this.insertMatch(matches[0]); + // No prefix or no results -> close + if (!prefix || !prefix.length || !matches || !matches.length) + return this.detach(); + + // Wrong prefx or wrong session -> ignore + if (prefix.indexOf(results.prefix) != 0 + || _id != this.gatherCompletionsId) + return; + this.completions = new FilteredList(matches); - this.completions.setFilter(results.prefix); + this.completions.setFilter(prefix); var filtered = this.completions.filtered; if (!filtered.length) return this.detach(); if (this.autoInsert && filtered.length == 1) return this.insertMatch(filtered[0]); - this.openPopup(this.editor, results.prefix, keepPopupPosition); + this.openPopup(this.editor, prefix, keepPopupPosition); }.bind(this)); }; From c36fac3a941a68a99e54f6d04877815d4ab9864d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samy=20Pess=C3=A9?= Date: Sun, 2 Feb 2014 20:52:57 +0100 Subject: [PATCH 11/19] Improve autocomplete results display and filtering Autocomplete: Display unique results when it's a snippet Fix autocomplete popup display Remove listener for afterExec when enableBasicAutocompletion change Fix autocomplete detach when no results are ready from the first completer Remove condition from detach --- lib/ace/autocomplete.js | 38 ++++++++++++++++++++++++++++------- lib/ace/ext/language_tools.js | 6 +----- 2 files changed, 32 insertions(+), 12 deletions(-) diff --git a/lib/ace/autocomplete.js b/lib/ace/autocomplete.js index de30a740..34462894 100644 --- a/lib/ace/autocomplete.js +++ b/lib/ace/autocomplete.js @@ -54,13 +54,14 @@ var Autocomplete = function() { }; (function() { + this.gatherCompletionsId = 0; + this.$init = function() { this.popup = new AcePopup(document.body || document.documentElement); this.popup.on("click", function(e) { this.insertMatch(); e.stop(); }.bind(this)); - this.gatherCompletionsId = 0; }; this.openPopup = function(editor, prefix, keepPopupPosition) { @@ -96,6 +97,10 @@ var Autocomplete = function() { this.editor.off("mousedown", this.mousedownListener); this.editor.off("mousewheel", this.mousewheelListener); this.changeTimer.cancel(); + + if (this.popup && this.popup.isOpen) { + this.gatherCompletionsId = this.gatherCompletionsId + 1; + } if (this.popup) this.popup.hide(); @@ -147,7 +152,6 @@ var Autocomplete = function() { data = this.popup.getData(this.popup.getRow()); if (!data) return false; - this.gatherCompletionsId = this.gatherCompletionsId + 1; if (data.completer && data.completer.insertMatch) { data.completer.insertMatch(this.editor); @@ -194,13 +198,15 @@ var Autocomplete = function() { this.base.column -= prefix.length; var matches = []; - editor.completers.forEach(function(completer) { + var total = editor.completers.length; + editor.completers.forEach(function(completer, i) { completer.getCompletions(editor, session, pos, prefix, function(err, results) { if (!err) matches = matches.concat(results); callback(null, { prefix: prefix, - matches: matches + matches: matches, + left: total - (i + 1) }); }); }); @@ -239,6 +245,10 @@ var Autocomplete = function() { this.completions.setFilter(prefix); if (!this.completions.filtered.length) return this.detach(); + if (this.completions.filtered.length == 1 + && this.completions.filtered[0].value == prefix + && !this.completions.filtered[0].snippet) + return this.detach(); this.openPopup(this.editor, prefix, keepPopupPosition); return; } @@ -246,6 +256,11 @@ var Autocomplete = function() { // Save current gatherCompletions session, session is close when a match is insert var _id = this.gatherCompletionsId; this.gatherCompletions(this.editor, function(err, results) { + var doDetach = function() { + if (results.left > 0) return; + return this.detach(); + }.bind(this); + // Calcul prefix var session = that.editor.getSession(); var pos = that.editor.getCursorPosition(); @@ -260,9 +275,9 @@ var Autocomplete = function() { // No prefix or no results -> close if (!prefix || !prefix.length || !matches || !matches.length) - return this.detach(); + return doDetach(); - // Wrong prefx or wrong session -> ignore + // Wrong prefix or wrong session -> ignore if (prefix.indexOf(results.prefix) != 0 || _id != this.gatherCompletionsId) return; @@ -270,10 +285,19 @@ var Autocomplete = function() { this.completions = new FilteredList(matches); this.completions.setFilter(prefix); var filtered = this.completions.filtered; + + // No results if (!filtered.length) - return this.detach(); + return doDetach(); + + // One result equals to the prefix + if (filtered.length == 1 && filtered[0].value == prefix && !filtered[0].snippet) + return doDetach(); + + // Autoinsert if one result if (this.autoInsert && filtered.length == 1) return this.insertMatch(filtered[0]); + this.openPopup(this.editor, prefix, keepPopupPosition); }.bind(this)); }; diff --git a/lib/ace/ext/language_tools.js b/lib/ace/ext/language_tools.js index deb1973d..fa913e5b 100644 --- a/lib/ace/ext/language_tools.js +++ b/lib/ace/ext/language_tools.js @@ -147,10 +147,6 @@ var onChangeAutocomplete = function(e) { return; } - // Append added text to the line - line += text; - pos.column += text.length; - // The prefix to autocomplete for var prefix = util.retrievePrecedingIdentifier(line, pos.column); @@ -186,7 +182,7 @@ require("../config").defineOptions(Editor.prototype, "editor", { // On each change automatically trigger the autocomplete this.commands.on('afterExec', onChangeAutocomplete); } else { - this.removeListener('change', onChangeAutocomplete); + this.removeListener('afterExec', onChangeAutocomplete); this.commands.removeCommand(Autocomplete.startCommand); } }, From e76a389e88d5d0837cb39e19164a04c11672c9f3 Mon Sep 17 00:00:00 2001 From: Aaron O'Mullan Date: Wed, 5 Feb 2014 20:10:20 +0100 Subject: [PATCH 12/19] Clarify typing condition in live autocomplete --- lib/ace/ext/language_tools.js | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/lib/ace/ext/language_tools.js b/lib/ace/ext/language_tools.js index fa913e5b..9bb5da6a 100644 --- a/lib/ace/ext/language_tools.js +++ b/lib/ace/ext/language_tools.js @@ -124,14 +124,10 @@ var onChangeAutocomplete = function(e) { var text = e.args || ""; - // Detect paste (poor man's paste detection) - var typing = !( - ( - e.command.name === "insertstring" && - text.length > 1 - ) || - e.command.name !== "insertstring" - ); + // Is the user entering text + // we only want to automatically show the autocomplete dialog + // whenever the user is typing in text not pasting, deleting, ... + var typing = (e.command.name === "insertstring" && text.length === 1); // We don't want to autocomplete with no prefix if( From 0e256ce80d9faf66a764c52d527d38d0c9b94636 Mon Sep 17 00:00:00 2001 From: Aaron O'Mullan Date: Wed, 5 Feb 2014 20:26:40 +0100 Subject: [PATCH 13/19] Remove autocomplete shadow from ambiance theme This fixes a regression caused by the newly introduced feature allowing autocomplete theming --- lib/ace/theme/ambiance.css | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/ace/theme/ambiance.css b/lib/ace/theme/ambiance.css index c2ac69cf..f45e1d0e 100644 --- a/lib/ace/theme/ambiance.css +++ b/lib/ace/theme/ambiance.css @@ -26,7 +26,8 @@ .ace-ambiance .ace_fold-widget.ace_start, .ace-ambiance .ace_fold-widget.ace_end, -.ace-ambiance .ace_fold-widget.ace_closed{ +.ace-ambiance .ace_fold-widget.ace_closed, +.ace-ambiance.ace_autocomplete .ace_scroller { background: none; border: none; box-shadow: none; @@ -74,7 +75,7 @@ .ace-ambiance.normal-mode .ace_cursor-layer { z-index: 0; } - + .ace-ambiance .ace_marker-layer .ace_selection { background: rgba(221, 240, 255, 0.20); } @@ -134,7 +135,7 @@ } .ace-ambiance .ace_constant.ace_library { - + } .ace-ambiance .ace_constant.ace_numeric { @@ -214,4 +215,4 @@ .ace-ambiance .ace_indent-guide { background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAEklEQVQImWNQUFD4z6Crq/sfAAuYAuYl+7lfAAAAAElFTkSuQmCC") right repeat-y; -} \ No newline at end of file +} From c52278a64e712b3492e02d14172624fd7c03a1d6 Mon Sep 17 00:00:00 2001 From: Aaron O'Mullan Date: Sat, 8 Feb 2014 00:32:16 +0100 Subject: [PATCH 14/19] Cleanup autocomplete.js Consistently use .bind(this) instead of that = this --- lib/ace/autocomplete.js | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/lib/ace/autocomplete.js b/lib/ace/autocomplete.js index 34462894..c5109ae1 100644 --- a/lib/ace/autocomplete.js +++ b/lib/ace/autocomplete.js @@ -47,7 +47,7 @@ var Autocomplete = function() { this.changeListener = this.changeListener.bind(this); this.mousedownListener = this.mousedownListener.bind(this); this.mousewheelListener = this.mousewheelListener.bind(this); - + this.changeTimer = lang.delayedCall(function() { this.updateCompletions(true); }.bind(this)) @@ -77,10 +77,10 @@ var Autocomplete = function() { this.popup.setFontSize(editor.getFontSize()); var lineHeight = renderer.layerConfig.lineHeight; - - var pos = renderer.$cursorLayer.getPixelPosition(this.base, true); + + var pos = renderer.$cursorLayer.getPixelPosition(this.base, true); pos.left -= this.popup.getTextLeftOffset(); - + var rect = editor.container.getBoundingClientRect(); pos.top += rect.top - renderer.layerConfig.offset; pos.left += rect.left - editor.renderer.scrollLeft; @@ -101,7 +101,7 @@ var Autocomplete = function() { if (this.popup && this.popup.isOpen) { this.gatherCompletionsId = this.gatherCompletionsId + 1; } - + if (this.popup) this.popup.hide(); @@ -190,10 +190,10 @@ var Autocomplete = function() { this.gatherCompletions = function(editor, callback) { var session = editor.getSession(); var pos = editor.getCursorPosition(); - + var line = session.getLine(pos.row); var prefix = util.retrievePrecedingIdentifier(line, pos.column); - + this.base = editor.getCursorPosition(); this.base.column -= prefix.length; @@ -216,7 +216,7 @@ var Autocomplete = function() { this.showPopup = function(editor) { if (this.editor) this.detach(); - + this.activated = true; this.editor = editor; @@ -231,12 +231,11 @@ var Autocomplete = function() { editor.on("blur", this.blurListener); editor.on("mousedown", this.mousedownListener); editor.on("mousewheel", this.mousewheelListener); - + this.updateCompletions(); }; - + this.updateCompletions = function(keepPopupPosition) { - var that = this; if (keepPopupPosition && this.base && this.completions) { var pos = this.editor.getCursorPosition(); var prefix = this.editor.session.getTextRange({start: this.base, end: pos}); @@ -262,14 +261,14 @@ var Autocomplete = function() { }.bind(this); // Calcul prefix - var session = that.editor.getSession(); - var pos = that.editor.getCursorPosition(); + var session = this.editor.getSession(); + var pos = this.editor.getCursorPosition(); var line = session.getLine(pos.row); var prefix = util.retrievePrecedingIdentifier(line, pos.column); // Results matches var matches = results && results.matches; - // TODO reenable this when we have proper change tracking + // TODO reenable this when we have proper change tracking // if (matches.length == 1) // return this.insertMatch(matches[0]); @@ -343,16 +342,16 @@ var FilteredList = function(array, filterText, mutateData) { matches = matches.sort(function(a, b) { return b.exactMatch - a.exactMatch || b.score - a.score; }); - + // make unique var prev = null; matches = matches.filter(function(item){ - var caption = item.value || item.caption || item.snippet; + var caption = item.value || item.caption || item.snippet; if (caption === prev) return false; prev = caption; return true; }); - + this.filtered = matches; }; this.filterCompletions = function(items, needle) { From cbbbf1c539ee84abf499c3601369545fd37b680f Mon Sep 17 00:00:00 2001 From: Aaron O'Mullan Date: Sat, 8 Feb 2014 00:50:50 +0100 Subject: [PATCH 15/19] Fix bug and clarify async autocomplete result gathering MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit @SamyPesse’s “left: total - (i + 1)” was flawed because it only would work if all completers were async --- lib/ace/autocomplete.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/ace/autocomplete.js b/lib/ace/autocomplete.js index c5109ae1..8bf3a401 100644 --- a/lib/ace/autocomplete.js +++ b/lib/ace/autocomplete.js @@ -206,7 +206,7 @@ var Autocomplete = function() { callback(null, { prefix: prefix, matches: matches, - left: total - (i + 1) + finished: (--total === 0) }); }); }); @@ -255,8 +255,9 @@ var Autocomplete = function() { // Save current gatherCompletions session, session is close when a match is insert var _id = this.gatherCompletionsId; this.gatherCompletions(this.editor, function(err, results) { + // Only detach if result gathering is finished var doDetach = function() { - if (results.left > 0) return; + if (!results.finished) return; return this.detach(); }.bind(this); From f7be399bd23aa8b35f91aa6dfc1653dbb94b50eb Mon Sep 17 00:00:00 2001 From: Aaron O'Mullan Date: Sat, 8 Feb 2014 01:01:30 +0100 Subject: [PATCH 16/19] Add "enableLiveAutocomplete" option MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This makes live autocomplete optional and is not tied to “enableBasicAutocomplete” anymore This also renames “onChangeAutocomplete” to “doLiveAutocomplete” --- demo/kitchen-sink/demo.js | 7 ++++--- lib/ace/ext/language_tools.js | 17 ++++++++++++----- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/demo/kitchen-sink/demo.js b/demo/kitchen-sink/demo.js index 9880d33e..52f71d20 100644 --- a/demo/kitchen-sink/demo.js +++ b/demo/kitchen-sink/demo.js @@ -251,7 +251,7 @@ commands.addCommand({ } }); -var keybindings = { +var keybindings = { ace: null, // Null = use "default" keymapping vim: require("ace/keyboard/vim").handler, emacs: "ace/keyboard/emacs", @@ -317,7 +317,7 @@ doclist.history.index = 0; doclist.cycleOpen = function(editor, dir) { var h = this.history; h.index += dir; - if (h.index >= h.length) + if (h.index >= h.length) h.index = 0; else if (h.index <= 0) h.index = h.length - 1; @@ -499,7 +499,7 @@ bindDropdown("split", function(value) { sp.setSplits(1); } else { var newEditor = (sp.getSplits() == 1); - sp.setOrientation(value == "below" ? sp.BELOW : sp.BESIDE); + sp.setOrientation(value == "below" ? sp.BELOW : sp.BESIDE); sp.setSplits(2); if (newEditor) { @@ -592,6 +592,7 @@ env.editSnippets = function() { require("ace/ext/language_tools"); env.editor.setOptions({ enableBasicAutocompletion: true, + enableLiveAutocomplete: true, enableSnippets: true }); diff --git a/lib/ace/ext/language_tools.js b/lib/ace/ext/language_tools.js index 9bb5da6a..80c6973a 100644 --- a/lib/ace/ext/language_tools.js +++ b/lib/ace/ext/language_tools.js @@ -115,7 +115,7 @@ var loadSnippetFile = function(id) { }); }; -var onChangeAutocomplete = function(e) { +var doLiveAutocomplete = function(e) { var editor = e.editor; var session = editor.getSession(); var pos = editor.getCursorPosition(); @@ -174,16 +174,23 @@ require("../config").defineOptions(Editor.prototype, "editor", { if (val) { this.completers = completers; this.commands.addCommand(Autocomplete.startCommand); - - // On each change automatically trigger the autocomplete - this.commands.on('afterExec', onChangeAutocomplete); } else { - this.removeListener('afterExec', onChangeAutocomplete); this.commands.removeCommand(Autocomplete.startCommand); } }, value: false }, + enableLiveAutocomplete: { + set: function(val) { + if (val) { + // On each change automatically trigger the autocomplete + this.commands.on('afterExec', doLiveAutocomplete); + } else { + this.removeListener('afterExec', doLiveAutocomplete); + } + }, + value: false + }, enableSnippets: { set: function(val) { if (val) { From e06a14ab92a7ab1aecbf0d23651629e40e005357 Mon Sep 17 00:00:00 2001 From: Aaron O'Mullan Date: Sat, 8 Feb 2014 01:38:24 +0100 Subject: [PATCH 17/19] Revert "Remove autocomplete shadow from ambiance theme" This reverts commit 0e256ce80d9faf66a764c52d527d38d0c9b94636. --- lib/ace/theme/ambiance.css | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/ace/theme/ambiance.css b/lib/ace/theme/ambiance.css index f45e1d0e..c2ac69cf 100644 --- a/lib/ace/theme/ambiance.css +++ b/lib/ace/theme/ambiance.css @@ -26,8 +26,7 @@ .ace-ambiance .ace_fold-widget.ace_start, .ace-ambiance .ace_fold-widget.ace_end, -.ace-ambiance .ace_fold-widget.ace_closed, -.ace-ambiance.ace_autocomplete .ace_scroller { +.ace-ambiance .ace_fold-widget.ace_closed{ background: none; border: none; box-shadow: none; @@ -75,7 +74,7 @@ .ace-ambiance.normal-mode .ace_cursor-layer { z-index: 0; } - + .ace-ambiance .ace_marker-layer .ace_selection { background: rgba(221, 240, 255, 0.20); } @@ -135,7 +134,7 @@ } .ace-ambiance .ace_constant.ace_library { - + } .ace-ambiance .ace_constant.ace_numeric { @@ -215,4 +214,4 @@ .ace-ambiance .ace_indent-guide { background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAEklEQVQImWNQUFD4z6Crq/sfAAuYAuYl+7lfAAAAAElFTkSuQmCC") right repeat-y; -} +} \ No newline at end of file From 889e9248f81f10ce9f9ddd50c5a123bcebd24a6c Mon Sep 17 00:00:00 2001 From: Aaron O'Mullan Date: Sat, 8 Feb 2014 01:47:01 +0100 Subject: [PATCH 18/19] Cancel out unwanted editor's css for autocomplete dialog --- lib/ace/autocomplete/popup.js | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/lib/ace/autocomplete/popup.js b/lib/ace/autocomplete/popup.js index cc4618b6..d27b904f 100644 --- a/lib/ace/autocomplete/popup.js +++ b/lib/ace/autocomplete/popup.js @@ -43,7 +43,7 @@ var $singleLineEditor = function(el) { var renderer = new Renderer(el); renderer.$maxLines = 4; - + var editor = new Editor(renderer); editor.setHighlightActiveLine(false); @@ -59,13 +59,13 @@ var $singleLineEditor = function(el) { var AcePopup = function(parentNode) { var el = dom.createElement("div"); var popup = new $singleLineEditor(el); - + if (parentNode) parentNode.appendChild(el); el.style.display = "none"; popup.renderer.content.style.cursor = "default"; popup.renderer.setStyle("ace_autocomplete"); - + popup.setOption("displayIndentGuides", false); var noop = function(){}; @@ -155,11 +155,11 @@ var AcePopup = function(parentNode) { popup.getHoveredRow = function() { return hoverMarker.start.row; }; - + event.addListener(popup.container, "mouseout", hideHoverMarker); popup.on("hide", hideHoverMarker); popup.on("changeSelection", hideHoverMarker); - + popup.session.doc.getLength = function() { return popup.data.length; }; @@ -203,7 +203,7 @@ var AcePopup = function(parentNode) { }; bgTokenizer.$updateOnChange = noop; bgTokenizer.start = noop; - + popup.session.$computeWidth = function() { return this.screenWidth = 0; } @@ -211,7 +211,7 @@ var AcePopup = function(parentNode) { // public popup.isOpen = false; popup.isTopdown = false; - + popup.data = []; popup.setData = function(list) { popup.data = list || []; @@ -236,7 +236,7 @@ var AcePopup = function(parentNode) { popup._signal("select"); } }; - + popup.on("changeSelection", function() { if (popup.isOpen) popup.setRow(popup.selection.lead.row); @@ -268,22 +268,22 @@ var AcePopup = function(parentNode) { el.style.display = ""; this.renderer.$textLayer.checkForSizeChanges(); - + var left = pos.left; if (left + el.offsetWidth > screenWidth) left = screenWidth - el.offsetWidth; - + el.style.left = left + "px"; - + this._signal("show"); lastMouseEvent = null; popup.isOpen = true; }; - + popup.getTextLeftOffset = function() { return this.$borderSize + this.renderer.$padding + this.$imageSize; }; - + popup.$imageSize = 0; popup.$borderSize = 1; @@ -304,6 +304,11 @@ dom.importCssString("\ position: absolute;\ z-index: 2;\ }\ +.ace_editor.ace_autocomplete .ace_scroller {\ + background: none;\ + border: none;\ + box-shadow: none;\ +}\ .ace_rightAlignedText {\ color: gray;\ display: inline-block;\ From 297c05831e5949e1c4363972e126d2b1cd197ffd Mon Sep 17 00:00:00 2001 From: Aaron O'Mullan Date: Sat, 8 Feb 2014 02:18:52 +0100 Subject: [PATCH 19/19] Fix minor typo in enableLiveAutocomplete --- lib/ace/ext/language_tools.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ace/ext/language_tools.js b/lib/ace/ext/language_tools.js index 80c6973a..0f9be6b9 100644 --- a/lib/ace/ext/language_tools.js +++ b/lib/ace/ext/language_tools.js @@ -186,7 +186,7 @@ require("../config").defineOptions(Editor.prototype, "editor", { // On each change automatically trigger the autocomplete this.commands.on('afterExec', doLiveAutocomplete); } else { - this.removeListener('afterExec', doLiveAutocomplete); + this.commands.removeListener('afterExec', doLiveAutocomplete); } }, value: false