diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/DefaultRazorFormattingService.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/DefaultRazorFormattingService.cs index fc7f417fbcf..9d6e1a50768 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/DefaultRazorFormattingService.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/DefaultRazorFormattingService.cs @@ -88,7 +88,8 @@ public override async Task FormatAsync(Uri uri, RazorCodeDocument co public override Task ApplyFormattedEditsAsync(Uri uri, RazorCodeDocument codeDocument, RazorLanguageKind kind, TextEdit[] formattedEdits, FormattingOptions options) { var span = TextSpan.FromBounds(0, codeDocument.Source.Length); - var range = span.AsRange(codeDocument.GetSourceText()); + var documentText = codeDocument.GetSourceText(); + var range = span.AsRange(documentText); var formattingContext = FormattingContext.Create(uri, codeDocument, range, options); // TODO diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/IRazorMapToDocumentEditsHandler.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/IRazorMapToDocumentEditsHandler.cs index 42a87fcdce6..2f31d0a26a5 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/IRazorMapToDocumentEditsHandler.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/IRazorMapToDocumentEditsHandler.cs @@ -10,4 +10,4 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer internal interface IRazorMapToDocumentEditsHandler : IJsonRpcRequestHandler { } -} \ No newline at end of file +} diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/RazorLanguageEndpoint.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/RazorLanguageEndpoint.cs index d5499726185..de18ba5a97c 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/RazorLanguageEndpoint.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/RazorLanguageEndpoint.cs @@ -62,7 +62,7 @@ public RazorLanguageEndpoint( throw new ArgumentNullException(nameof(documentMappingService)); } - if (razorFormattingService is null) + if (razorFormattingService == null) { throw new ArgumentNullException(nameof(razorFormattingService)); } @@ -167,8 +167,6 @@ await Task.Factory.StartNew(() => { documentVersion = UndefinedDocumentVersion; } - - return documentSnapshot; }, CancellationToken.None, TaskCreationOptions.None, _foregroundDispatcher.ForegroundScheduler); if (request.Kind != RazorLanguageKind.CSharp) @@ -211,7 +209,7 @@ public async Task Handle(RazorMapToDocumentEdit throw new ArgumentNullException(nameof(request)); } - long documentVersion = -1; + long documentVersion = UndefinedDocumentVersion; DocumentSnapshot documentSnapshot = null; await Task.Factory.StartNew(() => { @@ -225,6 +223,15 @@ await Task.Factory.StartNew(() => }, CancellationToken.None, TaskCreationOptions.None, _foregroundDispatcher.ForegroundScheduler); var codeDocument = await documentSnapshot.GetGeneratedOutputAsync(); + if (codeDocument.IsUnsupported()) + { + return new RazorMapToDocumentEditsResponse() + { + TextEdits = Array.Empty(), + HostDocumentVersion = documentVersion + }; + } + if (request.ShouldFormat) { var mappedEdits = await _razorFormattingService.ApplyFormattedEditsAsync( @@ -251,8 +258,7 @@ await Task.Factory.StartNew(() => for (var i = 0; i < request.ProjectedTextEdits.Length; i++) { var projectedRange = request.ProjectedTextEdits[i].Range; - if (codeDocument.IsUnsupported() || - !_documentMappingService.TryMapFromProjectedDocumentRange(codeDocument, projectedRange, out var originalRange)) + if (!_documentMappingService.TryMapFromProjectedDocumentRange(codeDocument, projectedRange, out var originalRange)) { // Can't map range. Discard this edit. continue; diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/RazorMapToDocumentEditsParams.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/RazorMapToDocumentEditsParams.cs index 5b2790bc66a..d0b1e07c720 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/RazorMapToDocumentEditsParams.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/RazorMapToDocumentEditsParams.cs @@ -19,4 +19,4 @@ internal class RazorMapToDocumentEditsParams : IRequest { - private static readonly IReadOnlyList CSharpTriggerCharacters = new[] { "}", ";", "\n" }; + private static readonly IReadOnlyList CSharpTriggerCharacters = new[] { "}", ";" }; private static readonly IReadOnlyList HtmlTriggerCharacters = Array.Empty(); private static readonly IReadOnlyList AllTriggerCharacters = CSharpTriggerCharacters.Concat(HtmlTriggerCharacters).ToArray(); @@ -91,6 +91,8 @@ public async Task HandleRequestAsync(DocumentOnTypeFormattingParams TextDocument = new TextDocumentIdentifier() { Uri = projectionResult.Uri } }; + cancellationToken.ThrowIfCancellationRequested(); + var serverKind = projectionResult.LanguageKind == RazorLanguageKind.CSharp ? LanguageServerKind.CSharp : LanguageServerKind.Html; var response = await _requestInvoker.ReinvokeRequestOnServerAsync( Methods.TextDocumentOnTypeFormattingName, @@ -103,6 +105,8 @@ public async Task HandleRequestAsync(DocumentOnTypeFormattingParams return null; } + cancellationToken.ThrowIfCancellationRequested(); + var remappedEdits = await _documentMappingProvider.RemapFormattedTextEditsAsync(projectionResult.Uri, response, request.Options, cancellationToken).ConfigureAwait(false); return remappedEdits; } diff --git a/src/Razor/src/Microsoft.VisualStudio.LanguageServerClient.Razor/HtmlCSharp/RazorMapToDocumentEditsParams.cs b/src/Razor/src/Microsoft.VisualStudio.LanguageServerClient.Razor/HtmlCSharp/RazorMapToDocumentEditsParams.cs index 2afbe8151e7..915f3c62935 100644 --- a/src/Razor/src/Microsoft.VisualStudio.LanguageServerClient.Razor/HtmlCSharp/RazorMapToDocumentEditsParams.cs +++ b/src/Razor/src/Microsoft.VisualStudio.LanguageServerClient.Razor/HtmlCSharp/RazorMapToDocumentEditsParams.cs @@ -2,11 +2,13 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Linq; +using Microsoft.Extensions.Internal; using Microsoft.VisualStudio.LanguageServer.Protocol; namespace Microsoft.VisualStudio.LanguageServerClient.Razor.HtmlCSharp { - internal class RazorMapToDocumentEditsParams + internal class RazorMapToDocumentEditsParams : IEquatable { public RazorLanguageKind Kind { get; set; } @@ -17,5 +19,52 @@ internal class RazorMapToDocumentEditsParams public bool ShouldFormat { get; set; } public FormattingOptions FormattingOptions { get; set; } + + // Everything below this is for testing purposes only. + public bool Equals(RazorMapToDocumentEditsParams other) + { + if (object.ReferenceEquals(this, other)) + { + return true; + } + + return + other != null && + Kind == other.Kind && + RazorDocumentUri == other.RazorDocumentUri && + Enumerable.SequenceEqual(ProjectedTextEdits?.Select(p => p.NewText), other.ProjectedTextEdits?.Select(p => p.NewText)) && + Enumerable.SequenceEqual(ProjectedTextEdits?.Select(p => p.Range), other.ProjectedTextEdits?.Select(p => p.Range)) && + ShouldFormat == other.ShouldFormat && + IsEqual(other.FormattingOptions); + } + + public override bool Equals(object obj) + { + return Equals(obj as RazorMapToDocumentEditsParams); + } + + public override int GetHashCode() + { + var hash = new HashCodeCombiner(); + hash.Add(Kind); + hash.Add(RazorDocumentUri); + hash.Add(ProjectedTextEdits); + return hash; + } + + private bool IsEqual(FormattingOptions other) + { + if (FormattingOptions == null || other == null) + { + return FormattingOptions == other; + } + + return + FormattingOptions.InsertSpaces == other.InsertSpaces && + FormattingOptions.TabSize == other.TabSize && + (object.ReferenceEquals(FormattingOptions.OtherOptions, other.OtherOptions) || + ((FormattingOptions.OtherOptions != null && other.OtherOptions != null) && + FormattingOptions.OtherOptions.OrderBy(k => k.Key).SequenceEqual(other.OtherOptions.OrderBy(k => k.Key)))); + } } } diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServerClient.Razor.Test/HtmlCSharp/DefaultLSPDocumentMappingProviderTest.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServerClient.Razor.Test/HtmlCSharp/DefaultLSPDocumentMappingProviderTest.cs index 32dd8a1841f..4a5545394d6 100644 --- a/src/Razor/test/Microsoft.VisualStudio.LanguageServerClient.Razor.Test/HtmlCSharp/DefaultLSPDocumentMappingProviderTest.cs +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServerClient.Razor.Test/HtmlCSharp/DefaultLSPDocumentMappingProviderTest.cs @@ -82,7 +82,7 @@ public async Task RemapWorkspaceEditAsync_RemapsEditsAsExpected() var requestInvoker = GetRequestInvoker(new[] { - ((RazorLanguageKind.CSharp, RazorFile, new[] { new TestRange(10, 10, 10, 15) }), (new[] { expectedRange }, expectedVersion)) + ((RazorLanguageKind.CSharp, RazorFile, new[] { new TestTextEdit("newText", new TestRange(10, 10, 10, 15)) }), (new[] { new TestTextEdit("newText", expectedRange) }, expectedVersion)) }); var mappingProvider = new DefaultLSPDocumentMappingProvider(requestInvoker, lazyDocumentManager); @@ -117,7 +117,7 @@ public async Task RemapWorkspaceEditAsync_DocumentChangesNull_RemapsEditsAsExpec var requestInvoker = GetRequestInvoker(new[] { - ((RazorLanguageKind.CSharp, RazorFile, new[] { new TestRange(10, 10, 10, 15) }), (new[] { expectedRange }, expectedVersion)) + ((RazorLanguageKind.CSharp, RazorFile, new[] { new TestTextEdit("newText", new TestRange(10, 10, 10, 15)) }), (new[] { new TestTextEdit("newText", expectedRange) }, expectedVersion)) }); var mappingProvider = new DefaultLSPDocumentMappingProvider(requestInvoker, lazyDocumentManager); @@ -187,8 +187,8 @@ public async Task RemapWorkspaceEditAsync_RemapsMultipleRazorFiles() var requestInvoker = GetRequestInvoker(new[] { - ((RazorLanguageKind.CSharp, RazorFile, new[] { new TestRange(10, 10, 10, 15) }), (new[] { expectedRange1 }, expectedVersion1)), - ((RazorLanguageKind.CSharp, AnotherRazorFile, new[] { new TestRange(20, 20, 20, 25) }), (new[] { expectedRange2 }, expectedVersion2)) + ((RazorLanguageKind.CSharp, RazorFile, new[] { new TestTextEdit("newText", new TestRange(10, 10, 10, 15)) }), (new[] { new TestTextEdit("newText", expectedRange1) }, expectedVersion1)), + ((RazorLanguageKind.CSharp, AnotherRazorFile, new[] { new TestTextEdit("newText", new TestRange(20, 20, 20, 25)) }), (new[] { new TestTextEdit("newText", expectedRange2) }, expectedVersion2)) }); var mappingProvider = new DefaultLSPDocumentMappingProvider(requestInvoker, lazyDocumentManager); @@ -221,7 +221,7 @@ public async Task RemapWorkspaceEditAsync_RemapsMultipleRazorFiles() }); } - private LSPRequestInvoker GetRequestInvoker(((RazorLanguageKind, Uri, TestRange[]), (TestRange[], int))[] mappingPairs) + private LSPRequestInvoker GetRequestInvoker(((RazorLanguageKind, Uri, TestTextEdit[]), (TestTextEdit[], int))[] mappingPairs) { var requestInvoker = new Mock(MockBehavior.Strict); if (mappingPairs == null) @@ -230,22 +230,22 @@ private LSPRequestInvoker GetRequestInvoker(((RazorLanguageKind, Uri, TestRange[ } // mappingPairs will contain the request/response pair for each of MapToDocumentRange LSP request we want to mock. - foreach (var ((kind, uri, projectedRanges), (mappedRanges, version)) in mappingPairs) + foreach (var ((kind, uri, projectedEdits), (mappedEdits, version)) in mappingPairs) { - var requestParams = new RazorMapToDocumentRangesParams() + var requestParams = new RazorMapToDocumentEditsParams() { Kind = kind, RazorDocumentUri = uri, - ProjectedRanges = projectedRanges + ProjectedTextEdits = projectedEdits }; - var response = new RazorMapToDocumentRangesResponse() + var response = new RazorMapToDocumentEditsResponse() { - Ranges = mappedRanges, + TextEdits = mappedEdits, HostDocumentVersion = version }; requestInvoker - .Setup(r => r.CustomRequestServerAsync(LanguageServerConstants.RazorMapToDocumentRangesEndpoint, LanguageServerKind.Razor, requestParams, It.IsAny())) + .Setup(r => r.CustomRequestServerAsync(LanguageServerConstants.RazorMapToDocumentEditsEndpoint, LanguageServerKind.Razor, requestParams, It.IsAny())) .Returns(Task.FromResult(response)); }