Create remote formatting service that delegates to the Razor formatting service

This commit is contained in:
David Wengier 2024-09-02 15:02:42 +10:00
Родитель cb0ae1321e
Коммит 3da875b6fe
5 изменённых файлов: 208 добавлений и 0 удалений

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

@ -29,5 +29,6 @@
<ServiceHubService Include="Microsoft.VisualStudio.Razor.GoToDefinition" ClassName="Microsoft.CodeAnalysis.Remote.Razor.RemoteGoToDefinitionService+Factory" />
<ServiceHubService Include="Microsoft.VisualStudio.Razor.Rename" ClassName="Microsoft.CodeAnalysis.Remote.Razor.RemoteRenameService+Factory" />
<ServiceHubService Include="Microsoft.VisualStudio.Razor.AutoInsert" ClassName="Microsoft.CodeAnalysis.Remote.Razor.RemoteAutoInsertService+Factory" />
<ServiceHubService Include="Microsoft.VisualStudio.Razor.Formatting" ClassName="Microsoft.CodeAnalysis.Remote.Razor.RemoteFormattingService+Factory" />
</ItemGroup>
</Project>

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

@ -1,17 +1,22 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license information.
using System.Runtime.Serialization;
using Microsoft.CodeAnalysis.ExternalAccess.Razor;
using Microsoft.VisualStudio.LanguageServer.Protocol;
namespace Microsoft.CodeAnalysis.Razor.Formatting;
[DataContract]
internal readonly record struct RazorFormattingOptions
{
public static readonly RazorFormattingOptions Default = new();
[DataMember(Order = 0)]
public bool InsertSpaces { get; init; } = true;
[DataMember(Order = 1)]
public int TabSize { get; init; } = 4;
[DataMember(Order = 2)]
public bool CodeBlockBraceOnNextLine { get; init; } = false;
public RazorFormattingOptions()

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

@ -0,0 +1,26 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license information.
using System.Collections.Immutable;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.ExternalAccess.Razor;
using Microsoft.CodeAnalysis.Razor.Formatting;
using Microsoft.CodeAnalysis.Text;
namespace Microsoft.CodeAnalysis.Razor.Remote;
internal interface IRemoteFormattingService
{
ValueTask<ImmutableArray<TextChange>> GetDocumentFormattingEditsAsync(RazorPinnedSolutionInfoWrapper solutionInfo, DocumentId documentId, ImmutableArray<TextChange> htmlChanges, RazorFormattingOptions options, CancellationToken cancellationToken);
ValueTask<ImmutableArray<TextChange>> GetRangeFormattingEditsAsync(RazorPinnedSolutionInfoWrapper solutionInfo, DocumentId documentId, ImmutableArray<TextChange> htmlChanges, LinePositionSpan linePositionSpan, RazorFormattingOptions options, CancellationToken cancellationToken);
ValueTask<ImmutableArray<TextChange>> GetOnTypeFormattingEditsAsync(RazorPinnedSolutionInfoWrapper solutionInfo, DocumentId documentId, ImmutableArray<TextChange> htmlChanges, LinePosition linePosition, string triggerCharacter, RazorFormattingOptions options, CancellationToken cancellationToken);
ValueTask<TriggerKind> GetOnTypeFormattingTriggerKindAsync(RazorPinnedSolutionInfoWrapper solutionInfo, DocumentId documentId, LinePosition linePosition, string triggerCharacter, CancellationToken cancellationToken);
internal enum TriggerKind
{
Invalid,
ValidHtml,
ValidCSharp,
}
}

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

@ -22,6 +22,7 @@ internal static class RazorServices
(typeof(IRemoteFoldingRangeService), null),
(typeof(IRemoteDocumentHighlightService), null),
(typeof(IRemoteAutoInsertService), null),
(typeof(IRemoteFormattingService), null),
];
// Internal for testing

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

@ -0,0 +1,175 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor;
using Microsoft.CodeAnalysis.ExternalAccess.Razor;
using Microsoft.CodeAnalysis.Razor.Formatting;
using Microsoft.CodeAnalysis.Razor.Protocol;
using Microsoft.CodeAnalysis.Razor.Remote;
using Microsoft.CodeAnalysis.Remote.Razor.ProjectSystem;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.LanguageServer.Protocol;
using Response = Microsoft.CodeAnalysis.Razor.Remote.IRemoteFormattingService.TriggerKind;
namespace Microsoft.CodeAnalysis.Remote.Razor;
internal sealed class RemoteFormattingService(in ServiceArgs args) : RazorDocumentServiceBase(in args), IRemoteFormattingService
{
internal sealed class Factory : FactoryBase<IRemoteFormattingService>
{
protected override IRemoteFormattingService CreateService(in ServiceArgs args)
=> new RemoteFormattingService(in args);
}
private readonly IRazorFormattingService _formattingService = args.ExportProvider.GetExportedValue<IRazorFormattingService>();
public ValueTask<ImmutableArray<TextChange>> GetDocumentFormattingEditsAsync(
RazorPinnedSolutionInfoWrapper solutionInfo,
DocumentId documentId,
ImmutableArray<TextChange> htmlChanges,
RazorFormattingOptions options,
CancellationToken cancellationToken)
=> RunServiceAsync(
solutionInfo,
documentId,
context => GetDocumentFormattingEditsAsync(context, htmlChanges, options, cancellationToken),
cancellationToken);
private async ValueTask<ImmutableArray<TextChange>> GetDocumentFormattingEditsAsync(
RemoteDocumentContext context,
ImmutableArray<TextChange> htmlChanges,
RazorFormattingOptions options,
CancellationToken cancellationToken)
{
var sourceText = await context.GetSourceTextAsync(cancellationToken).ConfigureAwait(false);
var htmlEdits = htmlChanges.Select(sourceText.GetTextEdit).ToArray();
var edits = await _formattingService.GetDocumentFormattingEditsAsync(context, htmlEdits, range: null, options, cancellationToken).ConfigureAwait(false);
if (edits is null)
{
return ImmutableArray<TextChange>.Empty;
}
return edits.SelectAsArray(sourceText.GetTextChange);
}
public ValueTask<ImmutableArray<TextChange>> GetRangeFormattingEditsAsync(
RazorPinnedSolutionInfoWrapper solutionInfo,
DocumentId documentId,
ImmutableArray<TextChange> htmlChanges,
LinePositionSpan linePositionSpan,
RazorFormattingOptions options,
CancellationToken cancellationToken)
=> RunServiceAsync(
solutionInfo,
documentId,
context => GetRangeFormattingEditsAsync(context, htmlChanges, linePositionSpan, options, cancellationToken),
cancellationToken);
private async ValueTask<ImmutableArray<TextChange>> GetRangeFormattingEditsAsync(
RemoteDocumentContext context,
ImmutableArray<TextChange> htmlChanges,
LinePositionSpan linePositionSpan,
RazorFormattingOptions options,
CancellationToken cancellationToken)
{
var sourceText = await context.GetSourceTextAsync(cancellationToken).ConfigureAwait(false);
var htmlEdits = htmlChanges.Select(sourceText.GetTextEdit).ToArray();
var edits = await _formattingService.GetDocumentFormattingEditsAsync(context, htmlEdits, range: linePositionSpan.ToRange(), options, cancellationToken).ConfigureAwait(false);
if (edits is null)
{
return ImmutableArray<TextChange>.Empty;
}
return edits.SelectAsArray(sourceText.GetTextChange);
}
public ValueTask<ImmutableArray<TextChange>> GetOnTypeFormattingEditsAsync(
RazorPinnedSolutionInfoWrapper solutionInfo,
DocumentId documentId,
ImmutableArray<TextChange> htmlChanges,
LinePosition linePosition,
string character,
RazorFormattingOptions options,
CancellationToken cancellationToken)
=> RunServiceAsync(
solutionInfo,
documentId,
context => GetOnTypeFormattingEditsAsync(context, htmlChanges, linePosition, character, options, cancellationToken),
cancellationToken);
private async ValueTask<ImmutableArray<TextChange>> GetOnTypeFormattingEditsAsync(RemoteDocumentContext context, ImmutableArray<TextChange> htmlChanges, LinePosition linePosition, string triggerCharacter, RazorFormattingOptions options, CancellationToken cancellationToken)
{
var codeDocument = await context.GetCodeDocumentAsync(cancellationToken).ConfigureAwait(false);
var sourceText = await context.GetSourceTextAsync(cancellationToken).ConfigureAwait(false);
if (!sourceText.TryGetAbsoluteIndex(linePosition, out var hostDocumentIndex))
{
return [];
}
if (!_formattingService.TryGetOnTypeFormattingTriggerKind(codeDocument, hostDocumentIndex, triggerCharacter, out var triggerCharacterKind))
{
return [];
}
TextEdit[] result;
if (triggerCharacterKind is RazorLanguageKind.Html)
{
var htmlEdits = htmlChanges.Select(sourceText.GetTextEdit).ToArray();
result = await _formattingService.GetHtmlOnTypeFormattingEditsAsync(context, htmlEdits, options, hostDocumentIndex, triggerCharacter[0], cancellationToken).ConfigureAwait(false);
}
else if (triggerCharacterKind is RazorLanguageKind.CSharp)
{
result = await _formattingService.GetCSharpOnTypeFormattingEditsAsync(context, options, hostDocumentIndex, triggerCharacter[0], cancellationToken).ConfigureAwait(false);
}
else
{
Assumed.Unreachable();
return [];
}
return result.SelectAsArray(sourceText.GetTextChange);
}
public ValueTask<Response> GetOnTypeFormattingTriggerKindAsync(
RazorPinnedSolutionInfoWrapper solutionInfo,
DocumentId documentId,
LinePosition linePosition,
string triggerCharacter,
CancellationToken cancellationToken)
=> RunServiceAsync(
solutionInfo,
documentId,
context => IsValidOnTypeFormattingTriggerAsync(context, linePosition, triggerCharacter, cancellationToken),
cancellationToken);
private async ValueTask<Response> IsValidOnTypeFormattingTriggerAsync(RemoteDocumentContext context, LinePosition linePosition, string triggerCharacter, CancellationToken cancellationToken)
{
var codeDocument = await context.GetCodeDocumentAsync(cancellationToken).ConfigureAwait(false);
var sourceText = await context.GetSourceTextAsync(cancellationToken).ConfigureAwait(false);
if (!sourceText.TryGetAbsoluteIndex(linePosition, out var hostDocumentIndex))
{
return Response.Invalid;
}
if (!_formattingService.TryGetOnTypeFormattingTriggerKind(codeDocument, hostDocumentIndex, triggerCharacter, out var triggerCharacterKind))
{
return Response.Invalid;
}
if (triggerCharacterKind is RazorLanguageKind.Html)
{
return Response.ValidHtml;
}
return Response.ValidCSharp;
}
}