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

Expose document symbols to Razor cohosting #74730

Merged
merged 4 commits into from
Aug 15, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler
/// </summary>
[ExportCSharpVisualBasicStatelessLspService(typeof(DocumentSymbolsHandler)), Shared]
[Method(Methods.TextDocumentDocumentSymbolName)]
internal sealed class DocumentSymbolsHandler : ILspServiceDocumentRequestHandler<RoslynDocumentSymbolParams, object[]>
internal sealed class DocumentSymbolsHandler : ILspServiceDocumentRequestHandler<RoslynDocumentSymbolParams, SumType<DocumentSymbol[], SymbolInformation[]>>
{
public bool MutatesSolutionState => false;
public bool RequiresLSPSolution => true;
Expand All @@ -38,53 +38,58 @@ public DocumentSymbolsHandler()

public TextDocumentIdentifier GetTextDocumentIdentifier(RoslynDocumentSymbolParams request) => request.TextDocument;

public async Task<object[]> HandleRequestAsync(RoslynDocumentSymbolParams request, RequestContext context, CancellationToken cancellationToken)
public Task<SumType<DocumentSymbol[], SymbolInformation[]>> HandleRequestAsync(RoslynDocumentSymbolParams request, RequestContext context, CancellationToken cancellationToken)
{
var document = context.GetRequiredDocument();
var clientCapabilities = context.GetRequiredClientCapabilities();
var useHierarchicalSymbols = clientCapabilities.TextDocument?.DocumentSymbol?.HierarchicalDocumentSymbolSupport == true || request.UseHierarchicalSymbols;
Copy link
Contributor

Choose a reason for hiding this comment

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

It seems weird that even if the client doesn't support HierarchicalSymbols we'd still use it if the request said to?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The Document Outline window in Roslyn calls the document symbols LSP endpoint, and wants hierarchical results all the time, but the VS LSP client doesn't support them. In the LSP spec the non-hierarchical result is deprecated and I should probably log an issue on the platform to move off it, and then a lot of this nonsense can go away.

Copy link
Member

Choose a reason for hiding this comment

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

this is for document outline. base vs client doesn't have hierarchical support IIRC, but we need it for our custom doc outline support.

var service = document.Project.Solution.Services.GetRequiredService<ILspSymbolInformationCreationService>();

return GetDocumentSymbolsAsync(document, useHierarchicalSymbols, service, cancellationToken);
}

internal static async Task<SumType<DocumentSymbol[], SymbolInformation[]>> GetDocumentSymbolsAsync(Document document, bool useHierarchicalSymbols, ILspSymbolInformationCreationService symbolInformationCreationService, CancellationToken cancellationToken)
{
var navBarService = document.Project.Services.GetRequiredService<INavigationBarItemService>();
var navBarItems = await navBarService.GetItemsAsync(document, supportsCodeGeneration: false, frozenPartialSemantics: false, cancellationToken).ConfigureAwait(false);
if (navBarItems.IsEmpty)
return [];
return Array.Empty<DocumentSymbol>();

var text = await document.GetValueTextAsync(cancellationToken).ConfigureAwait(false);

// TODO - Return more than 2 levels of symbols.
// https://github.com/dotnet/roslyn/projects/45#card-20033869
using var _ = ArrayBuilder<object>.GetInstance(out var symbols);
if (clientCapabilities.TextDocument?.DocumentSymbol?.HierarchicalDocumentSymbolSupport == true || request.UseHierarchicalSymbols)
if (useHierarchicalSymbols)
{
using var _ = ArrayBuilder<DocumentSymbol>.GetInstance(out var symbols);
// only top level ones
foreach (var item in navBarItems)
symbols.AddIfNotNull(GetDocumentSymbol(item, text, cancellationToken));

return symbols.ToArray();
}
else
{
using var _ = ArrayBuilder<SymbolInformation>.GetInstance(out var symbols);
foreach (var item in navBarItems)
{
symbols.AddIfNotNull(GetSymbolInformation(item, document, text, containerName: null));
symbols.AddIfNotNull(GetSymbolInformation(item, document, text, containerName: null, symbolInformationCreationService));

foreach (var childItem in item.ChildItems)
symbols.AddIfNotNull(GetSymbolInformation(childItem, document, text, item.Text));
symbols.AddIfNotNull(GetSymbolInformation(childItem, document, text, item.Text, symbolInformationCreationService));
}
}

var result = symbols.ToArray();
return result;
return symbols.ToArray();
}
}

/// <summary>
/// Get a symbol information from a specified nav bar item.
/// </summary>
private static SymbolInformation? GetSymbolInformation(
RoslynNavigationBarItem item, Document document, SourceText text, string? containerName = null)
RoslynNavigationBarItem item, Document document, SourceText text, string? containerName, ILspSymbolInformationCreationService symbolInformationCreationService)
{
if (item is not RoslynNavigationBarItem.SymbolItem symbolItem || symbolItem.Location.InDocumentInfo == null)
return null;

var service = document.Project.Solution.Services.GetRequiredService<ILspSymbolInformationCreationService>();
return service.Create(
return symbolInformationCreationService.Create(
GetDocumentSymbolName(item.Text),
containerName,
ProtocolConversions.GlyphToSymbolKind(item.Glyph),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.CodeAnalysis.LanguageServer.Handler;

namespace Roslyn.LanguageServer.Protocol;

Expand Down Expand Up @@ -47,6 +48,7 @@ private static void AddConverters(IList<JsonConverter> converters)
AddOrReplaceConverter<TextDocumentClientCapabilities, VSInternalTextDocumentClientCapabilities>();
AddOrReplaceConverter<RenameRange, VSInternalRenameRange>();
AddOrReplaceConverter<RenameParams, VSInternalRenameParams>();
AddOrReplaceConverter<DocumentSymbol, RoslynDocumentSymbol>();

void AddOrReplaceConverter<TBase, TExtension>()
where TExtension : TBase
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.LanguageServer.Handler;
using Roslyn.LanguageServer.Protocol;
using Roslyn.Utilities;
using LSP = Roslyn.LanguageServer.Protocol;

namespace Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost.Handlers;

internal static class DocumentSymbols
{
public static Task<SumType<DocumentSymbol[], SymbolInformation[]>> GetDocumentSymbolsAsync(Document document, bool useHierarchicalSymbols, CancellationToken cancellationToken)
{
// The symbol information service in Roslyn lives in EditorFeatures and has VS dependencies. for glyph images,
// so isn't available in OOP. The default implementation is available in OOP, but not in the Roslyn MEF composition,
Copy link
Member

Choose a reason for hiding this comment

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

but not in the Roslyn MEF composition

Hmm - not sure why this isn't the case. I think its regular workspace service. Not 100% sure but I would have thought it should be available.

Copy link
Member

Choose a reason for hiding this comment

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

oh is the protocol project not in the OOP mef composition?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, precisely. That might come up again in other endpoints so could be something we need to talk about in future, but document symbol didn't seem important enough to block on it

// so we have to provide our own.
return DocumentSymbolsHandler.GetDocumentSymbolsAsync(document, useHierarchicalSymbols, RazorLspSymbolInformationCreationService.Instance, cancellationToken);
}

private sealed class RazorLspSymbolInformationCreationService : ILspSymbolInformationCreationService
{
public static readonly RazorLspSymbolInformationCreationService Instance = new();

public SymbolInformation Create(string name, string? containerName, LSP.SymbolKind kind, LSP.Location location, Glyph glyph)
=> new()
{
Name = name,
ContainerName = containerName,
Kind = kind,
Location = location,
};
}
}
Loading