提交 bac41230 编写于 作者: C CyrusNajmabadi 提交者: GitHub

Merge pull request #18838 from CyrusNajmabadi/findRefsOOPWork

Move all expensive FAR work into the FAR engine, and remove from teh FAR window.
......@@ -105,6 +105,7 @@
</ItemGroup>
<ItemGroup>
<Compile Include="FindUsages\AbstractFindUsagesService.cs" />
<Compile Include="FindUsages\ClassifiedSpansAndHighlightSpan.cs" />
<Compile Include="FindUsages\AbstractFindUsagesService.DefinitionTrackingContext.cs" />
<Compile Include="FindUsages\FindUsagesHelpers.cs" />
<Compile Include="FindUsages\FindUsagesContext.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.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.FindSymbols;
......@@ -30,9 +30,13 @@ private class FindLiteralsProgressAdapter : IStreamingFindLiteralReferencesProgr
_definition = definition;
}
public Task OnReferenceFoundAsync(Document document, TextSpan span)
=> _context.OnReferenceFoundAsync(new SourceReferenceItem(
_definition, new DocumentSpan(document, span), isWrittenTo: false));
public async Task OnReferenceFoundAsync(Document document, TextSpan span)
{
var documentSpan = await ClassifiedSpansAndHighlightSpan.GetClassifiedDocumentSpanAsync(
document, span, _context.CancellationToken).ConfigureAwait(false);
await _context.OnReferenceFoundAsync(new SourceReferenceItem(
_definition, documentSpan, isWrittenTo: false)).ConfigureAwait(false);
}
public Task ReportProgressAsync(int current, int maximum)
=> _context.ReportProgressAsync(current, maximum);
......@@ -56,16 +60,15 @@ private class FindReferencesProgressAdapter : ForegroundThreadAffinitizedObject,
/// This dictionary allows us to make that mapping once and then keep it around for
/// all future callbacks.
/// </summary>
private readonly ConcurrentDictionary<ISymbol, DefinitionItem> _definitionToItem =
new ConcurrentDictionary<ISymbol, DefinitionItem>(MetadataUnifyingEquivalenceComparer.Instance);
private readonly Dictionary<ISymbol, DefinitionItem> _definitionToItem =
new Dictionary<ISymbol, DefinitionItem>(MetadataUnifyingEquivalenceComparer.Instance);
private readonly Func<ISymbol, DefinitionItem> _definitionFactory;
private readonly SemaphoreSlim _gate = new SemaphoreSlim(initialCount: 1);
public FindReferencesProgressAdapter(Solution solution, IFindUsagesContext context)
{
_solution = solution;
_context = context;
_definitionFactory = s => s.ToDefinitionItem(solution, includeHiddenLocations: false);
}
// Do nothing functions. The streaming far service doesn't care about
......@@ -83,14 +86,26 @@ public FindReferencesProgressAdapter(Solution solution, IFindUsagesContext conte
// used by the FAR engine to the INavigableItems used by the streaming FAR
// feature.
private DefinitionItem GetDefinitionItem(SymbolAndProjectId definition)
private async Task<DefinitionItem> GetDefinitionItemAsync(SymbolAndProjectId definition)
{
return _definitionToItem.GetOrAdd(definition.Symbol, _definitionFactory);
using (await _gate.DisposableWaitAsync(_context.CancellationToken).ConfigureAwait(false))
{
if (!_definitionToItem.TryGetValue(definition.Symbol, out var definitionItem))
{
definitionItem = await definition.Symbol.ToClassifiedDefinitionItemAsync(
_solution, includeHiddenLocations: false, cancellationToken: _context.CancellationToken).ConfigureAwait(false);
_definitionToItem[definition.Symbol] = definitionItem;
}
return definitionItem;
}
}
public Task OnDefinitionFoundAsync(SymbolAndProjectId definition)
public async Task OnDefinitionFoundAsync(SymbolAndProjectId definition)
{
return _context.OnDefinitionFoundAsync(GetDefinitionItem(definition));
var definitionItem = await GetDefinitionItemAsync(definition).ConfigureAwait(false);
await _context.OnDefinitionFoundAsync(definitionItem).ConfigureAwait(false);
}
public async Task OnReferenceFoundAsync(SymbolAndProjectId definition, ReferenceLocation location)
......@@ -101,8 +116,10 @@ public async Task OnReferenceFoundAsync(SymbolAndProjectId definition, Reference
return;
}
var referenceItem = location.TryCreateSourceReferenceItem(
GetDefinitionItem(definition), includeHiddenLocations: false);
var definitionItem = await GetDefinitionItemAsync(definition).ConfigureAwait(false);
var referenceItem = await location.TryCreateSourceReferenceItemAsync(
definitionItem, includeHiddenLocations: false,
cancellationToken: _context.CancellationToken).ConfigureAwait(false);
if (referenceItem != null)
{
......
......@@ -14,10 +14,12 @@ namespace Microsoft.CodeAnalysis.Editor.FindUsages
{
internal abstract partial class AbstractFindUsagesService : IFindUsagesService
{
public async Task FindImplementationsAsync(Document document, int position, IFindUsagesContext context)
public async Task FindImplementationsAsync(
Document document, int position, IFindUsagesContext context)
{
var cancellationToken = context.CancellationToken;
var tuple = await FindUsagesHelpers.FindImplementationsAsync(
document, position, context.CancellationToken).ConfigureAwait(false);
document, position, cancellationToken).ConfigureAwait(false);
if (tuple == null)
{
context.ReportMessage(EditorFeaturesResources.Cannot_navigate_to_the_symbol_under_the_caret);
......@@ -38,8 +40,8 @@ public async Task FindImplementationsAsync(Document document, int position, IFin
var project = tuple.Value.project;
foreach (var implementation in tuple.Value.implementations)
{
var definitionItem = implementation.ToDefinitionItem(
project.Solution, includeHiddenLocations: false);
var definitionItem = await implementation.ToClassifiedDefinitionItemAsync(
project.Solution, includeHiddenLocations: false, cancellationToken: cancellationToken).ConfigureAwait(false);
await context.OnDefinitionFoundAsync(definitionItem).ConfigureAwait(false);
}
}
......
// 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.Collections.Generic;
using System.Collections.Immutable;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Classification;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
namespace Microsoft.CodeAnalysis.Editor.FindUsages
{
internal struct ClassifiedSpansAndHighlightSpan
{
private const string Key = nameof(ClassifiedSpansAndHighlightSpan);
public readonly ImmutableArray<ClassifiedSpan> ClassifiedSpans;
public readonly TextSpan HighlightSpan;
public ClassifiedSpansAndHighlightSpan(
ImmutableArray<ClassifiedSpan> classifiedSpans,
TextSpan highlightSpan)
{
ClassifiedSpans = classifiedSpans;
HighlightSpan = highlightSpan;
}
public static async Task<DocumentSpan> GetClassifiedDocumentSpanAsync(
Document document, TextSpan sourceSpan, CancellationToken cancellationToken)
{
var classifiedSpans = await ClassifyAsync(
document, sourceSpan, cancellationToken).ConfigureAwait(false);
var properties = ImmutableDictionary<string, object>.Empty.Add(Key, classifiedSpans);
return new DocumentSpan(document, sourceSpan, properties);
}
public static async Task<ClassifiedSpansAndHighlightSpan> ClassifyAsync(
DocumentSpan documentSpan, CancellationToken cancellationToken)
{
// If the document span is providing us with the classified spans up front, then we
// can just use that. Otherwise, go back and actually classify the text for the line
// the document span is on.
if (documentSpan.Properties.TryGetValue(Key, out var value))
{
return (ClassifiedSpansAndHighlightSpan)value;
}
return await ClassifyAsync(
documentSpan.Document, documentSpan.SourceSpan, cancellationToken).ConfigureAwait(false);
}
private static async Task<ClassifiedSpansAndHighlightSpan> ClassifyAsync(
Document document, TextSpan sourceSpan, CancellationToken cancellationToken)
{
var sourceText = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);
var narrowSpan = sourceSpan;
var lineSpan = GetLineSpanForReference(sourceText, narrowSpan);
var taggedLineParts = await GetTaggedTextForDocumentRegionAsync(
document, narrowSpan, lineSpan, cancellationToken).ConfigureAwait(false);
return taggedLineParts;
}
private static TextSpan GetLineSpanForReference(SourceText sourceText, TextSpan referenceSpan)
{
var sourceLine = sourceText.Lines.GetLineFromPosition(referenceSpan.Start);
var firstNonWhitespacePosition = sourceLine.GetFirstNonWhitespacePosition().Value;
return TextSpan.FromBounds(firstNonWhitespacePosition, sourceLine.End);
}
private static async Task<ClassifiedSpansAndHighlightSpan> GetTaggedTextForDocumentRegionAsync(
Document document, TextSpan narrowSpan, TextSpan widenedSpan, CancellationToken cancellationToken)
{
var highlightSpan = new TextSpan(
start: narrowSpan.Start - widenedSpan.Start,
length: narrowSpan.Length);
var classifiedSpans = await GetClassifiedSpansAsync(
document, narrowSpan, widenedSpan, cancellationToken).ConfigureAwait(false);
return new ClassifiedSpansAndHighlightSpan(classifiedSpans, highlightSpan);
}
private static async Task<ImmutableArray<ClassifiedSpan>> GetClassifiedSpansAsync(
Document document, TextSpan narrowSpan, TextSpan widenedSpan, CancellationToken cancellationToken)
{
var classificationService = document.GetLanguageService<IEditorClassificationService>();
if (classificationService == null)
{
// For languages that don't expose a classification service, we show the entire
// item as plain text. Break the text into three spans so that we can properly
// highlight the 'narrow-span' later on when we display the item.
return ImmutableArray.Create(
new ClassifiedSpan(ClassificationTypeNames.Text, TextSpan.FromBounds(widenedSpan.Start, narrowSpan.Start)),
new ClassifiedSpan(ClassificationTypeNames.Text, narrowSpan),
new ClassifiedSpan(ClassificationTypeNames.Text, TextSpan.FromBounds(narrowSpan.End, widenedSpan.End)));
}
// Call out to the individual language to classify the chunk of text around the
// reference. We'll get both the syntactic and semantic spans for this region.
// Because the semantic tags may override the semantic ones (for example,
// "DateTime" might be syntactically an identifier, but semantically a struct
// name), we'll do a later merging step to get the final correct list of
// classifications. For tagging, normally the editor handles this. But as
// we're producing the list of Inlines ourselves, we have to handles this here.
var syntaxSpans = ListPool<ClassifiedSpan>.Allocate();
var semanticSpans = ListPool<ClassifiedSpan>.Allocate();
try
{
var sourceText = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);
await classificationService.AddSyntacticClassificationsAsync(
document, widenedSpan, syntaxSpans, cancellationToken).ConfigureAwait(false);
await classificationService.AddSemanticClassificationsAsync(
document, widenedSpan, semanticSpans, cancellationToken).ConfigureAwait(false);
var classifiedSpans = MergeClassifiedSpans(
syntaxSpans, semanticSpans, widenedSpan, sourceText);
return classifiedSpans;
}
finally
{
ListPool<ClassifiedSpan>.Free(syntaxSpans);
ListPool<ClassifiedSpan>.Free(semanticSpans);
}
}
private static ImmutableArray<ClassifiedSpan> MergeClassifiedSpans(
List<ClassifiedSpan> syntaxSpans, List<ClassifiedSpan> semanticSpans,
TextSpan widenedSpan, SourceText sourceText)
{
// The spans produced by the language services may not be ordered
// (indeed, this happens with semantic classification as different
// providers produce different results in an arbitrary order). Order
// them first before proceeding.
Order(syntaxSpans);
Order(semanticSpans);
// It's possible for us to get classified spans that occur *before*
// or after the span we want to present. This happens because the calls to
// AddSyntacticClassificationsAsync and AddSemanticClassificationsAsync
// may return more spans than the range asked for. While bad form,
// it's never been a requirement that implementation not do that.
// For example, the span may be the non-full-span of a node, but the
// classifiers may still return classifications for leading/trailing
// trivia even if it's out of the bounds of that span.
//
// To deal with that, we adjust all spans so that they don't go outside
// of the range we care about.
AdjustSpans(syntaxSpans, widenedSpan);
AdjustSpans(semanticSpans, widenedSpan);
// The classification service will only produce classifications for
// things it knows about. i.e. there will be gaps in what it produces.
// Fill in those gaps so we have *all* parts of the span
// classified properly.
var filledInSyntaxSpans = ArrayBuilder<ClassifiedSpan>.GetInstance();
var filledInSemanticSpans = ArrayBuilder<ClassifiedSpan>.GetInstance();
try
{
FillInClassifiedSpanGaps(sourceText, widenedSpan.Start, syntaxSpans, filledInSyntaxSpans);
FillInClassifiedSpanGaps(sourceText, widenedSpan.Start, semanticSpans, filledInSemanticSpans);
// Now merge the lists together, taking all the results from syntaxParts
// unless they were overridden by results in semanticParts.
return MergeParts(filledInSyntaxSpans, filledInSemanticSpans);
}
finally
{
filledInSyntaxSpans.Free();
filledInSemanticSpans.Free();
}
}
private static void Order(List<ClassifiedSpan> syntaxSpans)
=> syntaxSpans.Sort((s1, s2) => s1.TextSpan.Start - s2.TextSpan.Start);
private static void AdjustSpans(List<ClassifiedSpan> spans, TextSpan widenedSpan)
{
for (var i = 0; i < spans.Count; i++)
{
var span = spans[i];
// Make sure the span actually intersects 'widenedSpan'. If it
// does not, just put in an empty length span. It will get ignored later
// when we walk through this list.
var intersection = span.TextSpan.Intersection(widenedSpan);
if (i > 0 && intersection != null)
{
if (spans[i - 1].TextSpan.End > intersection.Value.Start)
{
// This span isn't strictly after the previous span. Ignore it.
intersection = null;
}
}
var newSpan = new ClassifiedSpan(span.ClassificationType,
intersection ?? new TextSpan());
spans[i] = newSpan;
}
}
private static void FillInClassifiedSpanGaps(
SourceText sourceText, int startPosition,
List<ClassifiedSpan> classifiedSpans, ArrayBuilder<ClassifiedSpan> result)
{
foreach (var span in classifiedSpans)
{
// Ignore empty spans. We can get those when the classification service
// returns spans outside of the range of the span we asked to classify.
if (span.TextSpan.Length == 0)
{
continue;
}
// If there is space between this span and the last one, then add a space.
if (startPosition != span.TextSpan.Start)
{
result.Add(new ClassifiedSpan(ClassificationTypeNames.Text,
TextSpan.FromBounds(
startPosition, span.TextSpan.Start)));
}
result.Add(span);
startPosition = span.TextSpan.End;
}
}
private static ImmutableArray<ClassifiedSpan> MergeParts(
ArrayBuilder<ClassifiedSpan> syntaxParts,
ArrayBuilder<ClassifiedSpan> semanticParts)
{
// Take all the syntax parts. However, if any have been overridden by a
// semantic part, then choose that one.
var finalParts = ArrayBuilder<ClassifiedSpan>.GetInstance();
var lastReplacementIndex = 0;
for (int i = 0, n = syntaxParts.Count; i < n; i++)
{
var syntaxPartAndSpan = syntaxParts[i];
// See if we can find a semantic part to replace this syntax part.
var replacementIndex = semanticParts.FindIndex(
lastReplacementIndex, t => t.TextSpan == syntaxPartAndSpan.TextSpan);
// Take the semantic part if it's just 'text'. We want to keep it if
// the semantic classifier actually produced an interesting result
// (as opposed to it just being a 'gap' classification).
var part = replacementIndex >= 0 && !IsClassifiedAsText(semanticParts[replacementIndex])
? semanticParts[replacementIndex]
: syntaxPartAndSpan;
finalParts.Add(part);
if (replacementIndex >= 0)
{
// If we found a semantic replacement, update the lastIndex.
// That way we can start searching from that point instead
// of checking all the elements each time.
lastReplacementIndex = replacementIndex + 1;
}
}
return finalParts.ToImmutableAndFree();
}
private static bool IsClassifiedAsText(ClassifiedSpan partAndSpan)
{
// Don't take 'text' from the semantic parts. We'll get those for the
// spaces between the actual interesting semantic spans, and we don't
// want them to override actual good syntax spans.
return partAndSpan.ClassificationType == ClassificationTypeNames.Text;
}
}
}
\ No newline at end of file
......@@ -4,6 +4,7 @@
using System.Composition;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Completion;
using Microsoft.CodeAnalysis.Features.RQName;
using Microsoft.CodeAnalysis.FindSymbols;
......@@ -36,10 +37,36 @@ internal class DefaultDefinitionsAndReferencesFactory : IDefinitionsAndReference
internal static class DefinitionItemExtensions
{
public static DefinitionItem ToDefinitionItem(
public static DefinitionItem ToNonClassifiedDefinitionItem(
this ISymbol definition,
Solution solution,
bool includeHiddenLocations)
{
// Because we're passing in 'false' for 'includeClassifiedSpans', this won't ever have
// to actually do async work. This is because the only asynchrony is when we are trying
// to compute the classified spans for the locations of the definition. So it's totally
// fine to pass in CancellationToken.None and block on the result.
return ToDefinitionItemAsync(definition, solution, includeHiddenLocations,
includeClassifiedSpans: false, cancellationToken: CancellationToken.None).Result;
}
public static Task<DefinitionItem> ToClassifiedDefinitionItemAsync(
this ISymbol definition,
Solution solution,
bool includeHiddenLocations,
CancellationToken cancellationToken)
{
return ToDefinitionItemAsync(definition, solution,
includeHiddenLocations, includeClassifiedSpans: true, cancellationToken: cancellationToken);
}
private static async Task<DefinitionItem> ToDefinitionItemAsync(
this ISymbol definition,
Solution solution,
bool includeHiddenLocations,
bool includeClassifiedSpans,
CancellationToken cancellationToken)
{
// Ensure we're working with the original definition for the symbol. I.e. When we're
// creating definition items, we want to create them for types like Dictionary<TKey,TValue>
......@@ -84,7 +111,12 @@ internal static class DefinitionItemExtensions
var document = solution.GetDocument(location.SourceTree);
if (document != null)
{
sourceLocations.Add(new DocumentSpan(document, location.SourceSpan));
var documentLocation = !includeClassifiedSpans
? new DocumentSpan(document, location.SourceSpan)
: await ClassifiedSpansAndHighlightSpan.GetClassifiedDocumentSpanAsync(
document, location.SourceSpan, cancellationToken).ConfigureAwait(false);
sourceLocations.Add(documentLocation);
}
}
}
......@@ -129,10 +161,11 @@ internal static class DefinitionItemExtensions
return properties;
}
public static SourceReferenceItem TryCreateSourceReferenceItem(
public static async Task<SourceReferenceItem> TryCreateSourceReferenceItemAsync(
this ReferenceLocation referenceLocation,
DefinitionItem definitionItem,
bool includeHiddenLocations)
bool includeHiddenLocations,
CancellationToken cancellationToken)
{
var location = referenceLocation.Location;
......@@ -143,10 +176,13 @@ internal static class DefinitionItemExtensions
return null;
}
return new SourceReferenceItem(
definitionItem,
new DocumentSpan(referenceLocation.Document, location.SourceSpan),
referenceLocation.IsWrittenTo);
var document = referenceLocation.Document;
var sourceSpan = location.SourceSpan;
var documentSpan = await ClassifiedSpansAndHighlightSpan.GetClassifiedDocumentSpanAsync(
document, sourceSpan, cancellationToken).ConfigureAwait(false);
return new SourceReferenceItem(definitionItem, documentSpan, referenceLocation.IsWrittenTo);
}
private static SymbolDisplayFormat GetFormat(ISymbol definition)
......
......@@ -61,7 +61,8 @@ internal static class GoToDefinitionHelpers
}
var definitions = ArrayBuilder<DefinitionItem>.GetInstance();
var definitionItem = symbol.ToDefinitionItem(solution, includeHiddenLocations: true);
var definitionItem = symbol.ToClassifiedDefinitionItemAsync(
solution, includeHiddenLocations: true, cancellationToken: cancellationToken).WaitAndGetResult(cancellationToken);
if (thirdPartyNavigationAllowed)
{
......@@ -72,34 +73,12 @@ internal static class GoToDefinitionHelpers
definitions.Add(definitionItem);
var presenter = GetFindUsagesPresenter(streamingPresenters);
var presenter = streamingPresenters.FirstOrDefault()?.Value;
var title = string.Format(EditorFeaturesResources._0_declarations,
FindUsagesHelpers.GetDisplayName(symbol));
return presenter.TryNavigateToOrPresentItemsAsync(
project.Solution.Workspace, title, definitions.ToImmutableAndFree()).WaitAndGetResult(cancellationToken);
}
private static IStreamingFindUsagesPresenter GetFindUsagesPresenter(
IEnumerable<Lazy<IStreamingFindUsagesPresenter>> streamingPresenters)
{
try
{
return streamingPresenters.FirstOrDefault()?.Value;
}
catch
{
return null;
}
}
private static bool TryThirdPartyNavigation(
ISymbol symbol, Solution solution, CancellationToken cancellationToken)
{
var symbolNavigationService = solution.Workspace.Services.GetService<ISymbolNavigationService>();
// Notify of navigation so third parties can intercept the navigation
return symbolNavigationService.TrySymbolNavigationNotify(symbol, solution, cancellationToken);
}
}
}
\ No newline at end of file
......@@ -60,7 +60,7 @@ private PeekableItemFactory(IMetadataAsSourceFileService metadataAsSourceFileSer
}
var symbolNavigationService = solution.Workspace.Services.GetService<ISymbolNavigationService>();
var definitionItem = symbol.ToDefinitionItem(solution, includeHiddenLocations: true);
var definitionItem = symbol.ToNonClassifiedDefinitionItem(solution, includeHiddenLocations: true);
if (symbolNavigationService.WouldNavigateToSymbol(
definitionItem, solution, cancellationToken,
......
// 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.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.GeneratedCodeRecognition;
using Microsoft.CodeAnalysis.Navigation;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
......@@ -19,10 +18,24 @@ internal struct DocumentSpan : IEquatable<DocumentSpan>
public Document Document { get; }
public TextSpan SourceSpan { get; }
/// <summary>
/// Additional information attached to a document span by it creator.
/// </summary>
public ImmutableDictionary<string, object> Properties { get; }
public DocumentSpan(Document document, TextSpan sourceSpan)
: this(document, sourceSpan, properties: null)
{
}
public DocumentSpan(
Document document,
TextSpan sourceSpan,
ImmutableDictionary<string, object> properties)
{
Document = document;
SourceSpan = sourceSpan;
Properties = properties;
}
public override bool Equals(object obj)
......
......@@ -158,8 +158,8 @@ public bool TrySymbolNavigationNotify(ISymbol symbol, Solution solution, Cancell
ISymbol symbol, Solution solution, CancellationToken cancellationToken)
{
AssertIsForeground();
var definitionItem = symbol.ToDefinitionItem(solution, includeHiddenLocations: true);
var definitionItem = symbol.ToNonClassifiedDefinitionItem(solution, includeHiddenLocations: true);
definitionItem.Properties.TryGetValue(DefinitionItem.RQNameKey1, out var rqName);
if (!TryGetNavigationAPIRequiredArguments(
......
......@@ -4,15 +4,12 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Classification;
using Microsoft.CodeAnalysis.Editor;
using Microsoft.CodeAnalysis.Editor.FindUsages;
using Microsoft.CodeAnalysis.FindUsages;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem;
using Microsoft.VisualStudio.Shell.FindAllReferences;
......@@ -271,22 +268,12 @@ protected async Task<(Guid, string projectName, SourceText)> GetGuidAndProjectNa
var document = documentSpan.Document;
var (guid, projectName, sourceText) = await GetGuidAndProjectNameAndSourceTextAsync(document).ConfigureAwait(false);
var narrowSpan = documentSpan.SourceSpan;
var lineSpan = GetLineSpanForReference(sourceText, narrowSpan);
var taggedLineParts = await GetTaggedTextForDocumentRegionAsync(document, narrowSpan, lineSpan).ConfigureAwait(false);
var classifiedSpansAndHighlightSpan =
await ClassifiedSpansAndHighlightSpan.ClassifyAsync(documentSpan, CancellationToken).ConfigureAwait(false);
return new DocumentSpanEntry(
this, definitionBucket, documentSpan, spanKind,
projectName, guid, sourceText, taggedLineParts);
}
private TextSpan GetLineSpanForReference(SourceText sourceText, TextSpan referenceSpan)
{
var sourceLine = sourceText.Lines.GetLineFromPosition(referenceSpan.Start);
var firstNonWhitespacePosition = sourceLine.GetFirstNonWhitespacePosition().Value;
return TextSpan.FromBounds(firstNonWhitespacePosition, sourceLine.End);
projectName, guid, sourceText, classifiedSpansAndHighlightSpan);
}
private TextSpan GetRegionSpanForReference(SourceText sourceText, TextSpan referenceSpan)
......@@ -302,202 +289,6 @@ private TextSpan GetRegionSpanForReference(SourceText sourceText, TextSpan refer
sourceText.Lines[lastLineNumber].End);
}
private async Task<ClassifiedSpansAndHighlightSpan> GetTaggedTextForDocumentRegionAsync(
Document document, TextSpan narrowSpan, TextSpan widenedSpan)
{
var highlightSpan = new TextSpan(
start: narrowSpan.Start - widenedSpan.Start,
length: narrowSpan.Length);
var classifiedSpans = await GetClassifiedSpansAsync(document, narrowSpan, widenedSpan).ConfigureAwait(false);
return new ClassifiedSpansAndHighlightSpan(classifiedSpans, highlightSpan);
}
private async Task<ImmutableArray<ClassifiedSpan>> GetClassifiedSpansAsync(
Document document, TextSpan narrowSpan, TextSpan widenedSpan)
{
var classificationService = document.GetLanguageService<IEditorClassificationService>();
if (classificationService == null)
{
// For languages that don't expose a classification service, we show the entire
// item as plain text. Break the text into three spans so that we can properly
// highlight the 'narrow-span' later on when we display the item.
return ImmutableArray.Create(
new ClassifiedSpan(ClassificationTypeNames.Text, TextSpan.FromBounds(widenedSpan.Start, narrowSpan.Start)),
new ClassifiedSpan(ClassificationTypeNames.Text, narrowSpan),
new ClassifiedSpan(ClassificationTypeNames.Text, TextSpan.FromBounds(narrowSpan.End, widenedSpan.End)));
}
// Call out to the individual language to classify the chunk of text around the
// reference. We'll get both the syntactic and semantic spans for this region.
// Because the semantic tags may override the semantic ones (for example,
// "DateTime" might be syntactically an identifier, but semantically a struct
// name), we'll do a later merging step to get the final correct list of
// classifications. For tagging, normally the editor handles this. But as
// we're producing the list of Inlines ourselves, we have to handles this here.
var syntaxSpans = ListPool<ClassifiedSpan>.Allocate();
var semanticSpans = ListPool<ClassifiedSpan>.Allocate();
try
{
var sourceText = await document.GetTextAsync(CancellationToken).ConfigureAwait(false);
await classificationService.AddSyntacticClassificationsAsync(
document, widenedSpan, syntaxSpans, CancellationToken).ConfigureAwait(false);
await classificationService.AddSemanticClassificationsAsync(
document, widenedSpan, semanticSpans, CancellationToken).ConfigureAwait(false);
var classifiedSpans = MergeClassifiedSpans(
syntaxSpans, semanticSpans, widenedSpan, sourceText);
return classifiedSpans;
}
finally
{
ListPool<ClassifiedSpan>.Free(syntaxSpans);
ListPool<ClassifiedSpan>.Free(semanticSpans);
}
}
private ImmutableArray<ClassifiedSpan> MergeClassifiedSpans(
List<ClassifiedSpan> syntaxSpans, List<ClassifiedSpan> semanticSpans,
TextSpan widenedSpan, SourceText sourceText)
{
// The spans produced by the language services may not be ordered
// (indeed, this happens with semantic classification as different
// providers produce different results in an arbitrary order). Order
// them first before proceeding.
Order(syntaxSpans);
Order(semanticSpans);
// It's possible for us to get classified spans that occur *before*
// or after the span we want to present. This happens because the calls to
// AddSyntacticClassificationsAsync and AddSemanticClassificationsAsync
// may return more spans than the range asked for. While bad form,
// it's never been a requirement that implementation not do that.
// For example, the span may be the non-full-span of a node, but the
// classifiers may still return classifications for leading/trailing
// trivia even if it's out of the bounds of that span.
//
// To deal with that, we adjust all spans so that they don't go outside
// of the range we care about.
AdjustSpans(syntaxSpans, widenedSpan);
AdjustSpans(semanticSpans, widenedSpan);
// The classification service will only produce classifications for
// things it knows about. i.e. there will be gaps in what it produces.
// Fill in those gaps so we have *all* parts of the span
// classified properly.
var filledInSyntaxSpans = ArrayBuilder<ClassifiedSpan>.GetInstance();
var filledInSemanticSpans = ArrayBuilder<ClassifiedSpan>.GetInstance();
try
{
FillInClassifiedSpanGaps(sourceText, widenedSpan.Start, syntaxSpans, filledInSyntaxSpans);
FillInClassifiedSpanGaps(sourceText, widenedSpan.Start, semanticSpans, filledInSemanticSpans);
// Now merge the lists together, taking all the results from syntaxParts
// unless they were overridden by results in semanticParts.
return MergeParts(filledInSyntaxSpans, filledInSemanticSpans);
}
finally
{
filledInSyntaxSpans.Free();
filledInSemanticSpans.Free();
}
}
private void AdjustSpans(List<ClassifiedSpan> spans, TextSpan widenedSpan)
{
for (var i = 0; i < spans.Count; i++)
{
var span = spans[i];
// Make sure the span actually intersects 'widenedSpan'. If it
// does not, just put in an empty length span. It will get ignored later
// when we walk through this list.
var intersection = span.TextSpan.Intersection(widenedSpan);
var newSpan = new ClassifiedSpan(span.ClassificationType,
intersection ?? new TextSpan());
spans[i] = newSpan;
}
}
private static void FillInClassifiedSpanGaps(
SourceText sourceText, int startPosition,
List<ClassifiedSpan> classifiedSpans, ArrayBuilder<ClassifiedSpan> result)
{
foreach (var span in classifiedSpans)
{
// Ignore empty spans. We can get those when the classification service
// returns spans outside of the range of the span we asked to classify.
if (span.TextSpan.Length == 0)
{
continue;
}
// If there is space between this span and the last one, then add a space.
if (startPosition != span.TextSpan.Start)
{
result.Add(new ClassifiedSpan(ClassificationTypeNames.Text,
TextSpan.FromBounds(
startPosition, span.TextSpan.Start)));
}
result.Add(span);
startPosition = span.TextSpan.End;
}
}
private void Order(List<ClassifiedSpan> syntaxSpans)
{
syntaxSpans.Sort((s1, s2) => s1.TextSpan.Start - s2.TextSpan.Start);
}
private ImmutableArray<ClassifiedSpan> MergeParts(
ArrayBuilder<ClassifiedSpan> syntaxParts,
ArrayBuilder<ClassifiedSpan> semanticParts)
{
// Take all the syntax parts. However, if any have been overridden by a
// semantic part, then choose that one.
var finalParts = ArrayBuilder<ClassifiedSpan>.GetInstance();
var lastReplacementIndex = 0;
for (int i = 0, n = syntaxParts.Count; i < n; i++)
{
var syntaxPartAndSpan = syntaxParts[i];
// See if we can find a semantic part to replace this syntax part.
var replacementIndex = semanticParts.FindIndex(
lastReplacementIndex, t => t.TextSpan == syntaxPartAndSpan.TextSpan);
// Take the semantic part if it's just 'text'. We want to keep it if
// the semantic classifier actually produced an interesting result
// (as opposed to it just being a 'gap' classification).
var part = replacementIndex >= 0 && !IsClassifiedAsText(semanticParts[replacementIndex])
? semanticParts[replacementIndex]
: syntaxPartAndSpan;
finalParts.Add(part);
if (replacementIndex >= 0)
{
// If we found a semantic replacement, update the lastIndex.
// That way we can start searching from that point instead
// of checking all the elements each time.
lastReplacementIndex = replacementIndex + 1;
}
}
return finalParts.ToImmutableAndFree();
}
private bool IsClassifiedAsText(ClassifiedSpan partAndSpan)
{
// Don't take 'text' from the semantic parts. We'll get those for the
// spaces between the actual interesting semantic spans, and we don't
// want them to override actual good syntax spans.
return partAndSpan.ClassificationType == ClassificationTypeNames.Text;
}
public sealed override Task OnReferenceFoundAsync(SourceReferenceItem reference)
=> OnReferenceFoundWorkerAsync(reference);
......
......@@ -8,10 +8,12 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Classification;
using Microsoft.CodeAnalysis.Editor;
using Microsoft.CodeAnalysis.Editor.FindUsages;
using Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.QuickInfo;
using Microsoft.CodeAnalysis.Editor.Implementation.ReferenceHighlighting;
using Microsoft.CodeAnalysis.Editor.Shared.Extensions;
using Microsoft.CodeAnalysis.Editor.Shared.Preview;
using Microsoft.CodeAnalysis.FindUsages;
using Microsoft.CodeAnalysis.Text;
using Microsoft.CodeAnalysis.Text.Shared.Extensions;
using Microsoft.VisualStudio.LanguageServices.Implementation.Extensions;
......
// 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.Collections.Immutable;
using Microsoft.CodeAnalysis.Classification;
using Microsoft.CodeAnalysis.Text;
namespace Microsoft.VisualStudio.LanguageServices.FindUsages
{
internal partial class StreamingFindUsagesPresenter
{
private struct ClassifiedSpansAndHighlightSpan
{
public readonly ImmutableArray<ClassifiedSpan> ClassifiedSpans;
public readonly TextSpan HighlightSpan;
public ClassifiedSpansAndHighlightSpan(
ImmutableArray<ClassifiedSpan> classifiedSpans,
TextSpan highlightSpan)
{
ClassifiedSpans = classifiedSpans;
HighlightSpan = highlightSpan;
}
}
}
}
\ No newline at end of file
......@@ -102,7 +102,6 @@
<Compile Include="FindReferences\StreamingFindUsagesPresenter.cs" />
<Compile Include="FindReferences\ToolTips\DisposableToolTip.cs" />
<Compile Include="FindReferences\TableEntriesSnapshot.cs" />
<Compile Include="FindReferences\TaggedTextAndHighlightSpan.cs" />
<Compile Include="FindReferences\ToolTips\LazyToolTip.cs" />
<Compile Include="ProjectSystem\DeferredProjectWorkspaceService.cs" />
<Compile Include="Remote\JsonRpcClient.cs" />
......
......@@ -80,9 +80,13 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Classification.Classifiers
Dim token = GetNameToken(node)
Return SpecializedCollections.SingletonEnumerable(
New ClassifiedSpan(token.Span, ClassificationTypeNames.Keyword))
Else
' We bound to a constructor, but we weren't something like the 'New' in 'X.New'.
' This can happen when we're actually just binding the full node 'X.New'. In this
' case, don't return anything for this full node. We'll end up hitting the
' 'New' node as the worker walks down, and we'll classify it then.
Return Nothing
End If
symbol = method.ContainingType
End If
End If
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册