diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs index 0cdad427b8d35..f9733ec8a0889 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs @@ -8,8 +8,11 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.PatternMatching; +using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Remote; +using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.Utilities; using Roslyn.Utilities; @@ -35,16 +38,16 @@ public async Task SearchDocumentAsync( await client.TryInvokeAsync( document.Project, (service, solutionInfo, callbackId, cancellationToken) => - service.SearchDocumentAsync(solutionInfo, document.Id, searchPattern, [.. kinds], callbackId, cancellationToken), + service.SearchDocumentAndRelatedDocumentsAsync(solutionInfo, document.Id, searchPattern, [.. kinds], callbackId, cancellationToken), callback, cancellationToken).ConfigureAwait(false); return; } - await SearchDocumentInCurrentProcessAsync(document, searchPattern, kinds, onItemsFound, cancellationToken).ConfigureAwait(false); + await SearchDocumentAndRelatedDocumentsInCurrentProcessAsync(document, searchPattern, kinds, onItemsFound, cancellationToken).ConfigureAwait(false); } - public static async Task SearchDocumentInCurrentProcessAsync( + public static Task SearchDocumentAndRelatedDocumentsInCurrentProcessAsync( Document document, string searchPattern, IImmutableSet kinds, @@ -54,12 +57,60 @@ public static async Task SearchDocumentInCurrentProcessAsync( var (patternName, patternContainerOpt) = PatternMatcher.GetNameAndContainer(searchPattern); var declaredSymbolInfoKindsSet = new DeclaredSymbolInfoKindSet(kinds); - var results = new ConcurrentSet(); - await SearchSingleDocumentAsync( - document, patternName, patternContainerOpt, declaredSymbolInfoKindsSet, t => results.Add(t), cancellationToken).ConfigureAwait(false); + // In parallel, search both the document requested, and any relevant 'related documents' we find for it. - if (results.Count > 0) - await onItemsFound([.. results], default, cancellationToken).ConfigureAwait(false); + return Task.WhenAll( + SearchDocumentsInCurrentProcessAsync([document]), + SearchRelatedDocumentsInCurrentProcessAsync()); + + Task SearchDocumentsInCurrentProcessAsync(ImmutableArray documents) + => ProducerConsumer.RunParallelAsync( + documents, + async (document, onItemFound, args, cancellationToken) => await SearchSingleDocumentAsync( + document, patternName, patternContainerOpt, declaredSymbolInfoKindsSet, onItemFound, cancellationToken).ConfigureAwait(false), + onItemsFound, + args: default, + cancellationToken); + + async Task SearchRelatedDocumentsInCurrentProcessAsync() + { + var relatedDocuments = await GetRelatedDocumentsAsync().ConfigureAwait(false); + await SearchDocumentsInCurrentProcessAsync(relatedDocuments).ConfigureAwait(false); + } + + async Task> GetRelatedDocumentsAsync() + { + // For C#/VB we define 'related documents' as those containing types in the inheritance chain of types in + // the originating file (as well as all partial parts of the original and inheritance types). This way a + // user can search for symbols scoped to the 'current document' and still get results for the members found + // in partial parts + + var solution = document.Project.Solution; + var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var semanticModel = await document.GetRequiredNullableDisabledSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var syntaxFacts = document.GetRequiredLanguageService(); + + using var _1 = ArrayBuilder.GetInstance(out var topLevelNodes); + using var _2 = PooledHashSet.GetInstance(out var relatedDocuments); + + syntaxFacts.AddTopLevelMembers(root, topLevelNodes); + + foreach (var topLevelMember in topLevelNodes) + { + if (semanticModel.GetDeclaredSymbol(topLevelMember, cancellationToken) is not INamedTypeSymbol namedTypeSymbol) + continue; + + foreach (var type in namedTypeSymbol.GetBaseTypesAndThis()) + { + foreach (var reference in type.DeclaringSyntaxReferences) + relatedDocuments.AddIfNotNull(solution.GetDocument(reference.SyntaxTree)); + } + } + + // Ensure we don't search the original document we were already searching. + relatedDocuments.Remove(document); + return [.. relatedDocuments]; + } } public async Task SearchProjectsAsync( diff --git a/src/Features/Core/Portable/NavigateTo/IRemoteNavigateToSearchService.cs b/src/Features/Core/Portable/NavigateTo/IRemoteNavigateToSearchService.cs index e50aa7fad8794..ef31f92f7c540 100644 --- a/src/Features/Core/Portable/NavigateTo/IRemoteNavigateToSearchService.cs +++ b/src/Features/Core/Portable/NavigateTo/IRemoteNavigateToSearchService.cs @@ -17,7 +17,7 @@ namespace Microsoft.CodeAnalysis.NavigateTo; internal interface IRemoteNavigateToSearchService { - ValueTask SearchDocumentAsync(Checksum solutionChecksum, DocumentId documentId, string searchPattern, ImmutableArray kinds, RemoteServiceCallbackId callbackId, CancellationToken cancellationToken); + ValueTask SearchDocumentAndRelatedDocumentsAsync(Checksum solutionChecksum, DocumentId documentId, string searchPattern, ImmutableArray kinds, RemoteServiceCallbackId callbackId, CancellationToken cancellationToken); ValueTask SearchProjectsAsync(Checksum solutionChecksum, ImmutableArray projectIds, ImmutableArray priorityDocumentIds, string searchPattern, ImmutableArray kinds, RemoteServiceCallbackId callbackId, CancellationToken cancellationToken); ValueTask SearchGeneratedDocumentsAsync(Checksum solutionChecksum, ImmutableArray projectIds, string searchPattern, ImmutableArray kinds, RemoteServiceCallbackId callbackId, CancellationToken cancellationToken); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs index 360493f97d2ec..9cbc626969729 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs @@ -23,13 +23,10 @@ namespace Microsoft.CodeAnalysis.CSharp.LanguageService; -internal class CSharpSyntaxFacts : ISyntaxFacts +internal class CSharpSyntaxFacts : AbstractSyntaxFacts, ISyntaxFacts { internal static readonly CSharpSyntaxFacts Instance = new(); - // Specifies false for trimOnFree as these objects commonly exceed the default ObjectPool threshold - private static readonly ObjectPool> s_syntaxNodeListPool = new ObjectPool>(() => [], trimOnFree: false); - protected CSharpSyntaxFacts() { } @@ -732,11 +729,7 @@ EnumMemberDeclarationSyntax or } public bool IsTopLevelNodeWithMembers([NotNullWhen(true)] SyntaxNode? node) - { - return node is BaseNamespaceDeclarationSyntax or - TypeDeclarationSyntax or - EnumDeclarationSyntax; - } + => node is BaseNamespaceDeclarationSyntax or BaseTypeDeclarationSyntax; private const string dotToken = "."; @@ -889,30 +882,10 @@ private static void AppendTypeParameterList(StringBuilder builder, TypeParameter } } - public PooledObject> GetTopLevelAndMethodLevelMembers(SyntaxNode? root) - { - var pooledObject = s_syntaxNodeListPool.GetPooledObject(); - var list = pooledObject.Object; - - AppendMembers(root, list, topLevel: true, methodLevel: true); - - return pooledObject; - } - - public PooledObject> GetMethodLevelMembers(SyntaxNode? root) - { - var pooledObject = s_syntaxNodeListPool.GetPooledObject(); - var list = pooledObject.Object; - - AppendMembers(root, list, topLevel: false, methodLevel: true); - - return pooledObject; - } - public SyntaxList GetMembersOfTypeDeclaration(SyntaxNode typeDeclaration) => ((TypeDeclarationSyntax)typeDeclaration).Members; - private void AppendMembers(SyntaxNode? node, List list, bool topLevel, bool methodLevel) + protected override void AppendMembers(SyntaxNode? node, ArrayBuilder list, bool topLevel, bool methodLevel) { Debug.Assert(topLevel || methodLevel); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems index 56d65226cc0ea..f06b5b75653bd 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems @@ -436,6 +436,7 @@ + diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/AbstractSyntaxFacts.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/AbstractSyntaxFacts.cs new file mode 100644 index 0000000000000..4b5bbc83d00ee --- /dev/null +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/AbstractSyntaxFacts.cs @@ -0,0 +1,21 @@ +// 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 Microsoft.CodeAnalysis.PooledObjects; + +namespace Microsoft.CodeAnalysis.LanguageService; + +internal abstract class AbstractSyntaxFacts +{ + public void AddTopLevelAndMethodLevelMembers(SyntaxNode? root, ArrayBuilder result) + => AppendMembers(root, result, topLevel: true, methodLevel: true); + + public void AddTopLevelMembers(SyntaxNode? root, ArrayBuilder result) + => AppendMembers(root, result, topLevel: true, methodLevel: false); + + public void AddMethodLevelMembers(SyntaxNode? root, ArrayBuilder result) + => AppendMembers(root, result, topLevel: false, methodLevel: true); + + protected abstract void AppendMembers(SyntaxNode? node, ArrayBuilder list, bool topLevel, bool methodLevel); +} diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFacts.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFacts.cs index e0abf008de631..7a2544c4bf028 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFacts.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFacts.cs @@ -7,6 +7,7 @@ using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using System.Threading; +using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.LanguageService; @@ -410,9 +411,12 @@ void GetPartsOfTupleExpression(SyntaxNode node, SyntaxNode? ConvertToSingleLine(SyntaxNode? node, bool useElasticTrivia = false); // Violation. This is a feature level API. - PooledObject> GetTopLevelAndMethodLevelMembers(SyntaxNode? root); + void AddTopLevelAndMethodLevelMembers(SyntaxNode? root, ArrayBuilder result); // Violation. This is a feature level API. - PooledObject> GetMethodLevelMembers(SyntaxNode? root); + void AddTopLevelMembers(SyntaxNode? root, ArrayBuilder result); + // Violation. This is a feature level API. + void AddMethodLevelMembers(SyntaxNode? root, ArrayBuilder result); + SyntaxList GetMembersOfTypeDeclaration(SyntaxNode typeDeclaration); // Violation. This is a feature level API. diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxFacts.vb b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxFacts.vb index 0c464486a45f4..c414cd2de2103 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxFacts.vb +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxFacts.vb @@ -21,6 +21,7 @@ Imports Microsoft.CodeAnalysis.Editing Namespace Microsoft.CodeAnalysis.VisualBasic.LanguageService Friend Class VisualBasicSyntaxFacts + Inherits AbstractSyntaxFacts Implements ISyntaxFacts Private Const DoesNotExistInVBErrorMessage = "This feature does not exist in VB" @@ -888,24 +889,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.LanguageService Return TextSpan.FromBounds(list.First.SpanStart, list.Last.Span.End) End Function - Public Function GetTopLevelAndMethodLevelMembers(root As SyntaxNode) As PooledObject(Of List(Of SyntaxNode)) Implements ISyntaxFacts.GetTopLevelAndMethodLevelMembers - Dim pooledList = PooledObject(Of List(Of SyntaxNode)).Create(s_syntaxNodeListPool) - Dim list = pooledList.Object - - AppendMembers(root, list, topLevel:=True, methodLevel:=True) - - Return pooledList - End Function - - Public Function GetMethodLevelMembers(root As SyntaxNode) As PooledObject(Of List(Of SyntaxNode)) Implements ISyntaxFacts.GetMethodLevelMembers - Dim pooledList = PooledObject(Of List(Of SyntaxNode)).Create(s_syntaxNodeListPool) - Dim list = pooledList.Object - - AppendMembers(root, list, topLevel:=False, methodLevel:=True) - - Return pooledList - End Function - Public Function GetMembersOfTypeDeclaration(typeDeclaration As SyntaxNode) As SyntaxList(Of SyntaxNode) Implements ISyntaxFacts.GetMembersOfTypeDeclaration Return DirectCast(typeDeclaration, TypeBlockSyntax).Members End Function @@ -1050,7 +1033,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.LanguageService End If End Sub - Private Sub AppendMembers(node As SyntaxNode, list As List(Of SyntaxNode), topLevel As Boolean, methodLevel As Boolean) + Protected Overrides Sub AppendMembers(node As SyntaxNode, list As ArrayBuilder(Of SyntaxNode), topLevel As Boolean, methodLevel As Boolean) Debug.Assert(topLevel OrElse methodLevel) For Each member In node.GetMembers()