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.
> **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:
+13 -2
View File
@@ -95,8 +95,19 @@ function __sponge_register_secret_values --on-event fish_prompt
set -l secret_values
set -l sensitive_vars (set --names --export | string match --regex -- \
'(?i)(?:TOKEN|PASSWORD|PASSWD|SECRET|API[_-]KEY|PRIVATE[_-]KEY|ACCESS[_-]KEY|AUTH[_-]KEY|CREDENTIAL|KOPIA_PASSWORD)')
# Base credential-name tokens, plus any user-supplied extras from
# __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
# 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)
# ────────────────────────── 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"
# 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 -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
# Replacing the exit builtin is opinionated (C3 overrides); smart_exit also
# 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
pager=## Editor and Pager
scrollback=## Scrollback History
__fish_scrollback_history_dir=## Scrollback History
__fish_scrollback_history_max_files=## Scrollback History
scrollback-dir=## Scrollback History
# ── Section 2: PATH ───────────────────────────────────────────
path=# 2. PATH SETUP
@@ -258,6 +261,9 @@ sponge=## Fisher-Managed Plugins
sponge-filtering=## Sponge History Filtering
history-filtering=## 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
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_DIR ~/.terminal_history
SCROLLBACK_HISTORY_MAX_FILES 100
__fish_scrollback_history_dir (unset → ~/.terminal_history)
__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.
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]
Opens an interactive TUI for managing fish configuration settings. Handles
the six opinionated component categories (C1C6), the master disable
variable, and the __fish_user_dots_path path variable — without having to
type or remember variable names. Two scope tabs allow independent per-scope
configuration for category toggles:
Opens an interactive TUI for managing fish configuration settings across
four pages, without having to type or remember variable names. Tab cycles
forward through the pages; Shift-Tab cycles backward.
Universal — persists across all sessions (set -U)
Session — current shell only (set -g)
Universal — opinionated category toggles (C1C6) + master, persistent (set -U)
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
active tab.
Toggle rows use ← → (or h/l) along an OFF ← DEFAULT → ON scale; DEFAULT
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 path variable use an inline edit prompt (Enter). Always available
regardless of the __fish_config_opinionated master state.
The Sponge and Paths pages always write universal variables — these are
persistent, set-and-forget settings with no per-session scope. Editing a
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
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)
Navigation:
↑ ↓ / k j Move cursor
← → / h l Set value: OFF ← DEFAULT → ON (toggle rows); LEFT clears path
Enter Edit the Dots Path (path row only)
Tab Switch scope (Universal ↔ Session)
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.
↑ ↓ / k j Move cursor
← → / h l Toggle rows: OFF ← DEFAULT → ON
← / h Value rows: clear to default
Enter Value rows: edit inline (Sponge / Paths pages)
Tab / S-Tab Next / previous page
q / Escape Exit
Flags:
--help / -h Show usage.
@@ -2043,6 +2058,20 @@ To add your own persistent patterns:
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
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
# SYNOPSIS
# __config_settings_draw <cur_row> <cur_scope> <var1> ... <var8>
# __config_settings_draw <cur_row> <cur_scope> <var1> ... <var7>
#
# DESCRIPTION
# Renders the 16-line config-settings TUI panel to stdout. Panel width and
@@ -20,9 +20,9 @@
# does not interfere with the redraw loop.
#
# ARGUMENTS
# cur_row 07, the currently highlighted row
# cur_row 06, the currently highlighted row
# 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
# 0 Always
@@ -32,7 +32,7 @@
# __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 __fish_user_dots_path
# __fish_config_opinionated
function __config_settings_draw
set -l cur_row $argv[1]
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
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 ────────────────────────────────────────────────────────
# ┌─ Opinionated Settings (iw-23)×─ ┐ total = iw+2
printf '%s┌─%s Opinionated Settings %s┐\n' \
$p $c_head $c_reset(string repeat -n (math $iw - 23) '─')
# ── Scope tab line ────────────────────────────────────────────────────
# Inner: " ●/○ Universal ●/○ Session " (25) + (iw-40)×space + "Tab to switch " (15) = iw
printf '%s│ %s %s%s│\n' \
$p $u_label $s_label \
(string repeat -n (math $iw - 40) ' ')"Tab to switch "
# ── Page-tab header ───────────────────────────────────────────────────
set -l active_idx 0
if test $cur_scope = session
set active_idx 1
end
printf '%s│%s│\n' $p (__config_settings_pagetab $active_idx $iw)
# ── Top divider ───────────────────────────────────────────────────────
printf '%s│%s│\n' $p $HBR
@@ -181,42 +171,18 @@ function __config_settings_draw
$badge \
(string pad -r -w (math $iw - 33) -- $descs[7])
# ── Separator before Path Settings ───────────────────────────────────
printf '%s│ %s │\n' $p (string repeat -n (math $iw - 6) '─')
# ── Path row (index 7) — always universal scope ───────────────────────
set -l path_var $vars[8]
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
# ── Filler (Dots Path moved to the Paths page) ────────────────────────
printf '%s│ %s%s│\n' $p \
"$c_dim→ Tab for Sponge & Path settings$c_reset" \
(string repeat -n (math $iw - 34) ' ')
printf '%s│%s│\n' $p (string repeat -n $iw ' ')
# ── Bottom divider ────────────────────────────────────────────────────
printf '%s│%s│\n' $p $HBR
# ── Keybind hint ──────────────────────────────────────────────────────
# 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
# ── 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
# 0 A key was read; one of these tokens is printed to stdout:
# up down left right arrow keys
# space tab enter escape
# space tab backtab enter escape backspace
# quit Ctrl-C (byte 3) in raw mode
# <char> any other single printable character
# "" nothing decodable was read
@@ -64,6 +64,8 @@ function __config_settings_read_key
echo right
case '27 91 68'
echo left
case '27 91 90'
echo backtab
case 27
echo escape
case 9
@@ -72,6 +74,8 @@ function __config_settings_read_key
echo space
case 10 13
echo enter
case 8 127
echo backspace
case 3
echo quit
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]
#
# DESCRIPTION
# Opens an interactive full-screen TUI for managing fish config settings,
# including the six opinionated component categories (C1C6) and the master
# disable variable, without having to remember variable names. Two scope
# tabs allow independent per-scope configuration:
# Opens an interactive full-screen TUI for managing fish config settings
# across four pages:
#
# Universal — persists across all sessions (set -U / set -Ue)
# Session — active for the current shell only (set -g / set -eg)
# Universal — opinionated-category toggles, persistent (set -U / set -Ue)
# 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)
# along an OFF ← DEFAULT → ON scale, and apply immediately — no confirm
# step. Always available regardless of __fish_config_opinionated state.
# Toggle rows use ← / → (or h / l) to step OFF ← DEFAULT → ON.
# Value rows (Sponge, Paths) use Enter to edit inline; ← / h clears to default.
# Tab / Shift-Tab cycle forward / backward through pages.
# Changes apply immediately — no confirm step. Always available regardless of
# __fish_config_opinionated state.
#
# ARGUMENTS
# -h, --help Print usage and exit
@@ -45,14 +47,17 @@ function config-settings --description 'Interactive TUI for managing fish config
echo
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""h l$c_reset Set value: OFF ← DEFAULT → ON"
echo " $c_flag""Tab$c_reset Switch scope (Universal ↔ Session)"
echo " $c_flag""Enter$c_reset Edit path (on Dots Path row)"
echo " $c_flag← →$c_reset or $c_flag""h l$c_reset Toggle pages: OFF ← DEFAULT → ON"
echo " $c_flag""Enter$c_reset Edit value (Sponge / Paths pages)"
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
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)"
echo "$c_head""Pages:$c_reset"
echo " $c_flag""Universal$c_reset Toggles, persistent ($c_dim""set -U$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
case '*'
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
# ── Variable list (matches Panel Layout Reference) ────
set -l vars \
# ── Toggle-page variables (rows 06: 6 categories + master) ───────────
set -l toggle_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 \
__fish_user_dots_path
__fish_config_opinionated
set -l cur_row 0 # 07
set -l cur_scope universal # or "session"
set -l panel_h 16 # total panel lines (all width tiers are 16 lines)
set -l last_cols $COLUMNS # COLUMNS at the time of the last draw
# ── Value-page row metadata (parallel: var / type) ────────────────────
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 sponge_types int bool bool list list
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 ────────────────────────────────────
printf '\e[?25l' # hide cursor
trap 'printf "\e[?25h"; set -g __config_settings_exit 1' INT
# ── Initial draw ──────────────────────────────────────
__config_settings_draw $cur_row $cur_scope $vars
# ── Draw dispatch (page 0/1 = toggle table; 2/3 = value page) ─────────
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 ────────────────────────────────────────
# __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
set cur_row (math "max(0, $cur_row - 1)")
case down j
set cur_row (math "min(7, $cur_row + 1)")
case tab # Tab — switch scope
if test $cur_scope = universal
set cur_scope session
# Hoist the page index: fish cannot expand a command-substitution
# index inside a quoted math string.
set -l pidx (math $cur_page + 1)
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
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
case enter
if test $cur_row -eq 7
# Erase panel (wrap-aware, same formula as cleanup)
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
printf '\e[?25h' # restore cursor
printf 'User Dots Path (leave blank to reset to default): '
read -l new_path
if test -n "$new_path"
set -U __fish_user_dots_path $new_path
else
set -Ue __fish_user_dots_path
if test $cur_page -ge 2
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]
# 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 last_cols $COLUMNS
__config_settings_draw $cur_row $cur_scope $vars
set did_redraw 1
end
case right l # → / l — step toward ON (clamped, no wrap)
if test $cur_row -eq 7
# no-op for path var; use Enter to set
else
set -l varname $vars[(math $cur_row + 1)]
set -l cur_val (__config_settings_get_val $varname $cur_scope)
set -l next_val on
switch $cur_val
case off
set next_val DEFAULT
case on
set next_val on
case '*'
set next_val on
set -l ek (__config_settings_read_key)
or break
switch $ek
case enter
set committed 1
break
case escape
break
case backspace
set buf (string sub -s 1 -e -1 -- "$buf")
case space
set buf "$buf "
case up down left right tab backtab quit ''
# ignored while editing
case '*'
set buf "$buf$ek"
end
end
if test $committed -eq 1
# 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
__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
case q Q quit escape
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))")
printf '\e[%dA\e[J' $erase_h
set last_cols $COLUMNS
__config_settings_draw $cur_row $cur_scope $vars
__cs_dispatch_draw
end
# ── 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))")
printf '\e[%dA\e[J' $erase_h # erase the panel (wrap-aware)
printf '\e[?25h' # restore cursor
functions --erase __cs_dispatch_draw
end