diff --git a/extensions/markdown-language-features/src/languageFeatures/copyPaste.ts b/extensions/markdown-language-features/src/languageFeatures/copyPaste.ts index d9e939b463c..c36403d06ee 100644 --- a/extensions/markdown-language-features/src/languageFeatures/copyPaste.ts +++ b/extensions/markdown-language-features/src/languageFeatures/copyPaste.ts @@ -4,23 +4,29 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; -import { tryInsertUriList } from './dropIntoEditor'; +import { tryGetUriListSnippet } from './dropIntoEditor'; export function registerPasteProvider(selector: vscode.DocumentSelector) { return vscode.languages.registerDocumentPasteEditProvider(selector, new class implements vscode.DocumentPasteEditProvider { async provideDocumentPasteEdits( document: vscode.TextDocument, - range: vscode.Range, + _ranges: readonly vscode.Range[], dataTransfer: vscode.DataTransfer, token: vscode.CancellationToken, - ): Promise { + ): Promise { const enabled = vscode.workspace.getConfiguration('markdown', document).get('experimental.editor.pasteLinks.enabled', false); if (!enabled) { return; } - return tryInsertUriList(document, range, dataTransfer, token); + const snippet = await tryGetUriListSnippet(document, dataTransfer, token); + if (snippet) { + return { insertText: snippet }; + } + return undefined; } + }, { + pasteMimeTypes: ['text/uri-list'] }); } diff --git a/extensions/markdown-language-features/src/languageFeatures/dropIntoEditor.ts b/extensions/markdown-language-features/src/languageFeatures/dropIntoEditor.ts index 2ad71ec0516..b451ea40a4e 100644 --- a/extensions/markdown-language-features/src/languageFeatures/dropIntoEditor.ts +++ b/extensions/markdown-language-features/src/languageFeatures/dropIntoEditor.ts @@ -28,16 +28,20 @@ export function registerDropIntoEditor(selector: vscode.DocumentSelector) { async provideDocumentOnDropEdits(document: vscode.TextDocument, position: vscode.Position, dataTransfer: vscode.DataTransfer, token: vscode.CancellationToken): Promise { const enabled = vscode.workspace.getConfiguration('markdown', document).get('editor.drop.enabled', true); if (!enabled) { - return; + return undefined; } const replacementRange = new vscode.Range(position, position); - return tryInsertUriList(document, replacementRange, dataTransfer, token); + const snippet = await tryGetUriListSnippet(document, dataTransfer, token); + if (snippet) { + return new vscode.SnippetTextEdit(replacementRange, snippet); + } + return undefined; } }); } -export async function tryInsertUriList(document: vscode.TextDocument, replacementRange: vscode.Range, dataTransfer: vscode.DataTransfer, token: vscode.CancellationToken): Promise { +export async function tryGetUriListSnippet(document: vscode.TextDocument, dataTransfer: vscode.DataTransfer, token: vscode.CancellationToken): Promise { const urlList = await dataTransfer.get('text/uri-list')?.asString(); if (!urlList || token.isCancellationRequested) { return undefined; @@ -72,5 +76,5 @@ export async function tryInsertUriList(document: vscode.TextDocument, replacemen } }); - return new vscode.SnippetTextEdit(replacementRange, snippet); + return snippet; } diff --git a/src/vs/editor/common/languages.ts b/src/vs/editor/common/languages.ts index 6ad570fcd54..250662ee098 100644 --- a/src/vs/editor/common/languages.ts +++ b/src/vs/editor/common/languages.ts @@ -724,10 +724,21 @@ export interface CodeActionProvider { /** * @internal */ -export interface DocumentPasteEditProvider { - prepareDocumentPaste?(model: model.ITextModel, selection: Selection, dataTransfer: VSDataTransfer, token: CancellationToken): Promise; +export interface DocumentPasteEdit { + insertSnippet: string; + additionalEdit?: WorkspaceEdit; +} - provideDocumentPasteEdits(model: model.ITextModel, selection: Selection, dataTransfer: VSDataTransfer, token: CancellationToken): Promise; +/** + * @internal + */ +export interface DocumentPasteEditProvider { + + readonly pasteMimeTypes: readonly string[]; + + prepareDocumentPaste?(model: model.ITextModel, selections: readonly Selection[], dataTransfer: VSDataTransfer, token: CancellationToken): Promise; + + provideDocumentPasteEdits(model: model.ITextModel, selections: readonly Selection[], dataTransfer: VSDataTransfer, token: CancellationToken): Promise; } /** diff --git a/src/vs/editor/contrib/copyPaste/browser/copyPasteController.ts b/src/vs/editor/contrib/copyPaste/browser/copyPasteController.ts index 41c53942b70..a8904ad30cb 100644 --- a/src/vs/editor/contrib/copyPaste/browser/copyPasteController.ts +++ b/src/vs/editor/contrib/copyPaste/browser/copyPasteController.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { DataTransfers } from 'vs/base/browser/dnd'; import { addDisposableListener } from 'vs/base/browser/dom'; import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -15,7 +16,7 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IBulkEditService, ResourceEdit } from 'vs/editor/browser/services/bulkEditService'; import { Selection } from 'vs/editor/common/core/selection'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; -import { DocumentPasteEditProvider, SnippetTextEdit, 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 { CodeEditorStateFlag, EditorStateCancellationTokenSource } from 'vs/editor/contrib/editorState/browser/editorState'; @@ -26,15 +27,14 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur const vscodeClipboardMime = 'application/vnd.code.copyId'; const defaultPasteEditProvider = new class implements DocumentPasteEditProvider { - async provideDocumentPasteEdits(model: ITextModel, selection: Selection, dataTransfer: VSDataTransfer, _token: CancellationToken): Promise { + pasteMimeTypes = [Mimes.text, 'text']; + + async provideDocumentPasteEdits(model: ITextModel, selections: Selection[], dataTransfer: VSDataTransfer, _token: CancellationToken): Promise { const textDataTransfer = dataTransfer.get(Mimes.text) ?? dataTransfer.get('text'); if (textDataTransfer) { const text = await textDataTransfer.asString(); return { - edits: [{ - resource: model.uri, - edit: { range: selection, text }, - }] + insertSnippet: text }; } @@ -76,8 +76,8 @@ export class CopyPasteController extends Disposable implements IEditorContributi } const model = editor.getModel(); - const selection = this._editor.getSelection(); - if (!model || !selection) { + const selections = this._editor.getSelections(); + if (!model || !selections?.length) { return; } @@ -98,7 +98,7 @@ export class CopyPasteController extends Disposable implements IEditorContributi const promise = createCancelablePromise(async token => { const results = await Promise.all(providers.map(provider => { - return provider.prepareDocumentPaste!(model, selection, dataTransfer, token); + return provider.prepareDocumentPaste!(model, selections, dataTransfer, token); })); for (const result of results) { @@ -115,8 +115,8 @@ export class CopyPasteController extends Disposable implements IEditorContributi })); this._register(addDisposableListener(container, 'paste', async (e: ClipboardEvent) => { - const selection = this._editor.getSelection(); - if (!e.clipboardData || !selection || !editor.hasModel()) { + const selections = this._editor.getSelections(); + if (!e.clipboardData || !selections?.length || !editor.hasModel()) { return; } @@ -125,21 +125,20 @@ export class CopyPasteController extends Disposable implements IEditorContributi return; } - const originalDocVersion = model.getVersionId(); + const handle = e.clipboardData?.getData(vscodeClipboardMime); + if (typeof handle !== 'string') { + return; + } const providers = this._languageFeaturesService.documentPasteEditProvider.ordered(model); if (!providers.length) { return; } - const handle = e.clipboardData?.getData(vscodeClipboardMime); - if (typeof handle !== 'string') { - return; - } - e.preventDefault(); e.stopImmediatePropagation(); + const originalDocVersion = model.getVersionId(); const tokenSource = new EditorStateCancellationTokenSource(editor, CodeEditorStateFlag.Value | CodeEditorStateFlag.Selection); try { @@ -163,16 +162,25 @@ export class CopyPasteController extends Disposable implements IEditorContributi dataTransfer.delete(vscodeClipboardMime); for (const provider of [...providers, defaultPasteEditProvider]) { - const edit = await provider.provideDocumentPasteEdits(model, selection, dataTransfer, tokenSource.token); + if (!provider.pasteMimeTypes.some(type => { + if (type.toLowerCase() === DataTransfers.FILES.toLowerCase()) { + return [...dataTransfer.values()].some(item => item.asFile()); + } + return dataTransfer.has(type); + })) { + continue; + } + + const edit = await provider.provideDocumentPasteEdits(model, selections, dataTransfer, tokenSource.token); if (originalDocVersion !== model.getVersionId()) { return; } if (edit) { - if ((edit as WorkspaceEdit).edits) { - await this._bulkEditService.apply(ResourceEdit.convert(edit as WorkspaceEdit), { editor }); - } else { - performSnippetEdit(editor, edit as SnippetTextEdit); + performSnippetEdit(editor, edit.insertSnippet, selections); + + if (edit.additionalEdit) { + await this._bulkEditService.apply(ResourceEdit.convert(edit.additionalEdit), { editor }); } return; } diff --git a/src/vs/editor/contrib/snippet/browser/snippetController2.ts b/src/vs/editor/contrib/snippet/browser/snippetController2.ts index f226b74458d..c7bcf241938 100644 --- a/src/vs/editor/contrib/snippet/browser/snippetController2.ts +++ b/src/vs/editor/contrib/snippet/browser/snippetController2.ts @@ -9,6 +9,7 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorCommand, registerEditorCommand, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; +import { ISelection } from 'vs/editor/common/core/selection'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { CompletionItem, CompletionItemKind, CompletionItemProvider, SnippetTextEdit } from 'vs/editor/common/languages'; @@ -335,13 +336,20 @@ registerEditorCommand(new CommandCtor({ // --- -export function performSnippetEdit(editor: ICodeEditor, edit: SnippetTextEdit) { +export function performSnippetEdit(editor: ICodeEditor, snippet: string, selections: ISelection[]): boolean; +export function performSnippetEdit(editor: ICodeEditor, edit: SnippetTextEdit): boolean; +export function performSnippetEdit(editor: ICodeEditor, editOrSnippet: string | SnippetTextEdit, selections?: ISelection[]): boolean { const controller = SnippetController2.get(editor); if (!controller) { return false; } editor.focus(); - editor.setSelection(edit.range); - controller.insert(edit.snippet); + if (typeof editOrSnippet === 'string') { + editor.setSelections(selections ?? []); + controller.insert(editOrSnippet); + } else { + editor.setSelection(editOrSnippet.range); + controller.insert(editOrSnippet.snippet); + } return controller.isInSnippet(); } diff --git a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts index ff55fc5844f..7ce9a493e39 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts @@ -30,7 +30,7 @@ import * as callh from 'vs/workbench/contrib/callHierarchy/common/callHierarchy' import * as search from 'vs/workbench/contrib/search/common/search'; import * as typeh from 'vs/workbench/contrib/typeHierarchy/common/typeHierarchy'; import { extHostNamedCustomer, IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers'; -import { ExtHostContext, ExtHostLanguageFeaturesShape, ICallHierarchyItemDto, ICodeActionDto, ICodeActionProviderMetadataDto, IdentifiableInlineCompletion, IdentifiableInlineCompletions, IDocumentFilterDto, IIndentationRuleDto, IInlayHintDto, ILanguageConfigurationDto, ILanguageWordDefinitionDto, ILinkDto, ILocationDto, ILocationLinkDto, IOnEnterRuleDto, IRegExpDto, ISignatureHelpProviderMetadataDto, ISuggestDataDto, ISuggestDataDtoField, ISuggestResultDtoField, ITypeHierarchyItemDto, IWorkspaceEditDto, IWorkspaceSymbolDto, MainContext, MainThreadLanguageFeaturesShape, reviveWorkspaceEditDto } from '../common/extHost.protocol'; +import { ExtHostContext, ExtHostLanguageFeaturesShape, ICallHierarchyItemDto, ICodeActionDto, ICodeActionProviderMetadataDto, IdentifiableInlineCompletion, IdentifiableInlineCompletions, IDocumentFilterDto, IIndentationRuleDto, IInlayHintDto, ILanguageConfigurationDto, ILanguageWordDefinitionDto, ILinkDto, ILocationDto, ILocationLinkDto, IOnEnterRuleDto, IRegExpDto, ISignatureHelpProviderMetadataDto, ISuggestDataDto, ISuggestDataDtoField, ISuggestResultDtoField, ITypeHierarchyItemDto, IWorkspaceSymbolDto, MainContext, MainThreadLanguageFeaturesShape, reviveWorkspaceEditDto } from '../common/extHost.protocol'; @extHostNamedCustomer(MainContext.MainThreadLanguageFeatures) export class MainThreadLanguageFeatures extends Disposable implements MainThreadLanguageFeaturesShape { @@ -368,21 +368,22 @@ export class MainThreadLanguageFeatures extends Disposable implements MainThread // --- copy paste action provider - $registerPasteEditProvider(handle: number, selector: IDocumentFilterDto[], supportsCopy: boolean): void { + $registerPasteEditProvider(handle: number, selector: IDocumentFilterDto[], supportsCopy: boolean, pasteMimeTypes: readonly string[]): void { const provider: languages.DocumentPasteEditProvider = { + pasteMimeTypes: pasteMimeTypes, + prepareDocumentPaste: supportsCopy - ? async (model: ITextModel, selection: Selection, dataTransfer: VSDataTransfer, token: CancellationToken): Promise => { + ? async (model: ITextModel, selections: Selection[], dataTransfer: VSDataTransfer, token: CancellationToken): Promise => { const dataTransferDto = await typeConvert.DataTransfer.toDataTransferDTO(dataTransfer); if (token.isCancellationRequested) { return undefined; } - const result = await this._proxy.$prepareDocumentPaste(handle, model.uri, selection, dataTransferDto, token); + const result = await this._proxy.$prepareDocumentPaste(handle, model.uri, selections, dataTransferDto, token); if (!result) { return undefined; } - const dataTransferOut = new VSDataTransfer(); result.items.forEach(([type, item]) => { dataTransferOut.replace(type, createStringDataTransferItem(item.asString)); @@ -391,16 +392,17 @@ export class MainThreadLanguageFeatures extends Disposable implements MainThread } : undefined, - provideDocumentPasteEdits: async (model: ITextModel, selection: Selection, dataTransfer: VSDataTransfer, token: CancellationToken) => { + provideDocumentPasteEdits: async (model: ITextModel, selections: Selection[], dataTransfer: VSDataTransfer, token: CancellationToken) => { const d = await typeConvert.DataTransfer.toDataTransferDTO(dataTransfer); - const result = await this._proxy.$providePasteEdits(handle, model.uri, selection, d, token); + const result = await this._proxy.$providePasteEdits(handle, model.uri, selections, d, token); if (!result) { - return; - } else if ((result as IWorkspaceEditDto).edits) { - return reviveWorkspaceEditDto(result as IWorkspaceEditDto); - } else { - return result as languages.SnippetTextEdit; + return undefined; } + + return { + insertSnippet: result.insertSnippet, + additionalEdit: result.additionalEdit ? reviveWorkspaceEditDto(result.additionalEdit) : undefined, + }; } }; diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index e649eb3a21a..dea88ef4494 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -457,9 +457,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I registerCodeActionsProvider(selector: vscode.DocumentSelector, provider: vscode.CodeActionProvider, metadata?: vscode.CodeActionProviderMetadata): vscode.Disposable { return extHostLanguageFeatures.registerCodeActionProvider(extension, checkSelector(selector), provider, metadata); }, - registerDocumentPasteEditProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentPasteEditProvider): vscode.Disposable { + registerDocumentPasteEditProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentPasteEditProvider, metadata: vscode.DocumentPasteProviderMetadata): vscode.Disposable { checkProposedApiEnabled(extension, 'documentPaste'); - return extHostLanguageFeatures.registerDocumentPasteEditProvider(extension, checkSelector(selector), provider); + return extHostLanguageFeatures.registerDocumentPasteEditProvider(extension, checkSelector(selector), provider, metadata); }, registerCodeLensProvider(selector: vscode.DocumentSelector, provider: vscode.CodeLensProvider): vscode.Disposable { return extHostLanguageFeatures.registerCodeLensProvider(extension, checkSelector(selector), provider); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 865a0e5c4eb..869db5359f2 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -372,7 +372,7 @@ export interface MainThreadLanguageFeaturesShape extends IDisposable { $registerLinkedEditingRangeProvider(handle: number, selector: IDocumentFilterDto[]): void; $registerReferenceSupport(handle: number, selector: IDocumentFilterDto[]): void; $registerQuickFixSupport(handle: number, selector: IDocumentFilterDto[], metadata: ICodeActionProviderMetadataDto, displayName: string, supportsResolve: boolean): void; - $registerPasteEditProvider(handle: number, selector: IDocumentFilterDto[], supportsCopy: boolean): void; + $registerPasteEditProvider(handle: number, selector: IDocumentFilterDto[], supportsCopy: boolean, pasteMimeTypes: readonly string[]): void; $registerDocumentFormattingSupport(handle: number, selector: IDocumentFilterDto[], extensionId: ExtensionIdentifier, displayName: string): void; $registerRangeFormattingSupport(handle: number, selector: IDocumentFilterDto[], extensionId: ExtensionIdentifier, displayName: string): void; $registerOnTypeFormattingSupport(handle: number, selector: IDocumentFilterDto[], autoFormatTriggerCharacters: string[], extensionId: ExtensionIdentifier): void; @@ -1713,6 +1713,11 @@ export interface IInlineValueContextDto { export type ITypeHierarchyItemDto = Dto; +export interface IPasteEditDto { + insertSnippet: string; + additionalEdit?: IWorkspaceEditDto; +} + export interface ExtHostLanguageFeaturesShape { $provideDocumentSymbols(handle: number, resource: UriComponents, token: CancellationToken): Promise; $provideCodeLenses(handle: number, resource: UriComponents, token: CancellationToken): Promise; @@ -1731,8 +1736,8 @@ export interface ExtHostLanguageFeaturesShape { $provideCodeActions(handle: number, resource: UriComponents, rangeOrSelection: IRange | ISelection, context: languages.CodeActionContext, token: CancellationToken): Promise; $resolveCodeAction(handle: number, id: ChainedCacheId, token: CancellationToken): Promise; $releaseCodeActions(handle: number, cacheId: number): void; - $prepareDocumentPaste(handle: number, uri: UriComponents, range: IRange, dataTransfer: DataTransferDTO, token: CancellationToken): Promise; - $providePasteEdits(handle: number, uri: UriComponents, range: IRange, dataTransfer: DataTransferDTO, token: CancellationToken): Promise | undefined>; + $prepareDocumentPaste(handle: number, uri: UriComponents, ranges: IRange[], dataTransfer: DataTransferDTO, token: CancellationToken): Promise; + $providePasteEdits(handle: number, uri: UriComponents, ranges: IRange[], dataTransfer: DataTransferDTO, token: CancellationToken): Promise; $provideDocumentFormattingEdits(handle: number, resource: UriComponents, options: languages.FormattingOptions, token: CancellationToken): Promise; $provideDocumentRangeFormattingEdits(handle: number, resource: UriComponents, range: IRange, options: languages.FormattingOptions, token: CancellationToken): Promise; $provideOnTypeFormattingEdits(handle: number, resource: UriComponents, position: IPosition, ch: string, options: languages.FormattingOptions, token: CancellationToken): Promise; diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index 4f06e60d82c..ead5bc1aa2f 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -7,7 +7,7 @@ import { URI, UriComponents } from 'vs/base/common/uri'; import { mixin } from 'vs/base/common/objects'; import type * as vscode from 'vscode'; import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; -import { Range, Disposable, CompletionList, SnippetString, CodeActionKind, SymbolInformation, DocumentSymbol, SemanticTokensEdits, SemanticTokens, SemanticTokensEdit, Location, InlineCompletionTriggerKindNew, InlineCompletionTriggerKind, WorkspaceEdit } from 'vs/workbench/api/common/extHostTypes'; +import { Range, Disposable, CompletionList, SnippetString, CodeActionKind, SymbolInformation, DocumentSymbol, SemanticTokensEdits, SemanticTokens, SemanticTokensEdit, Location, InlineCompletionTriggerKindNew, InlineCompletionTriggerKind } from 'vs/workbench/api/common/extHostTypes'; import { ISingleEditOperation } from 'vs/editor/common/core/editOperation'; import * as languages from 'vs/editor/common/languages'; import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; @@ -494,40 +494,39 @@ class DocumentPasteEditProvider { private readonly _handle: number, ) { } - async prepareDocumentPaste(resource: URI, range: IRange, dataTransferDto: extHostProtocol.DataTransferDTO, token: CancellationToken): Promise { + async prepareDocumentPaste(resource: URI, ranges: IRange[], dataTransferDto: extHostProtocol.DataTransferDTO, token: CancellationToken): Promise { if (!this._provider.prepareDocumentPaste) { return undefined; } const doc = this._documents.getDocument(resource); - const vscodeRange = typeConvert.Range.to(range); + const vscodeRanges = ranges.map(range => typeConvert.Range.to(range)); const dataTransfer = typeConvert.DataTransfer.toDataTransfer(dataTransferDto, () => { throw new NotImplementedError(); }); - await this._provider.prepareDocumentPaste(doc, vscodeRange, dataTransfer, token); + await this._provider.prepareDocumentPaste(doc, vscodeRanges, dataTransfer, token); return typeConvert.DataTransfer.toDataTransferDTO(dataTransfer); } - async providePasteEdits(requestId: number, resource: URI, range: IRange, dataTransferDto: extHostProtocol.DataTransferDTO, token: CancellationToken): Promise> { + async providePasteEdits(requestId: number, resource: URI, ranges: IRange[], dataTransferDto: extHostProtocol.DataTransferDTO, token: CancellationToken): Promise { const doc = this._documents.getDocument(resource); - const vscodeRange = typeConvert.Range.to(range); + const vscodeRanges = ranges.map(range => typeConvert.Range.to(range)); const dataTransfer = typeConvert.DataTransfer.toDataTransfer(dataTransferDto, async (index) => { return (await this._proxy.$resolveDocumentOnDropFileData(this._handle, requestId, index)).buffer; }); - const edit = await this._provider.provideDocumentPasteEdits(doc, vscodeRange, dataTransfer, token); + const edit = await this._provider.provideDocumentPasteEdits(doc, vscodeRanges, dataTransfer, token); if (!edit) { return; } - if (edit instanceof WorkspaceEdit) { - return typeConvert.WorkspaceEdit.from(edit); - } else { - return typeConvert.SnippetTextEdit.from(edit as vscode.SnippetTextEdit); - } + return { + insertSnippet: typeof edit.insertText === 'string' ? edit.insertText : edit.insertText.value, + additionalEdit: edit.additionalEdit ? typeConvert.WorkspaceEdit.from(edit.additionalEdit) : undefined, + }; } } @@ -2461,19 +2460,19 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF // --- copy/paste actions - registerDocumentPasteEditProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.DocumentPasteEditProvider): vscode.Disposable { + 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._proxy.$registerPasteEditProvider(handle, this._transformDocumentSelector(selector), !!provider.prepareDocumentPaste); + this._proxy.$registerPasteEditProvider(handle, this._transformDocumentSelector(selector), !!provider.prepareDocumentPaste, metadata.pasteMimeTypes); return this._createDisposable(handle); } - $prepareDocumentPaste(handle: number, resource: UriComponents, range: IRange, dataTransfer: extHostProtocol.DataTransferDTO, token: CancellationToken): Promise { - return this._withAdapter(handle, DocumentPasteEditProvider, adapter => adapter.prepareDocumentPaste(URI.revive(resource), range, dataTransfer, token), undefined, token); + $prepareDocumentPaste(handle: number, resource: UriComponents, ranges: IRange[], dataTransfer: extHostProtocol.DataTransferDTO, token: CancellationToken): Promise { + return this._withAdapter(handle, DocumentPasteEditProvider, adapter => adapter.prepareDocumentPaste(URI.revive(resource), ranges, dataTransfer, token), undefined, token); } - $providePasteEdits(handle: number, resource: UriComponents, range: IRange, dataTransferDto: extHostProtocol.DataTransferDTO, token: CancellationToken): Promise | undefined> { - return this._withAdapter(handle, DocumentPasteEditProvider, adapter => adapter.providePasteEdits(0, URI.revive(resource), range, dataTransferDto, token), undefined, token); + $providePasteEdits(handle: number, resource: UriComponents, ranges: IRange[], dataTransferDto: extHostProtocol.DataTransferDTO, token: CancellationToken): Promise { + return this._withAdapter(handle, DocumentPasteEditProvider, adapter => adapter.providePasteEdits(0, URI.revive(resource), ranges, dataTransferDto, token), undefined, token); } // --- configuration diff --git a/src/vscode-dts/vscode.proposed.documentPaste.d.ts b/src/vscode-dts/vscode.proposed.documentPaste.d.ts index 1777c8d4145..29335e1df21 100644 --- a/src/vscode-dts/vscode.proposed.documentPaste.d.ts +++ b/src/vscode-dts/vscode.proposed.documentPaste.d.ts @@ -19,11 +19,11 @@ declare module 'vscode' { * a {@link DataTransfer} and is passed back to the provider in {@link provideDocumentPasteEdits}. * * @param document Document where the copy took place. - * @param range Range being copied in the `document`. + * @param ranges Ranges being copied in the `document`. * @param dataTransfer The data transfer associated with the copy. You can store additional values on this for later use in {@link provideDocumentPasteEdits}. * @param token A cancellation token. */ - prepareDocumentPaste?(document: TextDocument, range: Range, dataTransfer: DataTransfer, token: CancellationToken): void | Thenable; + prepareDocumentPaste?(document: TextDocument, ranges: readonly Range[], dataTransfer: DataTransfer, token: CancellationToken): void | Thenable; /** * Invoked before the user pastes into a document. @@ -31,16 +31,40 @@ declare module 'vscode' { * In this method, extensions can return a workspace edit that replaces the standard pasting behavior. * * @param document Document being pasted into - * @param range Currently selected range in the document. + * @param ranges Currently selected ranges in the document. * @param dataTransfer The data transfer associated with the paste. * @param token A cancellation token. * * @return Optional workspace edit that applies the paste. Return undefined to use standard pasting. */ - provideDocumentPasteEdits(document: TextDocument, range: Range, dataTransfer: DataTransfer, token: CancellationToken): ProviderResult; + provideDocumentPasteEdits(document: TextDocument, ranges: readonly Range[], dataTransfer: DataTransfer, token: CancellationToken): ProviderResult; + } + + /** + * An operation applied on paste + */ + interface DocumentPasteEdit { + /** + * The text or snippet to insert at the pasted locations. + */ + readonly insertText: string | SnippetString; + + /** + * An optional additional edit to apply on paste. + */ + readonly additionalEdit?: WorkspaceEdit; + } + + interface DocumentPasteProviderMetadata { + /** + * Mime types that `provideDocumentPasteEdits` should be invoked for. + * + * Use the special `files` mimetype to indicate the provider should be invoked if any files are present in the `DataTransfer`. + */ + readonly pasteMimeTypes: readonly string[]; } namespace languages { - export function registerDocumentPasteEditProvider(selector: DocumentSelector, provider: DocumentPasteEditProvider): Disposable; + export function registerDocumentPasteEditProvider(selector: DocumentSelector, provider: DocumentPasteEditProvider, metadata: DocumentPasteProviderMetadata): Disposable; } }