提交 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. // 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.Collections.Immutable;
using System.Linq; using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.CodeAnalysis.FindSymbols; using Microsoft.CodeAnalysis.FindSymbols;
...@@ -15,21 +17,24 @@ namespace Microsoft.CodeAnalysis.NavigateTo ...@@ -15,21 +17,24 @@ namespace Microsoft.CodeAnalysis.NavigateTo
{ {
internal abstract partial class AbstractNavigateToSearchService 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( public static Task<ImmutableArray<INavigateToSearchResult>> SearchProjectInCurrentProcessAsync(
Project project, string searchPattern, CancellationToken cancellationToken) Project project, string searchPattern, CancellationToken cancellationToken)
{ {
return FindNavigableDeclaredSymbolInfosAsync( return FindSearchResultsAsync(
project, searchDocument: null, pattern: searchPattern, cancellationToken: cancellationToken); project, searchDocument: null, pattern: searchPattern, cancellationToken: cancellationToken);
} }
public static Task<ImmutableArray<INavigateToSearchResult>> SearchDocumentInCurrentProcessAsync( public static Task<ImmutableArray<INavigateToSearchResult>> SearchDocumentInCurrentProcessAsync(
Document document, string searchPattern, CancellationToken cancellationToken) Document document, string searchPattern, CancellationToken cancellationToken)
{ {
return FindNavigableDeclaredSymbolInfosAsync( return FindSearchResultsAsync(
document.Project, document, searchPattern, cancellationToken); 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) 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 // 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 ...@@ -48,9 +53,19 @@ internal abstract partial class AbstractNavigateToSearchService
try try
{ {
return await FindNavigableDeclaredSymbolInfosAsync( // If we're searching a single document, then just do a full search of
project, searchDocument, nameMatcher, containerMatcherOpt, // that document (we're fast enough to not need to optimize that case).
nameMatches, containerMatches, cancellationToken).ConfigureAwait(false); //
// 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 finally
{ {
...@@ -60,13 +75,77 @@ internal abstract partial class AbstractNavigateToSearchService ...@@ -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, Project project, Document searchDocument,
PatternMatcher nameMatcher, PatternMatcher containerMatcherOpt, PatternMatcher nameMatcher, PatternMatcher containerMatcherOpt,
ArrayBuilder<PatternMatch> nameMatches, ArrayBuilder<PatternMatch> containerMatches, ArrayBuilder<PatternMatch> nameMatches, ArrayBuilder<PatternMatch> containerMatches,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
var result = ArrayBuilder<INavigateToSearchResult>.GetInstance(); var result = ArrayBuilder<SearchResult>.GetInstance();
foreach (var document in project.Documents) foreach (var document in project.Documents)
{ {
if (searchDocument != null && document != searchDocument) if (searchDocument != null && document != searchDocument)
...@@ -79,23 +158,36 @@ internal abstract partial class AbstractNavigateToSearchService ...@@ -79,23 +158,36 @@ internal abstract partial class AbstractNavigateToSearchService
foreach (var declaredSymbolInfo in declarationInfo.DeclaredSymbolInfos) foreach (var declaredSymbolInfo in declarationInfo.DeclaredSymbolInfos)
{ {
nameMatches.Clear(); AddResultIfMatch(
containerMatches.Clear(); document, declaredSymbolInfo,
nameMatcher, containerMatcherOpt,
cancellationToken.ThrowIfCancellationRequested(); nameMatches, containerMatches,
if (nameMatcher.AddMatches(declaredSymbolInfo.Name, nameMatches) && result, cancellationToken);
containerMatcherOpt?.AddMatches(declaredSymbolInfo.FullyQualifiedContainerName, containerMatches) != false)
{
result.Add(ConvertResult(
declaredSymbolInfo, document, nameMatches, containerMatches));
}
} }
} }
return result.ToImmutableAndFree(); 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, DeclaredSymbolInfo declaredSymbolInfo, Document document,
ArrayBuilder<PatternMatch> nameMatches, ArrayBuilder<PatternMatch> containerMatches) ArrayBuilder<PatternMatch> nameMatches, ArrayBuilder<PatternMatch> containerMatches)
{ {
......
...@@ -16,7 +16,7 @@ internal abstract partial class AbstractNavigateToSearchService ...@@ -16,7 +16,7 @@ internal abstract partial class AbstractNavigateToSearchService
private class SearchResult : INavigateToSearchResult private class SearchResult : INavigateToSearchResult
{ {
public string AdditionalInformation => _lazyAdditionalInfo.Value; public string AdditionalInformation => _lazyAdditionalInfo.Value;
public string Name => _declaredSymbolInfo.Name; public string Name => DeclaredSymbolInfo.Name;
public string Summary { get; } public string Summary { get; }
public string Kind { get; } public string Kind { get; }
...@@ -26,8 +26,9 @@ private class SearchResult : INavigateToSearchResult ...@@ -26,8 +26,9 @@ private class SearchResult : INavigateToSearchResult
public bool IsCaseSensitive { get; } public bool IsCaseSensitive { get; }
public ImmutableArray<TextSpan> NameMatchSpans { get; } public ImmutableArray<TextSpan> NameMatchSpans { get; }
private readonly Document _document; public readonly Document Document;
private readonly DeclaredSymbolInfo _declaredSymbolInfo; public readonly DeclaredSymbolInfo DeclaredSymbolInfo;
private readonly Lazy<string> _lazyAdditionalInfo; private readonly Lazy<string> _lazyAdditionalInfo;
public SearchResult( public SearchResult(
...@@ -35,8 +36,8 @@ private class SearchResult : INavigateToSearchResult ...@@ -35,8 +36,8 @@ private class SearchResult : INavigateToSearchResult
NavigateToMatchKind matchKind, bool isCaseSensitive, INavigableItem navigableItem, NavigateToMatchKind matchKind, bool isCaseSensitive, INavigableItem navigableItem,
ImmutableArray<TextSpan> nameMatchSpans) ImmutableArray<TextSpan> nameMatchSpans)
{ {
_document = document; Document = document;
_declaredSymbolInfo = declaredSymbolInfo; DeclaredSymbolInfo = declaredSymbolInfo;
Kind = kind; Kind = kind;
MatchKind = matchKind; MatchKind = matchKind;
IsCaseSensitive = isCaseSensitive; IsCaseSensitive = isCaseSensitive;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册