Skip to content

Commit

Permalink
Better support for mixed formatting
Browse files Browse the repository at this point in the history
  • Loading branch information
ajaybhargavb committed Sep 22, 2020
1 parent ac2a03f commit b296c4f
Show file tree
Hide file tree
Showing 23 changed files with 434 additions and 132 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down Expand Up @@ -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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@
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;
Expand All @@ -23,12 +23,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)
Expand All @@ -41,50 +41,88 @@ 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));
}

_documentMappingService = documentMappingService;
_server = languageServer;
_projectSnapshotManagerAccessor = projectSnapshotManagerAccessor;
_filePathNormalizer = filePathNormalizer;

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");
}

public async Task<TextEdit[]> 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<TextEdit>();
}

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;

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;

return indentation;
}

private TextEdit[] MapEditsToHostDocument(RazorCodeDocument codeDocument, TextEdit[] csharpEdits)
{
var actualEdits = new List<TextEdit>();
Expand All @@ -104,18 +142,16 @@ private TextEdit[] MapEditsToHostDocument(RazorCodeDocument codeDocument, TextEd
}

private async Task<TextEdit[]> 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);
Expand All @@ -125,26 +161,19 @@ private async Task<TextEdit[]> FormatOnClientAsync(
}

private async Task<TextEdit[]> 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;
}
}
Expand Down
Loading

0 comments on commit b296c4f

Please sign in to comment.