提交 6f8c1126 编写于 作者: C Cyrus Najmabadi

Produce SymbolTreeInfo indices for projects and metadata in the background.

Now, Add-Using will use the indices if they have been built.  Or not search
those projects/metadata if they're not available.

Note: this doesn't change any of the searching of the current project.  That
works the same way it always has.  This is only for searching unreleated projects
and metadata dlls.
上级 cb3512f8
......@@ -407,7 +407,6 @@
<Compile Include="Implementation\Intellisense\Completion\FileSystem\CurrentWorkingDirectoryDiscoveryService.cs" />
<Compile Include="Implementation\Intellisense\Completion\FileSystem\FileSystemCompletionHelper.cs" />
<Compile Include="Implementation\Intellisense\Completion\FileSystem\ICurrentWorkingDirectoryDiscoveryService.cs" />
<Compile Include="Implementation\Intellisense\Completion\FileSystem\IOUtilities.cs" />
<Compile Include="Implementation\Intellisense\Completion\FileSystem\PathCompletionUtilities.cs" />
<Compile Include="Implementation\KeywordHighlighting\AbstractKeywordHighlighter.cs" />
<Compile Include="Implementation\KeywordHighlighting\HighlighterViewTaggerProvider.cs" />
......
......@@ -9,6 +9,7 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Completion;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Utilities;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
......
......@@ -143,6 +143,11 @@ public override ImmutableArray<string> FixableDiagnosticIds
}
}
protected override Compilation CreateCompilation(PortableExecutableReference reference)
{
return CSharpCompilation.Create("TempAssembly", references: SpecializedCollections.SingletonEnumerable(reference));
}
protected override bool CanAddImport(SyntaxNode node, CancellationToken cancellationToken)
{
if (cancellationToken.IsCancellationRequested)
......
// 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.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.FindSymbols;
using Microsoft.CodeAnalysis.FindSymbols.SymbolTree;
using Microsoft.CodeAnalysis.Host;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CodeFixes.AddImport
......@@ -61,28 +64,73 @@ public async Task<IEnumerable<SearchResult<ISymbol>>> FindDeclarationsAsync(stri
}
}
private class ProjectSearchScope : SearchScope
private abstract class ProjectSearchScope : SearchScope
{
private readonly bool _includeDirectReferences;
private readonly Project _project;
protected readonly Project _project;
public ProjectSearchScope(Project project, bool includeDirectReferences, bool ignoreCase, CancellationToken cancellationToken)
public ProjectSearchScope(Project project, bool ignoreCase, CancellationToken cancellationToken)
: base(ignoreCase, cancellationToken)
{
_project = project;
_includeDirectReferences = includeDirectReferences;
}
public override SymbolReference CreateReference<T>(SearchResult<T> searchResult)
{
return new ProjectSymbolReference(
searchResult.WithSymbol<INamespaceOrTypeSymbol>(searchResult.Symbol), _project.Id);
}
}
private class ProjectAndDirectReferencesSearchScope : ProjectSearchScope
{
public ProjectAndDirectReferencesSearchScope(Project project, bool ignoreCase, CancellationToken cancellationToken)
: base(project, ignoreCase, cancellationToken)
{
}
protected override Task<IEnumerable<ISymbol>> FindDeclarationsAsync(string name, SymbolFilter filter, SearchQuery searchQuery)
{
return SymbolFinder.FindDeclarationsAsync(
_project, searchQuery, filter, _includeDirectReferences, cancellationToken);
return SymbolFinder.FindDeclarationsAsync(_project, searchQuery, filter, cancellationToken);
}
}
public override SymbolReference CreateReference<T>(SearchResult<T> searchResult)
private class ProjectSourceOnlySearchScope : ProjectSearchScope
{
private readonly ConcurrentDictionary<Project, AsyncLazy<IAssemblySymbol>> _projectToAssembly;
public ProjectSourceOnlySearchScope(
ConcurrentDictionary<Project, AsyncLazy<IAssemblySymbol>> projectToAssembly,
Project project, bool ignoreCase, CancellationToken cancellationToken)
: base(project, ignoreCase, cancellationToken)
{
return new ProjectSymbolReference(
searchResult.WithSymbol<INamespaceOrTypeSymbol>(searchResult.Symbol), _project.Id);
_projectToAssembly = projectToAssembly;
}
protected override async Task<IEnumerable<ISymbol>> FindDeclarationsAsync(string name, SymbolFilter filter, SearchQuery searchQuery)
{
var service = _project.Solution.Workspace.Services.GetService<ISymbolTreeInfoCacheService>();
var result = await service.TryGetSymbolTreeInfoAsync(_project, cancellationToken).ConfigureAwait(false);
if (!result.Item1)
{
return SpecializedCollections.EmptyEnumerable<ISymbol>();
}
// Don't create the assembly until it is actually needed by the SymbolTreeInfo.FindAsync
// code. Creating the assembly can be costly and we want to avoid it until it is actually
// needed.
var lazyAssembly = _projectToAssembly.GetOrAdd(_project, CreateLazyAssembly);
return await result.Item2.FindAsync(searchQuery, lazyAssembly, cancellationToken).ConfigureAwait(false);
}
private static AsyncLazy<IAssemblySymbol> CreateLazyAssembly(Project project)
{
return new AsyncLazy<IAssemblySymbol>(
async c =>
{
var compilation = await project.GetCompilationAsync(c).ConfigureAwait(false);
return compilation.Assembly;
}, cacheResult: true);
}
}
......@@ -112,11 +160,17 @@ public override SymbolReference CreateReference<T>(SearchResult<T> searchResult)
_metadataReference);
}
protected override Task<IEnumerable<ISymbol>> FindDeclarationsAsync(string name, SymbolFilter filter, SearchQuery searchQuery)
protected override async Task<IEnumerable<ISymbol>> FindDeclarationsAsync(string name, SymbolFilter filter, SearchQuery searchQuery)
{
return SymbolFinder.FindDeclarationsAsync(
_solution, _assembly, _metadataReference, searchQuery, filter, cancellationToken);
var service = _solution.Workspace.Services.GetService<ISymbolTreeInfoCacheService>();
var result = await service.TryGetSymbolTreeInfoAsync(_metadataReference, cancellationToken).ConfigureAwait(false);
if (!result.Item1)
{
return SpecializedCollections.EmptyEnumerable<ISymbol>();
}
return await result.Item2.FindAsync(searchQuery, _assembly, cancellationToken).ConfigureAwait(false);
}
}
}
}
}
\ No newline at end of file
// 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.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeGeneration;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.LanguageServices;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Options;
using Roslyn.Utilities;
using static Roslyn.Utilities.PortableShim;
namespace Microsoft.CodeAnalysis.CodeFixes.AddImport
{
......@@ -38,6 +36,8 @@ internal abstract partial class AbstractAddImportCodeFixProvider<TSimpleNameSynt
internal abstract bool IsViableProperty(IPropertySymbol property, SyntaxNode expression, SemanticModel semanticModel, ISyntaxFactsService syntaxFacts, CancellationToken cancellationToken);
internal abstract bool IsAddMethodContext(SyntaxNode node, SemanticModel semanticModel);
protected abstract Compilation CreateCompilation(PortableExecutableReference reference);
public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
var document = context.Document;
......@@ -75,12 +75,17 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
var finder = new SymbolReferenceFinder(this, document, semanticModel, diagnostic, node, cancellationToken);
// Caches so we don't produce the same data multiple times while searching
// all over the solution.
var projectToAssembly = new ConcurrentDictionary<Project, AsyncLazy<IAssemblySymbol>>();
var referenceToCompilation = new ConcurrentDictionary<PortableExecutableReference, Compilation>();
// Look for exact matches first:
await FindResults(project, allSymbolReferences, finder, exact: true, cancellationToken: cancellationToken).ConfigureAwait(false);
await FindResults(projectToAssembly, referenceToCompilation, project, allSymbolReferences, finder, exact: true, cancellationToken: cancellationToken).ConfigureAwait(false);
if (allSymbolReferences.Count == 0)
{
// No exact matches found. Fall back to fuzzy searching.
await FindResults(project, allSymbolReferences, finder, exact: false, cancellationToken: cancellationToken).ConfigureAwait(false);
await FindResults(projectToAssembly, referenceToCompilation, project, allSymbolReferences, finder, exact: false, cancellationToken: cancellationToken).ConfigureAwait(false);
}
// Nothing found at all. No need to proceed.
......@@ -106,17 +111,21 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
}
}
private async Task FindResults(Project project, List<SymbolReference> allSymbolReferences, SymbolReferenceFinder finder, bool exact, CancellationToken cancellationToken)
private async Task FindResults(
ConcurrentDictionary<Project, AsyncLazy<IAssemblySymbol>> projectToAssembly,
ConcurrentDictionary<PortableExecutableReference, Compilation> referenceToCompilation,
Project project, List<SymbolReference> allSymbolReferences, SymbolReferenceFinder finder, bool exact, CancellationToken cancellationToken)
{
await FindResultsInCurrentProject(project, allSymbolReferences, finder, exact).ConfigureAwait(false);
await FindResultsInUnreferencedProjects(project, allSymbolReferences, finder, exact, cancellationToken).ConfigureAwait(false);
await FindResultsInUnreferencedMetadataReferences(project, allSymbolReferences, finder, exact, cancellationToken).ConfigureAwait(false);
await FindResultsInUnreferencedProjects(projectToAssembly, project, allSymbolReferences, finder, exact, cancellationToken).ConfigureAwait(false);
await FindResultsInUnreferencedMetadataReferences(referenceToCompilation, project, allSymbolReferences, finder, exact, cancellationToken).ConfigureAwait(false);
}
private async Task FindResultsInCurrentProject(
Project project, List<SymbolReference> allSymbolReferences, SymbolReferenceFinder finder, bool exact)
{
AddRange(allSymbolReferences, await finder.FindInProjectAsync(project, includeDirectReferences: true, exact: exact).ConfigureAwait(false));
var references = await finder.FindInProjectAndDirectReferencesAsync(project, exact).ConfigureAwait(false);
AddRange(allSymbolReferences, references);
}
private async Task<Solution> AddImportAndReferenceAsync(
......@@ -160,6 +169,7 @@ private async Task FindResults(Project project, List<SymbolReference> allSymbolR
}
private async Task FindResultsInUnreferencedProjects(
ConcurrentDictionary<Project, AsyncLazy<IAssemblySymbol>> projectToAssembly,
Project project, List<SymbolReference> allSymbolReferences, SymbolReferenceFinder finder, bool exact, CancellationToken cancellationToken)
{
// If we didn't find enough hits searching just in the project, then check
......@@ -176,7 +186,7 @@ private async Task FindResults(Project project, List<SymbolReference> allSymbolR
// direct references. i.e. we don't want to search in its metadata references
// or in the projects it references itself. We'll be searching those entities
// individually.
AddRange(allSymbolReferences, await finder.FindInProjectAsync(unreferencedProject, includeDirectReferences: false, exact: exact).ConfigureAwait(false));
AddRange(allSymbolReferences, await finder.FindInProjectSourceOnlyAsync(projectToAssembly, unreferencedProject, exact: exact).ConfigureAwait(false));
if (allSymbolReferences.Count >= MaxResults)
{
return;
......@@ -185,7 +195,9 @@ private async Task FindResults(Project project, List<SymbolReference> allSymbolR
}
private async Task FindResultsInUnreferencedMetadataReferences(
Project project, List<SymbolReference> allSymbolReferences, SymbolReferenceFinder finder, bool exact, CancellationToken cancellationToken)
ConcurrentDictionary<PortableExecutableReference, Compilation> referenceToCompilation,
Project project, List<SymbolReference> allSymbolReferences, SymbolReferenceFinder finder, bool exact,
CancellationToken cancellationToken)
{
if (allSymbolReferences.Count > 0)
{
......@@ -201,54 +213,44 @@ private async Task FindResults(Project project, List<SymbolReference> allSymbolR
var seenReferences = new HashSet<PortableExecutableReference>(comparer: this);
seenReferences.AddAll(project.MetadataReferences.OfType<PortableExecutableReference>());
// Check all the other projects in the system so see if they have a metadata reference
// with a potential result.
foreach (var otherProject in project.Solution.Projects)
var newReferences =
project.Solution.Projects.Where(p => p != project)
.SelectMany(p => p.MetadataReferences.OfType<PortableExecutableReference>())
.Distinct(comparer: this)
.Where(r => !seenReferences.Contains(r));
// Search all metadata references in parallel.
var findTasks = new HashSet<Task<List<SymbolReference>>>();
foreach (var reference in newReferences)
{
if (otherProject == project)
{
continue;
}
var compilation = referenceToCompilation.GetOrAdd(reference, CreateCompilation);
await FindResultsInMetadataReferences(
otherProject, allSymbolReferences, finder, seenReferences, exact, cancellationToken).ConfigureAwait(false);
if (allSymbolReferences.Count >= MaxResults)
// Ignore netmodules. First, they're incredibly esoteric and barely used.
// Second, the SymbolFinder api doesn't even support searching them.
var assembly = compilation.GetAssemblyOrModuleSymbol(reference) as IAssemblySymbol;
if (assembly != null)
{
break;
findTasks.Add(finder.FindInMetadataAsync(project.Solution, assembly, reference, exact));
}
}
}
private async Task FindResultsInMetadataReferences(
Project otherProject,
List<SymbolReference> allSymbolReferences,
SymbolReferenceFinder finder,
HashSet<PortableExecutableReference> seenReferences,
bool exact,
CancellationToken cancellationToken)
{
// See if this project has a metadata reference we haven't already looked at.
var newMetadataReferences = otherProject.MetadataReferences.OfType<PortableExecutableReference>();
Compilation compilation = null;
foreach (var reference in newMetadataReferences)
while (findTasks.Count > 0)
{
// Make sure we don't check the same metadata reference multiple times from
// different projects.
if (seenReferences.Add(reference))
{
// Defer making the compilation until necessary.
compilation = compilation ?? await otherProject.GetCompilationAsync(cancellationToken).ConfigureAwait(false);
// Keep on looping through the 'find' tasks, processing each when they finish.
cancellationToken.ThrowIfCancellationRequested();
var doneTask = await Task.WhenAny(findTasks).ConfigureAwait(false);
// Ignore netmodules. First, they're incredibly esoteric and barely used.
// Second, the SymbolFinder api doesn't even support searching them.
var assembly = compilation.GetAssemblyOrModuleSymbol(reference) as IAssemblySymbol;
if (assembly != null)
{
AddRange(allSymbolReferences, await finder.FindInMetadataAsync(otherProject.Solution, assembly, reference, exact).ConfigureAwait(false));
}
}
// One of the tasks finished. Remove it from the list we're waiting on.
findTasks.Remove(doneTask);
// Add its results to the final result set we're keeping.
AddRange(allSymbolReferences, await doneTask.ConfigureAwait(false));
// If we've got enough, no need to keep searching.
// Note: Should we cancel the existing work? IMO, no. These tasks will
// cause our indices to be created if necessary. And that's good for future searches.
// If the indices are already created, then searching them should be quick.
if (allSymbolReferences.Count >= MaxResults)
{
break;
......@@ -378,9 +380,18 @@ private class SymbolReferenceFinder
_syntaxFacts = document.Project.LanguageServices.GetService<ISyntaxFactsService>();
}
internal Task<List<SymbolReference>> FindInProjectAsync(Project project, bool includeDirectReferences, bool exact)
internal Task<List<SymbolReference>> FindInProjectAndDirectReferencesAsync(
Project project, bool exact)
{
var searchScope = new ProjectAndDirectReferencesSearchScope(project, exact, _cancellationToken);
return DoAsync(searchScope);
}
internal Task<List<SymbolReference>> FindInProjectSourceOnlyAsync(
ConcurrentDictionary<Project, AsyncLazy<IAssemblySymbol>> projectToAssembly,
Project project, bool exact)
{
var searchScope = new ProjectSearchScope(project, includeDirectReferences, exact, _cancellationToken);
var searchScope = new ProjectSourceOnlySearchScope(projectToAssembly, project, exact, _cancellationToken);
return DoAsync(searchScope);
}
......
......@@ -255,6 +255,7 @@
<Compile Include="Shared\Options\ServiceComponentOnOffOptionsProvider.cs" />
<Compile Include="Shared\Options\ServiceFeatureOnOffOptions.cs" />
<Compile Include="Shared\Options\ServiceFeatureOnOffOptionsProvider.cs" />
<Compile Include="Shared\Utilities\IOUtilities.cs" />
<Compile Include="Shared\Utilities\LinkedFilesSymbolEquivalenceComparer.cs" />
<Compile Include="Shared\Utilities\SupportedPlatformData.cs" />
<Compile Include="Completion\Providers\SymbolCompletionItem.cs" />
......
// 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.Concurrent;
using System.Collections.Generic;
using System.Composition;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.FindSymbols;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.FindSymbols.SymbolTree;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Shared.Utilities;
using Microsoft.CodeAnalysis.SolutionCrawler;
using Roslyn.Utilities;
using static Roslyn.Utilities.PortableShim;
namespace Microsoft.CodeAnalysis.IncrementalCaches
{
[ExportIncrementalAnalyzerProvider(WorkspaceKind.Host), Shared]
internal class SymbolTreeInfoIncrementalAnalyzerProvider : IIncrementalAnalyzerProvider
[Shared]
[ExportIncrementalAnalyzerProvider(WorkspaceKind.Host)]
[ExportWorkspaceServiceFactory(typeof(ISymbolTreeInfoCacheService))]
internal class SymbolTreeInfoIncrementalAnalyzerProvider : IIncrementalAnalyzerProvider, IWorkspaceServiceFactory
{
// Concurrent dictionaries so they can be read from the SymbolTreeInfoCacheService while
// they are being populated/updated by the IncrementalAnalyzer.
private readonly ConcurrentDictionary<ProjectId, Tuple<VersionStamp, SymbolTreeInfo>> _projectToInfo =
new ConcurrentDictionary<ProjectId, Tuple<VersionStamp, SymbolTreeInfo>>();
private readonly ConcurrentDictionary<string, Tuple<DateTime, SymbolTreeInfo, HashSet<ProjectId>>> _metadataPathToInfo =
new ConcurrentDictionary<string, Tuple<DateTime, SymbolTreeInfo, HashSet<ProjectId>>>();
public IIncrementalAnalyzer CreateIncrementalAnalyzer(Workspace workspace)
{
return new IncrementalAnalyzer();
return new IncrementalAnalyzer(_projectToInfo, _metadataPathToInfo);
}
private class IncrementalAnalyzer : IncrementalAnalyzerBase
public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices)
{
private SolutionId _solutionId;
private Dictionary<ProjectId, int> _symbolCountByProjectMap = new Dictionary<ProjectId, int>();
return new SymbolTreeInfoCacheService(_projectToInfo, _metadataPathToInfo);
}
public override async Task AnalyzeProjectAsync(Project project, bool semanticsChanged, CancellationToken cancellationToken)
private static string GetReferenceKey(PortableExecutableReference reference)
{
return reference.FilePath ?? reference.Display;
}
private static ValueTuple<bool, DateTime> GetLastWriteTime(string path)
{
return IOUtilities.PerformIO(
() => ValueTuple.Create(true, File.GetLastWriteTimeUtc(path)),
ValueTuple.Create(false, default(DateTime)));
}
private class SymbolTreeInfoCacheService : ISymbolTreeInfoCacheService
{
private readonly ConcurrentDictionary<ProjectId, Tuple<VersionStamp, SymbolTreeInfo>> _projectToInfo;
private readonly ConcurrentDictionary<string, Tuple<DateTime, SymbolTreeInfo, HashSet<ProjectId>>> _metadataPathToInfo;
public SymbolTreeInfoCacheService(
ConcurrentDictionary<ProjectId, Tuple<VersionStamp, SymbolTreeInfo>> projectToInfo,
ConcurrentDictionary<string, Tuple<DateTime, SymbolTreeInfo, HashSet<ProjectId>>> metadataPathToInfo)
{
_projectToInfo = projectToInfo;
_metadataPathToInfo = metadataPathToInfo;
}
public Task<ValueTuple<bool, SymbolTreeInfo>> TryGetSymbolTreeInfoAsync(PortableExecutableReference reference, CancellationToken cancellationToken)
{
if (_symbolCountByProjectMap == null || !project.SupportsCompilation || !semanticsChanged)
var key = GetReferenceKey(reference);
if (key != null)
{
return;
Tuple<DateTime, SymbolTreeInfo, HashSet<ProjectId>> tuple;
if (_metadataPathToInfo.TryGetValue(key, out tuple))
{
var version = GetLastWriteTime(key);
if (version.Item1 && version.Item2 == tuple.Item1)
{
return Task.FromResult(ValueTuple.Create(true, tuple.Item2));
}
}
}
// we do this just to report total symbol numbers
var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false);
return Task.FromResult(default(ValueTuple<bool, SymbolTreeInfo>));
}
var info = SymbolTreeInfo.Create(VersionStamp.Default, compilation.Assembly, cancellationToken);
if (info != null)
public async Task<ValueTuple<bool, SymbolTreeInfo>> TryGetSymbolTreeInfoAsync(Project project, CancellationToken cancellationToken)
{
Tuple<VersionStamp, SymbolTreeInfo> tuple;
if (_projectToInfo.TryGetValue(project.Id, out tuple))
{
RecordCount(project.Id, info.Count);
var version = await project.GetSemanticVersionAsync(cancellationToken).ConfigureAwait(false);
if (version == tuple.Item1)
{
return ValueTuple.Create(true, tuple.Item2);
}
}
return default(ValueTuple<bool, SymbolTreeInfo>);
}
}
private class IncrementalAnalyzer : IncrementalAnalyzerBase
{
private readonly ConcurrentDictionary<ProjectId, Tuple<VersionStamp, SymbolTreeInfo>> _projectToInfo;
// Note: the Incremental-Analyzer infrastructure guarantees that it will call all the methods
// on this type in a serial fashion. As such, we don't need explicit locking, or threadsafe
// collections (if they're only used by this type). So, for example, the map we populate
// needs to be a ConcurrentDictionary as it will be read and written from multiple types.
// However, the HashSet<ProjectId> is ok as it will only be used by this type and there is
// no concurrency in this type on its own.
private readonly ConcurrentDictionary<string, Tuple<DateTime, SymbolTreeInfo, HashSet<ProjectId>>> _metadataPathToInfo;
public IncrementalAnalyzer(
ConcurrentDictionary<ProjectId, Tuple<VersionStamp, SymbolTreeInfo>> projectToInfo,
ConcurrentDictionary<string, Tuple<DateTime, SymbolTreeInfo, HashSet<ProjectId>>> metadataPathToInfo)
{
_projectToInfo = projectToInfo;
_metadataPathToInfo = metadataPathToInfo;
}
public override Task NewSolutionSnapshotAsync(Solution solution, CancellationToken cancellationToken)
public override async Task AnalyzeProjectAsync(Project project, bool semanticsChanged, CancellationToken cancellationToken)
{
// check whether we are good to report total symbol numbers
if (_symbolCountByProjectMap == null || _symbolCountByProjectMap.Count < solution.ProjectIds.Count || string.IsNullOrEmpty(solution.FilePath))
if (!project.SupportsCompilation || project.FilePath == null)
{
return SpecializedTasks.EmptyTask;
return;
}
if (_solutionId != null && _solutionId != solution.Id)
await UpdateReferencesAync(project, cancellationToken).ConfigureAwait(false);
var version = await project.GetSemanticVersionAsync(cancellationToken).ConfigureAwait(false);
Tuple<VersionStamp, SymbolTreeInfo> tuple;
if (_projectToInfo.TryGetValue(project.Id, out tuple) && tuple.Item1 == version)
{
ReportCount();
return SpecializedTasks.EmptyTask;
return;
}
_solutionId = solution.Id;
foreach (var projectId in solution.ProjectIds)
var info = await SymbolTreeInfo.GetInfoForSourceAssemblyAsync(project, cancellationToken).ConfigureAwait(false);
tuple = Tuple.Create(version, info);
_projectToInfo.AddOrUpdate(project.Id, tuple, (_1, _2) => tuple);
}
private async Task UpdateReferencesAync(Project project, CancellationToken cancellationToken)
{
Compilation compilation = null;
foreach (var reference in project.MetadataReferences.OfType<PortableExecutableReference>())
{
if (!_symbolCountByProjectMap.ContainsKey(projectId))
{
return SpecializedTasks.EmptyTask;
}
compilation = await UpdateReferenceAsync(project, reference, compilation, cancellationToken).ConfigureAwait(false);
}
ReportCount();
return SpecializedTasks.EmptyTask;
}
public override void RemoveProject(ProjectId projectId)
private async Task<Compilation> UpdateReferenceAsync(
Project project, PortableExecutableReference reference, Compilation compilation, CancellationToken cancellationToken)
{
if (_symbolCountByProjectMap != null)
var key = GetReferenceKey(reference);
if (key != null)
{
_symbolCountByProjectMap.Remove(projectId);
var lastWriteTime = GetLastWriteTime(key);
if (!lastWriteTime.Item1)
{
// Couldn't get the write time. Just ignore this reference.
return compilation;
}
Tuple<DateTime, SymbolTreeInfo, HashSet<ProjectId>> tuple;
if (_metadataPathToInfo.TryGetValue(key, out tuple) && tuple.Item1 == lastWriteTime.Item2)
{
// We've already computed and cached the info for this reference.
return compilation;
}
compilation = compilation ?? await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false);
var assembly = compilation.GetAssemblyOrModuleSymbol(reference) as IAssemblySymbol;
if (assembly != null)
{
var info = await SymbolTreeInfo.TryGetInfoForMetadataAssemblyAsync(project.Solution, assembly, reference, cancellationToken).ConfigureAwait(false);
tuple = tuple ?? Tuple.Create(lastWriteTime.Item2, info, new HashSet<ProjectId>());
// Keep track that this dll is referenced by this project.
tuple.Item3.Add(project.Id);
_metadataPathToInfo.AddOrUpdate(key, tuple, (_1, _2) => tuple);
}
}
return compilation;
}
private void ReportCount()
public override void RemoveProject(ProjectId projectId)
{
var sourceSymbolCount = _symbolCountByProjectMap.Sum(kv => kv.Value);
Logger.Log(FunctionId.Run_Environment, KeyValueLogMessage.Create(m => m["SourceSymbolCount"] = sourceSymbolCount));
Tuple<VersionStamp, SymbolTreeInfo> tuple;
_projectToInfo.TryRemove(projectId, out tuple);
// we only report it once
_symbolCountByProjectMap = null;
_solutionId = null;
RemoveMetadataReferences(projectId);
}
private void RecordCount(ProjectId id, int count)
private void RemoveMetadataReferences(ProjectId projectId)
{
if (_symbolCountByProjectMap == null)
foreach (var kvp in _metadataPathToInfo.ToArray())
{
return;
var tuple = kvp.Value;
if (kvp.Value.Item3.Remove(projectId))
{
if (kvp.Value.Item3.Count == 0)
{
// This metadata dll isn't referenced by any project. We can just dump it.
Tuple<DateTime, SymbolTreeInfo, HashSet<ProjectId>> unneeded;
_metadataPathToInfo.TryRemove(kvp.Key, out unneeded);
}
}
}
_symbolCountByProjectMap[id] = count;
}
}
}
......
......@@ -4,7 +4,7 @@
using System.IO;
using System.Security;
namespace Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.Completion.FileSystem
namespace Microsoft.CodeAnalysis.Shared.Utilities
{
internal static class IOUtilities
{
......
......@@ -97,6 +97,10 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeFixes.AddImport
End Get
End Property
Protected Overrides Function CreateCompilation(reference As PortableExecutableReference) As Compilation
Return VisualBasicCompilation.Create("TempAssembly", references:=SpecializedCollections.SingletonEnumerable(reference))
End Function
Protected Overrides Function CanAddImport(node As SyntaxNode, cancellationToken As CancellationToken) As Boolean
If node.GetAncestor(Of ImportsStatementSyntax)() IsNot Nothing Then
Return False
......
......@@ -7,6 +7,7 @@
using Microsoft.CodeAnalysis.Completion;
using Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.Completion.FileSystem;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Utilities;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
......
......@@ -126,13 +126,13 @@ public static Task<IEnumerable<ISymbol>> FindDeclarationsAsync(Project project,
return SpecializedTasks.EmptyEnumerable<ISymbol>();
}
return FindDeclarationsAsync(project, SearchQuery.Create(name, ignoreCase), includeDirectReferences: true, cancellationToken: cancellationToken);
return FindDeclarationsAsync(project, SearchQuery.Create(name, ignoreCase), cancellationToken: cancellationToken);
}
internal static Task<IEnumerable<ISymbol>> FindDeclarationsAsync(
Project project, SearchQuery query, bool includeDirectReferences, CancellationToken cancellationToken)
Project project, SearchQuery query, CancellationToken cancellationToken)
{
return FindDeclarationsAsync(project, query, SymbolFilter.All, includeDirectReferences, cancellationToken);
return FindDeclarationsAsync(project, query, SymbolFilter.All, cancellationToken);
}
/// <summary>
......@@ -151,11 +151,11 @@ public static Task<IEnumerable<ISymbol>> FindDeclarationsAsync(Project project,
return SpecializedTasks.EmptyEnumerable<ISymbol>();
}
return FindDeclarationsAsync(project, SearchQuery.Create(name, ignoreCase), filter, includeDirectReferences: true, cancellationToken: cancellationToken);
return FindDeclarationsAsync(project, SearchQuery.Create(name, ignoreCase), filter, cancellationToken: cancellationToken);
}
internal static Task<IEnumerable<ISymbol>> FindDeclarationsAsync(
Project project, SearchQuery query, SymbolFilter filter, bool includeDirectReferences, CancellationToken cancellationToken)
Project project, SearchQuery query, SymbolFilter filter, CancellationToken cancellationToken)
{
if (project == null)
{
......@@ -169,12 +169,12 @@ public static Task<IEnumerable<ISymbol>> FindDeclarationsAsync(Project project,
using (Logger.LogBlock(FunctionId.SymbolFinder_FindDeclarationsAsync, cancellationToken))
{
return FindDeclarationsAsyncImpl(project, query, filter, includeDirectReferences, cancellationToken);
return FindDeclarationsAsyncImpl(project, query, filter, cancellationToken);
}
}
private static async Task<IEnumerable<ISymbol>> FindDeclarationsAsyncImpl(
Project project, SearchQuery query, SymbolFilter criteria, bool includeDirectReferences, CancellationToken cancellationToken)
Project project, SearchQuery query, SymbolFilter criteria, CancellationToken cancellationToken)
{
var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false);
......@@ -184,21 +184,18 @@ public static Task<IEnumerable<ISymbol>> FindDeclarationsAsync(Project project,
await AddDeclarationsAsync(project, query, criteria, list, cancellationToken).ConfigureAwait(false);
// get declarations from directly referenced projects and metadata
if (includeDirectReferences)
foreach (var assembly in compilation.GetReferencedAssemblySymbols())
{
foreach (var assembly in compilation.GetReferencedAssemblySymbols())
var assemblyProject = project.Solution.GetProject(assembly, cancellationToken);
if (assemblyProject != null)
{
var assemblyProject = project.Solution.GetProject(assembly, cancellationToken);
if (assemblyProject != null)
{
await AddDeclarationsAsync(assemblyProject, query, criteria, list, compilation, assembly, cancellationToken).ConfigureAwait(false);
}
else
{
await AddDeclarationsAsync(
project.Solution, assembly, compilation.GetMetadataReference(assembly) as PortableExecutableReference,
query, criteria, list, cancellationToken).ConfigureAwait(false);
}
await AddDeclarationsAsync(assemblyProject, query, criteria, list, compilation, assembly, cancellationToken).ConfigureAwait(false);
}
else
{
await AddDeclarationsAsync(
project.Solution, assembly, compilation.GetMetadataReference(assembly) as PortableExecutableReference,
query, criteria, list, cancellationToken).ConfigureAwait(false);
}
}
......@@ -291,35 +288,16 @@ private static IEnumerable<ISymbol> TranslateNamespaces(List<ISymbol> symbols, C
{
if (referenceOpt != null)
{
var info = await SymbolTreeInfo.TryGetInfoForAssemblyAsync(solution, assembly, referenceOpt, cancellationToken).ConfigureAwait(false);
var info = await SymbolTreeInfo.TryGetInfoForMetadataAssemblyAsync(solution, assembly, referenceOpt, cancellationToken).ConfigureAwait(false);
if (info != null)
{
list.AddRange(FilterByCriteria(Find(query, info, assembly, cancellationToken), filter));
var symbols = await info.FindAsync(query, assembly, cancellationToken).ConfigureAwait(false);
list.AddRange(FilterByCriteria(symbols, filter));
}
}
}
}
private static IEnumerable<ISymbol> Find(SearchQuery query, SymbolTreeInfo info, IAssemblySymbol assembly, CancellationToken cancellationToken)
{
// If the query has a specific string provided, then call into the SymbolTreeInfo
// helpers optimized for lookup based on an exact name.
switch (query.Kind)
{
case SearchKind.Exact:
return info.Find(assembly, query.Name, ignoreCase: false, cancellationToken: cancellationToken);
case SearchKind.ExactIgnoreCase:
return info.Find(assembly, query.Name, ignoreCase: true, cancellationToken: cancellationToken);
case SearchKind.Fuzzy:
return info.FuzzyFind(assembly, query.Name, cancellationToken);
case SearchKind.Custom:
// Otherwise, we'll have to do a slow linear search over all possible symbols.
return info.Find(assembly, query.GetPredicate(), cancellationToken);
}
throw new InvalidOperationException();
}
/// <summary>
/// Find the symbols for declarations made in source with the specified name.
/// </summary>
......@@ -500,7 +478,7 @@ internal static async Task<IEnumerable<ISymbol>> FindSourceDeclarationsAsync(Pro
}
}
private static IEnumerable<ISymbol> FilterByCriteria(IEnumerable<ISymbol> symbols, SymbolFilter criteria)
internal static IEnumerable<ISymbol> FilterByCriteria(IEnumerable<ISymbol> symbols, SymbolFilter criteria)
{
foreach (var symbol in symbols)
{
......
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Host;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.FindSymbols.SymbolTree
{
internal interface ISymbolTreeInfoCacheService : IWorkspaceService
{
Task<ValueTuple<bool, SymbolTreeInfo>> TryGetSymbolTreeInfoAsync(Project project, CancellationToken cancellationToken);
Task<ValueTuple<bool, SymbolTreeInfo>> TryGetSymbolTreeInfoAsync(PortableExecutableReference reference, CancellationToken cancellationToken);
}
}
\ No newline at end of file
......@@ -54,55 +54,92 @@ private SymbolTreeInfo(VersionStamp version, IReadOnlyList<Node> orderedNodes, S
public int Count => _nodes.Count;
public Task<IEnumerable<ISymbol>> FindAsync(SearchQuery query, IAssemblySymbol assembly, CancellationToken cancellationToken)
{
return FindAsync(query, new AsyncLazy<IAssemblySymbol>(assembly), cancellationToken);
}
public Task<IEnumerable<ISymbol>> FindAsync(SearchQuery query, AsyncLazy<IAssemblySymbol> lazyAssembly, CancellationToken cancellationToken)
{
// If the query has a specific string provided, then call into the SymbolTreeInfo
// helpers optimized for lookup based on an exact name.
switch (query.Kind)
{
case SearchKind.Exact:
return this.FindAsync(lazyAssembly, query.Name, ignoreCase: false, cancellationToken: cancellationToken);
case SearchKind.ExactIgnoreCase:
return this.FindAsync(lazyAssembly, query.Name, ignoreCase: true, cancellationToken: cancellationToken);
case SearchKind.Fuzzy:
return this.FuzzyFindAsync(lazyAssembly, query.Name, cancellationToken);
case SearchKind.Custom:
// Otherwise, we'll have to do a slow linear search over all possible symbols.
return this.FindAsync(lazyAssembly, query.GetPredicate(), cancellationToken);
}
throw new InvalidOperationException();
}
/// <summary>
/// Finds symbols in this assembly that match the provided name in a fuzzy manner.
/// </summary>
public IEnumerable<ISymbol> FuzzyFind(IAssemblySymbol assembly, string name, CancellationToken cancellationToken)
public async Task<IEnumerable<ISymbol>> FuzzyFindAsync(AsyncLazy<IAssemblySymbol> lazyAssembly, string name, CancellationToken cancellationToken)
{
var similarNames = _spellChecker.FindSimilarWords(name);
return similarNames.SelectMany(n => Find(assembly, n, ignoreCase: true, cancellationToken: cancellationToken));
var result = new List<ISymbol>();
foreach (var similarName in similarNames)
{
var symbols = await FindAsync(lazyAssembly, similarName, ignoreCase: true, cancellationToken: cancellationToken).ConfigureAwait(false);
result.AddRange(symbols);
}
return result;
}
/// <summary>
/// Get all symbols that have a name matching the specified name.
/// </summary>
public IEnumerable<ISymbol> Find(
IAssemblySymbol assembly,
public async Task<IEnumerable<ISymbol>> FindAsync(
AsyncLazy<IAssemblySymbol> lazyAssembly,
string name,
bool ignoreCase,
CancellationToken cancellationToken)
{
var comparer = GetComparer(ignoreCase);
var result = new List<ISymbol>();
IAssemblySymbol assemblySymbol = null;
foreach (var node in FindNodes(name, comparer))
{
cancellationToken.ThrowIfCancellationRequested();
foreach (var symbol in Bind(node, assembly.GlobalNamespace, cancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
yield return symbol;
}
assemblySymbol = assemblySymbol ?? await lazyAssembly.GetValueAsync(cancellationToken).ConfigureAwait(false);
result.AddRange(Bind(node, assemblySymbol.GlobalNamespace, cancellationToken));
}
return result;
}
/// <summary>
/// Slow, linear scan of all the symbols in this assembly to look for matches.
/// </summary>
public IEnumerable<ISymbol> Find(IAssemblySymbol assembly, Func<string, bool> predicate, CancellationToken cancellationToken)
public async Task<IEnumerable<ISymbol>> FindAsync(AsyncLazy<IAssemblySymbol> lazyAssembly, Func<string, bool> predicate, CancellationToken cancellationToken)
{
var result = new List<ISymbol>();
IAssemblySymbol assembly = null;
for (int i = 0, n = _nodes.Count; i < n; i++)
{
cancellationToken.ThrowIfCancellationRequested();
var node = _nodes[i];
if (predicate(node.Name))
{
foreach (var symbol in Bind(i, assembly.GlobalNamespace, cancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
yield return symbol;
}
assembly = assembly ?? await lazyAssembly.GetValueAsync(cancellationToken).ConfigureAwait(false);
result.AddRange(Bind(i, assembly.GlobalNamespace, cancellationToken));
}
}
return result;
}
private static StringComparer GetComparer(bool ignoreCase)
......@@ -199,7 +236,7 @@ private int BinarySearch(string name)
/// <summary>
/// this gives you SymbolTreeInfo for a metadata
/// </summary>
public static async Task<SymbolTreeInfo> TryGetInfoForAssemblyAsync(Solution solution, IAssemblySymbol assembly, PortableExecutableReference reference, CancellationToken cancellationToken)
public static async Task<SymbolTreeInfo> TryGetInfoForMetadataAssemblyAsync(Solution solution, IAssemblySymbol assembly, PortableExecutableReference reference, CancellationToken cancellationToken)
{
var metadata = assembly.GetMetadata();
if (metadata == null)
......@@ -225,6 +262,14 @@ public static async Task<SymbolTreeInfo> TryGetInfoForAssemblyAsync(Solution sol
}
}
public static async Task<SymbolTreeInfo> GetInfoForSourceAssemblyAsync(
Project project, CancellationToken cancellationToken)
{
var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false);
return await LoadOrCreateAsync(project.Solution, compilation.Assembly, project.FilePath, cancellationToken).ConfigureAwait(false);
}
internal static SymbolTreeInfo Create(VersionStamp version, IAssemblySymbol assembly, CancellationToken cancellationToken)
{
if (assembly == null)
......
......@@ -383,6 +383,7 @@
<Compile Include="ExtensionManager\IErrorReportingService.cs" />
<Compile Include="FindSymbols\DeclaredSymbolInfo.cs" />
<Compile Include="FindSymbols\FindReferences\Finders\ILanguageServiceReferenceFinder.cs" />
<Compile Include="FindSymbols\SymbolTree\ISymbolTreeInfoCacheService.cs" />
<Compile Include="Utilities\ArraySlice.cs" />
<Compile Include="Utilities\BKTree.cs" />
<Compile Include="FindSymbols\SyntaxTree\AbstractSyntaxTreeInfo.cs" />
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册