From 6d1e9844909a9baf5d0e9478037c781eff843744 Mon Sep 17 00:00:00 2001 From: Jiahao Li Date: Fri, 22 Feb 2019 20:06:12 -0500 Subject: [PATCH 1/2] Fixes textEdit completions Currently the original replacement prefix sent by the LSP server in the `textEdit` field is overwritten in getSuggestions(). This, for example, causes the following case: foo-> to autocomplete to foo->->bar() instead of the expected foo->bar() This commit fixes the issue by saving the original replacement prefix (`->` in the example above), and modifying `getSuggestions()` to append to instead of overwrite this original replacement prefix. --- lib/adapters/autocomplete-adapter.ts | 41 ++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/lib/adapters/autocomplete-adapter.ts b/lib/adapters/autocomplete-adapter.ts index bd9f024..eb76fa2 100644 --- a/lib/adapters/autocomplete-adapter.ts +++ b/lib/adapters/autocomplete-adapter.ts @@ -25,7 +25,15 @@ interface SuggestionCacheEntry { isIncomplete: boolean; triggerPoint: Point; triggerChar: string; + triggerPrefix: string; suggestionMap: Map; + + // Original replacement prefixes as returned by the LSP server. + // + // If the server used the `textEdit` field, this value will be non-null. + // Otherwise, it means that the server did not give us an explicit replacement + // prefix, and therefore this value will be null. + originalReplacementPrefixMap: Map; } type CompletionItemAdjuster = @@ -84,12 +92,25 @@ export default class AutocompleteAdapter { // Get the suggestions either from the cache or by calling the language server const suggestions = await - this.getOrBuildSuggestions(server, request, triggerChar, triggerOnly, onDidConvertCompletionItem); + this.getOrBuildSuggestions(server, request, triggerChar, triggerOnly, request.prefix, onDidConvertCompletionItem); + // Force unwrapping here is okay since this.getOrBuildSuggestions ensured that the following get() + // would not return undefined. + const cache = this._suggestionCache.get(server)!; // As the user types more characters to refine filter we must replace those characters on acceptance const replacementPrefix = (triggerChar !== '' && triggerOnly) ? '' : request.prefix; + const originalReplacementPrefixMap = cache.originalReplacementPrefixMap; for (const suggestion of suggestions) { - suggestion.replacementPrefix = replacementPrefix; + // Force unwrapping here is okay for similar reasons as in the comment above. + const originalReplacementPrefix = originalReplacementPrefixMap.get(suggestion); + if (originalReplacementPrefix) { + // The server gave us a replacement prefix via the `textEdit` field, which we must honor. However, + // we also need to append the extra bits that the user has typed since we made the initial request. + const extraReplacementPrefix = replacementPrefix.substr(cache.triggerPrefix.length); + suggestion.replacementPrefix = originalReplacementPrefix + extraReplacementPrefix; + } else { + suggestion.replacementPrefix = replacementPrefix; + } } const filtered = !(request.prefix === "" || (triggerChar !== '' && triggerOnly)); @@ -112,6 +133,7 @@ export default class AutocompleteAdapter { request: ac.SuggestionsRequestedEvent, triggerChar: string, triggerOnly: boolean, + triggerPrefix: string, onDidConvertCompletionItem?: CompletionItemAdjuster, ): Promise { const cache = this._suggestionCache.get(server); @@ -136,7 +158,20 @@ export default class AutocompleteAdapter { // Setup the cache for subsequent filtered results const isComplete = completions == null || Array.isArray(completions) || completions.isIncomplete === false; const suggestionMap = this.completionItemsToSuggestions(completions, request, onDidConvertCompletionItem); - this._suggestionCache.set(server, { isIncomplete: !isComplete, triggerChar, triggerPoint, suggestionMap }); + const originalReplacementPrefixMap = new Map( + Array.from(suggestionMap.keys()).map<[ac.AnySuggestion, string]>( + (suggestion) => [suggestion, suggestion.replacementPrefix || ''], + ), + ); + + this._suggestionCache.set(server, { + isIncomplete: !isComplete, + triggerChar, + triggerPoint, + triggerPrefix: (triggerChar !== '' && triggerOnly) ? '' : triggerPrefix, + suggestionMap, + originalReplacementPrefixMap, + }); return Array.from(suggestionMap.keys()); } From 86a9ac222c7cb5240147f69df2379299b47bd789 Mon Sep 17 00:00:00 2001 From: Jiahao Li Date: Sat, 23 Feb 2019 00:56:26 -0500 Subject: [PATCH 2/2] Removes unnecessary triggerPrefix argument to getOrBuildSuggestions --- lib/adapters/autocomplete-adapter.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/adapters/autocomplete-adapter.ts b/lib/adapters/autocomplete-adapter.ts index eb76fa2..48f8a3a 100644 --- a/lib/adapters/autocomplete-adapter.ts +++ b/lib/adapters/autocomplete-adapter.ts @@ -92,7 +92,7 @@ export default class AutocompleteAdapter { // Get the suggestions either from the cache or by calling the language server const suggestions = await - this.getOrBuildSuggestions(server, request, triggerChar, triggerOnly, request.prefix, onDidConvertCompletionItem); + this.getOrBuildSuggestions(server, request, triggerChar, triggerOnly, onDidConvertCompletionItem); // Force unwrapping here is okay since this.getOrBuildSuggestions ensured that the following get() // would not return undefined. @@ -133,7 +133,6 @@ export default class AutocompleteAdapter { request: ac.SuggestionsRequestedEvent, triggerChar: string, triggerOnly: boolean, - triggerPrefix: string, onDidConvertCompletionItem?: CompletionItemAdjuster, ): Promise { const cache = this._suggestionCache.get(server); @@ -168,7 +167,7 @@ export default class AutocompleteAdapter { isIncomplete: !isComplete, triggerChar, triggerPoint, - triggerPrefix: (triggerChar !== '' && triggerOnly) ? '' : triggerPrefix, + triggerPrefix: (triggerChar !== '' && triggerOnly) ? '' : request.prefix, suggestionMap, originalReplacementPrefixMap, });