diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer.Common/RazorCodeDocumentExtensions.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer.Common/RazorCodeDocumentExtensions.cs
index f825ddbecb6..2066c58b4ae 100644
--- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer.Common/RazorCodeDocumentExtensions.cs
+++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer.Common/RazorCodeDocumentExtensions.cs
@@ -11,6 +11,8 @@ internal static class RazorCodeDocumentExtensions
{
private static readonly object UnsupportedKey = new object();
private static readonly object SourceTextKey = new object();
+ private static readonly object CSharpSourceTextKey = new object();
+ private static readonly object HtmlSourceTextKey = new object();
public static bool IsUnsupported(this RazorCodeDocument document)
{
@@ -59,5 +61,45 @@ public static SourceText GetSourceText(this RazorCodeDocument document)
return (SourceText)sourceTextObj;
}
+
+ public static SourceText GetCSharpSourceText(this RazorCodeDocument document)
+ {
+ if (document == null)
+ {
+ throw new ArgumentNullException(nameof(document));
+ }
+
+ var sourceTextObj = document.Items[CSharpSourceTextKey];
+ if (sourceTextObj == null)
+ {
+ var csharpDocument = document.GetCSharpDocument();
+ var sourceText = SourceText.From(csharpDocument.GeneratedCode);
+ document.Items[CSharpSourceTextKey] = sourceText;
+
+ return sourceText;
+ }
+
+ return (SourceText)sourceTextObj;
+ }
+
+ public static SourceText GetHtmlSourceText(this RazorCodeDocument document)
+ {
+ if (document == null)
+ {
+ throw new ArgumentNullException(nameof(document));
+ }
+
+ var sourceTextObj = document.Items[HtmlSourceTextKey];
+ if (sourceTextObj == null)
+ {
+ var htmlDocument = document.GetHtmlDocument();
+ var sourceText = SourceText.From(htmlDocument.GeneratedHtml);
+ document.Items[HtmlSourceTextKey] = sourceText;
+
+ return sourceText;
+ }
+
+ return (SourceText)sourceTextObj;
+ }
}
}
diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/CSharpFormatter.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/CSharpFormatter.cs
index 8f06e1a1b58..ae02a03f540 100644
--- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/CSharpFormatter.cs
+++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/CSharpFormatter.cs
@@ -4,13 +4,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.LanguageServer.Common;
-using OmniSharp.Extensions.LanguageServer.Protocol;
-using Microsoft.CodeAnalysis;
-using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Formatting;
using Microsoft.CodeAnalysis.Text;
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
using OmniSharp.Extensions.LanguageServer.Protocol.Server;
@@ -23,12 +22,12 @@ internal class CSharpFormatter
private readonly RazorDocumentMappingService _documentMappingService;
private readonly FilePathNormalizer _filePathNormalizer;
private readonly IClientLanguageServer _server;
- private readonly ProjectSnapshotManagerAccessor _projectSnapshotManagerAccessor;
+ private readonly object _indentationService;
+ private readonly MethodInfo _getIndentationMethod;
public CSharpFormatter(
RazorDocumentMappingService documentMappingService,
IClientLanguageServer languageServer,
- ProjectSnapshotManagerAccessor projectSnapshotManagerAccessor,
FilePathNormalizer filePathNormalizer)
{
if (documentMappingService is null)
@@ -41,11 +40,6 @@ public CSharpFormatter(
throw new ArgumentNullException(nameof(languageServer));
}
- if (projectSnapshotManagerAccessor is null)
- {
- throw new ArgumentNullException(nameof(projectSnapshotManagerAccessor));
- }
-
if (filePathNormalizer is null)
{
throw new ArgumentNullException(nameof(filePathNormalizer));
@@ -53,20 +47,41 @@ public CSharpFormatter(
_documentMappingService = documentMappingService;
_server = languageServer;
- _projectSnapshotManagerAccessor = projectSnapshotManagerAccessor;
_filePathNormalizer = filePathNormalizer;
+
+ try
+ {
+ var type = typeof(CSharpFormattingOptions).Assembly.GetType("Microsoft.CodeAnalysis.CSharp.Indentation.CSharpIndentationService", throwOnError: true);
+ _indentationService = Activator.CreateInstance(type);
+ var indentationService = type.GetInterface("IIndentationService");
+ _getIndentationMethod = indentationService.GetMethod("GetIndentation");
+ }
+ catch (Exception ex)
+ {
+ throw new InvalidOperationException(
+ "Error occured when creating an instance of Roslyn's IIndentationService. Roslyn may have changed in an unexpected way.",
+ ex);
+ }
}
public async Task FormatAsync(
- RazorCodeDocument codeDocument,
- Range range,
- DocumentUri uri,
- FormattingOptions options,
+ FormattingContext context,
+ Range rangeToFormat,
CancellationToken cancellationToken,
bool formatOnClient = false)
{
+ if (context is null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ if (rangeToFormat is null)
+ {
+ throw new ArgumentNullException(nameof(rangeToFormat));
+ }
+
Range projectedRange = null;
- if (range != null && !_documentMappingService.TryMapToProjectedDocumentRange(codeDocument, range, out projectedRange))
+ if (rangeToFormat != null && !_documentMappingService.TryMapToProjectedDocumentRange(context.CodeDocument, rangeToFormat, out projectedRange))
{
return Array.Empty();
}
@@ -74,17 +89,64 @@ public async Task FormatAsync(
TextEdit[] edits;
if (formatOnClient)
{
- edits = await FormatOnClientAsync(codeDocument, projectedRange, uri, options, cancellationToken);
+ edits = await FormatOnClientAsync(context, projectedRange, cancellationToken);
}
else
{
- edits = await FormatOnServerAsync(codeDocument, projectedRange, uri, options, cancellationToken);
+ edits = await FormatOnServerAsync(context, projectedRange, cancellationToken);
}
- var mappedEdits = MapEditsToHostDocument(codeDocument, edits);
+ var mappedEdits = MapEditsToHostDocument(context.CodeDocument, edits);
return mappedEdits;
}
+ public int GetCSharpIndentation(FormattingContext context, int projectedDocumentIndex, CancellationToken cancellationToken)
+ {
+ if (context is null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ // Add a marker at the position where we need the indentation.
+ var changedText = context.CSharpSourceText;
+ var marker = $"{context.NewLineString}#line default{context.NewLineString}#line hidden{context.NewLineString}";
+ changedText = changedText.WithChanges(new TextChange(TextSpan.FromBounds(projectedDocumentIndex, projectedDocumentIndex), marker));
+ var changedDocument = context.CSharpWorkspaceDocument.WithText(changedText);
+
+ // Get the line number at the position after the marker
+ var line = changedText.Lines.GetLinePosition(projectedDocumentIndex + marker.Length).Line;
+
+ try
+ {
+ var result = _getIndentationMethod.Invoke(
+ _indentationService,
+ new object[] { changedDocument, line, CodeAnalysis.Formatting.FormattingOptions.IndentStyle.Smart, cancellationToken });
+
+ var baseProperty = result.GetType().GetProperty("BasePosition");
+ var basePosition = (int)baseProperty.GetValue(result);
+ var offsetProperty = result.GetType().GetProperty("Offset");
+ var offset = (int)offsetProperty.GetValue(result);
+
+ var resultLine = changedText.Lines.GetLinePosition(basePosition);
+ var indentation = resultLine.Character + offset;
+
+ // IIndentationService always returns offset as the number of spaces.
+ // So if the client uses tabs instead of spaces, we need to convert accordingly.
+ if (!context.Options.InsertSpaces)
+ {
+ indentation /= (int)context.Options.TabSize;
+ }
+
+ return indentation;
+ }
+ catch (Exception ex)
+ {
+ throw new InvalidOperationException(
+ "Error occured when reflection invoking Roslyn's IIndentationService. Roslyn may have changed in an unexpected way.",
+ ex);
+ }
+ }
+
private TextEdit[] MapEditsToHostDocument(RazorCodeDocument codeDocument, TextEdit[] csharpEdits)
{
var actualEdits = new List();
@@ -104,18 +166,16 @@ private TextEdit[] MapEditsToHostDocument(RazorCodeDocument codeDocument, TextEd
}
private async Task FormatOnClientAsync(
- RazorCodeDocument codeDocument,
+ FormattingContext context,
Range projectedRange,
- DocumentUri uri,
- FormattingOptions options,
CancellationToken cancellationToken)
{
var @params = new RazorDocumentRangeFormattingParams()
{
Kind = RazorLanguageKind.CSharp,
ProjectedRange = projectedRange,
- HostDocumentFilePath = _filePathNormalizer.Normalize(uri.GetAbsoluteOrUNCPath()),
- Options = options
+ HostDocumentFilePath = _filePathNormalizer.Normalize(context.Uri.GetAbsoluteOrUNCPath()),
+ Options = context.Options
};
var response = _server.SendRequest(LanguageServerConstants.RazorRangeFormattingEndpoint, @params);
@@ -125,26 +185,19 @@ private async Task FormatOnClientAsync(
}
private async Task FormatOnServerAsync(
- RazorCodeDocument codeDocument,
+ FormattingContext context,
Range projectedRange,
- DocumentUri uri,
- FormattingOptions options,
CancellationToken cancellationToken)
{
- var workspace = _projectSnapshotManagerAccessor.Instance.Workspace;
- var csharpOptions = workspace.Options
- .WithChangedOption(CodeAnalysis.Formatting.FormattingOptions.TabSize, LanguageNames.CSharp, (int)options.TabSize)
- .WithChangedOption(CodeAnalysis.Formatting.FormattingOptions.UseTabs, LanguageNames.CSharp, !options.InsertSpaces);
-
- var csharpDocument = codeDocument.GetCSharpDocument();
- var syntaxTree = CSharpSyntaxTree.ParseText(csharpDocument.GeneratedCode);
- var sourceText = SourceText.From(csharpDocument.GeneratedCode);
- var root = await syntaxTree.GetRootAsync();
- var spanToFormat = projectedRange.AsTextSpan(sourceText);
+ var csharpSourceText = context.CodeDocument.GetCSharpSourceText();
+ var spanToFormat = projectedRange.AsTextSpan(csharpSourceText);
+ var root = await context.CSharpWorkspaceDocument.GetSyntaxRootAsync(cancellationToken);
+ var workspace = context.CSharpWorkspace;
- var changes = CodeAnalysis.Formatting.Formatter.GetFormattedTextChanges(root, spanToFormat, workspace, options: csharpOptions);
+ // Formatting options will already be set in the workspace.
+ var changes = CodeAnalysis.Formatting.Formatter.GetFormattedTextChanges(root, spanToFormat, workspace);
- var edits = changes.Select(c => c.AsTextEdit(sourceText)).ToArray();
+ var edits = changes.Select(c => c.AsTextEdit(csharpSourceText)).ToArray();
return edits;
}
}
diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/CSharpFormattingPass.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/CSharpFormattingPass.cs
index de608818cf0..905785d3316 100644
--- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/CSharpFormattingPass.cs
+++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/CSharpFormattingPass.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -10,6 +11,7 @@
using Microsoft.AspNetCore.Razor.Language.Legacy;
using Microsoft.AspNetCore.Razor.Language.Syntax;
using Microsoft.AspNetCore.Razor.LanguageServer.Common;
+using Microsoft.CodeAnalysis.Text;
using Microsoft.Extensions.Logging;
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
using OmniSharp.Extensions.LanguageServer.Protocol.Server;
@@ -25,9 +27,8 @@ public CSharpFormattingPass(
RazorDocumentMappingService documentMappingService,
FilePathNormalizer filePathNormalizer,
IClientLanguageServer server,
- ProjectSnapshotManagerAccessor projectSnapshotManagerAccessor,
ILoggerFactory loggerFactory)
- : base(documentMappingService, filePathNormalizer, server, projectSnapshotManagerAccessor)
+ : base(documentMappingService, filePathNormalizer, server)
{
if (loggerFactory is null)
{
@@ -72,23 +73,17 @@ public async override Task ExecuteAsync(FormattingContext cont
cancellationToken.ThrowIfCancellationRequested();
- // Now, for each affected line in the edited version of the document, remove x amount of spaces
- // at the front to account for extra indentation applied by the C# formatter.
- // This should be based on context.
- // For instance, lines inside @code/@functions block should be reduced one level
- // and lines inside @{} should be reduced by two levels.
- var indentationChanges = AdjustCSharpIndentation(changedContext, startLine: 0, endLine: changedText.Lines.Count - 1);
+ // We make an optimistic attempt at fixing corner cases.
+ changedText = CleanupDocument(changedContext);
+ changedContext = await changedContext.WithTextAsync(changedText);
+ var indentationChanges = AdjustIndentation(changedContext, cancellationToken);
if (indentationChanges.Count > 0)
{
// Apply the edits that modify indentation.
changedText = changedText.WithChanges(indentationChanges);
- changedContext = await changedContext.WithTextAsync(changedText);
}
- // We make an optimistic attempt at fixing corner cases.
- changedText = CleanupDocument(changedContext);
-
var finalChanges = SourceTextDiffer.GetMinimalTextChanges(originalText, changedText, lineDiffOnly: false);
var finalEdits = finalChanges.Select(f => f.AsTextEdit(originalText)).ToArray();
@@ -110,13 +105,132 @@ private async Task> FormatCSharpAsync(FormattingContext context,
}
// These should already be remapped.
- var edits = await CSharpFormatter.FormatAsync(context.CodeDocument, range, context.Uri, context.Options, cancellationToken);
+ var edits = await CSharpFormatter.FormatAsync(context, range, cancellationToken);
csharpEdits.AddRange(edits.Where(e => range.Contains(e.Range)));
}
return csharpEdits;
}
+ private List AdjustIndentation(FormattingContext context, CancellationToken cancellationToken, Range range = null)
+ {
+ // In this method, the goal is to make final adjustments to the indentation of each line.
+ // We will take into account the following,
+ // 1. The indentation due to nested C# structures
+ // 2. The indentation due to Razor and HTML constructs
+
+ var text = context.SourceText;
+ range ??= TextSpan.FromBounds(0, text.Length).AsRange(text);
+
+ // First, let's build an understanding of the desired C# indentation at the beginning and end of each source mapping.
+ var sourceMappingIndentations = new SortedDictionary();
+ foreach (var mapping in context.CodeDocument.GetCSharpDocument().SourceMappings)
+ {
+ var mappingSpan = new TextSpan(mapping.OriginalSpan.AbsoluteIndex, mapping.OriginalSpan.Length);
+ var mappingRange = mappingSpan.AsRange(context.SourceText);
+ if (!ShouldFormat(context, mappingRange.Start))
+ {
+ // We don't care about this range as this can potentially lead to incorrect scopes.
+ continue;
+ }
+
+ var startIndentation = CSharpFormatter.GetCSharpIndentation(context, mapping.GeneratedSpan.AbsoluteIndex, cancellationToken);
+ sourceMappingIndentations[mapping.OriginalSpan.AbsoluteIndex] = startIndentation;
+
+ var endIndentation = CSharpFormatter.GetCSharpIndentation(context, mapping.GeneratedSpan.AbsoluteIndex + mapping.GeneratedSpan.Length + 1, cancellationToken);
+ sourceMappingIndentations[mapping.OriginalSpan.AbsoluteIndex + mapping.OriginalSpan.Length + 1] = endIndentation;
+ }
+
+ var sourceMappingIndentationScopes = sourceMappingIndentations.Keys.ToArray();
+
+ // Now, let's combine the C# desired indentation with the Razor and HTML indentation for each line.
+ var newIndentations = new Dictionary();
+ for (var i = range.Start.Line; i <= range.End.Line; i++)
+ {
+ var line = context.SourceText.Lines[i];
+ if (line.Span.Length == 0)
+ {
+ // We don't want to indent empty lines.
+ continue;
+ }
+
+ var lineStart = line.Start;
+ int csharpDesiredIndentation;
+ if (DocumentMappingService.TryMapToProjectedDocumentPosition(context.CodeDocument, lineStart, out _, out var projectedLineStart))
+ {
+ // We were able to map this line to C# directly.
+ csharpDesiredIndentation = CSharpFormatter.GetCSharpIndentation(context, projectedLineStart, cancellationToken);
+ }
+ else
+ {
+ // Couldn't remap. This is probably a non-C# location.
+ // Use SourceMapping indentations to locate the C# scope of this line.
+ // E.g,
+ //
+ // @if (true) {
+ //
+ // |
+ // }
+ //
+ // We can't find a direct mapping at |, but we can infer its base indentation from the
+ // indentation of the latest source mapping prior to this line.
+ // We use binary search to find that spot.
+
+ var index = Array.BinarySearch(sourceMappingIndentationScopes, lineStart);
+ if (index < 0)
+ {
+ // Couldn't find the exact value. Find the index of the element to the left of the searched value.
+ index = (~index) - 1;
+ }
+
+ // This will now be set to the same value as the end of the closest source mapping.
+ csharpDesiredIndentation = index < 0 ? 0 : sourceMappingIndentations[sourceMappingIndentationScopes[index]];
+ }
+
+ // Now let's use that information to figure out the effective C# indentation.
+ // This should be based on context.
+ // For instance, lines inside @code/@functions block should be reduced one level
+ // and lines inside @{} should be reduced by two levels.
+
+ var csharpDesiredIndentLevel = context.GetIndentationLevelForOffset(csharpDesiredIndentation);
+ var minCSharpIndentLevel = context.Indentations[i].MinCSharpIndentLevel;
+ if (csharpDesiredIndentLevel < minCSharpIndentLevel)
+ {
+ // CSharp formatter doesn't want to indent this. Let's not touch it.
+ continue;
+ }
+
+ var effectiveCSharpDesiredIndentationLevel = csharpDesiredIndentLevel - minCSharpIndentLevel;
+ var razorDesiredIndentationLevel = context.Indentations[i].IndentationLevel;
+ if (!context.Indentations[i].StartsInCSharpContext)
+ {
+ // This is a non-C# line. Given that the HTML formatter ran before this, we can assume
+ // HTML is already correctly formatted. So we can use the existing indentation as is.
+ razorDesiredIndentationLevel = context.GetIndentationLevelForOffset(context.Indentations[i].ExistingIndentation);
+ }
+ var effectiveDesiredIndentationLevel = razorDesiredIndentationLevel + effectiveCSharpDesiredIndentationLevel;
+
+ // This will now contain the indentation we ultimately want to apply to this line.
+ newIndentations[i] = effectiveDesiredIndentationLevel;
+ }
+
+ // Now that we have collected all the indentations for each line, let's convert them to text edits.
+ var changes = new List();
+ foreach (var item in newIndentations)
+ {
+ var line = item.Key;
+ var indentationLevel = item.Value;
+ Debug.Assert(indentationLevel >= 0, "Negative indent level. This is unexpected.");
+
+ var existingIndentationLength = context.Indentations[line].ExistingIndentation;
+ var spanToReplace = new TextSpan(context.SourceText.Lines[line].Start, existingIndentationLength);
+ var effectiveDesiredIndentation = context.GetIndentationLevelString(indentationLevel);
+ changes.Add(new TextChange(spanToReplace, effectiveDesiredIndentation));
+ }
+
+ return changes;
+ }
+
private static bool ShouldFormat(FormattingContext context, Position position)
{
// We should be called with start positions of various C# SourceMappings.
diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/CSharpOnTypeFormattingPass.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/CSharpOnTypeFormattingPass.cs
index db364a31a5d..a4a977a1fcc 100644
--- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/CSharpOnTypeFormattingPass.cs
+++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/CSharpOnTypeFormattingPass.cs
@@ -22,9 +22,8 @@ public CSharpOnTypeFormattingPass(
RazorDocumentMappingService documentMappingService,
FilePathNormalizer filePathNormalizer,
IClientLanguageServer server,
- ProjectSnapshotManagerAccessor projectSnapshotManagerAccessor,
ILoggerFactory loggerFactory)
- : base(documentMappingService, filePathNormalizer, server, projectSnapshotManagerAccessor)
+ : base(documentMappingService, filePathNormalizer, server)
{
if (loggerFactory is null)
{
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 a31e6b0f7c2..93f5d455da8 100644
--- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/DefaultRazorFormattingService.cs
+++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/DefaultRazorFormattingService.cs
@@ -58,7 +58,7 @@ public override async Task FormatAsync(DocumentUri uri, DocumentSnap
}
var codeDocument = await documentSnapshot.GetGeneratedOutputAsync();
- var context = FormattingContext.Create(uri, documentSnapshot, codeDocument, options, range);
+ using var context = FormattingContext.Create(uri, documentSnapshot, codeDocument, options, range);
var result = new FormattingResult(Array.Empty());
foreach (var pass in _formattingPasses)
@@ -80,7 +80,7 @@ public override async Task ApplyFormattedEditsAsync(DocumentUri uri,
}
var codeDocument = await documentSnapshot.GetGeneratedOutputAsync();
- var context = FormattingContext.Create(uri, documentSnapshot, codeDocument, options, isFormatOnType: true);
+ using var context = FormattingContext.Create(uri, documentSnapshot, codeDocument, options, isFormatOnType: true);
var result = new FormattingResult(formattedEdits, kind);
foreach (var pass in _formattingPasses)
diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/FormattingContentValidationPass.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/FormattingContentValidationPass.cs
index 72239e12df9..015a00f554e 100644
--- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/FormattingContentValidationPass.cs
+++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/FormattingContentValidationPass.cs
@@ -19,9 +19,8 @@ public FormattingContentValidationPass(
RazorDocumentMappingService documentMappingService,
FilePathNormalizer filePathNormalizer,
IClientLanguageServer server,
- ProjectSnapshotManagerAccessor projectSnapshotManagerAccessor,
ILoggerFactory loggerFactory)
- : base(documentMappingService, filePathNormalizer, server, projectSnapshotManagerAccessor)
+ : base(documentMappingService, filePathNormalizer, server)
{
if (loggerFactory is null)
{
diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/FormattingContext.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/FormattingContext.cs
index 76496dc840b..7f280063029 100644
--- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/FormattingContext.cs
+++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/FormattingContext.cs
@@ -7,6 +7,7 @@
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.Language.Legacy;
using Microsoft.AspNetCore.Razor.LanguageServer.Common;
+using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
using Microsoft.CodeAnalysis.Text;
using OmniSharp.Extensions.LanguageServer.Protocol;
@@ -15,25 +16,53 @@
namespace Microsoft.AspNetCore.Razor.LanguageServer.Formatting
{
- internal class FormattingContext
+ internal class FormattingContext : IDisposable
{
- public DocumentUri Uri { get; set; }
+ private Document _csharpWorkspaceDocument;
- public DocumentSnapshot OriginalSnapshot { get; set; }
+ public DocumentUri Uri { get; private set; }
- public RazorCodeDocument CodeDocument { get; set; }
+ public DocumentSnapshot OriginalSnapshot { get; private set; }
- public SourceText SourceText => CodeDocument?.GetSourceText();
+ public RazorCodeDocument CodeDocument { get; private set; }
- public FormattingOptions Options { get; set; }
+ public SourceText SourceText => CodeDocument.GetSourceText();
+
+ public SourceText CSharpSourceText => CodeDocument.GetCSharpSourceText();
+
+ public Document CSharpWorkspaceDocument
+ {
+ get
+ {
+ if (_csharpWorkspaceDocument == null)
+ {
+ var adhocWorkspace = new AdhocWorkspace();
+ var csharpOptions = adhocWorkspace.Options
+ .WithChangedOption(CodeAnalysis.Formatting.FormattingOptions.TabSize, LanguageNames.CSharp, (int)Options.TabSize)
+ .WithChangedOption(CodeAnalysis.Formatting.FormattingOptions.IndentationSize, LanguageNames.CSharp, (int)Options.TabSize)
+ .WithChangedOption(CodeAnalysis.Formatting.FormattingOptions.UseTabs, LanguageNames.CSharp, !Options.InsertSpaces);
+ adhocWorkspace.TryApplyChanges(adhocWorkspace.CurrentSolution.WithOptions(csharpOptions));
+
+ var project = adhocWorkspace.AddProject("TestProject", LanguageNames.CSharp);
+ var csharpSourceText = CodeDocument.GetCSharpSourceText();
+ _csharpWorkspaceDocument = adhocWorkspace.AddDocument(project.Id, "TestDocument", csharpSourceText);
+ }
+
+ return _csharpWorkspaceDocument;
+ }
+ }
+
+ public Workspace CSharpWorkspace => CSharpWorkspaceDocument.Project.Solution.Workspace;
+
+ public FormattingOptions Options { get; private set; }
public string NewLineString => Environment.NewLine;
- public bool IsFormatOnType { get; set; }
+ public bool IsFormatOnType { get; private set; }
- public Range Range { get; set; }
+ public Range Range { get; private set; }
- public Dictionary Indentations { get; } = new Dictionary();
+ public IReadOnlyDictionary Indentations { get; private set; }
///
/// Generates a string of indentation based on a specific indentation level. For instance, inside of a C# method represents 1 indentation level. A method within a class would have indentaiton level of 2 by default etc.
@@ -71,6 +100,21 @@ public string GetIndentationString(int indentation)
}
}
+ ///
+ /// Given an offset return the corresponding indent level.
+ ///
+ /// A value represents the number of spaces/tabs at the start of a line.
+ /// The corresponding indent level.
+ public int GetIndentationLevelForOffset(int offset)
+ {
+ if (Options.InsertSpaces)
+ {
+ offset /= (int)Options.TabSize;
+ }
+
+ return offset;
+ }
+
public bool TryGetIndentationLevel(int position, out int indentationLevel)
{
var syntaxTree = CodeDocument.GetSyntaxTree();
@@ -85,6 +129,15 @@ public bool TryGetIndentationLevel(int position, out int indentationLevel)
return false;
}
+ public void Dispose()
+ {
+ if (_csharpWorkspaceDocument != null)
+ {
+ CSharpWorkspace.Dispose();
+ _csharpWorkspaceDocument = null;
+ }
+ }
+
public async Task WithTextAsync(SourceText changedText)
{
if (changedText is null)
@@ -110,14 +163,14 @@ public async Task WithTextAsync(SourceText changedText)
var codeDocument = engine.ProcessDesignTime(changedSourceDocument, OriginalSnapshot.FileKind, importSources, OriginalSnapshot.Project.TagHelpers);
- var newContext = Create(Uri, OriginalSnapshot, codeDocument, Options, Range);
+ var newContext = Create(Uri, OriginalSnapshot, codeDocument, Options, Range, IsFormatOnType);
return newContext;
}
public static FormattingContext Create(
DocumentUri uri,
DocumentSnapshot originalSnapshot,
- RazorCodeDocument codedocument,
+ RazorCodeDocument codeDocument,
FormattingOptions options,
Range range = null,
bool isFormatOnType = false)
@@ -132,9 +185,9 @@ public static FormattingContext Create(
throw new ArgumentNullException(nameof(originalSnapshot));
}
- if (codedocument is null)
+ if (codeDocument is null)
{
- throw new ArgumentNullException(nameof(codedocument));
+ throw new ArgumentNullException(nameof(codeDocument));
}
if (options is null)
@@ -142,22 +195,23 @@ public static FormattingContext Create(
throw new ArgumentNullException(nameof(options));
}
- var text = codedocument.GetSourceText();
+ var text = codeDocument.GetSourceText();
range ??= TextSpan.FromBounds(0, text.Length).AsRange(text);
var result = new FormattingContext()
{
Uri = uri,
OriginalSnapshot = originalSnapshot,
- CodeDocument = codedocument,
+ CodeDocument = codeDocument,
Range = range,
Options = options,
IsFormatOnType = isFormatOnType
};
- var source = codedocument.Source;
- var syntaxTree = codedocument.GetSyntaxTree();
+ var source = codeDocument.Source;
+ var syntaxTree = codeDocument.GetSyntaxTree();
var formattingSpans = syntaxTree.GetFormattingSpans();
+ var indentations = new Dictionary();
var total = 0;
var previousIndentationLevel = 0;
@@ -179,7 +233,7 @@ public static FormattingContext Create(
// position now contains the first non-whitespace character or 0. Get the corresponding FormattingSpan.
if (TryGetFormattingSpan(total + nonWsChar, formattingSpans, out var span))
{
- result.Indentations[i] = new IndentationContext
+ indentations[i] = new IndentationContext
{
Line = i,
IndentationLevel = span.IndentationLevel,
@@ -201,7 +255,7 @@ public static FormattingContext Create(
indentationLevel: 0,
isInClassBody: false);
- result.Indentations[i] = new IndentationContext
+ indentations[i] = new IndentationContext
{
Line = i,
IndentationLevel = 0,
@@ -214,6 +268,8 @@ public static FormattingContext Create(
total += lineLength;
}
+ result.Indentations = indentations;
+
return result;
}
diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/FormattingDiagnosticValidationPass.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/FormattingDiagnosticValidationPass.cs
index 23a859b18db..a8f2bb0531b 100644
--- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/FormattingDiagnosticValidationPass.cs
+++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/FormattingDiagnosticValidationPass.cs
@@ -22,9 +22,8 @@ public FormattingDiagnosticValidationPass(
RazorDocumentMappingService documentMappingService,
FilePathNormalizer filePathNormalizer,
IClientLanguageServer server,
- ProjectSnapshotManagerAccessor projectSnapshotManagerAccessor,
ILoggerFactory loggerFactory)
- : base(documentMappingService, filePathNormalizer, server, projectSnapshotManagerAccessor)
+ : base(documentMappingService, filePathNormalizer, server)
{
if (loggerFactory is null)
{
diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/FormattingPassBase.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/FormattingPassBase.cs
index bd756bf79cd..ebcfccf3ec1 100644
--- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/FormattingPassBase.cs
+++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/FormattingPassBase.cs
@@ -21,13 +21,10 @@ internal abstract class FormattingPassBase : IFormattingPass
{
protected static readonly int DefaultOrder = 1000;
- private readonly RazorDocumentMappingService _documentMappingService;
-
public FormattingPassBase(
RazorDocumentMappingService documentMappingService,
FilePathNormalizer filePathNormalizer,
- IClientLanguageServer server,
- ProjectSnapshotManagerAccessor projectSnapshotManagerAccessor)
+ IClientLanguageServer server)
{
if (documentMappingService is null)
{
@@ -44,18 +41,15 @@ public FormattingPassBase(
throw new ArgumentNullException(nameof(server));
}
- if (projectSnapshotManagerAccessor is null)
- {
- throw new ArgumentNullException(nameof(projectSnapshotManagerAccessor));
- }
-
- _documentMappingService = documentMappingService;
- CSharpFormatter = new CSharpFormatter(documentMappingService, server, projectSnapshotManagerAccessor, filePathNormalizer);
+ DocumentMappingService = documentMappingService;
+ CSharpFormatter = new CSharpFormatter(documentMappingService, server, filePathNormalizer);
HtmlFormatter = new HtmlFormatter(server, filePathNormalizer);
}
public virtual int Order => DefaultOrder;
+ protected RazorDocumentMappingService DocumentMappingService { get; }
+
protected CSharpFormatter CSharpFormatter { get; }
protected HtmlFormatter HtmlFormatter { get; }
@@ -93,7 +87,7 @@ protected TextEdit[] RemapTextEdits(RazorCodeDocument codeDocument, TextEdit[] p
{
var projectedRange = projectedTextEdits[i].Range;
if (codeDocument.IsUnsupported() ||
- !_documentMappingService.TryMapFromProjectedDocumentRange(codeDocument, projectedRange, out var originalRange))
+ !DocumentMappingService.TryMapFromProjectedDocumentRange(codeDocument, projectedRange, out var originalRange))
{
// Can't map range. Discard this edit.
continue;
@@ -243,7 +237,7 @@ protected static SourceText CleanupDocument(FormattingContext context, Range ran
continue;
}
- var mappingStartLineIndex = (int)mappingRange.Start.Line;
+ var mappingStartLineIndex = mappingRange.Start.Line;
if (context.Indentations[mappingStartLineIndex].StartsInCSharpContext)
{
// Doesn't need cleaning up.
diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/FormattingVisitor.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/FormattingVisitor.cs
index cb403cf8449..fcdd7b57aeb 100644
--- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/FormattingVisitor.cs
+++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/FormattingVisitor.cs
@@ -48,7 +48,10 @@ public override void VisitRazorCommentBlock(RazorCommentBlockSyntax node)
// We need to generate a formatting span at this position. So insert a marker in its place.
comment = (SyntaxToken)SyntaxFactory.Token(SyntaxKind.Marker, string.Empty).Green.CreateRed(razorCommentSyntax, razorCommentSyntax.StartCommentStar.EndPosition);
}
+
+ _currentIndentationLevel++;
WriteSpan(comment, FormattingSpanKind.Comment);
+ _currentIndentationLevel--;
WriteSpan(razorCommentSyntax.EndCommentStar, FormattingSpanKind.MetaCode);
WriteSpan(razorCommentSyntax.EndCommentTransition, FormattingSpanKind.Transition);
diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/HtmlFormatter.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/HtmlFormatter.cs
index 4ffcbe623f9..fc1ee50c01c 100644
--- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/HtmlFormatter.cs
+++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/HtmlFormatter.cs
@@ -4,12 +4,9 @@
using System;
using System.Threading;
using System.Threading.Tasks;
-using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.LanguageServer.Common;
-using OmniSharp.Extensions.LanguageServer.Protocol;
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
using OmniSharp.Extensions.LanguageServer.Protocol.Server;
-using LSPFormattingOptions = OmniSharp.Extensions.LanguageServer.Protocol.Models.FormattingOptions;
using Range = OmniSharp.Extensions.LanguageServer.Protocol.Models.Range;
namespace Microsoft.AspNetCore.Razor.LanguageServer.Formatting
@@ -38,18 +35,26 @@ public HtmlFormatter(
}
public async Task FormatAsync(
- RazorCodeDocument codeDocument,
- Range range,
- DocumentUri uri,
- LSPFormattingOptions options,
+ FormattingContext context,
+ Range rangeToFormat,
CancellationToken cancellationToken)
{
+ if (context is null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ if (rangeToFormat is null)
+ {
+ throw new ArgumentNullException(nameof(rangeToFormat));
+ }
+
var @params = new RazorDocumentRangeFormattingParams()
{
Kind = RazorLanguageKind.Html,
- ProjectedRange = range,
- HostDocumentFilePath = _filePathNormalizer.Normalize(uri.GetAbsoluteOrUNCPath()),
- Options = options
+ ProjectedRange = rangeToFormat,
+ HostDocumentFilePath = _filePathNormalizer.Normalize(context.Uri.GetAbsoluteOrUNCPath()),
+ Options = context.Options
};
var response = _server.SendRequest(LanguageServerConstants.RazorRangeFormattingEndpoint, @params);
diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/HtmlFormattingPass.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/HtmlFormattingPass.cs
index a046a97c2af..714f443e24d 100644
--- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/HtmlFormattingPass.cs
+++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/HtmlFormattingPass.cs
@@ -21,9 +21,8 @@ public HtmlFormattingPass(
RazorDocumentMappingService documentMappingService,
FilePathNormalizer filePathNormalizer,
IClientLanguageServer server,
- ProjectSnapshotManagerAccessor projectSnapshotManagerAccessor,
ILoggerFactory loggerFactory)
- : base(documentMappingService, filePathNormalizer, server, projectSnapshotManagerAccessor)
+ : base(documentMappingService, filePathNormalizer, server)
{
if (loggerFactory is null)
{
@@ -46,20 +45,20 @@ public async override Task ExecuteAsync(FormattingContext cont
var originalText = context.SourceText;
- var htmlEdits = await HtmlFormatter.FormatAsync(context.CodeDocument, context.Range, context.Uri, context.Options, cancellationToken);
+ var htmlEdits = await HtmlFormatter.FormatAsync(context, context.Range, cancellationToken);
var normalizedEdits = NormalizeTextEdits(originalText, htmlEdits);
var mappedEdits = RemapTextEdits(context.CodeDocument, normalizedEdits, RazorLanguageKind.Html);
var changes = mappedEdits.Select(e => e.AsTextChange(originalText));
- if (!changes.Any())
+
+ var changedText = originalText;
+ var changedContext = context;
+ if (changes.Any())
{
- return result;
+ changedText = originalText.WithChanges(changes);
+ // Create a new formatting context for the changed razor document.
+ changedContext = await context.WithTextAsync(changedText);
}
- var changedText = originalText.WithChanges(changes);
-
- // Create a new formatting context for the changed razor document.
- var changedContext = await context.WithTextAsync(changedText);
-
var indentationChanges = AdjustRazorIndentation(changedContext);
if (indentationChanges.Count > 0)
{
diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/OnTypeFormattingStructureValidationPass.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/OnTypeFormattingStructureValidationPass.cs
index 2c708c9acba..5dd5c46566d 100644
--- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/OnTypeFormattingStructureValidationPass.cs
+++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/OnTypeFormattingStructureValidationPass.cs
@@ -21,9 +21,8 @@ public OnTypeFormattingStructureValidationPass(
RazorDocumentMappingService documentMappingService,
FilePathNormalizer filePathNormalizer,
IClientLanguageServer server,
- ProjectSnapshotManagerAccessor projectSnapshotManagerAccessor,
ILoggerFactory loggerFactory)
- : base(documentMappingService, filePathNormalizer, server, projectSnapshotManagerAccessor)
+ : base(documentMappingService, filePathNormalizer, server)
{
if (loggerFactory is null)
{
diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting/CodeDirectiveFormattingTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting/CodeDirectiveFormattingTest.cs
index 8ce4f6f468a..9f9f89bf70d 100644
--- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting/CodeDirectiveFormattingTest.cs
+++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting/CodeDirectiveFormattingTest.cs
@@ -133,8 +133,8 @@ void Method() { }
@functions {
public class Foo
{
-@* This is a Razor Comment *@
-void Method() { }
+ @* This is a Razor Comment *@
+ void Method() { }
}
}
");
@@ -155,7 +155,7 @@ public class Foo{
@functions {
public class Foo
{
-@* This is a Razor Comment *@
+ @* This is a Razor Comment *@
}
}
");
@@ -254,7 +254,7 @@ public class HelloWorld
}
@functions{
-
+
public class Bar {}
}
|",
@@ -323,7 +323,7 @@ public class Foo { }
");
}
- [Fact(Skip = "https://github.com/dotnet/aspnetcore/issues/25475")]
+ [Fact]
public async Task IndentsCodeBlockDirectiveStart()
{
await RunFormattingTestAsync(
@@ -340,7 +340,7 @@ public class Foo { }
");
}
- [Fact(Skip = "https://github.com/dotnet/aspnetcore/issues/25475")]
+ [Fact]
public async Task IndentsCodeBlockDirectiveEnd()
{
await RunFormattingTestAsync(
@@ -371,6 +371,11 @@ public Foo()
""One"", ""two"",
""three""
};
+ var str = @""
+This should
+not
+be indented.
+"";
}
public int MyProperty { get
{
@@ -391,9 +396,14 @@ public class Foo
public Foo()
{
var arr = new string[] {
-""One"", ""two"",
-""three""
- };
+ ""One"", ""two"",
+ ""three""
+ };
+ var str = @""
+This should
+not
+be indented.
+"";
}
public int MyProperty
{
diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting/FormattingContentValidationPassTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting/FormattingContentValidationPassTest.cs
index 4cf1fca295a..5a0ae92f496 100644
--- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting/FormattingContentValidationPassTest.cs
+++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting/FormattingContentValidationPassTest.cs
@@ -120,8 +120,7 @@ private FormattingContentValidationPass GetPass(RazorCodeDocument codeDocument)
var mappingService = new DefaultRazorDocumentMappingService();
var client = Mock.Of();
- var projectSnapshotManagerAccessor = Mock.Of();
- var pass = new FormattingContentValidationPass(mappingService, FilePathNormalizer, client, projectSnapshotManagerAccessor, LoggerFactory);
+ var pass = new FormattingContentValidationPass(mappingService, FilePathNormalizer, client, LoggerFactory);
pass.DebugAssertsEnabled = false;
return pass;
diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting/FormattingDiagnosticValidationPassTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting/FormattingDiagnosticValidationPassTest.cs
index 5832802da34..c15696671f6 100644
--- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting/FormattingDiagnosticValidationPassTest.cs
+++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting/FormattingDiagnosticValidationPassTest.cs
@@ -127,8 +127,7 @@ private FormattingDiagnosticValidationPass GetPass(RazorCodeDocument codeDocumen
var mappingService = new DefaultRazorDocumentMappingService();
var client = Mock.Of();
- var projectSnapshotManagerAccessor = Mock.Of();
- var pass = new FormattingDiagnosticValidationPass(mappingService, FilePathNormalizer, client, projectSnapshotManagerAccessor, LoggerFactory);
+ var pass = new FormattingDiagnosticValidationPass(mappingService, FilePathNormalizer, client, LoggerFactory);
pass.DebugAssertsEnabled = false;
return pass;
diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting/FormattingLanguageServerClient.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting/FormattingLanguageServerClient.cs
index 8187210f7fd..98358be2497 100644
--- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting/FormattingLanguageServerClient.cs
+++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting/FormattingLanguageServerClient.cs
@@ -81,6 +81,7 @@ private RazorDocumentRangeFormattingResponse Format(RazorDocumentRangeFormatting
var workspace = new AdhocWorkspace();
var cSharpOptions = workspace.Options
.WithChangedOption(FormattingOptions.TabSize, LanguageNames.CSharp, (int)options.TabSize)
+ .WithChangedOption(FormattingOptions.IndentationSize, LanguageNames.CSharp, (int)options.TabSize)
.WithChangedOption(FormattingOptions.UseTabs, LanguageNames.CSharp, !options.InsertSpaces);
var codeDocument = _documents[@params.HostDocumentFilePath];
@@ -100,7 +101,7 @@ private RazorDocumentRangeFormattingResponse Format(RazorDocumentRangeFormatting
var codeDocument = _documents[@params.HostDocumentFilePath];
var generatedHtml = codeDocument.GetHtmlDocument().GeneratedHtml;
- var inputText = SourceText.From(generatedHtml);
+ generatedHtml = generatedHtml.Replace("\r", "", StringComparison.Ordinal).Replace("\n", "\r\n", StringComparison.Ordinal);
// Get formatted baseline file
var baselineInputFileName = Path.ChangeExtension(_baselineFileName, ".input.html");
diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting/FormattingTestBase.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting/FormattingTestBase.cs
index c60811bfdef..9af1697b825 100644
--- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting/FormattingTestBase.cs
+++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting/FormattingTestBase.cs
@@ -73,7 +73,7 @@ protected async Task RunFormattingTestAsync(string input, string expected, int t
#if GENERATE_BASELINES
Assert.False(true, "GENERATE_BASELINES is set to true.");
#else
- Assert.Equal(expected, actual);
+ Assert.Equal(expected, actual, ignoreLineEndingDifferences: true);
#endif
}
@@ -148,15 +148,14 @@ private RazorFormattingService CreateFormattingService(RazorCodeDocument codeDoc
var client = new FormattingLanguageServerClient(TestProjectPath, FileName);
client.AddCodeDocument(codeDocument);
- var projectSnapshotManagerAccessor = Mock.Of(p => p.Instance.Workspace == new AdhocWorkspace());
var passes = new List()
{
- new HtmlFormattingPass(mappingService, FilePathNormalizer, client, projectSnapshotManagerAccessor, LoggerFactory),
- new CSharpFormattingPass(mappingService, FilePathNormalizer, client, projectSnapshotManagerAccessor, LoggerFactory),
- new CSharpOnTypeFormattingPass(mappingService, FilePathNormalizer, client, projectSnapshotManagerAccessor, LoggerFactory),
- new OnTypeFormattingStructureValidationPass(mappingService, FilePathNormalizer, client, projectSnapshotManagerAccessor, LoggerFactory),
- new FormattingDiagnosticValidationPass(mappingService, FilePathNormalizer, client, projectSnapshotManagerAccessor, LoggerFactory),
- new FormattingContentValidationPass(mappingService, FilePathNormalizer, client, projectSnapshotManagerAccessor, LoggerFactory),
+ new HtmlFormattingPass(mappingService, FilePathNormalizer, client, LoggerFactory),
+ new CSharpFormattingPass(mappingService, FilePathNormalizer, client, LoggerFactory),
+ new CSharpOnTypeFormattingPass(mappingService, FilePathNormalizer, client, LoggerFactory),
+ new OnTypeFormattingStructureValidationPass(mappingService, FilePathNormalizer, client, LoggerFactory),
+ new FormattingDiagnosticValidationPass(mappingService, FilePathNormalizer, client, LoggerFactory),
+ new FormattingContentValidationPass(mappingService, FilePathNormalizer, client, LoggerFactory),
};
return new DefaultRazorFormattingService(passes, LoggerFactory);
diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting/HtmlFormattingTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting/HtmlFormattingTest.cs
index ae10e9133d9..01a7cb99fac 100644
--- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting/HtmlFormattingTest.cs
+++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting/HtmlFormattingTest.cs
@@ -137,6 +137,45 @@ This is heavily nested
}
}
+");
+ }
+
+ [Fact]
+ public async Task FormatsMixedRazorBlock()
+ {
+ await RunFormattingTestAsync(
+input: @"|@page ""/test""
+
+Some Text
+
+@{
+
+ @if (true) {
+ var t = 1;
+if (true)
+{
+
@DateTime.Now
+ }
+ }
+
+}
+|",
+expected: @"@page ""/test""
+
+Some Text
+
+@{
+
+ @if (true)
+ {
+ var t = 1;
+ if (true)
+ {
+
@DateTime.Now
+ }
+ }
+
+}
");
}
}
diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting/OnTypeFormattingStructureValidationPassTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting/OnTypeFormattingStructureValidationPassTest.cs
index 2eb1f2c985e..9cebe4708d8 100644
--- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting/OnTypeFormattingStructureValidationPassTest.cs
+++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting/OnTypeFormattingStructureValidationPassTest.cs
@@ -239,8 +239,7 @@ private OnTypeFormattingStructureValidationPass GetPass(RazorCodeDocument codeDo
var mappingService = new DefaultRazorDocumentMappingService();
var client = Mock.Of();
- var projectSnapshotManagerAccessor = Mock.Of();
- var pass = new OnTypeFormattingStructureValidationPass(mappingService, FilePathNormalizer, client, projectSnapshotManagerAccessor, LoggerFactory);
+ var pass = new OnTypeFormattingStructureValidationPass(mappingService, FilePathNormalizer, client, LoggerFactory);
return pass;
}
diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting/TestFiles/CodeDirectiveFormattingTest/ComplexCodeBlockDirective.input.html b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting/TestFiles/CodeDirectiveFormattingTest/ComplexCodeBlockDirective.input.html
index 1b380465303..9fcf437f0c0 100644
--- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting/TestFiles/CodeDirectiveFormattingTest/ComplexCodeBlockDirective.input.html
+++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting/TestFiles/CodeDirectiveFormattingTest/ComplexCodeBlockDirective.input.html
@@ -8,6 +8,11 @@
~~~~~~ ~~~~~~
~~~~~~~
~~
+ ~~~ ~~~ ~ ~~
+~~~~ ~~~~~~
+~~~
+~~ ~~~~~~~~~
+~~
~
~~~~~~ ~~~ ~~~~~~~~~~ ~ ~~~
~
diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting/TestFiles/CodeDirectiveFormattingTest/ComplexCodeBlockDirective.output.html b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting/TestFiles/CodeDirectiveFormattingTest/ComplexCodeBlockDirective.output.html
index 1b380465303..9fcf437f0c0 100644
--- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting/TestFiles/CodeDirectiveFormattingTest/ComplexCodeBlockDirective.output.html
+++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting/TestFiles/CodeDirectiveFormattingTest/ComplexCodeBlockDirective.output.html
@@ -8,6 +8,11 @@
~~~~~~ ~~~~~~
~~~~~~~
~~
+ ~~~ ~~~ ~ ~~
+~~~~ ~~~~~~
+~~~
+~~ ~~~~~~~~~
+~~
~
~~~~~~ ~~~ ~~~~~~~~~~ ~ ~~~
~
diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting/TestFiles/CodeDirectiveFormattingTest/IndentsCodeBlockDirectiveEnd.input.html b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting/TestFiles/CodeDirectiveFormattingTest/IndentsCodeBlockDirectiveEnd.input.html
new file mode 100644
index 00000000000..c66280585df
--- /dev/null
+++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting/TestFiles/CodeDirectiveFormattingTest/IndentsCodeBlockDirectiveEnd.input.html
@@ -0,0 +1,4 @@
+
+ ~~~~~~~~~~ ~
+~~~~~~ ~~~~~ ~~~~~
+ ~
diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting/TestFiles/CodeDirectiveFormattingTest/IndentsCodeBlockDirectiveEnd.output.html b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting/TestFiles/CodeDirectiveFormattingTest/IndentsCodeBlockDirectiveEnd.output.html
new file mode 100644
index 00000000000..6ce3818453b
--- /dev/null
+++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting/TestFiles/CodeDirectiveFormattingTest/IndentsCodeBlockDirectiveEnd.output.html
@@ -0,0 +1,3 @@
+~~~~~~~~~~ ~
+~~~~~~ ~~~~~ ~~~~~
+~
diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting/TestFiles/CodeDirectiveFormattingTest/IndentsCodeBlockDirectiveStart.input.html b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting/TestFiles/CodeDirectiveFormattingTest/IndentsCodeBlockDirectiveStart.input.html
new file mode 100644
index 00000000000..39e8a639c5e
--- /dev/null
+++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting/TestFiles/CodeDirectiveFormattingTest/IndentsCodeBlockDirectiveStart.input.html
@@ -0,0 +1,4 @@
+
+Hello World
+ ~~~~~~~~~~ ~~~~~~~ ~~~~~ ~~~~~
+~
diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting/TestFiles/CodeDirectiveFormattingTest/IndentsCodeBlockDirectiveStart.output.html b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting/TestFiles/CodeDirectiveFormattingTest/IndentsCodeBlockDirectiveStart.output.html
new file mode 100644
index 00000000000..ee1f93c1c69
--- /dev/null
+++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting/TestFiles/CodeDirectiveFormattingTest/IndentsCodeBlockDirectiveStart.output.html
@@ -0,0 +1,3 @@
+Hello World
+~~~~~~~~~~ ~~~~~~~ ~~~~~ ~~~~~
+~
diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting/TestFiles/CodeDirectiveFormattingTest/MultipleCodeBlockDirectives2.input.html b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting/TestFiles/CodeDirectiveFormattingTest/MultipleCodeBlockDirectives2.input.html
index 45999c85f8e..1b2dff4b5fe 100644
--- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting/TestFiles/CodeDirectiveFormattingTest/MultipleCodeBlockDirectives2.input.html
+++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting/TestFiles/CodeDirectiveFormattingTest/MultipleCodeBlockDirectives2.input.html
@@ -6,6 +6,6 @@
~
~~~~~~~~~~~
-
+
~~~~~~ ~~~~~ ~~~ ~~
~
diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting/TestFiles/HtmlFormattingTest/FormatsMixedRazorBlock.input.html b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting/TestFiles/HtmlFormattingTest/FormatsMixedRazorBlock.input.html
new file mode 100644
index 00000000000..ec0b6c69829
--- /dev/null
+++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting/TestFiles/HtmlFormattingTest/FormatsMixedRazorBlock.input.html
@@ -0,0 +1,15 @@
+~~~~~ ~~~~~~~
+
+Some Text
+
+~~
+
+ ~~~ ~~~~~~ ~
+ ~~~ ~ ~ ~~
+~~ ~~~~~~
+~
+
~~~~~~~~~~~~~
+ ~
+ ~
+
+~
diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting/TestFiles/HtmlFormattingTest/FormatsMixedRazorBlock.output.html b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting/TestFiles/HtmlFormattingTest/FormatsMixedRazorBlock.output.html
new file mode 100644
index 00000000000..b9863e193c8
--- /dev/null
+++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting/TestFiles/HtmlFormattingTest/FormatsMixedRazorBlock.output.html
@@ -0,0 +1,15 @@
+~~~~~ ~~~~~~~
+
+Some Text
+
+~~
+
+ ~~~ ~~~~~~ ~
+ ~~~ ~ ~ ~~
+ ~~ ~~~~~~
+ ~
+
~~~~~~~~~~~~~
+ ~
+ ~
+
+~