File: NavigateTo\RoslynSearchItemsSource.cs
Web Access
Project: src\src\VisualStudio\Core\Def\Microsoft.VisualStudio.LanguageServices_pxr0p0dn_wpftmp.csproj (Microsoft.VisualStudio.LanguageServices)
// 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;
using System.Collections.Immutable;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.ErrorReporting;
using Microsoft.VisualStudio.Search.Data;
using Microsoft.VisualStudio.Threading;
 
namespace Microsoft.CodeAnalysis.NavigateTo;
 
internal sealed partial class RoslynSearchItemsSourceProvider
{
    /// <summary>
    /// Roslyn implementation of <see cref="ISearchItemsSource"/>.  This is the type actually responsible for
    /// calling into the underlying <see cref="NavigateToSearcher"/> and marshalling the results over to the ui.
    /// </summary>
    private sealed class RoslynSearchItemsSource(RoslynSearchItemsSourceProvider provider) : CodeSearchItemsSourceBase
    {
        private static readonly IImmutableSet<string> s_typeKinds = ImmutableHashSet<string>.Empty
            .Add(NavigateToItemKind.Class)
            .Add(NavigateToItemKind.Enum)
            .Add(NavigateToItemKind.Structure)
            .Add(NavigateToItemKind.Interface)
            .Add(NavigateToItemKind.Delegate)
            .Add(NavigateToItemKind.Module);
        private static readonly IImmutableSet<string> s_memberKinds = ImmutableHashSet<string>.Empty
            .Add(NavigateToItemKind.Constant)
            .Add(NavigateToItemKind.EnumItem)
            .Add(NavigateToItemKind.Field)
            .Add(NavigateToItemKind.Method)
            .Add(NavigateToItemKind.Property)
            .Add(NavigateToItemKind.Event);
        private static readonly IImmutableSet<string> s_allKinds = s_typeKinds.Union(s_memberKinds);
 
        public override async Task PerformSearchAsync(ISearchQuery searchQuery, ISearchCallback searchCallback, CancellationToken cancellationToken)
        {
            using var token = provider._asyncListener.BeginAsyncOperation(nameof(PerformSearchAsync));
 
            try
            {
                // Make a task that waits indefinitely, or until the cancellation token is signaled.
                var cancellationTriggeredTask = Task.Delay(-1, cancellationToken);
 
                // Now, kick off the actual search work concurrently with the waiting task.
                var searchTask = PerformSearchWorkerAsync(searchQuery, searchCallback, cancellationToken);
 
                // Now wait for either task to complete.  This allows us to bail out of the call into us once the
                // cancellation token is signaled, even if search work is still happening.  This is desirable as the
                // caller waits until this method returns before kicking off the next search.  And we want to let that
                // start as soon as possible, even if our current search hasn't gotten around to checking the
                // cancellation token yet.
                await Task.WhenAny(cancellationTriggeredTask, searchTask).ConfigureAwait(false);
            }
            catch (Exception ex) when (FatalError.ReportAndCatchUnlessCanceled(ex))
            {
            }
        }
 
        private async Task PerformSearchWorkerAsync(
            ISearchQuery searchQuery,
            ISearchCallback searchCallback,
            CancellationToken cancellationToken)
        {
            // Ensure we yield immediately so our caller can proceed with other work.
            await TaskScheduler.Default.SwitchTo(alwaysYield: true);
 
            var searchValue = searchQuery.QueryString.Trim();
            if (string.IsNullOrWhiteSpace(searchValue))
                return;
 
            var includeTypeResults = searchQuery.FiltersStates.TryGetValue("Types", out var typesValue) && typesValue == "True";
            var includeMembersResults = searchQuery.FiltersStates.TryGetValue("Members", out var membersValue) && membersValue == "True";
 
            var kinds = (includeTypeResults, includeMembersResults) switch
            {
                (true, false) => s_typeKinds,
                (false, true) => s_memberKinds,
                _ => s_allKinds,
            };
 
            var searchScope = searchQuery switch
            {
                ICodeSearchQuery { Scope: SearchScopes.CurrentDocument } => NavigateToSearchScope.Document,
                ICodeSearchQuery { Scope: SearchScopes.CurrentProject } => NavigateToSearchScope.Project,
                _ => NavigateToSearchScope.Solution,
            };
 
            // Create a nav-to callback that will take results and translate them to aiosp results for the
            // callback passed to us.
 
            var solution = provider._workspace.CurrentSolution;
            var searcher = NavigateToSearcher.Create(
                solution,
                provider._asyncListener,
                new RoslynNavigateToSearchCallback(solution, provider, searchCallback),
                searchValue,
                kinds,
                provider._threadingContext.DisposalToken);
 
            await searcher.SearchAsync(searchScope, cancellationToken).ConfigureAwait(false);
        }
    }
}