From 7e7955059ddd08ab8c1a2eb685992becb19000b0 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 14 Aug 2020 13:21:14 +0200 Subject: [PATCH 1/4] web - first cut paste support --- src/vs/editor/contrib/clipboard/clipboard.ts | 30 ++++++++++++++++---- 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/src/vs/editor/contrib/clipboard/clipboard.ts b/src/vs/editor/contrib/clipboard/clipboard.ts index 114004565c5de..c8e820748976a 100644 --- a/src/vs/editor/contrib/clipboard/clipboard.ts +++ b/src/vs/editor/contrib/clipboard/clipboard.ts @@ -16,6 +16,8 @@ import { MenuId } from 'vs/platform/actions/common/actions'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; +import { EditOperation } from 'vs/editor/common/core/editOperation'; const CLIPBOARD_CONTEXT_MENU_GROUP = '9_cutcopypaste'; @@ -23,10 +25,7 @@ const supportsCut = (platform.isNative || document.queryCommandSupported('cut')) const supportsCopy = (platform.isNative || document.queryCommandSupported('copy')); // IE and Edge have trouble with setting html content in clipboard const supportsCopyWithSyntaxHighlighting = (supportsCopy && !browser.isEdge); -// Chrome incorrectly returns true for document.queryCommandSupported('paste') -// when the paste feature is available but the calling script has insufficient -// privileges to actually perform the action -const supportsPaste = (platform.isNative || (!browser.isChrome && document.queryCommandSupported('paste'))); +const supportsPaste = true; function registerCommand(command: T): T { command.register(); @@ -167,8 +166,11 @@ function registerExecCommandImpl(target: MultiCommand | undefined, browserComman // 1. handle case when focus is in editor. target.addImplementation(10000, (accessor: ServicesAccessor, args: any) => { + const codeEditorService = accessor.get(ICodeEditorService); + const clipboardService = accessor.get(IClipboardService); + // Only if editor text focus (i.e. not if editor has widget focus). - const focusedEditor = accessor.get(ICodeEditorService).getFocusedCodeEditor(); + const focusedEditor = codeEditorService.getFocusedCodeEditor(); if (focusedEditor && focusedEditor.hasTextFocus()) { if (browserCommand === 'cut' || browserCommand === 'copy') { // Do not execute if there is no selection and empty selection clipboard is off @@ -178,7 +180,23 @@ function registerExecCommandImpl(target: MultiCommand | undefined, browserComman return true; } } - document.execCommand(browserCommand); + const result = document.execCommand(browserCommand); + // Web: certain browsers do not allow document.execCommand('paste') + // and as such we implement a workaround via the clipboard service + // Refs: https://github.com/microsoft/vscode/issues/82604 + if (!result && platform.isWeb && browserCommand === 'paste') { + (async () => { + const clipboardText = await clipboardService.readText(); + + const selection = focusedEditor.getSelection(); + if (selection) { + const edits = [EditOperation.insert(selection.getPosition(), clipboardText)]; + + focusedEditor.executeEdits('clipboard', edits); + } + })(); + return true; + } return true; } return false; From 353503a9183a14a917c10c6228d0254bbd6b8687 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Wed, 26 Aug 2020 16:33:07 +0200 Subject: [PATCH 2/4] Execute the editor's paste handler when pasting --- .../browser/controller/textAreaInput.ts | 2 +- src/vs/editor/contrib/clipboard/clipboard.ts | 26 +++++++++++++------ 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/vs/editor/browser/controller/textAreaInput.ts b/src/vs/editor/browser/controller/textAreaInput.ts index 238e47cd542bf..7b2868aa70105 100644 --- a/src/vs/editor/browser/controller/textAreaInput.ts +++ b/src/vs/editor/browser/controller/textAreaInput.ts @@ -72,7 +72,7 @@ interface InMemoryClipboardMetadata { * Every time we read from the cipboard, if the text matches our last written text, * we can fetch the previous metadata. */ -class InMemoryClipboardMetadataManager { +export class InMemoryClipboardMetadataManager { public static readonly INSTANCE = new InMemoryClipboardMetadataManager(); private _lastState: InMemoryClipboardMetadata | null; diff --git a/src/vs/editor/contrib/clipboard/clipboard.ts b/src/vs/editor/contrib/clipboard/clipboard.ts index c8e820748976a..742792579c2b8 100644 --- a/src/vs/editor/contrib/clipboard/clipboard.ts +++ b/src/vs/editor/contrib/clipboard/clipboard.ts @@ -7,7 +7,7 @@ import * as nls from 'vs/nls'; import * as browser from 'vs/base/browser/browser'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import * as platform from 'vs/base/common/platform'; -import { CopyOptions } from 'vs/editor/browser/controller/textAreaInput'; +import { CopyOptions, InMemoryClipboardMetadataManager } from 'vs/editor/browser/controller/textAreaInput'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorAction, registerEditorAction, Command, MultiCommand } from 'vs/editor/browser/editorExtensions'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; @@ -17,7 +17,7 @@ import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; -import { EditOperation } from 'vs/editor/common/core/editOperation'; +import { Handler } from 'vs/editor/common/editorCommon'; const CLIPBOARD_CONTEXT_MENU_GROUP = '9_cutcopypaste'; @@ -187,12 +187,22 @@ function registerExecCommandImpl(target: MultiCommand | undefined, browserComman if (!result && platform.isWeb && browserCommand === 'paste') { (async () => { const clipboardText = await clipboardService.readText(); - - const selection = focusedEditor.getSelection(); - if (selection) { - const edits = [EditOperation.insert(selection.getPosition(), clipboardText)]; - - focusedEditor.executeEdits('clipboard', edits); + 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; + } + focusedEditor.trigger('keyboard', Handler.Paste, { + text: clipboardText, + pasteOnNewLine, + multicursorText, + mode + }); } })(); return true; From 9345bb7ab6d36d5b43d50b661535acfdf15e2b26 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Wed, 26 Aug 2020 16:53:49 +0200 Subject: [PATCH 3/4] Separate the Paste implementation from the Cut & Copy one --- src/vs/editor/contrib/clipboard/clipboard.ts | 98 +++++++++++--------- 1 file changed, 55 insertions(+), 43 deletions(-) diff --git a/src/vs/editor/contrib/clipboard/clipboard.ts b/src/vs/editor/contrib/clipboard/clipboard.ts index 742792579c2b8..cbdeb7424d3a9 100644 --- a/src/vs/editor/contrib/clipboard/clipboard.ts +++ b/src/vs/editor/contrib/clipboard/clipboard.ts @@ -25,7 +25,6 @@ const supportsCut = (platform.isNative || document.queryCommandSupported('cut')) const supportsCopy = (platform.isNative || document.queryCommandSupported('copy')); // IE and Edge have trouble with setting html content in clipboard const supportsCopyWithSyntaxHighlighting = (supportsCopy && !browser.isEdge); -const supportsPaste = true; function registerCommand(command: T): T { command.register(); @@ -93,7 +92,7 @@ export const CopyAction = supportsCopy ? registerCommand(new MultiCommand({ }] })) : undefined; -export const PasteAction = supportsPaste ? registerCommand(new MultiCommand({ +export const PasteAction = registerCommand(new MultiCommand({ id: 'editor.action.clipboardPasteAction', precondition: undefined, kbOpts: ( @@ -123,7 +122,7 @@ export const PasteAction = supportsPaste ? registerCommand(new MultiCommand({ title: nls.localize('actions.clipboard.pasteLabel', "Paste"), order: 1 }] -})) : undefined; +})); class ExecCommandCopyWithSyntaxHighlightingAction extends EditorAction { @@ -159,54 +158,23 @@ class ExecCommandCopyWithSyntaxHighlightingAction extends EditorAction { } } -function registerExecCommandImpl(target: MultiCommand | undefined, browserCommand: 'cut' | 'copy' | 'paste'): void { +function registerExecCommandImpl(target: MultiCommand | undefined, browserCommand: 'cut' | 'copy'): void { if (!target) { return; } // 1. handle case when focus is in editor. target.addImplementation(10000, (accessor: ServicesAccessor, args: any) => { - const codeEditorService = accessor.get(ICodeEditorService); - const clipboardService = accessor.get(IClipboardService); - // Only if editor text focus (i.e. not if editor has widget focus). - const focusedEditor = codeEditorService.getFocusedCodeEditor(); + const focusedEditor = accessor.get(ICodeEditorService).getFocusedCodeEditor(); if (focusedEditor && focusedEditor.hasTextFocus()) { - if (browserCommand === 'cut' || browserCommand === 'copy') { - // Do not execute if there is no selection and empty selection clipboard is off - const emptySelectionClipboard = focusedEditor.getOption(EditorOption.emptySelectionClipboard); - const selection = focusedEditor.getSelection(); - if (selection && selection.isEmpty() && !emptySelectionClipboard) { - return true; - } - } - const result = document.execCommand(browserCommand); - // Web: certain browsers do not allow document.execCommand('paste') - // and as such we implement a workaround via the clipboard service - // Refs: https://github.com/microsoft/vscode/issues/82604 - if (!result && platform.isWeb && browserCommand === 'paste') { - (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; - } - focusedEditor.trigger('keyboard', Handler.Paste, { - text: clipboardText, - pasteOnNewLine, - multicursorText, - mode - }); - } - })(); + // Do not execute if there is no selection and empty selection clipboard is off + const emptySelectionClipboard = focusedEditor.getOption(EditorOption.emptySelectionClipboard); + const selection = focusedEditor.getSelection(); + if (selection && selection.isEmpty() && !emptySelectionClipboard) { return true; } + document.execCommand(browserCommand); return true; } return false; @@ -214,7 +182,6 @@ function registerExecCommandImpl(target: MultiCommand | undefined, browserComman // 2. (default) handle case when focus is somewhere else. target.addImplementation(0, (accessor: ServicesAccessor, args: any) => { - // Only if editor text focus (i.e. not if editor has widget focus). document.execCommand(browserCommand); return true; }); @@ -222,7 +189,52 @@ function registerExecCommandImpl(target: MultiCommand | undefined, browserComman registerExecCommandImpl(CutAction, 'cut'); registerExecCommandImpl(CopyAction, 'copy'); -registerExecCommandImpl(PasteAction, 'paste'); + +// 1. Paste: handle case when focus is in editor. +PasteAction.addImplementation(10000, (accessor: ServicesAccessor, args: any) => { + const codeEditorService = accessor.get(ICodeEditorService); + const clipboardService = accessor.get(IClipboardService); + + // Only if editor text focus (i.e. not if editor has widget focus). + const focusedEditor = codeEditorService.getFocusedCodeEditor(); + if (focusedEditor && focusedEditor.hasTextFocus()) { + const result = document.execCommand('paste'); + // Web: certain browsers do not allow document.execCommand('paste') + // and as such we implement a workaround via the clipboard service + // Refs: https://github.com/microsoft/vscode/issues/82604 + if (!result && platform.isWeb) { + (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; + } + focusedEditor.trigger('keyboard', Handler.Paste, { + text: clipboardText, + pasteOnNewLine, + multicursorText, + mode + }); + } + })(); + return true; + } + return true; + } + return false; +}); + +// 2. Paste: (default) handle case when focus is somewhere else. +PasteAction.addImplementation(0, (accessor: ServicesAccessor, args: any) => { + document.execCommand('paste'); + return true; +}); if (supportsCopyWithSyntaxHighlighting) { registerEditorAction(ExecCommandCopyWithSyntaxHighlightingAction); From f9d74e9be9562659ad3d0f14d47e484663ad297a Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Wed, 26 Aug 2020 18:18:39 +0200 Subject: [PATCH 4/4] Firefox does not support navigator.clipboard.readText() --- src/vs/editor/contrib/clipboard/clipboard.ts | 89 ++++++++++---------- 1 file changed, 46 insertions(+), 43 deletions(-) diff --git a/src/vs/editor/contrib/clipboard/clipboard.ts b/src/vs/editor/contrib/clipboard/clipboard.ts index cbdeb7424d3a9..6860a7bb61088 100644 --- a/src/vs/editor/contrib/clipboard/clipboard.ts +++ b/src/vs/editor/contrib/clipboard/clipboard.ts @@ -25,6 +25,9 @@ const supportsCut = (platform.isNative || document.queryCommandSupported('cut')) const supportsCopy = (platform.isNative || document.queryCommandSupported('copy')); // IE and Edge have trouble with setting html content in clipboard const supportsCopyWithSyntaxHighlighting = (supportsCopy && !browser.isEdge); +// Firefox only supports navigator.clipboard.readText() in browser extensions. +// See https://developer.mozilla.org/en-US/docs/Web/API/Clipboard/readText#Browser_compatibility +const supportsPaste = (browser.isFirefox ? document.queryCommandSupported('paste') : true); function registerCommand(command: T): T { command.register(); @@ -92,7 +95,7 @@ export const CopyAction = supportsCopy ? registerCommand(new MultiCommand({ }] })) : undefined; -export const PasteAction = registerCommand(new MultiCommand({ +export const PasteAction = supportsPaste ? registerCommand(new MultiCommand({ id: 'editor.action.clipboardPasteAction', precondition: undefined, kbOpts: ( @@ -122,7 +125,7 @@ export const PasteAction = registerCommand(new MultiCommand({ title: nls.localize('actions.clipboard.pasteLabel', "Paste"), order: 1 }] -})); +})) : undefined; class ExecCommandCopyWithSyntaxHighlightingAction extends EditorAction { @@ -190,51 +193,51 @@ function registerExecCommandImpl(target: MultiCommand | undefined, browserComman registerExecCommandImpl(CutAction, 'cut'); registerExecCommandImpl(CopyAction, 'copy'); -// 1. Paste: handle case when focus is in editor. -PasteAction.addImplementation(10000, (accessor: ServicesAccessor, args: any) => { - const codeEditorService = accessor.get(ICodeEditorService); - const clipboardService = accessor.get(IClipboardService); - - // Only if editor text focus (i.e. not if editor has widget focus). - const focusedEditor = codeEditorService.getFocusedCodeEditor(); - if (focusedEditor && focusedEditor.hasTextFocus()) { - const result = document.execCommand('paste'); - // Web: certain browsers do not allow document.execCommand('paste') - // and as such we implement a workaround via the clipboard service - // Refs: https://github.com/microsoft/vscode/issues/82604 - if (!result && platform.isWeb) { - (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; +if (PasteAction) { + // 1. Paste: handle case when focus is in editor. + PasteAction.addImplementation(10000, (accessor: ServicesAccessor, args: any) => { + const codeEditorService = accessor.get(ICodeEditorService); + const clipboardService = accessor.get(IClipboardService); + + // Only if editor text focus (i.e. not if editor has widget focus). + const focusedEditor = codeEditorService.getFocusedCodeEditor(); + if (focusedEditor && focusedEditor.hasTextFocus()) { + const result = document.execCommand('paste'); + // Use the clipboard service if document.execCommand('paste') was not successful + if (!result && platform.isWeb) { + (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; + } + focusedEditor.trigger('keyboard', Handler.Paste, { + text: clipboardText, + pasteOnNewLine, + multicursorText, + mode + }); } - focusedEditor.trigger('keyboard', Handler.Paste, { - text: clipboardText, - pasteOnNewLine, - multicursorText, - mode - }); - } - })(); + })(); + return true; + } return true; } + return false; + }); + + // 2. Paste: (default) handle case when focus is somewhere else. + PasteAction.addImplementation(0, (accessor: ServicesAccessor, args: any) => { + document.execCommand('paste'); return true; - } - return false; -}); - -// 2. Paste: (default) handle case when focus is somewhere else. -PasteAction.addImplementation(0, (accessor: ServicesAccessor, args: any) => { - document.execCommand('paste'); - return true; -}); + }); +} if (supportsCopyWithSyntaxHighlighting) { registerEditorAction(ExecCommandCopyWithSyntaxHighlightingAction);