@@ -24,17 +24,15 @@ namespace Microsoft.PowerShell.EditorServices.Handlers
2424 // TODO: Use ABCs.
2525 internal class PsesCompletionHandler : ICompletionHandler , ICompletionResolveHandler
2626 {
27- const int DefaultWaitTimeoutMilliseconds = 5000 ;
27+ private const int DefaultWaitTimeoutMilliseconds = 5000 ;
2828 private readonly ILogger _logger ;
2929 private readonly IRunspaceContext _runspaceContext ;
3030 private readonly IInternalPowerShellExecutionService _executionService ;
3131 private readonly WorkspaceService _workspaceService ;
32- private CompletionResults _mostRecentCompletions ;
33- private int _mostRecentRequestLine ;
34- private int _mostRecentRequestOffest ;
35- private string _mostRecentRequestFile ;
3632 private CompletionCapability _capability ;
3733 private readonly Guid _id = Guid . NewGuid ( ) ;
34+ private static readonly Regex _regex = new ( @"^(\[.+\])" ) ;
35+
3836 Guid ICanBeIdentifiedHandler . Id => _id ;
3937
4038 public PsesCompletionHandler (
@@ -49,7 +47,7 @@ public PsesCompletionHandler(
4947 _workspaceService = workspaceService ;
5048 }
5149
52- public CompletionRegistrationOptions GetRegistrationOptions ( CompletionCapability capability , ClientCapabilities clientCapabilities ) => new CompletionRegistrationOptions
50+ public CompletionRegistrationOptions GetRegistrationOptions ( CompletionCapability capability , ClientCapabilities clientCapabilities ) => new ( )
5351 {
5452 DocumentSelector = LspUtils . PowerShellDocumentSelector ,
5553 ResolveProvider = true ,
@@ -69,13 +67,9 @@ public async Task<CompletionList> Handle(CompletionParams request, CancellationT
6967 return Array . Empty < CompletionItem > ( ) ;
7068 }
7169
72- CompletionResults completionResults =
73- await GetCompletionsInFileAsync (
74- scriptFile ,
75- cursorLine ,
76- cursorColumn ) . ConfigureAwait ( false ) ;
70+ CompletionResults completionResults = await GetCompletionsInFileAsync ( scriptFile , cursorLine , cursorColumn ) . ConfigureAwait ( false ) ;
7771
78- if ( completionResults == null )
72+ if ( completionResults is null )
7973 {
8074 return Array . Empty < CompletionItem > ( ) ;
8175 }
@@ -110,13 +104,9 @@ public async Task<CompletionItem> Handle(CompletionItem request, CancellationTok
110104 }
111105
112106 // 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 ) ;
118108
119- if ( commandInfo != null )
109+ if ( commandInfo is not null )
120110 {
121111 request = request with
122112 {
@@ -158,13 +148,10 @@ public async Task<CompletionResults> GetCompletionsInFileAsync(
158148
159149 // Get the offset at the specified position. This method
160150 // will also validate the given position.
161- int fileOffset =
162- scriptFile . GetOffsetAtPosition (
163- lineNumber ,
164- columnNumber ) ;
151+ int fileOffset = scriptFile . GetOffsetAtPosition ( lineNumber , columnNumber ) ;
165152
166153 CommandCompletion commandCompletion = null ;
167- using ( var cts = new CancellationTokenSource ( DefaultWaitTimeoutMilliseconds ) )
154+ using ( CancellationTokenSource cts = new ( DefaultWaitTimeoutMilliseconds ) )
168155 {
169156 commandCompletion =
170157 await AstOperations . GetCompletionsAsync (
@@ -176,33 +163,20 @@ await AstOperations.GetCompletionsAsync(
176163 cts . Token ) . ConfigureAwait ( false ) ;
177164 }
178165
179- if ( commandCompletion == null )
166+ if ( commandCompletion is null )
180167 {
181168 return new CompletionResults ( ) ;
182169 }
183170
184171 try
185172 {
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 ) ;
198174 }
199175 catch ( ArgumentException e )
200176 {
201177 // Bad completion results could return an invalid
202178 // 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 ( ) } ") ;
206180 return new CompletionResults ( ) ;
207181 }
208182 }
@@ -212,57 +186,65 @@ private static CompletionItem CreateCompletionItem(
212186 BufferRange completionRange ,
213187 int sortIndex )
214188 {
215- string detailString = null ;
216- string documentationString = null ;
189+ string toolTipText = completionDetails . ToolTipText ;
217190 string completionText = completionDetails . CompletionText ;
191+ CompletionItemKind kind = MapCompletionKind ( completionDetails . CompletionType ) ;
218192 InsertTextFormat insertTextFormat = InsertTextFormat . PlainText ;
219193
220194 switch ( completionDetails . CompletionType )
221195 {
222- case CompletionType . Type :
223196 case CompletionType . Namespace :
224197 case CompletionType . ParameterValue :
225198 case CompletionType . Method :
226199 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 :
229204 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 ;
230215 case CompletionType . ParameterName :
231216 // Look for type encoded in the tooltip for parameters and variables.
232217 // Display PowerShell type names in [] to be consistent with PowerShell syntax
233218 // and how the debugger displays type names.
234- var matches = Regex . Matches ( completionDetails . ToolTipText , @"^(\[.+\])" ) ;
219+ MatchCollection matches = _regex . Matches ( toolTipText ) ;
235220 if ( ( matches . Count > 0 ) && ( matches [ 0 ] . Groups . Count > 1 ) )
236221 {
237- detailString = matches [ 0 ] . Groups [ 1 ] . Value ;
222+ toolTipText = matches [ 0 ] . Groups [ 1 ] . Value ;
238223 }
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
242228 {
243- detailString = completionDetails . ToolTipText ;
229+ kind = CompletionItemKind . Operator ;
244230 }
245231 break ;
246232 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 ) )
250236 {
251237 // 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+\[" ) )
261244 {
262- detailString = completionDetails . ToolTipText ;
245+ toolTipText = string . Empty ;
263246 }
264247 }
265-
266248 break ;
267249 case CompletionType . Folder :
268250 // Insert a final "tab stop" as identified by $0 in the snippet provided for completion.
@@ -275,14 +257,13 @@ private static CompletionItem CreateCompletionItem(
275257 // Since we want to use a "tab stop" we need to escape a few things for Textmate to render properly.
276258 if ( EndsWithQuote ( completionText ) )
277259 {
278- var sb = new StringBuilder ( completionDetails . CompletionText )
260+ StringBuilder sb = new StringBuilder ( completionText )
279261 . Replace ( @"\" , @"\\" )
280262 . Replace ( @"}" , @"\}" )
281263 . Replace ( @"$" , @"\$" ) ;
282264 completionText = sb . Insert ( sb . Length - 1 , "$0" ) . ToString ( ) ;
283265 insertTextFormat = InsertTextFormat . Snippet ;
284266 }
285-
286267 break ;
287268 }
288269
@@ -291,16 +272,16 @@ private static CompletionItem CreateCompletionItem(
291272 // make sure the default order also be the lexicographical order
292273 // which we do by prefixing the ListItemText with a leading 0's
293274 // four digit index.
294- var sortText = $ "{ sortIndex : D4} { completionDetails . ListItemText } ";
275+ string sortText = $ "{ sortIndex : D4} { completionDetails . ListItemText } ";
295276
296277 return new CompletionItem
297278 {
298279 InsertText = completionText ,
299280 InsertTextFormat = insertTextFormat ,
300281 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.
304285 SortText = sortText ,
305286 FilterText = completionDetails . CompletionText ,
306287 TextEdit = new TextEdit
@@ -323,43 +304,33 @@ private static CompletionItem CreateCompletionItem(
323304 } ;
324305 }
325306
307+ // TODO: Unwrap this, it's confusing as it doesn't cover all cases and we conditionally
308+ // override it when it's inaccurate.
326309 private static CompletionItemKind MapCompletionKind ( CompletionType completionType )
327310 {
328- switch ( completionType )
311+ return completionType switch
329312 {
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+ } ;
352329 }
353330
354331 private static bool EndsWithQuote ( string text )
355332 {
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 '\' ' ;
363334 }
364335 }
365336}
0 commit comments