mirror of
https://github.com/microsoft/vscode.git
synced 2025-12-24 12:19:20 +00:00
Add widget to change how content is pasted (#181290)
* Add widget to change how content is pasted For #30066 This adds a widget that lets you change how content is pasted if there are multiple ways it could be pasted To do this, I've made the post drop widget generic and reused it for pasting too * Update types * More code deduplication
This commit is contained in:
@@ -53,7 +53,7 @@ class CopyPasteEditProvider implements vscode.DocumentPasteEditProvider {
|
||||
return;
|
||||
}
|
||||
|
||||
const pasteEdit = new vscode.DocumentPasteEdit(insert.insertText);
|
||||
const pasteEdit = new vscode.DocumentPasteEdit(insert.insertText, vscode.l10n.t('Insert image as attachment'));
|
||||
pasteEdit.additionalEdit = insert.additionalEdit;
|
||||
return pasteEdit;
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ class PasteEditProvider implements vscode.DocumentPasteEditProvider {
|
||||
}
|
||||
|
||||
const snippet = await tryGetUriListSnippet(document, dataTransfer, token);
|
||||
return snippet ? new vscode.DocumentPasteEdit(snippet.snippet) : undefined;
|
||||
return snippet ? new vscode.DocumentPasteEdit(snippet.snippet, snippet.label) : undefined;
|
||||
}
|
||||
|
||||
private async _makeCreateImagePasteEdit(document: vscode.TextDocument, file: vscode.DataTransferFile, token: vscode.CancellationToken): Promise<vscode.DocumentPasteEdit | undefined> {
|
||||
@@ -55,7 +55,7 @@ class PasteEditProvider implements vscode.DocumentPasteEditProvider {
|
||||
const workspaceFolder = vscode.workspace.getWorkspaceFolder(file.uri);
|
||||
if (workspaceFolder) {
|
||||
const snippet = createUriListSnippet(document, [file.uri]);
|
||||
return snippet ? new vscode.DocumentPasteEdit(snippet.snippet) : undefined;
|
||||
return snippet ? new vscode.DocumentPasteEdit(snippet.snippet, snippet.label) : undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ class PasteEditProvider implements vscode.DocumentPasteEditProvider {
|
||||
const workspaceEdit = new vscode.WorkspaceEdit();
|
||||
workspaceEdit.createFile(uri, { contents: file });
|
||||
|
||||
const pasteEdit = new vscode.DocumentPasteEdit(snippet.snippet);
|
||||
const pasteEdit = new vscode.DocumentPasteEdit(snippet.snippet, snippet.label);
|
||||
pasteEdit.additionalEdit = workspaceEdit;
|
||||
return pasteEdit;
|
||||
}
|
||||
|
||||
@@ -783,7 +783,8 @@ export interface CodeActionProvider {
|
||||
* @internal
|
||||
*/
|
||||
export interface DocumentPasteEdit {
|
||||
insertText: string | { snippet: string };
|
||||
readonly label: string;
|
||||
insertText: string | { readonly snippet: string };
|
||||
additionalEdit?: WorkspaceEdit;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,11 +3,14 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { EditorContributionInstantiation, registerEditorContribution } from 'vs/editor/browser/editorExtensions';
|
||||
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { EditorCommand, EditorContributionInstantiation, ServicesAccessor, registerEditorCommand, registerEditorContribution } from 'vs/editor/browser/editorExtensions';
|
||||
import { editorConfigurationBaseNode } from 'vs/editor/common/config/editorConfigurationSchema';
|
||||
import { CopyPasteController } from 'vs/editor/contrib/copyPaste/browser/copyPasteController';
|
||||
import { CopyPasteController, changePasteTypeCommandId, pasteWidgetVisibleCtx } from 'vs/editor/contrib/copyPaste/browser/copyPasteController';
|
||||
import * as nls from 'vs/nls';
|
||||
import { ConfigurationScope, Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
|
||||
registerEditorContribution(CopyPasteController.ID, CopyPasteController, EditorContributionInstantiation.Eager); // eager because it listens to events on the container dom node of the editor
|
||||
@@ -23,3 +26,20 @@ Registry.as<IConfigurationRegistry>(Extensions.Configuration).registerConfigurat
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
registerEditorCommand(new class extends EditorCommand {
|
||||
constructor() {
|
||||
super({
|
||||
id: changePasteTypeCommandId,
|
||||
precondition: pasteWidgetVisibleCtx,
|
||||
kbOpts: {
|
||||
weight: KeybindingWeight.EditorContrib,
|
||||
primary: KeyMod.CtrlCmd | KeyCode.Period,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public override runEditorCommand(_accessor: ServicesAccessor | null, editor: ICodeEditor, _args: any) {
|
||||
CopyPasteController.get(editor)?.changePasteType();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { addDisposableListener } from 'vs/base/browser/dom';
|
||||
import { coalesce } from 'vs/base/common/arrays';
|
||||
import { CancelablePromise, createCancelablePromise, raceCancellation } from 'vs/base/common/async';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { UriList, VSDataTransfer, createStringDataTransferItem } from 'vs/base/common/dataTransfer';
|
||||
@@ -13,22 +14,27 @@ import { Schemas } from 'vs/base/common/network';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { toVSDataTransfer } from 'vs/editor/browser/dnd';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { IBulkEditService, ResourceTextEdit } from 'vs/editor/browser/services/bulkEditService';
|
||||
import { EditorOption } from 'vs/editor/common/config/editorOptions';
|
||||
import { IRange, Range } from 'vs/editor/common/core/range';
|
||||
import { Selection } from 'vs/editor/common/core/selection';
|
||||
import { Handler, IEditorContribution, PastePayload } from 'vs/editor/common/editorCommon';
|
||||
import { DocumentPasteEdit, DocumentPasteEditProvider, WorkspaceEdit } from 'vs/editor/common/languages';
|
||||
import { DocumentPasteEdit, DocumentPasteEditProvider } from 'vs/editor/common/languages';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures';
|
||||
import { registerDefaultPasteProviders } from 'vs/editor/contrib/copyPaste/browser/defaultPasteProviders';
|
||||
import { CodeEditorStateFlag, EditorStateCancellationTokenSource } from 'vs/editor/contrib/editorState/browser/editorState';
|
||||
import { InlineProgressManager } from 'vs/editor/contrib/inlineProgress/browser/inlineProgress';
|
||||
import { SnippetParser } from 'vs/editor/contrib/snippet/browser/snippetParser';
|
||||
import { PostEditWidgetManager } from 'vs/editor/contrib/postEditWidget/browser/postEditWidget';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
|
||||
export const changePasteTypeCommandId = 'editor.changePasteType';
|
||||
|
||||
export const pasteWidgetVisibleCtx = new RawContextKey<boolean>('pasteWidgetVisible', false, localize('pasteWidgetVisible', "Whether the paste widget is showing"));
|
||||
|
||||
const vscodeClipboardMime = 'application/vnd.code.copyMetadata';
|
||||
|
||||
interface CopyMetadata {
|
||||
@@ -51,15 +57,14 @@ export class CopyPasteController extends Disposable implements IEditorContributi
|
||||
readonly dataTransferPromise: CancelablePromise<VSDataTransfer>;
|
||||
};
|
||||
|
||||
private operationIdPool = 0;
|
||||
private _currentOperation?: { readonly id: number; readonly promise: CancelablePromise<void> };
|
||||
private _currentOperation?: CancelablePromise<void>;
|
||||
|
||||
private readonly _pasteProgressManager: InlineProgressManager;
|
||||
private readonly _postPasteWidgetManager: PostEditWidgetManager;
|
||||
|
||||
constructor(
|
||||
editor: ICodeEditor,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IBulkEditService private readonly _bulkEditService: IBulkEditService,
|
||||
@IClipboardService private readonly _clipboardService: IClipboardService,
|
||||
@IConfigurationService private readonly _configurationService: IConfigurationService,
|
||||
@ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService,
|
||||
@@ -74,6 +79,18 @@ export class CopyPasteController extends Disposable implements IEditorContributi
|
||||
this._register(addDisposableListener(container, 'paste', e => this.handlePaste(e), true));
|
||||
|
||||
this._pasteProgressManager = this._register(new InlineProgressManager('pasteIntoEditor', editor, instantiationService));
|
||||
|
||||
this._postPasteWidgetManager = this._register(instantiationService.createInstance(PostEditWidgetManager, 'pasteIntoEditor', editor, pasteWidgetVisibleCtx, { id: changePasteTypeCommandId, label: localize('postPasteWidgetTitle', "Show paste options...") }));
|
||||
|
||||
registerDefaultPasteProviders(_languageFeaturesService);
|
||||
}
|
||||
|
||||
public changePasteType() {
|
||||
this._postPasteWidgetManager.tryShowSelector();
|
||||
}
|
||||
|
||||
public clearWidgets() {
|
||||
this._postPasteWidgetManager.clear();
|
||||
}
|
||||
|
||||
private arePasteActionsEnabled(model: ITextModel): boolean {
|
||||
@@ -152,9 +169,7 @@ export class CopyPasteController extends Disposable implements IEditorContributi
|
||||
return;
|
||||
}
|
||||
|
||||
const operationId = this.operationIdPool++;
|
||||
this._currentOperation?.promise.cancel();
|
||||
this._pasteProgressManager.clear();
|
||||
this._currentOperation?.cancel();
|
||||
|
||||
const selections = this._editor.getSelections();
|
||||
if (!selections?.length || !this._editor.hasModel()) {
|
||||
@@ -180,7 +195,6 @@ export class CopyPasteController extends Disposable implements IEditorContributi
|
||||
e.preventDefault();
|
||||
e.stopImmediatePropagation();
|
||||
|
||||
|
||||
const p = createCancelablePromise(async (token) => {
|
||||
const editor = this._editor;
|
||||
if (!editor.hasModel()) {
|
||||
@@ -189,10 +203,6 @@ export class CopyPasteController extends Disposable implements IEditorContributi
|
||||
|
||||
const tokenSource = new EditorStateCancellationTokenSource(editor, CodeEditorStateFlag.Value | CodeEditorStateFlag.Selection, undefined, token);
|
||||
try {
|
||||
this._pasteProgressManager.setAtPosition(selections[0].getEndPosition(), localize('pasteIntoEditorProgress', "Running paste handlers. Click to cancel"), {
|
||||
cancel: () => tokenSource.cancel()
|
||||
});
|
||||
|
||||
const dataTransfer = toVSDataTransfer(e.clipboardData!);
|
||||
|
||||
if (metadata?.id && this._currentClipboardItem?.handle === metadata.id) {
|
||||
@@ -219,58 +229,37 @@ export class CopyPasteController extends Disposable implements IEditorContributi
|
||||
|
||||
dataTransfer.delete(vscodeClipboardMime);
|
||||
|
||||
const providerEdit = await this.getProviderPasteEdit(providers, dataTransfer, model, selections, tokenSource.token);
|
||||
const providerEdits = await this.getPasteEdits(providers, dataTransfer, model, selections, tokenSource.token);
|
||||
if (tokenSource.token.isCancellationRequested) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (providerEdit) {
|
||||
const snippet = typeof providerEdit.insertText === 'string' ? SnippetParser.escape(providerEdit.insertText) : providerEdit.insertText.snippet;
|
||||
const combinedWorkspaceEdit: WorkspaceEdit = {
|
||||
edits: [
|
||||
new ResourceTextEdit(model.uri, {
|
||||
range: Selection.liftSelection(editor.getSelection()),
|
||||
text: snippet,
|
||||
insertAsSnippet: true,
|
||||
}),
|
||||
...(providerEdit.additionalEdit?.edits ?? [])
|
||||
]
|
||||
};
|
||||
await this._bulkEditService.apply(combinedWorkspaceEdit, { editor });
|
||||
return;
|
||||
if (providerEdits.length) {
|
||||
return this._postPasteWidgetManager.applyEditAndShowIfNeeded(selections[0], { activeEditIndex: 0, allEdits: providerEdits }, tokenSource.token);
|
||||
}
|
||||
|
||||
await this.applyDefaultPasteHandler(dataTransfer, metadata, tokenSource.token);
|
||||
} finally {
|
||||
tokenSource.dispose();
|
||||
if (this._currentOperation?.id === operationId) {
|
||||
this._pasteProgressManager.clear();
|
||||
if (this._currentOperation === p) {
|
||||
this._currentOperation = undefined;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this._currentOperation = { id: operationId, promise: p };
|
||||
this._pasteProgressManager.showWhile(selections[0].getEndPosition(), localize('pasteIntoEditorProgress', "Running paste handlers. Click to cancel"), p);
|
||||
this._currentOperation = p;
|
||||
}
|
||||
|
||||
private getProviderPasteEdit(providers: DocumentPasteEditProvider[], dataTransfer: VSDataTransfer, model: ITextModel, selections: Selection[], token: CancellationToken): Promise<DocumentPasteEdit | undefined> {
|
||||
return raceCancellation((async () => {
|
||||
for (const provider of providers) {
|
||||
if (token.isCancellationRequested) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isSupportedProvider(provider, dataTransfer)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const edit = await provider.provideDocumentPasteEdits(model, selections, dataTransfer, token);
|
||||
if (edit) {
|
||||
return edit;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
})(), token);
|
||||
private async getPasteEdits(providers: readonly DocumentPasteEditProvider[], dataTransfer: VSDataTransfer, model: ITextModel, selections: Selection[], token: CancellationToken): Promise<DocumentPasteEdit[]> {
|
||||
const result = await raceCancellation(
|
||||
Promise.all(
|
||||
providers
|
||||
.filter(provider => isSupportedProvider(provider, dataTransfer))
|
||||
.map(provider => provider.provideDocumentPasteEdits(model, selections, dataTransfer, token))
|
||||
).then(coalesce),
|
||||
token);
|
||||
return result ?? [];
|
||||
}
|
||||
|
||||
private async applyDefaultPasteHandler(dataTransfer: VSDataTransfer, metadata: CopyMetadata | undefined, token: CancellationToken) {
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { VSDataTransfer } from 'vs/base/common/dataTransfer';
|
||||
import { Mimes } from 'vs/base/common/mime';
|
||||
import { IRange } from 'vs/editor/common/core/range';
|
||||
import { DocumentPasteEdit, DocumentPasteEditProvider } from 'vs/editor/common/languages';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures';
|
||||
import { localize } from 'vs/nls';
|
||||
|
||||
class DefaultTextPasteProvider implements DocumentPasteEditProvider {
|
||||
|
||||
readonly id = 'text';
|
||||
readonly pasteMimeTypes = [Mimes.text, 'text'];
|
||||
|
||||
async provideDocumentPasteEdits(_model: ITextModel, _ranges: readonly IRange[], dataTransfer: VSDataTransfer, _token: CancellationToken): Promise<DocumentPasteEdit | undefined> {
|
||||
const textEntry = dataTransfer.get('text') ?? dataTransfer.get(Mimes.text);
|
||||
if (!textEntry) {
|
||||
return;
|
||||
}
|
||||
|
||||
const text = await textEntry.asString();
|
||||
return {
|
||||
label: localize('defaultPasteProvider.text.label', "Insert Plain Text"),
|
||||
insertText: text
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let registeredDefaultProviders = false;
|
||||
|
||||
export function registerDefaultPasteProviders(
|
||||
languageFeaturesService: ILanguageFeaturesService
|
||||
) {
|
||||
if (!registeredDefaultProviders) {
|
||||
registeredDefaultProviders = true;
|
||||
|
||||
languageFeaturesService.documentPasteEditProvider.register('*', new DefaultTextPasteProvider());
|
||||
}
|
||||
}
|
||||
@@ -5,34 +5,32 @@
|
||||
|
||||
import { coalesce } from 'vs/base/common/arrays';
|
||||
import { CancelablePromise, createCancelablePromise, raceCancellation } from 'vs/base/common/async';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { VSDataTransfer } from 'vs/base/common/dataTransfer';
|
||||
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { addExternalEditorsDropData, toVSDataTransfer } from 'vs/editor/browser/dnd';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { EditorCommand, EditorContributionInstantiation, ServicesAccessor, registerEditorCommand, registerEditorContribution } from 'vs/editor/browser/editorExtensions';
|
||||
import { IBulkEditService, ResourceTextEdit } from 'vs/editor/browser/services/bulkEditService';
|
||||
import { EditorOption } from 'vs/editor/common/config/editorOptions';
|
||||
import { IPosition } from 'vs/editor/common/core/position';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { IEditorContribution } from 'vs/editor/common/editorCommon';
|
||||
import { DocumentOnDropEdit, WorkspaceEdit } from 'vs/editor/common/languages';
|
||||
import { TrackedRangeStickiness } from 'vs/editor/common/model';
|
||||
import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures';
|
||||
import { DraggedTreeItemsIdentifier } from 'vs/editor/common/services/treeViewsDnd';
|
||||
import { ITreeViewsDnDService } from 'vs/editor/common/services/treeViewsDndService';
|
||||
import { PostDropWidgetManager, changeDropTypeCommandId, dropWidgetVisibleCtx } from 'vs/editor/contrib/dropIntoEditor/browser/postDropWidget';
|
||||
import { CodeEditorStateFlag, EditorStateCancellationTokenSource } from 'vs/editor/contrib/editorState/browser/editorState';
|
||||
import { InlineProgressManager } from 'vs/editor/contrib/inlineProgress/browser/inlineProgress';
|
||||
import { SnippetParser } from 'vs/editor/contrib/snippet/browser/snippetParser';
|
||||
import { PostEditWidgetManager } from 'vs/editor/contrib/postEditWidget/browser/postEditWidget';
|
||||
import { localize } from 'vs/nls';
|
||||
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { LocalSelectionTransfer } from 'vs/platform/dnd/browser/dnd';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { registerDefaultDropProviders } from './defaultOnDropProviders';
|
||||
|
||||
const changeDropTypeCommandId = 'editor.changeDropType';
|
||||
|
||||
const dropWidgetVisibleCtx = new RawContextKey<boolean>('dropWidgetVisible', false, localize('dropWidgetVisible', "Whether the drop widget is showing"));
|
||||
|
||||
export class DropIntoEditorController extends Disposable implements IEditorContribution {
|
||||
|
||||
@@ -42,11 +40,10 @@ export class DropIntoEditorController extends Disposable implements IEditorContr
|
||||
return editor.getContribution<DropIntoEditorController>(DropIntoEditorController.ID);
|
||||
}
|
||||
|
||||
private operationIdPool = 0;
|
||||
private _currentOperation?: { readonly id: number; readonly promise: CancelablePromise<void> };
|
||||
private _currentOperation?: CancelablePromise<void>;
|
||||
|
||||
private readonly _dropProgressManager: InlineProgressManager;
|
||||
private readonly _postDropWidgetManager: PostDropWidgetManager;
|
||||
private readonly _postDropWidgetManager: PostEditWidgetManager;
|
||||
|
||||
private readonly treeItemsTransfer = LocalSelectionTransfer.getInstance<DraggedTreeItemsIdentifier>();
|
||||
|
||||
@@ -54,14 +51,13 @@ export class DropIntoEditorController extends Disposable implements IEditorContr
|
||||
editor: ICodeEditor,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IWorkspaceContextService workspaceContextService: IWorkspaceContextService,
|
||||
@IBulkEditService private readonly _bulkEditService: IBulkEditService,
|
||||
@ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService,
|
||||
@ITreeViewsDnDService private readonly _treeViewsDragAndDropService: ITreeViewsDnDService,
|
||||
) {
|
||||
super();
|
||||
|
||||
this._dropProgressManager = this._register(instantiationService.createInstance(InlineProgressManager, 'dropIntoEditor', editor));
|
||||
this._postDropWidgetManager = this._register(instantiationService.createInstance(PostDropWidgetManager, editor));
|
||||
this._postDropWidgetManager = this._register(instantiationService.createInstance(PostEditWidgetManager, 'dropIntoEditor', editor, dropWidgetVisibleCtx, { id: changeDropTypeCommandId, label: localize('postDropWidgetTitle', "Show drop options...") }));
|
||||
|
||||
this._register(editor.onDropIntoEditor(e => this.onDropIntoEditor(editor, e.position, e.event)));
|
||||
|
||||
@@ -73,7 +69,7 @@ export class DropIntoEditorController extends Disposable implements IEditorContr
|
||||
}
|
||||
|
||||
public changeDropType() {
|
||||
this._postDropWidgetManager.changeExistingDropType();
|
||||
this._postDropWidgetManager.tryShowSelector();
|
||||
}
|
||||
|
||||
private async onDropIntoEditor(editor: ICodeEditor, position: IPosition, dragEvent: DragEvent) {
|
||||
@@ -81,21 +77,14 @@ export class DropIntoEditorController extends Disposable implements IEditorContr
|
||||
return;
|
||||
}
|
||||
|
||||
this._currentOperation?.promise.cancel();
|
||||
this._dropProgressManager.clear();
|
||||
this._currentOperation?.cancel();
|
||||
|
||||
editor.focus();
|
||||
editor.setPosition(position);
|
||||
|
||||
const operationId = this.operationIdPool++;
|
||||
|
||||
const p = createCancelablePromise(async (token) => {
|
||||
const tokenSource = new EditorStateCancellationTokenSource(editor, CodeEditorStateFlag.Value, undefined, token);
|
||||
|
||||
this._dropProgressManager.setAtPosition(position, localize('dropIntoEditorProgress', "Running drop handlers. Click to cancel"), {
|
||||
cancel: () => tokenSource.cancel()
|
||||
});
|
||||
|
||||
try {
|
||||
const ourDataTransfer = await this.extractDataTransferData(dragEvent);
|
||||
if (ourDataTransfer.size === 0 || tokenSource.token.isCancellationRequested) {
|
||||
@@ -125,20 +114,20 @@ export class DropIntoEditorController extends Disposable implements IEditorContr
|
||||
}
|
||||
|
||||
if (possibleDropEdits) {
|
||||
const allEdits = coalesce(possibleDropEdits);
|
||||
// Pass in the parent token here as it tracks cancelling the entire drop operation.
|
||||
await this.applyDropResult(editor, position, 0, coalesce(possibleDropEdits), token);
|
||||
await this._postDropWidgetManager.applyEditAndShowIfNeeded(Range.fromPositions(position), { activeEditIndex: 0, allEdits }, token);
|
||||
}
|
||||
} finally {
|
||||
tokenSource.dispose();
|
||||
|
||||
if (this._currentOperation?.id === operationId) {
|
||||
this._dropProgressManager.clear();
|
||||
if (this._currentOperation === p) {
|
||||
this._currentOperation = undefined;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this._currentOperation = { id: operationId, promise: p };
|
||||
this._dropProgressManager.showWhile(position, localize('dropIntoEditorProgress', "Running drop handlers. Click to cancel"), p);
|
||||
this._currentOperation = p;
|
||||
}
|
||||
|
||||
private async extractDataTransferData(dragEvent: DragEvent): Promise<VSDataTransfer> {
|
||||
@@ -165,54 +154,6 @@ export class DropIntoEditorController extends Disposable implements IEditorContr
|
||||
|
||||
return dataTransfer;
|
||||
}
|
||||
|
||||
private async applyDropResult(editor: ICodeEditor, position: IPosition, selectedEditIndex: number, allEdits: readonly DocumentOnDropEdit[], token: CancellationToken): Promise<void> {
|
||||
const model = editor.getModel();
|
||||
if (!model) {
|
||||
return;
|
||||
}
|
||||
|
||||
const edit = allEdits[selectedEditIndex];
|
||||
if (!edit) {
|
||||
return;
|
||||
}
|
||||
|
||||
const snippet = typeof edit.insertText === 'string' ? SnippetParser.escape(edit.insertText) : edit.insertText.snippet;
|
||||
const combinedWorkspaceEdit: WorkspaceEdit = {
|
||||
edits: [
|
||||
new ResourceTextEdit(model.uri, {
|
||||
range: Range.fromPositions(position),
|
||||
text: snippet,
|
||||
insertAsSnippet: true,
|
||||
}),
|
||||
...(edit.additionalEdit?.edits ?? [])
|
||||
]
|
||||
};
|
||||
|
||||
// Use a decoration to track edits around the cursor
|
||||
const editTrackingDecoration = model.deltaDecorations([], [{
|
||||
range: Range.fromPositions(position),
|
||||
options: { description: 'drop-line-suffix', stickiness: TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges }
|
||||
}]);
|
||||
|
||||
const editResult = await this._bulkEditService.apply(combinedWorkspaceEdit, { editor, token });
|
||||
|
||||
const editRange = model.getDecorationRange(editTrackingDecoration[0]);
|
||||
model.deltaDecorations(editTrackingDecoration, []);
|
||||
|
||||
if (editResult.isApplied && allEdits.length > 1) {
|
||||
const options = editor.getOptions().get(EditorOption.dropIntoEditor);
|
||||
if (options.showDropSelector === 'afterDrop') {
|
||||
this._postDropWidgetManager.show(editRange ?? Range.fromPositions(position), {
|
||||
activeEditIndex: selectedEditIndex,
|
||||
allEdits: allEdits,
|
||||
}, async (newEditIndex) => {
|
||||
await model.undo();
|
||||
this.applyDropResult(editor, position, newEditIndex, allEdits, token);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
registerEditorContribution(DropIntoEditorController.ID, DropIntoEditorController, EditorContributionInstantiation.BeforeFirstInteraction);
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { disposableTimeout } from 'vs/base/common/async';
|
||||
import { CancelablePromise, disposableTimeout } from 'vs/base/common/async';
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle';
|
||||
import { noBreakWhitespace } from 'vs/base/common/strings';
|
||||
@@ -116,6 +116,9 @@ export class InlineProgressManager extends Disposable {
|
||||
private readonly _currentDecorations: IEditorDecorationsCollection;
|
||||
private readonly _currentWidget = new MutableDisposable<InlineProgressWidget>();
|
||||
|
||||
private _operationIdPool = 0;
|
||||
private _currentOperation?: number;
|
||||
|
||||
constructor(
|
||||
readonly id: string,
|
||||
private readonly _editor: ICodeEditor,
|
||||
@@ -126,7 +129,10 @@ export class InlineProgressManager extends Disposable {
|
||||
this._currentDecorations = _editor.createDecorationsCollection();
|
||||
}
|
||||
|
||||
public setAtPosition(position: IPosition, title: string, delegate: InlineProgressDelegate) {
|
||||
public async showWhile<R>(position: IPosition, title: string, promise: CancelablePromise<R>): Promise<R> {
|
||||
const operationId = this._operationIdPool++;
|
||||
this._currentOperation = operationId;
|
||||
|
||||
this.clear();
|
||||
|
||||
this._showPromise.value = disposableTimeout(() => {
|
||||
@@ -137,12 +143,21 @@ export class InlineProgressManager extends Disposable {
|
||||
}]);
|
||||
|
||||
if (decorationIds.length > 0) {
|
||||
this._currentWidget.value = this._instantiationService.createInstance(InlineProgressWidget, this.id, this._editor, range, title, delegate);
|
||||
this._currentWidget.value = this._instantiationService.createInstance(InlineProgressWidget, this.id, this._editor, range, title, promise);
|
||||
}
|
||||
}, this._showDelay);
|
||||
|
||||
try {
|
||||
return await promise;
|
||||
} finally {
|
||||
if (this._currentOperation === operationId) {
|
||||
this.clear();
|
||||
this._currentOperation = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public clear() {
|
||||
private clear() {
|
||||
this._showPromise.clear();
|
||||
this._currentDecorations.clear();
|
||||
this._currentWidget.clear();
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.post-drop-widget {
|
||||
.post-edit-widget {
|
||||
box-shadow: 0 0 8px 2px var(--vscode-widget-shadow);
|
||||
border: 1px solid var(--vscode-widget-border, transparent);
|
||||
border-radius: 4px;
|
||||
@@ -11,16 +11,16 @@
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.post-drop-widget .monaco-button {
|
||||
.post-edit-widget .monaco-button {
|
||||
padding: 2px;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.post-drop-widget .monaco-button:hover {
|
||||
.post-edit-widget .monaco-button:hover {
|
||||
background-color: var(--vscode-button-secondaryHoverBackground) !important;
|
||||
}
|
||||
|
||||
.post-drop-widget .monaco-button .codicon {
|
||||
.post-edit-widget .monaco-button .codicon {
|
||||
margin: 0;
|
||||
}
|
||||
@@ -6,29 +6,37 @@
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { Button } from 'vs/base/browser/ui/button/button';
|
||||
import { toAction } from 'vs/base/common/actions';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { Disposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import 'vs/css!./postDropWidget';
|
||||
import 'vs/css!./postEditWidget';
|
||||
import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition } from 'vs/editor/browser/editorBrowser';
|
||||
import { IBulkEditResult, IBulkEditService, ResourceTextEdit } from 'vs/editor/browser/services/bulkEditService';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { DocumentOnDropEdit } from 'vs/editor/common/languages';
|
||||
import { localize } from 'vs/nls';
|
||||
import { WorkspaceEdit } from 'vs/editor/common/languages';
|
||||
import { TrackedRangeStickiness } from 'vs/editor/common/model';
|
||||
import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
|
||||
export const changeDropTypeCommandId = 'editor.changeDropType';
|
||||
|
||||
export const dropWidgetVisibleCtx = new RawContextKey<boolean>('dropWidgetVisible', false, localize('dropWidgetVisible', "Whether the drop widget is showing"));
|
||||
|
||||
interface DropEditSet {
|
||||
interface EditSet {
|
||||
readonly activeEditIndex: number;
|
||||
readonly allEdits: readonly DocumentOnDropEdit[];
|
||||
readonly allEdits: ReadonlyArray<{
|
||||
readonly label: string;
|
||||
readonly insertText: string | { readonly snippet: string };
|
||||
readonly additionalEdit?: WorkspaceEdit;
|
||||
}>;
|
||||
}
|
||||
|
||||
class PostDropWidget extends Disposable implements IContentWidget {
|
||||
private static readonly ID = 'editor.widget.postDropWidget';
|
||||
interface ShowCommand {
|
||||
readonly id: string;
|
||||
readonly label: string;
|
||||
}
|
||||
|
||||
class PostEditWidget extends Disposable implements IContentWidget {
|
||||
private static readonly baseId = 'editor.widget.inlineProgressWidget';
|
||||
|
||||
readonly allowEditorOverflow = true;
|
||||
readonly suppressMouseDown = true;
|
||||
@@ -36,12 +44,15 @@ class PostDropWidget extends Disposable implements IContentWidget {
|
||||
private domNode!: HTMLElement;
|
||||
private button!: Button;
|
||||
|
||||
private readonly dropWidgetVisible: IContextKey<boolean>;
|
||||
private readonly visibleContext: IContextKey<boolean>;
|
||||
|
||||
constructor(
|
||||
private readonly typeId: string,
|
||||
private readonly editor: ICodeEditor,
|
||||
visibleContext: RawContextKey<boolean>,
|
||||
private readonly showCommand: ShowCommand,
|
||||
private readonly range: Range,
|
||||
private readonly edits: DropEditSet,
|
||||
private readonly edits: EditSet,
|
||||
private readonly onSelectNewEdit: (editIndex: number) => void,
|
||||
@IContextMenuService private readonly _contextMenuService: IContextMenuService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@@ -51,9 +62,9 @@ class PostDropWidget extends Disposable implements IContentWidget {
|
||||
|
||||
this.create();
|
||||
|
||||
this.dropWidgetVisible = dropWidgetVisibleCtx.bindTo(contextKeyService);
|
||||
this.dropWidgetVisible.set(true);
|
||||
this._register(toDisposable(() => this.dropWidgetVisible.reset()));
|
||||
this.visibleContext = visibleContext.bindTo(contextKeyService);
|
||||
this.visibleContext.set(true);
|
||||
this._register(toDisposable(() => this.visibleContext.reset()));
|
||||
|
||||
this.editor.addContentWidget(this);
|
||||
this.editor.layoutContentWidget(this);
|
||||
@@ -72,25 +83,23 @@ class PostDropWidget extends Disposable implements IContentWidget {
|
||||
}
|
||||
|
||||
private _updateButtonTitle() {
|
||||
const binding = this._keybindingService.lookupKeybinding(changeDropTypeCommandId)?.getLabel();
|
||||
this.button.element.title = binding
|
||||
? localize('postDropWidgetTitleWithBinding', "Show drop options... ({0})", binding)
|
||||
: localize('postDropWidgetTitle', "Show drop options...");
|
||||
const binding = this._keybindingService.lookupKeybinding(this.showCommand.id)?.getLabel();
|
||||
this.button.element.title = this.showCommand.label + (binding ? ` (${binding})` : '');
|
||||
}
|
||||
|
||||
private create(): void {
|
||||
this.domNode = dom.$('.post-drop-widget');
|
||||
this.domNode = dom.$('.post-edit-widget');
|
||||
|
||||
this.button = this._register(new Button(this.domNode, {
|
||||
supportIcons: true,
|
||||
}));
|
||||
this.button.label = '$(insert)';
|
||||
|
||||
this._register(dom.addDisposableListener(this.domNode, dom.EventType.CLICK, () => this.showDropSelector()));
|
||||
this._register(dom.addDisposableListener(this.domNode, dom.EventType.CLICK, () => this.showSelector()));
|
||||
}
|
||||
|
||||
getId(): string {
|
||||
return PostDropWidget.ID;
|
||||
return PostEditWidget.baseId + '.' + this.typeId;
|
||||
}
|
||||
|
||||
getDomNode(): HTMLElement {
|
||||
@@ -104,7 +113,7 @@ class PostDropWidget extends Disposable implements IContentWidget {
|
||||
};
|
||||
}
|
||||
|
||||
showDropSelector() {
|
||||
showSelector() {
|
||||
this._contextMenuService.showContextMenu({
|
||||
getAnchor: () => {
|
||||
const pos = dom.getDomNodePagePosition(this.button.element);
|
||||
@@ -126,13 +135,17 @@ class PostDropWidget extends Disposable implements IContentWidget {
|
||||
}
|
||||
}
|
||||
|
||||
export class PostDropWidgetManager extends Disposable {
|
||||
export class PostEditWidgetManager extends Disposable {
|
||||
|
||||
private readonly _currentWidget = this._register(new MutableDisposable<PostDropWidget>());
|
||||
private readonly _currentWidget = this._register(new MutableDisposable<PostEditWidget>());
|
||||
|
||||
constructor(
|
||||
private readonly _id: string,
|
||||
private readonly _editor: ICodeEditor,
|
||||
private readonly _visibleContext: RawContextKey<boolean>,
|
||||
private readonly _showCommand: ShowCommand,
|
||||
@IInstantiationService private readonly _instantiationService: IInstantiationService,
|
||||
@IBulkEditService private readonly _bulkEditService: IBulkEditService,
|
||||
) {
|
||||
super();
|
||||
|
||||
@@ -142,11 +155,61 @@ export class PostDropWidgetManager extends Disposable {
|
||||
)(() => this.clear()));
|
||||
}
|
||||
|
||||
public show(range: Range, edits: DropEditSet, onDidSelectEdit: (newIndex: number) => void) {
|
||||
public async applyEditAndShowIfNeeded(range: Range, edits: EditSet, token: CancellationToken) {
|
||||
const model = this._editor.getModel();
|
||||
if (!model) {
|
||||
return;
|
||||
}
|
||||
|
||||
const edit = edits.allEdits[edits.activeEditIndex];
|
||||
if (!edit) {
|
||||
return;
|
||||
}
|
||||
|
||||
const combinedWorkspaceEdit: WorkspaceEdit = {
|
||||
edits: [
|
||||
new ResourceTextEdit(model.uri,
|
||||
typeof edit.insertText === 'string'
|
||||
? { range, text: edit.insertText, insertAsSnippet: false }
|
||||
: { range, text: edit.insertText.snippet, insertAsSnippet: true }
|
||||
),
|
||||
...(edit.additionalEdit?.edits ?? [])
|
||||
]
|
||||
};
|
||||
|
||||
// Use a decoration to track edits around the trigger range
|
||||
const editTrackingDecoration = model.deltaDecorations([], [{
|
||||
range,
|
||||
options: { description: 'paste-line-suffix', stickiness: TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges }
|
||||
}]);
|
||||
|
||||
let editResult: IBulkEditResult;
|
||||
let editRange: Range | null;
|
||||
try {
|
||||
editResult = await this._bulkEditService.apply(combinedWorkspaceEdit, { editor: this._editor, token });
|
||||
editRange = model.getDecorationRange(editTrackingDecoration[0]);
|
||||
} finally {
|
||||
model.deltaDecorations(editTrackingDecoration, []);
|
||||
}
|
||||
|
||||
if (editResult.isApplied && edits.allEdits.length > 1) {
|
||||
this.show(editRange ?? range, edits, async (newEditIndex) => {
|
||||
const model = this._editor.getModel();
|
||||
if (!model) {
|
||||
return;
|
||||
}
|
||||
|
||||
await model.undo();
|
||||
this.applyEditAndShowIfNeeded(range, { activeEditIndex: newEditIndex, allEdits: edits.allEdits }, token);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public show(range: Range, edits: EditSet, onDidSelectEdit: (newIndex: number) => void) {
|
||||
this.clear();
|
||||
|
||||
if (this._editor.hasModel()) {
|
||||
this._currentWidget.value = this._instantiationService.createInstance(PostDropWidget, this._editor, range, edits, onDidSelectEdit);
|
||||
this._currentWidget.value = this._instantiationService.createInstance(PostEditWidget, this._id, this._editor, this._visibleContext, this._showCommand, range, edits, onDidSelectEdit);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,7 +217,7 @@ export class PostDropWidgetManager extends Disposable {
|
||||
this._currentWidget.clear();
|
||||
}
|
||||
|
||||
public changeExistingDropType() {
|
||||
this._currentWidget.value?.showDropSelector();
|
||||
public tryShowSelector() {
|
||||
this._currentWidget.value?.showSelector();
|
||||
}
|
||||
}
|
||||
@@ -973,6 +973,7 @@ class MainThreadPasteEditProvider implements languages.DocumentPasteEditProvider
|
||||
}
|
||||
|
||||
return {
|
||||
label: result.label,
|
||||
insertText: result.insertText,
|
||||
additionalEdit: result.additionalEdit ? reviveWorkspaceEditDto(result.additionalEdit, this._uriIdentService, dataId => this.resolveFileData(request.id, dataId)) : undefined,
|
||||
};
|
||||
|
||||
@@ -1818,6 +1818,7 @@ export interface IInlineValueContextDto {
|
||||
export type ITypeHierarchyItemDto = Dto<TypeHierarchyItem>;
|
||||
|
||||
export interface IPasteEditDto {
|
||||
label: string;
|
||||
insertText: string | { snippet: string };
|
||||
additionalEdit?: IWorkspaceEditDto;
|
||||
}
|
||||
|
||||
@@ -505,6 +505,7 @@ class DocumentPasteEditProvider {
|
||||
private readonly _documents: ExtHostDocuments,
|
||||
private readonly _provider: vscode.DocumentPasteEditProvider,
|
||||
private readonly _handle: number,
|
||||
private readonly _extension: IExtensionDescription,
|
||||
) { }
|
||||
|
||||
async prepareDocumentPaste(resource: URI, ranges: IRange[], dataTransferDto: extHostProtocol.DataTransferDTO, token: CancellationToken): Promise<extHostProtocol.DataTransferDTO | undefined> {
|
||||
@@ -537,6 +538,7 @@ class DocumentPasteEditProvider {
|
||||
}
|
||||
|
||||
return {
|
||||
label: edit.label ?? localize('defaultPasteLabel', "Paste using '{0}' extension", this._extension.displayName || this._extension.name),
|
||||
insertText: typeof edit.insertText === 'string' ? edit.insertText : { snippet: edit.insertText.value },
|
||||
additionalEdit: edit.additionalEdit ? typeConvert.WorkspaceEdit.from(edit.additionalEdit, undefined) : undefined,
|
||||
};
|
||||
@@ -2405,7 +2407,7 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF
|
||||
|
||||
registerDocumentPasteEditProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.DocumentPasteEditProvider, metadata: vscode.DocumentPasteProviderMetadata): vscode.Disposable {
|
||||
const handle = this._nextHandle();
|
||||
this._adapter.set(handle, new AdapterData(new DocumentPasteEditProvider(this._proxy, this._documents, provider, handle), extension));
|
||||
this._adapter.set(handle, new AdapterData(new DocumentPasteEditProvider(this._proxy, this._documents, provider, handle, extension), extension));
|
||||
this._proxy.$registerPasteEditProvider(handle, this._transformDocumentSelector(selector, extension), !!provider.prepareDocumentPaste, metadata.pasteMimeTypes);
|
||||
return this._createDisposable(handle);
|
||||
}
|
||||
|
||||
@@ -2686,11 +2686,14 @@ export class DocumentDropEdit {
|
||||
|
||||
@es5ClassCompat
|
||||
export class DocumentPasteEdit {
|
||||
label: string;
|
||||
|
||||
insertText: string | SnippetString;
|
||||
|
||||
additionalEdit?: WorkspaceEdit;
|
||||
|
||||
constructor(insertText: string | SnippetString) {
|
||||
constructor(insertText: string | SnippetString, label: string) {
|
||||
this.label = label;
|
||||
this.insertText = insertText;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,6 +90,7 @@ import { CellFindMatchModel } from 'vs/workbench/contrib/notebook/browser/contri
|
||||
import { INotebookLoggingService } from 'vs/workbench/contrib/notebook/common/notebookLoggingService';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { DropIntoEditorController } from 'vs/editor/contrib/dropIntoEditor/browser/dropIntoEditorContribution';
|
||||
import { CopyPasteController } from 'vs/editor/contrib/copyPaste/browser/copyPasteController';
|
||||
|
||||
const $ = DOM.$;
|
||||
|
||||
@@ -1900,6 +1901,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD
|
||||
if (this.getActiveCell() === cell && editor) {
|
||||
SuggestController.get(editor)?.cancelSuggestWidget();
|
||||
DropIntoEditorController.get(editor)?.clearWidgets();
|
||||
CopyPasteController.get(editor)?.clearWidgets();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -44,6 +44,11 @@ declare module 'vscode' {
|
||||
* An operation applied on paste
|
||||
*/
|
||||
class DocumentPasteEdit {
|
||||
/**
|
||||
* Human readable label that describes the edit.
|
||||
*/
|
||||
label: string;
|
||||
|
||||
/**
|
||||
* The text or snippet to insert at the pasted locations.
|
||||
*/
|
||||
@@ -56,8 +61,10 @@ declare module 'vscode' {
|
||||
|
||||
/**
|
||||
* @param insertText The text or snippet to insert at the pasted locations.
|
||||
*
|
||||
* TODO: Reverse args, but this will break existing consumers :(
|
||||
*/
|
||||
constructor(insertText: string | SnippetString);
|
||||
constructor(insertText: string | SnippetString, label: string);
|
||||
}
|
||||
|
||||
interface DocumentPasteProviderMetadata {
|
||||
|
||||
Reference in New Issue
Block a user