99using Microsoft . PowerShell . EditorServices . Protocol . MessageProtocol . Channel ;
1010using Microsoft . PowerShell . EditorServices . Session ;
1111using Microsoft . PowerShell . EditorServices . Utility ;
12+ using Newtonsoft . Json . Linq ;
1213using System ;
1314using System . Collections . Generic ;
1415using System . IO ;
@@ -30,6 +31,8 @@ public class LanguageServer : LanguageServerBase
3031 private OutputDebouncer outputDebouncer ;
3132 private LanguageServerEditorOperations editorOperations ;
3233 private LanguageServerSettings currentSettings = new LanguageServerSettings ( ) ;
34+ private Dictionary < string , Dictionary < string , MarkerCorrection > > codeActionsPerFile =
35+ new Dictionary < string , Dictionary < string , MarkerCorrection > > ( ) ;
3336
3437 /// <param name="hostDetails">
3538 /// Provides details about the host application.
@@ -92,6 +95,7 @@ protected override void Initialize()
9295 this . SetRequestHandler ( HoverRequest . Type , this . HandleHoverRequest ) ;
9396 this . SetRequestHandler ( DocumentSymbolRequest . Type , this . HandleDocumentSymbolRequest ) ;
9497 this . SetRequestHandler ( WorkspaceSymbolRequest . Type , this . HandleWorkspaceSymbolRequest ) ;
98+ this . SetRequestHandler ( CodeActionRequest . Type , this . HandleCodeActionRequest ) ;
9599
96100 this . SetRequestHandler ( ShowOnlineHelpRequest . Type , this . HandleShowOnlineHelpRequest ) ;
97101 this . SetRequestHandler ( ExpandAliasRequest . Type , this . HandleExpandAliasRequest ) ;
@@ -146,6 +150,7 @@ await requestContext.SendResult(
146150 DocumentSymbolProvider = true ,
147151 WorkspaceSymbolProvider = true ,
148152 HoverProvider = true ,
153+ CodeActionProvider = true ,
149154 CompletionProvider = new CompletionOptions
150155 {
151156 ResolveProvider = true ,
@@ -226,17 +231,17 @@ function __Expand-Alias {
226231 param($targetScript)
227232
228233 [ref]$errors=$null
229-
230- $tokens = [System.Management.Automation.PsParser]::Tokenize($targetScript, $errors).Where({$_.type -eq 'command'}) |
234+
235+ $tokens = [System.Management.Automation.PsParser]::Tokenize($targetScript, $errors).Where({$_.type -eq 'command'}) |
231236 Sort Start -Descending
232237
233238 foreach ($token in $tokens) {
234239 $definition=(Get-Command ('`'+$token.Content) -CommandType Alias -ErrorAction SilentlyContinue).Definition
235240
236- if($definition) {
241+ if($definition) {
237242 $lhs=$targetScript.Substring(0, $token.Start)
238243 $rhs=$targetScript.Substring($token.Start + $token.Length)
239-
244+
240245 $targetScript=$lhs + $definition + $rhs
241246 }
242247 }
@@ -346,13 +351,13 @@ protected async Task HandleDidChangeConfigurationNotification(
346351 EventContext eventContext )
347352 {
348353 bool oldLoadProfiles = this . currentSettings . EnableProfileLoading ;
349- bool oldScriptAnalysisEnabled =
354+ bool oldScriptAnalysisEnabled =
350355 this . currentSettings . ScriptAnalysis . Enable . HasValue ;
351356 string oldScriptAnalysisSettingsPath =
352357 this . currentSettings . ScriptAnalysis . SettingsPath ;
353358
354359 this . currentSettings . Update (
355- configChangeParams . Settings . Powershell ,
360+ configChangeParams . Settings . Powershell ,
356361 this . editorSession . Workspace . WorkspacePath ) ;
357362
358363 if ( ! this . profilesLoaded &&
@@ -386,6 +391,7 @@ protected async Task HandleDidChangeConfigurationNotification(
386391 await PublishScriptDiagnostics (
387392 scriptFile ,
388393 emptyAnalysisDiagnostics ,
394+ this . codeActionsPerFile ,
389395 eventContext ) ;
390396 }
391397 }
@@ -815,6 +821,35 @@ private bool IsQueryMatch(string query, string symbolName)
815821 return symbolName . IndexOf ( query , StringComparison . OrdinalIgnoreCase ) >= 0 ;
816822 }
817823
824+ protected async Task HandleCodeActionRequest (
825+ CodeActionRequest codeActionParams ,
826+ RequestContext < CodeActionCommand [ ] > requestContext )
827+ {
828+ MarkerCorrection correction = null ;
829+ Dictionary < string , MarkerCorrection > markerIndex = null ;
830+ List < CodeActionCommand > codeActionCommands = new List < CodeActionCommand > ( ) ;
831+
832+ if ( this . codeActionsPerFile . TryGetValue ( codeActionParams . TextDocument . Uri , out markerIndex ) )
833+ {
834+ foreach ( var diagnostic in codeActionParams . Context . Diagnostics )
835+ {
836+ if ( markerIndex . TryGetValue ( diagnostic . Code , out correction ) )
837+ {
838+ codeActionCommands . Add (
839+ new CodeActionCommand
840+ {
841+ Title = correction . Name ,
842+ Command = "PowerShell.ApplyCodeActionEdits" ,
843+ Arguments = JArray . FromObject ( correction . Edits )
844+ } ) ;
845+ }
846+ }
847+ }
848+
849+ await requestContext . SendResult (
850+ codeActionCommands . ToArray ( ) ) ;
851+ }
852+
818853 protected Task HandleEvaluateRequest (
819854 DebugAdapterMessages . EvaluateRequestArguments evaluateParams ,
820855 RequestContext < DebugAdapterMessages . EvaluateResponseBody > requestContext )
@@ -974,6 +1009,7 @@ private Task RunScriptDiagnostics(
9741009 DelayThenInvokeDiagnostics (
9751010 750 ,
9761011 filesToAnalyze ,
1012+ this . codeActionsPerFile ,
9771013 editorSession ,
9781014 eventContext ,
9791015 existingRequestCancellation . Token ) ,
@@ -987,6 +1023,7 @@ private Task RunScriptDiagnostics(
9871023 private static async Task DelayThenInvokeDiagnostics (
9881024 int delayMilliseconds ,
9891025 ScriptFile [ ] filesToAnalyze ,
1026+ Dictionary < string , Dictionary < string , MarkerCorrection > > correctionIndex ,
9901027 EditorSession editorSession ,
9911028 EventContext eventContext ,
9921029 CancellationToken cancellationToken )
@@ -1034,28 +1071,45 @@ private static async Task DelayThenInvokeDiagnostics(
10341071 await PublishScriptDiagnostics (
10351072 scriptFile ,
10361073 semanticMarkers ,
1074+ correctionIndex ,
10371075 eventContext ) ;
10381076 }
10391077 }
10401078
10411079 private static async Task PublishScriptDiagnostics (
10421080 ScriptFile scriptFile ,
10431081 ScriptFileMarker [ ] semanticMarkers ,
1082+ Dictionary < string , Dictionary < string , MarkerCorrection > > correctionIndex ,
10441083 EventContext eventContext )
10451084 {
1046- var allMarkers = scriptFile . SyntaxMarkers . Concat ( semanticMarkers ) ;
1085+ List < Diagnostic > diagnostics = new List < Diagnostic > ( ) ;
1086+
1087+ // Hold on to any corrections that may need to be applied later
1088+ Dictionary < string , MarkerCorrection > fileCorrections =
1089+ new Dictionary < string , MarkerCorrection > ( ) ;
1090+
1091+ foreach ( var marker in scriptFile . SyntaxMarkers . Concat ( semanticMarkers ) )
1092+ {
1093+ // Does the marker contain a correction?
1094+ Diagnostic markerDiagnostic = GetDiagnosticFromMarker ( marker ) ;
1095+ if ( marker . Correction != null )
1096+ {
1097+ fileCorrections . Add ( markerDiagnostic . Code , marker . Correction ) ;
1098+ }
1099+
1100+ diagnostics . Add ( markerDiagnostic ) ;
1101+ }
1102+
1103+ correctionIndex [ scriptFile . ClientFilePath ] = fileCorrections ;
10471104
1048- // Always send syntax and semantic errors. We want to
1105+ // Always send syntax and semantic errors. We want to
10491106 // make sure no out-of-date markers are being displayed.
10501107 await eventContext . SendEvent (
10511108 PublishDiagnosticsNotification . Type ,
10521109 new PublishDiagnosticsNotification
10531110 {
10541111 Uri = scriptFile . ClientFilePath ,
1055- Diagnostics =
1056- allMarkers
1057- . Select ( GetDiagnosticFromMarker )
1058- . ToArray ( )
1112+ Diagnostics = diagnostics . ToArray ( )
10591113 } ) ;
10601114 }
10611115
@@ -1065,6 +1119,7 @@ private static Diagnostic GetDiagnosticFromMarker(ScriptFileMarker scriptFileMar
10651119 {
10661120 Severity = MapDiagnosticSeverity ( scriptFileMarker . Level ) ,
10671121 Message = scriptFileMarker . Message ,
1122+ Code = Guid . NewGuid ( ) . ToString ( ) ,
10681123 Range = new Range
10691124 {
10701125 // TODO: What offsets should I use?
@@ -1145,7 +1200,7 @@ private static CompletionItem CreateCompletionItem(
11451200 if ( ! completionDetails . ListItemText . Equals (
11461201 completionDetails . ToolTipText ,
11471202 StringComparison . OrdinalIgnoreCase ) &&
1148- ! Regex . IsMatch ( completionDetails . ToolTipText ,
1203+ ! Regex . IsMatch ( completionDetails . ToolTipText ,
11491204 @"^\s*" + escapedToolTipText + @"\s+\[" ) )
11501205 {
11511206 detailString = completionDetails . ToolTipText ;
@@ -1158,7 +1213,7 @@ private static CompletionItem CreateCompletionItem(
11581213 // default (with common params at the end). We just need to make sure the default
11591214 // order also be the lexicographical order which we do by prefixig the ListItemText
11601215 // with a leading 0's four digit index. This would not sort correctly for a list
1161- // > 999 parameters but surely we won't have so many items in the "parameter name"
1216+ // > 999 parameters but surely we won't have so many items in the "parameter name"
11621217 // completion list. Technically we don't need the ListItemText at all but it may come
11631218 // in handy during debug.
11641219 var sortText = ( completionDetails . CompletionType == CompletionType . ParameterName )
0 commit comments