@@ -25,7 +25,15 @@ interface SuggestionCacheEntry {
25
25
isIncomplete : boolean ;
26
26
triggerPoint : Point ;
27
27
triggerChar : string ;
28
+ triggerPrefix : string ;
28
29
suggestionMap : Map < ac . AnySuggestion , PossiblyResolvedCompletionItem > ;
30
+
31
+ // Original replacement prefixes as returned by the LSP server.
32
+ //
33
+ // If the server used the `textEdit` field, this value will be non-null.
34
+ // Otherwise, it means that the server did not give us an explicit replacement
35
+ // prefix, and therefore this value will be null.
36
+ originalReplacementPrefixMap : Map < ac . AnySuggestion , string | null > ;
29
37
}
30
38
31
39
type CompletionItemAdjuster =
@@ -84,12 +92,25 @@ export default class AutocompleteAdapter {
84
92
85
93
// Get the suggestions either from the cache or by calling the language server
86
94
const suggestions = await
87
- this . getOrBuildSuggestions ( server , request , triggerChar , triggerOnly , onDidConvertCompletionItem ) ;
95
+ this . getOrBuildSuggestions ( server , request , triggerChar , triggerOnly , request . prefix , onDidConvertCompletionItem ) ;
88
96
97
+ // Force unwrapping here is okay since this.getOrBuildSuggestions ensured that the following get()
98
+ // would not return undefined.
99
+ const cache = this . _suggestionCache . get ( server ) ! ;
89
100
// As the user types more characters to refine filter we must replace those characters on acceptance
90
101
const replacementPrefix = ( triggerChar !== '' && triggerOnly ) ? '' : request . prefix ;
102
+ const originalReplacementPrefixMap = cache . originalReplacementPrefixMap ;
91
103
for ( const suggestion of suggestions ) {
92
- suggestion . replacementPrefix = replacementPrefix ;
104
+ // Force unwrapping here is okay for similar reasons as in the comment above.
105
+ const originalReplacementPrefix = originalReplacementPrefixMap . get ( suggestion ) ;
106
+ if ( originalReplacementPrefix ) {
107
+ // The server gave us a replacement prefix via the `textEdit` field, which we must honor. However,
108
+ // we also need to append the extra bits that the user has typed since we made the initial request.
109
+ const extraReplacementPrefix = replacementPrefix . substr ( cache . triggerPrefix . length ) ;
110
+ suggestion . replacementPrefix = originalReplacementPrefix + extraReplacementPrefix ;
111
+ } else {
112
+ suggestion . replacementPrefix = replacementPrefix ;
113
+ }
93
114
}
94
115
95
116
const filtered = ! ( request . prefix === "" || ( triggerChar !== '' && triggerOnly ) ) ;
@@ -112,6 +133,7 @@ export default class AutocompleteAdapter {
112
133
request : ac . SuggestionsRequestedEvent ,
113
134
triggerChar : string ,
114
135
triggerOnly : boolean ,
136
+ triggerPrefix : string ,
115
137
onDidConvertCompletionItem ?: CompletionItemAdjuster ,
116
138
) : Promise < ac . AnySuggestion [ ] > {
117
139
const cache = this . _suggestionCache . get ( server ) ;
@@ -136,7 +158,20 @@ export default class AutocompleteAdapter {
136
158
// Setup the cache for subsequent filtered results
137
159
const isComplete = completions == null || Array . isArray ( completions ) || completions . isIncomplete === false ;
138
160
const suggestionMap = this . completionItemsToSuggestions ( completions , request , onDidConvertCompletionItem ) ;
139
- this . _suggestionCache . set ( server , { isIncomplete : ! isComplete , triggerChar, triggerPoint, suggestionMap } ) ;
161
+ const originalReplacementPrefixMap = new Map < ac . AnySuggestion , string > (
162
+ Array . from ( suggestionMap . keys ( ) ) . map < [ ac . AnySuggestion , string ] > (
163
+ ( suggestion ) => [ suggestion , suggestion . replacementPrefix || '' ]
164
+ )
165
+ ) ;
166
+
167
+ this . _suggestionCache . set ( server , {
168
+ isIncomplete : ! isComplete ,
169
+ triggerChar,
170
+ triggerPoint,
171
+ triggerPrefix : ( triggerChar !== '' && triggerOnly ) ? '' : triggerPrefix ,
172
+ suggestionMap,
173
+ originalReplacementPrefixMap,
174
+ } ) ;
140
175
141
176
return Array . from ( suggestionMap . keys ( ) ) ;
142
177
}
0 commit comments