Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cohosting: Update Html virtual document from OOP #10175

Merged
merged 7 commits into from
Mar 30, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
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 @@ -8,21 +8,16 @@

namespace Microsoft.AspNetCore.Razor.LanguageServer;

internal class GeneratedDocumentSynchronizer : DocumentProcessedListener
internal class GeneratedDocumentSynchronizer(
IGeneratedDocumentPublisher publisher,
IDocumentVersionCache documentVersionCache,
ProjectSnapshotManagerDispatcher dispatcher,
LanguageServerFeatureOptions languageServerFeatureOptions) : DocumentProcessedListener
{
private readonly IGeneratedDocumentPublisher _publisher;
private readonly IDocumentVersionCache _documentVersionCache;
private readonly ProjectSnapshotManagerDispatcher _dispatcher;

public GeneratedDocumentSynchronizer(
IGeneratedDocumentPublisher publisher,
IDocumentVersionCache documentVersionCache,
ProjectSnapshotManagerDispatcher dispatcher)
{
_publisher = publisher;
_documentVersionCache = documentVersionCache;
_dispatcher = dispatcher;
}
private readonly IGeneratedDocumentPublisher _publisher = publisher;
private readonly IDocumentVersionCache _documentVersionCache = documentVersionCache;
private readonly ProjectSnapshotManagerDispatcher _dispatcher = dispatcher;
private readonly LanguageServerFeatureOptions _languageServerFeatureOptions = languageServerFeatureOptions;

public override void Initialize(IProjectSnapshotManager projectManager)
{
Expand All @@ -40,9 +35,13 @@ public override void DocumentProcessed(RazorCodeDocument codeDocument, IDocument

var filePath = document.FilePath.AssumeNotNull();

var htmlText = codeDocument.GetHtmlSourceText();
// If cohosting is on, then it is responsible for updating the Html buffer
if (!_languageServerFeatureOptions.UseRazorCohostServer)
{
var htmlText = codeDocument.GetHtmlSourceText();

_publisher.PublishHtml(document.Project.Key, filePath, htmlText, hostDocumentVersion.Value);
_publisher.PublishHtml(document.Project.Key, filePath, htmlText, hostDocumentVersion.Value);
}

var csharpText = codeDocument.GetCSharpSourceText();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license information.

using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.ExternalAccess.Razor;

namespace Microsoft.CodeAnalysis.Razor.Remote;

internal interface IRemoteHtmlDocumentService
{
ValueTask<string?> GetHtmlDocumentTextAsync(RazorPinnedSolutionInfoWrapper solutionInfo, DocumentId razorDocumentId, CancellationToken cancellationToken);
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,6 @@ internal static class RazorServices
(typeof(IRemoteTagHelperProviderService), null),
(typeof(IRemoteClientInitializationService), null),
(typeof(IRemoteSemanticTokensService), null),
(typeof(IRemoteHtmlDocumentService), null),
]);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license information.

using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.ExternalAccess.Razor;
using Microsoft.CodeAnalysis.ExternalAccess.Razor.Api;
using Microsoft.CodeAnalysis.Razor.Remote;
using Microsoft.CodeAnalysis.Razor.Workspaces;
using Microsoft.CodeAnalysis.Remote.Razor.ProjectSystem;
using Microsoft.ServiceHub.Framework;

namespace Microsoft.CodeAnalysis.Remote.Razor;

internal sealed class RemoteHtmlDocumentService(
IServiceBroker serviceBroker,
DocumentSnapshotFactory documentSnapshotFactory)
: RazorServiceBase(serviceBroker), IRemoteHtmlDocumentService
{
private readonly DocumentSnapshotFactory _documentSnapshotFactory = documentSnapshotFactory;

public ValueTask<string?> GetHtmlDocumentTextAsync(RazorPinnedSolutionInfoWrapper solutionInfo, DocumentId razorDocumentId, CancellationToken cancellationToken)
=> RazorBrokeredServiceImplementation.RunServiceAsync(
solutionInfo,
ServiceBrokerClient,
solution => GetHtmlDocumentTextAsync(solution, razorDocumentId, cancellationToken),
cancellationToken);

private async ValueTask<string?> GetHtmlDocumentTextAsync(Solution solution, DocumentId razorDocumentId, CancellationToken _)
{
var razorDocument = solution.GetAdditionalDocument(razorDocumentId);
if (razorDocument is null)
{
return null;
}

var documentSnapshot = _documentSnapshotFactory.GetOrCreate(razorDocument);
var codeDocument = await documentSnapshot.GetGeneratedOutputAsync();

return codeDocument.GetHtmlSourceText().ToString();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license information.

using Microsoft.CodeAnalysis.Razor.Remote;
using Microsoft.CodeAnalysis.Remote.Razor.ProjectSystem;
using Microsoft.ServiceHub.Framework;
using Microsoft.VisualStudio.Composition;

namespace Microsoft.CodeAnalysis.Remote.Razor;

internal sealed class RemoteHtmlDocumentServiceFactory : RazorServiceFactoryBase<IRemoteHtmlDocumentService>
{
// WARNING: We must always have a parameterless constructor in order to be properly handled by ServiceHub.
public RemoteHtmlDocumentServiceFactory()
: base(RazorServices.Descriptors)
{
}

protected override IRemoteHtmlDocumentService CreateService(IServiceBroker serviceBroker, ExportProvider exportProvider)
{
var documentSnapshotFactory = exportProvider.GetExportedValue<DocumentSnapshotFactory>();
return new RemoteHtmlDocumentService(serviceBroker, documentSnapshotFactory);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// 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.Composition;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor;
using Microsoft.CodeAnalysis.ExternalAccess.Razor;
using Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost;
using Microsoft.CodeAnalysis.Razor.Logging;
using Microsoft.CodeAnalysis.Razor.Remote;
using Microsoft.Extensions.Logging;
using Microsoft.VisualStudio.LanguageServer.ContainedLanguage;
using Microsoft.VisualStudio.LanguageServer.Protocol;
using Microsoft.VisualStudio.Threading;

namespace Microsoft.VisualStudio.LanguageServerClient.Razor.Cohost;

[Export(typeof(IRazorCohostTextDocumentSyncHandler)), Shared]
[method: ImportingConstructor]
internal class CohostTextDocumentSyncHandler(
IRemoteClientProvider remoteClientProvider,
LSPDocumentManager documentManager,
JoinableTaskContext joinableTaskContext,
IRazorLoggerFactory loggerFactory) : IRazorCohostTextDocumentSyncHandler
{
private readonly IRemoteClientProvider _remoteClientProvider = remoteClientProvider;
private readonly JoinableTaskContext _joinableTaskContext = joinableTaskContext;
private readonly TrackingLSPDocumentManager _documentManager = documentManager as TrackingLSPDocumentManager ?? throw new InvalidOperationException("Expected TrackingLSPDocumentManager");
private readonly ILogger _logger = loggerFactory.CreateLogger<CohostTextDocumentSyncHandler>();

public async Task HandleAsync(int version, RazorCohostRequestContext context, CancellationToken cancellationToken)
{
// For didClose, we don't need to do anything. We can't close the virtual document, because that requires the buffer
// so we just no-op and let our VS components handle closure.
if (context.Method == Methods.TextDocumentDidCloseName)
{
return;
}

var textDocument = context.TextDocument.AssumeNotNull();
var textDocumentPath = context.TextDocument.FilePath.AssumeNotNull();

_logger.LogDebug("[Cohost] {method} of '{document}' with version {version}.", context.Method, textDocumentPath, version);

var client = await _remoteClientProvider.TryGetClientAsync(cancellationToken);
if (client is null)
{
_logger.LogError("[Cohost] Couldn't get remote client for {method} of '{document}'. Html document contents will be stale", context.Method, textDocumentPath);
return;
}

var htmlText = await client.TryInvokeAsync<IRemoteHtmlDocumentService, string?>(textDocument.Project.Solution,
(service, solutionInfo, ct) => service.GetHtmlDocumentTextAsync(solutionInfo, textDocument.Id, ct),
cancellationToken).ConfigureAwait(false);

if (!htmlText.HasValue || htmlText.Value is null)
{
_logger.LogError("[Cohost] Couldn't get Html text for {method} of '{document}'. Html document contents will be stale", context.Method, textDocumentPath);
return;
}

// Eventually, for VS Code, the following piece of logic needs to make an LSP call rather than directly update the buffer

// Roslyn might have got changes from the LSP server, but we just get the actual source text, so we just construct one giant change
// from that. Guaranteed no sync issues, though we are passing long strings around unfortunately.
var uri = textDocument.CreateUri();
if (!_documentManager.TryGetDocument(uri, out var documentSnapshot) ||
!documentSnapshot.TryGetVirtualDocument<HtmlVirtualDocumentSnapshot>(out var htmlDocument))
{
Debug.Fail("Got an LSP text document update before getting informed of the VS buffer");
_logger.LogError("[Cohost] Couldn't get an Html document for {method} of '{document}'. Html document contents will be stale (or non-existent?)", context.Method, textDocumentPath);
return;
}

await _joinableTaskContext.Factory.SwitchToMainThreadAsync(cancellationToken);

VisualStudioTextChange[] changes = [new(0, htmlDocument.Snapshot.Length, htmlText.Value)];
_documentManager.UpdateVirtualDocument<HtmlVirtualDocument>(uri, changes, version, state: null);

_logger.LogDebug("[Cohost] Exiting {method} of '{document}' with version {version}.", context.Method, textDocumentPath, version);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"host": "netfx.anycpu",
"hostId": "RoslynCodeAnalysisService",
"hostGroupAllowed": true,
"entryPoint": {
"assemblyPath": "Microsoft.CodeAnalysis.Remote.Razor.dll",
"fullClassName": "Microsoft.CodeAnalysis.Remote.Razor.RemoteHtmlDocumentServiceFactory",
"appBasePath": "%VSAPPIDDIR%",
"configPath": "%PkgDefApplicationConfigFile%"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"host": "netfx.anycpu",
"hostId": "RoslynCodeAnalysisServiceS",
"hostGroupAllowed": true,
"entryPoint": {
"assemblyPath": "Microsoft.CodeAnalysis.Remote.Razor.dll",
"fullClassName": "Microsoft.CodeAnalysis.Remote.Razor.RemoteHtmlDocumentServiceFactory",
"appBasePath": "%VSAPPIDDIR%",
"configPath": "%PkgDefApplicationConfigFile%"
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These changes look worse than they are. What I did was rearrange the entries so that each service is grouped together, and I noticed some paths used the ServiceHubCoreSubPath variable, and some hardcoded the path, so I fixed that too.

I'm going to prioritize #10106 and get rid of all of this though, because I lost about half a day trying to get this new service to work, only to discover than Roslyn strips the word "Service" off the end of a service, and assumes that the json files do the same thing.

Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,11 @@
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<IncludeInVSIX>true</IncludeInVSIX>
</Content>
<Content Include="Microsoft.VisualStudio.Razor.ClientInitialization64.servicehub.service.json">
<Content Include="$(ServiceHubCoreSubPath)\Microsoft.VisualStudio.Razor.SemanticTokensCore64.servicehub.service.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<IncludeInVSIX>true</IncludeInVSIX>
</Content>
<Content Include="Microsoft.VisualStudio.Razor.ClientInitialization64S.servicehub.service.json">
<Content Include="$(ServiceHubCoreSubPath)\Microsoft.VisualStudio.Razor.SemanticTokensCore64S.servicehub.service.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<IncludeInVSIX>true</IncludeInVSIX>
</Content>
Expand All @@ -96,19 +96,35 @@
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<IncludeInVSIX>true</IncludeInVSIX>
</Content>
<Content Include="ServiceHubCore\Microsoft.VisualStudio.Razor.SemanticTokensCore64.servicehub.service.json">
<Content Include="Microsoft.VisualStudio.Razor.ClientInitialization64.servicehub.service.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<IncludeInVSIX>true</IncludeInVSIX>
</Content>
<Content Include="Microsoft.VisualStudio.Razor.ClientInitialization64S.servicehub.service.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<IncludeInVSIX>true</IncludeInVSIX>
</Content>
<Content Include="$(ServiceHubCoreSubPath)\Microsoft.VisualStudio.Razor.ClientInitializationCore64.servicehub.service.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<IncludeInVSIX>true</IncludeInVSIX>
</Content>
<Content Include="$(ServiceHubCoreSubPath)\Microsoft.VisualStudio.Razor.ClientInitializationCore64S.servicehub.service.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<IncludeInVSIX>true</IncludeInVSIX>
</Content>
<Content Include="Microsoft.VisualStudio.Razor.HtmlDocument64.servicehub.service.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<IncludeInVSIX>true</IncludeInVSIX>
</Content>
<Content Include="ServiceHubCore\Microsoft.VisualStudio.Razor.SemanticTokensCore64S.servicehub.service.json">
<Content Include="Microsoft.VisualStudio.Razor.HtmlDocument64S.servicehub.service.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<IncludeInVSIX>true</IncludeInVSIX>
</Content>
<Content Include="ServiceHubCore\Microsoft.VisualStudio.Razor.ClientInitializationCore64.servicehub.service.json">
<Content Include="ServiceHubCore\Microsoft.VisualStudio.Razor.HtmlDocumentCore64.servicehub.service.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<IncludeInVSIX>true</IncludeInVSIX>
</Content>
<Content Include="ServiceHubCore\Microsoft.VisualStudio.Razor.ClientInitializationCore64S.servicehub.service.json">
<Content Include="ServiceHubCore\Microsoft.VisualStudio.Razor.HtmlDocumentCore64S.servicehub.service.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<IncludeInVSIX>true</IncludeInVSIX>
</Content>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ namespace Microsoft.VisualStudio.RazorExtension;
[ProvideBrokeredServiceHubService("Microsoft.VisualStudio.Razor.ClientInitialization64S", Audience = ServiceAudience.Local)]
[ProvideBrokeredServiceHubService("Microsoft.VisualStudio.Razor.ClientInitializationCore64", ServiceLocation = ProvideBrokeredServiceHubServiceAttribute.DefaultServiceLocation + @"\ServiceHubCore", Audience = ServiceAudience.Local)]
[ProvideBrokeredServiceHubService("Microsoft.VisualStudio.Razor.ClientInitializationCore64S", ServiceLocation = ProvideBrokeredServiceHubServiceAttribute.DefaultServiceLocation + @"\ServiceHubCore", Audience = ServiceAudience.Local)]
[ProvideBrokeredServiceHubService("Microsoft.VisualStudio.Razor.HtmlDocument", Audience = ServiceAudience.Local)]
[ProvideBrokeredServiceHubService("Microsoft.VisualStudio.Razor.HtmlDocument64", Audience = ServiceAudience.Local)]
[ProvideBrokeredServiceHubService("Microsoft.VisualStudio.Razor.HtmlDocument64S", Audience = ServiceAudience.Local)]
[ProvideBrokeredServiceHubService("Microsoft.VisualStudio.Razor.HtmlDocumentCore64", ServiceLocation = ProvideBrokeredServiceHubServiceAttribute.DefaultServiceLocation + @"\ServiceHubCore", Audience = ServiceAudience.Local)]
[ProvideBrokeredServiceHubService("Microsoft.VisualStudio.Razor.HtmlDocumentCore64S", ServiceLocation = ProvideBrokeredServiceHubServiceAttribute.DefaultServiceLocation + @"\ServiceHubCore", Audience = ServiceAudience.Local)]
[ProvideBrokeredServiceHubService("Microsoft.VisualStudio.Razor.TagHelperProvider", Audience = ServiceAudience.Local)]
[ProvideBrokeredServiceHubService("Microsoft.VisualStudio.Razor.TagHelperProvider64", Audience = ServiceAudience.Local)]
[ProvideBrokeredServiceHubService("Microsoft.VisualStudio.Razor.TagHelperProvider64S", Audience = ServiceAudience.Local)]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"host": "dotnet",
"hostId": "RoslynCodeAnalysisService",
"hostGroupAllowed": true,
"entryPoint": {
"assemblyPath": "Microsoft.CodeAnalysis.Remote.Razor.dll",
"fullClassName": "Microsoft.CodeAnalysis.Remote.Razor.RemoteHtmlDocumentServiceFactory"
},
"friendServices": [ "Microsoft.VisualStudio.LanguageServices.DiagnosticAnalyzerCore64" ]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"host": "dotnet",
"hostId": "RoslynCodeAnalysisServiceS",
"hostGroupAllowed": true,
"entryPoint": {
"assemblyPath": "Microsoft.CodeAnalysis.Remote.Razor.dll",
"fullClassName": "Microsoft.CodeAnalysis.Remote.Razor.RemoteHtmlDocumentServiceFactory"
},
"friendServices": [ "Microsoft.VisualStudio.LanguageServices.DiagnosticAnalyzerCore64S" ]
}
Loading