From b391d0c7170e7357d85adf4bd5682a7f90b562cf Mon Sep 17 00:00:00 2001 From: Anton Kosyakov Date: Wed, 19 Jun 2019 11:15:55 +0000 Subject: [PATCH] fix #5519: clipboard vscode api Signed-off-by: Anton Kosyakov --- configs/warnings.tslint.json | 3 +- .../src/browser/browser-clipboard-service.ts | 50 +++++++++++++++++++ .../core/src/browser/clipboard-service.ts | 23 +++++++++ .../browser/window/browser-window-module.ts | 3 ++ .../electron-clipboard-service.ts | 32 ++++++++++++ .../window/electron-window-module.ts | 3 ++ packages/plugin-ext/src/api/plugin-api.ts | 8 ++- .../src/hosted/browser/worker/worker-main.ts | 5 +- .../src/hosted/node/plugin-host-rpc.ts | 5 +- .../src/main/browser/clipboard-main.ts | 38 ++++++++++++++ .../src/main/browser/main-context.ts | 4 ++ .../plugin-ext/src/plugin/clipboard-ext.ts | 37 ++++++++++++++ .../plugin-ext/src/plugin/plugin-context.ts | 6 ++- packages/plugin/src/theia.d.ts | 45 +++++++++++++---- 14 files changed, 245 insertions(+), 17 deletions(-) create mode 100644 packages/core/src/browser/browser-clipboard-service.ts create mode 100644 packages/core/src/browser/clipboard-service.ts create mode 100644 packages/core/src/electron-browser/electron-clipboard-service.ts create mode 100644 packages/plugin-ext/src/main/browser/clipboard-main.ts create mode 100644 packages/plugin-ext/src/plugin/clipboard-ext.ts diff --git a/configs/warnings.tslint.json b/configs/warnings.tslint.json index 9a1f1c18d17ee..3fc706d86217f 100644 --- a/configs/warnings.tslint.json +++ b/configs/warnings.tslint.json @@ -38,7 +38,8 @@ "vscode-languageserver-types", "vscode-ws-jsonrpc", "vscode-uri", - "yargs" + "yargs", + "electron" ] ] }, diff --git a/packages/core/src/browser/browser-clipboard-service.ts b/packages/core/src/browser/browser-clipboard-service.ts new file mode 100644 index 0000000000000..8b467faf04430 --- /dev/null +++ b/packages/core/src/browser/browser-clipboard-service.ts @@ -0,0 +1,50 @@ +/******************************************************************************** + * Copyright (C) 2019 TypeFox and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { injectable } from 'inversify'; +import { ClipboardService } from './clipboard-service'; + +export interface NavigatorClipboard { + readText(): Promise; + writeText(value: string): Promise; +} +export interface NavigatorPermissions { + query(options: { name: string }): Promise<{ state: 'granted' | 'prompt' | 'denied' }> +} + +@injectable() +export class BrowserClipboardService implements ClipboardService { + + async readText(): Promise { + if ('permissions' in navigator && 'clipboard' in navigator) { + const result = await (navigator['permissions']).query({ name: 'clipboard-read' }); + if (result.state === 'granted' || result.state === 'prompt') { + return (navigator['clipboard']).readText(); + } + } + return ''; + } + + async writeText(value: string): Promise { + if ('permissions' in navigator && 'clipboard' in navigator) { + const result = await (navigator['permissions']).query({ name: 'clipboard-write' }); + if (result.state === 'granted' || result.state === 'prompt') { + return (navigator['clipboard']).writeText(value); + } + } + } + +} diff --git a/packages/core/src/browser/clipboard-service.ts b/packages/core/src/browser/clipboard-service.ts new file mode 100644 index 0000000000000..224d7838bc3ca --- /dev/null +++ b/packages/core/src/browser/clipboard-service.ts @@ -0,0 +1,23 @@ +/******************************************************************************** + * Copyright (C) 2019 TypeFox and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { MaybePromise } from '../common/types'; + +export const ClipboardService = Symbol('ClipboardService'); +export interface ClipboardService { + readText(): MaybePromise; + writeText(value: string): MaybePromise; +} diff --git a/packages/core/src/browser/window/browser-window-module.ts b/packages/core/src/browser/window/browser-window-module.ts index f4bbbc3eaf76d..dd201ba404f2c 100644 --- a/packages/core/src/browser/window/browser-window-module.ts +++ b/packages/core/src/browser/window/browser-window-module.ts @@ -18,9 +18,12 @@ import { ContainerModule } from 'inversify'; import { WindowService } from '../../browser/window/window-service'; import { DefaultWindowService } from '../../browser/window/default-window-service'; import { FrontendApplicationContribution } from '../frontend-application'; +import { ClipboardService } from '../clipboard-service'; +import { BrowserClipboardService } from '../browser-clipboard-service'; export default new ContainerModule(bind => { bind(DefaultWindowService).toSelf().inSingletonScope(); bind(WindowService).toService(DefaultWindowService); bind(FrontendApplicationContribution).toService(DefaultWindowService); + bind(ClipboardService).to(BrowserClipboardService).inSingletonScope(); }); diff --git a/packages/core/src/electron-browser/electron-clipboard-service.ts b/packages/core/src/electron-browser/electron-clipboard-service.ts new file mode 100644 index 0000000000000..a90e80dedc49a --- /dev/null +++ b/packages/core/src/electron-browser/electron-clipboard-service.ts @@ -0,0 +1,32 @@ +/******************************************************************************** + * Copyright (C) 2019 TypeFox and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { clipboard } from 'electron'; +import { injectable } from 'inversify'; +import { ClipboardService } from '../browser/clipboard-service'; + +@injectable() +export class ElectronClipboardService implements ClipboardService { + + readText(): string { + return clipboard.readText(); + } + + writeText(value: string): void { + clipboard.writeText(value); + } + +} diff --git a/packages/core/src/electron-browser/window/electron-window-module.ts b/packages/core/src/electron-browser/window/electron-window-module.ts index 5b11e207d4a94..edad94a14c24e 100644 --- a/packages/core/src/electron-browser/window/electron-window-module.ts +++ b/packages/core/src/electron-browser/window/electron-window-module.ts @@ -18,8 +18,11 @@ import { ContainerModule } from 'inversify'; import { WindowService } from '../../browser/window/window-service'; import { ElectronWindowService } from './electron-window-service'; import { FrontendApplicationContribution } from '../../browser/frontend-application'; +import { ElectronClipboardService } from '../electron-clipboard-service'; +import { ClipboardService } from '../../browser/clipboard-service'; export default new ContainerModule(bind => { bind(WindowService).to(ElectronWindowService).inSingletonScope(); bind(FrontendApplicationContribution).toService(WindowService); + bind(ClipboardService).to(ElectronClipboardService).inSingletonScope(); }); diff --git a/packages/plugin-ext/src/api/plugin-api.ts b/packages/plugin-ext/src/api/plugin-api.ts index e9a946f2b4be7..7536870e4b269 100644 --- a/packages/plugin-ext/src/api/plugin-api.ts +++ b/packages/plugin-ext/src/api/plugin-api.ts @@ -1180,6 +1180,11 @@ export interface FileSystemMain { $unregisterProvider(handle: number): void; } +export interface ClipboardMain { + $readText(): Promise; + $writeText(value: string): Promise; +} + export const PLUGIN_RPC_CONTEXT = { COMMAND_REGISTRY_MAIN: >createProxyIdentifier('CommandRegistryMain'), QUICK_OPEN_MAIN: createProxyIdentifier('QuickOpenMain'), @@ -1204,7 +1209,8 @@ export const PLUGIN_RPC_CONTEXT = { DEBUG_MAIN: createProxyIdentifier('DebugMain'), FILE_SYSTEM_MAIN: createProxyIdentifier('FileSystemMain'), SCM_MAIN: createProxyIdentifier('ScmMain'), - DECORATIONS_MAIN: createProxyIdentifier('DecorationsMain') + DECORATIONS_MAIN: createProxyIdentifier('DecorationsMain'), + CLIPBOARD_MAIN: >createProxyIdentifier('ClipboardMain') }; export const MAIN_RPC_CONTEXT = { diff --git a/packages/plugin-ext/src/hosted/browser/worker/worker-main.ts b/packages/plugin-ext/src/hosted/browser/worker/worker-main.ts index 5d510edc78fcd..d0d1486696be4 100644 --- a/packages/plugin-ext/src/hosted/browser/worker/worker-main.ts +++ b/packages/plugin-ext/src/hosted/browser/worker/worker-main.ts @@ -29,6 +29,7 @@ import { WorkspaceExtImpl } from '../../../plugin/workspace'; import { MessageRegistryExt } from '../../../plugin/message-registry'; import { WorkerEnvExtImpl } from './worker-env-ext'; import { SelectionServiceExt } from '../../../plugin/selection-provider-ext'; +import { ClipboardExt } from '../../../plugin/clipboard-ext'; // tslint:disable-next-line:no-any const ctx = self as any; @@ -57,6 +58,7 @@ const workspaceExt = new WorkspaceExtImpl(rpc, editorsAndDocuments, messageRegis const preferenceRegistryExt = new PreferenceRegistryExtImpl(rpc, workspaceExt); const debugExt = createDebugExtStub(rpc); const selectionServiceExt = new SelectionServiceExt(); +const clipboardExt = new ClipboardExt(rpc); const pluginManager = new PluginManagerExtImpl({ // tslint:disable-next-line:no-any @@ -136,7 +138,8 @@ const apiFactory = createAPIFactory( editorsAndDocuments, workspaceExt, messageRegistryExt, - selectionServiceExt + selectionServiceExt, + clipboardExt ); let defaultApi: typeof theia; diff --git a/packages/plugin-ext/src/hosted/node/plugin-host-rpc.ts b/packages/plugin-ext/src/hosted/node/plugin-host-rpc.ts index c04ef3a8c53ab..77c7ba8ffa20f 100644 --- a/packages/plugin-ext/src/hosted/node/plugin-host-rpc.ts +++ b/packages/plugin-ext/src/hosted/node/plugin-host-rpc.ts @@ -27,6 +27,7 @@ import { WorkspaceExtImpl } from '../../plugin/workspace'; import { MessageRegistryExt } from '../../plugin/message-registry'; import { EnvNodeExtImpl } from '../../plugin/node/env-node-ext'; import { SelectionServiceExt } from '../../plugin/selection-provider-ext'; +import { ClipboardExt } from '../../plugin/clipboard-ext'; /** * Handle the RPC calls. @@ -49,6 +50,7 @@ export class PluginHostRPC { const workspaceExt = new WorkspaceExtImpl(this.rpc, editorsAndDocumentsExt, messageRegistryExt); const preferenceRegistryExt = new PreferenceRegistryExtImpl(this.rpc, workspaceExt); const selectionServiceExt = new SelectionServiceExt(); + const clipboardExt = new ClipboardExt(this.rpc); this.pluginManager = this.createPluginManager(envExt, preferenceRegistryExt, this.rpc); this.rpc.set(MAIN_RPC_CONTEXT.HOSTED_PLUGIN_MANAGER_EXT, this.pluginManager); this.rpc.set(MAIN_RPC_CONTEXT.EDITORS_AND_DOCUMENTS_EXT, editorsAndDocumentsExt); @@ -64,7 +66,8 @@ export class PluginHostRPC { editorsAndDocumentsExt, workspaceExt, messageRegistryExt, - selectionServiceExt + selectionServiceExt, + clipboardExt ); } diff --git a/packages/plugin-ext/src/main/browser/clipboard-main.ts b/packages/plugin-ext/src/main/browser/clipboard-main.ts new file mode 100644 index 0000000000000..df1e95a2eb524 --- /dev/null +++ b/packages/plugin-ext/src/main/browser/clipboard-main.ts @@ -0,0 +1,38 @@ +/******************************************************************************** + * Copyright (C) 2019 TypeFox and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { interfaces } from 'inversify'; +import { ClipboardMain } from '../../common'; +import { ClipboardService } from '@theia/core/lib/browser/clipboard-service'; + +export class ClipboardMainImpl implements ClipboardMain { + + protected readonly clipboardService: ClipboardService; + + constructor(container: interfaces.Container) { + this.clipboardService = container.get(ClipboardService); + } + + async $readText(): Promise { + const result = await this.clipboardService.readText(); + return result; + } + + async $writeText(value: string): Promise { + await this.clipboardService.writeText(value); + } + +} diff --git a/packages/plugin-ext/src/main/browser/main-context.ts b/packages/plugin-ext/src/main/browser/main-context.ts index 60c0b8cc7ec49..ed0070315fc21 100644 --- a/packages/plugin-ext/src/main/browser/main-context.ts +++ b/packages/plugin-ext/src/main/browser/main-context.ts @@ -40,6 +40,7 @@ import { DebugMainImpl } from './debug/debug-main'; import { FileSystemMainImpl } from './file-system-main'; import { ScmMainImpl } from './scm-main'; import { DecorationsMainImpl } from './decorations/decorations-main'; +import { ClipboardMainImpl } from './clipboard-main'; export function setUpPluginApi(rpc: RPCProtocol, container: interfaces.Container): void { const commandRegistryMain = new CommandRegistryMainImpl(rpc, container); @@ -111,4 +112,7 @@ export function setUpPluginApi(rpc: RPCProtocol, container: interfaces.Container const decorationsMain = new DecorationsMainImpl(rpc, container); rpc.set(PLUGIN_RPC_CONTEXT.DECORATIONS_MAIN, decorationsMain); + + const clipboardMain = new ClipboardMainImpl(container); + rpc.set(PLUGIN_RPC_CONTEXT.CLIPBOARD_MAIN, clipboardMain); } diff --git a/packages/plugin-ext/src/plugin/clipboard-ext.ts b/packages/plugin-ext/src/plugin/clipboard-ext.ts new file mode 100644 index 0000000000000..883702e9aa1cf --- /dev/null +++ b/packages/plugin-ext/src/plugin/clipboard-ext.ts @@ -0,0 +1,37 @@ +/******************************************************************************** + * Copyright (C) 2019 TypeFox and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import * as theia from '@theia/plugin'; +import { RPCProtocol } from '../api/rpc-protocol'; +import { PLUGIN_RPC_CONTEXT, ClipboardMain } from '../common'; + +export class ClipboardExt implements theia.Clipboard { + + protected readonly proxy: ClipboardMain; + + constructor(rpc: RPCProtocol) { + this.proxy = rpc.getProxy(PLUGIN_RPC_CONTEXT.CLIPBOARD_MAIN); + } + + readText(): Promise { + return this.proxy.$readText(); + } + + writeText(value: string): Promise { + return this.proxy.$writeText(value); + } + +} diff --git a/packages/plugin-ext/src/plugin/plugin-context.ts b/packages/plugin-ext/src/plugin/plugin-context.ts index bda31c4f6d00b..e6c6000c20242 100644 --- a/packages/plugin-ext/src/plugin/plugin-context.ts +++ b/packages/plugin-ext/src/plugin/plugin-context.ts @@ -136,6 +136,7 @@ import { SelectionServiceExt } from './selection-provider-ext'; import { ScmExtImpl } from './scm'; import { DecorationProvider, LineChange } from '@theia/plugin'; import { DecorationsExtImpl } from './decorations'; +import { ClipboardExt } from './clipboard-ext'; export function createAPIFactory( rpc: RPCProtocol, @@ -146,7 +147,8 @@ export function createAPIFactory( editorsAndDocumentsExt: EditorsAndDocumentsExtImpl, workspaceExt: WorkspaceExtImpl, messageRegistryExt: MessageRegistryExt, - selectionServiceExt: SelectionServiceExt + selectionServiceExt: SelectionServiceExt, + clipboard: ClipboardExt ): PluginAPIFactory { const commandRegistry = rpc.set(MAIN_RPC_CONTEXT.COMMAND_REGISTRY_EXT, new CommandRegistryImpl(rpc, selectionServiceExt)); @@ -480,7 +482,7 @@ export function createAPIFactory( get machineId() { return envExt.machineId; }, get sessionId() { return envExt.sessionId; }, get uriScheme() { return envExt.uriScheme; }, - + clipboard, getEnvVariable(envVarName: string): PromiseLike { return envExt.getEnvVariable(envVarName); }, diff --git a/packages/plugin/src/theia.d.ts b/packages/plugin/src/theia.d.ts index e364a7fd7ef7d..f0870c9a53f29 100644 --- a/packages/plugin/src/theia.d.ts +++ b/packages/plugin/src/theia.d.ts @@ -2940,6 +2940,24 @@ declare module '@theia/plugin' { deserializeWebviewPanel(webviewPanel: WebviewPanel, state: any): PromiseLike; } + /** + * The clipboard provides read and write access to the system's clipboard. + */ + export interface Clipboard { + + /** + * Read the current clipboard contents as text. + * @returns A thenable that resolves to a string. + */ + readText(): PromiseLike; + + /** + * Writes text into the clipboard. + * @returns A thenable that resolves when writing happened. + */ + writeText(value: string): PromiseLike; + } + /** * A uri handler is responsible for handling system-wide [uris](#Uri). * @@ -3911,11 +3929,11 @@ declare module '@theia/plugin' { } /** - * A type that filesystem providers should use to signal errors. - * - * This class has factory methods for common error-cases, like `EntryNotFound` when - * a file or folder doesn't exist, use them like so: `throw vscode.FileSystemError.EntryNotFound(someUri);` - */ + * A type that filesystem providers should use to signal errors. + * + * This class has factory methods for common error-cases, like `EntryNotFound` when + * a file or folder doesn't exist, use them like so: `throw vscode.FileSystemError.EntryNotFound(someUri);` + */ export class FileSystemError extends Error { /** @@ -4492,6 +4510,11 @@ declare module '@theia/plugin' { */ export const language: string; + /** + * The system clipboard. + */ + export const clipboard: Clipboard; + /** * A unique identifier for the computer. */ @@ -7172,9 +7195,9 @@ declare module '@theia/plugin' { resolveDebugConfiguration?(folder: WorkspaceFolder | undefined, debugConfiguration: DebugConfiguration, token?: CancellationToken): ProviderResult; } - /** - * A Debug Adapter Tracker is a means to track the communication between VS Code and a Debug Adapter. - */ + /** + * A Debug Adapter Tracker is a means to track the communication between VS Code and a Debug Adapter. + */ export interface DebugAdapterTracker { /** * A session with the debug adapter is about to be started. @@ -7246,9 +7269,9 @@ declare module '@theia/plugin' { readonly options?: DebugAdapterExecutableOptions; } - /** - * Options for a debug adapter executable. - */ + /** + * Options for a debug adapter executable. + */ export interface DebugAdapterExecutableOptions { /**