|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Documents;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Editor.Shared.Extensions;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.Editor.Wpf;
using Microsoft.CodeAnalysis.FindUsages;
using Microsoft.CodeAnalysis.Navigation;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.Shell.FindAllReferences;
using Microsoft.VisualStudio.Shell.TableControl;
using Microsoft.VisualStudio.Shell.TableManager;
namespace Microsoft.VisualStudio.LanguageServices.FindUsages;
internal partial class StreamingFindUsagesPresenter
{
private class RoslynDefinitionBucket : DefinitionBucket, ISupportsNavigation
{
private readonly StreamingFindUsagesPresenter _presenter;
public readonly DefinitionItem DefinitionItem;
private readonly IThreadingContext _threadingContext;
/// <summary>
/// Due to linked files, we may have results for several locations that are all effectively
/// the same file/span. So we represent this as one entry with several project flavors. If
/// we get more than one flavor, we'll show that the user in the UI.
/// </summary>
private readonly Dictionary<(string? filePath, TextSpan span), DocumentSpanEntry> _locationToEntry = [];
private string? _text;
public RoslynDefinitionBucket(
string name,
bool expandedByDefault,
StreamingFindUsagesPresenter presenter,
AbstractTableDataSourceFindUsagesContext context,
DefinitionItem definitionItem,
IThreadingContext threadingContext)
: base(name,
sourceTypeIdentifier: context.SourceTypeIdentifier,
identifier: context.Identifier,
expandedByDefault: expandedByDefault)
{
_presenter = presenter;
DefinitionItem = definitionItem;
_threadingContext = threadingContext;
}
public static RoslynDefinitionBucket Create(
StreamingFindUsagesPresenter presenter,
AbstractTableDataSourceFindUsagesContext context,
DefinitionItem definitionItem,
bool expandedByDefault,
IThreadingContext threadingContext)
{
var isPrimary = definitionItem.Properties.ContainsKey(DefinitionItem.Primary);
// Sort the primary item above everything else.
var name = $"{(isPrimary ? 0 : 1)} {definitionItem.DisplayParts.JoinText()} {definitionItem.GetHashCode()}";
return new RoslynDefinitionBucket(
name, expandedByDefault, presenter, context, definitionItem, threadingContext);
}
public bool CanNavigateTo()
=> true;
public async Task NavigateToAsync(NavigationOptions options, CancellationToken cancellationToken)
{
var location = await DefinitionItem.GetNavigableLocationAsync(
_presenter._workspace, cancellationToken).ConfigureAwait(false);
await location.TryNavigateToAsync(_threadingContext, options, cancellationToken).ConfigureAwait(false);
}
public override bool TryGetValue(string key, out object? content)
{
content = GetValue(key);
return content != null;
}
/// <summary>
/// The editor is presenting 'Text' while telling the screen reader to use the 'Name' field.
/// Workaround this bug by overriding the string content to provide the proper data for the screen reader.
/// https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1020534/
/// </summary>
public override bool TryCreateStringContent(out string? content)
{
if (TryGetValue(StandardTableKeyNames.Text, out var contentValue) && contentValue is string textContent)
{
content = textContent;
return true;
}
content = null;
return false;
}
private object? GetValue(string key)
{
switch (key)
{
case StandardTableKeyNames.Text:
case StandardTableKeyNames.FullText:
return _text ??= DefinitionItem.DisplayParts.JoinText();
case StandardTableKeyNames2.TextInlines:
var inlines = new List<Inline> { new Run(" ") };
inlines.AddRange(DefinitionItem.DisplayParts.ToInlines(_presenter.ClassificationFormatMap, _presenter.TypeMap));
foreach (var inline in inlines)
{
inline.SetValue(TextElement.FontWeightProperty, FontWeights.Bold);
}
return inlines;
case StandardTableKeyNames2.DefinitionIcon:
return DefinitionItem.Tags.GetFirstGlyph().GetImageMoniker();
}
return null;
}
public DocumentSpanEntry GetOrAddEntry(string? filePath, TextSpan sourceSpan, DocumentSpanEntry entry)
{
var key = (filePath, sourceSpan);
lock (_locationToEntry)
{
return _locationToEntry.GetOrAdd(key, entry);
}
}
}
}
|