Skip to content

Commit 934af75

Browse files
authored
Hook up prototype paste with imports for JS/TS (#204665)
* Hook up prototype paste with imports for JS/TS For microsoft/TypeScript#57262 but with proposed changes to ts protocol * Support new api * Update
1 parent fb7f5a9 commit 934af75

File tree

8 files changed

+192
-2
lines changed

8 files changed

+192
-2
lines changed

extensions/typescript-language-features/package.json

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
"multiDocumentHighlightProvider",
1414
"mappedEditsProvider",
1515
"codeActionAI",
16-
"codeActionRanges"
16+
"codeActionRanges",
17+
"documentPaste"
1718
],
1819
"capabilities": {
1920
"virtualWorkspaces": {
@@ -1316,6 +1317,20 @@
13161317
"default": true,
13171318
"markdownDescription": "%typescript.workspaceSymbols.excludeLibrarySymbols%",
13181319
"scope": "window"
1320+
},
1321+
"javascript.experimental.updateImportsOnPaste": {
1322+
"scope": "resource",
1323+
"type": "boolean",
1324+
"default": false,
1325+
"description": "%configuration.updateImportsOnPaste%",
1326+
"tags": ["experimental"]
1327+
},
1328+
"typescript.experimental.updateImportsOnPaste": {
1329+
"scope": "resource",
1330+
"type": "boolean",
1331+
"default": false,
1332+
"description": "%configuration.updateImportsOnPaste%",
1333+
"tags": ["experimental"]
13191334
}
13201335
}
13211336
},

extensions/typescript-language-features/package.nls.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,7 @@
219219
"configuration.tsserver.web.projectWideIntellisense.suppressSemanticErrors": "Suppresses semantic errors on web even when project wide IntelliSense is enabled. This is always on when project wide IntelliSense is not enabled or available. See `#typescript.tsserver.web.projectWideIntellisense.enabled#`",
220220
"configuration.tsserver.web.typeAcquisition.enabled": "Enable/disable package acquisition on the web. This enables IntelliSense for imported packages. Requires `#typescript.tsserver.web.projectWideIntellisense.enabled#`. Currently not supported for Safari.",
221221
"configuration.tsserver.nodePath": "Run TS Server on a custom Node installation. This can be a path to a Node executable, or 'node' if you want VS Code to detect a Node installation.",
222+
"configuration.updateImportsOnPaste": "Automatically update imports when pasting code. Requires TypeScript 5.5+.",
222223
"walkthroughs.nodejsWelcome.title": "Get started with JavaScript and Node.js",
223224
"walkthroughs.nodejsWelcome.description": "Make the most of Visual Studio Code's first-class JavaScript experience.",
224225
"walkthroughs.nodejsWelcome.downloadNode.forMacOrWindows.title": "Install Node.js",
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import * as vscode from 'vscode';
7+
import { DocumentSelector } from '../configuration/documentSelector';
8+
import * as typeConverters from '../typeConverters';
9+
import { ClientCapability, ITypeScriptServiceClient } from '../typescriptService';
10+
import { conditionalRegistration, requireMinVersion, requireSomeCapability } from './util/dependentRegistration';
11+
import protocol from '../tsServer/protocol/protocol';
12+
import { API } from '../tsServer/api';
13+
import { LanguageDescription } from '../configuration/languageDescription';
14+
15+
class CopyMetadata {
16+
constructor(
17+
readonly resource: vscode.Uri,
18+
readonly ranges: readonly vscode.Range[],
19+
) { }
20+
21+
toJSON() {
22+
return JSON.stringify({
23+
resource: this.resource.toJSON(),
24+
ranges: this.ranges,
25+
});
26+
}
27+
28+
static fromJSON(str: string): CopyMetadata | undefined {
29+
try {
30+
const parsed = JSON.parse(str);
31+
return new CopyMetadata(
32+
vscode.Uri.from(parsed.resource),
33+
parsed.ranges.map((r: any) => new vscode.Range(r[0].line, r[0].character, r[1].line, r[1].character)));
34+
} catch {
35+
// ignore
36+
}
37+
return undefined;
38+
}
39+
}
40+
41+
class DocumentPasteProvider implements vscode.DocumentPasteEditProvider {
42+
43+
static readonly kind = vscode.DocumentDropOrPasteEditKind.Empty.append('text', 'jsts', 'pasteWithImports');
44+
static readonly metadataMimeType = 'application/vnd.code.jsts.metadata';
45+
46+
constructor(
47+
private readonly _modeId: string,
48+
private readonly _client: ITypeScriptServiceClient,
49+
) { }
50+
51+
prepareDocumentPaste(document: vscode.TextDocument, ranges: readonly vscode.Range[], dataTransfer: vscode.DataTransfer, _token: vscode.CancellationToken) {
52+
dataTransfer.set(DocumentPasteProvider.metadataMimeType,
53+
new vscode.DataTransferItem(new CopyMetadata(document.uri, ranges).toJSON()));
54+
}
55+
56+
async provideDocumentPasteEdits(
57+
document: vscode.TextDocument,
58+
ranges: readonly vscode.Range[],
59+
dataTransfer: vscode.DataTransfer,
60+
_context: vscode.DocumentPasteEditContext,
61+
token: vscode.CancellationToken,
62+
): Promise<vscode.DocumentPasteEdit[] | undefined> {
63+
const config = vscode.workspace.getConfiguration(this._modeId, document.uri);
64+
if (!config.get('experimental.updateImportsOnPaste')) {
65+
return;
66+
}
67+
68+
const file = this._client.toOpenTsFilePath(document);
69+
if (!file) {
70+
return;
71+
}
72+
73+
const text = await dataTransfer.get('text/plain')?.asString();
74+
if (!text || token.isCancellationRequested) {
75+
return;
76+
}
77+
78+
// Get optional metadata
79+
const metadata = await this.extractMetadata(dataTransfer, token);
80+
if (token.isCancellationRequested) {
81+
return;
82+
}
83+
84+
let copiedFrom: {
85+
file: string;
86+
spans: protocol.TextSpan[];
87+
} | undefined;
88+
if (metadata) {
89+
const spans = metadata.ranges.map(typeConverters.Range.toTextSpan);
90+
const copyFile = this._client.toTsFilePath(metadata.resource);
91+
if (copyFile) {
92+
copiedFrom = { file: copyFile, spans };
93+
}
94+
}
95+
96+
const response = await this._client.execute('getPasteEdits', {
97+
file,
98+
// TODO: only supports a single paste for now
99+
pastedText: [text],
100+
pasteLocations: ranges.map(typeConverters.Range.toTextSpan),
101+
copiedFrom
102+
}, token);
103+
if (response.type !== 'response' || !response.body || token.isCancellationRequested) {
104+
return;
105+
}
106+
107+
const edit = new vscode.DocumentPasteEdit('', vscode.l10n.t("Paste with imports"), DocumentPasteProvider.kind);
108+
const additionalEdit = new vscode.WorkspaceEdit();
109+
for (const edit of response.body.edits) {
110+
additionalEdit.set(this._client.toResource(edit.fileName), edit.textChanges.map(typeConverters.TextEdit.fromCodeEdit));
111+
}
112+
edit.additionalEdit = additionalEdit;
113+
return [edit];
114+
}
115+
116+
private async extractMetadata(dataTransfer: vscode.DataTransfer, token: vscode.CancellationToken): Promise<CopyMetadata | undefined> {
117+
const metadata = await dataTransfer.get(DocumentPasteProvider.metadataMimeType)?.asString();
118+
if (token.isCancellationRequested) {
119+
return undefined;
120+
}
121+
122+
return metadata ? CopyMetadata.fromJSON(metadata) : undefined;
123+
}
124+
}
125+
126+
export function register(selector: DocumentSelector, language: LanguageDescription, client: ITypeScriptServiceClient) {
127+
return conditionalRegistration([
128+
requireSomeCapability(client, ClientCapability.Semantic),
129+
requireMinVersion(client, API.v550),
130+
], () => {
131+
return vscode.languages.registerDocumentPasteEditProvider(selector.semantic, new DocumentPasteProvider(language.id, client), {
132+
providedPasteEditKinds: [DocumentPasteProvider.kind],
133+
copyMimeTypes: [DocumentPasteProvider.metadataMimeType],
134+
pasteMimeTypes: ['text/plain'],
135+
});
136+
});
137+
}

extensions/typescript-language-features/src/languageProvider.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ export default class LanguageProvider extends Disposable {
6565
import('./languageFeatures/codeLens/implementationsCodeLens').then(provider => this._register(provider.register(selector, this.description, this.client, cachedNavTreeResponse))),
6666
import('./languageFeatures/codeLens/referencesCodeLens').then(provider => this._register(provider.register(selector, this.description, this.client, cachedNavTreeResponse))),
6767
import('./languageFeatures/completions').then(provider => this._register(provider.register(selector, this.description, this.client, this.typingsStatus, this.fileConfigurationManager, this.commandManager, this.telemetryReporter, this.onCompletionAccepted))),
68+
import('./languageFeatures/copyPaste').then(provider => this._register(provider.register(selector, this.description, this.client))),
6869
import('./languageFeatures/definitions').then(provider => this._register(provider.register(selector, this.client))),
6970
import('./languageFeatures/directiveCommentCompletions').then(provider => this._register(provider.register(selector, this.client))),
7071
import('./languageFeatures/documentHighlight').then(provider => this._register(provider.register(selector, this.client))),

extensions/typescript-language-features/src/tsServer/api.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export class API {
3737
public static readonly v520 = API.fromSimpleString('5.2.0');
3838
public static readonly v544 = API.fromSimpleString('5.4.4');
3939
public static readonly v540 = API.fromSimpleString('5.4.0');
40+
public static readonly v550 = API.fromSimpleString('5.5.0');
4041

4142
public static fromVersionString(versionString: string): API {
4243
let version = semver.valid(versionString);

extensions/typescript-language-features/src/tsServer/protocol/protocol.d.ts

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ declare module '../../../../node_modules/typescript/lib/typescript' {
2020
readonly _serverType?: ServerType;
2121
}
2222

23+
//#region MapCode
2324
export interface MapCodeRequestArgs extends FileRequestArgs {
2425
/**
2526
* The files and changes to try and apply/map.
@@ -49,7 +50,39 @@ declare module '../../../../node_modules/typescript/lib/typescript' {
4950
}
5051

5152
export interface MapCodeResponse extends Response {
52-
body: readonly FileCodeEdits[];
53+
body: FileCodeEdits[]
5354
}
55+
//#endregion
56+
57+
//#region Paste
58+
export interface GetPasteEditsRequest extends Request {
59+
command: 'getPasteEdits';
60+
arguments: GetPasteEditsRequestArgs;
61+
}
62+
63+
export interface GetPasteEditsRequestArgs extends FileRequestArgs {
64+
/** The text that gets pasted in a file. */
65+
pastedText: string[];
66+
/** Locations of where the `pastedText` gets added in a file. If the length of the `pastedText` and `pastedLocations` are not the same,
67+
* then the `pastedText` is combined into one and added at all the `pastedLocations`.
68+
*/
69+
pasteLocations: TextSpan[];
70+
/** The source location of each `pastedText`. If present, the length of `spans` must be equal to the length of `pastedText`. */
71+
copiedFrom?: {
72+
file: string;
73+
spans: TextSpan[];
74+
};
75+
}
76+
77+
export interface GetPasteEditsResponse extends Response {
78+
body: PasteEditsAction;
79+
}
80+
export interface PasteEditsAction {
81+
edits: FileCodeEdits[];
82+
fixId?: {};
83+
}
84+
//#endregion
5485
}
5586
}
87+
88+

extensions/typescript-language-features/src/typescriptService.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ interface StandardTsServerRequests {
7777
'getMoveToRefactoringFileSuggestions': [Proto.GetMoveToRefactoringFileSuggestionsRequestArgs, Proto.GetMoveToRefactoringFileSuggestions];
7878
'linkedEditingRange': [Proto.FileLocationRequestArgs, Proto.LinkedEditingRangeResponse];
7979
'mapCode': [Proto.MapCodeRequestArgs, Proto.MapCodeResponse];
80+
'getPasteEdits': [Proto.GetPasteEditsRequestArgs, Proto.GetPasteEditsResponse];
8081
}
8182

8283
interface NoResponseTsServerRequests {

extensions/typescript-language-features/tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,6 @@
1717
"../../src/vscode-dts/vscode.proposed.mappedEditsProvider.d.ts",
1818
"../../src/vscode-dts/vscode.proposed.multiDocumentHighlightProvider.d.ts",
1919
"../../src/vscode-dts/vscode.proposed.workspaceTrust.d.ts",
20+
"../../src/vscode-dts/vscode.proposed.documentPaste.d.ts",
2021
]
2122
}

0 commit comments

Comments
 (0)