Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions src/autocomplete.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down
69 changes: 69 additions & 0 deletions src/autocomplete_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,45 @@ module.exports = {
assert.equal(editor.getValue(), "<dialog");
done();
},
"test: completions are matched against filterText when it is provided": async function (done) {
editor = initEditor("<");

editor.completers = [
{
// a language in which "<" is part of the identifier the user is typing
identifierRegexps: [/[a-zA-Z_0-9<]/],
getCompletions: function (editor, session, pos, prefix, callback) {
var completions = [
{
caption: "iframe",
value: "<iframe></iframe>",
filterText: "<iframe"
}, {
caption: "img",
value: "<img>"
}
];
callback(null, completions);
}
}
];

editor.moveCursorTo(0, 1);
sendKey("ifra");
await lang.sleep(10);

// the prefix is "<ifra", which is only reachable through the filterText of the first completion,
// while the caption of the second one is matched as usual and therefore filtered out
var popup = editor.completer && editor.completer.popup;
assert.ok(popup && popup.isOpen, "the completion popup should be open");
assert.equal(popup.data.length, 1);
assert.equal(popup.getData(0).caption, "iframe");

editor.onCommandKey(null, 0, 13);
await lang.sleep(10);
assert.equal(editor.getValue(), "<iframe></iframe>");
done();
},
"test: symbols after selection are not removed when replacement range is present": async function (done) {
editor = initEditor("{}");
editor.completers = [
Expand Down Expand Up @@ -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");

Expand Down
7 changes: 7 additions & 0 deletions types/ace-modules.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
*/
Expand Down
Loading