File: Navigation\IDefinitionLocationService.cs
Web Access
Project: src\src\Features\Core\Portable\Microsoft.CodeAnalysis.Features.csproj (Microsoft.CodeAnalysis.Features)
// 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.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.Navigation;
 
/// <summary>
/// Service used by "go to definition" and "ctrl-click on symbol" to find the symbol definition location and navigate to
/// it. Specifically, services that do not intend to show any interesting UI for the symbol definition, they just intend
/// to navigate to it.  If richer information is desired (like determining what to display for the symbol name), then 
/// <see cref="INavigableItemsService"/> should be used instead.
/// </summary>
internal interface IDefinitionLocationService : ILanguageService
{
    /// <summary>
    /// If the supplied <paramref name="position"/> is on a code construct with a navigable location, then this
    /// returns that <see cref="INavigableLocation"/>.  The <see cref="TextSpan"/> returned in the span of the
    /// symbol in the code that references that navigable location.  e.g. the full identifier token that the
    /// position is within.
    /// </summary>
    Task<DefinitionLocation?> GetDefinitionLocationAsync(
        Document document, int position, CancellationToken cancellationToken);
}
 
/// <summary>
/// The result of a <see cref="IDefinitionLocationService.GetDefinitionLocationAsync"/> call.
/// </summary>
/// <param name="Location">The location where the symbol is actually defined at.  Can be used to then navigate to that
/// symbol.
/// </param>
/// <param name="Span">The <see cref="TextSpan"/> returned in the span of the symbol in the code that references that
/// navigable location.  e.g. the full identifier token that the position is within.  Can be used to highlight/underline
/// that text in the document in some fashion.</param>
internal sealed record DefinitionLocation(INavigableLocation Location, DocumentSpan Span);
 
internal static class DefinitionLocationServiceHelpers
{
    public static async Task<DefinitionLocation?> GetDefinitionLocationFromLegacyImplementationsAsync(
        Document document, int position, Func<CancellationToken, Task<IEnumerable<(Document document, TextSpan sourceSpan)>?>> getNavigableItems, CancellationToken cancellationToken)
    {
        var items = await getNavigableItems(cancellationToken).ConfigureAwait(false);
        if (items is null)
            return null;
 
        var firstItem = items.FirstOrNull();
        if (firstItem is null)
            return null;
 
        var navigableItem = await new DocumentSpan(firstItem.Value.document, firstItem.Value.sourceSpan).GetNavigableLocationAsync(cancellationToken).ConfigureAwait(false);
        if (navigableItem is null)
            return null;
 
        var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);
        return new DefinitionLocation(navigableItem, GetDocumentSpan());
 
        DocumentSpan GetDocumentSpan()
        {
            // To determine the span to underline, just use a simple heuristic of expanding out to the surrounding
            // letters and numbers.  F#/TS can reimplement this in the future to be more accurate to their language.
 
            var startPosition = position;
            var endPosition = position + 1;
 
            if (char.IsLetterOrDigit(text[position]))
            {
                while (char.IsLetterOrDigit(GetChar(startPosition - 1)))
                    startPosition--;
 
                while (char.IsLetterOrDigit(GetChar(endPosition + 1)))
                    endPosition++;
            }
 
            return new DocumentSpan(document, TextSpan.FromBounds(startPosition, endPosition));
        }
 
        char GetChar(int position)
            => position >= 0 && position < text.Length ? text[position] : (char)0;
    }
}