Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add mapped edits helper #11146

Merged
merged 23 commits into from
Nov 12, 2024
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change LGTM

Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#nullable disable

using System;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.CodeAnalysis.Text;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ public static void AddTextDocumentServices(this IServiceCollection services, Lan
services.AddHandler<DocumentDidSaveEndpoint>();

services.AddHandler<RazorMapToDocumentRangesEndpoint>();
services.AddHandler<RazorMapToDocumentEditsEndpoint>();
services.AddHandler<RazorLanguageQueryEndpoint>();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,25 +17,26 @@ internal sealed class LspDocumentMappingService(
IFilePathService filePathService,
IDocumentContextFactory documentContextFactory,
ILoggerFactory loggerFactory)
: AbstractDocumentMappingService(filePathService, loggerFactory.GetOrCreateLogger<LspDocumentMappingService>())
: AbstractDocumentMappingService(loggerFactory.GetOrCreateLogger<LspDocumentMappingService>())
{
private readonly IFilePathService _filePathService = filePathService;
private readonly IDocumentContextFactory _documentContextFactory = documentContextFactory;

public async Task<(Uri MappedDocumentUri, LinePositionSpan MappedRange)> MapToHostDocumentUriAndRangeAsync(
Uri generatedDocumentUri,
LinePositionSpan generatedDocumentRange,
CancellationToken cancellationToken)
{
var razorDocumentUri = FilePathService.GetRazorDocumentUri(generatedDocumentUri);
var razorDocumentUri = _filePathService.GetRazorDocumentUri(generatedDocumentUri);

// For Html we just map the Uri, the range will be the same
if (FilePathService.IsVirtualHtmlFile(generatedDocumentUri))
if (_filePathService.IsVirtualHtmlFile(generatedDocumentUri))
{
return (razorDocumentUri, generatedDocumentRange);
}

// We only map from C# files
if (!FilePathService.IsVirtualCSharpFile(generatedDocumentUri))
if (!_filePathService.IsVirtualCSharpFile(generatedDocumentUri))
{
return (generatedDocumentUri, generatedDocumentRange);
}
Expand All @@ -47,7 +48,7 @@ internal sealed class LspDocumentMappingService(

var codeDocument = await documentContext.GetCodeDocumentAsync(cancellationToken).ConfigureAwait(false);

if (!codeDocument.TryGetGeneratedDocument(generatedDocumentUri, FilePathService, out var generatedDocument))
if (!codeDocument.TryGetGeneratedDocument(generatedDocumentUri, _filePathService, out var generatedDocument))
{
return Assumed.Unreachable<(Uri, LinePositionSpan)>();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.LanguageServer.EndpointContracts;
using Microsoft.AspNetCore.Razor.Telemetry;
using Microsoft.CodeAnalysis.Razor.DocumentMapping;
using Microsoft.CodeAnalysis.Razor.Logging;
using Microsoft.CodeAnalysis.Razor.Protocol;
using Microsoft.CodeAnalysis.Razor.Protocol.DocumentMapping;
using Microsoft.CodeAnalysis.Text;
using Microsoft.CommonLanguageServerProtocol.Framework;
using Microsoft.VisualStudio.LanguageServer.Protocol;

namespace Microsoft.AspNetCore.Razor.LanguageServer.Mapping;

[RazorLanguageServerEndpoint(LanguageServerConstants.RazorMapToDocumentEditsEndpoint)]
internal partial class RazorMapToDocumentEditsEndpoint(IDocumentMappingService documentMappingService, ITelemetryReporter telemetryReporter, ILoggerFactory loggerFactory) :
IRazorDocumentlessRequestHandler<RazorMapToDocumentEditsParams, RazorMapToDocumentEditsResponse?>,
ITextDocumentIdentifierHandler<RazorMapToDocumentEditsParams, Uri>
{
private readonly IDocumentMappingService _documentMappingService = documentMappingService;
private readonly ITelemetryReporter _telemetryReporter = telemetryReporter;
private readonly ILogger _logger = loggerFactory.GetOrCreateLogger<RazorMapToDocumentEditsEndpoint>();

public bool MutatesSolutionState => false;

public Uri GetTextDocumentIdentifier(RazorMapToDocumentEditsParams request)
{
return request.RazorDocumentUri;
}

public async Task<RazorMapToDocumentEditsResponse?> HandleRequestAsync(RazorMapToDocumentEditsParams request, RazorRequestContext requestContext, CancellationToken cancellationToken)
{
var documentContext = requestContext.DocumentContext;
if (documentContext is null)
{
return null;
}

if (request.TextChanges.Length == 0)
{
return null;
}

if (request.Kind != RazorLanguageKind.CSharp)
{
// All other non-C# requests map directly to where they are in the document,
// so the edits do as well
return new RazorMapToDocumentEditsResponse()
{
TextChanges = request.TextChanges,
HostDocumentVersion = documentContext.Snapshot.Version,
};
}

var mappedEdits = await RazorEditHelper.MapCSharpEditsAsync(
request.TextChanges.ToImmutableArray(),
documentContext.Snapshot,
_documentMappingService,
_telemetryReporter,
cancellationToken).ConfigureAwait(false);

_logger.LogTrace($"""
Before:
{DisplayEdits(request.TextChanges)}

After:
{DisplayEdits(mappedEdits)}
""");

return new RazorMapToDocumentEditsResponse()
{
TextChanges = mappedEdits.ToArray(),
};
}

private string DisplayEdits(IEnumerable<RazorTextChange> changes)
ryzngard marked this conversation as resolved.
Show resolved Hide resolved
=> string.Join(
Environment.NewLine,
changes.Select(e => $"{e.Span} => '{e.NewText}'"));
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,13 @@
using Microsoft.AspNetCore.Razor.PooledObjects;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Razor.Logging;
using Microsoft.CodeAnalysis.Razor.Workspaces;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.LanguageServer.Protocol;

namespace Microsoft.CodeAnalysis.Razor.DocumentMapping;

internal abstract class AbstractDocumentMappingService(IFilePathService filePathService, ILogger logger) : IDocumentMappingService
internal abstract class AbstractDocumentMappingService(ILogger logger) : IDocumentMappingService
{
protected readonly IFilePathService FilePathService = filePathService;
protected readonly ILogger Logger = logger;

public IEnumerable<TextChange> GetHostDocumentEdits(IRazorGeneratedDocument generatedDocument, ImmutableArray<TextChange> generatedDocumentChanges)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license information.

using System.Collections.Generic;
using Microsoft.VisualStudio.LanguageServer.Protocol;

namespace Microsoft.CodeAnalysis.Razor.Workspaces.DocumentMapping;

internal sealed class RangeComparer : IComparer<Range>
{
public static readonly RangeComparer Instance = new();

public int Compare(Range? x, Range? y)
{
if (x is null)
{
return y is null ? 0 : 1;
}

if (y is null)
{
return -1;
}

return x.CompareTo(y);
}
}
Loading
Loading