зеркало из https://github.com/dotnet/razor.git
OnAutoInsert Cohosting Tests (#10829)
* OnAutoInsert Cohosting Tests * Fixing C# case (and correcting others) All text should already be in the document/buffer when OnAutoInsert is being executed. Tigger character is not being added to the buffer, it should already be in the buffer. * PR feedback Switching to applying edit instead of verifying edit contents and range. Switching from Theories to separate Facts where input was complex. Other misc cleanup. * Fixing options source and adding options tests * Tests for all options * Switching to use TestCode class * Create options object for cohost OnAutoInsert to combined individual options passed to the remove service. * More PR feedback * Switching to nested RazorFormattingOptions
This commit is contained in:
Родитель
07e138222c
Коммит
e9cd0b2e2e
|
@ -4,6 +4,7 @@
|
|||
using System.Runtime.Serialization;
|
||||
using Microsoft.CodeAnalysis.ExternalAccess.Razor;
|
||||
using Microsoft.VisualStudio.LanguageServer.Protocol;
|
||||
using RoslynFormattingOptions = Roslyn.LanguageServer.Protocol.FormattingOptions;
|
||||
|
||||
namespace Microsoft.CodeAnalysis.Razor.Formatting;
|
||||
|
||||
|
@ -36,4 +37,11 @@ internal readonly record struct RazorFormattingOptions
|
|||
UseTabs: !InsertSpaces,
|
||||
TabSize: TabSize,
|
||||
IndentationSize: TabSize);
|
||||
|
||||
public RoslynFormattingOptions ToRoslynFormattingOptions()
|
||||
=> new RoslynFormattingOptions()
|
||||
{
|
||||
InsertSpaces = InsertSpaces,
|
||||
TabSize = TabSize
|
||||
};
|
||||
}
|
||||
|
|
|
@ -3,12 +3,13 @@
|
|||
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.ExternalAccess.Razor;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
|
||||
using Response = Microsoft.CodeAnalysis.Razor.Remote.RemoteResponse<Microsoft.CodeAnalysis.Razor.Protocol.AutoInsert.RemoteAutoInsertTextEdit?>;
|
||||
|
||||
namespace Microsoft.CodeAnalysis.Razor.Remote;
|
||||
|
||||
internal interface IRemoteAutoInsertService
|
||||
{
|
||||
ValueTask<Response> GetAutoInsertTextEditAsync(
|
||||
|
@ -16,9 +17,6 @@ internal interface IRemoteAutoInsertService
|
|||
DocumentId documentId,
|
||||
LinePosition position,
|
||||
string character,
|
||||
bool autoCloseTags,
|
||||
bool formatOnType,
|
||||
bool indentWithTabs,
|
||||
int indentSize,
|
||||
RemoteAutoInsertOptions options,
|
||||
CancellationToken cancellationToken);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
using System.Runtime.Serialization;
|
||||
using Microsoft.CodeAnalysis.Razor.Formatting;
|
||||
using Microsoft.CodeAnalysis.Razor.Settings;
|
||||
using Microsoft.VisualStudio.LanguageServer.Protocol;
|
||||
|
||||
namespace Microsoft.CodeAnalysis.Razor.Remote;
|
||||
|
||||
[DataContract]
|
||||
internal readonly record struct RemoteAutoInsertOptions
|
||||
{
|
||||
[DataMember(Order = 0)]
|
||||
public bool EnableAutoClosingTags { get; init; } = true;
|
||||
|
||||
[DataMember(Order = 1)]
|
||||
public bool FormatOnType { get; init; } = true;
|
||||
|
||||
[DataMember(Order = 2)]
|
||||
public RazorFormattingOptions FormattingOptions { get; init; } = new RazorFormattingOptions()
|
||||
{
|
||||
InsertSpaces = true,
|
||||
TabSize = 4
|
||||
};
|
||||
|
||||
public RemoteAutoInsertOptions()
|
||||
{
|
||||
}
|
||||
|
||||
public static RemoteAutoInsertOptions From(ClientSettings clientSettings, FormattingOptions formattingOptions)
|
||||
=> new()
|
||||
{
|
||||
EnableAutoClosingTags = clientSettings.AdvancedSettings.AutoClosingTags,
|
||||
FormatOnType = clientSettings.AdvancedSettings.FormatOnType,
|
||||
FormattingOptions = RazorFormattingOptions.From(formattingOptions, codeBlockBraceOnNextLine: false)
|
||||
};
|
||||
}
|
|
@ -11,13 +11,13 @@ using Microsoft.CodeAnalysis.Razor.Formatting;
|
|||
using Microsoft.CodeAnalysis.Razor.Logging;
|
||||
using Microsoft.CodeAnalysis.Razor.Protocol;
|
||||
using Microsoft.CodeAnalysis.Razor.Protocol.AutoInsert;
|
||||
using Microsoft.CodeAnalysis.Razor.Remote;
|
||||
using Microsoft.CodeAnalysis.Razor.Workspaces;
|
||||
using Microsoft.CodeAnalysis.Remote.Razor.ProjectSystem;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
using Microsoft.VisualStudio.LanguageServer.Protocol;
|
||||
using Roslyn.LanguageServer.Protocol;
|
||||
using Response = Microsoft.CodeAnalysis.Razor.Remote.RemoteResponse<Microsoft.CodeAnalysis.Razor.Protocol.AutoInsert.RemoteAutoInsertTextEdit?>;
|
||||
using RoslynFormattingOptions = Roslyn.LanguageServer.Protocol.FormattingOptions;
|
||||
using RoslynInsertTextFormat = Roslyn.LanguageServer.Protocol.InsertTextFormat;
|
||||
|
||||
namespace Microsoft.CodeAnalysis.Remote.Razor;
|
||||
|
@ -42,10 +42,7 @@ internal sealed class RemoteAutoInsertService(in ServiceArgs args)
|
|||
DocumentId documentId,
|
||||
LinePosition linePosition,
|
||||
string character,
|
||||
bool autoCloseTags,
|
||||
bool formatOnType,
|
||||
bool indentWithTabs,
|
||||
int indentSize,
|
||||
RemoteAutoInsertOptions options,
|
||||
CancellationToken cancellationToken)
|
||||
=> RunServiceAsync(
|
||||
solutionInfo,
|
||||
|
@ -54,10 +51,7 @@ internal sealed class RemoteAutoInsertService(in ServiceArgs args)
|
|||
context,
|
||||
linePosition,
|
||||
character,
|
||||
autoCloseTags,
|
||||
formatOnType,
|
||||
indentWithTabs,
|
||||
indentSize,
|
||||
options,
|
||||
cancellationToken),
|
||||
cancellationToken);
|
||||
|
||||
|
@ -65,10 +59,7 @@ internal sealed class RemoteAutoInsertService(in ServiceArgs args)
|
|||
RemoteDocumentContext remoteDocumentContext,
|
||||
LinePosition linePosition,
|
||||
string character,
|
||||
bool autoCloseTags,
|
||||
bool formatOnType,
|
||||
bool indentWithTabs,
|
||||
int indentSize,
|
||||
RemoteAutoInsertOptions options,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var sourceText = await remoteDocumentContext.GetSourceTextAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
@ -86,7 +77,7 @@ internal sealed class RemoteAutoInsertService(in ServiceArgs args)
|
|||
codeDocument,
|
||||
VsLspExtensions.ToPosition(linePosition),
|
||||
character,
|
||||
autoCloseTags,
|
||||
options.EnableAutoClosingTags,
|
||||
out var insertTextEdit))
|
||||
{
|
||||
return Response.Results(RemoteAutoInsertTextEdit.FromLspInsertTextEdit(insertTextEdit));
|
||||
|
@ -110,9 +101,7 @@ internal sealed class RemoteAutoInsertService(in ServiceArgs args)
|
|||
remoteDocumentContext,
|
||||
mappedPosition,
|
||||
character,
|
||||
formatOnType,
|
||||
indentWithTabs,
|
||||
indentSize,
|
||||
options,
|
||||
cancellationToken);
|
||||
default:
|
||||
Logger.LogError($"Unsupported language {languageKind} in {nameof(RemoteAutoInsertService)}");
|
||||
|
@ -124,9 +113,7 @@ internal sealed class RemoteAutoInsertService(in ServiceArgs args)
|
|||
RemoteDocumentContext remoteDocumentContext,
|
||||
LinePosition mappedPosition,
|
||||
string character,
|
||||
bool formatOnType,
|
||||
bool indentWithTabs,
|
||||
int indentSize,
|
||||
RemoteAutoInsertOptions options,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
// Special case for C# where we use AutoInsert for two purposes:
|
||||
|
@ -140,7 +127,7 @@ internal sealed class RemoteAutoInsertService(in ServiceArgs args)
|
|||
// Therefore we are just going to no-op if the user has turned off on type formatting. Maybe one day we can make this
|
||||
// smarter, but at least the user can always turn the setting back on, type their "///", and turn it back off, without
|
||||
// having to restart VS. Not the worst compromise (hopefully!)
|
||||
if (!formatOnType)
|
||||
if (!options.FormatOnType)
|
||||
{
|
||||
return Response.NoFurtherHandling;
|
||||
}
|
||||
|
@ -151,17 +138,12 @@ internal sealed class RemoteAutoInsertService(in ServiceArgs args)
|
|||
}
|
||||
|
||||
var generatedDocument = await remoteDocumentContext.Snapshot.GetGeneratedDocumentAsync().ConfigureAwait(false);
|
||||
var formattingOptions = new RoslynFormattingOptions()
|
||||
{
|
||||
InsertSpaces = !indentWithTabs,
|
||||
TabSize = indentSize
|
||||
};
|
||||
|
||||
var autoInsertResponseItem = await OnAutoInsert.GetOnAutoInsertResponseAsync(
|
||||
generatedDocument,
|
||||
mappedPosition,
|
||||
character,
|
||||
formattingOptions,
|
||||
options.FormattingOptions.ToRoslynFormattingOptions(),
|
||||
cancellationToken
|
||||
);
|
||||
|
||||
|
@ -170,11 +152,7 @@ internal sealed class RemoteAutoInsertService(in ServiceArgs args)
|
|||
return Response.NoFurtherHandling;
|
||||
}
|
||||
|
||||
var razorFormattingOptions = new RazorFormattingOptions()
|
||||
{
|
||||
InsertSpaces = !indentWithTabs,
|
||||
TabSize = indentSize
|
||||
};
|
||||
var razorFormattingOptions = options.FormattingOptions;
|
||||
|
||||
var vsLspTextEdit = VsLspFactory.CreateTextEdit(
|
||||
autoInsertResponseItem.TextEdit.Range.ToLinePositionSpan(),
|
||||
|
|
|
@ -85,17 +85,15 @@ internal class CohostOnAutoInsertEndpoint(
|
|||
protected override RazorTextDocumentIdentifier? GetRazorTextDocumentIdentifier(VSInternalDocumentOnAutoInsertParams request)
|
||||
=> request.TextDocument.ToRazorTextDocumentIdentifier();
|
||||
|
||||
protected override async Task<VSInternalDocumentOnAutoInsertResponseItem?> HandleRequestAsync(VSInternalDocumentOnAutoInsertParams request, RazorCohostRequestContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
var razorDocument = context.TextDocument.AssumeNotNull();
|
||||
protected override Task<VSInternalDocumentOnAutoInsertResponseItem?> HandleRequestAsync(VSInternalDocumentOnAutoInsertParams request, RazorCohostRequestContext context, CancellationToken cancellationToken)
|
||||
=> HandleRequestAsync(request, context.TextDocument.AssumeNotNull(), cancellationToken);
|
||||
|
||||
private async Task<VSInternalDocumentOnAutoInsertResponseItem?> HandleRequestAsync(VSInternalDocumentOnAutoInsertParams request, TextDocument razorDocument, CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogDebug($"Resolving auto-insertion for {razorDocument.FilePath}");
|
||||
|
||||
var clientSettings = _clientSettingsManager.GetClientSettings();
|
||||
var enableAutoClosingTags = clientSettings.AdvancedSettings.AutoClosingTags;
|
||||
var formatOnType = clientSettings.AdvancedSettings.FormatOnType;
|
||||
var indentWithTabs = clientSettings.ClientSpaceSettings.IndentWithTabs;
|
||||
var indentSize = clientSettings.ClientSpaceSettings.IndentSize;
|
||||
var autoInsertOptions = RemoteAutoInsertOptions.From(clientSettings, request.Options);
|
||||
|
||||
_logger.LogDebug($"Calling OOP to resolve insertion at {request.Position} invoked by typing '{request.Character}'");
|
||||
var data = await _remoteServiceInvoker.TryInvokeAsync<IRemoteAutoInsertService, Response>(
|
||||
|
@ -106,10 +104,7 @@ internal class CohostOnAutoInsertEndpoint(
|
|||
razorDocument.Id,
|
||||
request.Position.ToLinePosition(),
|
||||
request.Character,
|
||||
enableAutoClosingTags,
|
||||
formatOnType,
|
||||
indentWithTabs,
|
||||
indentSize,
|
||||
autoInsertOptions,
|
||||
cancellationToken),
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
|
@ -173,4 +168,15 @@ internal class CohostOnAutoInsertEndpoint(
|
|||
|
||||
return result.Response;
|
||||
}
|
||||
|
||||
internal TestAccessor GetTestAccessor() => new(this);
|
||||
|
||||
internal readonly struct TestAccessor(CohostOnAutoInsertEndpoint instance)
|
||||
{
|
||||
public Task<VSInternalDocumentOnAutoInsertResponseItem?> HandleRequestAsync(
|
||||
VSInternalDocumentOnAutoInsertParams request,
|
||||
TextDocument razorDocument,
|
||||
CancellationToken cancellationToken)
|
||||
=> instance.HandleRequestAsync(request, razorDocument, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,268 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Razor.Test.Common;
|
||||
using Microsoft.CodBeAnalysis.Remote.Razor.AutoInsert;
|
||||
using Microsoft.CodeAnalysis.ExternalAccess.Razor;
|
||||
using Microsoft.CodeAnalysis.Razor.AutoInsert;
|
||||
using Microsoft.CodeAnalysis.Razor.Settings;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
using Microsoft.VisualStudio.LanguageServer.Protocol;
|
||||
using Microsoft.VisualStudio.LanguageServices.Razor.LanguageClient.Cohost;
|
||||
using Microsoft.VisualStudio.Razor.Settings;
|
||||
using Roslyn.Test.Utilities;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Microsoft.VisualStudio.Razor.LanguageClient.Cohost;
|
||||
|
||||
public class CohostOnAutoInsertEndpointTest(ITestOutputHelper testOutputHelper) : CohostEndpointTestBase(testOutputHelper)
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("PageTitle")]
|
||||
[InlineData("div")]
|
||||
[InlineData("text")]
|
||||
public async Task EndTag(string startTag)
|
||||
{
|
||||
await VerifyOnAutoInsertAsync(
|
||||
input: $"""
|
||||
This is a Razor document.
|
||||
|
||||
<{startTag}>$$
|
||||
|
||||
The end.
|
||||
""",
|
||||
output: $"""
|
||||
This is a Razor document.
|
||||
|
||||
<{startTag}>$0</{startTag}>
|
||||
|
||||
The end.
|
||||
""",
|
||||
triggerCharacter: ">");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("PageTitle")]
|
||||
[InlineData("div")]
|
||||
[InlineData("text")]
|
||||
public async Task DoNotAutoInsertEndTag_DisabledAutoClosingTags(string startTag)
|
||||
{
|
||||
await VerifyOnAutoInsertAsync(
|
||||
input: $"""
|
||||
This is a Razor document.
|
||||
|
||||
<{startTag}>$$
|
||||
|
||||
The end.
|
||||
""",
|
||||
output: null,
|
||||
triggerCharacter: ">",
|
||||
autoClosingTags: false);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AttributeQuotes()
|
||||
{
|
||||
await VerifyOnAutoInsertAsync(
|
||||
input: $"""
|
||||
This is a Razor document.
|
||||
|
||||
<PageTitle style=$$></PageTitle>
|
||||
|
||||
The end.
|
||||
""",
|
||||
output: $"""
|
||||
This is a Razor document.
|
||||
|
||||
<PageTitle style="$0"></PageTitle>
|
||||
|
||||
The end.
|
||||
""",
|
||||
triggerCharacter: "=",
|
||||
delegatedResponseText: "\"$0\"");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CSharp_OnForwardSlash()
|
||||
{
|
||||
await VerifyOnAutoInsertAsync(
|
||||
input: """
|
||||
@code {
|
||||
///$$
|
||||
void TestMethod() {}
|
||||
}
|
||||
""",
|
||||
output: """
|
||||
@code {
|
||||
/// <summary>
|
||||
/// $0
|
||||
/// </summary>
|
||||
void TestMethod() {}
|
||||
}
|
||||
""",
|
||||
triggerCharacter: "/");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DoNotAutoInsertCSharp_OnForwardSlashWithFormatOnTypeDisabled()
|
||||
{
|
||||
await VerifyOnAutoInsertAsync(
|
||||
input: """
|
||||
@code {
|
||||
///$$
|
||||
void TestMethod() {}
|
||||
}
|
||||
""",
|
||||
output: null,
|
||||
triggerCharacter: "/",
|
||||
formatOnType: false);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CSharp_OnEnter()
|
||||
{
|
||||
await VerifyOnAutoInsertAsync(
|
||||
input: """
|
||||
@code {
|
||||
void TestMethod() {
|
||||
$$}
|
||||
}
|
||||
""",
|
||||
output: """
|
||||
@code {
|
||||
void TestMethod()
|
||||
{
|
||||
$0
|
||||
}
|
||||
}
|
||||
""",
|
||||
triggerCharacter: "\n");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CSharp_OnEnter_TwoSpaceIndent()
|
||||
{
|
||||
await VerifyOnAutoInsertAsync(
|
||||
input: """
|
||||
@code {
|
||||
void TestMethod() {
|
||||
$$}
|
||||
}
|
||||
""",
|
||||
output: """
|
||||
@code {
|
||||
void TestMethod()
|
||||
{
|
||||
$0
|
||||
}
|
||||
}
|
||||
""",
|
||||
triggerCharacter: "\n",
|
||||
tabSize: 2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CSharp_OnEnter_UseTabs()
|
||||
{
|
||||
const char tab = '\t';
|
||||
await VerifyOnAutoInsertAsync(
|
||||
input: """
|
||||
@code {
|
||||
void TestMethod() {
|
||||
$$}
|
||||
}
|
||||
""",
|
||||
output: $$"""
|
||||
@code {
|
||||
{{tab}}void TestMethod()
|
||||
{{tab}}{
|
||||
{{tab}}{{tab}}$0
|
||||
{{tab}}}
|
||||
}
|
||||
""",
|
||||
triggerCharacter: "\n",
|
||||
insertSpaces: false);
|
||||
}
|
||||
|
||||
private async Task VerifyOnAutoInsertAsync(
|
||||
TestCode input,
|
||||
string? output,
|
||||
string triggerCharacter,
|
||||
string? delegatedResponseText = null,
|
||||
bool insertSpaces = true,
|
||||
int tabSize = 4,
|
||||
bool formatOnType = true,
|
||||
bool autoClosingTags = true)
|
||||
{
|
||||
var document = CreateProjectAndRazorDocument(input.Text);
|
||||
var sourceText = await document.GetTextAsync(DisposalToken);
|
||||
|
||||
var clientSettingsManager = new ClientSettingsManager([], null, null);
|
||||
clientSettingsManager.Update(ClientAdvancedSettings.Default with { FormatOnType = formatOnType, AutoClosingTags = autoClosingTags });
|
||||
|
||||
IOnAutoInsertTriggerCharacterProvider[] onAutoInsertTriggerCharacterProviders = [
|
||||
new RemoteAutoClosingTagOnAutoInsertProvider(),
|
||||
new RemoteCloseTextTagOnAutoInsertProvider()];
|
||||
|
||||
VSInternalDocumentOnAutoInsertResponseItem? response = null;
|
||||
if (delegatedResponseText is not null)
|
||||
{
|
||||
var start = sourceText.GetPosition(input.Position);
|
||||
var end = start;
|
||||
response = new VSInternalDocumentOnAutoInsertResponseItem()
|
||||
{
|
||||
TextEdit = new TextEdit() { NewText = delegatedResponseText, Range = new() { Start = start, End = end } },
|
||||
TextEditFormat = InsertTextFormat.Snippet
|
||||
};
|
||||
}
|
||||
|
||||
var requestInvoker = new TestLSPRequestInvoker([(VSInternalMethods.OnAutoInsertName, response)]);
|
||||
|
||||
var endpoint = new CohostOnAutoInsertEndpoint(
|
||||
RemoteServiceInvoker,
|
||||
clientSettingsManager,
|
||||
onAutoInsertTriggerCharacterProviders,
|
||||
TestHtmlDocumentSynchronizer.Instance,
|
||||
requestInvoker,
|
||||
LoggerFactory);
|
||||
|
||||
var formattingOptions = new FormattingOptions()
|
||||
{
|
||||
InsertSpaces = insertSpaces,
|
||||
TabSize = tabSize
|
||||
};
|
||||
|
||||
var request = new VSInternalDocumentOnAutoInsertParams()
|
||||
{
|
||||
TextDocument = new TextDocumentIdentifier()
|
||||
{
|
||||
Uri = document.CreateUri()
|
||||
},
|
||||
Position = sourceText.GetPosition(input.Position),
|
||||
Character = triggerCharacter,
|
||||
Options = formattingOptions
|
||||
};
|
||||
|
||||
var result = await endpoint.GetTestAccessor().HandleRequestAsync(request, document, DisposalToken);
|
||||
|
||||
if (output is not null)
|
||||
{
|
||||
Assert.NotNull(result);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.Null(result);
|
||||
return;
|
||||
}
|
||||
|
||||
if (result is not null)
|
||||
{
|
||||
var change = sourceText.GetTextChange(result.TextEdit);
|
||||
sourceText = sourceText.WithChanges(change);
|
||||
}
|
||||
|
||||
AssertEx.EqualOrDiff(output, sourceText.ToString());
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче