From 87960cc8cc6a096791cb9c81461d19bd824c28d8 Mon Sep 17 00:00:00 2001 From: rootiest Date: Thu, 11 Jun 2026 00:31:49 -0400 Subject: [PATCH 01/16] docs(specs): add config-toggle design spec 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. --- docs/specs/2026-06-11-config-toggle-design.md | 128 ++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 docs/specs/2026-06-11-config-toggle-design.md diff --git a/docs/specs/2026-06-11-config-toggle-design.md b/docs/specs/2026-06-11-config-toggle-design.md new file mode 100644 index 0000000..5cbeba8 --- /dev/null +++ b/docs/specs/2026-06-11-config-toggle-design.md @@ -0,0 +1,128 @@ +# Design: `config-toggle` + +**Date:** 2026-06-11 +**Status:** Approved + +--- + +## Overview + +A new interactive TUI function, `config-toggle`, that lets users toggle the six opinionated-component category variables and the master variable without having to type or remember variable names. It is modelled on the Claude Code settings panel UX: tabbed scope selector, arrow-key row navigation, Space to cycle state, instant apply on each keypress. + +The function lives at `functions/config-toggle.fish` and follows all project conventions (license header, man-page docblock, color palette, help flag). It is **always available** regardless of the opinionated-component state — it must never be wrapped in a guard. + +--- + +## Variables Under Management + +| Label | Variable | Category | +|---|---|---| +| Aliases | `__fish_config_op_aliases` | C1 | +| Auto-exec | `__fish_config_op_autoexec` | C2 | +| Overrides | `__fish_config_op_overrides` | C3 | +| Integrations | `__fish_config_op_integrations` | C4 | +| Logging | `__fish_config_op_logging` | C5 | +| Greeting | `__fish_config_op_greeting` | C6 | +| Master | `__fish_config_opinionated` | master | + +--- + +## UI Layout + +A fixed-height bordered panel rendered with ANSI escape sequences (`tput` for terminal dimensions, `\e[` sequences for cursor positioning). The panel redraws in-place on every keystroke — no scrolling, no paging. + +``` +┌─ Opinionated Settings ───────────────────────┐ +│ ● Universal ○ Session Tab to switch │ +│──────────────────────────────────────────────│ +│ ▶ Aliases [ ON ] cmd shadows │ +│ Auto-exec [ ON ] startup │ +│ Overrides [ DEFAULT ] keys/env/prompt│ +│ Integrations [ ON ] terminal coupling│ +│ Logging [ ON ] scrollback │ +│ Greeting [ DEFAULT ] fish_greeting │ +│ ────────────────────────────────────────── │ +│ Master [ DEFAULT ] disable all │ +│──────────────────────────────────────────────│ +│ ↑↓ move Space cycle Tab scope q quit │ +└──────────────────────────────────────────────┘ +``` + +### State badge colors + +| State | Display | Color | +|---|---|---| +| `ON` | `[ ON ]` | green (`set_color green`) | +| `OFF` | `[ OFF ]` | red (`set_color red`) | +| `DEFAULT` | `[ DEFAULT ]` | dim (`set_color brblack`) | + +--- + +## Keybindings + +| Key | Action | +|---|---| +| `↑` / `k` | Move cursor up one row | +| `↓` / `j` | Move cursor down one row | +| `Space` | Cycle selected row: ON → OFF → DEFAULT → ON | +| `Tab` | Switch active scope tab (Universal ↔ Session) | +| `q` / `Escape` | Exit, leaving all values in place | + +--- + +## Scope Tabs + +Two tabs: **Universal** and **Session**. Only one is active at a time (Tab to switch). The active tab label is highlighted. + +- **Universal tab** reads/writes universal variables (`set -U` / `set -Ue`) +- **Session tab** reads/writes global variables for the current shell session (`set -g` / `set -eg`) + +Both scopes can be set independently. For example: `Integrations = off` universally and `Integrations = on` for the current session. + +--- + +## State Management & Immediate Apply + +Each Space keypress on a row runs the corresponding fish command **immediately** — no confirm step, no exit required. + +| Resulting state | Universal scope | Session scope | +|---|---|---| +| `ON` | `set -U on` | `set -g on` | +| `OFF` | `set -U off` | `set -g off` | +| `DEFAULT` | `set -Ue ` (erase) | `set -eg ` (erase) | + +The TUI re-reads the live variable value before drawing each row, so external changes are reflected on next redraw. + +--- + +## Function Signature + +```fish +config-toggle [-h | --help] +``` + +No subcommands or positional arguments. `-h`/`--help` prints usage per Convention §12 and exits. + +--- + +## Implementation Notes + +- Use `tput lines` and `tput cols` to detect terminal size; center or top-align the panel accordingly. +- Save the cursor position on entry (`\e[s` or `tput sc`), hide the cursor (`tput civis`), and restore both on exit (including on `q`, Escape, and interrupt signals via `trap`). +- Use `read -k 1` to capture single keystrokes without Enter. Handle escape sequences for arrow keys (`\e[A` = up, `\e[B` = down) by reading the next 2 bytes after `\e`. +- The Master row is separated from the category rows by a divider line. +- The panel width is fixed at 48 characters (fits an 80-column terminal comfortably). +- Inline short descriptions (e.g. "cmd shadows", "startup", "keys/env/prompt") are shown to the right of each state badge. +- `trap` on `SIGINT` and function exit to restore terminal state (show cursor, restore position) so an abrupt Ctrl-C doesn't leave the terminal broken. + +--- + +## Conventions Checklist + +- [ ] License header (Convention §1) +- [ ] Man-page docblock: SYNOPSIS, DESCRIPTION, ARGUMENTS, RETURNS, EXAMPLE (Convention §4) +- [ ] Color palette variables at top of output block (Convention §11) +- [ ] `-h`/`--help` flag (Convention §12) +- [ ] No guard wrapper (function must be always available) +- [ ] Update `docs/fish-config.md` with new function entry (Convention §10) +- [ ] Update `docs/fish-config.index` to include `config-toggle` -- 2.52.0 From aa8e4437ecbe21312b4e4876692f4af4c69b4aff Mon Sep 17 00:00:00 2001 From: rootiest Date: Thu, 11 Jun 2026 00:49:41 -0400 Subject: [PATCH 02/16] docs(plans): add config-toggle implementation plan 6-task plan: scope-value reader, panel renderer, skeleton/help, event loop + navigation, cycling + immediate apply, docs update. --- docs/plans/2026-06-11-config-toggle.md | 713 +++++++++++++++++++++++++ 1 file changed, 713 insertions(+) create mode 100644 docs/plans/2026-06-11-config-toggle.md diff --git a/docs/plans/2026-06-11-config-toggle.md b/docs/plans/2026-06-11-config-toggle.md new file mode 100644 index 0000000..ce417cc --- /dev/null +++ b/docs/plans/2026-06-11-config-toggle.md @@ -0,0 +1,713 @@ +# config-toggle Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Build `config-toggle`, an interactive TUI that lets users cycle six opinionated-category variables and the master variable between ON / OFF / DEFAULT with immediate apply, across Universal and Session scopes. + +**Architecture:** Three fish functions: the main `config-toggle` entry point handles argument parsing, terminal setup/teardown, and the event loop; `__config_toggle_draw` renders the 14-line panel in-place; `__config_toggle_get_val` reads a scope-specific variable value via `set --show` parsing. Changes apply immediately on each Space keypress via `set -U`/`set -g`/erase. + +**Tech Stack:** Fish 4.x builtins only (`read`, `set`, `string`, `printf`, `tput`). No external dependencies. + +**Spec:** `docs/specs/2026-06-11-config-toggle-design.md` + +--- + +## File Map + +| Action | Path | Purpose | +|--------|------|---------| +| Create | `functions/config-toggle.fish` | Entry point: arg parse, terminal setup/teardown, event loop | +| Create | `functions/__config_toggle_draw.fish` | Renders the 14-line TUI panel | +| Create | `functions/__config_toggle_get_val.fish` | Reads a variable's value in a specific scope | +| Modify | `docs/fish-config.md` | Add `config-toggle` entry after `config-update` (line ~1222) | +| Modify | `docs/fish-config.index` | Add `toggle` and `config-toggle` keywords | + +--- + +## Panel Layout Reference + +``` +┌─ Opinionated Settings ────────────────────────────┐ ← line 1 +│ ● Universal ○ Session Tab to switch │ ← line 2 +│──────────────────────────────────────────────────│ ← line 3 +│ ▶ Aliases [ ON ] cmd shadows │ ← line 4 +│ Auto-exec [ ON ] startup │ ← line 5 +│ Overrides [ DEFAULT ] keys/env/prompt │ ← line 6 +│ Integrations [ ON ] terminal coupling │ ← line 7 +│ Logging [ ON ] scrollback │ ← line 8 +│ Greeting [ DEFAULT ] fish_greeting │ ← line 9 +│ ────────────────────────────────────────── │ ← line 10 +│ Master [ DEFAULT ] disable all │ ← line 11 +│──────────────────────────────────────────────────│ ← line 12 +│ ↑↓/jk: move Space: cycle Tab: scope q: quit │ ← line 13 +└──────────────────────────────────────────────────┘ ← line 14 +``` + +Outer width: 52 chars. Inner content: 50 chars. Panel = **14 lines**. + +Content row layout (50 chars visible between `│`): +``` + [cursor 2] [label padded to 12] [ badge 7 ] [desc padded to 17] += 2 + 2 + 12 + 3 + 7 + 4 + 17 + 3 = 50 chars +``` + +Badge values (7 chars, right-padded): `"ON "` / `"OFF "` / `"DEFAULT"` + +--- + +## Variable Reference + +| Row idx | Label | Variable | +|---------|-------|----------| +| 0 | Aliases | `__fish_config_op_aliases` | +| 1 | Auto-exec | `__fish_config_op_autoexec` | +| 2 | Overrides | `__fish_config_op_overrides` | +| 3 | Integrations | `__fish_config_op_integrations` | +| 4 | Logging | `__fish_config_op_logging` | +| 5 | Greeting | `__fish_config_op_greeting` | +| 6 | Master | `__fish_config_opinionated` | + +State cycle on Space: `on → off → DEFAULT → on` + +Apply commands: + +| Result state | Universal scope | Session scope | +|---|---|---| +| on | `set -U on` | `set -g on` | +| off | `set -U off` | `set -g off` | +| DEFAULT | `set -Ue ` | `set -eg ` | + +--- + +## Task 1: Scope-value reader helper + +**Files:** +- Create: `functions/__config_toggle_get_val.fish` + +- [ ] **Step 1: Create the file** + +```fish +# Copyright (C) 2026 Rootiest +# SPDX-License-Identifier: AGPL-3.0-or-later + +# SYNOPSIS +# __config_toggle_get_val +# +# DESCRIPTION +# Returns the current value of a named variable in the specified scope by +# parsing `set --show` output. Outputs "on", "off", or "DEFAULT" (when +# the variable is not set in that scope). Scope "session" maps to "global" +# in fish's internal terminology. +# +# ARGUMENTS +# varname Variable name without $ prefix +# scope "universal" or "session" +# +# RETURNS +# 0 Always; prints "on", "off", or "DEFAULT" to stdout +# +# EXAMPLE +# set result (__config_toggle_get_val __fish_config_op_aliases universal) +# # result == "on" | "off" | "DEFAULT" +function __config_toggle_get_val + set -l varname $argv[1] + set -l scope $argv[2] + + # fish uses "global" in set --show output for what we call "session" + set -l scope_kw $scope + if test $scope = session + set scope_kw global + end + + set -l found 0 + for line in (set --show $varname 2>/dev/null) + if string match -q "*$scope_kw scope*" $line + set found 1 + continue + end + if test $found -eq 1 + set found 0 + # Format: $varname[1]: |value| + set -l val (string replace -r '^\$[^\[]+\[1\]: \|(.+)\|$' '$1' $line 2>/dev/null) + if test -n "$val" -a "$val" != $line + echo $val + return 0 + end + end + end + + echo DEFAULT + return 0 +end +``` + +- [ ] **Step 2: Verify it reads correctly** + +In a fish shell, run: +```fish +set -U __fish_config_op_aliases on +set result (__config_toggle_get_val __fish_config_op_aliases universal) +echo "universal: $result" # expected: on +set result (__config_toggle_get_val __fish_config_op_aliases session) +echo "session: $result" # expected: DEFAULT (no global set) +set -g __fish_config_op_aliases off +set result (__config_toggle_get_val __fish_config_op_aliases session) +echo "session: $result" # expected: off +# cleanup +set -Ue __fish_config_op_aliases +set -eg __fish_config_op_aliases +``` + +- [ ] **Step 3: Commit** + +```bash +git add functions/__config_toggle_get_val.fish +git commit -m "feat(config-toggle): add scope-specific variable reader helper" +``` + +--- + +## Task 2: Panel renderer + +**Files:** +- Create: `functions/__config_toggle_draw.fish` + +- [ ] **Step 1: Create the file** + +```fish +# Copyright (C) 2026 Rootiest +# SPDX-License-Identifier: AGPL-3.0-or-later + +# SYNOPSIS +# __config_toggle_draw ... +# +# DESCRIPTION +# Renders the 14-line config-toggle TUI panel to stdout. Intended to be +# called once on startup, then again after each keypress preceded by +# `printf '\e[14A\e[J'` (cursor-up 14 lines + clear-to-end) to redraw +# in place. Does not move the cursor — caller manages positioning. +# +# ARGUMENTS +# cur_row 0–6, the currently highlighted row +# cur_scope "universal" or "session" +# var1–var7 Variable names for rows 0–6 (in order per Variable Reference) +# +# RETURNS +# 0 Always +# +# EXAMPLE +# __config_toggle_draw 0 universal \ +# __fish_config_op_aliases __fish_config_op_autoexec \ +# __fish_config_op_overrides __fish_config_op_integrations \ +# __fish_config_op_logging __fish_config_op_greeting \ +# __fish_config_opinionated +function __config_toggle_draw + set -l cur_row $argv[1] + set -l cur_scope $argv[2] + set -l vars $argv[3..] + + set -l c_ok (set_color green) + set -l c_err (set_color red) + set -l c_dim (set_color brblack) + set -l c_sel (set_color --bold magenta) + set -l c_hi (set_color --bold white) + set -l c_head (set_color --bold cyan) + set -l c_reset (set_color normal) + + set -l labels Aliases Auto-exec Overrides Integrations Logging Greeting Master + set -l descs "cmd shadows" startup "keys/env/prompt" "terminal coupling" scrollback fish_greeting "disable all" + + # Panel dimensions + set -l IW 50 # inner width (chars between │ │) + set -l HBR (string repeat -n $IW '─') + + # ── Scope tabs ──────────────────────────────────────── + if test $cur_scope = universal + set -l u_label "$c_hi● Universal$c_reset" + set -l s_label "○ Session " + else + set -l u_label "○ Universal" + set -l s_label "$c_hi● Session $c_reset" + end + + # ── Top border ──────────────────────────────────────── + # "─ Opinionated Settings " = 23 chars; fill rest to IW-1 then ┐ + printf '┌─%s Opinionated Settings %s┐\n' \ + $c_head $c_reset(string repeat -n (math $IW - 24) '─') + + # ── Scope tab line ──────────────────────────────────── + # Content: " ● Universal ○ Session Tab to switch " (50 vis chars) + printf '│ %s %s%s│\n' \ + $u_label $s_label \ + (string repeat -n 12 ' ')"Tab to switch " + + # ── Top divider ─────────────────────────────────────── + printf '│%s│\n' $HBR + + # ── Category rows 0–5 ───────────────────────────────── + for i in (seq 0 5) + set -l idx (math $i + 1) + set -l var $vars[$idx] + set -l label $labels[$idx] + set -l desc $descs[$idx] + + set -l val (__config_toggle_get_val $var $cur_scope) + + # Badge: 7 visible chars, coloured + switch $val + case on + set -l badge "$c_ok""ON $c_reset" + case off + set -l badge "$c_err""OFF $c_reset" + case '*' + set -l badge "$c_dim""DEFAULT$c_reset" + end + + # Cursor: 2 visible chars + if test $i -eq $cur_row + set -l curs "$c_sel▶$c_reset " + else + set -l curs " " + end + + # Label padded to 12, desc padded to 17, right margin 3 + set -l lpad (string pad -r -w 12 -- $label) + set -l dpad (string pad -r -w 17 -- $desc) + + printf '│ %s%s [ %s ] %s │\n' $curs $lpad $badge $dpad + end + + # ── Separator before Master ─────────────────────────── + printf '│ %s │\n' (string repeat -n (math $IW - 6) '─') + + # ── Master row (index 6) ────────────────────────────── + set -l val (__config_toggle_get_val $vars[7] $cur_scope) + switch $val + case on + set -l badge "$c_ok""ON $c_reset" + case off + set -l badge "$c_err""OFF $c_reset" + case '*' + set -l badge "$c_dim""DEFAULT$c_reset" + end + if test $cur_row -eq 6 + set -l curs "$c_sel▶$c_reset " + else + set -l curs " " + end + printf '│ %s%s [ %s ] %s │\n' \ + $curs \ + (string pad -r -w 12 -- Master) \ + $badge \ + (string pad -r -w 17 -- "disable all") + + # ── Bottom divider ──────────────────────────────────── + printf '│%s│\n' $HBR + + # ── Keybind hint ────────────────────────────────────── + printf '│ %s↑↓/jk: move Space: cycle Tab: scope q: quit%s │\n' \ + $c_dim $c_reset + + # ── Bottom border ───────────────────────────────────── + printf '└%s┘\n' $HBR +end +``` + +- [ ] **Step 2: Smoke-test the renderer** + +In a fish shell: +```fish +source functions/__config_toggle_draw.fish +source functions/__config_toggle_get_val.fish +__config_toggle_draw 0 universal \ + __fish_config_op_aliases __fish_config_op_autoexec \ + __fish_config_op_overrides __fish_config_op_integrations \ + __fish_config_op_logging __fish_config_op_greeting \ + __fish_config_opinionated +``` + +Expected: a 14-line bordered panel. Row 0 (Aliases) has `▶` in magenta. All badges show DEFAULT (dim) since no vars are set. Check that the panel is 52 chars wide. + +- [ ] **Step 3: Verify with a value set** + +```fish +set -U __fish_config_op_aliases on +__config_toggle_draw 0 universal \ + __fish_config_op_aliases __fish_config_op_autoexec \ + __fish_config_op_overrides __fish_config_op_integrations \ + __fish_config_op_logging __fish_config_op_greeting \ + __fish_config_opinionated +set -Ue __fish_config_op_aliases +``` + +Expected: Aliases row badge shows green `ON `. All others show `DEFAULT`. + +- [ ] **Step 4: Commit** + +```bash +git add functions/__config_toggle_draw.fish +git commit -m "feat(config-toggle): add panel renderer helper" +``` + +--- + +## Task 3: Main function — skeleton, help flag, terminal setup + +**Files:** +- Create: `functions/config-toggle.fish` + +- [ ] **Step 1: Create the skeleton with help flag** + +```fish +# Copyright (C) 2026 Rootiest +# SPDX-License-Identifier: AGPL-3.0-or-later + +# SYNOPSIS +# config-toggle [-h | --help] +# +# DESCRIPTION +# Opens an interactive full-screen TUI for toggling the six opinionated +# component categories (C1–C6) and the master disable variable without +# having to remember variable names. Two scope tabs allow independent +# per-scope configuration: +# +# Universal — persists across all sessions (set -U / set -Ue) +# Session — active for the current shell only (set -g / set -eg) +# +# Changes apply immediately on each Space keypress — no confirm step. +# Always available regardless of __fish_config_opinionated state. +# +# ARGUMENTS +# -h, --help Print usage and exit +# +# RETURNS +# 0 Exited normally (q or Escape pressed) +# 1 Unknown flag passed +# +# EXAMPLE +# config-toggle +function config-toggle --description 'Interactive TUI for toggling opinionated component settings' + set -l c_head (set_color --bold cyan) + set -l c_cmd (set_color --bold white) + set -l c_flag (set_color yellow) + set -l c_dim (set_color brblack) + set -l c_err (set_color red) + set -l c_reset (set_color normal) + + # ── Argument parsing ────────────────────────────────── + for arg in $argv + switch $arg + case -h --help + echo "$c_head""Usage:$c_reset $c_cmd""config-toggle$c_reset $c_flag""[-h]$c_reset" + echo + echo " Interactive TUI for toggling opinionated component categories." + echo " Changes apply immediately — no confirm step required." + echo + echo "$c_head""Navigation:$c_reset" + echo " $c_flag↑ ↓$c_reset or $c_flag""j k$c_reset Move cursor up / down" + echo " $c_flag""Space$c_reset Cycle: ON → OFF → DEFAULT → ON" + echo " $c_flag""Tab$c_reset Switch scope (Universal ↔ Session)" + echo " $c_flag""q$c_reset / $c_flag""Escape$c_reset Exit" + echo + echo "$c_head""Scopes:$c_reset" + echo " $c_flag""Universal$c_reset Persistent across all sessions ($c_dim""set -U$c_reset)" + echo " $c_flag""Session$c_reset Current session only ($c_dim""set -g$c_reset)" + return 0 + case '*' + echo "$c_err""Unknown option: $arg$c_reset" >&2 + echo "Run $c_cmd""config-toggle --help$c_reset for usage." >&2 + return 1 + end + end +end +``` + +- [ ] **Step 2: Verify help output** + +```fish +config-toggle --help +``` + +Expected: coloured usage output, exits 0. + +```fish +config-toggle --bogus +echo $status # expected: 1 +``` + +- [ ] **Step 3: Commit** + +```bash +git add functions/config-toggle.fish +git commit -m "feat(config-toggle): add function skeleton and help flag" +``` + +--- + +## Task 4: Event loop — navigation and scope switching + +**Files:** +- Modify: `functions/config-toggle.fish` (add after the arg-parse block, before the final `end`) + +- [ ] **Step 1: Add terminal setup, initial draw, and event loop** + +Replace the final `end` of `config-toggle` with this block: + +```fish + # ── Variable list (matches Panel Layout Reference) ──── + set -l vars \ + __fish_config_op_aliases \ + __fish_config_op_autoexec \ + __fish_config_op_overrides \ + __fish_config_op_integrations \ + __fish_config_op_logging \ + __fish_config_op_greeting \ + __fish_config_opinionated + + set -l cur_row 0 # 0–6 + set -l cur_scope universal # or "session" + set -l panel_h 14 # total panel lines + + # ── Terminal setup ──────────────────────────────────── + printf '\e[?25l' # hide cursor + trap 'printf "\e[?25h"; printf "\n"' INT + + # ── Initial draw ────────────────────────────────────── + __config_toggle_draw $cur_row $cur_scope $vars + + # ── Event loop ──────────────────────────────────────── + while true + read -k 1 -l key + + switch $key + case \e + # Arrow key: ESC [ A/B — read 2 more chars with short timeout + read -k 2 -l seq -t 0.1 + switch $seq + case '[A' # Up arrow + set cur_row (math "max(0, $cur_row - 1)") + case '[B' # Down arrow + set cur_row (math "min(6, $cur_row + 1)") + end + # If seq is empty (bare Escape), fall through to quit + if test -z "$seq" + break + end + + case k + set cur_row (math "max(0, $cur_row - 1)") + case j + set cur_row (math "min(6, $cur_row + 1)") + + case \t # Tab — switch scope + if test $cur_scope = universal + set cur_scope session + else + set cur_scope universal + end + + case q Q + break + end + + # Redraw in place + printf '\e[%dA\e[J' $panel_h + __config_toggle_draw $cur_row $cur_scope $vars + end + + # ── Cleanup ─────────────────────────────────────────── + printf '\e[?25h' # restore cursor +end +``` + +- [ ] **Step 2: Verify navigation** + +```fish +config-toggle +``` + +Expected: +- Panel appears, cursor is hidden +- ↑/↓ and j/k move the `▶` cursor between rows +- Tab switches the scope tab highlight between `● Universal` and `● Session` +- `q` or Escape exits cleanly and restores the cursor + +- [ ] **Step 3: Commit** + +```bash +git add functions/config-toggle.fish +git commit -m "feat(config-toggle): add event loop with navigation and scope switching" +``` + +--- + +## Task 5: Cycling + immediate apply + +**Files:** +- Modify: `functions/config-toggle.fish` (add Space handler inside the event loop) + +- [ ] **Step 1: Add the Space case to the switch block** + +Inside the `switch $key` block, add a `case ' '` handler. Insert it after the `case \t` block: + +```fish + case ' ' + set -l varname $vars[(math $cur_row + 1)] + set -l cur_val (__config_toggle_get_val $varname $cur_scope) + + # Cycle: on → off → DEFAULT → on + switch $cur_val + case on + set -l next_val off + case off + set -l next_val DEFAULT + case '*' # DEFAULT or any unrecognised + set -l next_val on + end + + # Apply immediately + switch $cur_scope + case universal + switch $next_val + case on + set -U $varname on + case off + set -U $varname off + case DEFAULT + set -Ue $varname + end + case session + switch $next_val + case on + set -g $varname on + case off + set -g $varname off + case DEFAULT + set -eg $varname + end + end +``` + +- [ ] **Step 2: Verify cycling and immediate apply** + +```fish +config-toggle +``` + +Expected: +- Navigate to **Aliases** (row 0), press Space → badge changes to green `ON `, variable set immediately +- Press Space again → badge changes to red `OFF ` +- Press Space again → badge changes to dim `DEFAULT` +- Press Space again → back to green `ON ` + +Verify the variable was actually set: +```fish +# In another terminal or after quitting: +echo $__fish_config_op_aliases # should reflect last state set +``` + +- [ ] **Step 3: Verify cross-scope independence** + +```fish +config-toggle +# Navigate to Integrations, Tab to Session, Space to ON +# Tab back to Universal — badge should still show DEFAULT (separate scope) +``` + +Expected: Universal and Session values for the same row are independent. + +- [ ] **Step 4: Commit** + +```bash +git add functions/config-toggle.fish +git commit -m "feat(config-toggle): add Space cycling with immediate apply" +``` + +--- + +## Task 6: Docs update + +**Files:** +- Modify: `docs/fish-config.md` +- Modify: `docs/fish-config.index` + +- [ ] **Step 1: Add entry to fish-config.md** + +In `docs/fish-config.md`, find the `### config-update` section (line ~1203). Insert the following block **immediately after** the `config-update` section ends (after the last example line `config-update --force`, before `### bash`): + +```markdown +### config-toggle + + Synopsis: config-toggle [-h] + + Opens an interactive full-screen TUI for toggling the six opinionated + component categories (C1–C6) and the master disable variable without + having to type or remember variable names. Two scope tabs allow + independent per-scope configuration: + + Universal — persists across all sessions (set -U) + Session — current shell only (set -g) + + Changes apply immediately on each Space keypress. Always available + regardless of the __fish_config_opinionated master state. + + Navigation: + ↑ ↓ / j k Move cursor + Space Cycle: ON → OFF → DEFAULT → ON + Tab Switch scope (Universal ↔ Session) + q / Escape Exit + + Flags: + --help / -h Show usage. + + config-toggle + +``` + +- [ ] **Step 2: Add entries to fish-config.index** + +In `docs/fish-config.index`, find the section that contains `config-help` and `config-update` keyword entries. Add after the last `config-update` line: + +``` +config-toggle=### config-toggle +toggle=### config-toggle +``` + +- [ ] **Step 3: Verify docs are reachable** + +```fish +config-help toggle +``` + +Expected: docs pager opens at the `config-toggle` section. + +- [ ] **Step 4: Commit** + +```bash +git add docs/fish-config.md docs/fish-config.index +git commit -m "docs(config-toggle): add config-toggle entry to offline docs and index" +``` + +--- + +## Self-Review + +**Spec coverage check:** + +| Spec requirement | Covered by | +|---|---| +| Interactive TUI, colorful | Task 2 (renderer with set_color) | +| ON / OFF / DEFAULT states | Tasks 2 + 5 (badge display + cycle logic) | +| Universal / Session scope tabs | Tasks 3 + 4 (tab line + Tab key handler) | +| Immediate apply on toggle | Task 5 (set -U/g/erase in Space handler) | +| Tab key switches scope | Task 4 | +| Always available (no guard) | Task 3 (function has no op_enabled wrapper) | +| -h/--help per Convention §12 | Task 3 | +| License header + docblock | Tasks 1, 2, 3 | +| Color palette (Convention §11) | Tasks 2, 3 | +| Docs update (Convention §10) | Task 6 | +| docs/fish-config.index update | Task 6 | + +**Placeholder scan:** No TBDs, TODOs, or "handle edge cases" vagueness found. All code blocks are complete. + +**Type consistency:** `__config_toggle_get_val` returns "on", "off", or "DEFAULT" — the Space handler's `switch $cur_val` matches against those exact strings. The `$vars` array indices are 1-based in fish (used as `$vars[(math $cur_row + 1)]`) consistently throughout. -- 2.52.0 From c372c96dd7b10d26ea0171a07ec894a970c5e20d Mon Sep 17 00:00:00 2001 From: rootiest Date: Thu, 11 Jun 2026 00:52:11 -0400 Subject: [PATCH 03/16] feat(config-toggle): add scope-specific variable reader helper --- functions/__config_toggle_get_val.fish | 50 ++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 functions/__config_toggle_get_val.fish diff --git a/functions/__config_toggle_get_val.fish b/functions/__config_toggle_get_val.fish new file mode 100644 index 0000000..376551a --- /dev/null +++ b/functions/__config_toggle_get_val.fish @@ -0,0 +1,50 @@ +# Copyright (C) 2026 Rootiest +# SPDX-License-Identifier: AGPL-3.0-or-later + +# SYNOPSIS +# __config_toggle_get_val +# +# DESCRIPTION +# Returns the current value of a named variable in the specified scope by +# parsing `set --show` output. Outputs "on", "off", or "DEFAULT" (when +# the variable is not set in that scope). Scope "session" maps to "global" +# in fish's internal terminology. +# +# ARGUMENTS +# varname Variable name without $ prefix +# scope "universal" or "session" +# +# RETURNS +# 0 Always; prints "on", "off", or "DEFAULT" to stdout +# +# EXAMPLE +# set result (__config_toggle_get_val __fish_config_op_aliases universal) +# # result == "on" | "off" | "DEFAULT" +function __config_toggle_get_val + set -l varname $argv[1] + set -l scope $argv[2] + + set -l scope_kw $scope + if test $scope = session + set scope_kw global + end + + set -l found 0 + for line in (set --show $varname 2>/dev/null) + if string match -q "*$scope_kw scope*" $line + set found 1 + continue + end + if test $found -eq 1 + set found 0 + set -l val (string replace -r '^\$[^\[]+\[1\]: \|(.+)\|$' '$1' $line 2>/dev/null) + if test -n "$val" -a "$val" != $line + echo $val + return 0 + end + end + end + + echo DEFAULT + return 0 +end -- 2.52.0 From c9174f9d4a50e2cb615c2897598d4c19a5ad1bb7 Mon Sep 17 00:00:00 2001 From: rootiest Date: Thu, 11 Jun 2026 01:01:19 -0400 Subject: [PATCH 04/16] feat(config-toggle): add panel renderer helper --- functions/__config_toggle_draw.fish | 139 ++++++++++++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 functions/__config_toggle_draw.fish diff --git a/functions/__config_toggle_draw.fish b/functions/__config_toggle_draw.fish new file mode 100644 index 0000000..b1fdd81 --- /dev/null +++ b/functions/__config_toggle_draw.fish @@ -0,0 +1,139 @@ +# Copyright (C) 2026 Rootiest +# SPDX-License-Identifier: AGPL-3.0-or-later + +# SYNOPSIS +# __config_toggle_draw ... +# +# DESCRIPTION +# Renders the 14-line config-toggle TUI panel to stdout. Intended to be +# called once on startup, then again after each keypress preceded by +# `printf '\e[14A\e[J'` (cursor-up 14 lines + clear-to-end) to redraw +# in place. Does not move the cursor — caller manages positioning. +# +# ARGUMENTS +# cur_row 0–6, the currently highlighted row +# cur_scope "universal" or "session" +# var1–var7 Variable names for rows 0–6 (in order per Variable Reference) +# +# RETURNS +# 0 Always +# +# EXAMPLE +# __config_toggle_draw 0 universal \ +# __fish_config_op_aliases __fish_config_op_autoexec \ +# __fish_config_op_overrides __fish_config_op_integrations \ +# __fish_config_op_logging __fish_config_op_greeting \ +# __fish_config_opinionated +function __config_toggle_draw + set -l cur_row $argv[1] + set -l cur_scope $argv[2] + set -l vars $argv[3..] + + set -l c_ok (set_color green) + set -l c_err (set_color red) + set -l c_dim (set_color brblack) + set -l c_sel (set_color --bold magenta) + set -l c_hi (set_color --bold white) + set -l c_head (set_color --bold cyan) + set -l c_reset (set_color normal) + + set -l labels Aliases Auto-exec Overrides Integrations Logging Greeting Master + set -l descs "cmd shadows" startup "keys/env/prompt" "terminal coupling" scrollback fish_greeting "disable all" + + # Panel dimensions + set -l IW 50 # inner width (chars between │ │) + set -l HBR (string repeat -n $IW '─') + + # ── Scope tabs ──────────────────────────────────────── + set -l u_label + set -l s_label + if test $cur_scope = universal + set u_label "$c_hi● Universal$c_reset" + set s_label "○ Session " + else + set u_label "○ Universal" + set s_label "$c_hi● Session $c_reset" + end + + # ── Top border ──────────────────────────────────────── + # Visible: ┌(1) ─(1) space(1) "Opinionated Settings"(20) space(1) N×─ ┐(1) = 52 + # N = 52 - 25 = 27 = IW - 23 + printf '┌─%s Opinionated Settings %s┐\n' \ + $c_head $c_reset(string repeat -n (math $IW - 23) '─') + + # ── Scope tab line ──────────────────────────────────── + # Visible inner (50): space(1) + u_label(11) + 2spaces + s_label(11) + 10spaces + "Tab to switch "(15) = 50 + printf '│ %s %s%s│\n' \ + $u_label $s_label \ + (string repeat -n 10 ' ')"Tab to switch " + + # ── Top divider ─────────────────────────────────────── + printf '│%s│\n' $HBR + + # ── Category rows 0–5 ───────────────────────────────── + for i in (seq 0 5) + set -l idx (math $i + 1) + set -l var $vars[$idx] + set -l label $labels[$idx] + set -l desc $descs[$idx] + + set -l val (__config_toggle_get_val $var $cur_scope) + + # Badge: 7 visible chars, coloured + set -l badge + switch $val + case on + set badge "$c_ok""ON $c_reset" + case off + set badge "$c_err""OFF $c_reset" + case '*' + set badge "$c_dim""DEFAULT$c_reset" + end + + # Cursor: 2 visible chars + set -l curs " " + if test $i -eq $cur_row + set curs "$c_sel▶$c_reset " + end + + # Label padded to 12, desc padded to 17, right margin 3 + set -l lpad (string pad -r -w 12 -- $label) + set -l dpad (string pad -r -w 17 -- $desc) + + printf '│ %s%s [ %s ] %s │\n' $curs $lpad $badge $dpad + end + + # ── Separator before Master ─────────────────────────── + printf '│ %s │\n' (string repeat -n (math $IW - 6) '─') + + # ── Master row (index 6) ────────────────────────────── + set -l val (__config_toggle_get_val $vars[7] $cur_scope) + set -l badge + switch $val + case on + set badge "$c_ok""ON $c_reset" + case off + set badge "$c_err""OFF $c_reset" + case '*' + set badge "$c_dim""DEFAULT$c_reset" + end + set -l curs " " + if test $cur_row -eq 6 + set curs "$c_sel▶$c_reset " + end + printf '│ %s%s [ %s ] %s │\n' \ + $curs \ + (string pad -r -w 12 -- Master) \ + $badge \ + (string pad -r -w 17 -- "disable all") + + # ── Bottom divider ──────────────────────────────────── + printf '│%s│\n' $HBR + + # ── Keybind hint ────────────────────────────────────── + printf '│ %s↑↓/jk: move Space: cycle Tab: scope q: quit%s │\n' \ + $c_dim $c_reset + + # ── Bottom border ───────────────────────────────────── + printf '└%s┘\n' $HBR +end -- 2.52.0 From a4f6ee26cbe69dfaf227c320d3ee11c7380c26e2 Mon Sep 17 00:00:00 2001 From: rootiest Date: Thu, 11 Jun 2026 01:07:15 -0400 Subject: [PATCH 05/16] feat(config-toggle): add function skeleton and help flag --- functions/config-toggle.fish | 61 ++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 functions/config-toggle.fish diff --git a/functions/config-toggle.fish b/functions/config-toggle.fish new file mode 100644 index 0000000..4142b48 --- /dev/null +++ b/functions/config-toggle.fish @@ -0,0 +1,61 @@ +# Copyright (C) 2026 Rootiest +# SPDX-License-Identifier: AGPL-3.0-or-later + +# SYNOPSIS +# config-toggle [-h | --help] +# +# DESCRIPTION +# Opens an interactive full-screen TUI for toggling the six opinionated +# component categories (C1–C6) and the master disable variable without +# having to remember variable names. Two scope tabs allow independent +# per-scope configuration: +# +# Universal — persists across all sessions (set -U / set -Ue) +# Session — active for the current shell only (set -g / set -eg) +# +# Changes apply immediately on each Space keypress — no confirm step. +# Always available regardless of __fish_config_opinionated state. +# +# ARGUMENTS +# -h, --help Print usage and exit +# +# RETURNS +# 0 Exited normally (q or Escape pressed) +# 1 Unknown flag passed +# +# EXAMPLE +# config-toggle +function config-toggle --description 'Interactive TUI for toggling opinionated component settings' + set -l c_head (set_color --bold cyan) + set -l c_cmd (set_color --bold white) + set -l c_flag (set_color yellow) + set -l c_dim (set_color brblack) + set -l c_err (set_color red) + set -l c_reset (set_color normal) + + # ── Argument parsing ────────────────────────────────── + for arg in $argv + switch $arg + case -h --help + echo "$c_head""Usage:$c_reset $c_cmd""config-toggle$c_reset $c_flag""[-h]$c_reset" + echo + echo " Interactive TUI for toggling opinionated component categories." + echo " Changes apply immediately — no confirm step required." + echo + echo "$c_head""Navigation:$c_reset" + echo " $c_flag↑ ↓$c_reset or $c_flag""j k$c_reset Move cursor up / down" + echo " $c_flag""Space$c_reset Cycle: ON → OFF → DEFAULT → ON" + echo " $c_flag""Tab$c_reset Switch scope (Universal ↔ Session)" + echo " $c_flag""q$c_reset / $c_flag""Escape$c_reset Exit" + echo + echo "$c_head""Scopes:$c_reset" + echo " $c_flag""Universal$c_reset Persistent across all sessions ($c_dim""set -U$c_reset)" + echo " $c_flag""Session$c_reset Current session only ($c_dim""set -g$c_reset)" + return 0 + case '*' + echo "$c_err""Unknown option: $arg$c_reset" >&2 + echo "Run $c_cmd""config-toggle --help$c_reset for usage." >&2 + return 1 + end + end +end -- 2.52.0 From d20f7f8d7bc6e0001f067ad918d66d519331e347 Mon Sep 17 00:00:00 2001 From: rootiest Date: Thu, 11 Jun 2026 01:11:04 -0400 Subject: [PATCH 06/16] feat(config-toggle): add event loop with navigation and scope switching --- functions/config-toggle.fish | 68 ++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/functions/config-toggle.fish b/functions/config-toggle.fish index 4142b48..c86cdf0 100644 --- a/functions/config-toggle.fish +++ b/functions/config-toggle.fish @@ -58,4 +58,72 @@ function config-toggle --description 'Interactive TUI for toggling opinionated c return 1 end end + + # ── Variable list (matches Panel Layout Reference) ──── + set -l vars \ + __fish_config_op_aliases \ + __fish_config_op_autoexec \ + __fish_config_op_overrides \ + __fish_config_op_integrations \ + __fish_config_op_logging \ + __fish_config_op_greeting \ + __fish_config_opinionated + + set -l cur_row 0 # 0–6 + set -l cur_scope universal # or "session" + set -l panel_h 14 # total panel lines + + # ── Terminal setup ──────────────────────────────────── + printf '\e[?25l' # hide cursor + trap 'printf "\e[?25h"; printf "\n"' INT + + # ── Initial draw ────────────────────────────────────── + __config_toggle_draw $cur_row $cur_scope $vars + + # ── Event loop ──────────────────────────────────────── + while true + read -k 1 -l key + + switch $key + case \e + # Arrow key: ESC [ A/B — read next 2 chars one at a time. + # Terminal sends escape sequences as an atomic burst, so + # read -k 1 returns each char immediately. + # If the char after ESC is not '[', treat as bare Escape → quit. + read -k 1 -l ch1 + if test "$ch1" != '[' + # Bare Escape (or unrecognised sequence) — exit + break + end + read -k 1 -l ch2 + switch $ch2 + case A # Up arrow + set cur_row (math "max(0, $cur_row - 1)") + case B # Down arrow + set cur_row (math "min(6, $cur_row + 1)") + end + + case k + set cur_row (math "max(0, $cur_row - 1)") + case j + set cur_row (math "min(6, $cur_row + 1)") + + case \t # Tab — switch scope + if test $cur_scope = universal + set cur_scope session + else + set cur_scope universal + end + + case q Q + break + end + + # Redraw in place + printf '\e[%dA\e[J' $panel_h + __config_toggle_draw $cur_row $cur_scope $vars + end + + # ── Cleanup ─────────────────────────────────────────── + printf '\e[?25h' # restore cursor end -- 2.52.0 From 9a820fc3e66ff3fc3950b8cc62098e66bb1fc019 Mon Sep 17 00:00:00 2001 From: rootiest Date: Thu, 11 Jun 2026 01:16:41 -0400 Subject: [PATCH 07/16] fix(config-toggle): use read -k 3 for ESC sequences, clear INT trap on exit 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. --- functions/config-toggle.fish | 37 ++++++++++++++---------------------- 1 file changed, 14 insertions(+), 23 deletions(-) diff --git a/functions/config-toggle.fish b/functions/config-toggle.fish index c86cdf0..b861ca8 100644 --- a/functions/config-toggle.fish +++ b/functions/config-toggle.fish @@ -81,40 +81,30 @@ function config-toggle --description 'Interactive TUI for toggling opinionated c __config_toggle_draw $cur_row $cur_scope $vars # ── Event loop ──────────────────────────────────────── + # read -k 3 reads UP TO 3 chars and returns immediately when input is + # exhausted — it does not block waiting for exactly 3 chars. A bare ESC + # sends 1 byte and read -k 3 returns at once with a 1-char string. + # Arrow keys send the 3-byte sequence ESC [ A/B, matched as \e\[A / \e\[B. while true - read -k 1 -l key + read -k 3 -l key switch $key - case \e - # Arrow key: ESC [ A/B — read next 2 chars one at a time. - # Terminal sends escape sequences as an atomic burst, so - # read -k 1 returns each char immediately. - # If the char after ESC is not '[', treat as bare Escape → quit. - read -k 1 -l ch1 - if test "$ch1" != '[' - # Bare Escape (or unrecognised sequence) — exit - break - end - read -k 1 -l ch2 - switch $ch2 - case A # Up arrow - set cur_row (math "max(0, $cur_row - 1)") - case B # Down arrow - set cur_row (math "min(6, $cur_row + 1)") - end - + case \e\[A # Up arrow (ESC [ A) + set cur_row (math "max(0, $cur_row - 1)") + case \e\[B # Down arrow (ESC [ B) + set cur_row (math "min(6, $cur_row + 1)") + case \e # Bare Escape (1 byte) — exit + break case k set cur_row (math "max(0, $cur_row - 1)") case j set cur_row (math "min(6, $cur_row + 1)") - - case \t # Tab — switch scope + case \t # Tab — switch scope if test $cur_scope = universal set cur_scope session else set cur_scope universal end - case q Q break end @@ -125,5 +115,6 @@ function config-toggle --description 'Interactive TUI for toggling opinionated c end # ── Cleanup ─────────────────────────────────────────── - printf '\e[?25h' # restore cursor + trap - INT # remove the signal handler + printf '\e[?25h' # restore cursor end -- 2.52.0 From 43fd36f5ea2a2dcc95e1011ce634ee3510979e7c Mon Sep 17 00:00:00 2001 From: rootiest Date: Thu, 11 Jun 2026 01:18:09 -0400 Subject: [PATCH 08/16] feat(config-toggle): add Space cycling with immediate apply --- functions/config-toggle.fish | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/functions/config-toggle.fish b/functions/config-toggle.fish index b861ca8..a110a7e 100644 --- a/functions/config-toggle.fish +++ b/functions/config-toggle.fish @@ -105,6 +105,42 @@ function config-toggle --description 'Interactive TUI for toggling opinionated c else set cur_scope universal end + case ' ' # Space — cycle and apply immediately + set -l varname $vars[(math $cur_row + 1)] + set -l cur_val (__config_toggle_get_val $varname $cur_scope) + set -l next_val "" + + # Cycle: on → off → DEFAULT → on + switch $cur_val + case on + set next_val off + case off + set next_val DEFAULT + case '*' # DEFAULT or any unrecognised + set next_val on + end + + # Apply immediately + switch $cur_scope + case universal + switch $next_val + case on + set -U $varname on + case off + set -U $varname off + case DEFAULT + set -Ue $varname + end + case session + switch $next_val + case on + set -g $varname on + case off + set -g $varname off + case DEFAULT + set -eg $varname + end + end case q Q break end -- 2.52.0 From f75d64bf9cd9f965c5945a69f5f387b1190275b9 Mon Sep 17 00:00:00 2001 From: rootiest Date: Thu, 11 Jun 2026 01:20:55 -0400 Subject: [PATCH 09/16] docs(config-toggle): add config-toggle entry to offline docs and index --- docs/fish-config.index | 4 ++++ docs/fish-config.md | 26 ++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/docs/fish-config.index b/docs/fish-config.index index f999958..8cf6510 100644 --- a/docs/fish-config.index +++ b/docs/fish-config.index @@ -139,6 +139,10 @@ media=## 5.13 Media and Utilities dng2avif=### dng2avif spark=### spark miscfns=## 5.14 Miscellaneous +config-help=### config-help +config-update=### config-update +config-toggle=### config-toggle +toggle=### config-toggle bash=### bash cheat=### cheat dockup=### dockup diff --git a/docs/fish-config.md b/docs/fish-config.md index 74ff4b0..78b3f4d 100644 --- a/docs/fish-config.md +++ b/docs/fish-config.md @@ -1220,6 +1220,32 @@ Add -i (interactive confirmation) to destructive commands: config-update --dry-run config-update --force +### config-toggle + + Synopsis: config-toggle [-h] + + Opens an interactive full-screen TUI for toggling the six opinionated + component categories (C1–C6) and the master disable variable without + having to type or remember variable names. Two scope tabs allow + independent per-scope configuration: + + Universal — persists across all sessions (set -U) + Session — current shell only (set -g) + + Changes apply immediately on each Space keypress. Always available + regardless of the __fish_config_opinionated master state. + + Navigation: + ↑ ↓ / j k Move cursor + Space Cycle: ON → OFF → DEFAULT → ON + Tab Switch scope (Universal ↔ Session) + q / Escape Exit + + Flags: + --help / -h Show usage. + + config-toggle + ### bash Synopsis: bash [args...] -- 2.52.0 From bff084db078f3ba91a2c8be483b129c5a0a71a5f Mon Sep 17 00:00:00 2001 From: rootiest Date: Thu, 11 Jun 2026 01:29:28 -0400 Subject: [PATCH 10/16] fix(config-toggle): exit on Ctrl-C via flag variable, add README tip --- README.md | 2 ++ functions/config-toggle.fish | 8 +++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 491e97b..4bcb657 100644 --- a/README.md +++ b/README.md @@ -198,6 +198,8 @@ set -Ue __fish_config_op_greeting Command shadows react immediately; bindings, prompt, and abbreviations take effect in new shells. With aliases disabled, `rm` deletes permanently again instead of trashing. See `help config opinionated` for the full component list. +> **Tip:** Run `config-toggle` for an interactive TUI to toggle these settings without typing variable names. + --- ## Attribution diff --git a/functions/config-toggle.fish b/functions/config-toggle.fish index a110a7e..3966cb4 100644 --- a/functions/config-toggle.fish +++ b/functions/config-toggle.fish @@ -75,7 +75,7 @@ function config-toggle --description 'Interactive TUI for toggling opinionated c # ── Terminal setup ──────────────────────────────────── printf '\e[?25l' # hide cursor - trap 'printf "\e[?25h"; printf "\n"' INT + trap 'printf "\e[?25h"; set -g __config_toggle_exit 1' INT # ── Initial draw ────────────────────────────────────── __config_toggle_draw $cur_row $cur_scope $vars @@ -86,6 +86,12 @@ function config-toggle --description 'Interactive TUI for toggling opinionated c # sends 1 byte and read -k 3 returns at once with a 1-char string. # Arrow keys send the 3-byte sequence ESC [ A/B, matched as \e\[A / \e\[B. while true + # Check for Ctrl-C signal (trap sets this flag) + if set -q __config_toggle_exit + set -eg __config_toggle_exit + break + end + read -k 3 -l key switch $key -- 2.52.0 From cc14f7fb3df0ed9cb4b492758108d0afb3c798ef Mon Sep 17 00:00:00 2001 From: rootiest Date: Thu, 11 Jun 2026 02:51:01 -0400 Subject: [PATCH 11/16] fix(config-toggle): replace read -k 3 with read -k 1 + ESC sequence detection 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). --- functions/config-toggle.fish | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/functions/config-toggle.fish b/functions/config-toggle.fish index 3966cb4..8f62c64 100644 --- a/functions/config-toggle.fish +++ b/functions/config-toggle.fish @@ -46,7 +46,7 @@ function config-toggle --description 'Interactive TUI for toggling opinionated c echo " $c_flag↑ ↓$c_reset or $c_flag""j k$c_reset Move cursor up / down" echo " $c_flag""Space$c_reset Cycle: ON → OFF → DEFAULT → ON" echo " $c_flag""Tab$c_reset Switch scope (Universal ↔ Session)" - echo " $c_flag""q$c_reset / $c_flag""Escape$c_reset Exit" + echo " $c_flag""q$c_reset / $c_flag""Q$c_reset Exit" echo echo "$c_head""Scopes:$c_reset" echo " $c_flag""Universal$c_reset Persistent across all sessions ($c_dim""set -U$c_reset)" @@ -81,10 +81,10 @@ function config-toggle --description 'Interactive TUI for toggling opinionated c __config_toggle_draw $cur_row $cur_scope $vars # ── Event loop ──────────────────────────────────────── - # read -k 3 reads UP TO 3 chars and returns immediately when input is - # exhausted — it does not block waiting for exactly 3 chars. A bare ESC - # sends 1 byte and read -k 3 returns at once with a 1-char string. - # Arrow keys send the 3-byte sequence ESC [ A/B, matched as \e\[A / \e\[B. + # read -k 1 reads exactly one raw byte. Arrow keys send ESC+[+letter as a + # 3-byte burst — after reading the ESC we immediately read 2 more bytes + # which are already in the terminal buffer. Single-char keys ('j','k',' ', + # 'q', Tab) are returned right away with no extra blocking. while true # Check for Ctrl-C signal (trap sets this flag) if set -q __config_toggle_exit @@ -92,15 +92,25 @@ function config-toggle --description 'Interactive TUI for toggling opinionated c break end - read -k 3 -l key + read -k 1 -l key + + # Detect CSI escape sequences (arrow keys: ESC [ A/B) + if test "$key" = \e + read -k 1 -l key2 + if test "$key2" = "[" + read -k 1 -l key3 + set key "$key$key2$key3" # \e[A or \e[B + else + # ESC + non-bracket (ALT+key or plain ESC): treat key2 as key + set key $key2 + end + end switch $key - case \e\[A # Up arrow (ESC [ A) + case \e\[A # Up arrow set cur_row (math "max(0, $cur_row - 1)") - case \e\[B # Down arrow (ESC [ B) + case \e\[B # Down arrow set cur_row (math "min(6, $cur_row + 1)") - case \e # Bare Escape (1 byte) — exit - break case k set cur_row (math "max(0, $cur_row - 1)") case j -- 2.52.0 From 5a018fe97551eed746f819c2fa5de917783e26fa Mon Sep 17 00:00:00 2001 From: rootiest Date: Thu, 11 Jun 2026 02:56:12 -0400 Subject: [PATCH 12/16] fix(config-toggle): use valid -n read flag, stopping the busy redraw loop 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. --- functions/config-toggle.fish | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/functions/config-toggle.fish b/functions/config-toggle.fish index 8f62c64..28908e9 100644 --- a/functions/config-toggle.fish +++ b/functions/config-toggle.fish @@ -81,10 +81,11 @@ function config-toggle --description 'Interactive TUI for toggling opinionated c __config_toggle_draw $cur_row $cur_scope $vars # ── Event loop ──────────────────────────────────────── - # read -k 1 reads exactly one raw byte. Arrow keys send ESC+[+letter as a - # 3-byte burst — after reading the ESC we immediately read 2 more bytes - # which are already in the terminal buffer. Single-char keys ('j','k',' ', - # 'q', Tab) are returned right away with no extra blocking. + # read -s -n 1 reads exactly one char without echoing it. Arrow keys send + # ESC+[+letter as a 3-byte burst — after reading the ESC we immediately + # read 2 more bytes which are already in the terminal buffer. Single-char + # keys ('j','k',' ','q', Tab) are returned right away with no extra + # blocking. NOTE: the nchars flag is -n; -k is not a valid read option. while true # Check for Ctrl-C signal (trap sets this flag) if set -q __config_toggle_exit @@ -92,13 +93,17 @@ function config-toggle --description 'Interactive TUI for toggling opinionated c break end - read -k 1 -l key + # -n 1 reads exactly one char; -s suppresses echo so keystrokes do + # not garble the panel. If read fails (EOF / non-tty stdin) bail out + # instead of spinning the redraw loop. + read -s -n 1 -l key + or break # Detect CSI escape sequences (arrow keys: ESC [ A/B) if test "$key" = \e - read -k 1 -l key2 + read -s -n 1 -l key2 if test "$key2" = "[" - read -k 1 -l key3 + read -s -n 1 -l key3 set key "$key$key2$key3" # \e[A or \e[B else # ESC + non-bracket (ALT+key or plain ESC): treat key2 as key -- 2.52.0 From 608b0227cb0dc9169e78eb4bd1ee9131687e3fa5 Mon Sep 17 00:00:00 2001 From: rootiest Date: Thu, 11 Jun 2026 03:03:32 -0400 Subject: [PATCH 13/16] fix(config-toggle): read keys via raw tty, fixing arrows/Tab/redraw/prompt 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. --- functions/__config_toggle_read_key.fish | 89 +++++++++++++++++++++++++ functions/config-toggle.fish | 47 ++++--------- 2 files changed, 103 insertions(+), 33 deletions(-) create mode 100644 functions/__config_toggle_read_key.fish diff --git a/functions/__config_toggle_read_key.fish b/functions/__config_toggle_read_key.fish new file mode 100644 index 0000000..1929836 --- /dev/null +++ b/functions/__config_toggle_read_key.fish @@ -0,0 +1,89 @@ +# Copyright (C) 2026 Rootiest +# SPDX-License-Identifier: AGPL-3.0-or-later + +# SYNOPSIS +# __config_toggle_read_key +# +# DESCRIPTION +# Reads a single keypress directly from the controlling terminal in raw +# mode and echoes a normalized token naming the key. Bypasses fish's +# `read` builtin, whose interactive line editor swallows Tab and arrow +# keys (and prints a `read> ` prompt) — none of which is usable for a TUI. +# +# The terminal is put into raw, no-echo mode with a 0.1s inter-byte timer +# (`stty raw -echo min 1 time 1`) so a multi-byte escape sequence (e.g. +# an arrow key, ESC [ A) is captured in one read while a lone key returns +# promptly. Original terminal settings are always restored before return. +# +# In raw mode Ctrl-C does not raise SIGINT; it arrives as byte 3 (ETX), +# which is reported as the token "quit". +# +# ARGUMENTS +# (none) +# +# RETURNS +# 0 A key was read; one of these tokens is printed to stdout: +# up down left right arrow keys +# space tab enter escape +# quit Ctrl-C (byte 3) in raw mode +# any other single printable character +# "" nothing decodable was read +# 1 The terminal could not be put into raw mode (stdin is not a TTY) +# +# EXAMPLE +# set -l key (__config_toggle_read_key) +# or return # not a TTY — bail +# switch $key +# case up; echo "moved up" +# case space; echo "toggled" +# end +function __config_toggle_read_key + # Snapshot current terminal settings; failure means stdin is not a TTY. + set -l saved (stty -g /dev/null) + or return 1 + + # Raw, no-echo. min 1 / time 1: block for the first byte, then wait up to + # 0.1s for the rest of a burst (escape sequence) before returning. + stty raw -echo min 1 time 1 /dev/null + + # One read() of up to 3 bytes — covers ESC [ A style sequences. od emits + # the bytes as space-separated decimal codes. + set -l codes (dd if=/dev/tty bs=3 count=1 2>/dev/null \ + | od -An -tu1 2>/dev/null | string trim | string split -n ' ') + + # Restore the terminal before doing anything else. + stty $saved /dev/null + + switch (string join ' ' $codes) + case '27 91 65' + echo up + case '27 91 66' + echo down + case '27 91 67' + echo right + case '27 91 68' + echo left + case 27 + echo escape + case 9 + echo tab + case 32 + echo space + case 10 13 + echo enter + case 3 + echo quit + case '' + echo '' + case '*' + # Single printable byte → emit its character; ignore stray + # multi-byte sequences we do not recognise. The two-step octal + # form avoids fish mangling a one-shot '\\%03o' format string. + if test (count $codes) -eq 1 + set -l oct (printf '%03o' $codes[1]) + printf '%b\n' "\\$oct" + else + echo '' + end + end +end diff --git a/functions/config-toggle.fish b/functions/config-toggle.fish index 28908e9..8d845bd 100644 --- a/functions/config-toggle.fish +++ b/functions/config-toggle.fish @@ -46,7 +46,7 @@ function config-toggle --description 'Interactive TUI for toggling opinionated c echo " $c_flag↑ ↓$c_reset or $c_flag""j k$c_reset Move cursor up / down" echo " $c_flag""Space$c_reset Cycle: ON → OFF → DEFAULT → ON" echo " $c_flag""Tab$c_reset Switch scope (Universal ↔ Session)" - echo " $c_flag""q$c_reset / $c_flag""Q$c_reset Exit" + echo " $c_flag""q$c_reset / $c_flag""Esc$c_reset Exit" echo echo "$c_head""Scopes:$c_reset" echo " $c_flag""Universal$c_reset Persistent across all sessions ($c_dim""set -U$c_reset)" @@ -81,52 +81,33 @@ function config-toggle --description 'Interactive TUI for toggling opinionated c __config_toggle_draw $cur_row $cur_scope $vars # ── Event loop ──────────────────────────────────────── - # read -s -n 1 reads exactly one char without echoing it. Arrow keys send - # ESC+[+letter as a 3-byte burst — after reading the ESC we immediately - # read 2 more bytes which are already in the terminal buffer. Single-char - # keys ('j','k',' ','q', Tab) are returned right away with no extra - # blocking. NOTE: the nchars flag is -n; -k is not a valid read option. + # __config_toggle_read_key reads a single keypress from /dev/tty in raw + # mode and returns a normalized token (up/down/tab/space/escape/quit or a + # literal char). It bypasses fish's `read`, whose line editor swallows Tab + # and arrow keys and prints a `read> ` prompt — unusable for a TUI. while true - # Check for Ctrl-C signal (trap sets this flag) + # Check for Ctrl-C signal (trap sets this flag during redraw, when the + # terminal is briefly back in cooked mode and SIGINT can fire). if set -q __config_toggle_exit set -eg __config_toggle_exit break end - # -n 1 reads exactly one char; -s suppresses echo so keystrokes do - # not garble the panel. If read fails (EOF / non-tty stdin) bail out - # instead of spinning the redraw loop. - read -s -n 1 -l key - or break - - # Detect CSI escape sequences (arrow keys: ESC [ A/B) - if test "$key" = \e - read -s -n 1 -l key2 - if test "$key2" = "[" - read -s -n 1 -l key3 - set key "$key$key2$key3" # \e[A or \e[B - else - # ESC + non-bracket (ALT+key or plain ESC): treat key2 as key - set key $key2 - end - end + set -l key (__config_toggle_read_key) + or break # not a TTY — exit instead of spinning switch $key - case \e\[A # Up arrow + case up k set cur_row (math "max(0, $cur_row - 1)") - case \e\[B # Down arrow + case down j set cur_row (math "min(6, $cur_row + 1)") - case k - set cur_row (math "max(0, $cur_row - 1)") - case j - set cur_row (math "min(6, $cur_row + 1)") - case \t # Tab — switch scope + case tab # Tab — switch scope if test $cur_scope = universal set cur_scope session else set cur_scope universal end - case ' ' # Space — cycle and apply immediately + case space # Space — cycle and apply immediately set -l varname $vars[(math $cur_row + 1)] set -l cur_val (__config_toggle_get_val $varname $cur_scope) set -l next_val "" @@ -162,7 +143,7 @@ function config-toggle --description 'Interactive TUI for toggling opinionated c set -eg $varname end end - case q Q + case q Q quit escape break end -- 2.52.0 From bddb068ec0ef692bb8760b6e911625f72288ab04 Mon Sep 17 00:00:00 2001 From: rootiest Date: Thu, 11 Jun 2026 03:07:17 -0400 Subject: [PATCH 14/16] feat(config-toggle): add Left/Right arrows for directional value changes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- docs/fish-config.md | 9 ++++- functions/__config_toggle_apply.fish | 53 +++++++++++++++++++++++++ functions/__config_toggle_draw.fish | 6 ++- functions/config-toggle.fish | 58 ++++++++++++++++------------ 4 files changed, 98 insertions(+), 28 deletions(-) create mode 100644 functions/__config_toggle_apply.fish diff --git a/docs/fish-config.md b/docs/fish-config.md index 78b3f4d..19ab311 100644 --- a/docs/fish-config.md +++ b/docs/fish-config.md @@ -1232,15 +1232,20 @@ Add -i (interactive confirmation) to destructive commands: Universal — persists across all sessions (set -U) Session — current shell only (set -g) - Changes apply immediately on each Space keypress. Always available + Changes apply immediately on each value keypress. Always available regardless of the __fish_config_opinionated master state. Navigation: ↑ ↓ / j k Move cursor - Space Cycle: ON → OFF → DEFAULT → ON + ← → Set value directionally: OFF ← DEFAULT → ON (clamped) + Space Cycle through all states: ON → OFF → DEFAULT → ON (wraps) Tab Switch scope (Universal ↔ Session) q / Escape Exit + Left/Right move one step along the OFF–DEFAULT–ON scale and stop at the + ends; Space cycles through every state and wraps around. DEFAULT erases + the variable so the master switch / built-in default applies. + Flags: --help / -h Show usage. diff --git a/functions/__config_toggle_apply.fish b/functions/__config_toggle_apply.fish new file mode 100644 index 0000000..dde90ba --- /dev/null +++ b/functions/__config_toggle_apply.fish @@ -0,0 +1,53 @@ +# Copyright (C) 2026 Rootiest +# SPDX-License-Identifier: AGPL-3.0-or-later + +# SYNOPSIS +# __config_toggle_apply +# +# DESCRIPTION +# Applies a state value to an opinionated-component variable in the given +# scope, immediately and persistently. Used by config-toggle's Space (cycle) +# and Left/Right (directional) handlers so the set/erase logic lives in one +# place. A value of "DEFAULT" erases the variable in that scope so the +# master switch / built-in default takes over again. +# +# universal → set -U (on/off) / set -Ue (DEFAULT) +# session → set -g (on/off) / set -eg (DEFAULT) +# +# ARGUMENTS +# varname Variable name without the $ prefix +# scope "universal" or "session" +# value "on", "off", or "DEFAULT" +# +# RETURNS +# 0 Always +# +# EXAMPLE +# __config_toggle_apply __fish_config_op_aliases universal off +# __config_toggle_apply __fish_config_op_greeting session DEFAULT +function __config_toggle_apply + set -l varname $argv[1] + set -l scope $argv[2] + set -l value $argv[3] + + switch $scope + case universal + switch $value + case on + set -U $varname on + case off + set -U $varname off + case DEFAULT + set -Ue $varname + end + case session + switch $value + case on + set -g $varname on + case off + set -g $varname off + case DEFAULT + set -eg $varname + end + end +end diff --git a/functions/__config_toggle_draw.fish b/functions/__config_toggle_draw.fish index b1fdd81..45585c3 100644 --- a/functions/__config_toggle_draw.fish +++ b/functions/__config_toggle_draw.fish @@ -131,8 +131,10 @@ function __config_toggle_draw printf '│%s│\n' $HBR # ── Keybind hint ────────────────────────────────────── - printf '│ %s↑↓/jk: move Space: cycle Tab: scope q: quit%s │\n' \ - $c_dim $c_reset + # Padded to the inner width so the right border stays aligned regardless + # of the hint text. string pad is width-aware (arrows count as 1 column). + set -l hint " ↑↓ move ←→ set Space cycle Tab scope q quit" + printf '│%s%s%s│\n' $c_dim (string pad -r -w $IW -- $hint) $c_reset # ── Bottom border ───────────────────────────────────── printf '└%s┘\n' $HBR diff --git a/functions/config-toggle.fish b/functions/config-toggle.fish index 8d845bd..4b2113c 100644 --- a/functions/config-toggle.fish +++ b/functions/config-toggle.fish @@ -44,7 +44,8 @@ function config-toggle --description 'Interactive TUI for toggling opinionated c echo echo "$c_head""Navigation:$c_reset" echo " $c_flag↑ ↓$c_reset or $c_flag""j k$c_reset Move cursor up / down" - echo " $c_flag""Space$c_reset Cycle: ON → OFF → DEFAULT → ON" + echo " $c_flag← →$c_reset Set value: OFF ← DEFAULT → ON (clamped)" + echo " $c_flag""Space$c_reset Cycle: ON → OFF → DEFAULT → ON (wraps)" echo " $c_flag""Tab$c_reset Switch scope (Universal ↔ Session)" echo " $c_flag""q$c_reset / $c_flag""Esc$c_reset Exit" echo @@ -107,12 +108,12 @@ function config-toggle --description 'Interactive TUI for toggling opinionated c else set cur_scope universal end - case space # Space — cycle and apply immediately + case space # Space — cycle through all states and apply set -l varname $vars[(math $cur_row + 1)] set -l cur_val (__config_toggle_get_val $varname $cur_scope) - set -l next_val "" - # Cycle: on → off → DEFAULT → on + # Cycle (wraps): on → off → DEFAULT → on + set -l next_val on switch $cur_val case on set next_val off @@ -121,28 +122,37 @@ function config-toggle --description 'Interactive TUI for toggling opinionated c case '*' # DEFAULT or any unrecognised set next_val on end + __config_toggle_apply $varname $cur_scope $next_val + case right # → step toward ON (clamped, no wrap) + set -l varname $vars[(math $cur_row + 1)] + set -l cur_val (__config_toggle_get_val $varname $cur_scope) - # Apply immediately - switch $cur_scope - case universal - switch $next_val - case on - set -U $varname on - case off - set -U $varname off - case DEFAULT - set -Ue $varname - end - case session - switch $next_val - case on - set -g $varname on - case off - set -g $varname off - case DEFAULT - set -eg $varname - end + # Order: OFF ← DEFAULT → ON + set -l next_val on + switch $cur_val + case off + set next_val DEFAULT + case on + set next_val on # already at the right end + case '*' # DEFAULT or unrecognised + set next_val on end + __config_toggle_apply $varname $cur_scope $next_val + case left # ← step toward OFF (clamped, no wrap) + set -l varname $vars[(math $cur_row + 1)] + set -l cur_val (__config_toggle_get_val $varname $cur_scope) + + # Order: OFF ← DEFAULT → ON + set -l next_val off + switch $cur_val + case on + set next_val DEFAULT + case off + set next_val off # already at the left end + case '*' # DEFAULT or unrecognised + set next_val off + end + __config_toggle_apply $varname $cur_scope $next_val case q Q quit escape break end -- 2.52.0 From cfd4749e720cb14ad568bbf31687af7cce8e540f Mon Sep 17 00:00:00 2001 From: rootiest Date: Thu, 11 Jun 2026 03:10:50 -0400 Subject: [PATCH 15/16] feat(config-toggle): drop Space, add vim h/l for value changes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- docs/fish-config.md | 11 +++++------ functions/__config_toggle_apply.fish | 4 ++-- functions/__config_toggle_draw.fish | 2 +- functions/config-toggle.fish | 29 +++++++--------------------- 4 files changed, 15 insertions(+), 31 deletions(-) diff --git a/docs/fish-config.md b/docs/fish-config.md index 19ab311..a24b2c4 100644 --- a/docs/fish-config.md +++ b/docs/fish-config.md @@ -1236,15 +1236,14 @@ Add -i (interactive confirmation) to destructive commands: regardless of the __fish_config_opinionated master state. Navigation: - ↑ ↓ / j k Move cursor - ← → Set value directionally: OFF ← DEFAULT → ON (clamped) - Space Cycle through all states: ON → OFF → DEFAULT → ON (wraps) + ↑ ↓ / k j Move cursor + ← → / h l Set value: OFF ← DEFAULT → ON (clamped) Tab Switch scope (Universal ↔ Session) q / Escape Exit - Left/Right move one step along the OFF–DEFAULT–ON scale and stop at the - ends; Space cycles through every state and wraps around. DEFAULT erases - the variable so the master switch / built-in default applies. + Left/Right (or vim-style h/l) move the highlighted value one step along + the OFF–DEFAULT–ON scale and stop at the ends. DEFAULT erases the + variable so the master switch / built-in default applies. Flags: --help / -h Show usage. diff --git a/functions/__config_toggle_apply.fish b/functions/__config_toggle_apply.fish index dde90ba..7ce27d0 100644 --- a/functions/__config_toggle_apply.fish +++ b/functions/__config_toggle_apply.fish @@ -6,8 +6,8 @@ # # DESCRIPTION # Applies a state value to an opinionated-component variable in the given -# scope, immediately and persistently. Used by config-toggle's Space (cycle) -# and Left/Right (directional) handlers so the set/erase logic lives in one +# scope, immediately and persistently. Used by config-toggle's Left/Right +# (and vim h/l) directional handlers so the set/erase logic lives in one # place. A value of "DEFAULT" erases the variable in that scope so the # master switch / built-in default takes over again. # diff --git a/functions/__config_toggle_draw.fish b/functions/__config_toggle_draw.fish index 45585c3..235e2f3 100644 --- a/functions/__config_toggle_draw.fish +++ b/functions/__config_toggle_draw.fish @@ -133,7 +133,7 @@ function __config_toggle_draw # ── Keybind hint ────────────────────────────────────── # Padded to the inner width so the right border stays aligned regardless # of the hint text. string pad is width-aware (arrows count as 1 column). - set -l hint " ↑↓ move ←→ set Space cycle Tab scope q quit" + set -l hint " ↑↓/kj move ←→/hl set Tab scope q quit" printf '│%s%s%s│\n' $c_dim (string pad -r -w $IW -- $hint) $c_reset # ── Bottom border ───────────────────────────────────── diff --git a/functions/config-toggle.fish b/functions/config-toggle.fish index 4b2113c..2cac262 100644 --- a/functions/config-toggle.fish +++ b/functions/config-toggle.fish @@ -13,8 +13,9 @@ # Universal — persists across all sessions (set -U / set -Ue) # Session — active for the current shell only (set -g / set -eg) # -# Changes apply immediately on each Space keypress — no confirm step. -# Always available regardless of __fish_config_opinionated state. +# Values are changed directionally with the arrow keys (or vim-style h/l) +# along an OFF ← DEFAULT → ON scale, and apply immediately — no confirm +# step. Always available regardless of __fish_config_opinionated state. # # ARGUMENTS # -h, --help Print usage and exit @@ -43,9 +44,8 @@ function config-toggle --description 'Interactive TUI for toggling opinionated c echo " Changes apply immediately — no confirm step required." echo echo "$c_head""Navigation:$c_reset" - echo " $c_flag↑ ↓$c_reset or $c_flag""j k$c_reset Move cursor up / down" - echo " $c_flag← →$c_reset Set value: OFF ← DEFAULT → ON (clamped)" - echo " $c_flag""Space$c_reset Cycle: ON → OFF → DEFAULT → ON (wraps)" + echo " $c_flag↑ ↓$c_reset or $c_flag""k j$c_reset Move cursor up / down" + echo " $c_flag← →$c_reset or $c_flag""h l$c_reset Set value: OFF ← DEFAULT → ON" echo " $c_flag""Tab$c_reset Switch scope (Universal ↔ Session)" echo " $c_flag""q$c_reset / $c_flag""Esc$c_reset Exit" echo @@ -108,22 +108,7 @@ function config-toggle --description 'Interactive TUI for toggling opinionated c else set cur_scope universal end - case space # Space — cycle through all states and apply - set -l varname $vars[(math $cur_row + 1)] - set -l cur_val (__config_toggle_get_val $varname $cur_scope) - - # Cycle (wraps): on → off → DEFAULT → on - set -l next_val on - switch $cur_val - case on - set next_val off - case off - set next_val DEFAULT - case '*' # DEFAULT or any unrecognised - set next_val on - end - __config_toggle_apply $varname $cur_scope $next_val - case right # → step toward ON (clamped, no wrap) + case right l # → / l — step toward ON (clamped, no wrap) set -l varname $vars[(math $cur_row + 1)] set -l cur_val (__config_toggle_get_val $varname $cur_scope) @@ -138,7 +123,7 @@ function config-toggle --description 'Interactive TUI for toggling opinionated c set next_val on end __config_toggle_apply $varname $cur_scope $next_val - case left # ← step toward OFF (clamped, no wrap) + case left h # ← / h — step toward OFF (clamped, no wrap) set -l varname $vars[(math $cur_row + 1)] set -l cur_val (__config_toggle_get_val $varname $cur_scope) -- 2.52.0 From 764a9d35e5e01658eea4c4bc4b1c07aea0af0146 Mon Sep 17 00:00:00 2001 From: rootiest Date: Thu, 11 Jun 2026 03:16:33 -0400 Subject: [PATCH 16/16] feat(config-toggle): right-align ON badge to mirror value direction MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- functions/__config_toggle_draw.fish | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/functions/__config_toggle_draw.fish b/functions/__config_toggle_draw.fish index 235e2f3..4dd5fbe 100644 --- a/functions/__config_toggle_draw.fish +++ b/functions/__config_toggle_draw.fish @@ -83,7 +83,7 @@ function __config_toggle_draw set -l badge switch $val case on - set badge "$c_ok""ON $c_reset" + set badge "$c_ok"" ON$c_reset" case off set badge "$c_err""OFF $c_reset" case '*' @@ -111,7 +111,7 @@ function __config_toggle_draw set -l badge switch $val case on - set badge "$c_ok""ON $c_reset" + set badge "$c_ok"" ON$c_reset" case off set badge "$c_err""OFF $c_reset" case '*' -- 2.52.0