File: Implementation\LanguageServer\Handler\OnTypeRename\OnTypeRenameHandler.cs
Web Access
Project: src\src\VisualStudio\Xaml\Impl\Microsoft.VisualStudio.LanguageServices.Xaml.csproj (Microsoft.VisualStudio.LanguageServices.Xaml)
// 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.Composition;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.LanguageServer;
using Microsoft.CodeAnalysis.LanguageServer.Handler;
using Roslyn.LanguageServer.Protocol;
using Microsoft.VisualStudio.LanguageServices.Xaml.Features.TypeRename;
using Microsoft.CodeAnalysis;
 
namespace Microsoft.VisualStudio.LanguageServices.Xaml.LanguageServer.Handler;
 
[ExportStatelessXamlLspService(typeof(OnTypeRenameHandler)), Shared]
[Method(Methods.TextDocumentLinkedEditingRangeName)]
internal sealed class OnTypeRenameHandler : ILspServiceRequestHandler<LinkedEditingRangeParams, LinkedEditingRanges?>
{
    // From https://www.w3.org/TR/xml/#NT-NameStartChar
    // Notes:
    //     \u10000-\uEFFFF isn't included as .NET regular expressions only allow 4 chars after \u.
    //     The : shouldn't really be used as start character either so included it in the name char pattern.
    //     We want to allow complete removal and replacement of names so we need to make the start char
    //     optional in the name char pattern.
 
    // NameStartChar ::= ":" | [A-Z] | "_" | [a-z] | [#xC0-#xD6] | [#xD8-#xF6] | [#xF8-#x2FF] | [#x370-#x37D] | [#x37F-#x1FFF] | [#x200C-#x200D] | [#x2070-#x218F] | [#x2C00-#x2FEF] | [#x3001-#xD7FF] | [#xF900-#xFDCF] | [#xFDF0-#xFFFD] | [#x10000-#xEFFFF]
    private const string NameStartCharPattern = "A-Z_a-z" +
                                                "\\u00C0-\\u00D6" +
                                                "\\u00D8-\\u00F6" +
                                                "\\u00F8-\\u02FF" +
                                                "\\u0370-\\u037D" +
                                                "\\u037F-\\u1FFF" +
                                                "\\u200C-\\u200D" +
                                                "\\u2070-\\u218F" +
                                                "\\u2C00-\\u2FEF" +
                                                "\\u3001-\\uD7FF" +
                                                "\\uF900-\\uFDCF" +
                                                "\\uFDF0-\\uFFFD";
 
    // NameChar ::= NameStartChar | "-" | "." | [0-9] | #xB7 | [#x0300-#x036F] | [#x203F-#x2040]
    private const string NameCharPattern = NameStartCharPattern +
                                                ":\\-.0-9" +
                                                "\\u00B7" +
                                                "\\u0300-\\u036F" +
                                                "\\u203F-\\u2040";
 
    // Name ::= NameStartChar (NameChar)*
    internal const string NamePattern = $"[{NameStartCharPattern}]?[{NameCharPattern}]*";
 
    [ImportingConstructor]
    [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
    public OnTypeRenameHandler()
    {
    }
 
    public bool MutatesSolutionState => false;
    public bool RequiresLSPSolution => true;
 
    public TextDocumentIdentifier GetTextDocumentIdentifier(LinkedEditingRangeParams request) => request.TextDocument;
 
    public async Task<LinkedEditingRanges?> HandleRequestAsync(LinkedEditingRangeParams request, RequestContext context, CancellationToken cancellationToken)
    {
        var document = context.Document;
        if (document == null)
        {
            return null;
        }
 
        var renameService = document.Project.Services.GetService<IXamlTypeRenameService>();
        if (renameService == null)
        {
            return null;
        }
 
        var text = await document.GetValueTextAsync(cancellationToken).ConfigureAwait(false);
        var offset = text.Lines.GetPosition(ProtocolConversions.PositionToLinePosition(request.Position));
 
        var result = await renameService.GetTypeRenameAsync(document, offset, cancellationToken).ConfigureAwait(false);
        if (result == null)
        {
            return null;
        }
 
        Contract.ThrowIfTrue(result.Ranges.IsDefault);
 
        return new LinkedEditingRanges
        {
            Ranges = [.. result.Ranges.Select(s => ProtocolConversions.TextSpanToRange(s, text))],
            WordPattern = result.WordPattern
        };
    }
}