Skip to content

Commit

Permalink
Hook up prototype paste with imports for JS/TS
Browse files Browse the repository at this point in the history
For microsoft/TypeScript#57262 but with proposed changes to ts protocol
  • Loading branch information
mjbvz committed Feb 7, 2024
1 parent a1fb0dc commit c85c0ed
Show file tree
Hide file tree
Showing 6 changed files with 140 additions and 1 deletion.
3 changes: 2 additions & 1 deletion extensions/typescript-language-features/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
"multiDocumentHighlightProvider",
"mappedEditsProvider",
"codeActionAI",
"codeActionRanges"
"codeActionRanges",
"documentPaste"
],
"capabilities": {
"virtualWorkspaces": {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/*---------------------------------------------------------------------------------------------
* 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 { DocumentSelector } from '../configuration/documentSelector';
import * as typeConverters from '../typeConverters';
import { ClientCapability, ITypeScriptServiceClient } from '../typescriptService';
import { conditionalRegistration, requireSomeCapability } from './util/dependentRegistration';

class CopyMetadata {
constructor(
readonly resource: vscode.Uri,
readonly ranges: readonly vscode.Range[],
) { }

toJSON() {
return JSON.stringify({
resource: this.resource.toJSON(),
ranges: this.ranges,
});
}

static fromJSON(str: string): CopyMetadata | undefined {
try {
const parsed = JSON.parse(str);
return new CopyMetadata(
vscode.Uri.from(parsed.resource),
parsed.ranges.map((r: any) => new vscode.Range(r[0].line, r[0].character, r[1].line, r[1].character)));
} catch {
// ignore
}
return undefined;
}
}

class DocumentPasteProvider implements vscode.DocumentPasteEditProvider {

static readonly metadataMimeType = 'application/vnd.code.jsts.metadata';

constructor(
private readonly _client: ITypeScriptServiceClient,
) { }

prepareDocumentPaste(document: vscode.TextDocument, ranges: readonly vscode.Range[], dataTransfer: vscode.DataTransfer, _token: vscode.CancellationToken) {
dataTransfer.set(DocumentPasteProvider.metadataMimeType,
new vscode.DataTransferItem(new CopyMetadata(document.uri, ranges).toJSON()));
}

async provideDocumentPasteEdits(document: vscode.TextDocument, ranges: readonly vscode.Range[], dataTransfer: vscode.DataTransfer, token: vscode.CancellationToken): Promise<vscode.DocumentPasteEdit | undefined> {
const file = this._client.toOpenTsFilePath(document);
if (!file) {
return;
}

const text = await dataTransfer.get('text/plain')?.asString();
if (!text || token.isCancellationRequested) {
return;
}

// Get optional metadata
const metadata = await this.extractMetadata(dataTransfer, token);
if (token.isCancellationRequested) {
return;
}

const copyRange = metadata?.ranges.at(0);
const copyFile = metadata ? this._client.toTsFilePath(metadata.resource) : undefined;

const response = await this._client.execute('getPostPasteImportFixes', {
file,
pastes: ranges.map(range => ({ text, range: typeConverters.Range.toTextSpan(range) })),
copy: metadata && copyFile && copyRange
? { file: copyFile, ...typeConverters.Range.toTextSpan(copyRange) }
: undefined,
}, token);
if (response.type !== 'response' || !response.body || token.isCancellationRequested) {
return;
}

const edit = new vscode.DocumentPasteEdit('', vscode.l10n.t("Paste with imports"));
const additionalEdit = new vscode.WorkspaceEdit();
for (const edit of response.body.edits) {
additionalEdit.set(this._client.toResource(edit.fileName), edit.textChanges.map(typeConverters.TextEdit.fromCodeEdit));
}
edit.additionalEdit = additionalEdit;
return edit;
}

private async extractMetadata(dataTransfer: vscode.DataTransfer, token: vscode.CancellationToken): Promise<CopyMetadata | undefined> {
const metadata = await dataTransfer.get(DocumentPasteProvider.metadataMimeType)?.asString();
if (token.isCancellationRequested) {
return undefined;
}

return metadata ? CopyMetadata.fromJSON(metadata) : undefined;
}
}

export function register(selector: DocumentSelector, client: ITypeScriptServiceClient) {
return conditionalRegistration([
requireSomeCapability(client, ClientCapability.Semantic),
], () => {
return vscode.languages.registerDocumentPasteEditProvider(selector.semantic, new DocumentPasteProvider(client), {
id: 'jsts.pasteWithImports',
copyMimeTypes: [DocumentPasteProvider.metadataMimeType],
pasteMimeTypes: ['text/plain'],
});
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ export default class LanguageProvider extends Disposable {
import('./languageFeatures/codeLens/implementationsCodeLens').then(provider => this._register(provider.register(selector, this.description, this.client, cachedNavTreeResponse))),
import('./languageFeatures/codeLens/referencesCodeLens').then(provider => this._register(provider.register(selector, this.description, this.client, cachedNavTreeResponse))),
import('./languageFeatures/completions').then(provider => this._register(provider.register(selector, this.description, this.client, this.typingsStatus, this.fileConfigurationManager, this.commandManager, this.telemetryReporter, this.onCompletionAccepted))),
import('./languageFeatures/copyPaste').then(provider => this._register(provider.register(selector, this.client))),
import('./languageFeatures/definitions').then(provider => this._register(provider.register(selector, this.client))),
import('./languageFeatures/directiveCommentCompletions').then(provider => this._register(provider.register(selector, this.client))),
import('./languageFeatures/documentHighlight').then(provider => this._register(provider.register(selector, this.client))),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ declare module 'typescript/lib/tsserverlibrary' {
readonly _serverType?: ServerType;
}

//#region MapCode
export interface MapCodeRequestArgs {
/// The files and changes to try and apply/map.
mappings: MapCodeRequestDocumentMapping[];
Expand Down Expand Up @@ -55,6 +56,29 @@ declare module 'typescript/lib/tsserverlibrary' {
export interface MapCodeResponse extends Response {
body: FileCodeEdits[]
}
//#endregion

//#region Paste
export interface GetPostPasteImportFixesRequest extends Request {
command: 'getPostPasteImportFixes';
arguments: GetPostPasteImportFixesRequestArgs;
}
export type DocumentRange = FileRangeRequestArgs;
export type CopyRange = {
start: Location;
end: Location;
}
export type GetPostPasteImportFixesRequestArgs = FileRequestArgs & {
pastes: Array<{ text: string; range: TextSpan }>,
copy?: FileSpan;
}
export interface GetPostPasteImportFixesResponse extends Response {
body: PostPasteImportAction;
}
export interface PostPasteImportAction {
edits: FileCodeEdits[];
}
//#endregion
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ interface StandardTsServerRequests {
'getMoveToRefactoringFileSuggestions': [Proto.GetMoveToRefactoringFileSuggestionsRequestArgs, Proto.GetMoveToRefactoringFileSuggestions];
'linkedEditingRange': [Proto.FileLocationRequestArgs, Proto.LinkedEditingRangeResponse];
'mapCode': [Proto.MapCodeRequestArgs, Proto.MapCodeResponse];
'getPostPasteImportFixes': [Proto.GetPostPasteImportFixesRequestArgs, Proto.GetPostPasteImportFixesResponse];
}

interface NoResponseTsServerRequests {
Expand Down
1 change: 1 addition & 0 deletions extensions/typescript-language-features/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,6 @@
"../../src/vscode-dts/vscode.proposed.mappedEditsProvider.d.ts",
"../../src/vscode-dts/vscode.proposed.multiDocumentHighlightProvider.d.ts",
"../../src/vscode-dts/vscode.proposed.workspaceTrust.d.ts",
"../../src/vscode-dts/vscode.proposed.documentPaste.d.ts",
]
}

0 comments on commit c85c0ed

Please sign in to comment.