File: SelectionRanges\RemoteSelectionRangeService.cs
Web Access
Project: src\src\Razor\src\Razor\src\Microsoft.CodeAnalysis.Remote.Razor\Microsoft.CodeAnalysis.Remote.Razor.csproj (Microsoft.CodeAnalysis.Remote.Razor)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.PooledObjects;
using Microsoft.CodeAnalysis.ExternalAccess.Razor;
using Microsoft.CodeAnalysis.Razor.DocumentMapping;
using Microsoft.CodeAnalysis.Razor.Protocol;
using Microsoft.CodeAnalysis.Razor.Remote;
using Microsoft.CodeAnalysis.Remote.Razor.ProjectSystem;
using Microsoft.CodeAnalysis.Text;
using ExternalHandlers = Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost.Handlers;
 
namespace Microsoft.CodeAnalysis.Remote.Razor;
 
internal sealed class RemoteSelectionRangeService(in ServiceArgs args) : RazorDocumentServiceBase(in args), IRemoteSelectionRangeService
{
    internal sealed class Factory : FactoryBase<IRemoteSelectionRangeService>
    {
        protected override IRemoteSelectionRangeService CreateService(in ServiceArgs args)
            => new RemoteSelectionRangeService(in args);
    }
 
    public ValueTask<SelectionRange[]?> GetSelectionRangesAsync(
        JsonSerializableRazorPinnedSolutionInfoWrapper solutionInfo,
        JsonSerializableDocumentId documentId,
        Position[] positions,
        CancellationToken cancellationToken)
        => RunServiceAsync(
            solutionInfo,
            documentId,
            context => GetSelectionRangesAsync(context, positions, cancellationToken),
            cancellationToken);
 
    private async ValueTask<SelectionRange[]?> GetSelectionRangesAsync(
        RemoteDocumentContext context,
        Position[] positions,
        CancellationToken cancellationToken)
    {
        var codeDocument = await context.GetCodeDocumentAsync(cancellationToken).ConfigureAwait(false);
 
        using var mappedPositions = new PooledArrayBuilder<LinePosition>();
        foreach (var position in positions)
        {
            if (!codeDocument.Source.Text.TryGetAbsoluteIndex(position, out var hostDocumentIndex))
            {
                return null;
            }
 
            var positionInfo = GetPositionInfo(codeDocument, hostDocumentIndex, preferCSharpOverHtml: true);
            if (positionInfo.LanguageKind is not RazorLanguageKind.CSharp)
            {
                return null;
            }
 
            mappedPositions.Add(positionInfo.Position.ToLinePosition());
        }
 
        var generatedDocument = await context.Snapshot
            .GetGeneratedDocumentAsync(cancellationToken)
            .ConfigureAwait(false);
 
        var linePositions = mappedPositions.ToImmutable();
        var csharpSelectionRanges = await ExternalHandlers.SelectionRanges
            .GetSelectionRangesAsync(generatedDocument, linePositions, cancellationToken)
            .ConfigureAwait(false);
 
        if (csharpSelectionRanges is null)
        {
            return null;
        }
 
        var csharpDocument = codeDocument.GetRequiredCSharpDocument();
        var selectionRanges = new SelectionRange[csharpSelectionRanges.Length];
        for (var i = 0; i < csharpSelectionRanges.Length; i++)
        {
            selectionRanges[i] = MapSelectionRange(csharpDocument, csharpSelectionRanges[i], positions[i], isRoot: true)!;
        }
 
        return selectionRanges;
    }
 
    private SelectionRange? MapSelectionRange(RazorCSharpDocument csharpDocument, SelectionRange? csharpSelectionRange, Position originalPosition, bool isRoot)
    {
        if (csharpSelectionRange is null)
        {
            return isRoot ? CreateEmptySelectionRange(originalPosition) : null;
        }
 
        var mappedParent = MapSelectionRange(csharpDocument, csharpSelectionRange.Parent, originalPosition, isRoot: false);
        if (!DocumentMappingService.TryMapToRazorDocumentRange(csharpDocument, csharpSelectionRange.Range, out var mappedRange))
        {
            return mappedParent;
        }
 
        if (mappedParent is not null && mappedParent.Range == mappedRange)
        {
            return mappedParent;
        }
 
        return new SelectionRange
        {
            Range = mappedRange,
            Parent = mappedParent
        };
    }
 
    private static SelectionRange CreateEmptySelectionRange(Position originalPosition)
        => new()
        {
            Range = LspFactory.CreateRange(originalPosition, originalPosition)
        };
}