# Copyright (C) 2026 Rootiest # SPDX-License-Identifier: AGPL-3.0-or-later # SYNOPSIS # config-settings [-h | --help] # # DESCRIPTION # Opens an interactive full-screen TUI for managing fish config settings # across four pages: # # 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 # # 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 # # RETURNS # 0 Exited normally (q or Escape pressed) # 1 Unknown flag passed # # EXAMPLE # config-settings function config-settings --description 'Interactive TUI for managing fish config settings' set -l c_head (set_color --bold cyan) set -l c_cmd (set_color --bold white) set -l c_flag (set_color yellow) set -l c_dim (set_color brblack) set -l c_err (set_color red) set -l c_reset (set_color normal) # ── Argument parsing ────────────────────────────────── for arg in $argv switch $arg case -h --help echo "$c_head""Usage:$c_reset $c_cmd""config-settings$c_reset $c_flag""[-h]$c_reset" echo echo " Interactive TUI for managing fish config settings." echo " Changes apply immediately — no confirm step required." 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 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""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 echo "Run $c_cmd""config-settings --help$c_reset for usage." >&2 return 1 end end # ── 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 # ── 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 __fish_user_dots_symlink set -l paths_types path int path bool set -l paths_labels "Log dir" "Log max" "Dots path" "Dots link" # 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 4 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 # ── 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 # mode and returns a normalized token (up/down/tab/space/escape/quit or a # literal char). It bypasses fish's `read`, whose line editor swallows Tab # and arrow keys and prints a `read> ` prompt — unusable for a TUI. while true # Check for Ctrl-C signal (trap sets this flag during redraw, when the # terminal is briefly back in cooked mode and SIGINT can fire). if set -q __config_settings_exit set -eg __config_settings_exit break end set -l key (__config_settings_read_key) or break # not a TTY — exit instead of spinning set -l did_redraw 0 switch $key case up k set cur_row (math "max(0, $cur_row - 1)") case down j # 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 # Value pages: bool rows are 2-state (true/false). → sets # true. Sponge reads its bools with no fallback; the Paths # "Dots link" bool drives __fish_user_dots_link on change. set -l v_vars $sponge_vars set -l v_types $sponge_types if test $cur_page -eq 3 set v_vars $paths_vars set v_types $paths_types end set -l ridx (math $cur_row + 1) if test "$v_types[$ridx]" = bool set -U $v_vars[$ridx] true 2>/dev/null test "$v_vars[$ridx]" = __fish_user_dots_symlink and __fish_user_dots_link 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 # 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 test "$varname" = __fish_user_dots_symlink and __fish_user_dots_link else __config_settings_set_value $varname $vtype "$v_defaults[$ridx]" end end case enter 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" 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 end case q Q quit escape break end # Skip redraw entirely when the key reader timed out with no resize if test -z "$key" -a "$COLUMNS" = "$last_cols" continue end # Skip redraw if the Enter handler already redrew (e.g. after path edit) if test $did_redraw -eq 1 continue end # Wrap-aware erase: a panel drawn on a wider terminal has longer lines # (due to center-padding) that wrap into extra physical rows when the # terminal narrows. 78 = widest box (IW=76+2); the formula gives the # worst-case old line width for any tier drawn at last_cols. set -l prev_max_lw (math --scale=0 "($last_cols + 78) / 2") set -l erase_h (math --scale=0 "$panel_h * max(1, ceil($prev_max_lw / $COLUMNS))") printf '\e[%dA\e[J' $erase_h set last_cols $COLUMNS __cs_dispatch_draw end # ── Cleanup ─────────────────────────────────────────── trap - INT # remove the signal handler set -l prev_max_lw (math --scale=0 "($last_cols + 78) / 2") set -l erase_h (math --scale=0 "$panel_h * max(1, ceil($prev_max_lw / $COLUMNS))") printf '\e[%dA\e[J' $erase_h # erase the panel (wrap-aware) printf '\e[?25h' # restore cursor functions --erase __cs_dispatch_draw end