-
Notifications
You must be signed in to change notification settings - Fork 4.1k
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
Extract a DocumentSymbolsService from the C# navbar provider #48912
Conversation
This extracts a new IDocumentSymbolsService from the C# navbar item service, for use in both the navbar service and in LSP (breaking the DocumentSymbolHandler's dependency on EditorFeatures). As part of this, I attempted to resolve the issue around many-level hierarchy support, and introduced a new DocumentSymbolInfo class to allow recursively-defined symbols, including locals and local functions. The new service has 2 operation modes: the old navbar service mode, which returns types on the top level, and their top-level members as nested children, and a new hierarchy mode which supports full symbol nesting (including representing nested types as truly nested in their containing types). I similarly implemented an IDocumentSymbolsService for VB, but I was not able to extract the logic out of the VB navbar service to use the new service. The VB navbar is too tightly-coupled to the code generation capabilities it needs to represent many parts of the existing navbars (such as event or constructor generation). As such, while I've tested the VB provider, I don't believe I've exactly replicated the navbar behavior in the legacy mode for the service. I think I've come close, but handling around events and methods that have WithEvents is different.
@dibarbet there are still some open questions I think:
|
src/EditorFeatures/CSharp/NavigationBar/CSharpNavigationBarItemService.cs
Outdated
Show resolved
Hide resolved
{ | ||
return SpecializedCollections.EmptyList<NavigationBarItem>(); | ||
} | ||
|
||
var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); | |
var tree = await document.GetRequiredSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); |
// If we got members from the symbol service, there had to have been a tree | ||
Debug.Assert(tree is not null); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// If we got members from the symbol service, there had to have been a tree | |
Debug.Assert(tree is not null); |
This is exported for C#. it's an invariant that C#/Vb always have syntax trees for all docs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
these comments both still apply.
var compilation = document.Project.GetCompilationAsync(cancellationToken).WaitAndGetResult(cancellationToken); | ||
// If we're being called back, there must have been a compilation to resolve the item in the first place | ||
Debug.Assert(compilation is not null); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
var compilation = document.Project.GetCompilationAsync(cancellationToken).WaitAndGetResult(cancellationToken); | |
// If we're being called back, there must have been a compilation to resolve the item in the first place | |
Debug.Assert(compilation is not null); | |
var compilation = document.Project.GetRequiredCompilationAsync(cancellationToken).WaitAndGetResult(cancellationToken); |
Same thing as above. all C#/VB documents have compilations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this comment still applies.
src/Features/CSharp/Portable/DocumentSymbols/CSharpDocumentSymbolInfo.cs
Outdated
Show resolved
Hide resolved
src/Features/CSharp/Portable/DocumentSymbols/CSharpDocumentSymbolsService.cs
Outdated
Show resolved
Hide resolved
src/Features/Core/Portable/DocumentSymbols/AbstractDocumentSymbolsService.cs
Outdated
Show resolved
Hide resolved
src/Features/Core/Portable/DocumentSymbols/AbstractDocumentSymbolsService.cs
Outdated
Show resolved
Hide resolved
src/Features/Core/Portable/DocumentSymbols/AbstractDocumentSymbolsService.cs
Outdated
Show resolved
Hide resolved
src/Features/Core/Portable/DocumentSymbols/AbstractDocumentSymbolsService.cs
Outdated
Show resolved
Hide resolved
src/Features/Core/Portable/DocumentSymbols/AbstractDocumentSymbolsService.cs
Outdated
Show resolved
Hide resolved
src/Features/Core/Portable/DocumentSymbols/AbstractDocumentSymbolsService.cs
Outdated
Show resolved
Hide resolved
src/Features/Core/Portable/DocumentSymbols/AbstractDocumentSymbolsService.cs
Outdated
Show resolved
Hide resolved
src/Features/Core/Portable/DocumentSymbols/AbstractDocumentSymbolsService.cs
Outdated
Show resolved
Hide resolved
src/Features/Core/Portable/DocumentSymbols/AbstractDocumentSymbolsService.cs
Outdated
Show resolved
Hide resolved
if (options == DocumentSymbolsOptions.TypesAndMethodsOnly) | ||
{ | ||
return node is BaseMethodDeclarationSyntax or BasePropertyDeclarationSyntax | ||
or BaseFieldDeclarationSyntax or StatementSyntax or ExpressionSyntax; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it's a bit odd that it says 'TypesAndMethods', but talks about statements/expressions. how do those show as hte parent if we're only considering types/methods?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we're only talking about types and methods, then there's no need to delve inside members. You can run into statements, such as at the top level, but if we see one we definitely know that there's nothing of interest inside it. I should also rename that enum to TypesAndMembers
, it's not method specific.
src/Features/Core/Portable/DocumentSymbols/IDocumentSymbolsService.cs
Outdated
Show resolved
Hide resolved
src/Features/Core/Portable/DocumentSymbols/IDocumentSymbolsService.cs
Outdated
Show resolved
Hide resolved
} | ||
} | ||
|
||
var location = symbol.Locations.FirstOrDefault(l => l.SourceTree.Equals(tree)); | ||
return location ?? symbol.Locations.FirstOrDefault(); | ||
return (range, selection); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
todo: review this.
src/Features/VisualBasic/Portable/DocumentSymbols/VisualBasicDocumentSymbolInfo.vb
Outdated
Show resolved
Hide resolved
src/Features/VisualBasic/Portable/DocumentSymbols/VisualBasicDocumentSymbolInfo.vb
Outdated
Show resolved
Hide resolved
src/Features/VisualBasic/Portable/DocumentSymbols/VisualBasicDocumentSymbolsService.vb
Show resolved
Hide resolved
src/Features/Core/Portable/DocumentSymbols/IDocumentSymbolsService.cs
Outdated
Show resolved
Hide resolved
else | ||
{ | ||
AddFieldOrLocalSpan(symbol, tree, spans); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
all these different cases are hard to read. ie. if it's a field or a local, except in teh case where it's an enum, otherwise do this.
Can you just break out hte separate cases and deal with each one individually?
return; | ||
} | ||
|
||
var declaringNode = reference.GetSyntax(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shouldn't this take a cancellation token?
typesBuilder.Sort((d1, d2) => d1.Text.CompareTo(d2.Text)); | ||
return typesBuilder.ToImmutable(); | ||
|
||
void GetTypesInFile(SemanticModel semanticModel, HashSet<INamedTypeSymbol> types, CancellationToken cancellationToken) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you make this static, or extract to method? i can't really tell what is captured here and how this affects outer state.
} | ||
} | ||
|
||
protected static ImmutableArray<TextSpan> GetDeclaringSpans(ISymbol symbol, SyntaxTree tree) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
protected static ImmutableArray<TextSpan> GetDeclaringSpans(ISymbol symbol, SyntaxTree tree) | |
protected static ImmutableArray<TextSpan> GetDeclaringSpansInTree(ISymbol symbol, SyntaxTree tree) |
?
{ | ||
if (tree.Equals(location.SourceTree)) | ||
{ | ||
return ImmutableArray.Create(location.SourceSpan); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what if teh symbol has multiple locaitons in the same tree? this will just return the first in Locations. Is that desirable/intentional? Regardless of answer, please doc.
/// <see cref="IDocumentSymbolsService.GetSymbolsInDocumentAsync(Document, DocumentSymbolsOptions, CancellationToken)"/>, | ||
/// with their top-level members returned as the <see cref="DocumentSymbolInfo.ChildrenSymbols"/> of those types. | ||
/// No info on locals or local functions is returned. Members not in the given document can be returned. This format | ||
/// matches the behavior of the Visual Studio toolbars. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
intead of matching the nav bars, can we just return the full hierarchy, and let the navbar code iself collapse things based on the rich hierarchy data you're returning?
otherwise, i don't see much point here in thse abstractions if we effectively have a bool that says "i'm from navbar (or not)", and then code on the other side that says "oh, for navbar, we get a differnet set of results".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The DocumentSymbolHandler uses this mode if hierarchical support is disabled.
/// The range enclosing this symbol not including leading/trailing whitespace | ||
/// but everything else like comments. | ||
/// </summary> | ||
public ImmutableArray<TextSpan> EnclosingSpans { get; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why is it 'spans', instead of just one span?
/// <summary> | ||
/// The range that should be selected and revealed when this symbol is being picked. | ||
/// </summary> | ||
public ImmutableArray<TextSpan> DeclaringSpans { get; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why is this an array, instead of one thing?
/// <summary> | ||
/// Any nested items from the document. | ||
/// </summary> | ||
public ImmutableArray<DocumentSymbolInfo> ChildrenSymbols { get; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in general we either use Children
or ChildXXX
.
/// <summary> | ||
/// Information about the symbols in this document | ||
/// </summary> | ||
internal sealed class DocumentSymbolInfo |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that this is not a record makes me sad.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I might be able to make this a record. I had originally intended to make this a struct, but when I did I got very weird test failures that DocumentSymbolInfo
wasn't able to be loaded by the test assembly.
var navBarItems = await navBarService.GetItemsAsync(document, cancellationToken).ConfigureAwait(false); | ||
if (navBarItems.Count == 0) | ||
var hierarchicalDocumentSymbolSupport = context.ClientCapabilities?.TextDocument?.DocumentSymbol?.HierarchicalDocumentSymbolSupport == true; | ||
var documentSymbolsService = document.Project.LanguageServices.GetRequiredService<IDocumentSymbolsService>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
var documentSymbolsService = document.Project.LanguageServices.GetRequiredService<IDocumentSymbolsService>(); | |
var documentSymbolsService = document.GetRequiredLanguageService<IDocumentSymbolsService>(); |
{ | ||
return symbols.ToArrayAndFree(); | ||
return symbols.ToArrayAndFree()!; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
somethind i very odd with these suppressions
{ | ||
return symbols.ToArrayAndFree(); | ||
return symbols.ToArrayAndFree()!; | ||
} | ||
|
||
var compilation = await document.Project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unused?
{ | ||
if (item.Spans.Count == 0) | ||
if (item.DeclaringSpans.IsEmpty) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
when can this happen?
Function(s) s.GetSyntax().FullSpan) | ||
End Function | ||
End Class | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Private Shared Function GetEnclosingSpansForSymbol( | ||
symbol As ISymbol, | ||
tree As SyntaxTree, | ||
declarationService As ISymbolDeclarationService) As ImmutableArray(Of TextSpan) | ||
Return declarationService.GetDeclarations(symbol).SelectAsArray(Function(s) s.SyntaxTree.Equals(tree), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Private Shared Function GetEnclosingSpansForSymbol( | |
symbol As ISymbol, | |
tree As SyntaxTree, | |
declarationService As ISymbolDeclarationService) As ImmutableArray(Of TextSpan) | |
Return declarationService.GetDeclarations(symbol).SelectAsArray(Function(s) s.SyntaxTree.Equals(tree), | |
Private Shared Function GetEnclosingSpansForSymbol( | |
symbol As ISymbol, | |
tree As SyntaxTree, | |
declarationService As ISymbolDeclarationService) As ImmutableArray(Of TextSpan) | |
Return declarationService.GetDeclarations(symbol).SelectAsArray(Function(s) s.SyntaxTree.Equals(tree), |
End Function | ||
End Class | ||
|
||
End Namespace |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
todo: review his file.
@@ -28,23 +26,6 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.NavigationBar | |||
[ExportLanguageService(typeof(INavigationBarItemService), LanguageNames.CSharp), Shared] | |||
internal class CSharpNavigationBarItemService : AbstractNavigationBarItemService |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i don't see the VisualBasicNavigationBarItemService change in this PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct, there is not.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done with pass. would much prefer an approach where there's a single hierarchical provider. and both LSP and NavBars both consume that. Clients can collape the hierarchy to a flatter list if that's what they want, and can filter down to the types of items they want as well based on their own policies.
this would take policy judgements out of the service, and into the feature, and would make the service just responsible for hierarchical symbolic data.
also need to see how this impacts the VB side of things.
I do not see a way in which the VB NavBar service could consume this. It's far too coupled to how events work in VB. I could certainly make the proposed structure work with the C# NavBar, but I don't think it's possible to preserve existing behavior in VB. |
@333fred: Last time I looked at this, the navigation bar item service is functioning in dual roles:
The former doesn't really have any logical dependency to the editor code; the later does due to some tracking spans that it uses. Splitting it into two services where the former lives in Features and the latter lives in EditorFeatures I think should be doable. |
Can you clarify this? Can the new service return a different type of info that the feature layer can decide what to do with? the LSP versoin can dump them, the VS version can use them.
Then i don't see a purpose here for this (esp. as we need options to basically say "i want LSP behavior" vs "I want VS behavior"). If we're just going to have a special option for LSP, and we're not going to share this code with C#/VB for VS/LSP, then why not just have a custom LSP service only for that scenario. In other words, i don't get what this system is buying us given htat we still hvae LSP vs VS, and we still have C# vs VB. |
My concern is all the event-based logic here: http://sourceroslyn.io/#Microsoft.CodeAnalysis.VisualBasic.EditorFeatures/NavigationBar/VisualBasicNavigationBarItemService.vb,113. The logic around
Well, this does allow us to collapse LSP vs VS, for C#. I don't know if LSP is ever going to be able to replicate the VB behavior: the documentSymbols protocol is fundamentally designed around navigation, not around generation (which the VB NavBar service is designed around). I could certainly back off my attempt to make the C# navbar service consume this, though IMO this new service should still live in the Features layer (just feels like the proper layering, as LSP should be about providing an interface to IDE features, not implementing them itself).
In VB, these 2 steps are fundamentally conflated, because it needs to determine a bunch of information about generating items when fetching them. |
I'm not asking for LSP to replicate it though. I'm asking for the core service to be a superset of functionality of LSP/VS/C#/VB. Then the particular consumers can decide how to expose that functionality to their platform. LSP, for example, would not expose the event-generation aspect. It would alos expose the full tree. VS would expose the event-generation, but would also collapse the tree to the flat form that it wants to show. |
I'm straight-up not certain how to do this though 🙂. If you have suggestions on an API shape that would be able to represent the logic that goes on inside the VB handlers without becoming very VB-specific, I'm all ears. |
So it looks like the enum items are pretty vanilla: https://github.com/dotnet/roslyn/blob/master/src/EditorFeatures/VisualBasic/NavigationBar/GenerateEventHandlerItem.vb WHy not just return these as items that just have a |
Superseded by #52481. |
This extracts a new IDocumentSymbolsService from the C# navbar item service, for use in both the navbar service and in LSP (breaking the DocumentSymbolHandler's dependency on EditorFeatures). As part of this, I attempted to resolve the issue around many-level hierarchy support, and introduced a new DocumentSymbolInfo class to allow recursively-defined symbols, including locals and local functions. The new service has 2 operation modes: the old navbar service mode, which returns types on the top level, and their top-level members as nested children, and a new hierarchy mode which supports full symbol nesting (including representing nested types as truly nested in their containing types).
I similarly implemented an IDocumentSymbolsService for VB, but I was not able to extract the logic out of the VB navbar service to use the new service. The VB navbar is too tightly coupled to the code generation capabilities it needs to represent many parts of the existing navbars (such as event or constructor generation). As such, while I've tested the VB provider, I don't believe I've exactly replicated the navbar behavior in the legacy mode for the service. I think I've come close, but handling around events and methods that have WithEvents is different.