diff --git a/packages/ide/src/client.ts b/packages/ide/src/client.ts index 3c0c4a4dd..038aa15d6 100644 --- a/packages/ide/src/client.ts +++ b/packages/ide/src/client.ts @@ -2,7 +2,7 @@ import { Event } from "@coder/events"; import { field, logger, time, Time } from "@coder/logger"; import { InitData, ISharedProcessData } from "@coder/protocol"; import { retry } from "./retry"; -import { Upload } from "./upload"; +import { upload } from "./upload"; import { client } from "./fill/client"; import { clipboard } from "./fill/clipboard"; import { INotificationService, NotificationService, IProgressService, ProgressService } from "./fill/notification"; @@ -21,7 +21,8 @@ export abstract class Client { public readonly retry = retry; public readonly clipboard = clipboard; public readonly uriFactory: IURIFactory; - public readonly upload = new Upload(new NotificationService(), new ProgressService()); + public readonly upload = upload; + private start: Time | undefined; private readonly progressElement: HTMLElement | undefined; private tasks: string[] = []; diff --git a/packages/ide/src/index.ts b/packages/ide/src/index.ts index 1c81e19a9..9d3493fbe 100644 --- a/packages/ide/src/index.ts +++ b/packages/ide/src/index.ts @@ -1,3 +1,6 @@ export * from "./client"; -export * from "./fill/uri"; +export * from "./fill/clipboard"; export * from "./fill/notification"; +export * from "./fill/uri"; +export * from "./retry"; +export * from "./upload"; diff --git a/packages/ide/src/upload.ts b/packages/ide/src/upload.ts index aac6c0f7f..00bd1580a 100644 --- a/packages/ide/src/upload.ts +++ b/packages/ide/src/upload.ts @@ -4,7 +4,7 @@ import { promisify } from "util"; import { logger, Logger } from "@coder/logger"; import { escapePath } from "@coder/protocol"; import { IURI } from "./fill/uri"; -import { INotificationService, IProgressService, IProgress, Severity } from "./fill/notification"; +import { NotificationService, INotificationService, ProgressService, IProgressService, IProgress, Severity } from "./fill/notification"; /** * Represents an uploadable directory, so we can query for existing files once. @@ -355,3 +355,6 @@ export class Upload { } } + +// Global instance. +export const upload = new Upload(new NotificationService(), new ProgressService()); diff --git a/packages/vscode/src/client.ts b/packages/vscode/src/client.ts index e3535581b..662fbd587 100644 --- a/packages/vscode/src/client.ts +++ b/packages/vscode/src/client.ts @@ -4,6 +4,7 @@ import "./fill/storageDatabase"; import "./fill/windowsService"; import "./fill/environmentService"; import "./fill/vscodeTextmate"; +import { PasteAction } from "./fill/paste"; import "./fill/dom"; import "./vscode.scss"; import { Client as IDEClient, IURI, IURIFactory, IProgress, INotificationHandle } from "@coder/ide"; @@ -19,8 +20,6 @@ import { IEditorGroup } from "vs/workbench/services/group/common/editorGroupsSer 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 { @@ -78,27 +77,8 @@ export class Client extends IDEClient { /** * 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 pasteAction(): PasteAction { + return new PasteAction(); } public get serviceCollection(): ServiceCollection { diff --git a/packages/vscode/src/fill/paste.ts b/packages/vscode/src/fill/paste.ts new file mode 100644 index 000000000..7e557d3ff --- /dev/null +++ b/packages/vscode/src/fill/paste.ts @@ -0,0 +1,86 @@ +import * as nls from "vs/nls"; +import { Action } from "vs/base/common/actions"; +import { TERMINAL_COMMAND_ID } from "vs/workbench/parts/terminal/common/terminalCommands"; +import { ITerminalService } from "vs/workbench/parts/terminal/common/terminal"; +import * as actions from "vs/workbench/parts/terminal/electron-browser/terminalActions"; +import * as instance from "vs/workbench/parts/terminal/electron-browser/terminalInstance"; +import { clipboard } from "@coder/ide"; + +const getLabel = (key: string, enabled: boolean): string => { + return enabled + ? nls.localize(key, "Paste") + : nls.localize(`${key}WithKeybind`, "Paste (must use keybind)"); +}; + +export class PasteAction extends Action { + + private static readonly KEY = "paste"; + + public constructor() { + super( + "editor.action.clipboardPasteAction", + getLabel(PasteAction.KEY, clipboard.isEnabled), + undefined, + clipboard.isEnabled, + async (): Promise => clipboard.paste(), + ); + + clipboard.onPermissionChange((enabled) => { + this.label = getLabel(PasteAction.KEY, enabled); + this.enabled = enabled; + }); + } + +} + +class TerminalPasteAction extends Action { + + private static readonly KEY = "workbench.action.terminal.paste"; + + public static readonly ID = TERMINAL_COMMAND_ID.PASTE; + public static readonly LABEL = nls.localize("workbench.action.terminal.paste", "Paste into Active Terminal"); + public static readonly SHORT_LABEL = getLabel(TerminalPasteAction.KEY, clipboard.isEnabled); + + public constructor( + id: string, label: string, + @ITerminalService private terminalService: ITerminalService, + ) { + super(id, label); + clipboard.onPermissionChange((enabled) => { + this._setLabel(getLabel(TerminalPasteAction.KEY, enabled)); + }); + this._setLabel(getLabel(TerminalPasteAction.KEY, clipboard.isEnabled)); + } + + public run(): Promise { + const instance = this.terminalService.getActiveOrCreateInstance(); + if (instance) { + // tslint:disable-next-line no-any it will return a promise (see below) + return (instance as any).paste(); + } + + return Promise.resolve(); + } + +} + +class TerminalInstance extends instance.TerminalInstance { + + public async paste(): Promise { + this.focus(); + if (clipboard.isEnabled) { + const text = await clipboard.readText(); + this.sendText(text, false); + } else { + document.execCommand("paste"); + } + } + +} + +const actionsTarget = actions as typeof actions; +// @ts-ignore TODO: don't ignore it. +actionsTarget.TerminalPasteAction = TerminalPasteAction; + +const instanceTarget = instance as typeof instance; +instanceTarget.TerminalInstance = TerminalInstance; diff --git a/scripts/vscode.patch b/scripts/vscode.patch index 17f9bb546..c9be5a1cd 100644 --- a/scripts/vscode.patch +++ b/scripts/vscode.patch @@ -229,6 +229,31 @@ index e600fb2f78..5d65a3124e 100644 const droppedResources = extractResources(originalEvent.browserEvent as DragEvent, true); // Check for dropped external files to be folders +diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.ts b/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.ts +index 2975294e75..73ffb6362d 100644 +--- a/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.ts ++++ b/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.ts +@@ -38,6 +38,7 @@ import { TerminalPanel } from 'vs/workbench/parts/terminal/electron-browser/term + import { TerminalPickerHandler } from 'vs/workbench/parts/terminal/browser/terminalQuickOpen'; + import { setupTerminalCommands, TERMINAL_COMMAND_ID } from 'vs/workbench/parts/terminal/common/terminalCommands'; + import { setupTerminalMenu } from 'vs/workbench/parts/terminal/common/terminalMenu'; ++import { client } from "../../../../../../../../packages/vscode"; + + const quickOpenRegistry = (Registry.as(QuickOpenExtensions.Quickopen)); + +@@ -434,9 +435,11 @@ actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FocusPreviousTer + actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(TerminalPasteAction, TerminalPasteAction.ID, TerminalPasteAction.LABEL, { + primary: KeyMod.CtrlCmd | KeyCode.KEY_V, + linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_V }, ++ win: { primary: KeyMod.CtrlCmd | KeyCode.KEY_V }, + // Don't apply to Mac since cmd+v works + mac: { primary: 0 } +-}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Paste into Active Terminal', category); ++// }, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Paste into Active Terminal', category); ++}, ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_FOCUS, client.clipboardContextKey)), 'Terminal: Paste into Active Terminal', category); + actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(SelectAllTerminalAction, SelectAllTerminalAction.ID, SelectAllTerminalAction.LABEL, { + // Don't use ctrl+a by default as that would override the common go to start + // of prompt shell binding diff --git a/src/vs/workbench/parts/welcome/walkThrough/node/walkThroughContentProvider.ts b/src/vs/workbench/parts/welcome/walkThrough/node/walkThroughContentProvider.ts index 7b4e8721ac..8f26dc2f28 100644 --- a/src/vs/workbench/parts/welcome/walkThrough/node/walkThroughContentProvider.ts