diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer.Common/LanguageServerConstants.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer.Common/LanguageServerConstants.cs index 8ab6c3ec4e3..9bfcb9200b4 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer.Common/LanguageServerConstants.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer.Common/LanguageServerConstants.cs @@ -29,10 +29,6 @@ public static class LanguageServerConstants public const string RazorCodeActionRunnerCommand = "razor/runCodeAction"; - public const string RazorDocumentFormattingEndpoint = "textDocument/formatting"; - - public const string RazorDocumentOnTypeFormattingEndpoint = "textDocument/onTypeFormatting"; - public const string RazorCompletionEndpointName = "razor/completion"; public const string RazorCompletionResolveEndpointName = "razor/completionItem/resolve"; diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer.Common/RazorLanguageServerCustomMessageTargets.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer.Common/RazorLanguageServerCustomMessageTargets.cs index 7999c141fb7..5552e891271 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer.Common/RazorLanguageServerCustomMessageTargets.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer.Common/RazorLanguageServerCustomMessageTargets.cs @@ -5,32 +5,28 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.Common; internal static class RazorLanguageServerCustomMessageTargets { - public const string RazorUpdateCSharpBufferEndpoint = "razor/updateCSharpBuffer"; + // VS Internal + public const string RazorInlineCompletionEndpoint = "razor/inlineCompletion"; + public const string RazorValidateBreakpointRangeName = "razor/validateBreakpointRange"; + public const string RazorOnAutoInsertEndpointName = "razor/onAutoInsert"; + public const string RazorSemanticTokensRefreshEndpoint = "razor/semanticTokensRefresh"; + public const string RazorTextPresentationEndpoint = "razor/textPresentation"; + public const string RazorUriPresentationEndpoint = "razor/uriPresentation"; + // Cross platform + public const string RazorUpdateCSharpBufferEndpoint = "razor/updateCSharpBuffer"; public const string RazorUpdateHtmlBufferEndpoint = "razor/updateHtmlBuffer"; - - public const string RazorRangeFormattingEndpoint = "razor/rangeFormatting"; - public const string RazorProvideCodeActionsEndpoint = "razor/provideCodeActions"; - public const string RazorResolveCodeActionsEndpoint = "razor/resolveCodeActions"; - - public const string RazorProvideSemanticTokensRangeEndpoint = "razor/provideSemanticTokensRange"; - - public const string RazorProvideHtmlDocumentColorEndpoint = "razor/provideHtmlDocumentColor"; - public const string RazorProvideHtmlColorPresentationEndpoint = "razor/provideHtmlColorPresentation"; - - public const string RazorInlineCompletionEndpoint = "razor/inlineCompletion"; - + public const string RazorProvideHtmlDocumentColorEndpoint = "razor/provideHtmlDocumentColor"; + public const string RazorPullDiagnosticEndpointName = "razor/pullDiagnostics"; + public const string RazorProvideSemanticTokensRangeEndpoint = "razor/provideSemanticTokensRange"; public const string RazorFoldingRangeEndpoint = "razor/foldingRange"; + public const string RazorHtmlFormattingEndpoint = "razor/htmlFormatting"; + public const string RazorHtmlOnTypeFormattingEndpoint = "razor/htmlOnTypeFormatting"; - public const string RazorSemanticTokensRefreshEndpoint = "razor/semanticTokensRefresh"; - - public const string RazorTextPresentationEndpoint = "razor/textPresentation"; - - public const string RazorUriPresentationEndpoint = "razor/uriPresentation"; - + // Still to migrate public const string RazorRenameEndpointName = "razor/rename"; public const string RazorHoverEndpointName = "razor/hover"; @@ -43,11 +39,5 @@ internal static class RazorLanguageServerCustomMessageTargets public const string RazorImplementationEndpointName = "razor/implementation"; - public const string RazorOnAutoInsertEndpointName = "razor/onAutoInsert"; - - public const string RazorValidateBreakpointRangeName = "razor/validateBreakpointRange"; - - public const string RazorPullDiagnosticEndpointName = "razor/pullDiagnostics"; - public const string RazorReferencesEndpointName = "razor/references"; } 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 1d34318c505..c50f456584a 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/HtmlFormatter.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/HtmlFormatter.cs @@ -41,7 +41,7 @@ public async Task FormatAsync( return Array.Empty(); } - var @params = new VersionedDocumentFormattingParams() + var @params = new RazorDocumentFormattingParams() { TextDocument = new TextDocumentIdentifier { @@ -52,7 +52,7 @@ public async Task FormatAsync( }; var result = await _server.SendRequestAsync( - LanguageServerConstants.RazorDocumentFormattingEndpoint, + RazorLanguageServerCustomMessageTargets.RazorHtmlFormattingEndpoint, @params, cancellationToken); @@ -80,7 +80,7 @@ public async Task FormatOnTypeAsync( }; var result = await _server.SendRequestAsync( - LanguageServerConstants.RazorDocumentOnTypeFormattingEndpoint, + RazorLanguageServerCustomMessageTargets.RazorHtmlOnTypeFormattingEndpoint, @params, cancellationToken); diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/VersionedDocumentFormattingParams.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/RazorDocumentFormattingParams.cs similarity index 74% rename from src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/VersionedDocumentFormattingParams.cs rename to src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/RazorDocumentFormattingParams.cs index d4bfead3562..a8095a3be28 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/VersionedDocumentFormattingParams.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/RazorDocumentFormattingParams.cs @@ -7,8 +7,8 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.Formatting; [DataContract] -internal class VersionedDocumentFormattingParams : DocumentFormattingParams +internal class RazorDocumentFormattingParams : DocumentFormattingParams { - [DataMember(Name = "_vs_hostDocumentVersion")] + [DataMember(Name = "hostDocumentVersion")] public int HostDocumentVersion { get; set; } } diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/RazorDocumentRangeFormattingParams.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/RazorDocumentRangeFormattingParams.cs deleted file mode 100644 index 7eb35238136..00000000000 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/RazorDocumentRangeFormattingParams.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root for license information. - -using Microsoft.AspNetCore.Razor.LanguageServer.Protocol; -using Microsoft.VisualStudio.LanguageServer.Protocol; - -namespace Microsoft.AspNetCore.Razor.LanguageServer.Formatting; - -internal class RazorDocumentRangeFormattingParams -{ - public RazorLanguageKind Kind { get; set; } - - public string? HostDocumentFilePath { get; set; } - - public Range? ProjectedRange { get; set; } - - public FormattingOptions? Options { get; set; } -} diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.VSCode/src/Formatting/FormattingHandler.ts b/src/Razor/src/Microsoft.AspNetCore.Razor.VSCode/src/Formatting/FormattingHandler.ts index 36be596695f..ca55edb2b34 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.VSCode/src/Formatting/FormattingHandler.ts +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.VSCode/src/Formatting/FormattingHandler.ts @@ -5,27 +5,36 @@ import * as vscode from 'vscode'; import { RequestType } from 'vscode-languageclient'; +import { IRazorDocument } from '../Document/IRazorDocument'; import { RazorDocumentManager } from '../Document/RazorDocumentManager'; +import { RazorDocumentSynchronizer } from '../Document/RazorDocumentSynchronizer'; import { RazorLanguageServerClient } from '../RazorLanguageServerClient'; import { RazorLogger } from '../RazorLogger'; import { convertTextEditToSerializable, SerializableTextEdit } from '../RPC/SerializableTextEdit'; import { SerializableFormattingParams } from './SerializableFormattingParams'; import { SerializableFormattingResponse } from './SerializableFormattingResponse'; +import { SerializableOnTypeFormattingParams } from './SerializableOnTypeFormattingParams'; export class FormattingHandler { - private static readonly provideFormattingEndpoint = 'textDocument/formatting'; + private static readonly provideFormattingEndpoint = 'razor/htmlFormatting'; + private static readonly provideOnTypeFormattingEndpoint = 'razor/htmlOnTypeFormatting'; private formattingRequestType: RequestType = new RequestType(FormattingHandler.provideFormattingEndpoint); + private onTypeFormattingRequestType: RequestType = new RequestType(FormattingHandler.provideOnTypeFormattingEndpoint); private emptyFormattingResponse = new SerializableFormattingResponse(); constructor( private readonly documentManager: RazorDocumentManager, + private readonly documentSynchronizer: RazorDocumentSynchronizer, private readonly serverClient: RazorLanguageServerClient, private readonly logger: RazorLogger) { } - public register() { - return this.serverClient.onRequestWithParams( + public async register() { + await this.serverClient.onRequestWithParams( this.formattingRequestType, async (request, token) => this.provideFormatting(request, token)); + await this.serverClient.onRequestWithParams( + this.onTypeFormattingRequestType, + async (request, token) => this.provideOnTypeFormatting(request, token)); } private async provideFormatting( @@ -38,6 +47,12 @@ export class FormattingHandler { return this.emptyFormattingResponse; } + const textDocument = await vscode.workspace.openTextDocument(razorDocumentUri); + const synchronized = await this.documentSynchronizer.trySynchronizeProjectedDocument(textDocument, razorDocument.csharpDocument, formattingParams.hostDocumentVersion, cancellationToken); + if (!synchronized) { + return this.emptyFormattingResponse; + } + const virtualHtmlUri = razorDocument.htmlDocument.uri; const textEdits = await vscode.commands.executeCommand( @@ -49,24 +64,47 @@ export class FormattingHandler { return this.emptyFormattingResponse; } - const htmlDocText = razorDocument.htmlDocument.getContent(); - const zeroBasedLineCount = this.countLines(htmlDocText); - const serializableTextEdits = Array(); - for (let textEdit of textEdits) { - // The below workaround is needed due to a bug on the HTML side where - // they'll sometimes send us an end position that exceeds the length - // of the document. Tracked by https://github.com/microsoft/vscode/issues/175298. - if (textEdit.range.end.line > zeroBasedLineCount) { - const lastLineLength = this.getLastLineLength(htmlDocText); - const updatedEndPosition = new vscode.Position(zeroBasedLineCount, lastLineLength); - const updatedRange = new vscode.Range(textEdit.range.start, updatedEndPosition); - textEdit = new vscode.TextEdit(updatedRange, textEdit.newText); - } + const serializableTextEdits = this.sanitizeTextEdits(razorDocument, textEdits); + + return new SerializableFormattingResponse(serializableTextEdits); + } catch (error) { + this.logger.logWarning(`${FormattingHandler.provideFormattingEndpoint} failed with ${error}`); + } + + return this.emptyFormattingResponse; + } - const serializableTextEdit = convertTextEditToSerializable(textEdit); - serializableTextEdits.push(serializableTextEdit); + private async provideOnTypeFormatting( + formattingParams: SerializableOnTypeFormattingParams, + cancellationToken: vscode.CancellationToken) { + try { + const razorDocumentUri = vscode.Uri.parse(formattingParams.textDocument.uri); + const razorDocument = await this.documentManager.getDocument(razorDocumentUri); + if (razorDocument === undefined) { + return this.emptyFormattingResponse; + } + + const textDocument = await vscode.workspace.openTextDocument(razorDocumentUri); + const synchronized = await this.documentSynchronizer.trySynchronizeProjectedDocument(textDocument, razorDocument.csharpDocument, formattingParams.hostDocumentVersion, cancellationToken); + if (!synchronized) { + return this.emptyFormattingResponse; } + const virtualHtmlUri = razorDocument.htmlDocument.uri; + + const textEdits = await vscode.commands.executeCommand( + 'vscode.executeFormatOnTypeProvider', + virtualHtmlUri, + formattingParams.position, + formattingParams.ch, + formattingParams.options); + + if (textEdits === undefined) { + return this.emptyFormattingResponse; + } + + const serializableTextEdits = this.sanitizeTextEdits(razorDocument, textEdits); + return new SerializableFormattingResponse(serializableTextEdits); } catch (error) { this.logger.logWarning(`${FormattingHandler.provideFormattingEndpoint} failed with ${error}`); @@ -75,6 +113,39 @@ export class FormattingHandler { return this.emptyFormattingResponse; } + private sanitizeTextEdits(razorDocument: IRazorDocument, textEdits: vscode.TextEdit[]) { + const htmlDocText = razorDocument.htmlDocument.getContent(); + const zeroBasedLineCount = this.countLines(htmlDocText); + const serializableTextEdits = Array(); + for (let textEdit of textEdits) { + // The below workaround is needed due to a bug on the HTML side where + // they'll sometimes send us an end position that exceeds the length + // of the document. Tracked by https://github.com/microsoft/vscode/issues/175298. + if (textEdit.range.end.line > zeroBasedLineCount || + textEdit.range.start.line > zeroBasedLineCount) { + const lastLineLength = this.getLastLineLength(htmlDocText); + const updatedPosition = new vscode.Position(zeroBasedLineCount, lastLineLength); + + let start = textEdit.range.start; + let end = textEdit.range.end; + if (textEdit.range.start.line > zeroBasedLineCount) { + start = updatedPosition; + } + + if (textEdit.range.end.line > zeroBasedLineCount) { + end = updatedPosition; + } + + const updatedRange = new vscode.Range(start, end); + textEdit = new vscode.TextEdit(updatedRange, textEdit.newText); + } + + const serializableTextEdit = convertTextEditToSerializable(textEdit); + serializableTextEdits.push(serializableTextEdit); + } + return serializableTextEdits; + } + private countLines(text: string) { let lineCount = 0; for (const i of text) { diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.VSCode/src/Formatting/RazorDocumentRangeFormattingRequest.ts b/src/Razor/src/Microsoft.AspNetCore.Razor.VSCode/src/Formatting/RazorDocumentRangeFormattingRequest.ts deleted file mode 100644 index 1d00da01457..00000000000 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.VSCode/src/Formatting/RazorDocumentRangeFormattingRequest.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* -------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - * ------------------------------------------------------------------------------------------ */ - -import * as vscode from 'vscode'; -import { LanguageKind } from '../RPC/LanguageKind'; -import { SerializableRange } from '../RPC/SerializableRange'; - -export interface RazorDocumentRangeFormattingRequest { - kind: LanguageKind; - hostDocumentFilePath: string; - projectedRange: SerializableRange; - options: vscode.FormattingOptions; -} diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.VSCode/src/Formatting/RazorDocumentRangeFormattingResponse.ts b/src/Razor/src/Microsoft.AspNetCore.Razor.VSCode/src/Formatting/RazorDocumentRangeFormattingResponse.ts deleted file mode 100644 index 2c5e056e93c..00000000000 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.VSCode/src/Formatting/RazorDocumentRangeFormattingResponse.ts +++ /dev/null @@ -1,11 +0,0 @@ -/* -------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - * ------------------------------------------------------------------------------------------ */ - -import { SerializableTextEdit } from '../RPC/SerializableTextEdit'; - -export class RazorDocumentRangeFormattingResponse { - constructor(public readonly edits: SerializableTextEdit[]) { - } -} diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.VSCode/src/Formatting/RazorFormatOnTypeProvider.ts b/src/Razor/src/Microsoft.AspNetCore.Razor.VSCode/src/Formatting/RazorFormatOnTypeProvider.ts deleted file mode 100644 index 0515b047f0a..00000000000 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.VSCode/src/Formatting/RazorFormatOnTypeProvider.ts +++ /dev/null @@ -1,19 +0,0 @@ -/* -------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - * ------------------------------------------------------------------------------------------ */ - -import * as vscode from 'vscode'; - -export class RazorFormatOnTypeProvider - implements vscode.OnTypeFormattingEditProvider { - - public provideOnTypeFormattingEdits( - document: vscode.TextDocument, - position: vscode.Position, - character: string, - formattingOptions: vscode.FormattingOptions, - cancellationToken: vscode.CancellationToken): vscode.ProviderResult { - return new Array(); - } -} diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.VSCode/src/Formatting/RazorFormattingFeature.ts b/src/Razor/src/Microsoft.AspNetCore.Razor.VSCode/src/Formatting/RazorFormattingFeature.ts deleted file mode 100644 index 5bc8dcfd992..00000000000 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.VSCode/src/Formatting/RazorFormattingFeature.ts +++ /dev/null @@ -1,72 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as vscode from 'vscode'; -import { RequestType } from 'vscode-languageclient'; -import { RazorDocumentManager } from '../Document/RazorDocumentManager'; -import { RazorLanguageServerClient } from '../RazorLanguageServerClient'; -import { RazorLogger } from '../RazorLogger'; -import { LanguageKind } from '../RPC/LanguageKind'; -import { convertRangeFromSerializable } from '../RPC/SerializableRange'; -import { convertTextEditToSerializable } from '../RPC/SerializableTextEdit'; -import { RazorDocumentRangeFormattingRequest } from './RazorDocumentRangeFormattingRequest'; -import { RazorDocumentRangeFormattingResponse } from './RazorDocumentRangeFormattingResponse'; - -export class RazorFormattingFeature { - - private rangeFormattingRequestType: RequestType = new RequestType('razor/rangeFormatting'); - private emptyRangeFormattingResponse: RazorDocumentRangeFormattingResponse = new RazorDocumentRangeFormattingResponse([]); - - constructor( - private readonly serverClient: RazorLanguageServerClient, - private readonly documentManager: RazorDocumentManager, - private readonly logger: RazorLogger) { - } - - public register() { - return this.serverClient.onRequestWithParams( - this.rangeFormattingRequestType, - async (request, token) => this.handleRangeFormatting(request, token)); - } - - private async handleRangeFormatting(request: RazorDocumentRangeFormattingRequest, token: vscode.CancellationToken) { - if (request.kind === LanguageKind.Razor) { - // We shouldn't attempt to format the actual Razor document here. - // Doing so could potentially lead to an infinite loop. - return this.emptyRangeFormattingResponse; - } - - try { - const uri = vscode.Uri.file(request.hostDocumentFilePath); - const razorDocument = await this.documentManager.getDocument(uri); - if (!razorDocument) { - return this.emptyRangeFormattingResponse; - } - - let documentUri = uri; - if (request.kind === LanguageKind.CSharp) { - documentUri = razorDocument.csharpDocument.uri; - } else { - documentUri = razorDocument.htmlDocument.uri; - } - - // Get the edits - const textEdits = await vscode.commands.executeCommand( - 'vscode.executeFormatRangeProvider', - documentUri, - convertRangeFromSerializable(request.projectedRange), - request.options); - - if (textEdits) { - const edits = textEdits.map(item => convertTextEditToSerializable(item)); - return new RazorDocumentRangeFormattingResponse(edits); - } - } catch (error) { - this.logger.logWarning(`razor/rangeFormatting failed with ${error}`); - } - - return this.emptyRangeFormattingResponse; - } -} diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.VSCode/src/Formatting/SerializableFormattingParams.ts b/src/Razor/src/Microsoft.AspNetCore.Razor.VSCode/src/Formatting/SerializableFormattingParams.ts index 3328dc4c28b..41251fb9ba2 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.VSCode/src/Formatting/SerializableFormattingParams.ts +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.VSCode/src/Formatting/SerializableFormattingParams.ts @@ -7,6 +7,7 @@ import * as vscode from 'vscode'; import { SerializableTextDocumentIdentifier } from './../RPC/SerializableTextDocumentIdentifier'; export interface SerializableFormattingParams { + hostDocumentVersion: number; textDocument: SerializableTextDocumentIdentifier; options: vscode.FormattingOptions; } diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.VSCode/src/Formatting/SerializableOnTypeFormattingParams.ts b/src/Razor/src/Microsoft.AspNetCore.Razor.VSCode/src/Formatting/SerializableOnTypeFormattingParams.ts new file mode 100644 index 00000000000..947a913935f --- /dev/null +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.VSCode/src/Formatting/SerializableOnTypeFormattingParams.ts @@ -0,0 +1,16 @@ +/* -------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + * ------------------------------------------------------------------------------------------ */ + +import * as vscode from 'vscode'; +import { SerializablePosition } from '../RPC/SerializablePosition'; +import { SerializableTextDocumentIdentifier } from './../RPC/SerializableTextDocumentIdentifier'; + +export interface SerializableOnTypeFormattingParams { + hostDocumentVersion: number; + textDocument: SerializableTextDocumentIdentifier; + ch: string; + position: SerializablePosition; + options: vscode.FormattingOptions; +} diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.VSCode/src/extension.ts b/src/Razor/src/Microsoft.AspNetCore.Razor.VSCode/src/extension.ts index 4d4b135c00d..657e043fcf0 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.VSCode/src/extension.ts +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.VSCode/src/extension.ts @@ -23,8 +23,6 @@ import { RazorDocumentHighlightProvider } from './DocumentHighlight/RazorDocumen import { reportTelemetryForDocuments } from './DocumentTelemetryListener'; import { FoldingRangeHandler } from './Folding/FoldingRangeHandler'; import { FormattingHandler } from './Formatting/FormattingHandler'; -import { RazorFormatOnTypeProvider } from './Formatting/RazorFormatOnTypeProvider'; -import { RazorFormattingFeature } from './Formatting/RazorFormattingFeature'; import { HostEventStream } from './HostEventStream'; import { RazorHoverProvider } from './Hover/RazorHoverProvider'; import { RazorHtmlFeature } from './Html/RazorHtmlFeature'; @@ -69,7 +67,6 @@ export async function activate(vscodeType: typeof vscodeapi, context: ExtensionC const htmlFeature = new RazorHtmlFeature(documentManager, languageServiceClient, eventEmitterFactory, logger); const localRegistrations: vscode.Disposable[] = []; const reportIssueCommand = new ReportIssueCommand(vscodeType, documentManager, logger); - const razorFormattingFeature = new RazorFormattingFeature(languageServerClient, documentManager, logger); const razorCodeActionRunner = new RazorCodeActionRunner(languageServerClient, logger); let documentSynchronizer: RazorDocumentSynchronizer; @@ -96,6 +93,7 @@ export async function activate(vscodeType: typeof vscodeapi, context: ExtensionC logger); const formattingHandler = new FormattingHandler( documentManager, + documentSynchronizer, languageServerClient, logger); @@ -145,7 +143,6 @@ export async function activate(vscodeType: typeof vscodeapi, context: ExtensionC documentManager, languageServiceClient, logger); - const onTypeFormattingEditProvider = new RazorFormatOnTypeProvider(); localRegistrations.push( languageConfiguration.register(), @@ -179,13 +176,6 @@ export async function activate(vscodeType: typeof vscodeapi, context: ExtensionC vscodeType.languages.registerDocumentHighlightProvider( RazorLanguage.id, documentHighlightProvider), - // Our OnTypeFormatter doesn't do anything at the moment, but it's needed so - // VS Code doesn't throw an exception when it tries to send us an - // OnTypeFormatting request. - vscodeType.languages.registerOnTypeFormattingEditProvider( - RazorLanguage.documentSelector, - onTypeFormattingEditProvider, - ''), documentManager.register(), csharpFeature.register(), htmlFeature.register(), @@ -200,7 +190,6 @@ export async function activate(vscodeType: typeof vscodeapi, context: ExtensionC } razorCodeActionRunner.register(); - await razorFormattingFeature.register(); await colorPresentationHandler.register(); await documentColorHandler.register(); await foldingRangeHandler.register(); diff --git a/src/Razor/src/Microsoft.VisualStudio.LanguageServerClient.Razor/DefaultRazorLanguageServerCustomMessageTarget.cs b/src/Razor/src/Microsoft.VisualStudio.LanguageServerClient.Razor/DefaultRazorLanguageServerCustomMessageTarget.cs index 52cdfa283c2..290eaf1da55 100644 --- a/src/Razor/src/Microsoft.VisualStudio.LanguageServerClient.Razor/DefaultRazorLanguageServerCustomMessageTarget.cs +++ b/src/Razor/src/Microsoft.VisualStudio.LanguageServerClient.Razor/DefaultRazorLanguageServerCustomMessageTarget.cs @@ -179,9 +179,9 @@ internal void UpdateHtmlBuffer(UpdateBufferRequest request) state: null); } - public override async Task RazorDocumentFormattingAsync(VersionedDocumentFormattingParams request, CancellationToken cancellationToken) + public override async Task HtmlFormattingAsync(RazorDocumentFormattingParams request, CancellationToken cancellationToken) { - var response = new RazorDocumentRangeFormattingResponse() { Edits = Array.Empty() }; + var response = new RazorDocumentFormattingResponse() { Edits = Array.Empty() }; await _joinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); @@ -218,9 +218,9 @@ public override async Task RazorDocumentFo return response; } - public override async Task HtmlOnTypeFormattingAsync(RazorDocumentOnTypeFormattingParams request, CancellationToken cancellationToken) + public override async Task HtmlOnTypeFormattingAsync(RazorDocumentOnTypeFormattingParams request, CancellationToken cancellationToken) { - var response = new RazorDocumentRangeFormattingResponse() { Edits = Array.Empty() }; + var response = new RazorDocumentFormattingResponse() { Edits = Array.Empty() }; var hostDocumentUri = request.TextDocument.Uri; @@ -254,63 +254,6 @@ public override async Task HtmlOnTypeForma return response; } - public override async Task RazorRangeFormattingAsync(RazorDocumentRangeFormattingParams request, CancellationToken cancellationToken) - { - var response = new RazorDocumentRangeFormattingResponse() { Edits = Array.Empty() }; - - if (request.Kind == RazorLanguageKind.Razor) - { - return response; - } - - await _joinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); - - var hostDocumentUri = new Uri(request.HostDocumentFilePath); - var (synchronized, csharpDocument) = await _documentSynchronizer.TrySynchronizeVirtualDocumentAsync( - request.HostDocumentVersion, - hostDocumentUri, - cancellationToken); - - string languageServerName; - Uri projectedUri; - - if (!synchronized) - { - // Document could not be synchronized - return response; - } - - if (request.Kind == RazorLanguageKind.CSharp) - { - languageServerName = RazorLSPConstants.RazorCSharpLanguageServerName; - projectedUri = csharpDocument.Uri; - } - else - { - Debug.Fail("Unexpected RazorLanguageKind. This can't really happen in a real scenario."); - return response; - } - - var formattingParams = new DocumentRangeFormattingParams() - { - TextDocument = new TextDocumentIdentifier() { Uri = projectedUri }, - Range = request.ProjectedRange, - Options = request.Options - }; - - var textBuffer = csharpDocument.Snapshot.TextBuffer; - var edits = await _requestInvoker.ReinvokeRequestOnServerAsync( - textBuffer, - Methods.TextDocumentRangeFormattingName, - languageServerName, - formattingParams, - cancellationToken).ConfigureAwait(false); - - response.Edits = edits?.Response ?? Array.Empty(); - - return response; - } - public override async Task?> ProvideCodeActionsAsync(DelegatedCodeActionParams codeActionParams, CancellationToken cancellationToken) { if (codeActionParams is null) diff --git a/src/Razor/src/Microsoft.VisualStudio.LanguageServerClient.Razor/RazorDocumentRangeFormattingParams.cs b/src/Razor/src/Microsoft.VisualStudio.LanguageServerClient.Razor/RazorDocumentRangeFormattingParams.cs deleted file mode 100644 index 917499986c4..00000000000 --- a/src/Razor/src/Microsoft.VisualStudio.LanguageServerClient.Razor/RazorDocumentRangeFormattingParams.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root for license information. - -using Microsoft.AspNetCore.Razor.LanguageServer.Protocol; -using Microsoft.VisualStudio.LanguageServer.Protocol; - -namespace Microsoft.VisualStudio.LanguageServerClient.Razor; - -internal class RazorDocumentRangeFormattingParams -{ - public RazorLanguageKind Kind { get; init; } - - public required string HostDocumentFilePath { get; init; } - - public required Range ProjectedRange { get; init; } - - public required FormattingOptions Options { get; init; } - - public int HostDocumentVersion { get; set; } -} diff --git a/src/Razor/src/Microsoft.VisualStudio.LanguageServerClient.Razor/RazorDocumentRangeFormattingResponse.cs b/src/Razor/src/Microsoft.VisualStudio.LanguageServerClient.Razor/RazorDocumentRangeFormattingResponse.cs deleted file mode 100644 index d36a0a25969..00000000000 --- a/src/Razor/src/Microsoft.VisualStudio.LanguageServerClient.Razor/RazorDocumentRangeFormattingResponse.cs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root for license information. - -using Microsoft.VisualStudio.LanguageServer.Protocol; - -namespace Microsoft.VisualStudio.LanguageServerClient.Razor; - -internal class RazorDocumentRangeFormattingResponse -{ - public required TextEdit[] Edits { get; set; } -} diff --git a/src/Razor/src/Microsoft.VisualStudio.LanguageServerClient.Razor/RazorLanguageServerCustomMessageTarget.cs b/src/Razor/src/Microsoft.VisualStudio.LanguageServerClient.Razor/RazorLanguageServerCustomMessageTarget.cs index 9387f055a0a..b0edace6eae 100644 --- a/src/Razor/src/Microsoft.VisualStudio.LanguageServerClient.Razor/RazorLanguageServerCustomMessageTarget.cs +++ b/src/Razor/src/Microsoft.VisualStudio.LanguageServerClient.Razor/RazorLanguageServerCustomMessageTarget.cs @@ -43,20 +43,13 @@ internal abstract class RazorLanguageServerCustomMessageTarget [JsonRpcMethod(RazorLanguageServerCustomMessageTargets.RazorUpdateHtmlBufferEndpoint, UseSingleObjectParameterDeserialization = true)] public abstract Task UpdateHtmlBufferAsync(UpdateBufferRequest token, CancellationToken cancellationToken); - // Called by the Razor Language Server to invoke a textDocument/formatting request - // on the virtual Html/CSharp buffer. - [JsonRpcMethod(LanguageServerConstants.RazorDocumentFormattingEndpoint, UseSingleObjectParameterDeserialization = true)] - public abstract Task RazorDocumentFormattingAsync(VersionedDocumentFormattingParams token, CancellationToken cancellationToken); - - // Called by the Razor Language Server to invoke a textDocument/onTypeFormatting request - // on the virtual Html buffer. - [JsonRpcMethod(LanguageServerConstants.RazorDocumentOnTypeFormattingEndpoint, UseSingleObjectParameterDeserialization = true)] - public abstract Task HtmlOnTypeFormattingAsync(RazorDocumentOnTypeFormattingParams token, CancellationToken cancellationToken); - - // Called by the Razor Language Server to invoke a textDocument/rangeFormatting request - // on the virtual Html/CSharp buffer. - [JsonRpcMethod(RazorLanguageServerCustomMessageTargets.RazorRangeFormattingEndpoint, UseSingleObjectParameterDeserialization = true)] - public abstract Task RazorRangeFormattingAsync(RazorDocumentRangeFormattingParams token, CancellationToken cancellationToken); + // Called by the Razor Language Server to invoke a textDocument/formatting request on the virtual Html buffer. + [JsonRpcMethod(RazorLanguageServerCustomMessageTargets.RazorHtmlFormattingEndpoint, UseSingleObjectParameterDeserialization = true)] + public abstract Task HtmlFormattingAsync(RazorDocumentFormattingParams token, CancellationToken cancellationToken); + + // Called by the Razor Language Server to invoke a textDocument/onTypeFormatting request on the virtual Html buffer. + [JsonRpcMethod(RazorLanguageServerCustomMessageTargets.RazorHtmlOnTypeFormattingEndpoint, UseSingleObjectParameterDeserialization = true)] + public abstract Task HtmlOnTypeFormattingAsync(RazorDocumentOnTypeFormattingParams token, CancellationToken cancellationToken); // Called by the Razor Language Server to provide code actions from the platform. [JsonRpcMethod(RazorLanguageServerCustomMessageTargets.RazorProvideCodeActionsEndpoint, UseSingleObjectParameterDeserialization = true)] 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 c56abf206c9..28bc28625c3 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 @@ -11,6 +11,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Razor.Language; +using Microsoft.AspNetCore.Razor.LanguageServer.Common; using Microsoft.AspNetCore.Razor.LanguageServer.Extensions; using Microsoft.AspNetCore.Razor.LanguageServer.Protocol; using Microsoft.AspNetCore.Razor.LanguageServer.Test.Common; @@ -152,40 +153,6 @@ private RazorDocumentFormattingResponse CallWebToolsApplyFormattedEditsHandler(s return response; } - private RazorDocumentFormattingResponse Format(RazorDocumentRangeFormattingParams @params) - { - if (@params.Kind == RazorLanguageKind.Razor) - { - throw new InvalidOperationException("We shouldn't be asked to format Razor language kind."); - } - - var options = @params.Options; - var response = new RazorDocumentFormattingResponse(); - - if (@params.Kind == RazorLanguageKind.CSharp) - { - var codeDocument = _documents[@params.HostDocumentFilePath]; - var csharpSourceText = codeDocument.GetCSharpSourceText(); - var csharpDocument = GetCSharpDocument(codeDocument, @params.Options); - if (!csharpDocument.TryGetSyntaxRoot(out var root)) - { - throw new InvalidOperationException("Couldn't get syntax root."); - } - - var spanToFormat = @params.ProjectedRange.AsTextSpan(csharpSourceText); - - var changes = Formatter.GetFormattedTextChanges(root, spanToFormat, csharpDocument.Project.Solution.Workspace); - - response.Edits = changes.Select(c => c.AsTextEdit(csharpSourceText)).ToArray(); - } - else - { - throw new InvalidOperationException($"We shouldn't be asked to format {@params.Kind} language kind."); - } - - return response; - } - private struct HtmlFormatterTextEdit { #pragma warning disable CS0649 // Field 'name' is never assigned to, and will always have its default value @@ -230,22 +197,15 @@ private static Document GetCSharpDocument(RazorCodeDocument codeDocument, Format public override Task SendRequestAsync(string method, TParams @params, CancellationToken cancellationToken) { - if (@params is RazorDocumentRangeFormattingParams rangeFormattingParams && - string.Equals(method, "razor/rangeFormatting", StringComparison.Ordinal)) - { - var response = Format(rangeFormattingParams); - - return Task.FromResult(Convert(response)); - } - else if (@params is DocumentFormattingParams formattingParams && - string.Equals(method, "textDocument/formatting", StringComparison.Ordinal)) + if (@params is DocumentFormattingParams formattingParams && + string.Equals(method, RazorLanguageServerCustomMessageTargets.RazorHtmlFormattingEndpoint, StringComparison.Ordinal)) { var response = Format(formattingParams); return Task.FromResult(Convert(response)); } else if (@params is DocumentOnTypeFormattingParams onTypeFormattingParams && - string.Equals(method, "textDocument/onTypeFormatting", StringComparison.Ordinal)) + string.Equals(method, RazorLanguageServerCustomMessageTargets.RazorHtmlOnTypeFormattingEndpoint, StringComparison.Ordinal)) { var response = Format(onTypeFormattingParams); diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServerClient.Razor.Test/DefaultRazorLanguageServerCustomMessageTargetTest.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServerClient.Razor.Test/DefaultRazorLanguageServerCustomMessageTargetTest.cs index bd2a935012d..02b9d6afc08 100644 --- a/src/Razor/test/Microsoft.VisualStudio.LanguageServerClient.Razor.Test/DefaultRazorLanguageServerCustomMessageTargetTest.cs +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServerClient.Razor.Test/DefaultRazorLanguageServerCustomMessageTargetTest.cs @@ -94,144 +94,6 @@ public void UpdateCSharpBuffer_UpdatesDocument() documentManager.VerifyAll(); } - [Fact] - public async Task RazorRangeFormattingAsync_LanguageKindRazor_ReturnsEmpty() - { - // Arrange - var documentManager = Mock.Of(MockBehavior.Strict); - var requestInvoker = new Mock(MockBehavior.Strict); - var outputWindowLogger = Mock.Of(MockBehavior.Strict); - - var documentSynchronizer = new Mock(MockBehavior.Strict); - var telemetryReporter = new Mock(MockBehavior.Strict); - - var target = new DefaultRazorLanguageServerCustomMessageTarget( - documentManager, JoinableTaskContext, requestInvoker.Object, - TestFormattingOptionsProvider.Default, _editorSettingsManager, documentSynchronizer.Object, telemetryReporter.Object, outputWindowLogger); - - var request = new RazorDocumentRangeFormattingParams() - { - HostDocumentFilePath = "c:/Some/path/to/file.razor", - Kind = RazorLanguageKind.Razor, - ProjectedRange = new Range(), - Options = new FormattingOptions() - { - TabSize = 4, - InsertSpaces = true - } - }; - - // Act - var result = await target.RazorRangeFormattingAsync(request, DisposalToken); - - // Assert - Assert.NotNull(result); - Assert.Empty(result.Edits); - } - - [Fact] - public async Task RazorRangeFormattingAsync_DocumentNotFound_ReturnsEmpty() - { - // Arrange - var documentManager = new Mock(MockBehavior.Strict).Object; - Mock.Get(documentManager) - .Setup(m => m.TryGetDocument( - new Uri("c:/Some/path/to/file.razor"), - out It.Ref.IsAny)) - .Returns(false); - var requestInvoker = new Mock(MockBehavior.Strict); - var outputWindowLogger = Mock.Of(MockBehavior.Strict); - - var documentSynchronizer = GetDocumentSynchronizer(); - var telemetryReporter = new Mock(MockBehavior.Strict); - - var target = new DefaultRazorLanguageServerCustomMessageTarget( - documentManager, JoinableTaskContext, requestInvoker.Object, - TestFormattingOptionsProvider.Default, _editorSettingsManager, documentSynchronizer, telemetryReporter.Object, outputWindowLogger); - - var request = new RazorDocumentRangeFormattingParams() - { - HostDocumentFilePath = "c:/Some/path/to/file.razor", - Kind = RazorLanguageKind.CSharp, - ProjectedRange = new Range(), - Options = new FormattingOptions() - { - TabSize = 4, - InsertSpaces = true - } - }; - - // Act - var result = await target.RazorRangeFormattingAsync(request, DisposalToken); - - // Assert - Assert.NotNull(result); - Assert.Empty(result.Edits); - } - - [Fact] - public async Task RazorRangeFormattingAsync_ValidRequest_InvokesLanguageServer() - { - // Arrange - var filePath = "c:/Some/path/to/file.razor"; - var uri = new Uri(filePath); - var virtualDocument = new CSharpVirtualDocumentSnapshot(new Uri($"{filePath}.g.cs"), _textBuffer.CurrentSnapshot, 1); - LSPDocumentSnapshot document = new TestLSPDocumentSnapshot(uri, 1, new[] { virtualDocument }); - var documentManager = new Mock(MockBehavior.Strict); - documentManager - .Setup(manager => manager.TryGetDocument(It.IsAny(), out document)) - .Returns(true); - - var expectedEdit = new TextEdit() - { - NewText = "SomeEdit", - Range = new Range() { Start = new Position(), End = new Position() } - }; - - var requestInvoker = new Mock(MockBehavior.Strict); - requestInvoker - .Setup(r => r.ReinvokeRequestOnServerAsync( - _textBuffer, - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny())) - .ReturnsAsync(new ReinvocationResponse("languageClient", new[] { expectedEdit })); - var outputWindowLogger = Mock.Of(MockBehavior.Strict); - - var documentSynchronizer = GetDocumentSynchronizer(GetCSharpSnapshot(uri, hostDocumentSyncVersion: 1)); - var telemetryReporter = new Mock(MockBehavior.Strict); - telemetryReporter.Setup(r => r.BeginBlock(It.IsAny(), It.IsAny(), It.IsAny>())).Returns(NullScope.Instance); - - var target = new DefaultRazorLanguageServerCustomMessageTarget( - documentManager.Object, JoinableTaskContext, requestInvoker.Object, - TestFormattingOptionsProvider.Default, _editorSettingsManager, documentSynchronizer, telemetryReporter.Object, outputWindowLogger); - - var request = new RazorDocumentRangeFormattingParams() - { - HostDocumentFilePath = filePath, - Kind = RazorLanguageKind.CSharp, - ProjectedRange = new Range() - { - Start = new Position(), - End = new Position() - }, - Options = new FormattingOptions() - { - TabSize = 4, - InsertSpaces = true - } - }; - - // Act - var result = await target.RazorRangeFormattingAsync(request, DisposalToken); - - // Assert - Assert.NotNull(result); - var edit = Assert.Single(result.Edits); - Assert.Equal("SomeEdit", edit.NewText); - } - [Fact] public async Task ProvideCodeActionsAsync_CannotLookupDocument_ReturnsNullAsync() {