/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ 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, 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'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; 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 { Handler } from 'vs/editor/common/editorCommon'; const CLIPBOARD_CONTEXT_MENU_GROUP = '9_cutcopypaste'; 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(); return command; } export const CutAction = supportsCut ? registerCommand(new MultiCommand({ id: 'editor.action.clipboardCutAction', precondition: undefined, kbOpts: ( // Do not bind cut keybindings in the browser, // since browsers do that for us and it avoids security prompts platform.isNative ? { primary: KeyMod.CtrlCmd | KeyCode.KEY_X, win: { primary: KeyMod.CtrlCmd | KeyCode.KEY_X, secondary: [KeyMod.Shift | KeyCode.Delete] }, weight: KeybindingWeight.EditorContrib } : undefined ), menuOpts: [{ menuId: MenuId.MenubarEditMenu, group: '2_ccp', title: nls.localize({ key: 'miCut', comment: ['&& denotes a mnemonic'] }, "Cu&&t"), order: 1 }, { menuId: MenuId.EditorContext, group: CLIPBOARD_CONTEXT_MENU_GROUP, title: nls.localize('actions.clipboard.cutLabel', "Cut"), when: EditorContextKeys.writable, order: 1, }, { menuId: MenuId.CommandPalette, group: '', title: nls.localize('actions.clipboard.cutLabel', "Cut"), order: 1 }] })) : undefined; export const CopyAction = supportsCopy ? registerCommand(new MultiCommand({ id: 'editor.action.clipboardCopyAction', precondition: undefined, kbOpts: ( // Do not bind copy keybindings in the browser, // since browsers do that for us and it avoids security prompts platform.isNative ? { primary: KeyMod.CtrlCmd | KeyCode.KEY_C, win: { primary: KeyMod.CtrlCmd | KeyCode.KEY_C, secondary: [KeyMod.CtrlCmd | KeyCode.Insert] }, weight: KeybindingWeight.EditorContrib } : undefined ), menuOpts: [{ menuId: MenuId.MenubarEditMenu, group: '2_ccp', title: nls.localize({ key: 'miCopy', comment: ['&& denotes a mnemonic'] }, "&&Copy"), order: 2 }, { menuId: MenuId.EditorContext, group: CLIPBOARD_CONTEXT_MENU_GROUP, title: nls.localize('actions.clipboard.copyLabel', "Copy"), order: 2, }, { menuId: MenuId.CommandPalette, group: '', title: nls.localize('actions.clipboard.copyLabel', "Copy"), order: 1 }] })) : undefined; export const PasteAction = supportsPaste ? registerCommand(new MultiCommand({ id: 'editor.action.clipboardPasteAction', precondition: undefined, kbOpts: ( // Do not bind paste keybindings in the browser, // since browsers do that for us and it avoids security prompts platform.isNative ? { primary: KeyMod.CtrlCmd | KeyCode.KEY_V, win: { primary: KeyMod.CtrlCmd | KeyCode.KEY_V, secondary: [KeyMod.Shift | KeyCode.Insert] }, linux: { primary: KeyMod.CtrlCmd | KeyCode.KEY_V, secondary: [KeyMod.Shift | KeyCode.Insert] }, weight: KeybindingWeight.EditorContrib } : undefined ), menuOpts: [{ menuId: MenuId.MenubarEditMenu, group: '2_ccp', title: nls.localize({ key: 'miPaste', comment: ['&& denotes a mnemonic'] }, "&&Paste"), order: 3 }, { menuId: MenuId.EditorContext, group: CLIPBOARD_CONTEXT_MENU_GROUP, title: nls.localize('actions.clipboard.pasteLabel', "Paste"), when: EditorContextKeys.writable, order: 3, }, { menuId: MenuId.CommandPalette, group: '', title: nls.localize('actions.clipboard.pasteLabel', "Paste"), order: 1 }] })) : undefined; class ExecCommandCopyWithSyntaxHighlightingAction extends EditorAction { constructor() { super({ id: 'editor.action.clipboardCopyWithSyntaxHighlightingAction', label: nls.localize('actions.clipboard.copyWithSyntaxHighlightingLabel', "Copy With Syntax Highlighting"), alias: 'Copy With Syntax Highlighting', precondition: undefined, kbOpts: { kbExpr: EditorContextKeys.textInputFocus, primary: 0, weight: KeybindingWeight.EditorContrib } }); } public run(accessor: ServicesAccessor, editor: ICodeEditor): void { if (!editor.hasModel()) { return; } const emptySelectionClipboard = editor.getOption(EditorOption.emptySelectionClipboard); if (!emptySelectionClipboard && editor.getSelection().isEmpty()) { return; } CopyOptions.forceCopyWithSyntaxHighlighting = true; editor.focus(); document.execCommand('copy'); CopyOptions.forceCopyWithSyntaxHighlighting = false; } } 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) => { // Only if editor text focus (i.e. not if editor has widget focus). const focusedEditor = accessor.get(ICodeEditorService).getFocusedCodeEditor(); if (focusedEditor && focusedEditor.hasTextFocus()) { // 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; }); // 2. (default) handle case when focus is somewhere else. target.addImplementation(0, (accessor: ServicesAccessor, args: any) => { document.execCommand(browserCommand); return true; }); } registerExecCommandImpl(CutAction, 'cut'); registerExecCommandImpl(CopyAction, 'copy'); 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 }); } })(); 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); }