Introduce service for getting code actions from delegated servers

This commit is contained in:
David Wengier 2024-10-24 13:24:25 +11:00
Родитель 03e900f144
Коммит 001ebcfa7e
7 изменённых файлов: 97 добавлений и 45 удалений

Просмотреть файл

@ -12,8 +12,8 @@ using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.LanguageServer.CodeActions;
using Microsoft.AspNetCore.Razor.LanguageServer.EndpointContracts;
using Microsoft.AspNetCore.Razor.LanguageServer.Hosting;
using Microsoft.AspNetCore.Razor.Telemetry;
using Microsoft.CodeAnalysis.Razor.DocumentMapping;
using Microsoft.CodeAnalysis.Razor.Logging;
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
using Microsoft.CodeAnalysis.Razor.Protocol.CodeActions;
using Microsoft.CodeAnalysis.Razor.Workspaces;
@ -53,10 +53,9 @@ public class RazorCodeActionsBenchmark : RazorLanguageServerBenchmarkBase
razorCodeActionProviders: RazorLanguageServerHost.GetRequiredService<IEnumerable<IRazorCodeActionProvider>>(),
csharpCodeActionProviders: RazorLanguageServerHost.GetRequiredService<IEnumerable<ICSharpCodeActionProvider>>(),
htmlCodeActionProviders: RazorLanguageServerHost.GetRequiredService<IEnumerable<IHtmlCodeActionProvider>>(),
clientConnection: RazorLanguageServerHost.GetRequiredService<IClientConnection>(),
delegatedCodeActionsProvider: RazorLanguageServerHost.GetRequiredService<IDelegatedCodeActionsProvider>(),
languageServerFeatureOptions: RazorLanguageServerHost.GetRequiredService<LanguageServerFeatureOptions>(),
loggerFactory: RazorLanguageServerHost.GetRequiredService<ILoggerFactory>(),
telemetryReporter: null);
telemetryReporter: NoOpTelemetryReporter.Instance);
var projectRoot = Path.Combine(RepoRoot, "src", "Razor", "test", "testapps", "ComponentApp");
var projectFilePath = Path.Combine(projectRoot, "ComponentApp.csproj");

Просмотреть файл

@ -12,9 +12,9 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.LanguageServer.CodeActions.Models;
using Microsoft.AspNetCore.Razor.LanguageServer.EndpointContracts;
using Microsoft.AspNetCore.Razor.LanguageServer.Hosting;
using Microsoft.AspNetCore.Razor.PooledObjects;
using Microsoft.AspNetCore.Razor.Telemetry;
using Microsoft.AspNetCore.Razor.Threading;
using Microsoft.CodeAnalysis.ExternalAccess.Razor;
using Microsoft.CodeAnalysis.Razor.DocumentMapping;
using Microsoft.CodeAnalysis.Razor.Logging;
@ -24,7 +24,6 @@ using Microsoft.CodeAnalysis.Razor.Protocol.CodeActions;
using Microsoft.CodeAnalysis.Razor.Workspaces;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.LanguageServer.Protocol;
using StreamJsonRpc;
namespace Microsoft.AspNetCore.Razor.LanguageServer.CodeActions;
@ -34,22 +33,20 @@ internal sealed class CodeActionEndpoint(
IEnumerable<IRazorCodeActionProvider> razorCodeActionProviders,
IEnumerable<ICSharpCodeActionProvider> csharpCodeActionProviders,
IEnumerable<IHtmlCodeActionProvider> htmlCodeActionProviders,
IClientConnection clientConnection,
IDelegatedCodeActionsProvider delegatedCodeActionsProvider,
LanguageServerFeatureOptions languageServerFeatureOptions,
ILoggerFactory loggerFactory,
ITelemetryReporter? telemetryReporter)
ITelemetryReporter telemetryReporter)
: IRazorRequestHandler<VSCodeActionParams, SumType<Command, CodeAction>[]?>, ICapabilitiesProvider
{
private const string LspEndpointName = Methods.TextDocumentCodeActionName;
private readonly IDocumentMappingService _documentMappingService = documentMappingService ?? throw new ArgumentNullException(nameof(documentMappingService));
private readonly IEnumerable<IRazorCodeActionProvider> _razorCodeActionProviders = razorCodeActionProviders ?? throw new ArgumentNullException(nameof(razorCodeActionProviders));
private readonly IEnumerable<ICSharpCodeActionProvider> _csharpCodeActionProviders = csharpCodeActionProviders ?? throw new ArgumentNullException(nameof(csharpCodeActionProviders));
private readonly IEnumerable<IHtmlCodeActionProvider> _htmlCodeActionProviders = htmlCodeActionProviders ?? throw new ArgumentNullException(nameof(htmlCodeActionProviders));
private readonly LanguageServerFeatureOptions _languageServerFeatureOptions = languageServerFeatureOptions ?? throw new ArgumentNullException(nameof(languageServerFeatureOptions));
private readonly IClientConnection _clientConnection = clientConnection ?? throw new ArgumentNullException(nameof(clientConnection));
private readonly ILogger _logger = loggerFactory.GetOrCreateLogger<CodeActionEndpoint>();
private readonly ITelemetryReporter? _telemetryReporter = telemetryReporter;
private readonly IDocumentMappingService _documentMappingService = documentMappingService;
private readonly IEnumerable<IRazorCodeActionProvider> _razorCodeActionProviders = razorCodeActionProviders;
private readonly IEnumerable<ICSharpCodeActionProvider> _csharpCodeActionProviders = csharpCodeActionProviders;
private readonly IEnumerable<IHtmlCodeActionProvider> _htmlCodeActionProviders = htmlCodeActionProviders;
private readonly LanguageServerFeatureOptions _languageServerFeatureOptions = languageServerFeatureOptions;
private readonly IDelegatedCodeActionsProvider _delegatedCodeActionsProvider = delegatedCodeActionsProvider;
private readonly ITelemetryReporter _telemetryReporter = telemetryReporter;
internal bool _supportsCodeActionResolve = false;
@ -74,7 +71,7 @@ internal sealed class CodeActionEndpoint(
cancellationToken.ThrowIfCancellationRequested();
var correlationId = Guid.NewGuid();
using var __ = _telemetryReporter?.TrackLspRequest(LspEndpointName, LanguageServerConstants.RazorLanguageServerName, correlationId);
using var __ = _telemetryReporter.TrackLspRequest(LspEndpointName, LanguageServerConstants.RazorLanguageServerName, correlationId);
var razorCodeActions = await GetRazorCodeActionsAsync(razorCodeActionContext, cancellationToken).ConfigureAwait(false);
cancellationToken.ThrowIfCancellationRequested();
@ -265,14 +262,14 @@ internal sealed class CodeActionEndpoint(
}
// Internal for testing
internal async Task<RazorVSInternalCodeAction[]> GetCodeActionsFromLanguageServerAsync(RazorLanguageKind languageKind, RazorCodeActionContext context, Guid correlationId, CancellationToken cancellationToken)
internal Task<RazorVSInternalCodeAction[]> GetCodeActionsFromLanguageServerAsync(RazorLanguageKind languageKind, RazorCodeActionContext context, Guid correlationId, CancellationToken cancellationToken)
{
if (languageKind == RazorLanguageKind.CSharp)
{
// For C# we have to map the ranges to the generated document
if (!_documentMappingService.TryMapToGeneratedDocumentRange(context.CodeDocument.GetCSharpDocument(), context.Request.Range, out var projectedRange))
{
return [];
return SpecializedTasks.EmptyArray<RazorVSInternalCodeAction>();
}
var newContext = context.Request.Context;
@ -288,25 +285,7 @@ internal sealed class CodeActionEndpoint(
}
cancellationToken.ThrowIfCancellationRequested();
var delegatedParams = new DelegatedCodeActionParams()
{
HostDocumentVersion = context.DocumentSnapshot.Version,
CodeActionParams = context.Request,
LanguageKind = languageKind,
CorrelationId = correlationId
};
try
{
return await _clientConnection.SendRequestAsync<DelegatedCodeActionParams, RazorVSInternalCodeAction[]>(CustomMessageNames.RazorProvideCodeActionsEndpoint, delegatedParams, cancellationToken).ConfigureAwait(false);
}
catch (RemoteInvocationException e)
{
_telemetryReporter?.ReportFault(e, "Error getting code actions from delegate language server for {languageKind}", languageKind);
_logger.LogError(e, $"Error getting code actions from delegate language server for {languageKind}");
return [];
}
return _delegatedCodeActionsProvider.GetDelegatedCodeActionsAsync(languageKind, context.Request, context.DocumentSnapshot.Version, correlationId, cancellationToken);
}
private async Task<ImmutableArray<RazorVSInternalCodeAction>> GetRazorCodeActionsAsync(RazorCodeActionContext context, CancellationToken cancellationToken)

Просмотреть файл

@ -0,0 +1,51 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.LanguageServer.CodeActions.Models;
using Microsoft.AspNetCore.Razor.LanguageServer.Hosting;
using Microsoft.AspNetCore.Razor.Telemetry;
using Microsoft.CodeAnalysis.Razor.Logging;
using Microsoft.CodeAnalysis.Razor.Protocol;
using Microsoft.CodeAnalysis.Razor.Protocol.CodeActions;
using Microsoft.VisualStudio.RpcContracts;
using StreamJsonRpc;
namespace Microsoft.AspNetCore.Razor.LanguageServer.CodeActions;
internal sealed class DelegatedCodeActionsProvider(
IClientConnection clientConnection,
ITelemetryReporter telemetryReporter,
ILoggerFactory loggerFactory) : IDelegatedCodeActionsProvider
{
private readonly IClientConnection _clientConnection = clientConnection;
private readonly ITelemetryReporter _telemetryReporter = telemetryReporter;
private readonly ILogger _logger = loggerFactory.GetOrCreateLogger<DelegatedCodeActionsProvider>();
public async Task<RazorVSInternalCodeAction[]> GetDelegatedCodeActionsAsync(RazorLanguageKind languageKind, VSCodeActionParams request, int hostDocumentVersion, Guid correlationId, CancellationToken cancellationToken)
{
var delegatedParams = new DelegatedCodeActionParams()
{
HostDocumentVersion = hostDocumentVersion,
CodeActionParams = request,
LanguageKind = languageKind,
CorrelationId = correlationId
};
try
{
return await _clientConnection.SendRequestAsync<DelegatedCodeActionParams, RazorVSInternalCodeAction[]>(CustomMessageNames.RazorProvideCodeActionsEndpoint, delegatedParams, cancellationToken).ConfigureAwait(false);
}
catch (RemoteInvocationException e)
{
_telemetryReporter.ReportFault(e, "Error getting code actions from delegate language server for {languageKind}", languageKind);
_logger.LogError(e, $"Error getting code actions from delegate language server for {languageKind}");
return [];
}
}
}

Просмотреть файл

@ -0,0 +1,16 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license information.
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.LanguageServer.CodeActions.Models;
using Microsoft.CodeAnalysis.Razor.Protocol;
using Microsoft.CodeAnalysis.Razor.Protocol.CodeActions;
namespace Microsoft.AspNetCore.Razor.LanguageServer.CodeActions;
internal interface IDelegatedCodeActionsProvider
{
Task<RazorVSInternalCodeAction[]> GetDelegatedCodeActionsAsync(RazorLanguageKind languageKind, VSCodeActionParams request, int hostDocumentVersion, Guid correlationId, CancellationToken cancellationToken);
}

Просмотреть файл

@ -137,6 +137,7 @@ internal static class IServiceCollectionExtensions
services.AddHandlerWithCapabilities<CodeActionEndpoint>();
services.AddHandler<CodeActionResolveEndpoint>();
services.AddSingleton<IDelegatedCodeActionsProvider, DelegatedCodeActionsProvider>();
services.AddSingleton<IDelegatedCodeActionResolver, DelegatedCodeActionResolver>();
// CSharp Code actions

Просмотреть файл

@ -15,6 +15,7 @@ using Microsoft.AspNetCore.Razor.LanguageServer.CodeActions.Razor;
using Microsoft.AspNetCore.Razor.LanguageServer.EndpointContracts;
using Microsoft.AspNetCore.Razor.LanguageServer.Formatting;
using Microsoft.AspNetCore.Razor.LanguageServer.Hosting;
using Microsoft.AspNetCore.Razor.Telemetry;
using Microsoft.AspNetCore.Razor.Test.Common.LanguageServer;
using Microsoft.AspNetCore.Razor.Test.Common.Workspaces;
using Microsoft.AspNetCore.Razor.Utilities;
@ -1160,6 +1161,7 @@ public class CodeActionEndToEndTest(ITestOutputHelper testOutput) : SingleServer
IRazorCodeActionProvider[]? razorProviders = null,
Diagnostic[]? diagnostics = null)
{
var delegatedCodeActionsProvider = new DelegatedCodeActionsProvider(clientConnection, NoOpTelemetryReporter.Instance, LoggerFactory);
var endpoint = new CodeActionEndpoint(
DocumentMappingService.AssumeNotNull(),
razorCodeActionProviders: razorProviders ?? [],
@ -1169,10 +1171,9 @@ public class CodeActionEndToEndTest(ITestOutputHelper testOutput) : SingleServer
new TypeAccessibilityCodeActionProvider()
],
htmlCodeActionProviders: [],
clientConnection,
delegatedCodeActionsProvider,
LanguageServerFeatureOptions.AssumeNotNull(),
LoggerFactory,
telemetryReporter: null);
NoOpTelemetryReporter.Instance);
// Call GetRegistration, so the endpoint knows we support resolve
endpoint.ApplyCapabilities(new(), new VSInternalClientCapabilities

Просмотреть файл

@ -12,6 +12,7 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.LanguageServer.CodeActions.Models;
using Microsoft.AspNetCore.Razor.LanguageServer.Hosting;
using Microsoft.AspNetCore.Razor.Telemetry;
using Microsoft.AspNetCore.Razor.Test.Common;
using Microsoft.AspNetCore.Razor.Test.Common.LanguageServer;
using Microsoft.CodeAnalysis.Razor.DocumentMapping;
@ -591,15 +592,19 @@ public class CodeActionEndpointTest(ITestOutputHelper testOutput) : LanguageServ
LanguageServerFeatureOptions? languageServerFeatureOptions = null,
bool supportsCodeActionResolve = false)
{
var delegatedCodeActionsProvider = new DelegatedCodeActionsProvider(
clientConnection ?? StrictMock.Of<IClientConnection>(),
NoOpTelemetryReporter.Instance,
LoggerFactory);
return new CodeActionEndpoint(
documentMappingService ?? CreateDocumentMappingService(),
razorCodeActionProviders.NullToEmpty(),
csharpCodeActionProviders.NullToEmpty(),
htmlCodeActionProviders.NullToEmpty(),
clientConnection ?? StrictMock.Of<IClientConnection>(),
delegatedCodeActionsProvider,
languageServerFeatureOptions ?? StrictMock.Of<LanguageServerFeatureOptions>(x => x.SupportsFileManipulation == true),
LoggerFactory,
telemetryReporter: null)
NoOpTelemetryReporter.Instance)
{
_supportsCodeActionResolve = supportsCodeActionResolve
};