File: FindReferences\Entries\AbstractDocumentSpanEntry.cs
Web Access
Project: src\src\VisualStudio\Core\Def\Microsoft.VisualStudio.LanguageServices_4r0c5m1y_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;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Navigation;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.Shell.TableManager;
using Roslyn.Utilities;
 
namespace Microsoft.VisualStudio.LanguageServices.FindUsages;
 
internal partial class StreamingFindUsagesPresenter
{
    /// <summary>
    /// Base type of all <see cref="Entry"/>s that represent some source location in a <see cref="Document"/>.
    /// Navigation to that location is provided by this type. Subclasses can be used to provide customized line text to
    /// display in the entry.
    /// 
    /// Implements navigation since the target document doesn't necessarily exist on disk, and only Roslyn knows how to
    /// navigate to it.
    /// </summary>
    private abstract class AbstractDocumentSpanEntry(
        AbstractTableDataSourceFindUsagesContext context,
        RoslynDefinitionBucket definitionBucket,
        Guid projectGuid,
        string projectName,
        SourceText lineText,
        MappedSpanResult mappedSpanResult,
        IThreadingContext threadingContext)
        : AbstractItemEntry(definitionBucket, context.Presenter), ISupportsNavigation
    {
        private readonly object _boxedProjectGuid = projectGuid;
        private readonly string _projectName = projectName;
        private string? _trimmedLineText = null;
 
        protected abstract Document Document { get; }
 
        protected virtual TextSpan NavigateToTargetSpan
            => mappedSpanResult.Span;
 
        public bool CanNavigateTo()
            => true;
 
        public async Task NavigateToAsync(NavigationOptions options, CancellationToken cancellationToken)
        {
            var document = Document;
            var documentNavigationService = document.Project.Solution.Services.GetRequiredService<IDocumentNavigationService>();
 
            await documentNavigationService.TryNavigateToPositionAsync(
                threadingContext,
                document.Project.Solution.Workspace,
                document.Id,
                NavigateToTargetSpan.Start,
                virtualSpace: 0,
                // The location we're trying to navigate to may be gone at this point.  For example if the location was
                // at the end of a file, and the user edited the document to be shorter.  We want to not throw in this
                // case as stale results are a normal part of how find-references works.
                allowInvalidPosition: true,
                options,
                cancellationToken).ConfigureAwait(false);
        }
 
        protected override object? GetValueWorker(string keyName)
            => keyName switch
            {
                StandardTableKeyNames.DocumentName => mappedSpanResult.FilePath,
                StandardTableKeyNames.Line => mappedSpanResult.LinePositionSpan.Start.Line,
                StandardTableKeyNames.Column => mappedSpanResult.LinePositionSpan.Start.Character,
                StandardTableKeyNames.ProjectName => _projectName,
                StandardTableKeyNames.ProjectGuid => _boxedProjectGuid,
                StandardTableKeyNames.Text => _trimmedLineText ??= lineText.ToString().Trim(),
                _ => null,
            };
 
        public static async Task<MappedSpanResult?> TryMapAndGetFirstAsync(DocumentSpan documentSpan, SourceText sourceText, CancellationToken cancellationToken)
        {
            var service = documentSpan.Document.DocumentServiceProvider.GetService<ISpanMappingService>();
            if (service == null)
            {
                return new MappedSpanResult(documentSpan.Document.FilePath, sourceText.Lines.GetLinePositionSpan(documentSpan.SourceSpan), documentSpan.SourceSpan);
            }
 
            var results = await service.MapSpansAsync(
                documentSpan.Document, [documentSpan.SourceSpan], cancellationToken).ConfigureAwait(false);
 
            if (results.IsDefaultOrEmpty)
            {
                return new MappedSpanResult(documentSpan.Document.FilePath, sourceText.Lines.GetLinePositionSpan(documentSpan.SourceSpan), documentSpan.SourceSpan);
            }
 
            // if span mapping service filtered out the span, make sure
            // to return null so that we remove the span from the result
            return results.FirstOrNull(r => !r.IsDefault);
        }
 
        public static SourceText GetLineContainingPosition(SourceText text, int position)
        {
            var line = text.Lines.GetLineFromPosition(position);
 
            return text.GetSubText(line.Span);
        }
    }
}