This commit is contained in:
Ajay Bhargav Baaskaran 2020-02-12 19:29:57 -08:00
Родитель ee127559a0
Коммит a6d803b280
10 изменённых файлов: 188 добавлений и 51 удалений

Просмотреть файл

@ -12,7 +12,6 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer
{
internal class DefaultRazorDocumentMappingService : RazorDocumentMappingService
{
public override bool TryMapFromProjectedDocumentRange(RazorCodeDocument codeDocument, Range projectedRange, out Range originalRange)
{
if (codeDocument is null)

Просмотреть файл

@ -65,12 +65,12 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.Formatting
var result = await _server.Client.SendRequest<RazorDocumentRangeFormattingParams, RazorDocumentRangeFormattingResponse>(
"razor/rangeFormatting", @params);
var mappedEdits = MapProjectedCSharpEdits(codeDocument, result.Edits);
var mappedEdits = MapEditsToHostDocument(codeDocument, result.Edits);
return mappedEdits;
}
private TextEdit[] MapProjectedCSharpEdits(RazorCodeDocument codeDocument, TextEdit[] csharpEdits)
private TextEdit[] MapEditsToHostDocument(RazorCodeDocument codeDocument, TextEdit[] csharpEdits)
{
var actualEdits = new List<TextEdit>();
foreach (var edit in csharpEdits)

Просмотреть файл

@ -21,7 +21,7 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.Formatting
internal class DefaultRazorFormattingService : RazorFormattingService
{
private readonly ILanguageServer _server;
private readonly CSharpFormatter _cSharpFormatter;
private readonly CSharpFormatter _csharpFormatter;
private readonly HtmlFormatter _htmlFormatter;
private readonly ILogger _logger;
@ -52,7 +52,7 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.Formatting
}
_server = server;
_cSharpFormatter = new CSharpFormatter(documentMappingService, server, filePathNormalizer);
_csharpFormatter = new CSharpFormatter(documentMappingService, server, filePathNormalizer);
_htmlFormatter = new HtmlFormatter(server, filePathNormalizer);
_logger = loggerFactory.CreateLogger<DefaultRazorFormattingService>();
}
@ -125,7 +125,7 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.Formatting
var rangeToFormat = codeBlockRange.Overlap(context.Range);
if (rangeToFormat != null)
{
var codeEdits = await _cSharpFormatter.FormatAsync(context.CodeDocument, rangeToFormat, context.Uri, context.Options);
var codeEdits = await _csharpFormatter.FormatAsync(context.CodeDocument, rangeToFormat, context.Uri, context.Options);
changedText = ApplyCSharpEdits(context, codeBlockRange, codeEdits, minCSharpIndentLevel: 2);
}
@ -144,8 +144,11 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.Formatting
return allEdits.ToArray();
}
// minCSharpIndentLevel refers to the minimum level of how much the C# formatter would indent code.
// This is typically 2 for @code/@functions blocks and 3 for @{} blocks and other Razor blocks.
//
// 'minCSharpIndentLevel' refers to the minimum level of how much the C# formatter would indent code.
// @code/@functions blocks contain class members and so are typically indented by 2 levels.
// @{} blocks are put inside method body which means they are typically indented by 3 levels.
//
private SourceText ApplyCSharpEdits(FormattingContext context, Range codeBlockRange, TextEdit[] edits, int minCSharpIndentLevel)
{
var originalText = context.SourceText;
@ -187,7 +190,7 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.Formatting
}
var leadingWhitespace = line.GetLeadingWhitespace();
var minCSharpIndentLength = context.Options.TabSize * minCSharpIndentLevel;
var minCSharpIndentLength = GetIndentationString(context, minCSharpIndentLevel).Length;
if (leadingWhitespace.Length < minCSharpIndentLength)
{
// For whatever reason, the C# formatter decided to not indent this. Leave it as is.
@ -196,17 +199,16 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.Formatting
else
{
var effectiveDesiredIndentationLevel = desiredIndentationLevel - minCSharpIndentLevel;
var effectiveDesiredIndentation = GetIndentationString(context, Math.Abs(effectiveDesiredIndentationLevel));
if (effectiveDesiredIndentationLevel < 0)
{
// This means that we need to unindent.
var length = Math.Abs(effectiveDesiredIndentationLevel * context.Options.TabSize);
var span = new TextSpan(line.Start, (int)length);
var span = new TextSpan(line.Start, effectiveDesiredIndentation.Length);
editsToApply.Add(new TextChange(span, string.Empty));
}
else if (effectiveDesiredIndentationLevel > 0)
{
// This means that we need to indent.
var effectiveDesiredIndentation = GetIndentationString(context, effectiveDesiredIndentationLevel);
var span = new TextSpan(line.Start, 0);
editsToApply.Add(new TextChange(span, effectiveDesiredIndentation));
}
@ -363,13 +365,21 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.Formatting
private void TrackChangeInSpan(SourceText oldText, TextSpan originalSpan, SourceText newText, out TextSpan changedSpan, out TextSpan changeEncompassingSpan)
{
var affectedRange = newText.GetEncompassingTextChangeRange(oldText);
// The span of text before the edit which is being changed
changeEncompassingSpan = affectedRange.Span;
if (!originalSpan.Contains(changeEncompassingSpan))
{
_logger.LogDebug($"The changed region {changeEncompassingSpan} was not a subset of the span {originalSpan} being tracked. This is unexpected.");
}
changedSpan = TextSpan.FromBounds(originalSpan.Start, originalSpan.End + affectedRange.NewLength - affectedRange.Span.Length);
// We now know what was the range that changed and the length of that span after the change.
// Let's now compute what the original span looks like after the change.
// We know it still starts from the same location but could have grown or shrunk in length.
// Compute the change in length and then update the original span.
var changeInOriginalSpanLength = affectedRange.NewLength - changeEncompassingSpan.Length;
changedSpan = TextSpan.FromBounds(originalSpan.Start, originalSpan.End + changeInOriginalSpanLength);
}
private TextChange[] Diff(SourceText oldText, SourceText newText, TextSpan? spanToDiff = default)
@ -395,7 +405,12 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.Formatting
private static string GetIndentationString(FormattingContext context, int indentationLevel)
{
var indentChar = context.Options.InsertSpaces ? ' ' : '\t';
var indentation = new string(indentChar, (int)context.Options.TabSize * indentationLevel);
var indentationLength = indentationLevel;
if (context.Options.InsertSpaces)
{
indentationLength *= (int)context.Options.TabSize;
}
var indentation = new string(indentChar, indentationLength);
return indentation;
}

Просмотреть файл

@ -25,22 +25,4 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.Formatting
public Dictionary<int, IndentationContext> Indentations { get; } = new Dictionary<int, IndentationContext>();
}
internal class IndentationContext
{
public int Line { get; set; }
public int IndentationLevel { get; set; }
public int RelativeIndentationLevel { get; set; }
public int ExistingIndentation { get; set; }
public FormattingSpan FirstSpan { get; set; }
public override string ToString()
{
return $"Line: {Line}, IndentationLevel: {IndentationLevel}, RelativeIndentationLevel: {RelativeIndentationLevel}, ExistingIndentation: {ExistingIndentation}";
}
}
}

Просмотреть файл

@ -0,0 +1,23 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNetCore.Razor.LanguageServer.Formatting
{
internal class IndentationContext
{
public int Line { get; set; }
public int IndentationLevel { get; set; }
public int RelativeIndentationLevel { get; set; }
public int ExistingIndentation { get; set; }
public FormattingSpan FirstSpan { get; set; }
public override string ToString()
{
return $"Line: {Line}, IndentationLevel: {IndentationLevel}, RelativeIndentationLevel: {RelativeIndentationLevel}, ExistingIndentation: {ExistingIndentation}";
}
}
}

Просмотреть файл

@ -85,7 +85,7 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.Formatting
return documentSnapshot;
}, cancellationToken, TaskCreationOptions.None, _foregroundDispatcher.ForegroundScheduler);
if (document is null)
if (document is null || cancellationToken.IsCancellationRequested)
{
return null;
}

Просмотреть файл

@ -40,8 +40,10 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.Formatting
var startLocation = source.Lines.GetLocation(start);
var endLocation = source.Lines.GetLocation(end);
var startPosition = GetLinePosition(startLocation);
var endPosition = GetLinePosition(endLocation);
return new LinePositionSpan(GetLinePosition(startLocation), GetLinePosition(endLocation));
return new LinePositionSpan(startPosition, endPosition);
static LinePosition GetLinePosition(SourceLocation location)
{

Просмотреть файл

@ -1,6 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.CodeAnalysis.Text;
namespace Microsoft.AspNetCore.Razor.LanguageServer
@ -11,12 +12,12 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer
{
if (newText is null)
{
throw new System.ArgumentNullException(nameof(newText));
throw new ArgumentNullException(nameof(newText));
}
if (oldText is null)
{
throw new System.ArgumentNullException(nameof(oldText));
throw new ArgumentNullException(nameof(oldText));
}
var ranges = newText.GetChangeRanges(oldText);
@ -38,7 +39,7 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer
{
if (source is null)
{
throw new System.ArgumentNullException(nameof(source));
throw new ArgumentNullException(nameof(source));
}
var line = source.Lines.GetLineFromPosition(position);
@ -57,7 +58,7 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer
{
if (source is null)
{
throw new System.ArgumentNullException(nameof(source));
throw new ArgumentNullException(nameof(source));
}
source.GetLineAndOffset(textSpan.Start, out startLineNumber, out startOffset);
@ -68,7 +69,7 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer
{
if (source is null)
{
throw new System.ArgumentNullException(nameof(source));
throw new ArgumentNullException(nameof(source));
}
var charBuffer = new char[span.Length];

Просмотреть файл

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Language;
using Xunit;
namespace Microsoft.AspNetCore.Razor.LanguageServer.Formatting
@ -13,14 +14,14 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.Formatting
{
await RunFormattingTestAsync(
input: @"
|@functions {
|@code {
public class Foo{}
public interface Bar {
}
}|
",
expected: @"
@functions {
@code {
public class Foo { }
public interface Bar
{
@ -74,14 +75,14 @@ void Method() { <div></div> }
{
await RunFormattingTestAsync(
input: @"
|@functions {
|@code {
public class Foo{
void Method() { @DateTime.Now }
}
}|
",
expected: @"
@functions {
@code {
public class Foo{
void Method() { @DateTime.Now }
}
@ -106,7 +107,8 @@ expected: @"
void Method() { @(DateTime.Now) }
}
}
");
",
fileKind: FileKinds.Legacy);
}
[Fact]
@ -207,7 +209,8 @@ Hello World
{ }
}
}
");
",
fileKind: FileKinds.Legacy);
}
[Fact]
@ -216,7 +219,7 @@ Hello World
await RunFormattingTestAsync(
input: @"|
Hello World
@functions {
@code {
public class HelloWorld
{
}
@ -229,7 +232,7 @@ public class HelloWorld
|",
expected: @"
Hello World
@functions {
@code {
public class HelloWorld
{
}
@ -379,5 +382,116 @@ expected: @"
}
");
}
[Fact]
public async Task CodeBlockDirective_UseTabs()
{
await RunFormattingTestAsync(
input: @"
|@code {
public class Foo{}
void Method( ) {
}
}|
",
expected: @"
@code {
public class Foo { }
void Method()
{
}
}
",
insertSpaces: false);
}
[Fact]
public async Task CodeBlockDirective_UseTabsWithTabSize8()
{
await RunFormattingTestAsync(
input: @"
|@code {
public class Foo{}
void Method( ) {
}
}|
",
expected: @"
@code {
public class Foo { }
void Method()
{
}
}
",
tabSize: 8,
insertSpaces: false);
}
[Fact(Skip = "https://github.com/dotnet/aspnetcore/issues/18996")]
public async Task CodeBlockDirective_WithTabSize3()
{
await RunFormattingTestAsync(
input: @"
|@code {
public class Foo{}
void Method( ) {
}
}|
",
expected: @"
@code {
public class Foo { }
void Method()
{
}
}
",
tabSize: 3);
}
[Fact]
public async Task CodeBlockDirective_WithTabSize8()
{
await RunFormattingTestAsync(
input: @"
|@code {
public class Foo{}
void Method( ) {
}
}|
",
expected: @"
@code {
public class Foo { }
void Method()
{
}
}
",
tabSize: 8);
}
[Fact(Skip = "https://github.com/dotnet/aspnetcore/issues/18996")]
public async Task CodeBlockDirective_WithTabSize12()
{
await RunFormattingTestAsync(
input: @"
|@code {
public class Foo{}
void Method( ) {
}
}|
",
expected: @"
@code {
public class Foo { }
void Method()
{
}
}
",
tabSize: 12);
}
}
}

Просмотреть файл

@ -18,7 +18,7 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.Formatting
{
public class FormattingTestBase : LanguageServerTestBase
{
protected async Task RunFormattingTestAsync(string input, string expected, int tabSize = 4, bool insertSpaces = true)
protected async Task RunFormattingTestAsync(string input, string expected, int tabSize = 4, bool insertSpaces = true, string fileKind = default)
{
// Arrange
var start = input.IndexOf('|');
@ -31,7 +31,7 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.Formatting
var path = "file:///path/to/document.razor";
var uri = new Uri(path);
var codeDocument = CreateCodeDocument(source, uri.AbsolutePath);
var codeDocument = CreateCodeDocument(source, uri.AbsolutePath, fileKind: fileKind);
var options = new FormattingOptions()
{
TabSize = tabSize,
@ -55,12 +55,13 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.Formatting
return source.WithChanges(changes);
}
private static RazorCodeDocument CreateCodeDocument(SourceText text, string path, IReadOnlyList<TagHelperDescriptor> tagHelpers = null)
private static RazorCodeDocument CreateCodeDocument(SourceText text, string path, IReadOnlyList<TagHelperDescriptor> tagHelpers = null, string fileKind = default)
{
fileKind ??= FileKinds.Component;
tagHelpers ??= Array.Empty<TagHelperDescriptor>();
var sourceDocument = text.GetRazorSourceDocument(path, path);
var projectEngine = RazorProjectEngine.Create(builder => { });
var codeDocument = projectEngine.ProcessDesignTime(sourceDocument, FileKinds.Legacy, Array.Empty<RazorSourceDocument>(), tagHelpers);
var codeDocument = projectEngine.ProcessDesignTime(sourceDocument, fileKind, Array.Empty<RazorSourceDocument>(), tagHelpers);
return codeDocument;
}