diff --git a/src/vs/editor/browser/controller/editContext/clipboardUtils.ts b/src/vs/editor/browser/controller/editContext/clipboardUtils.ts index edfb62977d3..09ca8745315 100644 --- a/src/vs/editor/browser/controller/editContext/clipboardUtils.ts +++ b/src/vs/editor/browser/controller/editContext/clipboardUtils.ts @@ -83,42 +83,6 @@ function getDataToCopy(viewModel: IViewModel, modelSelections: Range[], emptySel return dataToCopy; } -export interface IPasteData { - text: string; - pasteOnNewLine: boolean; - multicursorText: string[] | null; - mode: string | null; -} - -export function computePasteData(e: ClipboardEvent, context: ViewContext, logService: ILogService): IPasteData | undefined { - e.preventDefault(); - if (!e.clipboardData) { - return; - } - let [text, metadata] = ClipboardEventUtils.getTextData(e.clipboardData); - logService.trace('computePasteData with id : ', metadata?.id, ' with text.length: ', text.length); - if (!text) { - return; - } - PasteOptions.electronBugWorkaroundPasteEventHasFired = true; - logService.trace('(computePasteData) PasteOptions.electronBugWorkaroundPasteEventHasFired : ', PasteOptions.electronBugWorkaroundPasteEventHasFired); - metadata = metadata || InMemoryClipboardMetadataManager.INSTANCE.get(text); - return getPasteDataFromMetadata(text, metadata, context); -} - -export function getPasteDataFromMetadata(text: string, metadata: ClipboardStoredMetadata | null, context: ViewContext): IPasteData { - let pasteOnNewLine = false; - let multicursorText: string[] | null = null; - let mode: string | null = null; - if (metadata) { - const options = context.configuration.options; - const emptySelectionClipboard = options.get(EditorOption.emptySelectionClipboard); - pasteOnNewLine = emptySelectionClipboard && !!metadata.isFromEmptySelection; - multicursorText = typeof metadata.multicursorText !== 'undefined' ? metadata.multicursorText : null; - mode = metadata.mode; - } - return { text, pasteOnNewLine, multicursorText, mode }; -} /** * Every time we write to the clipboard, we record a bit of extra metadata here. * Every time we read from the cipboard, if the text matches our last written text, @@ -168,11 +132,6 @@ export const CopyOptions = { electronBugWorkaroundCopyEventHasFired: false }; -export const PasteOptions = { - electronBugWorkaroundPasteEventHasFired: false, - electronBugWorkaroundPasteEventLock: false -}; - interface InMemoryClipboardMetadata { lastCopiedValue: string; data: ClipboardStoredMetadata; diff --git a/src/vs/editor/browser/controller/editContext/native/nativeEditContext.ts b/src/vs/editor/browser/controller/editContext/native/nativeEditContext.ts index 369f42f3eb7..6334e8bdfe1 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 { ensureClipboardGetsEditorSelection, computePasteData } from '../clipboardUtils.js'; +import { ClipboardEventUtils, ensureClipboardGetsEditorSelection, InMemoryClipboardMetadataManager } from '../clipboardUtils.js'; import { AbstractEditContext } from '../editContext.js'; import { editContextAddDisposableListener, FocusTracker, ITypeData } from './nativeEditContextUtils.js'; import { ScreenReaderSupport } from './screenReaderSupport.js'; @@ -141,12 +141,28 @@ export class NativeEditContext extends AbstractEditContext { })); this._register(addDisposableListener(this.domNode.domNode, 'paste', (e) => { this.logService.trace('NativeEditContext#paste'); - const pasteData = computePasteData(e, this._context, this.logService); - if (!pasteData) { + e.preventDefault(); + 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) { + return; + } + metadata = metadata || InMemoryClipboardMetadataManager.INSTANCE.get(text); + let pasteOnNewLine = false; + let multicursorText: string[] | null = null; + let mode: string | null = null; + if (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; + } this.logService.trace('NativeEditContext#paste (before viewController.paste)'); - this._viewController.paste(pasteData.text, pasteData.pasteOnNewLine, pasteData.multicursorText, pasteData.mode); + this._viewController.paste(text, pasteOnNewLine, multicursorText, mode); })); // Edit context events diff --git a/src/vs/editor/browser/controller/editContext/textArea/textAreaEditContext.ts b/src/vs/editor/browser/controller/editContext/textArea/textAreaEditContext.ts index f63dd5ec5f5..b1eab383d05 100644 --- a/src/vs/editor/browser/controller/editContext/textArea/textAreaEditContext.ts +++ b/src/vs/editor/browser/controller/editContext/textArea/textAreaEditContext.ts @@ -35,12 +35,11 @@ import { IME } from '../../../../../base/common/ime.js'; import { IKeybindingService } from '../../../../../platform/keybinding/common/keybinding.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; import { AbstractEditContext } from '../editContext.js'; -import { ICompositionData, ITextAreaInputHost, TextAreaInput, TextAreaWrapper } from './textAreaEditContextInput.js'; +import { ICompositionData, IPasteData, ITextAreaInputHost, TextAreaInput, TextAreaWrapper } from './textAreaEditContextInput.js'; import { ariaLabelForScreenReaderContent, newlinecount, SimplePagedScreenReaderStrategy } from '../screenReaderUtils.js'; import { _debugComposition, ITypeData, TextAreaState } from './textAreaEditContextState.js'; import { getMapForWordSeparators, WordCharacterClass } from '../../../../common/core/wordCharacterClassifier.js'; import { TextAreaEditContextRegistry } from './textAreaEditContextRegistry.js'; -import { IPasteData } from '../clipboardUtils.js'; export interface IVisibleRangeProvider { visibleRangeForPosition(position: Position): HorizontalPosition | null; @@ -126,6 +125,7 @@ export class TextAreaEditContext extends AbstractEditContext { private _contentWidth: number; private _contentHeight: number; private _fontInfo: FontInfo; + private _emptySelectionClipboard: boolean; /** * Defined only when the text area is visible (composition case). @@ -168,6 +168,7 @@ export class TextAreaEditContext extends AbstractEditContext { this._contentWidth = layoutInfo.contentWidth; this._contentHeight = layoutInfo.height; this._fontInfo = options.get(EditorOption.fontInfo); + this._emptySelectionClipboard = options.get(EditorOption.emptySelectionClipboard); this._visibleTextArea = null; this._selections = [new Selection(1, 1, 1, 1)]; @@ -285,7 +286,15 @@ export class TextAreaEditContext extends AbstractEditContext { })); this._register(this._textAreaInput.onPaste((e: IPasteData) => { - this._viewController.paste(e.text, e.pasteOnNewLine, e.multicursorText, e.mode); + let pasteOnNewLine = false; + let multicursorText: string[] | null = null; + let mode: string | null = null; + if (e.metadata) { + pasteOnNewLine = (this._emptySelectionClipboard && !!e.metadata.isFromEmptySelection); + multicursorText = (typeof e.metadata.multicursorText !== 'undefined' ? e.metadata.multicursorText : null); + mode = e.metadata.mode; + } + this._viewController.paste(e.text, pasteOnNewLine, multicursorText, mode); })); this._register(this._textAreaInput.onCut(() => { @@ -562,6 +571,7 @@ export class TextAreaEditContext extends AbstractEditContext { this._contentWidth = layoutInfo.contentWidth; this._contentHeight = layoutInfo.height; this._fontInfo = options.get(EditorOption.fontInfo); + this._emptySelectionClipboard = options.get(EditorOption.emptySelectionClipboard); this.textArea.setAttribute('wrap', this._textAreaWrapping && !this._visibleTextArea ? 'on' : 'off'); const { tabSize } = this._context.viewModel.model.getOptions(); this.textArea.domNode.style.tabSize = `${tabSize * this._fontInfo.spaceWidth}px`; diff --git a/src/vs/editor/browser/controller/editContext/textArea/textAreaEditContextInput.ts b/src/vs/editor/browser/controller/editContext/textArea/textAreaEditContextInput.ts index 3a57cce766c..fa7ecddebff 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 { ensureClipboardGetsEditorSelection, computePasteData, InMemoryClipboardMetadataManager, IPasteData, getPasteDataFromMetadata } from '../clipboardUtils.js'; +import { ClipboardEventUtils, ClipboardStoredMetadata, ensureClipboardGetsEditorSelection, InMemoryClipboardMetadataManager } from '../clipboardUtils.js'; import { _debugComposition, ITextAreaWrapper, ITypeData, TextAreaState } from './textAreaEditContextState.js'; import { ViewContext } from '../../../../common/viewModel/viewContext.js'; @@ -30,6 +30,12 @@ export interface ICompositionData { data: string; } + +export interface IPasteData { + text: string; + metadata: ClipboardStoredMetadata | null; +} + export interface ITextAreaInputHost { readonly context: ViewContext | null; getScreenReaderContent(): TextAreaState; @@ -338,12 +344,11 @@ export class TextAreaInput extends Disposable { || typeInput.positionDelta !== 0 ) { // https://w3c.github.io/input-events/#interface-InputEvent-Attributes - if (this._host.context && e.inputType === 'insertFromPaste') { - this._onPaste.fire(getPasteDataFromMetadata( - typeInput.text, - InMemoryClipboardMetadataManager.INSTANCE.get(typeInput.text), - this._host.context - )); + if (e.inputType === 'insertFromPaste') { + this._onPaste.fire({ + text: typeInput.text, + metadata: InMemoryClipboardMetadataManager.INSTANCE.get(typeInput.text) + }); } else { this._onType.fire(typeInput); } @@ -376,15 +381,27 @@ export class TextAreaInput extends Disposable { // Pretend here we touched the text area, as the `paste` event will most likely // result in a `selectionchange` event which we want to ignore this._textArea.setIgnoreSelectionChangeTime('received paste event'); - if (!this._host.context) { + + e.preventDefault(); + + if (!e.clipboardData) { return; } - const pasteData = computePasteData(e, this._host.context, this._logService); - if (!pasteData) { + + 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(pasteData); + this._onPaste.fire({ + text: text, + metadata: metadata + }); })); this._register(this._textArea.onFocus(() => { diff --git a/src/vs/editor/contrib/clipboard/browser/clipboard.ts b/src/vs/editor/contrib/clipboard/browser/clipboard.ts index 751ef28109f..59079079e51 100644 --- a/src/vs/editor/contrib/clipboard/browser/clipboard.ts +++ b/src/vs/editor/contrib/clipboard/browser/clipboard.ts @@ -14,7 +14,7 @@ import { ContextKeyExpr } from '../../../../platform/contextkey/common/contextke import { ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js'; import { ILogService } from '../../../../platform/log/common/log.js'; -import { CopyOptions, generateDataToCopyAndStoreInMemory, InMemoryClipboardMetadataManager, PasteOptions } from '../../../browser/controller/editContext/clipboardUtils.js'; +import { CopyOptions, generateDataToCopyAndStoreInMemory, InMemoryClipboardMetadataManager } from '../../../browser/controller/editContext/clipboardUtils.js'; import { NativeEditContextRegistry } from '../../../browser/controller/editContext/native/nativeEditContextRegistry.js'; import { IActiveCodeEditor, ICodeEditor } from '../../../browser/editorBrowser.js'; import { Command, EditorAction, MultiCommand, registerEditorAction } from '../../../browser/editorExtensions.js'; @@ -208,28 +208,6 @@ function executeClipboardCopyWithWorkaround(editor: IActiveCodeEditor, clipboard } } -async function pasteWithNavigatorAPI(editor: IActiveCodeEditor, clipboardService: IClipboardService, logService: ILogService): Promise { - const clipboardText = await clipboardService.readText(); - if (clipboardText !== '') { - const metadata = InMemoryClipboardMetadataManager.INSTANCE.get(clipboardText); - let pasteOnNewLine = false; - let multicursorText: string[] | null = null; - let mode: string | null = null; - if (metadata) { - pasteOnNewLine = (editor.getOption(EditorOption.emptySelectionClipboard) && !!metadata.isFromEmptySelection); - multicursorText = (typeof metadata.multicursorText !== 'undefined' ? metadata.multicursorText : null); - mode = metadata.mode; - } - logService.trace('pasteWithNavigatorAPI with id : ', metadata?.id, ', clipboardText.length : ', clipboardText.length); - editor.trigger('keyboard', Handler.Paste, { - text: clipboardText, - pasteOnNewLine, - multicursorText, - mode - }); - } -} - function registerExecCommandImpl(target: MultiCommand | undefined, browserCommand: 'cut' | 'copy'): void { if (!target) { return; @@ -317,25 +295,10 @@ if (PasteAction) { } logService.trace('registerExecCommandImpl (before triggerPaste)'); - PasteOptions.electronBugWorkaroundPasteEventHasFired = false; - logService.trace('(before triggerPaste) PasteOptions.electronBugWorkaroundPasteEventHasFired : ', PasteOptions.electronBugWorkaroundPasteEventHasFired); const triggerPaste = clipboardService.triggerPaste(getActiveWindow().vscodeWindowId); if (triggerPaste) { logService.trace('registerExecCommandImpl (triggerPaste defined)'); - PasteOptions.electronBugWorkaroundPasteEventLock = false; return triggerPaste.then(async () => { - logService.trace('(triggerPaste) PasteOptions.electronBugWorkaroundPasteEventHasFired : ', PasteOptions.electronBugWorkaroundPasteEventHasFired); - if (PasteOptions.electronBugWorkaroundPasteEventHasFired === false) { - // Ensure this doesn't run twice, what appears to be happening is - // triggerPasteis called once but it's handler is called multiple times - // when it reproduces - logService.trace('(triggerPaste) PasteOptions.electronBugWorkaroundPasteEventLock : ', PasteOptions.electronBugWorkaroundPasteEventLock); - if (PasteOptions.electronBugWorkaroundPasteEventLock === true) { - return; - } - PasteOptions.electronBugWorkaroundPasteEventLock = true; - return pasteWithNavigatorAPI(focusedEditor, clipboardService, logService); - } logService.trace('registerExecCommandImpl (after triggerPaste)'); return CopyPasteController.get(focusedEditor)?.finishedPaste() ?? Promise.resolve(); }); @@ -345,7 +308,27 @@ if (PasteAction) { if (platform.isWeb) { logService.trace('registerExecCommandImpl (Paste handling on web)'); // Use the clipboard service if document.execCommand('paste') was not successful - return pasteWithNavigatorAPI(focusedEditor, clipboardService, logService); + return (async () => { + const clipboardText = await clipboardService.readText(); + if (clipboardText !== '') { + const metadata = InMemoryClipboardMetadataManager.INSTANCE.get(clipboardText); + let pasteOnNewLine = false; + let multicursorText: string[] | null = null; + let mode: string | null = null; + if (metadata) { + pasteOnNewLine = (focusedEditor.getOption(EditorOption.emptySelectionClipboard) && !!metadata.isFromEmptySelection); + multicursorText = (typeof metadata.multicursorText !== 'undefined' ? metadata.multicursorText : null); + mode = metadata.mode; + } + logService.trace('registerExecCommandImpl (clipboardText.length : ', clipboardText.length, ' id : ', metadata?.id, ')'); + focusedEditor.trigger('keyboard', Handler.Paste, { + text: clipboardText, + pasteOnNewLine, + multicursorText, + mode + }); + } + })(); } return true; } diff --git a/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteController.ts b/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteController.ts index ad1e8131381..3d524982981 100644 --- a/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteController.ts +++ b/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteController.ts @@ -25,7 +25,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, CopyOptions, InMemoryClipboardMetadataManager, PasteOptions } from '../../../browser/controller/editContext/clipboardUtils.js'; +import { ClipboardEventUtils, InMemoryClipboardMetadataManager } from '../../../browser/controller/editContext/clipboardUtils.js'; import { toExternalVSDataTransfer, toVSDataTransfer } from '../../../browser/dataTransfer.js'; import { ICodeEditor, PastePayload } from '../../../browser/editorBrowser.js'; import { IBulkEditService } from '../../../browser/services/bulkEditService.js'; @@ -172,7 +172,6 @@ export class CopyPasteController extends Disposable implements IEditorContributi } private handleCopy(e: ClipboardEvent) { - CopyOptions.electronBugWorkaroundCopyEventHasFired = true; let id: string | null = null; if (e.clipboardData) { const [text, metadata] = ClipboardEventUtils.getTextData(e.clipboardData); @@ -183,7 +182,6 @@ export class CopyPasteController extends Disposable implements IEditorContributi this._logService.trace('CopyPasteController#handleCopy'); } if (!this._editor.hasTextFocus()) { - this._logService.trace('CopyPasteController#handleCopy/earlyReturn1'); return; } @@ -193,14 +191,12 @@ export class CopyPasteController extends Disposable implements IEditorContributi this._clipboardService.clearInternalState?.(); if (!e.clipboardData || !this.isPasteAsEnabled()) { - this._logService.trace('CopyPasteController#handleCopy/earlyReturn2'); return; } const model = this._editor.getModel(); const selections = this._editor.getSelections(); if (!model || !selections?.length) { - this._logService.trace('CopyPasteController#handleCopy/earlyReturn3'); return; } @@ -210,7 +206,6 @@ export class CopyPasteController extends Disposable implements IEditorContributi const wasFromEmptySelection = selections.length === 1 && selections[0].isEmpty(); if (wasFromEmptySelection) { if (!enableEmptySelectionClipboard) { - this._logService.trace('CopyPasteController#handleCopy/earlyReturn4'); return; } @@ -231,7 +226,6 @@ export class CopyPasteController extends Disposable implements IEditorContributi .filter(x => !!x.prepareDocumentPaste); if (!providers.length) { this.setCopyMetadata(e.clipboardData, { defaultPastePayload }); - this._logService.trace('CopyPasteController#handleCopy/earlyReturn5'); return; } @@ -260,12 +254,9 @@ export class CopyPasteController extends Disposable implements IEditorContributi CopyPasteController._currentCopyOperation?.operations.forEach(entry => entry.operation.cancel()); CopyPasteController._currentCopyOperation = { handle, operations }; - this._logService.trace('CopyPasteController#handleCopy/end'); } private async handlePaste(e: ClipboardEvent) { - PasteOptions.electronBugWorkaroundPasteEventHasFired = true; - this._logService.trace('(handlePaste) PasteOptions.electronBugWorkaroundPasteEventHasFired : ', PasteOptions.electronBugWorkaroundPasteEventHasFired); if (e.clipboardData) { const [text, metadata] = ClipboardEventUtils.getTextData(e.clipboardData); const metadataComputed = metadata || InMemoryClipboardMetadataManager.INSTANCE.get(text); @@ -274,7 +265,6 @@ export class CopyPasteController extends Disposable implements IEditorContributi this._logService.trace('CopyPasteController#handlePaste'); } if (!e.clipboardData || !this._editor.hasTextFocus()) { - this._logService.trace('CopyPasteController#handlePaste/earlyReturn1'); return; } @@ -285,7 +275,6 @@ export class CopyPasteController extends Disposable implements IEditorContributi const model = this._editor.getModel(); const selections = this._editor.getSelections(); if (!selections?.length || !model) { - this._logService.trace('CopyPasteController#handlePaste/earlyReturn2'); return; } @@ -293,7 +282,6 @@ export class CopyPasteController extends Disposable implements IEditorContributi this._editor.getOption(EditorOption.readOnly) // Never enabled if editor is readonly. || (!this.isPasteAsEnabled() && !this._pasteAsActionContext) // Or feature disabled (but still enable if paste was explicitly requested) ) { - this._logService.trace('CopyPasteController#handlePaste/earlyReturn3'); return; } @@ -336,7 +324,6 @@ export class CopyPasteController extends Disposable implements IEditorContributi e.preventDefault(); e.stopImmediatePropagation(); } - this._logService.trace('CopyPasteController#handlePaste/earlyReturn4'); return; } @@ -351,7 +338,6 @@ export class CopyPasteController extends Disposable implements IEditorContributi } else { this.doPasteInline(allProviders, selections, dataTransfer, metadata, e); } - this._logService.trace('CopyPasteController#handlePaste/end'); } private showPasteAsNoEditMessage(selections: readonly Selection[], preference: PastePreference) {