@@ -32,7 +32,6 @@ internal class PsesCompletionHandler : ICompletionHandler, ICompletionResolveHan
32
32
private readonly WorkspaceService _workspaceService ;
33
33
private CompletionCapability _capability ;
34
34
private readonly Guid _id = Guid . NewGuid ( ) ;
35
- private static readonly Regex _typeRegex = new ( @"^(\[.+\])" ) ;
36
35
37
36
Guid ICanBeIdentifiedHandler . Id => _id ;
38
37
@@ -168,163 +167,131 @@ public async Task<IEnumerable<CompletionItem>> GetCompletionsInFileAsync(
168
167
}
169
168
170
169
internal static CompletionItem CreateCompletionItem (
171
- CompletionResult completion ,
170
+ CompletionResult result ,
172
171
BufferRange completionRange ,
173
172
int sortIndex )
174
173
{
175
- Validate . IsNotNull ( nameof ( completion ) , completion ) ;
174
+ Validate . IsNotNull ( nameof ( result ) , result ) ;
176
175
177
- // Some tooltips may have newlines or whitespace for unknown reasons.
178
- string toolTipText = completion . ToolTip ? . Trim ( ) ;
176
+ TextEdit textEdit = new ( )
177
+ {
178
+ NewText = result . CompletionText ,
179
+ Range = new Range
180
+ {
181
+ Start = new Position
182
+ {
183
+ Line = completionRange . Start . Line - 1 ,
184
+ Character = completionRange . Start . Column - 1
185
+ } ,
186
+ End = new Position
187
+ {
188
+ Line = completionRange . End . Line - 1 ,
189
+ Character = completionRange . End . Column - 1
190
+ }
191
+ }
192
+ } ;
179
193
180
- string completionText = completion . CompletionText ;
181
- InsertTextFormat insertTextFormat = InsertTextFormat . PlainText ;
182
- CompletionItemKind kind ;
194
+ // Some tooltips may have newlines or whitespace for unknown reasons.
195
+ string detail = result . ToolTip ? . Trim ( ) ;
183
196
184
- // Force the client to maintain the sort order in which the original completion results
185
- // were returned. We just need to make sure the default order also be the
186
- // lexicographical order which we do by prefixing the ListItemText with a leading 0's
187
- // four digit index.
188
- string sortText = $ "{ sortIndex : D4} { completion . ListItemText } ";
197
+ CompletionItem item = new ( )
198
+ {
199
+ Label = result . ListItemText ,
200
+ Detail = result . ListItemText . Equals ( detail , StringComparison . CurrentCulture )
201
+ ? string . Empty : detail , // Don't repeat label.
202
+ // Retain PowerShell's sort order with the given index.
203
+ SortText = $ "{ sortIndex : D4} { result . ListItemText } ",
204
+ FilterText = result . CompletionText ,
205
+ TextEdit = textEdit // Used instead of InsertText.
206
+ } ;
189
207
190
- switch ( completion . ResultType )
208
+ return result . ResultType switch
191
209
{
192
- case CompletionResultType . Command :
193
- kind = CompletionItemKind . Function ;
194
- break ;
195
- case CompletionResultType . History :
196
- kind = CompletionItemKind . Reference ;
197
- break ;
198
- case CompletionResultType . Keyword :
199
- case CompletionResultType . DynamicKeyword :
200
- kind = CompletionItemKind . Keyword ;
201
- break ;
202
- case CompletionResultType . Method :
203
- kind = CompletionItemKind . Method ;
204
- break ;
205
- case CompletionResultType . Namespace :
206
- kind = CompletionItemKind . Module ;
207
- break ;
208
- case CompletionResultType . ParameterName :
209
- kind = CompletionItemKind . Variable ;
210
- // Look for type encoded in the tooltip for parameters and variables.
211
- // Display PowerShell type names in [] to be consistent with PowerShell syntax
212
- // and how the debugger displays type names.
213
- MatchCollection matches = _typeRegex . Matches ( toolTipText ) ;
214
- if ( ( matches . Count > 0 ) && ( matches [ 0 ] . Groups . Count > 1 ) )
210
+ CompletionResultType . Text => item with { Kind = CompletionItemKind . Text } ,
211
+ CompletionResultType . History => item with { Kind = CompletionItemKind . Reference } ,
212
+ CompletionResultType . Command => item with { Kind = CompletionItemKind . Function } ,
213
+ CompletionResultType . ProviderItem => item with { Kind = CompletionItemKind . File } ,
214
+ CompletionResultType . ProviderContainer => IsSnippet ( result . CompletionText , out string snippet )
215
+ ? item with
215
216
{
216
- toolTipText = matches [ 0 ] . Groups [ 1 ] . Value ;
217
+ Kind = CompletionItemKind . Folder ,
218
+ InsertTextFormat = InsertTextFormat . Snippet ,
219
+ TextEdit = textEdit with { NewText = snippet }
217
220
}
221
+ : item with { Kind = CompletionItemKind . Folder } ,
222
+ CompletionResultType . Property => item with { Kind = CompletionItemKind . Property } ,
223
+ CompletionResultType . Method => item with { Kind = CompletionItemKind . Method } ,
224
+ CompletionResultType . ParameterName => ExtractTypeFromToolTip ( detail , out string type )
225
+ ? item with { Kind = CompletionItemKind . Variable , Detail = type }
218
226
// The comparison operators (-eq, -not, -gt, etc) unfortunately come across as
219
227
// ParameterName types but they don't have a type associated to them, so we can
220
- // deduce its an operator.
221
- else
222
- {
223
- kind = CompletionItemKind . Operator ;
224
- }
225
- break ;
226
- case CompletionResultType . ParameterValue :
227
- kind = CompletionItemKind . Value ;
228
- break ;
229
- case CompletionResultType . Property :
230
- kind = CompletionItemKind . Property ;
231
- break ;
232
- case CompletionResultType . ProviderContainer :
233
- kind = CompletionItemKind . Folder ;
234
- // Insert a final "tab stop" as identified by $0 in the snippet provided for
235
- // completion. For folder paths, we take the path returned by PowerShell e.g.
236
- // 'C:\Program Files' and insert the tab stop marker before the closing quote
237
- // char e.g. 'C:\Program Files$0'. This causes the editing cursor to be placed
238
- // *before* the final quote after completion, which makes subsequent path
239
- // completions work. See this part of the LSP spec for details:
240
- // https://microsoft.github.io/language-server-protocol/specification#textDocument_completion
241
-
242
- // Since we want to use a "tab stop" we need to escape a few things for Textmate
243
- // to render properly.
244
- if ( EndsWithQuote ( completionText ) )
245
- {
246
- StringBuilder sb = new StringBuilder ( completionText )
247
- . Replace ( @"\" , @"\\" )
248
- . Replace ( @"}" , @"\}" )
249
- . Replace ( @"$" , @"\$" ) ;
250
- completionText = sb . Insert ( sb . Length - 1 , "$0" ) . ToString ( ) ;
251
- insertTextFormat = InsertTextFormat . Snippet ;
252
- }
253
- break ;
254
- case CompletionResultType . ProviderItem :
255
- kind = CompletionItemKind . File ;
256
- break ;
257
- case CompletionResultType . Text :
258
- kind = CompletionItemKind . Text ;
259
- break ;
260
- case CompletionResultType . Type :
261
- kind = CompletionItemKind . TypeParameter ;
228
+ // deduce it is an operator.
229
+ : item with { Kind = CompletionItemKind . Operator } ,
230
+ CompletionResultType . ParameterValue => item with { Kind = CompletionItemKind . Value } ,
231
+ CompletionResultType . Variable => ExtractTypeFromToolTip ( detail , out string type )
232
+ ? item with { Kind = CompletionItemKind . Variable , Detail = type }
233
+ : item with { Kind = CompletionItemKind . Variable } ,
234
+ CompletionResultType . Namespace => item with { Kind = CompletionItemKind . Module } ,
235
+ CompletionResultType . Type => detail . StartsWith ( "Class " , StringComparison . CurrentCulture )
262
236
// Custom classes come through as types but the PowerShell completion tooltip
263
237
// will start with "Class ", so we can more accurately display its icon.
264
- if ( toolTipText . StartsWith ( "Class " , StringComparison . Ordinal ) )
265
- {
266
- kind = CompletionItemKind . Class ;
267
- }
268
- break ;
269
- case CompletionResultType . Variable :
270
- kind = CompletionItemKind . Variable ;
271
- // Look for type encoded in the tooltip for parameters and variables.
272
- // Display PowerShell type names in [] to be consistent with PowerShell syntax
273
- // and how the debugger displays type names.
274
- matches = _typeRegex . Matches ( toolTipText ) ;
275
- if ( ( matches . Count > 0 ) && ( matches [ 0 ] . Groups . Count > 1 ) )
276
- {
277
- toolTipText = matches [ 0 ] . Groups [ 1 ] . Value ;
278
- }
279
- break ;
280
- default :
281
- throw new ArgumentOutOfRangeException ( nameof ( completion ) ) ;
282
- }
238
+ ? item with { Kind = CompletionItemKind . Class }
239
+ : item with { Kind = CompletionItemKind . TypeParameter } ,
240
+ CompletionResultType . Keyword or CompletionResultType . DynamicKeyword =>
241
+ item with { Kind = CompletionItemKind . Keyword } ,
242
+ _ => throw new ArgumentOutOfRangeException ( nameof ( result ) )
243
+ } ;
244
+ }
283
245
284
- // Don't display tooltip if it is the same as the ListItemText.
285
- if ( completion . ListItemText . Equals ( toolTipText , StringComparison . OrdinalIgnoreCase ) )
246
+ /// <summary>
247
+ /// Look for type encoded in the tooltip for parameters and variables. Display PowerShell
248
+ /// type names in [] to be consistent with PowerShell syntax and how the debugger displays
249
+ /// type names.
250
+ /// </summary>
251
+ /// <param name="toolTipText"></param>
252
+ /// <param name="type"></param>
253
+ /// <returns>Whether or not the type was found.</returns>
254
+ private static bool ExtractTypeFromToolTip ( string toolTipText , out string type )
255
+ {
256
+ Regex _typeRegex = new ( @"^(\[.+\])" ) ;
257
+ MatchCollection matches = _typeRegex . Matches ( toolTipText ) ;
258
+ type = string . Empty ;
259
+ if ( ( matches . Count > 0 ) && ( matches [ 0 ] . Groups . Count > 1 ) )
286
260
{
287
- toolTipText = string . Empty ;
261
+ type = matches [ 0 ] . Groups [ 1 ] . Value ;
262
+ return true ;
288
263
}
289
-
290
- Validate . IsNotNull ( nameof ( CompletionItemKind ) , kind ) ;
291
-
292
- // TODO: We used to extract the symbol type from the tooltip using a regex, but it
293
- // wasn't actually used.
294
- return new CompletionItem
295
- {
296
- Kind = kind ,
297
- TextEdit = new TextEdit
298
- {
299
- NewText = completionText ,
300
- Range = new Range
301
- {
302
- Start = new Position
303
- {
304
- Line = completionRange . Start . Line - 1 ,
305
- Character = completionRange . Start . Column - 1
306
- } ,
307
- End = new Position
308
- {
309
- Line = completionRange . End . Line - 1 ,
310
- Character = completionRange . End . Column - 1
311
- }
312
- }
313
- } ,
314
- InsertTextFormat = insertTextFormat ,
315
- InsertText = completionText ,
316
- FilterText = completion . CompletionText ,
317
- SortText = sortText ,
318
- // TODO: Documentation
319
- Detail = toolTipText ,
320
- Label = completion . ListItemText ,
321
- // TODO: Command
322
- } ;
264
+ return false ;
323
265
}
324
266
325
- private static bool EndsWithQuote ( string text )
267
+ /// <summary>
268
+ /// Insert a final "tab stop" as identified by $0 in the snippet provided for completion.
269
+ /// For folder paths, we take the path returned by PowerShell e.g. 'C:\Program Files' and
270
+ /// insert the tab stop marker before the closing quote char e.g. 'C:\Program Files$0'. This
271
+ /// causes the editing cursor to be placed *before* the final quote after completion, which
272
+ /// makes subsequent path completions work. See this part of the LSP spec for details:
273
+ /// https://microsoft.github.io/language-server-protocol/specification#textDocument_completion
274
+ /// </summary>
275
+ /// <param name="completionText"></param>
276
+ /// <param name="snippet"></param>
277
+ /// <returns>
278
+ /// Whether or not the completion ended with a quote and so was a snippet.
279
+ /// </returns>
280
+ private static bool IsSnippet ( string completionText , out string snippet )
326
281
{
327
- return ! string . IsNullOrEmpty ( text ) && text [ text . Length - 1 ] is '"' or '\' ' ;
282
+ snippet = string . Empty ;
283
+ if ( ! string . IsNullOrEmpty ( completionText )
284
+ && completionText [ completionText . Length - 1 ] is '"' or '\' ' )
285
+ {
286
+ // Since we want to use a "tab stop" we need to escape a few things.
287
+ StringBuilder sb = new StringBuilder ( completionText )
288
+ . Replace ( @"\" , @"\\" )
289
+ . Replace ( @"}" , @"\}" )
290
+ . Replace ( @"$" , @"\$" ) ;
291
+ snippet = sb . Insert ( sb . Length - 1 , "$0" ) . ToString ( ) ;
292
+ return true ;
293
+ }
294
+ return false ;
328
295
}
329
296
}
330
297
}
0 commit comments