/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/EditorFeatures/Core/Host/IStreamingFindReferencesPresenter.cs
173 строки
8 KB
Cyrus Najmabadi
Fix
17 окт 2025, 03:37
17 окт 2025, 03:37
7cc83f7
Код
Авторство
О чём код?
// 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 System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.FindUsages; using Microsoft.CodeAnalysis.Navigation; using Microsoft.CodeAnalysis.PooledObjects; namespace Microsoft.CodeAnalysis.Editor.Host; /// <summary> /// API for hosts to provide if they can present FindUsages results in a streaming manner. /// i.e. if they support showing results as they are found instead of after all of the results /// are found. /// </summary> internal interface IStreamingFindUsagesPresenter { /// <summary> /// Tells the presenter that a search is starting. The returned <see cref="FindUsagesContext"/> /// is used to push information about the search into. i.e. when a reference is found /// <see cref="FindUsagesContext.OnReferencesFoundAsync"/> should be called. When the /// search completes <see cref="FindUsagesContext.OnCompletedAsync"/> should be called. /// etc. etc. /// </summary> /// <param name="title">A title to display to the user in the presentation of the results.</param> /// <param name="options">Options</param> /// <returns>A cancellation token that will be triggered if the presenter thinks the search /// should stop. This can normally happen if the presenter view is closed, or recycled to /// start a new search in it. Callers should only use this if they intend to report results /// asynchronously and thus relinquish their own control over cancellation from their own /// surrounding context. If the caller intends to populate the presenter synchronously, /// then this cancellation token can be ignored.</returns> (FindUsagesContext context, CancellationToken cancellationToken) StartSearch(string title, StreamingFindUsagesPresenterOptions options); /// <summary> /// Clears all the items from the presenter. /// </summary> void ClearAll(); } /// <summary> /// <see cref="IStreamingFindUsagesPresenter.StartSearch(string, StreamingFindUsagesPresenterOptions)"/> options. /// </summary> /// <param name="SupportsReferences"> /// Whether or not showing references is supported. /// If true, then the presenter can group by definition, showing references underneath. /// It can also show messages about no references being found at the end of the search. /// If false, the presenter will not group by definitions, and will show the definition /// items in isolation.</param> /// <param name="IncludeContainingTypeAndMemberColumns"></param> /// <param name="IncludeKindColumn"></param> internal readonly record struct StreamingFindUsagesPresenterOptions( bool SupportsReferences = false, bool IncludeContainingTypeAndMemberColumns = false, bool IncludeKindColumn = false) { public static readonly StreamingFindUsagesPresenterOptions Default = new(); } internal static class IStreamingFindUsagesPresenterExtensions { public static async Task<bool> TryPresentLocationOrNavigateIfOneAsync( this IStreamingFindUsagesPresenter presenter, IThreadingContext threadingContext, Workspace workspace, string title, ImmutableArray<DefinitionItem> items, CancellationToken cancellationToken) { var location = await presenter.GetStreamingLocationAsync( threadingContext, workspace, title, items, cancellationToken).ConfigureAwait(false); return await location.TryNavigateToAsync( threadingContext, new NavigationOptions(PreferProvisionalTab: true, ActivateTab: true), cancellationToken).ConfigureAwait(false); } /// <summary> /// Returns a navigable location that will take the user to the location there's only destination, or which will /// present all the locations if there are many. /// </summary> public static async Task<INavigableLocation?> GetStreamingLocationAsync( this IStreamingFindUsagesPresenter presenter, IThreadingContext threadingContext, Workspace workspace, string title, ImmutableArray<DefinitionItem> items, CancellationToken cancellationToken) { if (items.IsDefaultOrEmpty) return null; using var _ = ArrayBuilder<(DefinitionItem item, INavigableLocation location)>.GetInstance(out var builder); foreach (var item in items) { // Ignore any definitions that we can't navigate to. var navigableItem = await item.GetNavigableLocationAsync(workspace, cancellationToken).ConfigureAwait(false); if (navigableItem != null) { // If there's a third party external item we can navigate to. Defer to that item and finish. if (item.IsExternal) return navigableItem; builder.Add((item, navigableItem)); } } if (builder.Count == 0) return null; if (builder is [{ item.SourceSpans.Length: <= 1, location: var location }]) { // There was only one location to navigate to. Just directly go to that location. If we're directly // going to a location we need to activate the preview so that focus follows to the new cursor position. return location; } if (presenter == null) return null; // Find the first location from a non-generated document to navigate to automatically. // This way the user gets taken to their user code while still being able to see all the results // (including generated code) in the tool window. var preferredLocation = GetPreferredNonGeneratedLocation(builder); var navigableItems = builder.SelectAsArray(t => t.item); return new NavigableLocation(async (options, cancellationToken) => { // Can only navigate or present items on UI thread. await threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); // We have multiple definitions, or we have definitions with multiple locations. Present this to the // user so they can decide where they want to go to. // // We ignore the cancellation token returned by StartSearch as we're in a context where // we've computed all the results and we're synchronously populating the UI with it. var (context, _) = presenter.StartSearch(title, StreamingFindUsagesPresenterOptions.Default); try { foreach (var item in navigableItems) await context.OnDefinitionFoundAsync(item, cancellationToken).ConfigureAwait(false); } finally { await context.OnCompletedAsync(cancellationToken).ConfigureAwait(false); } return true; }); } /// <summary> /// Finds the first navigable location from a non-generated document in the list of definition items. /// This allows Go To Definition to automatically navigate to user code when there are multiple locations /// including generated code. /// </summary> private static INavigableLocation? GetPreferredNonGeneratedLocation( ArrayBuilder<(DefinitionItem item, INavigableLocation location)> builder) { foreach (var (item, location) in builder) { foreach (var sourceSpan in item.SourceSpans) { if (!sourceSpan.IsGeneratedCode) return location; } } return null; } }