33// See the LICENSE file in the project root for more information.
44
55using System ;
6+ using System . Diagnostics . CodeAnalysis ;
67using System . Threading ;
78using System . Threading . Tasks ;
89using Microsoft . CodeAnalysis . Classification ;
1718using Microsoft . CodeAnalysis . Options ;
1819using Microsoft . CodeAnalysis . Shared . Extensions ;
1920using Microsoft . CodeAnalysis . Shared . TestHooks ;
20- using Microsoft . CodeAnalysis . Text ;
2121using Microsoft . CodeAnalysis . Threading ;
22- using Microsoft . VisualStudio . Commanding ;
23- using Microsoft . VisualStudio . Text ;
24- using Microsoft . VisualStudio . Text . Editor . Commanding ;
2522using Microsoft . VisualStudio . Threading ;
2623
27- namespace Microsoft . CodeAnalysis . GoToDefinition ;
24+ namespace Microsoft . CodeAnalysis . GoOrFind ;
2825
29- internal abstract class AbstractGoOrFindCommandHandler < TLanguageService , TCommandArgs > (
26+ /// <summary>
27+ /// Core service responsible for handling an operation (like 'go to base, go to impl, find references')
28+ /// and trying to navigate quickly to them if possible, or show their results in the find-usages window.
29+ /// </summary>
30+ internal abstract class AbstractGoOrFindNavigationService < TLanguageService > (
3031 IThreadingContext threadingContext ,
3132 IStreamingFindUsagesPresenter streamingPresenter ,
3233 IAsynchronousOperationListener listener ,
33- IGlobalOptionService globalOptions ) : ICommandHandler < TCommandArgs >
34+ IGlobalOptionService globalOptions )
35+ : IGoOrFindNavigationService
3436 where TLanguageService : class , ILanguageService
35- where TCommandArgs : EditorCommandArgs
3637{
3738 private readonly IThreadingContext _threadingContext = threadingContext ;
3839 private readonly IStreamingFindUsagesPresenter _streamingPresenter = streamingPresenter ;
@@ -59,7 +60,7 @@ internal abstract class AbstractGoOrFindCommandHandler<TLanguageService, TComman
5960 /// the presenter. In that case, the presenter will notify us that it has be re-purposed and we will also cancel
6061 /// this source.
6162 /// </remarks>
62- private readonly CancellationSeries _cancellationSeries = new ( threadingContext . DisposalToken ) ;
63+ private CancellationTokenSource _cancellationTokenSource = new ( ) ;
6364
6465 /// <summary>
6566 /// This hook allows for stabilizing the asynchronous nature of this command handler for integration testing.
@@ -80,49 +81,34 @@ protected virtual StreamingFindUsagesPresenterOptions GetStreamingPresenterOptio
8081
8182 protected abstract Task FindActionAsync ( IFindUsagesContext context , Document document , TLanguageService service , int caretPosition , CancellationToken cancellationToken ) ;
8283
83- private static ( Document ? , TLanguageService ? ) GetDocumentAndService ( ITextSnapshot snapshot )
84- {
85- var document = snapshot . GetOpenDocumentInCurrentContextWithChanges ( ) ;
86- return ( document , document ? . GetLanguageService < TLanguageService > ( ) ) ;
87- }
84+ public bool IsAvailable ( [ NotNullWhen ( true ) ] Document ? document )
85+ => document ? . GetLanguageService < TLanguageService > ( ) != null ;
8886
89- public CommandState GetCommandState ( TCommandArgs args )
90- {
91- var ( _, service ) = GetDocumentAndService ( args . SubjectBuffer . CurrentSnapshot ) ;
92- return service != null
93- ? CommandState . Available
94- : CommandState . Unspecified ;
95- }
96-
97- public bool ExecuteCommand ( TCommandArgs args , CommandExecutionContext context )
87+ public bool ExecuteCommand ( Document document , int position )
9888 {
9989 _threadingContext . ThrowIfNotOnUIThread ( ) ;
100-
101- var subjectBuffer = args . SubjectBuffer ;
102- var caret = args . TextView . GetCaretPoint ( subjectBuffer ) ;
103- if ( ! caret . HasValue )
90+ if ( document is null )
10491 return false ;
10592
106- var ( document , service ) = GetDocumentAndService ( subjectBuffer . CurrentSnapshot ) ;
93+ var service = document . GetLanguageService < TLanguageService > ( ) ;
10794 if ( service == null )
10895 return false ;
10996
110- Contract . ThrowIfNull ( document ) ;
111-
11297 // cancel any prior find-refs that might be in progress.
113- var cancellationToken = _cancellationSeries . CreateNext ( ) ;
98+ _cancellationTokenSource . Cancel ( ) ;
99+ _cancellationTokenSource = new ( ) ;
114100
115101 // we're going to return immediately from ExecuteCommand and kick off our own async work to invoke the
116102 // operation. Once this returns, the editor will close the threaded wait dialog it created.
117- _inProgressCommand = ExecuteCommandAsync ( document , service , caret . Value . Position , cancellationToken ) ;
103+ _inProgressCommand = ExecuteCommandAsync ( document , service , position , _cancellationTokenSource ) ;
118104 return true ;
119105 }
120106
121107 private async Task ExecuteCommandAsync (
122108 Document document ,
123109 TLanguageService service ,
124110 int position ,
125- CancellationToken cancellationToken )
111+ CancellationTokenSource cancellationTokenSource )
126112 {
127113 // This is a fire-and-forget method (nothing guarantees observing it). As such, we have to handle cancellation
128114 // and failure ourselves.
@@ -141,7 +127,7 @@ private async Task ExecuteCommandAsync(
141127 // any failures from it. Technically this should not be possible as it should be inside this same
142128 // try/catch. however this code wants to be very resilient to any prior mistakes infecting later operations.
143129 await _inProgressCommand . NoThrowAwaitable ( captureContext : false ) ;
144- await ExecuteCommandWorkerAsync ( document , service , position , cancellationToken ) . ConfigureAwait ( false ) ;
130+ await ExecuteCommandWorkerAsync ( document , service , position , cancellationTokenSource ) . ConfigureAwait ( false ) ;
145131 }
146132 catch ( OperationCanceledException )
147133 {
@@ -155,7 +141,7 @@ private async Task ExecuteCommandWorkerAsync(
155141 Document document ,
156142 TLanguageService service ,
157143 int position ,
158- CancellationToken cancellationToken )
144+ CancellationTokenSource cancellationTokenSource )
159145 {
160146 // Switch to the BG immediately so we can keep as much work off the UI thread.
161147 await TaskScheduler . Default ;
@@ -173,6 +159,7 @@ private async Task ExecuteCommandWorkerAsync(
173159 // IStreamingFindUsagesPresenter.
174160 var findContext = new BufferedFindUsagesContext ( ) ;
175161
162+ var cancellationToken = cancellationTokenSource . Token ;
176163 var delayBeforeShowingResultsWindowTask = DelayAsync ( cancellationToken ) ;
177164 var findTask = FindResultsAsync ( findContext , document , service , position , cancellationToken ) ;
178165
@@ -203,7 +190,7 @@ await _streamingPresenter.TryPresentLocationOrNavigateIfOneAsync(
203190 // We either got no results, or 1.5 has passed and we didn't figure out the symbols to navigate to or
204191 // present. So pop up the presenter to show the user that we're involved in a longer search, without
205192 // blocking them.
206- await PresentResultsInStreamingPresenterAsync ( document , findContext , findTask , cancellationToken ) . ConfigureAwait ( false ) ;
193+ await PresentResultsInStreamingPresenterAsync ( document , findContext , findTask , cancellationTokenSource ) . ConfigureAwait ( false ) ;
207194 }
208195
209196 private Task DelayAsync ( CancellationToken cancellationToken )
@@ -213,7 +200,7 @@ private Task DelayAsync(CancellationToken cancellationToken)
213200 return delayHook ( cancellationToken ) ;
214201 }
215202
216- // If we want to navigate to a single result if it is found quickly, then delay showing the find-refs winfor
203+ // If we want to navigate to a single result if it is found quickly, then delay showing the find-refs window
217204 // for 1.5 seconds to see if a result comes in by then. If we're not navigating and are always showing the
218205 // far window, then don't have any delay showing the window.
219206 var delay = this . NavigateToSingleResultIfQuick
@@ -227,8 +214,9 @@ private async Task PresentResultsInStreamingPresenterAsync(
227214 Document document ,
228215 BufferedFindUsagesContext findContext ,
229216 Task findTask ,
230- CancellationToken cancellationToken )
217+ CancellationTokenSource cancellationTokenSource )
231218 {
219+ var cancellationToken = cancellationTokenSource . Token ;
232220 await _threadingContext . JoinableTaskFactory . SwitchToMainThreadAsync ( cancellationToken ) ;
233221 var ( presenterContext , presenterCancellationToken ) = _streamingPresenter . StartSearch ( DisplayName , GetStreamingPresenterOptions ( document ) ) ;
234222
@@ -244,7 +232,7 @@ private async Task PresentResultsInStreamingPresenterAsync(
244232 // Hook up the presenter's cancellation token to our overall governing cancellation token. In other
245233 // words, if something else decides to present in the presenter (like a find-refs call) we'll hear about
246234 // that and can cancel all our work.
247- presenterCancellationToken . Register ( ( ) => _cancellationSeries . CreateNext ( ) ) ;
235+ presenterCancellationToken . Register ( ( ) => cancellationTokenSource . Cancel ( ) ) ;
248236
249237 // now actually wait for the find work to be done.
250238 await findTask . ConfigureAwait ( false ) ;
@@ -291,9 +279,9 @@ internal TestAccessor GetTestAccessor()
291279
292280 internal readonly struct TestAccessor
293281 {
294- private readonly AbstractGoOrFindCommandHandler < TLanguageService , TCommandArgs > _instance ;
282+ private readonly AbstractGoOrFindNavigationService < TLanguageService > _instance ;
295283
296- internal TestAccessor ( AbstractGoOrFindCommandHandler < TLanguageService , TCommandArgs > instance )
284+ internal TestAccessor ( AbstractGoOrFindNavigationService < TLanguageService > instance )
297285 => _instance = instance ;
298286
299287 internal ref Func < CancellationToken , Task > ? DelayHook
0 commit comments