diff --git a/src/vs/editor/browser/controller/editContext/clipboardUtils.ts b/src/vs/editor/browser/controller/editContext/clipboardUtils.ts index 313966b6828..96afd175c85 100644 --- a/src/vs/editor/browser/controller/editContext/clipboardUtils.ts +++ b/src/vs/editor/browser/controller/editContext/clipboardUtils.ts @@ -10,6 +10,8 @@ import { ViewContext } from '../../../common/viewModel/viewContext.js'; import { ILogService, LogLevel } from '../../../../platform/log/common/log.js'; import { EditorOption, IComputedEditorOptions } from '../../../common/config/editorOptions.js'; import { generateUuid } from '../../../../base/common/uuid.js'; +import { VSDataTransfer } from '../../../../base/common/dataTransfer.js'; +import { toExternalVSDataTransfer } from '../../dataTransfer.js'; export function ensureClipboardGetsEditorSelection(e: ClipboardEvent, context: ViewContext, logService: ILogService, isFirefox: boolean): void { const viewModel = context.viewModel; @@ -139,7 +141,7 @@ interface InMemoryClipboardMetadata { export const ClipboardEventUtils = { - getTextData(clipboardData: DataTransfer): [string, ClipboardStoredMetadata | null] { + getTextData(clipboardData: IReadableClipboardData | DataTransfer): [string, ClipboardStoredMetadata | null] { const text = clipboardData.getData(Mimes.text); let metadata: ClipboardStoredMetadata | null = null; const rawmetadata = clipboardData.getData('vscode-editor-data'); @@ -161,7 +163,7 @@ export const ClipboardEventUtils = { return [text, metadata]; }, - setTextData(clipboardData: DataTransfer, text: string, html: string | null | undefined, metadata: ClipboardStoredMetadata): void { + setTextData(clipboardData: IWritableClipboardData, text: string, html: string | null | undefined, metadata: ClipboardStoredMetadata): void { clipboardData.setData(Mimes.text, text); if (typeof html === 'string') { clipboardData.setData('text/html', html); @@ -171,29 +173,13 @@ export const ClipboardEventUtils = { }; /** - * Abstracted clipboard data that does not directly expose DOM ClipboardEvent/DataTransfer. - * This allows editor contributions to work with clipboard data without DOM dependencies. + * Readable clipboard data for paste operations. */ -export interface IClipboardData { - /** - * The text content from the clipboard. - */ - readonly text: string; - - /** - * The HTML content from the clipboard, if available. - */ - readonly html: string | undefined; - - /** - * VS Code editor metadata associated with this clipboard data. - */ - readonly metadata: ClipboardStoredMetadata | null; - +export interface IReadableClipboardData { /** * All MIME types present in the clipboard. */ - readonly types: readonly string[]; + types: string[]; /** * Files from the clipboard (for paste operations). @@ -209,7 +195,7 @@ export interface IClipboardData { /** * Writable clipboard data for copy/cut operations. */ -export interface IWritableClipboardData extends IClipboardData { +export interface IWritableClipboardData { /** * Set data for a specific MIME type. */ @@ -248,7 +234,17 @@ export interface IClipboardPasteEvent { /** * The clipboard data being pasted. */ - readonly clipboardData: IClipboardData; + readonly clipboardData: IReadableClipboardData; + + /** + * The metadata stored alongside the clipboard data, if any. + */ + readonly metadata: ClipboardStoredMetadata | null; + + /** + * The text content being pasted. + */ + readonly text: string; /** * The underlying DOM event, if available. @@ -256,6 +252,8 @@ export interface IClipboardPasteEvent { */ readonly browserEvent: ClipboardEvent | undefined; + toExternalVSDataTransfer(): VSDataTransfer | undefined; + /** * Signal that the event has been handled and default processing should be skipped. */ @@ -267,35 +265,6 @@ export interface IClipboardPasteEvent { readonly isHandled: boolean; } -/** - * Creates an IClipboardData from a DOM DataTransfer. - */ -export function createClipboardData(dataTransfer: DataTransfer): IClipboardData { - const [text, metadata] = ClipboardEventUtils.getTextData(dataTransfer); - const html = dataTransfer.getData('text/html') || undefined; - const files: File[] = Array.prototype.slice.call(dataTransfer.files, 0); - - return { - text, - html, - metadata, - types: Array.from(dataTransfer.types), - files, - getData: (type: string) => dataTransfer.getData(type), - }; -} - -/** - * Creates an IWritableClipboardData from a DOM DataTransfer. - */ -export function createWritableClipboardData(dataTransfer: DataTransfer): IWritableClipboardData { - const base = createClipboardData(dataTransfer); - return { - ...base, - setData: (type: string, value: string) => dataTransfer.setData(type, value), - }; -} - /** * Creates an IClipboardCopyEvent from a DOM ClipboardEvent. */ @@ -303,14 +272,10 @@ export function createClipboardCopyEvent(e: ClipboardEvent, isCut: boolean): ICl let handled = false; return { isCut, - clipboardData: e.clipboardData ? createWritableClipboardData(e.clipboardData) : { - text: '', - html: undefined, - metadata: null, - types: [], - files: [], - getData: () => '', - setData: () => { }, + clipboardData: { + setData: (type: string, value: string) => { + e.clipboardData?.setData(type, value); + }, }, setHandled: () => { handled = true; @@ -326,15 +291,13 @@ export function createClipboardCopyEvent(e: ClipboardEvent, isCut: boolean): ICl */ export function createClipboardPasteEvent(e: ClipboardEvent): IClipboardPasteEvent { let handled = false; + let [text, metadata] = e.clipboardData ? ClipboardEventUtils.getTextData(e.clipboardData) : ['', null]; + metadata = metadata || InMemoryClipboardMetadataManager.INSTANCE.get(text); return { - clipboardData: e.clipboardData ? createClipboardData(e.clipboardData) : { - text: '', - html: undefined, - metadata: null, - types: [], - files: [], - getData: () => '', - }, + clipboardData: createReadableClipboardData(e.clipboardData), + metadata, + text, + toExternalVSDataTransfer: () => e.clipboardData ? toExternalVSDataTransfer(e.clipboardData) : undefined, browserEvent: e, setHandled: () => { handled = true; @@ -344,3 +307,17 @@ export function createClipboardPasteEvent(e: ClipboardEvent): IClipboardPasteEve get isHandled() { return handled; }, }; } + +export function createReadableClipboardData(dataTransfer: DataTransfer | undefined | null): IReadableClipboardData { + return { + types: Array.from(dataTransfer?.types ?? []), + files: Array.prototype.slice.call(dataTransfer?.files ?? [], 0), + getData: (type: string) => dataTransfer?.getData(type) ?? '', + }; +} + +export function createWritableClipboardData(dataTransfer: DataTransfer | undefined | null): IWritableClipboardData { + return { + setData: (type: string, value: string) => dataTransfer?.setData(type, value), + }; +} diff --git a/src/vs/editor/browser/controller/editContext/native/nativeEditContext.ts b/src/vs/editor/browser/controller/editContext/native/nativeEditContext.ts index 0ccadb7d8bd..f1c27d90805 100644 --- a/src/vs/editor/browser/controller/editContext/native/nativeEditContext.ts +++ b/src/vs/editor/browser/controller/editContext/native/nativeEditContext.ts @@ -16,7 +16,7 @@ import { ViewConfigurationChangedEvent, ViewCursorStateChangedEvent, ViewDecorat import { ViewContext } from '../../../../common/viewModel/viewContext.js'; import { RestrictedRenderingContext, RenderingContext, HorizontalPosition } from '../../../view/renderingContext.js'; import { ViewController } from '../../../view/viewController.js'; -import { ClipboardEventUtils, createClipboardCopyEvent, createClipboardPasteEvent, ensureClipboardGetsEditorSelection, InMemoryClipboardMetadataManager } from '../clipboardUtils.js'; +import { createClipboardCopyEvent, createClipboardPasteEvent, ensureClipboardGetsEditorSelection } from '../clipboardUtils.js'; import { AbstractEditContext } from '../editContext.js'; import { editContextAddDisposableListener, FocusTracker, ITypeData } from './nativeEditContextUtils.js'; import { ScreenReaderSupport } from './screenReaderSupport.js'; @@ -161,24 +161,22 @@ export class NativeEditContext extends AbstractEditContext { if (!e.clipboardData) { return; } - let [text, metadata] = ClipboardEventUtils.getTextData(e.clipboardData); - this.logService.trace('NativeEditContext#paste with id : ', metadata?.id, ' with text.length: ', text.length); - if (!text) { + this.logService.trace('NativeEditContext#paste with id : ', pasteEvent.metadata?.id, ' with text.length: ', pasteEvent.text.length); + if (!pasteEvent.text) { return; } - metadata = metadata || InMemoryClipboardMetadataManager.INSTANCE.get(text); let pasteOnNewLine = false; let multicursorText: string[] | null = null; let mode: string | null = null; - if (metadata) { + if (pasteEvent.metadata) { const options = this._context.configuration.options; const emptySelectionClipboard = options.get(EditorOption.emptySelectionClipboard); - pasteOnNewLine = emptySelectionClipboard && !!metadata.isFromEmptySelection; - multicursorText = typeof metadata.multicursorText !== 'undefined' ? metadata.multicursorText : null; - mode = metadata.mode; + pasteOnNewLine = emptySelectionClipboard && !!pasteEvent.metadata.isFromEmptySelection; + multicursorText = typeof pasteEvent.metadata.multicursorText !== 'undefined' ? pasteEvent.metadata.multicursorText : null; + mode = pasteEvent.metadata.mode; } this.logService.trace('NativeEditContext#paste (before viewController.paste)'); - this._viewController.paste(text, pasteOnNewLine, multicursorText, mode); + this._viewController.paste(pasteEvent.text, pasteOnNewLine, multicursorText, mode); })); // Edit context events diff --git a/src/vs/editor/browser/controller/editContext/textArea/textAreaEditContextInput.ts b/src/vs/editor/browser/controller/editContext/textArea/textAreaEditContextInput.ts index a3e1d8a5b3c..04bd4419162 100644 --- a/src/vs/editor/browser/controller/editContext/textArea/textAreaEditContextInput.ts +++ b/src/vs/editor/browser/controller/editContext/textArea/textAreaEditContextInput.ts @@ -18,7 +18,7 @@ import { Position } from '../../../../common/core/position.js'; import { Selection } from '../../../../common/core/selection.js'; import { IAccessibilityService } from '../../../../../platform/accessibility/common/accessibility.js'; import { ILogService } from '../../../../../platform/log/common/log.js'; -import { ClipboardEventUtils, ClipboardStoredMetadata, createClipboardCopyEvent, createClipboardPasteEvent, ensureClipboardGetsEditorSelection, IClipboardCopyEvent, IClipboardPasteEvent, InMemoryClipboardMetadataManager } from '../clipboardUtils.js'; +import { ClipboardStoredMetadata, createClipboardCopyEvent, createClipboardPasteEvent, ensureClipboardGetsEditorSelection, IClipboardCopyEvent, IClipboardPasteEvent, InMemoryClipboardMetadataManager } from '../clipboardUtils.js'; import { _debugComposition, ITextAreaWrapper, ITypeData, TextAreaState } from './textAreaEditContextState.js'; import { ViewContext } from '../../../../common/viewModel/viewContext.js'; @@ -420,23 +420,15 @@ export class TextAreaInput extends Disposable { e.preventDefault(); - if (!e.clipboardData) { + this._logService.trace(`TextAreaInput#onPaste with id : `, pasteEvent.metadata?.id, ' with text.length: ', pasteEvent.text.length); + if (!pasteEvent.text) { return; } - let [text, metadata] = ClipboardEventUtils.getTextData(e.clipboardData); - this._logService.trace(`TextAreaInput#onPaste with id : `, metadata?.id, ' with text.length: ', text.length); - if (!text) { - return; - } - - // try the in-memory store - metadata = metadata || InMemoryClipboardMetadataManager.INSTANCE.get(text); - this._logService.trace(`TextAreaInput#onPaste (before onPaste)`); this._onPaste.fire({ - text: text, - metadata: metadata + text: pasteEvent.text, + metadata: pasteEvent.metadata }); })); diff --git a/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteController.ts b/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteController.ts index f5c8fb37aee..d2ed77fb5a4 100644 --- a/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteController.ts +++ b/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteController.ts @@ -24,8 +24,7 @@ import { IInstantiationService } from '../../../../platform/instantiation/common import { ILogService } from '../../../../platform/log/common/log.js'; import { IProgressService, ProgressLocation } from '../../../../platform/progress/common/progress.js'; import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from '../../../../platform/quickinput/common/quickInput.js'; -import { ClipboardEventUtils, IClipboardCopyEvent, IClipboardPasteEvent, InMemoryClipboardMetadataManager, IWritableClipboardData } from '../../../browser/controller/editContext/clipboardUtils.js'; -import { toExternalVSDataTransfer } from '../../../browser/dataTransfer.js'; +import { IClipboardCopyEvent, IClipboardPasteEvent, IWritableClipboardData } from '../../../browser/controller/editContext/clipboardUtils.js'; import { ICodeEditor, PastePayload } from '../../../browser/editorBrowser.js'; import { IBulkEditService } from '../../../browser/services/bulkEditService.js'; import { EditorOption } from '../../../common/config/editorOptions.js'; @@ -247,18 +246,18 @@ export class CopyPasteController extends Disposable implements IEditorContributi } private async handlePaste(e: IClipboardPasteEvent) { - const clipboardData = e.browserEvent?.clipboardData; - if (clipboardData) { - const [text, metadata] = ClipboardEventUtils.getTextData(clipboardData); - const metadataComputed = metadata || InMemoryClipboardMetadataManager.INSTANCE.get(text); - this._logService.trace('CopyPasteController#handlePaste for id : ', metadataComputed?.id); - } else { - this._logService.trace('CopyPasteController#handlePaste'); - } - if (!clipboardData || !this._editor.hasTextFocus()) { + this._logService.trace('CopyPasteController#handlePaste for id : ', e.metadata?.id); + + if (!this._editor.hasTextFocus()) { return; } + const dataTransfer = e.toExternalVSDataTransfer(); + if (!dataTransfer) { + return; + } + dataTransfer.delete(vscodeClipboardMime); + MessageController.get(this._editor)?.closeMessage(); this._currentPasteOperation?.cancel(); this._currentPasteOperation = undefined; @@ -276,15 +275,13 @@ export class CopyPasteController extends Disposable implements IEditorContributi return; } - const metadata = this.fetchCopyMetadata(clipboardData); - this._logService.trace('CopyPasteController#handlePaste with metadata : ', metadata?.id, ' and text.length : ', clipboardData.getData('text/plain').length); - const dataTransfer = toExternalVSDataTransfer(clipboardData); - dataTransfer.delete(vscodeClipboardMime); + const metadata = this.fetchCopyMetadata(e); + this._logService.trace('CopyPasteController#handlePaste with metadata : ', metadata?.id, ' and text.length : ', e.clipboardData.getData('text/plain').length); - const fileTypes = Array.from(clipboardData.files).map(file => file.type); + const fileTypes = Array.from(e.clipboardData.files).map(file => file.type); const allPotentialMimeTypes = [ - ...clipboardData.types, + ...e.clipboardData.types, ...fileTypes, ...metadata?.providerCopyMimeTypes ?? [], // TODO: always adds `uri-list` because this get set if there are resources in the system clipboard. @@ -551,11 +548,11 @@ export class CopyPasteController extends Disposable implements IEditorContributi clipboardData.setData(vscodeClipboardMime, JSON.stringify(metadata)); } - private fetchCopyMetadata(clipboardData: DataTransfer): CopyMetadata | undefined { + private fetchCopyMetadata(e: IClipboardPasteEvent): CopyMetadata | undefined { this._logService.trace('CopyPasteController#fetchCopyMetadata'); // Prefer using the clipboard data we saved off - const rawMetadata = clipboardData.getData(vscodeClipboardMime); + const rawMetadata = e.clipboardData.getData(vscodeClipboardMime); if (rawMetadata) { try { return JSON.parse(rawMetadata); @@ -564,14 +561,12 @@ export class CopyPasteController extends Disposable implements IEditorContributi } } - // Otherwise try to extract the generic text editor metadata - const [_, metadata] = ClipboardEventUtils.getTextData(clipboardData); - if (metadata) { + if (e.metadata) { return { defaultPastePayload: { - mode: metadata.mode, - multicursorText: metadata.multicursorText ?? null, - pasteOnNewLine: !!metadata.isFromEmptySelection, + mode: e.metadata.mode, + multicursorText: e.metadata.multicursorText ?? null, + pasteOnNewLine: !!e.metadata.isFromEmptySelection, }, }; }