88using System . Linq ;
99using System . Threading ;
1010using System . Threading . Tasks ;
11+ using Microsoft . CodeAnalysis . LanguageService ;
1112using Microsoft . CodeAnalysis . PatternMatching ;
13+ using Microsoft . CodeAnalysis . PooledObjects ;
1214using Microsoft . CodeAnalysis . Remote ;
15+ using Microsoft . CodeAnalysis . Shared ;
16+ using Microsoft . CodeAnalysis . Shared . Extensions ;
1317using Microsoft . CodeAnalysis . Shared . Utilities ;
18+ using Microsoft . CodeAnalysis . Text ;
1419using Microsoft . CodeAnalysis . Threading ;
1520using Roslyn . Utilities ;
1621
@@ -36,16 +41,16 @@ public async Task SearchDocumentAsync(
3641 await client . TryInvokeAsync < IRemoteNavigateToSearchService > (
3742 document . Project ,
3843 ( service , solutionInfo , callbackId , cancellationToken ) =>
39- service . SearchDocumentAsync ( solutionInfo , document . Id , searchPattern , [ .. kinds ] , callbackId , cancellationToken ) ,
44+ service . SearchDocumentAndRelatedDocumentsAsync ( solutionInfo , document . Id , searchPattern , [ .. kinds ] , callbackId , cancellationToken ) ,
4045 callback , cancellationToken ) . ConfigureAwait ( false ) ;
4146
4247 return ;
4348 }
4449
45- await SearchDocumentInCurrentProcessAsync ( document , searchPattern , kinds , onItemsFound , cancellationToken ) . ConfigureAwait ( false ) ;
50+ await SearchDocumentAndRelatedDocumentsInCurrentProcessAsync ( document , searchPattern , kinds , onItemsFound , cancellationToken ) . ConfigureAwait ( false ) ;
4651 }
4752
48- public static async Task SearchDocumentInCurrentProcessAsync (
53+ public static async Task SearchDocumentAndRelatedDocumentsInCurrentProcessAsync (
4954 Document document ,
5055 string searchPattern ,
5156 IImmutableSet < string > kinds ,
@@ -55,12 +60,85 @@ public static async Task SearchDocumentInCurrentProcessAsync(
5560 var ( patternName , patternContainerOpt ) = PatternMatcher . GetNameAndContainer ( searchPattern ) ;
5661 var declaredSymbolInfoKindsSet = new DeclaredSymbolInfoKindSet ( kinds ) ;
5762
58- var results = new ConcurrentSet < RoslynNavigateToItem > ( ) ;
59- await SearchSingleDocumentAsync (
60- document , patternName , patternContainerOpt , declaredSymbolInfoKindsSet , t => results . Add ( t ) , cancellationToken ) . ConfigureAwait ( false ) ;
63+ // In parallel, search both the document requested, and any relevant 'related documents' we find for it. For the
64+ // original document, search the entirety of it (by passing 'null' in for the 'spans' argument). For related
65+ // documents, only search the spans of the partial-types/inheriting-types that we find for the types in this
66+ // starting document.
67+ await Task . WhenAll (
68+ SearchDocumentsInCurrentProcessAsync ( [ ( document , spans : null ) ] ) ,
69+ SearchRelatedDocumentsInCurrentProcessAsync ( ) ) . ConfigureAwait ( false ) ;
70+
71+ Task SearchDocumentsInCurrentProcessAsync ( ImmutableArray < ( Document document , NormalizedTextSpanCollection ? spans ) > documentAndSpans )
72+ => ProducerConsumer < RoslynNavigateToItem > . RunParallelAsync (
73+ documentAndSpans ,
74+ produceItems : static async ( documentAndSpan , onItemFound , args , cancellationToken ) =>
75+ {
76+ var ( patternName , patternContainerOpt , declaredSymbolInfoKindsSet , onItemsFound ) = args ;
77+ await SearchSingleDocumentAsync (
78+ documentAndSpan . document , patternName , patternContainerOpt , declaredSymbolInfoKindsSet ,
79+ item =>
80+ {
81+ // Ensure that the results found while searching the single document intersect the desired
82+ // subrange of the document we're searching in. For the primary document this will always
83+ // succeed (since we're searching the full document). But for related documents this may fail
84+ // if the results is not in the span of any of the types in those files we're searching.
85+ if ( documentAndSpan . spans is null || documentAndSpan . spans . IntersectsWith ( item . DeclaredSymbolInfo . Span ) )
86+ onItemFound ( item ) ;
87+ } ,
88+ cancellationToken ) . ConfigureAwait ( false ) ;
89+ } ,
90+ consumeItems : static ( values , args , cancellationToken ) => args . onItemsFound ( values , default , cancellationToken ) ,
91+ args : ( patternName , patternContainerOpt , declaredSymbolInfoKindsSet , onItemsFound ) ,
92+ cancellationToken ) ;
93+
94+ async Task SearchRelatedDocumentsInCurrentProcessAsync ( )
95+ {
96+ var relatedDocuments = await GetRelatedDocumentsAsync ( ) . ConfigureAwait ( false ) ;
97+ await SearchDocumentsInCurrentProcessAsync ( relatedDocuments ) . ConfigureAwait ( false ) ;
98+ }
6199
62- if ( results . Count > 0 )
63- await onItemsFound ( [ .. results ] , default , cancellationToken ) . ConfigureAwait ( false ) ;
100+ async Task < ImmutableArray < ( Document document , NormalizedTextSpanCollection ? spans ) > > GetRelatedDocumentsAsync ( )
101+ {
102+ // For C#/VB we define 'related documents' as those containing types in the inheritance chain of types in
103+ // the originating file (as well as all partial parts of the original and inheritance types). This way a
104+ // user can search for symbols scoped to the 'current document' and still get results for the members found
105+ // in partial parts.
106+
107+ var solution = document . Project . Solution ;
108+ var root = await document . GetRequiredSyntaxRootAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
109+ var semanticModel = await document . GetRequiredNullableDisabledSemanticModelAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
110+ var syntaxFacts = document . GetRequiredLanguageService < ISyntaxFactsService > ( ) ;
111+
112+ using var _ = ArrayBuilder < SyntaxNode > . GetInstance ( out var topLevelNodes ) ;
113+ syntaxFacts . AddTopLevelMembers ( root , topLevelNodes ) ;
114+
115+ // Keep track of all of the interesting spans in each document we find. Note: we will convert this to a
116+ // NormalizedTextSpanCollection before returning it. That way the span of an outer partial type will
117+ // encompass the span of an inner one and we won't get duplicates for the same symbol.
118+ var documentToTextSpans = new MultiDictionary < Document , TextSpan > ( ) ;
119+
120+ foreach ( var topLevelMember in topLevelNodes )
121+ {
122+ if ( semanticModel . GetDeclaredSymbol ( topLevelMember , cancellationToken ) is not INamedTypeSymbol namedTypeSymbol )
123+ continue ;
124+
125+ foreach ( var type in namedTypeSymbol . GetBaseTypesAndThis ( ) )
126+ {
127+ foreach ( var reference in type . DeclaringSyntaxReferences )
128+ {
129+ var relatedDocument = solution . GetDocument ( reference . SyntaxTree ) ;
130+ if ( relatedDocument is null )
131+ continue ;
132+
133+ documentToTextSpans . Add ( relatedDocument , reference . Span ) ;
134+ }
135+ }
136+ }
137+
138+ // Ensure we don't search the original document we were already searching.
139+ documentToTextSpans . Remove ( document ) ;
140+ return documentToTextSpans . SelectAsArray ( kvp => ( kvp . Key , new NormalizedTextSpanCollection ( kvp . Value ) ) ) ! ;
141+ }
64142 }
65143
66144 public async Task SearchProjectsAsync (
0 commit comments