mirror of
https://github.com/stablyai/orca
synced 2026-04-21 14:17:16 +00:00
fix(terminal): correct shortcut docs + cover copy-selection in E2E
The table JSDoc claimed ordering matters, but `chordMatches` rejects any event with modifiers the entry doesn't require, so entries are already mutually exclusive by construction. Rewrote the comment to reflect the real invariant. docs/terminal-shortcuts.md's "Reserved shell chords" list claimed `Ctrl+K` / `Ctrl+W` always fall through, but on Linux/Windows those are `Mod+K` / `Mod+W` and the app intercepts them. Removed them from the never-intercepted bullet and added a platform caveat. Also added E2E coverage for `Mod+Shift+C` (copy-selection): replaces the `clipboard:writeText` IPC handler with a spy so the test captures the selection without mutating the real system clipboard on headful runs.
This commit is contained in:
parent
cbf68a1534
commit
6a61e1aea4
3 changed files with 70 additions and 3 deletions
|
|
@ -65,4 +65,6 @@ These fall through to xterm.js and the shell regardless of platform:
|
|||
- `Ctrl+Z` — SIGTSTP
|
||||
- `Ctrl+\` — SIGQUIT
|
||||
- `Home` / `End` — start/end of line (xterm's terminfo sequences)
|
||||
- `Ctrl+R` / `Ctrl+U` / `Ctrl+A` / `Ctrl+E` / `Ctrl+K` / `Ctrl+W` — readline
|
||||
- `Ctrl+R` / `Ctrl+U` / `Ctrl+A` / `Ctrl+E` — readline
|
||||
|
||||
Note: on Linux/Windows, `Ctrl+K` and `Ctrl+W` ARE intercepted by the app (they are `Mod+K` / `Mod+W` — see the Windows/Linux table above). On macOS, `Mod` is Cmd, so `Ctrl+K` and `Ctrl+W` fall through to readline there.
|
||||
|
|
|
|||
|
|
@ -9,8 +9,12 @@ import type { TerminalShortcutAction } from './terminal-shortcut-policy-types'
|
|||
* - `nonMac: true` → entry applies on Windows/Linux (mod = Ctrl)
|
||||
* - both → platform-agnostic chord
|
||||
*
|
||||
* Ordering matters: the first matching row wins. More specific chords (those
|
||||
* requiring Shift) must come before the base variant or they'll never fire.
|
||||
* Entries are mutually exclusive by construction: `chordMatches` (in
|
||||
* terminal-shortcut-policy.ts) rejects any event whose modifier set isn't
|
||||
* exactly what the entry requires, so a base chord (e.g. Mod+D) cannot match
|
||||
* a Shift-variant event regardless of where it sits in this array. Ordering
|
||||
* is not load-bearing for correctness — keep entries grouped by feature for
|
||||
* readability only.
|
||||
*
|
||||
* Chords that require dynamic runtime state (Mac Option-as-Alt composition,
|
||||
* which branches on `optionKeyLocation` and the `macOptionAsAlt` setting) are
|
||||
|
|
|
|||
|
|
@ -64,6 +64,52 @@ async function getPtyWrites(app: ElectronApplication): Promise<string[]> {
|
|||
})
|
||||
}
|
||||
|
||||
// Why: copySelection writes through the `clipboard:writeText` ipcMain.handle
|
||||
// channel. Replace the handler (instead of spying on Electron's clipboard
|
||||
// object) so the test both captures the call and avoids mutating the real
|
||||
// system clipboard on headful runs.
|
||||
async function installMainProcessClipboardSpy(app: ElectronApplication): Promise<void> {
|
||||
await app.evaluate(({ ipcMain }) => {
|
||||
const g = globalThis as unknown as {
|
||||
__clipboardTextLog?: string[]
|
||||
__clipboardSpyInstalled?: boolean
|
||||
}
|
||||
if (g.__clipboardSpyInstalled) {
|
||||
return
|
||||
}
|
||||
g.__clipboardTextLog = []
|
||||
g.__clipboardSpyInstalled = true
|
||||
ipcMain.removeHandler('clipboard:writeText')
|
||||
ipcMain.handle('clipboard:writeText', (_event, text: string) => {
|
||||
g.__clipboardTextLog!.push(text)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async function getClipboardWrites(app: ElectronApplication): Promise<string[]> {
|
||||
return app.evaluate(() => {
|
||||
const g = globalThis as unknown as { __clipboardTextLog?: string[] }
|
||||
return g.__clipboardTextLog ?? []
|
||||
})
|
||||
}
|
||||
|
||||
async function selectAllActiveTerminal(page: Page): Promise<void> {
|
||||
await page.evaluate(() => {
|
||||
const paneManagers = window.__paneManagers
|
||||
if (!paneManagers) {
|
||||
return
|
||||
}
|
||||
const state = window.__store?.getState()
|
||||
const tabId = state?.activeTabId
|
||||
if (!tabId) {
|
||||
return
|
||||
}
|
||||
const manager = paneManagers.get(tabId)
|
||||
const pane = manager?.getActivePane?.() ?? manager?.getPanes?.()[0]
|
||||
pane?.terminal.selectAll()
|
||||
})
|
||||
}
|
||||
|
||||
// Why: the window-level keydown handler is gated on non-editable targets; the
|
||||
// xterm helper textarea is treated as non-editable on purpose. Focusing it
|
||||
// guarantees each chord reaches the shortcut policy through the real DOM path.
|
||||
|
|
@ -131,6 +177,7 @@ test.describe('Terminal Shortcuts', () => {
|
|||
electronApp
|
||||
}) => {
|
||||
await installMainProcessPtyWriteSpy(electronApp)
|
||||
await installMainProcessClipboardSpy(electronApp)
|
||||
|
||||
// Seed the buffer so Cmd+K has something to clear.
|
||||
const ptyId = await discoverActivePtyId(orcaPage)
|
||||
|
|
@ -138,6 +185,20 @@ test.describe('Terminal Shortcuts', () => {
|
|||
await execInTerminal(orcaPage, ptyId, `echo ${marker}`)
|
||||
await waitForTerminalOutput(orcaPage, marker)
|
||||
|
||||
// --- copy-selection chord (Mod+Shift+C) ---
|
||||
// Why: must run before Cmd+K clears the buffer; selectAll + Mod+Shift+C
|
||||
// must route through the shortcut policy and write the selection through
|
||||
// the clipboard:writeText IPC (which the spy captured).
|
||||
await selectAllActiveTerminal(orcaPage)
|
||||
await focusActiveTerminal(orcaPage)
|
||||
await orcaPage.keyboard.press(`${mod}+Shift+c`)
|
||||
await expect
|
||||
.poll(async () => (await getClipboardWrites(electronApp)).some((t) => t.includes(marker)), {
|
||||
timeout: 5_000,
|
||||
message: 'Mod+Shift+C did not write the terminal selection to the clipboard'
|
||||
})
|
||||
.toBe(true)
|
||||
|
||||
// --- send-input chords (platform-agnostic) ---
|
||||
|
||||
// Alt+←/→ → readline backward-word / forward-word (\eb / \ef).
|
||||
|
|
|
|||
Loading…
Reference in a new issue