feat(config-toggle): add interactive TUI for toggling opinionated component settings #43

Merged
rootiest merged 16 commits from feat/config-toggle into main 2026-06-11 07:19:55 +00:00
Owner

Summary

  • Add config-toggle — a full ANSI TUI (pure fish, no external deps beyond stty/dd/od) for toggling all 7 opinionated component variables (the six C1–C6 categories plus the master __fish_config_opinionated) without typing variable names
  • Two scope tabs (Universal / Session) switchable with Tab; changes apply immediately via set -U / set -g — no confirm step. The two scopes are independent (e.g. Integrations off universally, on for this session)
  • Three states per variable — ON, OFF, DEFAULT — set directionally along an OFF ← DEFAULT → ON scale (clamped at the ends). DEFAULT erases the variable so the master switch / built-in default takes over
  • Fully hjkl-consistent keymap (see below)

Keymap

Key Action
or k j Move cursor
or h l Set value: OFF ← DEFAULT → ON (clamped)
Tab Switch scope (Universal ↔ Session)
q / Esc Exit

Files

  • functions/config-toggle.fish — entry point: arg/help parsing, terminal setup, event loop
  • functions/__config_toggle_read_key.fish — raw-tty single-keypress reader (see note below)
  • functions/__config_toggle_draw.fish — bordered panel renderer
  • functions/__config_toggle_get_val.fish — scope-aware value reader (parses set --show)
  • functions/__config_toggle_apply.fish — shared scope-aware set -U/-g/-Ue/-eg apply logic
  • docs/fish-config.md + docs/fish-config.index — offline docs entry; README.md — Minimal Mode tip

Implementation note: why not fish read?

fish's read builtin invokes its interactive line editor on a TTY, which prints a read> prompt, intercepts Tab and arrow keys for its own editing (so they never reach the handler), and shifts the cursor — breaking in-place redraws. __config_toggle_read_key instead puts the terminal in raw mode (stty raw -echo min 1 time 1), reads one keypress from /dev/tty, and decodes the raw bytes into normalized tokens (up/down/left/right/tab/escape/quit/literal char). This makes arrow keys and Tab work, removes the stray prompt, keeps the redraw aligned, and lets bare Esc exit cleanly (Ctrl-C in raw mode arrives as byte 3 → handled as quit).

Manual Verification

  • Run config-toggle — panel renders once with a clean border, 7 rows (Aliases → Master), Universal tab active, no read> line, no flicker/stacking
  • Press / and j/k — cursor moves through rows, clamped at the first and last row
  • Press /l on a row — value steps OFF → DEFAULT → ON and stops at ON (no wrap)
  • Press /h on a row — value steps ON → DEFAULT → OFF and stops at OFF (no wrap)
  • After changing a value, run set --show <var> in another shell — the universal variable reflects the change immediately
  • Press Tab — scope switches between Universal and Session; the highlighted tab updates
  • Set a category ON in Universal and OFF in Session (Tab between scopes) — both held independently; confirm with set --show __fish_config_op_aliases
  • Press q, Esc, and Ctrl-C (separately) — each exits cleanly, cursor restored, prompt on a fresh line
  • Run config-toggle -h — prints the keymap/help to stdout without entering the TUI
  • Run help config toggle — jumps to the config-toggle section in the offline docs
## Summary - Add `config-toggle` — a full ANSI TUI (pure fish, no external deps beyond `stty`/`dd`/`od`) for toggling all 7 opinionated component variables (the six C1–C6 categories plus the master `__fish_config_opinionated`) without typing variable names - Two scope tabs (**Universal** / **Session**) switchable with `Tab`; changes apply immediately via `set -U` / `set -g` — no confirm step. The two scopes are independent (e.g. Integrations off universally, on for this session) - Three states per variable — `ON`, `OFF`, `DEFAULT` — set **directionally** along an `OFF ← DEFAULT → ON` scale (clamped at the ends). `DEFAULT` erases the variable so the master switch / built-in default takes over - Fully `hjkl`-consistent keymap (see below) ### Keymap | Key | Action | |---|---| | `↑` `↓` or `k` `j` | Move cursor | | `←` `→` or `h` `l` | Set value: `OFF ← DEFAULT → ON` (clamped) | | `Tab` | Switch scope (Universal ↔ Session) | | `q` / `Esc` | Exit | ### Files - `functions/config-toggle.fish` — entry point: arg/help parsing, terminal setup, event loop - `functions/__config_toggle_read_key.fish` — raw-tty single-keypress reader (see note below) - `functions/__config_toggle_draw.fish` — bordered panel renderer - `functions/__config_toggle_get_val.fish` — scope-aware value reader (parses `set --show`) - `functions/__config_toggle_apply.fish` — shared scope-aware `set -U`/`-g`/`-Ue`/`-eg` apply logic - `docs/fish-config.md` + `docs/fish-config.index` — offline docs entry; `README.md` — Minimal Mode tip ### Implementation note: why not fish `read`? fish's `read` builtin invokes its interactive line editor on a TTY, which prints a `read> ` prompt, **intercepts Tab and arrow keys** for its own editing (so they never reach the handler), and shifts the cursor — breaking in-place redraws. `__config_toggle_read_key` instead puts the terminal in raw mode (`stty raw -echo min 1 time 1`), reads one keypress from `/dev/tty`, and decodes the raw bytes into normalized tokens (`up`/`down`/`left`/`right`/`tab`/`escape`/`quit`/literal char). This makes arrow keys and Tab work, removes the stray prompt, keeps the redraw aligned, and lets bare `Esc` exit cleanly (Ctrl-C in raw mode arrives as byte 3 → handled as quit). ## Manual Verification - [x] Run `config-toggle` — panel renders once with a clean border, 7 rows (Aliases → Master), Universal tab active, no `read>` line, no flicker/stacking - [x] Press `↓`/`↑` and `j`/`k` — cursor moves through rows, clamped at the first and last row - [x] Press `→`/`l` on a row — value steps `OFF → DEFAULT → ON` and stops at `ON` (no wrap) - [x] Press `←`/`h` on a row — value steps `ON → DEFAULT → OFF` and stops at `OFF` (no wrap) - [x] After changing a value, run `set --show <var>` in another shell — the universal variable reflects the change immediately - [x] Press `Tab` — scope switches between Universal and Session; the highlighted tab updates - [x] Set a category `ON` in Universal and `OFF` in Session (Tab between scopes) — both held independently; confirm with `set --show __fish_config_op_aliases` - [x] Press `q`, `Esc`, and `Ctrl-C` (separately) — each exits cleanly, cursor restored, prompt on a fresh line - [x] Run `config-toggle -h` — prints the keymap/help to stdout without entering the TUI - [x] Run `help config toggle` — jumps to the config-toggle section in the offline docs
rootiest added 10 commits 2026-06-11 05:40:43 +00:00
Interactive TUI for toggling opinionated component categories and master
variable. Tabbed Universal/Session scope, arrow-key navigation, Space to
cycle ON/OFF/DEFAULT with immediate apply.
6-task plan: scope-value reader, panel renderer, skeleton/help,
event loop + navigation, cycling + immediate apply, docs update.
Replace two-read-k-1 ESC handling with a single read -k 3 so that a bare
ESC returns immediately instead of blocking the shell indefinitely waiting
for a second byte.  Add trap - INT in the cleanup block so the signal
handler does not leak into the global session after config-toggle exits.
rootiest added 1 commit 2026-06-11 06:51:08 +00:00
fish's read -k N waits for exactly N bytes; single-char keys ('q', 'j',
Space, Tab) were blocking indefinitely waiting for 2 more bytes, causing
the input loop to appear unresponsive. Random 3-key bundles were then
processed as unrecognised input, triggering infinite redraws.

Switch to read -k 1 (one raw byte per call). Arrow keys still work: the
terminal sends ESC+[+A/B as a burst, so after reading ESC the two
continuation bytes are already in the TTY buffer and the follow-up reads
return immediately. Bare ESC now passes the next keypress through as the
effective key rather than exiting (q/Q remain the exit keys).
rootiest added 1 commit 2026-06-11 06:56:21 +00:00
The event loop used `read -k 1`, but `-k` is not a valid fish read option
(it errors with "unknown option", status 2). read therefore returned
instantly every iteration without consuming a keypress, so the loop
redrew the panel as fast as the terminal could render and ignored all
input. The nchars flag is `-n`, not `-k`.

Switch to `read -s -n 1` (one char, silent so keystrokes don't garble the
panel) and add `or break` so a read failure (EOF / non-tty stdin) exits
cleanly instead of spinning. Applies to the primary read and both escape
sequence continuation reads.
rootiest added 1 commit 2026-06-11 07:03:39 +00:00
fish's `read` invokes its interactive line editor on a TTY, which (1)
prints a `read> ` prompt below the panel, (2) intercepts Tab and arrow
keys for its own line editing so they never reach the switch, and (3)
shifts the cursor down a line, throwing off the `\e[14A` redraw so top
borders stacked on every keypress. Plain keys (j/k/q/space) passed
through, masking the problem.

Add __config_toggle_read_key: puts the terminal in raw, no-echo mode
(`stty raw -echo min 1 time 1`), reads one keypress from /dev/tty, and
returns a normalized token (up/down/left/right/tab/space/escape/quit or
the literal char) by decoding the bytes via od. Arrow keys and Tab now
work, there is no stray prompt, and the redraw stays aligned. Ctrl-C in
raw mode arrives as byte 3 and maps to quit; bare Esc exits after the
0.1s inter-byte timer.

Rewrite the event loop to consume these tokens and restore Esc to the
help text.
rootiest added 1 commit 2026-06-11 07:07:24 +00:00
Left/Right now adjust the highlighted value one step along the
OFF ← DEFAULT → ON scale, clamped at the ends (no wrap), complementing
Space which cycles through all states and wraps. Right steps toward ON,
Left toward OFF.

Extract the set/erase side-effect into __config_toggle_apply so Space,
Left, and Right share one implementation of the scope-aware
set -U/-g/-Ue/-eg logic. Update the in-panel keybind hint (now
width-padded via string pad so the border stays aligned) and the help
text and offline docs to cover the new keys.
rootiest added 1 commit 2026-06-11 07:10:55 +00:00
Directional arrows fully cover value changes, so the Space cycle is
removed. Add h/l as vim-style aliases for ←/→ (matching the existing
j/k for up/down), keeping the whole keymap hjkl-consistent. Update the
in-panel hint, help text, docstrings, and offline docs accordingly.
rootiest added 1 commit 2026-06-11 07:16:37 +00:00
The badge now positions each state by where it sits on the scale: OFF
left-aligned, DEFAULT centered, ON right-aligned. This makes the
OFF ← DEFAULT → ON ordering visible at a glance and reinforces the
direction the ←→/h l keys move.
rootiest merged commit 97f783e9a8 into main 2026-06-11 07:19:55 +00:00
rootiest deleted branch feat/config-toggle 2026-06-11 07:19:55 +00:00
Sign in to join this conversation.