diff --git a/packages/ide/src/client.ts b/packages/ide/src/client.ts index 9738a430b..3c0c4a4dd 100644 --- a/packages/ide/src/client.ts +++ b/packages/ide/src/client.ts @@ -4,7 +4,7 @@ import { InitData, ISharedProcessData } from "@coder/protocol"; import { retry } from "./retry"; import { Upload } from "./upload"; import { client } from "./fill/client"; -import { Clipboard, clipboard } from "./fill/clipboard"; +import { clipboard } from "./fill/clipboard"; import { INotificationService, NotificationService, IProgressService, ProgressService } from "./fill/notification"; import { IURIFactory } from "./fill/uri"; @@ -19,7 +19,7 @@ import { IURIFactory } from "./fill/uri"; export abstract class Client { public readonly retry = retry; - public readonly clipboard: Clipboard = clipboard; + public readonly clipboard = clipboard; public readonly uriFactory: IURIFactory; public readonly upload = new Upload(new NotificationService(), new ProgressService()); private start: Time | undefined; diff --git a/packages/ide/src/fill/clipboard.ts b/packages/ide/src/fill/clipboard.ts index 065e73f49..eb82f0436 100644 --- a/packages/ide/src/fill/clipboard.ts +++ b/packages/ide/src/fill/clipboard.ts @@ -36,6 +36,30 @@ export class Clipboard { } } + /** + * Paste currently copied text. + */ + public async paste(): Promise { + if (this.isEnabled) { + try { + const element = document.activeElement as HTMLInputElement | HTMLTextAreaElement; + const start = element.selectionStart || 0; + const end = element.selectionEnd; + const allText = element.value; + const newText = allText.substring(0, start) + + (await this.readText()) + + allText.substring(end || start); + element.value = newText; + + return true; + } catch (ex) { + // Will try execCommand below. + } + } + + return document.execCommand("paste"); + } + /** * Return true if the native clipboard is supported. */ diff --git a/packages/vscode/src/client.ts b/packages/vscode/src/client.ts index 59598b36b..e3535581b 100644 --- a/packages/vscode/src/client.ts +++ b/packages/vscode/src/client.ts @@ -9,7 +9,6 @@ import "./vscode.scss"; import { Client as IDEClient, IURI, IURIFactory, IProgress, INotificationHandle } from "@coder/ide"; import { registerContextMenuListener } from "vs/base/parts/contextmenu/electron-main/contextmenu"; import { LogLevel } from "vs/platform/log/common/log"; -// import { RawContextKey, IContextKeyService } from "vs/platform/contextkey/common/contextkey"; import { URI } from "vs/base/common/uri"; import { INotificationService } from "vs/platform/notification/common/notification"; import { IProgressService2, ProgressLocation } from "vs/platform/progress/common/progress"; @@ -19,11 +18,15 @@ import { IEditorService, IResourceEditor } from "vs/workbench/services/editor/co import { IEditorGroup } from "vs/workbench/services/group/common/editorGroupsService"; import { IWindowsService } from "vs/platform/windows/common/windows"; import { ServiceCollection } from "vs/platform/instantiation/common/serviceCollection"; +import { RawContextKey, IContextKeyService } from "vs/platform/contextkey/common/contextkey"; +import { Action } from "vs/base/common/actions"; +import * as nls from "vs/nls"; export class Client extends IDEClient { private readonly windowId = parseInt(new Date().toISOString().replace(/[-:.TZ]/g, ""), 10); private _serviceCollection: ServiceCollection | undefined; + private _clipboardContextKey: RawContextKey | undefined; public async handleExternalDrop(target: ExplorerItem | Model, originalEvent: DragMouseEvent): Promise { await this.upload.uploadDropped( @@ -57,6 +60,47 @@ export class Client extends IDEClient { }); } + /** + * Use to toggle the paste option inside editors based on the native clipboard. + */ + public get clipboardContextKey(): RawContextKey { + if (!this._clipboardContextKey) { + throw new Error("Trying to access clipboard context key before it has been set"); + } + + return this._clipboardContextKey; + } + + public get clipboardText(): Promise { + return this.clipboard.readText(); + } + + /** + * Create a paste action for use in text inputs. + */ + public get pasteAction(): Action { + const getLabel = (enabled: boolean): string => { + return enabled + ? nls.localize("paste", "Paste") + : nls.localize("pasteWithKeybind", "Paste (must use keybind)"); + }; + + const pasteAction = new Action( + "editor.action.clipboardPasteAction", + getLabel(this.clipboard.isEnabled), + undefined, + this.clipboard.isEnabled, + async (): Promise => this.clipboard.paste(), + ); + + this.clipboard.onPermissionChange((enabled) => { + pasteAction.label = getLabel(enabled); + pasteAction.enabled = enabled; + }); + + return pasteAction; + } + public get serviceCollection(): ServiceCollection { if (!this._serviceCollection) { throw new Error("Trying to access service collection before it has been set"); @@ -134,6 +178,8 @@ export class Client extends IDEClient { process.env.VSCODE_LOGS = data.logPath; }); + this._clipboardContextKey = new RawContextKey("nativeClipboard", this.clipboard.isEnabled); + return this.task("Start workbench", 1000, async (data) => { paths._paths.appData = data.dataDirectory; paths._paths.defaultUserData = data.dataDirectory; @@ -154,14 +200,11 @@ export class Client extends IDEClient { folderUri: URI.file(data.workingDirectory), }); - // TODO: Set up clipboard context. - // const workbench = workbenchShell.workbench; - // const contextKeys = workbench.workbenchParams.serviceCollection.get(IContextKeyService) as IContextKeyService; - // const clipboardContextKey = new RawContextKey("nativeClipboard", this.clipboard.isSupported); - // const bounded = clipboardContextKey.bindTo(contextKeys); - // this.clipboard.onPermissionChange((enabled) => { - // bounded.set(enabled); - // }); + const contextKeys = this.serviceCollection.get(IContextKeyService) as IContextKeyService; + const bounded = this.clipboardContextKey.bindTo(contextKeys); + this.clipboard.onPermissionChange((enabled) => { + bounded.set(enabled); + }); this.clipboard.initialize(); }, this.initData, pathSets); } diff --git a/scripts/vscode.patch b/scripts/vscode.patch index ea0eb7c24..17f9bb546 100644 --- a/scripts/vscode.patch +++ b/scripts/vscode.patch @@ -8,6 +8,67 @@ index 457818a975..ad45ffe58a 100644 } + +startup({ machineId: "1" }); +diff --git a/src/vs/editor/contrib/clipboard/clipboard.ts b/src/vs/editor/contrib/clipboard/clipboard.ts +index 5e43f1b39e..7775e3b6da 100644 +--- a/src/vs/editor/contrib/clipboard/clipboard.ts ++++ b/src/vs/editor/contrib/clipboard/clipboard.ts +@@ -16,6 +16,10 @@ 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 { CodeEditorWidget } from "vs/editor/browser/widget/codeEditorWidget"; ++import { Handler } from "vs/editor/common/editorCommon"; ++import { ContextKeyExpr } from "vs/platform/contextkey/common/contextkey"; ++import { client } from "../../../../../../../packages/vscode"; + + const CLIPBOARD_CONTEXT_MENU_GROUP = '9_cutcopypaste'; + +@@ -26,7 +30,8 @@ const supportsCopyWithSyntaxHighlighting = (supportsCopy && !browser.isEdgeOrIE) + // 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 = (platform.isNative || (!browser.isChrome && document.queryCommandSupported('paste'))); ++const supportsPaste = true; + + type ExecCommand = 'cut' | 'copy' | 'paste'; + +@@ -178,7 +183,7 @@ class ExecCommandPasteAction extends ExecCommandAction { + id: 'editor.action.clipboardPasteAction', + label: nls.localize('actions.clipboard.pasteLabel', "Paste"), + alias: 'Paste', +- precondition: EditorContextKeys.writable, ++ precondition: ContextKeyExpr.and(EditorContextKeys.writable, client.clipboardContextKey), + kbOpts: kbOpts, + menuOpts: { + group: CLIPBOARD_CONTEXT_MENU_GROUP, +@@ -188,10 +193,25 @@ class ExecCommandPasteAction extends ExecCommandAction { + menuId: MenuId.MenubarEditMenu, + group: '2_ccp', + title: nls.localize({ key: 'miPaste', comment: ['&& denotes a mnemonic'] }, "&&Paste"), +- order: 3 ++ order: 3, ++ when: client.clipboardContextKey, + } + }); + } ++ ++ public async run(accessor, editor: ICodeEditor): Promise { ++ if (editor instanceof CodeEditorWidget) { ++ try { ++ editor.trigger('', Handler.Paste, { ++ text: await client.clipboardText, ++ }); ++ } catch (ex) { ++ super.run(accessor, editor); ++ } ++ } else { ++ super.run(accessor, editor); ++ } ++ } + } + + class ExecCommandCopyWithSyntaxHighlightingAction extends ExecCommandAction { diff --git a/src/vs/loader.js b/src/vs/loader.js index 2bf7fe37d7..81cc668f12 100644 --- a/src/vs/loader.js @@ -94,6 +155,27 @@ index a43d63aa51..4c6df2fcd9 100644 }); }); }); +diff --git a/src/vs/workbench/electron-browser/window.ts b/src/vs/workbench/electron-browser/window.ts +index ea348f3a04..7c943a71c2 100644 +--- a/src/vs/workbench/electron-browser/window.ts ++++ b/src/vs/workbench/electron-browser/window.ts +@@ -38,6 +38,7 @@ import { AccessibilitySupport, isRootUser, isWindows, isMacintosh } from 'vs/bas + import product from 'vs/platform/node/product'; + import { INotificationService } from 'vs/platform/notification/common/notification'; + import { EditorServiceImpl } from 'vs/workbench/browser/parts/editor/editor'; ++import { client } from "../../../../../../packages/vscode"; + + const TextInputActions: IAction[] = [ + new Action('undo', nls.localize('undo', "Undo"), null, true, () => document.execCommand('undo') && Promise.resolve(true)), +@@ -45,7 +46,7 @@ const TextInputActions: IAction[] = [ + new Separator(), + new Action('editor.action.clipboardCutAction', nls.localize('cut', "Cut"), null, true, () => document.execCommand('cut') && Promise.resolve(true)), + new Action('editor.action.clipboardCopyAction', nls.localize('copy', "Copy"), null, true, () => document.execCommand('copy') && Promise.resolve(true)), +- new Action('editor.action.clipboardPasteAction', nls.localize('paste', "Paste"), null, true, () => document.execCommand('paste') && Promise.resolve(true)), ++ client.pasteAction, + new Separator(), + new Action('editor.action.selectAll', nls.localize('selectAll', "Select All"), null, true, () => document.execCommand('selectAll') && Promise.resolve(true)) + ]; diff --git a/src/vs/workbench/electron-browser/workbench.ts b/src/vs/workbench/electron-browser/workbench.ts index 35bc4a82b3..9cc84bdf28 100644 --- a/src/vs/workbench/electron-browser/workbench.ts