@@ -24,17 +24,15 @@ namespace Microsoft.PowerShell.EditorServices.Handlers
24
24
// TODO: Use ABCs.
25
25
internal class PsesCompletionHandler : ICompletionHandler , ICompletionResolveHandler
26
26
{
27
- const int DefaultWaitTimeoutMilliseconds = 5000 ;
27
+ private const int DefaultWaitTimeoutMilliseconds = 5000 ;
28
28
private readonly ILogger _logger ;
29
29
private readonly IRunspaceContext _runspaceContext ;
30
30
private readonly IInternalPowerShellExecutionService _executionService ;
31
31
private readonly WorkspaceService _workspaceService ;
32
- private CompletionResults _mostRecentCompletions ;
33
- private int _mostRecentRequestLine ;
34
- private int _mostRecentRequestOffest ;
35
- private string _mostRecentRequestFile ;
36
32
private CompletionCapability _capability ;
37
33
private readonly Guid _id = Guid . NewGuid ( ) ;
34
+ private static readonly Regex _regex = new ( @"^(\[.+\])" ) ;
35
+
38
36
Guid ICanBeIdentifiedHandler . Id => _id ;
39
37
40
38
public PsesCompletionHandler (
@@ -49,7 +47,7 @@ public PsesCompletionHandler(
49
47
_workspaceService = workspaceService ;
50
48
}
51
49
52
- public CompletionRegistrationOptions GetRegistrationOptions ( CompletionCapability capability , ClientCapabilities clientCapabilities ) => new CompletionRegistrationOptions
50
+ public CompletionRegistrationOptions GetRegistrationOptions ( CompletionCapability capability , ClientCapabilities clientCapabilities ) => new ( )
53
51
{
54
52
DocumentSelector = LspUtils . PowerShellDocumentSelector ,
55
53
ResolveProvider = true ,
@@ -69,13 +67,9 @@ public async Task<CompletionList> Handle(CompletionParams request, CancellationT
69
67
return Array . Empty < CompletionItem > ( ) ;
70
68
}
71
69
72
- CompletionResults completionResults =
73
- await GetCompletionsInFileAsync (
74
- scriptFile ,
75
- cursorLine ,
76
- cursorColumn ) . ConfigureAwait ( false ) ;
70
+ CompletionResults completionResults = await GetCompletionsInFileAsync ( scriptFile , cursorLine , cursorColumn ) . ConfigureAwait ( false ) ;
77
71
78
- if ( completionResults == null )
72
+ if ( completionResults is null )
79
73
{
80
74
return Array . Empty < CompletionItem > ( ) ;
81
75
}
@@ -110,13 +104,9 @@ public async Task<CompletionItem> Handle(CompletionItem request, CancellationTok
110
104
}
111
105
112
106
// Get the documentation for the function
113
- CommandInfo commandInfo =
114
- await CommandHelpers . GetCommandInfoAsync (
115
- request . Label ,
116
- _runspaceContext . CurrentRunspace ,
117
- _executionService ) . ConfigureAwait ( false ) ;
107
+ CommandInfo commandInfo = await CommandHelpers . GetCommandInfoAsync ( request . Label , _runspaceContext . CurrentRunspace , _executionService ) . ConfigureAwait ( false ) ;
118
108
119
- if ( commandInfo != null )
109
+ if ( commandInfo is not null )
120
110
{
121
111
request = request with
122
112
{
@@ -158,13 +148,10 @@ public async Task<CompletionResults> GetCompletionsInFileAsync(
158
148
159
149
// Get the offset at the specified position. This method
160
150
// will also validate the given position.
161
- int fileOffset =
162
- scriptFile . GetOffsetAtPosition (
163
- lineNumber ,
164
- columnNumber ) ;
151
+ int fileOffset = scriptFile . GetOffsetAtPosition ( lineNumber , columnNumber ) ;
165
152
166
153
CommandCompletion commandCompletion = null ;
167
- using ( var cts = new CancellationTokenSource ( DefaultWaitTimeoutMilliseconds ) )
154
+ using ( CancellationTokenSource cts = new ( DefaultWaitTimeoutMilliseconds ) )
168
155
{
169
156
commandCompletion =
170
157
await AstOperations . GetCompletionsAsync (
@@ -176,33 +163,20 @@ await AstOperations.GetCompletionsAsync(
176
163
cts . Token ) . ConfigureAwait ( false ) ;
177
164
}
178
165
179
- if ( commandCompletion == null )
166
+ if ( commandCompletion is null )
180
167
{
181
168
return new CompletionResults ( ) ;
182
169
}
183
170
184
171
try
185
172
{
186
- CompletionResults completionResults =
187
- CompletionResults . Create (
188
- scriptFile ,
189
- commandCompletion ) ;
190
-
191
- // save state of most recent completion
192
- _mostRecentCompletions = completionResults ;
193
- _mostRecentRequestFile = scriptFile . Id ;
194
- _mostRecentRequestLine = lineNumber ;
195
- _mostRecentRequestOffest = columnNumber ;
196
-
197
- return completionResults ;
173
+ return CompletionResults . Create ( scriptFile , commandCompletion ) ;
198
174
}
199
175
catch ( ArgumentException e )
200
176
{
201
177
// Bad completion results could return an invalid
202
178
// replacement range, catch that here
203
- _logger . LogError (
204
- $ "Caught exception while trying to create CompletionResults:\n \n { e . ToString ( ) } ") ;
205
-
179
+ _logger . LogError ( $ "Caught exception while trying to create CompletionResults:\n \n { e . ToString ( ) } ") ;
206
180
return new CompletionResults ( ) ;
207
181
}
208
182
}
@@ -212,57 +186,65 @@ private static CompletionItem CreateCompletionItem(
212
186
BufferRange completionRange ,
213
187
int sortIndex )
214
188
{
215
- string detailString = null ;
216
- string documentationString = null ;
189
+ string toolTipText = completionDetails . ToolTipText ;
217
190
string completionText = completionDetails . CompletionText ;
191
+ CompletionItemKind kind = MapCompletionKind ( completionDetails . CompletionType ) ;
218
192
InsertTextFormat insertTextFormat = InsertTextFormat . PlainText ;
219
193
220
194
switch ( completionDetails . CompletionType )
221
195
{
222
- case CompletionType . Type :
223
196
case CompletionType . Namespace :
224
197
case CompletionType . ParameterValue :
225
198
case CompletionType . Method :
226
199
case CompletionType . Property :
227
- detailString = completionDetails . ToolTipText ;
228
- break ;
200
+ case CompletionType . Keyword :
201
+ case CompletionType . File :
202
+ case CompletionType . History :
203
+ case CompletionType . Text :
229
204
case CompletionType . Variable :
205
+ case CompletionType . Unknown :
206
+ break ;
207
+ case CompletionType . Type :
208
+ // Custom classes come through as types but the PowerShell completion tooltip
209
+ // will start with "Class ", so we can more accurately display its icon.
210
+ if ( toolTipText . StartsWith ( "Class " , StringComparison . Ordinal ) )
211
+ {
212
+ kind = CompletionItemKind . Class ;
213
+ }
214
+ break ;
230
215
case CompletionType . ParameterName :
231
216
// Look for type encoded in the tooltip for parameters and variables.
232
217
// Display PowerShell type names in [] to be consistent with PowerShell syntax
233
218
// and how the debugger displays type names.
234
- var matches = Regex . Matches ( completionDetails . ToolTipText , @"^(\[.+\])" ) ;
219
+ MatchCollection matches = _regex . Matches ( toolTipText ) ;
235
220
if ( ( matches . Count > 0 ) && ( matches [ 0 ] . Groups . Count > 1 ) )
236
221
{
237
- detailString = matches [ 0 ] . Groups [ 1 ] . Value ;
222
+ toolTipText = matches [ 0 ] . Groups [ 1 ] . Value ;
238
223
}
239
- // The comparison operators (-eq, -not, -gt, etc) are unfortunately fall into ParameterName
240
- // but they don't have a type associated to them. This allows those tooltips to show up.
241
- else if ( ! string . IsNullOrEmpty ( completionDetails . ToolTipText ) )
224
+ // The comparison operators (-eq, -not, -gt, etc) unfortunately come across as
225
+ // ParameterName types but they don't have a type associated to them, so we can
226
+ // deduce its an operator.
227
+ else
242
228
{
243
- detailString = completionDetails . ToolTipText ;
229
+ kind = CompletionItemKind . Operator ;
244
230
}
245
231
break ;
246
232
case CompletionType . Command :
247
- // For Commands , let's extract the resolved command or the path for an exe
248
- // from the ToolTipText - if there is any ToolTipText .
249
- if ( completionDetails . ToolTipText != null )
233
+ // For commands , let's extract the resolved command or the path for an
234
+ // executable from the tooltip .
235
+ if ( ! string . IsNullOrEmpty ( toolTipText ) )
250
236
{
251
237
// Fix for #240 - notepad++.exe in tooltip text caused regex parser to throw.
252
- string escapedToolTipText = Regex . Escape ( completionDetails . ToolTipText ) ;
253
-
254
- // Don't display ToolTipText if it is the same as the ListItemText.
255
- // Reject command syntax ToolTipText - it's too much to display as a detailString.
256
- if ( ! completionDetails . ListItemText . Equals (
257
- completionDetails . ToolTipText ,
258
- StringComparison . OrdinalIgnoreCase ) &&
259
- ! Regex . IsMatch ( completionDetails . ToolTipText ,
260
- @"^\s*" + escapedToolTipText + @"\s+\[" ) )
238
+ string escapedToolTipText = Regex . Escape ( toolTipText ) ;
239
+
240
+ // Don't display tooltip if it is the same as the ListItemText. Don't
241
+ // display command syntax tooltip because it's too much.
242
+ if ( completionDetails . ListItemText . Equals ( toolTipText , StringComparison . OrdinalIgnoreCase )
243
+ || Regex . IsMatch ( toolTipText , @"^\s*" + escapedToolTipText + @"\s+\[" ) )
261
244
{
262
- detailString = completionDetails . ToolTipText ;
245
+ toolTipText = string . Empty ;
263
246
}
264
247
}
265
-
266
248
break ;
267
249
case CompletionType . Folder :
268
250
// Insert a final "tab stop" as identified by $0 in the snippet provided for completion.
@@ -275,14 +257,13 @@ private static CompletionItem CreateCompletionItem(
275
257
// Since we want to use a "tab stop" we need to escape a few things for Textmate to render properly.
276
258
if ( EndsWithQuote ( completionText ) )
277
259
{
278
- var sb = new StringBuilder ( completionDetails . CompletionText )
260
+ StringBuilder sb = new StringBuilder ( completionText )
279
261
. Replace ( @"\" , @"\\" )
280
262
. Replace ( @"}" , @"\}" )
281
263
. Replace ( @"$" , @"\$" ) ;
282
264
completionText = sb . Insert ( sb . Length - 1 , "$0" ) . ToString ( ) ;
283
265
insertTextFormat = InsertTextFormat . Snippet ;
284
266
}
285
-
286
267
break ;
287
268
}
288
269
@@ -291,16 +272,16 @@ private static CompletionItem CreateCompletionItem(
291
272
// make sure the default order also be the lexicographical order
292
273
// which we do by prefixing the ListItemText with a leading 0's
293
274
// four digit index.
294
- var sortText = $ "{ sortIndex : D4} { completionDetails . ListItemText } ";
275
+ string sortText = $ "{ sortIndex : D4} { completionDetails . ListItemText } ";
295
276
296
277
return new CompletionItem
297
278
{
298
279
InsertText = completionText ,
299
280
InsertTextFormat = insertTextFormat ,
300
281
Label = completionDetails . ListItemText ,
301
- Kind = MapCompletionKind ( completionDetails . CompletionType ) ,
302
- Detail = detailString ,
303
- Documentation = documentationString ,
282
+ Kind = kind ,
283
+ Detail = toolTipText ,
284
+ Documentation = string . Empty , // TODO: Fill this in agin.
304
285
SortText = sortText ,
305
286
FilterText = completionDetails . CompletionText ,
306
287
TextEdit = new TextEdit
@@ -323,43 +304,33 @@ private static CompletionItem CreateCompletionItem(
323
304
} ;
324
305
}
325
306
307
+ // TODO: Unwrap this, it's confusing as it doesn't cover all cases and we conditionally
308
+ // override it when it's inaccurate.
326
309
private static CompletionItemKind MapCompletionKind ( CompletionType completionType )
327
310
{
328
- switch ( completionType )
311
+ return completionType switch
329
312
{
330
- case CompletionType . Command :
331
- return CompletionItemKind . Function ;
332
-
333
- case CompletionType . Property :
334
- return CompletionItemKind . Property ;
335
-
336
- case CompletionType . Method :
337
- return CompletionItemKind . Method ;
338
-
339
- case CompletionType . Variable :
340
- case CompletionType . ParameterName :
341
- return CompletionItemKind . Variable ;
342
-
343
- case CompletionType . File :
344
- return CompletionItemKind . File ;
345
-
346
- case CompletionType . Folder :
347
- return CompletionItemKind . Folder ;
348
-
349
- default :
350
- return CompletionItemKind . Text ;
351
- }
313
+ CompletionType . Unknown => CompletionItemKind . Text ,
314
+ CompletionType . Command => CompletionItemKind . Function ,
315
+ CompletionType . Method => CompletionItemKind . Method ,
316
+ CompletionType . ParameterName => CompletionItemKind . Variable ,
317
+ CompletionType . ParameterValue => CompletionItemKind . Value ,
318
+ CompletionType . Property => CompletionItemKind . Property ,
319
+ CompletionType . Variable => CompletionItemKind . Variable ,
320
+ CompletionType . Namespace => CompletionItemKind . Module ,
321
+ CompletionType . Type => CompletionItemKind . TypeParameter ,
322
+ CompletionType . Keyword => CompletionItemKind . Keyword ,
323
+ CompletionType . File => CompletionItemKind . File ,
324
+ CompletionType . Folder => CompletionItemKind . Folder ,
325
+ CompletionType . History => CompletionItemKind . Reference ,
326
+ CompletionType . Text => CompletionItemKind . Text ,
327
+ _ => throw new ArgumentOutOfRangeException ( nameof ( completionType ) ) ,
328
+ } ;
352
329
}
353
330
354
331
private static bool EndsWithQuote ( string text )
355
332
{
356
- if ( string . IsNullOrEmpty ( text ) )
357
- {
358
- return false ;
359
- }
360
-
361
- char lastChar = text [ text . Length - 1 ] ;
362
- return lastChar == '"' || lastChar == '\' ' ;
333
+ return ! string . IsNullOrEmpty ( text ) && text [ text . Length - 1 ] is '"' or '\' ' ;
363
334
}
364
335
}
365
336
}
0 commit comments