diff --git a/src/autocomplete.js b/src/autocomplete.js index 041829bb4a..6dc65455b1 100644 --- a/src/autocomplete.js +++ b/src/autocomplete.js @@ -29,6 +29,10 @@ var preventParentScroll = require("./lib/scroll").preventParentScroll; * it would be used instead of `docText`. * @property {string} [completerId] - the identifier of the completer * @property {boolean} [skipFilter] - a boolean value to decide if the popup item is going to skip the filtering process done using prefix text. + * @property {string} [filterText] - the text this completion is filtered and scored against, in place of the text it + * displays. Follows the semantics of the language server protocol's `CompletionItem.filterText`, and takes precedence + * over the `ignoreCaption` option. An empty string is ignored. The match highlight in the popup is still derived from + * the caption, so a completion reached through text that does not occur in its caption is shown without a highlight. * @property {import("../ace-internal").Ace.IRange} [range] - An object specifying the range of text to be replaced with the new completion value (experimental) * @property {any} [command] - A command to be executed after the completion is inserted (experimental) * @property {string} [snippet] - a text snippet that would be inserted when the completion is selected @@ -1085,28 +1089,29 @@ class FilteredList { } var caption = (!this.ignoreCaption && item.caption) || item.value || item.snippet; if (!caption) continue; + var matchText = item.filterText || caption; var lastIndex = -1; var matchMask = 0; var penalty = 0; var index, distance; if (this.exactMatch) { - if (needle !== caption.substr(0, needle.length)) + if (needle !== matchText.substr(0, needle.length)) continue loop; } else { /** * It is for situation then, for example, we find some like 'tab' in item.value="Check the table" * and want to see "Check the TABle" but see "Check The tABle". */ - var fullMatchIndex = caption.toLowerCase().indexOf(lower); + var fullMatchIndex = matchText.toLowerCase().indexOf(lower); if (fullMatchIndex > -1) { penalty = fullMatchIndex; } else { - // caption char iteration is faster in Chrome but slower in Firefox, so lets use indexOf + // 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); + var i1 = matchText.indexOf(lower[j], lastIndex + 1); + var i2 = matchText.indexOf(upper[j], lastIndex + 1); index = (i1 >= 0) ? ((i2 < 0 || i1 < i2) ? i1 : i2) : i2; if (index < 0) continue loop; diff --git a/src/autocomplete_test.js b/src/autocomplete_test.js index f598f29bcc..0ba765cad3 100644 --- a/src/autocomplete_test.js +++ b/src/autocomplete_test.js @@ -201,6 +201,45 @@ module.exports = { assert.equal(editor.getValue(), "", + filterText: "" + } + ]; + callback(null, completions); + } + } + ]; + + editor.moveCursorTo(0, 1); + sendKey("ifra"); + await lang.sleep(10); + + // the prefix is ""); + done(); + }, "test: symbols after selection are not removed when replacement range is present": async function (done) { editor = initEditor("{}"); editor.completers = [ @@ -874,6 +913,36 @@ module.exports = { user.type(" value"); assert.equal(completer.popup.isOpen, true); }, + "test: should filter using filterText even if ignoreCaption true": function() { + editor = initEditor("hello world\n"); + + var completer = { + getCompletions: function (editor, session, pos, prefix, callback) { + var completions = [ + { + caption: "caption", + value: "value", + filterText: "filter" + } + ]; + callback(null, completions); + } + }; + + editor.completers = [completer]; + + var autocomplete = Autocomplete.for(editor); + autocomplete.ignoreCaption = true; + + // Neither the caption nor the value is matched once the completion carries a filterText. + user.type(" val"); + assert.equal(autocomplete.popup, undefined); + + // Should filter using the filterText instead. + user.type(" filt"); + assert.equal(autocomplete.popup.isOpen, true); + assert.equal(autocomplete.popup.data.length, 1); + }, "test: should skip filter if skipFilter flag is set to true in completion": function() { editor = initEditor("hello world\n"); diff --git a/types/ace-modules.d.ts b/types/ace-modules.d.ts index 08e4c19aeb..31b9995c90 100644 --- a/types/ace-modules.d.ts +++ b/types/ace-modules.d.ts @@ -3437,6 +3437,13 @@ declare module "ace-code/src/autocomplete" { * - a boolean value to decide if the popup item is going to skip the filtering process done using prefix text. */ skipFilter?: boolean; + /** + * - the text this completion is filtered and scored against, in place of the text it + * displays. Follows the semantics of the language server protocol's `CompletionItem.filterText`, and takes precedence + * over the `ignoreCaption` option. An empty string is ignored. The match highlight in the popup is still derived from + * the caption, so a completion reached through text that does not occur in its caption is shown without a highlight. + */ + filterText?: string; /** * - An object specifying the range of text to be replaced with the new completion value (experimental) */