Skip to content

Commit

Permalink
Allow more results than just the original file in nav to searches
Browse files Browse the repository at this point in the history
  • Loading branch information
CyrusNajmabadi committed Feb 5, 2025
1 parent 0ba72bd commit 7ca7de3
Show file tree
Hide file tree
Showing 7 changed files with 93 additions and 60 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -35,16 +38,16 @@ public async Task SearchDocumentAsync(
await client.TryInvokeAsync<IRemoteNavigateToSearchService>(
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<string> kinds,
Expand All @@ -54,12 +57,60 @@ public static async Task SearchDocumentInCurrentProcessAsync(
var (patternName, patternContainerOpt) = PatternMatcher.GetNameAndContainer(searchPattern);
var declaredSymbolInfoKindsSet = new DeclaredSymbolInfoKindSet(kinds);

var results = new ConcurrentSet<RoslynNavigateToItem>();
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<Document> documents)
=> ProducerConsumer<RoslynNavigateToItem>.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<ImmutableArray<Document>> 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<ISyntaxFactsService>();

using var _1 = ArrayBuilder<SyntaxNode>.GetInstance(out var topLevelNodes);
using var _2 = PooledHashSet<Document>.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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ namespace Microsoft.CodeAnalysis.NavigateTo;

internal interface IRemoteNavigateToSearchService
{
ValueTask SearchDocumentAsync(Checksum solutionChecksum, DocumentId documentId, string searchPattern, ImmutableArray<string> kinds, RemoteServiceCallbackId callbackId, CancellationToken cancellationToken);
ValueTask SearchDocumentAndRelatedDocumentsAsync(Checksum solutionChecksum, DocumentId documentId, string searchPattern, ImmutableArray<string> kinds, RemoteServiceCallbackId callbackId, CancellationToken cancellationToken);
ValueTask SearchProjectsAsync(Checksum solutionChecksum, ImmutableArray<ProjectId> projectIds, ImmutableArray<DocumentId> priorityDocumentIds, string searchPattern, ImmutableArray<string> kinds, RemoteServiceCallbackId callbackId, CancellationToken cancellationToken);

ValueTask SearchGeneratedDocumentsAsync(Checksum solutionChecksum, ImmutableArray<ProjectId> projectIds, string searchPattern, ImmutableArray<string> kinds, RemoteServiceCallbackId callbackId, CancellationToken cancellationToken);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<List<SyntaxNode>> s_syntaxNodeListPool = new ObjectPool<List<SyntaxNode>>(() => [], trimOnFree: false);

protected CSharpSyntaxFacts()
{
}
Expand Down Expand Up @@ -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 = ".";

Expand Down Expand Up @@ -889,30 +882,10 @@ private static void AppendTypeParameterList(StringBuilder builder, TypeParameter
}
}

public PooledObject<List<SyntaxNode>> GetTopLevelAndMethodLevelMembers(SyntaxNode? root)
{
var pooledObject = s_syntaxNodeListPool.GetPooledObject();
var list = pooledObject.Object;

AppendMembers(root, list, topLevel: true, methodLevel: true);

return pooledObject;
}

public PooledObject<List<SyntaxNode>> GetMethodLevelMembers(SyntaxNode? root)
{
var pooledObject = s_syntaxNodeListPool.GetPooledObject();
var list = pooledObject.Object;

AppendMembers(root, list, topLevel: false, methodLevel: true);

return pooledObject;
}

public SyntaxList<SyntaxNode> GetMembersOfTypeDeclaration(SyntaxNode typeDeclaration)
=> ((TypeDeclarationSyntax)typeDeclaration).Members;

private void AppendMembers(SyntaxNode? node, List<SyntaxNode> list, bool topLevel, bool methodLevel)
protected override void AppendMembers(SyntaxNode? node, ArrayBuilder<SyntaxNode> list, bool topLevel, bool methodLevel)
{
Debug.Assert(topLevel || methodLevel);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,7 @@
<Compile Include="$(MSBuildThisFileDirectory)Services\SyntaxFacts\AbstractDocumentationCommentService.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Services\FileBannerFacts\AbstractFileBannerFacts.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Services\HeaderFacts\AbstractHeaderFacts.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Services\SyntaxFacts\AbstractSyntaxFacts.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Services\SyntaxFacts\ExternalSourceInfo.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Services\SyntaxFacts\IAccessibilityFacts.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Services\SyntaxFacts\IDocumentationCommentService.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -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<SyntaxNode> result)
=> AppendMembers(root, result, topLevel: true, methodLevel: true);

public void AddTopLevelMembers(SyntaxNode? root, ArrayBuilder<SyntaxNode> result)
=> AppendMembers(root, result, topLevel: true, methodLevel: false);

public void AddMethodLevelMembers(SyntaxNode? root, ArrayBuilder<SyntaxNode> result)
=> AppendMembers(root, result, topLevel: false, methodLevel: true);

protected abstract void AppendMembers(SyntaxNode? node, ArrayBuilder<SyntaxNode> list, bool topLevel, bool methodLevel);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -410,9 +411,12 @@ void GetPartsOfTupleExpression<TArgumentSyntax>(SyntaxNode node,
SyntaxNode? ConvertToSingleLine(SyntaxNode? node, bool useElasticTrivia = false);

// Violation. This is a feature level API.
PooledObject<List<SyntaxNode>> GetTopLevelAndMethodLevelMembers(SyntaxNode? root);
void AddTopLevelAndMethodLevelMembers(SyntaxNode? root, ArrayBuilder<SyntaxNode> result);
// Violation. This is a feature level API.
PooledObject<List<SyntaxNode>> GetMethodLevelMembers(SyntaxNode? root);
void AddTopLevelMembers(SyntaxNode? root, ArrayBuilder<SyntaxNode> result);
// Violation. This is a feature level API.
void AddMethodLevelMembers(SyntaxNode? root, ArrayBuilder<SyntaxNode> result);

SyntaxList<SyntaxNode> GetMembersOfTypeDeclaration(SyntaxNode typeDeclaration);

// Violation. This is a feature level API.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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()
Expand Down

0 comments on commit 7ca7de3

Please sign in to comment.