未验证 提交 32149eb8 编写于 作者: D David 提交者: GitHub

Merge pull request #37331 from dibarbet/move_lsp_code_actions

Move lsp code actions
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
......@@ -12,6 +12,7 @@
using Microsoft.CodeAnalysis.LanguageServer;
using Microsoft.CodeAnalysis.LanguageServer.CustomProtocol;
using Microsoft.CodeAnalysis.LanguageServer.Handler;
using Microsoft.CodeAnalysis.LanguageServer.Handler.Commands;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.Composition;
......@@ -30,10 +31,13 @@ protected virtual ExportProvider GetExportProvider()
{
var requestHelperTypes = DesktopTestHelpers.GetAllTypesImplementingGivenInterface(
typeof(IRequestHandler).Assembly, typeof(IRequestHandler));
var executeCommandHandlerTypes = DesktopTestHelpers.GetAllTypesImplementingGivenInterface(
typeof(IExecuteWorkspaceCommandHandler).Assembly, typeof(IExecuteWorkspaceCommandHandler));
var exportProviderFactory = ExportProviderCache.GetOrCreateExportProviderFactory(
TestExportProvider.EntireAssemblyCatalogWithCSharpAndVisualBasic
.WithPart(typeof(LanguageServerProtocol))
.WithParts(requestHelperTypes));
.WithParts(requestHelperTypes)
.WithParts(executeCommandHandlerTypes));
return exportProviderFactory.CreateExportProvider();
}
......
......@@ -28,30 +28,28 @@ public CodeActionsHandler(ICodeFixService codeFixService, ICodeRefactoringServic
}
public async Task<object[]> HandleRequestAsync(Solution solution, LSP.CodeActionParams request,
LSP.ClientCapabilities clientCapabilities, CancellationToken cancellationToken)
LSP.ClientCapabilities clientCapabilities, CancellationToken cancellationToken, bool keepThreadContext = false)
{
var codeActions = await GetCodeActionsAsync(solution,
request.TextDocument.Uri,
request.Range,
cancellationToken).ConfigureAwait(false);
keepThreadContext,
cancellationToken).ConfigureAwait(keepThreadContext);
// 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 commands = new ArrayBuilder<LSP.Command>();
var clientSupportsWorkspaceEdits = true;
if (clientCapabilities?.Experimental is JObject clientCapabilitiesExtensions)
{
clientSupportsWorkspaceEdits = clientCapabilitiesExtensions.SelectToken("supportsWorkspaceEdits")?.Value<bool>() ?? clientSupportsWorkspaceEdits;
}
var result = new ArrayBuilder<object>();
foreach (var codeAction in codeActions)
{
object[] remoteCommandArguments;
// If we have a codeaction with a single applychangesoperation, we want to send the codeaction with the edits.
var operations = await codeAction.GetOperationsAsync(cancellationToken).ConfigureAwait(false);
var clientSupportsWorkspaceEdits = true;
if (clientCapabilities?.Experimental is JObject clientCapabilitiesExtensions)
{
clientSupportsWorkspaceEdits = clientCapabilitiesExtensions.SelectToken("supportsWorkspaceEdits")?.Value<bool>() ?? clientSupportsWorkspaceEdits;
}
var operations = await codeAction.GetOperationsAsync(cancellationToken).ConfigureAwait(keepThreadContext);
if (clientSupportsWorkspaceEdits && operations.Length == 1 && operations.First() is ApplyChangesOperation applyChangesOperation)
{
var workspaceEdit = new LSP.WorkspaceEdit { Changes = new Dictionary<string, LSP.TextEdit[]>() };
......@@ -62,8 +60,8 @@ public CodeActionsHandler(ICodeFixService codeFixService, ICodeRefactoringServic
{
var newDoc = applyChangesOperation.ChangedSolution.GetDocument(docId);
var oldDoc = solution.GetDocument(docId);
var oldText = await oldDoc.GetTextAsync(cancellationToken).ConfigureAwait(false);
var textChanges = await newDoc.GetTextChangesAsync(oldDoc).ConfigureAwait(false);
var oldText = await oldDoc.GetTextAsync(cancellationToken).ConfigureAwait(keepThreadContext);
var textChanges = await newDoc.GetTextChangesAsync(oldDoc).ConfigureAwait(keepThreadContext);
var edits = textChanges.Select(tc => new LSP.TextEdit
{
......@@ -74,7 +72,7 @@ public CodeActionsHandler(ICodeFixService codeFixService, ICodeRefactoringServic
workspaceEdit.Changes.Add(newDoc.FilePath, edits.ToArray());
}
remoteCommandArguments = new object[] { new LSP.CodeAction { Title = codeAction.Title, Edit = workspaceEdit } };
result.Add(new LSP.CodeAction { Title = codeAction.Title, Edit = workspaceEdit });
}
// Otherwise, send the original request to be executed on the host.
else
......@@ -82,8 +80,7 @@ public CodeActionsHandler(ICodeFixService codeFixService, ICodeRefactoringServic
// Note that we can pass through the params for this
// request (like range, filename) because between getcodeaction and runcodeaction there can be no
// changes on the IDE side (it will requery for codeactions if there are changes).
remoteCommandArguments = new object[]
{
result.Add(
new LSP.Command
{
CommandIdentifier = RunCodeActionCommandName,
......@@ -96,24 +93,11 @@ public CodeActionsHandler(ICodeFixService codeFixService, ICodeRefactoringServic
Title = codeAction.Title
}
}
}
};
});
}
// We need to return a command that is a generic wrapper that VS Code can execute.
// The argument to this wrapper will either be a RunCodeAction command which will carry
// enough information to run the command or a CodeAction with the edits.
var command = new LSP.Command
{
Title = codeAction.Title,
CommandIdentifier = $"{RemoteCommandNamePrefix}.{ProviderName}",
Arguments = remoteCommandArguments
};
commands.Add(command);
}
return commands.ToArrayAndFree();
return result.ToArrayAndFree();
}
}
}
......@@ -18,10 +18,7 @@ internal abstract class CodeActionsHandlerBase
private readonly ICodeFixService _codeFixService;
private readonly ICodeRefactoringService _codeRefactoringService;
// TODO - this should not be liveshare.
protected const string RemoteCommandNamePrefix = "_liveshare.remotecommand";
protected const string RunCodeActionCommandName = "Roslyn.RunCodeAction";
protected const string ProviderName = "Roslyn";
public CodeActionsHandlerBase(ICodeFixService codeFixService, ICodeRefactoringService codeRefactoringService)
{
......@@ -29,7 +26,7 @@ public CodeActionsHandlerBase(ICodeFixService codeFixService, ICodeRefactoringSe
_codeRefactoringService = codeRefactoringService ?? throw new ArgumentNullException(nameof(codeRefactoringService));
}
public async Task<IEnumerable<CodeAction>> GetCodeActionsAsync(Solution solution, Uri documentUri, LSP.Range selection, CancellationToken cancellationToken)
public async Task<IEnumerable<CodeAction>> GetCodeActionsAsync(Solution solution, Uri documentUri, LSP.Range selection, bool keepThreadContext, CancellationToken cancellationToken)
{
var document = solution.GetDocumentFromURI(documentUri);
if (document == null)
......@@ -37,11 +34,11 @@ public async Task<IEnumerable<CodeAction>> GetCodeActionsAsync(Solution solution
return ImmutableArray<CodeAction>.Empty;
}
var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);
var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(keepThreadContext);
var textSpan = ProtocolConversions.RangeToTextSpan(selection, text);
var codeFixCollections = await _codeFixService.GetFixesAsync(document, textSpan, true, cancellationToken).ConfigureAwait(false);
var codeRefactorings = await _codeRefactoringService.GetRefactoringsAsync(document, textSpan, cancellationToken).ConfigureAwait(false);
var codeFixCollections = await _codeFixService.GetFixesAsync(document, textSpan, true, cancellationToken).ConfigureAwait(keepThreadContext);
var codeRefactorings = await _codeRefactoringService.GetRefactoringsAsync(document, textSpan, cancellationToken).ConfigureAwait(keepThreadContext);
var codeActions = codeFixCollections.SelectMany(c => c.Fixes.Select(f => f.Action)).Concat(
codeRefactorings.SelectMany(r => r.Actions));
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.ComponentModel.Composition;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CodeRefactorings;
using Microsoft.CodeAnalysis.LanguageServer.CustomProtocol;
using Microsoft.CodeAnalysis.LanguageServer.Handler.Commands;
using Newtonsoft.Json.Linq;
using LSP = Microsoft.VisualStudio.LanguageServer.Protocol;
namespace Microsoft.CodeAnalysis.LanguageServer.Handler
{
[ExportExecuteWorkspaceCommand(RunCodeActionCommandName)]
internal class RunCodeActionsHandler : CodeActionsHandlerBase, IExecuteWorkspaceCommandHandler
{
[ImportingConstructor]
public RunCodeActionsHandler(ICodeFixService codeFixService, ICodeRefactoringService codeRefactoringService)
: base(codeFixService, codeRefactoringService)
{
}
public async Task<object> HandleRequestAsync(Solution solution, LSP.ExecuteCommandParams request, LSP.ClientCapabilities clientCapabilities,
CancellationToken cancellationToken, bool keepThreadContext = false)
{
var runRequest = ((JToken)request.Arguments.Single()).ToObject<RunCodeActionParams>();
var codeActions = await GetCodeActionsAsync(solution,
runRequest.CodeActionParams.TextDocument.Uri,
runRequest.CodeActionParams.Range,
keepThreadContext,
cancellationToken).ConfigureAwait(keepThreadContext);
var actionToRun = codeActions?.FirstOrDefault(a => a.Title == runRequest.Title);
if (actionToRun != null)
{
foreach (var operation in await actionToRun.GetOperationsAsync(cancellationToken).ConfigureAwait(keepThreadContext))
{
operation.Apply(solution.Workspace, cancellationToken);
}
}
return true;
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Composition;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.LanguageServer.Handler.Commands;
using LSP = Microsoft.VisualStudio.LanguageServer.Protocol;
namespace Microsoft.CodeAnalysis.LanguageServer.Handler
{
[Shared]
[ExportLspMethod(LSP.Methods.WorkspaceExecuteCommandName)]
internal class ExecuteWorkspaceCommandHandler : IRequestHandler<LSP.ExecuteCommandParams, object>
{
private readonly ImmutableDictionary<string, Lazy<IExecuteWorkspaceCommandHandler, IExecuteWorkspaceCommandHandlerMetadata>> _executeCommandHandlers;
[ImportingConstructor]
public ExecuteWorkspaceCommandHandler([ImportMany] IEnumerable<Lazy<IExecuteWorkspaceCommandHandler, IExecuteWorkspaceCommandHandlerMetadata>> executeCommandHandlers)
{
_executeCommandHandlers = CreateMap(executeCommandHandlers);
}
private static ImmutableDictionary<string, Lazy<IExecuteWorkspaceCommandHandler, IExecuteWorkspaceCommandHandlerMetadata>> CreateMap(
IEnumerable<Lazy<IExecuteWorkspaceCommandHandler, IExecuteWorkspaceCommandHandlerMetadata>> requestHandlers)
{
var requestHandlerDictionary = ImmutableDictionary.CreateBuilder<string, Lazy<IExecuteWorkspaceCommandHandler, IExecuteWorkspaceCommandHandlerMetadata>>();
foreach (var lazyHandler in requestHandlers)
{
requestHandlerDictionary.Add(lazyHandler.Metadata.CommandName, lazyHandler);
}
return requestHandlerDictionary.ToImmutable();
}
/// <summary>
/// Handles an <see cref="LSP.Methods.WorkspaceExecuteCommand"/>
/// by delegating to a handler for the specific command requested.
/// </summary>
public Task<object> HandleRequestAsync(Solution solution, LSP.ExecuteCommandParams request, LSP.ClientCapabilities clientCapabilities,
CancellationToken cancellationToken, bool keepThreadContext = false)
{
var commandName = request.Command;
if (string.IsNullOrEmpty(commandName) || !_executeCommandHandlers.TryGetValue(commandName, out var executeCommandHandler))
{
throw new ArgumentException(string.Format("Command name ({0}) is invalid", commandName));
}
return executeCommandHandler.Value.HandleRequestAsync(solution, request, clientCapabilities, cancellationToken, keepThreadContext);
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.ComponentModel.Composition;
namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Commands
{
/// <summary>
/// Defines an attribute to export LSP handlers to handle a specific command from the 'workspace/executeCommand' method.
/// </summary>
[AttributeUsage(AttributeTargets.Class), MetadataAttribute]
internal class ExportExecuteWorkspaceCommandAttribute : ExportAttribute, IExecuteWorkspaceCommandHandlerMetadata
{
public string CommandName { get; }
public ExportExecuteWorkspaceCommandAttribute(string commandName) : base(typeof(IExecuteWorkspaceCommandHandler))
{
if (string.IsNullOrEmpty(commandName))
{
throw new ArgumentException(nameof(commandName));
}
CommandName = commandName;
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.LanguageServer.Protocol;
namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Commands
{
internal interface IExecuteWorkspaceCommandHandler
{
/// <summary>
/// Handles a specific command from a <see cref="Methods.WorkspaceExecuteCommandName"/> request.
/// </summary>
Task<object> HandleRequestAsync(Solution solution, ExecuteCommandParams request, ClientCapabilities clientCapabilities, CancellationToken cancellationToken, bool keepThreadContext = false);
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Commands
{
internal interface IExecuteWorkspaceCommandHandlerMetadata
{
/// <summary>
/// Defines the 'workspace/executeCommand' command name that should be handled.
/// </summary>
string CommandName { get; }
}
}
......@@ -21,7 +21,8 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler
[ExportLspMethod(LSP.Methods.TextDocumentCompletionName)]
internal class CompletionHandler : IRequestHandler<LSP.CompletionParams, object>
{
public async Task<object> HandleRequestAsync(Solution solution, LSP.CompletionParams request, LSP.ClientCapabilities clientCapabilities, CancellationToken cancellationToken)
public async Task<object> HandleRequestAsync(Solution solution, LSP.CompletionParams request, LSP.ClientCapabilities clientCapabilities,
CancellationToken cancellationToken, bool keepThreadContext = false)
{
var document = solution.GetDocumentFromURI(request.TextDocument.Uri);
if (document == null)
......@@ -29,10 +30,10 @@ public async Task<object> HandleRequestAsync(Solution solution, LSP.CompletionPa
return Array.Empty<LSP.CompletionItem>();
}
var position = await document.GetPositionFromLinePositionAsync(ProtocolConversions.PositionToLinePosition(request.Position), cancellationToken).ConfigureAwait(false);
var position = await document.GetPositionFromLinePositionAsync(ProtocolConversions.PositionToLinePosition(request.Position), cancellationToken).ConfigureAwait(keepThreadContext);
var completionService = document.Project.LanguageServices.GetService<CompletionService>();
var list = await completionService.GetCompletionsAsync(document, position, cancellationToken: cancellationToken).ConfigureAwait(false);
var list = await completionService.GetCompletionsAsync(document, position, cancellationToken: cancellationToken).ConfigureAwait(keepThreadContext);
if (list == null)
{
return Array.Empty<LSP.CompletionItem>();
......
......@@ -20,7 +20,7 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler
internal class CompletionResolveHandler : IRequestHandler<LSP.CompletionItem, LSP.CompletionItem>
{
public async Task<LSP.CompletionItem> HandleRequestAsync(Solution solution, LSP.CompletionItem completionItem,
LSP.ClientCapabilities clientCapabilities, CancellationToken cancellationToken)
LSP.ClientCapabilities clientCapabilities, CancellationToken cancellationToken, bool keepThreadContext)
{
CompletionResolveData data;
if (completionItem.Data is CompletionResolveData)
......@@ -40,10 +40,10 @@ internal class CompletionResolveHandler : IRequestHandler<LSP.CompletionItem, LS
return completionItem;
}
var position = await document.GetPositionFromLinePositionAsync(ProtocolConversions.PositionToLinePosition(request.Position), cancellationToken).ConfigureAwait(false);
var position = await document.GetPositionFromLinePositionAsync(ProtocolConversions.PositionToLinePosition(request.Position), cancellationToken).ConfigureAwait(keepThreadContext);
var completionService = document.Project.LanguageServices.GetService<CompletionService>();
var list = await completionService.GetCompletionsAsync(document, position, cancellationToken: cancellationToken).ConfigureAwait(false);
var list = await completionService.GetCompletionsAsync(document, position, cancellationToken: cancellationToken).ConfigureAwait(keepThreadContext);
if (list == null)
{
return completionItem;
......@@ -55,7 +55,7 @@ internal class CompletionResolveHandler : IRequestHandler<LSP.CompletionItem, LS
return completionItem;
}
var description = await completionService.GetDescriptionAsync(document, selectedItem, cancellationToken).ConfigureAwait(false);
var description = await completionService.GetDescriptionAsync(document, selectedItem, cancellationToken).ConfigureAwait(keepThreadContext);
var lspVSClientCapability = clientCapabilities?.HasVisualStudioLspCapability() == true;
LSP.CompletionItem resolvedCompletionItem;
......
......@@ -18,9 +18,9 @@ public GoToDefinitionHandler(IMetadataAsSourceFileService metadataAsSourceFileSe
}
public async Task<object> HandleRequestAsync(Solution solution, LSP.TextDocumentPositionParams request,
LSP.ClientCapabilities clientCapabilities, CancellationToken cancellationToken)
LSP.ClientCapabilities clientCapabilities, CancellationToken cancellationToken, bool keepThreadContext = false)
{
return await GetDefinitionAsync(solution, request, false, cancellationToken).ConfigureAwait(false);
return await GetDefinitionAsync(solution, request, typeOnly: false, keepThreadContext, cancellationToken).ConfigureAwait(keepThreadContext);
}
}
}
......@@ -21,7 +21,7 @@ public GoToDefinitionHandlerBase(IMetadataAsSourceFileService metadataAsSourceFi
_metadataAsSourceFileService = metadataAsSourceFileService;
}
protected async Task<LSP.Location[]> GetDefinitionAsync(Solution solution, LSP.TextDocumentPositionParams request, bool typeOnly, CancellationToken cancellationToken)
protected async Task<LSP.Location[]> GetDefinitionAsync(Solution solution, LSP.TextDocumentPositionParams request, bool typeOnly, bool keepThreadContext, CancellationToken cancellationToken)
{
var locations = ArrayBuilder<LSP.Location>.GetInstance();
......@@ -31,10 +31,10 @@ protected async Task<LSP.Location[]> GetDefinitionAsync(Solution solution, LSP.T
return locations.ToArrayAndFree();
}
var position = await document.GetPositionFromLinePositionAsync(ProtocolConversions.PositionToLinePosition(request.Position), cancellationToken).ConfigureAwait(false);
var position = await document.GetPositionFromLinePositionAsync(ProtocolConversions.PositionToLinePosition(request.Position), cancellationToken).ConfigureAwait(keepThreadContext);
var definitionService = document.Project.LanguageServices.GetService<IGoToDefinitionService>();
var definitions = await definitionService.FindDefinitionsAsync(document, position, cancellationToken).ConfigureAwait(false);
var definitions = await definitionService.FindDefinitionsAsync(document, position, cancellationToken).ConfigureAwait(keepThreadContext);
if (definitions != null && definitions.Count() > 0)
{
foreach (var definition in definitions)
......@@ -44,7 +44,7 @@ protected async Task<LSP.Location[]> GetDefinitionAsync(Solution solution, LSP.T
continue;
}
var definitionText = await definition.Document.GetTextAsync(cancellationToken).ConfigureAwait(false);
var definitionText = await definition.Document.GetTextAsync(cancellationToken).ConfigureAwait(keepThreadContext);
locations.Add(new LSP.Location
{
Uri = definition.Document.GetURI(),
......@@ -55,12 +55,12 @@ protected async Task<LSP.Location[]> GetDefinitionAsync(Solution solution, LSP.T
else if (document.SupportsSemanticModel && _metadataAsSourceFileService != null)
{
// No definition found - see if we can get metadata as source but that's only applicable for C#\VB.
var symbol = await SymbolFinder.FindSymbolAtPositionAsync(document, position, cancellationToken).ConfigureAwait(false);
var symbol = await SymbolFinder.FindSymbolAtPositionAsync(document, position, cancellationToken).ConfigureAwait(keepThreadContext);
if (symbol != null && symbol.Locations != null && !symbol.Locations.IsEmpty && symbol.Locations.First().IsInMetadata)
{
if (!typeOnly || symbol is ITypeSymbol)
{
var declarationFile = await _metadataAsSourceFileService.GetGeneratedFileAsync(document.Project, symbol, false, cancellationToken).ConfigureAwait(false);
var declarationFile = await _metadataAsSourceFileService.GetGeneratedFileAsync(document.Project, symbol, false, cancellationToken).ConfigureAwait(keepThreadContext);
var linePosSpan = declarationFile.IdentifierLocation.GetLineSpan().Span;
locations.Add(new LSP.Location
......
......@@ -18,9 +18,9 @@ public GoToTypeDefinitionHandler(IMetadataAsSourceFileService metadataAsSourceFi
}
public async Task<LSP.Location[]> HandleRequestAsync(Solution solution, LSP.TextDocumentPositionParams request,
LSP.ClientCapabilities clientCapabilities, CancellationToken cancellationToken)
LSP.ClientCapabilities clientCapabilities, CancellationToken cancellationToken, bool keepThreadContext)
{
return await GetDefinitionAsync(solution, request, true, cancellationToken).ConfigureAwait(false);
return await GetDefinitionAsync(solution, request, typeOnly: true, keepThreadContext, cancellationToken).ConfigureAwait(keepThreadContext);
}
}
}
......@@ -15,6 +15,11 @@ internal class ExportLspMethodAttribute : ExportAttribute, IRequestHandlerMetada
public ExportLspMethodAttribute(string methodName) : base(typeof(IRequestHandler))
{
if (string.IsNullOrEmpty(methodName))
{
throw new ArgumentException(nameof(methodName));
}
MethodName = methodName;
}
}
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Composition;
using System.Threading;
......@@ -14,7 +14,7 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler
internal class FoldingRangesHandler : IRequestHandler<FoldingRangeParams, FoldingRange[]>
{
public async Task<FoldingRange[]> HandleRequestAsync(Solution solution, FoldingRangeParams request,
ClientCapabilities clientCapabilities, CancellationToken cancellationToken)
ClientCapabilities clientCapabilities, CancellationToken cancellationToken, bool keepThreadContext = false)
{
var foldingRanges = ArrayBuilder<FoldingRange>.GetInstance();
......@@ -30,13 +30,13 @@ internal class FoldingRangesHandler : IRequestHandler<FoldingRangeParams, Foldin
return foldingRanges.ToArrayAndFree();
}
var blockStructure = await blockStructureService.GetBlockStructureAsync(document, cancellationToken).ConfigureAwait(false);
var blockStructure = await blockStructureService.GetBlockStructureAsync(document, cancellationToken).ConfigureAwait(keepThreadContext);
if (blockStructure == null)
{
return foldingRanges.ToArrayAndFree();
}
var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);
var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(keepThreadContext);
foreach (var span in blockStructure.Spans)
{
......
......@@ -11,9 +11,10 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler
[ExportLspMethod(LSP.Methods.TextDocumentFormattingName)]
internal class FormatDocumentHandler : FormatDocumentHandlerBase, IRequestHandler<LSP.DocumentFormattingParams, LSP.TextEdit[]>
{
public async Task<LSP.TextEdit[]> HandleRequestAsync(Solution solution, LSP.DocumentFormattingParams request, LSP.ClientCapabilities clientCapabilities, CancellationToken cancellationToken)
public async Task<LSP.TextEdit[]> HandleRequestAsync(Solution solution, LSP.DocumentFormattingParams request, LSP.ClientCapabilities clientCapabilities,
CancellationToken cancellationToken, bool keepThreadContext = false)
{
return await GetTextEdits(solution, request.TextDocument.Uri, cancellationToken).ConfigureAwait(false);
return await GetTextEdits(solution, request.TextDocument.Uri, keepThreadContext, cancellationToken).ConfigureAwait(keepThreadContext);
}
}
}
......@@ -15,7 +15,7 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler
{
internal class FormatDocumentHandlerBase
{
protected async Task<LSP.TextEdit[]> GetTextEdits(Solution solution, Uri documentUri, CancellationToken cancellationToken, LSP.Range range = null)
protected async Task<LSP.TextEdit[]> GetTextEdits(Solution solution, Uri documentUri, bool keepThreadContext, CancellationToken cancellationToken, LSP.Range range = null)
{
var edits = new ArrayBuilder<LSP.TextEdit>();
var document = solution.GetDocumentFromURI(documentUri);
......@@ -23,14 +23,14 @@ protected async Task<LSP.TextEdit[]> GetTextEdits(Solution solution, Uri documen
if (document != null)
{
var formattingService = document.Project.LanguageServices.GetService<IEditorFormattingService>();
var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);
var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(keepThreadContext);
TextSpan? textSpan = null;
if (range != null)
{
textSpan = ProtocolConversions.RangeToTextSpan(range, text);
}
var textChanges = await formattingService.GetFormattingChangesAsync(document, textSpan, cancellationToken).ConfigureAwait(false);
var textChanges = await formattingService.GetFormattingChangesAsync(document, textSpan, cancellationToken).ConfigureAwait(keepThreadContext);
edits.AddRange(textChanges.Select(change => ProtocolConversions.TextChangeToTextEdit(change, text)));
}
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Composition;
......@@ -17,14 +17,15 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler
[ExportLspMethod(Methods.TextDocumentOnTypeFormattingName)]
internal class FormatDocumentOnTypeHandler : IRequestHandler<DocumentOnTypeFormattingParams, TextEdit[]>
{
public async Task<TextEdit[]> HandleRequestAsync(Solution solution, DocumentOnTypeFormattingParams request, ClientCapabilities clientCapabilities, CancellationToken cancellationToken)
public async Task<TextEdit[]> HandleRequestAsync(Solution solution, DocumentOnTypeFormattingParams request, ClientCapabilities clientCapabilities,
CancellationToken cancellationToken, bool keepThreadContext = false)
{
var edits = new ArrayBuilder<TextEdit>();
var document = solution.GetDocumentFromURI(request.TextDocument.Uri);
if (document != null)
{
var formattingService = document.Project.LanguageServices.GetService<IEditorFormattingService>();
var position = await document.GetPositionFromLinePositionAsync(ProtocolConversions.PositionToLinePosition(request.Position), cancellationToken).ConfigureAwait(false);
var position = await document.GetPositionFromLinePositionAsync(ProtocolConversions.PositionToLinePosition(request.Position), cancellationToken).ConfigureAwait(keepThreadContext);
if (string.IsNullOrEmpty(request.Character))
{
......@@ -34,14 +35,14 @@ public async Task<TextEdit[]> HandleRequestAsync(Solution solution, DocumentOnTy
IList<TextChange> textChanges;
if (SyntaxFacts.IsNewLine(request.Character[0]))
{
textChanges = await formattingService.GetFormattingChangesOnReturnAsync(document, position, cancellationToken).ConfigureAwait(false);
textChanges = await formattingService.GetFormattingChangesOnReturnAsync(document, position, cancellationToken).ConfigureAwait(keepThreadContext);
}
else
{
textChanges = await formattingService.GetFormattingChangesAsync(document, request.Character[0], position, cancellationToken).ConfigureAwait(false);
textChanges = await formattingService.GetFormattingChangesAsync(document, request.Character[0], position, cancellationToken).ConfigureAwait(keepThreadContext);
}
var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);
var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(keepThreadContext);
if (textChanges != null)
{
edits.AddRange(textChanges.Select(change => ProtocolConversions.TextChangeToTextEdit(change, text)));
......
......@@ -11,9 +11,10 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler
[ExportLspMethod(Methods.TextDocumentRangeFormattingName)]
internal class FormatDocumentRangeHandler : FormatDocumentHandlerBase, IRequestHandler<DocumentRangeFormattingParams, TextEdit[]>
{
public async Task<TextEdit[]> HandleRequestAsync(Solution solution, DocumentRangeFormattingParams request, ClientCapabilities clientCapabilities, CancellationToken cancellationToken)
public async Task<TextEdit[]> HandleRequestAsync(Solution solution, DocumentRangeFormattingParams request, ClientCapabilities clientCapabilities,
CancellationToken cancellationToken, bool keepThreadContext = false)
{
return await GetTextEdits(solution, request.TextDocument.Uri, cancellationToken, range: request.Range).ConfigureAwait(false);
return await GetTextEdits(solution, request.TextDocument.Uri, keepThreadContext, cancellationToken, range: request.Range).ConfigureAwait(keepThreadContext);
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Immutable;
......@@ -16,7 +16,7 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler
internal class DocumentHighlightsHandler : IRequestHandler<TextDocumentPositionParams, DocumentHighlight[]>
{
public async Task<DocumentHighlight[]> HandleRequestAsync(Solution solution, TextDocumentPositionParams request,
ClientCapabilities clientCapabilities, CancellationToken cancellationToken)
ClientCapabilities clientCapabilities, CancellationToken cancellationToken, bool keepThreadContext)
{
var document = solution.GetDocumentFromURI(request.TextDocument.Uri);
if (document == null)
......@@ -25,19 +25,19 @@ internal class DocumentHighlightsHandler : IRequestHandler<TextDocumentPositionP
}
var documentHighlightService = document.Project.LanguageServices.GetService<IDocumentHighlightsService>();
var position = await document.GetPositionFromLinePositionAsync(ProtocolConversions.PositionToLinePosition(request.Position), cancellationToken).ConfigureAwait(false);
var position = await document.GetPositionFromLinePositionAsync(ProtocolConversions.PositionToLinePosition(request.Position), cancellationToken).ConfigureAwait(keepThreadContext);
var highlights = await documentHighlightService.GetDocumentHighlightsAsync(
document,
position,
ImmutableHashSet.Create(document),
cancellationToken).ConfigureAwait(false);
cancellationToken).ConfigureAwait(keepThreadContext);
if (!highlights.IsDefaultOrEmpty)
{
// LSP requests are only for a single document. So just get the highlights for the requested document.
var highlightsForDocument = highlights.FirstOrDefault(h => h.Document.Id == document.Id);
var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);
var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(keepThreadContext);
return highlightsForDocument.HighlightSpans.Select(h => new DocumentHighlight
{
......
......@@ -15,7 +15,7 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler
internal class HoverHandler : IRequestHandler<TextDocumentPositionParams, Hover>
{
public async Task<Hover> HandleRequestAsync(Solution solution, TextDocumentPositionParams request,
ClientCapabilities clientCapabilities, CancellationToken cancellationToken)
ClientCapabilities clientCapabilities, CancellationToken cancellationToken, bool keepThreadContext = false)
{
var document = solution.GetDocumentFromURI(request.TextDocument.Uri);
if (document == null)
......@@ -23,16 +23,16 @@ internal class HoverHandler : IRequestHandler<TextDocumentPositionParams, Hover>
return null;
}
var position = await document.GetPositionFromLinePositionAsync(ProtocolConversions.PositionToLinePosition(request.Position), cancellationToken).ConfigureAwait(false);
var position = await document.GetPositionFromLinePositionAsync(ProtocolConversions.PositionToLinePosition(request.Position), cancellationToken).ConfigureAwait(keepThreadContext);
var quickInfoService = document.Project.LanguageServices.GetService<QuickInfoService>();
var info = await quickInfoService.GetQuickInfoAsync(document, position, cancellationToken).ConfigureAwait(false);
var info = await quickInfoService.GetQuickInfoAsync(document, position, cancellationToken).ConfigureAwait(keepThreadContext);
if (info == null)
{
return null;
}
var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);
var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(keepThreadContext);
return new Hover
{
Range = ProtocolConversions.TextSpanToRange(info.Span, text),
......
......@@ -15,6 +15,15 @@ internal interface IRequestHandler
internal interface IRequestHandler<RequestType, ResponseType> : IRequestHandler
{
Task<ResponseType> HandleRequestAsync(Solution solution, RequestType request, ClientCapabilities clientCapabilities, CancellationToken cancellationToken);
/// <summary>
/// Handles an LSP request.
/// </summary>
/// <param name="solution">the solution to apply the request to.</param>
/// <param name="request">the lsp request.</param>
/// <param name="clientCapabilities">the client capabilities for the request.</param>
/// <param name="cancellationToken">a cancellation token.</param>
/// <param name="keepThreadContext">a value to set if the threading context in the handler should be kept from the caller.</param>
/// <returns>the lps response.</returns>
Task<ResponseType> HandleRequestAsync(Solution solution, RequestType request, ClientCapabilities clientCapabilities, CancellationToken cancellationToken, bool keepThreadContext = false);
}
}
......@@ -33,7 +33,7 @@ internal class InitializeHandler : IRequestHandler<InitializeParams, InitializeR
}
};
public Task<InitializeResult> HandleRequestAsync(Solution solution, InitializeParams request, ClientCapabilities clientCapabilities, CancellationToken cancellationToken)
public Task<InitializeResult> HandleRequestAsync(Solution solution, InitializeParams request, ClientCapabilities clientCapabilities, CancellationToken cancellationToken, bool keepThreadContext = false)
=> Task.FromResult(s_initializeResult);
}
}
......@@ -15,7 +15,7 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler
internal class FindImplementationsHandler : IRequestHandler<LSP.TextDocumentPositionParams, object>
{
public async Task<object> HandleRequestAsync(Solution solution, LSP.TextDocumentPositionParams request,
LSP.ClientCapabilities clientCapabilities, CancellationToken cancellationToken)
LSP.ClientCapabilities clientCapabilities, CancellationToken cancellationToken, bool keepThreadContext = false)
{
var locations = ArrayBuilder<LSP.Location>.GetInstance();
......@@ -26,11 +26,11 @@ internal class FindImplementationsHandler : IRequestHandler<LSP.TextDocumentPosi
}
var findUsagesService = document.Project.LanguageServices.GetService<IFindUsagesService>();
var position = await document.GetPositionFromLinePositionAsync(ProtocolConversions.PositionToLinePosition(request.Position), cancellationToken).ConfigureAwait(false);
var position = await document.GetPositionFromLinePositionAsync(ProtocolConversions.PositionToLinePosition(request.Position), cancellationToken).ConfigureAwait(keepThreadContext);
var context = new SimpleFindUsagesContext(cancellationToken);
await findUsagesService.FindImplementationsAsync(document, position, context).ConfigureAwait(false);
await findUsagesService.FindImplementationsAsync(document, position, context).ConfigureAwait(keepThreadContext);
foreach (var definition in context.GetDefinitions())
{
......@@ -39,11 +39,11 @@ internal class FindImplementationsHandler : IRequestHandler<LSP.TextDocumentPosi
{
if (clientCapabilities?.HasVisualStudioLspCapability() == true)
{
locations.Add(await ProtocolConversions.DocumentSpanToLocationWithTextAsync(sourceSpan, text, cancellationToken).ConfigureAwait(false));
locations.Add(await ProtocolConversions.DocumentSpanToLocationWithTextAsync(sourceSpan, text, cancellationToken).ConfigureAwait(keepThreadContext));
}
else
{
locations.Add(await ProtocolConversions.DocumentSpanToLocationAsync(sourceSpan, cancellationToken).ConfigureAwait(false));
locations.Add(await ProtocolConversions.DocumentSpanToLocationAsync(sourceSpan, cancellationToken).ConfigureAwait(keepThreadContext));
}
}
}
......
......@@ -28,7 +28,7 @@ public SignatureHelpHandler([ImportMany] IEnumerable<Lazy<ISignatureHelpProvider
}
public async Task<LSP.SignatureHelp> HandleRequestAsync(Solution solution, LSP.TextDocumentPositionParams request,
LSP.ClientCapabilities clientCapabilities, CancellationToken cancellationToken)
LSP.ClientCapabilities clientCapabilities, CancellationToken cancellationToken, bool keepThreadContext = false)
{
var document = solution.GetDocumentFromURI(request.TextDocument.Uri);
if (document == null)
......@@ -36,14 +36,14 @@ public SignatureHelpHandler([ImportMany] IEnumerable<Lazy<ISignatureHelpProvider
return new LSP.SignatureHelp();
}
var position = await document.GetPositionFromLinePositionAsync(ProtocolConversions.PositionToLinePosition(request.Position), cancellationToken).ConfigureAwait(false);
var position = await document.GetPositionFromLinePositionAsync(ProtocolConversions.PositionToLinePosition(request.Position), cancellationToken).ConfigureAwait(keepThreadContext);
var providers = _allProviders.Where(p => p.Metadata.Language == document.Project.Language);
var triggerInfo = new SignatureHelpTriggerInfo(SignatureHelpTriggerReason.InvokeSignatureHelpCommand);
foreach (var provider in providers)
{
var items = await provider.Value.GetItemsAsync(document, position, triggerInfo, cancellationToken).ConfigureAwait(false);
var items = await provider.Value.GetItemsAsync(document, position, triggerInfo, cancellationToken).ConfigureAwait(keepThreadContext);
if (items != null)
{
......
......@@ -21,7 +21,7 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler
internal class DocumentSymbolsHandler : IRequestHandler<DocumentSymbolParams, object[]>
{
public async Task<object[]> HandleRequestAsync(Solution solution, DocumentSymbolParams request,
ClientCapabilities clientCapabilities, CancellationToken cancellationToken)
ClientCapabilities clientCapabilities, CancellationToken cancellationToken, bool keepThreadContext = false)
{
var document = solution.GetDocumentFromURI(request.TextDocument.Uri);
if (document == null)
......@@ -32,15 +32,15 @@ internal class DocumentSymbolsHandler : IRequestHandler<DocumentSymbolParams, ob
var symbols = ArrayBuilder<object>.GetInstance();
var navBarService = document.Project.LanguageServices.GetService<INavigationBarItemService>();
var navBarItems = await navBarService.GetItemsAsync(document, cancellationToken).ConfigureAwait(false);
var navBarItems = await navBarService.GetItemsAsync(document, cancellationToken).ConfigureAwait(keepThreadContext);
if (navBarItems.Count == 0)
{
return symbols.ToArrayAndFree();
}
var compilation = await document.Project.GetCompilationAsync(cancellationToken).ConfigureAwait(false);
var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);
var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);
var compilation = await document.Project.GetCompilationAsync(cancellationToken).ConfigureAwait(keepThreadContext);
var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(keepThreadContext);
var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(keepThreadContext);
// TODO - Return more than 2 levels of symbols.
// https://github.com/dotnet/roslyn/projects/45#card-20033869
......@@ -49,7 +49,7 @@ internal class DocumentSymbolsHandler : IRequestHandler<DocumentSymbolParams, ob
foreach (var item in navBarItems)
{
// only top level ones
symbols.Add(await GetDocumentSymbolAsync(item, compilation, tree, text, cancellationToken).ConfigureAwait(false));
symbols.Add(await GetDocumentSymbolAsync(item, compilation, tree, text, keepThreadContext, cancellationToken).ConfigureAwait(keepThreadContext));
}
}
else
......@@ -110,7 +110,7 @@ static SymbolInformation Create(NavigationBarItem item, TextSpan span, string co
/// Get a document symbol from a specified nav bar item.
/// </summary>
private static async Task<DocumentSymbol> GetDocumentSymbolAsync(NavigationBarItem item, Compilation compilation, SyntaxTree tree,
SourceText text, CancellationToken cancellationToken)
SourceText text, bool keepThreadContext, CancellationToken cancellationToken)
{
// it is actually symbol location getter. but anyway.
var location = GetLocation(item, compilation, tree, cancellationToken);
......@@ -119,7 +119,7 @@ static SymbolInformation Create(NavigationBarItem item, TextSpan span, string co
return null;
}
var symbol = await GetSymbolAsync(location, compilation, cancellationToken).ConfigureAwait(false);
var symbol = await GetSymbolAsync(location, compilation, keepThreadContext, cancellationToken).ConfigureAwait(keepThreadContext);
if (symbol == null)
{
return null;
......@@ -133,25 +133,25 @@ static SymbolInformation Create(NavigationBarItem item, TextSpan span, string co
Deprecated = symbol.GetAttributes().Any(x => x.AttributeClass.MetadataName == "ObsoleteAttribute"),
Range = ProtocolConversions.TextSpanToRange(item.Spans.First(), text),
SelectionRange = ProtocolConversions.TextSpanToRange(location.SourceSpan, text),
Children = await GetChildrenAsync(item.ChildItems, compilation, tree, text, cancellationToken).ConfigureAwait(false),
Children = await GetChildrenAsync(item.ChildItems, compilation, tree, text, keepThreadContext, cancellationToken).ConfigureAwait(keepThreadContext),
};
static async Task<DocumentSymbol[]> GetChildrenAsync(IEnumerable<NavigationBarItem> items, Compilation compilation, SyntaxTree tree,
SourceText text, CancellationToken cancellationToken)
SourceText text, bool keepThreadContext, CancellationToken cancellationToken)
{
var list = new ArrayBuilder<DocumentSymbol>();
foreach (var item in items)
{
list.Add(await GetDocumentSymbolAsync(item, compilation, tree, text, cancellationToken).ConfigureAwait(false));
list.Add(await GetDocumentSymbolAsync(item, compilation, tree, text, keepThreadContext, cancellationToken).ConfigureAwait(keepThreadContext));
}
return list.ToArrayAndFree();
}
static async Task<ISymbol> GetSymbolAsync(Location location, Compilation compilation, CancellationToken cancellationToken)
static async Task<ISymbol> GetSymbolAsync(Location location, Compilation compilation, bool keepThreadContext, CancellationToken cancellationToken)
{
var model = compilation.GetSemanticModel(location.SourceTree);
var root = await model.SyntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false);
var root = await model.SyntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(keepThreadContext);
var node = root.FindNode(location.SourceSpan);
while (node != null)
......
......@@ -15,13 +15,13 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler
internal class WorkspaceSymbolsHandler : IRequestHandler<WorkspaceSymbolParams, SymbolInformation[]>
{
public async Task<SymbolInformation[]> HandleRequestAsync(Solution solution, WorkspaceSymbolParams request,
ClientCapabilities clientCapabilities, CancellationToken cancellationToken)
ClientCapabilities clientCapabilities, CancellationToken cancellationToken, bool keepThreadContext = false)
{
var searchTasks = Task.WhenAll(solution.Projects.Select(project => SearchProjectAsync(project, request, cancellationToken)));
return (await searchTasks.ConfigureAwait(false)).SelectMany(s => s).ToArray();
var searchTasks = Task.WhenAll(solution.Projects.Select(project => SearchProjectAsync(project, request, keepThreadContext, cancellationToken)));
return (await searchTasks.ConfigureAwait(keepThreadContext)).SelectMany(s => s).ToArray();
// local functions
static async Task<ImmutableArray<SymbolInformation>> SearchProjectAsync(Project project, WorkspaceSymbolParams request, CancellationToken cancellationToken)
static async Task<ImmutableArray<SymbolInformation>> SearchProjectAsync(Project project, WorkspaceSymbolParams request, bool keepThreadContext, CancellationToken cancellationToken)
{
var searchService = project.LanguageServices.GetService<INavigateToSearchService_RemoveInterfaceAboveAndRenameThisAfterInternalsVisibleToUsersUpdate>();
if (searchService != null)
......@@ -33,20 +33,20 @@ static async Task<ImmutableArray<SymbolInformation>> SearchProjectAsync(Project
ImmutableArray<Document>.Empty,
request.Query,
searchService.KindsProvided,
cancellationToken).ConfigureAwait(false);
var projectSymbolsTasks = Task.WhenAll(items.Select(item => CreateSymbolInformation(item, cancellationToken)));
return (await projectSymbolsTasks.ConfigureAwait(false)).ToImmutableArray();
cancellationToken).ConfigureAwait(keepThreadContext);
var projectSymbolsTasks = Task.WhenAll(items.Select(item => CreateSymbolInformation(item, keepThreadContext, cancellationToken)));
return (await projectSymbolsTasks.ConfigureAwait(keepThreadContext)).ToImmutableArray();
}
return ImmutableArray.Create<SymbolInformation>();
static async Task<SymbolInformation> CreateSymbolInformation(INavigateToSearchResult result, CancellationToken cancellationToken)
static async Task<SymbolInformation> CreateSymbolInformation(INavigateToSearchResult result, bool keepThreadContext, CancellationToken cancellationToken)
{
return new SymbolInformation
{
Name = result.Name,
Kind = ProtocolConversions.NavigateToKindToSymbolKind(result.Kind),
Location = await ProtocolConversions.TextSpanToLocationAsync(result.NavigableItem.Document, result.NavigableItem.SourceSpan, cancellationToken).ConfigureAwait(false),
Location = await ProtocolConversions.TextSpanToLocationAsync(result.NavigableItem.Document, result.NavigableItem.SourceSpan, cancellationToken).ConfigureAwait(keepThreadContext),
};
}
}
......
......@@ -55,12 +55,25 @@ public LanguageServerProtocol([ImportMany] IEnumerable<Lazy<IRequestHandler, IRe
return handler.HandleRequestAsync(solution, request, clientCapabilities, cancellationToken);
}
/// <summary>
/// Answers an execute workspace command request by handling the specified command name.
/// https://microsoft.github.io/language-server-protocol/specification#workspace_executeCommand
/// </summary>
/// <param name="solution">the solution relevant to the workspace.</param>
/// <param name="request">the request command name and arguments.</param>
/// <param name="clientCapabilities">the client capabilities for the request.</param>
/// <param name="cancellationToken">a cancellation token.</param>
/// <returns>any or null.</returns>
public Task<object> ExecuteWorkspaceCommandAsync(Solution solution, LSP.ExecuteCommandParams request, LSP.ClientCapabilities clientCapabilities, CancellationToken cancellationToken)
=> ExecuteRequestAsync<LSP.ExecuteCommandParams, object>(LSP.Methods.WorkspaceExecuteCommandName, solution, request, clientCapabilities, cancellationToken);
/// <summary>
/// Answers an implementation request by returning the implementation location(s) of a given symbol.
/// https://microsoft.github.io/language-server-protocol/specification#textDocument_implementation
/// </summary>
/// <param name="solution">the solution containing the request document.</param>
/// <param name="request">the request document symbol location.</param>
/// <param name="clientCapabilities">the client capabilities for the request.</param>
/// <param name="cancellationToken">a cancellation token.</param>
/// <returns>the location(s) of the implementations of the symbol.</returns>
public Task<object> FindImplementationsAsync(Solution solution, LSP.TextDocumentPositionParams request, LSP.ClientCapabilities clientCapabilities, CancellationToken cancellationToken)
......@@ -72,6 +85,7 @@ public Task<object> FindImplementationsAsync(Solution solution, LSP.TextDocument
/// </summary>
/// <param name="solution">the solution containing the document.</param>
/// <param name="request">the request document and formatting options.</param>
/// <param name="clientCapabilities">the client capabilities for the request.</param>
/// <param name="cancellationToken">a cancellation token.</param>
/// <returns>the text edits describing the document modifications.</returns>
public Task<LSP.TextEdit[]> FormatDocumentAsync(Solution solution, LSP.DocumentFormattingParams request, LSP.ClientCapabilities clientCapabilities, CancellationToken cancellationToken)
......@@ -83,6 +97,7 @@ public Task<LSP.TextEdit[]> FormatDocumentAsync(Solution solution, LSP.DocumentF
/// </summary>
/// <param name="solution">the solution containing the document.</param>
/// <param name="request">the request document, formatting options, and typing information.</param>
/// <param name="clientCapabilities">the client capabilities for the request.</param>
/// <param name="cancellationToken">a cancellation token.</param>
/// <returns>the text edits describing the document modifications.</returns>
public Task<LSP.TextEdit[]> FormatDocumentOnTypeAsync(Solution solution, LSP.DocumentOnTypeFormattingParams request, LSP.ClientCapabilities clientCapabilities, CancellationToken cancellationToken)
......@@ -94,6 +109,7 @@ public Task<LSP.TextEdit[]> FormatDocumentOnTypeAsync(Solution solution, LSP.Doc
/// </summary>
/// <param name="solution">the solution containing the document.</param>
/// <param name="request">the request document, formatting options, and range to format.</param>
/// <param name="clientCapabilities">the client capabilities for the request.</param>
/// <param name="cancellationToken">a cancellation token.</param>
/// <returns>the text edits describing the document modifications.</returns>
public Task<LSP.TextEdit[]> FormatDocumentRangeAsync(Solution solution, LSP.DocumentRangeFormattingParams request, LSP.ClientCapabilities clientCapabilities, CancellationToken cancellationToken)
......@@ -105,6 +121,7 @@ public Task<LSP.TextEdit[]> FormatDocumentRangeAsync(Solution solution, LSP.Docu
/// </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)
......@@ -116,6 +133,7 @@ public Task<object[]> GetCodeActionsAsync(Solution solution, LSP.CodeActionParam
/// </summary>
/// <param name="solution">the solution containing the document.</param>
/// <param name="request">the document position and completion context.</param>
/// <param name="clientCapabilities">the client capabilities for the request.</param>
/// <param name="cancellationToken">a cancellation token.</param>
/// <returns>a list of completions.</returns>
public Task<object> GetCompletionsAsync(Solution solution, LSP.CompletionParams request, LSP.ClientCapabilities clientCapabilities, CancellationToken cancellationToken)
......@@ -127,6 +145,7 @@ public Task<object> GetCompletionsAsync(Solution solution, LSP.CompletionParams
/// </summary>
/// <param name="solution">the solution containing the request document.</param>
/// <param name="request">the request document location.</param>
/// <param name="clientCapabilities">the client capabilities for the request.</param>
/// <param name="cancellationToken">a cancellation token.</param>
/// <returns>the highlights in the document for the given document location.</returns>
public Task<LSP.DocumentHighlight[]> GetDocumentHighlightAsync(Solution solution, LSP.TextDocumentPositionParams request, LSP.ClientCapabilities clientCapabilities, CancellationToken cancellationToken)
......@@ -138,6 +157,7 @@ public Task<LSP.DocumentHighlight[]> GetDocumentHighlightAsync(Solution solution
/// </summary>
/// <param name="solution">the solution containing the document.</param>
/// <param name="request">the document to get symbols from.</param>
/// <param name="clientCapabilities">the client capabilities for the request.</param>
/// <param name="cancellationToken">a cancellation token.</param>
/// <returns>a list of symbols in the document.</returns>
public Task<object[]> GetDocumentSymbolsAsync(Solution solution, LSP.DocumentSymbolParams request, LSP.ClientCapabilities clientCapabilities, CancellationToken cancellationToken)
......@@ -149,6 +169,7 @@ public Task<object[]> GetDocumentSymbolsAsync(Solution solution, LSP.DocumentSym
/// </summary>
/// <param name="solution">the solution containing the document.</param>
/// <param name="request">the request document.</param>
/// <param name="clientCapabilities">the client capabilities for the request.</param>
/// <param name="cancellationToken">a cancellation token.</param>
/// <returns>a list of folding ranges in the document.</returns>
public Task<LSP.FoldingRange[]> GetFoldingRangeAsync(Solution solution, LSP.FoldingRangeParams request, LSP.ClientCapabilities clientCapabilities, CancellationToken cancellationToken)
......@@ -160,6 +181,7 @@ public Task<LSP.FoldingRange[]> GetFoldingRangeAsync(Solution solution, LSP.Fold
/// </summary>
/// <param name="solution">the solution containing any documents in the request.</param>
/// <param name="request">the hover requesst.</param>
/// <param name="clientCapabilities">the client capabilities for the request.</param>
/// <param name="cancellationToken">a cancellation token.</param>
/// <returns>the Hover using MarkupContent.</returns>
public Task<LSP.Hover> GetHoverAsync(Solution solution, LSP.TextDocumentPositionParams request, LSP.ClientCapabilities clientCapabilities, CancellationToken cancellationToken)
......@@ -171,6 +193,7 @@ public Task<LSP.Hover> GetHoverAsync(Solution solution, LSP.TextDocumentPosition
/// </summary>
/// <param name="solution">the solution containing the document.</param>
/// <param name="request">the request document position.</param>
/// <param name="clientCapabilities">the client capabilities for the request.</param>
/// <param name="cancellationToken">a cancellation token.</param>
/// <returns>the signature help at a given location.</returns>
public Task<LSP.SignatureHelp> GetSignatureHelpAsync(Solution solution, LSP.TextDocumentPositionParams request, LSP.ClientCapabilities clientCapabilities, CancellationToken cancellationToken)
......@@ -182,6 +205,7 @@ public Task<LSP.SignatureHelp> GetSignatureHelpAsync(Solution solution, LSP.Text
/// </summary>
/// <param name="solution">the current solution.</param>
/// <param name="request">the workspace request with the query to invoke.</param>
/// <param name="clientCapabilities">the client capabilities for the request.</param>
/// <param name="cancellationToken">a cancellation token.</param>
/// <returns>a list of symbols in the workspace.</returns>
public Task<LSP.SymbolInformation[]> GetWorkspaceSymbolsAsync(Solution solution, LSP.WorkspaceSymbolParams request, LSP.ClientCapabilities clientCapabilities, CancellationToken cancellationToken)
......@@ -193,6 +217,7 @@ public Task<LSP.SymbolInformation[]> GetWorkspaceSymbolsAsync(Solution solution,
/// </summary>
/// <param name="solution">the solution containing the request.</param>
/// <param name="request">the document position of the symbol to go to.</param>
/// <param name="clientCapabilities">the client capabilities for the request.</param>
/// <param name="cancellationToken">a cancellation token.</param>
/// <returns>the location(s) of a given symbol.</returns>
public Task<object> GoToDefinitionAsync(Solution solution, LSP.TextDocumentPositionParams request, LSP.ClientCapabilities clientCapabilities, CancellationToken cancellationToken)
......@@ -204,6 +229,7 @@ public Task<object> GoToDefinitionAsync(Solution solution, LSP.TextDocumentPosit
/// </summary>
/// <param name="solution">the solution containing the request.</param>
/// <param name="request">the document position of the type to go to.</param>
/// <param name="clientCapabilities">the client capabilities for the request.</param>
/// <param name="cancellationToken">a cancellation token.</param>
/// <returns>the location of a type definition.</returns>
public Task<LSP.Location[]> GoToTypeDefinitionAsync(Solution solution, LSP.TextDocumentPositionParams request, LSP.ClientCapabilities clientCapabilities, CancellationToken cancellationToken)
......@@ -215,6 +241,7 @@ public Task<LSP.Location[]> GoToTypeDefinitionAsync(Solution solution, LSP.TextD
/// </summary>
/// <param name="solution">the solution containing the document.</param>
/// <param name="request">the initialize parameters.</param>
/// <param name="clientCapabilities">the client capabilities for the request.</param>
/// <param name="cancellationToken">a cancellation token.</param>
/// <returns>the server cababilities.</returns>
public Task<LSP.InitializeResult> InitializeAsync(Solution solution, LSP.InitializeParams request, LSP.ClientCapabilities clientCapabilities, CancellationToken cancellationToken)
......@@ -226,6 +253,7 @@ public Task<LSP.InitializeResult> InitializeAsync(Solution solution, LSP.Initial
/// </summary>
/// <param name="solution">the solution containing the document.</param>
/// <param name="request">the completion item to resolve.</param>
/// <param name="clientCapabilities">the client capabilities for the request.</param>
/// <param name="cancellationToken">a cancellation token.</param>
/// <returns>a resolved completion item.</returns>
public Task<LSP.CompletionItem> ResolveCompletionItemAsync(Solution solution, LSP.CompletionItem request, LSP.ClientCapabilities clientCapabilities, CancellationToken cancellationToken)
......
......@@ -9,7 +9,7 @@
using Xunit;
using LSP = Microsoft.VisualStudio.LanguageServer.Protocol;
namespace Microsoft.CodeAnalysis.LanguageServer.UnitTests
namespace Microsoft.CodeAnalysis.LanguageServer.UnitTests.CodeActions
{
public class CodeActionsTests : AbstractLanguageServerProtocolTests
{
......@@ -28,13 +28,16 @@ void M()
var expected = CreateCommand(CSharpFeaturesResources.Use_implicit_type, locations["caret"].Single());
var clientCapabilities = CreateClientCapabilitiesWithExperimentalValue("supportsWorkspaceEdits", JToken.FromObject(false));
var results = await RunCodeActionsAsync(solution, locations["caret"].Single(), clientCapabilities);
var results = await RunGetCodeActionsAsync(solution, locations["caret"].Single(), clientCapabilities);
var useImplicitTypeResult = results.Single(r => r.Title == CSharpFeaturesResources.Use_implicit_type);
AssertJsonEquals(expected, useImplicitTypeResult);
}
private static async Task<LSP.Command[]> RunCodeActionsAsync(Solution solution, LSP.Location caret, LSP.ClientCapabilities clientCapabilities = null)
=> (LSP.Command[])await GetLanguageServer(solution).GetCodeActionsAsync(solution, CreateCodeActionParams(caret), clientCapabilities, CancellationToken.None);
private static async Task<LSP.Command[]> RunGetCodeActionsAsync(Solution solution, LSP.Location caret, LSP.ClientCapabilities clientCapabilities = null)
{
var results = await GetLanguageServer(solution).GetCodeActionsAsync(solution, CreateCodeActionParams(caret), clientCapabilities, CancellationToken.None);
return results.Select(r => (LSP.Command)r).ToArray();
}
private static LSP.ClientCapabilities CreateClientCapabilitiesWithExperimentalValue(string experimentalProperty, JToken value)
=> new LSP.ClientCapabilities()
......@@ -60,18 +63,10 @@ private static LSP.Command CreateCommand(string title, LSP.Location location)
=> new LSP.Command()
{
Title = title,
CommandIdentifier = "_liveshare.remotecommand.Roslyn",
CommandIdentifier = "Roslyn.RunCodeAction",
Arguments = new object[]
{
new LSP.Command()
{
Title = title,
CommandIdentifier = "Roslyn.RunCodeAction",
Arguments = new object[]
{
CreateRunCodeActionParams(title, location)
}
}
CreateRunCodeActionParams(title, location)
}
};
}
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.LanguageServer.CustomProtocol;
using Newtonsoft.Json.Linq;
using Roslyn.Test.Utilities;
using Xunit;
using LSP = Microsoft.VisualStudio.LanguageServer.Protocol;
namespace Microsoft.CodeAnalysis.LanguageServer.UnitTests.CodeActions
{
public class RunCodeActionsTests : AbstractLanguageServerProtocolTests
{
[WpfFact]
public async Task TestRunCodeActionsAsync()
{
var markup =
@"class A
{
void M()
{
{|caret:|}int i = 1;
}
}";
var (solution, ranges) = CreateTestSolution(markup);
var codeActionLocation = ranges["caret"].First();
var results = await RunExecuteWorkspaceCommand(solution, codeActionLocation, CSharpFeaturesResources.Use_implicit_type);
Assert.True(results);
}
private static async Task<bool> RunExecuteWorkspaceCommand(Solution solution, LSP.Location caret, string title, LSP.ClientCapabilities clientCapabilities = null)
=> (bool)await GetLanguageServer(solution).ExecuteWorkspaceCommandAsync(solution, CreateExecuteCommandParams(caret, title),
clientCapabilities, CancellationToken.None);
private static LSP.ExecuteCommandParams CreateExecuteCommandParams(LSP.Location location, string title)
=> new LSP.ExecuteCommandParams()
{
Command = "Roslyn.RunCodeAction",
Arguments = new object[]
{
JObject.FromObject(CreateRunCodeActionParams(title, location))
},
};
}
}
......@@ -30,6 +30,7 @@ public async Task<LSP.TextEdit[]> HandleAsync(RunCodeActionParams request, Reque
var codeActions = await GetCodeActionsAsync(solution,
request.CodeActionParams.TextDocument.Uri,
request.CodeActionParams.Range,
keepThreadContext: false,
cancellationToken).ConfigureAwait(false);
var actionToRun = codeActions?.FirstOrDefault(a => a.Title == request.Title);
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.ComponentModel.Composition;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CodeRefactorings;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.VisualStudio.LanguageServer.Protocol;
using Microsoft.VisualStudio.LiveShare.LanguageServices;
namespace Microsoft.VisualStudio.LanguageServices.LiveShare
{
[ExportLspRequestHandler(LiveShareConstants.RoslynContractName, Methods.WorkspaceExecuteCommandName)]
[Obsolete("Used for backwards compatibility with old liveshare clients.")]
internal class RoslynRunCodeActionsHandler : RunCodeActionsHandler
{
[ImportingConstructor]
public RoslynRunCodeActionsHandler(ICodeFixService codeFixService, ICodeRefactoringService codeRefactoringService, IThreadingContext threadingContext)
: base(codeFixService, codeRefactoringService, threadingContext)
{
}
}
[ExportLspRequestHandler(LiveShareConstants.CSharpContractName, Methods.WorkspaceExecuteCommandName)]
internal class CSharpRunCodeActionsHandler : RunCodeActionsHandler
{
[ImportingConstructor]
public CSharpRunCodeActionsHandler(ICodeFixService codeFixService, ICodeRefactoringService codeRefactoringService, IThreadingContext threadingContext)
: base(codeFixService, codeRefactoringService, threadingContext)
{
}
}
[ExportLspRequestHandler(LiveShareConstants.VisualBasicContractName, Methods.WorkspaceExecuteCommandName)]
internal class VisualBasicRunCodeActionsHandler : RunCodeActionsHandler
{
[ImportingConstructor]
public VisualBasicRunCodeActionsHandler(ICodeFixService codeFixService, ICodeRefactoringService codeRefactoringService, IThreadingContext threadingContext)
: base(codeFixService, codeRefactoringService, threadingContext)
{
}
}
[ExportLspRequestHandler(LiveShareConstants.TypeScriptContractName, Methods.WorkspaceExecuteCommandName)]
internal class TypeScriptRunCodeActionsHandler : RunCodeActionsHandler
{
[ImportingConstructor]
public TypeScriptRunCodeActionsHandler(ICodeFixService codeFixService, ICodeRefactoringService codeRefactoringService, IThreadingContext threadingContext)
: base(codeFixService, codeRefactoringService, threadingContext)
{
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CodeRefactorings;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.LanguageServer.CustomProtocol;
using Microsoft.CodeAnalysis.LanguageServer.Handler;
using Microsoft.VisualStudio.LiveShare.LanguageServices;
using Newtonsoft.Json.Linq;
using LSP = Microsoft.VisualStudio.LanguageServer.Protocol;
namespace Microsoft.VisualStudio.LanguageServices.LiveShare
{
/// <summary>
/// Run code actions handler. Called when lightbulb invoked.
/// TODO - Move to CodeAnalysis.LanguageServer once the UI thread dependency is removed.
/// </summary>
internal class RunCodeActionsHandler : CodeActionsHandlerBase, ILspRequestHandler<LSP.ExecuteCommandParams, object, Solution>
{
private readonly IThreadingContext _threadingContext;
public RunCodeActionsHandler(ICodeFixService codeFixService, ICodeRefactoringService codeRefactoringService, IThreadingContext threadingContext)
: base(codeFixService, codeRefactoringService)
{
_threadingContext = threadingContext;
}
public async Task<object> HandleAsync(LSP.ExecuteCommandParams request, RequestContext<Solution> requestContext, CancellationToken cancellationToken)
{
var solution = requestContext.Context;
if (request.Command.StartsWith(RemoteCommandNamePrefix))
{
var command = ((JObject)request.Arguments[0]).ToObject<LSP.Command>();
request.Command = command.CommandIdentifier;
request.Arguments = command.Arguments;
}
if (request.Command == RunCodeActionCommandName)
{
var runRequest = ((JToken)request.Arguments.Single()).ToObject<RunCodeActionParams>();
var codeActions = await GetCodeActionsAsync(solution,
runRequest.CodeActionParams.TextDocument.Uri,
runRequest.CodeActionParams.Range,
cancellationToken).ConfigureAwait(false);
var actionToRun = codeActions?.FirstOrDefault(a => a.Title == runRequest.Title);
if (actionToRun != null)
{
// CodeAction Operation's Apply methods needs to be called on the UI thread.
await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
foreach (var operation in await actionToRun.GetOperationsAsync(cancellationToken).ConfigureAwait(true))
{
operation.Apply(solution.Workspace, cancellationToken);
}
}
}
else
{
// TODO - Modify to our telemetry.
}
// Return true even in the case that we didn't run the command. Returning false would prompt the guest to tell the host to
// enable command executuion, which wouldn't solve their problem.
return true;
}
}
}
......@@ -29,6 +29,15 @@ public virtual Task<ResponseType> HandleAsync(RequestType param, RequestContext<
return ((IRequestHandler<RequestType, ResponseType>)LazyRequestHandler.Value).HandleRequestAsync(requestContext.Context, param, requestContext.ClientCapabilities?.ToObject<ClientCapabilities>(), cancellationToken);
}
/// <summary>
/// Certain implementations require that the processing be done on the UI thread.
/// So allow the handler to specifiy that the thread context should be preserved.
/// </summary>
protected Task<ResponseType> HandleAsyncPreserveThreadContext(RequestType param, RequestContext<Solution> requestContext, CancellationToken cancellationToken)
{
return ((IRequestHandler<RequestType, ResponseType>)LazyRequestHandler.Value).HandleRequestAsync(requestContext.Context, param, requestContext.ClientCapabilities?.ToObject<ClientCapabilities>(), cancellationToken, keepThreadContext: true);
}
protected Lazy<IRequestHandler, IRequestHandlerMetadata> GetRequestHandler(IEnumerable<Lazy<IRequestHandler, IRequestHandlerMetadata>> requestHandlers, string methodName)
{
return requestHandlers.First(handler => handler.Metadata.MethodName == methodName);
......
......@@ -3,11 +3,11 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.LanguageServer.Handler;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.VisualStudio.LanguageServer.Protocol;
using Microsoft.VisualStudio.LiveShare.LanguageServices;
......@@ -15,14 +15,46 @@ namespace Microsoft.VisualStudio.LanguageServices.LiveShare
{
internal class CodeActionsHandlerShim : AbstractLiveShareHandlerShim<CodeActionParams, object[]>
{
public const string RemoteCommandNamePrefix = "_liveshare.remotecommand";
protected const string ProviderName = "Roslyn";
public CodeActionsHandlerShim(IEnumerable<Lazy<IRequestHandler, IRequestHandlerMetadata>> requestHandlers)
: base(requestHandlers, Methods.TextDocumentCodeActionName)
{
}
/// <summary>
/// Handle a <see cref="Methods.TextDocumentCodeActionName"/> by delegating to the base LSP implementation
/// from <see cref="CodeActionsHandler"/>.
///
/// We need to return a command that is a generic wrapper that VS Code can execute.
/// The argument to this wrapper will either be a RunCodeAction command which will carry
/// enough information to run the command or a CodeAction with the edits.
/// There are server and client side dependencies on this shape in liveshare.
/// </summary>
/// <param name="param"></param>
/// <param name="requestContext"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async override Task<object[]> HandleAsync(CodeActionParams param, RequestContext<Solution> requestContext, CancellationToken cancellationToken)
{
return await base.HandleAsync(param, requestContext, cancellationToken).ConfigureAwait(false);
var result = await base.HandleAsync(param, requestContext, cancellationToken).ConfigureAwait(false);
var commands = new ArrayBuilder<Command>();
foreach (var resultObj in result)
{
var commandArguments = resultObj;
var title = resultObj is CodeAction codeAction ? codeAction.Title : ((Command)resultObj).Title;
commands.Add(new Command
{
Title = title,
// Overwrite the command identifier to match the expected liveshare remote command identifier.
CommandIdentifier = $"{RemoteCommandNamePrefix}.{ProviderName}",
Arguments = new object[] { commandArguments }
});
}
return commands.ToArrayAndFree();
}
}
......
......@@ -27,7 +27,7 @@ public override async Task<object> HandleAsync(TextDocumentPositionParams param,
{
// TypeScript requires this call to be on the UI thread.
await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
return await base.HandleAsync(param, requestContext, cancellationToken).ConfigureAwait(false);
return await base.HandleAsyncPreserveThreadContext(param, requestContext, cancellationToken).ConfigureAwait(false);
}
}
......
......@@ -27,7 +27,7 @@ public override async Task<TextEdit[]> HandleAsync(DocumentFormattingParams para
{
// To get the formatting options, TypeScript expects to be called on the UI thread.
await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
return await base.HandleAsync(param, requestContext, cancellationToken).ConfigureAwait(false);
return await base.HandleAsyncPreserveThreadContext(param, requestContext, cancellationToken).ConfigureAwait(false);
}
}
......
......@@ -27,7 +27,7 @@ public override async Task<TextEdit[]> HandleAsync(DocumentOnTypeFormattingParam
{
// To get the formatting options, TypeScript expects to be called on the UI thread.
await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
return await base.HandleAsync(param, requestContext, cancellationToken).ConfigureAwait(false);
return await base.HandleAsyncPreserveThreadContext(param, requestContext, cancellationToken).ConfigureAwait(false);
}
}
......
......@@ -27,7 +27,7 @@ public override async Task<TextEdit[]> HandleAsync(DocumentRangeFormattingParams
{
// To get the formatting options, TypeScript expects to be called on the UI thread.
await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
return await base.HandleAsync(param, requestContext, cancellationToken).ConfigureAwait(false);
return await base.HandleAsyncPreserveThreadContext(param, requestContext, cancellationToken).ConfigureAwait(false);
}
}
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.LanguageServer.CustomProtocol;
using Microsoft.CodeAnalysis.LanguageServer.Handler;
using Microsoft.VisualStudio.LiveShare.LanguageServices;
using Newtonsoft.Json.Linq;
using LSP = Microsoft.VisualStudio.LanguageServer.Protocol;
namespace Microsoft.VisualStudio.LanguageServices.LiveShare
{
/// <summary>
/// Run code actions handler. Called when lightbulb invoked.
/// </summary>
internal class RunCodeActionsHandlerShim : AbstractLiveShareHandlerShim<LSP.ExecuteCommandParams, object>
{
private readonly IThreadingContext _threadingContext;
public RunCodeActionsHandlerShim(IEnumerable<Lazy<IRequestHandler, IRequestHandlerMetadata>> requestHandlers, IThreadingContext threadingContext)
: base(requestHandlers, LSP.Methods.WorkspaceExecuteCommandName)
{
_threadingContext = threadingContext;
}
/// <summary>
/// Unwraps the run code action request from the liveshare shape into the expected shape
/// handled by <see cref="RunCodeActionsHandler"/>. This shape is an <see cref="LSP.Command"/>
/// containing <see cref="RunCodeActionParams"/> as the argument.
/// </summary>
public async override Task<object> HandleAsync(LSP.ExecuteCommandParams request, RequestContext<Solution> requestContext, CancellationToken cancellationToken)
{
// Unwrap the command to get the RunCodeActions command.
if (request.Command.StartsWith(CodeActionsHandlerShim.RemoteCommandNamePrefix))
{
var command = ((JObject)request.Arguments[0]).ToObject<LSP.Command>();
// The CommandIdentifier should match the exported sub-handler for workspace execute command request.
request.Command = command.CommandIdentifier;
request.Arguments = command.Arguments;
}
try
{
// Code actions must be applied from the UI thread in the VS context.
await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
return await base.HandleAsyncPreserveThreadContext(request, requestContext, cancellationToken).ConfigureAwait(false);
}
catch (ArgumentException ex)
{
// We may not have a handler for the command identifier passed in so the base throws.
// This is possible, especially in VSCode scenarios, so rather than blow up, log telemetry.
Logger.Log(FunctionId.Liveshare_UnknownCodeAction, KeyValueLogMessage.Create(m => m["callstack"] = ex.ToString()));
// Return true even in the case that we didn't run the command. Returning false would prompt the guest to tell the host to
// enable command executuion, which wouldn't solve their problem.
return true;
}
}
}
[ExportLspRequestHandler(LiveShareConstants.RoslynContractName, LSP.Methods.WorkspaceExecuteCommandName)]
[Obsolete("Used for backwards compatibility with old liveshare clients.")]
internal class RoslynRunCodeActionsHandlerShim : RunCodeActionsHandlerShim
{
[ImportingConstructor]
public RoslynRunCodeActionsHandlerShim([ImportMany] IEnumerable<Lazy<IRequestHandler, IRequestHandlerMetadata>> requestHandlers, IThreadingContext threadingContext) : base(requestHandlers, threadingContext)
{
}
}
[ExportLspRequestHandler(LiveShareConstants.CSharpContractName, LSP.Methods.WorkspaceExecuteCommandName)]
internal class CSharpRunCodeActionsHandlerShim : RunCodeActionsHandlerShim
{
[ImportingConstructor]
public CSharpRunCodeActionsHandlerShim([ImportMany] IEnumerable<Lazy<IRequestHandler, IRequestHandlerMetadata>> requestHandlers, IThreadingContext threadingContext) : base(requestHandlers, threadingContext)
{
}
}
[ExportLspRequestHandler(LiveShareConstants.VisualBasicContractName, LSP.Methods.WorkspaceExecuteCommandName)]
internal class VisualBasicRunCodeActionsHandlerShim : RunCodeActionsHandlerShim
{
[ImportingConstructor]
public VisualBasicRunCodeActionsHandlerShim([ImportMany] IEnumerable<Lazy<IRequestHandler, IRequestHandlerMetadata>> requestHandlers, IThreadingContext threadingContext) : base(requestHandlers, threadingContext)
{
}
}
[ExportLspRequestHandler(LiveShareConstants.TypeScriptContractName, LSP.Methods.WorkspaceExecuteCommandName)]
internal class TypeScriptRunCodeActionsHandlerShim : RunCodeActionsHandlerShim
{
[ImportingConstructor]
public TypeScriptRunCodeActionsHandlerShim([ImportMany] IEnumerable<Lazy<IRequestHandler, IRequestHandlerMetadata>> requestHandlers, IThreadingContext threadingContext) : base(requestHandlers, threadingContext)
{
}
}
}
......@@ -7,6 +7,7 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Editor.UnitTests;
using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces;
using Microsoft.CodeAnalysis.LanguageServer.Handler.Commands;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.VisualStudio.Composition;
using Microsoft.VisualStudio.LanguageServer.Protocol;
......@@ -60,11 +61,15 @@ protected override ExportProvider GetExportProvider()
// Get all of the roslyn request helpers in M.CA.LanguageServer
var roslynRequestHelperTypes = DesktopTestHelpers.GetAllTypesImplementingGivenInterface(
typeof(RoslynHandlers.IRequestHandler).Assembly, typeof(RoslynHandlers.IRequestHandler));
// Get all of the execute workspace command handlers in M.CA.LanguageServer
var executeCommandHandlerTypes = DesktopTestHelpers.GetAllTypesImplementingGivenInterface(
typeof(IExecuteWorkspaceCommandHandler).Assembly, typeof(IExecuteWorkspaceCommandHandler));
var exportProviderFactory = ExportProviderCache.GetOrCreateExportProviderFactory(
TestExportProvider.EntireAssemblyCatalogWithCSharpAndVisualBasic
.WithPart(typeof(MockDocumentNavigationServiceFactory))
.WithParts(liveShareRequestHelperTypes)
.WithParts(roslynRequestHelperTypes));
.WithParts(roslynRequestHelperTypes)
.WithParts(executeCommandHandlerTypes));
return exportProviderFactory.CreateExportProvider();
}
......
......@@ -2,6 +2,7 @@
using System.Linq;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.LanguageServer.CustomProtocol;
using Newtonsoft.Json.Linq;
using Roslyn.Test.Utilities;
......@@ -26,7 +27,7 @@ void M()
var (solution, ranges) = CreateTestSolution(markup);
var codeActionLocation = ranges["caret"].First();
var results = await TestHandleAsync<LSP.ExecuteCommandParams, object>(solution, CreateExecuteCommandParams(codeActionLocation, "Use implicit type"));
var results = await TestHandleAsync<LSP.ExecuteCommandParams, object>(solution, CreateExecuteCommandParams(codeActionLocation, CSharpFeaturesResources.Use_implicit_type));
Assert.True((bool)results);
}
......
......@@ -458,5 +458,7 @@ internal enum FunctionId
Intellisense_CompletionProviders_Data,
SnapshotService_IsExperimentEnabledAsync,
Liveshare_UnknownCodeAction,
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册