File: FindReferences\RoslynDefinitionBucket.cs
Web Access
Project: src\src\VisualStudio\Core\Def\Microsoft.VisualStudio.LanguageServices_pxr0p0dn_wpftmp.csproj (Microsoft.VisualStudio.LanguageServices)
// 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);
            }
        }
    }
}