Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
2067838
Copy ObjectPool.dll to output folder
davidwengier Apr 26, 2025
b417850
Make initialization much more reliable, and easier to diagnose
davidwengier Apr 26, 2025
ae431ae
Extract interface for better MEF
davidwengier Apr 26, 2025
556f228
Extract base class for shared functionality
davidwengier Apr 26, 2025
63c3f19
Move extension method down
davidwengier Apr 26, 2025
250e04d
Move endpoints
davidwengier Apr 26, 2025
4fd540f
Create diagnostics endpoint
davidwengier Apr 26, 2025
226d3a1
Fix logging
davidwengier Apr 27, 2025
4ae7e03
Log startup so we can start to see timing info
davidwengier Apr 27, 2025
3c8611e
Fix some telemetry
davidwengier Apr 27, 2025
1dc6084
Redo diagnostics to use public types for VS Code
davidwengier Apr 27, 2025
ef71287
Disable the bits of features we don't support in VS Code
davidwengier Apr 27, 2025
b4f16f7
Make a proper system for getting at the workspace in cohosting, and p…
davidwengier Apr 28, 2025
2774561
Create well known startup order
davidwengier Apr 29, 2025
cd87207
Parameter order
davidwengier Apr 29, 2025
ecbc7a7
Use VerifyCompleted
davidwengier Apr 29, 2025
80647c0
Use exception filter
davidwengier Apr 29, 2025
d61a3d2
Rename type parameters
davidwengier Apr 29, 2025
bdc538b
Clean up diagnostics endpoint API a bit
davidwengier Apr 29, 2025
6376ba6
One workspace provider interface is enough
davidwengier Apr 29, 2025
cd1c7c1
Seal classes
davidwengier Apr 29, 2025
a19208c
Use Assumed.Unreachable
davidwengier Apr 29, 2025
0eb0e9b
PR Feedback
davidwengier Apr 30, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

namespace Microsoft.AspNetCore.Razor.LanguageServer.EndpointContracts;

internal interface IRazorNotificationHandler<TRequestType> : INotificationHandler<TRequestType, RazorRequestContext>
internal interface IRazorNotificationHandler<TRequest> : INotificationHandler<TRequest, RazorRequestContext>
{
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@
using System;
using System.Collections.Immutable;
using System.Composition;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor;
using Microsoft.AspNetCore.Razor.LanguageServer.Hosting;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost;
using Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost.Handlers;
using Microsoft.CodeAnalysis.ExternalAccess.Razor.Features;
using Microsoft.CodeAnalysis.Razor.CodeActions;
using Microsoft.CodeAnalysis.Razor.CodeActions.Models;
using Microsoft.CodeAnalysis.Razor.Protocol;
Expand All @@ -24,7 +26,7 @@ namespace Microsoft.VisualStudio.Razor.LanguageClient.Cohost;
[Shared]
[CohostEndpoint(Methods.TextDocumentCodeActionName)]
[Export(typeof(IDynamicRegistrationProvider))]
[ExportCohostStatelessLspService(typeof(CohostCodeActionsEndpoint))]
[ExportRazorStatelessLspService(typeof(CohostCodeActionsEndpoint))]
[method: ImportingConstructor]
#pragma warning restore RS0030 // Do not use banned APIs
internal sealed class CohostCodeActionsEndpoint(
Expand Down Expand Up @@ -80,9 +82,15 @@ public ImmutableArray<Registration> GetRegistrations(VSInternalClientCapabilitie
return null;
}

// This is just to prevent a warning for an unused field in the VS Code extension
Debug.Assert(_requestInvoker is not null);

var delegatedCodeActions = requestInfo.LanguageKind switch
{
// We don't support Html code actions in VS Code
#if !VSCODE
RazorLanguageKind.Html => await GetHtmlCodeActionsAsync(razorDocument, request, correlationId, cancellationToken).ConfigureAwait(false),
#endif
RazorLanguageKind.CSharp => await GetCSharpCodeActionsAsync(razorDocument, requestInfo.CSharpRequest.AssumeNotNull(), correlationId, cancellationToken).ConfigureAwait(false),
_ => []
};
Expand Down Expand Up @@ -110,6 +118,7 @@ private async Task<RazorVSInternalCodeAction[]> GetCSharpCodeActionsAsync(TextDo
return JsonHelpers.ConvertAll<CodeAction, RazorVSInternalCodeAction>(csharpCodeActions);
}

#if !VSCODE
private async Task<RazorVSInternalCodeAction[]> GetHtmlCodeActionsAsync(TextDocument razorDocument, VSCodeActionParams request, Guid correlationId, CancellationToken cancellationToken)
{
var result = await _requestInvoker.MakeHtmlLspRequestAsync<VSCodeActionParams, RazorVSInternalCodeAction[]>(
Expand All @@ -127,6 +136,7 @@ private async Task<RazorVSInternalCodeAction[]> GetHtmlCodeActionsAsync(TextDocu

return result;
}
#endif

internal TestAccessor GetTestAccessor() => new(this);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,32 +10,38 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost;
using Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost.Handlers;
using Microsoft.CodeAnalysis.ExternalAccess.Razor.Features;
using Microsoft.CodeAnalysis.Razor.CodeActions;
using Microsoft.CodeAnalysis.Razor.CodeActions.Models;
using Microsoft.CodeAnalysis.Razor.Formatting;
using Microsoft.CodeAnalysis.Razor.Protocol;
using Microsoft.CodeAnalysis.Razor.Remote;
using Microsoft.VisualStudio.Razor.Settings;
using Microsoft.CodeAnalysis.Razor.Workspaces.Settings;

namespace Microsoft.VisualStudio.Razor.LanguageClient.Cohost;

#pragma warning disable RS0030 // Do not use banned APIs
#if !VSCODE
// Visual Studio requires us to register for every method name, VS Code correctly realises that if you
// register for code actions, and say you have resolve support, then registering for resolve is unnecessary.
// In fact it's an error.
[Export(typeof(IDynamicRegistrationProvider))]
#endif
[Shared]
[CohostEndpoint(Methods.CodeActionResolveName)]
[Export(typeof(IDynamicRegistrationProvider))]
[ExportCohostStatelessLspService(typeof(CohostCodeActionsResolveEndpoint))]
[ExportRazorStatelessLspService(typeof(CohostCodeActionsResolveEndpoint))]
[method: ImportingConstructor]
#pragma warning restore RS0030 // Do not use banned APIs
internal sealed class CohostCodeActionsResolveEndpoint(
IRemoteServiceInvoker remoteServiceInvoker,
IClientCapabilitiesService clientCapabilitiesService,
IClientSettingsManager clientSettingsManager,
IClientSettingsReader clientSettingsManager,
IHtmlRequestInvoker requestInvoker)
: AbstractRazorCohostDocumentRequestHandler<CodeAction, CodeAction?>, IDynamicRegistrationProvider
{
private readonly IRemoteServiceInvoker _remoteServiceInvoker = remoteServiceInvoker;
private readonly IClientCapabilitiesService _clientCapabilitiesService = clientCapabilitiesService;
private readonly IClientSettingsManager _clientSettingsManager = clientSettingsManager;
private readonly IClientSettingsReader _clientSettingsManager = clientSettingsManager;
private readonly IHtmlRequestInvoker _requestInvoker = requestInvoker;

protected override bool MutatesSolutionState => false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,45 +5,49 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.ComponentModel.Composition;
using System.Linq;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost;
using Microsoft.CodeAnalysis.Razor.Logging;
using Microsoft.CodeAnalysis.Razor.Protocol;
using Microsoft.CodeAnalysis.Razor.Workspaces;

namespace Microsoft.VisualStudio.Razor.LanguageClient.Cohost;

[Export(typeof(ICohostStartupService))]
[method: ImportingConstructor]
internal class CohostStartupService(
LanguageServerFeatureOptions languageServerFeatureOptions,
[ImportMany] IEnumerable<Lazy<IRazorCohostStartupService>> lazyStartupServices)
: ICohostStartupService
internal sealed class CohostStartupService(
[ImportMany] IEnumerable<Lazy<IRazorCohostStartupService>> lazyStartupServices,
ILoggerFactory loggerFactory) : ICohostStartupService
{
private readonly LanguageServerFeatureOptions _languageServerFeatureOptions = languageServerFeatureOptions;
private readonly ImmutableArray<Lazy<IRazorCohostStartupService>> _lazyStartupServices = [.. lazyStartupServices];
private readonly ILogger _logger = loggerFactory.GetOrCreateLogger<CohostStartupService>();

public async Task StartupAsync(string clientCapabilitiesString, RazorCohostRequestContext requestContext, CancellationToken cancellationToken)
{
var clientCapabilities = JsonSerializer.Deserialize<VSInternalClientCapabilities>(clientCapabilitiesString, JsonHelpers.JsonSerializerOptions) ?? new();

// We want to make sure LanguageServerFeatureOptions is initialized first, so decisions can be made based on flags,
// and we also ensure we have a RazorClientLanguageServerManager available for anything that needs to call back to the client,
// so we do those two things first, manually.
if (_languageServerFeatureOptions is IRazorCohostStartupService startupService)
{
await startupService.StartupAsync(clientCapabilities, requestContext, cancellationToken).ConfigureAwait(false);
}
var providers = _lazyStartupServices.Select(p => p.Value).OrderByAsArray(p => p.Order);

if (!_languageServerFeatureOptions.UseRazorCohostServer)
foreach (var provider in providers)
{
return;
}
if (cancellationToken.IsCancellationRequested)
{
_logger.LogInformation($"Razor extension startup cancelled.");
return;
}

foreach (var provider in _lazyStartupServices)
{
await provider.Value.StartupAsync(clientCapabilities, requestContext, cancellationToken).ConfigureAwait(false);
try
{
await provider.StartupAsync(clientCapabilities, requestContext, cancellationToken).ConfigureAwait(false);
}
catch (Exception ex) when (ex is not OperationCanceledException)
{
_logger.LogError(ex, $"Error initializing Razor startup service '{provider.GetType().Name}'");
}
}

_logger.LogInformation($"Razor extension startup finished.");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using Microsoft.AspNetCore.Razor.PooledObjects;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost;
using Microsoft.CodeAnalysis.ExternalAccess.Razor.Features;
using Microsoft.CodeAnalysis.Razor.Completion;
using Microsoft.CodeAnalysis.Razor.Completion.Delegation;
using Microsoft.CodeAnalysis.Razor.Logging;
Expand All @@ -19,8 +20,7 @@
using Microsoft.CodeAnalysis.Razor.Remote;
using Microsoft.CodeAnalysis.Razor.Telemetry;
using Microsoft.CodeAnalysis.Razor.Workspaces;
using Microsoft.VisualStudio.Razor.Settings;
using Microsoft.VisualStudio.Razor.Snippets;
using Microsoft.CodeAnalysis.Razor.Workspaces.Settings;
using Response = Microsoft.CodeAnalysis.Razor.Remote.RemoteResponse<Roslyn.LanguageServer.Protocol.RazorVSInternalCompletionList?>;

namespace Microsoft.VisualStudio.Razor.LanguageClient.Cohost;
Expand All @@ -29,14 +29,16 @@ namespace Microsoft.VisualStudio.Razor.LanguageClient.Cohost;
[Shared]
[CohostEndpoint(Methods.TextDocumentCompletionName)]
[Export(typeof(IDynamicRegistrationProvider))]
[ExportCohostStatelessLspService(typeof(CohostDocumentCompletionEndpoint))]
[ExportRazorStatelessLspService(typeof(CohostDocumentCompletionEndpoint))]
[method: ImportingConstructor]
#pragma warning restore RS0030 // Do not use banned APIs
internal sealed class CohostDocumentCompletionEndpoint(
IRemoteServiceInvoker remoteServiceInvoker,
IClientSettingsManager clientSettingsManager,
IClientSettingsReader clientSettingsManager,
IClientCapabilitiesService clientCapabilitiesService,
SnippetCompletionItemProvider snippetCompletionItemProvider,
#pragma warning disable RS0030 // Do not use banned APIs
[Import(AllowDefault = true)] ISnippetCompletionItemProvider? snippetCompletionItemProvider,
#pragma warning restore RS0030 // Do not use banned APIs
LanguageServerFeatureOptions languageServerFeatureOptions,
IHtmlRequestInvoker requestInvoker,
CompletionListCache completionListCache,
Expand All @@ -45,9 +47,9 @@ internal sealed class CohostDocumentCompletionEndpoint(
: AbstractRazorCohostDocumentRequestHandler<CompletionParams, RazorVSInternalCompletionList?>, IDynamicRegistrationProvider
{
private readonly IRemoteServiceInvoker _remoteServiceInvoker = remoteServiceInvoker;
private readonly IClientSettingsManager _clientSettingsManager = clientSettingsManager;
private readonly IClientSettingsReader _clientSettingsManager = clientSettingsManager;
private readonly IClientCapabilitiesService _clientCapabilitiesService = clientCapabilitiesService;
private readonly SnippetCompletionItemProvider _snippetCompletionItemProvider = snippetCompletionItemProvider;
private readonly ISnippetCompletionItemProvider? _snippetCompletionItemProvider = snippetCompletionItemProvider;
private readonly CompletionTriggerAndCommitCharacters _triggerAndCommitCharacters = new(languageServerFeatureOptions);
private readonly IHtmlRequestInvoker _requestInvoker = requestInvoker;
private readonly CompletionListCache _completionListCache = completionListCache;
Expand Down Expand Up @@ -194,7 +196,8 @@ public ImmutableArray<Registration> GetRegistrations(VSInternalClientCapabilitie
combinedCompletionList = htmlCompletionList;
}

if (completionPositionInfo.ShouldIncludeDelegationSnippets)
if (completionPositionInfo.ShouldIncludeDelegationSnippets &&
_snippetCompletionItemProvider is not null)
{
combinedCompletionList = AddSnippets(
combinedCompletionList,
Expand Down Expand Up @@ -250,11 +253,11 @@ public ImmutableArray<Registration> GetRegistrations(VSInternalClientCapabilitie
string? triggerCharacter)
{
using var builder = new PooledArrayBuilder<VSInternalCompletionItem>();
_snippetCompletionItemProvider.AddSnippetCompletions(
_snippetCompletionItemProvider.AssumeNotNull().AddSnippetCompletions(
ref builder.AsRef(),
languageKind,
invokeKind,
triggerCharacter,
ref builder.AsRef());
triggerCharacter);

// If there were no snippets, just return the original list
if (builder.Count == 0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,34 +9,40 @@
using Microsoft.AspNetCore.Razor;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost;
using Microsoft.CodeAnalysis.ExternalAccess.Razor.Features;
using Microsoft.CodeAnalysis.Razor.Completion;
using Microsoft.CodeAnalysis.Razor.Completion.Delegation;
using Microsoft.CodeAnalysis.Razor.Formatting;
using Microsoft.CodeAnalysis.Razor.Logging;
using Microsoft.CodeAnalysis.Razor.Protocol;
using Microsoft.CodeAnalysis.Razor.Remote;
using Microsoft.VisualStudio.Razor.Settings;
using Microsoft.CodeAnalysis.Razor.Workspaces.Settings;

namespace Microsoft.VisualStudio.Razor.LanguageClient.Cohost;

#pragma warning disable RS0030 // Do not use banned APIs
#if !VSCODE
// Visual Studio requires us to register for every method name, VS Code correctly realises that if you
// register for code actions, and say you have resolve support, then registering for resolve is unnecessary.
// In fact it's an error.
[Export(typeof(IDynamicRegistrationProvider))]
#endif
[Shared]
[CohostEndpoint(Methods.TextDocumentCompletionResolveName)]
[Export(typeof(IDynamicRegistrationProvider))]
[ExportCohostStatelessLspService(typeof(CohostDocumentCompletionResolveEndpoint))]
[ExportRazorStatelessLspService(typeof(CohostDocumentCompletionResolveEndpoint))]
[method: ImportingConstructor]
#pragma warning restore RS0030 // Do not use banned APIs
internal sealed class CohostDocumentCompletionResolveEndpoint(
CompletionListCache completionListCache,
IRemoteServiceInvoker remoteServiceInvoker,
IClientSettingsManager clientSettingsManager,
IClientSettingsReader clientSettingsManager,
IHtmlRequestInvoker requestInvoker,
ILoggerFactory loggerFactory)
: AbstractRazorCohostDocumentRequestHandler<VSInternalCompletionItem, VSInternalCompletionItem?>, IDynamicRegistrationProvider
{
private readonly CompletionListCache _completionListCache = completionListCache;
private readonly IRemoteServiceInvoker _remoteServiceInvoker = remoteServiceInvoker;
private readonly IClientSettingsManager _clientSettingsManager = clientSettingsManager;
private readonly IClientSettingsReader _clientSettingsManager = clientSettingsManager;
private readonly IHtmlRequestInvoker _requestInvoker = requestInvoker;
private readonly ILogger _logger = loggerFactory.GetOrCreateLogger<CohostDocumentCompletionEndpoint>();

Expand Down Expand Up @@ -92,8 +98,17 @@ public ImmutableArray<Registration> GetRegistrations(VSInternalClientCapabilitie
{
Debug.Assert(delegatedContext.ProjectedKind == RazorLanguageKind.Html);

#if VSCODE
Debug.Assert(_requestInvoker is not null);
Debug.Assert(_logger is not null);
Debug.Assert(nameof(DelegatedCompletionHelper).Length > 0);

// We don't support completion resolve in VS Code
return completionItem;
#else
completionItem.Data = DelegatedCompletionHelper.GetOriginalCompletionItemData(completionItem, completionList, delegatedContext.OriginalCompletionListData);
return await ResolveHtmlCompletionItemAsync(completionItem, razorDocument, cancellationToken).ConfigureAwait(false);
#endif
}

var clientSettings = _clientSettingsManager.GetClientSettings();
Expand All @@ -119,6 +134,7 @@ public ImmutableArray<Registration> GetRegistrations(VSInternalClientCapabilitie
return result;
}

#if !VSCODE
private async Task<VSInternalCompletionItem> ResolveHtmlCompletionItemAsync(
VSInternalCompletionItem request,
TextDocument razorDocument,
Expand All @@ -134,6 +150,7 @@ private async Task<VSInternalCompletionItem> ResolveHtmlCompletionItemAsync(

return result ?? request;
}
#endif

internal TestAccessor GetTestAccessor() => new(this);

Expand Down
Loading