@@ -23,14 +23,24 @@ interface ParameterListParts {
23
23
readonly hasOptionalParameters : boolean ;
24
24
}
25
25
26
+ export interface CompletionContext {
27
+ readonly isMemberCompletion : boolean ;
28
+ readonly dotAccessorContext ?: {
29
+ range : lsp . Range ;
30
+ text : string ;
31
+ } ;
32
+ readonly line : string ;
33
+ readonly optionalReplacementRange : lsp . Range | undefined ;
34
+ }
35
+
26
36
export function asCompletionItem (
27
37
entry : ts . server . protocol . CompletionEntry ,
28
- optionalReplacementSpan : ts . server . protocol . TextSpan | undefined ,
29
38
file : string , position : lsp . Position ,
30
39
document : LspDocument ,
31
40
filePathConverter : IFilePathToResourceConverter ,
32
41
options : WorkspaceConfigurationCompletionOptions ,
33
42
features : SupportedFeatures ,
43
+ completionContext : CompletionContext ,
34
44
) : lsp . CompletionItem | null {
35
45
const item : lsp . CompletionItem = {
36
46
label : entry . name ,
@@ -71,7 +81,26 @@ export function asCompletionItem(
71
81
item . detail = Previewer . plainWithLinks ( sourceDisplay , filePathConverter ) ;
72
82
}
73
83
84
+ const { line, optionalReplacementRange, isMemberCompletion, dotAccessorContext } = completionContext ;
85
+ let range = getRangeFromReplacementSpan ( replacementSpan , optionalReplacementRange , position , document , features ) ;
74
86
let { insertText } = entry ;
87
+ item . filterText = getFilterText ( entry , optionalReplacementRange , line , insertText ) ;
88
+
89
+ if ( isMemberCompletion && dotAccessorContext && ! entry . isSnippet ) {
90
+ item . filterText = dotAccessorContext . text + ( insertText || item . label ) ;
91
+ if ( ! range ) {
92
+ if ( features . completionInsertReplaceSupport && optionalReplacementRange ) {
93
+ range = {
94
+ insert : dotAccessorContext . range ,
95
+ replace : Range . union ( dotAccessorContext . range , optionalReplacementRange ) ,
96
+ } ;
97
+ } else {
98
+ range = { replace : dotAccessorContext . range } ;
99
+ }
100
+ insertText = item . filterText ;
101
+ }
102
+ }
103
+
75
104
if ( entry . kindModifiers ) {
76
105
const kindModifiers = new Set ( entry . kindModifiers . split ( / , | \s + / g) ) ;
77
106
if ( kindModifiers . has ( KindModifiers . optional ) ) {
@@ -101,20 +130,21 @@ export function asCompletionItem(
101
130
}
102
131
}
103
132
}
104
- const range = getRangeFromReplacementSpan ( replacementSpan , optionalReplacementSpan , position , document , features ) ;
133
+
105
134
if ( range ) {
106
135
item . textEdit = range . insert
107
136
? lsp . InsertReplaceEdit . create ( insertText || item . label , range . insert , range . replace )
108
137
: lsp . TextEdit . replace ( range . replace , insertText || item . label ) ;
109
138
} else {
110
139
item . insertText = insertText ;
111
140
}
141
+
112
142
return item ;
113
143
}
114
144
115
145
function getRangeFromReplacementSpan (
116
146
replacementSpan : ts . server . protocol . TextSpan | undefined ,
117
- optionalReplacementSpan : ts . server . protocol . TextSpan | undefined ,
147
+ optionalReplacementRange : lsp . Range | undefined ,
118
148
position : lsp . Position ,
119
149
document : LspDocument ,
120
150
features : SupportedFeatures ,
@@ -125,15 +155,50 @@ function getRangeFromReplacementSpan(
125
155
replace : ensureRangeIsOnSingleLine ( Range . fromTextSpan ( replacementSpan ) , document ) ,
126
156
} ;
127
157
}
128
- if ( features . completionInsertReplaceSupport && optionalReplacementSpan ) {
129
- const range = ensureRangeIsOnSingleLine ( Range . fromTextSpan ( optionalReplacementSpan ) , document ) ;
158
+ if ( features . completionInsertReplaceSupport && optionalReplacementRange ) {
159
+ const range = ensureRangeIsOnSingleLine ( optionalReplacementRange , document ) ;
130
160
return {
131
161
insert : lsp . Range . create ( range . start , position ) ,
132
- replace : ensureRangeIsOnSingleLine ( range , document ) ,
162
+ replace : range ,
133
163
} ;
134
164
}
135
165
}
136
166
167
+ function getFilterText ( entry : ts . server . protocol . CompletionEntry , wordRange : lsp . Range | undefined , line : string , insertText : string | undefined ) : string | undefined {
168
+ // Handle private field completions
169
+ if ( entry . name . startsWith ( '#' ) ) {
170
+ const wordStart = wordRange ? line . charAt ( wordRange . start . character ) : undefined ;
171
+ if ( insertText ) {
172
+ if ( insertText . startsWith ( 'this.#' ) ) {
173
+ return wordStart === '#' ? insertText : insertText . replace ( / & t h i s \. # / , '' ) ;
174
+ } else {
175
+ return wordStart ;
176
+ }
177
+ } else {
178
+ return wordStart === '#' ? undefined : entry . name . replace ( / ^ # / , '' ) ;
179
+ }
180
+ }
181
+
182
+ // For `this.` completions, generally don't set the filter text since we don't want them to be overly prioritized. #74164
183
+ if ( insertText ?. startsWith ( 'this.' ) ) {
184
+ return undefined ;
185
+ }
186
+
187
+ // Handle the case:
188
+ // ```
189
+ // const xyz = { 'ab c': 1 };
190
+ // xyz.ab|
191
+ // ```
192
+ // In which case we want to insert a bracket accessor but should use `.abc` as the filter text instead of
193
+ // the bracketed insert text.
194
+ if ( insertText ?. startsWith ( '[' ) ) {
195
+ return insertText . replace ( / ^ \[ [ ' " ] ( .+ ) [ [ ' " ] \] $ / , '.$1' ) ;
196
+ }
197
+
198
+ // In all other cases, fallback to using the insertText
199
+ return insertText ;
200
+ }
201
+
137
202
function ensureRangeIsOnSingleLine ( range : lsp . Range , document : LspDocument ) : lsp . Range {
138
203
if ( range . start . line !== range . end . line ) {
139
204
return lsp . Range . create ( range . start , document . getLineEnd ( range . start . line ) ) ;
@@ -279,7 +344,7 @@ function createSnippetOfFunctionCall(item: lsp.CompletionItem, detail: ts.server
279
344
const { displayParts } = detail ;
280
345
const parameterListParts = getParameterListParts ( displayParts ) ;
281
346
const snippet = new SnippetString ( ) ;
282
- snippet . appendText ( `${ item . insertText || item . label } (` ) ;
347
+ snippet . appendText ( `${ item . insertText || item . textEdit ?. newText || item . label } (` ) ;
283
348
appendJoinedPlaceholders ( snippet , parameterListParts . parts , ', ' ) ;
284
349
if ( parameterListParts . hasOptionalParameters ) {
285
350
snippet . appendTabstop ( ) ;
0 commit comments