提交 219887d5 编写于 作者: C CyrusNajmabadi 提交者: GitHub

Merge pull request #20597 from CyrusNajmabadi/navToCaching

Cache previous results of NavigateTo so we can filter quickly as the user types.
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Immutable;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.FindSymbols;
......@@ -15,21 +17,24 @@ namespace Microsoft.CodeAnalysis.NavigateTo
{
internal abstract partial class AbstractNavigateToSearchService
{
private static ConditionalWeakTable<Project, Tuple<string, ImmutableArray<SearchResult>>> s_lastProjectSearchCache =
new ConditionalWeakTable<Project, Tuple<string, ImmutableArray<SearchResult>>>();
public static Task<ImmutableArray<INavigateToSearchResult>> SearchProjectInCurrentProcessAsync(
Project project, string searchPattern, CancellationToken cancellationToken)
{
return FindNavigableDeclaredSymbolInfosAsync(
return FindSearchResultsAsync(
project, searchDocument: null, pattern: searchPattern, cancellationToken: cancellationToken);
}
public static Task<ImmutableArray<INavigateToSearchResult>> SearchDocumentInCurrentProcessAsync(
Document document, string searchPattern, CancellationToken cancellationToken)
{
return FindNavigableDeclaredSymbolInfosAsync(
return FindSearchResultsAsync(
document.Project, document, searchPattern, cancellationToken);
}
private static async Task<ImmutableArray<INavigateToSearchResult>> FindNavigableDeclaredSymbolInfosAsync(
private static async Task<ImmutableArray<INavigateToSearchResult>> FindSearchResultsAsync(
Project project, Document searchDocument, string pattern, CancellationToken cancellationToken)
{
// If the user created a dotted pattern then we'll grab the last part of the name
......@@ -48,9 +53,19 @@ internal abstract partial class AbstractNavigateToSearchService
try
{
return await FindNavigableDeclaredSymbolInfosAsync(
project, searchDocument, nameMatcher, containerMatcherOpt,
nameMatches, containerMatches, cancellationToken).ConfigureAwait(false);
// If we're searching a single document, then just do a full search of
// that document (we're fast enough to not need to optimize that case).
//
// If, however, we are searching a project, then see if we could potentially
// use the last computed results we have for that project. If so, it can
// be much faster to reuse and filter that result than to compute it from
// scratch.
var task = searchDocument != null
? ComputeSearchResultsAsync(project, searchDocument, nameMatcher, containerMatcherOpt, nameMatches, containerMatches, cancellationToken)
: TryFilterPreviousSearchResultsAsync(project, searchDocument, pattern, nameMatcher, containerMatcherOpt, nameMatches, containerMatches, cancellationToken);
var searchResults = await task.ConfigureAwait(false);
return ImmutableArray<INavigateToSearchResult>.CastUp(searchResults);
}
finally
{
......@@ -60,13 +75,77 @@ internal abstract partial class AbstractNavigateToSearchService
}
}
private static async Task<ImmutableArray<INavigateToSearchResult>> FindNavigableDeclaredSymbolInfosAsync(
private static async Task<ImmutableArray<SearchResult>> TryFilterPreviousSearchResultsAsync(
Project project, Document searchDocument, string pattern,
PatternMatcher nameMatcher, PatternMatcher containerMatcherOpt,
ArrayBuilder<PatternMatch> nameMatches, ArrayBuilder<PatternMatch> containerMatches,
CancellationToken cancellationToken)
{
// Searching an entire project. See if we already performed that same
// search with a substring of the current pattern. if so, we can use
// the previous result and just filter that down. This is useful for
// the common case where a user types some pattern, then keeps adding
// to it.
ImmutableArray<SearchResult> searchResults;
if (s_lastProjectSearchCache.TryGetValue(project, out var previousResult) &&
pattern.StartsWith(previousResult.Item1))
{
// We can reuse the previous results and just filter them.
searchResults = FilterPreviousResults(
previousResult.Item2,
nameMatcher, containerMatcherOpt,
nameMatches, containerMatches, cancellationToken);
}
else
{
// Didn't have previous results. Or it was a very different pattern.
// Can't reuse.
searchResults = await ComputeSearchResultsAsync(
project, searchDocument,
nameMatcher, containerMatcherOpt,
nameMatches, containerMatches, cancellationToken).ConfigureAwait(false);
}
// Would like to use CWT.AddOrUpdate. But that is not available on the
// version of .Net that we're using. So we need to take lock as we're
// making multiple mutations.
lock (s_lastProjectSearchCache)
{
s_lastProjectSearchCache.Remove(project);
s_lastProjectSearchCache.Add(project, Tuple.Create(pattern, searchResults));
}
return searchResults;
}
private static ImmutableArray<SearchResult> FilterPreviousResults(
ImmutableArray<SearchResult> previousResults,
PatternMatcher nameMatcher, PatternMatcher containerMatcherOpt,
ArrayBuilder<PatternMatch> nameMatches, ArrayBuilder<PatternMatch> containerMatches,
CancellationToken cancellationToken)
{
var result = ArrayBuilder<SearchResult>.GetInstance();
foreach (var previousResult in previousResults)
{
var document = previousResult.Document;
var info = previousResult.DeclaredSymbolInfo;
AddResultIfMatch(
document, info, nameMatcher, containerMatcherOpt,
nameMatches, containerMatches, result, cancellationToken);
}
return result.ToImmutableAndFree();
}
private static async Task<ImmutableArray<SearchResult>> ComputeSearchResultsAsync(
Project project, Document searchDocument,
PatternMatcher nameMatcher, PatternMatcher containerMatcherOpt,
ArrayBuilder<PatternMatch> nameMatches, ArrayBuilder<PatternMatch> containerMatches,
CancellationToken cancellationToken)
{
var result = ArrayBuilder<INavigateToSearchResult>.GetInstance();
var result = ArrayBuilder<SearchResult>.GetInstance();
foreach (var document in project.Documents)
{
if (searchDocument != null && document != searchDocument)
......@@ -79,23 +158,36 @@ internal abstract partial class AbstractNavigateToSearchService
foreach (var declaredSymbolInfo in declarationInfo.DeclaredSymbolInfos)
{
nameMatches.Clear();
containerMatches.Clear();
cancellationToken.ThrowIfCancellationRequested();
if (nameMatcher.AddMatches(declaredSymbolInfo.Name, nameMatches) &&
containerMatcherOpt?.AddMatches(declaredSymbolInfo.FullyQualifiedContainerName, containerMatches) != false)
{
result.Add(ConvertResult(
declaredSymbolInfo, document, nameMatches, containerMatches));
}
AddResultIfMatch(
document, declaredSymbolInfo,
nameMatcher, containerMatcherOpt,
nameMatches, containerMatches,
result, cancellationToken);
}
}
return result.ToImmutableAndFree();
}
private static INavigateToSearchResult ConvertResult(
private static void AddResultIfMatch(
Document document, DeclaredSymbolInfo declaredSymbolInfo,
PatternMatcher nameMatcher, PatternMatcher containerMatcherOpt,
ArrayBuilder<PatternMatch> nameMatches, ArrayBuilder<PatternMatch> containerMatches,
ArrayBuilder<SearchResult> result, CancellationToken cancellationToken)
{
nameMatches.Clear();
containerMatches.Clear();
cancellationToken.ThrowIfCancellationRequested();
if (nameMatcher.AddMatches(declaredSymbolInfo.Name, nameMatches) &&
containerMatcherOpt?.AddMatches(declaredSymbolInfo.FullyQualifiedContainerName, containerMatches) != false)
{
result.Add(ConvertResult(
declaredSymbolInfo, document, nameMatches, containerMatches));
}
}
private static SearchResult ConvertResult(
DeclaredSymbolInfo declaredSymbolInfo, Document document,
ArrayBuilder<PatternMatch> nameMatches, ArrayBuilder<PatternMatch> containerMatches)
{
......
......@@ -16,7 +16,7 @@ internal abstract partial class AbstractNavigateToSearchService
private class SearchResult : INavigateToSearchResult
{
public string AdditionalInformation => _lazyAdditionalInfo.Value;
public string Name => _declaredSymbolInfo.Name;
public string Name => DeclaredSymbolInfo.Name;
public string Summary { get; }
public string Kind { get; }
......@@ -26,8 +26,9 @@ private class SearchResult : INavigateToSearchResult
public bool IsCaseSensitive { get; }
public ImmutableArray<TextSpan> NameMatchSpans { get; }
private readonly Document _document;
private readonly DeclaredSymbolInfo _declaredSymbolInfo;
public readonly Document Document;
public readonly DeclaredSymbolInfo DeclaredSymbolInfo;
private readonly Lazy<string> _lazyAdditionalInfo;
public SearchResult(
......@@ -35,8 +36,8 @@ private class SearchResult : INavigateToSearchResult
NavigateToMatchKind matchKind, bool isCaseSensitive, INavigableItem navigableItem,
ImmutableArray<TextSpan> nameMatchSpans)
{
_document = document;
_declaredSymbolInfo = declaredSymbolInfo;
Document = document;
DeclaredSymbolInfo = declaredSymbolInfo;
Kind = kind;
MatchKind = matchKind;
IsCaseSensitive = isCaseSensitive;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册