diff --git a/extensions/typescript-language-features/src/languageFeatures/jsxLinkedEditing.ts b/extensions/typescript-language-features/src/languageFeatures/jsxLinkedEditing.ts new file mode 100644 index 00000000000000..467885b2a9a8f1 --- /dev/null +++ b/extensions/typescript-language-features/src/languageFeatures/jsxLinkedEditing.ts @@ -0,0 +1,49 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { ClientCapability, ITypeScriptServiceClient } from '../typescriptService'; +import { conditionalRegistration, requireMinVersion, requireSomeCapability } from '../utils/dependentRegistration'; +import { DocumentSelector } from '../utils/documentSelector'; +import * as typeConverters from '../utils/typeConverters'; +import API from '../utils/api'; + +class JsxLinkedEditingSupport implements vscode.LinkedEditingRangeProvider { + + public static readonly minVersion = API.v510; + + public constructor( + private readonly client: ITypeScriptServiceClient + ) { } + + async provideLinkedEditingRanges(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise { + const filepath = this.client.toOpenTsFilePath(document); + if (!filepath) { + return undefined; + } + + const args = typeConverters.Position.toFileLocationRequestArgs(filepath, position); + const response = await this.client.execute('jsxLinkedTag', args, token); + if (response.type !== 'response' || !response.body) { + return undefined; + } + + const wordPattern = response.body.wordPattern ? new RegExp(response.body.wordPattern) : undefined; + return new vscode.LinkedEditingRanges(response.body.ranges.map(range => typeConverters.Range.fromTextSpan(range)), wordPattern); + } +} + +export function register( + selector: DocumentSelector, + client: ITypeScriptServiceClient +) { + return conditionalRegistration([ + requireMinVersion(client, JsxLinkedEditingSupport.minVersion), + requireSomeCapability(client, ClientCapability.Syntax), + ], () => { + return vscode.languages.registerLinkedEditingRangeProvider(selector.semantic, + new JsxLinkedEditingSupport(client)); + }); +} diff --git a/extensions/typescript-language-features/src/languageProvider.ts b/extensions/typescript-language-features/src/languageProvider.ts index 321a2c845b3725..5bfa09da589aca 100644 --- a/extensions/typescript-language-features/src/languageProvider.ts +++ b/extensions/typescript-language-features/src/languageProvider.ts @@ -75,6 +75,7 @@ export default class LanguageProvider extends Disposable { import('./languageFeatures/implementations').then(provider => this._register(provider.register(selector, this.client))), import('./languageFeatures/inlayHints').then(provider => this._register(provider.register(selector, this.description, this.client, this.fileConfigurationManager))), import('./languageFeatures/jsDocCompletions').then(provider => this._register(provider.register(selector, this.description, this.client, this.fileConfigurationManager))), + import('./languageFeatures/jsxLinkedEditing').then(provider => this._register(provider.register(selector, this.client))), import('./languageFeatures/organizeImports').then(provider => this._register(provider.register(selector, this.client, this.commandManager, this.fileConfigurationManager, this.telemetryReporter))), import('./languageFeatures/quickFix').then(provider => this._register(provider.register(selector, this.client, this.fileConfigurationManager, this.commandManager, this.client.diagnosticsManager, this.telemetryReporter))), import('./languageFeatures/refactor').then(provider => this._register(provider.register(selector, this.client, this.fileConfigurationManager, this.commandManager, this.telemetryReporter))), diff --git a/extensions/typescript-language-features/src/protocol.d.ts b/extensions/typescript-language-features/src/protocol.d.ts index 38345971fc88e1..1a99bee855c7ad 100644 --- a/extensions/typescript-language-features/src/protocol.d.ts +++ b/extensions/typescript-language-features/src/protocol.d.ts @@ -19,5 +19,16 @@ declare module 'typescript/lib/tsserverlibrary' { interface Response { readonly _serverType?: ServerType; } + + interface LinkedEditingRanges { + ranges: TextSpan[]; + wordPattern?: string; + } + + interface JsxLinkedTagRequest extends FileLocationRequest { } + + interface JsxLinkedTagResponse extends Response { + body?: LinkedEditingRanges; + } } } diff --git a/extensions/typescript-language-features/src/typescriptService.ts b/extensions/typescript-language-features/src/typescriptService.ts index faba971b1d6719..5a641820b80794 100644 --- a/extensions/typescript-language-features/src/typescriptService.ts +++ b/extensions/typescript-language-features/src/typescriptService.ts @@ -74,6 +74,7 @@ interface StandardTsServerRequests { 'provideInlayHints': [Proto.InlayHintsRequestArgs, Proto.InlayHintsResponse]; 'encodedSemanticClassifications-full': [Proto.EncodedSemanticClassificationsRequestArgs, Proto.EncodedSemanticClassificationsResponse]; 'findSourceDefinition': [Proto.FileLocationRequestArgs, Proto.DefinitionResponse]; + 'jsxLinkedTag': [Proto.FileLocationRequestArgs, Proto.JsxLinkedTagResponse]; } interface NoResponseTsServerRequests { diff --git a/extensions/typescript-language-features/src/utils/api.ts b/extensions/typescript-language-features/src/utils/api.ts index fbf74fe993d887..e8a642e046ea3d 100644 --- a/extensions/typescript-language-features/src/utils/api.ts +++ b/extensions/typescript-language-features/src/utils/api.ts @@ -34,6 +34,7 @@ export default class API { public static readonly v470 = API.fromSimpleString('4.7.0'); public static readonly v480 = API.fromSimpleString('4.8.0'); public static readonly v490 = API.fromSimpleString('4.9.0'); + public static readonly v510 = API.fromSimpleString('5.1.0'); public static fromVersionString(versionString: string): API { let version = semver.valid(versionString);