From 94648eb4e9ef1743d39c6d1cd7fac2e0e4cef83f Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Mon, 8 Apr 2024 02:47:26 +0300 Subject: [PATCH] Update addon-clipboard --- addons/addon-clipboard/.gitignore | 2 +- addons/addon-clipboard/package.json | 2 +- addons/addon-clipboard/src/ClipboardAddon.ts | 117 +++++++++++++++++- .../addon-clipboard/src/ClipboardProvider.ts | 35 ------ addons/addon-clipboard/src/tsconfig.json | 19 ++- .../test/ClipboardAddon.api.ts | 2 +- addons/addon-clipboard/test/tsconfig.json | 3 +- .../typings/addon-clipboard.d.ts | 79 ++++++++++-- addons/addon-clipboard/yarn.lock | 6 +- 9 files changed, 201 insertions(+), 64 deletions(-) delete mode 100644 addons/addon-clipboard/src/ClipboardProvider.ts diff --git a/addons/addon-clipboard/.gitignore b/addons/addon-clipboard/.gitignore index a9f4ed54..3063f07d 100644 --- a/addons/addon-clipboard/.gitignore +++ b/addons/addon-clipboard/.gitignore @@ -1,2 +1,2 @@ lib -node_modules \ No newline at end of file +node_modules diff --git a/addons/addon-clipboard/package.json b/addons/addon-clipboard/package.json index 2e7cd1eb..06d0730a 100644 --- a/addons/addon-clipboard/package.json +++ b/addons/addon-clipboard/package.json @@ -21,7 +21,7 @@ "prepublishOnly": "npm run package" }, "peerDependencies": { - "xterm": "^5.3.0" + "@xterm/xterm": "^5.5.0" }, "dependencies": { "js-base64": "^3.7.5" diff --git a/addons/addon-clipboard/src/ClipboardAddon.ts b/addons/addon-clipboard/src/ClipboardAddon.ts index ccb75545..1a8c6101 100644 --- a/addons/addon-clipboard/src/ClipboardAddon.ts +++ b/addons/addon-clipboard/src/ClipboardAddon.ts @@ -3,18 +3,125 @@ * @license MIT */ -import { ClipboardProvider } from './ClipboardProvider'; -import { IClipboardProvider, IDisposable, ITerminalAddon, Terminal } from '@xterm/xterm'; +import type { IDisposable, ITerminalAddon, Terminal } from '@xterm/xterm'; +import { type IClipboardProvider, ClipboardSelectionType } from '@xterm/addon-clipboard'; +import { Base64 as JSBase64 } from 'js-base64'; export class ClipboardAddon implements ITerminalAddon { - private _disposable: IDisposable | undefined; - constructor(private _provider: IClipboardProvider = new ClipboardProvider()) {} + private readonly _provider: IClipboardProvider; + private _terminal?: Terminal; + private _disposable?: IDisposable; + + constructor(provider: IClipboardProvider = new ClipboardProvider()) { + this._provider = provider; + } public activate(terminal: Terminal): void { - this._disposable = terminal.registerClipboardProvider(this._provider); + this._disposable = terminal.parser.registerOscHandler(52, this._setOrReportClipboard); + this._terminal = terminal; } public dispose(): void { return this._disposable?.dispose(); } + + private _setOrReportClipboard(data: string): boolean | Promise { + const args = data.split(';'); + if (args.length < 2) { + return true; + } + + const pc = args[0]; + const pd = args[1]; + if (pd.length === 0) { + return true; + } + + switch (pc) { + case ClipboardSelectionType.SYSTEM: + case ClipboardSelectionType.PRIMARY: + try { + if (pd === '?') { + // Report clipboard + return this._provider.readText(pc).then(data => { + this._terminal?.input(data, false); + return true; + }); + } + return this._provider.writeText(pc, pd).then(() => true); + } catch (e) { + console.error(e); + } + } + + return true; + } +} + +export class ClipboardProvider implements IClipboardProvider { + private _base64: IBase64; + public limit: number; + + constructor( + /** + * The base64 encoder/decoder to use. + */ + base64: IBase64 = new Base64(), + + /** + * The maximum amount of data that can be copied to the clipboard. + * Zero means no limit. + */ + limit: number = 0 // unlimited + ){ + this._base64 = base64; + this.limit = limit; + } + + public readText(selection: ClipboardSelectionType): Promise { + if (selection !== 'c') { + return Promise.resolve(''); + } + return navigator.clipboard.readText().then(this._base64.encodeText); + } + + public writeText(selection: ClipboardSelectionType, data: string): Promise { + if (selection !== 'c' || (this.limit > 0 && data.length > this.limit)) { + return Promise.resolve(); + } + try { + const text = this._base64.decodeText(data); + return navigator.clipboard.writeText(text); + } catch { + // clear the clipboard if the data is not valid base64 + return navigator.clipboard.writeText(''); + } + } +} + +export interface IBase64 { + /** + * Converts a utf-8 string to a base64 string. + * @param data The utf-8 string to convert to base64 string. + */ + encodeText(data: string): string; + + /** + * Converts a base64 string to a utf-8 string. + * @param data The base64 string to convert to utf-8 string. + */ + decodeText(data: string): string; +} + +export class Base64 implements IBase64 { + public encodeText(data: string): string { + return JSBase64.encode(data); + } + public decodeText(data: string): string { + const text = JSBase64.decode(data); + if (!JSBase64.isValid(data) || JSBase64.encode(text) !== data) { + return ''; + } + return text; + } } diff --git a/addons/addon-clipboard/src/ClipboardProvider.ts b/addons/addon-clipboard/src/ClipboardProvider.ts deleted file mode 100644 index c14dbe57..00000000 --- a/addons/addon-clipboard/src/ClipboardProvider.ts +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Copyright (c) 2023 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { Base64 } from 'js-base64'; -import { ClipboardSelectionType, IClipboardProvider } from '@xterm/xterm'; - -export class ClipboardProvider implements IClipboardProvider { - constructor( - /** - * The maximum amount of data that can be copied to the clipboard. - * Zero means no limit. - */ - public limit = 1000000 // 1MB - ){} - public readText(selection: ClipboardSelectionType): Promise { - if (selection !== 'c') { - return Promise.resolve(''); - } - return navigator.clipboard.readText().then((text) => - Base64.encode(text)); - } - public writeText(selection: ClipboardSelectionType, data: string): Promise { - if (selection !== 'c' || (this.limit > 0 && data.length > this.limit)) { - return Promise.resolve(); - } - const text = Base64.decode(data); - // clear the clipboard if the data is not valid base64 - if (!Base64.isValid(data) || Base64.encode(text) !== data) { - return navigator.clipboard.writeText(''); - } - return navigator.clipboard.writeText(text); - } -} diff --git a/addons/addon-clipboard/src/tsconfig.json b/addons/addon-clipboard/src/tsconfig.json index 55cdc7c5..b6107280 100644 --- a/addons/addon-clipboard/src/tsconfig.json +++ b/addons/addon-clipboard/src/tsconfig.json @@ -2,22 +2,24 @@ "compilerOptions": { "module": "commonjs", "target": "es2021", - "sourceMap": true, - "outDir": "../out", + "lib": [ + "dom", + "es2015" + ], "rootDir": ".", + "outDir": "../out", + "sourceMap": true, + "removeComments": true, "strict": true, - "noUnusedLocals": true, - "preserveWatchOutput": true, "types": [ "../../../node_modules/@types/mocha" ], - "baseUrl": ".", "paths": { "browser/*": [ "../../../src/browser/*" ], - "common/*": [ - "../../../src/common/*" + "@xterm/addon-clipboard": [ + "../typings/addon-clipboard.d.ts" ] } }, @@ -28,9 +30,6 @@ "references": [ { "path": "../../../src/browser" - }, - { - "path": "../../../src/common" } ] } diff --git a/addons/addon-clipboard/test/ClipboardAddon.api.ts b/addons/addon-clipboard/test/ClipboardAddon.api.ts index d6eea6ab..962cb3f2 100644 --- a/addons/addon-clipboard/test/ClipboardAddon.api.ts +++ b/addons/addon-clipboard/test/ClipboardAddon.api.ts @@ -34,7 +34,7 @@ describe('ClipboardAddon', () => { page = await context.newPage(); await page.setViewportSize({ width, height }); await page.goto(APP); - await openTerminal(page, { allowClipboardAccess: true }); + await openTerminal(page); await page.evaluate(` window.clipboardAddon = new ClipboardAddon(); window.term.loadAddon(window.clipboardAddon); diff --git a/addons/addon-clipboard/test/tsconfig.json b/addons/addon-clipboard/test/tsconfig.json index ffa1c5fa..67ad42b7 100644 --- a/addons/addon-clipboard/test/tsconfig.json +++ b/addons/addon-clipboard/test/tsconfig.json @@ -3,7 +3,7 @@ "module": "commonjs", "target": "es2021", "lib": [ - "es2021" + "es2015" ], "rootDir": ".", "outDir": "../out-test", @@ -13,6 +13,7 @@ "types": [ "../../../node_modules/@types/mocha", "../../../node_modules/@types/node", + "../../../out-test/api/TestUtils" ] }, "include": [ diff --git a/addons/addon-clipboard/typings/addon-clipboard.d.ts b/addons/addon-clipboard/typings/addon-clipboard.d.ts index 651e0a9a..06cb3703 100644 --- a/addons/addon-clipboard/typings/addon-clipboard.d.ts +++ b/addons/addon-clipboard/typings/addon-clipboard.d.ts @@ -3,14 +3,9 @@ * @license MIT */ -import { Terminal, ITerminalAddon, IClipboardProvider, ClipboardSelection as ClipboardSelectionType } from 'xterm'; +import { Terminal, ITerminalAddon } from '@xterm/xterm'; declare module '@xterm/addon-clipboard' { - export class ClipboardProvider implements IClipboardProvider{ - public readText(selection: ClipboardSelectionType): Promise; - public writeText(selection: ClipboardSelectionType, data: string): Promise; - } - /** * An xterm.js addon that enables accessing the system clipboard from * xterm.js. @@ -19,7 +14,7 @@ declare module '@xterm/addon-clipboard' { /** * Creates a new clipboard addon. */ - constructor(_provider: IClipboardProvider); + constructor(provider?: IClipboardProvider); /** * Activates the addon @@ -32,4 +27,74 @@ declare module '@xterm/addon-clipboard' { */ public dispose(): void } + + /** + * The clipboard provider interface that enables xterm.js to access the system clipboard. + */ + export class ClipboardProvider implements IClipboardProvider{ + /** + * Creates a new clipboard provider. + * @param _base64 The base64 encoder/decoder to use. + */ + constructor(base64?: IBase64, limit?: number); + + /** + * Reads text from the clipboard. + * @param selection The selection type to read from. + * @returns A promise that resolves with the text from the clipboard. + */ + public readText(selection: ClipboardSelectionType): Promise; + + /** + * Writes text to the clipboard. + * @param selection The selection type to write to. + * @param data The text to write to the clipboard. + * @returns A promise that resolves when the text has been written to the clipboard. + */ + public writeText(selection: ClipboardSelectionType, data: string): Promise; + } + + + export interface IBase64 { + /** + * Converts a utf-8 string to a base64 string. + * @param data The utf-8 string to convert to base64 string. + */ + encodeText(data: string): string; + + /** + * Converts a base64 string to a utf-8 string. + * @param data The base64 string to convert to utf-8 string. + */ + decodeText(data: string): string; + } + + export interface IClipboardProvider { + /** + * Gets the clipboard content. + * @param selection The clipboard selection to read. + * @returns A promise that resolves with the base64 encoded data. + */ + readText(selection: ClipboardSelectionType): Promise; + + /** + * Sets the clipboard content. + * @param selection The clipboard selection to set. + * @param data The base64 encoded data to set. If the data is invalid + * base64, the clipboard is cleared. + */ + writeText(selection: ClipboardSelectionType, data: string): Promise; + } + + /** + * Clipboard selection type. This is used to specify which selection buffer to + * read or write to. + * - SYSTEM `c`: The system clipboard. + * - PRIMARY `p`: The primary clipboard. This is provided for compatibility + * with Linux X11. + */ + export const enum ClipboardSelectionType { + SYSTEM = 'c', + PRIMARY = 'p', + } } diff --git a/addons/addon-clipboard/yarn.lock b/addons/addon-clipboard/yarn.lock index de7e5b43..01d54e36 100644 --- a/addons/addon-clipboard/yarn.lock +++ b/addons/addon-clipboard/yarn.lock @@ -3,6 +3,6 @@ js-base64@^3.7.5: - version "3.7.5" - resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-3.7.5.tgz#21e24cf6b886f76d6f5f165bfcd69cc55b9e3fca" - integrity sha512-3MEt5DTINKqfScXKfJFrRbxkrnk2AxPWGBL/ycjz4dK8iqiSJ06UxD8jh8xuh6p10TX4t2+7FsBYVxxQbMg+qA== + version "3.7.7" + resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-3.7.7.tgz#e51b84bf78fbf5702b9541e2cb7bfcb893b43e79" + integrity sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==