mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-08 00:19:42 +02:00
feat(tui): switch sessions with the web UI's shortcuts
Alt+1..9 switches to that session, and `[` / `]` / Tab step through them, so the
muscle memory from the web UI carries over.
Alt+N SELECTS rather than attaches, which is what the web UI's Alt+N does:
switching which tab you look at is cheap and reversible, and the terminal
equivalent is moving the selection and its preview, not handing the whole
terminal to a pane. Bare 1-9 keeps its documented jump-and-attach meaning.
Two of the web UI's chords cannot cross into a terminal, so the nearest
transmittable keys carry them instead:
Alt+[ / Alt+] ESC+[ IS the CSI introducer every arrow key arrives on, and
ESC+] is OSC, so neither chord is distinguishable from a
sequence. Bare `[` and `]` do the job.
Ctrl+Tab a terminal cannot report the Ctrl, so plain Tab carries it.
⚠️ The parser now decodes ESC + a printable character in ONE read as an Alt
chord, and the app replays every chord it does not claim as `escape` then that
character. That fallback is load-bearing, not tidiness: a real Esc landing in
the same read as the next keystroke is byte-identical to a chord, and without
the replay "Esc then q" typed quickly decoded as Alt+Q, matched nothing and was
swallowed. The e2e suite caught exactly that as the dashboard refusing to quit.
A lone Esc is still held and flushed on the caller's timer, which is what keeps
the two separable at all.
This commit is contained in:
+19
-3
@@ -38,6 +38,7 @@ export type TuiInputEvent =
|
||||
| { type: 'backspace' }
|
||||
| { type: 'escape' }
|
||||
| { type: 'ctrl'; key: string }
|
||||
| { type: 'alt'; value: string }
|
||||
| { type: 'key'; name: TuiNamedKey }
|
||||
| { type: 'mouse'; kind: TuiMouseKind; x: number; y: number; button: number };
|
||||
|
||||
@@ -107,9 +108,24 @@ export function createKeyParser(): TuiKeyParser {
|
||||
return { consumed: 3, events: name ? [{ type: 'key', name }] : NOTHING };
|
||||
}
|
||||
|
||||
// Anything that is not a CSI is a lone ESC as far as we are concerned; the
|
||||
// next byte then parses on its own (so Alt+x reads as Escape then `x`).
|
||||
if (second !== 0x5b) return { consumed: 1, events: [{ type: 'escape' }] };
|
||||
// ESC followed by a printable character IN THE SAME READ is Alt+that key:
|
||||
// that is how every terminal sends a meta chord. A lone Esc cannot look
|
||||
// like this, because a buffer holding only ESC returns 'incomplete' above
|
||||
// and is flushed as `escape` when the read ends, which is the standard way
|
||||
// to tell the two apart without a timer.
|
||||
//
|
||||
// ⚠️ Three characters are deliberately NOT treated as Alt chords, because
|
||||
// the terminal uses them to introduce sequences and a chord is
|
||||
// indistinguishable from one: `[` (CSI) and `O` (SS3) would swallow every
|
||||
// arrow key, and `]` (OSC) would swallow a terminal's colour-query reply.
|
||||
// Alt+[ and Alt+] therefore cannot exist in a terminal at all, which is why
|
||||
// the list binds bare `[` and `]` for the same job.
|
||||
if (second !== 0x5b) {
|
||||
if (second >= 0x20 && second <= 0x7e && second !== 0x4f && second !== 0x5d) {
|
||||
return { consumed: 2, events: [{ type: 'alt', value: String.fromCharCode(second) }] };
|
||||
}
|
||||
return { consumed: 1, events: [{ type: 'escape' }] };
|
||||
}
|
||||
|
||||
let j = 2;
|
||||
while (j < buf.length && buf[j] >= 0x30 && buf[j] <= 0x3f) j++;
|
||||
|
||||
Reference in New Issue
Block a user