mirror of
https://github.com/sotashimozono/obsidian-remote-ssh.git
synced 2026-07-22 06:52:07 +00:00
1 commit
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
e44c476c68
|
feat: integrated remote terminal pane (xterm.js + ssh2 shell channel) (#149) (#234)
Closes #149. Adds a single-pane remote terminal in the right sidebar, backed by xterm.js and an ssh2 PTY channel multiplexed onto the already-authenticated SSH connection that SftpClient owns. ## Files - `src/ssh/SftpClient.ts` — `openShell({rows, cols, term?, cmd?})` method (mirrors existing `openUnixStream` / `exec` shape). When `cmd` is supplied, runs it under the PTY (e.g. `/usr/bin/zsh -l`); otherwise the remote launches the user's default login shell. - `src/ssh/RemoteShell.ts` (NEW) — thin lifecycle wrapper around the ssh2 ClientChannel. `open() / write() / resize() / close() / isOpen()`. Stdout + stderr both surface as a single `onData(chunk)`. Two synchronous-flag guards keep `open()` race-free: `opening` blocks concurrent open() calls before they reach the await; the post-await `if (this.closed) ch.end()` check prevents a channel leak when close() runs while open() is pending. - `src/ui/RemoteTerminalView.ts` (NEW) — `extends ItemView`, renders xterm.js + FitAddon, debounced 100 ms ResizeObserver → setWindow, graceful "Not connected" / "Failed to open shell" disconnected states. Reads font/scrollback/shell from plugin settings each `onOpen`. The `onClose` callback logs `reason`/`cause` unconditionally so the channel-close audit trail isn't lost when the View has torn down. The `shell.open()` catch block disposes + nulls shell/term/fit so late channel events can't reach a half-constructed view. - `src/main.ts` — `registerView(VIEW_TYPE_REMOTE_TERMINAL, ...)` plus `addCommand("Open remote terminal")` (checkCallback gates on client.isAlive). `disconnect()` calls `app.workspace.detachLeavesOfType(VIEW_TYPE_REMOTE_TERMINAL)` so the shell channel close fires while ssh2.Client is still around. `openRemoteTerminal()` uses `setActiveLeaf` rather than `revealLeaf` to keep the minAppVersion 1.4.0 contract (revealLeaf needs 1.7.2). `openingTerminal` re-entrant flag prevents two leaves being created by rapid command-palette activations. - `src/types.ts` + `src/constants.ts` — three new optional settings: `terminalShell?: string`, `terminalFontSize?: number` (default 12), `terminalScrollback?: number` (default 1000). - `src/settings/SettingsTab.ts` — Terminal section with the three inputs (font validated 6-32 px, scrollback 100-100_000 lines). - `styles.css` — terminal pane host/disconnected styling + inlined xterm.js v5 default stylesheet (~3 KB, MIT license preserved) so users don't need a separate CSS asset. - `package.json` — `@xterm/xterm@^5.5` + `@xterm/addon-fit@^0.10` added as production dependencies. - `.github/workflows/release.yml` + `.github/workflows/ci.yml` — bundle cap raised 600 -> 800 KB to absorb the bundled xterm (770 KB actual). esbuild splitting needs ESM which Obsidian doesn't support, so single-bundle is the only option. Comment in workflows explains the rationale. - `tests/RemoteShell.test.ts` (NEW) — 20 unit tests against a fake ssh2 ClientChannel: open/double-open guards, concurrent-open guard, close-while-pending end()s the channel + bypasses listener-attach, stdout+stderr routing, write/resize/close, lifecycle invariants (no double-onClose, suppressed-after-close events, etc.). - `vitest.config.ts` — `RemoteTerminalView.ts` added to coverage exclude (xterm.js + ResizeObserver under jsdom is impractical to unit-test; covered by manual smoke against a real Obsidian window, consistent with the other src/ui/*Modal/Bar exclusions). ## Verified - `tsc --noEmit` clean - `npm run lint` clean (sentence-case + active-window-timers + no-floating-promises + minAppVersion gates all satisfied) - `vitest run` 780 pass / 1 Windows-skipped (was 760) - `node esbuild.config.mjs production` -> 770 KB main.js (under 800 KB cap) ## Out of scope (per #149 v1) - Multiple terminal tabs - Persisted scrollback across re-opens - Split panes - Search-in-scrollback - back-pressure on huge pastes (ssh2 handles it; we trust write() return) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |