22// The .NET Foundation licenses this file to you under the MIT license.
33// See the LICENSE file in the project root for more information.
44
5+ using System . Collections . Generic ;
56using System . Threading ;
67using System . Threading . Tasks ;
8+ using Microsoft . CodeAnalysis . Classification ;
79using Microsoft . CodeAnalysis . Editor . Host ;
810using Microsoft . CodeAnalysis . Editor . Shared . Utilities ;
911using Microsoft . CodeAnalysis . FindUsages ;
1012using Microsoft . CodeAnalysis . GoToDefinition ;
1113using Microsoft . CodeAnalysis . LanguageService ;
1214using Microsoft . CodeAnalysis . PooledObjects ;
1315using Microsoft . CodeAnalysis . Shared . Extensions ;
16+ using Microsoft . CodeAnalysis . Shared . Utilities ;
1417
1518namespace Microsoft . CodeAnalysis . Navigation ;
1619
17- internal abstract class AbstractDefinitionLocationService : IDefinitionLocationService
20+ internal abstract partial class AbstractDefinitionLocationService (
21+ IThreadingContext threadingContext ,
22+ IStreamingFindUsagesPresenter streamingPresenter ) : IDefinitionLocationService
1823{
19- private readonly IThreadingContext _threadingContext ;
20- private readonly IStreamingFindUsagesPresenter _streamingPresenter ;
21-
22- protected AbstractDefinitionLocationService (
23- IThreadingContext threadingContext ,
24- IStreamingFindUsagesPresenter streamingPresenter )
25- {
26- _threadingContext = threadingContext ;
27- _streamingPresenter = streamingPresenter ;
28- }
24+ private readonly IThreadingContext _threadingContext = threadingContext ;
25+ private readonly IStreamingFindUsagesPresenter _streamingPresenter = streamingPresenter ;
2926
3027 private static Task < INavigableLocation ? > GetNavigableLocationAsync (
3128 Document document , int position , CancellationToken cancellationToken )
@@ -40,54 +37,62 @@ protected AbstractDefinitionLocationService(
4037
4138 public async Task < DefinitionLocation ? > GetDefinitionLocationAsync ( Document document , int position , CancellationToken cancellationToken )
4239 {
40+ var symbolService = document . GetRequiredLanguageService < IGoToDefinitionSymbolService > ( ) ;
41+
4342 // We want to compute this as quickly as possible so that the symbol be squiggled and navigated to. We
4443 // don't want to wait on expensive operations like computing source-generators or skeletons if we can avoid
4544 // it. So first try with a frozen document, then fallback to a normal document. This mirrors how go-to-def
4645 // works as well.
4746 return await GetDefinitionLocationWorkerAsync ( document . WithFrozenPartialSemantics ( cancellationToken ) ) . ConfigureAwait ( false ) ??
4847 await GetDefinitionLocationWorkerAsync ( document ) . ConfigureAwait ( false ) ;
4948
50- async Task < DefinitionLocation ? > GetDefinitionLocationWorkerAsync ( Document document )
49+ async ValueTask < DefinitionLocation ? > GetDefinitionLocationWorkerAsync ( Document document )
50+ {
51+ return await GetControlFlowTargetLocationAsync ( document ) . ConfigureAwait ( false ) ??
52+ await GetSymbolLocationAsync ( document ) . ConfigureAwait ( false ) ;
53+ }
54+
55+ async ValueTask < DefinitionLocation ? > GetControlFlowTargetLocationAsync ( Document document )
5156 {
52- var symbolService = document . GetRequiredLanguageService < IGoToDefinitionSymbolService > ( ) ;
5357 var ( controlFlowTarget , controlFlowSpan ) = await symbolService . GetTargetIfControlFlowAsync (
5458 document , position , cancellationToken ) . ConfigureAwait ( false ) ;
55- if ( controlFlowTarget != null )
56- {
57- var location = await GetNavigableLocationAsync (
58- document , controlFlowTarget . Value , cancellationToken ) . ConfigureAwait ( false ) ;
59- return location is null ? null : new DefinitionLocation ( location , new DocumentSpan ( document , controlFlowSpan ) ) ;
60- }
61- else
62- {
63- // Try to compute the referenced symbol and attempt to go to definition for the symbol.
64- var ( symbol , project , span ) = await symbolService . GetSymbolProjectAndBoundSpanAsync (
65- document , position , cancellationToken ) . ConfigureAwait ( false ) ;
66- if ( symbol is null )
67- return null ;
68-
69- // if the symbol only has a single source location, and we're already on it,
70- // try to see if there's a better symbol we could navigate to.
71- var remappedLocation = await GetAlternativeLocationIfAlreadyOnDefinitionAsync (
72- project , position , symbol , originalDocument : document , cancellationToken ) . ConfigureAwait ( false ) ;
73- if ( remappedLocation != null )
74- return new DefinitionLocation ( remappedLocation , new DocumentSpan ( document , span ) ) ;
75-
76- var isThirdPartyNavigationAllowed = await IsThirdPartyNavigationAllowedAsync (
77- symbol , position , document , cancellationToken ) . ConfigureAwait ( false ) ;
78-
79- var location = await GoToDefinitionHelpers . GetDefinitionLocationAsync (
80- symbol ,
81- project . Solution ,
82- _threadingContext ,
83- _streamingPresenter ,
84- thirdPartyNavigationAllowed : isThirdPartyNavigationAllowed ,
85- cancellationToken : cancellationToken ) . ConfigureAwait ( false ) ;
86- if ( location is null )
87- return null ;
88-
89- return new DefinitionLocation ( location , new DocumentSpan ( document , span ) ) ;
90- }
59+ if ( controlFlowTarget == null )
60+ return null ;
61+
62+ var location = await GetNavigableLocationAsync (
63+ document , controlFlowTarget . Value , cancellationToken ) . ConfigureAwait ( false ) ;
64+ return location is null ? null : new DefinitionLocation ( location , new DocumentSpan ( document , controlFlowSpan ) ) ;
65+ }
66+
67+ async ValueTask < DefinitionLocation ? > GetSymbolLocationAsync ( Document document )
68+ {
69+ // Try to compute the referenced symbol and attempt to go to definition for the symbol.
70+ var ( symbol , project , span ) = await symbolService . GetSymbolProjectAndBoundSpanAsync (
71+ document , position , cancellationToken ) . ConfigureAwait ( false ) ;
72+ if ( symbol is null )
73+ return null ;
74+
75+ // if the symbol only has a single source location, and we're already on it,
76+ // try to see if there's a better symbol we could navigate to.
77+ var remappedLocation = await GetAlternativeLocationIfAlreadyOnDefinitionAsync (
78+ project , position , symbol , originalDocument : document , cancellationToken ) . ConfigureAwait ( false ) ;
79+ if ( remappedLocation != null )
80+ return new DefinitionLocation ( remappedLocation , new DocumentSpan ( document , span ) ) ;
81+
82+ var isThirdPartyNavigationAllowed = await IsThirdPartyNavigationAllowedAsync (
83+ symbol , position , document , cancellationToken ) . ConfigureAwait ( false ) ;
84+
85+ var location = await GoToDefinitionHelpers . GetDefinitionLocationAsync (
86+ symbol ,
87+ project . Solution ,
88+ _threadingContext ,
89+ _streamingPresenter ,
90+ thirdPartyNavigationAllowed : isThirdPartyNavigationAllowed ,
91+ cancellationToken : cancellationToken ) . ConfigureAwait ( false ) ;
92+ if ( location is null )
93+ return null ;
94+
95+ return new DefinitionLocation ( location , new DocumentSpan ( document , span ) ) ;
9196 }
9297 }
9398
@@ -114,29 +119,118 @@ protected AbstractDefinitionLocationService(
114119 if ( definitionDocument != originalDocument )
115120 return null ;
116121
117- // Ok, we were already on the definition. Look for better symbols we could show results
118- // for instead. For now, just see if we're on an interface member impl. If so, we can
119- // instead navigate to the actual interface member.
120- //
121- // In the future we can expand this with other mappings if appropriate.
122- var interfaceImpls = symbol . ExplicitOrImplicitInterfaceImplementations ( ) ;
123- if ( interfaceImpls . Length == 0 )
124- return null ;
125-
126- var title = string . Format ( EditorFeaturesResources . _0_implemented_members ,
127- FindUsagesHelpers . GetDisplayName ( symbol ) ) ;
122+ // Ok, we were already on the definition. Look for better symbols we could show results for instead. This can be
123+ // expanded with other mappings in the future if appropriate.
124+ return await TryGetExplicitInterfaceLocationAsync ( ) . ConfigureAwait ( false ) ??
125+ await TryGetInterceptedLocationAsync ( ) . ConfigureAwait ( false ) ;
128126
129- using var _ = ArrayBuilder < DefinitionItem > . GetInstance ( out var builder ) ;
130- foreach ( var impl in interfaceImpls )
127+ async ValueTask < INavigableLocation ? > TryGetExplicitInterfaceLocationAsync ( )
131128 {
132- builder . AddRange ( await GoToDefinitionFeatureHelpers . GetDefinitionsAsync (
133- impl , solution , thirdPartyNavigationAllowed : false , cancellationToken ) . ConfigureAwait ( false ) ) ;
129+ var interfaceImpls = symbol . ExplicitOrImplicitInterfaceImplementations ( ) ;
130+ if ( interfaceImpls . Length == 0 )
131+ return null ;
132+
133+ var title = string . Format ( EditorFeaturesResources . _0_implemented_members ,
134+ FindUsagesHelpers . GetDisplayName ( symbol ) ) ;
135+
136+ using var _ = ArrayBuilder < DefinitionItem > . GetInstance ( out var builder ) ;
137+ foreach ( var impl in interfaceImpls )
138+ {
139+ builder . AddRange ( await GoToDefinitionFeatureHelpers . GetDefinitionsAsync (
140+ impl , solution , thirdPartyNavigationAllowed : false , cancellationToken ) . ConfigureAwait ( false ) ) ;
141+ }
142+
143+ var definitions = builder . ToImmutable ( ) ;
144+
145+ return await _streamingPresenter . GetStreamingLocationAsync (
146+ _threadingContext , solution . Workspace , title , definitions , cancellationToken ) . ConfigureAwait ( false ) ;
134147 }
135148
136- var definitions = builder . ToImmutable ( ) ;
149+ async ValueTask < INavigableLocation ? > TryGetInterceptedLocationAsync ( )
150+ {
151+ if ( symbol is not IMethodSymbol method )
152+ return null ;
153+
154+ // Find attributes of the form: [InterceptsLocationAttribute(version: 1, data: "...")];
155+
156+ var attributes = method . GetAttributes ( ) ;
157+ var interceptsLocationDatas = InterceptsLocationUtilities . GetInterceptsLocationData ( attributes ) ;
158+ if ( interceptsLocationDatas . Length == 0 )
159+ return null ;
160+
161+ using var _ = ArrayBuilder < DocumentSpan > . GetInstance ( out var documentSpans ) ;
162+
163+ foreach ( var ( contentHash , position ) in interceptsLocationDatas )
164+ {
165+ var document = await project . GetDocumentAsync ( contentHash , cancellationToken ) . ConfigureAwait ( false ) ;
166+
167+ if ( document != null )
168+ {
169+ var root = await document . GetRequiredSyntaxRootAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
137170
138- return await _streamingPresenter . GetStreamingLocationAsync (
139- _threadingContext , solution . Workspace , title , definitions , cancellationToken ) . ConfigureAwait ( false ) ;
171+ if ( position >= 0 && position < root . FullSpan . Length )
172+ {
173+ var token = root . FindToken ( position ) ;
174+ documentSpans . Add ( new DocumentSpan ( document , token . Span ) ) ;
175+ }
176+ }
177+ }
178+
179+ documentSpans . RemoveDuplicates ( ) ;
180+
181+ if ( documentSpans . Count == 0 )
182+ {
183+ return null ;
184+ }
185+ else if ( documentSpans . Count == 1 )
186+ {
187+ // Just one document span this mapped to. Navigate directly do that.
188+ return await documentSpans [ 0 ] . GetNavigableLocationAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
189+ }
190+ else
191+ {
192+ var title = string . Format ( EditorFeaturesResources . _0_intercepted_locations ,
193+ FindUsagesHelpers . GetDisplayName ( method ) ) ;
194+
195+ var definitionItem = method . ToNonClassifiedDefinitionItem ( solution , includeHiddenLocations : true ) ;
196+
197+ var referenceItems = new List < SourceReferenceItem > ( capacity : documentSpans . Count ) ;
198+ var classificationOptions = ClassificationOptions . Default with { ClassifyObsoleteSymbols = false } ;
199+ foreach ( var documentSpan in documentSpans )
200+ {
201+ var classifiedSpans = await ClassifiedSpansAndHighlightSpanFactory . ClassifyAsync (
202+ documentSpan , classifiedSpans : null , classificationOptions , cancellationToken ) . ConfigureAwait ( false ) ;
203+
204+ referenceItems . Add ( new SourceReferenceItem (
205+ definitionItem , documentSpan , classifiedSpans , SymbolUsageInfo . None , additionalProperties : [ ] ) ) ;
206+ }
207+
208+ // Multiple document spans this mapped to. Show them all.
209+ return new NavigableLocation ( async ( options , cancellationToken ) =>
210+ {
211+ // Can only navigate or present items on UI thread.
212+ await _threadingContext . JoinableTaskFactory . SwitchToMainThreadAsync ( cancellationToken ) ;
213+
214+ // We have multiple definitions, or we have definitions with multiple locations. Present this to the
215+ // user so they can decide where they want to go to.
216+ //
217+ // We ignore the cancellation token returned by StartSearch as we're in a context where
218+ // we've computed all the results and we're synchronously populating the UI with it.
219+ var ( context , _) = _streamingPresenter . StartSearch ( title , new StreamingFindUsagesPresenterOptions ( SupportsReferences : true ) ) ;
220+ try
221+ {
222+ await context . OnDefinitionFoundAsync ( definitionItem , cancellationToken ) . ConfigureAwait ( false ) ;
223+ await context . OnReferencesFoundAsync ( referenceItems . AsAsyncEnumerable ( ) , cancellationToken ) . ConfigureAwait ( false ) ;
224+ }
225+ finally
226+ {
227+ await context . OnCompletedAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
228+ }
229+
230+ return true ;
231+ } ) ;
232+ }
233+ }
140234 }
141235
142236 private static async Task < bool > IsThirdPartyNavigationAllowedAsync (
0 commit comments