Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Web: provide a "Paste" editor action #104646

Merged
merged 5 commits into from
Aug 26, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/vs/editor/browser/controller/textAreaInput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
73 changes: 58 additions & 15 deletions src/vs/editor/contrib/clipboard/clipboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -16,17 +16,18 @@ 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);
// 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')));
// 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<T extends Command>(command: T): T {
command.register();
Expand Down Expand Up @@ -160,7 +161,7 @@ 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;
}
Expand All @@ -170,13 +171,11 @@ function registerExecCommandImpl(target: MultiCommand | undefined, browserComman
// Only if editor text focus (i.e. not if editor has widget focus).
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;
}
// 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;
Expand All @@ -186,15 +185,59 @@ 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;
});
}

registerExecCommandImpl(CutAction, 'cut');
registerExecCommandImpl(CopyAction, 'copy');
registerExecCommandImpl(PasteAction, 'paste');

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);
Expand Down