Skip to content

Commit bef4639

Browse files
code-asherkylecarbs
authored andcommitted
Enable native clipboard for editor and inputs
StackOverflow will be useful again.
1 parent ebe5e1b commit bef4639

File tree

4 files changed

+160
-11
lines changed

4 files changed

+160
-11
lines changed

packages/ide/src/client.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { InitData, ISharedProcessData } from "@coder/protocol";
44
import { retry } from "./retry";
55
import { Upload } from "./upload";
66
import { client } from "./fill/client";
7-
import { Clipboard, clipboard } from "./fill/clipboard";
7+
import { clipboard } from "./fill/clipboard";
88
import { INotificationService, NotificationService, IProgressService, ProgressService } from "./fill/notification";
99
import { IURIFactory } from "./fill/uri";
1010

@@ -19,7 +19,7 @@ import { IURIFactory } from "./fill/uri";
1919
export abstract class Client {
2020

2121
public readonly retry = retry;
22-
public readonly clipboard: Clipboard = clipboard;
22+
public readonly clipboard = clipboard;
2323
public readonly uriFactory: IURIFactory;
2424
public readonly upload = new Upload(new NotificationService(), new ProgressService());
2525
private start: Time | undefined;

packages/ide/src/fill/clipboard.ts

+24
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,30 @@ export class Clipboard {
3636
}
3737
}
3838

39+
/**
40+
* Paste currently copied text.
41+
*/
42+
public async paste(): Promise<boolean> {
43+
if (this.isEnabled) {
44+
try {
45+
const element = document.activeElement as HTMLInputElement | HTMLTextAreaElement;
46+
const start = element.selectionStart || 0;
47+
const end = element.selectionEnd;
48+
const allText = element.value;
49+
const newText = allText.substring(0, start)
50+
+ (await this.readText())
51+
+ allText.substring(end || start);
52+
element.value = newText;
53+
54+
return true;
55+
} catch (ex) {
56+
// Will try execCommand below.
57+
}
58+
}
59+
60+
return document.execCommand("paste");
61+
}
62+
3963
/**
4064
* Return true if the native clipboard is supported.
4165
*/

packages/vscode/src/client.ts

+52-9
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import "./vscode.scss";
99
import { Client as IDEClient, IURI, IURIFactory, IProgress, INotificationHandle } from "@coder/ide";
1010
import { registerContextMenuListener } from "vs/base/parts/contextmenu/electron-main/contextmenu";
1111
import { LogLevel } from "vs/platform/log/common/log";
12-
// import { RawContextKey, IContextKeyService } from "vs/platform/contextkey/common/contextkey";
1312
import { URI } from "vs/base/common/uri";
1413
import { INotificationService } from "vs/platform/notification/common/notification";
1514
import { IProgressService2, ProgressLocation } from "vs/platform/progress/common/progress";
@@ -19,11 +18,15 @@ import { IEditorService, IResourceEditor } from "vs/workbench/services/editor/co
1918
import { IEditorGroup } from "vs/workbench/services/group/common/editorGroupsService";
2019
import { IWindowsService } from "vs/platform/windows/common/windows";
2120
import { ServiceCollection } from "vs/platform/instantiation/common/serviceCollection";
21+
import { RawContextKey, IContextKeyService } from "vs/platform/contextkey/common/contextkey";
22+
import { Action } from "vs/base/common/actions";
23+
import * as nls from "vs/nls";
2224

2325
export class Client extends IDEClient {
2426

2527
private readonly windowId = parseInt(new Date().toISOString().replace(/[-:.TZ]/g, ""), 10);
2628
private _serviceCollection: ServiceCollection | undefined;
29+
private _clipboardContextKey: RawContextKey<boolean> | undefined;
2730

2831
public async handleExternalDrop(target: ExplorerItem | Model, originalEvent: DragMouseEvent): Promise<void> {
2932
await this.upload.uploadDropped(
@@ -57,6 +60,47 @@ export class Client extends IDEClient {
5760
});
5861
}
5962

63+
/**
64+
* Use to toggle the paste option inside editors based on the native clipboard.
65+
*/
66+
public get clipboardContextKey(): RawContextKey<boolean> {
67+
if (!this._clipboardContextKey) {
68+
throw new Error("Trying to access clipboard context key before it has been set");
69+
}
70+
71+
return this._clipboardContextKey;
72+
}
73+
74+
public get clipboardText(): Promise<string> {
75+
return this.clipboard.readText();
76+
}
77+
78+
/**
79+
* Create a paste action for use in text inputs.
80+
*/
81+
public get pasteAction(): Action {
82+
const getLabel = (enabled: boolean): string => {
83+
return enabled
84+
? nls.localize("paste", "Paste")
85+
: nls.localize("pasteWithKeybind", "Paste (must use keybind)");
86+
};
87+
88+
const pasteAction = new Action(
89+
"editor.action.clipboardPasteAction",
90+
getLabel(this.clipboard.isEnabled),
91+
undefined,
92+
this.clipboard.isEnabled,
93+
async (): Promise<boolean> => this.clipboard.paste(),
94+
);
95+
96+
this.clipboard.onPermissionChange((enabled) => {
97+
pasteAction.label = getLabel(enabled);
98+
pasteAction.enabled = enabled;
99+
});
100+
101+
return pasteAction;
102+
}
103+
60104
public get serviceCollection(): ServiceCollection {
61105
if (!this._serviceCollection) {
62106
throw new Error("Trying to access service collection before it has been set");
@@ -134,6 +178,8 @@ export class Client extends IDEClient {
134178
process.env.VSCODE_LOGS = data.logPath;
135179
});
136180

181+
this._clipboardContextKey = new RawContextKey("nativeClipboard", this.clipboard.isEnabled);
182+
137183
return this.task("Start workbench", 1000, async (data) => {
138184
paths._paths.appData = data.dataDirectory;
139185
paths._paths.defaultUserData = data.dataDirectory;
@@ -154,14 +200,11 @@ export class Client extends IDEClient {
154200
folderUri: URI.file(data.workingDirectory),
155201
});
156202

157-
// TODO: Set up clipboard context.
158-
// const workbench = workbenchShell.workbench;
159-
// const contextKeys = workbench.workbenchParams.serviceCollection.get(IContextKeyService) as IContextKeyService;
160-
// const clipboardContextKey = new RawContextKey("nativeClipboard", this.clipboard.isSupported);
161-
// const bounded = clipboardContextKey.bindTo(contextKeys);
162-
// this.clipboard.onPermissionChange((enabled) => {
163-
// bounded.set(enabled);
164-
// });
203+
const contextKeys = this.serviceCollection.get(IContextKeyService) as IContextKeyService;
204+
const bounded = this.clipboardContextKey.bindTo(contextKeys);
205+
this.clipboard.onPermissionChange((enabled) => {
206+
bounded.set(enabled);
207+
});
165208
this.clipboard.initialize();
166209
}, this.initData, pathSets);
167210
}

scripts/vscode.patch

+82
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,67 @@ index 457818a975..ad45ffe58a 100644
88
}
99
+
1010
+startup({ machineId: "1" });
11+
diff --git a/src/vs/editor/contrib/clipboard/clipboard.ts b/src/vs/editor/contrib/clipboard/clipboard.ts
12+
index 5e43f1b39e..7775e3b6da 100644
13+
--- a/src/vs/editor/contrib/clipboard/clipboard.ts
14+
+++ b/src/vs/editor/contrib/clipboard/clipboard.ts
15+
@@ -16,6 +16,10 @@ import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
16+
import { MenuId } from 'vs/platform/actions/common/actions';
17+
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
18+
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
19+
+import { CodeEditorWidget } from "vs/editor/browser/widget/codeEditorWidget";
20+
+import { Handler } from "vs/editor/common/editorCommon";
21+
+import { ContextKeyExpr } from "vs/platform/contextkey/common/contextkey";
22+
+import { client } from "../../../../../../../packages/vscode";
23+
24+
const CLIPBOARD_CONTEXT_MENU_GROUP = '9_cutcopypaste';
25+
26+
@@ -26,7 +30,8 @@ const supportsCopyWithSyntaxHighlighting = (supportsCopy && !browser.isEdgeOrIE)
27+
// Chrome incorrectly returns true for document.queryCommandSupported('paste')
28+
// when the paste feature is available but the calling script has insufficient
29+
// privileges to actually perform the action
30+
-const supportsPaste = (platform.isNative || (!browser.isChrome && document.queryCommandSupported('paste')));
31+
+// const supportsPaste = (platform.isNative || (!browser.isChrome && document.queryCommandSupported('paste')));
32+
+const supportsPaste = true;
33+
34+
type ExecCommand = 'cut' | 'copy' | 'paste';
35+
36+
@@ -178,7 +183,7 @@ class ExecCommandPasteAction extends ExecCommandAction {
37+
id: 'editor.action.clipboardPasteAction',
38+
label: nls.localize('actions.clipboard.pasteLabel', "Paste"),
39+
alias: 'Paste',
40+
- precondition: EditorContextKeys.writable,
41+
+ precondition: ContextKeyExpr.and(EditorContextKeys.writable, client.clipboardContextKey),
42+
kbOpts: kbOpts,
43+
menuOpts: {
44+
group: CLIPBOARD_CONTEXT_MENU_GROUP,
45+
@@ -188,10 +193,25 @@ class ExecCommandPasteAction extends ExecCommandAction {
46+
menuId: MenuId.MenubarEditMenu,
47+
group: '2_ccp',
48+
title: nls.localize({ key: 'miPaste', comment: ['&& denotes a mnemonic'] }, "&&Paste"),
49+
- order: 3
50+
+ order: 3,
51+
+ when: client.clipboardContextKey,
52+
}
53+
});
54+
}
55+
+
56+
+ public async run(accessor, editor: ICodeEditor): Promise<void> {
57+
+ if (editor instanceof CodeEditorWidget) {
58+
+ try {
59+
+ editor.trigger('', Handler.Paste, {
60+
+ text: await client.clipboardText,
61+
+ });
62+
+ } catch (ex) {
63+
+ super.run(accessor, editor);
64+
+ }
65+
+ } else {
66+
+ super.run(accessor, editor);
67+
+ }
68+
+ }
69+
}
70+
71+
class ExecCommandCopyWithSyntaxHighlightingAction extends ExecCommandAction {
1172
diff --git a/src/vs/loader.js b/src/vs/loader.js
1273
index 2bf7fe37d7..81cc668f12 100644
1374
--- a/src/vs/loader.js
@@ -94,6 +155,27 @@ index a43d63aa51..4c6df2fcd9 100644
94155
});
95156
});
96157
});
158+
diff --git a/src/vs/workbench/electron-browser/window.ts b/src/vs/workbench/electron-browser/window.ts
159+
index ea348f3a04..7c943a71c2 100644
160+
--- a/src/vs/workbench/electron-browser/window.ts
161+
+++ b/src/vs/workbench/electron-browser/window.ts
162+
@@ -38,6 +38,7 @@ import { AccessibilitySupport, isRootUser, isWindows, isMacintosh } from 'vs/bas
163+
import product from 'vs/platform/node/product';
164+
import { INotificationService } from 'vs/platform/notification/common/notification';
165+
import { EditorServiceImpl } from 'vs/workbench/browser/parts/editor/editor';
166+
+import { client } from "../../../../../../packages/vscode";
167+
168+
const TextInputActions: IAction[] = [
169+
new Action('undo', nls.localize('undo', "Undo"), null, true, () => document.execCommand('undo') && Promise.resolve(true)),
170+
@@ -45,7 +46,7 @@ const TextInputActions: IAction[] = [
171+
new Separator(),
172+
new Action('editor.action.clipboardCutAction', nls.localize('cut', "Cut"), null, true, () => document.execCommand('cut') && Promise.resolve(true)),
173+
new Action('editor.action.clipboardCopyAction', nls.localize('copy', "Copy"), null, true, () => document.execCommand('copy') && Promise.resolve(true)),
174+
- new Action('editor.action.clipboardPasteAction', nls.localize('paste', "Paste"), null, true, () => document.execCommand('paste') && Promise.resolve(true)),
175+
+ client.pasteAction,
176+
new Separator(),
177+
new Action('editor.action.selectAll', nls.localize('selectAll', "Select All"), null, true, () => document.execCommand('selectAll') && Promise.resolve(true))
178+
];
97179
diff --git a/src/vs/workbench/electron-browser/workbench.ts b/src/vs/workbench/electron-browser/workbench.ts
98180
index 35bc4a82b3..9cc84bdf28 100644
99181
--- a/src/vs/workbench/electron-browser/workbench.ts

0 commit comments

Comments
 (0)