Skip to content

Commit

Permalink
Merge pull request #44474 from dibarbet/lsp_solution
Browse files Browse the repository at this point in the history
Add ILspSolutionProvider in lsp
  • Loading branch information
dibarbet authored Jun 11, 2020
2 parents 28932fc + 501f7ee commit a85202d
Show file tree
Hide file tree
Showing 65 changed files with 496 additions and 304 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,26 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

#nullable enable

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.ComponentModel.Composition;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Editor.Shared.Extensions;
using Microsoft.CodeAnalysis.Editor.UnitTests;
using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.LanguageServer;
using Microsoft.CodeAnalysis.LanguageServer.CustomProtocol;
using Microsoft.CodeAnalysis.LanguageServer.Handler;
using Microsoft.CodeAnalysis.LanguageServer.Handler.Commands;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.Composition;
Expand All @@ -30,6 +36,36 @@ namespace Roslyn.Test.Utilities
[UseExportProvider]
public abstract class AbstractLanguageServerProtocolTests
{
[Export(typeof(ILspSolutionProvider)), PartNotDiscoverable]
internal class TestLspSolutionProvider : ILspSolutionProvider
{
[DisallowNull]
private Solution? _currentSolution;

[ImportingConstructor]
[Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
public TestLspSolutionProvider()
{
}

public void UpdateSolution(Solution solution)
{
_currentSolution = solution;
}

public Solution GetCurrentSolutionForMainWorkspace()
{
Contract.ThrowIfNull(_currentSolution);
return _currentSolution;
}

public ImmutableArray<Document> GetDocuments(Uri documentUri)
{
Contract.ThrowIfNull(_currentSolution);
return _currentSolution.GetDocuments(documentUri);
}
}

protected virtual ExportProvider GetExportProvider()
{
var requestHelperTypes = DesktopTestHelpers.GetAllTypesImplementingGivenInterface(
Expand All @@ -40,7 +76,8 @@ protected virtual ExportProvider GetExportProvider()
TestExportProvider.EntireAssemblyCatalogWithCSharpAndVisualBasic
.WithPart(typeof(LanguageServerProtocol))
.WithParts(requestHelperTypes)
.WithParts(executeCommandHandlerTypes));
.WithParts(executeCommandHandlerTypes)
.WithPart(typeof(TestLspSolutionProvider)));
return exportProviderFactory.CreateExportProvider();
}

Expand Down Expand Up @@ -105,7 +142,7 @@ protected static string ApplyTextEdits(LSP.TextEdit[] edits, SourceText original
return text.ToString();
}

protected static LSP.SymbolInformation CreateSymbolInformation(LSP.SymbolKind kind, string name, LSP.Location location, string containerName = null)
protected static LSP.SymbolInformation CreateSymbolInformation(LSP.SymbolKind kind, string name, LSP.Location location, string? containerName = null)
=> new LSP.SymbolInformation()
{
Kind = kind,
Expand All @@ -114,7 +151,7 @@ protected static LSP.SymbolInformation CreateSymbolInformation(LSP.SymbolKind ki
ContainerName = containerName
};

protected static LSP.TextDocumentIdentifier CreateTextDocumentIdentifier(Uri uri, ProjectId projectContext = null)
protected static LSP.TextDocumentIdentifier CreateTextDocumentIdentifier(Uri uri, ProjectId? projectContext = null)
{
var documentIdentifier = new LSP.VSTextDocumentIdentifier() { Uri = uri };

Expand All @@ -127,7 +164,7 @@ protected static LSP.TextDocumentIdentifier CreateTextDocumentIdentifier(Uri uri
return documentIdentifier;
}

protected static LSP.TextDocumentPositionParams CreateTextDocumentPositionParams(LSP.Location caret, ProjectId projectContext = null)
protected static LSP.TextDocumentPositionParams CreateTextDocumentPositionParams(LSP.Location caret, ProjectId? projectContext = null)
=> new LSP.TextDocumentPositionParams()
{
TextDocument = CreateTextDocumentIdentifier(caret.Uri, projectContext),
Expand Down Expand Up @@ -207,22 +244,31 @@ protected Workspace CreateTestWorkspace(string[] markups, out Dictionary<string,
workspace.ChangeSolution(solution);

locations = GetAnnotatedLocations(workspace, solution);

UpdateSolutionProvider(workspace, solution);
return workspace;
}

protected TestWorkspace CreateXmlTestWorkspace(string xmlContent, out Dictionary<string, IList<LSP.Location>> locations)
{
var workspace = TestWorkspace.Create(xmlContent, exportProvider: GetExportProvider());
locations = GetAnnotatedLocations(workspace, workspace.CurrentSolution);
UpdateSolutionProvider(workspace, workspace.CurrentSolution);
return workspace;
}

private void UpdateSolutionProvider(TestWorkspace workspace, Solution solution)
{
var provider = (TestLspSolutionProvider)workspace.ExportProvider.GetExportedValue<ILspSolutionProvider>();
provider.UpdateSolution(solution);
}

private Dictionary<string, IList<LSP.Location>> GetAnnotatedLocations(TestWorkspace workspace, Solution solution)
{
var locations = new Dictionary<string, IList<LSP.Location>>();
foreach (var testDocument in workspace.Documents)
{
var document = solution.GetDocument(testDocument.Id);
var document = solution.GetRequiredDocument(testDocument.Id);
var text = document.GetTextSynchronously(CancellationToken.None);
foreach (var (name, spans) in testDocument.AnnotatedSpans)
{
Expand Down
17 changes: 11 additions & 6 deletions src/Features/LanguageServer/Protocol/Extensions/Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,24 @@ public static Uri GetURI(this Document document)
return ProtocolConversions.GetUriFromFilePath(document.FilePath);
}

public static ImmutableArray<Document> GetDocuments(this Solution solution, Uri uri, string? clientName = null)
public static ImmutableArray<Document> GetDocuments(this Solution solution, Uri documentUri)
{
// TODO: we need to normalize this. but for now, we check both absolute and local path
// right now, based on who calls this, solution might has "/" or "\\" as directory
// separator
var documentIds = solution.GetDocumentIdsWithFilePath(uri.AbsolutePath);
var documentIds = solution.GetDocumentIdsWithFilePath(documentUri.AbsolutePath);

if (!documentIds.Any())
{
documentIds = solution.GetDocumentIdsWithFilePath(uri.LocalPath);
documentIds = solution.GetDocumentIdsWithFilePath(documentUri.LocalPath);
}

var documents = documentIds.SelectAsArray(id => solution.GetRequiredDocument(id));
return documentIds.SelectAsArray(id => solution.GetRequiredDocument(id));
}

public static ImmutableArray<Document> GetDocuments(this ILspSolutionProvider solutionProvider, Uri uri, string? clientName)
{
var documents = solutionProvider.GetDocuments(uri);

// If we don't have a client name, then we're done filtering
if (clientName == null)
Expand All @@ -58,9 +63,9 @@ public static ImmutableArray<Document> GetDocuments(this Solution solution, Uri
});
}

public static Document? GetDocument(this Solution solution, TextDocumentIdentifier documentIdentifier, string? clientName = null)
public static Document? GetDocument(this ILspSolutionProvider solutionProvider, TextDocumentIdentifier documentIdentifier, string? clientName = null)
{
var documents = solution.GetDocuments(documentIdentifier.Uri, clientName);
var documents = solutionProvider.GetDocuments(documentIdentifier.Uri, clientName);

if (documents.Length == 0)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

#nullable enable

using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.LanguageServer.Protocol;

namespace Microsoft.CodeAnalysis.LanguageServer.Handler
{
internal abstract class AbstractRequestHandler<RequestType, ResponseType> : IRequestHandler<RequestType, ResponseType>
{
protected readonly ILspSolutionProvider SolutionProvider;

protected AbstractRequestHandler(ILspSolutionProvider solutionProvider)
{
SolutionProvider = solutionProvider;
}

public abstract Task<ResponseType> HandleRequestAsync(RequestType request, ClientCapabilities clientCapabilities, string? clientName, CancellationToken cancellationToken);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
#nullable enable

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Composition;
using System.Linq;
using System.Threading;
Expand All @@ -24,21 +26,31 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler
/// </summary>
[Shared]
[ExportLspMethod(LSP.Methods.TextDocumentCodeActionName)]
internal class CodeActionsHandler : CodeActionsHandlerBase, IRequestHandler<LSP.CodeActionParams, LSP.SumType<LSP.Command, LSP.CodeAction>[]>
internal class CodeActionsHandler : AbstractRequestHandler<LSP.CodeActionParams, LSP.SumType<LSP.Command, LSP.CodeAction>[]>
{
private readonly ICodeFixService _codeFixService;
private readonly ICodeRefactoringService _codeRefactoringService;

internal const string RunCodeActionCommandName = "Roslyn.RunCodeAction";

[ImportingConstructor]
[Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
public CodeActionsHandler(ICodeFixService codeFixService, ICodeRefactoringService codeRefactoringService) : base(codeFixService, codeRefactoringService)
public CodeActionsHandler(ICodeFixService codeFixService, ICodeRefactoringService codeRefactoringService, ILspSolutionProvider solutionProvider)
: base(solutionProvider)
{
_codeFixService = codeFixService;
_codeRefactoringService = codeRefactoringService;
}

public async Task<LSP.SumType<LSP.Command, LSP.CodeAction>[]> HandleRequestAsync(Solution solution, LSP.CodeActionParams request,
LSP.ClientCapabilities clientCapabilities, string? clientName, CancellationToken cancellationToken)
public override async Task<LSP.SumType<LSP.Command, LSP.CodeAction>[]> HandleRequestAsync(LSP.CodeActionParams request, LSP.ClientCapabilities clientCapabilities,
string? clientName, CancellationToken cancellationToken)
{
var codeActions = await GetCodeActionsAsync(solution,
request.TextDocument,
var document = SolutionProvider.GetDocument(request.TextDocument, clientName);
var codeActions = await GetCodeActionsAsync(document,
_codeFixService,
_codeRefactoringService,
request.Range,
clientName, cancellationToken).ConfigureAwait(false);
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));
Expand Down Expand Up @@ -72,5 +84,32 @@ public CodeActionsHandler(ICodeFixService codeFixService, ICodeRefactoringServic

return result.ToArrayAndFree();
}

internal static async Task<IEnumerable<CodeAction>> GetCodeActionsAsync(Document? document,
ICodeFixService codeFixService,
ICodeRefactoringService codeRefactoringService,
LSP.Range selection,
CancellationToken cancellationToken)
{
if (document == null)
{
return ImmutableArray<CodeAction>.Empty;
}

var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);

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 codeActions = codeFixCollections.SelectMany(c => c.Fixes.Select(f => f.Action)).Concat(
codeRefactorings.SelectMany(r => r.CodeActions.Select(ca => ca.action)));

// Flatten out the nested codeactions.
var nestedCodeActions = codeActions.Where(c => c is CodeAction.CodeActionWithNestedActions nc && nc.IsInlinable).SelectMany(nc => nc.NestedCodeActions);
codeActions = codeActions.Where(c => !(c is CodeAction.CodeActionWithNestedActions)).Concat(nestedCodeActions);

return codeActions;
}
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -43,16 +43,16 @@ private static ImmutableDictionary<string, Lazy<IExecuteWorkspaceCommandHandler,
/// 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,
string? clientName, CancellationToken cancellationToken)
public Task<object> HandleRequestAsync(LSP.ExecuteCommandParams request, LSP.ClientCapabilities clientCapabilities, string? clientName,
CancellationToken cancellationToken)
{
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);
return executeCommandHandler.Value.HandleRequestAsync(request, clientCapabilities, cancellationToken);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@ 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);
Task<object> HandleRequestAsync(ExecuteCommandParams request, ClientCapabilities clientCapabilities, CancellationToken cancellationToken);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,18 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler
/// </summary>
[Shared]
[ExportLspMethod(LSP.Methods.TextDocumentCompletionName)]
internal class CompletionHandler : IRequestHandler<LSP.CompletionParams, LSP.CompletionItem[]>
internal class CompletionHandler : AbstractRequestHandler<LSP.CompletionParams, LSP.CompletionItem[]>
{
[ImportingConstructor]
[Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
public CompletionHandler()
public CompletionHandler(ILspSolutionProvider solutionProvider) : base(solutionProvider)
{
}

public async Task<LSP.CompletionItem[]> HandleRequestAsync(Solution solution, LSP.CompletionParams request, LSP.ClientCapabilities clientCapabilities,
string? clientName, CancellationToken cancellationToken)
public override async Task<LSP.CompletionItem[]> HandleRequestAsync(LSP.CompletionParams request, LSP.ClientCapabilities clientCapabilities, string? clientName,
CancellationToken cancellationToken)
{
var document = solution.GetDocument(request.TextDocument, clientName);
var document = SolutionProvider.GetDocument(request.TextDocument, clientName);
if (document == null)
{
return Array.Empty<LSP.CompletionItem>();
Expand Down
Loading

0 comments on commit a85202d

Please sign in to comment.