public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context) { // This provider is exported for all workspaces - so limit it to just our workspace. var(document, span, cancellationToken) = context; if (document.Project.Solution.Workspace.Kind != WorkspaceKind.AnyCodeRoslynWorkspace) { return; } var lspClient = _roslynLspClientServiceFactory.ActiveLanguageServerClient; if (lspClient == null) { return; } var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); var diagnostics = await _diagnosticAnalyzerService.GetDiagnosticsForSpanAsync(document, span, cancellationToken : cancellationToken).ConfigureAwait(false); var diagnostic = diagnostics?.FirstOrDefault(); if (diagnostic != null) { span = diagnostic.TextSpan; } var codeActionParams = new LSP.CodeActionParams { TextDocument = ProtocolConversions.DocumentToTextDocumentIdentifier(document), Range = ProtocolConversions.TextSpanToRange(span, text) }; var commands = await lspClient.RequestAsync(LSP.Methods.TextDocumentCodeAction.ToLSRequest(), codeActionParams, cancellationToken).ConfigureAwait(false); if (commands == null) { return; } foreach (var command in commands) { if (LanguageServicesUtils.TryParseJson(command, out LSP.Command lspCommand)) { // The command can either wrap a Command or a CodeAction. // If a Command, leave it unchanged; we want to dispatch it to the host to execute. // If a CodeAction, unwrap the CodeAction so the guest can run it locally. var commandArguments = lspCommand.Arguments.Single(); if (LanguageServicesUtils.TryParseJson(commandArguments, out LSP.CodeAction lspCodeAction)) { context.RegisterRefactoring(new RoslynRemoteCodeAction(document, lspCodeAction.Command, lspCodeAction.Edit, lspCodeAction.Title, lspClient)); } else { context.RegisterRefactoring(new RoslynRemoteCodeAction(document, lspCommand, lspCommand?.Title, lspClient)); } } } }
public async Task TestCodeActionHasCorrectDiagnostics() { var markup = @"class A { void M() { {|caret:|}Task.Delay(1); } }"; using var testLspServer = await CreateTestLspServerAsync(markup); testLspServer.InitializeDiagnostics(BackgroundAnalysisScope.ActiveFile, DiagnosticMode.Default, new TestAnalyzerReferenceByLanguage(DiagnosticExtensions.GetCompilerDiagnosticAnalyzersMap())); await testLspServer.WaitForDiagnosticsAsync(); var caret = testLspServer.GetLocations("caret").Single(); var codeActionParams = new LSP.CodeActionParams { TextDocument = CreateTextDocumentIdentifier(caret.Uri), Range = caret.Range, Context = new LSP.CodeActionContext { Diagnostics = new[] { new LSP.Diagnostic { Code = AddImportDiagnosticIds.CS0103 }, new LSP.Diagnostic { Code = "SomeCode" } } } }; var results = await RunGetCodeActionsAsync(testLspServer, codeActionParams); var addImport = results.FirstOrDefault(r => r.Title.Contains($"using System.Threading.Tasks")); Assert.Equal(1, addImport.Diagnostics.Length); Assert.Equal(AddImportDiagnosticIds.CS0103, addImport.Diagnostics.Single().Code.Value); }
public async Task TestCodeActionHasCorrectDiagnostics() { var markup = @"class A { void M() { {|caret:|}Task.Delay(1); } }"; using var testLspServer = await CreateTestLspServerAsync(markup); var caret = testLspServer.GetLocations("caret").Single(); var codeActionParams = new LSP.CodeActionParams { TextDocument = CreateTextDocumentIdentifier(caret.Uri), Range = caret.Range, Context = new LSP.CodeActionContext { Diagnostics = new[] { new LSP.Diagnostic { Code = AddImportDiagnosticIds.CS0103 }, new LSP.Diagnostic { Code = "SomeCode" } } } }; var results = await RunGetCodeActionsAsync(testLspServer, codeActionParams); var addImport = results.FirstOrDefault(r => r.Title.Contains($"using System.Threading.Tasks")); Assert.Equal(1, addImport.Diagnostics.Length); Assert.Equal(AddImportDiagnosticIds.CS0103, addImport.Diagnostics.Single().Code.Value); }
/// <summary> /// Answers a code action request by returning the code actions for the document and range. /// https://microsoft.github.io/language-server-protocol/specification#textDocument_codeAction /// </summary> /// <param name="solution">the solution containing the document.</param> /// <param name="request">the document and range to get code actions for.</param> /// <param name="clientCapabilities">the client capabilities for the request.</param> /// <param name="cancellationToken">a cancellation token.</param> /// <returns>a list of commands representing code actions.</returns> public Task <object[]> GetCodeActionsAsync(Solution solution, LSP.CodeActionParams request, LSP.ClientCapabilities clientCapabilities, CancellationToken cancellationToken) => ExecuteRequestAsync <LSP.CodeActionParams, object[]>(LSP.Methods.TextDocumentCodeActionName, solution, request, clientCapabilities, cancellationToken);
public async Task <LSP.SumType <LSP.Command, LSP.CodeAction>[]> HandleRequestAsync(Solution solution, LSP.CodeActionParams request, LSP.ClientCapabilities clientCapabilities, string?clientName, CancellationToken cancellationToken) { var codeActions = await GetCodeActionsAsync(solution, request.TextDocument, request.Range, clientName, cancellationToken).ConfigureAwait(false); // Filter out code actions with options since they'll show dialogs and we can't remote the UI and the options. codeActions = codeActions.Where(c => !(c is CodeActionWithOptions)); var result = new ArrayBuilder <LSP.SumType <LSP.Command, LSP.CodeAction> >(); foreach (var codeAction in codeActions) { // Always return the Command instead of a precalculated set of workspace edits. // The edits will be calculated when the code action is either previewed or // invoked. // It's safe for the client to pass back the range/filename in the command to run // on the server because the client will always re-issue a get code actions request // before invoking a preview or running the command on the server. result.Add( new LSP.Command { CommandIdentifier = RunCodeActionCommandName, Title = codeAction.Title, Arguments = new object[] { new RunCodeActionParams { CodeActionParams = request, Title = codeAction.Title } } }); } return(result.ToArrayAndFree()); }