feat(config-toggle): adaptive width, centering, and resize handling

Panel now selects from four width tiers based on $COLUMNS (with a 6-col
buffer per side before stepping up): 52-wide (default), 70, 74, or 78.
Each tier carries richer category descriptions sized to fit the layout.
The box is horizontally centered on every draw via a left-padding prefix.

Key reader switches from stty min 1 to min 0 / time 3, giving a 0.3 s
poll interval so COLUMNS changes are detected without a keypress. The
erase formula is now wrap-aware: prev_max_line_width / COLUMNS gives the
wrap factor, ensuring old wider panels are fully cleared after narrowing.
Idle timeout ticks that detect no resize are skipped entirely.
This commit is contained in:
2026-06-12 19:17:49 -04:00
parent 1253f67c9c
commit 93fc5e0517
5 changed files with 134 additions and 56 deletions
+1 -1
View File
@@ -168,7 +168,7 @@ end
Everything opinionated in this config — command shadows, startup side-effects, key and environment overrides, terminal integrations, logging, and the first-run greeting — is active by default but can be switched off. Everything opinionated in this config — command shadows, startup side-effects, key and environment overrides, terminal integrations, logging, and the first-run greeting — is active by default but can be switched off.
> **The easy way — `config-toggle`:** Run `config-toggle` for an interactive TUI that flips every setting below on, off, or back to default — per-session or universally — without typing a single variable name. Use the arrow keys (or `h`/`j`/`k`/`l`) to navigate and adjust, `Tab` to switch scope, and `q` to quit. Changes apply instantly. > **The easy way — `config-toggle`:** Run `config-toggle` for an interactive TUI that flips every setting below on, off, or back to default — per-session or universally — without typing a single variable name. Use the arrow keys (or `h`/`j`/`k`/`l`) to navigate and adjust, `Tab` to switch scope, and `q` to quit. Changes apply instantly. The panel auto-sizes to your terminal width (four tiers from 52- to 78-wide with a 6-column margin), centers itself horizontally, and redraws within ~0.3 s of a resize.
If you'd rather set them by hand, each category is controlled by a universal variable. Six category toggles and one master switch are available: If you'd rather set them by hand, each category is controlled by a universal variable. Six category toggles and one master switch are available:
+14 -4
View File
@@ -1246,10 +1246,10 @@ Add -i (interactive confirmation) to destructive commands:
Synopsis: config-toggle [-h] Synopsis: config-toggle [-h]
Opens an interactive full-screen TUI for toggling the six opinionated Opens an interactive TUI for toggling the six opinionated component
component categories (C1C6) and the master disable variable without categories (C1C6) and the master disable variable without having to
having to type or remember variable names. Two scope tabs allow type or remember variable names. Two scope tabs allow independent
independent per-scope configuration: per-scope configuration:
Universal — persists across all sessions (set -U) Universal — persists across all sessions (set -U)
Session — current shell only (set -g) Session — current shell only (set -g)
@@ -1257,6 +1257,16 @@ Add -i (interactive confirmation) to destructive commands:
Changes apply immediately on each value keypress. Always available Changes apply immediately on each value keypress. Always available
regardless of the __fish_config_opinionated master state. regardless of the __fish_config_opinionated master state.
The panel adapts to the terminal width automatically, selecting from
four layout tiers (with a 6-column buffer on each side before stepping
up to the next tier) and horizontally centering the box. The panel
redraws within ~0.3 s of a terminal resize with no keypress required.
COLUMNS >= 90 → 78-wide panel (most detail)
COLUMNS >= 86 → 74-wide panel
COLUMNS >= 82 → 70-wide panel
COLUMNS < 82 → 52-wide panel (default)
Navigation: Navigation:
↑ ↓ / k j Move cursor ↑ ↓ / k j Move cursor
← → / h l Set value: OFF ← DEFAULT → ON (clamped) ← → / h l Set value: OFF ← DEFAULT → ON (clamped)
+97 -44
View File
@@ -5,10 +5,19 @@
# __config_toggle_draw <cur_row> <cur_scope> <var1> ... <var7> # __config_toggle_draw <cur_row> <cur_scope> <var1> ... <var7>
# #
# DESCRIPTION # DESCRIPTION
# Renders the 14-line config-toggle TUI panel to stdout. Intended to be # Renders the 14-line config-toggle TUI panel to stdout. Panel width and
# called once on startup, then again after each keypress preceded by # horizontal position are chosen automatically from $COLUMNS each call,
# `printf '\e[14A\e[J'` (cursor-up 14 lines + clear-to-end) to redraw # so a terminal resize takes effect on the next keypress without any
# in place. Does not move the cursor — caller manages positioning. # extra bookkeeping. Four width tiers with a 6-col buffer per side:
#
# COLUMNS ≥ 9078-wide (IW=76, desc=43 chars)
# COLUMNS ≥ 8674-wide (IW=72, desc=39 chars)
# COLUMNS ≥ 8270-wide (IW=68, desc=35 chars)
# COLUMNS < 8252-wide (IW=50, desc=17 chars) ← default
#
# The box is horizontally centered via a left-padding prefix on every
# output line. \e[14A\e[J erases by line count so the horizontal offset
# does not interfere with the redraw loop.
# #
# ARGUMENTS # ARGUMENTS
# cur_row 06, the currently highlighted row # cur_row 06, the currently highlighted row
@@ -37,14 +46,60 @@ function __config_toggle_draw
set -l c_head (set_color --bold cyan) set -l c_head (set_color --bold cyan)
set -l c_reset (set_color normal) set -l c_reset (set_color normal)
set -l labels Aliases Auto-exec Overrides Integrations Logging Greeting Master 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 # ── Width tier: 6-col buffer per side before stepping up ──────────────
set -l IW 50 # inner width (chars between │ │) # IW = inner width (chars between │ │); desc field = IW - 33.
set -l HBR (string repeat -n $IW '─') # All four layouts are exactly 14 lines tall — panel_h in caller stays 14.
set -l iw 50
set -l descs \
"cmd shadows" \
startup \
"keys/env/prompt" \
"terminal coupling" \
scrollback \
fish_greeting \
"disable all"
# ── Scope tabs ──────────────────────────────────────── if test "$COLUMNS" -ge 90
set iw 76
set descs \
"shadows: ls→eza, cat→bat, cd→z, rm→trash" \
"Fisher bootstrap, themes, py-venv activate" \
"vi-mode, bang-bang, PAGER, CDPATH, starship" \
"Kitty/WezTerm tab/split fns, notifications" \
"scrollback capture & paru/yay AUR wrappers" \
"fish_greeting & first-run welcome banner" \
"master off-switch: overrides all categories"
else if test "$COLUMNS" -ge 86
set iw 72
set descs \
"ls→eza, cat→bat, cd→zoxide, rm→trash" \
"Fisher bootstrap, themes, py-venv auto" \
"vi-mode, bang-bang, PAGER, starship" \
"Kitty/WezTerm fns, done notifications" \
"scrollback capture & paru/yay wrappers" \
"fish_greeting: first-run welcome banner" \
"master off-switch for all categories"
else if test "$COLUMNS" -ge 82
set iw 68
set descs \
"ls→eza, cat→bat, cd→z, rm→trash" \
"Fisher, themes, py-venv activate" \
"vi-mode, bang-bang, PAGER, starship" \
"Kitty/WezTerm, done notifications" \
"scrollback & paru/yay log wrappers" \
"fish_greeting & first-run banner" \
"master disable for all categories"
end
set -l HBR (string repeat -n $iw '─')
# ── Center padding ────────────────────────────────────────────────────
# ponytail: floor division — left margin may be 1 col less than right if gap is odd
set -l p (string repeat -n (math --scale=0 "max(0, ($COLUMNS - ($iw + 2)) / 2)") ' ')
# ── Scope tabs ────────────────────────────────────────────────────────
set -l u_label set -l u_label
set -l s_label set -l s_label
if test $cur_scope = universal if test $cur_scope = universal
@@ -55,27 +110,26 @@ function __config_toggle_draw
set s_label "$c_hi● Session $c_reset" set s_label "$c_hi● Session $c_reset"
end end
# ── Top border ──────────────────────────────────────── # ── Top border ────────────────────────────────────────────────────────
# Visible: ┌(1)(1) space(1) "Opinionated Settings"(20) space(1) N×─ ┐(1) = 52 # ┌─ Opinionated Settings (iw-23)×─ ┐ total = iw+2
# N = 52 - 25 = 27 = IW - 23 printf '%s┌─%s Opinionated Settings %s┐\n' \
printf '┌─%s Opinionated Settings %s┐\n' \ $p $c_head $c_reset(string repeat -n (math $iw - 23) '─')
$c_head $c_reset(string repeat -n (math $IW - 23) '─')
# ── Scope tab line ──────────────────────────────────── # ── Scope tab line ────────────────────────────────────────────────────
# Visible inner (50): space(1) + u_label(11) + 2spaces + s_label(11) + 10spaces + "Tab to switch "(15) = 50 # Inner: " ●/○ Universal ●/○ Session " (25) + (iw-40)×space + "Tab to switch " (15) = iw
printf '│ %s %s%s│\n' \ printf '%s│ %s %s%s│\n' \
$u_label $s_label \ $p $u_label $s_label \
(string repeat -n 10 ' ')"Tab to switch " (string repeat -n (math $iw - 40) ' ')"Tab to switch "
# ── Top divider ─────────────────────────────────────── # ── Top divider ───────────────────────────────────────────────────────
printf '│%s│\n' $HBR printf '%s│%s│\n' $p $HBR
# ── Category rows 05 ───────────────────────────────── # ── Category rows 05 ─────────────────────────────────────────────────
for i in (seq 0 5) for i in (seq 0 5)
set -l idx (math $i + 1) set -l idx (math $i + 1)
set -l var $vars[$idx] set -l var $vars[$idx]
set -l label $labels[$idx] set -l label $labels[$idx]
set -l desc $descs[$idx] set -l desc $descs[$idx]
set -l val (__config_toggle_get_val $var $cur_scope) set -l val (__config_toggle_get_val $var $cur_scope)
@@ -96,17 +150,17 @@ function __config_toggle_draw
set curs "$c_sel$c_reset " set curs "$c_sel$c_reset "
end end
# Label padded to 12, desc padded to 17, right margin 3 # Label padded to 12, desc padded to (iw-33), right margin 3
set -l lpad (string pad -r -w 12 -- $label) set -l lpad (string pad -r -w 12 -- $label)
set -l dpad (string pad -r -w 17 -- $desc) set -l dpad (string pad -r -w (math $iw - 33) -- $desc)
printf '│ %s%s [ %s ] %s │\n' $curs $lpad $badge $dpad printf '%s│ %s%s [ %s ] %s │\n' $p $curs $lpad $badge $dpad
end end
# ── Separator before Master ─────────────────────────── # ── Separator before Master ───────────────────────────────────────────
printf '│ %s │\n' (string repeat -n (math $IW - 6) '─') printf '%s│ %s │\n' $p (string repeat -n (math $iw - 6) '─')
# ── Master row (index 6) ────────────────────────────── # ── Master row (index 6) ──────────────────────────────────────────────
set -l val (__config_toggle_get_val $vars[7] $cur_scope) set -l val (__config_toggle_get_val $vars[7] $cur_scope)
set -l badge set -l badge
switch $val switch $val
@@ -121,21 +175,20 @@ function __config_toggle_draw
if test $cur_row -eq 6 if test $cur_row -eq 6
set curs "$c_sel$c_reset " set curs "$c_sel$c_reset "
end end
printf '│ %s%s [ %s ] %s │\n' \ printf '%s│ %s%s [ %s ] %s │\n' \
$curs \ $p $curs \
(string pad -r -w 12 -- Master) \ (string pad -r -w 12 -- Master) \
$badge \ $badge \
(string pad -r -w 17 -- "disable all") (string pad -r -w (math $iw - 33) -- $descs[7])
# ── Bottom divider ──────────────────────────────────── # ── Bottom divider ────────────────────────────────────────────────────
printf '│%s│\n' $HBR printf '%s│%s│\n' $p $HBR
# ── Keybind hint ────────────────────────────────────── # ── Keybind hint ──────────────────────────────────────────────────────
# Padded to the inner width so the right border stays aligned regardless # string pad is width-aware (arrows count as 1 column)
# of the hint text. string pad is width-aware (arrows count as 1 column).
set -l hint " ↑↓/kj move ←→/hl set 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 printf '%s│%s%s%s│\n' $p $c_dim (string pad -r -w $iw -- $hint) $c_reset
# ── Bottom border ───────────────────────────────────── # ── Bottom border ─────────────────────────────────────────────────────
printf '└%s┘\n' $HBR printf '%s└%s┘\n' $p $HBR
end end
+4 -3
View File
@@ -42,9 +42,10 @@ function __config_toggle_read_key
set -l saved (stty -g </dev/tty 2>/dev/null) set -l saved (stty -g </dev/tty 2>/dev/null)
or return 1 or return 1
# Raw, no-echo. min 1 / time 1: block for the first byte, then wait up to # Raw, no-echo. min 0 / time 3: return after 0.3s even with no bytes (poll
# 0.1s for the rest of a burst (escape sequence) before returning. # interval for resize detection), or immediately when any bytes arrive.
stty raw -echo min 1 time 1 </dev/tty 2>/dev/null # Escape sequences (e.g. arrow keys) arrive fast enough to land in one read.
stty raw -echo min 0 time 3 </dev/tty 2>/dev/null
# One read() of up to 3 bytes — covers ESC [ A style sequences. od emits # One read() of up to 3 bytes — covers ESC [ A style sequences. od emits
# the bytes as space-separated decimal codes. # the bytes as space-separated decimal codes.
+18 -4
View File
@@ -72,7 +72,8 @@ function config-toggle --description 'Interactive TUI for toggling opinionated c
set -l cur_row 0 # 06 set -l cur_row 0 # 06
set -l cur_scope universal # or "session" set -l cur_scope universal # or "session"
set -l panel_h 14 # total panel lines set -l panel_h 14 # total panel lines (all width tiers are 14 lines)
set -l last_cols $COLUMNS # COLUMNS at the time of the last draw
# ── Terminal setup ──────────────────────────────────── # ── Terminal setup ────────────────────────────────────
printf '\e[?25l' # hide cursor printf '\e[?25l' # hide cursor
@@ -142,13 +143,26 @@ function config-toggle --description 'Interactive TUI for toggling opinionated c
break break
end end
# Redraw in place # Skip redraw entirely when the key reader timed out with no resize
printf '\e[%dA\e[J' $panel_h if test -z "$key" -a "$COLUMNS" = "$last_cols"
continue
end
# Wrap-aware erase: a panel drawn on a wider terminal has longer lines
# (due to center-padding) that wrap into extra physical rows when the
# terminal narrows. 78 = widest box (IW=76+2); the formula gives the
# worst-case old line width for any tier drawn at last_cols.
set -l prev_max_lw (math --scale=0 "($last_cols + 78) / 2")
set -l erase_h (math --scale=0 "$panel_h * max(1, ceil($prev_max_lw / $COLUMNS))")
printf '\e[%dA\e[J' $erase_h
set last_cols $COLUMNS
__config_toggle_draw $cur_row $cur_scope $vars __config_toggle_draw $cur_row $cur_scope $vars
end end
# ── Cleanup ─────────────────────────────────────────── # ── Cleanup ───────────────────────────────────────────
trap - INT # remove the signal handler trap - INT # remove the signal handler
printf '\e[%dA\e[J' $panel_h # erase the panel set -l prev_max_lw (math --scale=0 "($last_cols + 78) / 2")
set -l erase_h (math --scale=0 "$panel_h * max(1, ceil($prev_max_lw / $COLUMNS))")
printf '\e[%dA\e[J' $erase_h # erase the panel (wrap-aware)
printf '\e[?25h' # restore cursor printf '\e[?25h' # restore cursor
end end