diff --git a/README.md b/README.md index 8401fc5..a4e9ddc 100644 --- a/README.md +++ b/README.md @@ -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: diff --git a/conf.d/sponge_privacy.fish b/conf.d/sponge_privacy.fish index 49a1d17..3df5892 100644 --- a/conf.d/sponge_privacy.fish +++ b/conf.d/sponge_privacy.fish @@ -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. diff --git a/config.fish b/config.fish index 58857de..058e85c 100644 --- a/config.fish +++ b/config.fish @@ -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. diff --git a/docs/fish-config.index b/docs/fish-config.index index 561b157..faa37dd 100644 --- a/docs/fish-config.index +++ b/docs/fish-config.index @@ -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 diff --git a/docs/fish-config.md b/docs/fish-config.md index c564532..c58b1ba 100644 --- a/docs/fish-config.md +++ b/docs/fish-config.md @@ -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 (C1–C6), 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 (C1–C6) + 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 OFF–DEFAULT–ON 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 diff --git a/functions/__config_settings_draw.fish b/functions/__config_settings_draw.fish index b7d1551..756479f 100644 --- a/functions/__config_settings_draw.fish +++ b/functions/__config_settings_draw.fish @@ -2,7 +2,7 @@ # SPDX-License-Identifier: AGPL-3.0-or-later # SYNOPSIS -# __config_settings_draw ... +# __config_settings_draw ... # # 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 0–7, the currently highlighted row +# cur_row 0–6, the currently highlighted row # cur_scope "universal" or "session" -# var1–var8 Variable names for rows 0–7 (in order per Variable Reference) +# var1–var7 Variable names for rows 0–6 (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 ───────────────────────────────────────────────────── diff --git a/functions/__config_settings_draw_value.fish b/functions/__config_settings_draw_value.fish new file mode 100644 index 0000000..27e015d --- /dev/null +++ b/functions/__config_settings_draw_value.fish @@ -0,0 +1,184 @@ +# Copyright (C) 2026 Rootiest +# SPDX-License-Identifier: AGPL-3.0-or-later + +# SYNOPSIS +# __config_settings_draw_value +# +# 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 diff --git a/functions/__config_settings_get_raw.fish b/functions/__config_settings_get_raw.fish new file mode 100644 index 0000000..4ecb93a --- /dev/null +++ b/functions/__config_settings_get_raw.fish @@ -0,0 +1,27 @@ +# Copyright (C) 2026 Rootiest +# SPDX-License-Identifier: AGPL-3.0-or-later + +# SYNOPSIS +# __config_settings_get_raw +# +# 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 diff --git a/functions/__config_settings_pagetab.fish b/functions/__config_settings_pagetab.fish new file mode 100644 index 0000000..dd31e63 --- /dev/null +++ b/functions/__config_settings_pagetab.fish @@ -0,0 +1,43 @@ +# Copyright (C) 2026 Rootiest +# SPDX-License-Identifier: AGPL-3.0-or-later + +# SYNOPSIS +# __config_settings_pagetab +# +# 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 +# printable columns (the caller adds the │ │ border and center offset). +# +# ARGUMENTS +# active_idx 0–3, 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 diff --git a/functions/__config_settings_read_key.fish b/functions/__config_settings_read_key.fish index 0109064..4040d81 100644 --- a/functions/__config_settings_read_key.fish +++ b/functions/__config_settings_read_key.fish @@ -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 # 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 '' diff --git a/functions/__config_settings_set_value.fish b/functions/__config_settings_set_value.fish new file mode 100644 index 0000000..46ddfd1 --- /dev/null +++ b/functions/__config_settings_set_value.fish @@ -0,0 +1,67 @@ +# Copyright (C) 2026 Rootiest +# SPDX-License-Identifier: AGPL-3.0-or-later + +# SYNOPSIS +# __config_settings_set_value +# +# DESCRIPTION +# Sets a universal config variable from a config-settings value page, +# immediately and persistently. An empty erases the variable so the +# built-in default takes over. List types split on whitespace; all +# other types store it as a single value. When 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 diff --git a/functions/config-settings.fish b/functions/config-settings.fish index b4b8e37..6e76229 100644 --- a/functions/config-settings.fish +++ b/functions/config-settings.fish @@ -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 (C1–C6) 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 0–6: 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 # 0–7 - 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