diff --git a/client-node-tests/package.json b/client-node-tests/package.json index 8927303a5..995ab944f 100644 --- a/client-node-tests/package.json +++ b/client-node-tests/package.json @@ -44,5 +44,6 @@ "sinon": "^11.1.2", "uuid": "^8.3.2", "vscode-test": "^1.6.1" - } + }, + "enabledApiProposals": ["formatMultipleRanges"] } diff --git a/client-node-tests/src/integration.test.ts b/client-node-tests/src/integration.test.ts index 962405ca3..edf90a5a2 100644 --- a/client-node-tests/src/integration.test.ts +++ b/client-node-tests/src/integration.test.ts @@ -241,7 +241,9 @@ suite('Client integration', () => { resolveProvider: true }, documentFormattingProvider: true, - documentRangeFormattingProvider: true, + documentRangeFormattingProvider: { + rangesSupport: true + }, documentOnTypeFormattingProvider: { firstTriggerCharacter: ':' }, diff --git a/client-node-tests/src/servers/testServer.ts b/client-node-tests/src/servers/testServer.ts index a6fc5bc21..c7c134ad8 100644 --- a/client-node-tests/src/servers/testServer.ts +++ b/client-node-tests/src/servers/testServer.ts @@ -73,7 +73,9 @@ connection.onInitialize((params: InitializeParams): any => { resolveProvider: true }, documentFormattingProvider: true, - documentRangeFormattingProvider: true, + documentRangeFormattingProvider: { + rangesSupport: true + }, documentOnTypeFormattingProvider: { firstTriggerCharacter: ':' }, diff --git a/client/package.json b/client/package.json index ab127bd41..8b8e3b1e1 100644 --- a/client/package.json +++ b/client/package.json @@ -15,7 +15,7 @@ "bugs": { "url": "https://github.com/Microsoft/vscode-languageserver-node/issues" }, - "enabledApiProposals": [], + "enabledApiProposals": ["formatMultipleRanges"], "main": "./lib/node/main.js", "browser": { "./lib/node/main.js": "./lib/browser/main.js" diff --git a/client/src/common/codeConverter.ts b/client/src/common/codeConverter.ts index f8ae76717..1cb3b07a0 100644 --- a/client/src/common/codeConverter.ts +++ b/client/src/common/codeConverter.ts @@ -86,6 +86,8 @@ export interface Converter { asRange(value: code.Range): proto.Range; asRange(value: code.Range | undefined | null): proto.Range | undefined | null; + asRanges(values: readonly code.Range[]): proto.Range[]; + asLocation(value: null): null; asLocation(value: undefined): undefined; asLocation(value: code.Location): proto.Location; @@ -441,6 +443,10 @@ export function createConverter(uriConverter?: URIConverter): Converter { return { start: asPosition(value.start), end: asPosition(value.end) }; } + function asRanges(values: readonly code.Range[]): proto.Range[] { + return values.map(asRange as (item: code.Range) => proto.Range); + } + function asLocation(value: code.Location): proto.Location; function asLocation(value: undefined): undefined; function asLocation(value: null): null; @@ -959,6 +965,7 @@ export function createConverter(uriConverter?: URIConverter): Converter { asSignatureHelpParams, asWorkerPosition, asRange, + asRanges, asPosition, asPositions, asPositionsSync, diff --git a/client/src/common/formatting.ts b/client/src/common/formatting.ts index f55c3e1d0..5a56d7a34 100644 --- a/client/src/common/formatting.ts +++ b/client/src/common/formatting.ts @@ -2,6 +2,7 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. * ------------------------------------------------------------------------------------------ */ +/// import { languages as Languages, Disposable, TextDocument, ProviderResult, Range as VRange, Position as VPosition, TextEdit as VTextEdit, FormattingOptions as VFormattingOptions, @@ -9,7 +10,7 @@ import { } from 'vscode'; import { - ClientCapabilities, CancellationToken, ServerCapabilities, DocumentSelector, DocumentHighlightRegistrationOptions, DocumentFormattingOptions, DocumentFormattingRequest, TextDocumentRegistrationOptions, DocumentFormattingParams, DocumentRangeFormattingRegistrationOptions, DocumentRangeFormattingOptions, DocumentRangeFormattingRequest, DocumentRangeFormattingParams, DocumentOnTypeFormattingOptions, DocumentOnTypeFormattingRegistrationOptions, DocumentOnTypeFormattingRequest, DocumentOnTypeFormattingParams} from 'vscode-languageserver-protocol'; + ClientCapabilities, CancellationToken, ServerCapabilities, DocumentSelector, DocumentHighlightRegistrationOptions, DocumentFormattingOptions, DocumentFormattingRequest, TextDocumentRegistrationOptions, DocumentFormattingParams, DocumentRangeFormattingRegistrationOptions, DocumentRangeFormattingOptions, DocumentRangeFormattingRequest, DocumentRangeFormattingParams, DocumentRangesFormattingRequest, DocumentRangesFormattingParams, DocumentOnTypeFormattingOptions, DocumentOnTypeFormattingRegistrationOptions, DocumentOnTypeFormattingRequest, DocumentOnTypeFormattingParams} from 'vscode-languageserver-protocol'; import * as UUID from './utils/uuid'; @@ -36,6 +37,10 @@ export interface ProvideDocumentRangeFormattingEditsSignature { (this: void, document: TextDocument, range: VRange, options: VFormattingOptions, token: CancellationToken): ProviderResult; } +export interface ProvideDocumentRangesFormattingEditsSignature { + (this: void, document: TextDocument, ranges: VRange[], options: VFormattingOptions, token: CancellationToken): ProviderResult; +} + export interface ProvideOnTypeFormattingEditsSignature { (this: void, document: TextDocument, position: VPosition, ch: string, options: VFormattingOptions, token: CancellationToken): ProviderResult; } @@ -44,6 +49,7 @@ export interface ProvideOnTypeFormattingEditsSignature { export interface FormattingMiddleware { provideDocumentFormattingEdits?: (this: void, document: TextDocument, options: VFormattingOptions, token: CancellationToken, next: ProvideDocumentFormattingEditsSignature) => ProviderResult; provideDocumentRangeFormattingEdits?: (this: void, document: TextDocument, range: VRange, options: VFormattingOptions, token: CancellationToken, next: ProvideDocumentRangeFormattingEditsSignature) => ProviderResult; + provideDocumentRangesFormattingEdits?: (this: void, document: TextDocument, range: VRange[], options: VFormattingOptions, token: CancellationToken, next: ProvideDocumentRangesFormattingEditsSignature) => ProviderResult; provideOnTypeFormattingEdits?: (this: void, document: TextDocument, position: VPosition, ch: string, options: VFormattingOptions, token: CancellationToken, next: ProvideOnTypeFormattingEditsSignature) => ProviderResult; } @@ -102,7 +108,9 @@ export class DocumentRangeFormattingFeature extends TextDocumentLanguageFeature< } public fillClientCapabilities(capabilities: ClientCapabilities): void { - ensure(ensure(capabilities, 'textDocument')!, 'rangeFormatting')!.dynamicRegistration = true; + const capability = ensure(ensure(capabilities, 'textDocument')!, 'rangeFormatting')!; + capability.dynamicRegistration = true; + capability.rangesSupport = true; } public initialize(capabilities: ServerCapabilities, documentSelector: DocumentSelector): void { @@ -113,7 +121,7 @@ export class DocumentRangeFormattingFeature extends TextDocumentLanguageFeature< this.register({ id: UUID.generateUuid(), registerOptions: options }); } - protected registerLanguageProvider(options: TextDocumentRegistrationOptions): [Disposable, DocumentRangeFormattingEditProvider] { + protected registerLanguageProvider(options: DocumentRangeFormattingRegistrationOptions): [Disposable, DocumentRangeFormattingEditProvider] { const selector = options.documentSelector!; const provider: DocumentRangeFormattingEditProvider = { provideDocumentRangeFormattingEdits: (document, range, options, token) => { @@ -139,6 +147,31 @@ export class DocumentRangeFormattingFeature extends TextDocumentLanguageFeature< : provideDocumentRangeFormattingEdits(document, range, options, token); } }; + + if (options.rangesSupport) { + provider.provideDocumentRangesFormattingEdits = (document, ranges, options, token) => { + const client = this._client; + const provideDocumentRangesFormattingEdits: ProvideDocumentRangesFormattingEditsSignature = (document, ranges, options, token) => { + const params: DocumentRangesFormattingParams = { + textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(document), + ranges: client.code2ProtocolConverter.asRanges(ranges), + options: client.code2ProtocolConverter.asFormattingOptions(options, FileFormattingOptions.fromConfiguration(document)) + }; + return client.sendRequest(DocumentRangesFormattingRequest.type, params, token).then((result) => { + if (token.isCancellationRequested) { + return null; + } + return client.protocol2CodeConverter.asTextEdits(result, token); + }, (error) => { + return client.handleFailedRequest(DocumentRangesFormattingRequest.type, token, error, null); + }); + }; + const middleware = client.middleware; + return middleware.provideDocumentRangesFormattingEdits + ? middleware.provideDocumentRangesFormattingEdits(document, ranges, options, token, provideDocumentRangesFormattingEdits) + : provideDocumentRangesFormattingEdits(document, ranges, options, token); + }; + } return [Languages.registerDocumentRangeFormattingEditProvider(this._client.protocol2CodeConverter.asDocumentSelector(selector), provider), provider]; } } diff --git a/client/src/node/main.ts b/client/src/node/main.ts index dac300f3a..859e0cea5 100644 --- a/client/src/node/main.ts +++ b/client/src/node/main.ts @@ -23,7 +23,7 @@ import semverSatisfies = require('semver/functions/satisfies'); export * from 'vscode-languageserver-protocol/node'; export * from '../common/api'; -const REQUIRED_VSCODE_VERSION = '^1.68.0'; // do not change format, updated by `updateVSCode` script +const REQUIRED_VSCODE_VERSION = '^1.78.0'; // do not change format, updated by `updateVSCode` script export enum TransportKind { stdio, diff --git a/client/typings/vscode.proposed.formatMultipleRanges.d.ts b/client/typings/vscode.proposed.formatMultipleRanges.d.ts new file mode 100644 index 000000000..702bc14b4 --- /dev/null +++ b/client/typings/vscode.proposed.formatMultipleRanges.d.ts @@ -0,0 +1,29 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // https://github.com/microsoft/vscode/issues/158776 + + + export interface DocumentRangeFormattingEditProvider { + + /** + * Provide formatting edits for multiple ranges in a document. + * + * The given ranges are hints and providers can decide to format a smaller + * or larger range. Often this is done by adjusting the start and end + * of the range to full syntax nodes. + * + * @param document The document in which the command was invoked. + * @param ranges The ranges which should be formatted. + * @param options Options controlling formatting. + * @param token A cancellation token. + * @return A set of text edits or a thenable that resolves to such. The lack of a result can be + * signaled by returning `undefined`, `null`, or an empty array. + */ + provideDocumentRangesFormattingEdits?(document: TextDocument, ranges: Range[], options: FormattingOptions, token: CancellationToken): ProviderResult; + } +} diff --git a/protocol/metaModel.json b/protocol/metaModel.json index 8b8303746..e2190dc2e 100644 --- a/protocol/metaModel.json +++ b/protocol/metaModel.json @@ -1706,6 +1706,37 @@ }, "documentation": "A request to format a range in a document." }, + { + "method": "textDocument/rangesFormatting", + "result": { + "kind": "or", + "items": [ + { + "kind": "array", + "element": { + "kind": "reference", + "name": "TextEdit" + } + }, + { + "kind": "base", + "name": "null" + } + ] + }, + "messageDirection": "clientToServer", + "params": { + "kind": "reference", + "name": "DocumentRangesFormattingParams" + }, + "registrationOptions": { + "kind": "reference", + "name": "DocumentRangeFormattingRegistrationOptions" + }, + "documentation": "A request to format ranges in a document.\n\n@since 3.18.0\n@proposed", + "since": "3.18.0", + "proposed": true + }, { "method": "textDocument/onTypeFormatting", "result": { @@ -5982,6 +6013,47 @@ ], "documentation": "Registration options for a {@link DocumentRangeFormattingRequest}." }, + { + "name": "DocumentRangesFormattingParams", + "properties": [ + { + "name": "textDocument", + "type": { + "kind": "reference", + "name": "TextDocumentIdentifier" + }, + "documentation": "The document to format." + }, + { + "name": "ranges", + "type": { + "kind": "array", + "element": { + "kind": "reference", + "name": "Range" + } + }, + "documentation": "The ranges to format" + }, + { + "name": "options", + "type": { + "kind": "reference", + "name": "FormattingOptions" + }, + "documentation": "The format options" + } + ], + "mixins": [ + { + "kind": "reference", + "name": "WorkDoneProgressParams" + } + ], + "documentation": "The parameters of a {@link DocumentRangesFormattingRequest}.\n\n@since 3.18.0\n@proposed", + "since": "3.18.0", + "proposed": true + }, { "name": "DocumentOnTypeFormattingParams", "properties": [ @@ -9477,7 +9549,19 @@ }, { "name": "DocumentRangeFormattingOptions", - "properties": [], + "properties": [ + { + "name": "rangesSupport", + "type": { + "kind": "base", + "name": "boolean" + }, + "optional": true, + "documentation": "Whether the server supports formatting multiple ranges at once.\n\n@since 3.18.0\n@proposed", + "since": "3.18.0", + "proposed": true + } + ], "mixins": [ { "kind": "reference", @@ -12193,6 +12277,17 @@ }, "optional": true, "documentation": "Whether range formatting supports dynamic registration." + }, + { + "name": "rangesSupport", + "type": { + "kind": "base", + "name": "boolean" + }, + "optional": true, + "documentation": "Whether the client supports formatting multiple ranges at once.\n\n@since 3.18.0\n@proposed", + "since": "3.18.0", + "proposed": true } ], "documentation": "Client capabilities of a {@link DocumentRangeFormattingRequest}." diff --git a/protocol/src/common/protocol.ts b/protocol/src/common/protocol.ts index ced2a8bd2..7143b0257 100644 --- a/protocol/src/common/protocol.ts +++ b/protocol/src/common/protocol.ts @@ -3426,6 +3426,14 @@ export interface DocumentRangeFormattingClientCapabilities { * Whether range formatting supports dynamic registration. */ dynamicRegistration?: boolean; + + /** + * Whether the client supports formatting multiple ranges at once. + * + * @since 3.18.0 + * @proposed + */ + rangesSupport?: boolean; } /** @@ -3448,10 +3456,40 @@ export interface DocumentRangeFormattingParams extends WorkDoneProgressParams { options: FormattingOptions; } +/** + * The parameters of a {@link DocumentRangesFormattingRequest}. + * + * @since 3.18.0 + * @proposed + */ +export interface DocumentRangesFormattingParams extends WorkDoneProgressParams { + /** + * The document to format. + */ + textDocument: TextDocumentIdentifier; + + /** + * The ranges to format + */ + ranges: Range[]; + + /** + * The format options + */ + options: FormattingOptions; +} + /** * Provider options for a {@link DocumentRangeFormattingRequest}. */ export interface DocumentRangeFormattingOptions extends WorkDoneProgressOptions { + /** + * Whether the server supports formatting multiple ranges at once. + * + * @since 3.18.0 + * @proposed + */ + rangesSupport?: boolean; } /** @@ -3469,6 +3507,18 @@ export namespace DocumentRangeFormattingRequest { export const type = new ProtocolRequestType(method); } +/** + * A request to format ranges in a document. + * + * @since 3.18.0 + * @proposed + */ +export namespace DocumentRangesFormattingRequest { + export const method: 'textDocument/rangesFormatting' = 'textDocument/rangesFormatting'; + export const messageDirection: MessageDirection = MessageDirection.clientToServer; + export const type = new ProtocolRequestType(method); +} + /** * Client capabilities of a {@link DocumentOnTypeFormattingRequest}. */ diff --git a/testbed/server/src/server.ts b/testbed/server/src/server.ts index 385e35ef5..96ab34818 100644 --- a/testbed/server/src/server.ts +++ b/testbed/server/src/server.ts @@ -143,7 +143,9 @@ connection.onInitialize((params, cancel, progress): Thenable | resolveProvider: true }, documentFormattingProvider: true, - documentRangeFormattingProvider: true, + documentRangeFormattingProvider: { + rangesSupport: true + }, documentOnTypeFormattingProvider: { firstTriggerCharacter: ';', moreTriggerCharacter: ['{', '\n']