@celljs/ta-web 4.6.1 → 4.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +90 -0
- package/README.md +13 -7
- package/esm/browser/chat/chat-protocol.d.ts +4 -0
- package/esm/browser/chat/chat-view.js +4 -1
- package/esm/browser/chat/chat-view.js.map +1 -1
- package/esm/browser/chat/chat.d.ts +8 -1
- package/esm/browser/chat/chat.js +34 -15
- package/esm/browser/chat/chat.js.map +1 -1
- package/esm/browser/chat/draft-model.d.ts +15 -0
- package/esm/browser/chat/draft-model.js +53 -0
- package/esm/browser/chat/draft-model.js.map +1 -0
- package/esm/browser/components/account-menu.d.ts +3 -2
- package/esm/browser/components/account-menu.js +21 -16
- package/esm/browser/components/account-menu.js.map +1 -1
- package/esm/browser/components/app-view.js +13 -4
- package/esm/browser/components/app-view.js.map +1 -1
- package/esm/browser/components/chat-list.d.ts +3 -1
- package/esm/browser/components/chat-list.js +6 -1
- package/esm/browser/components/chat-list.js.map +1 -1
- package/esm/browser/components/composer-box.d.ts +6 -0
- package/esm/browser/components/composer-box.js +50 -13
- package/esm/browser/components/composer-box.js.map +1 -1
- package/esm/browser/components/composer.js +1 -1
- package/esm/browser/components/composer.js.map +1 -1
- package/esm/browser/components/dialog-view.d.ts +4 -1
- package/esm/browser/components/dialog-view.js +11 -7
- package/esm/browser/components/dialog-view.js.map +1 -1
- package/esm/browser/components/folder-picker.d.ts +37 -6
- package/esm/browser/components/folder-picker.js +155 -33
- package/esm/browser/components/folder-picker.js.map +1 -1
- package/esm/browser/components/notice-center.js +3 -1
- package/esm/browser/components/notice-center.js.map +1 -1
- package/esm/browser/components/remote-hosts.d.ts +7 -0
- package/esm/browser/components/remote-hosts.js +94 -0
- package/esm/browser/components/remote-hosts.js.map +1 -0
- package/esm/browser/components/remote-prompts.d.ts +7 -0
- package/esm/browser/components/remote-prompts.js +72 -0
- package/esm/browser/components/remote-prompts.js.map +1 -0
- package/esm/browser/components/session-view.js +48 -4
- package/esm/browser/components/session-view.js.map +1 -1
- package/esm/browser/components/settings-view.js +8 -1
- package/esm/browser/components/settings-view.js.map +1 -1
- package/esm/browser/components/terminal-art.d.ts +7 -0
- package/esm/browser/components/terminal-art.js +12 -0
- package/esm/browser/components/terminal-art.js.map +1 -0
- package/esm/browser/components/tool-view.js +1 -1
- package/esm/browser/components/tool-view.js.map +1 -1
- package/esm/browser/components/web-composer.d.ts +4 -1
- package/esm/browser/components/web-composer.js +2 -2
- package/esm/browser/components/web-composer.js.map +1 -1
- package/esm/browser/components/working-directory.js +1 -1
- package/esm/browser/components/working-directory.js.map +1 -1
- package/esm/browser/extension/icons.js +6 -1
- package/esm/browser/extension/icons.js.map +1 -1
- package/esm/browser/extension/web-extension-api.d.ts +120 -2
- package/esm/browser/extension/web-extensions.d.ts +3 -0
- package/esm/browser/extension/web-extensions.js +33 -1
- package/esm/browser/extension/web-extensions.js.map +1 -1
- package/esm/browser/i18n/locales.d.ts +44 -1
- package/esm/browser/i18n/messages.d.ts +43 -0
- package/esm/browser/i18n/messages.js +99 -13
- package/esm/browser/i18n/messages.js.map +1 -1
- package/esm/browser/notices/notices-protocol.d.ts +3 -0
- package/esm/browser/notices/notices.d.ts +3 -0
- package/esm/browser/notices/notices.js +15 -0
- package/esm/browser/notices/notices.js.map +1 -1
- package/esm/browser/style/base.d.ts +1 -1
- package/esm/browser/style/base.js +6 -1
- package/esm/browser/style/base.js.map +1 -1
- package/esm/browser/style/conversation.d.ts +1 -1
- package/esm/browser/style/conversation.js +16 -1
- package/esm/browser/style/conversation.js.map +1 -1
- package/esm/browser/style/layout.d.ts +1 -1
- package/esm/browser/style/layout.js +53 -15
- package/esm/browser/style/layout.js.map +1 -1
- package/esm/browser/style/shell.d.ts +1 -1
- package/esm/browser/style/shell.js +1 -0
- package/esm/browser/style/shell.js.map +1 -1
- package/esm/browser/terminal/canvas-font.d.ts +10 -0
- package/esm/browser/terminal/canvas-font.js +34 -0
- package/esm/browser/terminal/canvas-font.js.map +1 -0
- package/esm/browser/terminal/terminal-links.d.ts +18 -0
- package/esm/browser/terminal/terminal-links.js +72 -0
- package/esm/browser/terminal/terminal-links.js.map +1 -0
- package/esm/browser/terminal/terminal-theme.d.ts +8 -0
- package/esm/browser/terminal/terminal-theme.js +65 -0
- package/esm/browser/terminal/terminal-theme.js.map +1 -0
- package/esm/browser/terminal/web-terminal.d.ts +74 -0
- package/esm/browser/terminal/web-terminal.js +300 -0
- package/esm/browser/terminal/web-terminal.js.map +1 -0
- package/esm/browser/terminal/xterm.d.ts +5 -0
- package/esm/browser/terminal/xterm.js +9 -0
- package/esm/browser/terminal/xterm.js.map +1 -0
- package/esm/browser/util/desktop.d.ts +43 -0
- package/esm/browser/util/desktop.js +55 -0
- package/esm/browser/util/desktop.js.map +1 -0
- package/esm/browser/util/file-drag.d.ts +26 -0
- package/esm/browser/util/file-drag.js +73 -0
- package/esm/browser/util/file-drag.js.map +1 -0
- package/esm/browser/util/message.d.ts +1 -0
- package/esm/browser/util/message.js +7 -5
- package/esm/browser/util/message.js.map +1 -1
- package/esm/browser/util/terminal-text.d.ts +33 -0
- package/esm/browser/util/terminal-text.js +209 -0
- package/esm/browser/util/terminal-text.js.map +1 -0
- package/esm/browser/util/tool.js +16 -0
- package/esm/browser/util/tool.js.map +1 -1
- package/esm/browser/workbench-protocol.d.ts +7 -1
- package/esm/browser/workbench-protocol.js.map +1 -1
- package/esm/browser/workbench.d.ts +7 -1
- package/esm/browser/workbench.js +23 -0
- package/esm/browser/workbench.js.map +1 -1
- package/lib/browser/chat/chat-protocol.d.ts +4 -0
- package/lib/browser/chat/chat-protocol.d.ts.map +1 -1
- package/lib/browser/chat/chat-view.d.ts.map +1 -1
- package/lib/browser/chat/chat-view.js +4 -1
- package/lib/browser/chat/chat-view.js.map +1 -1
- package/lib/browser/chat/chat.d.ts +8 -1
- package/lib/browser/chat/chat.d.ts.map +1 -1
- package/lib/browser/chat/chat.js +34 -15
- package/lib/browser/chat/chat.js.map +1 -1
- package/lib/browser/chat/chat.spec.js +30 -0
- package/lib/browser/chat/chat.spec.js.map +1 -1
- package/lib/browser/chat/draft-model.d.ts +16 -0
- package/lib/browser/chat/draft-model.d.ts.map +1 -0
- package/lib/browser/chat/draft-model.js +90 -0
- package/lib/browser/chat/draft-model.js.map +1 -0
- package/lib/browser/chat/draft-model.spec.d.ts +2 -0
- package/lib/browser/chat/draft-model.spec.d.ts.map +1 -0
- package/lib/browser/chat/draft-model.spec.js +31 -0
- package/lib/browser/chat/draft-model.spec.js.map +1 -0
- package/lib/browser/components/account-menu.d.ts +3 -2
- package/lib/browser/components/account-menu.d.ts.map +1 -1
- package/lib/browser/components/account-menu.js +21 -16
- package/lib/browser/components/account-menu.js.map +1 -1
- package/lib/browser/components/app-view.d.ts.map +1 -1
- package/lib/browser/components/app-view.js +13 -4
- package/lib/browser/components/app-view.js.map +1 -1
- package/lib/browser/components/chat-list.d.ts +3 -1
- package/lib/browser/components/chat-list.d.ts.map +1 -1
- package/lib/browser/components/chat-list.js +6 -1
- package/lib/browser/components/chat-list.js.map +1 -1
- package/lib/browser/components/composer-box.d.ts +6 -0
- package/lib/browser/components/composer-box.d.ts.map +1 -1
- package/lib/browser/components/composer-box.js +51 -14
- package/lib/browser/components/composer-box.js.map +1 -1
- package/lib/browser/components/composer.d.ts.map +1 -1
- package/lib/browser/components/composer.js +1 -1
- package/lib/browser/components/composer.js.map +1 -1
- package/lib/browser/components/dialog-view.d.ts +4 -1
- package/lib/browser/components/dialog-view.d.ts.map +1 -1
- package/lib/browser/components/dialog-view.js +11 -7
- package/lib/browser/components/dialog-view.js.map +1 -1
- package/lib/browser/components/folder-picker.d.ts +37 -6
- package/lib/browser/components/folder-picker.d.ts.map +1 -1
- package/lib/browser/components/folder-picker.js +156 -33
- package/lib/browser/components/folder-picker.js.map +1 -1
- package/lib/browser/components/folder-picker.spec.js +15 -0
- package/lib/browser/components/folder-picker.spec.js.map +1 -1
- package/lib/browser/components/notice-center.d.ts.map +1 -1
- package/lib/browser/components/notice-center.js +3 -1
- package/lib/browser/components/notice-center.js.map +1 -1
- package/lib/browser/components/plus-menu.spec.d.ts +2 -0
- package/lib/browser/components/plus-menu.spec.d.ts.map +1 -0
- package/lib/browser/components/plus-menu.spec.js +18 -0
- package/lib/browser/components/plus-menu.spec.js.map +1 -0
- package/lib/browser/components/remote-hosts.d.ts +8 -0
- package/lib/browser/components/remote-hosts.d.ts.map +1 -0
- package/lib/browser/components/remote-hosts.js +130 -0
- package/lib/browser/components/remote-hosts.js.map +1 -0
- package/lib/browser/components/remote-prompts.d.ts +8 -0
- package/lib/browser/components/remote-prompts.d.ts.map +1 -0
- package/lib/browser/components/remote-prompts.js +108 -0
- package/lib/browser/components/remote-prompts.js.map +1 -0
- package/lib/browser/components/session-view.d.ts.map +1 -1
- package/lib/browser/components/session-view.js +48 -4
- package/lib/browser/components/session-view.js.map +1 -1
- package/lib/browser/components/settings-view.d.ts.map +1 -1
- package/lib/browser/components/settings-view.js +7 -0
- package/lib/browser/components/settings-view.js.map +1 -1
- package/lib/browser/components/terminal-art.d.ts +8 -0
- package/lib/browser/components/terminal-art.d.ts.map +1 -0
- package/lib/browser/components/terminal-art.js +48 -0
- package/lib/browser/components/terminal-art.js.map +1 -0
- package/lib/browser/components/tool-view.js +1 -1
- package/lib/browser/components/tool-view.js.map +1 -1
- package/lib/browser/components/web-composer.d.ts +4 -1
- package/lib/browser/components/web-composer.d.ts.map +1 -1
- package/lib/browser/components/web-composer.js +2 -2
- package/lib/browser/components/web-composer.js.map +1 -1
- package/lib/browser/components/working-directory.js +1 -1
- package/lib/browser/components/working-directory.js.map +1 -1
- package/lib/browser/extension/icons.d.ts.map +1 -1
- package/lib/browser/extension/icons.js +5 -0
- package/lib/browser/extension/icons.js.map +1 -1
- package/lib/browser/extension/web-extension-api.d.ts +120 -2
- package/lib/browser/extension/web-extension-api.d.ts.map +1 -1
- package/lib/browser/extension/web-extensions.d.ts +3 -0
- package/lib/browser/extension/web-extensions.d.ts.map +1 -1
- package/lib/browser/extension/web-extensions.js +33 -1
- package/lib/browser/extension/web-extensions.js.map +1 -1
- package/lib/browser/i18n/locales.d.ts +44 -1
- package/lib/browser/i18n/locales.d.ts.map +1 -1
- package/lib/browser/i18n/messages.d.ts +43 -0
- package/lib/browser/i18n/messages.d.ts.map +1 -1
- package/lib/browser/i18n/messages.js +99 -13
- package/lib/browser/i18n/messages.js.map +1 -1
- package/lib/browser/notices/notices-protocol.d.ts +3 -0
- package/lib/browser/notices/notices-protocol.d.ts.map +1 -1
- package/lib/browser/notices/notices.d.ts +3 -0
- package/lib/browser/notices/notices.d.ts.map +1 -1
- package/lib/browser/notices/notices.js +15 -0
- package/lib/browser/notices/notices.js.map +1 -1
- package/lib/browser/notices/notices.spec.js +8 -3
- package/lib/browser/notices/notices.spec.js.map +1 -1
- package/lib/browser/style/base.d.ts +1 -1
- package/lib/browser/style/base.d.ts.map +1 -1
- package/lib/browser/style/base.js +6 -1
- package/lib/browser/style/base.js.map +1 -1
- package/lib/browser/style/conversation.d.ts +1 -1
- package/lib/browser/style/conversation.d.ts.map +1 -1
- package/lib/browser/style/conversation.js +16 -1
- package/lib/browser/style/conversation.js.map +1 -1
- package/lib/browser/style/layout.d.ts +1 -1
- package/lib/browser/style/layout.d.ts.map +1 -1
- package/lib/browser/style/layout.js +53 -15
- package/lib/browser/style/layout.js.map +1 -1
- package/lib/browser/style/shell.d.ts +1 -1
- package/lib/browser/style/shell.d.ts.map +1 -1
- package/lib/browser/style/shell.js +1 -0
- package/lib/browser/style/shell.js.map +1 -1
- package/lib/browser/terminal/canvas-font.d.ts +11 -0
- package/lib/browser/terminal/canvas-font.d.ts.map +1 -0
- package/lib/browser/terminal/canvas-font.js +37 -0
- package/lib/browser/terminal/canvas-font.js.map +1 -0
- package/lib/browser/terminal/canvas-font.spec.d.ts +2 -0
- package/lib/browser/terminal/canvas-font.spec.d.ts.map +1 -0
- package/lib/browser/terminal/canvas-font.spec.js +32 -0
- package/lib/browser/terminal/canvas-font.spec.js.map +1 -0
- package/lib/browser/terminal/terminal-links.d.ts +19 -0
- package/lib/browser/terminal/terminal-links.d.ts.map +1 -0
- package/lib/browser/terminal/terminal-links.js +75 -0
- package/lib/browser/terminal/terminal-links.js.map +1 -0
- package/lib/browser/terminal/terminal-links.spec.d.ts +2 -0
- package/lib/browser/terminal/terminal-links.spec.d.ts.map +1 -0
- package/lib/browser/terminal/terminal-links.spec.js +30 -0
- package/lib/browser/terminal/terminal-links.spec.js.map +1 -0
- package/lib/browser/terminal/terminal-theme.d.ts +9 -0
- package/lib/browser/terminal/terminal-theme.d.ts.map +1 -0
- package/lib/browser/terminal/terminal-theme.js +68 -0
- package/lib/browser/terminal/terminal-theme.js.map +1 -0
- package/lib/browser/terminal/terminal-theme.spec.d.ts +2 -0
- package/lib/browser/terminal/terminal-theme.spec.d.ts.map +1 -0
- package/lib/browser/terminal/terminal-theme.spec.js +24 -0
- package/lib/browser/terminal/terminal-theme.spec.js.map +1 -0
- package/lib/browser/terminal/web-terminal.d.ts +75 -0
- package/lib/browser/terminal/web-terminal.d.ts.map +1 -0
- package/lib/browser/terminal/web-terminal.js +304 -0
- package/lib/browser/terminal/web-terminal.js.map +1 -0
- package/lib/browser/terminal/xterm.d.ts +6 -0
- package/lib/browser/terminal/xterm.d.ts.map +1 -0
- package/lib/browser/terminal/xterm.js +16 -0
- package/lib/browser/terminal/xterm.js.map +1 -0
- package/lib/browser/util/desktop.d.ts +44 -0
- package/lib/browser/util/desktop.d.ts.map +1 -0
- package/lib/browser/util/desktop.js +58 -0
- package/lib/browser/util/desktop.js.map +1 -0
- package/lib/browser/util/desktop.spec.d.ts +2 -0
- package/lib/browser/util/desktop.spec.d.ts.map +1 -0
- package/lib/browser/util/desktop.spec.js +67 -0
- package/lib/browser/util/desktop.spec.js.map +1 -0
- package/lib/browser/util/file-drag.d.ts +27 -0
- package/lib/browser/util/file-drag.d.ts.map +1 -0
- package/lib/browser/util/file-drag.js +76 -0
- package/lib/browser/util/file-drag.js.map +1 -0
- package/lib/browser/util/file-drag.spec.d.ts +2 -0
- package/lib/browser/util/file-drag.spec.d.ts.map +1 -0
- package/lib/browser/util/file-drag.spec.js +46 -0
- package/lib/browser/util/file-drag.spec.js.map +1 -0
- package/lib/browser/util/message.d.ts +1 -0
- package/lib/browser/util/message.d.ts.map +1 -1
- package/lib/browser/util/message.js +7 -5
- package/lib/browser/util/message.js.map +1 -1
- package/lib/browser/util/message.spec.js +2 -0
- package/lib/browser/util/message.spec.js.map +1 -1
- package/lib/browser/util/terminal-text.d.ts +34 -0
- package/lib/browser/util/terminal-text.d.ts.map +1 -0
- package/lib/browser/util/terminal-text.js +212 -0
- package/lib/browser/util/terminal-text.js.map +1 -0
- package/lib/browser/util/terminal-text.spec.d.ts +2 -0
- package/lib/browser/util/terminal-text.spec.d.ts.map +1 -0
- package/lib/browser/util/terminal-text.spec.js +54 -0
- package/lib/browser/util/terminal-text.spec.js.map +1 -0
- package/lib/browser/util/tool.d.ts.map +1 -1
- package/lib/browser/util/tool.js +16 -0
- package/lib/browser/util/tool.js.map +1 -1
- package/lib/browser/util/tool.spec.js +12 -0
- package/lib/browser/util/tool.spec.js.map +1 -1
- package/lib/browser/workbench-protocol.d.ts +7 -1
- package/lib/browser/workbench-protocol.d.ts.map +1 -1
- package/lib/browser/workbench-protocol.js.map +1 -1
- package/lib/browser/workbench.d.ts +7 -1
- package/lib/browser/workbench.d.ts.map +1 -1
- package/lib/browser/workbench.js +23 -0
- package/lib/browser/workbench.js.map +1 -1
- package/package.json +11 -7
- package/src/browser/chat/chat-protocol.ts +4 -0
- package/src/browser/chat/chat-view.tsx +5 -1
- package/src/browser/chat/chat.spec.ts +31 -0
- package/src/browser/chat/chat.ts +36 -14
- package/src/browser/chat/draft-model.spec.tsx +31 -0
- package/src/browser/chat/draft-model.tsx +63 -0
- package/src/browser/components/account-menu.tsx +23 -20
- package/src/browser/components/app-view.tsx +12 -3
- package/src/browser/components/chat-list.tsx +9 -2
- package/src/browser/components/composer-box.tsx +52 -6
- package/src/browser/components/composer.tsx +1 -0
- package/src/browser/components/dialog-view.tsx +16 -7
- package/src/browser/components/folder-picker.spec.ts +17 -0
- package/src/browser/components/folder-picker.tsx +221 -39
- package/src/browser/components/notice-center.tsx +7 -1
- package/src/browser/components/plus-menu.spec.ts +16 -0
- package/src/browser/components/remote-hosts.tsx +149 -0
- package/src/browser/components/remote-prompts.tsx +111 -0
- package/src/browser/components/session-view.tsx +65 -4
- package/src/browser/components/settings-view.tsx +8 -0
- package/src/browser/components/terminal-art.tsx +21 -0
- package/src/browser/components/tool-view.tsx +1 -1
- package/src/browser/components/web-composer.tsx +18 -3
- package/src/browser/components/working-directory.tsx +1 -1
- package/src/browser/extension/icons.ts +10 -0
- package/src/browser/extension/web-extension-api.ts +134 -2
- package/src/browser/extension/web-extensions.ts +47 -2
- package/src/browser/i18n/messages.ts +99 -13
- package/src/browser/notices/notices-protocol.ts +3 -0
- package/src/browser/notices/notices.spec.ts +9 -3
- package/src/browser/notices/notices.ts +17 -1
- package/src/browser/style/base.ts +6 -1
- package/src/browser/style/conversation.ts +16 -1
- package/src/browser/style/layout.ts +53 -15
- package/src/browser/style/shell.ts +1 -0
- package/src/browser/terminal/canvas-font.spec.ts +29 -0
- package/src/browser/terminal/canvas-font.ts +32 -0
- package/src/browser/terminal/terminal-links.spec.ts +32 -0
- package/src/browser/terminal/terminal-links.ts +75 -0
- package/src/browser/terminal/terminal-theme.spec.ts +23 -0
- package/src/browser/terminal/terminal-theme.ts +66 -0
- package/src/browser/terminal/web-terminal.ts +330 -0
- package/src/browser/terminal/xterm.ts +10 -0
- package/src/browser/util/desktop.spec.ts +68 -0
- package/src/browser/util/desktop.ts +96 -0
- package/src/browser/util/file-drag.spec.ts +48 -0
- package/src/browser/util/file-drag.ts +78 -0
- package/src/browser/util/message.spec.ts +2 -0
- package/src/browser/util/message.ts +7 -4
- package/src/browser/util/terminal-text.spec.ts +60 -0
- package/src/browser/util/terminal-text.ts +223 -0
- package/src/browser/util/tool.spec.ts +12 -0
- package/src/browser/util/tool.ts +17 -0
- package/src/browser/workbench-protocol.ts +8 -0
- package/src/browser/workbench.ts +29 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const INSTALLED = Symbol.for('ta.canvasFontTrim');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* xterm.js's WebGL renderer sets the font of its canvases as `${italic} ${weight} ${size}px ${family}`,
|
|
5
|
+
* which starts with a space when the text is not italic. Chromium 156 (Electron 45, the desktop app)
|
|
6
|
+
* rejects a font with leading spaces and keeps `10px sans-serif`: the glyphs come out tiny in cells of
|
|
7
|
+
* the right size. Setting `font` without the leading spaces makes it the font xterm.js meant; a font
|
|
8
|
+
* that was valid is unchanged. Remove when @xterm/addon-webgl writes `normal` there.
|
|
9
|
+
*/
|
|
10
|
+
export namespace CanvasFontFix {
|
|
11
|
+
export function install(prototypes: readonly (object | undefined)[] = defaults()): void {
|
|
12
|
+
for (const prototype of prototypes) {
|
|
13
|
+
const target = prototype as Record<PropertyKey, unknown> | undefined;
|
|
14
|
+
const font = target && Object.getOwnPropertyDescriptor(target, 'font');
|
|
15
|
+
if (!target || !font?.set || target[INSTALLED]) {
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
const set = font.set;
|
|
19
|
+
Object.defineProperty(target, 'font', {
|
|
20
|
+
...font,
|
|
21
|
+
set(this: object, value: unknown) {
|
|
22
|
+
set.call(this, typeof value === 'string' ? value.trimStart() : value);
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
Object.defineProperty(target, INSTALLED, { value: true });
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function defaults(): (object | undefined)[] {
|
|
30
|
+
return [globalThis.CanvasRenderingContext2D?.prototype, globalThis.OffscreenCanvasRenderingContext2D?.prototype];
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { describe, expect, it } from 'bun:test';
|
|
2
|
+
import { TerminalLinkUtil } from './terminal-links';
|
|
3
|
+
|
|
4
|
+
const texts = (line: string) => TerminalLinkUtil.find(line).map(link => line.slice(link.start, link.end));
|
|
5
|
+
|
|
6
|
+
describe('TerminalLinkUtil', () => {
|
|
7
|
+
it('finds web pages, without the punctuation after them', () => {
|
|
8
|
+
expect(texts('Local: http://localhost:5173/, docs at https://example.com/a?b=1.')).toEqual(['http://localhost:5173/', 'https://example.com/a?b=1']);
|
|
9
|
+
expect(TerminalLinkUtil.find('see (https://x.dev/a)')[0].target).toEqual({ type: 'url', url: 'https://x.dev/a' });
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('finds paths with the place compilers and grep print', () => {
|
|
13
|
+
const line = "src/app.ts:12:5 - error TS2304: Cannot find name 'x'.";
|
|
14
|
+
expect(texts(line)).toEqual(['src/app.ts:12:5']);
|
|
15
|
+
expect(TerminalLinkUtil.find(line)[0].target).toEqual({ type: 'path', path: 'src/app.ts', selection: { startLine: 12, startColumn: 5 } });
|
|
16
|
+
expect(texts('src/a.ts(10,5): error')).toEqual(['src/a.ts(10,5)']);
|
|
17
|
+
expect(TerminalLinkUtil.find('src/a.ts(10,5): error')[0].target).toMatchObject({ path: 'src/a.ts', selection: { startLine: 10, startColumn: 5 } });
|
|
18
|
+
expect(texts('lib/x.js:3:const a = 1')).toEqual(['lib/x.js:3']);
|
|
19
|
+
expect(texts('modified: ./docs/README.md and /etc/hosts, AGENTS.md.')).toEqual(['./docs/README.md', '/etc/hosts', 'AGENTS.md']);
|
|
20
|
+
expect(texts('cd docs/ ; ls')).toEqual(['docs/']);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('leaves out what is no file', () => {
|
|
24
|
+
expect(texts('v4.6.1 1.2.3 e.g. console.log ~/x deepseek-flash <inline:ta> Node.js /help and/or')).toEqual([]);
|
|
25
|
+
expect(texts('see https://example.com/src/a.ts')).toEqual(['https://example.com/src/a.ts']);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('reads the directory of OSC 7', () => {
|
|
29
|
+
expect(TerminalLinkUtil.cwdOf('file://mac.local/Users/me/my%20app')).toBe('/Users/me/my app');
|
|
30
|
+
expect(TerminalLinkUtil.cwdOf('nonsense')).toBeUndefined();
|
|
31
|
+
});
|
|
32
|
+
});
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { type LinkTarget, LinkUtil } from '../util/link';
|
|
2
|
+
|
|
3
|
+
/** A link in a line of a terminal: `text.slice(start, end)`. */
|
|
4
|
+
export interface TerminalLink {
|
|
5
|
+
start: number;
|
|
6
|
+
end: number;
|
|
7
|
+
target: LinkTarget;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const URL_START = /\b(?:https?|file):\/\//gi;
|
|
11
|
+
/** What a path may be in: anything up to a space, a quote or a bracket. */
|
|
12
|
+
const WORD = /[^\s'"`<>()[\]{}|;,]+/g;
|
|
13
|
+
/** A path at the start of a word, with the line and column a compiler or grep prints after it (`:10`, `:10:5`). */
|
|
14
|
+
const PATH = /^(?:\.{1,2}\/|\/)?(?:[\w@+.-]+\/)*[\w@+.-]+\/?(?::\d+(?::\d+)?)?/;
|
|
15
|
+
/** A path no one mistakes for a word, with or without an extension: `./x`, `../x`, `/etc/hosts` (not `/help`). */
|
|
16
|
+
const EXPLICIT = /^(?:\.{1,2}\/[^/]|\/[^/]+\/[^/])/;
|
|
17
|
+
/** TypeScript's and MSBuild's `(10,5)` right after a path. */
|
|
18
|
+
const PARENS = /^\((\d+)(?:,(\d+))?\)/;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The links of a line of a terminal, as VS Code finds them: http(s) and file URLs, and paths of files
|
|
22
|
+
* (`src/a.ts:10:5`, `./b.md`, `/etc/hosts`, `docs/`) with the place a compiler or grep prints after
|
|
23
|
+
* them. A name alone is a file only with an extension files have (LinkUtil.codeReference); a path
|
|
24
|
+
* starting with `./`, `../` or two names under `/` is one with any name.
|
|
25
|
+
*/
|
|
26
|
+
export namespace TerminalLinkUtil {
|
|
27
|
+
export function find(text: string): TerminalLink[] {
|
|
28
|
+
const links: TerminalLink[] = [];
|
|
29
|
+
for (const found of text.matchAll(URL_START)) {
|
|
30
|
+
const start = found.index;
|
|
31
|
+
const url = /^file:/i.test(found[0]) ? /^file:\/\/[^\s'"`<>]+/i.exec(text.slice(start))?.[0] : LinkUtil.bareUrl(text.slice(start));
|
|
32
|
+
const target = url && LinkUtil.parse(url);
|
|
33
|
+
if (target) {
|
|
34
|
+
links.push({ start, end: start + url.length, target });
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const inUrl = (index: number) => links.some(link => index >= link.start && index < link.end);
|
|
38
|
+
const urls = links.length;
|
|
39
|
+
for (const word of text.matchAll(WORD)) {
|
|
40
|
+
if (inUrl(word.index)) {
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
const path = PATH.exec(word[0])?.[0].replace(/[.:]+$/, '');
|
|
44
|
+
if (!path) {
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
const start = word.index;
|
|
48
|
+
let end = start + path.length;
|
|
49
|
+
let reference = path;
|
|
50
|
+
const parens = path.length === word[0].length ? PARENS.exec(text.slice(end)) : null;
|
|
51
|
+
if (parens && !/:\d+$/.test(path)) {
|
|
52
|
+
reference = `${path}:${parens[1]}${parens[2] ? `:${parens[2]}` : ''}`;
|
|
53
|
+
end += parens[0].length;
|
|
54
|
+
}
|
|
55
|
+
const target = LinkUtil.codeReference(reference) ?? (EXPLICIT.test(reference) ? LinkUtil.parse(reference) : undefined);
|
|
56
|
+
if (target) {
|
|
57
|
+
links.push({ start, end, target });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return urls === links.length ? links : links.sort((a, b) => a.start - b.start);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** `file://host/path` of OSC 7, the directory a shell says it is in. */
|
|
64
|
+
export function cwdOf(osc7: string): string | undefined {
|
|
65
|
+
const found = /^file:\/\/[^/]*(\/.*)$/i.exec(osc7.trim());
|
|
66
|
+
if (!found) {
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
return decodeURIComponent(found[1]);
|
|
71
|
+
} catch {
|
|
72
|
+
return undefined;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { describe, expect, it } from 'bun:test';
|
|
2
|
+
import { TerminalTheme } from './terminal-theme';
|
|
3
|
+
|
|
4
|
+
describe('TerminalTheme', () => {
|
|
5
|
+
it("takes the page's background and text, and VS Code's colours for the theme", () => {
|
|
6
|
+
const page: Record<string, string> = { '--ta-code': ' #161618', '--ta-text': '#e5e5e5', '--ta-mono': " 'SF Mono', monospace" };
|
|
7
|
+
const dark = TerminalTheme.of('dark', name => page[name] ?? '');
|
|
8
|
+
expect(dark.background).toBe('#161618');
|
|
9
|
+
expect(dark.foreground).toBe('#e5e5e5');
|
|
10
|
+
expect(dark.cursor).toBe('#e5e5e5');
|
|
11
|
+
expect(dark.cursorAccent).toBe('#161618');
|
|
12
|
+
expect(dark.green).toBe('#0dbc79');
|
|
13
|
+
expect(TerminalTheme.fontFamily(name => page[name] ?? '')).toBe("'SF Mono', monospace");
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('has colours of its own when the page has none yet', () => {
|
|
17
|
+
const light = TerminalTheme.of('light', () => '');
|
|
18
|
+
expect(light.background).toBe('#f7f8fa');
|
|
19
|
+
expect(light.foreground).toBe('#1a1a1a');
|
|
20
|
+
expect(light.blue).toBe('#0451a5');
|
|
21
|
+
expect(TerminalTheme.fontFamily(() => '')).toBe('monospace');
|
|
22
|
+
});
|
|
23
|
+
});
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { ITheme } from '@xterm/xterm';
|
|
2
|
+
import type { Theme } from '../settings/settings-protocol';
|
|
3
|
+
|
|
4
|
+
/** The sixteen colours of VS Code's terminal, for a dark and a light page. */
|
|
5
|
+
const ANSI: Record<Theme, ITheme> = {
|
|
6
|
+
dark: {
|
|
7
|
+
black: '#000000',
|
|
8
|
+
red: '#cd3131',
|
|
9
|
+
green: '#0dbc79',
|
|
10
|
+
yellow: '#e5e510',
|
|
11
|
+
blue: '#2472c8',
|
|
12
|
+
magenta: '#bc3fbc',
|
|
13
|
+
cyan: '#11a8cd',
|
|
14
|
+
white: '#e5e5e5',
|
|
15
|
+
brightBlack: '#666666',
|
|
16
|
+
brightRed: '#f14c4c',
|
|
17
|
+
brightGreen: '#23d18b',
|
|
18
|
+
brightYellow: '#f5f543',
|
|
19
|
+
brightBlue: '#3b8eea',
|
|
20
|
+
brightMagenta: '#d670d6',
|
|
21
|
+
brightCyan: '#29b8db',
|
|
22
|
+
brightWhite: '#e5e5e5',
|
|
23
|
+
selectionBackground: '#264f78',
|
|
24
|
+
selectionInactiveBackground: '#3a3d41',
|
|
25
|
+
scrollbarSliderBackground: 'rgba(121, 121, 121, 0.4)',
|
|
26
|
+
scrollbarSliderHoverBackground: 'rgba(100, 100, 100, 0.7)',
|
|
27
|
+
scrollbarSliderActiveBackground: 'rgba(191, 191, 191, 0.4)',
|
|
28
|
+
},
|
|
29
|
+
light: {
|
|
30
|
+
black: '#000000',
|
|
31
|
+
red: '#cd3131',
|
|
32
|
+
green: '#00bc00',
|
|
33
|
+
yellow: '#949800',
|
|
34
|
+
blue: '#0451a5',
|
|
35
|
+
magenta: '#bc05bc',
|
|
36
|
+
cyan: '#0598bc',
|
|
37
|
+
white: '#555555',
|
|
38
|
+
brightBlack: '#666666',
|
|
39
|
+
brightRed: '#cd3131',
|
|
40
|
+
brightGreen: '#14ce14',
|
|
41
|
+
brightYellow: '#b5ba00',
|
|
42
|
+
brightBlue: '#0451a5',
|
|
43
|
+
brightMagenta: '#bc05bc',
|
|
44
|
+
brightCyan: '#0598bc',
|
|
45
|
+
brightWhite: '#a5a5a5',
|
|
46
|
+
selectionBackground: '#add6ff',
|
|
47
|
+
selectionInactiveBackground: '#e5ebf1',
|
|
48
|
+
scrollbarSliderBackground: 'rgba(100, 100, 100, 0.4)',
|
|
49
|
+
scrollbarSliderHoverBackground: 'rgba(100, 100, 100, 0.7)',
|
|
50
|
+
scrollbarSliderActiveBackground: 'rgba(0, 0, 0, 0.6)',
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export namespace TerminalTheme {
|
|
55
|
+
/** The colours of a terminal on the page: its background and text are the page's (`--ta-*`), the rest VS Code's. */
|
|
56
|
+
export function of(theme: Theme, variable: (name: string) => string): ITheme {
|
|
57
|
+
const background = variable('--ta-code').trim() || (theme === 'dark' ? '#161618' : '#f7f8fa');
|
|
58
|
+
const foreground = variable('--ta-text').trim() || (theme === 'dark' ? '#e5e5e5' : '#1a1a1a');
|
|
59
|
+
return { ...ANSI[theme], background, foreground, cursor: foreground, cursorAccent: background };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** The font of the page's code: `--ta-mono`. */
|
|
63
|
+
export function fontFamily(variable: (name: string) => string): string {
|
|
64
|
+
return variable('--ta-mono').trim() || 'monospace';
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
import type { FitAddon } from '@xterm/addon-fit';
|
|
2
|
+
import type { IBufferCellPosition, ILink, Terminal } from '@xterm/xterm';
|
|
3
|
+
import type { WebTerminal, WebTerminalOptions } from '../extension/web-extension-api';
|
|
4
|
+
import { message } from '../i18n/messages';
|
|
5
|
+
import type { OpenLinkOptions } from '../links/links-protocol';
|
|
6
|
+
import { ShortcutUtil } from '../settings/agent-settings';
|
|
7
|
+
import type { TaSettings } from '../settings/settings-protocol';
|
|
8
|
+
import type { LinkTarget } from '../util/link';
|
|
9
|
+
import { TerminalLinkUtil } from './terminal-links';
|
|
10
|
+
import { TerminalTheme } from './terminal-theme';
|
|
11
|
+
|
|
12
|
+
type XtermModule = typeof import('./xterm.js');
|
|
13
|
+
|
|
14
|
+
/** Loads xterm.js the first time a terminal is made, not with the page. */
|
|
15
|
+
export namespace XtermLoader {
|
|
16
|
+
let loading: Promise<XtermModule> | undefined;
|
|
17
|
+
|
|
18
|
+
export function load(): Promise<XtermModule> {
|
|
19
|
+
loading ??= import('./xterm.js').catch(error => {
|
|
20
|
+
loading = undefined;
|
|
21
|
+
throw error;
|
|
22
|
+
});
|
|
23
|
+
return loading;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const DEFAULT_COLS = 80;
|
|
28
|
+
const DEFAULT_ROWS = 24;
|
|
29
|
+
const SCROLLBACK = 5000;
|
|
30
|
+
/** Rows of one line that wraps, at most, read for its links. */
|
|
31
|
+
const MAX_WRAPPED_ROWS = 50;
|
|
32
|
+
/** A view being dragged to a new size fits once it stops. */
|
|
33
|
+
const FIT_MS = 50;
|
|
34
|
+
|
|
35
|
+
/** Opens what a link points to, as a link of a reply (TaLinks.open). */
|
|
36
|
+
export type TerminalLinkOpener = (target: LinkTarget, options: OpenLinkOptions) => void;
|
|
37
|
+
|
|
38
|
+
function cssVariable(name: string): string {
|
|
39
|
+
return getComputedStyle(document.documentElement).getPropertyValue(name);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* A terminal as VS Code's: xterm.js on the GPU (its WebGL renderer; the DOM one when the browser has
|
|
44
|
+
* no WebGL or loses it), in the theme of the page, which it follows. It opens in an element of its own
|
|
45
|
+
* that moves from view to view, so that it keeps its screen and scrollback while no view shows it.
|
|
46
|
+
* URLs and paths of files are links, opened with Cmd or Ctrl + click, from the directory the shell says
|
|
47
|
+
* it is in (OSC 7), else the one it started in.
|
|
48
|
+
*/
|
|
49
|
+
export class DefaultWebTerminal implements WebTerminal {
|
|
50
|
+
cols: number;
|
|
51
|
+
rows: number;
|
|
52
|
+
/** Where paths of its links are read from. */
|
|
53
|
+
protected cwd?: string;
|
|
54
|
+
protected term?: Terminal;
|
|
55
|
+
protected fit?: FitAddon;
|
|
56
|
+
protected module?: XtermModule;
|
|
57
|
+
protected opened = false;
|
|
58
|
+
protected disposed = false;
|
|
59
|
+
/** Focus asked for before xterm.js opened: it takes it once it does. */
|
|
60
|
+
protected wantsFocus = false;
|
|
61
|
+
/** Output that came before xterm.js loaded. */
|
|
62
|
+
protected pending: string[] = [];
|
|
63
|
+
protected readonly dataListeners = new Set<(data: string) => void>();
|
|
64
|
+
protected readonly resizeListeners = new Set<(size: { cols: number; rows: number }) => void>();
|
|
65
|
+
protected readonly host = document.createElement('div');
|
|
66
|
+
protected parent?: HTMLElement;
|
|
67
|
+
protected observer?: ResizeObserver;
|
|
68
|
+
protected fitTimer?: ReturnType<typeof setTimeout>;
|
|
69
|
+
protected unsubscribe?: () => void;
|
|
70
|
+
|
|
71
|
+
constructor(
|
|
72
|
+
protected readonly settings: TaSettings,
|
|
73
|
+
protected readonly openLink: TerminalLinkOpener,
|
|
74
|
+
options: WebTerminalOptions = {},
|
|
75
|
+
) {
|
|
76
|
+
this.cols = options.cols ?? DEFAULT_COLS;
|
|
77
|
+
this.rows = options.rows ?? DEFAULT_ROWS;
|
|
78
|
+
this.cwd = options.cwd;
|
|
79
|
+
this.host.className = 'ta-terminal';
|
|
80
|
+
XtermLoader.load().then(
|
|
81
|
+
module => this.start(module),
|
|
82
|
+
error => console.error('The terminal did not load:', error),
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
write(data: string): void {
|
|
87
|
+
if (this.term) {
|
|
88
|
+
this.term.write(data);
|
|
89
|
+
} else if (!this.disposed) {
|
|
90
|
+
this.pending.push(data);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
onData(listener: (data: string) => void): () => void {
|
|
95
|
+
this.dataListeners.add(listener);
|
|
96
|
+
return () => this.dataListeners.delete(listener);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
onResize(listener: (size: { cols: number; rows: number }) => void): () => void {
|
|
100
|
+
this.resizeListeners.add(listener);
|
|
101
|
+
return () => this.resizeListeners.delete(listener);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
attach(element: HTMLElement): () => void {
|
|
105
|
+
this.detach();
|
|
106
|
+
this.parent = element;
|
|
107
|
+
element.appendChild(this.host);
|
|
108
|
+
this.observer = new ResizeObserver(() => this.layout());
|
|
109
|
+
this.observer.observe(element);
|
|
110
|
+
this.open();
|
|
111
|
+
this.layout();
|
|
112
|
+
return () => {
|
|
113
|
+
if (this.parent === element) {
|
|
114
|
+
this.detach();
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
focus(): void {
|
|
120
|
+
if (this.opened) {
|
|
121
|
+
this.term?.focus();
|
|
122
|
+
} else {
|
|
123
|
+
this.wantsFocus = true;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
dispose(): void {
|
|
128
|
+
this.disposed = true;
|
|
129
|
+
this.detach();
|
|
130
|
+
this.unsubscribe?.();
|
|
131
|
+
this.term?.dispose();
|
|
132
|
+
this.pending = [];
|
|
133
|
+
this.dataListeners.clear();
|
|
134
|
+
this.resizeListeners.clear();
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
protected start(module: XtermModule): void {
|
|
138
|
+
if (this.disposed) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
this.module = module;
|
|
142
|
+
const term = new module.Terminal({
|
|
143
|
+
cols: this.cols,
|
|
144
|
+
rows: this.rows,
|
|
145
|
+
// The Unicode 11 widths of emoji and CJK, as programs reckon them.
|
|
146
|
+
allowProposedApi: true,
|
|
147
|
+
fontFamily: TerminalTheme.fontFamily(cssVariable),
|
|
148
|
+
fontSize: 12,
|
|
149
|
+
lineHeight: 1.2,
|
|
150
|
+
scrollback: SCROLLBACK,
|
|
151
|
+
cursorBlink: true,
|
|
152
|
+
cursorInactiveStyle: 'outline',
|
|
153
|
+
minimumContrastRatio: 4.5,
|
|
154
|
+
drawBoldTextInBrightColors: true,
|
|
155
|
+
rescaleOverlappingGlyphs: true,
|
|
156
|
+
theme: TerminalTheme.of(this.settings.state.theme, cssVariable),
|
|
157
|
+
});
|
|
158
|
+
const unicode = new module.Unicode11Addon();
|
|
159
|
+
term.loadAddon(unicode);
|
|
160
|
+
term.unicode.activeVersion = '11';
|
|
161
|
+
this.fit = new module.FitAddon();
|
|
162
|
+
term.loadAddon(this.fit);
|
|
163
|
+
// Shift+Enter as the kitty keyboard protocol writes it: a new line in the editor of pi (ta), not a send.
|
|
164
|
+
term.attachCustomKeyEventHandler(event => {
|
|
165
|
+
if (event.key === 'Enter' && event.shiftKey && !event.ctrlKey && !event.altKey && !event.metaKey) {
|
|
166
|
+
if (event.type === 'keydown') {
|
|
167
|
+
this.emitData('\x1b[13;2u');
|
|
168
|
+
}
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
return true;
|
|
172
|
+
});
|
|
173
|
+
term.onData(data => this.emitData(data));
|
|
174
|
+
// Mouse reports in the old encoding, bytes over 127: as characters, which the server sends as they are.
|
|
175
|
+
term.onBinary(data => this.emitData(data));
|
|
176
|
+
term.onResize(size => {
|
|
177
|
+
this.cols = size.cols;
|
|
178
|
+
this.rows = size.rows;
|
|
179
|
+
for (const listener of this.resizeListeners) {
|
|
180
|
+
listener(size);
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
term.parser.registerOscHandler(7, data => {
|
|
184
|
+
this.cwd = TerminalLinkUtil.cwdOf(data) ?? this.cwd;
|
|
185
|
+
return true;
|
|
186
|
+
});
|
|
187
|
+
term.registerLinkProvider({ provideLinks: (y, callback) => callback(this.linksAt(term, y)) });
|
|
188
|
+
for (const data of this.pending) {
|
|
189
|
+
term.write(data);
|
|
190
|
+
}
|
|
191
|
+
this.pending = [];
|
|
192
|
+
this.term = term;
|
|
193
|
+
this.unsubscribe = this.settings.subscribe(state => {
|
|
194
|
+
term.options.theme = TerminalTheme.of(state.theme, cssVariable);
|
|
195
|
+
});
|
|
196
|
+
this.open();
|
|
197
|
+
this.layout();
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Opens xterm.js where it can measure its font: once, the first time it is in the page with a size
|
|
202
|
+
* (a hidden tab has none; ResizeObserver calls again when it shows).
|
|
203
|
+
*/
|
|
204
|
+
protected open(): void {
|
|
205
|
+
const { term, module, parent } = this;
|
|
206
|
+
if (!term || !module || this.opened || !parent?.clientWidth || !parent.clientHeight) {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
this.opened = true;
|
|
210
|
+
term.open(this.host);
|
|
211
|
+
const input = this.host.querySelector('.xterm-helper-textarea');
|
|
212
|
+
for (const name of ['autocomplete', 'autocorrect', 'autocapitalize']) {
|
|
213
|
+
input?.setAttribute(name, 'off');
|
|
214
|
+
}
|
|
215
|
+
input?.setAttribute('spellcheck', 'false');
|
|
216
|
+
try {
|
|
217
|
+
const webgl = new module.WebglAddon();
|
|
218
|
+
webgl.onContextLoss(() => webgl.dispose());
|
|
219
|
+
term.loadAddon(webgl);
|
|
220
|
+
} catch (error) {
|
|
221
|
+
console.warn('No WebGL for the terminal; it draws with the DOM:', error);
|
|
222
|
+
}
|
|
223
|
+
// After the renderer: WebGL measures characters otherwise than the DOM, so the size it fits differs.
|
|
224
|
+
this.fit?.fit();
|
|
225
|
+
if (this.wantsFocus) {
|
|
226
|
+
this.wantsFocus = false;
|
|
227
|
+
term.focus();
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Opens it the first time its view has a size, and fits it to the view after that, once the size
|
|
233
|
+
* settled; a hidden view (no size) keeps the size it had. Timers, not frames: a window in the
|
|
234
|
+
* background gets no frames, and the terminal would stay unopened there.
|
|
235
|
+
*/
|
|
236
|
+
protected layout(): void {
|
|
237
|
+
if (!this.parent?.clientWidth || !this.parent.clientHeight) {
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
if (!this.opened) {
|
|
241
|
+
this.open();
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
clearTimeout(this.fitTimer);
|
|
245
|
+
this.fitTimer = setTimeout(() => {
|
|
246
|
+
const size = this.parent?.clientWidth && this.parent.clientHeight ? this.fit?.proposeDimensions() : undefined;
|
|
247
|
+
if (size && (size.cols !== this.term?.cols || size.rows !== this.term?.rows)) {
|
|
248
|
+
this.fit?.fit();
|
|
249
|
+
}
|
|
250
|
+
}, FIT_MS);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/** The links of the line (with the rows it wraps onto) that crosses row `y` (from 1). */
|
|
254
|
+
protected linksAt(term: Terminal, y: number): ILink[] | undefined {
|
|
255
|
+
const buffer = term.buffer.active;
|
|
256
|
+
let first = y - 1;
|
|
257
|
+
while (first > 0 && y - 1 - first < MAX_WRAPPED_ROWS && buffer.getLine(first)?.isWrapped) {
|
|
258
|
+
first--;
|
|
259
|
+
}
|
|
260
|
+
// The text of the line, and where each of its characters (UTF-16 units) is on the screen.
|
|
261
|
+
let text = '';
|
|
262
|
+
const places: (IBufferCellPosition & { width: number })[] = [];
|
|
263
|
+
for (let row = first; row - first < MAX_WRAPPED_ROWS; row++) {
|
|
264
|
+
const line = buffer.getLine(row);
|
|
265
|
+
if (!line || (row > first && !line.isWrapped)) {
|
|
266
|
+
break;
|
|
267
|
+
}
|
|
268
|
+
for (let x = 0; x < line.length; x++) {
|
|
269
|
+
const cell = line.getCell(x);
|
|
270
|
+
const width = cell?.getWidth() ?? 1;
|
|
271
|
+
if (!cell || width === 0) {
|
|
272
|
+
// The second half of a wide character.
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
const chars = cell.getChars() || ' ';
|
|
276
|
+
for (let unit = 0; unit < chars.length; unit++) {
|
|
277
|
+
places.push({ x: x + 1, y: row + 1, width });
|
|
278
|
+
}
|
|
279
|
+
text += chars;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
const links = TerminalLinkUtil.find(text).filter(link => places[link.start].y <= y && places[link.end - 1].y >= y);
|
|
283
|
+
if (!links.length) {
|
|
284
|
+
return undefined;
|
|
285
|
+
}
|
|
286
|
+
const mac = ShortcutUtil.isMac();
|
|
287
|
+
return links.map(link => {
|
|
288
|
+
const start = places[link.start];
|
|
289
|
+
const last = places[link.end - 1];
|
|
290
|
+
const hint =
|
|
291
|
+
link.target.type === 'path'
|
|
292
|
+
? mac
|
|
293
|
+
? 'ta.terminal.openLinkMac'
|
|
294
|
+
: 'ta.terminal.openLink'
|
|
295
|
+
: mac
|
|
296
|
+
? 'ta.terminal.openUrlMac'
|
|
297
|
+
: 'ta.terminal.openUrl';
|
|
298
|
+
return {
|
|
299
|
+
range: { start: { x: start.x, y: start.y }, end: { x: last.x + last.width - 1, y: last.y } },
|
|
300
|
+
text: text.slice(link.start, link.end),
|
|
301
|
+
decorations: { underline: true, pointerCursor: true },
|
|
302
|
+
activate: (event: MouseEvent) => {
|
|
303
|
+
if (mac ? event.metaKey : event.ctrlKey) {
|
|
304
|
+
this.openLink(link.target, { cwd: this.cwd, external: event.shiftKey && link.target.type !== 'path' });
|
|
305
|
+
}
|
|
306
|
+
},
|
|
307
|
+
hover: () => {
|
|
308
|
+
this.host.title = message(this.settings.state.language, hint);
|
|
309
|
+
},
|
|
310
|
+
leave: () => {
|
|
311
|
+
this.host.title = '';
|
|
312
|
+
},
|
|
313
|
+
};
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
protected detach(): void {
|
|
318
|
+
this.observer?.disconnect();
|
|
319
|
+
this.observer = undefined;
|
|
320
|
+
clearTimeout(this.fitTimer);
|
|
321
|
+
this.host.remove();
|
|
322
|
+
this.parent = undefined;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
protected emitData(data: string): void {
|
|
326
|
+
for (const listener of this.dataListeners) {
|
|
327
|
+
listener(data);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// What a terminal loads the first time one is made: xterm.js, its WebGL renderer and its styles.
|
|
2
|
+
import '@xterm/xterm/css/xterm.css';
|
|
3
|
+
import { CanvasFontFix } from './canvas-font';
|
|
4
|
+
|
|
5
|
+
CanvasFontFix.install();
|
|
6
|
+
|
|
7
|
+
export { FitAddon } from '@xterm/addon-fit';
|
|
8
|
+
export { Unicode11Addon } from '@xterm/addon-unicode11';
|
|
9
|
+
export { WebglAddon } from '@xterm/addon-webgl';
|
|
10
|
+
export { Terminal } from '@xterm/xterm';
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { describe, expect, it } from 'bun:test';
|
|
2
|
+
import { DesktopUtil } from './desktop';
|
|
3
|
+
|
|
4
|
+
describe('DesktopUtil', () => {
|
|
5
|
+
it('reads the features Ta Desktop gives its page', () => {
|
|
6
|
+
expect(DesktopUtil.has('webview', { taDesktop: { features: ['webview'] } })).toBe(true);
|
|
7
|
+
expect(DesktopUtil.has('debugWebview', { taDesktop: { features: ['webview'] } })).toBe(false);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it('has none in a browser, or from what is not a list', () => {
|
|
11
|
+
expect(DesktopUtil.has('webview', {})).toBe(false);
|
|
12
|
+
expect(DesktopUtil.has('webview', { taDesktop: {} })).toBe(false);
|
|
13
|
+
expect(DesktopUtil.has('webview', { taDesktop: { features: 'webview' } })).toBe(false);
|
|
14
|
+
expect(DesktopUtil.has('webview')).toBe(false);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('gives the file manager of Ta Desktop, and none without it', async () => {
|
|
18
|
+
const shown: string[] = [];
|
|
19
|
+
const scope = { taDesktop: { features: ['showItemInFolder'], platform: 'darwin', showItemInFolder: async (path: string) => shown.push(path) } };
|
|
20
|
+
const manager = DesktopUtil.fileManager(scope);
|
|
21
|
+
expect(manager?.platform).toBe('darwin');
|
|
22
|
+
await manager?.showItemInFolder('/work/app');
|
|
23
|
+
expect(shown).toEqual(['/work/app']);
|
|
24
|
+
expect(DesktopUtil.fileManager({ taDesktop: { features: ['webview'], showItemInFolder: () => undefined } })).toBeUndefined();
|
|
25
|
+
expect(DesktopUtil.fileManager({})).toBeUndefined();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('takes the sign-ins of browsers through the app, and tells a refusal as nothing', async () => {
|
|
29
|
+
const asked: string[] = [];
|
|
30
|
+
const taDesktop = {
|
|
31
|
+
features: ['showItemInFolder', 'browserData'],
|
|
32
|
+
showItemInFolder: () => undefined,
|
|
33
|
+
browserData: {
|
|
34
|
+
profiles: async () => [{ id: 'chrome:Default', browser: 'Google Chrome', name: 'Work' }],
|
|
35
|
+
import: async (id: string) => {
|
|
36
|
+
asked.push(id);
|
|
37
|
+
return id === 'no' ? { cancelled: true } : { imported: 3, skipped: 1, appBound: 0 };
|
|
38
|
+
},
|
|
39
|
+
clear: async () => 'cleared',
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
const data = DesktopUtil.fileManager({ taDesktop })?.browserData;
|
|
43
|
+
expect(await data?.profiles()).toEqual([{ id: 'chrome:Default', browser: 'Google Chrome', name: 'Work' }]);
|
|
44
|
+
expect(await data?.import('chrome:Default')).toEqual({ imported: 3, skipped: 1, appBound: 0 });
|
|
45
|
+
expect(await data?.import('no')).toBeUndefined();
|
|
46
|
+
expect(await data?.clear()).toBeUndefined();
|
|
47
|
+
expect(asked).toEqual(['chrome:Default', 'no']);
|
|
48
|
+
expect(DesktopUtil.browserData({ taDesktop: { ...taDesktop, features: ['showItemInFolder'] } })).toBeUndefined();
|
|
49
|
+
expect(DesktopUtil.fileManager({ taDesktop: { ...taDesktop, features: ['showItemInFolder'] } })?.browserData).toBeUndefined();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('sends commands to a webview through the app, only when it says it can', async () => {
|
|
53
|
+
const calls: unknown[][] = [];
|
|
54
|
+
const taDesktop = {
|
|
55
|
+
features: ['webview', 'debugWebview'],
|
|
56
|
+
debugWebview: async (...args: unknown[]) => {
|
|
57
|
+
calls.push(args);
|
|
58
|
+
return { ok: 1 };
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
const debug = DesktopUtil.debugWebview({ taDesktop });
|
|
62
|
+
expect(await debug?.(3, 'Runtime.evaluate', { expression: '1' })).toEqual({ ok: 1 });
|
|
63
|
+
expect(calls).toEqual([[3, 'Runtime.evaluate', { expression: '1' }]]);
|
|
64
|
+
expect(DesktopUtil.debugWebview({ taDesktop: { features: ['webview'], debugWebview: taDesktop.debugWebview } })).toBeUndefined();
|
|
65
|
+
expect(DesktopUtil.debugWebview({ taDesktop: { features: ['debugWebview'] } })).toBeUndefined();
|
|
66
|
+
expect(DesktopUtil.debugWebview({})).toBeUndefined();
|
|
67
|
+
});
|
|
68
|
+
});
|