Merge pull request 'feat(config-settings): four-page TUI (Universal · Session · Sponge · Paths)' (#66) from feat/config-settings-pages into main
Offline docs drift reminder / remind (push) Successful in 13s
Generate documentation / build-docs (push) Successful in 39s

Reviewed-on: #66
This commit was merged in pull request #66.
This commit is contained in:
2026-07-04 05:26:10 +00:00
12 changed files with 611 additions and 169 deletions
+1 -1
View File
@@ -255,7 +255,7 @@ test -f "$__fish_user_dots_path/local.fish"
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-settings`:** Run `config-settings` 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. > **The easy way — `config-settings`:** Run `config-settings` for an interactive TUI that manages settings across four pages — **Universal** and **Session** (the opinionated category toggles below, persistent or per-shell), **Sponge** (history-scrubbing: delay, exit codes, purge-on-exit, and extra sensitive variable names), and **Paths** (scrollback log dir, max files, and the user-dots path) — without typing a single variable name. Navigate with the arrow keys (or `h`/`j`/`k`/`l`); toggle rows step OFF ← DEFAULT → ON, value rows edit inline with `Enter` and clear with `←`. `Tab`/`Shift-Tab` cycle pages and `q` quits. 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:
+13 -2
View File
@@ -95,8 +95,19 @@ function __sponge_register_secret_values --on-event fish_prompt
set -l secret_values set -l secret_values
set -l sensitive_vars (set --names --export | string match --regex -- \ # Base credential-name tokens, plus any user-supplied extras from
'(?i)(?:TOKEN|PASSWORD|PASSWD|SECRET|API[_-]KEY|PRIVATE[_-]KEY|ACCESS[_-]KEY|AUTH[_-]KEY|CREDENTIAL|KOPIA_PASSWORD)') # __fish_sponge_extra_sensitive (set via config-settings → Sponge page).
set -l _sensitive_names \
TOKEN PASSWORD PASSWD SECRET 'API[_-]KEY' 'PRIVATE[_-]KEY' \
'ACCESS[_-]KEY' 'AUTH[_-]KEY' CREDENTIAL KOPIA_PASSWORD \
$__fish_sponge_extra_sensitive
set -l _sensitive_alt (string join '|' $_sensitive_names)
# --entire returns the full matching variable NAME (e.g. GITHUB_TOKEN), not
# just the matched token substring (TOKEN) — required so $$var below
# dereferences the real variable instead of an unset partial name.
set -l sensitive_vars (set --names --export | string match --regex --entire -- \
"(?i)(?:$_sensitive_alt)")
for var in $sensitive_vars for var in $sensitive_vars
# Take only the first element — array vars yield multiple values. # Take only the first element — array vars yield multiple values.
+10 -2
View File
@@ -116,10 +116,18 @@ set -gx SUDO_EDITOR $EDITOR
set -gx GPG_TTY (tty) set -gx GPG_TTY (tty)
# ────────────────────────── Scrollback History ────────────────────────── # ────────────────────────── Scrollback History ──────────────────────────
# Directory where scrollback history is saved into log files. # Optional fish-style source of truth (set -U via config-settings); exported
# for the POSIX wrappers (paru/yay/tmux/zellij/_prune_terminal_logs) that read
# them. We export the default unconditionally, then override from the universal
# var only if set — deliberately NOT creating a global, which would shadow the
# universal and stop live edits (config-settings → Paths) from taking effect.
set -gx SCROLLBACK_HISTORY_DIR "$HOME/.terminal_history" set -gx SCROLLBACK_HISTORY_DIR "$HOME/.terminal_history"
# Maximum number of scrollback history files to keep set -q __fish_scrollback_history_dir
and set -gx SCROLLBACK_HISTORY_DIR $__fish_scrollback_history_dir
set -gx SCROLLBACK_HISTORY_MAX_FILES 100 set -gx SCROLLBACK_HISTORY_MAX_FILES 100
set -q __fish_scrollback_history_max_files
and set -gx SCROLLBACK_HISTORY_MAX_FILES $__fish_scrollback_history_max_files
# Wire up a clean exit function that won't fire on background subshells # Wire up a clean exit function that won't fire on background subshells
# Replacing the exit builtin is opinionated (C3 overrides); smart_exit also # Replacing the exit builtin is opinionated (C3 overrides); smart_exit also
# guards itself so a live toggle takes effect without restarting the shell. # guards itself so a live toggle takes effect without restarting the shell.
+6
View File
@@ -24,6 +24,9 @@ toolhomes=## Tool Homes (XDG-compliant)
editor=## Editor and Pager editor=## Editor and Pager
pager=## Editor and Pager pager=## Editor and Pager
scrollback=## Scrollback History scrollback=## Scrollback History
__fish_scrollback_history_dir=## Scrollback History
__fish_scrollback_history_max_files=## Scrollback History
scrollback-dir=## Scrollback History
# ── Section 2: PATH ─────────────────────────────────────────── # ── Section 2: PATH ───────────────────────────────────────────
path=# 2. PATH SETUP path=# 2. PATH SETUP
@@ -258,6 +261,9 @@ sponge=## Fisher-Managed Plugins
sponge-filtering=## Sponge History Filtering sponge-filtering=## Sponge History Filtering
history-filtering=## Sponge History Filtering history-filtering=## Sponge History Filtering
privacy=## Sponge History Filtering privacy=## Sponge History Filtering
__fish_sponge_extra_sensitive=## Sponge History Filtering
extra-sensitive=## Sponge History Filtering
sponge-settings=### config-settings
bundled-plugins=## Bundled Plugin Functionality bundled-plugins=## Bundled Plugin Functionality
fish-plugins-manifest=## fish_plugins Manifest fish-plugins-manifest=## fish_plugins Manifest
+57 -28
View File
@@ -166,8 +166,19 @@ Tools that respect XDG are directed to these paths rather than polluting $HOME.
## Scrollback History ## Scrollback History
SCROLLBACK_HISTORY_DIR ~/.terminal_history __fish_scrollback_history_dir (unset → ~/.terminal_history)
SCROLLBACK_HISTORY_MAX_FILES 100 __fish_scrollback_history_max_files (unset → 100)
SCROLLBACK_HISTORY_DIR ~/.terminal_history (exported mirror)
SCROLLBACK_HISTORY_MAX_FILES 100 (exported mirror)
The __fish_scrollback_history_* universal variables are the fish-style source
of truth — set them via `config-settings` → Paths, or `set -U` directly.
config.fish exports the SCROLLBACK_HISTORY_* mirrors from them, because the
POSIX wrapper scripts (paru/yay/tmux/zellij logging and _prune_terminal_logs)
read the exported names from the environment. When the __fish_ vars are unset,
the documented defaults are exported. config.fish deliberately does not create
a global source var, which would shadow the universal and stop live edits from
taking effect.
Scrollback logs accumulate in SCROLLBACK_HISTORY_DIR as timestamped files. Scrollback logs accumulate in SCROLLBACK_HISTORY_DIR as timestamped files.
When the count exceeds SCROLLBACK_HISTORY_MAX_FILES the oldest are pruned When the count exceeds SCROLLBACK_HISTORY_MAX_FILES the oldest are pruned
@@ -1352,21 +1363,33 @@ Add -i (interactive confirmation) to destructive commands:
Synopsis: config-settings [-h] Synopsis: config-settings [-h]
Opens an interactive TUI for managing fish configuration settings. Handles Opens an interactive TUI for managing fish configuration settings across
the six opinionated component categories (C1C6), the master disable four pages, without having to type or remember variable names. Tab cycles
variable, and the __fish_user_dots_path path variable — without having to forward through the pages; Shift-Tab cycles backward.
type or remember variable names. Two scope tabs allow independent per-scope
configuration for category toggles:
Universal — persists across all sessions (set -U) Universal — opinionated category toggles (C1C6) + master, persistent (set -U)
Session — current shell only (set -g) Session — the same toggles, current shell only (set -g)
Sponge — sponge history-scrubbing settings: delay, successful exit
codes, purge-only-on-exit, allow-previously-successful, and
extra sensitive variable-name tokens
Paths — scrollback log directory, scrollback max files, and the
user-dots path
The Dots Path row always operates on the universal scope regardless of the Toggle rows use ← → (or h/l) along an OFF ← DEFAULT → ON scale; DEFAULT
active tab. erases the variable so the master switch / built-in default applies. Value
rows (the path/int/list settings on the Sponge and Paths pages) use Enter to
edit inline; ← / h clears the value back to its default. List rows (e.g.
Extra secret, OK codes) accept values separated by commas and/or whitespace
— "A, B", "A,B" and "A B" all yield the same two entries. Changes apply
immediately. Always available regardless of the __fish_config_opinionated
master state.
Changes to category toggles apply immediately on each keypress. Changes to The Sponge and Paths pages always write universal variables — these are
the path variable use an inline edit prompt (Enter). Always available persistent, set-and-forget settings with no per-session scope. Editing a
regardless of the __fish_config_opinionated master state. scrollback row updates both the __fish_scrollback_history_* source-of-truth
variables and the exported SCROLLBACK_HISTORY_* mirrors, so the AUR/tmux/
zellij log wrappers (which read the exported names) see the change in the
running session.
The panel adapts to the terminal width automatically, selecting from The panel adapts to the terminal width automatically, selecting from
four layout tiers (with a 6-column buffer on each side before stepping four layout tiers (with a 6-column buffer on each side before stepping
@@ -1379,20 +1402,12 @@ Add -i (interactive confirmation) to destructive commands:
COLUMNS < 82 → 52-wide panel (default) COLUMNS < 82 → 52-wide panel (default)
Navigation: Navigation:
↑ ↓ / k j Move cursor ↑ ↓ / k j Move cursor
← → / h l Set value: OFF ← DEFAULT → ON (toggle rows); LEFT clears path ← → / h l Toggle rows: OFF ← DEFAULT → ON
Enter Edit the Dots Path (path row only) ← / h Value rows: clear to default
Tab Switch scope (Universal ↔ Session) Enter Value rows: edit inline (Sponge / Paths pages)
q / Escape Exit Tab / S-Tab Next / previous page
q / Escape Exit
Left/Right (or vim-style h/l) move the highlighted toggle value one step
along the OFFDEFAULTON scale and stop at the ends. DEFAULT erases the
variable so the master switch / built-in default applies. On the Dots Path
row, LEFT/h clears the universal variable; RIGHT/l is a no-op (use Enter).
Dots Path row [U] tag: the tag confirms this row always writes to the
universal scope, regardless of which tab is active. Setting it per-session
would have no effect on startup sourcing.
Flags: Flags:
--help / -h Show usage. --help / -h Show usage.
@@ -2043,6 +2058,20 @@ To add your own persistent patterns:
set -U -a sponge_regex_patterns 'your-regex-here' set -U -a sponge_regex_patterns 'your-regex-here'
To mark additional variable NAMES as credential-bearing (so Layer 2 scrubs
their values), add name tokens — via `config-settings` → Sponge, or directly:
set -U -a __fish_sponge_extra_sensitive ACME_API VAULT_PW
Tokens are folded into the Layer 2 name match case-insensitively as substrings,
so ACME_API also covers ACME_API_KEY. (The match uses `--entire` to return the
full variable name, so partial-name tokens dereference the right value.)
The `config-settings` Sponge page also surfaces sponge's own tuning variables —
sponge_delay, sponge_successful_exit_codes, sponge_purge_only_on_exit, and
sponge_allow_previously_successful — so they can be changed without typing
variable names.
## Bundled Plugin Functionality ## Bundled Plugin Functionality
The remaining plugin functionality is bundled directly with this config rather The remaining plugin functionality is bundled directly with this config rather
+16 -50
View File
@@ -2,7 +2,7 @@
# SPDX-License-Identifier: AGPL-3.0-or-later # SPDX-License-Identifier: AGPL-3.0-or-later
# SYNOPSIS # SYNOPSIS
# __config_settings_draw <cur_row> <cur_scope> <var1> ... <var8> # __config_settings_draw <cur_row> <cur_scope> <var1> ... <var7>
# #
# DESCRIPTION # DESCRIPTION
# Renders the 16-line config-settings TUI panel to stdout. Panel width and # Renders the 16-line config-settings TUI panel to stdout. Panel width and
@@ -20,9 +20,9 @@
# does not interfere with the redraw loop. # does not interfere with the redraw loop.
# #
# ARGUMENTS # ARGUMENTS
# cur_row 07, the currently highlighted row # cur_row 06, the currently highlighted row
# cur_scope "universal" or "session" # cur_scope "universal" or "session"
# var1var8 Variable names for rows 07 (in order per Variable Reference) # var1var7 Variable names for rows 06 (6 categories + master)
# #
# RETURNS # RETURNS
# 0 Always # 0 Always
@@ -32,7 +32,7 @@
# __fish_config_op_aliases __fish_config_op_autoexec \ # __fish_config_op_aliases __fish_config_op_autoexec \
# __fish_config_op_overrides __fish_config_op_integrations \ # __fish_config_op_overrides __fish_config_op_integrations \
# __fish_config_op_logging __fish_config_op_greeting \ # __fish_config_op_logging __fish_config_op_greeting \
# __fish_config_opinionated __fish_user_dots_path # __fish_config_opinionated
function __config_settings_draw function __config_settings_draw
set -l cur_row $argv[1] set -l cur_row $argv[1]
set -l cur_scope $argv[2] set -l cur_scope $argv[2]
@@ -99,27 +99,17 @@ function __config_settings_draw
# ponytail: floor division — left margin may be 1 col less than right if gap is odd # 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)") ' ') set -l p (string repeat -n (math --scale=0 "max(0, ($COLUMNS - ($iw + 2)) / 2)") ' ')
# ── 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 ──────────────────────────────────────────────────────── # ── Top border ────────────────────────────────────────────────────────
# ┌─ Opinionated Settings (iw-23)×─ ┐ total = iw+2 # ┌─ Opinionated Settings (iw-23)×─ ┐ total = iw+2
printf '%s┌─%s Opinionated Settings %s┐\n' \ printf '%s┌─%s Opinionated Settings %s┐\n' \
$p $c_head $c_reset(string repeat -n (math $iw - 23) '─') $p $c_head $c_reset(string repeat -n (math $iw - 23) '─')
# ── Scope tab line ──────────────────────────────────────────────────── # ── Page-tab header ───────────────────────────────────────────────────
# Inner: " ●/○ Universal ●/○ Session " (25) + (iw-40)×space + "Tab to switch " (15) = iw set -l active_idx 0
printf '%s│ %s %s%s│\n' \ if test $cur_scope = session
$p $u_label $s_label \ set active_idx 1
(string repeat -n (math $iw - 40) ' ')"Tab to switch " end
printf '%s│%s│\n' $p (__config_settings_pagetab $active_idx $iw)
# ── Top divider ─────────────────────────────────────────────────────── # ── Top divider ───────────────────────────────────────────────────────
printf '%s│%s│\n' $p $HBR printf '%s│%s│\n' $p $HBR
@@ -181,42 +171,18 @@ function __config_settings_draw
$badge \ $badge \
(string pad -r -w (math $iw - 33) -- $descs[7]) (string pad -r -w (math $iw - 33) -- $descs[7])
# ── Separator before Path Settings ─────────────────────────────────── # ── Filler (Dots Path moved to the Paths page) ────────────────────────
printf '%s│ %s │\n' $p (string repeat -n (math $iw - 6) '─') printf '%s│ %s%s│\n' $p \
"$c_dim→ Tab for Sponge & Path settings$c_reset" \
# ── Path row (index 7) — always universal scope ─────────────────────── (string repeat -n (math $iw - 34) ' ')
set -l path_var $vars[8] printf '%s│%s│\n' $p (string repeat -n $iw ' ')
set -l raw_path (__config_settings_get_val $path_var universal)
set -l path_badge
set -l path_dpad
if test "$raw_path" = DEFAULT
set path_badge "$c_dim""DEFAULT$c_reset"
set path_dpad (string pad -r -w (math $iw - 33) -- "—default— [U]")
else
set path_badge "$c_ok"" PATH $c_reset"
set -l max_path (math $iw - 37)
set -l truncated (string shorten -m $max_path -- "$raw_path")
set path_dpad (string pad -r -w (math $iw - 33) -- "$truncated [U]")
end
set -l path_curs " "
if test $cur_row -eq 7
set path_curs "$c_sel$c_reset "
end
printf '%s│ %s%s [ %s ] %s │\n' \
$p $path_curs \
(string pad -r -w 12 -- "Dots Path") \
$path_badge \
$path_dpad
# ── Bottom divider ──────────────────────────────────────────────────── # ── Bottom divider ────────────────────────────────────────────────────
printf '%s│%s│\n' $p $HBR printf '%s│%s│\n' $p $HBR
# ── Keybind hint ────────────────────────────────────────────────────── # ── Keybind hint ──────────────────────────────────────────────────────
# string pad is width-aware (arrows count as 1 column) # string pad is width-aware (arrows count as 1 column)
set -l hint " ↑↓/kj move ←→/hl set Enter edit path q quit" set -l hint " ↑↓/kj move ←→/hl set Tab page q quit"
printf '%s│%s%s%s│\n' $p $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 ─────────────────────────────────────────────────────
+184
View File
@@ -0,0 +1,184 @@
# Copyright (C) 2026 Rootiest
# SPDX-License-Identifier: AGPL-3.0-or-later
# SYNOPSIS
# __config_settings_draw_value <cur_row> <page>
#
# DESCRIPTION
# Renders a config-settings value page (Sponge or Paths) as exactly 16
# lines, matching the box geometry of the opinionated toggle page so the
# caller's wrap-aware erase (panel_h=16) is unchanged. Each row shows a
# label, a badge, and the variable's current value (or its default hint).
# Toggle-type rows (the two sponge booleans) reuse the ON/OFF/DEFAULT badge;
# value rows (path/int/list/string) show a type badge and the live value.
#
# Page width follows the same $COLUMNS tiers as the toggle page for a
# consistent look; exact width is not required for the erase (the erase
# over-clears to end of screen using the 78-col worst case).
#
# ARGUMENTS
# cur_row 0-based highlighted row within the page
# page "sponge" or "paths"
#
# RETURNS
# 0 Always
#
# EXAMPLE
# __config_settings_draw_value 0 sponge
function __config_settings_draw_value
set -l cur_row $argv[1]
set -l page $argv[2]
# Inline-edit state: when argv[3] is "edit", the cur_row field renders the
# live input buffer (argv[4]) with a caret instead of its stored value.
set -l edit_mode $argv[3]
set -l edit_buf $argv[4]
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_head (set_color --bold cyan)
set -l c_reset (set_color normal)
# ── Page row metadata (parallel lists) ────────────────────────────────
set -l title
set -l vars
set -l labels
set -l types
set -l hints # default hint shown when unset
set -l active_idx
if test $page = sponge
set title "Sponge Settings"
set active_idx 2
set vars sponge_delay sponge_purge_only_on_exit sponge_allow_previously_successful sponge_successful_exit_codes __fish_sponge_extra_sensitive
set labels Delay "Purge@exit" "Allow prev" "OK codes" "Extra secret"
set types int bool bool list list
set hints 2 false true 0 "(none)"
else
set title "Path Settings"
set active_idx 3
set vars __fish_scrollback_history_dir __fish_scrollback_history_max_files __fish_user_dots_path
set labels "Log dir" "Log max" "Dots path"
set types path int path
set hints "~/.terminal_history" 100 "(default)"
end
set -l nrows (count $vars)
# ── Width tier (same thresholds as the toggle page) ───────────────────
set -l iw 50
if test "$COLUMNS" -ge 90
set iw 76
else if test "$COLUMNS" -ge 86
set iw 72
else if test "$COLUMNS" -ge 82
set iw 68
end
set -l HBR (string repeat -n $iw '─')
set -l p (string repeat -n (math --scale=0 "max(0, ($COLUMNS - ($iw + 2)) / 2)") ' ')
# ── Line 1: top border with title ─────────────────────────────────────
set -l title_dashes (math $iw - (string length -- $title) - 3)
printf '%s┌─%s %s %s┐\n' \
$p $c_head "$title$c_reset" (string repeat -n $title_dashes '─')
# ── Line 2: page-tab header ───────────────────────────────────────────
printf '%s│%s│\n' $p (__config_settings_pagetab $active_idx $iw)
# ── Line 3: divider ───────────────────────────────────────────────────
printf '%s│%s│\n' $p $HBR
# ── Value rows ────────────────────────────────────────────────────────
for i in (seq 0 (math $nrows - 1))
set -l idx (math $i + 1)
set -l var $vars[$idx]
set -l label $labels[$idx]
set -l type $types[$idx]
set -l hint $hints[$idx]
# Badge (7 visible cols) + value field
set -l badge
set -l field
if test $type = bool
# Booleans store true/false (sponge convention); unset = DEFAULT.
set -l val (__config_settings_get_raw $var)
switch $val
case true
set badge "$c_ok"" ON$c_reset"
case false
set badge "$c_err""OFF $c_reset"
case '*'
set badge "$c_dim""DEFAULT$c_reset"
end
set field "default: $hint"
else
set -l raw (__config_settings_get_raw $var)
if test "$raw" = DEFAULT
set badge "$c_dim""DEFAULT$c_reset"
set field "$hint"
else
switch $type
case path
set badge "$c_ok"" PATH $c_reset"
case int
set badge "$c_ok"" INT $c_reset"
case list
set badge "$c_ok"" LIST $c_reset"
case '*'
set badge "$c_ok"" STR $c_reset"
end
set field "$raw"
end
end
# Inline edit: render the active row's field as the live buffer with a
# block caret, tail-anchored so the caret stays visible as text grows.
if test "$edit_mode" = edit -a $i -eq $cur_row
set -l fw (math $iw - 33)
set -l avail (math $fw - 1)
set -l shown "$edit_buf"
set -l blen (string length -- "$edit_buf")
if test $blen -gt $avail
set shown (string sub -s (math $blen - $avail + 1) -- "$edit_buf")
end
set badge "$c_head"" EDIT $c_reset"
set field "$shown"(set_color --reverse)" "(set_color normal)
end
set -l curs " "
if test $i -eq $cur_row
set curs "$c_sel$c_reset "
end
set -l fw (math $iw - 33)
set -l lpad (string pad -r -w 12 -- $label)
# The edit field is already length-constrained and contains a reverse
# caret; running it through `string shorten` miscounts the escapes, so
# pad it directly. Non-edit fields still shorten to add an ellipsis.
set -l fpad
if test "$edit_mode" = edit -a $i -eq $cur_row
set fpad (string pad -r -w $fw -- "$field")
else
set fpad (string pad -r -w $fw -- (string shorten -m $fw -- "$field"))
end
printf '%s│ %s%s [ %s ] %s │\n' $p $curs $lpad $badge $fpad
end
# ── Pad blank rows so chrome(6) + nrows + blanks = 16 ─────────────────
set -l blanks (math 10 - $nrows)
for i in (seq 1 $blanks)
printf '%s│%s│\n' $p (string repeat -n $iw ' ')
end
# ── Bottom divider ────────────────────────────────────────────────────
printf '%s│%s│\n' $p $HBR
# ── Hint line (changes while editing) ─────────────────────────────────
set -l hint_line " ↑↓ move Enter edit ←/h clear Tab page q quit"
if test "$edit_mode" = edit
set hint_line " type value Enter save Esc cancel ⌫ delete"
end
printf '%s│%s%s%s│\n' $p $c_dim (string pad -r -w $iw -- $hint_line) $c_reset
# ── Bottom border ─────────────────────────────────────────────────────
printf '%s└%s┘\n' $p $HBR
end
+27
View File
@@ -0,0 +1,27 @@
# Copyright (C) 2026 Rootiest
# SPDX-License-Identifier: AGPL-3.0-or-later
# SYNOPSIS
# __config_settings_get_raw <varname>
#
# DESCRIPTION
# Prints the current value of a universal variable for display in the
# config-settings value pages. List variables are printed space-joined.
# Prints "DEFAULT" when the variable is unset in every scope.
#
# ARGUMENTS
# varname Variable name without the $ prefix
#
# RETURNS
# 0 Always; prints the value or "DEFAULT" to stdout
#
# EXAMPLE
# set v (__config_settings_get_raw sponge_delay) # "2" or "DEFAULT"
function __config_settings_get_raw
set -l varname $argv[1]
if set -q $varname
string join ' ' -- $$varname
else
echo DEFAULT
end
end
+43
View File
@@ -0,0 +1,43 @@
# Copyright (C) 2026 Rootiest
# SPDX-License-Identifier: AGPL-3.0-or-later
# SYNOPSIS
# __config_settings_pagetab <active_idx> <iw>
#
# DESCRIPTION
# Renders the four-page tab strip used as the header line of every
# config-settings page. Pages: 0 Universal, 1 Session, 2 Sponge, 3 Paths.
# The active page is marked with a filled bullet and bold text; the others
# with a hollow bullet. The returned string is padded to exactly <iw>
# printable columns (the caller adds the │ │ border and center offset).
#
# ARGUMENTS
# active_idx 03, the active page index
# iw inner width in columns to pad the strip to
#
# RETURNS
# 0 Always; prints the tab strip (no trailing newline beyond printf's)
#
# EXAMPLE
# set strip (__config_settings_pagetab 2 76)
function __config_settings_pagetab
set -l active $argv[1]
set -l iw $argv[2]
set -l c_hi (set_color --bold white)
set -l c_reset (set_color normal)
set -l names Universal Session Sponge Paths
set -l strip ' '
for i in (seq 0 3)
set -l idx (math $i + 1)
if test $i -eq $active
set strip "$strip$c_hi$names[$idx]$c_reset "
else
set strip "$strip$names[$idx] "
end
end
# string pad is width-aware: color escapes count as 0 columns, the bullets
# and letters as their printable width.
string pad -r -w $iw -- $strip
end
+5 -1
View File
@@ -24,7 +24,7 @@
# RETURNS # RETURNS
# 0 A key was read; one of these tokens is printed to stdout: # 0 A key was read; one of these tokens is printed to stdout:
# up down left right arrow keys # up down left right arrow keys
# space tab enter escape # space tab backtab enter escape backspace
# quit Ctrl-C (byte 3) in raw mode # quit Ctrl-C (byte 3) in raw mode
# <char> any other single printable character # <char> any other single printable character
# "" nothing decodable was read # "" nothing decodable was read
@@ -64,6 +64,8 @@ function __config_settings_read_key
echo right echo right
case '27 91 68' case '27 91 68'
echo left echo left
case '27 91 90'
echo backtab
case 27 case 27
echo escape echo escape
case 9 case 9
@@ -72,6 +74,8 @@ function __config_settings_read_key
echo space echo space
case 10 13 case 10 13
echo enter echo enter
case 8 127
echo backspace
case 3 case 3
echo quit echo quit
case '' case ''
@@ -0,0 +1,67 @@
# Copyright (C) 2026 Rootiest
# SPDX-License-Identifier: AGPL-3.0-or-later
# SYNOPSIS
# __config_settings_set_value <varname> <type> <value>
#
# DESCRIPTION
# Sets a universal config variable from a config-settings value page,
# immediately and persistently. An empty <value> erases the variable so the
# built-in default takes over. List types split <value> on whitespace; all
# other types store it as a single value. When <varname> is a scrollback
# variable, the exported SCROLLBACK_HISTORY_* mirror is re-derived so the
# POSIX wrappers in the live session see the change without a re-login.
#
# ARGUMENTS
# varname Variable name without the $ prefix
# type "list" (whitespace-split) or any other tag (single value)
# value The new value; empty string erases (reset to default)
#
# RETURNS
# 0 Always
#
# EXAMPLE
# __config_settings_set_value sponge_delay int 5
# __config_settings_set_value sponge_successful_exit_codes list "0 1 130"
# __config_settings_set_value __fish_scrollback_history_dir path ~/logs
# __config_settings_set_value __fish_user_dots_path path '' # reset
function __config_settings_set_value
set -l varname $argv[1]
set -l type $argv[2]
set -l value $argv[3]
# stderr suppressed: editing a universal while a global of the same name
# shadows it makes interactive fish emit a shadow warning that would
# corrupt the in-place panel redraw (see __config_settings_apply).
if test -z "$value"
set -Ue $varname 2>/dev/null
else
switch $type
case list
# Accept commas and/or whitespace as separators: collapse any run
# of them to a single space, then split (dropping empties). So
# "A,B", "A, B", "A B" and "A B" all yield the same two tokens.
set -U -- $varname \
(string split -n ' ' -- (string replace -ra '[,\s]+' ' ' -- $value)) 2>/dev/null
case '*'
# -- (before the name) guards typed input that begins with a dash
set -U -- $varname $value 2>/dev/null
end
end
# Scrollback export mirror — POSIX wrappers read these from the env.
switch $varname
case __fish_scrollback_history_dir
if set -q __fish_scrollback_history_dir
set -gx SCROLLBACK_HISTORY_DIR $__fish_scrollback_history_dir
else
set -gx SCROLLBACK_HISTORY_DIR "$HOME/.terminal_history"
end
case __fish_scrollback_history_max_files
if set -q __fish_scrollback_history_max_files
set -gx SCROLLBACK_HISTORY_MAX_FILES $__fish_scrollback_history_max_files
else
set -gx SCROLLBACK_HISTORY_MAX_FILES 100
end
end
end
+182 -85
View File
@@ -5,17 +5,19 @@
# config-settings [-h | --help] # config-settings [-h | --help]
# #
# DESCRIPTION # DESCRIPTION
# Opens an interactive full-screen TUI for managing fish config settings, # Opens an interactive full-screen TUI for managing fish config settings
# including the six opinionated component categories (C1C6) and the master # across four pages:
# 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) # Universal — opinionated-category toggles, persistent (set -U / set -Ue)
# Session — active for the current shell only (set -g / set -eg) # Session — opinionated-category toggles, this shell only (set -g / set -eg)
# Sponge — sponge history-scrubbing settings (delay, codes, secrets …)
# Paths — scrollback log dir, log max, and user-dots path
# #
# Values are changed directionally with the arrow keys (or vim-style h/l) # Toggle rows use ← / → (or h / l) to step OFF ← DEFAULT → ON.
# along an OFF ← DEFAULT → ON scale, and apply immediately — no confirm # Value rows (Sponge, Paths) use Enter to edit inline; ← / h clears to default.
# step. Always available regardless of __fish_config_opinionated state. # Tab / Shift-Tab cycle forward / backward through pages.
# Changes apply immediately — no confirm step. Always available regardless of
# __fish_config_opinionated state.
# #
# ARGUMENTS # ARGUMENTS
# -h, --help Print usage and exit # -h, --help Print usage and exit
@@ -45,14 +47,17 @@ function config-settings --description 'Interactive TUI for managing fish config
echo echo
echo "$c_head""Navigation:$c_reset" echo "$c_head""Navigation:$c_reset"
echo " $c_flag↑ ↓$c_reset or $c_flag""k j$c_reset Move cursor up / down" 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← →$c_reset or $c_flag""h l$c_reset Toggle pages: OFF ← DEFAULT → ON"
echo " $c_flag""Tab$c_reset Switch scope (Universal ↔ Session)" echo " $c_flag""Enter$c_reset Edit value (Sponge / Paths pages)"
echo " $c_flag""Enter$c_reset Edit path (on Dots Path row)" echo " $c_flag← / h$c_reset Clear value to default (value rows)"
echo " $c_flag""Tab / S-Tab$c_reset Next / previous page"
echo " $c_flag""q$c_reset / $c_flag""Esc$c_reset Exit" echo " $c_flag""q$c_reset / $c_flag""Esc$c_reset Exit"
echo echo
echo "$c_head""Scopes:$c_reset" echo "$c_head""Pages:$c_reset"
echo " $c_flag""Universal$c_reset Persistent across all sessions ($c_dim""set -U$c_reset)" echo " $c_flag""Universal$c_reset Toggles, persistent ($c_dim""set -U$c_reset)"
echo " $c_flag""Session$c_reset Current session only ($c_dim""set -g$c_reset)" echo " $c_flag""Session$c_reset Toggles, this shell ($c_dim""set -g$c_reset)"
echo " $c_flag""Sponge$c_reset sponge history-scrubbing settings"
echo " $c_flag""Paths$c_reset scrollback & user-dots paths"
return 0 return 0
case '*' case '*'
echo "$c_err""Unknown option: $arg$c_reset" >&2 echo "$c_err""Unknown option: $arg$c_reset" >&2
@@ -61,28 +66,59 @@ function config-settings --description 'Interactive TUI for managing fish config
end end
end end
# ── Variable list (matches Panel Layout Reference) ──── # ── Toggle-page variables (rows 06: 6 categories + master) ───────────
set -l vars \ set -l toggle_vars \
__fish_config_op_aliases \ __fish_config_op_aliases \
__fish_config_op_autoexec \ __fish_config_op_autoexec \
__fish_config_op_overrides \ __fish_config_op_overrides \
__fish_config_op_integrations \ __fish_config_op_integrations \
__fish_config_op_logging \ __fish_config_op_logging \
__fish_config_op_greeting \ __fish_config_op_greeting \
__fish_config_opinionated \ __fish_config_opinionated
__fish_user_dots_path
set -l cur_row 0 # 07 # ── Value-page row metadata (parallel: var / type) ────────────────────
set -l cur_scope universal # or "session" set -l sponge_vars sponge_delay sponge_purge_only_on_exit sponge_allow_previously_successful sponge_successful_exit_codes __fish_sponge_extra_sensitive
set -l panel_h 16 # total panel lines (all width tiers are 16 lines) set -l sponge_types int bool bool list list
set -l last_cols $COLUMNS # COLUMNS at the time of the last draw set -l sponge_labels Delay "Purge@exit" "Allow prev" "OK codes" "Extra secret"
set -l paths_vars __fish_scrollback_history_dir __fish_scrollback_history_max_files __fish_user_dots_path
set -l paths_types path int path
set -l paths_labels "Log dir" "Log max" "Dots path"
# Reset/blank-edit target for each value row. A non-empty entry is written
# verbatim (sponge reads sponge_delay / sponge_successful_exit_codes with no
# fallback, so they must never be left unset); an empty entry erases the var
# so its own built-in default applies (scrollback/dots paths and the
# extra-sensitive list all tolerate being unset). Bool rows are not reset
# through this path — they are a 2-state true/false with no unset state.
set -l sponge_defaults 2 '' '' 0 ''
set -l paths_defaults '' '' ''
# Rows per page index 0..3
set -l page_rows 7 7 5 3
set -l cur_page 0 # 0=Universal 1=Session 2=Sponge 3=Paths
set -l cur_row 0
set -l panel_h 16
set -l last_cols $COLUMNS
# ── Terminal setup ──────────────────────────────────── # ── Terminal setup ────────────────────────────────────
printf '\e[?25l' # hide cursor printf '\e[?25l' # hide cursor
trap 'printf "\e[?25h"; set -g __config_settings_exit 1' INT trap 'printf "\e[?25h"; set -g __config_settings_exit 1' INT
# ── Initial draw ────────────────────────────────────── # ── Draw dispatch (page 0/1 = toggle table; 2/3 = value page) ─────────
__config_settings_draw $cur_row $cur_scope $vars function __cs_dispatch_draw --no-scope-shadowing
switch $cur_page
case 0
__config_settings_draw $cur_row universal $toggle_vars
case 1
__config_settings_draw $cur_row session $toggle_vars
case 2
__config_settings_draw_value $cur_row sponge
case 3
__config_settings_draw_value $cur_row paths
end
end
__cs_dispatch_draw
# ── Event loop ──────────────────────────────────────── # ── Event loop ────────────────────────────────────────
# __config_settings_read_key reads a single keypress from /dev/tty in raw # __config_settings_read_key reads a single keypress from /dev/tty in raw
@@ -106,71 +142,131 @@ function config-settings --description 'Interactive TUI for managing fish config
case up k case up k
set cur_row (math "max(0, $cur_row - 1)") set cur_row (math "max(0, $cur_row - 1)")
case down j case down j
set cur_row (math "min(7, $cur_row + 1)") # Hoist the page index: fish cannot expand a command-substitution
case tab # Tab — switch scope # index inside a quoted math string.
if test $cur_scope = universal set -l pidx (math $cur_page + 1)
set cur_scope session set cur_row (math "min($page_rows[$pidx] - 1, $cur_row + 1)")
case tab
set cur_page (math "($cur_page + 1) % 4")
set cur_row 0
case backtab
set cur_page (math "($cur_page + 3) % 4")
set cur_row 0
case right l
if test $cur_page -le 1
# Toggle page: step toward ON
set -l scope universal
test $cur_page -eq 1; and set scope session
set -l varname $toggle_vars[(math $cur_row + 1)]
set -l cur_val (__config_settings_get_val $varname $scope)
set -l next_val on
test "$cur_val" = off; and set next_val DEFAULT
__config_settings_apply $varname $scope $next_val
else if test $cur_page -eq 2
# Sponge bool rows are 2-state (true/false) with no unset
# state — sponge reads them with no fallback. → sets true.
set -l ridx (math $cur_row + 1)
if test "$sponge_types[$ridx]" = bool
set -U $sponge_vars[$ridx] true 2>/dev/null
end
end
case left h
if test $cur_page -le 1
set -l scope universal
test $cur_page -eq 1; and set scope session
set -l varname $toggle_vars[(math $cur_row + 1)]
set -l cur_val (__config_settings_get_val $varname $scope)
set -l next_val off
test "$cur_val" = on; and set next_val DEFAULT
__config_settings_apply $varname $scope $next_val
else else
set cur_scope universal # Value pages: bool rows set false; other value rows reset to
# their default (a literal value, or erase when the var
# tolerates being unset — see sponge_defaults/paths_defaults).
set -l v_vars $sponge_vars
set -l v_types $sponge_types
set -l v_defaults $sponge_defaults
if test $cur_page -eq 3
set v_vars $paths_vars
set v_types $paths_types
set v_defaults $paths_defaults
end
set -l ridx (math $cur_row + 1)
set -l varname $v_vars[$ridx]
set -l vtype $v_types[$ridx]
if test "$vtype" = bool
set -U $varname false 2>/dev/null
else
__config_settings_set_value $varname $vtype "$v_defaults[$ridx]"
end
end end
case enter case enter
if test $cur_row -eq 7 if test $cur_page -ge 2
# Erase panel (wrap-aware, same formula as cleanup) set -l v_vars $sponge_vars
set -l prev_max_lw (math --scale=0 "($last_cols + 78) / 2") set -l v_types $sponge_types
set -l erase_h (math --scale=0 "$panel_h * max(1, ceil($prev_max_lw / $COLUMNS))") set -l v_defaults $sponge_defaults
printf '\e[%dA\e[J' $erase_h if test $cur_page -eq 3
printf '\e[?25h' # restore cursor set v_vars $paths_vars
set v_types $paths_types
printf 'User Dots Path (leave blank to reset to default): ' set v_defaults $paths_defaults
read -l new_path
if test -n "$new_path"
set -U __fish_user_dots_path $new_path
else
set -Ue __fish_user_dots_path
end end
set -l ridx (math $cur_row + 1)
set -l varname $v_vars[$ridx]
set -l vtype $v_types[$ridx]
# Only path/int/list rows are editable; bool rows toggle with ←/→.
if test "$vtype" != toggle -a "$vtype" != bool
# Inline editor: edit in-place in the row's value field
# using the raw key reader — no fish `read` / `read>`
# prompt, and the panel cleans itself up on exit. The
# buffer is pre-filled with the current value; clearing it
# and pressing Enter reverts to the row's default.
set -l page sponge
test $cur_page -eq 3; and set page paths
set -l buf (__config_settings_get_raw $varname)
test "$buf" = DEFAULT; and set buf ""
set -l committed 0
while true
set -l pml (math --scale=0 "($last_cols + 78) / 2")
set -l eh (math --scale=0 "$panel_h * max(1, ceil($pml / $COLUMNS))")
printf '\e[%dA\e[J' $eh
set last_cols $COLUMNS
__config_settings_draw_value $cur_row $page edit "$buf"
printf '\e[?25l' # hide cursor set -l ek (__config_settings_read_key)
set last_cols $COLUMNS or break
__config_settings_draw $cur_row $cur_scope $vars switch $ek
set did_redraw 1 case enter
end set committed 1
case right l # → / l — step toward ON (clamped, no wrap) break
if test $cur_row -eq 7 case escape
# no-op for path var; use Enter to set break
else case backspace
set -l varname $vars[(math $cur_row + 1)] set buf (string sub -s 1 -e -1 -- "$buf")
set -l cur_val (__config_settings_get_val $varname $cur_scope) case space
set buf "$buf "
set -l next_val on case up down left right tab backtab quit ''
switch $cur_val # ignored while editing
case off case '*'
set next_val DEFAULT set buf "$buf$ek"
case on end
set next_val on end
case '*' if test $committed -eq 1
set next_val on # Empty buffer reverts to the row default (a value, or
# erase when the var tolerates being unset).
if test -n "$buf"
__config_settings_set_value $varname $vtype "$buf"
else
__config_settings_set_value $varname $vtype "$v_defaults[$ridx]"
end
end
# Redraw the normal panel in place of the editor.
set -l pml (math --scale=0 "($last_cols + 78) / 2")
set -l eh (math --scale=0 "$panel_h * max(1, ceil($pml / $COLUMNS))")
printf '\e[%dA\e[J' $eh
set last_cols $COLUMNS
__cs_dispatch_draw
set did_redraw 1
end end
__config_settings_apply $varname $cur_scope $next_val
end
case left h # ← / h — step toward OFF (clamped, no wrap)
if test $cur_row -eq 7
# Clear the universal path var (restore default)
__config_settings_apply __fish_user_dots_path universal DEFAULT
else
set -l varname $vars[(math $cur_row + 1)]
set -l cur_val (__config_settings_get_val $varname $cur_scope)
set -l next_val off
switch $cur_val
case on
set next_val DEFAULT
case off
set next_val off
case '*'
set next_val off
end
__config_settings_apply $varname $cur_scope $next_val
end end
case q Q quit escape case q Q quit escape
break break
@@ -194,7 +290,7 @@ function config-settings --description 'Interactive TUI for managing fish config
set -l erase_h (math --scale=0 "$panel_h * max(1, ceil($prev_max_lw / $COLUMNS))") set -l erase_h (math --scale=0 "$panel_h * max(1, ceil($prev_max_lw / $COLUMNS))")
printf '\e[%dA\e[J' $erase_h printf '\e[%dA\e[J' $erase_h
set last_cols $COLUMNS set last_cols $COLUMNS
__config_settings_draw $cur_row $cur_scope $vars __cs_dispatch_draw
end end
# ── Cleanup ─────────────────────────────────────────── # ── Cleanup ───────────────────────────────────────────
@@ -203,4 +299,5 @@ function config-settings --description 'Interactive TUI for managing fish config
set -l erase_h (math --scale=0 "$panel_h * max(1, ceil($prev_max_lw / $COLUMNS))") 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[%dA\e[J' $erase_h # erase the panel (wrap-aware)
printf '\e[?25h' # restore cursor printf '\e[?25h' # restore cursor
functions --erase __cs_dispatch_draw
end end