Path: blob/main/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteController.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { addDisposableListener } from '../../../../base/browser/dom.js';6import { IAction } from '../../../../base/common/actions.js';7import { coalesce } from '../../../../base/common/arrays.js';8import { CancelablePromise, createCancelablePromise, DeferredPromise, raceCancellation } from '../../../../base/common/async.js';9import { CancellationToken, CancellationTokenSource } from '../../../../base/common/cancellation.js';10import { createStringDataTransferItem, IReadonlyVSDataTransfer, matchesMimeType, UriList, VSDataTransfer } from '../../../../base/common/dataTransfer.js';11import { isCancellationError } from '../../../../base/common/errors.js';12import { HierarchicalKind } from '../../../../base/common/hierarchicalKind.js';13import { Disposable, DisposableStore } from '../../../../base/common/lifecycle.js';14import { Mimes } from '../../../../base/common/mime.js';15import * as platform from '../../../../base/common/platform.js';16import { upcast } from '../../../../base/common/types.js';17import { generateUuid } from '../../../../base/common/uuid.js';18import { localize } from '../../../../nls.js';19import { IClipboardService } from '../../../../platform/clipboard/common/clipboardService.js';20import { ICommandService } from '../../../../platform/commands/common/commands.js';21import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';22import { RawContextKey } from '../../../../platform/contextkey/common/contextkey.js';23import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';24import { ILogService } from '../../../../platform/log/common/log.js';25import { IProgressService, ProgressLocation } from '../../../../platform/progress/common/progress.js';26import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from '../../../../platform/quickinput/common/quickInput.js';27import { ClipboardEventUtils, InMemoryClipboardMetadataManager } from '../../../browser/controller/editContext/clipboardUtils.js';28import { toExternalVSDataTransfer, toVSDataTransfer } from '../../../browser/dataTransfer.js';29import { ICodeEditor, PastePayload } from '../../../browser/editorBrowser.js';30import { IBulkEditService } from '../../../browser/services/bulkEditService.js';31import { EditorOption } from '../../../common/config/editorOptions.js';32import { IRange, Range } from '../../../common/core/range.js';33import { Selection } from '../../../common/core/selection.js';34import { Handler, IEditorContribution } from '../../../common/editorCommon.js';35import { DocumentPasteContext, DocumentPasteEdit, DocumentPasteEditProvider, DocumentPasteTriggerKind } from '../../../common/languages.js';36import { ITextModel } from '../../../common/model.js';37import { ILanguageFeaturesService } from '../../../common/services/languageFeatures.js';38import { CodeEditorStateFlag, EditorStateCancellationTokenSource } from '../../editorState/browser/editorState.js';39import { InlineProgressManager } from '../../inlineProgress/browser/inlineProgress.js';40import { MessageController } from '../../message/browser/messageController.js';41import { PreferredPasteConfiguration } from './copyPasteContribution.js';42import { DefaultTextPasteOrDropEditProvider } from './defaultProviders.js';43import { createCombinedWorkspaceEdit, sortEditsByYieldTo } from './edit.js';44import { PostEditWidgetManager } from './postEditWidget.js';4546export const changePasteTypeCommandId = 'editor.changePasteType';4748export const pasteAsPreferenceConfig = 'editor.pasteAs.preferences';4950export const pasteWidgetVisibleCtx = new RawContextKey<boolean>('pasteWidgetVisible', false, localize('pasteWidgetVisible', "Whether the paste widget is showing"));5152const vscodeClipboardMime = 'application/vnd.code.copymetadata';5354interface CopyMetadata {55readonly id?: string;56readonly providerCopyMimeTypes?: readonly string[];5758readonly defaultPastePayload: Omit<PastePayload, 'text'>;59}6061type PasteEditWithProvider = DocumentPasteEdit & {62provider: DocumentPasteEditProvider;63};646566interface DocumentPasteWithProviderEditsSession {67edits: readonly PasteEditWithProvider[];68dispose(): void;69}7071export type PastePreference =72| { readonly only: HierarchicalKind }73| { readonly preferences: readonly HierarchicalKind[] }74| { readonly providerId: string } // Only used internally75;7677interface CopyOperation {78readonly providerMimeTypes: readonly string[];79readonly operation: CancelablePromise<IReadonlyVSDataTransfer | undefined>;80}8182export class CopyPasteController extends Disposable implements IEditorContribution {8384public static readonly ID = 'editor.contrib.copyPasteActionController';8586public static get(editor: ICodeEditor): CopyPasteController | null {87return editor.getContribution<CopyPasteController>(CopyPasteController.ID);88}8990public static setConfigureDefaultAction(action: IAction) {91CopyPasteController._configureDefaultAction = action;92}9394private static _configureDefaultAction?: IAction;9596/**97* Global tracking the last copy operation.98*99* This is shared across all editors so that you can copy and paste between groups.100*101* TODO: figure out how to make this work with multiple windows102*/103private static _currentCopyOperation?: {104readonly handle: string;105readonly operations: ReadonlyArray<CopyOperation>;106};107108private readonly _editor: ICodeEditor;109110private _currentPasteOperation?: CancelablePromise<void>;111private _pasteAsActionContext?: { readonly preferred?: PastePreference };112113private readonly _pasteProgressManager: InlineProgressManager;114private readonly _postPasteWidgetManager: PostEditWidgetManager<PasteEditWithProvider>;115116constructor(117editor: ICodeEditor,118@IInstantiationService instantiationService: IInstantiationService,119@ILogService private readonly _logService: ILogService,120@IBulkEditService private readonly _bulkEditService: IBulkEditService,121@IClipboardService private readonly _clipboardService: IClipboardService,122@ICommandService private readonly _commandService: ICommandService,123@IConfigurationService private readonly _configService: IConfigurationService,124@ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService,125@IQuickInputService private readonly _quickInputService: IQuickInputService,126@IProgressService private readonly _progressService: IProgressService,127) {128super();129130this._editor = editor;131132const container = editor.getContainerDomNode();133this._register(addDisposableListener(container, 'copy', e => this.handleCopy(e)));134this._register(addDisposableListener(container, 'cut', e => this.handleCopy(e)));135this._register(addDisposableListener(container, 'paste', e => this.handlePaste(e), true));136137this._pasteProgressManager = this._register(new InlineProgressManager('pasteIntoEditor', editor, instantiationService));138139this._postPasteWidgetManager = this._register(instantiationService.createInstance(PostEditWidgetManager, 'pasteIntoEditor', editor, pasteWidgetVisibleCtx,140{ id: changePasteTypeCommandId, label: localize('postPasteWidgetTitle', "Show paste options...") },141() => CopyPasteController._configureDefaultAction ? [CopyPasteController._configureDefaultAction] : []142));143}144145public changePasteType() {146this._postPasteWidgetManager.tryShowSelector();147}148149public async pasteAs(preferred?: PastePreference) {150this._logService.trace('CopyPasteController.pasteAs');151this._editor.focus();152try {153this._logService.trace('Before calling editor.action.clipboardPasteAction');154this._pasteAsActionContext = { preferred };155await this._commandService.executeCommand('editor.action.clipboardPasteAction');156} finally {157this._pasteAsActionContext = undefined;158}159}160161public clearWidgets() {162this._postPasteWidgetManager.clear();163}164165private isPasteAsEnabled(): boolean {166return this._editor.getOption(EditorOption.pasteAs).enabled;167}168169public async finishedPaste(): Promise<void> {170await this._currentPasteOperation;171}172173private handleCopy(e: ClipboardEvent) {174let id: string | null = null;175if (e.clipboardData) {176const [text, metadata] = ClipboardEventUtils.getTextData(e.clipboardData);177const storedMetadata = metadata || InMemoryClipboardMetadataManager.INSTANCE.get(text);178id = storedMetadata?.id || null;179this._logService.trace('CopyPasteController#handleCopy for id : ', id, ' with text.length : ', text.length);180} else {181this._logService.trace('CopyPasteController#handleCopy');182}183if (!this._editor.hasTextFocus()) {184return;185}186187// Explicitly clear the clipboard internal state.188// This is needed because on web, the browser clipboard is faked out using an in-memory store.189// This means the resources clipboard is not properly updated when copying from the editor.190this._clipboardService.clearInternalState?.();191192if (!e.clipboardData || !this.isPasteAsEnabled()) {193return;194}195196const model = this._editor.getModel();197const selections = this._editor.getSelections();198if (!model || !selections?.length) {199return;200}201202const enableEmptySelectionClipboard = this._editor.getOption(EditorOption.emptySelectionClipboard);203204let ranges: readonly IRange[] = selections;205const wasFromEmptySelection = selections.length === 1 && selections[0].isEmpty();206if (wasFromEmptySelection) {207if (!enableEmptySelectionClipboard) {208return;209}210211ranges = [new Range(ranges[0].startLineNumber, 1, ranges[0].startLineNumber, 1 + model.getLineLength(ranges[0].startLineNumber))];212}213214const toCopy = this._editor._getViewModel()?.getPlainTextToCopy(selections, enableEmptySelectionClipboard, platform.isWindows);215const multicursorText = Array.isArray(toCopy) ? toCopy : null;216217const defaultPastePayload = {218multicursorText,219pasteOnNewLine: wasFromEmptySelection,220mode: null221};222223const providers = this._languageFeaturesService.documentPasteEditProvider224.ordered(model)225.filter(x => !!x.prepareDocumentPaste);226if (!providers.length) {227this.setCopyMetadata(e.clipboardData, { defaultPastePayload });228return;229}230231const dataTransfer = toVSDataTransfer(e.clipboardData);232const providerCopyMimeTypes = providers.flatMap(x => x.copyMimeTypes ?? []);233234// Save off a handle pointing to data that VS Code maintains.235const handle = id ?? generateUuid();236this.setCopyMetadata(e.clipboardData, {237id: handle,238providerCopyMimeTypes,239defaultPastePayload240});241242const operations = providers.map((provider): CopyOperation => {243return {244providerMimeTypes: provider.copyMimeTypes,245operation: createCancelablePromise(token =>246provider.prepareDocumentPaste!(model, ranges, dataTransfer, token)247.catch(err => {248console.error(err);249return undefined;250}))251};252});253254CopyPasteController._currentCopyOperation?.operations.forEach(entry => entry.operation.cancel());255CopyPasteController._currentCopyOperation = { handle, operations };256}257258private async handlePaste(e: ClipboardEvent) {259if (e.clipboardData) {260const [text, metadata] = ClipboardEventUtils.getTextData(e.clipboardData);261const metadataComputed = metadata || InMemoryClipboardMetadataManager.INSTANCE.get(text);262this._logService.trace('CopyPasteController#handlePaste for id : ', metadataComputed?.id);263} else {264this._logService.trace('CopyPasteController#handlePaste');265}266if (!e.clipboardData || !this._editor.hasTextFocus()) {267return;268}269270MessageController.get(this._editor)?.closeMessage();271this._currentPasteOperation?.cancel();272this._currentPasteOperation = undefined;273274const model = this._editor.getModel();275const selections = this._editor.getSelections();276if (!selections?.length || !model) {277return;278}279280if (281this._editor.getOption(EditorOption.readOnly) // Never enabled if editor is readonly.282|| (!this.isPasteAsEnabled() && !this._pasteAsActionContext) // Or feature disabled (but still enable if paste was explicitly requested)283) {284return;285}286287const metadata = this.fetchCopyMetadata(e);288this._logService.trace('CopyPasteController#handlePaste with metadata : ', metadata?.id, ' and text.length : ', e.clipboardData.getData('text/plain').length);289const dataTransfer = toExternalVSDataTransfer(e.clipboardData);290dataTransfer.delete(vscodeClipboardMime);291292const fileTypes = Array.from(e.clipboardData.files).map(file => file.type);293294const allPotentialMimeTypes = [295...e.clipboardData.types,296...fileTypes,297...metadata?.providerCopyMimeTypes ?? [],298// TODO: always adds `uri-list` because this get set if there are resources in the system clipboard.299// However we can only check the system clipboard async. For this early check, just add it in.300// We filter providers again once we have the final dataTransfer we will use.301Mimes.uriList,302];303304const allProviders = this._languageFeaturesService.documentPasteEditProvider305.ordered(model)306.filter(provider => {307// Filter out providers that don't match the requested paste types308const preference = this._pasteAsActionContext?.preferred;309if (preference) {310if (!this.providerMatchesPreference(provider, preference)) {311return false;312}313}314315// And providers that don't handle any of mime types in the clipboard316return provider.pasteMimeTypes?.some(type => matchesMimeType(type, allPotentialMimeTypes));317});318if (!allProviders.length) {319if (this._pasteAsActionContext?.preferred) {320this.showPasteAsNoEditMessage(selections, this._pasteAsActionContext.preferred);321322// Also prevent default paste from applying323e.preventDefault();324e.stopImmediatePropagation();325}326return;327}328329// Prevent the editor's default paste handler from running.330// Note that after this point, we are fully responsible for handling paste.331// If we can't provider a paste for any reason, we need to explicitly delegate pasting back to the editor.332e.preventDefault();333e.stopImmediatePropagation();334335if (this._pasteAsActionContext) {336this.showPasteAsPick(this._pasteAsActionContext.preferred, allProviders, selections, dataTransfer, metadata);337} else {338this.doPasteInline(allProviders, selections, dataTransfer, metadata, e);339}340}341342private showPasteAsNoEditMessage(selections: readonly Selection[], preference: PastePreference) {343const kindLabel = 'only' in preference344? preference.only.value345: 'preferences' in preference346? (preference.preferences.length ? preference.preferences.map(preference => preference.value).join(', ') : localize('noPreferences', "empty"))347: preference.providerId;348349MessageController.get(this._editor)?.showMessage(localize('pasteAsError', "No paste edits for '{0}' found", kindLabel), selections[0].getStartPosition());350}351352private doPasteInline(allProviders: readonly DocumentPasteEditProvider[], selections: readonly Selection[], dataTransfer: VSDataTransfer, metadata: CopyMetadata | undefined, clipboardEvent: ClipboardEvent): void {353this._logService.trace('CopyPasteController#doPasteInline');354const editor = this._editor;355if (!editor.hasModel()) {356return;357}358359const editorStateCts = new EditorStateCancellationTokenSource(editor, CodeEditorStateFlag.Value | CodeEditorStateFlag.Selection, undefined);360361const p = createCancelablePromise(async (pToken) => {362const editor = this._editor;363if (!editor.hasModel()) {364return;365}366const model = editor.getModel();367368const disposables = new DisposableStore();369const cts = disposables.add(new CancellationTokenSource(pToken));370disposables.add(editorStateCts.token.onCancellationRequested(() => cts.cancel()));371372const token = cts.token;373try {374await this.mergeInDataFromCopy(allProviders, dataTransfer, metadata, token);375if (token.isCancellationRequested) {376return;377}378379const supportedProviders = allProviders.filter(provider => this.isSupportedPasteProvider(provider, dataTransfer));380if (!supportedProviders.length381|| (supportedProviders.length === 1 && supportedProviders[0] instanceof DefaultTextPasteOrDropEditProvider) // Only our default text provider is active382) {383return this.applyDefaultPasteHandler(dataTransfer, metadata, token, clipboardEvent);384}385386const context: DocumentPasteContext = {387triggerKind: DocumentPasteTriggerKind.Automatic,388};389390const editSession = await this.getPasteEdits(supportedProviders, dataTransfer, model, selections, context, token);391disposables.add(editSession);392if (token.isCancellationRequested) {393return;394}395396// If the only edit returned is our default text edit, use the default paste handler397if (editSession.edits.length === 1 && editSession.edits[0].provider instanceof DefaultTextPasteOrDropEditProvider) {398return this.applyDefaultPasteHandler(dataTransfer, metadata, token, clipboardEvent);399}400401if (editSession.edits.length) {402const canShowWidget = editor.getOption(EditorOption.pasteAs).showPasteSelector === 'afterPaste';403return this._postPasteWidgetManager.applyEditAndShowIfNeeded(selections, { activeEditIndex: this.getInitialActiveEditIndex(model, editSession.edits), allEdits: editSession.edits }, canShowWidget, async (edit, resolveToken) => {404if (!edit.provider.resolveDocumentPasteEdit) {405return edit;406}407408const resolveP = edit.provider.resolveDocumentPasteEdit(edit, resolveToken);409const showP = new DeferredPromise<void>();410const resolved = await this._pasteProgressManager.showWhile(selections[0].getEndPosition(), localize('resolveProcess', "Resolving paste edit for '{0}'. Click to cancel", edit.title), raceCancellation(Promise.race([showP.p, resolveP]), resolveToken), {411cancel: () => showP.cancel()412}, 0);413414if (resolved) {415edit.insertText = resolved.insertText;416edit.additionalEdit = resolved.additionalEdit;417}418return edit;419}, token);420}421422await this.applyDefaultPasteHandler(dataTransfer, metadata, token, clipboardEvent);423} finally {424disposables.dispose();425if (this._currentPasteOperation === p) {426this._currentPasteOperation = undefined;427}428}429});430431this._pasteProgressManager.showWhile(selections[0].getEndPosition(), localize('pasteIntoEditorProgress', "Running paste handlers. Click to cancel and do basic paste"), p, {432cancel: async () => {433p.cancel();434if (editorStateCts.token.isCancellationRequested) {435return;436}437438await this.applyDefaultPasteHandler(dataTransfer, metadata, editorStateCts.token, clipboardEvent);439}440}).finally(() => {441editorStateCts.dispose();442});443this._currentPasteOperation = p;444}445446private showPasteAsPick(preference: PastePreference | undefined, allProviders: readonly DocumentPasteEditProvider[], selections: readonly Selection[], dataTransfer: VSDataTransfer, metadata: CopyMetadata | undefined): void {447this._logService.trace('CopyPasteController#showPasteAsPick');448const p = createCancelablePromise(async (token) => {449const editor = this._editor;450if (!editor.hasModel()) {451return;452}453const model = editor.getModel();454455const disposables = new DisposableStore();456const tokenSource = disposables.add(new EditorStateCancellationTokenSource(editor, CodeEditorStateFlag.Value | CodeEditorStateFlag.Selection, undefined, token));457try {458await this.mergeInDataFromCopy(allProviders, dataTransfer, metadata, tokenSource.token);459if (tokenSource.token.isCancellationRequested) {460return;461}462463// Filter out any providers the don't match the full data transfer we will send them.464let supportedProviders = allProviders.filter(provider => this.isSupportedPasteProvider(provider, dataTransfer, preference));465if (preference) {466// We are looking for a specific edit467supportedProviders = supportedProviders.filter(provider => this.providerMatchesPreference(provider, preference));468}469470const context: DocumentPasteContext = {471triggerKind: DocumentPasteTriggerKind.PasteAs,472only: preference && 'only' in preference ? preference.only : undefined,473};474let editSession = disposables.add(await this.getPasteEdits(supportedProviders, dataTransfer, model, selections, context, tokenSource.token));475if (tokenSource.token.isCancellationRequested) {476return;477}478479// Filter out any edits that don't match the requested kind480if (preference) {481editSession = {482edits: editSession.edits.filter(edit => {483if ('only' in preference) {484return preference.only.contains(edit.kind);485} else if ('preferences' in preference) {486return preference.preferences.some(preference => preference.contains(edit.kind));487} else {488return preference.providerId === edit.provider.id;489}490}),491dispose: editSession.dispose492};493}494495if (!editSession.edits.length) {496if (preference) {497this.showPasteAsNoEditMessage(selections, preference);498}499return;500}501502let pickedEdit: DocumentPasteEdit | undefined;503if (preference) {504pickedEdit = editSession.edits.at(0);505} else {506type ItemWithEdit = IQuickPickItem & { edit?: DocumentPasteEdit };507const configureDefaultItem: ItemWithEdit = {508id: 'editor.pasteAs.default',509label: localize('pasteAsDefault', "Configure default paste action"),510edit: undefined,511};512513const selected = await this._quickInputService.pick<ItemWithEdit>(514[515...editSession.edits.map((edit): ItemWithEdit => ({516label: edit.title,517description: edit.kind?.value,518edit,519})),520...(CopyPasteController._configureDefaultAction ? [521upcast<IQuickPickSeparator>({ type: 'separator' }),522{523label: CopyPasteController._configureDefaultAction.label,524edit: undefined,525}526] : [])527], {528placeHolder: localize('pasteAsPickerPlaceholder', "Select Paste Action"),529});530531if (selected === configureDefaultItem) {532CopyPasteController._configureDefaultAction?.run();533return;534}535536pickedEdit = selected?.edit;537}538539if (!pickedEdit) {540return;541}542543const combinedWorkspaceEdit = createCombinedWorkspaceEdit(model.uri, selections, pickedEdit);544await this._bulkEditService.apply(combinedWorkspaceEdit, { editor: this._editor });545} finally {546disposables.dispose();547if (this._currentPasteOperation === p) {548this._currentPasteOperation = undefined;549}550}551});552553this._progressService.withProgress({554location: ProgressLocation.Window,555title: localize('pasteAsProgress', "Running paste handlers"),556}, () => p);557}558559private setCopyMetadata(dataTransfer: DataTransfer, metadata: CopyMetadata) {560this._logService.trace('CopyPasteController#setCopyMetadata new id : ', metadata.id);561dataTransfer.setData(vscodeClipboardMime, JSON.stringify(metadata));562}563564private fetchCopyMetadata(e: ClipboardEvent): CopyMetadata | undefined {565this._logService.trace('CopyPasteController#fetchCopyMetadata');566if (!e.clipboardData) {567return;568}569570// Prefer using the clipboard data we saved off571const rawMetadata = e.clipboardData.getData(vscodeClipboardMime);572if (rawMetadata) {573try {574return JSON.parse(rawMetadata);575} catch {576return undefined;577}578}579580// Otherwise try to extract the generic text editor metadata581const [_, metadata] = ClipboardEventUtils.getTextData(e.clipboardData);582if (metadata) {583return {584defaultPastePayload: {585mode: metadata.mode,586multicursorText: metadata.multicursorText ?? null,587pasteOnNewLine: !!metadata.isFromEmptySelection,588},589};590}591592return undefined;593}594595private async mergeInDataFromCopy(allProviders: readonly DocumentPasteEditProvider[], dataTransfer: VSDataTransfer, metadata: CopyMetadata | undefined, token: CancellationToken): Promise<void> {596this._logService.trace('CopyPasteController#mergeInDataFromCopy with metadata : ', metadata?.id);597if (metadata?.id && CopyPasteController._currentCopyOperation?.handle === metadata.id) {598// Only resolve providers that have data we may care about599const toResolve = CopyPasteController._currentCopyOperation.operations600.filter(op => allProviders.some(provider => provider.pasteMimeTypes.some(type => matchesMimeType(type, op.providerMimeTypes))))601.map(op => op.operation);602603const toMergeResults = await Promise.all(toResolve);604if (token.isCancellationRequested) {605return;606}607608// Values from higher priority providers should overwrite values from lower priority ones.609// Reverse the array to so that the calls to `DataTransfer.replace` later will do this610for (const toMergeData of toMergeResults.reverse()) {611if (toMergeData) {612for (const [key, value] of toMergeData) {613dataTransfer.replace(key, value);614}615}616}617}618619if (!dataTransfer.has(Mimes.uriList)) {620const resources = await this._clipboardService.readResources();621if (token.isCancellationRequested) {622return;623}624625if (resources.length) {626dataTransfer.append(Mimes.uriList, createStringDataTransferItem(UriList.create(resources)));627}628}629}630631private async getPasteEdits(providers: readonly DocumentPasteEditProvider[], dataTransfer: VSDataTransfer, model: ITextModel, selections: readonly Selection[], context: DocumentPasteContext, token: CancellationToken): Promise<DocumentPasteWithProviderEditsSession> {632const disposables = new DisposableStore();633634const results = await raceCancellation(635Promise.all(providers.map(async provider => {636try {637const edits = await provider.provideDocumentPasteEdits?.(model, selections, dataTransfer, context, token);638if (edits) {639disposables.add(edits);640}641return edits?.edits?.map(edit => ({ ...edit, provider }));642} catch (err) {643if (!isCancellationError(err)) {644console.error(err);645}646return undefined;647}648})),649token);650const edits = coalesce(results ?? []).flat().filter(edit => {651return !context.only || context.only.contains(edit.kind);652});653return {654edits: sortEditsByYieldTo(edits),655dispose: () => disposables.dispose()656};657}658659private async applyDefaultPasteHandler(dataTransfer: VSDataTransfer, metadata: CopyMetadata | undefined, token: CancellationToken, clipboardEvent: ClipboardEvent) {660const textDataTransfer = dataTransfer.get(Mimes.text) ?? dataTransfer.get('text');661const text = (await textDataTransfer?.asString()) ?? '';662if (token.isCancellationRequested) {663return;664}665666const payload: PastePayload = {667clipboardEvent,668text,669pasteOnNewLine: metadata?.defaultPastePayload.pasteOnNewLine ?? false,670multicursorText: metadata?.defaultPastePayload.multicursorText ?? null,671mode: null,672};673this._logService.trace('CopyPasteController#applyDefaultPasteHandler for id : ', metadata?.id);674this._editor.trigger('keyboard', Handler.Paste, payload);675}676677/**678* Filter out providers if they:679* - Don't handle any of the data transfer types we have680* - Don't match the preferred paste kind681*/682private isSupportedPasteProvider(provider: DocumentPasteEditProvider, dataTransfer: VSDataTransfer, preference?: PastePreference): boolean {683if (!provider.pasteMimeTypes?.some(type => dataTransfer.matches(type))) {684return false;685}686687return !preference || this.providerMatchesPreference(provider, preference);688}689690private providerMatchesPreference(provider: DocumentPasteEditProvider, preference: PastePreference): boolean {691if ('only' in preference) {692return provider.providedPasteEditKinds.some(providedKind => preference.only.contains(providedKind));693} else if ('preferences' in preference) {694return preference.preferences.some(providedKind => preference.preferences.some(preferredKind => preferredKind.contains(providedKind)));695} else {696return provider.id === preference.providerId;697}698}699700private getInitialActiveEditIndex(model: ITextModel, edits: readonly DocumentPasteEdit[]): number {701const preferredProviders = this._configService.getValue<PreferredPasteConfiguration[]>(pasteAsPreferenceConfig, { resource: model.uri });702for (const config of Array.isArray(preferredProviders) ? preferredProviders : []) {703const desiredKind = new HierarchicalKind(config);704const editIndex = edits.findIndex(edit => desiredKind.contains(edit.kind));705if (editIndex >= 0) {706return editIndex;707}708}709710return 0;711}712}713714715