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

Cohost Document Symbols #10728

Merged
merged 6 commits into from
Aug 21, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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
1 change: 1 addition & 0 deletions eng/targets/Services.props
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,6 @@
<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" />
<ServiceHubService Include="Microsoft.VisualStudio.Razor.DocumentSymbol" ClassName="Microsoft.CodeAnalysis.Remote.Razor.RemoteDocumentSymbolService+Factory" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -10,29 +10,24 @@
using Microsoft.AspNetCore.Razor.LanguageServer.Hosting;
using Microsoft.AspNetCore.Razor.PooledObjects;
using Microsoft.CodeAnalysis.Razor.DocumentMapping;
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
using Microsoft.CodeAnalysis.Razor.Protocol;
using Microsoft.CodeAnalysis.Razor.Protocol.DocumentSymbols;
using Microsoft.CodeAnalysis.Razor.Workspaces;
using Microsoft.VisualStudio.LanguageServer.Protocol;
using Microsoft.VisualStudio.Text.Tagging;

namespace Microsoft.AspNetCore.Razor.LanguageServer.DocumentSymbols;

[RazorLanguageServerEndpoint(Methods.TextDocumentDocumentSymbolName)]
internal class DocumentSymbolEndpoint : IRazorRequestHandler<DocumentSymbolParams, SumType<DocumentSymbol[], SymbolInformation[]>?>, ICapabilitiesProvider
internal class DocumentSymbolEndpoint(
IClientConnection clientConnection,
IDocumentSymbolService documentSymbolService,
LanguageServerFeatureOptions languageServerFeatureOptions) : IRazorRequestHandler<DocumentSymbolParams, SumType<DocumentSymbol[], SymbolInformation[]>?>, ICapabilitiesProvider
{
private readonly IClientConnection _clientConnection;
private readonly IDocumentMappingService _documentMappingService;
private readonly LanguageServerFeatureOptions _languageServerFeatureOptions;

public DocumentSymbolEndpoint(
IClientConnection clientConnection,
IDocumentMappingService documentMappingService,
LanguageServerFeatureOptions languageServerFeatureOptions)
{
_clientConnection = clientConnection ?? throw new ArgumentNullException(nameof(clientConnection));
_documentMappingService = documentMappingService ?? throw new ArgumentNullException(nameof(documentMappingService));
_languageServerFeatureOptions = languageServerFeatureOptions;
}
private readonly IClientConnection _clientConnection = clientConnection;
private readonly IDocumentSymbolService _documentSymbolService = documentSymbolService;
private readonly LanguageServerFeatureOptions _languageServerFeatureOptions = languageServerFeatureOptions;

public bool MutatesSolutionState => false;

Expand Down Expand Up @@ -76,74 +71,6 @@ public TextDocumentIdentifier GetTextDocumentIdentifier(DocumentSymbolParams req

var codeDocument = await documentContext.GetCodeDocumentAsync(cancellationToken).ConfigureAwait(false);
var csharpDocument = codeDocument.GetCSharpDocument();

if (symbols.TryGetFirst(out var documentSymbols))
{
return RemapDocumentSymbols(csharpDocument, documentSymbols);
}
else if (symbols.TryGetSecond(out var symbolInformations))
{
using var _ = ListPool<SymbolInformation>.GetPooledObject(out var mappedSymbols);

foreach (var symbolInformation in symbolInformations)
{
if (_documentMappingService.TryMapToHostDocumentRange(csharpDocument, symbolInformation.Location.Range, out var newRange))
{
symbolInformation.Location.Range = newRange;
symbolInformation.Location.Uri = documentContext.Uri;
mappedSymbols.Add(symbolInformation);
}
}

return mappedSymbols.ToArray();
}
else
{
Debug.Fail("Unsupported response type");
throw new InvalidOperationException();
}
}

private DocumentSymbol[]? RemapDocumentSymbols(RazorCSharpDocument csharpDocument, DocumentSymbol[]? documentSymbols)
{
if (documentSymbols is null)
{
return null;
}

using var _ = ListPool<DocumentSymbol>.GetPooledObject(out var mappedSymbols);

foreach (var documentSymbol in documentSymbols)
{
if (TryRemapRanges(csharpDocument, documentSymbol))
{
documentSymbol.Children = RemapDocumentSymbols(csharpDocument, documentSymbol.Children);

mappedSymbols.Add(documentSymbol);
}
else if (documentSymbol.Children is [_, ..] &&
RemapDocumentSymbols(csharpDocument, documentSymbol.Children) is [_, ..] mappedChildren)
{
// This range didn't map, but some/all of its children did, so we promote them to this level so we don't
// lose any information.
mappedSymbols.AddRange(mappedChildren);
}
}

return mappedSymbols.ToArray();

bool TryRemapRanges(RazorCSharpDocument csharpDocument, DocumentSymbol documentSymbol)
{
if (_documentMappingService.TryMapToHostDocumentRange(csharpDocument, documentSymbol.Range, out var newRange) &&
_documentMappingService.TryMapToHostDocumentRange(csharpDocument, documentSymbol.SelectionRange, out var newSelectionRange))
{
documentSymbol.Range = newRange;
documentSymbol.SelectionRange = newSelectionRange;

return true;
}

return false;
}
return _documentSymbolService.GetDocumentSymbols(documentContext.Uri, csharpDocument, symbols);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
using Microsoft.AspNetCore.Razor.Telemetry;
using Microsoft.CodeAnalysis.Razor.FoldingRanges;
using Microsoft.CodeAnalysis.Razor.Logging;
using Microsoft.CodeAnalysis.Razor.Protocol.DocumentSymbols;
using Microsoft.CodeAnalysis.Razor.Workspaces;
using Microsoft.CommonLanguageServerProtocol.Framework;
using Microsoft.Extensions.DependencyInjection;
Expand Down Expand Up @@ -190,6 +191,9 @@ static void AddHandlers(IServiceCollection services, LanguageServerFeatureOption
services.AddSingleton<IInlayHintService, InlayHintService>();
services.AddHandlerWithCapabilities<InlayHintEndpoint>();
services.AddHandler<InlayHintResolveEndpoint>();

services.AddHandlerWithCapabilities<DocumentSymbolEndpoint>();
services.AddSingleton<IDocumentSymbolService, DocumentSymbolService>();
}

services.AddHandler<WrapWithTagEndpoint>();
Expand All @@ -203,7 +207,6 @@ static void AddHandlers(IServiceCollection services, LanguageServerFeatureOption
services.AddHandlerWithCapabilities<ValidateBreakpointRangeEndpoint>();
services.AddHandlerWithCapabilities<FindAllReferencesEndpoint>();
services.AddHandlerWithCapabilities<ProjectContextsEndpoint>();
services.AddHandlerWithCapabilities<DocumentSymbolEndpoint>();
services.AddHandlerWithCapabilities<MapCodeEndpoint>();
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// 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.Diagnostics;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.PooledObjects;
using Microsoft.CodeAnalysis.Razor.DocumentMapping;
using Microsoft.VisualStudio.LanguageServer.Protocol;

namespace Microsoft.CodeAnalysis.Razor.Protocol.DocumentSymbols;

internal class DocumentSymbolService(IDocumentMappingService documentMappingService) : IDocumentSymbolService
{
private readonly IDocumentMappingService _documentMappingService = documentMappingService;

public SumType<DocumentSymbol[], SymbolInformation[]>? GetDocumentSymbols(Uri razorDocumentUri, RazorCSharpDocument csharpDocument, SumType<DocumentSymbol[], SymbolInformation[]> csharpSymbols)
{
if (csharpSymbols.TryGetFirst(out var documentSymbols))
{
return RemapDocumentSymbols(csharpDocument, documentSymbols);
}
else if (csharpSymbols.TryGetSecond(out var symbolInformations))
{
using var _ = ListPool<SymbolInformation>.GetPooledObject(out var mappedSymbols);

foreach (var symbolInformation in symbolInformations)
{
if (_documentMappingService.TryMapToHostDocumentRange(csharpDocument, symbolInformation.Location.Range, out var newRange))
{
symbolInformation.Location.Range = newRange;
symbolInformation.Location.Uri = razorDocumentUri;
mappedSymbols.Add(symbolInformation);
}
}

return mappedSymbols.ToArray();
}
else
{
Debug.Fail("Unsupported response type");
throw new InvalidOperationException();
}
}

private DocumentSymbol[]? RemapDocumentSymbols(RazorCSharpDocument csharpDocument, DocumentSymbol[]? documentSymbols)
{
if (documentSymbols is null)
{
return null;
}

using var _ = ListPool<DocumentSymbol>.GetPooledObject(out var mappedSymbols);

foreach (var documentSymbol in documentSymbols)
{
if (TryRemapRanges(csharpDocument, documentSymbol))
{
documentSymbol.Children = RemapDocumentSymbols(csharpDocument, documentSymbol.Children);

mappedSymbols.Add(documentSymbol);
}
else if (documentSymbol.Children is [_, ..] &&
RemapDocumentSymbols(csharpDocument, documentSymbol.Children) is [_, ..] mappedChildren)
{
// This range didn't map, but some/all of its children did, so we promote them to this level so we don't
// lose any information.
mappedSymbols.AddRange(mappedChildren);
}
}

return mappedSymbols.ToArray();

bool TryRemapRanges(RazorCSharpDocument csharpDocument, DocumentSymbol documentSymbol)
{
if (_documentMappingService.TryMapToHostDocumentRange(csharpDocument, documentSymbol.Range, out var newRange) &&
_documentMappingService.TryMapToHostDocumentRange(csharpDocument, documentSymbol.SelectionRange, out var newSelectionRange))
{
documentSymbol.Range = newRange;
documentSymbol.SelectionRange = newSelectionRange;

return true;
}

return false;
}
}
}
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;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.VisualStudio.LanguageServer.Protocol;

namespace Microsoft.CodeAnalysis.Razor.Protocol.DocumentSymbols;

internal interface IDocumentSymbolService
{
SumType<DocumentSymbol[], SymbolInformation[]>? GetDocumentSymbols(Uri razorDocumentUri, RazorCSharpDocument csharpDocument, SumType<DocumentSymbol[], SymbolInformation[]> csharpSymbols);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// 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.VisualStudio.LanguageServer.Protocol;

namespace Microsoft.CodeAnalysis.Razor.Remote;

internal interface IRemoteDocumentSymbolService : IRemoteJsonService
{
ValueTask<SumType<DocumentSymbol[], SymbolInformation[]>?> GetDocumentSymbolsAsync(JsonSerializableRazorPinnedSolutionInfoWrapper solutionInfo, JsonSerializableDocumentId documentId, bool useHierarchicalSymbols, CancellationToken cancellationToken);
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ internal static class RazorServices
[
(typeof(IRemoteSignatureHelpService), null),
(typeof(IRemoteInlayHintService), null),
(typeof(IRemoteDocumentSymbolService), null),
];

private const string ComponentName = "Razor";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// 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 Microsoft.CodeAnalysis.Razor.DocumentMapping;
using Microsoft.CodeAnalysis.Razor.Protocol.DocumentSymbols;

namespace Microsoft.CodeAnalysis.Remote.Razor.DocumentSymbols;

[Export(typeof(IDocumentSymbolService)), Shared]
[method: ImportingConstructor]
internal class OOPDocumentSymbolService(IDocumentMappingService documentMappingService) : DocumentSymbolService(documentMappingService)
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license information.

using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.CodeAnalysis.ExternalAccess.Razor;
using Microsoft.CodeAnalysis.Razor.Protocol.DocumentSymbols;
using Microsoft.CodeAnalysis.Razor.Remote;
using Microsoft.CodeAnalysis.Razor.Workspaces;
using Microsoft.CodeAnalysis.Remote.Razor.ProjectSystem;
using Microsoft.VisualStudio.LanguageServer.Protocol;
using ExternalHandlers = Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost.Handlers;

namespace Microsoft.CodeAnalysis.Remote.Razor;

internal sealed partial class RemoteDocumentSymbolService(in ServiceArgs args) : RazorDocumentServiceBase(in args), IRemoteDocumentSymbolService
{
internal sealed class Factory : FactoryBase<IRemoteDocumentSymbolService>
{
protected override IRemoteDocumentSymbolService CreateService(in ServiceArgs args)
=> new RemoteDocumentSymbolService(in args);
}

private readonly IDocumentSymbolService _documentSymbolService = args.ExportProvider.GetExportedValue<IDocumentSymbolService>();
private readonly IFilePathService _filePathService = args.ExportProvider.GetExportedValue<IFilePathService>();

public ValueTask<SumType<DocumentSymbol[], SymbolInformation[]>?> GetDocumentSymbolsAsync(JsonSerializableRazorPinnedSolutionInfoWrapper solutionInfo, JsonSerializableDocumentId razorDocumentId, bool useHierarchicalSymbols, CancellationToken cancellationToken)
=> RunServiceAsync(
solutionInfo,
razorDocumentId,
context => GetDocumentSymbolsAsync(context, useHierarchicalSymbols, cancellationToken),
cancellationToken);

private async ValueTask<SumType<DocumentSymbol[], SymbolInformation[]>?> GetDocumentSymbolsAsync(RemoteDocumentContext context, bool useHierarchicalSymbols, CancellationToken cancellationToken)
{
var generatedDocument = await context.GetGeneratedDocumentAsync(_filePathService, cancellationToken).ConfigureAwait(false);
var csharpSymbols = await ExternalHandlers.DocumentSymbols.GetDocumentSymbolsAsync(generatedDocument, useHierarchicalSymbols, cancellationToken).ConfigureAwait(false);

Check failure on line 39 in src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/DocumentSymbols/RemoteDocumentSymbolService.cs

View check run for this annotation

Azure Pipelines / razor-tooling-ci (Build macOS release)

src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/DocumentSymbols/RemoteDocumentSymbolService.cs#L39

src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/DocumentSymbols/RemoteDocumentSymbolService.cs(39,35): error CS0234: (NETCORE_ENGINEERING_TELEMETRY=Build) The type or namespace name 'DocumentSymbols' does not exist in the namespace 'Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost.Handlers' (are you missing an assembly reference?)

Check failure on line 39 in src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/DocumentSymbols/RemoteDocumentSymbolService.cs

View check run for this annotation

Azure Pipelines / razor-tooling-ci (Build macOS debug)

src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/DocumentSymbols/RemoteDocumentSymbolService.cs#L39

src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/DocumentSymbols/RemoteDocumentSymbolService.cs(39,35): error CS0234: (NETCORE_ENGINEERING_TELEMETRY=Build) The type or namespace name 'DocumentSymbols' does not exist in the namespace 'Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost.Handlers' (are you missing an assembly reference?)

Check failure on line 39 in src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/DocumentSymbols/RemoteDocumentSymbolService.cs

View check run for this annotation

Azure Pipelines / razor-tooling-ci (Build Linux release)

src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/DocumentSymbols/RemoteDocumentSymbolService.cs#L39

src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/DocumentSymbols/RemoteDocumentSymbolService.cs(39,35): error CS0234: (NETCORE_ENGINEERING_TELEMETRY=Build) The type or namespace name 'DocumentSymbols' does not exist in the namespace 'Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost.Handlers' (are you missing an assembly reference?)

Check failure on line 39 in src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/DocumentSymbols/RemoteDocumentSymbolService.cs

View check run for this annotation

Azure Pipelines / razor-tooling-ci (Build Linux debug)

src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/DocumentSymbols/RemoteDocumentSymbolService.cs#L39

src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/DocumentSymbols/RemoteDocumentSymbolService.cs(39,35): error CS0234: (NETCORE_ENGINEERING_TELEMETRY=Build) The type or namespace name 'DocumentSymbols' does not exist in the namespace 'Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost.Handlers' (are you missing an assembly reference?)

Check failure on line 39 in src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/DocumentSymbols/RemoteDocumentSymbolService.cs

View check run for this annotation

Azure Pipelines / razor-tooling-ci

src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/DocumentSymbols/RemoteDocumentSymbolService.cs#L39

src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/DocumentSymbols/RemoteDocumentSymbolService.cs(39,35): error CS0234: (NETCORE_ENGINEERING_TELEMETRY=Build) The type or namespace name 'DocumentSymbols' does not exist in the namespace 'Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost.Handlers' (are you missing an assembly reference?)

Check failure on line 39 in src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/DocumentSymbols/RemoteDocumentSymbolService.cs

View check run for this annotation

Azure Pipelines / razor-tooling-ci

src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/DocumentSymbols/RemoteDocumentSymbolService.cs#L39

src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/DocumentSymbols/RemoteDocumentSymbolService.cs(39,35): error CS0234: (NETCORE_ENGINEERING_TELEMETRY=Build) The type or namespace name 'DocumentSymbols' does not exist in the namespace 'Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost.Handlers' (are you missing an assembly reference?)

var codeDocument = await context.GetCodeDocumentAsync(cancellationToken).ConfigureAwait(false);
var csharpDocument = codeDocument.GetCSharpDocument();

// This is, to say the least, not ideal. In future we're going to normalize on to Roslyn LSP types, and this can go.
var options = new JsonSerializerOptions();
foreach (var converter in RazorServiceDescriptorsWrapper.GetLspConverters())
{
options.Converters.Add(converter);
}

var vsCSharpSymbols = JsonSerializer.Deserialize<SumType<DocumentSymbol[], SymbolInformation[]>?>(JsonSerializer.SerializeToDocument(csharpSymbols), options);
if (vsCSharpSymbols is not { } convertedSymbols)
{
return null;
}

return _documentSymbolService.GetDocumentSymbols(context.Uri, csharpDocument, convertedSymbols);
}
}
Loading
Loading