Skip to content

Commit b83131d

Browse files
authored
Improve initialization, logging, and add more features to cohosting in VS Code (#11788)
Part of #11759 Commit-at-a-time probably makes sense, there are a few things here Goes with dotnet/vscode-csharp#8218
2 parents 8babb3a + 0eb0e9b commit b83131d

File tree

43 files changed

+533
-199
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+533
-199
lines changed

src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/EndpointContracts/IRazorNotificationHandler.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
namespace Microsoft.AspNetCore.Razor.LanguageServer.EndpointContracts;
77

8-
internal interface IRazorNotificationHandler<TRequestType> : INotificationHandler<TRequestType, RazorRequestContext>
8+
internal interface IRazorNotificationHandler<TRequest> : INotificationHandler<TRequest, RazorRequestContext>
99
{
1010
}
1111

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@
44
using System;
55
using System.Collections.Immutable;
66
using System.Composition;
7+
using System.Diagnostics;
78
using System.Threading;
89
using System.Threading.Tasks;
910
using Microsoft.AspNetCore.Razor;
1011
using Microsoft.AspNetCore.Razor.LanguageServer.Hosting;
1112
using Microsoft.CodeAnalysis;
1213
using Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost;
1314
using Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost.Handlers;
15+
using Microsoft.CodeAnalysis.ExternalAccess.Razor.Features;
1416
using Microsoft.CodeAnalysis.Razor.CodeActions;
1517
using Microsoft.CodeAnalysis.Razor.CodeActions.Models;
1618
using Microsoft.CodeAnalysis.Razor.Protocol;
@@ -24,7 +26,7 @@ namespace Microsoft.VisualStudio.Razor.LanguageClient.Cohost;
2426
[Shared]
2527
[CohostEndpoint(Methods.TextDocumentCodeActionName)]
2628
[Export(typeof(IDynamicRegistrationProvider))]
27-
[ExportCohostStatelessLspService(typeof(CohostCodeActionsEndpoint))]
29+
[ExportRazorStatelessLspService(typeof(CohostCodeActionsEndpoint))]
2830
[method: ImportingConstructor]
2931
#pragma warning restore RS0030 // Do not use banned APIs
3032
internal sealed class CohostCodeActionsEndpoint(
@@ -80,9 +82,15 @@ public ImmutableArray<Registration> GetRegistrations(VSInternalClientCapabilitie
8082
return null;
8183
}
8284

85+
// This is just to prevent a warning for an unused field in the VS Code extension
86+
Debug.Assert(_requestInvoker is not null);
87+
8388
var delegatedCodeActions = requestInfo.LanguageKind switch
8489
{
90+
// We don't support Html code actions in VS Code
91+
#if !VSCODE
8592
RazorLanguageKind.Html => await GetHtmlCodeActionsAsync(razorDocument, request, correlationId, cancellationToken).ConfigureAwait(false),
93+
#endif
8694
RazorLanguageKind.CSharp => await GetCSharpCodeActionsAsync(razorDocument, requestInfo.CSharpRequest.AssumeNotNull(), correlationId, cancellationToken).ConfigureAwait(false),
8795
_ => []
8896
};
@@ -110,6 +118,7 @@ private async Task<RazorVSInternalCodeAction[]> GetCSharpCodeActionsAsync(TextDo
110118
return JsonHelpers.ConvertAll<CodeAction, RazorVSInternalCodeAction>(csharpCodeActions);
111119
}
112120

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

128137
return result;
129138
}
139+
#endif
130140

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

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,32 +10,38 @@
1010
using Microsoft.CodeAnalysis;
1111
using Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost;
1212
using Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost.Handlers;
13+
using Microsoft.CodeAnalysis.ExternalAccess.Razor.Features;
1314
using Microsoft.CodeAnalysis.Razor.CodeActions;
1415
using Microsoft.CodeAnalysis.Razor.CodeActions.Models;
1516
using Microsoft.CodeAnalysis.Razor.Formatting;
1617
using Microsoft.CodeAnalysis.Razor.Protocol;
1718
using Microsoft.CodeAnalysis.Razor.Remote;
18-
using Microsoft.VisualStudio.Razor.Settings;
19+
using Microsoft.CodeAnalysis.Razor.Workspaces.Settings;
1920

2021
namespace Microsoft.VisualStudio.Razor.LanguageClient.Cohost;
2122

2223
#pragma warning disable RS0030 // Do not use banned APIs
24+
#if !VSCODE
25+
// Visual Studio requires us to register for every method name, VS Code correctly realises that if you
26+
// register for code actions, and say you have resolve support, then registering for resolve is unnecessary.
27+
// In fact it's an error.
28+
[Export(typeof(IDynamicRegistrationProvider))]
29+
#endif
2330
[Shared]
2431
[CohostEndpoint(Methods.CodeActionResolveName)]
25-
[Export(typeof(IDynamicRegistrationProvider))]
26-
[ExportCohostStatelessLspService(typeof(CohostCodeActionsResolveEndpoint))]
32+
[ExportRazorStatelessLspService(typeof(CohostCodeActionsResolveEndpoint))]
2733
[method: ImportingConstructor]
2834
#pragma warning restore RS0030 // Do not use banned APIs
2935
internal sealed class CohostCodeActionsResolveEndpoint(
3036
IRemoteServiceInvoker remoteServiceInvoker,
3137
IClientCapabilitiesService clientCapabilitiesService,
32-
IClientSettingsManager clientSettingsManager,
38+
IClientSettingsReader clientSettingsManager,
3339
IHtmlRequestInvoker requestInvoker)
3440
: AbstractRazorCohostDocumentRequestHandler<CodeAction, CodeAction?>, IDynamicRegistrationProvider
3541
{
3642
private readonly IRemoteServiceInvoker _remoteServiceInvoker = remoteServiceInvoker;
3743
private readonly IClientCapabilitiesService _clientCapabilitiesService = clientCapabilitiesService;
38-
private readonly IClientSettingsManager _clientSettingsManager = clientSettingsManager;
44+
private readonly IClientSettingsReader _clientSettingsManager = clientSettingsManager;
3945
private readonly IHtmlRequestInvoker _requestInvoker = requestInvoker;
4046

4147
protected override bool MutatesSolutionState => false;

src/Razor/src/Microsoft.CodeAnalysis.Razor.CohostingShared/CohostStartupService.cs

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,45 +5,49 @@
55
using System.Collections.Generic;
66
using System.Collections.Immutable;
77
using System.ComponentModel.Composition;
8+
using System.Linq;
89
using System.Text.Json;
910
using System.Threading;
1011
using System.Threading.Tasks;
1112
using Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost;
13+
using Microsoft.CodeAnalysis.Razor.Logging;
1214
using Microsoft.CodeAnalysis.Razor.Protocol;
13-
using Microsoft.CodeAnalysis.Razor.Workspaces;
1415

1516
namespace Microsoft.VisualStudio.Razor.LanguageClient.Cohost;
1617

1718
[Export(typeof(ICohostStartupService))]
1819
[method: ImportingConstructor]
19-
internal class CohostStartupService(
20-
LanguageServerFeatureOptions languageServerFeatureOptions,
21-
[ImportMany] IEnumerable<Lazy<IRazorCohostStartupService>> lazyStartupServices)
22-
: ICohostStartupService
20+
internal sealed class CohostStartupService(
21+
[ImportMany] IEnumerable<Lazy<IRazorCohostStartupService>> lazyStartupServices,
22+
ILoggerFactory loggerFactory) : ICohostStartupService
2323
{
24-
private readonly LanguageServerFeatureOptions _languageServerFeatureOptions = languageServerFeatureOptions;
2524
private readonly ImmutableArray<Lazy<IRazorCohostStartupService>> _lazyStartupServices = [.. lazyStartupServices];
25+
private readonly ILogger _logger = loggerFactory.GetOrCreateLogger<CohostStartupService>();
2626

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

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

39-
if (!_languageServerFeatureOptions.UseRazorCohostServer)
33+
foreach (var provider in providers)
4034
{
41-
return;
42-
}
35+
if (cancellationToken.IsCancellationRequested)
36+
{
37+
_logger.LogInformation($"Razor extension startup cancelled.");
38+
return;
39+
}
4340

44-
foreach (var provider in _lazyStartupServices)
45-
{
46-
await provider.Value.StartupAsync(clientCapabilities, requestContext, cancellationToken).ConfigureAwait(false);
41+
try
42+
{
43+
await provider.StartupAsync(clientCapabilities, requestContext, cancellationToken).ConfigureAwait(false);
44+
}
45+
catch (Exception ex) when (ex is not OperationCanceledException)
46+
{
47+
_logger.LogError(ex, $"Error initializing Razor startup service '{provider.GetType().Name}'");
48+
}
4749
}
50+
51+
_logger.LogInformation($"Razor extension startup finished.");
4852
}
4953
}
Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
using Microsoft.AspNetCore.Razor.PooledObjects;
1212
using Microsoft.CodeAnalysis;
1313
using Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost;
14+
using Microsoft.CodeAnalysis.ExternalAccess.Razor.Features;
1415
using Microsoft.CodeAnalysis.Razor.Completion;
1516
using Microsoft.CodeAnalysis.Razor.Completion.Delegation;
1617
using Microsoft.CodeAnalysis.Razor.Logging;
@@ -19,8 +20,7 @@
1920
using Microsoft.CodeAnalysis.Razor.Remote;
2021
using Microsoft.CodeAnalysis.Razor.Telemetry;
2122
using Microsoft.CodeAnalysis.Razor.Workspaces;
22-
using Microsoft.VisualStudio.Razor.Settings;
23-
using Microsoft.VisualStudio.Razor.Snippets;
23+
using Microsoft.CodeAnalysis.Razor.Workspaces.Settings;
2424
using Response = Microsoft.CodeAnalysis.Razor.Remote.RemoteResponse<Roslyn.LanguageServer.Protocol.RazorVSInternalCompletionList?>;
2525

2626
namespace Microsoft.VisualStudio.Razor.LanguageClient.Cohost;
@@ -29,14 +29,16 @@ namespace Microsoft.VisualStudio.Razor.LanguageClient.Cohost;
2929
[Shared]
3030
[CohostEndpoint(Methods.TextDocumentCompletionName)]
3131
[Export(typeof(IDynamicRegistrationProvider))]
32-
[ExportCohostStatelessLspService(typeof(CohostDocumentCompletionEndpoint))]
32+
[ExportRazorStatelessLspService(typeof(CohostDocumentCompletionEndpoint))]
3333
[method: ImportingConstructor]
3434
#pragma warning restore RS0030 // Do not use banned APIs
3535
internal sealed class CohostDocumentCompletionEndpoint(
3636
IRemoteServiceInvoker remoteServiceInvoker,
37-
IClientSettingsManager clientSettingsManager,
37+
IClientSettingsReader clientSettingsManager,
3838
IClientCapabilitiesService clientCapabilitiesService,
39-
SnippetCompletionItemProvider snippetCompletionItemProvider,
39+
#pragma warning disable RS0030 // Do not use banned APIs
40+
[Import(AllowDefault = true)] ISnippetCompletionItemProvider? snippetCompletionItemProvider,
41+
#pragma warning restore RS0030 // Do not use banned APIs
4042
LanguageServerFeatureOptions languageServerFeatureOptions,
4143
IHtmlRequestInvoker requestInvoker,
4244
CompletionListCache completionListCache,
@@ -45,9 +47,9 @@ internal sealed class CohostDocumentCompletionEndpoint(
4547
: AbstractRazorCohostDocumentRequestHandler<CompletionParams, RazorVSInternalCompletionList?>, IDynamicRegistrationProvider
4648
{
4749
private readonly IRemoteServiceInvoker _remoteServiceInvoker = remoteServiceInvoker;
48-
private readonly IClientSettingsManager _clientSettingsManager = clientSettingsManager;
50+
private readonly IClientSettingsReader _clientSettingsManager = clientSettingsManager;
4951
private readonly IClientCapabilitiesService _clientCapabilitiesService = clientCapabilitiesService;
50-
private readonly SnippetCompletionItemProvider _snippetCompletionItemProvider = snippetCompletionItemProvider;
52+
private readonly ISnippetCompletionItemProvider? _snippetCompletionItemProvider = snippetCompletionItemProvider;
5153
private readonly CompletionTriggerAndCommitCharacters _triggerAndCommitCharacters = new(languageServerFeatureOptions);
5254
private readonly IHtmlRequestInvoker _requestInvoker = requestInvoker;
5355
private readonly CompletionListCache _completionListCache = completionListCache;
@@ -194,7 +196,8 @@ public ImmutableArray<Registration> GetRegistrations(VSInternalClientCapabilitie
194196
combinedCompletionList = htmlCompletionList;
195197
}
196198

197-
if (completionPositionInfo.ShouldIncludeDelegationSnippets)
199+
if (completionPositionInfo.ShouldIncludeDelegationSnippets &&
200+
_snippetCompletionItemProvider is not null)
198201
{
199202
combinedCompletionList = AddSnippets(
200203
combinedCompletionList,
@@ -250,11 +253,11 @@ public ImmutableArray<Registration> GetRegistrations(VSInternalClientCapabilitie
250253
string? triggerCharacter)
251254
{
252255
using var builder = new PooledArrayBuilder<VSInternalCompletionItem>();
253-
_snippetCompletionItemProvider.AddSnippetCompletions(
256+
_snippetCompletionItemProvider.AssumeNotNull().AddSnippetCompletions(
257+
ref builder.AsRef(),
254258
languageKind,
255259
invokeKind,
256-
triggerCharacter,
257-
ref builder.AsRef());
260+
triggerCharacter);
258261

259262
// If there were no snippets, just return the original list
260263
if (builder.Count == 0)
Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,34 +9,40 @@
99
using Microsoft.AspNetCore.Razor;
1010
using Microsoft.CodeAnalysis;
1111
using Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost;
12+
using Microsoft.CodeAnalysis.ExternalAccess.Razor.Features;
1213
using Microsoft.CodeAnalysis.Razor.Completion;
1314
using Microsoft.CodeAnalysis.Razor.Completion.Delegation;
1415
using Microsoft.CodeAnalysis.Razor.Formatting;
1516
using Microsoft.CodeAnalysis.Razor.Logging;
1617
using Microsoft.CodeAnalysis.Razor.Protocol;
1718
using Microsoft.CodeAnalysis.Razor.Remote;
18-
using Microsoft.VisualStudio.Razor.Settings;
19+
using Microsoft.CodeAnalysis.Razor.Workspaces.Settings;
1920

2021
namespace Microsoft.VisualStudio.Razor.LanguageClient.Cohost;
2122

2223
#pragma warning disable RS0030 // Do not use banned APIs
24+
#if !VSCODE
25+
// Visual Studio requires us to register for every method name, VS Code correctly realises that if you
26+
// register for code actions, and say you have resolve support, then registering for resolve is unnecessary.
27+
// In fact it's an error.
28+
[Export(typeof(IDynamicRegistrationProvider))]
29+
#endif
2330
[Shared]
2431
[CohostEndpoint(Methods.TextDocumentCompletionResolveName)]
25-
[Export(typeof(IDynamicRegistrationProvider))]
26-
[ExportCohostStatelessLspService(typeof(CohostDocumentCompletionResolveEndpoint))]
32+
[ExportRazorStatelessLspService(typeof(CohostDocumentCompletionResolveEndpoint))]
2733
[method: ImportingConstructor]
2834
#pragma warning restore RS0030 // Do not use banned APIs
2935
internal sealed class CohostDocumentCompletionResolveEndpoint(
3036
CompletionListCache completionListCache,
3137
IRemoteServiceInvoker remoteServiceInvoker,
32-
IClientSettingsManager clientSettingsManager,
38+
IClientSettingsReader clientSettingsManager,
3339
IHtmlRequestInvoker requestInvoker,
3440
ILoggerFactory loggerFactory)
3541
: AbstractRazorCohostDocumentRequestHandler<VSInternalCompletionItem, VSInternalCompletionItem?>, IDynamicRegistrationProvider
3642
{
3743
private readonly CompletionListCache _completionListCache = completionListCache;
3844
private readonly IRemoteServiceInvoker _remoteServiceInvoker = remoteServiceInvoker;
39-
private readonly IClientSettingsManager _clientSettingsManager = clientSettingsManager;
45+
private readonly IClientSettingsReader _clientSettingsManager = clientSettingsManager;
4046
private readonly IHtmlRequestInvoker _requestInvoker = requestInvoker;
4147
private readonly ILogger _logger = loggerFactory.GetOrCreateLogger<CohostDocumentCompletionEndpoint>();
4248

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

101+
#if VSCODE
102+
Debug.Assert(_requestInvoker is not null);
103+
Debug.Assert(_logger is not null);
104+
Debug.Assert(nameof(DelegatedCompletionHelper).Length > 0);
105+
106+
// We don't support completion resolve in VS Code
107+
return completionItem;
108+
#else
95109
completionItem.Data = DelegatedCompletionHelper.GetOriginalCompletionItemData(completionItem, completionList, delegatedContext.OriginalCompletionListData);
96110
return await ResolveHtmlCompletionItemAsync(completionItem, razorDocument, cancellationToken).ConfigureAwait(false);
111+
#endif
97112
}
98113

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

137+
#if !VSCODE
122138
private async Task<VSInternalCompletionItem> ResolveHtmlCompletionItemAsync(
123139
VSInternalCompletionItem request,
124140
TextDocument razorDocument,
@@ -134,6 +150,7 @@ private async Task<VSInternalCompletionItem> ResolveHtmlCompletionItemAsync(
134150

135151
return result ?? request;
136152
}
153+
#endif
137154

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

0 commit comments

Comments
 (0)