Skip to content

Commit 1f24912

Browse files
author
Andrew Hall
authored
Add mapped edits helper (dotnet#11146)
Razor side of implementing IRazorMappingService. Adds a RazorEditorHelper that specifically is designed to handle complex edits in csharp files that are generated by razor. Handles adding, removing, renaming, and a mix of both for usings. The rest of edits are put exactly where they map to.
1 parent 95f862d commit 1f24912

File tree

35 files changed

+2344
-187
lines changed

35 files changed

+2344
-187
lines changed

src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/CSharp/TextChangeExtensions.cs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4-
#nullable disable
5-
6-
using System;
74
using Microsoft.AspNetCore.Razor.Language;
85
using Microsoft.CodeAnalysis.Text;
96

src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Extensions/IServiceCollectionExtensions.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ public static void AddTextDocumentServices(this IServiceCollection services, Lan
180180
services.AddHandler<DocumentDidSaveEndpoint>();
181181

182182
services.AddHandler<RazorMapToDocumentRangesEndpoint>();
183+
services.AddHandler<RazorMapToDocumentEditsEndpoint>();
183184
services.AddHandler<RazorLanguageQueryEndpoint>();
184185
}
185186

src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/LspDocumentMappingService.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,25 +17,26 @@ internal sealed class LspDocumentMappingService(
1717
IFilePathService filePathService,
1818
IDocumentContextFactory documentContextFactory,
1919
ILoggerFactory loggerFactory)
20-
: AbstractDocumentMappingService(filePathService, loggerFactory.GetOrCreateLogger<LspDocumentMappingService>())
20+
: AbstractDocumentMappingService(loggerFactory.GetOrCreateLogger<LspDocumentMappingService>())
2121
{
22+
private readonly IFilePathService _filePathService = filePathService;
2223
private readonly IDocumentContextFactory _documentContextFactory = documentContextFactory;
2324

2425
public async Task<(Uri MappedDocumentUri, LinePositionSpan MappedRange)> MapToHostDocumentUriAndRangeAsync(
2526
Uri generatedDocumentUri,
2627
LinePositionSpan generatedDocumentRange,
2728
CancellationToken cancellationToken)
2829
{
29-
var razorDocumentUri = FilePathService.GetRazorDocumentUri(generatedDocumentUri);
30+
var razorDocumentUri = _filePathService.GetRazorDocumentUri(generatedDocumentUri);
3031

3132
// For Html we just map the Uri, the range will be the same
32-
if (FilePathService.IsVirtualHtmlFile(generatedDocumentUri))
33+
if (_filePathService.IsVirtualHtmlFile(generatedDocumentUri))
3334
{
3435
return (razorDocumentUri, generatedDocumentRange);
3536
}
3637

3738
// We only map from C# files
38-
if (!FilePathService.IsVirtualCSharpFile(generatedDocumentUri))
39+
if (!_filePathService.IsVirtualCSharpFile(generatedDocumentUri))
3940
{
4041
return (generatedDocumentUri, generatedDocumentRange);
4142
}
@@ -47,7 +48,7 @@ internal sealed class LspDocumentMappingService(
4748

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

50-
if (!codeDocument.TryGetGeneratedDocument(generatedDocumentUri, FilePathService, out var generatedDocument))
51+
if (!codeDocument.TryGetGeneratedDocument(generatedDocumentUri, _filePathService, out var generatedDocument))
5152
{
5253
return Assumed.Unreachable<(Uri, LinePositionSpan)>();
5354
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the MIT license. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Collections.Immutable;
7+
using System.Linq;
8+
using System.Threading;
9+
using System.Threading.Tasks;
10+
using Microsoft.AspNetCore.Razor.Language;
11+
using Microsoft.AspNetCore.Razor.LanguageServer.EndpointContracts;
12+
using Microsoft.AspNetCore.Razor.Telemetry;
13+
using Microsoft.CodeAnalysis.Razor.DocumentMapping;
14+
using Microsoft.CodeAnalysis.Razor.Logging;
15+
using Microsoft.CodeAnalysis.Razor.Protocol;
16+
using Microsoft.CodeAnalysis.Razor.Protocol.DocumentMapping;
17+
using Microsoft.CodeAnalysis.Text;
18+
using Microsoft.CommonLanguageServerProtocol.Framework;
19+
using Microsoft.VisualStudio.LanguageServer.Protocol;
20+
21+
namespace Microsoft.AspNetCore.Razor.LanguageServer.Mapping;
22+
23+
[RazorLanguageServerEndpoint(LanguageServerConstants.RazorMapToDocumentEditsEndpoint)]
24+
internal partial class RazorMapToDocumentEditsEndpoint(IDocumentMappingService documentMappingService, ITelemetryReporter telemetryReporter, ILoggerFactory loggerFactory) :
25+
IRazorDocumentlessRequestHandler<RazorMapToDocumentEditsParams, RazorMapToDocumentEditsResponse?>,
26+
ITextDocumentIdentifierHandler<RazorMapToDocumentEditsParams, Uri>
27+
{
28+
private readonly IDocumentMappingService _documentMappingService = documentMappingService;
29+
private readonly ITelemetryReporter _telemetryReporter = telemetryReporter;
30+
private readonly ILogger _logger = loggerFactory.GetOrCreateLogger<RazorMapToDocumentEditsEndpoint>();
31+
32+
public bool MutatesSolutionState => false;
33+
34+
public Uri GetTextDocumentIdentifier(RazorMapToDocumentEditsParams request)
35+
{
36+
return request.RazorDocumentUri;
37+
}
38+
39+
public async Task<RazorMapToDocumentEditsResponse?> HandleRequestAsync(RazorMapToDocumentEditsParams request, RazorRequestContext requestContext, CancellationToken cancellationToken)
40+
{
41+
var documentContext = requestContext.DocumentContext;
42+
if (documentContext is null)
43+
{
44+
return null;
45+
}
46+
47+
if (request.TextChanges.Length == 0)
48+
{
49+
return null;
50+
}
51+
52+
if (request.Kind != RazorLanguageKind.CSharp)
53+
{
54+
// All other non-C# requests map directly to where they are in the document,
55+
// so the edits do as well
56+
return new RazorMapToDocumentEditsResponse()
57+
{
58+
TextChanges = request.TextChanges,
59+
HostDocumentVersion = documentContext.Snapshot.Version,
60+
};
61+
}
62+
63+
var mappedEdits = await RazorEditHelper.MapCSharpEditsAsync(
64+
request.TextChanges.ToImmutableArray(),
65+
documentContext.Snapshot,
66+
_documentMappingService,
67+
_telemetryReporter,
68+
cancellationToken).ConfigureAwait(false);
69+
70+
_logger.LogTrace($"""
71+
Before:
72+
{DisplayEdits(request.TextChanges)}
73+
74+
After:
75+
{DisplayEdits(mappedEdits)}
76+
""");
77+
78+
return new RazorMapToDocumentEditsResponse()
79+
{
80+
TextChanges = mappedEdits.ToArray(),
81+
};
82+
}
83+
84+
private static string DisplayEdits(IEnumerable<RazorTextChange> changes)
85+
=> string.Join(
86+
Environment.NewLine,
87+
changes.Select(e => $"{e.Span} => '{e.NewText}'"));
88+
}

src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/DocumentMapping/AbstractDocumentMappingService.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,13 @@
1111
using Microsoft.AspNetCore.Razor.PooledObjects;
1212
using Microsoft.CodeAnalysis;
1313
using Microsoft.CodeAnalysis.Razor.Logging;
14-
using Microsoft.CodeAnalysis.Razor.Workspaces;
1514
using Microsoft.CodeAnalysis.Text;
1615
using Microsoft.VisualStudio.LanguageServer.Protocol;
1716

1817
namespace Microsoft.CodeAnalysis.Razor.DocumentMapping;
1918

20-
internal abstract class AbstractDocumentMappingService(IFilePathService filePathService, ILogger logger) : IDocumentMappingService
19+
internal abstract class AbstractDocumentMappingService(ILogger logger) : IDocumentMappingService
2120
{
22-
protected readonly IFilePathService FilePathService = filePathService;
2321
protected readonly ILogger Logger = logger;
2422

2523
public IEnumerable<TextChange> GetHostDocumentEdits(IRazorGeneratedDocument generatedDocument, ImmutableArray<TextChange> generatedDocumentChanges)
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the MIT license. See License.txt in the project root for license information.
3+
4+
using System.Collections.Generic;
5+
using Microsoft.VisualStudio.LanguageServer.Protocol;
6+
7+
namespace Microsoft.CodeAnalysis.Razor.Workspaces.DocumentMapping;
8+
9+
internal sealed class RangeComparer : IComparer<Range>
10+
{
11+
public static readonly RangeComparer Instance = new();
12+
13+
public int Compare(Range? x, Range? y)
14+
{
15+
if (x is null)
16+
{
17+
return y is null ? 0 : 1;
18+
}
19+
20+
if (y is null)
21+
{
22+
return -1;
23+
}
24+
25+
return x.CompareTo(y);
26+
}
27+
}

0 commit comments

Comments
 (0)