diff --git a/src/autocomplete.js b/src/autocomplete.js index 041829bb4a..57fa6f6a4e 100644 --- a/src/autocomplete.js +++ b/src/autocomplete.js @@ -1061,12 +1061,13 @@ class FilteredList { || (a.caption || a.value).localeCompare(b.caption || b.value); }); - // make unique - var prev = null; + // make unique: completions are the same only when they read the same in the popup + // and insert the same text, and duplicates need not be next to each other + var seen = new Set(); matches = matches.filter(function(item){ - var caption = item.snippet || item.caption || item.value; - if (caption === prev) return false; - prev = caption; + var key = (item.caption || item.value || item.snippet) + "\0" + (item.snippet || item.value); + if (seen.has(key)) return false; + seen.add(key); return true; }); diff --git a/src/autocomplete_test.js b/src/autocomplete_test.js index f598f29bcc..052bc050ff 100644 --- a/src/autocomplete_test.js +++ b/src/autocomplete_test.js @@ -925,6 +925,49 @@ module.exports = { assert.equal(completer.popup.isOpen, true); assert.equal(completer.popup.data.length, 1); }, + "test: duplicate completions are removed regardless of their position": function() { + editor = initEditor(""); + + editor.completers = [ + { + getCompletions: function (editor, session, pos, prefix, callback) { + var completions = [ + { + value: "foo", + meta: "higher", + score: 5 + }, { + value: "bar", + score: 3 + }, { + value: "foo", + meta: "lower", + score: 1 + }, { + caption: "snippet a", + snippet: "shared body" + }, { + caption: "snippet b", + snippet: "shared body" + } + ]; + callback(null, completions); + } + } + ]; + + editor.execCommand('startAutocomplete'); + var data = editor.completer.popup.data; + + // sorting by score keeps the two identical completions apart, while the two snippets + // read differently in the popup and are therefore not duplicates of each other + assert.jsonEquals(data.map(function(item) { + return item.caption || item.value; + }), ["foo", "bar", "snippet a", "snippet b"]); + + // of a set of duplicates the first one after sorting survives, which is the highest scored + assert.equal(data[0].meta, "higher"); + }, "test: should add inline preview content to aria-describedby": function() { editor = initEditor("fun");