提交 799b963c 编写于 作者: C CyrusNajmabadi

Don't group by definition when presenting result for FindImplementations or GoToDef.

上级 2e1cb224
......@@ -126,7 +126,7 @@ private IStreamingFindUsagesPresenter GetStreamingPresenter()
// Let the presented know we're starging a search. It will give us back
// the context object that the FAR service will push results into.
var context = presenter.StartSearch(
EditorFeaturesResources.Find_References, alwaysShowDeclarations: false);
EditorFeaturesResources.Find_References, canShowReferences: true);
await findUsagesService.FindReferencesAsync(document, caretPosition, context).ConfigureAwait(false);
// Note: we don't need to put this in a finally. The only time we might not hit
......
......@@ -3,15 +3,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using Microsoft.CodeAnalysis.Editor.FindReferences;
using Microsoft.CodeAnalysis.Editor.FindUsages;
using Microsoft.CodeAnalysis.Editor.Host;
using Microsoft.CodeAnalysis.FindSymbols;
using Microsoft.CodeAnalysis.FindUsages;
using Microsoft.CodeAnalysis.Navigation;
using Microsoft.CodeAnalysis.Options;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Editor.GoToDefinition
......@@ -75,8 +72,7 @@ internal static class GoToDefinitionHelpers
FindUsagesHelpers.GetDisplayName(symbol));
return presenter.TryNavigateToOrPresentItemsAsync(
title, definitions.ToImmutableAndFree(),
alwaysShowDeclarations: true).WaitAndGetResult(cancellationToken);
title, definitions.ToImmutableAndFree()).WaitAndGetResult(cancellationToken);
}
private static IStreamingFindUsagesPresenter GetFindUsagesPresenter(
......
......@@ -136,8 +136,7 @@ public void ExecuteCommand(GoToImplementationCommandArgs args, Action nextHandle
var definitionItems = goToImplContext.GetDefinitions();
streamingPresenter.TryNavigateToOrPresentItemsAsync(
goToImplContext.SearchTitle, definitionItems,
alwaysShowDeclarations: true).Wait(cancellationToken);
goToImplContext.SearchTitle, definitionItems).Wait(cancellationToken);
}
private IStreamingFindUsagesPresenter GetStreamingPresenter()
......
......@@ -21,7 +21,7 @@ internal interface IStreamingFindUsagesPresenter
/// search completes <see cref="FindUsagesContext.OnCompletedAsync"/> should be called.
/// etc. etc.
/// </summary>
FindUsagesContext StartSearch(string title, bool alwaysShowDeclarations);
FindUsagesContext StartSearch(string title, bool canShowReferences);
}
internal static class IStreamingFindUsagesPresenterExtensions
......@@ -31,8 +31,8 @@ internal static class IStreamingFindUsagesPresenterExtensions
/// items to the user.
/// </summary>
public static async Task<bool> TryNavigateToOrPresentItemsAsync(
this IStreamingFindUsagesPresenter presenter, string title,
ImmutableArray<DefinitionItem> items, bool alwaysShowDeclarations)
this IStreamingFindUsagesPresenter presenter,
string title, ImmutableArray<DefinitionItem> items)
{
// Ignore any definitions that we can't navigate to.
var definitions = items.WhereAsArray(d => d.CanNavigateTo());
......@@ -66,7 +66,7 @@ internal static class IStreamingFindUsagesPresenterExtensions
// We have multiple definitions, or we have definitions with multiple locations.
// Present this to the user so they can decide where they want to go to.
var context = presenter.StartSearch(title, alwaysShowDeclarations);
var context = presenter.StartSearch(title, canShowReferences: false);
foreach (var definition in nonExternalItems)
{
await context.OnDefinitionFoundAsync(definition).ConfigureAwait(false);
......
......@@ -27,60 +27,102 @@ namespace Microsoft.VisualStudio.LanguageServices.FindUsages
{
internal partial class StreamingFindUsagesPresenter
{
private class DocumentSpanEntry : Entry
private abstract class AbstractDocumentSpanEntry : Entry
{
private readonly TableDataSourceFindUsagesContext _context;
private readonly AbstractTableDataSourceFindUsagesContext _context;
private readonly DocumentSpan _documentSpan;
private readonly bool _isDefinitionLocation;
private readonly object _boxedProjectGuid;
private readonly SourceText _sourceText;
private readonly ClassifiedSpansAndHighlightSpan _classifiedSpans;
protected readonly SourceText _sourceText;
public DocumentSpanEntry(
TableDataSourceFindUsagesContext context,
protected AbstractDocumentSpanEntry(
AbstractTableDataSourceFindUsagesContext context,
RoslynDefinitionBucket definitionBucket,
DocumentSpan documentSpan,
bool isDefinitionLocation,
Guid projectGuid,
SourceText sourceText,
ClassifiedSpansAndHighlightSpan classifiedSpans)
SourceText sourceText)
: base(definitionBucket)
{
_context = context;
_documentSpan = documentSpan;
_isDefinitionLocation = isDefinitionLocation;
_boxedProjectGuid = projectGuid;
_sourceText = sourceText;
_classifiedSpans = classifiedSpans;
}
private StreamingFindUsagesPresenter Presenter => _context.Presenter;
protected StreamingFindUsagesPresenter Presenter => _context.Presenter;
private Document Document => _documentSpan.Document;
private TextSpan SourceSpan => _documentSpan.SourceSpan;
protected Document Document => _documentSpan.Document;
protected TextSpan SourceSpan => _documentSpan.SourceSpan;
protected override object GetValueWorker(string keyName)
{
switch (keyName)
{
case StandardTableKeyNames.DocumentName:
return Document.FilePath;
case StandardTableKeyNames.Line:
return _sourceText.Lines.GetLinePosition(SourceSpan.Start).Line;
case StandardTableKeyNames.Column:
return _sourceText.Lines.GetLinePosition(SourceSpan.Start).Character;
case StandardTableKeyNames.ProjectName:
return Document.Project.Name;
case StandardTableKeyNames.ProjectGuid:
return _boxedProjectGuid;
case StandardTableKeyNames.Text:
return _sourceText.Lines.GetLineFromPosition(SourceSpan.Start).ToString().Trim();
case StandardTableKeyNames.DocumentName:
return Document.FilePath;
case StandardTableKeyNames.Line:
return _sourceText.Lines.GetLinePosition(SourceSpan.Start).Line;
case StandardTableKeyNames.Column:
return _sourceText.Lines.GetLinePosition(SourceSpan.Start).Character;
case StandardTableKeyNames.ProjectName:
return Document.Project.Name;
case StandardTableKeyNames.ProjectGuid:
return _boxedProjectGuid;
case StandardTableKeyNames.Text:
return _sourceText.Lines.GetLineFromPosition(SourceSpan.Start).ToString().Trim();
}
return null;
}
}
private class DefinitionItemEntry : AbstractDocumentSpanEntry
{
public DefinitionItemEntry(
AbstractTableDataSourceFindUsagesContext context,
RoslynDefinitionBucket definitionBucket,
DocumentSpan documentSpan,
Guid projectGuid,
SourceText sourceText)
: base(context, definitionBucket, documentSpan, projectGuid, sourceText)
{
}
public override bool TryCreateColumnContent(string columnName, out FrameworkElement content)
{
if (columnName == StandardTableColumnDefinitions2.LineText)
{
var inlines = DefinitionBucket.DefinitionItem.DisplayParts.ToInlines(Presenter._typeMap);
var textBlock = inlines.ToTextBlock(Presenter._typeMap, wrap: false);
content = textBlock;
return true;
}
content = null;
return false;
}
}
private class DocumentSpanEntry : AbstractDocumentSpanEntry
{
private readonly bool _isDefinitionLocation;
private readonly ClassifiedSpansAndHighlightSpan _classifiedSpans;
public DocumentSpanEntry(
AbstractTableDataSourceFindUsagesContext context,
RoslynDefinitionBucket definitionBucket,
DocumentSpan documentSpan,
bool isDefinitionLocation,
Guid projectGuid,
SourceText sourceText,
ClassifiedSpansAndHighlightSpan classifiedSpans)
: base(context, definitionBucket, documentSpan, projectGuid, sourceText)
{
_isDefinitionLocation = isDefinitionLocation;
_classifiedSpans = classifiedSpans;
}
public override bool TryCreateColumnContent(string columnName, out FrameworkElement content)
{
......
......@@ -17,13 +17,13 @@ internal partial class StreamingFindUsagesPresenter
private class RoslynDefinitionBucket : DefinitionBucket, ISupportsNavigation
{
private readonly StreamingFindUsagesPresenter _presenter;
private readonly TableDataSourceFindUsagesContext _context;
private readonly AbstractTableDataSourceFindUsagesContext _context;
public readonly DefinitionItem DefinitionItem;
public RoslynDefinitionBucket(
StreamingFindUsagesPresenter presenter,
TableDataSourceFindUsagesContext context,
AbstractTableDataSourceFindUsagesContext context,
DefinitionItem definitionItem)
: base(name: definitionItem.DisplayParts.JoinText() + " " + definitionItem.GetHashCode(),
sourceTypeIdentifier: context.SourceTypeIdentifier,
......
......@@ -26,7 +26,7 @@ namespace Microsoft.VisualStudio.LanguageServices.FindUsages
{
internal partial class StreamingFindUsagesPresenter
{
private class TableDataSourceFindUsagesContext :
private abstract class AbstractTableDataSourceFindUsagesContext :
FindUsagesContext, ITableDataSource, ITableEntriesSnapshotFactory
{
private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
......@@ -35,10 +35,9 @@ private class TableDataSourceFindUsagesContext :
public readonly StreamingFindUsagesPresenter Presenter;
private readonly IFindAllReferencesWindow _findReferencesWindow;
private readonly IWpfTableControl2 _tableControl;
private readonly bool _alwaysIncludeDeclarations;
protected readonly IWpfTableControl2 _tableControl;
private readonly object _gate = new object();
protected readonly object _gate = new object();
#region Fields that should be locked by _gate
......@@ -49,7 +48,7 @@ private class TableDataSourceFindUsagesContext :
/// us to not display it if it has no references, and we don't run into any
/// references for it (common with implicitly declared symbols).
/// </summary>
private readonly List<DefinitionItem> _definitions = new List<DefinitionItem>();
protected readonly List<DefinitionItem> _definitions = new List<DefinitionItem>();
/// <summary>
/// We will hear about the same definition over and over again. i.e. for each reference
......@@ -70,23 +69,21 @@ private class TableDataSourceFindUsagesContext :
/// </summary>
private bool _currentlyGroupingByDefinition;
private ImmutableList<Entry> _entriesWithDeclarations = ImmutableList<Entry>.Empty;
private ImmutableList<Entry> _entriesWithoutDeclarations = ImmutableList<Entry>.Empty;
protected ImmutableList<Entry> _entriesWhenNotGroupingByDefinition = ImmutableList<Entry>.Empty;
protected ImmutableList<Entry> _entriesWhenGroupingByDefinition = ImmutableList<Entry>.Empty;
private TableEntriesSnapshot _lastSnapshot;
public int CurrentVersionNumber { get; private set; }
public int CurrentVersionNumber { get; protected set; }
#endregion
public TableDataSourceFindUsagesContext(
protected AbstractTableDataSourceFindUsagesContext(
StreamingFindUsagesPresenter presenter,
IFindAllReferencesWindow findReferencesWindow,
bool alwaysIncludeDeclarations)
IFindAllReferencesWindow findReferencesWindow)
{
presenter.AssertIsForeground();
Presenter = presenter;
_alwaysIncludeDeclarations = alwaysIncludeDeclarations;
_findReferencesWindow = findReferencesWindow;
_tableControl = (IWpfTableControl2)findReferencesWindow.TableControl;
_tableControl.GroupingsChanged += OnTableControlGroupingsChanged;
......@@ -110,6 +107,9 @@ private class TableDataSourceFindUsagesContext :
Debug.Assert(_tableDataSink != null);
}
protected void NotifyChange()
=> _tableDataSink.FactorySnapshotChanged(this);
private void OnFindReferencesWindowClosed(object sender, EventArgs e)
{
Presenter.AssertIsForeground();
......@@ -172,7 +172,7 @@ internal void OnSubscriptionDisposed()
CancelSearch();
}
public override CancellationToken CancellationToken => _cancellationTokenSource.Token;
public sealed override CancellationToken CancellationToken => _cancellationTokenSource.Token;
#region ITableDataSource
......@@ -199,293 +199,50 @@ public IDisposable Subscribe(ITableDataSink sink)
#region FindUsagesContext overrides.
public override void SetSearchTitle(string title)
public sealed override void SetSearchTitle(string title)
=> _findReferencesWindow.Title = title;
public override async Task OnCompletedAsync()
public sealed override async Task OnCompletedAsync()
{
// Now that we know the search is over, create and display any error messages
// for definitions that were not found.
await CreateMissingReferenceEntriesIfNecessaryAsync().ConfigureAwait(false);
await CreateNoResultsFoundEntryIfNecessaryAsync().ConfigureAwait(false);
await OnCompletedAsyncWorkerAsync().ConfigureAwait(false);
_tableDataSink.IsStable = true;
}
private async Task CreateNoResultsFoundEntryIfNecessaryAsync()
{
bool noDefinitions;
lock(_gate)
{
noDefinitions = this._definitions.Count == 0;
}
if (noDefinitions)
{
// Create a fake definition/reference called "search found no results"
await OnEntryFoundAsync(NoResultsDefinitionItem,
bucket => SimpleMessageEntry.CreateAsync(
bucket, ServicesVisualStudioNextResources.Search_found_no_results),
addToEntriesWithDeclarations: true,
addToEntriesWithoutDeclarations: true).ConfigureAwait(false);
}
}
private static readonly DefinitionItem NoResultsDefinitionItem =
DefinitionItem.CreateNonNavigableItem(
GlyphTags.GetTags(Glyph.StatusInformation),
ImmutableArray.Create(new TaggedText(
TextTags.Text,
ServicesVisualStudioNextResources.Search_found_no_results)));
private async Task CreateMissingReferenceEntriesIfNecessaryAsync()
{
await CreateMissingReferenceEntriesIfNecessaryAsync(withDeclarations: true).ConfigureAwait(false);
await CreateMissingReferenceEntriesIfNecessaryAsync(withDeclarations: false).ConfigureAwait(false);
}
protected abstract Task OnCompletedAsyncWorkerAsync();
private async Task CreateMissingReferenceEntriesIfNecessaryAsync(
bool withDeclarations)
{
// Go through and add dummy entries for any definitions that
// that we didn't find any references for.
var definitions = GetDefinitionsToCreateMissingReferenceItemsFor(withDeclarations);
foreach (var definition in definitions)
{
// Create a fake reference to this definition that says
// "no references found to <symbolname>".
await OnEntryFoundAsync(definition,
bucket => SimpleMessageEntry.CreateAsync(
bucket, GetMessage(bucket.DefinitionItem)),
addToEntriesWithDeclarations: withDeclarations,
addToEntriesWithoutDeclarations: !withDeclarations).ConfigureAwait(false);
}
}
private static string GetMessage(DefinitionItem definition)
{
if (definition.IsExternal)
{
return ServicesVisualStudioNextResources.External_reference_found;
}
return string.Format(
ServicesVisualStudioNextResources.No_references_found_to_0,
definition.NameDisplayParts.JoinText());
}
private ImmutableArray<DefinitionItem> GetDefinitionsToCreateMissingReferenceItemsFor(
bool withDeclarations)
{
lock (_gate)
{
var entries = withDeclarations
? _entriesWithDeclarations
: _entriesWithoutDeclarations;
// Find any definitions that we didn't have any references to. But only show
// them if they want to be displayed without any references. This will
// ensure that we still see things like overrides and whatnot, but we
// won't show property-accessors.
var seenDefinitions = entries.Select(r => r.DefinitionBucket.DefinitionItem).ToSet();
var q = from definition in _definitions
where !seenDefinitions.Contains(definition) &&
definition.DisplayIfNoReferences
select definition;
// If we find at least one of these types of definitions, then just return those.
var result = ImmutableArray.CreateRange(q);
if (result.Length > 0)
{
return result;
}
// We found no definitions that *want* to be displayed. However, we still
// want to show something. So, if necessary, show at lest the first definition
// even if we found no references and even if it would prefer to not be seen.
if (entries.Count == 0 && _definitions.Count > 0)
{
return ImmutableArray.Create(_definitions.First());
}
return ImmutableArray<DefinitionItem>.Empty;
}
}
public override async Task OnDefinitionFoundAsync(DefinitionItem definition)
public sealed override Task OnDefinitionFoundAsync(DefinitionItem definition)
{
lock (_gate)
{
_definitions.Add(definition);
}
// If this is a definition we always want to show, then create entries
// for all the declaration locations immediately. Otherwise, we'll
// create them on demand when we hear about references for this definition.
if (definition.DisplayIfNoReferences)
{
await AddDeclarationEntriesAsync(definition).ConfigureAwait(false);
}
}
private bool HasDeclarationEntries(DefinitionItem definition)
{
lock (_gate)
{
return _entriesWithDeclarations.Any(e => e.DefinitionBucket.DefinitionItem == definition);
}
}
private async Task AddDeclarationEntriesAsync(DefinitionItem definition)
{
CancellationToken.ThrowIfCancellationRequested();
// Don't do anything if we already have declaration entries for this definition
// (i.e. another thread beat us to this).
if (HasDeclarationEntries(definition))
{
return;
}
var definitionBucket = GetOrCreateDefinitionBucket(definition);
// We could do this inside the lock. but that would mean async activity in a
// lock, and i'd like to avoid that. That does mean that we might do extra
// work if multiple threads end up down htis path. But only one of them will
// win when we access the lock below.
var declarations = ArrayBuilder<Entry>.GetInstance();
foreach (var declarationLocation in definition.SourceSpans)
{
var definitionEntry = await CreateDocumentLocationEntryAsync(
definitionBucket, declarationLocation, isDefinitionLocation: true).ConfigureAwait(false);
if (definitionEntry != null)
{
declarations.Add(definitionEntry);
}
}
lock (_gate)
{
// Do one final check to ensure that no other thread beat us here.
if (!HasDeclarationEntries(definition))
{
// We only include declaration entries in the entries we show when
// not grouping by definition.
_entriesWithDeclarations = _entriesWithDeclarations.AddRange(declarations);
CurrentVersionNumber++;
}
}
declarations.Free();
// Let all our subscriptions know that we've updated.
_tableDataSink.FactorySnapshotChanged(this);
return OnDefinitionFoundWorkerAsync(definition);
}
public override Task OnReferenceFoundAsync(SourceReferenceItem reference)
{
// Normal references go into both sets of entries.
return OnEntryFoundAsync(
reference.Definition,
bucket => CreateDocumentLocationEntryAsync(
bucket, reference.SourceSpan, isDefinitionLocation: false),
addToEntriesWithDeclarations: true,
addToEntriesWithoutDeclarations: true);
}
private async Task OnEntryFoundAsync(
DefinitionItem definition,
Func<RoslynDefinitionBucket, Task<Entry>> createEntryAsync,
bool addToEntriesWithDeclarations,
bool addToEntriesWithoutDeclarations)
{
Debug.Assert(addToEntriesWithDeclarations || addToEntriesWithoutDeclarations);
CancellationToken.ThrowIfCancellationRequested();
// First find the bucket corresponding to our definition.
var definitionBucket = GetOrCreateDefinitionBucket(definition);
var entry = await createEntryAsync(definitionBucket).ConfigureAwait(false);
// Ok, we got a *reference* to some definition item. This may have been
// a reference for some definition that we haven't created any declaration
// entries for (i.e. becuase it had DisplayIfNoReferences = false). Because
// we've now found a reference, we want to make sure all its declaration
// entries are added.
await AddDeclarationEntriesAsync(definition).ConfigureAwait(false);
protected abstract Task OnDefinitionFoundWorkerAsync(DefinitionItem definition);
lock (_gate)
{
// Once we can make the new entry, add it to the appropriate list.
if (addToEntriesWithDeclarations)
{
_entriesWithDeclarations = _entriesWithDeclarations.Add(entry);
}
if (addToEntriesWithoutDeclarations)
{
_entriesWithoutDeclarations = _entriesWithoutDeclarations.Add(entry);
}
CurrentVersionNumber++;
}
// Let all our subscriptions know that we've updated.
_tableDataSink.FactorySnapshotChanged(this);
}
private async Task<Entry> CreateDocumentLocationEntryAsync(
protected async Task<Entry> CreateDocumentLocationEntryAsync(
RoslynDefinitionBucket definitionBucket,
DocumentSpan documentSpan,
bool isDefinitionLocation)
{
var document = documentSpan.Document;
var (guid, sourceText) = await GetGuidAndSourceTextAsync(document).ConfigureAwait(false);
// The FAR system needs to know the guid for the project that a def/reference is
// from (to support features like filtering). Normally that would mean we could
// only support this from a VisualStudioWorkspace. However, we want till work
// in cases lke Any-Code (which does not use a VSWorkspace). So we are tolerant
// when we have another type of workspace. This means we will show results, but
// certain features (like filtering) may not work in that context.
var workspace = document.Project.Solution.Workspace as VisualStudioWorkspaceImpl;
var guid = workspace?.GetHostProject(document.Project.Id)?.Guid ?? Guid.Empty;
var sourceText = await document.GetTextAsync(CancellationToken).ConfigureAwait(false);
var referenceSpan = documentSpan.SourceSpan;
var lineSpan = GetLineSpanForReference(sourceText, referenceSpan);
var narrowSpan = documentSpan.SourceSpan;
var lineSpan = GetLineSpanForReference(sourceText, narrowSpan);
var taggedLineParts = await GetTaggedTextForReferenceAsync(document, referenceSpan, lineSpan).ConfigureAwait(false);
var taggedLineParts = await GetTaggedTextForDocumentRegionAsync(document, narrowSpan, lineSpan).ConfigureAwait(false);
return new DocumentSpanEntry(
this, definitionBucket, documentSpan, isDefinitionLocation,
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);
}
private TextSpan GetRegionSpanForReference(SourceText sourceText, TextSpan referenceSpan)
{
const int AdditionalLineCountPerSide = 3;
var lineNumber = sourceText.Lines.GetLineFromPosition(referenceSpan.Start).LineNumber;
var firstLineNumber = Math.Max(0, lineNumber - AdditionalLineCountPerSide);
var lastLineNumber = Math.Min(sourceText.Lines.Count - 1, lineNumber + AdditionalLineCountPerSide);
return TextSpan.FromBounds(
sourceText.Lines[firstLineNumber].Start,
sourceText.Lines[lastLineNumber].End);
}
private async Task<ClassifiedSpansAndHighlightSpan> GetTaggedTextForReferenceAsync(
Document document, TextSpan referenceSpan, TextSpan widenedSpan)
private async Task<ClassifiedSpansAndHighlightSpan> GetTaggedTextForDocumentRegionAsync(
Document document, TextSpan narrowSpan, TextSpan widenedSpan)
{
var classificationService = document.GetLanguageService<IEditorClassificationService>();
if (classificationService == null)
......@@ -515,8 +272,8 @@ private TextSpan GetRegionSpanForReference(SourceText sourceText, TextSpan refer
syntaxSpans, semanticSpans, widenedSpan, sourceText);
var highlightSpan = new TextSpan(
start: referenceSpan.Start - widenedSpan.Start,
length: referenceSpan.Length);
start: narrowSpan.Start - widenedSpan.Start,
length: narrowSpan.Length);
return new ClassifiedSpansAndHighlightSpan(classifiedSpans, highlightSpan);
}
......@@ -527,6 +284,27 @@ private TextSpan GetRegionSpanForReference(SourceText sourceText, TextSpan refer
}
}
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);
}
private TextSpan GetRegionSpanForReference(SourceText sourceText, TextSpan referenceSpan)
{
const int AdditionalLineCountPerSide = 3;
var lineNumber = sourceText.Lines.GetLineFromPosition(referenceSpan.Start).LineNumber;
var firstLineNumber = Math.Max(0, lineNumber - AdditionalLineCountPerSide);
var lastLineNumber = Math.Min(sourceText.Lines.Count - 1, lineNumber + AdditionalLineCountPerSide);
return TextSpan.FromBounds(
sourceText.Lines[firstLineNumber].Start,
sourceText.Lines[lastLineNumber].End);
}
private ImmutableArray<ClassifiedSpan> MergeClassifiedSpans(
List<ClassifiedSpan> syntaxSpans, List<ClassifiedSpan> semanticSpans,
TextSpan widenedSpan, SourceText sourceText)
......@@ -668,7 +446,12 @@ private bool IsClassifiedAsText(ClassifiedSpan partAndSpan)
return partAndSpan.ClassificationType == ClassificationTypeNames.Text;
}
private RoslynDefinitionBucket GetOrCreateDefinitionBucket(DefinitionItem definition)
public sealed override Task OnReferenceFoundAsync(SourceReferenceItem reference)
=> OnReferenceFoundWorkerAsync(reference);
protected abstract Task OnReferenceFoundWorkerAsync(SourceReferenceItem reference);
protected RoslynDefinitionBucket GetOrCreateDefinitionBucket(DefinitionItem definition)
{
lock (_gate)
{
......@@ -682,7 +465,7 @@ private RoslynDefinitionBucket GetOrCreateDefinitionBucket(DefinitionItem defini
}
}
public override Task ReportProgressAsync(int current, int maximum)
public sealed override Task ReportProgressAsync(int current, int maximum)
{
// https://devdiv.visualstudio.com/web/wi.aspx?pcguid=011b8bdf-6d56-4f87-be0d-0092136884d9&id=359162
// Right now VS actually responds to each SetProgess call by enqueueing a UI task
......@@ -717,9 +500,9 @@ public ITableEntriesSnapshot GetCurrentSnapshot()
// our version.
if (_lastSnapshot?.VersionNumber != CurrentVersionNumber)
{
var entries = _currentlyGroupingByDefinition && !_alwaysIncludeDeclarations
? _entriesWithoutDeclarations
: _entriesWithDeclarations;
var entries = _currentlyGroupingByDefinition
? _entriesWhenGroupingByDefinition
: _entriesWhenNotGroupingByDefinition;
_lastSnapshot = new TableEntriesSnapshot(entries, CurrentVersionNumber);
}
......@@ -754,7 +537,356 @@ void IDisposable.Dispose()
CancelSearch();
}
protected async Task<(Guid, SourceText)> GetGuidAndSourceTextAsync(Document document) {
// The FAR system needs to know the guid for the project that a def/reference is
// from (to support features like filtering). Normally that would mean we could
// only support this from a VisualStudioWorkspace. However, we want till work
// in cases lke Any-Code (which does not use a VSWorkspace). So we are tolerant
// when we have another type of workspace. This means we will show results, but
// certain features (like filtering) may not work in that context.
var workspace = document.Project.Solution.Workspace as VisualStudioWorkspaceImpl;
var guid = workspace?.GetHostProject(document.Project.Id)?.Guid ?? Guid.Empty;
var sourceText = await document.GetTextAsync(CancellationToken).ConfigureAwait(false);
return (guid, sourceText);
}
#endregion
}
private class WithReferencesFindUsagesContext : AbstractTableDataSourceFindUsagesContext
{
public WithReferencesFindUsagesContext(
StreamingFindUsagesPresenter presenter,
IFindAllReferencesWindow findReferencesWindow)
: base(presenter, findReferencesWindow)
{
}
protected override async Task OnDefinitionFoundWorkerAsync(DefinitionItem definition)
{
// If this is a definition we always want to show, then create entries
// for all the declaration locations immediately. Otherwise, we'll
// create them on demand when we hear about references for this definition.
if (definition.DisplayIfNoReferences)
{
await AddDeclarationEntriesAsync(definition).ConfigureAwait(false);
}
}
private async Task AddDeclarationEntriesAsync(DefinitionItem definition)
{
CancellationToken.ThrowIfCancellationRequested();
// Don't do anything if we already have declaration entries for this definition
// (i.e. another thread beat us to this).
if (HasDeclarationEntries(definition))
{
return;
}
var definitionBucket = GetOrCreateDefinitionBucket(definition);
// We could do this inside the lock. but that would mean async activity in a
// lock, and i'd like to avoid that. That does mean that we might do extra
// work if multiple threads end up down htis path. But only one of them will
// win when we access the lock below.
var declarations = ArrayBuilder<Entry>.GetInstance();
foreach (var declarationLocation in definition.SourceSpans)
{
var definitionEntry = await CreateDocumentLocationEntryAsync(
definitionBucket, declarationLocation, isDefinitionLocation: true).ConfigureAwait(false);
if (definitionEntry != null)
{
declarations.Add(definitionEntry);
}
}
var changed = false;
lock (_gate)
{
// Do one final check to ensure that no other thread beat us here.
if (!HasDeclarationEntries(definition))
{
// We only include declaration entries in the entries we show when
// not grouping by definition.
_entriesWhenNotGroupingByDefinition = _entriesWhenNotGroupingByDefinition.AddRange(declarations);
CurrentVersionNumber++;
changed = true;
}
}
declarations.Free();
if (changed)
{
// Let all our subscriptions know that we've updated.
NotifyChange();
}
}
private bool HasDeclarationEntries(DefinitionItem definition)
{
lock (_gate)
{
return _entriesWhenNotGroupingByDefinition.Any(
e => e.DefinitionBucket.DefinitionItem == definition);
}
}
protected override Task OnReferenceFoundWorkerAsync(SourceReferenceItem reference)
{
// Normal references go into both sets of entries.
return OnEntryFoundAsync(
reference.Definition,
bucket => CreateDocumentLocationEntryAsync(
bucket, reference.SourceSpan, isDefinitionLocation: false),
addToEntriesWhenGroupingByDefinition: true,
addToEntriesWhenNotGroupingByDefinition: true);
}
protected async Task OnEntryFoundAsync(
DefinitionItem definition,
Func<RoslynDefinitionBucket, Task<Entry>> createEntryAsync,
bool addToEntriesWhenGroupingByDefinition,
bool addToEntriesWhenNotGroupingByDefinition)
{
Debug.Assert(addToEntriesWhenGroupingByDefinition || addToEntriesWhenNotGroupingByDefinition);
CancellationToken.ThrowIfCancellationRequested();
// Ok, we got a *reference* to some definition item. This may have been
// a reference for some definition that we haven't created any declaration
// entries for (i.e. becuase it had DisplayIfNoReferences = false). Because
// we've now found a reference, we want to make sure all its declaration
// entries are added.
await AddDeclarationEntriesAsync(definition).ConfigureAwait(false);
// First find the bucket corresponding to our definition.
var definitionBucket = GetOrCreateDefinitionBucket(definition);
var entry = await createEntryAsync(definitionBucket).ConfigureAwait(false);
lock (_gate)
{
// Once we can make the new entry, add it to the appropriate list.
if (addToEntriesWhenGroupingByDefinition)
{
_entriesWhenGroupingByDefinition = _entriesWhenGroupingByDefinition.Add(entry);
}
if (addToEntriesWhenNotGroupingByDefinition)
{
_entriesWhenNotGroupingByDefinition = _entriesWhenNotGroupingByDefinition.Add(entry);
}
CurrentVersionNumber++;
}
// Let all our subscriptions know that we've updated.
NotifyChange();
}
protected override async Task OnCompletedAsyncWorkerAsync()
{
// Now that we know the search is over, create and display any error messages
// for definitions that were not found.
await CreateMissingReferenceEntriesIfNecessaryAsync().ConfigureAwait(false);
await CreateNoResultsFoundEntryIfNecessaryAsync().ConfigureAwait(false);
}
private async Task CreateMissingReferenceEntriesIfNecessaryAsync()
{
await CreateMissingReferenceEntriesIfNecessaryAsync(whenGroupingByDefinition: true).ConfigureAwait(false);
await CreateMissingReferenceEntriesIfNecessaryAsync(whenGroupingByDefinition: false).ConfigureAwait(false);
}
private async Task CreateMissingReferenceEntriesIfNecessaryAsync(
bool whenGroupingByDefinition)
{
// Go through and add dummy entries for any definitions that
// that we didn't find any references for.
var definitions = GetDefinitionsToCreateMissingReferenceItemsFor(whenGroupingByDefinition);
foreach (var definition in definitions)
{
// Create a fake reference to this definition that says
// "no references found to <symbolname>".
await OnEntryFoundAsync(definition,
bucket => SimpleMessageEntry.CreateAsync(
bucket, GetMessage(bucket.DefinitionItem)),
addToEntriesWhenGroupingByDefinition: whenGroupingByDefinition,
addToEntriesWhenNotGroupingByDefinition: !whenGroupingByDefinition).ConfigureAwait(false);
}
}
private static string GetMessage(DefinitionItem definition)
{
if (definition.IsExternal)
{
return ServicesVisualStudioNextResources.External_reference_found;
}
return string.Format(
ServicesVisualStudioNextResources.No_references_found_to_0,
definition.NameDisplayParts.JoinText());
}
private ImmutableArray<DefinitionItem> GetDefinitionsToCreateMissingReferenceItemsFor(
bool whenGroupingByDefinition)
{
lock (_gate)
{
var entries = whenGroupingByDefinition
? _entriesWhenGroupingByDefinition
: _entriesWhenNotGroupingByDefinition;
// Find any definitions that we didn't have any references to. But only show
// them if they want to be displayed without any references. This will
// ensure that we still see things like overrides and whatnot, but we
// won't show property-accessors.
var seenDefinitions = entries.Select(r => r.DefinitionBucket.DefinitionItem).ToSet();
var q = from definition in _definitions
where !seenDefinitions.Contains(definition) &&
definition.DisplayIfNoReferences
select definition;
// If we find at least one of these types of definitions, then just return those.
var result = ImmutableArray.CreateRange(q);
if (result.Length > 0)
{
return result;
}
// We found no definitions that *want* to be displayed. However, we still
// want to show something. So, if necessary, show at lest the first definition
// even if we found no references and even if it would prefer to not be seen.
if (entries.Count == 0 && _definitions.Count > 0)
{
return ImmutableArray.Create(_definitions.First());
}
return ImmutableArray<DefinitionItem>.Empty;
}
}
private async Task CreateNoResultsFoundEntryIfNecessaryAsync()
{
bool noDefinitions;
lock (_gate)
{
noDefinitions = this._definitions.Count == 0;
}
if (noDefinitions)
{
// Create a fake definition/reference called "search found no results"
await OnEntryFoundAsync(NoResultsDefinitionItem,
bucket => SimpleMessageEntry.CreateAsync(
bucket, ServicesVisualStudioNextResources.Search_found_no_results),
addToEntriesWhenGroupingByDefinition: true,
addToEntriesWhenNotGroupingByDefinition: true).ConfigureAwait(false);
}
}
private static readonly DefinitionItem NoResultsDefinitionItem =
DefinitionItem.CreateNonNavigableItem(
GlyphTags.GetTags(Glyph.StatusInformation),
ImmutableArray.Create(new TaggedText(
TextTags.Text,
ServicesVisualStudioNextResources.Search_found_no_results)));
}
private class WithoutReferencesFindUsagesContext : AbstractTableDataSourceFindUsagesContext
{
public WithoutReferencesFindUsagesContext(
StreamingFindUsagesPresenter presenter,
IFindAllReferencesWindow findReferencesWindow)
: base(presenter, findReferencesWindow)
{
DisableGroupingByDefinition();
}
private void DisableGroupingByDefinition()
{
Presenter.AssertIsForeground();
var newColumns = ArrayBuilder<ColumnState>.GetInstance();
foreach (var columnState in _tableControl.ColumnStates)
{
var columnState2 = columnState as ColumnState2;
if (columnState?.Name == StandardTableColumnDefinitions2.Definition)
{
newColumns.Add(new ColumnState2(
columnState2.Name,
isVisible: false,
width: columnState2.Width,
sortPriority: columnState2.SortPriority,
descendingSort: columnState2.DescendingSort,
groupingPriority: 0));
}
else
{
newColumns.Add(columnState);
}
}
_tableControl.SetColumnStates(newColumns);
newColumns.Free();
}
protected override Task OnReferenceFoundWorkerAsync(SourceReferenceItem reference)
=> throw new InvalidOperationException();
protected override Task OnCompletedAsyncWorkerAsync()
=> SpecializedTasks.EmptyTask;
protected override async Task OnDefinitionFoundWorkerAsync(DefinitionItem definition)
{
var definitionBucket = GetOrCreateDefinitionBucket(definition);
var entries = ArrayBuilder<Entry>.GetInstance();
try
{
if (definition.SourceSpans.Length == 1)
{
var entry = await CreateEntryAsync(definitionBucket, definition).ConfigureAwait(false);
entries.Add(entry);
}
else
{
foreach (var sourceSpan in definition.SourceSpans)
{
var entry = await CreateDocumentLocationEntryAsync(
definitionBucket, sourceSpan, isDefinitionLocation: true).ConfigureAwait(false);
entries.Add(entry);
}
}
if (entries.Count > 0)
{
lock (_gate)
{
_entriesWhenGroupingByDefinition = _entriesWhenGroupingByDefinition.AddRange(entries);
_entriesWhenNotGroupingByDefinition = _entriesWhenNotGroupingByDefinition.AddRange(entries);
}
this.NotifyChange();
}
}
finally
{
entries.Free();
}
}
private async Task<Entry> CreateEntryAsync(
RoslynDefinitionBucket definitionBucket, DefinitionItem definition)
{
var documentSpan = definition.SourceSpans[0];
var (guid, sourceText) = await GetGuidAndSourceTextAsync(documentSpan.Document).ConfigureAwait(false);
return new DefinitionItemEntry(this, definitionBucket, documentSpan, guid, sourceText);
}
}
}
}
\ No newline at end of file
......@@ -60,7 +60,7 @@ internal partial class StreamingFindUsagesPresenter :
_vsFindAllReferencesService = (IFindAllReferencesService)_serviceProvider.GetService(typeof(SVsFindAllReferences));
}
public FindUsagesContext StartSearch(string title, bool alwaysShowDeclarations)
public FindUsagesContext StartSearch(string title, bool canShowReferences)
{
this.AssertIsForeground();
......@@ -68,8 +68,10 @@ public FindUsagesContext StartSearch(string title, bool alwaysShowDeclarations)
var window = _vsFindAllReferencesService.StartSearch(title);
// Make the data source that will feed data into this window.
var dataSource = new TableDataSourceFindUsagesContext(
this, window, alwaysShowDeclarations);
var dataSource = canShowReferences
? (AbstractTableDataSourceFindUsagesContext)new WithReferencesFindUsagesContext(this, window)
: new WithoutReferencesFindUsagesContext(this, window);
// And return the data source so that the FindRefs engine can report results
// which the data source can then create the appropriate presentation items for
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册