提交 38a7837e 编写于 作者: C CyrusNajmabadi

Disable 'definition grouping' when doing a FindImplementations/GoToDef call.

上级 799b963c
// 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.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Completion;
using Microsoft.CodeAnalysis.FindUsages;
using Microsoft.VisualStudio.Shell.FindAllReferences;
using Roslyn.Utilities;
namespace Microsoft.VisualStudio.LanguageServices.FindUsages
{
internal 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 CreateDocumentSpanEntryAsync(
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 => CreateDocumentSpanEntryAsync(
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)));
}
}
\ 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.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.FindUsages;
using Microsoft.VisualStudio.Shell.FindAllReferences;
using Roslyn.Utilities;
namespace Microsoft.VisualStudio.LanguageServices.FindUsages
{
internal class WithoutReferencesFindUsagesContext : AbstractTableDataSourceFindUsagesContext
{
public WithoutReferencesFindUsagesContext(
StreamingFindUsagesPresenter presenter,
IFindAllReferencesWindow findReferencesWindow)
: base(presenter, findReferencesWindow)
{
}
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)
{
// If we only have a single location, then use the DisplayParts of the
// definition as what to show. That way we show enough information for things
// methods. i.e. we'll show "void TypeName.MethodName(args...)" allowing
// the user to see the type the method was created in.
var entry = await CreateEntryAsync(definitionBucket, definition).ConfigureAwait(false);
entries.Add(entry);
}
else
{
// If we have multiple spans (i.e. for partial types), then create a
// DocumentSpanEntry for each. That way we can easily see the source
// code where each location is to help the user decide which they want
// to navigate to.
foreach (var sourceSpan in definition.SourceSpans)
{
var entry = await CreateDocumentSpanEntryAsync(
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
// 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 Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.Shell.TableManager;
namespace Microsoft.VisualStudio.LanguageServices.FindUsages
{
internal abstract class AbstractDocumentSpanEntry : Entry
{
private readonly AbstractTableDataSourceFindUsagesContext _context;
private readonly DocumentSpan _documentSpan;
private readonly object _boxedProjectGuid;
protected readonly SourceText _sourceText;
protected AbstractDocumentSpanEntry(
AbstractTableDataSourceFindUsagesContext context,
RoslynDefinitionBucket definitionBucket,
DocumentSpan documentSpan,
Guid projectGuid,
SourceText sourceText)
: base(definitionBucket)
{
_context = context;
_documentSpan = documentSpan;
_boxedProjectGuid = projectGuid;
_sourceText = sourceText;
}
protected StreamingFindUsagesPresenter Presenter => _context.Presenter;
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();
}
return null;
}
}
}
\ 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.Windows;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Editor.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.Shell.TableControl;
namespace Microsoft.VisualStudio.LanguageServices.FindUsages
{
internal 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;
}
}
}
\ 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.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Classification;
using Microsoft.CodeAnalysis.Editor;
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.Text;
using Microsoft.CodeAnalysis.Text.Shared.Extensions;
using Microsoft.VisualStudio.LanguageServices.Implementation.Extensions;
using Microsoft.VisualStudio.PlatformUI;
using Microsoft.VisualStudio.Shell.TableControl;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Editor;
namespace Microsoft.VisualStudio.LanguageServices.FindUsages
{
internal 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)
{
if (columnName == StandardTableColumnDefinitions2.LineText)
{
var inlines = GetHighlightedInlines(Presenter, _sourceText, _classifiedSpans, _isDefinitionLocation);
var textBlock = inlines.ToTextBlock(Presenter.TypeMap, wrap: false);
LazyToolTip.AttachTo(textBlock, CreateDisposableToolTip);
content = textBlock;
return true;
}
content = null;
return false;
}
private static IList<System.Windows.Documents.Inline> GetHighlightedInlines(
StreamingFindUsagesPresenter presenter,
SourceText sourceText,
ClassifiedSpansAndHighlightSpan classifiedSpansAndHighlight,
bool isDefinition)
{
var propertyId = isDefinition
? DefinitionHighlightTag.TagId
: ReferenceHighlightTag.TagId;
var properties = presenter.FormatMapService
.GetEditorFormatMap("text")
.GetProperties(propertyId);
var highlightBrush = properties["Background"] as Brush;
var classifiedSpans = classifiedSpansAndHighlight.ClassifiedSpans;
var classifiedTexts = classifiedSpans.SelectAsArray(
cs => new ClassifiedText(cs.ClassificationType, sourceText.ToString(cs.TextSpan)));
var inlines = classifiedTexts.ToInlines(
presenter.TypeMap,
runCallback: (run, classifiedText, position) =>
{
if (highlightBrush != null)
{
if (position == classifiedSpansAndHighlight.HighlightSpan.Start)
{
run.SetValue(
System.Windows.Documents.TextElement.BackgroundProperty,
highlightBrush);
}
}
});
return inlines;
}
private DisposableToolTip CreateDisposableToolTip()
{
Presenter.AssertIsForeground();
// Create a new buffer that we'll show a preview for. We can't search for an
// existing buffer because:
// 1. the file may not be open.
// 2. our results may not be in sync with what's actually in the editor.
var textBuffer = CreateNewBuffer();
// Create the actual tooltip around the region of that text buffer we want to show.
var toolTip = new ToolTip
{
Content = CreateToolTipContent(textBuffer),
Background = (Brush)Application.Current.Resources[EnvironmentColors.ToolWindowBackgroundBrushKey]
};
// Create a preview workspace for this text buffer and open it's corresponding
// document. That way we'll get nice things like classification as well as the
// reference highlight span.
var newDocument = Document.WithText(textBuffer.AsTextContainer().CurrentText);
var workspace = new PreviewWorkspace(newDocument.Project.Solution);
workspace.OpenDocument(newDocument.Id);
return new DisposableToolTip(toolTip, workspace);
}
private ContentControl CreateToolTipContent(ITextBuffer textBuffer)
{
var regionSpan = this.GetRegionSpanForReference();
var snapshotSpan = textBuffer.CurrentSnapshot.GetSpan(regionSpan);
var contentType = Presenter.ContentTypeRegistryService.GetContentType(
IProjectionBufferFactoryServiceExtensions.RoslynPreviewContentType);
var roleSet = Presenter.TextEditorFactoryService.CreateTextViewRoleSet(
TextViewRoles.PreviewRole,
PredefinedTextViewRoles.Analyzable,
PredefinedTextViewRoles.Document,
PredefinedTextViewRoles.Editable);
var content = new ElisionBufferDeferredContent(
snapshotSpan,
Presenter.ProjectionBufferFactoryService,
Presenter.EditorOptionsFactoryService,
Presenter.TextEditorFactoryService,
contentType,
roleSet);
var element = content.Create();
return element;
}
private ITextBuffer CreateNewBuffer()
{
Presenter.AssertIsForeground();
// is it okay to create buffer from threads other than UI thread?
var contentTypeService = Document.Project.LanguageServices.GetService<IContentTypeLanguageService>();
var contentType = contentTypeService.GetDefaultContentType();
var textBuffer = Presenter.TextBufferFactoryService.CreateTextBuffer(
_sourceText.ToString(), contentType);
// Create an appropriate highlight span on that buffer for the reference.
var key = _isDefinitionLocation
? PredefinedPreviewTaggerKeys.DefinitionHighlightingSpansKey
: PredefinedPreviewTaggerKeys.ReferenceHighlightingSpansKey;
textBuffer.Properties.RemoveProperty(key);
textBuffer.Properties.AddProperty(key, new NormalizedSnapshotSpanCollection(
SourceSpan.ToSnapshotSpan(textBuffer.CurrentSnapshot)));
return textBuffer;
}
private Span GetRegionSpanForReference()
{
const int AdditionalLineCountPerSide = 3;
var referenceSpan = this.SourceSpan;
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 Span.FromBounds(
_sourceText.Lines[firstLineNumber].Start,
_sourceText.Lines[lastLineNumber].End);
}
}
}
\ No newline at end of file
......@@ -7,12 +7,10 @@
namespace Microsoft.VisualStudio.LanguageServices.FindUsages
{
internal partial class StreamingFindUsagesPresenter
{
/// <summary>
/// Represents a single entry (i.e. row) in the ungrouped FAR table.
/// </summary>
private abstract class Entry
internal abstract class Entry
{
public readonly RoslynDefinitionBucket DefinitionBucket;
......@@ -31,11 +29,11 @@ private object GetValue(string keyName)
{
switch (keyName)
{
case StandardTableKeyNames2.Definition:
return DefinitionBucket;
case StandardTableKeyNames2.Definition:
return DefinitionBucket;
case StandardTableKeyNames2.DefinitionIcon:
return DefinitionBucket.DefinitionItem.Tags.GetGlyph().GetImageMoniker();
case StandardTableKeyNames2.DefinitionIcon:
return DefinitionBucket.DefinitionItem.Tags.GetGlyph().GetImageMoniker();
}
return GetValueWorker(keyName);
......@@ -49,5 +47,4 @@ public virtual bool TryCreateColumnContent(string columnName, out FrameworkEleme
return false;
}
}
}
}
\ No newline at end of file
......@@ -7,9 +7,7 @@
namespace Microsoft.VisualStudio.LanguageServices.FindUsages
{
internal partial class StreamingFindUsagesPresenter
{
private class SimpleMessageEntry : Entry
internal class SimpleMessageEntry : Entry
{
private readonly string _message;
......@@ -43,11 +41,10 @@ protected override object GetValueWorker(string keyName)
case StandardTableKeyNames.ProjectName:
return this.TryGetDocument()?.Project.Name;
case StandardTableKeyNames.Text:
return _message;
return _message;
}
return null;
}
}
}
}
\ No newline at end of file
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Options;
namespace Microsoft.VisualStudio.LanguageServices.FindUsages
{
internal static class FindUsagesOptions
{
private const string LocalRegistryPath = @"Roslyn\Internal\FindUsages\";
/// <summary>
/// Used to store the user's explicit 'grouping priority' for the 'Definition' column.
/// We store this because we'll disable this grouping sometimes (i.e. for GoToImplementation),
/// and we want to restore the value back to its original state when the user does the
/// next FindReferences call.
/// </summary>
[ExportOption]
public static readonly Option<int> DefinitionGroupingPriority = new Option<int>(
nameof(FindUsagesOptions), nameof(DefinitionGroupingPriority), defaultValue: -1,
storageLocations: new LocalUserProfileStorageLocation(LocalRegistryPath + nameof(DefinitionGroupingPriority)));
}
}
\ 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.Linq;
using Microsoft.VisualStudio.Shell.FindAllReferences;
using Microsoft.VisualStudio.Shell.TableControl;
namespace Microsoft.VisualStudio.LanguageServices.FindUsages
{
internal static class IFindAllReferencesWindowExtensions
{
public static ColumnState2 GetDefinitionColumn(this IFindAllReferencesWindow window)
{
return window.TableControl.ColumnStates.FirstOrDefault(
s => s.Name == StandardTableColumnDefinitions2.Definition) as ColumnState2;
}
}
}
\ No newline at end of file
......@@ -12,9 +12,7 @@
namespace Microsoft.VisualStudio.LanguageServices.FindUsages
{
internal partial class StreamingFindUsagesPresenter
{
private class RoslynDefinitionBucket : DefinitionBucket, ISupportsNavigation
internal class RoslynDefinitionBucket : DefinitionBucket, ISupportsNavigation
{
private readonly StreamingFindUsagesPresenter _presenter;
private readonly AbstractTableDataSourceFindUsagesContext _context;
......@@ -49,25 +47,24 @@ private object GetValue(string key)
{
switch (key)
{
case StandardTableKeyNames.Text:
case StandardTableKeyNames.FullText:
return DefinitionItem.DisplayParts.JoinText();
case StandardTableKeyNames.Text:
case StandardTableKeyNames.FullText:
return DefinitionItem.DisplayParts.JoinText();
case StandardTableKeyNames2.TextInlines:
var inlines = new List<Inline> { new Run(" ") };
inlines.AddRange(DefinitionItem.DisplayParts.ToInlines(_presenter._typeMap));
foreach (var inline in inlines)
{
inline.SetValue(TextElement.FontWeightProperty, FontWeights.Bold);
}
return inlines;
case StandardTableKeyNames2.TextInlines:
var inlines = new List<Inline> { new Run(" ") };
inlines.AddRange(DefinitionItem.DisplayParts.ToInlines(_presenter.TypeMap));
foreach (var inline in inlines)
{
inline.SetValue(TextElement.FontWeightProperty, FontWeights.Bold);
}
return inlines;
case StandardTableKeyNames2.DefinitionIcon:
return DefinitionItem.Tags.GetGlyph().GetImageMoniker();
case StandardTableKeyNames2.DefinitionIcon:
return DefinitionItem.Tags.GetGlyph().GetImageMoniker();
}
return null;
}
}
}
}
\ 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.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Classification;
using Microsoft.CodeAnalysis.Editor;
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.Text;
using Microsoft.CodeAnalysis.Text.Shared.Extensions;
using Microsoft.VisualStudio.LanguageServices.Implementation.Extensions;
using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem;
using Microsoft.VisualStudio.PlatformUI;
using Microsoft.VisualStudio.Shell.TableControl;
using Microsoft.VisualStudio.Shell.TableManager;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Editor;
using Roslyn.Utilities;
namespace Microsoft.VisualStudio.LanguageServices.FindUsages
{
internal partial class StreamingFindUsagesPresenter
{
private abstract class AbstractDocumentSpanEntry : Entry
{
private readonly AbstractTableDataSourceFindUsagesContext _context;
private readonly DocumentSpan _documentSpan;
private readonly object _boxedProjectGuid;
protected readonly SourceText _sourceText;
protected AbstractDocumentSpanEntry(
AbstractTableDataSourceFindUsagesContext context,
RoslynDefinitionBucket definitionBucket,
DocumentSpan documentSpan,
Guid projectGuid,
SourceText sourceText)
: base(definitionBucket)
{
_context = context;
_documentSpan = documentSpan;
_boxedProjectGuid = projectGuid;
_sourceText = sourceText;
}
protected StreamingFindUsagesPresenter Presenter => _context.Presenter;
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();
}
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)
{
if (columnName == StandardTableColumnDefinitions2.LineText)
{
var inlines = GetHighlightedInlines(Presenter, _sourceText, _classifiedSpans, _isDefinitionLocation);
var textBlock = inlines.ToTextBlock(Presenter._typeMap, wrap: false);
LazyToolTip.AttachTo(textBlock, CreateDisposableToolTip);
content = textBlock;
return true;
}
content = null;
return false;
}
private static IList<System.Windows.Documents.Inline> GetHighlightedInlines(
StreamingFindUsagesPresenter presenter,
SourceText sourceText,
ClassifiedSpansAndHighlightSpan classifiedSpansAndHighlight,
bool isDefinition)
{
var propertyId = isDefinition
? DefinitionHighlightTag.TagId
: ReferenceHighlightTag.TagId;
var properties = presenter._formatMapService
.GetEditorFormatMap("text")
.GetProperties(propertyId);
var highlightBrush = properties["Background"] as Brush;
var classifiedSpans = classifiedSpansAndHighlight.ClassifiedSpans;
var classifiedTexts = classifiedSpans.SelectAsArray(
cs => new ClassifiedText(cs.ClassificationType, sourceText.ToString(cs.TextSpan)));
var inlines = classifiedTexts.ToInlines(
presenter._typeMap,
runCallback: (run, classifiedText, position) =>
{
if (highlightBrush != null)
{
if (position == classifiedSpansAndHighlight.HighlightSpan.Start)
{
run.SetValue(
System.Windows.Documents.TextElement.BackgroundProperty,
highlightBrush);
}
}
});
return inlines;
}
private DisposableToolTip CreateDisposableToolTip()
{
Presenter.AssertIsForeground();
// Create a new buffer that we'll show a preview for. We can't search for an
// existing buffer because:
// 1. the file may not be open.
// 2. our results may not be in sync with what's actually in the editor.
var textBuffer = CreateNewBuffer();
// Create the actual tooltip around the region of that text buffer we want to show.
var toolTip = new ToolTip
{
Content = CreateToolTipContent(textBuffer),
Background = (Brush)Application.Current.Resources[EnvironmentColors.ToolWindowBackgroundBrushKey]
};
// Create a preview workspace for this text buffer and open it's corresponding
// document. That way we'll get nice things like classification as well as the
// reference highlight span.
var newDocument = Document.WithText(textBuffer.AsTextContainer().CurrentText);
var workspace = new PreviewWorkspace(newDocument.Project.Solution);
workspace.OpenDocument(newDocument.Id);
return new DisposableToolTip(toolTip, workspace);
}
private ContentControl CreateToolTipContent(ITextBuffer textBuffer)
{
var regionSpan = this.GetRegionSpanForReference();
var snapshotSpan = textBuffer.CurrentSnapshot.GetSpan(regionSpan);
var contentType = Presenter._contentTypeRegistryService.GetContentType(
IProjectionBufferFactoryServiceExtensions.RoslynPreviewContentType);
var roleSet = Presenter._textEditorFactoryService.CreateTextViewRoleSet(
TextViewRoles.PreviewRole,
PredefinedTextViewRoles.Analyzable,
PredefinedTextViewRoles.Document,
PredefinedTextViewRoles.Editable);
var content = new ElisionBufferDeferredContent(
snapshotSpan,
Presenter._projectionBufferFactoryService,
Presenter._editorOptionsFactoryService,
Presenter._textEditorFactoryService,
contentType,
roleSet);
var element = content.Create();
return element;
}
private ITextBuffer CreateNewBuffer()
{
Presenter.AssertIsForeground();
// is it okay to create buffer from threads other than UI thread?
var contentTypeService = Document.Project.LanguageServices.GetService<IContentTypeLanguageService>();
var contentType = contentTypeService.GetDefaultContentType();
var textBuffer = Presenter._textBufferFactoryService.CreateTextBuffer(
_sourceText.ToString(), contentType);
// Create an appropriate highlight span on that buffer for the reference.
var key = _isDefinitionLocation
? PredefinedPreviewTaggerKeys.DefinitionHighlightingSpansKey
: PredefinedPreviewTaggerKeys.ReferenceHighlightingSpansKey;
textBuffer.Properties.RemoveProperty(key);
textBuffer.Properties.AddProperty(key, new NormalizedSnapshotSpanCollection(
SourceSpan.ToSnapshotSpan(textBuffer.CurrentSnapshot)));
return textBuffer;
}
private Span GetRegionSpanForReference()
{
const int AdditionalLineCountPerSide = 3;
var referenceSpan = this.SourceSpan;
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 Span.FromBounds(
_sourceText.Lines[firstLineNumber].Start,
_sourceText.Lines[lastLineNumber].End);
}
}
}
}
\ No newline at end of file
......@@ -2,10 +2,12 @@
using System;
using System.Composition;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Editor.Host;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.FindUsages;
using Microsoft.VisualStudio.Shell.FindAllReferences;
using Microsoft.VisualStudio.Shell.TableControl;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Classification;
using Microsoft.VisualStudio.Text.Editor;
......@@ -26,18 +28,20 @@ internal partial class StreamingFindUsagesPresenter :
private readonly IServiceProvider _serviceProvider;
private readonly ITextBufferFactoryService _textBufferFactoryService;
private readonly IProjectionBufferFactoryService _projectionBufferFactoryService;
private readonly IEditorOptionsFactoryService _editorOptionsFactoryService;
private readonly ITextEditorFactoryService _textEditorFactoryService;
private readonly IContentTypeRegistryService _contentTypeRegistryService;
private readonly ClassificationTypeMap _typeMap;
private readonly IEditorFormatMapService _formatMapService;
private readonly IFindAllReferencesService _vsFindAllReferencesService;
private readonly VisualStudioWorkspace _workspace;
public readonly ITextBufferFactoryService TextBufferFactoryService;
public readonly IProjectionBufferFactoryService ProjectionBufferFactoryService;
public readonly IEditorOptionsFactoryService EditorOptionsFactoryService;
public readonly ITextEditorFactoryService TextEditorFactoryService;
public readonly IContentTypeRegistryService ContentTypeRegistryService;
public readonly IEditorFormatMapService FormatMapService;
public readonly ClassificationTypeMap TypeMap;
[ImportingConstructor]
public StreamingFindUsagesPresenter(
VisualStudioWorkspace workspace,
Shell.SVsServiceProvider serviceProvider,
ITextBufferFactoryService textBufferFactoryService,
IProjectionBufferFactoryService projectionBufferFactoryService,
......@@ -47,15 +51,16 @@ internal partial class StreamingFindUsagesPresenter :
ClassificationTypeMap typeMap,
IEditorFormatMapService formatMapService)
{
_workspace = workspace;
_serviceProvider = serviceProvider;
_textBufferFactoryService = textBufferFactoryService;
_projectionBufferFactoryService = projectionBufferFactoryService;
_editorOptionsFactoryService = editorOptionsFactoryService;
_contentTypeRegistryService = contentTypeRegistryService;
TextBufferFactoryService = textBufferFactoryService;
ProjectionBufferFactoryService = projectionBufferFactoryService;
EditorOptionsFactoryService = editorOptionsFactoryService;
ContentTypeRegistryService = contentTypeRegistryService;
_textEditorFactoryService = textEditorFactoryService;
_typeMap = typeMap;
_formatMapService = formatMapService;
TextEditorFactoryService = textEditorFactoryService;
TypeMap = typeMap;
FormatMapService = formatMapService;
_vsFindAllReferencesService = (IFindAllReferencesService)_serviceProvider.GetService(typeof(SVsFindAllReferences));
}
......@@ -67,16 +72,83 @@ public FindUsagesContext StartSearch(string title, bool canShowReferences)
// Get the appropriate window for FAR results to go into.
var window = _vsFindAllReferencesService.StartSearch(title);
// Make the data source that will feed data into this window.
// Keep track of the users preference for grouping by definition if we don't already know it.
// We need this because we disable the Definition column when wer're not showing references
// (i.e. GoToImplementation/GoToDef). However, we want to restore the user's choice if they
// then do another FindAllReferences.
var desiredGroupingPriority = _workspace.Options.GetOption(FindUsagesOptions.DefinitionGroupingPriority);
if (desiredGroupingPriority < 0)
{
StoreCurrentGroupingPriority(window);
}
return canShowReferences
? StartSearchWithReferences(window, desiredGroupingPriority)
: StartSearchWithoutReferences(window);
}
private FindUsagesContext StartSearchWithReferences(IFindAllReferencesWindow window, int desiredGroupingPriority)
{
// Ensure that the window's definition-grouping reflects what the user wants.
// i.e. we may have disabled this column for a previous GoToImplementation call.
// We want to still show the column as long as the user has not disabled it themselves.
var definitionColumn = window.GetDefinitionColumn();
if (definitionColumn.GroupingPriority != desiredGroupingPriority)
{
SetDefinitionGroupingPriority(window, desiredGroupingPriority);
}
// If the user changes the grouping, then store their current preference.
var tableControl = (IWpfTableControl2)window.TableControl;
tableControl.GroupingsChanged += (s, e) => StoreCurrentGroupingPriority(window);
return new WithReferencesFindUsagesContext(this, window);
}
private FindUsagesContext StartSearchWithoutReferences(IFindAllReferencesWindow window)
{
// If we're not showing references, then disable grouping by definition, as that will
// just lead to a poor experience. i.e. we'll have the definition entry buckets,
// with the same items showing underneath them.
SetDefinitionGroupingPriority(window, 0);
return new WithoutReferencesFindUsagesContext(this, window);
}
private void StoreCurrentGroupingPriority(IFindAllReferencesWindow window)
{
var definitionColumn = window.GetDefinitionColumn();
_workspace.Options = _workspace.Options.WithChangedOption(
FindUsagesOptions.DefinitionGroupingPriority, definitionColumn.GroupingPriority);
}
private void SetDefinitionGroupingPriority(IFindAllReferencesWindow window, int priority)
{
this.AssertIsForeground();
var newColumns = ArrayBuilder<ColumnState>.GetInstance();
var tableControl = (IWpfTableControl2)window.TableControl;
var dataSource = canShowReferences
? (AbstractTableDataSourceFindUsagesContext)new WithReferencesFindUsagesContext(this, window)
: new WithoutReferencesFindUsagesContext(this, window);
foreach (var columnState in window.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: priority));
}
else
{
newColumns.Add(columnState);
}
}
// 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
// for the window.
return dataSource;
tableControl.SetColumnStates(newColumns);
newColumns.Free();
}
}
}
\ No newline at end of file
......@@ -7,9 +7,7 @@
namespace Microsoft.VisualStudio.LanguageServices.FindUsages
{
internal partial class StreamingFindUsagesPresenter
{
private class TableEntriesSnapshot : WpfTableEntriesSnapshotBase
internal class TableEntriesSnapshot : WpfTableEntriesSnapshotBase
{
private readonly int _versionNumber;
......@@ -43,5 +41,4 @@ public override bool TryGetValue(int index, string keyName, out object content)
return this._entries[index].TryCreateColumnContent(columnName, out content);
}
}
}
}
\ 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.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Controls;
using Microsoft.CodeAnalysis.Editor.Shared.Preview;
namespace Microsoft.VisualStudio.LanguageServices.FindUsages
{
internal partial class StreamingFindUsagesPresenter
{
private class DisposableToolTip : IDisposable
internal class DisposableToolTip : IDisposable
{
public readonly ToolTip ToolTip;
private PreviewWorkspace _workspace;
......@@ -34,5 +28,4 @@ public void Dispose()
_workspace = null;
}
}
}
}
\ No newline at end of file
......@@ -2,19 +2,15 @@
using System;
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
namespace Microsoft.VisualStudio.LanguageServices.FindUsages
{
internal partial class StreamingFindUsagesPresenter
{
/// <summary>
/// Class which allows us to provide a delay-created tooltip for our reference entries.
/// </summary>
private class LazyToolTip
internal class LazyToolTip
{
private readonly ForegroundThreadAffinitizedObject _foregroundObject = new ForegroundThreadAffinitizedObject();
private readonly Func<DisposableToolTip> _createToolTip;
......@@ -72,5 +68,4 @@ private void OnToolTipClosing(object sender, ToolTipEventArgs e)
_disposableToolTip = null;
}
}
}
}
\ No newline at end of file
......@@ -80,18 +80,24 @@
<Link>Shared\ServerDirectStream.cs</Link>
</Compile>
<Compile Include="CodeLens\RemoteCodeLensReferencesService.cs" />
<Compile Include="FindReferences\Contexts\AbstractTableDataSourceFindUsagesContext.cs" />
<Compile Include="FindReferences\Contexts\WithoutReferencesFindUsagesContext.cs" />
<Compile Include="FindReferences\Contexts\WithReferencesFindUsagesContext.cs" />
<Compile Include="FindReferences\Entries\AbstractDocumentSpanEntry.cs" />
<Compile Include="FindReferences\Entries\DefinitionItemEntry.cs" />
<Compile Include="FindReferences\Entries\DocumentSpanEntry.cs" />
<Compile Include="FindReferences\Entries\Entry.cs" />
<Compile Include="FindReferences\Entries\SimpleMessageEntry.cs" />
<Compile Include="FindReferences\FindReferencesTableControlEventProcessorProvider.cs" />
<Compile Include="FindReferences\FindUsagesOptions.cs" />
<Compile Include="FindReferences\IFindAllReferencesWindowExtensions.cs" />
<Compile Include="FindReferences\ISupportsNavigation.cs" />
<Compile Include="FindReferences\RoslynDefinitionBucket.cs" />
<Compile Include="FindReferences\StreamingFindUsagesPresenter.cs" />
<Compile Include="FindReferences\StreamingFindUsagesPresenter.DisposableToolTip.cs" />
<Compile Include="FindReferences\StreamingFindUsagesPresenter.DocumentLocationEntry.cs" />
<Compile Include="FindReferences\StreamingFindUsagesPresenter.Entry.cs" />
<Compile Include="FindReferences\StreamingFindUsagesPresenter.LazyToolTip.cs" />
<Compile Include="FindReferences\StreamingFindUsagesPresenter.RoslynDefinitionBucket.cs" />
<Compile Include="FindReferences\StreamingFindUsagesPresenter.SimpleMessageEntry.cs" />
<Compile Include="FindReferences\StreamingFindUsagesPresenter.TableDataSourceFindUsagesContext.cs" />
<Compile Include="FindReferences\StreamingFindUsagesPresenter.TableEntriesSnapshot.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" />
<Compile Include="Remote\JsonRpcSession.cs" />
......@@ -117,4 +123,4 @@
</ItemGroup>
<ItemGroup />
<Import Project="..\..\..\..\build\Targets\Imports.targets" />
</Project>
</Project>
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册