зеркало из https://github.com/dotnet/razor.git
Cohost inlay hint support (#10672)
Part of https://github.com/dotnet/razor/issues/9519 Needs https://github.com/dotnet/roslyn/pull/74548 before it will compile Needs https://devdiv.visualstudio.com/DevDiv/_git/VSLanguageServerClient/pullrequest/567229 before it will work in VS There were a few side quests on this one: * Roslyn OOP, at least the way we access it, doesn't have any options set, so took a few tries to get the Roslyn side of this right for our needs * I wrote this feature test-first so only discovered the lack of options after I modified Roslyn and our test infra to allow us to set global options, so ended up removing most of that code, Kept the bit about isolated workspaces because it just makes sense. * Had to re-write one of the `DocumentMappingService` methods to use `TextChange` instead of `TextEdit` so I could use Roslyn LSP types * The hacky, and fortunately temporary, way we were doing generated C# content was causing cache misses in Roslyn in tests, so fixed that up * When I finally got up to manual testing I found a bug in the platform that meant inlay hints just don't work with dynamic registration, so filed the above linked PR to fix that If reviewing commit-at-a-time please note that the first commit was written before the reworking of extension methods and LSP types, and the fixes for that are in the last commit.
This commit is contained in:
Коммит
c78f5f24f9
|
@ -11,82 +11,82 @@
|
|||
<Sha>9ae78a4e6412926d19ba97cfed159bf9de70b538</Sha>
|
||||
<SourceBuild RepoName="source-build-reference-packages" ManagedOnly="true" />
|
||||
</Dependency>
|
||||
<Dependency Name="Microsoft.Net.Compilers.Toolset" Version="4.12.0-1.24366.6">
|
||||
<Dependency Name="Microsoft.Net.Compilers.Toolset" Version="4.12.0-1.24379.11">
|
||||
<Uri>https://github.com/dotnet/roslyn</Uri>
|
||||
<Sha>30edd04fd41dec9e8f9f48e698ebd5b80d9f7677</Sha>
|
||||
<Sha>cf82d399c36008e7936d545cde24141f8d3790fa</Sha>
|
||||
</Dependency>
|
||||
<Dependency Name="Microsoft.CommonLanguageServerProtocol.Framework" Version="4.12.0-1.24366.6">
|
||||
<Dependency Name="Microsoft.CommonLanguageServerProtocol.Framework" Version="4.12.0-1.24379.11">
|
||||
<Uri>https://github.com/dotnet/roslyn</Uri>
|
||||
<Sha>30edd04fd41dec9e8f9f48e698ebd5b80d9f7677</Sha>
|
||||
<Sha>cf82d399c36008e7936d545cde24141f8d3790fa</Sha>
|
||||
</Dependency>
|
||||
<Dependency Name="Microsoft.CodeAnalysis.ExternalAccess.Razor" Version="4.12.0-1.24366.6">
|
||||
<Dependency Name="Microsoft.CodeAnalysis.ExternalAccess.Razor" Version="4.12.0-1.24379.11">
|
||||
<Uri>https://github.com/dotnet/roslyn</Uri>
|
||||
<Sha>30edd04fd41dec9e8f9f48e698ebd5b80d9f7677</Sha>
|
||||
<Sha>cf82d399c36008e7936d545cde24141f8d3790fa</Sha>
|
||||
</Dependency>
|
||||
<Dependency Name="Microsoft.CodeAnalysis.Common" Version="4.12.0-1.24366.6">
|
||||
<Dependency Name="Microsoft.CodeAnalysis.Common" Version="4.12.0-1.24379.11">
|
||||
<Uri>https://github.com/dotnet/roslyn</Uri>
|
||||
<Sha>30edd04fd41dec9e8f9f48e698ebd5b80d9f7677</Sha>
|
||||
<Sha>cf82d399c36008e7936d545cde24141f8d3790fa</Sha>
|
||||
</Dependency>
|
||||
<Dependency Name="Microsoft.CodeAnalysis.CSharp" Version="4.12.0-1.24366.6">
|
||||
<Dependency Name="Microsoft.CodeAnalysis.CSharp" Version="4.12.0-1.24379.11">
|
||||
<Uri>https://github.com/dotnet/roslyn</Uri>
|
||||
<Sha>30edd04fd41dec9e8f9f48e698ebd5b80d9f7677</Sha>
|
||||
<Sha>cf82d399c36008e7936d545cde24141f8d3790fa</Sha>
|
||||
</Dependency>
|
||||
<Dependency Name="Microsoft.CodeAnalysis.CSharp.EditorFeatures" Version="4.12.0-1.24366.6">
|
||||
<Dependency Name="Microsoft.CodeAnalysis.CSharp.EditorFeatures" Version="4.12.0-1.24379.11">
|
||||
<Uri>https://github.com/dotnet/roslyn</Uri>
|
||||
<Sha>30edd04fd41dec9e8f9f48e698ebd5b80d9f7677</Sha>
|
||||
<Sha>cf82d399c36008e7936d545cde24141f8d3790fa</Sha>
|
||||
</Dependency>
|
||||
<Dependency Name="Microsoft.CodeAnalysis.CSharp.Features" Version="4.12.0-1.24366.6">
|
||||
<Dependency Name="Microsoft.CodeAnalysis.CSharp.Features" Version="4.12.0-1.24379.11">
|
||||
<Uri>https://github.com/dotnet/roslyn</Uri>
|
||||
<Sha>30edd04fd41dec9e8f9f48e698ebd5b80d9f7677</Sha>
|
||||
<Sha>cf82d399c36008e7936d545cde24141f8d3790fa</Sha>
|
||||
</Dependency>
|
||||
<Dependency Name="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.12.0-1.24366.6">
|
||||
<Dependency Name="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.12.0-1.24379.11">
|
||||
<Uri>https://github.com/dotnet/roslyn</Uri>
|
||||
<Sha>30edd04fd41dec9e8f9f48e698ebd5b80d9f7677</Sha>
|
||||
<Sha>cf82d399c36008e7936d545cde24141f8d3790fa</Sha>
|
||||
</Dependency>
|
||||
<Dependency Name="Microsoft.CodeAnalysis.EditorFeatures" Version="4.12.0-1.24366.6">
|
||||
<Dependency Name="Microsoft.CodeAnalysis.EditorFeatures" Version="4.12.0-1.24379.11">
|
||||
<Uri>https://github.com/dotnet/roslyn</Uri>
|
||||
<Sha>30edd04fd41dec9e8f9f48e698ebd5b80d9f7677</Sha>
|
||||
<Sha>cf82d399c36008e7936d545cde24141f8d3790fa</Sha>
|
||||
</Dependency>
|
||||
<Dependency Name="Microsoft.CodeAnalysis.EditorFeatures.Common" Version="4.12.0-1.24366.6">
|
||||
<Dependency Name="Microsoft.CodeAnalysis.EditorFeatures.Common" Version="4.12.0-1.24379.11">
|
||||
<Uri>https://github.com/dotnet/roslyn</Uri>
|
||||
<Sha>30edd04fd41dec9e8f9f48e698ebd5b80d9f7677</Sha>
|
||||
<Sha>cf82d399c36008e7936d545cde24141f8d3790fa</Sha>
|
||||
</Dependency>
|
||||
<Dependency Name="Microsoft.CodeAnalysis.EditorFeatures.Text" Version="4.12.0-1.24366.6">
|
||||
<Dependency Name="Microsoft.CodeAnalysis.EditorFeatures.Text" Version="4.12.0-1.24379.11">
|
||||
<Uri>https://github.com/dotnet/roslyn</Uri>
|
||||
<Sha>30edd04fd41dec9e8f9f48e698ebd5b80d9f7677</Sha>
|
||||
<Sha>cf82d399c36008e7936d545cde24141f8d3790fa</Sha>
|
||||
</Dependency>
|
||||
<Dependency Name="Microsoft.CodeAnalysis.EditorFeatures.Wpf" Version="4.12.0-1.24366.6">
|
||||
<Dependency Name="Microsoft.CodeAnalysis.EditorFeatures.Wpf" Version="4.12.0-1.24379.11">
|
||||
<Uri>https://github.com/dotnet/roslyn</Uri>
|
||||
<Sha>30edd04fd41dec9e8f9f48e698ebd5b80d9f7677</Sha>
|
||||
<Sha>cf82d399c36008e7936d545cde24141f8d3790fa</Sha>
|
||||
</Dependency>
|
||||
<Dependency Name="Microsoft.CodeAnalysis.Remote.ServiceHub" Version="4.12.0-1.24366.6">
|
||||
<Dependency Name="Microsoft.CodeAnalysis.Remote.ServiceHub" Version="4.12.0-1.24379.11">
|
||||
<Uri>https://github.com/dotnet/roslyn</Uri>
|
||||
<Sha>30edd04fd41dec9e8f9f48e698ebd5b80d9f7677</Sha>
|
||||
<Sha>cf82d399c36008e7936d545cde24141f8d3790fa</Sha>
|
||||
</Dependency>
|
||||
<Dependency Name="Microsoft.CodeAnalysis.VisualBasic.Workspaces" Version="4.12.0-1.24366.6">
|
||||
<Dependency Name="Microsoft.CodeAnalysis.VisualBasic.Workspaces" Version="4.12.0-1.24379.11">
|
||||
<Uri>https://github.com/dotnet/roslyn</Uri>
|
||||
<Sha>30edd04fd41dec9e8f9f48e698ebd5b80d9f7677</Sha>
|
||||
<Sha>cf82d399c36008e7936d545cde24141f8d3790fa</Sha>
|
||||
</Dependency>
|
||||
<Dependency Name="Microsoft.CodeAnalysis.Workspaces.Common" Version="4.12.0-1.24366.6">
|
||||
<Dependency Name="Microsoft.CodeAnalysis.Workspaces.Common" Version="4.12.0-1.24379.11">
|
||||
<Uri>https://github.com/dotnet/roslyn</Uri>
|
||||
<Sha>30edd04fd41dec9e8f9f48e698ebd5b80d9f7677</Sha>
|
||||
<Sha>cf82d399c36008e7936d545cde24141f8d3790fa</Sha>
|
||||
</Dependency>
|
||||
<Dependency Name="Microsoft.CodeAnalysis.Workspaces.MSBuild" Version="4.12.0-1.24366.6">
|
||||
<Dependency Name="Microsoft.CodeAnalysis.Workspaces.MSBuild" Version="4.12.0-1.24379.11">
|
||||
<Uri>https://github.com/dotnet/roslyn</Uri>
|
||||
<Sha>30edd04fd41dec9e8f9f48e698ebd5b80d9f7677</Sha>
|
||||
<Sha>cf82d399c36008e7936d545cde24141f8d3790fa</Sha>
|
||||
</Dependency>
|
||||
<Dependency Name="Microsoft.VisualStudio.LanguageServices" Version="4.12.0-1.24366.6">
|
||||
<Dependency Name="Microsoft.VisualStudio.LanguageServices" Version="4.12.0-1.24379.11">
|
||||
<Uri>https://github.com/dotnet/roslyn</Uri>
|
||||
<Sha>30edd04fd41dec9e8f9f48e698ebd5b80d9f7677</Sha>
|
||||
<Sha>cf82d399c36008e7936d545cde24141f8d3790fa</Sha>
|
||||
</Dependency>
|
||||
<Dependency Name="Microsoft.CodeAnalysis.Test.Utilities" Version="4.12.0-1.24366.6">
|
||||
<Dependency Name="Microsoft.CodeAnalysis.Test.Utilities" Version="4.12.0-1.24379.11">
|
||||
<Uri>https://github.com/dotnet/roslyn</Uri>
|
||||
<Sha>30edd04fd41dec9e8f9f48e698ebd5b80d9f7677</Sha>
|
||||
<Sha>cf82d399c36008e7936d545cde24141f8d3790fa</Sha>
|
||||
</Dependency>
|
||||
<!-- Intermediate is necessary for source build. -->
|
||||
<Dependency Name="Microsoft.SourceBuild.Intermediate.roslyn" Version="4.12.0-1.24366.6">
|
||||
<Dependency Name="Microsoft.SourceBuild.Intermediate.roslyn" Version="4.12.0-1.24379.11">
|
||||
<Uri>https://github.com/dotnet/roslyn</Uri>
|
||||
<Sha>30edd04fd41dec9e8f9f48e698ebd5b80d9f7677</Sha>
|
||||
<Sha>cf82d399c36008e7936d545cde24141f8d3790fa</Sha>
|
||||
<SourceBuild RepoName="roslyn" ManagedOnly="true" />
|
||||
</Dependency>
|
||||
</ProductDependencies>
|
||||
|
|
|
@ -53,25 +53,25 @@
|
|||
<MicrosoftSourceBuildIntermediatearcadePackageVersion>9.0.0-beta.24352.2</MicrosoftSourceBuildIntermediatearcadePackageVersion>
|
||||
<MicrosoftDotNetXliffTasksPackageVersion>1.0.0-beta.23475.1</MicrosoftDotNetXliffTasksPackageVersion>
|
||||
<MicrosoftSourceBuildIntermediatexlifftasksPackageVersion>1.0.0-beta.23475.1</MicrosoftSourceBuildIntermediatexlifftasksPackageVersion>
|
||||
<MicrosoftNetCompilersToolsetPackageVersion>4.12.0-1.24366.6</MicrosoftNetCompilersToolsetPackageVersion>
|
||||
<MicrosoftCommonLanguageServerProtocolFrameworkPackageVersion>4.12.0-1.24366.6</MicrosoftCommonLanguageServerProtocolFrameworkPackageVersion>
|
||||
<MicrosoftCodeAnalysisExternalAccessRazorPackageVersion>4.12.0-1.24366.6</MicrosoftCodeAnalysisExternalAccessRazorPackageVersion>
|
||||
<MicrosoftCodeAnalysisCommonPackageVersion>4.12.0-1.24366.6</MicrosoftCodeAnalysisCommonPackageVersion>
|
||||
<MicrosoftCodeAnalysisCSharpPackageVersion>4.12.0-1.24366.6</MicrosoftCodeAnalysisCSharpPackageVersion>
|
||||
<MicrosoftCodeAnalysisCSharpEditorFeaturesPackageVersion>4.12.0-1.24366.6</MicrosoftCodeAnalysisCSharpEditorFeaturesPackageVersion>
|
||||
<MicrosoftCodeAnalysisCSharpFeaturesPackageVersion>4.12.0-1.24366.6</MicrosoftCodeAnalysisCSharpFeaturesPackageVersion>
|
||||
<MicrosoftCodeAnalysisCSharpWorkspacesPackageVersion>4.12.0-1.24366.6</MicrosoftCodeAnalysisCSharpWorkspacesPackageVersion>
|
||||
<MicrosoftCodeAnalysisEditorFeaturesPackageVersion>4.12.0-1.24366.6</MicrosoftCodeAnalysisEditorFeaturesPackageVersion>
|
||||
<MicrosoftCodeAnalysisEditorFeaturesCommonPackageVersion>4.12.0-1.24366.6</MicrosoftCodeAnalysisEditorFeaturesCommonPackageVersion>
|
||||
<MicrosoftCodeAnalysisEditorFeaturesTextPackageVersion>4.12.0-1.24366.6</MicrosoftCodeAnalysisEditorFeaturesTextPackageVersion>
|
||||
<MicrosoftCodeAnalysisEditorFeaturesWpfPackageVersion>4.12.0-1.24366.6</MicrosoftCodeAnalysisEditorFeaturesWpfPackageVersion>
|
||||
<MicrosoftCodeAnalysisRemoteServiceHubPackageVersion>4.12.0-1.24366.6</MicrosoftCodeAnalysisRemoteServiceHubPackageVersion>
|
||||
<MicrosoftCodeAnalysisTestUtilitiesPackageVersion>4.12.0-1.24366.6</MicrosoftCodeAnalysisTestUtilitiesPackageVersion>
|
||||
<MicrosoftCodeAnalysisVisualBasicWorkspacesPackageVersion>4.12.0-1.24366.6</MicrosoftCodeAnalysisVisualBasicWorkspacesPackageVersion>
|
||||
<MicrosoftCodeAnalysisWorkspacesCommonPackageVersion>4.12.0-1.24366.6</MicrosoftCodeAnalysisWorkspacesCommonPackageVersion>
|
||||
<MicrosoftCodeAnalysisWorkspacesMSBuildPackageVersion>4.12.0-1.24366.6</MicrosoftCodeAnalysisWorkspacesMSBuildPackageVersion>
|
||||
<MicrosoftSourceBuildIntermediateroslynPackageVersion>4.12.0-1.24366.6</MicrosoftSourceBuildIntermediateroslynPackageVersion>
|
||||
<MicrosoftVisualStudioLanguageServicesPackageVersion>4.12.0-1.24366.6</MicrosoftVisualStudioLanguageServicesPackageVersion>
|
||||
<MicrosoftNetCompilersToolsetPackageVersion>4.12.0-1.24379.11</MicrosoftNetCompilersToolsetPackageVersion>
|
||||
<MicrosoftCommonLanguageServerProtocolFrameworkPackageVersion>4.12.0-1.24379.11</MicrosoftCommonLanguageServerProtocolFrameworkPackageVersion>
|
||||
<MicrosoftCodeAnalysisExternalAccessRazorPackageVersion>4.12.0-1.24379.11</MicrosoftCodeAnalysisExternalAccessRazorPackageVersion>
|
||||
<MicrosoftCodeAnalysisCommonPackageVersion>4.12.0-1.24379.11</MicrosoftCodeAnalysisCommonPackageVersion>
|
||||
<MicrosoftCodeAnalysisCSharpPackageVersion>4.12.0-1.24379.11</MicrosoftCodeAnalysisCSharpPackageVersion>
|
||||
<MicrosoftCodeAnalysisCSharpEditorFeaturesPackageVersion>4.12.0-1.24379.11</MicrosoftCodeAnalysisCSharpEditorFeaturesPackageVersion>
|
||||
<MicrosoftCodeAnalysisCSharpFeaturesPackageVersion>4.12.0-1.24379.11</MicrosoftCodeAnalysisCSharpFeaturesPackageVersion>
|
||||
<MicrosoftCodeAnalysisCSharpWorkspacesPackageVersion>4.12.0-1.24379.11</MicrosoftCodeAnalysisCSharpWorkspacesPackageVersion>
|
||||
<MicrosoftCodeAnalysisEditorFeaturesPackageVersion>4.12.0-1.24379.11</MicrosoftCodeAnalysisEditorFeaturesPackageVersion>
|
||||
<MicrosoftCodeAnalysisEditorFeaturesCommonPackageVersion>4.12.0-1.24379.11</MicrosoftCodeAnalysisEditorFeaturesCommonPackageVersion>
|
||||
<MicrosoftCodeAnalysisEditorFeaturesTextPackageVersion>4.12.0-1.24379.11</MicrosoftCodeAnalysisEditorFeaturesTextPackageVersion>
|
||||
<MicrosoftCodeAnalysisEditorFeaturesWpfPackageVersion>4.12.0-1.24379.11</MicrosoftCodeAnalysisEditorFeaturesWpfPackageVersion>
|
||||
<MicrosoftCodeAnalysisRemoteServiceHubPackageVersion>4.12.0-1.24379.11</MicrosoftCodeAnalysisRemoteServiceHubPackageVersion>
|
||||
<MicrosoftCodeAnalysisTestUtilitiesPackageVersion>4.12.0-1.24379.11</MicrosoftCodeAnalysisTestUtilitiesPackageVersion>
|
||||
<MicrosoftCodeAnalysisVisualBasicWorkspacesPackageVersion>4.12.0-1.24379.11</MicrosoftCodeAnalysisVisualBasicWorkspacesPackageVersion>
|
||||
<MicrosoftCodeAnalysisWorkspacesCommonPackageVersion>4.12.0-1.24379.11</MicrosoftCodeAnalysisWorkspacesCommonPackageVersion>
|
||||
<MicrosoftCodeAnalysisWorkspacesMSBuildPackageVersion>4.12.0-1.24379.11</MicrosoftCodeAnalysisWorkspacesMSBuildPackageVersion>
|
||||
<MicrosoftSourceBuildIntermediateroslynPackageVersion>4.12.0-1.24379.11</MicrosoftSourceBuildIntermediateroslynPackageVersion>
|
||||
<MicrosoftVisualStudioLanguageServicesPackageVersion>4.12.0-1.24379.11</MicrosoftVisualStudioLanguageServicesPackageVersion>
|
||||
<!--
|
||||
Exception - Microsoft.Extensions.ObjectPool and System.Collections.Immutable packages are not updated by automation,
|
||||
but are present in Version.Details.xml for source-build PVP flow. See the comment in Version.Details.xml for more information.
|
||||
|
|
|
@ -24,5 +24,6 @@
|
|||
<ServiceHubService Include="Microsoft.VisualStudio.Razor.FoldingRange" ClassName="Microsoft.CodeAnalysis.Remote.Razor.RemoteFoldingRangeService+Factory" />
|
||||
<ServiceHubService Include="Microsoft.VisualStudio.Razor.SignatureHelp" ClassName="Microsoft.CodeAnalysis.Remote.Razor.RemoteSignatureHelpService+Factory" />
|
||||
<ServiceHubService Include="Microsoft.VisualStudio.Razor.DocumentHighlight" ClassName="Microsoft.CodeAnalysis.Remote.Razor.RemoteDocumentHighlightService+Factory" />
|
||||
<ServiceHubService Include="Microsoft.VisualStudio.Razor.InlayHint" ClassName="Microsoft.CodeAnalysis.Remote.Razor.RemoteInlayHintService+Factory" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
|
@ -6,16 +6,14 @@ using System.Threading.Tasks;
|
|||
using Microsoft.AspNetCore.Razor.LanguageServer.EndpointContracts;
|
||||
using Microsoft.AspNetCore.Razor.LanguageServer.Hosting;
|
||||
using Microsoft.AspNetCore.Razor.Threading;
|
||||
using Microsoft.CodeAnalysis.Razor.Workspaces;
|
||||
using Microsoft.VisualStudio.LanguageServer.Protocol;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer.InlayHints;
|
||||
|
||||
[RazorLanguageServerEndpoint(Methods.TextDocumentInlayHintName)]
|
||||
internal sealed class InlayHintEndpoint(LanguageServerFeatureOptions featureOptions, IInlayHintService inlayHintService, IClientConnection clientConnection)
|
||||
internal sealed class InlayHintEndpoint(IInlayHintService inlayHintService, IClientConnection clientConnection)
|
||||
: IRazorRequestHandler<InlayHintParams, InlayHint[]?>, ICapabilitiesProvider
|
||||
{
|
||||
private readonly LanguageServerFeatureOptions _featureOptions = featureOptions;
|
||||
private readonly IInlayHintService _inlayHintService = inlayHintService;
|
||||
private readonly IClientConnection _clientConnection = clientConnection;
|
||||
|
||||
|
|
|
@ -189,6 +189,10 @@ internal partial class RazorLanguageServer : SystemTextJsonLanguageServer<RazorR
|
|||
services.AddHandlerWithCapabilities<SignatureHelpEndpoint>();
|
||||
services.AddHandlerWithCapabilities<LinkedEditingRangeEndpoint>();
|
||||
services.AddHandlerWithCapabilities<FoldingRangeEndpoint>();
|
||||
|
||||
services.AddSingleton<IInlayHintService, InlayHintService>();
|
||||
services.AddHandlerWithCapabilities<InlayHintEndpoint>();
|
||||
services.AddHandler<InlayHintResolveEndpoint>();
|
||||
}
|
||||
|
||||
services.AddHandler<WrapWithTagEndpoint>();
|
||||
|
@ -204,11 +208,6 @@ internal partial class RazorLanguageServer : SystemTextJsonLanguageServer<RazorR
|
|||
services.AddHandlerWithCapabilities<ProjectContextsEndpoint>();
|
||||
services.AddHandlerWithCapabilities<DocumentSymbolEndpoint>();
|
||||
services.AddHandlerWithCapabilities<MapCodeEndpoint>();
|
||||
|
||||
services.AddSingleton<IInlayHintService, InlayHintService>();
|
||||
|
||||
services.AddHandlerWithCapabilities<InlayHintEndpoint>();
|
||||
services.AddHandler<InlayHintResolveEndpoint>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ using System.Diagnostics;
|
|||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Razor;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.AspNetCore.Razor.Language.Legacy;
|
||||
using Microsoft.AspNetCore.Razor.PooledObjects;
|
||||
|
@ -31,29 +32,25 @@ internal abstract class AbstractRazorDocumentMappingService(
|
|||
private readonly IDocumentContextFactory _documentContextFactory = documentContextFactory ?? throw new ArgumentNullException(nameof(documentContextFactory));
|
||||
private readonly ILogger _logger = logger;
|
||||
|
||||
public TextEdit[] GetHostDocumentEdits(IRazorGeneratedDocument generatedDocument, TextEdit[] generatedDocumentEdits)
|
||||
public IEnumerable<TextChange> GetHostDocumentEdits(IRazorGeneratedDocument generatedDocument, IEnumerable<TextChange> generatedDocumentChanges)
|
||||
{
|
||||
using var _1 = ListPool<TextEdit>.GetPooledObject(out var hostDocumentEdits);
|
||||
var generatedDocumentSourceText = GetGeneratedSourceText(generatedDocument);
|
||||
var generatedDocumentSourceText = generatedDocument.GetGeneratedSourceText();
|
||||
var lastNewLineAddedToLine = 0;
|
||||
|
||||
foreach (var edit in generatedDocumentEdits)
|
||||
foreach (var change in generatedDocumentChanges)
|
||||
{
|
||||
var range = edit.Range;
|
||||
if (!IsRangeWithinDocument(range.ToLinePositionSpan(), generatedDocumentSourceText))
|
||||
var span = change.Span;
|
||||
// Deliberately doing a naive check to avoid telemetry for truly bad data
|
||||
if (span.Start <= 0 || span.Start >= generatedDocumentSourceText.Length || span.End <= 0 || span.End >= generatedDocumentSourceText.Length)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var startSync = generatedDocumentSourceText.TryGetAbsoluteIndex(range.Start, out var startIndex);
|
||||
var endSync = generatedDocumentSourceText.TryGetAbsoluteIndex(range.End, out var endIndex);
|
||||
if (startSync is false || endSync is false)
|
||||
{
|
||||
break;
|
||||
}
|
||||
var (startLine, startChar) = generatedDocumentSourceText.GetLinePosition(span.Start);
|
||||
var (endLine, _) = generatedDocumentSourceText.GetLinePosition(span.End);
|
||||
|
||||
var mappedStart = this.TryMapToHostDocumentPosition(generatedDocument, startIndex, out Position? hostDocumentStart, out _);
|
||||
var mappedEnd = this.TryMapToHostDocumentPosition(generatedDocument, endIndex, out Position? hostDocumentEnd, out _);
|
||||
var mappedStart = this.TryMapToHostDocumentPosition(generatedDocument, span.Start, out var hostDocumentStart, out var hostStartIndex);
|
||||
var mappedEnd = this.TryMapToHostDocumentPosition(generatedDocument, span.End, out var hostDocumentEnd, out var hostEndIndex);
|
||||
|
||||
// Ideal case, both start and end can be mapped so just return the edit
|
||||
if (mappedStart && mappedEnd)
|
||||
|
@ -61,8 +58,8 @@ internal abstract class AbstractRazorDocumentMappingService(
|
|||
// If the previous edit was on the same line, and added a newline, then we need to add a space
|
||||
// between this edit and the previous one, because the normalization will have swallowed it. See
|
||||
// below for a more info.
|
||||
var newText = (lastNewLineAddedToLine == range.Start.Line ? " " : "") + edit.NewText;
|
||||
hostDocumentEdits.Add(VsLspFactory.CreateTextEdit(hostDocumentStart!, hostDocumentEnd!, newText));
|
||||
var newText = (lastNewLineAddedToLine == startLine ? " " : "") + change.NewText;
|
||||
yield return new TextChange(TextSpan.FromBounds(hostStartIndex, hostEndIndex), newText);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -82,33 +79,30 @@ internal abstract class AbstractRazorDocumentMappingService(
|
|||
// To indent the 'var x' line the formatter will return an edit that starts the line before,
|
||||
// with a NewText of '\n '. The start of that edit is outside our mapping, but we
|
||||
// still want to know how to format the 'var x' line, so we have to break up the edit.
|
||||
if (!mappedStart && mappedEnd && range.SpansMultipleLines())
|
||||
if (!mappedStart && mappedEnd && startLine != endLine)
|
||||
{
|
||||
// Construct a theoretical edit that is just for the last line of the edit that the C# formatter
|
||||
// gave us, and see if we can map that.
|
||||
// The +1 here skips the newline character that is found, but also protects from Substring throwing
|
||||
// if there are no newlines (which should be impossible anyway)
|
||||
var lastNewLine = edit.NewText.LastIndexOfAny(new char[] { '\n', '\r' }) + 1;
|
||||
var lastNewLine = change.NewText.AssumeNotNull().LastIndexOfAny(['\n', '\r']) + 1;
|
||||
|
||||
// Strictly speaking we could be dropping more lines than we need to, because our mapping point could be anywhere within the edit
|
||||
// but we know that the C# formatter will only be returning blank lines up until the first bit of content that needs to be indented
|
||||
// so we can ignore all but the last line. This assert ensures that is true, just in case something changes in Roslyn
|
||||
Debug.Assert(lastNewLine == 0 || edit.NewText[..(lastNewLine - 1)].All(c => c == '\r' || c == '\n'), "We are throwing away part of an edit that has more than just empty lines!");
|
||||
Debug.Assert(lastNewLine == 0 || change.NewText[..(lastNewLine - 1)].All(c => c == '\r' || c == '\n'), "We are throwing away part of an edit that has more than just empty lines!");
|
||||
|
||||
var proposedRange = VsLspFactory.CreateSingleLineRange(range.End.Line, character: 0, length: range.End.Character);
|
||||
startSync = generatedDocumentSourceText.TryGetAbsoluteIndex(proposedRange.Start, out startIndex);
|
||||
endSync = generatedDocumentSourceText.TryGetAbsoluteIndex(proposedRange.End, out endIndex);
|
||||
if (startSync is false || endSync is false)
|
||||
var startSync = generatedDocumentSourceText.TryGetAbsoluteIndex((endLine, 0), out var startIndex);
|
||||
if (startSync is false)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
mappedStart = this.TryMapToHostDocumentPosition(generatedDocument, startIndex, out hostDocumentStart, out _);
|
||||
mappedEnd = this.TryMapToHostDocumentPosition(generatedDocument, endIndex, out hostDocumentEnd, out _);
|
||||
mappedStart = this.TryMapToHostDocumentPosition(generatedDocument, startIndex, out _, out hostStartIndex);
|
||||
|
||||
if (mappedStart && mappedEnd)
|
||||
{
|
||||
hostDocumentEdits.Add(VsLspFactory.CreateTextEdit(hostDocumentStart!, hostDocumentEnd!, edit.NewText[lastNewLine..]));
|
||||
yield return new TextChange(TextSpan.FromBounds(hostStartIndex, hostEndIndex), change.NewText[lastNewLine..]);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
@ -136,15 +130,15 @@ internal abstract class AbstractRazorDocumentMappingService(
|
|||
// with "public class Goo" would come in as one edit for "public", one for "class" and one for "Goo", all on the same line.
|
||||
// When we map the edit for "public" we will push everything down a line, so we don't want to do it for other edits
|
||||
// on that line.
|
||||
if (!mappedStart && !mappedEnd && !range.SpansMultipleLines())
|
||||
if (!mappedStart && !mappedEnd && startLine == endLine)
|
||||
{
|
||||
// If the new text doesn't have any content we don't care - throwing away invisible whitespace is fine
|
||||
if (string.IsNullOrWhiteSpace(edit.NewText))
|
||||
if (string.IsNullOrWhiteSpace(change.NewText))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var line = generatedDocumentSourceText.Lines[range.Start.Line];
|
||||
var line = generatedDocumentSourceText.Lines[startLine];
|
||||
|
||||
// If the line isn't blank, then this isn't a functions directive
|
||||
if (line.GetFirstNonWhitespaceOffset() is not null)
|
||||
|
@ -153,40 +147,30 @@ internal abstract class AbstractRazorDocumentMappingService(
|
|||
}
|
||||
|
||||
// Only do anything if the end of the line in question is a valid mapping point (ie, a transition)
|
||||
var endOfLine = line.Span.End;
|
||||
if (this.TryMapToHostDocumentPosition(generatedDocument, endOfLine, out Position? hostDocumentIndex, out _))
|
||||
if (this.TryMapToHostDocumentPosition(generatedDocument, line.Span.End, out _, out hostEndIndex))
|
||||
{
|
||||
if (range.Start.Line == lastNewLineAddedToLine)
|
||||
if (startLine == lastNewLineAddedToLine)
|
||||
{
|
||||
// If we already added a newline to this line, then we don't want to add another one, but
|
||||
// we do need to add a space between this edit and the previous one, because the normalization
|
||||
// will have swallowed it.
|
||||
hostDocumentEdits.Add(VsLspFactory.CreateTextEdit(hostDocumentIndex, " " + edit.NewText));
|
||||
yield return new TextChange(new TextSpan(hostEndIndex, 0), " " + change.NewText);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Otherwise, add a newline and the real content, and remember where we added it
|
||||
lastNewLineAddedToLine = range.Start.Line;
|
||||
hostDocumentEdits.Add(VsLspFactory.CreateTextEdit(
|
||||
hostDocumentIndex,
|
||||
Environment.NewLine + new string(' ', range.Start.Character) + edit.NewText));
|
||||
lastNewLineAddedToLine = startLine;
|
||||
yield return new TextChange(new TextSpan(hostEndIndex, 0), " " + Environment.NewLine + new string(' ', startChar) + change.NewText);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return hostDocumentEdits.ToArray();
|
||||
}
|
||||
|
||||
public bool TryMapToHostDocumentRange(IRazorGeneratedDocument generatedDocument, LinePositionSpan generatedDocumentRange, MappingBehavior mappingBehavior, out LinePositionSpan hostDocumentRange)
|
||||
{
|
||||
if (generatedDocument is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(generatedDocument));
|
||||
}
|
||||
|
||||
if (mappingBehavior == MappingBehavior.Strict)
|
||||
{
|
||||
return TryMapToHostDocumentRangeStrict(generatedDocument, generatedDocumentRange, out hostDocumentRange);
|
||||
|
@ -207,11 +191,6 @@ internal abstract class AbstractRazorDocumentMappingService(
|
|||
|
||||
public bool TryMapToGeneratedDocumentRange(IRazorGeneratedDocument generatedDocument, LinePositionSpan hostDocumentRange, out LinePositionSpan generatedDocumentRange)
|
||||
{
|
||||
if (generatedDocument is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(generatedDocument));
|
||||
}
|
||||
|
||||
if (generatedDocument.CodeDocument is not { } codeDocument)
|
||||
{
|
||||
throw new InvalidOperationException("Cannot use document mapping service on a generated document that has a null CodeDocument.");
|
||||
|
@ -220,8 +199,8 @@ internal abstract class AbstractRazorDocumentMappingService(
|
|||
generatedDocumentRange = default;
|
||||
|
||||
if (hostDocumentRange.End.Line < hostDocumentRange.Start.Line ||
|
||||
hostDocumentRange.End.Line == hostDocumentRange.Start.Line &&
|
||||
hostDocumentRange.End.Character < hostDocumentRange.Start.Character)
|
||||
(hostDocumentRange.End.Line == hostDocumentRange.Start.Line &&
|
||||
hostDocumentRange.End.Character < hostDocumentRange.Start.Character))
|
||||
{
|
||||
_logger.LogWarning($"RazorDocumentMappingService:TryMapToGeneratedDocumentRange original range end < start '{hostDocumentRange}'");
|
||||
Debug.Fail($"RazorDocumentMappingService:TryMapToGeneratedDocumentRange original range end < start '{hostDocumentRange}'");
|
||||
|
@ -265,11 +244,6 @@ internal abstract class AbstractRazorDocumentMappingService(
|
|||
|
||||
public bool TryMapToHostDocumentPosition(IRazorGeneratedDocument generatedDocument, int generatedDocumentIndex, out LinePosition hostDocumentPosition, out int hostDocumentIndex)
|
||||
{
|
||||
if (generatedDocument is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(generatedDocument));
|
||||
}
|
||||
|
||||
if (generatedDocument.CodeDocument is not { } codeDocument)
|
||||
{
|
||||
throw new InvalidOperationException("Cannot use document mapping service on a generated document that has a null CodeDocument.");
|
||||
|
@ -324,11 +298,6 @@ internal abstract class AbstractRazorDocumentMappingService(
|
|||
|
||||
private static bool TryMapToGeneratedDocumentPositionInternal(IRazorGeneratedDocument generatedDocument, int hostDocumentIndex, bool nextCSharpPositionOnFailure, out LinePosition generatedPosition, out int generatedIndex)
|
||||
{
|
||||
if (generatedDocument is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(generatedDocument));
|
||||
}
|
||||
|
||||
if (generatedDocument.CodeDocument is not { } codeDocument)
|
||||
{
|
||||
throw new InvalidOperationException("Cannot use document mapping service on a generated document that has a null CodeDocument.");
|
||||
|
@ -375,18 +344,13 @@ internal abstract class AbstractRazorDocumentMappingService(
|
|||
|
||||
static LinePosition GetGeneratedPosition(IRazorGeneratedDocument generatedDocument, int generatedIndex)
|
||||
{
|
||||
var generatedSource = GetGeneratedSourceText(generatedDocument);
|
||||
var generatedSource = generatedDocument.GetGeneratedSourceText();
|
||||
return generatedSource.GetLinePosition(generatedIndex);
|
||||
}
|
||||
}
|
||||
|
||||
public RazorLanguageKind GetLanguageKind(RazorCodeDocument codeDocument, int hostDocumentIndex, bool rightAssociative)
|
||||
{
|
||||
if (codeDocument is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(codeDocument));
|
||||
}
|
||||
|
||||
var classifiedSpans = GetClassifiedSpans(codeDocument);
|
||||
var tagHelperSpans = GetTagHelperSpans(codeDocument);
|
||||
var documentLength = codeDocument.Source.Text.Length;
|
||||
|
@ -555,7 +519,7 @@ internal abstract class AbstractRazorDocumentMappingService(
|
|||
{
|
||||
hostDocumentRange = default;
|
||||
|
||||
var generatedSourceText = GetGeneratedSourceText(generatedDocument);
|
||||
var generatedSourceText = generatedDocument.GetGeneratedSourceText();
|
||||
var range = generatedDocumentRange;
|
||||
if (!IsRangeWithinDocument(range, generatedSourceText))
|
||||
{
|
||||
|
@ -594,7 +558,7 @@ internal abstract class AbstractRazorDocumentMappingService(
|
|||
|
||||
hostDocumentRange = default;
|
||||
|
||||
var generatedSourceText = GetGeneratedSourceText(generatedDocument);
|
||||
var generatedSourceText = generatedDocument.GetGeneratedSourceText();
|
||||
|
||||
if (!IsRangeWithinDocument(generatedDocumentRange, generatedSourceText))
|
||||
{
|
||||
|
@ -679,7 +643,7 @@ internal abstract class AbstractRazorDocumentMappingService(
|
|||
// Doesn't map so lets try and infer some mappings
|
||||
|
||||
hostDocumentRange = default;
|
||||
var generatedSourceText = GetGeneratedSourceText(generatedDocument);
|
||||
var generatedSourceText = generatedDocument.GetGeneratedSourceText();
|
||||
|
||||
if (!IsRangeWithinDocument(generatedDocumentRange, generatedSourceText))
|
||||
{
|
||||
|
@ -794,7 +758,7 @@ internal abstract class AbstractRazorDocumentMappingService(
|
|||
var codeDocument = await documentContext.GetCodeDocumentAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var remappedEdits = RemapTextEditsCore(generatedDocumentUri, codeDocument, entry.Edits);
|
||||
if (remappedEdits is null || remappedEdits.Length == 0)
|
||||
if (remappedEdits.Length == 0)
|
||||
{
|
||||
// Nothing to do.
|
||||
continue;
|
||||
|
@ -811,7 +775,7 @@ internal abstract class AbstractRazorDocumentMappingService(
|
|||
});
|
||||
}
|
||||
|
||||
return remappedDocumentEdits.ToArray();
|
||||
return [.. remappedDocumentEdits];
|
||||
}
|
||||
|
||||
private async Task<Dictionary<string, TextEdit[]>> RemapDocumentEditsAsync(Dictionary<string, TextEdit[]> changes, CancellationToken cancellationToken)
|
||||
|
@ -836,7 +800,7 @@ internal abstract class AbstractRazorDocumentMappingService(
|
|||
|
||||
var codeDocument = await documentContext.GetCodeDocumentAsync(cancellationToken).ConfigureAwait(false);
|
||||
var remappedEdits = RemapTextEditsCore(uri, codeDocument, edits);
|
||||
if (remappedEdits is null || remappedEdits.Length == 0)
|
||||
if (remappedEdits.Length == 0)
|
||||
{
|
||||
// Nothing to do.
|
||||
continue;
|
||||
|
@ -871,7 +835,7 @@ internal abstract class AbstractRazorDocumentMappingService(
|
|||
remappedEdits.Add(edit);
|
||||
}
|
||||
|
||||
return remappedEdits.ToArray();
|
||||
return [.. remappedEdits];
|
||||
}
|
||||
|
||||
private IRazorGeneratedDocument? GetGeneratedDocumentFromGeneratedDocumentUri(Uri generatedDocumentUri, RazorCodeDocument codeDocument)
|
||||
|
@ -890,16 +854,6 @@ internal abstract class AbstractRazorDocumentMappingService(
|
|||
}
|
||||
}
|
||||
|
||||
private static SourceText GetGeneratedSourceText(IRazorGeneratedDocument generatedDocument)
|
||||
{
|
||||
if (generatedDocument.CodeDocument is not { } codeDocument)
|
||||
{
|
||||
throw new InvalidOperationException("Cannot use document mapping service on a generated document that has a null CodeDocument.");
|
||||
}
|
||||
|
||||
return codeDocument.GetGeneratedSourceText(generatedDocument);
|
||||
}
|
||||
|
||||
private static ImmutableArray<ClassifiedSpanInternal> GetClassifiedSpans(RazorCodeDocument document)
|
||||
{
|
||||
// Since this service is called so often, we get a good performance improvement by caching these values
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
|
@ -13,7 +14,7 @@ namespace Microsoft.CodeAnalysis.Razor.DocumentMapping;
|
|||
|
||||
internal interface IRazorDocumentMappingService
|
||||
{
|
||||
TextEdit[] GetHostDocumentEdits(IRazorGeneratedDocument generatedDocument, TextEdit[] generatedDocumentEdits);
|
||||
IEnumerable<TextChange> GetHostDocumentEdits(IRazorGeneratedDocument generatedDocument, IEnumerable<TextChange> generatedDocumentEdits);
|
||||
|
||||
bool TryMapToHostDocumentRange(IRazorGeneratedDocument generatedDocument, LinePositionSpan generatedDocumentRange, MappingBehavior mappingBehavior, out LinePositionSpan hostDocumentRange);
|
||||
|
||||
|
|
|
@ -3,8 +3,10 @@
|
|||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Razor;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
|
||||
using Microsoft.CodeAnalysis.Razor.Protocol;
|
||||
|
@ -16,6 +18,16 @@ namespace Microsoft.CodeAnalysis.Razor.DocumentMapping;
|
|||
|
||||
internal static class IRazorDocumentMappingServiceExtensions
|
||||
{
|
||||
public static TextEdit[] GetHostDocumentEdits(this IRazorDocumentMappingService service, IRazorGeneratedDocument generatedDocument, TextEdit[] generatedDocumentEdits)
|
||||
{
|
||||
var generatedDocumentSourceText = generatedDocument.GetGeneratedSourceText();
|
||||
var documentText = generatedDocument.CodeDocument.AssumeNotNull().Source.Text;
|
||||
|
||||
var changes = generatedDocumentEdits.Select(generatedDocumentSourceText.GetTextChange);
|
||||
var mappedChanges = service.GetHostDocumentEdits(generatedDocument, changes);
|
||||
return mappedChanges.Select(documentText.GetTextEdit).ToArray();
|
||||
}
|
||||
|
||||
public static bool TryMapToHostDocumentRange(this IRazorDocumentMappingService service, IRazorGeneratedDocument generatedDocument, LinePositionSpan projectedRange, out LinePositionSpan originalRange)
|
||||
=> service.TryMapToHostDocumentRange(generatedDocument, projectedRange, MappingBehavior.Strict, out originalRange);
|
||||
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
using System;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Language;
|
||||
|
||||
internal static class IRazorGeneratedDocumentExtensions
|
||||
{
|
||||
public static SourceText GetGeneratedSourceText(this IRazorGeneratedDocument generatedDocument)
|
||||
{
|
||||
if (generatedDocument.CodeDocument is not { } codeDocument)
|
||||
{
|
||||
throw new InvalidOperationException("Cannot use document mapping service on a generated document that has a null CodeDocument.");
|
||||
}
|
||||
|
||||
return codeDocument.GetGeneratedSourceText(generatedDocument);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNetCore.Razor;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
|
||||
namespace Roslyn.LanguageServer.Protocol;
|
||||
|
||||
internal static partial class RoslynLspExtensions
|
||||
{
|
||||
public static Range GetRange(this SourceText text, TextSpan span)
|
||||
=> text.GetLinePositionSpan(span).ToRange();
|
||||
|
||||
public static TextSpan GetTextSpan(this SourceText text, Range range)
|
||||
=> text.GetTextSpan(range.Start.Line, range.Start.Character, range.End.Line, range.End.Character);
|
||||
|
||||
public static TextChange GetTextChange(this SourceText text, TextEdit edit)
|
||||
=> new(text.GetTextSpan(edit.Range), edit.NewText);
|
||||
|
||||
public static TextEdit GetTextEdit(this SourceText text, TextChange change)
|
||||
=> RoslynLspFactory.CreateTextEdit(text.GetRange(change.Span), change.NewText.AssumeNotNull());
|
||||
|
||||
}
|
|
@ -3,11 +3,10 @@
|
|||
|
||||
using System;
|
||||
using Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost;
|
||||
using Microsoft.VisualStudio.LanguageServer.Protocol;
|
||||
|
||||
namespace Microsoft.VisualStudio.Razor.LanguageClient.Extensions;
|
||||
namespace Roslyn.LanguageServer.Protocol;
|
||||
|
||||
internal static class TextDocumentExtensions
|
||||
internal static partial class RoslynLspExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns a copy of the passed in <see cref="TextDocumentIdentifier"/> with the passed in <see cref="Uri"/>.
|
|
@ -12,4 +12,10 @@ internal static class SolutionExtensions
|
|||
return solution.GetProject(projectId)
|
||||
?? ThrowHelper.ThrowInvalidOperationException<Project>($"The projectId {projectId} did not exist in {solution}.");
|
||||
}
|
||||
|
||||
internal static Document GetRequiredDocument(this Solution solution, DocumentId documentId)
|
||||
{
|
||||
return solution.GetDocument(documentId)
|
||||
?? ThrowHelper.ThrowInvalidOperationException<Document>($"The document {documentId} did not exist in {solution.FilePath ?? "solution"}.");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
// 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 Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost;
|
||||
|
||||
namespace Microsoft.VisualStudio.LanguageServer.Protocol;
|
||||
|
||||
internal static partial class VsLspExtensions
|
||||
|
@ -9,4 +12,27 @@ internal static partial class VsLspExtensions
|
|||
=> textDocumentIdentifier is VSTextDocumentIdentifier vsIdentifier
|
||||
? vsIdentifier.ProjectContext
|
||||
: null;
|
||||
|
||||
/// <summary>
|
||||
/// Returns a copy of the passed in <see cref="TextDocumentIdentifier"/> with the passed in <see cref="Uri"/>.
|
||||
/// </summary>
|
||||
public static TextDocumentIdentifier WithUri(this TextDocumentIdentifier textDocumentIdentifier, Uri uri)
|
||||
{
|
||||
if (textDocumentIdentifier is VSTextDocumentIdentifier vsTdi)
|
||||
{
|
||||
return new VSTextDocumentIdentifier
|
||||
{
|
||||
Uri = uri,
|
||||
ProjectContext = vsTdi.ProjectContext
|
||||
};
|
||||
}
|
||||
|
||||
return new TextDocumentIdentifier
|
||||
{
|
||||
Uri = uri
|
||||
};
|
||||
}
|
||||
|
||||
public static RazorTextDocumentIdentifier ToRazorTextDocumentIdentifier(this TextDocumentIdentifier textDocumentIdentifier)
|
||||
=> new RazorTextDocumentIdentifier(textDocumentIdentifier.Uri, (textDocumentIdentifier as VSTextDocumentIdentifier)?.ProjectContext?.Id);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
using Roslyn.LanguageServer.Protocol;
|
||||
|
||||
namespace Microsoft.CodeAnalysis.Razor.Protocol.InlayHints;
|
||||
|
||||
internal record class InlayHintDataWrapper(TextDocumentIdentifier TextDocument, object? OriginalData, Position OriginalPosition);
|
|
@ -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.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CodeAnalysis.ExternalAccess.Razor;
|
||||
using Roslyn.LanguageServer.Protocol;
|
||||
|
||||
namespace Microsoft.CodeAnalysis.Razor.Remote;
|
||||
|
||||
internal interface IRemoteInlayHintService : IRemoteJsonService
|
||||
{
|
||||
ValueTask<InlayHint[]?> GetInlayHintsAsync(JsonSerializableRazorPinnedSolutionInfoWrapper solutionInfo, JsonSerializableDocumentId razorDocumentId, InlayHintParams inlayHintParams, bool displayAllOverride, CancellationToken cancellationToken);
|
||||
|
||||
ValueTask<InlayHint> ResolveHintAsync(JsonSerializableRazorPinnedSolutionInfoWrapper solutionInfo, JsonSerializableDocumentId razorDocumentId, InlayHint inlayHint, CancellationToken cancellationToken);
|
||||
}
|
|
@ -27,6 +27,7 @@ internal static class RazorServices
|
|||
internal static readonly IEnumerable<(Type, Type?)> JsonServices =
|
||||
[
|
||||
(typeof(IRemoteSignatureHelpService), null),
|
||||
(typeof(IRemoteInlayHintService), null),
|
||||
];
|
||||
|
||||
private const string ComponentName = "Razor";
|
||||
|
|
|
@ -0,0 +1,115 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.AspNetCore.Razor.Language.Syntax;
|
||||
using Microsoft.AspNetCore.Razor.PooledObjects;
|
||||
using Microsoft.CodeAnalysis.ExternalAccess.Razor;
|
||||
using Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost.Handlers;
|
||||
using Microsoft.CodeAnalysis.Razor.DocumentMapping;
|
||||
using Microsoft.CodeAnalysis.Razor.Protocol.InlayHints;
|
||||
using Microsoft.CodeAnalysis.Razor.Remote;
|
||||
using Microsoft.CodeAnalysis.Razor.Workspaces;
|
||||
using Microsoft.CodeAnalysis.Remote.Razor.ProjectSystem;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
using Roslyn.LanguageServer.Protocol;
|
||||
|
||||
namespace Microsoft.CodeAnalysis.Remote.Razor;
|
||||
|
||||
internal sealed partial class RemoteInlayHintService(in ServiceArgs args) : RazorDocumentServiceBase(in args), IRemoteInlayHintService
|
||||
{
|
||||
internal sealed class Factory : FactoryBase<IRemoteInlayHintService>
|
||||
{
|
||||
protected override IRemoteInlayHintService CreateService(in ServiceArgs args)
|
||||
=> new RemoteInlayHintService(in args);
|
||||
}
|
||||
|
||||
private readonly IRazorDocumentMappingService _documentMappingService = args.ExportProvider.GetExportedValue<IRazorDocumentMappingService>();
|
||||
private readonly IFilePathService _filePathService = args.ExportProvider.GetExportedValue<IFilePathService>();
|
||||
|
||||
public ValueTask<InlayHint[]?> GetInlayHintsAsync(JsonSerializableRazorPinnedSolutionInfoWrapper solutionInfo, JsonSerializableDocumentId razorDocumentId, InlayHintParams inlayHintParams, bool displayAllOverride, CancellationToken cancellationToken)
|
||||
=> RunServiceAsync(
|
||||
solutionInfo,
|
||||
razorDocumentId,
|
||||
context => GetInlayHintsAsync(context, inlayHintParams, displayAllOverride, cancellationToken),
|
||||
cancellationToken);
|
||||
|
||||
private async ValueTask<InlayHint[]?> GetInlayHintsAsync(RemoteDocumentContext context, InlayHintParams inlayHintParams, bool displayAllOverride, CancellationToken cancellationToken)
|
||||
{
|
||||
var codeDocument = await context.GetCodeDocumentAsync(cancellationToken).ConfigureAwait(false);
|
||||
var csharpDocument = codeDocument.GetCSharpDocument();
|
||||
|
||||
var span = inlayHintParams.Range.ToLinePositionSpan();
|
||||
|
||||
// We are given a range by the client, but our mapping only succeeds if the start and end of the range can both be mapped
|
||||
// to C#. Since that doesn't logically match what we want from inlay hints, we instead get the minimum range of mappable
|
||||
// C# to get hints for. We'll filter that later, to remove the sections that can't be mapped back.
|
||||
if (!_documentMappingService.TryMapToGeneratedDocumentRange(csharpDocument, span, out var projectedLinePositionSpan) &&
|
||||
!codeDocument.TryGetMinimalCSharpRange(span, out projectedLinePositionSpan))
|
||||
{
|
||||
// There's no C# in the range.
|
||||
return null;
|
||||
}
|
||||
|
||||
var generatedDocument = await context.GetGeneratedDocumentAsync(_filePathService, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var textDocument = inlayHintParams.TextDocument.WithUri(generatedDocument.CreateUri());
|
||||
var range = projectedLinePositionSpan.ToRange();
|
||||
|
||||
var hints = await InlayHints.GetInlayHintsAsync(generatedDocument, textDocument, range, displayAllOverride, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (hints is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
using var inlayHintsBuilder = new PooledArrayBuilder<InlayHint>();
|
||||
var razorSourceText = codeDocument.Source.Text;
|
||||
var csharpSourceText = codeDocument.GetCSharpSourceText();
|
||||
var syntaxTree = codeDocument.GetSyntaxTree();
|
||||
foreach (var hint in hints)
|
||||
{
|
||||
if (csharpSourceText.TryGetAbsoluteIndex(hint.Position.ToLinePosition(), out var absoluteIndex) &&
|
||||
_documentMappingService.TryMapToHostDocumentPosition(csharpDocument, absoluteIndex, out var hostDocumentPosition, out var hostDocumentIndex))
|
||||
{
|
||||
// We know this C# maps to Razor, but does it map to Razor that we like?
|
||||
var node = syntaxTree.Root.FindInnermostNode(hostDocumentIndex);
|
||||
if (node?.FirstAncestorOrSelf<MarkupTagHelperAttributeValueSyntax>() is not null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (hint.TextEdits is not null)
|
||||
{
|
||||
var changes = hint.TextEdits.Select(csharpSourceText.GetTextChange);
|
||||
var mappedChanges = _documentMappingService.GetHostDocumentEdits(csharpDocument, changes);
|
||||
hint.TextEdits = mappedChanges.Select(razorSourceText.GetTextEdit).ToArray();
|
||||
}
|
||||
|
||||
hint.Data = new InlayHintDataWrapper(inlayHintParams.TextDocument, hint.Data, hint.Position);
|
||||
hint.Position = hostDocumentPosition.ToPosition();
|
||||
|
||||
inlayHintsBuilder.Add(hint);
|
||||
}
|
||||
}
|
||||
|
||||
return inlayHintsBuilder.ToArray();
|
||||
}
|
||||
|
||||
public ValueTask<InlayHint> ResolveHintAsync(JsonSerializableRazorPinnedSolutionInfoWrapper solutionInfo, JsonSerializableDocumentId razorDocumentId, InlayHint inlayHint, CancellationToken cancellationToken)
|
||||
=> RunServiceAsync(
|
||||
solutionInfo,
|
||||
razorDocumentId,
|
||||
context => ResolveInlayHintAsync(context, inlayHint, cancellationToken),
|
||||
cancellationToken);
|
||||
|
||||
private async ValueTask<InlayHint> ResolveInlayHintAsync(RemoteDocumentContext context, InlayHint inlayHint, CancellationToken cancellationToken)
|
||||
{
|
||||
var generatedDocument = await context.GetGeneratedDocumentAsync(_filePathService, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return await InlayHints.ResolveInlayHintAsync(generatedDocument, inlayHint, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
|
@ -19,6 +19,12 @@ internal static class DocumentContextExtensions
|
|||
Debug.Assert(documentContext.Snapshot is RemoteDocumentSnapshot, "This method only works on document contexts created in the OOP process");
|
||||
|
||||
var snapshot = (RemoteDocumentSnapshot)documentContext.Snapshot;
|
||||
|
||||
if (snapshot.TryGetGeneratedDocument(out var generatedDocument))
|
||||
{
|
||||
return generatedDocument;
|
||||
}
|
||||
|
||||
var razorDocument = snapshot.TextDocument;
|
||||
var solution = razorDocument.Project.Solution;
|
||||
|
||||
|
@ -27,13 +33,25 @@ internal static class DocumentContextExtensions
|
|||
var projectKey = razorDocument.Project.ToProjectKey();
|
||||
var generatedFilePath = filePathService.GetRazorCSharpFilePath(projectKey, razorDocument.FilePath.AssumeNotNull());
|
||||
var generatedDocumentId = solution.GetDocumentIdsWithFilePath(generatedFilePath).First(d => d.ProjectId == razorDocument.Project.Id);
|
||||
var generatedDocument = solution.GetDocument(generatedDocumentId).AssumeNotNull();
|
||||
generatedDocument = solution.GetRequiredDocument(generatedDocumentId);
|
||||
|
||||
var csharpSourceText = await documentContext.GetCSharpSourceTextAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// HACK: We're not in the same solution fork as the LSP server that provides content for this document
|
||||
generatedDocument = generatedDocument.WithText(csharpSourceText);
|
||||
|
||||
return generatedDocument;
|
||||
// Obviously this lock is not sufficient to avoid wasted work, but it does at least avoid mutating the snapshot
|
||||
// any more than just a once of caching of the generated document, which is what is really happening with the set
|
||||
// method call below.
|
||||
lock (snapshot)
|
||||
{
|
||||
if (snapshot.TryGetGeneratedDocument(out var generatedDocument2))
|
||||
{
|
||||
return generatedDocument2;
|
||||
}
|
||||
|
||||
snapshot.SetGeneratedDocument(generatedDocument);
|
||||
return generatedDocument;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,9 @@ namespace Microsoft.CodeAnalysis.Remote.Razor.ProjectSystem;
|
|||
|
||||
internal class RemoteDocumentSnapshot(TextDocument textDocument, RemoteProjectSnapshot projectSnapshot) : IDocumentSnapshot
|
||||
{
|
||||
// TODO: Delete this field when the source generator is hooked up
|
||||
private Document? _generatedDocument;
|
||||
|
||||
private readonly TextDocument _textDocument = textDocument;
|
||||
private readonly RemoteProjectSnapshot _projectSnapshot = projectSnapshot;
|
||||
|
||||
|
@ -81,4 +84,44 @@ internal class RemoteDocumentSnapshot(TextDocument textDocument, RemoteProjectSn
|
|||
|
||||
return new RemoteDocumentSnapshot(newDocument, _projectSnapshot);
|
||||
}
|
||||
|
||||
public bool TryGetGeneratedDocument([NotNullWhen(true)] out Document? generatedDocument)
|
||||
{
|
||||
// TODO: Delete this method when the source generator is hooked up
|
||||
generatedDocument = _generatedDocument;
|
||||
return _generatedDocument is not null;
|
||||
}
|
||||
|
||||
public void SetGeneratedDocument(Document generatedDocument)
|
||||
{
|
||||
if (_generatedDocument is not null)
|
||||
{
|
||||
ThrowHelper.ThrowInvalidOperationException("A single document snapshot can only ever possibly have a single generated document");
|
||||
}
|
||||
|
||||
_generatedDocument = generatedDocument;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the generated C# document for this snapshot
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// You're right, dear reader, it's very strange for a seemingly immutable object to have a set method, but we can get away
|
||||
/// with it here for some arguably tenuous reasons:
|
||||
/// 1. The generated document is generated from this snapshot, and we're only allowing setting it because it could be
|
||||
/// expensive to generate in the constructor.
|
||||
/// 2. This is only temporary until the source generator is properly hooked up.
|
||||
/// 3. If the Razor document changes, which would invalidate this generated document, then a new document snapshot would
|
||||
/// be created and this instance would never be used again
|
||||
/// </remarks>
|
||||
internal Document GetOrAddGeneratedDocument(Document generatedDocument)
|
||||
{
|
||||
// TODO: Delete this method when the source generator is hooked up
|
||||
if (_generatedDocument is null)
|
||||
{
|
||||
_generatedDocument = generatedDocument;
|
||||
}
|
||||
|
||||
return generatedDocument;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,6 @@ using Microsoft.CodeAnalysis.Razor.Remote;
|
|||
using Microsoft.CodeAnalysis.Razor.Workspaces;
|
||||
using Microsoft.VisualStudio.LanguageServer.ContainedLanguage;
|
||||
using Microsoft.VisualStudio.LanguageServer.Protocol;
|
||||
using Microsoft.VisualStudio.Razor.LanguageClient.Extensions;
|
||||
|
||||
namespace Microsoft.VisualStudio.Razor.LanguageClient.Cohost;
|
||||
|
||||
|
|
|
@ -15,7 +15,6 @@ using Microsoft.CodeAnalysis.Razor.Protocol.Folding;
|
|||
using Microsoft.CodeAnalysis.Razor.Remote;
|
||||
using Microsoft.VisualStudio.LanguageServer.ContainedLanguage;
|
||||
using Microsoft.VisualStudio.LanguageServer.Protocol;
|
||||
using Microsoft.VisualStudio.Razor.LanguageClient.Extensions;
|
||||
|
||||
namespace Microsoft.VisualStudio.Razor.LanguageClient.Cohost;
|
||||
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
using System.Composition;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Razor;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost;
|
||||
using Microsoft.CodeAnalysis.Razor.Remote;
|
||||
using Roslyn.LanguageServer.Protocol;
|
||||
using VSLSP = Microsoft.VisualStudio.LanguageServer.Protocol;
|
||||
|
||||
namespace Microsoft.VisualStudio.Razor.LanguageClient.Cohost;
|
||||
|
||||
#pragma warning disable RS0030 // Do not use banned APIs
|
||||
[Shared]
|
||||
[CohostEndpoint(Methods.TextDocumentInlayHintName)]
|
||||
[Export(typeof(IDynamicRegistrationProvider))]
|
||||
[ExportCohostStatelessLspService(typeof(CohostInlayHintEndpoint))]
|
||||
[method: ImportingConstructor]
|
||||
#pragma warning restore RS0030 // Do not use banned APIs
|
||||
internal class CohostInlayHintEndpoint(IRemoteServiceInvoker remoteServiceInvoker)
|
||||
: AbstractRazorCohostDocumentRequestHandler<InlayHintParams, InlayHint[]?>, IDynamicRegistrationProvider
|
||||
{
|
||||
private readonly IRemoteServiceInvoker _remoteServiceInvoker = remoteServiceInvoker;
|
||||
|
||||
protected override bool MutatesSolutionState => false;
|
||||
|
||||
protected override bool RequiresLSPSolution => true;
|
||||
|
||||
public VSLSP.Registration? GetRegistration(VSLSP.VSInternalClientCapabilities clientCapabilities, VSLSP.DocumentFilter[] filter, RazorCohostRequestContext requestContext)
|
||||
{
|
||||
if (clientCapabilities.TextDocument?.InlayHint?.DynamicRegistration == true)
|
||||
{
|
||||
return new VSLSP.Registration
|
||||
{
|
||||
Method = Methods.TextDocumentInlayHintName,
|
||||
RegisterOptions = new VSLSP.InlayHintRegistrationOptions()
|
||||
{
|
||||
DocumentSelector = filter
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected override RazorTextDocumentIdentifier? GetRazorTextDocumentIdentifier(InlayHintParams request)
|
||||
=> request.TextDocument.ToRazorTextDocumentIdentifier();
|
||||
|
||||
protected override Task<InlayHint[]?> HandleRequestAsync(InlayHintParams request, RazorCohostRequestContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
// TODO: Once the platform team have finished the work, check the "Show inlay hints while key pressed" option, and pass it along
|
||||
return HandleRequestAsync(request, context.TextDocument.AssumeNotNull(), displayAllOverride: false, cancellationToken);
|
||||
}
|
||||
|
||||
private async Task<InlayHint[]?> HandleRequestAsync(InlayHintParams request, TextDocument razorDocument, bool displayAllOverride, CancellationToken cancellationToken)
|
||||
{
|
||||
// Normally we could remove the await here, but in this case it neatly converts from ValueTask to Task for us,
|
||||
// and more importantly this method is essentially a public API entry point (via LSP) so having it appear in
|
||||
// call stacks is desirable
|
||||
return await _remoteServiceInvoker.TryInvokeAsync<IRemoteInlayHintService, InlayHint[]?>(
|
||||
razorDocument.Project.Solution,
|
||||
(service, solutionInfo, cancellationToken) => service.GetInlayHintsAsync(solutionInfo, razorDocument.Id, request, displayAllOverride, cancellationToken),
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
internal TestAccessor GetTestAccessor() => new(this);
|
||||
|
||||
internal readonly struct TestAccessor(CohostInlayHintEndpoint instance)
|
||||
{
|
||||
public Task<InlayHint[]?> HandleRequestAsync(InlayHintParams request, TextDocument razorDocument, bool displayAllOverride, CancellationToken cancellationToken)
|
||||
=> instance.HandleRequestAsync(request, razorDocument, displayAllOverride, cancellationToken);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,120 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
using System.Composition;
|
||||
using System.Diagnostics;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Razor;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost;
|
||||
using Microsoft.CodeAnalysis.Razor.Logging;
|
||||
using Microsoft.CodeAnalysis.Razor.Protocol.InlayHints;
|
||||
using Microsoft.CodeAnalysis.Razor.Remote;
|
||||
using Roslyn.LanguageServer.Protocol;
|
||||
using VSLSP = Microsoft.VisualStudio.LanguageServer.Protocol;
|
||||
|
||||
namespace Microsoft.VisualStudio.Razor.LanguageClient.Cohost;
|
||||
|
||||
#pragma warning disable RS0030 // Do not use banned APIs
|
||||
[Shared]
|
||||
[CohostEndpoint(Methods.InlayHintResolveName)]
|
||||
[Export(typeof(IDynamicRegistrationProvider))]
|
||||
[ExportCohostStatelessLspService(typeof(CohostInlayHintResolveEndpoint))]
|
||||
[method: ImportingConstructor]
|
||||
#pragma warning restore RS0030 // Do not use banned APIs
|
||||
internal class CohostInlayHintResolveEndpoint(IRemoteServiceInvoker remoteServiceInvoker, ILoggerFactory loggerFactory)
|
||||
: AbstractRazorCohostDocumentRequestHandler<InlayHint, InlayHint?>, IDynamicRegistrationProvider
|
||||
{
|
||||
private readonly IRemoteServiceInvoker _remoteServiceInvoker = remoteServiceInvoker;
|
||||
private readonly ILogger _logger = loggerFactory.GetOrCreateLogger<CohostInlayHintResolveEndpoint>();
|
||||
|
||||
protected override bool MutatesSolutionState => false;
|
||||
|
||||
protected override bool RequiresLSPSolution => true;
|
||||
|
||||
public VSLSP.Registration? GetRegistration(VSLSP.VSInternalClientCapabilities clientCapabilities, VSLSP.DocumentFilter[] filter, RazorCohostRequestContext requestContext)
|
||||
{
|
||||
if (clientCapabilities.TextDocument?.InlayHint?.DynamicRegistration == true)
|
||||
{
|
||||
return new VSLSP.Registration
|
||||
{
|
||||
Method = Methods.TextDocumentInlayHintName,
|
||||
RegisterOptions = new VSLSP.InlayHintRegistrationOptions()
|
||||
{
|
||||
DocumentSelector = filter
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected override RazorTextDocumentIdentifier? GetRazorTextDocumentIdentifier(InlayHint request)
|
||||
=> GetTextDocumentIdentifier(request)?.ToRazorTextDocumentIdentifier() ?? null;
|
||||
|
||||
private TextDocumentIdentifier? GetTextDocumentIdentifier(InlayHint request)
|
||||
{
|
||||
var data = GetInlayHintResolveData(request);
|
||||
if (data is null)
|
||||
{
|
||||
_logger.LogError($"Got a resolve request for an inlay hint but couldn't extract the data object. Raw data is: {request.Data}");
|
||||
return null;
|
||||
}
|
||||
|
||||
return data.TextDocument;
|
||||
}
|
||||
|
||||
protected override Task<InlayHint?> HandleRequestAsync(InlayHint request, RazorCohostRequestContext context, CancellationToken cancellationToken)
|
||||
=> HandleRequestAsync(request, context.TextDocument.AssumeNotNull(), cancellationToken);
|
||||
|
||||
private async Task<InlayHint?> HandleRequestAsync(InlayHint request, TextDocument razorDocument, CancellationToken cancellationToken)
|
||||
{
|
||||
var razorData = GetInlayHintResolveData(request).AssumeNotNull();
|
||||
var razorPosition = request.Position;
|
||||
request.Data = razorData.OriginalData;
|
||||
request.Position = razorData.OriginalPosition;
|
||||
|
||||
var hint = await _remoteServiceInvoker.TryInvokeAsync<IRemoteInlayHintService, InlayHint>(
|
||||
razorDocument.Project.Solution,
|
||||
(service, solutionInfo, cancellationToken) => service.ResolveHintAsync(solutionInfo, razorDocument.Id, request, cancellationToken),
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (hint is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
Debug.Assert(request.Position == hint.Position, "Resolving inlay hints should not change the position of them.");
|
||||
hint.Position = razorPosition;
|
||||
|
||||
return hint;
|
||||
}
|
||||
|
||||
private static InlayHintDataWrapper? GetInlayHintResolveData(InlayHint inlayHint)
|
||||
{
|
||||
if (inlayHint.Data is InlayHintDataWrapper wrapper)
|
||||
{
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
if (inlayHint.Data is JsonElement json)
|
||||
{
|
||||
return JsonSerializer.Deserialize<InlayHintDataWrapper>(json);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
internal TestAccessor GetTestAccessor() => new(this);
|
||||
|
||||
internal readonly struct TestAccessor(CohostInlayHintResolveEndpoint instance)
|
||||
{
|
||||
public TextDocumentIdentifier? GetTextDocumentIdentifier(InlayHint request)
|
||||
=> instance.GetTextDocumentIdentifier(request);
|
||||
|
||||
public Task<InlayHint?> HandleRequestAsync(InlayHint request, TextDocument razorDocument, CancellationToken cancellationToken)
|
||||
=> instance.HandleRequestAsync(request, razorDocument, cancellationToken);
|
||||
}
|
||||
}
|
|
@ -8,12 +8,10 @@ using Microsoft.AspNetCore.Razor;
|
|||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost;
|
||||
using Microsoft.CodeAnalysis.Razor.LinkedEditingRange;
|
||||
using Microsoft.CodeAnalysis.Razor.Logging;
|
||||
using Microsoft.CodeAnalysis.Razor.Remote;
|
||||
using Microsoft.CodeAnalysis.Razor.Workspaces;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
using Microsoft.VisualStudio.LanguageServer.Protocol;
|
||||
using Microsoft.VisualStudio.Razor.LanguageClient.Extensions;
|
||||
|
||||
namespace Microsoft.VisualStudio.Razor.LanguageClient.Cohost;
|
||||
|
||||
|
@ -24,11 +22,10 @@ namespace Microsoft.VisualStudio.Razor.LanguageClient.Cohost;
|
|||
[ExportCohostStatelessLspService(typeof(CohostLinkedEditingRangeEndpoint))]
|
||||
[method: ImportingConstructor]
|
||||
#pragma warning restore RS0030 // Do not use banned APIs
|
||||
internal class CohostLinkedEditingRangeEndpoint(IRemoteServiceInvoker remoteServiceInvoker, ILoggerFactory loggerFactory)
|
||||
internal class CohostLinkedEditingRangeEndpoint(IRemoteServiceInvoker remoteServiceInvoker)
|
||||
: AbstractRazorCohostDocumentRequestHandler<LinkedEditingRangeParams, LinkedEditingRanges?>, IDynamicRegistrationProvider
|
||||
{
|
||||
private readonly IRemoteServiceInvoker _remoteServiceInvoker = remoteServiceInvoker;
|
||||
private readonly ILogger _logger = loggerFactory.GetOrCreateLogger<CohostLinkedEditingRangeEndpoint>();
|
||||
|
||||
protected override bool MutatesSolutionState => false;
|
||||
|
||||
|
|
|
@ -11,12 +11,10 @@ using Microsoft.AspNetCore.Razor.LanguageServer.Hosting;
|
|||
using Microsoft.AspNetCore.Razor.Telemetry;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost;
|
||||
using Microsoft.CodeAnalysis.Razor.Logging;
|
||||
using Microsoft.CodeAnalysis.Razor.Remote;
|
||||
using Microsoft.CodeAnalysis.Razor.SemanticTokens;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
using Microsoft.VisualStudio.LanguageServer.Protocol;
|
||||
using Microsoft.VisualStudio.Razor.LanguageClient.Extensions;
|
||||
using Microsoft.VisualStudio.Razor.Settings;
|
||||
|
||||
namespace Microsoft.VisualStudio.Razor.LanguageClient.Cohost;
|
||||
|
@ -32,15 +30,13 @@ internal sealed class CohostSemanticTokensRangeEndpoint(
|
|||
IRemoteServiceInvoker remoteServiceInvoker,
|
||||
IClientSettingsManager clientSettingsManager,
|
||||
ISemanticTokensLegendService semanticTokensLegendService,
|
||||
ITelemetryReporter telemetryReporter,
|
||||
ILoggerFactory loggerFactory)
|
||||
ITelemetryReporter telemetryReporter)
|
||||
: AbstractRazorCohostDocumentRequestHandler<SemanticTokensRangeParams, SemanticTokens?>, IDynamicRegistrationProvider
|
||||
{
|
||||
private readonly IRemoteServiceInvoker _remoteServiceInvoker = remoteServiceInvoker;
|
||||
private readonly IClientSettingsManager _clientSettingsManager = clientSettingsManager;
|
||||
private readonly ISemanticTokensLegendService _semanticTokensLegendService = semanticTokensLegendService;
|
||||
private readonly ITelemetryReporter _telemetryReporter = telemetryReporter;
|
||||
private readonly ILogger _logger = loggerFactory.GetOrCreateLogger<CohostSemanticTokensRangeEndpoint>();
|
||||
|
||||
protected override bool MutatesSolutionState => false;
|
||||
protected override bool RequiresLSPSolution => true;
|
||||
|
|
|
@ -11,11 +11,11 @@ using Microsoft.CodeAnalysis;
|
|||
using Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost;
|
||||
using Microsoft.CodeAnalysis.Razor.Logging;
|
||||
using Microsoft.CodeAnalysis.Razor.Remote;
|
||||
using Microsoft.CodeAnalysis.Razor.Workspaces;
|
||||
using Microsoft.VisualStudio.LanguageServer.ContainedLanguage;
|
||||
using Microsoft.VisualStudio.LanguageServer.Protocol;
|
||||
using Microsoft.VisualStudio.Razor.LanguageClient;
|
||||
using Microsoft.VisualStudio.Razor.LanguageClient.Cohost;
|
||||
using Microsoft.VisualStudio.Razor.LanguageClient.Extensions;
|
||||
using Microsoft.VisualStudio.Razor.Settings;
|
||||
using static Roslyn.LanguageServer.Protocol.RoslynLspExtensions;
|
||||
using RoslynLspFactory = Roslyn.LanguageServer.Protocol.RoslynLspFactory;
|
||||
|
|
|
@ -12,7 +12,6 @@ using Microsoft.CodeAnalysis.Razor.Workspaces;
|
|||
using Microsoft.CodeAnalysis.Text;
|
||||
using Microsoft.VisualStudio.LanguageServer.ContainedLanguage;
|
||||
using Microsoft.VisualStudio.LanguageServer.Protocol;
|
||||
using Microsoft.VisualStudio.Razor.LanguageClient.Extensions;
|
||||
|
||||
namespace Microsoft.VisualStudio.Razor.LanguageClient.Cohost;
|
||||
|
||||
|
|
|
@ -11,7 +11,6 @@ using Microsoft.CodeAnalysis.Razor.Protocol;
|
|||
using Microsoft.CodeAnalysis.Razor.Protocol.Completion;
|
||||
using Microsoft.VisualStudio.LanguageServer.ContainedLanguage;
|
||||
using Microsoft.VisualStudio.LanguageServer.Protocol;
|
||||
using Microsoft.VisualStudio.Razor.LanguageClient.Extensions;
|
||||
using Microsoft.VisualStudio.Razor.Snippets;
|
||||
using StreamJsonRpc;
|
||||
|
||||
|
|
|
@ -9,7 +9,6 @@ using Microsoft.CodeAnalysis.Razor.Protocol;
|
|||
using Microsoft.CodeAnalysis.Razor.Protocol.Diagnostics;
|
||||
using Microsoft.VisualStudio.LanguageServer.ContainedLanguage;
|
||||
using Microsoft.VisualStudio.LanguageServer.Protocol;
|
||||
using Microsoft.VisualStudio.Razor.LanguageClient.Extensions;
|
||||
using StreamJsonRpc;
|
||||
|
||||
namespace Microsoft.VisualStudio.Razor.LanguageClient.Endpoints;
|
||||
|
|
|
@ -5,7 +5,6 @@ using System.Threading;
|
|||
using System.Threading.Tasks;
|
||||
using Microsoft.CodeAnalysis.Razor.Protocol;
|
||||
using Microsoft.VisualStudio.LanguageServer.Protocol;
|
||||
using Microsoft.VisualStudio.Razor.LanguageClient.Extensions;
|
||||
using StreamJsonRpc;
|
||||
|
||||
namespace Microsoft.VisualStudio.Razor.LanguageClient.Endpoints;
|
||||
|
|
|
@ -9,7 +9,6 @@ using System.Threading.Tasks;
|
|||
using Microsoft.CodeAnalysis.Razor.Protocol;
|
||||
using Microsoft.CodeAnalysis.Razor.Protocol.Folding;
|
||||
using Microsoft.VisualStudio.LanguageServer.Protocol;
|
||||
using Microsoft.VisualStudio.Razor.LanguageClient.Extensions;
|
||||
using StreamJsonRpc;
|
||||
|
||||
namespace Microsoft.VisualStudio.Razor.LanguageClient.Endpoints;
|
||||
|
|
|
@ -8,7 +8,6 @@ using System.Threading.Tasks;
|
|||
using Microsoft.CodeAnalysis.Razor.Protocol;
|
||||
using Microsoft.CodeAnalysis.Razor.Protocol.Formatting;
|
||||
using Microsoft.VisualStudio.LanguageServer.Protocol;
|
||||
using Microsoft.VisualStudio.Razor.LanguageClient.Extensions;
|
||||
using StreamJsonRpc;
|
||||
|
||||
namespace Microsoft.VisualStudio.Razor.LanguageClient.Endpoints;
|
||||
|
|
|
@ -5,7 +5,6 @@ using System.Threading;
|
|||
using System.Threading.Tasks;
|
||||
using Microsoft.CodeAnalysis.Razor.Protocol;
|
||||
using Microsoft.VisualStudio.LanguageServer.Protocol;
|
||||
using Microsoft.VisualStudio.Razor.LanguageClient.Extensions;
|
||||
using StreamJsonRpc;
|
||||
|
||||
namespace Microsoft.VisualStudio.Razor.LanguageClient.Endpoints;
|
||||
|
|
|
@ -5,7 +5,6 @@ using System.Threading;
|
|||
using System.Threading.Tasks;
|
||||
using Microsoft.CodeAnalysis.Razor.Protocol;
|
||||
using Microsoft.VisualStudio.LanguageServer.Protocol;
|
||||
using Microsoft.VisualStudio.Razor.LanguageClient.Extensions;
|
||||
using StreamJsonRpc;
|
||||
|
||||
namespace Microsoft.VisualStudio.Razor.LanguageClient.Endpoints;
|
||||
|
|
|
@ -5,7 +5,6 @@ using System.Threading;
|
|||
using System.Threading.Tasks;
|
||||
using Microsoft.CodeAnalysis.Razor.Protocol;
|
||||
using Microsoft.VisualStudio.LanguageServer.Protocol;
|
||||
using Microsoft.VisualStudio.Razor.LanguageClient.Extensions;
|
||||
using StreamJsonRpc;
|
||||
|
||||
namespace Microsoft.VisualStudio.Razor.LanguageClient.Endpoints;
|
||||
|
|
|
@ -5,7 +5,6 @@ using System.Threading;
|
|||
using System.Threading.Tasks;
|
||||
using Microsoft.CodeAnalysis.Razor.Protocol;
|
||||
using Microsoft.VisualStudio.LanguageServer.Protocol;
|
||||
using Microsoft.VisualStudio.Razor.LanguageClient.Extensions;
|
||||
using StreamJsonRpc;
|
||||
|
||||
namespace Microsoft.VisualStudio.Razor.LanguageClient.Endpoints;
|
||||
|
|
|
@ -6,7 +6,6 @@ using System.Threading.Tasks;
|
|||
using Microsoft.CodeAnalysis.Razor.Protocol;
|
||||
using Microsoft.CodeAnalysis.Razor.Protocol.CodeActions;
|
||||
using Microsoft.VisualStudio.LanguageServer.Protocol;
|
||||
using Microsoft.VisualStudio.Razor.LanguageClient.Extensions;
|
||||
using StreamJsonRpc;
|
||||
|
||||
namespace Microsoft.VisualStudio.Razor.LanguageClient.Endpoints;
|
||||
|
|
|
@ -6,7 +6,6 @@ using System.Threading;
|
|||
using System.Threading.Tasks;
|
||||
using Microsoft.CodeAnalysis.Razor.Protocol;
|
||||
using Microsoft.VisualStudio.LanguageServer.Protocol;
|
||||
using Microsoft.VisualStudio.Razor.LanguageClient.Extensions;
|
||||
using StreamJsonRpc;
|
||||
|
||||
namespace Microsoft.VisualStudio.Razor.LanguageClient.Endpoints;
|
||||
|
|
|
@ -5,7 +5,6 @@ using System.Threading;
|
|||
using System.Threading.Tasks;
|
||||
using Microsoft.CodeAnalysis.Razor.Protocol;
|
||||
using Microsoft.VisualStudio.LanguageServer.Protocol;
|
||||
using Microsoft.VisualStudio.Razor.LanguageClient.Extensions;
|
||||
using StreamJsonRpc;
|
||||
|
||||
namespace Microsoft.VisualStudio.Razor.LanguageClient.Endpoints;
|
||||
|
|
|
@ -1,41 +0,0 @@
|
|||
// 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.Cohost;
|
||||
using Microsoft.CodeAnalysis.Razor.Logging;
|
||||
using Microsoft.VisualStudio.Razor.LanguageClient.Cohost;
|
||||
using StreamJsonRpc;
|
||||
|
||||
namespace Microsoft.VisualStudio.Razor.LanguageClient.Extensions;
|
||||
|
||||
internal static class RazorCohostRequestContextExtensions
|
||||
{
|
||||
public static async Task<TResponse?> DelegateRequestAsync<TDelegatedParams, TResponse>(this RazorCohostRequestContext requestContext, string target, TDelegatedParams @params, ILogger logger, CancellationToken cancellationToken)
|
||||
{
|
||||
var clientConnection = requestContext.GetClientConnection();
|
||||
|
||||
try
|
||||
{
|
||||
return await clientConnection.SendRequestAsync<TDelegatedParams, TResponse>(target, @params, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (RemoteInvocationException e)
|
||||
{
|
||||
logger.LogError(e, $"Error calling delegate server for {target}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public static RazorCohostClientConnection GetClientConnection(this RazorCohostRequestContext requestContext)
|
||||
{
|
||||
// TODO: We can't MEF import IRazorCohostClientLanguageServerManager in the constructor. We can make this work
|
||||
// by having it implement a base class, RazorClientConnectionBase or something, that in turn implements
|
||||
// AbstractRazorLspService (defined in Roslyn) and then move everything from importing IClientConnection
|
||||
// to importing the new base class, so we can continue to share services.
|
||||
//
|
||||
// Until then we have to get the service from the request context.
|
||||
var clientLanguageServerManager = requestContext.GetRequiredService<IRazorCohostClientLanguageServerManager>();
|
||||
return new RazorCohostClientConnection(clientLanguageServerManager);
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@
|
|||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
|
@ -18,8 +19,8 @@ internal class TestDocumentMappingService : IRazorDocumentMappingService
|
|||
public LinePosition? GeneratedPosition { get; set; }
|
||||
public int GeneratedIndex { get; set; }
|
||||
|
||||
public TextEdit[] GetHostDocumentEdits(IRazorGeneratedDocument generatedDocument, TextEdit[] generatedDocumentEdits)
|
||||
=> Array.Empty<TextEdit>();
|
||||
public IEnumerable<TextChange> GetHostDocumentEdits(IRazorGeneratedDocument generatedDocument, IEnumerable<TextChange> generatedDocumentEdits)
|
||||
=> [];
|
||||
|
||||
public RazorLanguageKind GetLanguageKind(RazorCodeDocument codeDocument, int hostDocumentIndex, bool rightAssociative)
|
||||
=> LanguageKind;
|
||||
|
|
|
@ -6,7 +6,6 @@ using System.Collections.Generic;
|
|||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Razor.Test.Common.Workspaces;
|
||||
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
|
||||
using Microsoft.CodeAnalysis.Testing;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
|
@ -96,7 +95,7 @@ public class InlayHintEndpointTest(ITestOutputHelper testOutput) : SingleServerD
|
|||
|
||||
var service = new InlayHintService(DocumentMappingService);
|
||||
|
||||
var endpoint = new InlayHintEndpoint(TestLanguageServerFeatureOptions.Instance, service, languageServer);
|
||||
var endpoint = new InlayHintEndpoint(service, languageServer);
|
||||
var resolveEndpoint = new InlayHintResolveEndpoint(service, languageServer);
|
||||
|
||||
var request = new InlayHintParams()
|
||||
|
|
|
@ -5,9 +5,11 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Xml;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.AspNetCore.Razor.Test.Common;
|
||||
using Microsoft.CodeAnalysis.ExternalAccess.Razor;
|
||||
using Microsoft.CodeAnalysis.Remote.Razor;
|
||||
using Roslyn.Test.Utilities;
|
||||
using Xunit;
|
||||
|
@ -17,6 +19,9 @@ namespace Microsoft.CodeAnalysis.Razor.Remote;
|
|||
|
||||
public class RazorServicesTest(ITestOutputHelper testOutputHelper) : ToolingTestBase(testOutputHelper)
|
||||
{
|
||||
private const string Prefix = "IRemote";
|
||||
private const string Suffix = "Service";
|
||||
|
||||
private readonly static XmlDocument s_servicesFile = LoadServicesFile();
|
||||
|
||||
[Theory]
|
||||
|
@ -34,6 +39,32 @@ public class RazorServicesTest(ITestOutputHelper testOutputHelper) : ToolingTest
|
|||
VerifyService(serviceType, callbackType);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(JsonServices))]
|
||||
public void JsonServicesHaveTheRightParameters(Type serviceType, Type? _)
|
||||
{
|
||||
Assert.True(typeof(IRemoteJsonService).IsAssignableFrom(serviceType));
|
||||
|
||||
var found = false;
|
||||
foreach (var method in serviceType.GetMethods(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public))
|
||||
{
|
||||
if (method.Name != "RunServiceAsync" &&
|
||||
method.GetParameters() is [{ ParameterType: { } parameterType }, ..])
|
||||
{
|
||||
if (typeof(RazorPinnedSolutionInfoWrapper).IsAssignableFrom(parameterType))
|
||||
{
|
||||
Assert.Fail($"Method {method.Name} in a Json service has a pinned solution info wrapper parameter that isn't Json serializable");
|
||||
}
|
||||
else if (typeof(JsonSerializableRazorPinnedSolutionInfoWrapper).IsAssignableFrom(parameterType))
|
||||
{
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Assert.True(found, "Didn't find a method to validate, which means maybe this test is invalid");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RazorServicesContainsAllServices()
|
||||
{
|
||||
|
@ -82,16 +113,13 @@ public class RazorServicesTest(ITestOutputHelper testOutputHelper) : ToolingTest
|
|||
|
||||
private static void VerifyService(Type serviceType, Type? callbackType)
|
||||
{
|
||||
const string prefix = "IRemote";
|
||||
const string suffix = "Service";
|
||||
|
||||
Assert.Null(callbackType);
|
||||
|
||||
var serviceName = serviceType.Name;
|
||||
Assert.StartsWith(prefix, serviceName);
|
||||
Assert.EndsWith(suffix, serviceName);
|
||||
Assert.StartsWith(Prefix, serviceName);
|
||||
Assert.EndsWith(Suffix, serviceName);
|
||||
|
||||
var shortName = serviceName.Substring(prefix.Length, serviceName.Length - prefix.Length - suffix.Length);
|
||||
var shortName = serviceName.Substring(Prefix.Length, serviceName.Length - Prefix.Length - Suffix.Length);
|
||||
var servicePropsEntry = $"Microsoft.VisualStudio.Razor.{shortName}";
|
||||
|
||||
var serviceNode = s_servicesFile.SelectSingleNode($"/Project/ItemGroup/ServiceHubService[@Include='{servicePropsEntry}']");
|
||||
|
|
|
@ -0,0 +1,204 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Razor;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.ExternalAccess.Razor;
|
||||
using Microsoft.CodeAnalysis.Testing;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
using Roslyn.LanguageServer.Protocol;
|
||||
using Roslyn.Test.Utilities;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Microsoft.VisualStudio.Razor.LanguageClient.Cohost;
|
||||
|
||||
public class CohostInlayHintEndpointTest(ITestOutputHelper testOutputHelper) : CohostEndpointTestBase(testOutputHelper)
|
||||
{
|
||||
[Fact]
|
||||
public Task InlayHints()
|
||||
=> VerifyInlayHintsAsync(
|
||||
input: """
|
||||
|
||||
<div></div>
|
||||
|
||||
@functions {
|
||||
private void M(string thisIsMyString)
|
||||
{
|
||||
var {|int:x|} = 5;
|
||||
|
||||
var {|string:y|} = "Hello";
|
||||
|
||||
M({|thisIsMyString:"Hello"|});
|
||||
}
|
||||
}
|
||||
|
||||
""",
|
||||
toolTipMap: new Dictionary<string, string>
|
||||
{
|
||||
{ "int", "struct System.Int32" },
|
||||
{ "string", "class System.String" },
|
||||
{ "thisIsMyString", "(parameter) string thisIsMyStr" }
|
||||
},
|
||||
output: """
|
||||
|
||||
<div></div>
|
||||
|
||||
@functions {
|
||||
private void M(string thisIsMyString)
|
||||
{
|
||||
int x = 5;
|
||||
|
||||
string y = "Hello";
|
||||
|
||||
M(thisIsMyString: "Hello");
|
||||
}
|
||||
}
|
||||
|
||||
""");
|
||||
|
||||
[Fact]
|
||||
public Task InlayHints_DisplayAllOverride()
|
||||
=> VerifyInlayHintsAsync(
|
||||
input: """
|
||||
|
||||
<div></div>
|
||||
|
||||
@functions {
|
||||
private void M(string thisIsMyString)
|
||||
{
|
||||
{|int:var|} x = 5;
|
||||
|
||||
{|string:var|} y = "Hello";
|
||||
|
||||
M({|thisIsMyString:"Hello"|});
|
||||
}
|
||||
}
|
||||
|
||||
""",
|
||||
toolTipMap: new Dictionary<string, string>
|
||||
{
|
||||
{ "int", "struct System.Int32" },
|
||||
{ "string", "class System.String" },
|
||||
{ "thisIsMyString", "(parameter) string thisIsMyStr" }
|
||||
},
|
||||
output: """
|
||||
|
||||
<div></div>
|
||||
|
||||
@functions {
|
||||
private void M(string thisIsMyString)
|
||||
{
|
||||
int x = 5;
|
||||
|
||||
string y = "Hello";
|
||||
|
||||
M(thisIsMyString: "Hello");
|
||||
}
|
||||
}
|
||||
|
||||
""",
|
||||
displayAllOverride: true);
|
||||
|
||||
[Fact]
|
||||
public Task InlayHints_ComponentAttributes()
|
||||
=> VerifyInlayHintsAsync(
|
||||
input: """
|
||||
|
||||
<div>
|
||||
<InputText Value="_value" />
|
||||
<InputText Value="@_value" />
|
||||
<InputText Value="@(_value)" />
|
||||
</div>
|
||||
|
||||
""",
|
||||
toolTipMap: [],
|
||||
output: """
|
||||
|
||||
<div>
|
||||
<InputText Value="_value" />
|
||||
<InputText Value="@_value" />
|
||||
<InputText Value="@(_value)" />
|
||||
</div>
|
||||
|
||||
""");
|
||||
|
||||
private async Task VerifyInlayHintsAsync(string input, Dictionary<string, string> toolTipMap, string output, bool displayAllOverride = false)
|
||||
{
|
||||
TestFileMarkupParser.GetSpans(input, out input, out ImmutableDictionary<string, ImmutableArray<TextSpan>> spansDict);
|
||||
var document = CreateProjectAndRazorDocument(input);
|
||||
var inputText = await document.GetTextAsync(DisposalToken);
|
||||
|
||||
var endpoint = new CohostInlayHintEndpoint(RemoteServiceInvoker);
|
||||
var resolveEndpoint = new CohostInlayHintResolveEndpoint(RemoteServiceInvoker, LoggerFactory);
|
||||
|
||||
var request = new InlayHintParams()
|
||||
{
|
||||
TextDocument = new TextDocumentIdentifier() { Uri = document.CreateUri() },
|
||||
Range = new()
|
||||
{
|
||||
Start = new(0, 0),
|
||||
End = new(inputText.Lines.Count, 0)
|
||||
}
|
||||
};
|
||||
|
||||
var hints = await endpoint.GetTestAccessor().HandleRequestAsync(request, document, displayAllOverride, DisposalToken);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(hints);
|
||||
Assert.Equal(spansDict.Values.Count(), hints.Length);
|
||||
|
||||
foreach (var hint in hints)
|
||||
{
|
||||
// Because our test input data can't have colons in the input, but parameter info returned from Roslyn does, we have to strip them off.
|
||||
var label = hint.Label.First.TrimEnd(':');
|
||||
Assert.True(spansDict.TryGetValue(label, out var spans), $"Expected {label} to be in test provided markers");
|
||||
|
||||
var span = Assert.Single(spans);
|
||||
var expectedRange = inputText.GetRange(span);
|
||||
// Inlay hints only have a position, so we ignore the end of the range that comes from the test input
|
||||
Assert.Equal(expectedRange.Start, hint.Position);
|
||||
|
||||
// This looks weird, but its what we have to do to satisfy the compiler :)
|
||||
string? expectedTooltip = null;
|
||||
Assert.True(toolTipMap?.TryGetValue(label, out expectedTooltip));
|
||||
Assert.NotNull(expectedTooltip);
|
||||
|
||||
// We need to pretend we're making real LSP requests by serializing the data blob at least
|
||||
var serializedHint = JsonSerializer.Deserialize<InlayHint>(JsonSerializer.SerializeToElement(hint)).AssumeNotNull();
|
||||
// Make sure we can resolve the document correctly
|
||||
var tdi = resolveEndpoint.GetTestAccessor().GetTextDocumentIdentifier(serializedHint);
|
||||
Assert.NotNull(tdi);
|
||||
Assert.Equal(document.CreateUri(), tdi.Uri);
|
||||
|
||||
// Make sure we, or really Roslyn, can resolve the hint correctly
|
||||
var resolvedHint = await resolveEndpoint.GetTestAccessor().HandleRequestAsync(serializedHint, document, DisposalToken);
|
||||
Assert.NotNull(resolvedHint);
|
||||
Assert.NotNull(resolvedHint.ToolTip);
|
||||
|
||||
if (resolvedHint.ToolTip.Value.TryGetFirst(out var plainTextTooltip))
|
||||
{
|
||||
Assert.Equal(expectedTooltip, plainTextTooltip);
|
||||
}
|
||||
else if (resolvedHint.ToolTip.Value.TryGetSecond(out var markupTooltip))
|
||||
{
|
||||
Assert.Contains(expectedTooltip, markupTooltip.Value);
|
||||
}
|
||||
}
|
||||
|
||||
// To validate edits, we have to collect them all together, and apply them backwards
|
||||
var changes = hints
|
||||
.SelectMany(h => h.TextEdits ?? [])
|
||||
.OrderByDescending(e => e.Range.Start.Line)
|
||||
.ThenByDescending(e => e.Range.Start.Character)
|
||||
.Select(inputText.GetTextChange);
|
||||
inputText = inputText.WithChanges(changes);
|
||||
|
||||
AssertEx.EqualOrDiff(output, inputText.ToString());
|
||||
}
|
||||
}
|
|
@ -162,7 +162,7 @@ public class CohostLinkedEditingRangeEndpointTest(ITestOutputHelper testOutputHe
|
|||
var document = CreateProjectAndRazorDocument(input);
|
||||
var sourceText = await document.GetTextAsync(DisposalToken);
|
||||
|
||||
var endpoint = new CohostLinkedEditingRangeEndpoint(RemoteServiceInvoker, LoggerFactory);
|
||||
var endpoint = new CohostLinkedEditingRangeEndpoint(RemoteServiceInvoker);
|
||||
|
||||
var request = new LinkedEditingRangeParams()
|
||||
{
|
||||
|
|
|
@ -9,8 +9,6 @@ using Microsoft.AspNetCore.Razor.LanguageServer.Semantic;
|
|||
using Microsoft.AspNetCore.Razor.PooledObjects;
|
||||
using Microsoft.AspNetCore.Razor.Telemetry;
|
||||
using Microsoft.CodeAnalysis.Razor.Settings;
|
||||
using Microsoft.CodeAnalysis.Razor.Workspaces;
|
||||
using Microsoft.CodeAnalysis.Remote.Razor;
|
||||
using Microsoft.CodeAnalysis.Remote.Razor.SemanticTokens;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
using Microsoft.VisualStudio.Razor.Settings;
|
||||
|
@ -107,7 +105,7 @@ public class CohostSemanticTokensRangeEndpointTest(ITestOutputHelper testOutputH
|
|||
var clientSettingsManager = new ClientSettingsManager([], null, null);
|
||||
clientSettingsManager.Update(ClientAdvancedSettings.Default with { ColorBackground = colorBackground });
|
||||
|
||||
var endpoint = new CohostSemanticTokensRangeEndpoint(RemoteServiceInvoker, clientSettingsManager, legend, NoOpTelemetryReporter.Instance, LoggerFactory);
|
||||
var endpoint = new CohostSemanticTokensRangeEndpoint(RemoteServiceInvoker, clientSettingsManager, legend, NoOpTelemetryReporter.Instance);
|
||||
|
||||
var span = new LinePositionSpan(new(0, 0), new(sourceText.Lines.Count, 0));
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче