From 2b761cbac55e38a45f40104ac148dd9622006bec Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 16 Nov 2023 15:13:57 -0800 Subject: [PATCH] Hook up mapped code edits experiment for TS For https://github.com/microsoft/TypeScript/pull/55406 --- .../typescript-language-features/package.json | 1 + .../mappedCodeEditProvider.ts | 66 +++++++++++++++++++ .../src/languageProvider.ts | 1 + .../src/tsServer/api.ts | 1 + .../src/tsServer/protocol/protocol.d.ts | 38 +++++++++++ .../src/typescriptService.ts | 1 + .../tsconfig.json | 1 + 7 files changed, 109 insertions(+) create mode 100644 extensions/typescript-language-features/src/languageFeatures/mappedCodeEditProvider.ts diff --git a/extensions/typescript-language-features/package.json b/extensions/typescript-language-features/package.json index f8cf80d7d4e69..9a81218001ac0 100644 --- a/extensions/typescript-language-features/package.json +++ b/extensions/typescript-language-features/package.json @@ -8,6 +8,7 @@ "license": "MIT", "aiKey": "0c6ae279ed8443289764825290e4f9e2-1a736e7c-1324-4338-be46-fc2a58ae4d14-7255", "enabledApiProposals": [ + "mappedEditsProvider", "workspaceTrust" ], "capabilities": { diff --git a/extensions/typescript-language-features/src/languageFeatures/mappedCodeEditProvider.ts b/extensions/typescript-language-features/src/languageFeatures/mappedCodeEditProvider.ts new file mode 100644 index 0000000000000..0d04d2dc8082d --- /dev/null +++ b/extensions/typescript-language-features/src/languageFeatures/mappedCodeEditProvider.ts @@ -0,0 +1,66 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { API } from '../tsServer/api'; +import { FileSpan } from '../tsServer/protocol/protocol'; +import { ITypeScriptServiceClient } from '../typescriptService'; +import { conditionalRegistration, requireMinVersion } from './util/dependentRegistration'; +import { Range, WorkspaceEdit } from '../typeConverters'; +import { DocumentSelector } from '../configuration/documentSelector'; + +class TsMappedEditsProvider implements vscode.MappedEditsProvider { + constructor( + private readonly client: ITypeScriptServiceClient + ) { } + + async provideMappedEdits(document: vscode.TextDocument, codeBlocks: string[], context: vscode.MappedEditsContext, token: vscode.CancellationToken): Promise { + if (!this.isEnabled()) { + return; + } + + const file = this.client.toOpenTsFilePath(document); + if (!file) { + return; + } + + const response = await this.client.execute('mapCode', { + mappings: [{ + file, + contents: codeBlocks, + focusLocations: context.documents.map(documents => { + return documents.flatMap((contextItem): FileSpan[] => { + const file = this.client.toTsFilePath(contextItem.uri); + if (!file) { + return []; + } + return contextItem.ranges.map((range): FileSpan => ({ file, ...Range.toTextSpan(range) })); + }); + }), + }], + }, token); + if (response.type !== 'response' || !response.body) { + return; + } + + return WorkspaceEdit.fromFileCodeEdits(this.client, response.body); + } + + private isEnabled(): boolean { + return vscode.workspace.getConfiguration('typescript').get('experimental.mappedCodeEdits.enabled', false); + } +} + +export function register( + selector: DocumentSelector, + client: ITypeScriptServiceClient, +) { + return conditionalRegistration([ + requireMinVersion(client, API.v540) + ], () => { + const provider = new TsMappedEditsProvider(client); + return vscode.chat.registerMappedEditsProvider(selector.semantic, provider); + }); +} diff --git a/extensions/typescript-language-features/src/languageProvider.ts b/extensions/typescript-language-features/src/languageProvider.ts index 7acbf733f0cea..ee95a4403a4f8 100644 --- a/extensions/typescript-language-features/src/languageProvider.ts +++ b/extensions/typescript-language-features/src/languageProvider.ts @@ -77,6 +77,7 @@ export default class LanguageProvider extends Disposable { import('./languageFeatures/inlayHints').then(provider => this._register(provider.register(selector, this.description, this.client, this.fileConfigurationManager, this.telemetryReporter))), import('./languageFeatures/jsDocCompletions').then(provider => this._register(provider.register(selector, this.description, this.client, this.fileConfigurationManager))), import('./languageFeatures/linkedEditing').then(provider => this._register(provider.register(selector, this.client))), + import('./languageFeatures/mappedCodeEditProvider').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/tsServer/api.ts b/extensions/typescript-language-features/src/tsServer/api.ts index d7d24848baa03..92e4503ec85a4 100644 --- a/extensions/typescript-language-features/src/tsServer/api.ts +++ b/extensions/typescript-language-features/src/tsServer/api.ts @@ -35,6 +35,7 @@ export class API { public static readonly v500 = API.fromSimpleString('5.0.0'); public static readonly v510 = API.fromSimpleString('5.1.0'); public static readonly v520 = API.fromSimpleString('5.2.0'); + public static readonly v540 = API.fromSimpleString('5.4.0'); public static fromVersionString(versionString: string): API { let version = semver.valid(versionString); diff --git a/extensions/typescript-language-features/src/tsServer/protocol/protocol.d.ts b/extensions/typescript-language-features/src/tsServer/protocol/protocol.d.ts index 38345971fc88e..b8d863ebb1aba 100644 --- a/extensions/typescript-language-features/src/tsServer/protocol/protocol.d.ts +++ b/extensions/typescript-language-features/src/tsServer/protocol/protocol.d.ts @@ -19,5 +19,43 @@ declare module 'typescript/lib/tsserverlibrary' { interface Response { readonly _serverType?: ServerType; } + + export interface MapCodeRequestArgs { + /// The files and changes to try and apply/map. + mappings: MapCodeRequestDocumentMapping[]; + + /// Edits to apply to the current workspace before performing the mapping. + updates?: FileCodeEdits[] + } + + export interface MapCodeRequestDocumentMapping { + /// The file for the request (absolute pathname required). Null/undefined + /// if specific file is unknown. + file?: string; + + /// Optional name of project that contains file + projectFileName?: string; + + /// The specific code to map/insert/replace in the file. + contents: string[]; + + /// Areas of "focus" to inform the code mapper with. For example, cursor + /// location, current selection, viewport, etc. Nested arrays denote + /// priority: toplevel arrays are more important than inner arrays, and + /// inner array priorities are based on items within that array. Items + /// earlier in the arrays have higher priority. + focusLocations?: FileSpan[][]; + } + + export interface MapCodeRequest extends Request { + command: 'mapCode', + arguments: MapCodeRequestArgs; + } + + export interface MapCodeResponse extends Response { + body: FileCodeEdits[] + } } } + + diff --git a/extensions/typescript-language-features/src/typescriptService.ts b/extensions/typescript-language-features/src/typescriptService.ts index 6eb30e2098633..5823430c416fa 100644 --- a/extensions/typescript-language-features/src/typescriptService.ts +++ b/extensions/typescript-language-features/src/typescriptService.ts @@ -76,6 +76,7 @@ interface StandardTsServerRequests { 'findSourceDefinition': [Proto.FileLocationRequestArgs, Proto.DefinitionResponse]; 'getMoveToRefactoringFileSuggestions': [Proto.GetMoveToRefactoringFileSuggestionsRequestArgs, Proto.GetMoveToRefactoringFileSuggestions]; 'linkedEditingRange': [Proto.FileLocationRequestArgs, Proto.LinkedEditingRangeResponse]; + 'mapCode': [Proto.MapCodeRequestArgs, Proto.MapCodeResponse]; } interface NoResponseTsServerRequests { diff --git a/extensions/typescript-language-features/tsconfig.json b/extensions/typescript-language-features/tsconfig.json index 73957dde9321e..2a4a57847d4f6 100644 --- a/extensions/typescript-language-features/tsconfig.json +++ b/extensions/typescript-language-features/tsconfig.json @@ -11,6 +11,7 @@ "include": [ "src/**/*", "../../src/vscode-dts/vscode.d.ts", + "../../src/vscode-dts/vscode.proposed.mappedEditsProvider.d.ts", "../../src/vscode-dts/vscode.proposed.workspaceTrust.d.ts" ] }