From 17e20847d6f8947a6b5bc7f3a643fa6bda004490 Mon Sep 17 00:00:00 2001 From: iliya Date: Fri, 27 Feb 2026 14:44:02 +0200 Subject: [PATCH] improvemtns --- package.json | 1 + pnpm-lock.yaml | 8 ++++++++ .../components/terminal/EmbeddedTerminal.tsx | 20 +++++++++++++++++++ .../components/terminal/TerminalLogPanel.tsx | 8 ++++++++ 4 files changed, 37 insertions(+) diff --git a/package.json b/package.json index 679f1dfb..f3965ad1 100644 --- a/package.json +++ b/package.json @@ -101,6 +101,7 @@ "@radix-ui/react-tooltip": "^1.2.8", "@tanstack/react-virtual": "^3.10.8", "@xterm/addon-fit": "^0.11.0", + "@xterm/addon-web-links": "^0.12.0", "@xterm/xterm": "^6.0.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2ecedb1d..2f77cab5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -128,6 +128,9 @@ importers: '@xterm/addon-fit': specifier: ^0.11.0 version: 0.11.0 + '@xterm/addon-web-links': + specifier: ^0.12.0 + version: 0.12.0 '@xterm/xterm': specifier: ^6.0.0 version: 6.0.0 @@ -2133,6 +2136,9 @@ packages: '@xterm/addon-fit@0.11.0': resolution: {integrity: sha512-jYcgT6xtVYhnhgxh3QgYDnnNMYTcf8ElbxxFzX0IZo+vabQqSPAjC3c1wJrKB5E19VwQei89QCiZZP86DCPF7g==} + '@xterm/addon-web-links@0.12.0': + resolution: {integrity: sha512-4Smom3RPyVp7ZMYOYDoC/9eGJJJqYhnPLGGqJ6wOBfB8VxPViJNSKdgRYb8NpaM6YSelEKbA2SStD7lGyqaobw==} + '@xterm/xterm@6.0.0': resolution: {integrity: sha512-TQwDdQGtwwDt+2cgKDLn0IRaSxYu1tSUjgKarSDkUM0ZNiSRXFpjxEsvc/Zgc5kq5omJ+V0a8/kIM2WD3sMOYg==} @@ -7624,6 +7630,8 @@ snapshots: '@xterm/addon-fit@0.11.0': {} + '@xterm/addon-web-links@0.12.0': {} + '@xterm/xterm@6.0.0': {} abbrev@1.1.1: {} diff --git a/src/renderer/components/terminal/EmbeddedTerminal.tsx b/src/renderer/components/terminal/EmbeddedTerminal.tsx index f30994cd..57d15f48 100644 --- a/src/renderer/components/terminal/EmbeddedTerminal.tsx +++ b/src/renderer/components/terminal/EmbeddedTerminal.tsx @@ -4,6 +4,7 @@ import { useEffect, useRef } from 'react'; import { api } from '@renderer/api'; import { FitAddon } from '@xterm/addon-fit'; +import { WebLinksAddon } from '@xterm/addon-web-links'; import { Terminal } from '@xterm/xterm'; import type { PtySpawnOptions } from '@shared/types/terminal'; @@ -51,11 +52,30 @@ export const EmbeddedTerminal = ({ const fitAddon = new FitAddon(); term.loadAddon(fitAddon); + + // Clickable URLs — opens in external browser + const webLinksAddon = new WebLinksAddon((_event, uri) => { + void api.openExternal(uri); + }); + term.loadAddon(webLinksAddon); + term.open(container); // Fit after opening so dimensions are correct const rafId = requestAnimationFrame(() => fitAddon.fit()); + // Ctrl+C with selection → copy to clipboard (instead of sending SIGINT) + term.attachCustomKeyEventHandler((event) => { + if (event.type === 'keydown' && event.key === 'c' && (event.ctrlKey || event.metaKey)) { + const selection = term.getSelection(); + if (selection) { + void navigator.clipboard.writeText(selection); + return false; // Prevent sending to PTY + } + } + return true; + }); + // User input → PTY (returns IDisposable — must dispose in cleanup) const inputDisposable = term.onData((data) => { if (ptyId) api.terminal.write(ptyId, data); diff --git a/src/renderer/components/terminal/TerminalLogPanel.tsx b/src/renderer/components/terminal/TerminalLogPanel.tsx index ea9d41c4..7d769d81 100644 --- a/src/renderer/components/terminal/TerminalLogPanel.tsx +++ b/src/renderer/components/terminal/TerminalLogPanel.tsx @@ -2,7 +2,9 @@ import '@xterm/xterm/css/xterm.css'; import { useEffect, useRef } from 'react'; +import { api } from '@renderer/api'; import { FitAddon } from '@xterm/addon-fit'; +import { WebLinksAddon } from '@xterm/addon-web-links'; import { Terminal } from '@xterm/xterm'; interface TerminalLogPanelProps { @@ -40,6 +42,12 @@ export const TerminalLogPanel = ({ const fitAddon = new FitAddon(); term.loadAddon(fitAddon); + + const webLinksAddon = new WebLinksAddon((_event, uri) => { + void api.openExternal(uri); + }); + term.loadAddon(webLinksAddon); + term.open(container); const rafId = requestAnimationFrame(() => fitAddon.fit());