From de6349f2dce44feeaca4941f07cfaf2dcf74d329 Mon Sep 17 00:00:00 2001 From: Rootiest Date: Wed, 9 Sep 2026 19:51:17 -0400 Subject: [PATCH 1/8] test(net): add network isolation harnesses and failure edge case coverage --- tests/test-network-fish.fish | 694 +++++++++++++++++++++++++++++++++ tests/test-network-python.fish | 31 ++ tests/test_sync_labels.py | 429 ++++++++++++++++++++ 3 files changed, 1154 insertions(+) create mode 100644 tests/test-network-fish.fish create mode 100644 tests/test-network-python.fish create mode 100644 tests/test_sync_labels.py diff --git a/tests/test-network-fish.fish b/tests/test-network-fish.fish new file mode 100644 index 0000000..aa2fbe5 --- /dev/null +++ b/tests/test-network-fish.fish @@ -0,0 +1,694 @@ +#!/usr/bin/env fish +# Copyright (C) 2026 Rootiest +# SPDX-License-Identifier: AGPL-3.0-or-later +# +# Hermetic unit tests for network-dependent fish functions with full mock coverage +# and failure edge-case testing: +# - gi (gitignore generation, API fetch, deduplication, error handling) +# - gip, gip4, gip6 (public IP resolution, timeouts, IPv4/IPv6 fallback, failure notices) +# - qr (terminal QR code generator, qrencode local bypass, curl fallback & network drop) +# - bd-pull (Gitea issues sync, missing tokens, API failures, malformed JSON, linking) +# - _auto_pull_sync (background safe fast-forward, dirty trees, network drop on fetch) +# - gitup (remote fetch & status, network drop handling) +# - git-clean (remote fetch/prune, orphaned branch detection & deletion) +# - config-update (upstream fetch, up-to-date check, dry-run, force stash & restore) +# - repo-open (origin URL parsing, offline local tracking, ls-remote fallback & drop) +# - fzf-update (git pull/clone network failure handling) +# +# MODE: isolated + +source (realpath (dirname (status filename)))/lib.fish +set -p fish_function_path $repo_root/functions + +set -gx TERM xterm-256color +set -g TMPDIRS + +# Ensure git operations inside tests are hermetic and do not prompt +set -gx GIT_AUTHOR_NAME t +set -gx GIT_AUTHOR_EMAIL t@t +set -gx GIT_COMMITTER_NAME t +set -gx GIT_COMMITTER_EMAIL t@t +set -gx GIT_CONFIG_COUNT 2 +set -gx GIT_CONFIG_KEY_0 commit.gpgsign +set -gx GIT_CONFIG_VALUE_0 false +set -gx GIT_CONFIG_KEY_1 init.defaultBranch +set -gx GIT_CONFIG_VALUE_1 main +set -gx GIT_TERMINAL_PROMPT 0 + +# Helper to create temporary git repos +function new_repo + set -l d (mktemp -d) + set -ga TMPDIRS $d + git -C $d init -q + git -C $d config user.email t@t + git -C $d config user.name t + git -C $d config commit.gpgsign false + git -C $d config core.hooksPath /dev/null + printf '%s\n' $d +end + +# Build a hermetic mock bin directory +set -g MOCK_DIR (mktemp -d) +set -ga TMPDIRS $MOCK_DIR +set -l real_git (command -s git) + +# 1. Mock curl +printf '%s\n' \ + '#!/bin/sh' \ + 'if [ -n "$MOCK_CURL_LOG" ]; then' \ + ' printf "%s\n" "$*" >> "$MOCK_CURL_LOG"' \ + 'fi' \ + 'if [ -n "$MOCK_CURL_HANDLER" ] && [ -x "$MOCK_CURL_HANDLER" ]; then' \ + ' exec "$MOCK_CURL_HANDLER" "$@"' \ + 'fi' \ + 'if [ -n "$MOCK_CURL_DELAY" ]; then' \ + ' sleep "$MOCK_CURL_DELAY"' \ + 'fi' \ + 'if [ -n "$MOCK_CURL_STDERR" ]; then' \ + ' printf "%s\n" "$MOCK_CURL_STDERR" >&2' \ + 'fi' \ + 'if [ -f "$MOCK_CURL_BODY_FILE" ]; then' \ + ' cat "$MOCK_CURL_BODY_FILE"' \ + 'elif [ -n "$MOCK_CURL_BODY" ]; then' \ + ' printf "%s\n" "$MOCK_CURL_BODY"' \ + 'fi' \ + 'exit "${MOCK_CURL_STATUS:-0}"' > $MOCK_DIR/curl +chmod +x $MOCK_DIR/curl + +# 2. Mock git shim +printf '%s\n' \ + '#!/bin/sh' \ + 'if [ -n "$MOCK_GIT_LOG" ]; then' \ + ' printf "%s\n" "$*" >> "$MOCK_GIT_LOG"' \ + 'fi' \ + 'if [ -n "$MOCK_GIT_HANDLER" ] && [ -x "$MOCK_GIT_HANDLER" ]; then' \ + ' exec "$MOCK_GIT_HANDLER" "$@"' \ + 'fi' \ + 'for a in "$@"; do' \ + ' if [ "$a" = "fetch" ] && [ -n "$MOCK_GIT_FAIL_FETCH" ]; then' \ + ' echo "fatal: unable to access: Could not resolve host" >&2' \ + ' exit "${MOCK_GIT_FETCH_STATUS:-128}"' \ + ' fi' \ + ' if [ "$a" = "pull" ] && [ -n "$MOCK_GIT_FAIL_PULL" ]; then' \ + ' echo "fatal: unable to access: Could not resolve host" >&2' \ + ' exit "${MOCK_GIT_PULL_STATUS:-128}"' \ + ' fi' \ + ' if [ "$a" = "clone" ] && [ -n "$MOCK_GIT_FAIL_CLONE" ]; then' \ + ' echo "fatal: unable to access: Could not resolve host" >&2' \ + ' exit "${MOCK_GIT_CLONE_STATUS:-128}"' \ + ' fi' \ + ' if [ "$a" = "ls-remote" ] && [ -n "$MOCK_GIT_FAIL_LS_REMOTE" ]; then' \ + ' echo "fatal: unable to access: Could not resolve host" >&2' \ + ' exit "${MOCK_GIT_LS_REMOTE_STATUS:-128}"' \ + ' fi' \ + 'done' \ + "exec $real_git \"\$@\"" > $MOCK_DIR/git +chmod +x $MOCK_DIR/git + +# 3. Mock bd CLI +printf '%s\n' \ + '#!/bin/sh' \ + 'if [ "$1" = "create" ]; then' \ + ' echo "Created issue bd-99"' \ + ' echo "{\"id\":\"bd-99\"}" >> .beads/issues.jsonl' \ + ' exit 0' \ + 'elif [ "$1" = "sync" ]; then' \ + ' exit 0' \ + 'fi' \ + 'exit 0' > $MOCK_DIR/bd +chmod +x $MOCK_DIR/bd + +# 4. Mock qrencode +printf '%s\n' \ + '#!/bin/sh' \ + 'if [ -n "$MOCK_QRENCODE_LOG" ]; then' \ + ' printf "%s\n" "$*" >> "$MOCK_QRENCODE_LOG"' \ + 'fi' \ + 'echo "LOCAL_QRENCODE: $*"' \ + 'exit 0' > $MOCK_DIR/qrencode +chmod +x $MOCK_DIR/qrencode + +# Prepend MOCK_DIR to PATH so mocks take precedence +set -gx PATH $MOCK_DIR $PATH + +function reset_mocks + set -e MOCK_CURL_STATUS + set -e MOCK_CURL_BODY + set -e MOCK_CURL_BODY_FILE + set -e MOCK_CURL_STDERR + set -e MOCK_CURL_DELAY + set -e MOCK_CURL_LOG + set -e MOCK_CURL_HANDLER + set -e MOCK_GIT_FAIL_FETCH + set -e MOCK_GIT_FETCH_STATUS + set -e MOCK_GIT_FAIL_PULL + set -e MOCK_GIT_PULL_STATUS + set -e MOCK_GIT_FAIL_CLONE + set -e MOCK_GIT_CLONE_STATUS + set -e MOCK_GIT_FAIL_LS_REMOTE + set -e MOCK_GIT_LS_REMOTE_STATUS + set -e MOCK_GIT_LOG + set -e MOCK_GIT_HANDLER + set -e MOCK_QRENCODE_LOG +end + +function cleanup + for d in $TMPDIRS + test -n "$d"; and command rm -rf $d + end +end + + +# ───────────────────────────────────────────────────────────────────────────── +# 1. gi (gitignore generator) +# ───────────────────────────────────────────────────────────────────────────── +section "network isolation: gi" + +reset_mocks +set -gx MOCK_CURL_BODY "c,cpp,python,rust" +set -l list_out (gi -l) +check "gi -l: returns API target list" "c,cpp,python,rust" "$list_out" + +# Complete network drop on list +set -gx MOCK_CURL_STATUS 7 +set -gx MOCK_CURL_BODY "" +gi -l >/dev/null 2>&1 +check "gi -l: returns 0 per contract" 0 $status + +# stdout mode with valid content +reset_mocks +set -gx MOCK_CURL_BODY "# Python gitignore\n*.pyc\n__pycache__/" +set -l stdout_out (gi -s python) +check "gi -s: prints fetched patterns to stdout" "# Python gitignore\n*.pyc\n__pycache__/" "$stdout_out" + +# stdout mode with network failure / 404 (curl exit 22) +reset_mocks +set -gx MOCK_CURL_STATUS 22 +set -gx MOCK_CURL_BODY "" +gi -s invalid_target >/dev/null 2>&1 +check "gi -s: API failure returns 1" 1 $status + +# Append mode outside git repository +reset_mocks +set -l non_git_dir (mktemp -d) +set -ga TMPDIRS $non_git_dir +begin + set -l prev_pwd $PWD + builtin cd $non_git_dir + gi python >/dev/null 2>&1 + set -l rc $status + builtin cd $prev_pwd + check "gi : outside git repo returns 1" 1 $rc +end + +# Append mode inside git repo + deduplication +reset_mocks +set -l repo (new_repo) +set -gx MOCK_CURL_BODY "# Python gitignore\n*.pyc" +begin + set -l prev_pwd $PWD + builtin cd $repo + gi python >/dev/null 2>&1 + check "gi python: first run returns 0" 0 $status + check "gi python: creates .gitignore" true (test -f "$repo/.gitignore"; and echo true; or echo false) + set -l first_lines (count (command cat "$repo/.gitignore")) + + # Second run with identical pattern triggers deduplication + gi python >/dev/null 2>&1 + check "gi python: second run returns 0" 0 $status + set -l second_lines (count (command cat "$repo/.gitignore")) + check "gi python: deduplication prevents duplicate append" $first_lines $second_lines + + # Network failure during append skips modifying file + set -gx MOCK_CURL_STATUS 7 + gi ruby >/dev/null 2>&1 + set -l third_lines (count (command cat "$repo/.gitignore")) + check "gi: network drop during append preserves file" $second_lines $third_lines + builtin cd $prev_pwd +end + + +# ───────────────────────────────────────────────────────────────────────────── +# 2. gip, gip4, gip6 (IP resolution) +# ───────────────────────────────────────────────────────────────────────────── +section "network isolation: gip, gip4, gip6" + +# Create a specialized curl handler for gip IPv4/IPv6 dispatch +set -l gip_handler $MOCK_DIR/gip_curl.sh +printf '%s\n' \ + '#!/bin/sh' \ + 'for a in "$@"; do' \ + ' if [ "$a" = "-4" ]; then' \ + ' if [ -n "$MOCK_FAIL_IPV4" ]; then exit "${MOCK_IPV4_STATUS:-28}"; fi' \ + ' echo "198.51.100.1"' \ + ' exit 0' \ + ' fi' \ + ' if [ "$a" = "-6" ]; then' \ + ' if [ -n "$MOCK_FAIL_IPV6" ]; then exit "${MOCK_IPV6_STATUS:-28}"; fi' \ + ' echo "2001:db8::1"' \ + ' exit 0' \ + ' fi' \ + 'done' \ + 'exit 0' > $gip_handler +chmod +x $gip_handler + +reset_mocks +set -gx MOCK_CURL_HANDLER $gip_handler + +# gip: both IPv4 and IPv6 succeed +set -l out_both (gip) +check "gip: both succeed outputs IPv4" true (string match -q '*IPv4: 198.51.100.1*' -- $out_both; and echo true; or echo false) +check "gip: both succeed outputs IPv6" true (string match -q '*IPv6: 2001:db8::1*' -- $out_both; and echo true; or echo false) + +# gip: IPv4 timeout / failure, IPv6 success +set -gx MOCK_FAIL_IPV4 1 +set -l out_v4_fail (gip) +check "gip: IPv4 failure reports 'Not detected'" true (string match -q '*IPv4: Not detected*' -- $out_v4_fail; and echo true; or echo false) +check "gip: IPv4 failure still reports IPv6" true (string match -q '*IPv6: 2001:db8::1*' -- $out_v4_fail; and echo true; or echo false) + +# gip: IPv4 success, IPv6 failure +set -e MOCK_FAIL_IPV4 +set -gx MOCK_FAIL_IPV6 1 +set -l out_v6_fail (gip) +check "gip: IPv6 failure reports IPv4" true (string match -q '*IPv4: 198.51.100.1*' -- $out_v6_fail; and echo true; or echo false) +check "gip: IPv6 failure reports 'Not detected'" true (string match -q '*IPv6: Not detected*' -- $out_v6_fail; and echo true; or echo false) + +# gip: complete network drop (both fail) +set -gx MOCK_FAIL_IPV4 1 +set -gx MOCK_FAIL_IPV6 1 +set -l out_all_fail (gip) +check "gip: complete drop reports IPv4 Not detected" true (string match -q '*IPv4: Not detected*' -- $out_all_fail; and echo true; or echo false) +check "gip: complete drop reports IPv6 Not detected" true (string match -q '*IPv6: Not detected*' -- $out_all_fail; and echo true; or echo false) + +# gip4: success +set -e MOCK_FAIL_IPV4 +set -l out_gip4 (gip4) +check "gip4: success prints IP" "198.51.100.1" "$out_gip4" +check "gip4: success exits 0" 0 $status + +# gip4: failure +set -gx MOCK_FAIL_IPV4 1 +set -gx MOCK_IPV4_STATUS 7 +gip4 >/dev/null 2>&1 +check "gip4: failure returns curl status" 7 $status + +# gip6: success +set -e MOCK_FAIL_IPV6 +set -l out_gip6 (gip6) +check "gip6: success prints IPv6" "2001:db8::1" "$out_gip6" +check "gip6: success exits 0" 0 $status + +# gip6: failure / unsupported network +set -gx MOCK_FAIL_IPV6 1 +set -l out_gip6_err (gip6 2>&1) +check "gip6: failure exits 1" 1 $status +check "gip6: failure prints notice" true (string match -q '*IPv6 is currently unavailable*' -- $out_gip6_err; and echo true; or echo false) + + +# ───────────────────────────────────────────────────────────────────────────── +# 3. qr (QR code generator) +# ───────────────────────────────────────────────────────────────────────────── +section "network isolation: qr" + +reset_mocks +# Case A: Local qrencode tool is present +set -gx MOCK_QRENCODE_LOG (mktemp) +set -ga TMPDIRS $MOCK_QRENCODE_LOG +set -l qr_local (qr "hello-world") +check "qr: local qrencode is preferred offline" true (string match -q '*LOCAL_QRENCODE*' -- $qr_local; and echo true; or echo false) +check "qr: curl is never called when qrencode exists" 0 (test -f $MOCK_DIR/curl_log; and count (cat $MOCK_DIR/curl_log); or echo 0) + +# Case B: Local qrencode is missing, falling back to curl +function type + if test "$argv[1]" = "-q" -a "$argv[2]" = "qrencode" + return 1 + end + builtin type $argv +end + +set -gx MOCK_CURL_BODY "UTF8_QR_BODY" +set -l qr_curl (qr "hello-curl") +check "qr: fallback to curl when qrencode is missing" "UTF8_QR_BODY" "$qr_curl" + +# Network drop during curl fallback +set -gx MOCK_CURL_STATUS 7 +set -gx MOCK_CURL_BODY "" +qr "fail" >/dev/null 2>&1 +check "qr: curl network drop returns non-zero" 7 $status + +# Argument-based curl fallback +set -gx MOCK_CURL_STATUS 0 +set -gx MOCK_CURL_BODY "TEXT_QR" +set -l qr_arg (qr "arg-text") +check "qr: argument works via curl fallback" "TEXT_QR" "$qr_arg" + +functions -e type + + +# ───────────────────────────────────────────────────────────────────────────── +# 4. bd-pull (Gitea issues sync) +# ───────────────────────────────────────────────────────────────────────────── +section "network isolation: bd-pull" + +reset_mocks +# Missing required arguments & env vars +bd-pull >/dev/null 2>&1 +check "bd-pull: missing repo argument exits 1" 1 $status + +begin + set -e GITEA_TOKEN + set -e GITEA_URL + bd-pull rootiest/test >/dev/null 2>&1 + check "bd-pull: missing GITEA_TOKEN exits 1" 1 $status + + set -gx GITEA_TOKEN "test_token" + bd-pull rootiest/test >/dev/null 2>&1 + check "bd-pull: missing GITEA_URL exits 1" 1 $status +end + +# Complete network drop / empty response +set -gx GITEA_TOKEN "dummy_token" +set -gx GITEA_URL "https://git.test" +set -gx MOCK_CURL_STATUS 7 +set -gx MOCK_CURL_BODY "" +set -l out_drop (bd-pull rootiest/test) +check "bd-pull: network drop reports no unlinked issues" true (string match -q '*No unlinked issues found*' -- $out_drop; and echo true; or echo false) + +# Empty JSON issue list +set -gx MOCK_CURL_STATUS 0 +set -gx MOCK_CURL_BODY "[]" +set -l out_empty (bd-pull rootiest/test) +check "bd-pull: empty issue list handled cleanly" true (string match -q '*No unlinked issues found*' -- $out_empty; and echo true; or echo false) + +# Issues already linked with [ID] prefix +set -gx MOCK_CURL_BODY '[{"number": 1, "title": "[bd-1] Already linked issue"}]' +set -l out_already (bd-pull rootiest/test) +check "bd-pull: already-linked issues skipped" true (string match -q '*No unlinked issues found*' -- $out_already; and echo true; or echo false) + +# Unlinked issue found: creates bead, patches title, commits & pushes +set -l bd_remote (new_repo) +git -C $bd_remote config --bool core.bare true +set -l bd_repo (new_repo) +begin + set -l prev_pwd $PWD + builtin cd $bd_repo + git remote add origin $bd_remote + mkdir -p .beads + touch .beads/issues.jsonl + git add .beads/issues.jsonl + git commit -q -m "init beads" + git push -q -u origin main + + set -gx MOCK_CURL_BODY '[{"number": 2, "title": "Web original issue without ID"}]' + set -l out_unlinked (bd-pull rootiest/test 2>&1) + check "bd-pull: unlinked issue is linked" true (string match -q '*Linked 1 issues*' -- $out_unlinked; and echo true; or echo false) + + # Verify git commit was made + set -l last_msg (git log -1 --pretty=%s) + check "bd-pull: commits synced IDs to git" "chore: sync local IDs for web issues" "$last_msg" + builtin cd $prev_pwd +end + + +# ───────────────────────────────────────────────────────────────────────────── +# 5. _auto_pull_sync (background fast-forward) +# ───────────────────────────────────────────────────────────────────────────── +section "network isolation: _auto_pull_sync" + +reset_mocks +# Non-git directory +_auto_pull_sync /tmp >/dev/null 2>&1 +check "_auto_pull_sync: non-git directory returns 1" 1 $status + +# Git repo without upstream branch +set -l sync_repo (new_repo) +echo "test" > $sync_repo/file.txt +git -C $sync_repo add file.txt +git -C $sync_repo commit -q -m "initial" +_auto_pull_sync $sync_repo >/dev/null 2>&1 +check "_auto_pull_sync: missing upstream returns 1" 1 $status + +# Setup upstream branch for sync_repo +set -l sync_upstream (new_repo) +git -C $sync_upstream config --bool core.bare true +git -C $sync_repo remote add origin $sync_upstream +git -C $sync_repo push -q -u origin main >/dev/null 2>&1 + +# Dirty working tree (unstaged modifications) +echo "dirty" >> $sync_repo/file.txt +_auto_pull_sync $sync_repo >/dev/null 2>&1 +check "_auto_pull_sync: dirty worktree returns 1" 1 $status +git -C $sync_repo checkout -q -- file.txt + +# Dirty index (staged modifications) +echo "staged" >> $sync_repo/staged.txt +git -C $sync_repo add staged.txt +_auto_pull_sync $sync_repo >/dev/null 2>&1 +check "_auto_pull_sync: dirty index returns 1" 1 $status +git -C $sync_repo reset -q HEAD staged.txt +command rm -f $sync_repo/staged.txt + +# Clean repo, network failure on git fetch +set -gx MOCK_GIT_FAIL_FETCH 1 +_auto_pull_sync $sync_repo >/dev/null 2>&1 +check "_auto_pull_sync: fetch failure returns 1" 1 $status + +# Clean repo, successful fast-forward +reset_mocks +# Add commit to upstream +set -l peer_clone (new_repo) +git -C $peer_clone clone -q $sync_upstream $peer_clone/work +echo "new commit" > $peer_clone/work/new.txt +git -C $peer_clone/work add new.txt +git -C $peer_clone/work commit -q -m "upstream work" +git -C $peer_clone/work push -q origin main + +_auto_pull_sync $sync_repo >/dev/null 2>&1 +check "_auto_pull_sync: clean fast-forward returns 0" 0 $status +check "_auto_pull_sync: changes merged into working tree" true (test -f $sync_repo/new.txt; and echo true; or echo false) + + +# ───────────────────────────────────────────────────────────────────────────── +# 6. gitup (fetch and status) +# ───────────────────────────────────────────────────────────────────────────── +section "network isolation: gitup" + +reset_mocks +begin + set -l prev_pwd $PWD + builtin cd /tmp + gitup >/dev/null 2>&1 + check "gitup: outside git repo exits 1" 1 $status + + set -l r (new_repo) + builtin cd $r + echo a > a && git add a && git commit -q -m a + + # Network failure on git fetch + set -gx MOCK_GIT_FAIL_FETCH 1 + gitup >/dev/null 2>&1 + check "gitup: network drop during fetch exits non-zero" true (test $status -ne 0; and echo true; or echo false) + + # Successful fetch + reset_mocks + set -l up_out (gitup 2>&1) + check "gitup: success exits 0" 0 $status + check "gitup: displays git status output" true (string match -q '*On branch main*' -- $up_out; and echo true; or echo false) + builtin cd $prev_pwd +end + + +# ───────────────────────────────────────────────────────────────────────────── +# 7. git-clean (fetch --prune and delete orphaned branches) +# ───────────────────────────────────────────────────────────────────────────── +section "network isolation: git-clean" + +reset_mocks +git-clean --help >/dev/null 2>&1 +check "git-clean: --help exits 0" 0 $status + +begin + set -l prev_pwd $PWD + set -l r (new_repo) + builtin cd $r + echo a > a && git add a && git commit -q -m a + + # Network drop during git fetch --prune + set -gx MOCK_GIT_FAIL_FETCH 1 + # git-clean continues to local cleanup even if fetch fails + set -l clean_out (git-clean 2>&1) + check "git-clean: network drop handled, reports tidy" true (string match -q '*No orphaned branches found*' -- $clean_out; and echo true; or echo false) + + # Orphaned branch detection and cleanup + reset_mocks + # Simulate an orphaned branch with [gone] tracking + git branch orphaned-feat + # Create git-clean handler that simulates git branch -vv showing ': gone]' + set -l clean_git_handler $MOCK_DIR/git_clean_shim.sh + printf '%s\n' \ + '#!/bin/sh' \ + 'for a in "$@"; do' \ + ' if [ "$a" = "-vv" ]; then' \ + ' echo " main 1234567 [origin/main] initial"' \ + ' echo " orphaned-feat abcdef0 [origin/orphaned-feat: gone] feature"' \ + ' exit 0' \ + ' fi' \ + 'done' \ + "exec $real_git \"\$@\"" > $clean_git_handler + chmod +x $clean_git_handler + + set -gx MOCK_GIT_HANDLER $clean_git_handler + set -l clean_del_out (git-clean 2>&1) + check "git-clean: detects and deletes orphaned branch" true (string match -q '*Deleting orphaned local branches*' -- $clean_del_out; and echo true; or echo false) + check "git-clean: orphaned branch was deleted" false (git rev-parse --verify --quiet orphaned-feat >/dev/null 2>&1; and echo true; or echo false) + + builtin cd $prev_pwd +end + + +# ───────────────────────────────────────────────────────────────────────────── +# 8. config-update (configuration repository sync) +# ───────────────────────────────────────────────────────────────────────────── +section "network isolation: config-update" + +reset_mocks +config-update --help >/dev/null 2>&1 +check "config-update: --help exits 0" 0 $status + +# Non-git CONFIG_DIR (using mock handler) +set -l cfg_update_handler $MOCK_DIR/cfg_update_shim.sh +printf '%s\n' \ + '#!/bin/sh' \ + 'for a in "$@"; do' \ + ' if [ "$a" = "fetch" ] && [ -n "$MOCK_CFG_FAIL_FETCH" ]; then' \ + ' exit 1' \ + ' fi' \ + 'done' \ + "exec $real_git \"\$@\"" > $cfg_update_handler +chmod +x $cfg_update_handler +set -gx MOCK_GIT_HANDLER $cfg_update_handler + +# Network failure on upstream fetch +set -l fake_home (mktemp -d) +set -ga TMPDIRS $fake_home +set -l fake_fish_cfg "$fake_home/.config/fish" +mkdir -p (dirname $fake_fish_cfg) +set -l cfg_repo (new_repo) +command cp -r $cfg_repo $fake_fish_cfg + +# Test config-update against fake_fish_cfg by setting HOME +begin + set -lx HOME $fake_home + set -gx MOCK_CFG_FAIL_FETCH 1 + config-update >/dev/null 2>&1 + check "config-update: network fetch failure returns 1" 1 $status +end + + +# ───────────────────────────────────────────────────────────────────────────── +# 9. repo-open (origin URL normalization and browser deep-linking) +# ───────────────────────────────────────────────────────────────────────────── +section "network isolation: repo-open" + +reset_mocks +repo-open --help >/dev/null 2>&1 +check "repo-open: --help exits 0" 0 $status + +# Outside git repo +begin + set -l prev_pwd $PWD + builtin cd /tmp + repo-open -p >/dev/null 2>&1 + check "repo-open: outside git repo returns 1" 1 $status + builtin cd $prev_pwd +end + +# In git repo without origin +set -l r_no_origin (new_repo) +begin + set -l prev_pwd $PWD + builtin cd $r_no_origin + repo-open -p >/dev/null 2>&1 + check "repo-open: without origin remote returns 1" 1 $status + builtin cd $prev_pwd +end + +# In git repo with GitHub origin remote +set -l r_gh (new_repo) +git -C $r_gh remote add origin "https://github.com/rootiest/fish-config.git" +echo init > $r_gh/file && git -C $r_gh add file && git -C $r_gh commit -q -m init +begin + set -l prev_pwd $PWD + builtin cd $r_gh + + # Remote ls-remote network failure falls back to default branch + set -gx MOCK_GIT_FAIL_LS_REMOTE 1 + set -l url_fallback (repo-open -p) + check "repo-open: ls-remote network drop falls back to default branch" "https://github.com/rootiest/fish-config" "$url_fallback" + + # With ls-remote success for current branch + reset_mocks + set -l gh_ls_handler $MOCK_DIR/gh_ls_shim.sh + printf '%s\n' \ + '#!/bin/sh' \ + 'for a in "$@"; do' \ + ' if [ "$a" = "ls-remote" ]; then' \ + ' echo "abcdef01 refs/heads/main"' \ + ' exit 0' \ + ' fi' \ + 'done' \ + "exec $real_git \"\$@\"" > $gh_ls_handler + chmod +x $gh_ls_handler + set -gx MOCK_GIT_HANDLER $gh_ls_handler + + # Test gitlab provider normalization + git remote set-url origin "git@gitlab.com:rootiest/fish-config.git" + git checkout -q -b feat-test + set -l url_gl (repo-open -p) + check "repo-open: gitlab ssh url normalized with branch tree" "https://gitlab.com/rootiest/fish-config/-/tree/feat-test" "$url_gl" + + # Test gitea provider normalization + git remote set-url origin "https://git.rootiest.dev/rootiest/fish-config.git" + git config browse.provider gitea + set -l url_gitea (repo-open -p) + check "repo-open: gitea url normalized with src/branch" "https://git.rootiest.dev/rootiest/fish-config/src/branch/feat-test" "$url_gitea" + + builtin cd $prev_pwd +end + + +# ───────────────────────────────────────────────────────────────────────────── +# 10. fzf-update (fzf install / git pull) +# ───────────────────────────────────────────────────────────────────────────── +section "network isolation: fzf-update" + +reset_mocks +# When ~/.fzf exists, git pull fails due to network drop +set -l fake_home_fzf (mktemp -d) +set -ga TMPDIRS $fake_home_fzf +mkdir -p $fake_home_fzf/.fzf + +begin + set -lx HOME $fake_home_fzf + set -gx MOCK_GIT_FAIL_PULL 1 + fzf-update >/dev/null 2>&1 + check "fzf-update: git pull network drop returns non-zero" true (test $status -ne 0; and echo true; or echo false) +end + +# When ~/.fzf does not exist, git clone fails due to network drop +set -l fake_home_no_fzf (mktemp -d) +set -ga TMPDIRS $fake_home_no_fzf + +begin + set -lx HOME $fake_home_no_fzf + set -gx MOCK_GIT_FAIL_CLONE 1 + fzf-update >/dev/null 2>&1 + check "fzf-update: git clone network drop returns non-zero" true (test $status -ne 0; and echo true; or echo false) +end + + +# ───────────────────────────────────────────────────────────────────────────── +# Teardown and Final Report +# ───────────────────────────────────────────────────────────────────────────── +cleanup +report diff --git a/tests/test-network-python.fish b/tests/test-network-python.fish new file mode 100644 index 0000000..2345bc8 --- /dev/null +++ b/tests/test-network-python.fish @@ -0,0 +1,31 @@ +#!/usr/bin/env fish +# Copyright (C) 2026 Rootiest +# SPDX-License-Identifier: AGPL-3.0-or-later +# +# Isolated test suite for Python helper scripts network isolation and edge case mocking. +# Exercises scripts/sync-labels.py under offline conditions with full mock coverage. +# +# Runs isolated (no # MODE: marker, defaulting to isolated). + +source (realpath (dirname (status filename)))/lib.fish + +section "python network isolation: sync-labels.py" + +set -l py_script $repo_root/tests/test_sync_labels.py +check "tests/test_sync_labels.py exists" true (test -f $py_script; and echo true; or echo false) + +set -l py_lines (python3 $py_script -v 2>&1) +set -l py_status $status + +for line in $py_lines + if string match -qr '^\s*(test_\w+)\s+\([^)]+\)\s+\.\.\.\s+(\w+)' -- $line + set -l match (string match -r '^\s*(test_\w+)\s+\([^)]+\)\s+\.\.\.\s+(\w+)' -- $line) + set -l tname $match[2] + set -l tres $match[3] + check "$tname" ok (string lower $tres) + end +end + +check "test_sync_labels.py suite exited 0" 0 $py_status + +report diff --git a/tests/test_sync_labels.py b/tests/test_sync_labels.py new file mode 100644 index 0000000..eddf36a --- /dev/null +++ b/tests/test_sync_labels.py @@ -0,0 +1,429 @@ +#!/usr/bin/env python3 +# Copyright (C) 2026 Rootiest +# SPDX-License-Identifier: AGPL-3.0-or-later +"""Comprehensive unit tests for scripts/sync-labels.py with network isolation.""" + +import io +import json +import os +import socket +import sys +import tempfile +import unittest +import warnings +from pathlib import Path +from unittest.mock import MagicMock, patch, call +import urllib.error +import urllib.request +import importlib.util + +# Load scripts/sync-labels.py via importlib (handling the hyphen in filename) +_script_path = Path(__file__).resolve().parent.parent / "scripts" / "sync-labels.py" +_spec = importlib.util.spec_from_file_location("sync_labels", _script_path) +sl = importlib.util.module_from_spec(_spec) +sys.modules["sync_labels"] = sl +_spec.loader.exec_module(sl) + + +def _make_http_error(url, code, msg, headers, body_bytes): + fp = io.BytesIO(body_bytes) + return urllib.error.HTTPError(url, code, msg, headers, fp) + + +class TestRequestNetworkDrop(unittest.TestCase): + """Edge cases for complete network drop and connection failures.""" + + @patch("urllib.request.urlopen") + def test_request_connection_refused(self, mock_urlopen): + mock_urlopen.side_effect = urllib.error.URLError( + ConnectionRefusedError(111, "Connection refused") + ) + with self.assertRaises(sl.SyncError) as ctx: + sl.request("https://api.github.com/test") + self.assertIn("Connection refused", str(ctx.exception)) + + @patch("urllib.request.urlopen") + def test_request_dns_resolution_failure(self, mock_urlopen): + mock_urlopen.side_effect = urllib.error.URLError( + socket.gaierror(-2, "Name or service not known") + ) + with self.assertRaises(sl.SyncError) as ctx: + sl.request("https://nonexistent.invalid/test") + self.assertIn("Name or service not known", str(ctx.exception)) + + @patch("urllib.request.urlopen") + def test_request_network_unreachable(self, mock_urlopen): + mock_urlopen.side_effect = urllib.error.URLError( + OSError(101, "Network is unreachable") + ) + with self.assertRaises(sl.SyncError) as ctx: + sl.request("https://api.github.com/test") + self.assertIn("Network is unreachable", str(ctx.exception)) + + +class TestRequestTimeouts(unittest.TestCase): + """Edge cases for slow or hung connections and timeouts.""" + + @patch("urllib.request.urlopen") + def test_request_socket_timeout(self, mock_urlopen): + mock_urlopen.side_effect = urllib.error.URLError( + socket.timeout("The read operation timed out") + ) + with self.assertRaises(sl.SyncError) as ctx: + sl.request("https://api.github.com/test") + self.assertIn("timed out", str(ctx.exception)) + + @patch("urllib.request.urlopen") + def test_request_timeout_parameter_passed(self, mock_urlopen): + mock_resp = MagicMock() + mock_resp.read.return_value = b'{"ok": true}' + mock_resp.__enter__.return_value = mock_resp + mock_resp.__exit__.return_value = None + mock_urlopen.return_value = mock_resp + + sl.request("https://api.github.com/test") + mock_urlopen.assert_called_once() + _, kwargs = mock_urlopen.call_args + self.assertEqual(kwargs.get("timeout"), sl.TIMEOUT) + + +class TestRequestHttpCodes(unittest.TestCase): + """Edge cases for HTTP 4xx and 5xx responses, rate limiting, and errors.""" + + @patch("urllib.request.urlopen") + def test_request_http_401_unauthorized(self, mock_urlopen): + err = _make_http_error( + "https://api.github.com/test", + 401, + "Unauthorized", + {}, + b'{"message": "Bad credentials"}', + ) + mock_urlopen.side_effect = err + with self.assertRaises(sl.SyncError) as ctx: + sl.request("https://api.github.com/test", token="invalid") + self.assertIn("HTTP 401", str(ctx.exception)) + self.assertIn("Bad credentials", str(ctx.exception)) + err.close() + + @patch("urllib.request.urlopen") + def test_request_http_403_rate_limit(self, mock_urlopen): + err = _make_http_error( + "https://api.github.com/test", + 403, + "Forbidden", + {"X-RateLimit-Remaining": "0"}, + b'{"message": "API rate limit exceeded for user"}', + ) + mock_urlopen.side_effect = err + with self.assertRaises(sl.SyncError) as ctx: + sl.request("https://api.github.com/test") + self.assertIn("HTTP 403", str(ctx.exception)) + self.assertIn("rate limit exceeded", str(ctx.exception)) + err.close() + + @patch("urllib.request.urlopen") + def test_request_http_404_not_found(self, mock_urlopen): + err = _make_http_error( + "https://api.github.com/test", + 404, + "Not Found", + {}, + b'{"message": "Not Found"}', + ) + mock_urlopen.side_effect = err + with self.assertRaises(sl.SyncError) as ctx: + sl.request("https://api.github.com/test") + self.assertIn("HTTP 404", str(ctx.exception)) + err.close() + + @patch("urllib.request.urlopen") + def test_request_http_500_server_error(self, mock_urlopen): + err = _make_http_error( + "https://api.github.com/test", + 500, + "Internal Server Error", + {}, + b"Internal Server Error", + ) + mock_urlopen.side_effect = err + with self.assertRaises(sl.SyncError) as ctx: + sl.request("https://api.github.com/test") + self.assertIn("HTTP 500", str(ctx.exception)) + err.close() + + @patch("urllib.request.urlopen") + def test_request_http_502_bad_gateway(self, mock_urlopen): + err = _make_http_error( + "https://api.github.com/test", + 502, + "Bad Gateway", + {}, + b"502 Bad Gateway", + ) + mock_urlopen.side_effect = err + with self.assertRaises(sl.SyncError) as ctx: + sl.request("https://api.github.com/test") + self.assertIn("HTTP 502", str(ctx.exception)) + err.close() + + +class TestRequestPayloads(unittest.TestCase): + """Edge cases for payloads, parsing, and request formatting.""" + + @patch("urllib.request.urlopen") + def test_request_valid_json(self, mock_urlopen): + mock_resp = MagicMock() + mock_resp.read.return_value = b'[{"name": "Kind/Bug", "color": "ee0701"}]' + mock_resp.__enter__.return_value = mock_resp + mock_resp.__exit__.return_value = None + mock_urlopen.return_value = mock_resp + + res = sl.request("https://api.github.com/test") + self.assertEqual(res, [{"name": "Kind/Bug", "color": "ee0701"}]) + + @patch("urllib.request.urlopen") + def test_request_empty_body_204(self, mock_urlopen): + mock_resp = MagicMock() + mock_resp.read.return_value = b"" + mock_resp.__enter__.return_value = mock_resp + mock_resp.__exit__.return_value = None + mock_urlopen.return_value = mock_resp + + res = sl.request("https://api.github.com/test", method="DELETE") + self.assertIsNone(res) + + @patch("urllib.request.urlopen") + def test_request_headers_and_auth(self, mock_urlopen): + mock_resp = MagicMock() + mock_resp.read.return_value = b"{}" + mock_resp.__enter__.return_value = mock_resp + mock_resp.__exit__.return_value = None + mock_urlopen.return_value = mock_resp + + sl.request( + "https://api.github.com/test", + token="secret_token", + method="POST", + payload={"name": "test"}, + ) + req = mock_urlopen.call_args[0][0] + self.assertEqual(req.get_header("Authorization"), "Bearer secret_token") + self.assertEqual(req.get_header("Accept"), "application/json") + self.assertEqual(req.get_header("Content-type"), "application/json") + self.assertEqual(req.get_method(), "POST") + self.assertEqual(req.data, b'{"name": "test"}') + + @patch("urllib.request.urlopen") + def test_request_corrupted_json_payload(self, mock_urlopen): + mock_resp = MagicMock() + mock_resp.read.return_value = b'{"name": "broken", invalid_json' + mock_resp.__enter__.return_value = mock_resp + mock_resp.__exit__.return_value = None + mock_urlopen.return_value = mock_resp + + with self.assertRaises(json.JSONDecodeError): + sl.request("https://api.github.com/test") + + +class TestPaginate(unittest.TestCase): + """Edge cases for pagination across API responses.""" + + @patch("sync_labels.request") + def test_paginate_single_page(self, mock_req): + mock_req.return_value = [{"name": "A"}, {"name": "B"}] + res = sl.paginate("https://api.test?page={page}&per_page={per_page}", per_page=50) + self.assertEqual(len(res), 2) + mock_req.assert_called_once_with( + "https://api.test?page=1&per_page=50", token=None + ) + + @patch("sync_labels.request") + def test_paginate_multiple_pages(self, mock_req): + page1 = [{"name": f"item{i}"} for i in range(50)] + page2 = [{"name": f"item{i}"} for i in range(50, 70)] + mock_req.side_effect = [page1, page2] + + res = sl.paginate("https://api.test?page={page}&per_page={per_page}", per_page=50) + self.assertEqual(len(res), 70) + self.assertEqual(mock_req.call_count, 2) + + @patch("sync_labels.request") + def test_paginate_exact_page_boundary(self, mock_req): + page1 = [{"name": f"item{i}"} for i in range(50)] + page2 = [] + mock_req.side_effect = [page1, page2] + + res = sl.paginate("https://api.test?page={page}&per_page={per_page}", per_page=50) + self.assertEqual(len(res), 50) + self.assertEqual(mock_req.call_count, 2) + + @patch("sync_labels.request") + def test_paginate_empty(self, mock_req): + mock_req.return_value = [] + res = sl.paginate("https://api.test?page={page}&per_page={per_page}", per_page=50) + self.assertEqual(res, []) + mock_req.assert_called_once() + + +class TestLabelOperations(unittest.TestCase): + """Edge cases for label CRUD operations and usage querying.""" + + @patch("sync_labels.request") + def test_usage_count_zero(self, mock_req): + mock_req.return_value = [] + count = sl.usage_count("Unused Label", "token") + self.assertEqual(count, 0) + mock_req.assert_called_once_with( + f"{sl.GITHUB_API}/repos/{sl.GITHUB_REPO}/issues" + f"?labels=Unused%20Label&state=all&per_page=100", + token="token", + ) + + @patch("sync_labels.request") + def test_usage_count_active(self, mock_req): + mock_req.return_value = [{"number": 1}, {"number": 2}] + count = sl.usage_count("Priority/High", "token") + self.assertEqual(count, 2) + mock_req.assert_called_once_with( + f"{sl.GITHUB_API}/repos/{sl.GITHUB_REPO}/issues" + f"?labels=Priority%2FHigh&state=all&per_page=100", + token="token", + ) + + @patch("sync_labels.request") + def test_create_label(self, mock_req): + label = {"name": "Test/Label", "color": "123456", "description": "Desc"} + sl.create_label(label, "tok") + mock_req.assert_called_once_with( + f"{sl.GITHUB_API}/repos/{sl.GITHUB_REPO}/labels", + token="tok", + method="POST", + payload=label, + ) + + @patch("sync_labels.request") + def test_update_label(self, mock_req): + label = {"name": "Area/Prompt & Theme", "color": "abcdef", "description": "New"} + sl.update_label(label, "tok") + mock_req.assert_called_once_with( + f"{sl.GITHUB_API}/repos/{sl.GITHUB_REPO}/labels/Area%2FPrompt%20%26%20Theme", + token="tok", + method="PATCH", + payload={ + "new_name": "Area/Prompt & Theme", + "color": "abcdef", + "description": "New", + }, + ) + + @patch("sync_labels.request") + def test_delete_label(self, mock_req): + sl.delete_label("Reviewed/Won't Fix", "tok") + mock_req.assert_called_once_with( + f"{sl.GITHUB_API}/repos/{sl.GITHUB_REPO}/labels/Reviewed%2FWon%27t%20Fix", + token="tok", + method="DELETE", + ) + + +class TestRunAndMainWorkflow(unittest.TestCase): + """End-to-end execution testing dry-run, live execution, and error handling.""" + + def setUp(self): + self.env_patcher = patch.dict(os.environ, {}, clear=True) + self.env_patcher.start() + + def tearDown(self): + self.env_patcher.stop() + + def test_run_missing_token_not_dry_run_raises(self): + with self.assertRaises(sl.SyncError) as ctx: + sl.run(dry_run=False) + self.assertIn("GH_MIRROR_TOKEN is not set", str(ctx.exception)) + + @patch("sync_labels.fetch_gitea_labels") + def test_run_gitea_empty_raises(self, mock_gitea): + os.environ[sl.TOKEN_ENV] = "dummy_token" + mock_gitea.return_value = [] + with self.assertRaises(sl.SyncError) as ctx: + sl.run(dry_run=False) + self.assertIn("Gitea returned no labels", str(ctx.exception)) + + @patch("sys.stdout", new_callable=io.StringIO) + @patch("sync_labels.delete_label") + @patch("sync_labels.update_label") + @patch("sync_labels.create_label") + @patch("sync_labels.usage_count") + @patch("sync_labels.fetch_github_labels") + @patch("sync_labels.fetch_gitea_labels") + def test_run_dry_run_makes_no_mutating_calls( + self, mock_gitea, mock_gh, mock_usage, mock_create, mock_update, mock_delete, mock_stdout + ): + mock_gitea.return_value = [ + {"name": "Keep", "color": "111111", "description": "Same"}, + {"name": "New", "color": "222222", "description": "Created"}, + {"name": "Change", "color": "333333", "description": "Updated"}, + ] + mock_gh.return_value = [ + {"name": "Keep", "color": "111111", "description": "Same"}, + {"name": "Change", "color": "000000", "description": "Old"}, + {"name": "ExtraUnused", "color": "444444", "description": "Deleted"}, + {"name": "ExtraUsed", "color": "555555", "description": "Kept"}, + ] + mock_usage.side_effect = lambda name, token: 1 if name == "ExtraUsed" else 0 + + # In dry run, token is not strictly required + status = sl.run(dry_run=True) + self.assertEqual(status, 0) + mock_create.assert_not_called() + mock_update.assert_not_called() + mock_delete.assert_not_called() + + @patch("sys.stdout", new_callable=io.StringIO) + @patch("sync_labels.delete_label") + @patch("sync_labels.update_label") + @patch("sync_labels.create_label") + @patch("sync_labels.usage_count") + @patch("sync_labels.fetch_github_labels") + @patch("sync_labels.fetch_gitea_labels") + def test_run_live_executes_plan( + self, mock_gitea, mock_gh, mock_usage, mock_create, mock_update, mock_delete, mock_stdout + ): + os.environ[sl.TOKEN_ENV] = "my_token" + mock_gitea.return_value = [ + {"name": "Keep", "color": "111111", "description": "Same"}, + {"name": "New", "color": "222222", "description": "Created"}, + {"name": "Change", "color": "333333", "description": "Updated"}, + ] + mock_gh.return_value = [ + {"name": "Keep", "color": "111111", "description": "Same"}, + {"name": "Change", "color": "000000", "description": "Old"}, + {"name": "ExtraUnused", "color": "444444", "description": "Deleted"}, + {"name": "ExtraUsed", "color": "555555", "description": "Kept"}, + ] + mock_usage.side_effect = lambda name, token: 1 if name == "ExtraUsed" else 0 + + status = sl.run(dry_run=False) + self.assertEqual(status, 0) + mock_create.assert_called_once_with( + {"name": "New", "color": "222222", "description": "Created"}, "my_token" + ) + mock_update.assert_called_once() + mock_delete.assert_called_once_with("ExtraUnused", "my_token") + + @patch("sys.stdout", new_callable=io.StringIO) + def test_main_self_test(self, mock_stdout): + self.assertEqual(sl.main(["--self-test"]), 0) + + @patch("sync_labels.run") + def test_main_sync_error_exit_code(self, mock_run): + mock_run.side_effect = sl.SyncError("Boom") + with patch("sys.stderr", new=io.StringIO()) as fake_err: + rc = sl.main(["--dry-run"]) + self.assertEqual(rc, 1) + self.assertIn("error: Boom", fake_err.getvalue()) + + +if __name__ == "__main__": + unittest.main() -- 2.54.0 From 2dc978e719c00a10a3d282ac9db1ed36511f6428 Mon Sep 17 00:00:00 2001 From: Rootiest Date: Wed, 9 Sep 2026 20:27:21 -0400 Subject: [PATCH 2/8] fix(docs): align registry generator output with fish_indent --- docs/generate_component_registry.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/generate_component_registry.py b/docs/generate_component_registry.py index 6a1b9e6..cf64c0f 100644 --- a/docs/generate_component_registry.py +++ b/docs/generate_component_registry.py @@ -99,10 +99,16 @@ def render(registry: dict[str, list[str]]) -> str: lines += [f" {k} \\" for k in quoted_keys[:-1]] + [f" {quoted_keys[-1]}"] lines.append("") - values = ['"' + " ".join(registry[k]) + '"' for k in keys] + values = [ + ( + '"' + " ".join(registry[k]) + '"' + if any(c in " ".join(registry[k]) for c in ' \t*?[]"\'\\$') + else " ".join(registry[k]) + ) + for k in keys + ] lines.append("set -g __fish_config_op_registry_values \\") lines += [f" {v} \\" for v in values[:-1]] + [f" {values[-1]}"] - lines.append("") return "\n".join(lines) + "\n" -- 2.54.0 From e40d67df592cc454b2b4ba1e2b5e9168d9275c0a Mon Sep 17 00:00:00 2001 From: Rootiest Date: Wed, 9 Sep 2026 20:27:48 -0400 Subject: [PATCH 3/8] style: format fish files with fish_indent --- completions/bd.fish | 9 +- completions/copilot.fish | 44 +- completions/deadbranch.fish | 120 +-- completions/ov.fish | 9 +- completions/tailscale.fish | 5 +- conf.d/__fish_config_op_registry.fish | 131 ++- conf.d/bash_expands.fish | 34 +- conf.d/paru-wrapper.fish | 11 +- conf.d/wakatime.fish | 53 +- conf.d/yay-wrapper.fish | 11 +- config.fish | 2 +- functions/__auto_source_fallback_venv.fish | 22 +- functions/__config_settings_apply.fish | 4 +- functions/__config_settings_set_value.fish | 4 +- functions/__config_settings_subcats.fish | 6 +- functions/__fish_user_dots_link.fish | 4 +- functions/__insert_previous_path_head.fish | 2 +- functions/__interactive_history_sub.fish | 8 +- functions/__substitute_typo.fish | 10 +- functions/_agents_init_ensure_gitignore.fish | 4 +- functions/_fish_deps_install.fish | 24 +- functions/_fish_deps_status.fish | 46 +- functions/_fish_deps_update.fish | 18 +- functions/_fish_mkdir_p.fish | 2 +- functions/_puffer_fish_expand_bang.fish | 1 - functions/_puffer_fish_expand_star.fish | 2 +- functions/_smart_execute.fish | 6 +- functions/agents-vault.fish | 6 +- functions/agy.fish | 8 +- functions/bd-pull.fish | 25 +- functions/branch.fish | 6 +- functions/cheat.fish | 2 +- functions/cleanup.fish | 6 +- functions/config-update.fish | 2 +- functions/fish_prompt.fish | 18 +- functions/fisher.fish | 6 +- functions/gip6.fish | 2 +- functions/gitup.fish | 4 +- functions/hist.fish | 2 +- functions/logs.fish | 11 +- functions/pkg.fish | 80 +- functions/rand_string.fish | 12 +- functions/rawfish.fish | 4 +- functions/repo-open.fish | 8 +- functions/sbver.fish | 86 +- functions/spark.fish | 2 +- functions/sudo-toggle.fish | 4 +- functions/swapstat.fish | 4 +- functions/view.fish | 2 +- functions/y.fish | 2 +- functions/yt-dlp.fish | 2 +- integrations/fzf.fish | 878 ++++++++++--------- tests/palette-bytes.fish | 15 +- tests/run-tests.fish | 3 +- tests/test-agents-vault.fish | 306 ++++--- tests/test-guards.fish | 8 +- tests/test-help.fish | 6 +- tests/test-network-fish.fish | 87 +- 58 files changed, 1184 insertions(+), 1015 deletions(-) diff --git a/completions/bd.fish b/completions/bd.fish index 2605beb..8e90718 100644 --- a/completions/bd.fish +++ b/completions/bd.fish @@ -3,7 +3,7 @@ function __bd_debug set -l file "$BASH_COMP_DEBUG_FILE" if test -n "$file" - echo "$argv" >> $file + echo "$argv" >>$file end end @@ -108,7 +108,6 @@ function __bd_requires_order_preservation return 1 end - # This function does two things: # - Obtain the completions and store them in the global __bd_comp_results # - Return false if file completion should be performed @@ -215,18 +214,18 @@ end # so we can properly delete any completions provided by another script. # Only do this if the program can be found, or else fish may print some errors; besides, # the existing completions will only be loaded if the program can be found. -if type -q "bd" +if type -q bd # The space after the program name is essential to trigger completion for the program # and not completion of the program name itself. # Also, we use '> /dev/null 2>&1' since '&>' is not supported in older versions of fish. - complete --do-complete "bd " > /dev/null 2>&1 + complete --do-complete "bd " >/dev/null 2>&1 end # Remove any pre-existing completions for the program since we will be handling all of them. complete -c bd -e # this will get called after the two calls below and clear the $__bd_perform_completion_once_result global -complete -c bd -n '__bd_clear_perform_completion_once_result' +complete -c bd -n __bd_clear_perform_completion_once_result # The call to __bd_prepare_completions will setup __bd_comp_results # which provides the program's completion choices. # If this doesn't require order preservation, we don't use the -k flag diff --git a/completions/copilot.fish b/completions/copilot.fish index 17fdc40..0d9b380 100644 --- a/completions/copilot.fish +++ b/completions/copilot.fish @@ -1,14 +1,14 @@ # fish completion for copilot # Generated by `copilot completion fish`. Do not edit by hand. -complete -c copilot -n '__fish_use_subcommand' -f -a 'login' -d 'Authenticate with Copilot' -complete -c copilot -n '__fish_use_subcommand' -f -a 'help' -d 'Display help information' -complete -c copilot -n '__fish_use_subcommand' -f -a 'init' -d 'Initialize Copilot instructions' -complete -c copilot -n '__fish_use_subcommand' -f -a 'update' -d 'Download the latest version' -complete -c copilot -n '__fish_use_subcommand' -f -a 'version' -d 'Display version information' -complete -c copilot -n '__fish_use_subcommand' -f -a 'plugin' -d 'Manage plugins' -complete -c copilot -n '__fish_use_subcommand' -f -a 'mcp' -d 'Manage MCP servers' -complete -c copilot -n '__fish_use_subcommand' -f -a 'completion' -d 'Generate a shell completion script' +complete -c copilot -n __fish_use_subcommand -f -a login -d 'Authenticate with Copilot' +complete -c copilot -n __fish_use_subcommand -f -a help -d 'Display help information' +complete -c copilot -n __fish_use_subcommand -f -a init -d 'Initialize Copilot instructions' +complete -c copilot -n __fish_use_subcommand -f -a update -d 'Download the latest version' +complete -c copilot -n __fish_use_subcommand -f -a version -d 'Display version information' +complete -c copilot -n __fish_use_subcommand -f -a plugin -d 'Manage plugins' +complete -c copilot -n __fish_use_subcommand -f -a mcp -d 'Manage MCP servers' +complete -c copilot -n __fish_use_subcommand -f -a completion -d 'Generate a shell completion script' complete -c copilot -l version -s v -f -d 'show version information' complete -c copilot -l interactive -s i -r -d 'Start interactive mode and automatically execute this prompt' complete -c copilot -l prompt -s p -r -d 'Execute a prompt in non-interactive mode (exits after completion)' @@ -75,22 +75,22 @@ complete -c copilot -l acp -f -d 'Start as Agent Client Protocol server' complete -c copilot -l remote -f -d 'Enable remote control of your session from GitHub web and mobile' complete -c copilot -l no-remote -f -d 'Disable remote control of your session from GitHub web and mobile' complete -c copilot -n '__fish_seen_subcommand_from login' -l host -r -d 'GitHub host URL (default: https://github.com)' -complete -c copilot -n '__fish_seen_subcommand_from plugin' -f -a 'install' -d 'Install a plugin' -complete -c copilot -n '__fish_seen_subcommand_from plugin' -f -a 'uninstall' -d 'Uninstall a plugin' -complete -c copilot -n '__fish_seen_subcommand_from plugin' -f -a 'update' -d 'Update a plugin' -complete -c copilot -n '__fish_seen_subcommand_from plugin' -f -a 'list' -d 'List installed plugins' -complete -c copilot -n '__fish_seen_subcommand_from plugin' -f -a 'marketplace' -d 'Manage plugin marketplaces' +complete -c copilot -n '__fish_seen_subcommand_from plugin' -f -a install -d 'Install a plugin' +complete -c copilot -n '__fish_seen_subcommand_from plugin' -f -a uninstall -d 'Uninstall a plugin' +complete -c copilot -n '__fish_seen_subcommand_from plugin' -f -a update -d 'Update a plugin' +complete -c copilot -n '__fish_seen_subcommand_from plugin' -f -a list -d 'List installed plugins' +complete -c copilot -n '__fish_seen_subcommand_from plugin' -f -a marketplace -d 'Manage plugin marketplaces' complete -c copilot -n '__fish_seen_subcommand_from plugin; and __fish_seen_subcommand_from update' -l all -f -d 'Update all installed plugins' -complete -c copilot -n '__fish_seen_subcommand_from plugin; and __fish_seen_subcommand_from marketplace' -f -a 'add' -d 'Add a marketplace' -complete -c copilot -n '__fish_seen_subcommand_from plugin; and __fish_seen_subcommand_from marketplace' -f -a 'remove' -d 'Remove a marketplace' -complete -c copilot -n '__fish_seen_subcommand_from plugin; and __fish_seen_subcommand_from marketplace' -f -a 'list' -d 'List registered marketplaces' -complete -c copilot -n '__fish_seen_subcommand_from plugin; and __fish_seen_subcommand_from marketplace' -f -a 'browse' -d 'Browse plugins in a marketplace' -complete -c copilot -n '__fish_seen_subcommand_from plugin; and __fish_seen_subcommand_from marketplace' -f -a 'update' -d 'Update marketplace plugin catalogs' +complete -c copilot -n '__fish_seen_subcommand_from plugin; and __fish_seen_subcommand_from marketplace' -f -a add -d 'Add a marketplace' +complete -c copilot -n '__fish_seen_subcommand_from plugin; and __fish_seen_subcommand_from marketplace' -f -a remove -d 'Remove a marketplace' +complete -c copilot -n '__fish_seen_subcommand_from plugin; and __fish_seen_subcommand_from marketplace' -f -a list -d 'List registered marketplaces' +complete -c copilot -n '__fish_seen_subcommand_from plugin; and __fish_seen_subcommand_from marketplace' -f -a browse -d 'Browse plugins in a marketplace' +complete -c copilot -n '__fish_seen_subcommand_from plugin; and __fish_seen_subcommand_from marketplace' -f -a update -d 'Update marketplace plugin catalogs' complete -c copilot -n '__fish_seen_subcommand_from plugin; and __fish_seen_subcommand_from marketplace; and __fish_seen_subcommand_from remove' -l force -s f -f -d 'Force removal even if plugins are installed' -complete -c copilot -n '__fish_seen_subcommand_from mcp' -f -a 'list' -d 'List configured MCP servers' -complete -c copilot -n '__fish_seen_subcommand_from mcp' -f -a 'get' -d 'Show server details' -complete -c copilot -n '__fish_seen_subcommand_from mcp' -f -a 'add' -d 'Add an MCP server' -complete -c copilot -n '__fish_seen_subcommand_from mcp' -f -a 'remove' -d 'Remove an MCP server' +complete -c copilot -n '__fish_seen_subcommand_from mcp' -f -a list -d 'List configured MCP servers' +complete -c copilot -n '__fish_seen_subcommand_from mcp' -f -a get -d 'Show server details' +complete -c copilot -n '__fish_seen_subcommand_from mcp' -f -a add -d 'Add an MCP server' +complete -c copilot -n '__fish_seen_subcommand_from mcp' -f -a remove -d 'Remove an MCP server' complete -c copilot -n '__fish_seen_subcommand_from mcp; and __fish_seen_subcommand_from list' -l json -f -d 'Output as JSON' complete -c copilot -n '__fish_seen_subcommand_from mcp; and __fish_seen_subcommand_from get' -l json -f -d 'Output as JSON' complete -c copilot -n '__fish_seen_subcommand_from mcp; and __fish_seen_subcommand_from get' -l show-secrets -f -d 'Show full environment variable and header values (masked by default)' diff --git a/completions/deadbranch.fish b/completions/deadbranch.fish index 8b41569..9489261 100644 --- a/completions/deadbranch.fish +++ b/completions/deadbranch.fish @@ -1,38 +1,38 @@ # Print an optspec for argparse to handle cmd's options that are independent of any subcommand. function __fish_deadbranch_global_optspecs - string join \n h/help V/version + string join \n h/help V/version end function __fish_deadbranch_needs_command - # Figure out if the current invocation already has a command. - set -l cmd (commandline -opc) - set -e cmd[1] - argparse -s (__fish_deadbranch_global_optspecs) -- $cmd 2>/dev/null - or return - if set -q argv[1] - # Also print the command, so this can be used to figure out what it is. - echo $argv[1] - return 1 - end - return 0 + # Figure out if the current invocation already has a command. + set -l cmd (commandline -opc) + set -e cmd[1] + argparse -s (__fish_deadbranch_global_optspecs) -- $cmd 2>/dev/null + or return + if set -q argv[1] + # Also print the command, so this can be used to figure out what it is. + echo $argv[1] + return 1 + end + return 0 end function __fish_deadbranch_using_subcommand - set -l cmd (__fish_deadbranch_needs_command) - test -z "$cmd" - and return 1 - contains -- $cmd[1] $argv + set -l cmd (__fish_deadbranch_needs_command) + test -z "$cmd" + and return 1 + contains -- $cmd[1] $argv end -complete -c deadbranch -n "__fish_deadbranch_needs_command" -s h -l help -d 'Print help' -complete -c deadbranch -n "__fish_deadbranch_needs_command" -s V -l version -d 'Print version' -complete -c deadbranch -n "__fish_deadbranch_needs_command" -f -a "list" -d 'List stale branches' -complete -c deadbranch -n "__fish_deadbranch_needs_command" -f -a "clean" -d 'Delete stale branches (merged only by default, use --force for unmerged)' -complete -c deadbranch -n "__fish_deadbranch_needs_command" -f -a "config" -d 'Manage configuration' -complete -c deadbranch -n "__fish_deadbranch_needs_command" -f -a "backup" -d 'Manage backups' -complete -c deadbranch -n "__fish_deadbranch_needs_command" -f -a "stats" -d 'Show repository branch statistics' -complete -c deadbranch -n "__fish_deadbranch_needs_command" -f -a "completions" -d 'Generate shell completion scripts' -complete -c deadbranch -n "__fish_deadbranch_needs_command" -f -a "help" -d 'Print this message or the help of the given subcommand(s)' +complete -c deadbranch -n __fish_deadbranch_needs_command -s h -l help -d 'Print help' +complete -c deadbranch -n __fish_deadbranch_needs_command -s V -l version -d 'Print version' +complete -c deadbranch -n __fish_deadbranch_needs_command -f -a list -d 'List stale branches' +complete -c deadbranch -n __fish_deadbranch_needs_command -f -a clean -d 'Delete stale branches (merged only by default, use --force for unmerged)' +complete -c deadbranch -n __fish_deadbranch_needs_command -f -a config -d 'Manage configuration' +complete -c deadbranch -n __fish_deadbranch_needs_command -f -a backup -d 'Manage backups' +complete -c deadbranch -n __fish_deadbranch_needs_command -f -a stats -d 'Show repository branch statistics' +complete -c deadbranch -n __fish_deadbranch_needs_command -f -a completions -d 'Generate shell completion scripts' +complete -c deadbranch -n __fish_deadbranch_needs_command -f -a help -d 'Print this message or the help of the given subcommand(s)' complete -c deadbranch -n "__fish_deadbranch_using_subcommand list" -s d -l days -d 'Only show branches older than N days (default: from config or 30)' -r complete -c deadbranch -n "__fish_deadbranch_using_subcommand list" -l local -d 'Only show local branches' complete -c deadbranch -n "__fish_deadbranch_using_subcommand list" -l remote -d 'Only show remote branches' @@ -51,11 +51,11 @@ complete -c deadbranch -n "__fish_deadbranch_using_subcommand clean" -s h -l hel complete -c deadbranch -n "__fish_deadbranch_using_subcommand clean" -s V -l version -d 'Print version' complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and not __fish_seen_subcommand_from set show edit reset help" -s h -l help -d 'Print help' complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and not __fish_seen_subcommand_from set show edit reset help" -s V -l version -d 'Print version' -complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and not __fish_seen_subcommand_from set show edit reset help" -f -a "set" -d 'Set a configuration value' -complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and not __fish_seen_subcommand_from set show edit reset help" -f -a "show" -d 'Show current configuration' -complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and not __fish_seen_subcommand_from set show edit reset help" -f -a "edit" -d 'Open config file in $EDITOR' -complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and not __fish_seen_subcommand_from set show edit reset help" -f -a "reset" -d 'Reset configuration to defaults' -complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and not __fish_seen_subcommand_from set show edit reset help" -f -a "help" -d 'Print this message or the help of the given subcommand(s)' +complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and not __fish_seen_subcommand_from set show edit reset help" -f -a set -d 'Set a configuration value' +complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and not __fish_seen_subcommand_from set show edit reset help" -f -a show -d 'Show current configuration' +complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and not __fish_seen_subcommand_from set show edit reset help" -f -a edit -d 'Open config file in $EDITOR' +complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and not __fish_seen_subcommand_from set show edit reset help" -f -a reset -d 'Reset configuration to defaults' +complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and not __fish_seen_subcommand_from set show edit reset help" -f -a help -d 'Print this message or the help of the given subcommand(s)' complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and __fish_seen_subcommand_from set" -s h -l help -d 'Print help' complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and __fish_seen_subcommand_from set" -s V -l version -d 'Print version' complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and __fish_seen_subcommand_from show" -s h -l help -d 'Print help' @@ -64,18 +64,18 @@ complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and __fish complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and __fish_seen_subcommand_from edit" -s V -l version -d 'Print version' complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and __fish_seen_subcommand_from reset" -s h -l help -d 'Print help' complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and __fish_seen_subcommand_from reset" -s V -l version -d 'Print version' -complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and __fish_seen_subcommand_from help" -f -a "set" -d 'Set a configuration value' -complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and __fish_seen_subcommand_from help" -f -a "show" -d 'Show current configuration' -complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and __fish_seen_subcommand_from help" -f -a "edit" -d 'Open config file in $EDITOR' -complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and __fish_seen_subcommand_from help" -f -a "reset" -d 'Reset configuration to defaults' -complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and __fish_seen_subcommand_from help" -f -a "help" -d 'Print this message or the help of the given subcommand(s)' +complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and __fish_seen_subcommand_from help" -f -a set -d 'Set a configuration value' +complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and __fish_seen_subcommand_from help" -f -a show -d 'Show current configuration' +complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and __fish_seen_subcommand_from help" -f -a edit -d 'Open config file in $EDITOR' +complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and __fish_seen_subcommand_from help" -f -a reset -d 'Reset configuration to defaults' +complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and __fish_seen_subcommand_from help" -f -a help -d 'Print this message or the help of the given subcommand(s)' complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and not __fish_seen_subcommand_from list restore stats clean help" -s h -l help -d 'Print help' complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and not __fish_seen_subcommand_from list restore stats clean help" -s V -l version -d 'Print version' -complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and not __fish_seen_subcommand_from list restore stats clean help" -f -a "list" -d 'List available backups' -complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and not __fish_seen_subcommand_from list restore stats clean help" -f -a "restore" -d 'Restore a branch from backup' -complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and not __fish_seen_subcommand_from list restore stats clean help" -f -a "stats" -d 'Show backup storage statistics' -complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and not __fish_seen_subcommand_from list restore stats clean help" -f -a "clean" -d 'Remove old backups, keeping the most recent ones' -complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and not __fish_seen_subcommand_from list restore stats clean help" -f -a "help" -d 'Print this message or the help of the given subcommand(s)' +complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and not __fish_seen_subcommand_from list restore stats clean help" -f -a list -d 'List available backups' +complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and not __fish_seen_subcommand_from list restore stats clean help" -f -a restore -d 'Restore a branch from backup' +complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and not __fish_seen_subcommand_from list restore stats clean help" -f -a stats -d 'Show backup storage statistics' +complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and not __fish_seen_subcommand_from list restore stats clean help" -f -a clean -d 'Remove old backups, keeping the most recent ones' +complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and not __fish_seen_subcommand_from list restore stats clean help" -f -a help -d 'Print this message or the help of the given subcommand(s)' complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and __fish_seen_subcommand_from list" -l repo -d 'Show backups for a specific repository by name' -r complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and __fish_seen_subcommand_from list" -l current -d 'Only show backups for current repository' complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and __fish_seen_subcommand_from list" -s h -l help -d 'Print help' @@ -94,28 +94,28 @@ complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and __fish complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and __fish_seen_subcommand_from clean" -s y -l yes -d 'Skip confirmation prompt' complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and __fish_seen_subcommand_from clean" -s h -l help -d 'Print help' complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and __fish_seen_subcommand_from clean" -s V -l version -d 'Print version' -complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and __fish_seen_subcommand_from help" -f -a "list" -d 'List available backups' -complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and __fish_seen_subcommand_from help" -f -a "restore" -d 'Restore a branch from backup' -complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and __fish_seen_subcommand_from help" -f -a "stats" -d 'Show backup storage statistics' -complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and __fish_seen_subcommand_from help" -f -a "clean" -d 'Remove old backups, keeping the most recent ones' -complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and __fish_seen_subcommand_from help" -f -a "help" -d 'Print this message or the help of the given subcommand(s)' +complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and __fish_seen_subcommand_from help" -f -a list -d 'List available backups' +complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and __fish_seen_subcommand_from help" -f -a restore -d 'Restore a branch from backup' +complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and __fish_seen_subcommand_from help" -f -a stats -d 'Show backup storage statistics' +complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and __fish_seen_subcommand_from help" -f -a clean -d 'Remove old backups, keeping the most recent ones' +complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and __fish_seen_subcommand_from help" -f -a help -d 'Print this message or the help of the given subcommand(s)' complete -c deadbranch -n "__fish_deadbranch_using_subcommand stats" -s d -l days -d 'Treat branches older than N days as stale (default: from config or 30)' -r complete -c deadbranch -n "__fish_deadbranch_using_subcommand stats" -s h -l help -d 'Print help' complete -c deadbranch -n "__fish_deadbranch_using_subcommand stats" -s V -l version -d 'Print version' complete -c deadbranch -n "__fish_deadbranch_using_subcommand completions" -s h -l help -d 'Print help' complete -c deadbranch -n "__fish_deadbranch_using_subcommand completions" -s V -l version -d 'Print version' -complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and not __fish_seen_subcommand_from list clean config backup stats completions help" -f -a "list" -d 'List stale branches' -complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and not __fish_seen_subcommand_from list clean config backup stats completions help" -f -a "clean" -d 'Delete stale branches (merged only by default, use --force for unmerged)' -complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and not __fish_seen_subcommand_from list clean config backup stats completions help" -f -a "config" -d 'Manage configuration' -complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and not __fish_seen_subcommand_from list clean config backup stats completions help" -f -a "backup" -d 'Manage backups' -complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and not __fish_seen_subcommand_from list clean config backup stats completions help" -f -a "stats" -d 'Show repository branch statistics' -complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and not __fish_seen_subcommand_from list clean config backup stats completions help" -f -a "completions" -d 'Generate shell completion scripts' -complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and not __fish_seen_subcommand_from list clean config backup stats completions help" -f -a "help" -d 'Print this message or the help of the given subcommand(s)' -complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and __fish_seen_subcommand_from config" -f -a "set" -d 'Set a configuration value' -complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and __fish_seen_subcommand_from config" -f -a "show" -d 'Show current configuration' -complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and __fish_seen_subcommand_from config" -f -a "edit" -d 'Open config file in $EDITOR' -complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and __fish_seen_subcommand_from config" -f -a "reset" -d 'Reset configuration to defaults' -complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and __fish_seen_subcommand_from backup" -f -a "list" -d 'List available backups' -complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and __fish_seen_subcommand_from backup" -f -a "restore" -d 'Restore a branch from backup' -complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and __fish_seen_subcommand_from backup" -f -a "stats" -d 'Show backup storage statistics' -complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and __fish_seen_subcommand_from backup" -f -a "clean" -d 'Remove old backups, keeping the most recent ones' +complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and not __fish_seen_subcommand_from list clean config backup stats completions help" -f -a list -d 'List stale branches' +complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and not __fish_seen_subcommand_from list clean config backup stats completions help" -f -a clean -d 'Delete stale branches (merged only by default, use --force for unmerged)' +complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and not __fish_seen_subcommand_from list clean config backup stats completions help" -f -a config -d 'Manage configuration' +complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and not __fish_seen_subcommand_from list clean config backup stats completions help" -f -a backup -d 'Manage backups' +complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and not __fish_seen_subcommand_from list clean config backup stats completions help" -f -a stats -d 'Show repository branch statistics' +complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and not __fish_seen_subcommand_from list clean config backup stats completions help" -f -a completions -d 'Generate shell completion scripts' +complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and not __fish_seen_subcommand_from list clean config backup stats completions help" -f -a help -d 'Print this message or the help of the given subcommand(s)' +complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and __fish_seen_subcommand_from config" -f -a set -d 'Set a configuration value' +complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and __fish_seen_subcommand_from config" -f -a show -d 'Show current configuration' +complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and __fish_seen_subcommand_from config" -f -a edit -d 'Open config file in $EDITOR' +complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and __fish_seen_subcommand_from config" -f -a reset -d 'Reset configuration to defaults' +complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and __fish_seen_subcommand_from backup" -f -a list -d 'List available backups' +complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and __fish_seen_subcommand_from backup" -f -a restore -d 'Restore a branch from backup' +complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and __fish_seen_subcommand_from backup" -f -a stats -d 'Show backup storage statistics' +complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and __fish_seen_subcommand_from backup" -f -a clean -d 'Remove old backups, keeping the most recent ones' diff --git a/completions/ov.fish b/completions/ov.fish index a8ce5d0..9ecee38 100644 --- a/completions/ov.fish +++ b/completions/ov.fish @@ -3,7 +3,7 @@ function __ov_debug set -l file "$BASH_COMP_DEBUG_FILE" if test -n "$file" - echo "$argv" >> $file + echo "$argv" >>$file end end @@ -108,7 +108,6 @@ function __ov_requires_order_preservation return 1 end - # This function does two things: # - Obtain the completions and store them in the global __ov_comp_results # - Return false if file completion should be performed @@ -215,18 +214,18 @@ end # so we can properly delete any completions provided by another script. # Only do this if the program can be found, or else fish may print some errors; besides, # the existing completions will only be loaded if the program can be found. -if type -q "ov" +if type -q ov # The space after the program name is essential to trigger completion for the program # and not completion of the program name itself. # Also, we use '> /dev/null 2>&1' since '&>' is not supported in older versions of fish. - complete --do-complete "ov " > /dev/null 2>&1 + complete --do-complete "ov " >/dev/null 2>&1 end # Remove any pre-existing completions for the program since we will be handling all of them. complete -c ov -e # this will get called after the two calls below and clear the $__ov_perform_completion_once_result global -complete -c ov -n '__ov_clear_perform_completion_once_result' +complete -c ov -n __ov_clear_perform_completion_once_result # The call to __ov_prepare_completions will setup __ov_comp_results # which provides the program's completion choices. # If this doesn't require order preservation, we don't use the -k flag diff --git a/completions/tailscale.fish b/completions/tailscale.fish index 1c5ce0e..22180e3 100644 --- a/completions/tailscale.fish +++ b/completions/tailscale.fish @@ -21,7 +21,7 @@ end function __tailscale_debug set -l file "$BASH_COMP_DEBUG_FILE" if test -n "$file" - echo "$argv" >> $file + echo "$argv" >>$file end end @@ -125,7 +125,6 @@ function __tailscale_requires_order_preservation return 1 end - # This function does two things: # - Obtain the completions and store them in the global __tailscale_comp_results # - Return false if file completion should be performed @@ -242,7 +241,7 @@ end complete -c tailscale -e # this will get called after the two calls below and clear the $__tailscale_perform_completion_once_result global -complete -c tailscale -n '__tailscale_clear_perform_completion_once_result' +complete -c tailscale -n __tailscale_clear_perform_completion_once_result # The call to __tailscale_prepare_completions will setup __tailscale_comp_results # which provides the program's completion choices. # If this doesn't require order preservation, we don't use the -k flag diff --git a/conf.d/__fish_config_op_registry.fish b/conf.d/__fish_config_op_registry.fish index 4978e72..71e3800 100644 --- a/conf.d/__fish_config_op_registry.fish +++ b/conf.d/__fish_config_op_registry.fish @@ -80,69 +80,68 @@ set -g __fish_config_op_registry_keys \ "zoxide:" set -g __fish_config_op_registry_values \ - "autoexec/venv" \ - "logging/terminal-capture" \ - "autoexec/sync" \ - "logging/multiplexer-capture" \ - "integrations/terminal-abbrs" \ - "overrides/key-bindings" \ - "aliases/dev-tools" \ - "autoexec/sync" \ - "overrides/key-bindings" \ - "aliases/shell-tools" \ - "overrides/key-bindings" \ - "aliases/filesystem" \ - "aliases/dev-tools" \ - "aliases/filesystem" \ - "overrides/key-bindings" \ - "overrides/environment" \ - "overrides/environment" \ - "overrides/key-bindings" \ - "greeting/greeting-message" \ - "overrides/environment" \ - "overrides/environment" \ - "overrides/privacy" \ - "overrides/key-bindings" \ - "integrations/notifications" \ - "aliases/filesystem" \ - "aliases/dev-tools" \ - "autoexec/plugin-management" \ - "greeting/first-run" \ - "overrides/prompt" \ - "aliases/shell-tools" \ - "integrations/history-logs" \ - "overrides/key-bindings" \ - "logging/terminal-capture" \ - "logging/terminal-capture" \ - "aliases/shell-tools" \ - "integrations/history-logs" \ - "aliases/filesystem" \ - "aliases/filesystem" \ - "aliases/filesystem" \ - "autoexec/pkg-wrappers" \ - "logging/pkg-logs" \ - "aliases/network" \ - "overrides/key-bindings" \ - "aliases/search" \ - "aliases/filesystem" \ - "overrides/key-bindings" \ - "logging/terminal-capture" \ - "integrations/window-mgmt" \ - "integrations/window-mgmt" \ - "aliases/network" \ - "overrides/prompt" \ - "integrations/window-mgmt" \ - "overrides/prompt" \ - "logging/multiplexer-capture" \ - "aliases/monitor" \ - "aliases/filesystem" \ - "overrides/key-bindings" \ - "overrides/environment" \ - "integrations/pkg-upgrade" \ - "autoexec/telemetry" \ - "integrations/notifications" \ - "autoexec/pkg-wrappers" \ - "logging/pkg-logs" \ - "aliases/network" \ - "aliases/filesystem" - + autoexec/venv \ + logging/terminal-capture \ + autoexec/sync \ + logging/multiplexer-capture \ + integrations/terminal-abbrs \ + overrides/key-bindings \ + aliases/dev-tools \ + autoexec/sync \ + overrides/key-bindings \ + aliases/shell-tools \ + overrides/key-bindings \ + aliases/filesystem \ + aliases/dev-tools \ + aliases/filesystem \ + overrides/key-bindings \ + overrides/environment \ + overrides/environment \ + overrides/key-bindings \ + greeting/greeting-message \ + overrides/environment \ + overrides/environment \ + overrides/privacy \ + overrides/key-bindings \ + integrations/notifications \ + aliases/filesystem \ + aliases/dev-tools \ + autoexec/plugin-management \ + greeting/first-run \ + overrides/prompt \ + aliases/shell-tools \ + integrations/history-logs \ + overrides/key-bindings \ + logging/terminal-capture \ + logging/terminal-capture \ + aliases/shell-tools \ + integrations/history-logs \ + aliases/filesystem \ + aliases/filesystem \ + aliases/filesystem \ + autoexec/pkg-wrappers \ + logging/pkg-logs \ + aliases/network \ + overrides/key-bindings \ + aliases/search \ + aliases/filesystem \ + overrides/key-bindings \ + logging/terminal-capture \ + integrations/window-mgmt \ + integrations/window-mgmt \ + aliases/network \ + overrides/prompt \ + integrations/window-mgmt \ + overrides/prompt \ + logging/multiplexer-capture \ + aliases/monitor \ + aliases/filesystem \ + overrides/key-bindings \ + overrides/environment \ + integrations/pkg-upgrade \ + autoexec/telemetry \ + integrations/notifications \ + autoexec/pkg-wrappers \ + logging/pkg-logs \ + aliases/network \ + aliases/filesystem diff --git a/conf.d/bash_expands.fish b/conf.d/bash_expands.fish index 89c5bf7..53a7895 100644 --- a/conf.d/bash_expands.fish +++ b/conf.d/bash_expands.fish @@ -17,8 +17,10 @@ function expand_bang_all --description 'Execute expand_bang_all' __fish_config_op_enabled (status basename); or return 1 set -l token $argv[1] - if test -z "$token"; set token (commandline -t); end - + if test -z "$token" + set token (commandline -t) + end + set -l tokens (string split -n " " -- $history[1]) if test (count $tokens) -gt 1 echo -- (string join " " -- $tokens[2..-1]) @@ -46,12 +48,14 @@ function expand_bang_minus_n --description 'Execute expand_bang_minus_n' __fish_config_op_enabled (status basename); or return 1 set -l token $argv[1] - if test -z "$token"; set token (commandline -t); end - + if test -z "$token" + set token (commandline -t) + end + # Extract the number from the regex match if string match -qr '!-(\d+)' -- "$token" set -l n (string match -r '!-(\d+)' -- "$token")[2] - + if test (count $history) -ge $n echo -- $history[$n] else @@ -71,20 +75,20 @@ function expand_bang_search --description 'Execute expand_bang_search' if test -z "$token" set token (commandline -t) end - + # Extract query: looks for text after !? and before an optional ? set -l query (string match -r '!\?([^?]+)' -- $token)[2] - + if test -n "$query" # Search history for a match anywhere in the command set -l match (builtin history search --contains --max=1 -- $query) - + if test -n "$match" echo -- $match return end end - + echo -- $token end @@ -98,20 +102,20 @@ function expand_bang_string --description 'Execute expand_bang_string' if test -z "$token" set token (commandline -t) end - + # Remove the '!' to get the search query set -l query (string sub -s 2 -- $token) - + if test -n "$query" # Search history for a prefix match set -l match (builtin history search --prefix --max=1 -- $query) - + if test -n "$match" echo -- $match return end end - + # If no match or empty query, return the token so it doesn't vanish echo -- $token end @@ -128,12 +132,12 @@ function expand_typo_sub --description 'Execute expand_typo_sub' if test -z "$current_token" set current_token (commandline -t) end - + if string match -qr '\^([^^]+)\^([^^]*)' -- "$current_token" set -l captured (string match -r '\^([^^]+)\^([^^]*)' -- "$current_token") set -l old $captured[2] set -l new $captured[3] - + if test -n "$old" # Using -- to ensure strings starting with '-' aren't treated as flags echo -- (string replace -a -- "$old" "$new" "$last_cmd") diff --git a/conf.d/paru-wrapper.fish b/conf.d/paru-wrapper.fish index e5e629e..400e3e0 100644 --- a/conf.d/paru-wrapper.fish +++ b/conf.d/paru-wrapper.fish @@ -60,7 +60,7 @@ printf '%s\n' \ "cmd_str=\"$_paru_real\"" \ 'for arg in "$@"; do' \ ' cmd_str+=" $(printf '"'"'%q'"'"' "$arg")"' \ - 'done' \ + done \ 'script -q -e -c "$cmd_str" "$log_file"' \ 'exit_code=$?' \ '' \ @@ -70,19 +70,18 @@ printf '%s\n' \ 'cleaner="${XDG_CONFIG_HOME:-$HOME/.config}/fish/scripts/clean_progress_log.py"' \ 'if command -v python3 >/dev/null 2>&1 && [[ -f "$cleaner" ]]; then' \ ' python3 "$cleaner" < "$log_file" > "${log_file}.tmp" 2>/dev/null && mv "${log_file}.tmp" "$log_file" || rm -f "${log_file}.tmp"' \ - 'else' \ + else \ ' sed -i "/^Script \(started\|done\) on /d" "$log_file" 2>/dev/null || true' \ - 'fi' \ + fi \ '' \ 'max_files="${SCROLLBACK_HISTORY_MAX_FILES:-100}"' \ 'mapfile -t logs < <(ls -1t "$log_dir"/paru_*.log 2>/dev/null)' \ 'excess=$(( ${#logs[@]} - max_files ))' \ 'for (( i = ${#logs[@]} - 1; i >= ${#logs[@]} - excess && i >= 0; i-- )); do' \ ' rm -f "${logs[$i]}"' \ - 'done' \ + done \ '' \ - 'exit $exit_code' \ - > $_paru_wrapper + 'exit $exit_code' >$_paru_wrapper chmod +x $_paru_wrapper diff --git a/conf.d/wakatime.fish b/conf.d/wakatime.fish index e4568bf..909f5ef 100644 --- a/conf.d/wakatime.fish +++ b/conf.d/wakatime.fish @@ -20,37 +20,38 @@ __fish_config_op_enabled (status basename) wakatime-autoexec; or exit __fish_config_op_enabled (status basename) wakatime-hook; or exit function __register_wakatime_fish_before_exec -e fish_postexec - if set -q FISH_WAKATIME_DISABLED - return 0 - end - - set -l exec_command_str + if set -q FISH_WAKATIME_DISABLED + return 0 + end - set exec_command_str (string split -f1 ' ' "$argv") + set -l exec_command_str - if test "$exec_command_str" = 'exit' - return 0 - end + set exec_command_str (string split -f1 ' ' "$argv") - set -l PLUGIN_NAME "ik11235/wakatime.fish" - set -l PLUGIN_VERSION "0.0.6" + if test "$exec_command_str" = exit + return 0 + end - set -l project - set -l wakatime_path + set -l PLUGIN_NAME "ik11235/wakatime.fish" + set -l PLUGIN_VERSION "0.0.6" - if type -p wakatime 2>&1 > /dev/null - set wakatime_path (type -p wakatime) - else if type -p ~/.wakatime/wakatime-cli 2>&1 > /dev/null - set wakatime_path (type -p ~/.wakatime/wakatime-cli) - else - return 1 - end + set -l project + set -l wakatime_path - if git rev-parse --is-inside-work-tree &> /dev/null - set project (basename (git rev-parse --show-toplevel)) - else - set project "Terminal" - end + if type -p wakatime 2>&1 >/dev/null + set wakatime_path (type -p wakatime) + else if type -p ~/.wakatime/wakatime-cli 2>&1 >/dev/null + set wakatime_path (type -p ~/.wakatime/wakatime-cli) + else + return 1 + end - $wakatime_path --write --plugin "$PLUGIN_NAME/$PLUGIN_VERSION" --entity-type app --project "$project" --entity "$exec_command_str" &> /dev/null&; disown + if git rev-parse --is-inside-work-tree &>/dev/null + set project (basename (git rev-parse --show-toplevel)) + else + set project Terminal + end + + $wakatime_path --write --plugin "$PLUGIN_NAME/$PLUGIN_VERSION" --entity-type app --project "$project" --entity "$exec_command_str" &>/dev/null & + disown end diff --git a/conf.d/yay-wrapper.fish b/conf.d/yay-wrapper.fish index 6f43360..14d9ad6 100644 --- a/conf.d/yay-wrapper.fish +++ b/conf.d/yay-wrapper.fish @@ -60,7 +60,7 @@ printf '%s\n' \ "cmd_str=\"$_yay_real\"" \ 'for arg in "$@"; do' \ ' cmd_str+=" $(printf '"'"'%q'"'"' "$arg")"' \ - 'done' \ + done \ 'script -q -e -c "$cmd_str" "$log_file"' \ 'exit_code=$?' \ '' \ @@ -70,19 +70,18 @@ printf '%s\n' \ 'cleaner="${XDG_CONFIG_HOME:-$HOME/.config}/fish/scripts/clean_progress_log.py"' \ 'if command -v python3 >/dev/null 2>&1 && [[ -f "$cleaner" ]]; then' \ ' python3 "$cleaner" < "$log_file" > "${log_file}.tmp" 2>/dev/null && mv "${log_file}.tmp" "$log_file" || rm -f "${log_file}.tmp"' \ - 'else' \ + else \ ' sed -i "/^Script \(started\|done\) on /d" "$log_file" 2>/dev/null || true' \ - 'fi' \ + fi \ '' \ 'max_files="${SCROLLBACK_HISTORY_MAX_FILES:-100}"' \ 'mapfile -t logs < <(ls -1t "$log_dir"/yay_*.log 2>/dev/null)' \ 'excess=$(( ${#logs[@]} - max_files ))' \ 'for (( i = ${#logs[@]} - 1; i >= ${#logs[@]} - excess && i >= 0; i-- )); do' \ ' rm -f "${logs[$i]}"' \ - 'done' \ + done \ '' \ - 'exit $exit_code' \ - > $_yay_wrapper + 'exit $exit_code' >$_yay_wrapper chmod +x $_yay_wrapper diff --git a/config.fish b/config.fish index aecfba0..0fb6080 100644 --- a/config.fish +++ b/config.fish @@ -253,7 +253,7 @@ if status is-interactive # # Resolve user-dots path. Customize via: set -U __fish_user_dots_path /your/path set -q __fish_user_dots_path - or set -l __fish_user_dots_path "$XDG_CONFIG_HOME/.user-dots/fish" + or set -l __fish_user_dots_path "$XDG_CONFIG_HOME/.user-dots/fish" # ────────────────────── user-dots convenience symlink ─────────────────── # Keep $__fish_config_dir/user-dots tracking the resolved path so it can be # browsed from the fish config dir. Git-ignored. Controlled by the diff --git a/functions/__auto_source_fallback_venv.fish b/functions/__auto_source_fallback_venv.fish index 6ca0e83..b1e595e 100644 --- a/functions/__auto_source_fallback_venv.fish +++ b/functions/__auto_source_fallback_venv.fish @@ -25,19 +25,19 @@ function __auto_source_fallback_venv --on-variable PWD if set -q DIRENV_DIR; or test -e ".envrc" return end - + # 2. If we are already in a venv, check if we've left its tree - if set -q VIRTUAL_ENV - # Check if the current PWD is still within the directory that owns the venv - # (Assuming the venv is at the root of the project) - set -l venv_root (string replace -r '/.venv$' '' $VIRTUAL_ENV) - if not string match -q "$venv_root*" "$PWD" - type -q deactivate; and deactivate - end - return + if set -q VIRTUAL_ENV + # Check if the current PWD is still within the directory that owns the venv + # (Assuming the venv is at the root of the project) + set -l venv_root (string replace -r '/.venv$' '' $VIRTUAL_ENV) + if not string match -q "$venv_root*" "$PWD" + type -q deactivate; and deactivate end - - # 3. Only source the venv if we aren't already in one + return + end + + # 3. Only source the venv if we aren't already in one if test -e ".venv/bin/activate.fish" source .venv/bin/activate.fish end diff --git a/functions/__config_settings_apply.fish b/functions/__config_settings_apply.fish index 56bcf24..5723897 100644 --- a/functions/__config_settings_apply.fish +++ b/functions/__config_settings_apply.fish @@ -27,8 +27,8 @@ # __config_settings_apply __fish_config_op_greeting session DEFAULT function __config_settings_apply set -l varname $argv[1] - set -l scope $argv[2] - set -l value $argv[3] + set -l scope $argv[2] + set -l value $argv[3] # stderr is suppressed because setting a value in one scope while the # other scope already holds the same variable makes interactive fish diff --git a/functions/__config_settings_set_value.fish b/functions/__config_settings_set_value.fish index 3de0f28..433f684 100644 --- a/functions/__config_settings_set_value.fish +++ b/functions/__config_settings_set_value.fish @@ -27,8 +27,8 @@ # __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] + 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 diff --git a/functions/__config_settings_subcats.fish b/functions/__config_settings_subcats.fish index eeea1f8..dbc34ed 100644 --- a/functions/__config_settings_subcats.fish +++ b/functions/__config_settings_subcats.fish @@ -28,9 +28,9 @@ function __config_settings_subcats --description 'List the sub-categories for on case __fish_config_op_aliases printf '%s\t%s\t%s\n' \ filesystem Filesystem "ls, cat, cd, du, mkdir, rm, mv, zoxide" \ - search Search "rg" \ + search Search rg \ network Network "ping, ssh, yt-dlp" \ - monitor Monitor "top" \ + monitor Monitor top \ shell-tools Shell-tools "bash, less, help" \ dev-tools Dev-tools "claude, edit, agy" case __fish_config_op_autoexec @@ -52,7 +52,7 @@ function __config_settings_subcats --description 'List the sub-categories for on window-mgmt Window-mgmt "spwin, tab, split" \ notifications Notifications "done, WakaTime hook" \ history-logs History-logs "hist, logs" \ - pkg-upgrade Pkg-upgrade "upgrade" + pkg-upgrade Pkg-upgrade upgrade case __fish_config_op_logging printf '%s\t%s\t%s\n' \ terminal-capture Term-capture "Kitty watcher, smart_exit scrollback" \ diff --git a/functions/__fish_user_dots_link.fish b/functions/__fish_user_dots_link.fish index 5bec228..1a5d46b 100644 --- a/functions/__fish_user_dots_link.fish +++ b/functions/__fish_user_dots_link.fish @@ -34,7 +34,7 @@ function __fish_user_dots_link --description 'Manage the user-dots convenience symlink' set -l link "$__fish_config_dir/user-dots" set -q __fish_user_dots_path - or set -l __fish_user_dots_path "$XDG_CONFIG_HOME/.user-dots/fish" + or set -l __fish_user_dots_path "$XDG_CONFIG_HOME/.user-dots/fish" # Explicit opt-out: remove our symlink (never a real file/dir) and stop. __fish_variable_check __fish_user_dots_symlink @@ -49,7 +49,7 @@ function __fish_user_dots_link --description 'Manage the user-dots convenience s test -d "$__fish_user_dots_path"; or return 0 if test -L "$link" test (readlink "$link") != "$__fish_user_dots_path" - and ln -sfn "$__fish_user_dots_path" "$link" + and ln -sfn "$__fish_user_dots_path" "$link" else if not test -e "$link" ln -s "$__fish_user_dots_path" "$link" end diff --git a/functions/__insert_previous_path_head.fish b/functions/__insert_previous_path_head.fish index c87fd5f..f607fd9 100644 --- a/functions/__insert_previous_path_head.fish +++ b/functions/__insert_previous_path_head.fish @@ -14,7 +14,7 @@ function __insert_previous_path_head # Get the last command tokens set -l tokens (string split -n " " -- $history[1]) - + # If there are tokens, take the last one and strip the 'tail' if set -q tokens[-1] set -l path_head (dirname -- $tokens[-1]) diff --git a/functions/__interactive_history_sub.fish b/functions/__interactive_history_sub.fish index 77a19fc..0bf1558 100644 --- a/functions/__interactive_history_sub.fish +++ b/functions/__interactive_history_sub.fish @@ -14,19 +14,19 @@ function __interactive_history_sub set -l current_line (commandline -b) set -l last_cmd $history[1] - + if string match -qr '(.+)/(.+)' -- "$current_line" set -l parts (string split '/' -- "$current_line") set -l old $parts[1] set -l new $parts[2] set -l history_index 1 - + if test (count $parts) -ge 3; and string match -qr '^[1-9][0-9]*$' -- "$parts[3]" set history_index $parts[3] end - + set -l target_cmd $history[$history_index] - + if test -n "$target_cmd" set -l expanded (string replace -a -- "$old" "$new" "$target_cmd") commandline -r "$expanded" diff --git a/functions/__substitute_typo.fish b/functions/__substitute_typo.fish index a8ee28d..755b3bd 100644 --- a/functions/__substitute_typo.fish +++ b/functions/__substitute_typo.fish @@ -16,21 +16,21 @@ function __substitute_typo set -l cursor_pos (commandline -C) set -l cmd (commandline) - + # Check if the current line matches the ^old^new pattern if string match -qr '\^([^^]+)\^([^^]*)' -- "$cmd" set -l last_cmd $history[1] set -l captured (string match -r '\^([^^]+)\^([^^]*)' -- "$cmd") set -l old $captured[2] set -l new $captured[3] - + if test -n "$old" set -l expanded (string replace -a -- "$old" "$new" "$last_cmd") commandline -r "$expanded" # No need to move cursor, it's a whole new line - end - else - # If it's just a normal caret (not part of a pattern), just insert it + end + else + # If it's just a normal caret (not part of a pattern), just insert it commandline -i '^' end end diff --git a/functions/_agents_init_ensure_gitignore.fish b/functions/_agents_init_ensure_gitignore.fish index f8b9ee3..724af70 100644 --- a/functions/_agents_init_ensure_gitignore.fish +++ b/functions/_agents_init_ensure_gitignore.fish @@ -38,8 +38,8 @@ function _agents_init_ensure_gitignore return 1 end - set -l root $argv[1] - set -l label $argv[2] + set -l root $argv[1] + set -l label $argv[2] set -l patterns $argv[3..] set -l gitignore "$root/.gitignore" diff --git a/functions/_fish_deps_install.fish b/functions/_fish_deps_install.fish index 8ff3e54..487187b 100644 --- a/functions/_fish_deps_install.fish +++ b/functions/_fish_deps_install.fish @@ -75,14 +75,14 @@ function _fish_deps_install set -l _major (fish --version 2>&1 | string match -r 'version (\d+)')[2] if test -n "$_major"; and test "$_major" -lt 4 set needs_install 1 - set upgrade_label "Upgrade" + set upgrade_label Upgrade end end if test $needs_install -eq 1 - set -l cargo_crate $_fdc_cargo[$i] - set -l pm_pkg $_fdc_pm[$i] - set -l special $_fdc_special[$i] + set -l cargo_crate $_fdc_cargo[$i] + set -l pm_pkg $_fdc_pm[$i] + set -l special $_fdc_special[$i] # Build list of available install methods set -l methods @@ -193,7 +193,9 @@ function _fish_deps_install echo " Available methods:" set -l m 1 for lbl in $method_labels - set_color brblack; echo -n " $m) "; set_color normal + set_color brblack + echo -n " $m) " + set_color normal echo $lbl set m (math $m + 1) end @@ -202,7 +204,9 @@ function _fish_deps_install set chosen_method $methods[$_choice] end else - set_color brblack; echo " "(string lower $upgrade_label)"ing via $method_labels[1]"; set_color normal + set_color brblack + echo " "(string lower $upgrade_label)"ing via $method_labels[1]" + set_color normal end # Execute chosen method @@ -329,14 +333,18 @@ function _fish_deps_install if test $status -eq 0 set installed_any 1 - set_color green; echo " $bin "(string lower $upgrade_label)"ed."; set_color normal + set_color green + echo " $bin "(string lower $upgrade_label)"ed." + set_color normal if test "$bin" = fish set_color yellow echo " Fish upgraded — restart your shell to use the new version." set_color normal end else - set_color red; echo " $bin "(string lower $upgrade_label)" failed."; set_color normal + set_color red + echo " $bin "(string lower $upgrade_label)" failed." + set_color normal end end set i (math $i + 1) diff --git a/functions/_fish_deps_status.fish b/functions/_fish_deps_status.fish index 6bbbc90..ee8af58 100644 --- a/functions/_fish_deps_status.fish +++ b/functions/_fish_deps_status.fish @@ -23,35 +23,57 @@ function _fish_deps_status set -l _major (fish --version 2>&1 | string match -r 'version (\d+)')[2] if test -n "$_major"; and test "$_major" -lt 4 set -l _ver (fish --version 2>&1 | string replace 'fish, ' '') - set_color yellow; echo -n " ⚠ "; set_color normal + set_color yellow + echo -n " ⚠ " + set_color normal echo -n "$bin " - set_color brblack; echo "($_ver — upgrade to 4.0+ required)"; set_color normal + set_color brblack + echo "($_ver — upgrade to 4.0+ required)" + set_color normal return end end - set_color green; echo -n " ✓ "; set_color normal + set_color green + echo -n " ✓ " + set_color normal echo -n "$bin " - set_color brblack; echo "(Found at "(__fish_real_command $bin)")"; set_color normal + set_color brblack + echo "(Found at "(__fish_real_command $bin)")" + set_color normal else if test "$tier" = req - set_color red; echo -n " ✗ "; set_color normal + set_color red + echo -n " ✗ " + set_color normal echo -n "$bin " - set_color brblack; echo "(Not installed)"; set_color normal + set_color brblack + echo "(Not installed)" + set_color normal else if test "$tier" = rec - set_color yellow; echo -n " ⚠ "; set_color normal + set_color yellow + echo -n " ⚠ " + set_color normal echo -n "$bin " - set_color brblack; echo "(Not installed)"; set_color normal + set_color brblack + echo "(Not installed)" + set_color normal else # opt / term / int: absence is expected and not alarming - set_color brblack; echo -n " – "; set_color normal + set_color brblack + echo -n " – " + set_color normal echo -n "$bin " - set_color brblack; echo "(Not installed)"; set_color normal + set_color brblack + echo "(Not installed)" + set_color normal end end for tier_label in "Required Dependencies:req" "Recommended Dependencies:rec" "Optional Dependencies:opt" "Terminal Emulators:term" "Integrations:int" set -l label (string split : $tier_label)[1] - set -l tier (string split : $tier_label)[2] - set_color cyan; echo $label; set_color normal + set -l tier (string split : $tier_label)[2] + set_color cyan + echo $label + set_color normal set -l i 1 for bin in $_fdc_bins if test "$_fdc_tiers[$i]" = $tier diff --git a/functions/_fish_deps_update.fish b/functions/_fish_deps_update.fish index 8082506..f427c30 100644 --- a/functions/_fish_deps_update.fish +++ b/functions/_fish_deps_update.fish @@ -33,9 +33,9 @@ function _fish_deps_update continue end - set -l cargo_crate $_fdc_cargo[$i] - set -l pm_pkg $_fdc_pm[$i] - set -l special $_fdc_special[$i] + set -l cargo_crate $_fdc_cargo[$i] + set -l pm_pkg $_fdc_pm[$i] + set -l special $_fdc_special[$i] # yay: update via paru if available, else system PM if test "$special" = yay-build @@ -101,10 +101,14 @@ function _fish_deps_update echo "Updating $bin..." set -l _arch (uname -m) switch $_arch - case x86_64; set _arch amd64 - case aarch64 arm64; set _arch arm64 - case armv7l; set _arch arm - case '*'; set _arch amd64 + case x86_64 + set _arch amd64 + case aarch64 arm64 + set _arch arm64 + case armv7l + set _arch arm + case '*' + set _arch amd64 end set -l _zip "wakatime-cli-linux-$_arch.zip" set -l _bin_src "wakatime-cli-linux-$_arch" diff --git a/functions/_fish_mkdir_p.fish b/functions/_fish_mkdir_p.fish index 835ab5c..29f25b2 100644 --- a/functions/_fish_mkdir_p.fish +++ b/functions/_fish_mkdir_p.fish @@ -46,7 +46,7 @@ function _fish_mkdir_p --description 'mkdir -p with configurable verbose output' while not test -d $cursor set -p to_create $cursor set -l up (dirname $cursor) - test "$up" = "$cursor"; and break # filesystem root guard + test "$up" = "$cursor"; and break # filesystem root guard set cursor $up end diff --git a/functions/_puffer_fish_expand_bang.fish b/functions/_puffer_fish_expand_bang.fish index ea3f9ea..772e0c9 100644 --- a/functions/_puffer_fish_expand_bang.fish +++ b/functions/_puffer_fish_expand_bang.fish @@ -7,4 +7,3 @@ function _puffer_fish_expand_bang commandline --insert '!' end end - diff --git a/functions/_puffer_fish_expand_star.fish b/functions/_puffer_fish_expand_star.fish index b74c382..733c853 100644 --- a/functions/_puffer_fish_expand_star.fish +++ b/functions/_puffer_fish_expand_star.fish @@ -4,7 +4,7 @@ function _puffer_fish_expand_star else if string match --quiet -- '!' "$(commandline --current-token)" set -l prev_cmd $history[1] set -l prev_args (string split ' ' $prev_cmd) - set -e prev_args[1] # remove command name + set -e prev_args[1] # remove command name set -l arg_str (string join ' ' $prev_args) # replace !* with all arguments commandline --current-token '' diff --git a/functions/_smart_execute.fish b/functions/_smart_execute.fish index 388ad69..7c87fa9 100644 --- a/functions/_smart_execute.fish +++ b/functions/_smart_execute.fish @@ -28,9 +28,9 @@ function _smart_execute --description 'Execute different functions based on the # If it ends in =, run qalc; fall back to normal execute if qalc is absent _qalc_eval; or commandline -f execute -# case 'g *' -# # EXAMPLE FUTURE EXTENSION -# _some_git_helper + # case 'g *' + # # EXAMPLE FUTURE EXTENSION + # _some_git_helper case '*' # Default: execute the command line as-is diff --git a/functions/agents-vault.fish b/functions/agents-vault.fish index fdc2aaf..499fb5a 100644 --- a/functions/agents-vault.fish +++ b/functions/agents-vault.fish @@ -893,8 +893,7 @@ function agents-vault --description 'track curated agent memory in a host-scoped command cp -rn "$stash/." "$entry/" 2>/dev/null rm -rf "$stash" end - printf 'renamed: %s → %s (%s)\n' "$prev_slug" "$slug" (date -I) \ - >>"$entry/origin" + printf 'renamed: %s → %s (%s)\n' "$prev_slug" "$slug" (date -I) >>"$entry/origin" # Only a link is dropped here, and only so the relink below has # somewhere to put the new one. Usually $live is exactly that: a # symlink at the old entry, now dangling. But the migration is @@ -1078,8 +1077,7 @@ function agents-vault --description 'track curated agent memory in a host-scoped end set -l reached 1 if git -C "$vault" rev-parse --abbrev-ref --symbolic-full-name '@{u}' >/dev/null 2>&1 - if not $gitnet git -C "$vault" pull --rebase --autostash -q \ - >/dev/null 2>/dev/null + if not $gitnet git -C "$vault" pull --rebase --autostash -q >/dev/null 2>/dev/null # Two unrelated failures land here and reporting them as # one sends the user hunting for a conflict that never # existed. A rebase that genuinely started and stopped diff --git a/functions/agy.fish b/functions/agy.fish index 2cd47c1..c68cae7 100644 --- a/functions/agy.fish +++ b/functions/agy.fish @@ -52,10 +52,10 @@ function agy --wraps=agy --description 'agy wrapper: auto-initializes AGENTS/ su agents-vault --quiet for i in (seq (count $argv)) - if test "$argv[$i]" = "-r" - set argv[$i] "-c" - else if test "$argv[$i]" = "--resume" - set argv[$i] "--continue" + if test "$argv[$i]" = -r + set argv[$i] -c + else if test "$argv[$i]" = --resume + set argv[$i] --continue else if string match -q -- "--resume=*" "$argv[$i]" set argv[$i] (string replace -- "--resume=" "--continue=" "$argv[$i]") end diff --git a/functions/bd-pull.fish b/functions/bd-pull.fish index 0def40d..30c0819 100644 --- a/functions/bd-pull.fish +++ b/functions/bd-pull.fish @@ -25,11 +25,20 @@ function bd-pull --description 'Pull new Gitea issues into local Beads and link them' __fish_help_header (status current-function) $argv; and return 0 - if not set -q argv[1]; echo "Need repo owner/name"; return 1; end - if not set -q GITEA_TOKEN; echo "\$GITEA_TOKEN not set"; return 1; end + if not set -q argv[1] + echo "Need repo owner/name" + return 1 + end + if not set -q GITEA_TOKEN + echo "\$GITEA_TOKEN not set" + return 1 + end set -l REPO $argv[1] - if not set -q GITEA_URL; echo "\$GITEA_URL not set"; return 1; end + if not set -q GITEA_URL + echo "\$GITEA_URL not set" + return 1 + end set -l IMPORT_COUNT 0 echo (set_color blue)"📡 Checking Gitea: $REPO..."(set_color normal) @@ -46,12 +55,12 @@ function bd-pull --description 'Pull new Gitea issues into local Beads and link # If it doesn't have [ID] brackets, it's a "Web-Original" issue if not string match -qr "^\[.*\]" "$title" echo (set_color yellow)"➕ Linking Web Issue #$number: $title"(set_color normal) - + # A. Create local Bead and capture the new ID # This captures the output of bd create to find the ID it generated set -l bd_output (bd create --title "$title") set -l bid (echo $bd_output | string match -r "bd-[a-z0-9]+" | head -n 1) - + if test -z "$bid" # Fallback: find the latest ID in the jsonl if regex fails set bid (tail -n 1 .beads/issues.jsonl | jq -r '.id') @@ -61,9 +70,9 @@ function bd-pull --description 'Pull new Gitea issues into local Beads and link # This prevents the Gitea Action from creating a duplicate set -l new_title "[$bid] $title" curl -s -X PATCH -H "Authorization: token $GITEA_TOKEN" \ - -H "Content-Type: application/json" \ - -d "{\"title\":\"$new_title\"}" \ - "$GITEA_URL/api/v1/repos/$REPO/issues/$number" > /dev/null + -H "Content-Type: application/json" \ + -d "{\"title\":\"$new_title\"}" \ + "$GITEA_URL/api/v1/repos/$REPO/issues/$number" >/dev/null set IMPORT_COUNT (math $IMPORT_COUNT + 1) end diff --git a/functions/branch.fish b/functions/branch.fish index 6143685..ed0336c 100644 --- a/functions/branch.fish +++ b/functions/branch.fish @@ -27,12 +27,12 @@ function branch --description 'Switch to or create a git branch' echo "Not a git repo." return 1 end - + # Check if the branch already exists locally if git show-ref --verify --quiet refs/heads/$argv[1] git checkout $argv else # If it doesn't exist, create it - git checkout -b $argv - end + git checkout -b $argv + end end diff --git a/functions/cheat.fish b/functions/cheat.fish index e13948c..0d9e20d 100644 --- a/functions/cheat.fish +++ b/functions/cheat.fish @@ -26,5 +26,5 @@ function cheat --wraps='cheat' --description 'alias cheat=cheat -c' else man $argv end - + end diff --git a/functions/cleanup.fish b/functions/cleanup.fish index a2d1297..33e1d8e 100644 --- a/functions/cleanup.fish +++ b/functions/cleanup.fish @@ -23,9 +23,9 @@ function cleanup --description 'Log orphans to ~/.removed_orphans and remove the set -l orphans (pacman -Qtdq) if test -n "$orphans" echo "📝 Logging orphans to ~/.removed_orphans..." - echo "--- Removed on $(date) ---" >> ~/.removed_orphans - pacman -Qi $orphans | grep -E '^(Name|Version)' >> ~/.removed_orphans - + echo "--- Removed on $(date) ---" >>~/.removed_orphans + pacman -Qi $orphans | grep -E '^(Name|Version)' >>~/.removed_orphans + echo "🧹 Removing orphans..." sudo pacman -Rns $orphans else diff --git a/functions/config-update.fish b/functions/config-update.fish index c756166..4d0ea5f 100644 --- a/functions/config-update.fish +++ b/functions/config-update.fish @@ -80,7 +80,7 @@ function config-update --description 'Pull latest fish config from upstream' end # ── compare local HEAD to upstream ─────────────────────────── - set -l local_sha (git -C "$CONFIG_DIR" rev-parse HEAD 2>/dev/null) + set -l local_sha (git -C "$CONFIG_DIR" rev-parse HEAD 2>/dev/null) set -l remote_sha (git -C "$CONFIG_DIR" rev-parse _config_update/main 2>/dev/null) if test "$local_sha" = "$remote_sha" diff --git a/functions/fish_prompt.fish b/functions/fish_prompt.fish index c7a43b1..63040fe 100644 --- a/functions/fish_prompt.fish +++ b/functions/fish_prompt.fish @@ -25,15 +25,15 @@ function fish_prompt set -l last_status $status # Catppuccin Mocha hex palette - set -l c_green '#a6e3a1' - set -l c_red '#f38ba8' + set -l c_green '#a6e3a1' + set -l c_red '#f38ba8' set -l c_yellow '#f9e2af' - set -l c_text '#cdd6f4' - set -l c_blue '#89b4fa' - set -l c_teal '#94e2d5' - set -l c_pink '#f5c2e7' - set -l c_dim '#6c7086' - set -l c_mauve '#cba6f7' + set -l c_text '#cdd6f4' + set -l c_blue '#89b4fa' + set -l c_teal '#94e2d5' + set -l c_pink '#f5c2e7' + set -l c_dim '#6c7086' + set -l c_mauve '#cba6f7' # Line/connector color tracks last exit status; brackets stay bold green set -l retc $c_green @@ -129,7 +129,7 @@ function fish_prompt set_color normal end - echo # newline + echo # newline # Background jobs (one per line, dim) for job in (jobs -c) diff --git a/functions/fisher.fish b/functions/fisher.fish index 4f5be4f..7986bb9 100644 --- a/functions/fisher.fish +++ b/functions/fisher.fish @@ -8,8 +8,8 @@ function fisher --argument-names cmd --description "A plugin manager for Fish" echo "fisher, version $fisher_version" case "" -h --help echo "Usage: fisher install Install plugins" - echo " fisher remove Remove installed plugins" - echo " fisher uninstall Remove installed plugins (alias)" + echo " fisher remove Remove installed plugins" + echo " fisher uninstall Remove installed plugins (alias)" echo " fisher update Update installed plugins" echo " fisher update Update all installed plugins" echo " fisher list [] List installed plugins matching regex" @@ -41,7 +41,7 @@ function fisher --argument-names cmd --description "A plugin manager for Fish" echo "fisher: \"$fish_plugins\" file not found: \"$cmd\"" >&2 && return 1 end set arg_plugins $file_plugins - else if test "$cmd" = install && ! set --query old_plugins[1] + else if test "$cmd" = install && ! set --query old_plugins[1] set --append arg_plugins $file_plugins end diff --git a/functions/gip6.fish b/functions/gip6.fish index 52ca05c..d61b778 100644 --- a/functions/gip6.fish +++ b/functions/gip6.fish @@ -25,7 +25,7 @@ function gip6 --description 'Get public IPv6 address' # Use -6 to force IPv6 and --fail to catch network errors set -l ip (curl -6 -s --fail https://icanhazip.com 2>/dev/null) - + if test $status -eq 0 echo $ip else diff --git a/functions/gitup.fish b/functions/gitup.fish index ae2c831..dea143e 100644 --- a/functions/gitup.fish +++ b/functions/gitup.fish @@ -27,12 +27,12 @@ function gitup --description 'Fetch updates and show git status' echo "Check your map! You aren't in a git repository." return 1 end - + if count $argv >/dev/null git fetch $argv else git fetch end - + and git status end diff --git a/functions/hist.fish b/functions/hist.fish index c8e0dbd..a23a3fd 100644 --- a/functions/hist.fish +++ b/functions/hist.fish @@ -35,7 +35,7 @@ function hist --description 'Search fish history and put it in the prompt' if test -n "$selected" # Strip the timestamp for the final output set -l command (echo $selected | string replace -r '^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} ' '') - + echo $command | wl-copy 2>/dev/null commandline -r $command end diff --git a/functions/logs.fish b/functions/logs.fish index 12efbf9..8c9cb06 100644 --- a/functions/logs.fish +++ b/functions/logs.fish @@ -135,11 +135,11 @@ function logs --description 'Browse terminal log files interactively with fzf' set -l togglescript (mktemp) set -l deletescript (mktemp) set -l helpflag "$tmpfile.help" - printf '%s\n' $fzf_lines > $tmpfile - printf 'Terminal Logs — Key Bindings\n\n Enter View selected log in pager\n Ctrl-E Edit selected log in editor\n Ctrl-D Delete selected log\n Ctrl-C Quit\n ? Toggle this help\n\nFiltering:\n Type to fuzzy-filter by date or category\n Use -c flag to limit: scrollback, paru, yay' > $helpfile + printf '%s\n' $fzf_lines >$tmpfile + printf 'Terminal Logs — Key Bindings\n\n Enter View selected log in pager\n Ctrl-E Edit selected log in editor\n Ctrl-D Delete selected log\n Ctrl-C Quit\n ? Toggle this help\n\nFiltering:\n Type to fuzzy-filter by date or category\n Use -c flag to limit: scrollback, paru, yay' >$helpfile # Help toggle script — avoids nested parens inside transform() printf '#!/bin/sh\nif test -f %s; then\n rm -f %s\n printf "change-preview(command cat {1})"\nelse\n touch %s\n printf "change-preview(command cat %s)"\nfi\n' \ - $helpflag $helpflag $helpflag $helpfile > $togglescript + $helpflag $helpflag $helpflag $helpfile >$togglescript chmod +x $togglescript # Delete confirmation script — avoids nested parens inside execute(), and ensures # tmpfile is updated before +reload fires (execute is synchronous, execute-silent is not) @@ -151,8 +151,7 @@ function logs --description 'Browse terminal log files interactively with fzf' 'case "$confirm" in [nN]) exit 0 ;; esac' \ "fish -c \"rm \$FILE\"" \ "grep -vF \"\$FILE\" $tmpfile > $tmpfile.new" \ - "mv $tmpfile.new $tmpfile" \ - > $deletescript + "mv $tmpfile.new $tmpfile" >$deletescript chmod +x $deletescript set -l selected (command cat $tmpfile | fzf \ @@ -196,7 +195,7 @@ function logs --description 'Browse terminal log files interactively with fzf' # is displayed without ov's default slateblue background, preserving # the original ANSI colors of the starship prompt. set -l ov_cfg (mktemp --suffix .yaml) - printf 'Mode:\n scrollback:\n Style:\n SectionLine:\n Background: ""\n Foreground: ""\n' > $ov_cfg + printf 'Mode:\n scrollback:\n Style:\n SectionLine:\n Background: ""\n Foreground: ""\n' >$ov_cfg command ov --config $ov_cfg --view-mode scrollback \ --section-delimiter '\x1b\]133;A' --section-header --section-header-num 1 $file rm -f $ov_cfg diff --git a/functions/pkg.fish b/functions/pkg.fish index 15f968d..a596087 100644 --- a/functions/pkg.fish +++ b/functions/pkg.fish @@ -187,28 +187,52 @@ function pkg --description 'Install or remove packages via the system package ma switch $pm case paru yay $pm -S $to_install - or begin; functions -e __pkg_is_installed; return $status; end + or begin + functions -e __pkg_is_installed + return $status + end case pacman sudo pacman -S $to_install - or begin; functions -e __pkg_is_installed; return $status; end + or begin + functions -e __pkg_is_installed + return $status + end case apt sudo apt install -y $to_install - or begin; functions -e __pkg_is_installed; return $status; end + or begin + functions -e __pkg_is_installed + return $status + end case dnf sudo dnf install -y $to_install - or begin; functions -e __pkg_is_installed; return $status; end + or begin + functions -e __pkg_is_installed + return $status + end case zypper sudo zypper install -y $to_install - or begin; functions -e __pkg_is_installed; return $status; end + or begin + functions -e __pkg_is_installed + return $status + end case brew brew install $to_install - or begin; functions -e __pkg_is_installed; return $status; end + or begin + functions -e __pkg_is_installed + return $status + end case pkg sudo pkg install -y $to_install - or begin; functions -e __pkg_is_installed; return $status; end + or begin + functions -e __pkg_is_installed + return $status + end case yum sudo yum install -y $to_install - or begin; functions -e __pkg_is_installed; return $status; end + or begin + functions -e __pkg_is_installed + return $status + end end end @@ -217,28 +241,52 @@ function pkg --description 'Install or remove packages via the system package ma switch $pm case paru yay $pm -Rns $to_remove - or begin; functions -e __pkg_is_installed; return $status; end + or begin + functions -e __pkg_is_installed + return $status + end case pacman sudo pacman -Rns $to_remove - or begin; functions -e __pkg_is_installed; return $status; end + or begin + functions -e __pkg_is_installed + return $status + end case apt sudo apt remove -y $to_remove - or begin; functions -e __pkg_is_installed; return $status; end + or begin + functions -e __pkg_is_installed + return $status + end case dnf sudo dnf remove -y $to_remove - or begin; functions -e __pkg_is_installed; return $status; end + or begin + functions -e __pkg_is_installed + return $status + end case zypper sudo zypper remove -y $to_remove - or begin; functions -e __pkg_is_installed; return $status; end + or begin + functions -e __pkg_is_installed + return $status + end case brew brew uninstall $to_remove - or begin; functions -e __pkg_is_installed; return $status; end + or begin + functions -e __pkg_is_installed + return $status + end case pkg sudo pkg delete -y $to_remove - or begin; functions -e __pkg_is_installed; return $status; end + or begin + functions -e __pkg_is_installed + return $status + end case yum sudo yum remove -y $to_remove - or begin; functions -e __pkg_is_installed; return $status; end + or begin + functions -e __pkg_is_installed + return $status + end end end end diff --git a/functions/rand_string.fish b/functions/rand_string.fish index 1fcd6db..2d96f5d 100644 --- a/functions/rand_string.fish +++ b/functions/rand_string.fish @@ -62,9 +62,9 @@ function rand_string --description 'Generate random, memorable strings from cura return 0 end - set -l sep "dash" - set -l casing "lower" - + set -l sep dash + set -l casing lower + # Locate the words directory relative to this function set -l words_dir "" if set -q __fish_config_dir @@ -114,9 +114,9 @@ function rand_string --description 'Generate random, memorable strings from cura set -l actual_sep $sep switch $sep case dash - set actual_sep "-" + set actual_sep - case underscore - set actual_sep "_" + set actual_sep _ case dot set actual_sep "." case none empty @@ -144,7 +144,7 @@ function rand_string --description 'Generate random, memorable strings from cura echo "rand_string: unknown category or file missing for '$arg'" >&2 return 1 end - + # Fetch random line (shuf is fastest, random choice is portable fallback) if command -q shuf set part (command shuf -n 1 "$db") diff --git a/functions/rawfish.fish b/functions/rawfish.fish index 588f34a..be0e586 100644 --- a/functions/rawfish.fish +++ b/functions/rawfish.fish @@ -17,6 +17,6 @@ # EXAMPLE # rawfish function rawfish --wraps='env NO_TMUX=1 fish' --description 'alias rawfish=env NO_TMUX=1 fish' - env NO_TMUX=1 fish $argv - + env NO_TMUX=1 fish $argv + end diff --git a/functions/repo-open.fish b/functions/repo-open.fish index 8cc0c91..039558c 100644 --- a/functions/repo-open.fish +++ b/functions/repo-open.fish @@ -90,12 +90,12 @@ function repo-open --description 'Open the origin remote of the current repo in set had_scheme 1 end set -l u (string replace -r '^[a-z0-9]+://' '' -- $remote) # strip scheme - set u (string replace -r '^[^@/]+@' '' -- $u) # strip user@ - set u (string replace -r '\.git$' '' -- $u) # strip .git + set u (string replace -r '^[^@/]+@' '' -- $u) # strip user@ + set u (string replace -r '\.git$' '' -- $u) # strip .git if test $had_scheme -eq 0 - set u (string replace ':' '/' -- $u) # scp: first colon → path sep + set u (string replace ':' '/' -- $u) # scp: first colon → path sep else - set u (string replace -r ':[0-9]+/' '/' -- $u) # url: drop :port + set u (string replace -r ':[0-9]+/' '/' -- $u) # url: drop :port end set -l parts (string split -m1 '/' -- $u) diff --git a/functions/sbver.fish b/functions/sbver.fish index 87d7965..6a342be 100644 --- a/functions/sbver.fish +++ b/functions/sbver.fish @@ -24,53 +24,53 @@ # sbver # sbver --brief function sbver --description 'Verifies Secure Boot status of EFI binaries using sbctl' - __fish_help_header (status current-function) $argv; and return 0 + __fish_help_header (status current-function) $argv; and return 0 - if not type -q sbctl - echo "Error: 'sbctl' is not installed." - return 1 - end + if not type -q sbctl + echo "Error: 'sbctl' is not installed." + return 1 + end - # ANSI color codes (Fish uses set_color for easier management) - set RED (set_color red) - set GREEN (set_color green) - set NC (set_color normal) + # ANSI color codes (Fish uses set_color for easier management) + set RED (set_color red) + set GREEN (set_color green) + set NC (set_color normal) - # Flags - set brief_mode false - if test "$argv[1]" = "--brief" - set brief_mode true - end + # Flags + set brief_mode false + if test "$argv[1]" = --brief + set brief_mode true + end - # Counters - set pass_count 0 - set fail_count 0 + # Counters + set pass_count 0 + set fail_count 0 - # Run and process sbctl output - # Fish doesn't use 'done < <()'; we pipe directly into the while loop - sudo sbctl verify 2>&1 | grep -v -i 'invalid pe header' | while read -l line - if string match -q "*✓*" -- "$line" - set pass_count (math $pass_count + 1) - if not $brief_mode - echo -e "$GREEN$line$NC" - end - else if string match -q "*✗*" -- "$line" - set fail_count (math $fail_count + 1) - if not $brief_mode - echo -e "$RED$line$NC" - end - else - if not $brief_mode - echo "$line" - end - end - end + # Run and process sbctl output + # Fish doesn't use 'done < <()'; we pipe directly into the while loop + sudo sbctl verify 2>&1 | grep -v -i 'invalid pe header' | while read -l line + if string match -q "*✓*" -- "$line" + set pass_count (math $pass_count + 1) + if not $brief_mode + echo -e "$GREEN$line$NC" + end + else if string match -q "*✗*" -- "$line" + set fail_count (math $fail_count + 1) + if not $brief_mode + echo -e "$RED$line$NC" + end + else + if not $brief_mode + echo "$line" + end + end + end - # Summary - echo - if test $fail_count -eq 0 - echo -e "$GREEN✅ All images are signed ($pass_count verified)$NC" - else - echo -e "$RED❌ Some images are not signed ($fail_count failed, $pass_count passed)$NC" - end + # Summary + echo + if test $fail_count -eq 0 + echo -e "$GREEN✅ All images are signed ($pass_count verified)$NC" + else + echo -e "$RED❌ Some images are not signed ($fail_count failed, $pass_count passed)$NC" + end end diff --git a/functions/spark.fish b/functions/spark.fish index 2420de1..81b56fb 100644 --- a/functions/spark.fish +++ b/functions/spark.fish @@ -23,7 +23,7 @@ # spark 1 1 2 5 14 42 # seq 64 | sort --random-sort | spark # echo "3 7 2 9 1" | spark -function spark --description 'Sparklines' +function spark --description Sparklines argparse --ignore-unknown --name=spark v/version h/help m/min= M/max= -- $argv || return if set --query _flag_version[1] diff --git a/functions/sudo-toggle.fish b/functions/sudo-toggle.fish index c218e98..8255df3 100644 --- a/functions/sudo-toggle.fish +++ b/functions/sudo-toggle.fish @@ -23,7 +23,7 @@ function sudo-toggle --description 'Toggle sudo password requirement on/off' # Check the file size using sudo stat to see if our bypass rule is active set -l file_size (sudo stat -c %s /etc/sudoers.d/nofail-toggle 2>/dev/null) - + if test -n "$file_size"; and test "$file_size" -gt 0 # 1. Toggle is currently OFF (Bypass is active). We want to turn security back ON. # We use 'sudo -k' to clear the execution cache so it locks down instantly. @@ -32,7 +32,7 @@ function sudo-toggle --description 'Toggle sudo password requirement on/off' else # 2. Toggle is currently ON (Security active). We want to BYPASS it. # We write a high-priority user-specific NOPASSWD rule. - echo "$USER ALL=(ALL:ALL) NOPASSWD:ALL" | sudo tee /etc/sudoers.d/nofail-toggle > /dev/null + echo "$USER ALL=(ALL:ALL) NOPASSWD:ALL" | sudo tee /etc/sudoers.d/nofail-toggle >/dev/null echo "🔓 Sudo security: DISABLED (Bypass active)" end end diff --git a/functions/swapstat.fish b/functions/swapstat.fish index 0ffa51a..86ca013 100644 --- a/functions/swapstat.fish +++ b/functions/swapstat.fish @@ -22,9 +22,9 @@ function swapstat --description 'View colorized zRAM and swappiness status' set -l swappiness (sysctl -n vm.swappiness) set -l zdata (zramctl --bytes --noheadings --output DATA,TOTAL /dev/zram0 2>/dev/null) - + echo (set_color --bold blue)"── Memory & zRAM Report ──"(set_color normal) - + # Kernel & Compression Stats if test -n "$zdata" set -l raw (echo $zdata | awk '{print $1}') diff --git a/functions/view.fish b/functions/view.fish index 6872b0c..fa0f665 100644 --- a/functions/view.fish +++ b/functions/view.fish @@ -22,5 +22,5 @@ function view --wraps='nvim -R' --description 'alias view=nvim -R' else less $argv end - + end diff --git a/functions/y.fish b/functions/y.fish index 26e5361..1b862df 100644 --- a/functions/y.fish +++ b/functions/y.fish @@ -70,4 +70,4 @@ end # ls | cb function cb --wraps='y' --description 'Alias cb=y' y $argv -end \ No newline at end of file +end diff --git a/functions/yt-dlp.fish b/functions/yt-dlp.fish index da8c317..340f7ea 100644 --- a/functions/yt-dlp.fish +++ b/functions/yt-dlp.fish @@ -62,7 +62,7 @@ function yt-dlp --description 'yt-dlp with embedding + SponsorBlock defaults' # --sponsorblock-remove all # Skip if the user set their own remove (bare or --opt=value form), or # disabled SponsorBlock entirely with --no-sponsorblock. - if not string match -q -- '--sponsorblock-remove' $argv + if not string match -q -- --sponsorblock-remove $argv and not string match -q -- '--sponsorblock-remove=*' $argv and not contains -- --no-sponsorblock $argv set -a extra --sponsorblock-remove all diff --git a/integrations/fzf.fish b/integrations/fzf.fish index 8bd410a..943544f 100644 --- a/integrations/fzf.fish +++ b/integrations/fzf.fish @@ -13,7 +13,6 @@ # - $FZF_ALT_C_COMMAND # - $FZF_ALT_C_OPTS - # Key bindings # ------------ # The oldest supported fish version is 3.1b1. To maintain compatibility, the @@ -21,289 +20,289 @@ # check, otherwise the source command will fail on fish versions older than 3.4.0. function fzf_key_bindings - # Check fish version - if set -l -- fish_ver (string match -r '^(\d+)\.(\d+)' $version 2>/dev/null) - and test "$fish_ver[2]" -lt 3 -o "$fish_ver[2]" -eq 3 -a "$fish_ver[3]" -lt 1 - echo "This script requires fish version 3.1b1 or newer." >&2 - return 1 - else if not type -q fzf - echo "fzf was not found in path." >&2 - return 1 - end - - # bg-transform runs the preview toggle in a background thread (non-blocking). - # It was added after fzf 0.60; fall back to the synchronous transform on older builds. - set -l _fzf_ver (fzf --version | string match -r '^(\d+)\.(\d+)') - set -l _fzf_transform_action transform - if test -n "$_fzf_ver[3]"; and test "$_fzf_ver[3]" -ge 62 - set _fzf_transform_action bg-transform - end - -#----BEGIN INCLUDE common.fish -# NOTE: Do not directly edit this section, which is copied from "common.fish". -# To modify it, one can edit "common.fish" and run "./update.sh" to apply -# the changes. See code comments in "common.fish" for the implementation details. - - function __fzf_defaults - test -n "$FZF_TMUX_HEIGHT"; or set -l FZF_TMUX_HEIGHT 40% - string join ' ' -- \ - "--height $FZF_TMUX_HEIGHT --min-height=20+ --bind=ctrl-z:ignore" $argv[1] \ - (test -r "$FZF_DEFAULT_OPTS_FILE"; and string join -- ' ' <$FZF_DEFAULT_OPTS_FILE) \ - $FZF_DEFAULT_OPTS $argv[2..-1] - end - - function __fzfcmd - test -n "$FZF_TMUX_HEIGHT"; or set -l FZF_TMUX_HEIGHT 40% - if test -n "$FZF_TMUX_OPTS" - echo "fzf-tmux $FZF_TMUX_OPTS -- " - else if test "$FZF_TMUX" = "1" - echo "fzf-tmux -d$FZF_TMUX_HEIGHT -- " - else - echo "fzf" - end - end - - function __fzf_cmd_tokens -d 'Return command line tokens, skipping leading env assignments and command prefixes' - set -l tokens - if test (string match -r -- '^\d+' $version) -ge 4 - set -- tokens (commandline -xpc) - else - set -- tokens (commandline -opc) + # Check fish version + if set -l -- fish_ver (string match -r '^(\d+)\.(\d+)' $version 2>/dev/null) + and test "$fish_ver[2]" -lt 3 -o "$fish_ver[2]" -eq 3 -a "$fish_ver[3]" -lt 1 + echo "This script requires fish version 3.1b1 or newer." >&2 + return 1 + else if not type -q fzf + echo "fzf was not found in path." >&2 + return 1 end - set -l -- var_count 0 - for i in $tokens - if string match -qr -- '^[\w]+=' $i - set var_count (math $var_count + 1) - else - break - end - end - set -e -- tokens[0..$var_count] - - while true - switch "$tokens[1]" - case builtin command - set -e -- tokens[1] - test "$tokens[1]" = "--"; and set -e -- tokens[1] - case env - set -e -- tokens[1] - test "$tokens[1]" = "--"; and set -e -- tokens[1] - while string match -qr -- '^[\w]+=' "$tokens[1]" - set -e -- tokens[1] - end - case '*' - break - end + # bg-transform runs the preview toggle in a background thread (non-blocking). + # It was added after fzf 0.60; fall back to the synchronous transform on older builds. + set -l _fzf_ver (fzf --version | string match -r '^(\d+)\.(\d+)') + set -l _fzf_transform_action transform + if test -n "$_fzf_ver[3]"; and test "$_fzf_ver[3]" -ge 62 + set _fzf_transform_action bg-transform end - string escape -n -- $tokens - end + #----BEGIN INCLUDE common.fish + # NOTE: Do not directly edit this section, which is copied from "common.fish". + # To modify it, one can edit "common.fish" and run "./update.sh" to apply + # the changes. See code comments in "common.fish" for the implementation details. - function __fzf_parse_commandline -d 'Parse the current command line token and return split of existing filepath, fzf query, and optional -option= prefix' - set -l fzf_query '' - set -l prefix '' - set -l dir '.' - - set -l -- fish_major (string match -r -- '^\d+' $version) - set -l -- fish_minor (string match -r -- '^\d+\.(\d+)' $version)[2] - - set -l -- match_regex '(?[\s\S]*?(?=\n?$)$)' - set -l -- prefix_regex '^-[^\s=]+=|^-(?!-)\S' - if test "$fish_major" -eq 3 -a "$fish_minor" -lt 3 - or string match -q -v -- '* -- *' (string sub -l (commandline -Cp) -- (commandline -p)) - set -- match_regex "(?$prefix_regex)?$match_regex" + function __fzf_defaults + test -n "$FZF_TMUX_HEIGHT"; or set -l FZF_TMUX_HEIGHT 40% + string join ' ' -- \ + "--height $FZF_TMUX_HEIGHT --min-height=20+ --bind=ctrl-z:ignore" $argv[1] \ + (test -r "$FZF_DEFAULT_OPTS_FILE"; and string join -- ' ' <$FZF_DEFAULT_OPTS_FILE) \ + $FZF_DEFAULT_OPTS $argv[2..-1] end - if test "$fish_major" -ge 4 - string match -q -r -- $match_regex (commandline --current-token --tokens-expanded | string collect -N) - else if test "$fish_major" -eq 3 -a "$fish_minor" -ge 2 - string match -q -r -- $match_regex (commandline --current-token --tokenize | string collect -N) - eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r -a '^\\\(?=~)|\\\(?=\$\w)' '') - else - set -l -- cl_token (commandline --current-token --tokenize | string collect -N) - set -- prefix (string match -r -- $prefix_regex $cl_token) - set -- fzf_query (string replace -- "$prefix" '' $cl_token | string collect -N) - eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r -a '^\\\(?=~)|\\\(?=\$\w)|\\\n\\\n$' '') - end - - if test -n "$fzf_query" - if test \( "$fish_major" -ge 4 \) -o \( "$fish_major" -eq 3 -a "$fish_minor" -ge 5 \) - set -- fzf_query (path normalize -- $fzf_query) - set -- dir $fzf_query - while not path is -d $dir - set -- dir (path dirname $dir) - end - else - if test "$fish_major" -eq 3 -a "$fish_minor" -ge 2 - string match -q -r -- '(?^[\s\S]*?(?=\n?$)$)' \ - (string replace -r -a -- '(?<=/)/|(?[\s\S]*?(?=\n?$)$)' + set -l -- prefix_regex '^-[^\s=]+=|^-(?!-)\S' + if test "$fish_major" -eq 3 -a "$fish_minor" -lt 3 + or string match -q -v -- '* -- *' (string sub -l (commandline -Cp) -- (commandline -p)) + set -- match_regex "(?$prefix_regex)?$match_regex" end - set -- dir $fzf_query - while not test -d "$dir" - set -- dir (dirname -z -- "$dir" | string split0) - end - end - if not string match -q -- '.' $dir; or string match -q -r -- '^\./|^\.$' $fzf_query if test "$fish_major" -ge 4 - string match -q -r -- '^'(string escape --style=regex -- $dir)'/?(?[\s\S]*)' $fzf_query + string match -q -r -- $match_regex (commandline --current-token --tokens-expanded | string collect -N) else if test "$fish_major" -eq 3 -a "$fish_minor" -ge 2 - string match -q -r -- '^/?(?[\s\S]*?(?=\n?$)$)' \ - (string replace -- "$dir" '' $fzf_query | string collect -N) + string match -q -r -- $match_regex (commandline --current-token --tokenize | string collect -N) + eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r -a '^\\\(?=~)|\\\(?=\$\w)' '') else - set -- fzf_query (string replace -- "$dir" '' $fzf_query | string collect -N) - eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r -a '^/?|\\\n$' '') + set -l -- cl_token (commandline --current-token --tokenize | string collect -N) + set -- prefix (string match -r -- $prefix_regex $cl_token) + set -- fzf_query (string replace -- "$prefix" '' $cl_token | string collect -N) + eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r -a '^\\\(?=~)|\\\(?=\$\w)|\\\n\\\n$' '') end - end + + if test -n "$fzf_query" + if test \( "$fish_major" -ge 4 \) -o \( "$fish_major" -eq 3 -a "$fish_minor" -ge 5 \) + set -- fzf_query (path normalize -- $fzf_query) + set -- dir $fzf_query + while not path is -d $dir + set -- dir (path dirname $dir) + end + else + if test "$fish_major" -eq 3 -a "$fish_minor" -ge 2 + string match -q -r -- '(?^[\s\S]*?(?=\n?$)$)' \ + (string replace -r -a -- '(?<=/)/|(?[\s\S]*)' $fzf_query + else if test "$fish_major" -eq 3 -a "$fish_minor" -ge 2 + string match -q -r -- '^/?(?[\s\S]*?(?=\n?$)$)' \ + (string replace -- "$dir" '' $fzf_query | string collect -N) + else + set -- fzf_query (string replace -- "$dir" '' $fzf_query | string collect -N) + eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r -a '^/?|\\\n$' '') + end + end + end + + string escape -n -- "$dir" "$fzf_query" "$prefix" end + #----END INCLUDE - string escape -n -- "$dir" "$fzf_query" "$prefix" - end -#----END INCLUDE + # Store current token in $dir as root for the 'find' command + function fzf-file-widget -d "List files and folders" + set -l commandline (__fzf_parse_commandline) + set -lx dir $commandline[1] + set -l fzf_query $commandline[2] + set -l prefix $commandline[3] - # Store current token in $dir as root for the 'find' command - function fzf-file-widget -d "List files and folders" - set -l commandline (__fzf_parse_commandline) - set -lx dir $commandline[1] - set -l fzf_query $commandline[2] - set -l prefix $commandline[3] - - set -lx FZF_DEFAULT_OPTS (__fzf_defaults \ + set -lx FZF_DEFAULT_OPTS (__fzf_defaults \ "--reverse --walker=file,dir,follow,hidden --scheme=path" \ "--multi $FZF_CTRL_T_OPTS --print0") - set -lx FZF_DEFAULT_COMMAND "$FZF_CTRL_T_COMMAND" - set -lx FZF_DEFAULT_OPTS_FILE + set -lx FZF_DEFAULT_COMMAND "$FZF_CTRL_T_COMMAND" + set -lx FZF_DEFAULT_OPTS_FILE - set -l result (eval (__fzfcmd) --walker-root=$dir --query=$fzf_query | string split0) - and commandline -rt -- (string join -- ' ' $prefix(string escape --no-quoted -- $result))' ' + set -l result (eval (__fzfcmd) --walker-root=$dir --query=$fzf_query | string split0) + and commandline -rt -- (string join -- ' ' $prefix(string escape --no-quoted -- $result))' ' - commandline -f repaint - end - - function fzf-history-widget --inherit-variable _fzf_transform_action -d "Show command history" - set -l -- command_line (commandline) - set -l -- current_line (commandline -L) - set -l -- total_lines (count $command_line) - set -l -- fzf_query (string escape -- $command_line[$current_line]) - - # These options require newer fzf; omit them on older builds - set -l _fzf_preview_wrap_sign '' - set -l _fzf_toggle_raw '' - if test "$_fzf_transform_action" = bg-transform - set _fzf_preview_wrap_sign ' --preview-wrap-sign="↳ "' - set _fzf_toggle_raw ,alt-r:toggle-raw + commandline -f repaint end - set -lx -- FZF_DEFAULT_OPTS (__fzf_defaults '' \ + function fzf-history-widget --inherit-variable _fzf_transform_action -d "Show command history" + set -l -- command_line (commandline) + set -l -- current_line (commandline -L) + set -l -- total_lines (count $command_line) + set -l -- fzf_query (string escape -- $command_line[$current_line]) + + # These options require newer fzf; omit them on older builds + set -l _fzf_preview_wrap_sign '' + set -l _fzf_toggle_raw '' + if test "$_fzf_transform_action" = bg-transform + set _fzf_preview_wrap_sign ' --preview-wrap-sign="↳ "' + set _fzf_toggle_raw ,alt-r:toggle-raw + end + + set -lx -- FZF_DEFAULT_OPTS (__fzf_defaults '' \ '--nth=2..,.. --scheme=history --multi --no-multi-line --no-wrap --wrap-sign="\t\t\t↳ "'$_fzf_preview_wrap_sign \ '--bind=\'shift-delete:execute-silent(for i in (string split0 -- <{+f}); eval builtin history delete --exact --case-sensitive -- (string escape -n -- $i | string replace -r "^\d*\\\\\\t" ""); end)+reload(eval $FZF_DEFAULT_COMMAND)\'' \ '--bind="alt-enter:become(string join0 -- (string collect -- {+2..} | fish_indent -i))"' \ "--bind=ctrl-r:toggle-sort$_fzf_toggle_raw --highlight-line $FZF_CTRL_R_OPTS" \ '--accept-nth=2.. --delimiter="\t" --tabstop=4 --read0 --print0 --with-shell='(status fish-path)\\ -c) - # Add dynamic preview options if preview command isn't already set by user - if string match -qvr -- '--preview[= ]' "$FZF_DEFAULT_OPTS" - # Convert the highlighted timestamp using the date command if available - set -l -- date_cmd '{1}' - if type -q date - if date -d @0 '+%s' 2>/dev/null | string match -q 0 - # GNU date - set -- date_cmd '(date -d @{1} \\"+%F %a %T\\")' - else if date -r 0 '+%s' 2>/dev/null | string match -q 0 - # BSD date - set -- date_cmd '(date -r {1} \\"+%F %a %T\\")' + # Add dynamic preview options if preview command isn't already set by user + if string match -qvr -- '--preview[= ]' "$FZF_DEFAULT_OPTS" + # Convert the highlighted timestamp using the date command if available + set -l -- date_cmd '{1}' + if type -q date + if date -d @0 '+%s' 2>/dev/null | string match -q 0 + # GNU date + set -- date_cmd '(date -d @{1} \\"+%F %a %T\\")' + else if date -r 0 '+%s' 2>/dev/null | string match -q 0 + # BSD date + set -- date_cmd '(date -r {1} \\"+%F %a %T\\")' + end + end + + # Prepend the options to allow user customizations + if test "$_fzf_transform_action" = bg-transform + # Newer fzf: dynamic show/hide preview based on terminal width + set -p -- FZF_DEFAULT_OPTS \ + '--bind="focus,resize:bg-transform:if test \\"$FZF_COLUMNS\\" -gt 100 -a \\\\( \\"$FZF_SELECT_COUNT\\" -gt 0 -o \\\\( -z \\"$FZF_WRAP\\" -a (string length -- {}) -gt (math $FZF_COLUMNS - 4) \\\\) -o (string collect -- {2..} | fish_indent | count) -gt 1 \\\\); echo show-preview; else echo hide-preview; end"' \ + '--preview="string collect -- (test \\"$FZF_SELECT_COUNT\\" -gt 0; and string collect -- {+2..}) \\"\\n# \\"'$date_cmd' {2..} | fish_indent --ansi"' \ + '--preview-window="right,50%,wrap-word,follow,info,hidden"' + else + # Older fzf: static preview; focus/resize events not reliably supported + set -p -- FZF_DEFAULT_OPTS \ + '--preview="string collect -- (test \\"$FZF_SELECT_COUNT\\" -gt 0; and string collect -- {+2..}) \\"\\n# \\"'$date_cmd' {2..} | fish_indent --ansi"' \ + '--preview-window="right,50%,wrap,follow,info"' + end end - end - # Prepend the options to allow user customizations - if test "$_fzf_transform_action" = bg-transform - # Newer fzf: dynamic show/hide preview based on terminal width - set -p -- FZF_DEFAULT_OPTS \ - '--bind="focus,resize:bg-transform:if test \\"$FZF_COLUMNS\\" -gt 100 -a \\\\( \\"$FZF_SELECT_COUNT\\" -gt 0 -o \\\\( -z \\"$FZF_WRAP\\" -a (string length -- {}) -gt (math $FZF_COLUMNS - 4) \\\\) -o (string collect -- {2..} | fish_indent | count) -gt 1 \\\\); echo show-preview; else echo hide-preview; end"' \ - '--preview="string collect -- (test \\"$FZF_SELECT_COUNT\\" -gt 0; and string collect -- {+2..}) \\"\\n# \\"'$date_cmd' {2..} | fish_indent --ansi"' \ - '--preview-window="right,50%,wrap-word,follow,info,hidden"' - else - # Older fzf: static preview; focus/resize events not reliably supported - set -p -- FZF_DEFAULT_OPTS \ - '--preview="string collect -- (test \\"$FZF_SELECT_COUNT\\" -gt 0; and string collect -- {+2..}) \\"\\n# \\"'$date_cmd' {2..} | fish_indent --ansi"' \ - '--preview-window="right,50%,wrap,follow,info"' - end + set -lx FZF_DEFAULT_OPTS_FILE + + set -lx -- FZF_DEFAULT_COMMAND 'builtin history -z --show-time="%s%t"' + + # Enable syntax highlighting colors on fish v4.3.3 and newer + if set -l -- v (string match -r -- '^(\d+)\.(\d+)(?:\.(\d+))?' $version) + and test "$v[2]" -gt 4 -o "$v[2]" -eq 4 -a \ + \( "$v[3]" -gt 3 -o "$v[3]" -eq 3 -a \ + \( -n "$v[4]" -a "$v[4]" -ge 3 \) \) + + set -a -- FZF_DEFAULT_OPTS --ansi + set -a -- FZF_DEFAULT_COMMAND '--color=always' + end + + # Merge history from other sessions before searching + test -z "$fish_private_mode"; and builtin history merge + + if set -l result (eval $FZF_DEFAULT_COMMAND \| (__fzfcmd) --query=$fzf_query | string split0) + if test "$total_lines" -eq 1 + commandline -- $result + else + set -l a (math $current_line - 1) + set -l b (math $current_line + 1) + commandline -- $command_line[1..$a] $result + commandline -a -- '' $command_line[$b..-1] + end + end + + commandline -f repaint end - set -lx FZF_DEFAULT_OPTS_FILE + function fzf-cd-widget -d "Change directory" + set -l commandline (__fzf_parse_commandline) + set -lx dir $commandline[1] + set -l fzf_query $commandline[2] + set -l prefix $commandline[3] - set -lx -- FZF_DEFAULT_COMMAND 'builtin history -z --show-time="%s%t"' - - # Enable syntax highlighting colors on fish v4.3.3 and newer - if set -l -- v (string match -r -- '^(\d+)\.(\d+)(?:\.(\d+))?' $version) - and test "$v[2]" -gt 4 -o "$v[2]" -eq 4 -a \ - \( "$v[3]" -gt 3 -o "$v[3]" -eq 3 -a \ - \( -n "$v[4]" -a "$v[4]" -ge 3 \) \) - - set -a -- FZF_DEFAULT_OPTS '--ansi' - set -a -- FZF_DEFAULT_COMMAND '--color=always' - end - - # Merge history from other sessions before searching - test -z "$fish_private_mode"; and builtin history merge - - if set -l result (eval $FZF_DEFAULT_COMMAND \| (__fzfcmd) --query=$fzf_query | string split0) - if test "$total_lines" -eq 1 - commandline -- $result - else - set -l a (math $current_line - 1) - set -l b (math $current_line + 1) - commandline -- $command_line[1..$a] $result - commandline -a -- '' $command_line[$b..-1] - end - end - - commandline -f repaint - end - - function fzf-cd-widget -d "Change directory" - set -l commandline (__fzf_parse_commandline) - set -lx dir $commandline[1] - set -l fzf_query $commandline[2] - set -l prefix $commandline[3] - - set -lx FZF_DEFAULT_OPTS (__fzf_defaults \ + set -lx FZF_DEFAULT_OPTS (__fzf_defaults \ "--reverse --walker=dir,follow,hidden --scheme=path" \ "$FZF_ALT_C_OPTS --no-multi --print0") - set -lx FZF_DEFAULT_OPTS_FILE - set -lx FZF_DEFAULT_COMMAND "$FZF_ALT_C_COMMAND" + set -lx FZF_DEFAULT_OPTS_FILE + set -lx FZF_DEFAULT_COMMAND "$FZF_ALT_C_COMMAND" - if set -l result (eval (__fzfcmd) --query=$fzf_query --walker-root=$dir | string split0) - cd -- $result - commandline -rt -- $prefix + if set -l result (eval (__fzfcmd) --query=$fzf_query --walker-root=$dir | string split0) + cd -- $result + commandline -rt -- $prefix + end + + commandline -f repaint end - commandline -f repaint - end - - if not set -q FZF_CTRL_R_COMMAND; or test -n "$FZF_CTRL_R_COMMAND" - if test -n "$FZF_CTRL_R_COMMAND" - echo "warning: FZF_CTRL_R_COMMAND is set to a custom command, but custom commands are not yet supported for CTRL-R" >&2 + if not set -q FZF_CTRL_R_COMMAND; or test -n "$FZF_CTRL_R_COMMAND" + if test -n "$FZF_CTRL_R_COMMAND" + echo "warning: FZF_CTRL_R_COMMAND is set to a custom command, but custom commands are not yet supported for CTRL-R" >&2 + end + bind \cr fzf-history-widget + bind -M insert \cr fzf-history-widget end - bind \cr fzf-history-widget - bind -M insert \cr fzf-history-widget - end - if not set -q FZF_CTRL_T_COMMAND; or test -n "$FZF_CTRL_T_COMMAND" - bind \ct fzf-file-widget - bind -M insert \ct fzf-file-widget - end + if not set -q FZF_CTRL_T_COMMAND; or test -n "$FZF_CTRL_T_COMMAND" + bind \ct fzf-file-widget + bind -M insert \ct fzf-file-widget + end - if not set -q FZF_ALT_C_COMMAND; or test -n "$FZF_ALT_C_COMMAND" - bind \ec fzf-cd-widget - bind -M insert \ec fzf-cd-widget - end + if not set -q FZF_ALT_C_COMMAND; or test -n "$FZF_ALT_C_COMMAND" + bind \ec fzf-cd-widget + bind -M insert \ec fzf-cd-widget + end end @@ -321,233 +320,238 @@ fzf_key_bindings function fzf_completion_setup -#----BEGIN INCLUDE common.fish -# NOTE: Do not directly edit this section, which is copied from "common.fish". -# To modify it, one can edit "common.fish" and run "./update.sh" to apply -# the changes. See code comments in "common.fish" for the implementation details. + #----BEGIN INCLUDE common.fish + # NOTE: Do not directly edit this section, which is copied from "common.fish". + # To modify it, one can edit "common.fish" and run "./update.sh" to apply + # the changes. See code comments in "common.fish" for the implementation details. - function __fzf_defaults - test -n "$FZF_TMUX_HEIGHT"; or set -l FZF_TMUX_HEIGHT 40% - string join ' ' -- \ - "--height $FZF_TMUX_HEIGHT --min-height=20+ --bind=ctrl-z:ignore" $argv[1] \ - (test -r "$FZF_DEFAULT_OPTS_FILE"; and string join -- ' ' <$FZF_DEFAULT_OPTS_FILE) \ - $FZF_DEFAULT_OPTS $argv[2..-1] - end - - function __fzfcmd - test -n "$FZF_TMUX_HEIGHT"; or set -l FZF_TMUX_HEIGHT 40% - if test -n "$FZF_TMUX_OPTS" - echo "fzf-tmux $FZF_TMUX_OPTS -- " - else if test "$FZF_TMUX" = "1" - echo "fzf-tmux -d$FZF_TMUX_HEIGHT -- " - else - echo "fzf" - end - end - - function __fzf_cmd_tokens -d 'Return command line tokens, skipping leading env assignments and command prefixes' - set -l tokens - if test (string match -r -- '^\d+' $version) -ge 4 - set -- tokens (commandline -xpc) - else - set -- tokens (commandline -opc) + function __fzf_defaults + test -n "$FZF_TMUX_HEIGHT"; or set -l FZF_TMUX_HEIGHT 40% + string join ' ' -- \ + "--height $FZF_TMUX_HEIGHT --min-height=20+ --bind=ctrl-z:ignore" $argv[1] \ + (test -r "$FZF_DEFAULT_OPTS_FILE"; and string join -- ' ' <$FZF_DEFAULT_OPTS_FILE) \ + $FZF_DEFAULT_OPTS $argv[2..-1] end - set -l -- var_count 0 - for i in $tokens - if string match -qr -- '^[\w]+=' $i - set var_count (math $var_count + 1) - else - break - end - end - set -e -- tokens[0..$var_count] - - while true - switch "$tokens[1]" - case builtin command - set -e -- tokens[1] - test "$tokens[1]" = "--"; and set -e -- tokens[1] - case env - set -e -- tokens[1] - test "$tokens[1]" = "--"; and set -e -- tokens[1] - while string match -qr -- '^[\w]+=' "$tokens[1]" - set -e -- tokens[1] - end - case '*' - break - end - end - - string escape -n -- $tokens - end - - function __fzf_parse_commandline -d 'Parse the current command line token and return split of existing filepath, fzf query, and optional -option= prefix' - set -l fzf_query '' - set -l prefix '' - set -l dir '.' - - set -l -- fish_major (string match -r -- '^\d+' $version) - set -l -- fish_minor (string match -r -- '^\d+\.(\d+)' $version)[2] - - set -l -- match_regex '(?[\s\S]*?(?=\n?$)$)' - set -l -- prefix_regex '^-[^\s=]+=|^-(?!-)\S' - if test "$fish_major" -eq 3 -a "$fish_minor" -lt 3 - or string match -q -v -- '* -- *' (string sub -l (commandline -Cp) -- (commandline -p)) - set -- match_regex "(?$prefix_regex)?$match_regex" - end - - if test "$fish_major" -ge 4 - string match -q -r -- $match_regex (commandline --current-token --tokens-expanded | string collect -N) - else if test "$fish_major" -eq 3 -a "$fish_minor" -ge 2 - string match -q -r -- $match_regex (commandline --current-token --tokenize | string collect -N) - eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r -a '^\\\(?=~)|\\\(?=\$\w)' '') - else - set -l -- cl_token (commandline --current-token --tokenize | string collect -N) - set -- prefix (string match -r -- $prefix_regex $cl_token) - set -- fzf_query (string replace -- "$prefix" '' $cl_token | string collect -N) - eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r -a '^\\\(?=~)|\\\(?=\$\w)|\\\n\\\n$' '') - end - - if test -n "$fzf_query" - if test \( "$fish_major" -ge 4 \) -o \( "$fish_major" -eq 3 -a "$fish_minor" -ge 5 \) - set -- fzf_query (path normalize -- $fzf_query) - set -- dir $fzf_query - while not path is -d $dir - set -- dir (path dirname $dir) - end - else - if test "$fish_major" -eq 3 -a "$fish_minor" -ge 2 - string match -q -r -- '(?^[\s\S]*?(?=\n?$)$)' \ - (string replace -r -a -- '(?<=/)/|(?[\s\S]*?(?=\n?$)$)' + set -l -- prefix_regex '^-[^\s=]+=|^-(?!-)\S' + if test "$fish_major" -eq 3 -a "$fish_minor" -lt 3 + or string match -q -v -- '* -- *' (string sub -l (commandline -Cp) -- (commandline -p)) + set -- match_regex "(?$prefix_regex)?$match_regex" end - set -- dir $fzf_query - while not test -d "$dir" - set -- dir (dirname -z -- "$dir" | string split0) - end - end - if not string match -q -- '.' $dir; or string match -q -r -- '^\./|^\.$' $fzf_query if test "$fish_major" -ge 4 - string match -q -r -- '^'(string escape --style=regex -- $dir)'/?(?[\s\S]*)' $fzf_query + string match -q -r -- $match_regex (commandline --current-token --tokens-expanded | string collect -N) else if test "$fish_major" -eq 3 -a "$fish_minor" -ge 2 - string match -q -r -- '^/?(?[\s\S]*?(?=\n?$)$)' \ - (string replace -- "$dir" '' $fzf_query | string collect -N) + string match -q -r -- $match_regex (commandline --current-token --tokenize | string collect -N) + eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r -a '^\\\(?=~)|\\\(?=\$\w)' '') else - set -- fzf_query (string replace -- "$dir" '' $fzf_query | string collect -N) - eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r -a '^/?|\\\n$' '') + set -l -- cl_token (commandline --current-token --tokenize | string collect -N) + set -- prefix (string match -r -- $prefix_regex $cl_token) + set -- fzf_query (string replace -- "$prefix" '' $cl_token | string collect -N) + eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r -a '^\\\(?=~)|\\\(?=\$\w)|\\\n\\\n$' '') end - end + + if test -n "$fzf_query" + if test \( "$fish_major" -ge 4 \) -o \( "$fish_major" -eq 3 -a "$fish_minor" -ge 5 \) + set -- fzf_query (path normalize -- $fzf_query) + set -- dir $fzf_query + while not path is -d $dir + set -- dir (path dirname $dir) + end + else + if test "$fish_major" -eq 3 -a "$fish_minor" -ge 2 + string match -q -r -- '(?^[\s\S]*?(?=\n?$)$)' \ + (string replace -r -a -- '(?<=/)/|(?[\s\S]*)' $fzf_query + else if test "$fish_major" -eq 3 -a "$fish_minor" -ge 2 + string match -q -r -- '^/?(?[\s\S]*?(?=\n?$)$)' \ + (string replace -- "$dir" '' $fzf_query | string collect -N) + else + set -- fzf_query (string replace -- "$dir" '' $fzf_query | string collect -N) + eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r -a '^/?|\\\n$' '') + end + end + end + + string escape -n -- "$dir" "$fzf_query" "$prefix" end + #----END INCLUDE - string escape -n -- "$dir" "$fzf_query" "$prefix" - end -#----END INCLUDE + # Use complete builtin for specific commands + function __fzf_complete_native + set -l -- token (commandline -t) + set -l -- completions (eval complete -C \"$argv[1]\") + test -n "$completions"; or begin + commandline -f repaint + return + end - # Use complete builtin for specific commands - function __fzf_complete_native - set -l -- token (commandline -t) - set -l -- completions (eval complete -C \"$argv[1]\") - test -n "$completions"; or begin commandline -f repaint; return; end + # Calculate tabstop based on longest completion item (sample first 500 for performance) + set -l -- tabstop 20 + set -l -- sample_size (math "min(500, "(count $completions)")") + for c in $completions[1..$sample_size] + set -l -- len (string length -V -- (string split -- \t $c)) + test -n "$len[2]" -a "$len[1]" -gt "$tabstop" + and set -- tabstop $len[1] + end + # limit to 120 to prevent long lines + set -- tabstop (math "min($tabstop + 4, 120)") - # Calculate tabstop based on longest completion item (sample first 500 for performance) - set -l -- tabstop 20 - set -l -- sample_size (math "min(500, "(count $completions)")") - for c in $completions[1..$sample_size] - set -l -- len (string length -V -- (string split -- \t $c)) - test -n "$len[2]" -a "$len[1]" -gt "$tabstop" - and set -- tabstop $len[1] - end - # limit to 120 to prevent long lines - set -- tabstop (math "min($tabstop + 4, 120)") - - set -l result - set -lx -- FZF_DEFAULT_OPTS (__fzf_defaults \ + set -l result + set -lx -- FZF_DEFAULT_OPTS (__fzf_defaults \ "--reverse --delimiter=\\t --nth=1 --tabstop=$tabstop --color=fg:dim,nth:regular" \ $FZF_COMPLETION_OPTS $argv[2..-1] --accept-nth=1 --read0 --print0) - set -- result (string join0 -- $completions | eval (__fzfcmd) | string split0) - and begin - set -l -- tail ' ' - # Append / to bare ~username results (fish omits it unlike other shells) - set -- result (string replace -r -- '^(~\w+)\s?$' '$1/' $result) - # Don't add trailing space if single result is a directory - test (count $result) -eq 1 - and string match -q -- '*/' "$result"; and set -- tail '' + set -- result (string join0 -- $completions | eval (__fzfcmd) | string split0) + and begin + set -l -- tail ' ' + # Append / to bare ~username results (fish omits it unlike other shells) + set -- result (string replace -r -- '^(~\w+)\s?$' '$1/' $result) + # Don't add trailing space if single result is a directory + test (count $result) -eq 1 + and string match -q -- '*/' "$result"; and set -- tail '' - set -l -- result (string escape -n -- $result) + set -l -- result (string escape -n -- $result) - string match -q -- '~*' "$token" - and set result (string replace -r -- '^\\\\~' '~' $result) + string match -q -- '~*' "$token" + and set result (string replace -r -- '^\\\\~' '~' $result) - string match -q -- '$*' "$token" - and set result (string replace -r -- '^\\\\\$' '\$' $result) + string match -q -- '$*' "$token" + and set result (string replace -r -- '^\\\\\$' '\$' $result) - commandline -rt -- (string join ' ' -- $result)$tail + commandline -rt -- (string join ' ' -- $result)$tail + end + commandline -f repaint end - commandline -f repaint - end - function _fzf_complete - set -l -- args (string escape -- $argv | string join ' ' | string split -- ' -- ') - set -l -- post_func (status function)_(string split -- ' ' $args[2])[1]_post - set -lx -- FZF_DEFAULT_OPTS (__fzf_defaults --reverse $FZF_COMPLETION_OPTS $args[1]) - set -lx FZF_DEFAULT_OPTS_FILE - set -lx FZF_DEFAULT_COMMAND - set -l -- fzf_query (commandline -t | string escape) - set -l result - eval (__fzfcmd) --query=$fzf_query | while read -l r; set -a -- result $r; end - and if functions -q $post_func - commandline -rt -- (string collect -- $result | eval $post_func $args[2] | string join ' ')' ' - else - commandline -rt -- (string join -- ' ' (string escape -- $result))' ' + function _fzf_complete + set -l -- args (string escape -- $argv | string join ' ' | string split -- ' -- ') + set -l -- post_func (status function)_(string split -- ' ' $args[2])[1]_post + set -lx -- FZF_DEFAULT_OPTS (__fzf_defaults --reverse $FZF_COMPLETION_OPTS $args[1]) + set -lx FZF_DEFAULT_OPTS_FILE + set -lx FZF_DEFAULT_COMMAND + set -l -- fzf_query (commandline -t | string escape) + set -l result + eval (__fzfcmd) --query=$fzf_query | while read -l r + set -a -- result $r + end + and if functions -q $post_func + commandline -rt -- (string collect -- $result | eval $post_func $args[2] | string join ' ')' ' + else + commandline -rt -- (string join -- ' ' (string escape -- $result))' ' + end + commandline -f repaint end - commandline -f repaint - end - # Kill completion (process selection) - function _fzf_complete_kill - set -l -- fzf_query (commandline -t | string escape) - set -lx -- FZF_DEFAULT_OPTS (__fzf_defaults --reverse $FZF_COMPLETION_OPTS \ + # Kill completion (process selection) + function _fzf_complete_kill + set -l -- fzf_query (commandline -t | string escape) + set -lx -- FZF_DEFAULT_OPTS (__fzf_defaults --reverse $FZF_COMPLETION_OPTS \ --accept-nth=2 -m --header-lines=1 --no-preview --wrap) - set -lx FZF_DEFAULT_OPTS_FILE - if type -q ps - set -l -- ps_cmd 'begin command ps -eo user,pid,ppid,start,time,command 2>/dev/null;' \ - 'or command ps -eo user,pid,ppid,time,args 2>/dev/null;' \ - 'or command ps --everyone --full --windows 2>/dev/null; end' - set -l -- result (eval $ps_cmd \| (__fzfcmd) --query=$fzf_query) - and commandline -rt -- (string join ' ' -- $result)" " - else - __fzf_complete_native "kill " --multi --query=$fzf_query + set -lx FZF_DEFAULT_OPTS_FILE + if type -q ps + set -l -- ps_cmd 'begin command ps -eo user,pid,ppid,start,time,command 2>/dev/null;' \ + 'or command ps -eo user,pid,ppid,time,args 2>/dev/null;' \ + 'or command ps --everyone --full --windows 2>/dev/null; end' + set -l -- result (eval $ps_cmd \| (__fzfcmd) --query=$fzf_query) + and commandline -rt -- (string join ' ' -- $result)" " + else + __fzf_complete_native "kill " --multi --query=$fzf_query + end + commandline -f repaint end - commandline -f repaint - end - # Main completion function - function fzf-completion - set -l -- tokens (__fzf_cmd_tokens) - set -l -- current_token (commandline -t) - set -l -- cmd_name $tokens[1] + # Main completion function + function fzf-completion + set -l -- tokens (__fzf_cmd_tokens) + set -l -- current_token (commandline -t) + set -l -- cmd_name $tokens[1] - # Route to appropriate completion function - if test -n "$tokens"; and functions -q _fzf_complete_$cmd_name - _fzf_complete_$cmd_name $tokens - else - set -l -- fzf_opt --query=$current_token --multi - __fzf_complete_native "$tokens $current_token" $fzf_opt + # Route to appropriate completion function + if test -n "$tokens"; and functions -q _fzf_complete_$cmd_name + _fzf_complete_$cmd_name $tokens + else + set -l -- fzf_opt --query=$current_token --multi + __fzf_complete_native "$tokens $current_token" $fzf_opt + end end - end - # Bind Shift-Tab to fzf-completion (Tab retains native Fish behavior) - if test (string match -r -- '^\d+' $version) -ge 4 - bind shift-tab fzf-completion - bind -M insert shift-tab fzf-completion - else - bind -k btab fzf-completion - bind -M insert -k btab fzf-completion - end + # Bind Shift-Tab to fzf-completion (Tab retains native Fish behavior) + if test (string match -r -- '^\d+' $version) -ge 4 + bind shift-tab fzf-completion + bind -M insert shift-tab fzf-completion + else + bind -k btab fzf-completion + bind -M insert -k btab fzf-completion + end end # Run setup diff --git a/tests/palette-bytes.fish b/tests/palette-bytes.fish index 9eb3a55..acfeb0d 100644 --- a/tests/palette-bytes.fish +++ b/tests/palette-bytes.fish @@ -24,9 +24,12 @@ set -l mode bytes set -l baseline main for i in (seq (count $argv)) switch $argv[$i] - case --structural; set mode structural - case --startup; set mode startup - case --baseline; set baseline $argv[(math $i + 1)] + case --structural + set mode structural + case --startup + set mode startup + case --baseline + set baseline $argv[(math $i + 1)] end end @@ -36,8 +39,8 @@ function __pb_cleanup --on-event fish_exit --inherit-variable tmp end # ── Build the two sandboxes ──────────────────────────────────────────── -set -l A $tmp/base/fish # pristine baseline ref -set -l B $tmp/work/fish # current working tree +set -l A $tmp/base/fish # pristine baseline ref +set -l B $tmp/work/fish # current working tree mkdir -p $A $B git -C $repo archive $baseline | tar -x -C $A or begin @@ -71,7 +74,7 @@ set -l cases \ "p --help" "pkg --help" "play-media --help" "qc --help" \ "rand_string --help" "replay --help" "repo-open --help" "scrub --help" \ "smart_exit --help" "spark --help" "y --help" \ - "mkcd" "auto-pull remove __no_such_repo__" \ + mkcd "auto-pull remove __no_such_repo__" \ "agents-init --no-such-flag" "pkg __no_such_subcommand__" function __pb_run --argument-names cfg stub cmd out diff --git a/tests/run-tests.fish b/tests/run-tests.fish index 7379bf4..ace5741 100755 --- a/tests/run-tests.fish +++ b/tests/run-tests.fish @@ -160,8 +160,7 @@ if test (count $session_suites) -gt 0 __fish_config_op_autoexec=off \ FISH_CONFIG_TEST_ROOT=$repo_root \ FISH_CONFIG_TEST_COUNTS=$counts \ - fish -i -c "source $repo_root/tests/lib.fish; $srcs report" \ - 2>$err_file + fish -i -c "source $repo_root/tests/lib.fish; $srcs report" 2>$err_file set -l session_status $status set -l stderr_out (command cat $err_file) diff --git a/tests/test-agents-vault.fish b/tests/test-agents-vault.fish index 5628e1f..9a92f0b 100644 --- a/tests/test-agents-vault.fish +++ b/tests/test-agents-vault.fish @@ -70,7 +70,7 @@ function failing_shim --argument-names name match '#!/bin/sh' \ 'for a in "$@"; do' \ " case \"\$a\" in *$match*) exit 1 ;; esac" \ - 'done' \ + done \ "exec $real \"\$@\"" >$d/$name chmod +x $d/$name printf '%s\n' $d @@ -166,7 +166,8 @@ git -C $r3 remote add upstream https://git.rootiest.dev/rootiest/fish-config.git check "falls back to first remote" $want (_agents_repo_slug $r3) # Slug sanitization test: special chars in fallback (local-) branch. -set -l dirt (mktemp -d); set -ga TMPDIRS $dirt +set -l dirt (mktemp -d) +set -ga TMPDIRS $dirt mkdir -p "$dirt/projects/My Project!" git -C "$dirt/projects/My Project!" init -q git -C "$dirt/projects/My Project!" config user.email t@t @@ -179,7 +180,8 @@ check "sanitizes special chars in local slug" false "$has_bad_chars" echo "" echo "== _agents_repo_ensure_symlink ==" -set -l w (mktemp -d); set -ga TMPDIRS $w +set -l w (mktemp -d) +set -ga TMPDIRS $w mkdir -p $w/target $w/live # Fresh link onto an empty live parent. @@ -200,7 +202,8 @@ check "refuses missing target" 1 (_agents_repo_ensure_symlink $w/live/f3 $w/nope check "no dangling link left" false (test -L $w/live/f3; and echo true; or echo false) # Non-destructive adoption: content on both sides, nothing overwritten. -set -l a (mktemp -d); set -ga TMPDIRS $a +set -l a (mktemp -d) +set -ga TMPDIRS $a mkdir -p $a/vault $a/live/memory echo vault-version >$a/vault/shared.md echo vault-only >$a/vault/vaultonly.md @@ -213,7 +216,8 @@ check "adoption keeps vault-only file" vault-only (cat $a/vault/vaultonly.md) check "adoption replaced dir with link" true (test -L $a/live/memory; and echo true; or echo false) # Repins a link that points somewhere else. -set -l p (mktemp -d); set -ga TMPDIRS $p +set -l p (mktemp -d) +set -ga TMPDIRS $p mkdir -p $p/one $p/two ln -s $p/one $p/link _agents_repo_ensure_symlink $p/link $p/two >/dev/null @@ -239,12 +243,14 @@ check "idempotent, silent" "" "$out" # guarantee, and the old pull-first shape broke it: a failed fetch took # the local commit down with it. The divergence is still built here so # that guarantee is tested against the case that used to fail. -set -l origin (mktemp -d); set -ga TMPDIRS $origin +set -l origin (mktemp -d) +set -ga TMPDIRS $origin git -C $origin init -q --bare git -C $s remote add origin $origin git -C $s push -q -u origin HEAD:refs/heads/main 2>/dev/null -set -l clone (mktemp -d); set -ga TMPDIRS $clone +set -l clone (mktemp -d) +set -ga TMPDIRS $clone git clone -q $origin $clone git -C $clone config user.email t@t git -C $clone config user.name t @@ -297,7 +303,8 @@ set -l h (new_repo) echo first >$h/a.md _agents_repo_sync $h "chore: init" >/dev/null -set -l hooks (mktemp -d); set -ga TMPDIRS $hooks +set -l hooks (mktemp -d) +set -ga TMPDIRS $hooks printf '#!/bin/sh\nexit 1\n' >$hooks/pre-commit chmod +x $hooks/pre-commit git -C $h config core.hooksPath $hooks @@ -312,8 +319,10 @@ check "commit hook rejection commits nothing" $before_hook_count (git -C $h rev- echo "" echo "== agents-vault (scaffold + link) ==" -set -l vroot (mktemp -d); set -ga TMPDIRS $vroot -set -l croot (mktemp -d); set -ga TMPDIRS $croot +set -l vroot (mktemp -d) +set -ga TMPDIRS $vroot +set -l croot (mktemp -d) +set -ga TMPDIRS $croot set -g __fish_agent_vault_dir $vroot/agent-vault set -g __fish_agent_vault_claude_root $croot @@ -359,8 +368,10 @@ set -e __fish_agent_vault_claude_root echo "" echo "== agents-vault (emergent restore) ==" -set -l vroot2 (mktemp -d); set -ga TMPDIRS $vroot2 -set -l croot2 (mktemp -d); set -ga TMPDIRS $croot2 +set -l vroot2 (mktemp -d) +set -ga TMPDIRS $vroot2 +set -l croot2 (mktemp -d) +set -ga TMPDIRS $croot2 set -g __fish_agent_vault_dir $vroot2/agent-vault set -g __fish_agent_vault_claude_root $croot2 @@ -393,8 +404,10 @@ set -e __fish_agent_vault_claude_root echo "" echo "== agents-vault (link failure surfaces as exit 1) ==" -set -l vroot3 (mktemp -d); set -ga TMPDIRS $vroot3 -set -l croot3 (mktemp -d); set -ga TMPDIRS $croot3 +set -l vroot3 (mktemp -d) +set -ga TMPDIRS $vroot3 +set -l croot3 (mktemp -d) +set -ga TMPDIRS $croot3 set -g __fish_agent_vault_dir $vroot3/agent-vault set -g __fish_agent_vault_claude_root $croot3 @@ -420,8 +433,10 @@ set -e __fish_agent_vault_claude_root echo "" echo "== agents-vault (slug migration) ==" -set -l vroot2 (mktemp -d); set -ga TMPDIRS $vroot2 -set -l croot2 (mktemp -d); set -ga TMPDIRS $croot2 +set -l vroot2 (mktemp -d) +set -ga TMPDIRS $vroot2 +set -l croot2 (mktemp -d) +set -ga TMPDIRS $croot2 set -g __fish_agent_vault_dir $vroot2/agent-vault set -g __fish_agent_vault_claude_root $croot2 @@ -429,7 +444,7 @@ set -g __fish_agent_vault_claude_root $croot2 set -l mp (new_repo) set -l mmangled (string replace -a '/' '-' -- $mp | string replace -a '.' '-') mkdir -p $croot2/$mmangled/memory -echo "precious" >$croot2/$mmangled/memory/keep.md +echo precious >$croot2/$mmangled/memory/keep.md pushd $mp >/dev/null agents-vault --silent @@ -495,8 +510,7 @@ set -l enew_slug git.rootiest.dev-rootiest-emptycase # hand-built shape is covered separately just below. mkdir -p $vroot2/agent-vault/projects/$enew_slug printf 'remote: %s\npath: %s\nhost: %s\n' \ - https://git.rootiest.dev/rootiest/emptycase.git /gone/elsewhere othermachine \ - >$vroot2/agent-vault/projects/$enew_slug/origin + https://git.rootiest.dev/rootiest/emptycase.git /gone/elsewhere othermachine >$vroot2/agent-vault/projects/$enew_slug/origin pushd $emp >/dev/null set -l erc (agents-vault --silent 2>/dev/null; echo $status) @@ -579,7 +593,8 @@ check "removal: old remote entry removed" false (test -d $vroot2/agent-vault/pro # still find and adopt it. Before the fix, the fallback only lowercased # the basename instead of sanitizing it like _agents_repo_slug does, so it # could never match the real entry directory for a name like this. -set -l dirty_root (mktemp -d); set -ga TMPDIRS $dirty_root +set -l dirty_root (mktemp -d) +set -ga TMPDIRS $dirty_root set -l dp "$dirty_root/My Project!" mkdir -p "$dp" git -C "$dp" init -q @@ -590,7 +605,7 @@ git -C "$dp" config core.hooksPath /dev/null set -l dmangled (string replace -a '/' '-' -- $dp | string replace -a '.' '-') mkdir -p $croot2/$dmangled/memory -echo "dirty-precious" >$croot2/$dmangled/memory/keep.md +echo dirty-precious >$croot2/$dmangled/memory/keep.md pushd $dp >/dev/null agents-vault --silent @@ -621,7 +636,8 @@ check "fallback old entry removed" false (test -d $vroot2/agent-vault/projects/$ # is not: a --silent run that did the right thing and returned 0 still # printed "rm: cannot remove ...: Is a directory", which is the only # thing the user sees and reads as a failure. -set -l real_root (mktemp -d); set -ga TMPDIRS $real_root +set -l real_root (mktemp -d) +set -ga TMPDIRS $real_root set -l rp "$real_root/proj" mkdir -p "$rp" git -C "$rp" init -q @@ -632,7 +648,7 @@ git -C "$rp" config core.hooksPath /dev/null set -l rmangled (string replace -a '/' '-' -- $rp | string replace -a '.' '-') mkdir -p $croot2/$rmangled/memory -echo "banked" >$croot2/$rmangled/memory/old.md +echo banked >$croot2/$rmangled/memory/old.md pushd $rp >/dev/null agents-vault --silent @@ -643,10 +659,11 @@ popd >/dev/null # never seen, then change the slug so the migration runs. rm -f $croot2/$rmangled/memory mkdir -p $croot2/$rmangled/memory -echo "written-live" >$croot2/$rmangled/memory/fresh.md +echo written-live >$croot2/$rmangled/memory/fresh.md git -C $rp remote add origin https://git.rootiest.dev/rootiest/realdir.git -set -l rerr (mktemp); set -ga TMPDIRS $rerr +set -l rerr (mktemp) +set -ga TMPDIRS $rerr pushd $rp >/dev/null set -l real_rc (agents-vault --silent 2>$rerr; echo $status) popd >/dev/null @@ -673,8 +690,10 @@ echo "" echo "== agents-vault (a real git clone) ==" # Machine A: populate a vault and let agents-vault commit it. -set -l cl_vroot (mktemp -d); set -ga TMPDIRS $cl_vroot -set -l cl_croot (mktemp -d); set -ga TMPDIRS $cl_croot +set -l cl_vroot (mktemp -d) +set -ga TMPDIRS $cl_vroot +set -l cl_croot (mktemp -d) +set -ga TMPDIRS $cl_croot set -g __fish_agent_vault_dir $cl_vroot/agent-vault set -g __fish_agent_vault_claude_root $cl_croot @@ -698,7 +717,8 @@ agents-vault --silent popd >/dev/null # The clone, exactly as the README tells a user to make it. -set -l cl_new (mktemp -d); set -ga TMPDIRS $cl_new +set -l cl_new (mktemp -d) +set -ga TMPDIRS $cl_new git clone -q $cl_vroot/agent-vault $cl_new/agent-vault git -C $cl_new/agent-vault config user.email t@t git -C $cl_new/agent-vault config user.name t @@ -709,7 +729,8 @@ check "clone: the empty entry has no claude/ subtree" false (test -d $cl_new/age check "clone: the empty entry is its origin file alone" true (test -f $cl_new/agent-vault/projects/$cl_empty_slug/origin; and echo true; or echo false) # Machine B, case 1: an ordinary run against the clone restores memory. -set -l cl_croot2 (mktemp -d); set -ga TMPDIRS $cl_croot2 +set -l cl_croot2 (mktemp -d) +set -ga TMPDIRS $cl_croot2 set -g __fish_agent_vault_dir $cl_new/agent-vault set -g __fish_agent_vault_claude_root $cl_croot2 @@ -762,10 +783,14 @@ set -e __fish_agent_vault_claude_root echo "" echo "== agents-vault (global state) ==" -set -l vroot5 (mktemp -d); set -ga TMPDIRS $vroot5 -set -l croot5 (mktemp -d); set -ga TMPDIRS $croot5 -set -l chome5 (mktemp -d); set -ga TMPDIRS $chome5 -set -l agy5 (mktemp -d); set -ga TMPDIRS $agy5 +set -l vroot5 (mktemp -d) +set -ga TMPDIRS $vroot5 +set -l croot5 (mktemp -d) +set -ga TMPDIRS $croot5 +set -l chome5 (mktemp -d) +set -ga TMPDIRS $chome5 +set -l agy5 (mktemp -d) +set -ga TMPDIRS $agy5 set -g __fish_agent_vault_dir $vroot5/agent-vault set -g __fish_agent_vault_claude_root $croot5 set -g __fish_agent_vault_claude_home $chome5 @@ -802,7 +827,8 @@ printf 'transcript\n' >$agy5/knowledge/session.jsonl # the store boundary has to hold on its own. $outside5 stands in for that # home: a directory the store has no business reaching into, planted with # exactly the shapes that qualify. -set -l outside5 (mktemp -d); set -ga TMPDIRS $outside5 +set -l outside5 (mktemp -d) +set -ga TMPDIRS $outside5 mkdir -p $outside5/nested echo SECRET-OUTSIDE-KNOWLEDGE >$outside5/leaked.json echo SECRET-OUTSIDE-KNOWLEDGE >$outside5/target.md @@ -905,10 +931,14 @@ set -e __fish_agent_vault_claude_root echo "" echo "== agents-vault (global emergent restore) ==" -set -l vroot6 (mktemp -d); set -ga TMPDIRS $vroot6 -set -l croot6 (mktemp -d); set -ga TMPDIRS $croot6 -set -l chome6 (mktemp -d); set -ga TMPDIRS $chome6 -set -l agy6 (mktemp -d); set -ga TMPDIRS $agy6 +set -l vroot6 (mktemp -d) +set -ga TMPDIRS $vroot6 +set -l croot6 (mktemp -d) +set -ga TMPDIRS $croot6 +set -l chome6 (mktemp -d) +set -ga TMPDIRS $chome6 +set -l agy6 (mktemp -d) +set -ga TMPDIRS $agy6 set -g __fish_agent_vault_dir $vroot6/agent-vault set -g __fish_agent_vault_claude_root $croot6 set -g __fish_agent_vault_claude_home $chome6 @@ -927,8 +957,10 @@ check "global restore: vault content readable through the link" restored-global # A home with neither side populated must not have a memory/ invented for # it: ~/.claude/memory does not exist by default. -set -l chome7 (mktemp -d); set -ga TMPDIRS $chome7 -set -l vroot7 (mktemp -d); set -ga TMPDIRS $vroot7 +set -l chome7 (mktemp -d) +set -ga TMPDIRS $chome7 +set -l vroot7 (mktemp -d) +set -ga TMPDIRS $vroot7 set -g __fish_agent_vault_dir $vroot7/agent-vault set -g __fish_agent_vault_claude_home $chome7 set -l gp7 (new_repo https://git.rootiest.dev/rootiest/globals-absent.git) @@ -951,10 +983,14 @@ set -g __fish_agent_vault_agy_root $HERMETIC_HOME/agy echo "" echo "== agents-vault (global link failure is non-fatal) ==" -set -l vroot8 (mktemp -d); set -ga TMPDIRS $vroot8 -set -l croot8 (mktemp -d); set -ga TMPDIRS $croot8 -set -l chome8 (mktemp -d); set -ga TMPDIRS $chome8 -set -l agy8 (mktemp -d); set -ga TMPDIRS $agy8 +set -l vroot8 (mktemp -d) +set -ga TMPDIRS $vroot8 +set -l croot8 (mktemp -d) +set -ga TMPDIRS $croot8 +set -l chome8 (mktemp -d) +set -ga TMPDIRS $chome8 +set -l agy8 (mktemp -d) +set -ga TMPDIRS $agy8 set -g __fish_agent_vault_dir $vroot8/agent-vault set -g __fish_agent_vault_claude_root $croot8 set -g __fish_agent_vault_claude_home $chome8 @@ -972,7 +1008,8 @@ set -l fmangled (string replace -a '/' '-' -- $fp | string replace -a '.' '-') mkdir -p $croot8/$fmangled/memory echo project-memory >$croot8/$fmangled/memory/p.md -set -l ferr (mktemp); set -ga TMPDIRS $ferr +set -l ferr (mktemp) +set -ga TMPDIRS $ferr pushd $fp >/dev/null set -l frc (agents-vault --silent 2>$ferr; echo $status) popd >/dev/null @@ -991,7 +1028,8 @@ check "global link failure: per-project memory is committed" true (git -C $vroot # A failed global link must not be recorded as done: the next run has to # re-enter the block and warn again, not treat the vault as correct. pushd $fp >/dev/null -set -l ferr2 (mktemp); set -ga TMPDIRS $ferr2 +set -l ferr2 (mktemp) +set -ga TMPDIRS $ferr2 agents-vault --silent 2>$ferr2 popd >/dev/null check "global link failure: retried on the next run" true (string match -q "*$chome8/memory*" -- (cat $ferr2); and echo true; or echo false) @@ -1008,17 +1046,22 @@ set -g __fish_agent_vault_agy_root $HERMETIC_HOME/agy echo "" echo "== agents-vault (status, remote, adopt) ==" -set -l vroot9 (mktemp -d); set -ga TMPDIRS $vroot9 -set -l croot9 (mktemp -d); set -ga TMPDIRS $croot9 -set -l chome9 (mktemp -d); set -ga TMPDIRS $chome9 -set -l agy9 (mktemp -d); set -ga TMPDIRS $agy9 +set -l vroot9 (mktemp -d) +set -ga TMPDIRS $vroot9 +set -l croot9 (mktemp -d) +set -ga TMPDIRS $croot9 +set -l chome9 (mktemp -d) +set -ga TMPDIRS $chome9 +set -l agy9 (mktemp -d) +set -ga TMPDIRS $agy9 set -g __fish_agent_vault_dir $vroot9/agent-vault set -g __fish_agent_vault_claude_root $croot9 set -g __fish_agent_vault_claude_home $chome9 set -g __fish_agent_vault_agy_root $agy9 # A report asked for before the vault exists must say so, not scaffold one. -set -l s0out (mktemp); set -ga TMPDIRS $s0out +set -l s0out (mktemp) +set -ga TMPDIRS $s0out set -l s0rc (agents-vault --status >$s0out; echo $status) check "status without a vault exits 0" 0 "$s0rc" check "status without a vault says so" true (string match -q '*no vault*' -- (cat $s0out); and echo true; or echo false) @@ -1068,7 +1111,8 @@ check "status reports the remote" true (string match -q '*rootiest/agent-vault.g # A *failed* remote update must return non-zero. Reporting success after a # git command that did not run is the same silent-false-success shape that # a hook-rejected commit produced earlier in this project. -set -l rerr (mktemp); set -ga TMPDIRS $rerr +set -l rerr (mktemp) +set -ga TMPDIRS $rerr set -l rshim (failing_shim git set-url) set -l rpath $PATH set PATH $rshim $PATH @@ -1106,10 +1150,11 @@ agents-vault --silent popd >/dev/null set -l projects_before (command ls -A $vroot9/agent-vault/projects | sort | string join ',') -set -l bad_slugs ../escape has/slash . .. 'UPPER' 'sp ace' '' +set -l bad_slugs ../escape has/slash . .. UPPER 'sp ace' '' pushd $bp >/dev/null for bad in $bad_slugs - set -l berr (mktemp); set -ga TMPDIRS $berr + set -l berr (mktemp) + set -ga TMPDIRS $berr set -l brc (agents-vault --adopt=$bad --silent 2>$berr; echo $status) check "adopt refuses '$bad'" 1 "$brc" check "adopt refuses '$bad' out loud" true (string match -q '*invalid*' -- (cat $berr); and echo true; or echo false) @@ -1150,7 +1195,8 @@ set -l pre_head (git -C $vroot9/agent-vault rev-list --count HEAD) set -l pre_porcelain (git -C $vroot9/agent-vault status --porcelain | string join ',') set -l pre_link (path resolve $croot9/$tmang/memory) -set -l terr (mktemp); set -ga TMPDIRS $terr +set -l terr (mktemp) +set -ga TMPDIRS $terr set -l tshim (failing_shim rm $tmang) set -l tpath $PATH set PATH $tshim $PATH @@ -1203,8 +1249,7 @@ popd >/dev/null # The target must be *tracked* for this to bite: stashing it takes files # git knows about out from under the index, which is the whole hazard. mkdir -p $vroot9/agent-vault/projects/stash-target/claude/memory -printf 'remote: (none)\npath: %s\nhost: t\n' /nowhere \ - >$vroot9/agent-vault/projects/stash-target/origin +printf 'remote: (none)\npath: %s\nhost: t\n' /nowhere >$vroot9/agent-vault/projects/stash-target/origin pushd $sa >/dev/null agents-vault --silent popd >/dev/null @@ -1245,8 +1290,7 @@ agents-vault --silent popd >/dev/null mkdir -p $vroot9/agent-vault/projects/stashfail-target/claude/memory -printf 'remote: (none)\npath: %s\nhost: t\n' /nowhere-else \ - >$vroot9/agent-vault/projects/stashfail-target/origin +printf 'remote: (none)\npath: %s\nhost: t\n' /nowhere-else >$vroot9/agent-vault/projects/stashfail-target/origin pushd $sf >/dev/null agents-vault --silent popd >/dev/null @@ -1257,7 +1301,8 @@ set -l sf_porcelain (git -C $vroot9/agent-vault status --porcelain | string join set -l sf_link (path resolve $croot9/$sfmang/memory) check "stash adopt rollback: vault clean before the adopt" "" "$sf_porcelain" -set -l sferr (mktemp); set -ga TMPDIRS $sferr +set -l sferr (mktemp) +set -ga TMPDIRS $sferr set -l sfshim (failing_shim rm $sfmang) set -l sfpath $PATH set PATH $sfshim $PATH @@ -1359,10 +1404,14 @@ set -g __fish_agent_vault_agy_root $HERMETIC_HOME/agy echo "" echo "== agents-vault (restore) ==" -set -l vroot10 (mktemp -d); set -ga TMPDIRS $vroot10 -set -l croot10 (mktemp -d); set -ga TMPDIRS $croot10 -set -l chome10 (mktemp -d); set -ga TMPDIRS $chome10 -set -l agy10 (mktemp -d); set -ga TMPDIRS $agy10 +set -l vroot10 (mktemp -d) +set -ga TMPDIRS $vroot10 +set -l croot10 (mktemp -d) +set -ga TMPDIRS $croot10 +set -l chome10 (mktemp -d) +set -ga TMPDIRS $chome10 +set -l agy10 (mktemp -d) +set -ga TMPDIRS $agy10 set -g __fish_agent_vault_dir $vroot10/agent-vault set -g __fish_agent_vault_claude_root $croot10 set -g __fish_agent_vault_claude_home $chome10 @@ -1389,9 +1438,9 @@ check "restore: names what it restored" true (string match -q '*restoreme*' -- " # succeeds and says which entry needs --adopt. mkdir -p $vroot10/agent-vault/projects/ghost-entry/claude/memory echo x >$vroot10/agent-vault/projects/ghost-entry/claude/memory/x.md -printf 'remote: (none)\npath: %s\nhost: t\n' $vroot10/gone-forever \ - >$vroot10/agent-vault/projects/ghost-entry/origin -set -l r2out (mktemp); set -ga TMPDIRS $r2out +printf 'remote: (none)\npath: %s\nhost: t\n' $vroot10/gone-forever >$vroot10/agent-vault/projects/ghost-entry/origin +set -l r2out (mktemp) +set -ga TMPDIRS $r2out set -l r2rc (agents-vault --restore >$r2out; echo $status) check "restore: exits 0 with an unplaceable entry" 0 "$r2rc" check "restore: reports the unplaceable entry" true (string match -q '*ghost-entry*' -- (cat $r2out); and echo true; or echo false) @@ -1406,8 +1455,7 @@ set -l dotp (new_repo https://git.rootiest.dev/rootiest/dotted.git) set -l dotmang (string replace -a '/' '-' -- $dotp | string replace -a '.' '-') mkdir -p $vroot10/agent-vault/projects/.dot-entry/claude/memory echo dot-precious >$vroot10/agent-vault/projects/.dot-entry/claude/memory/keep.md -printf 'remote: (none)\npath: %s\nhost: t\n' $dotp \ - >$vroot10/agent-vault/projects/.dot-entry/origin +printf 'remote: (none)\npath: %s\nhost: t\n' $dotp >$vroot10/agent-vault/projects/.dot-entry/origin check "dot-led entry: a glob really does skip it" false (string match -q '*.dot-entry*' -- (echo $vroot10/agent-vault/projects/*); and echo true; or echo false) set -l dotreport (agents-vault --status) @@ -1425,7 +1473,8 @@ rm -f $croot10/$rmang/memory set -l r3shim (failing_shim ln $rmang) set -l r3path $PATH set PATH $r3shim $PATH -set -l r3err (mktemp); set -ga TMPDIRS $r3err +set -l r3err (mktemp) +set -ga TMPDIRS $r3err set -l r3rc (agents-vault --restore >/dev/null 2>$r3err; echo $status) set PATH $r3path check "restore: a failed relink returns non-zero" 1 "$r3rc" @@ -1440,11 +1489,16 @@ set -g __fish_agent_vault_agy_root $HERMETIC_HOME/agy echo "" echo "== agents-vault (push) ==" -set -l vroot11 (mktemp -d); set -ga TMPDIRS $vroot11 -set -l croot11 (mktemp -d); set -ga TMPDIRS $croot11 -set -l chome11 (mktemp -d); set -ga TMPDIRS $chome11 -set -l agy11 (mktemp -d); set -ga TMPDIRS $agy11 -set -l bare (mktemp -d); set -ga TMPDIRS $bare +set -l vroot11 (mktemp -d) +set -ga TMPDIRS $vroot11 +set -l croot11 (mktemp -d) +set -ga TMPDIRS $croot11 +set -l chome11 (mktemp -d) +set -ga TMPDIRS $chome11 +set -l agy11 (mktemp -d) +set -ga TMPDIRS $agy11 +set -l bare (mktemp -d) +set -ga TMPDIRS $bare git init -q --bare $bare set -g __fish_agent_vault_dir $vroot11/agent-vault set -g __fish_agent_vault_claude_root $croot11 @@ -1459,7 +1513,8 @@ echo pushed >$croot11/$pmang/memory/p.md # --push with no remote must fail loudly. The commit still happened, so # silently returning 0 would read as "backed up off this machine". -set -l perr (mktemp); set -ga TMPDIRS $perr +set -l perr (mktemp) +set -ga TMPDIRS $perr pushd $pp >/dev/null set -l prc0 (agents-vault --push --silent 2>$perr; echo $status) popd >/dev/null @@ -1498,7 +1553,8 @@ check "autopush pushes when enabled" pushed-auto (git -C $bare show $vbranch:pro set -l deadremote $vroot11/not-a-repo.git agents-vault --remote=$deadremote --silent echo pushed-never >$croot11/$pmang/memory/p4.md -set -l fperr (mktemp); set -ga TMPDIRS $fperr +set -l fperr (mktemp) +set -ga TMPDIRS $fperr pushd $pp >/dev/null set -l fprc (agents-vault --push --silent 2>$fperr; echo $status) popd >/dev/null @@ -1510,7 +1566,8 @@ check "failing push still committed locally" true (git -C $vroot11/agent-vault l # saying "Synced" must not come with a zero exit when the push failed. echo pushed-never-2 >$croot11/$pmang/memory/p5.md set -g __fish_agent_vault_autopush 1 -set -l fp2err (mktemp); set -ga TMPDIRS $fp2err +set -l fp2err (mktemp) +set -ga TMPDIRS $fp2err pushd $pp >/dev/null set -l fp2rc (agents-vault --quiet 2>$fp2err >/dev/null; echo $status) popd >/dev/null @@ -1561,10 +1618,14 @@ set -g __fish_agent_vault_agy_root $HERMETIC_HOME/agy echo "" echo "== agents-vault (the user's ssh command wins) ==" -set -l vroot14 (mktemp -d); set -ga TMPDIRS $vroot14 -set -l croot14 (mktemp -d); set -ga TMPDIRS $croot14 -set -l chome14 (mktemp -d); set -ga TMPDIRS $chome14 -set -l agy14 (mktemp -d); set -ga TMPDIRS $agy14 +set -l vroot14 (mktemp -d) +set -ga TMPDIRS $vroot14 +set -l croot14 (mktemp -d) +set -ga TMPDIRS $croot14 +set -l chome14 (mktemp -d) +set -ga TMPDIRS $chome14 +set -l agy14 (mktemp -d) +set -ga TMPDIRS $agy14 set -g __fish_agent_vault_dir $vroot14/agent-vault set -g __fish_agent_vault_claude_root $croot14 set -g __fish_agent_vault_claude_home $chome14 @@ -1574,7 +1635,8 @@ set -g __fish_agent_vault_agy_root $agy14 # plain `ssh` (what the injected default resolves to) and one named # explicitly by the user. Both refuse the connection, so nothing leaves # the machine and no test waits on a network. -set -l sbin (mktemp -d); set -ga TMPDIRS $sbin +set -l sbin (mktemp -d) +set -ga TMPDIRS $sbin set -l pathssh_log $sbin/path-ssh.log set -l usessh $sbin/user-ssh set -l usessh_log $sbin/user-ssh.log @@ -1655,11 +1717,16 @@ set -g __fish_agent_vault_agy_root $HERMETIC_HOME/agy echo "" echo "== agents-vault (autopush without timeout) ==" -set -l vroot15 (mktemp -d); set -ga TMPDIRS $vroot15 -set -l croot15 (mktemp -d); set -ga TMPDIRS $croot15 -set -l chome15 (mktemp -d); set -ga TMPDIRS $chome15 -set -l agy15 (mktemp -d); set -ga TMPDIRS $agy15 -set -l bare15 (mktemp -d); set -ga TMPDIRS $bare15 +set -l vroot15 (mktemp -d) +set -ga TMPDIRS $vroot15 +set -l croot15 (mktemp -d) +set -ga TMPDIRS $croot15 +set -l chome15 (mktemp -d) +set -ga TMPDIRS $chome15 +set -l agy15 (mktemp -d) +set -ga TMPDIRS $agy15 +set -l bare15 (mktemp -d) +set -ga TMPDIRS $bare15 git init -q --bare $bare15 set -g __fish_agent_vault_dir $vroot15/agent-vault set -g __fish_agent_vault_claude_root $croot15 @@ -1678,7 +1745,8 @@ popd >/dev/null # rev-parse would report the literal string HEAD with a fatal on stderr. set -l vb15 (git -C $vroot15/agent-vault symbolic-ref --short HEAD) -set -l shimroot (mktemp -d); set -ga TMPDIRS $shimroot +set -l shimroot (mktemp -d) +set -ga TMPDIRS $shimroot set -l nopath set -l shimn 0 for d in $PATH @@ -1696,7 +1764,8 @@ end set -l realpath15 $PATH echo t2 >$croot15/$tmang15/memory/t2.md -set -l terr (mktemp); set -ga TMPDIRS $terr +set -l terr (mktemp) +set -ga TMPDIRS $terr set -g __fish_agent_vault_autopush 1 set -g PATH $nopath check "the shimmed PATH really has no timeout" false (type -q timeout; and echo true; or echo false) @@ -1737,10 +1806,14 @@ set -g __fish_agent_vault_agy_root $HERMETIC_HOME/agy echo "" echo "== agents-vault (a failed vault commit is fatal) ==" -set -l vroot13 (mktemp -d); set -ga TMPDIRS $vroot13 -set -l croot13 (mktemp -d); set -ga TMPDIRS $croot13 -set -l chome13 (mktemp -d); set -ga TMPDIRS $chome13 -set -l agy13 (mktemp -d); set -ga TMPDIRS $agy13 +set -l vroot13 (mktemp -d) +set -ga TMPDIRS $vroot13 +set -l croot13 (mktemp -d) +set -ga TMPDIRS $croot13 +set -l chome13 (mktemp -d) +set -ga TMPDIRS $chome13 +set -l agy13 (mktemp -d) +set -ga TMPDIRS $agy13 set -g __fish_agent_vault_dir $vroot13/agent-vault set -g __fish_agent_vault_claude_root $croot13 set -g __fish_agent_vault_claude_home $chome13 @@ -1763,7 +1836,8 @@ printf '#!/bin/sh\nexit 1\n' >$vroot13/agent-vault/.agents-tools/hooks/pre-commi chmod +x $vroot13/agent-vault/.agents-tools/hooks/pre-commit echo hook-more >$croot13/$hmang13/memory/keep2.md set -l hhead13 (git -C $vroot13/agent-vault rev-list --count HEAD) -set -l herr13 (mktemp); set -ga TMPDIRS $herr13 +set -l herr13 (mktemp) +set -ga TMPDIRS $herr13 pushd $hp13 >/dev/null set -l hrc13 (agents-vault --silent 2>$herr13; echo $status) popd >/dev/null @@ -1782,16 +1856,21 @@ set -g __fish_agent_vault_agy_root $HERMETIC_HOME/agy # moves ahead (or goes out of reach) is not a backup; --push is where the # divergence has to be reckoned with, and where the two ways it can fail # have to be told apart. -set -l vroot14 (mktemp -d); set -ga TMPDIRS $vroot14 -set -l croot14 (mktemp -d); set -ga TMPDIRS $croot14 -set -l chome14 (mktemp -d); set -ga TMPDIRS $chome14 -set -l agy14 (mktemp -d); set -ga TMPDIRS $agy14 +set -l vroot14 (mktemp -d) +set -ga TMPDIRS $vroot14 +set -l croot14 (mktemp -d) +set -ga TMPDIRS $croot14 +set -l chome14 (mktemp -d) +set -ga TMPDIRS $chome14 +set -l agy14 (mktemp -d) +set -ga TMPDIRS $agy14 set -g __fish_agent_vault_dir $vroot14/agent-vault set -g __fish_agent_vault_claude_root $croot14 set -g __fish_agent_vault_claude_home $chome14 set -g __fish_agent_vault_agy_root $agy14 -set -l bare14 (mktemp -d); set -ga TMPDIRS $bare14 +set -l bare14 (mktemp -d) +set -ga TMPDIRS $bare14 git init -q --bare $bare14 set -l cp14 (new_repo https://git.rootiest.dev/rootiest/conflict.git) @@ -1806,7 +1885,8 @@ agents-vault --remote=$bare14 --silent git -C $vroot14/agent-vault push -q -u origin HEAD:refs/heads/main # Another machine records a conflicting change to the same line... -set -l cclone14 (mktemp -d); set -ga TMPDIRS $cclone14 +set -l cclone14 (mktemp -d) +set -ga TMPDIRS $cclone14 git clone -q $bare14 $cclone14 git -C $cclone14 config user.email t@t git -C $cclone14 config user.name t @@ -1819,7 +1899,8 @@ git -C $cclone14 push -q origin HEAD:main # ... while this one writes conflicting memory of its own on the same # line. The ordinary run has to commit it: it never fetches, so the # divergence is invisible to it and irrelevant. -set -l cerr14 (mktemp); set -ga TMPDIRS $cerr14 +set -l cerr14 (mktemp) +set -ga TMPDIRS $cerr14 echo ours >$croot14/$cmang14/memory/keep.md set -l chead14 (git -C $vroot14/agent-vault rev-list --count HEAD) pushd $cp14 >/dev/null @@ -1885,10 +1966,14 @@ set -g __fish_agent_vault_agy_root $HERMETIC_HOME/agy echo "" echo "== agents-vault (dangling global memory link) ==" -set -l vroot12 (mktemp -d); set -ga TMPDIRS $vroot12 -set -l croot12 (mktemp -d); set -ga TMPDIRS $croot12 -set -l chome12 (mktemp -d); set -ga TMPDIRS $chome12 -set -l agy12 (mktemp -d); set -ga TMPDIRS $agy12 +set -l vroot12 (mktemp -d) +set -ga TMPDIRS $vroot12 +set -l croot12 (mktemp -d) +set -ga TMPDIRS $croot12 +set -l chome12 (mktemp -d) +set -ga TMPDIRS $chome12 +set -l agy12 (mktemp -d) +set -ga TMPDIRS $agy12 set -g __fish_agent_vault_dir $vroot12/agent-vault set -g __fish_agent_vault_claude_root $croot12 set -g __fish_agent_vault_claude_home $chome12 @@ -1900,7 +1985,8 @@ check "dangling: -L is the only signal" true (test -L $chome12/memory; and echo check "dangling: the vault side is empty" false (test -e $vroot12/agent-vault/global/claude/memory; and echo true; or echo false) set -l dp (new_repo https://git.rootiest.dev/rootiest/dangling.git) -set -l derr (mktemp); set -ga TMPDIRS $derr +set -l derr (mktemp) +set -ga TMPDIRS $derr pushd $dp >/dev/null set -l drc (agents-vault --silent 2>$derr; echo $status) popd >/dev/null @@ -1934,7 +2020,8 @@ check "agents-init: committed the AGENTS repo" true (test (git -C $ip/AGENTS rev # Offline. The pull that used to run here blocked the launch until the # remote timed out and then took the commit down with it, so an agent's # edits went unrecorded on every launch away from the network. -set -l ibare (mktemp -d); set -ga TMPDIRS $ibare +set -l ibare (mktemp -d) +set -ga TMPDIRS $ibare git init -q --bare $ibare git -C $ip/AGENTS remote add origin $ibare git -C $ip/AGENTS push -q -u origin HEAD 2>/dev/null @@ -1957,7 +2044,8 @@ printf '#!/bin/sh\nexit 1\n' >$ip/AGENTS/.agents-tools/hooks/pre-commit chmod +x $ip/AGENTS/.agents-tools/hooks/pre-commit echo blocked >$ip/AGENTS/devlogs/blocked.md set -l ibhead (git -C $ip/AGENTS rev-list --count HEAD) -set -l ierr (mktemp); set -ga TMPDIRS $ierr +set -l ierr (mktemp) +set -ga TMPDIRS $ierr pushd $ip >/dev/null set -l ibrc (agents-init --silent 2>$ierr; echo $status) popd >/dev/null diff --git a/tests/test-guards.fish b/tests/test-guards.fish index 525e295..c459ad3 100644 --- a/tests/test-guards.fish +++ b/tests/test-guards.fish @@ -245,10 +245,10 @@ section "op_enabled: always/* and AND, via a synthetic registry" # this process reads them, so overriding them is free. set -g __fish_config_op_registry_keys "syn_on:" "syn_off:" "syn_and:" "syn_bare:" "syn_multi:" set -g __fish_config_op_registry_values \ - "always/on" \ - "always/off" \ + always/on \ + always/off \ "aliases/filesystem integrations/notifications" \ - "aliases" \ + aliases \ "always/off always/on" set -e __fish_config_op_aliases __fish_config_op_integrations __fish_config_opinionated @@ -312,7 +312,7 @@ section "op_enabled: C5 through the guard" # The path production code actually takes, as opposed to calling the cascade # directly. Same rule: unset means off and the master cannot enable it. set -g __fish_config_op_registry_keys "syn_log:" -set -g __fish_config_op_registry_values "logging/terminal-capture" +set -g __fish_config_op_registry_values logging/terminal-capture set -e __fish_config_op_logging __fish_config_op_logging_terminal_capture __fish_config_op_enabled syn_log diff --git a/tests/test-help.fish b/tests/test-help.fish index f87b375..7dd763c 100644 --- a/tests/test-help.fish +++ b/tests/test-help.fish @@ -45,7 +45,7 @@ function test_help_renderer 'function fixturefn' \ ' __fish_help_header (status current-function) $argv; and return 0' \ ' echo RAN-BODY' \ - 'end' >$tmp/fixturefn.fish + end >$tmp/fixturefn.fish set -l out (_help_probe $tmp 'fixturefn --help') set -l code $status @@ -106,14 +106,14 @@ function test_help_renderer_degrades_safely 'function headerless' \ ' __fish_help_header (status current-function) $argv; and return 0' \ " touch $tmp/BODY-RAN" \ - 'end' >$tmp/headerless.fish + end >$tmp/headerless.fish # A comment run carrying no `# LABEL` line at all. printf '%s\n' \ '# just an ordinary comment, no labels here' \ 'function malformed' \ ' __fish_help_header (status current-function) $argv; and return 0' \ " touch $tmp/BODY-RAN" \ - 'end' >$tmp/malformed.fish + end >$tmp/malformed.fish set -l failed 0 for fn in headerless malformed diff --git a/tests/test-network-fish.fish b/tests/test-network-fish.fish index aa2fbe5..2d170c1 100644 --- a/tests/test-network-fish.fish +++ b/tests/test-network-fish.fish @@ -57,22 +57,22 @@ printf '%s\n' \ '#!/bin/sh' \ 'if [ -n "$MOCK_CURL_LOG" ]; then' \ ' printf "%s\n" "$*" >> "$MOCK_CURL_LOG"' \ - 'fi' \ + fi \ 'if [ -n "$MOCK_CURL_HANDLER" ] && [ -x "$MOCK_CURL_HANDLER" ]; then' \ ' exec "$MOCK_CURL_HANDLER" "$@"' \ - 'fi' \ + fi \ 'if [ -n "$MOCK_CURL_DELAY" ]; then' \ ' sleep "$MOCK_CURL_DELAY"' \ - 'fi' \ + fi \ 'if [ -n "$MOCK_CURL_STDERR" ]; then' \ ' printf "%s\n" "$MOCK_CURL_STDERR" >&2' \ - 'fi' \ + fi \ 'if [ -f "$MOCK_CURL_BODY_FILE" ]; then' \ ' cat "$MOCK_CURL_BODY_FILE"' \ 'elif [ -n "$MOCK_CURL_BODY" ]; then' \ ' printf "%s\n" "$MOCK_CURL_BODY"' \ - 'fi' \ - 'exit "${MOCK_CURL_STATUS:-0}"' > $MOCK_DIR/curl + fi \ + 'exit "${MOCK_CURL_STATUS:-0}"' >$MOCK_DIR/curl chmod +x $MOCK_DIR/curl # 2. Mock git shim @@ -80,10 +80,10 @@ printf '%s\n' \ '#!/bin/sh' \ 'if [ -n "$MOCK_GIT_LOG" ]; then' \ ' printf "%s\n" "$*" >> "$MOCK_GIT_LOG"' \ - 'fi' \ + fi \ 'if [ -n "$MOCK_GIT_HANDLER" ] && [ -x "$MOCK_GIT_HANDLER" ]; then' \ ' exec "$MOCK_GIT_HANDLER" "$@"' \ - 'fi' \ + fi \ 'for a in "$@"; do' \ ' if [ "$a" = "fetch" ] && [ -n "$MOCK_GIT_FAIL_FETCH" ]; then' \ ' echo "fatal: unable to access: Could not resolve host" >&2' \ @@ -101,8 +101,8 @@ printf '%s\n' \ ' echo "fatal: unable to access: Could not resolve host" >&2' \ ' exit "${MOCK_GIT_LS_REMOTE_STATUS:-128}"' \ ' fi' \ - 'done' \ - "exec $real_git \"\$@\"" > $MOCK_DIR/git + done \ + "exec $real_git \"\$@\"" >$MOCK_DIR/git chmod +x $MOCK_DIR/git # 3. Mock bd CLI @@ -114,8 +114,8 @@ printf '%s\n' \ ' exit 0' \ 'elif [ "$1" = "sync" ]; then' \ ' exit 0' \ - 'fi' \ - 'exit 0' > $MOCK_DIR/bd + fi \ + 'exit 0' >$MOCK_DIR/bd chmod +x $MOCK_DIR/bd # 4. Mock qrencode @@ -123,9 +123,9 @@ printf '%s\n' \ '#!/bin/sh' \ 'if [ -n "$MOCK_QRENCODE_LOG" ]; then' \ ' printf "%s\n" "$*" >> "$MOCK_QRENCODE_LOG"' \ - 'fi' \ + fi \ 'echo "LOCAL_QRENCODE: $*"' \ - 'exit 0' > $MOCK_DIR/qrencode + 'exit 0' >$MOCK_DIR/qrencode chmod +x $MOCK_DIR/qrencode # Prepend MOCK_DIR to PATH so mocks take precedence @@ -158,7 +158,6 @@ function cleanup end end - # ───────────────────────────────────────────────────────────────────────────── # 1. gi (gitignore generator) # ───────────────────────────────────────────────────────────────────────────── @@ -227,7 +226,6 @@ begin builtin cd $prev_pwd end - # ───────────────────────────────────────────────────────────────────────────── # 2. gip, gip4, gip6 (IP resolution) # ───────────────────────────────────────────────────────────────────────────── @@ -248,8 +246,8 @@ printf '%s\n' \ ' echo "2001:db8::1"' \ ' exit 0' \ ' fi' \ - 'done' \ - 'exit 0' > $gip_handler + done \ + 'exit 0' >$gip_handler chmod +x $gip_handler reset_mocks @@ -304,7 +302,6 @@ set -l out_gip6_err (gip6 2>&1) check "gip6: failure exits 1" 1 $status check "gip6: failure prints notice" true (string match -q '*IPv6 is currently unavailable*' -- $out_gip6_err; and echo true; or echo false) - # ───────────────────────────────────────────────────────────────────────────── # 3. qr (QR code generator) # ───────────────────────────────────────────────────────────────────────────── @@ -320,31 +317,30 @@ check "qr: curl is never called when qrencode exists" 0 (test -f $MOCK_DIR/curl_ # Case B: Local qrencode is missing, falling back to curl function type - if test "$argv[1]" = "-q" -a "$argv[2]" = "qrencode" + if test "$argv[1]" = -q -a "$argv[2]" = qrencode return 1 end builtin type $argv end -set -gx MOCK_CURL_BODY "UTF8_QR_BODY" +set -gx MOCK_CURL_BODY UTF8_QR_BODY set -l qr_curl (qr "hello-curl") -check "qr: fallback to curl when qrencode is missing" "UTF8_QR_BODY" "$qr_curl" +check "qr: fallback to curl when qrencode is missing" UTF8_QR_BODY "$qr_curl" # Network drop during curl fallback set -gx MOCK_CURL_STATUS 7 set -gx MOCK_CURL_BODY "" -qr "fail" >/dev/null 2>&1 +qr fail >/dev/null 2>&1 check "qr: curl network drop returns non-zero" 7 $status # Argument-based curl fallback set -gx MOCK_CURL_STATUS 0 -set -gx MOCK_CURL_BODY "TEXT_QR" +set -gx MOCK_CURL_BODY TEXT_QR set -l qr_arg (qr "arg-text") -check "qr: argument works via curl fallback" "TEXT_QR" "$qr_arg" +check "qr: argument works via curl fallback" TEXT_QR "$qr_arg" functions -e type - # ───────────────────────────────────────────────────────────────────────────── # 4. bd-pull (Gitea issues sync) # ───────────────────────────────────────────────────────────────────────────── @@ -361,13 +357,13 @@ begin bd-pull rootiest/test >/dev/null 2>&1 check "bd-pull: missing GITEA_TOKEN exits 1" 1 $status - set -gx GITEA_TOKEN "test_token" + set -gx GITEA_TOKEN test_token bd-pull rootiest/test >/dev/null 2>&1 check "bd-pull: missing GITEA_URL exits 1" 1 $status end # Complete network drop / empty response -set -gx GITEA_TOKEN "dummy_token" +set -gx GITEA_TOKEN dummy_token set -gx GITEA_URL "https://git.test" set -gx MOCK_CURL_STATUS 7 set -gx MOCK_CURL_BODY "" @@ -409,7 +405,6 @@ begin builtin cd $prev_pwd end - # ───────────────────────────────────────────────────────────────────────────── # 5. _auto_pull_sync (background fast-forward) # ───────────────────────────────────────────────────────────────────────────── @@ -422,9 +417,9 @@ check "_auto_pull_sync: non-git directory returns 1" 1 $status # Git repo without upstream branch set -l sync_repo (new_repo) -echo "test" > $sync_repo/file.txt +echo test >$sync_repo/file.txt git -C $sync_repo add file.txt -git -C $sync_repo commit -q -m "initial" +git -C $sync_repo commit -q -m initial _auto_pull_sync $sync_repo >/dev/null 2>&1 check "_auto_pull_sync: missing upstream returns 1" 1 $status @@ -435,13 +430,13 @@ git -C $sync_repo remote add origin $sync_upstream git -C $sync_repo push -q -u origin main >/dev/null 2>&1 # Dirty working tree (unstaged modifications) -echo "dirty" >> $sync_repo/file.txt +echo dirty >>$sync_repo/file.txt _auto_pull_sync $sync_repo >/dev/null 2>&1 check "_auto_pull_sync: dirty worktree returns 1" 1 $status git -C $sync_repo checkout -q -- file.txt # Dirty index (staged modifications) -echo "staged" >> $sync_repo/staged.txt +echo staged >>$sync_repo/staged.txt git -C $sync_repo add staged.txt _auto_pull_sync $sync_repo >/dev/null 2>&1 check "_auto_pull_sync: dirty index returns 1" 1 $status @@ -458,7 +453,7 @@ reset_mocks # Add commit to upstream set -l peer_clone (new_repo) git -C $peer_clone clone -q $sync_upstream $peer_clone/work -echo "new commit" > $peer_clone/work/new.txt +echo "new commit" >$peer_clone/work/new.txt git -C $peer_clone/work add new.txt git -C $peer_clone/work commit -q -m "upstream work" git -C $peer_clone/work push -q origin main @@ -467,7 +462,6 @@ _auto_pull_sync $sync_repo >/dev/null 2>&1 check "_auto_pull_sync: clean fast-forward returns 0" 0 $status check "_auto_pull_sync: changes merged into working tree" true (test -f $sync_repo/new.txt; and echo true; or echo false) - # ───────────────────────────────────────────────────────────────────────────── # 6. gitup (fetch and status) # ───────────────────────────────────────────────────────────────────────────── @@ -482,7 +476,7 @@ begin set -l r (new_repo) builtin cd $r - echo a > a && git add a && git commit -q -m a + echo a >a && git add a && git commit -q -m a # Network failure on git fetch set -gx MOCK_GIT_FAIL_FETCH 1 @@ -497,7 +491,6 @@ begin builtin cd $prev_pwd end - # ───────────────────────────────────────────────────────────────────────────── # 7. git-clean (fetch --prune and delete orphaned branches) # ───────────────────────────────────────────────────────────────────────────── @@ -511,7 +504,7 @@ begin set -l prev_pwd $PWD set -l r (new_repo) builtin cd $r - echo a > a && git add a && git commit -q -m a + echo a >a && git add a && git commit -q -m a # Network drop during git fetch --prune set -gx MOCK_GIT_FAIL_FETCH 1 @@ -533,8 +526,8 @@ begin ' echo " orphaned-feat abcdef0 [origin/orphaned-feat: gone] feature"' \ ' exit 0' \ ' fi' \ - 'done' \ - "exec $real_git \"\$@\"" > $clean_git_handler + done \ + "exec $real_git \"\$@\"" >$clean_git_handler chmod +x $clean_git_handler set -gx MOCK_GIT_HANDLER $clean_git_handler @@ -545,7 +538,6 @@ begin builtin cd $prev_pwd end - # ───────────────────────────────────────────────────────────────────────────── # 8. config-update (configuration repository sync) # ───────────────────────────────────────────────────────────────────────────── @@ -563,8 +555,8 @@ printf '%s\n' \ ' if [ "$a" = "fetch" ] && [ -n "$MOCK_CFG_FAIL_FETCH" ]; then' \ ' exit 1' \ ' fi' \ - 'done' \ - "exec $real_git \"\$@\"" > $cfg_update_handler + done \ + "exec $real_git \"\$@\"" >$cfg_update_handler chmod +x $cfg_update_handler set -gx MOCK_GIT_HANDLER $cfg_update_handler @@ -584,7 +576,6 @@ begin check "config-update: network fetch failure returns 1" 1 $status end - # ───────────────────────────────────────────────────────────────────────────── # 9. repo-open (origin URL normalization and browser deep-linking) # ───────────────────────────────────────────────────────────────────────────── @@ -616,7 +607,7 @@ end # In git repo with GitHub origin remote set -l r_gh (new_repo) git -C $r_gh remote add origin "https://github.com/rootiest/fish-config.git" -echo init > $r_gh/file && git -C $r_gh add file && git -C $r_gh commit -q -m init +echo init >$r_gh/file && git -C $r_gh add file && git -C $r_gh commit -q -m init begin set -l prev_pwd $PWD builtin cd $r_gh @@ -636,8 +627,8 @@ begin ' echo "abcdef01 refs/heads/main"' \ ' exit 0' \ ' fi' \ - 'done' \ - "exec $real_git \"\$@\"" > $gh_ls_handler + done \ + "exec $real_git \"\$@\"" >$gh_ls_handler chmod +x $gh_ls_handler set -gx MOCK_GIT_HANDLER $gh_ls_handler @@ -656,7 +647,6 @@ begin builtin cd $prev_pwd end - # ───────────────────────────────────────────────────────────────────────────── # 10. fzf-update (fzf install / git pull) # ───────────────────────────────────────────────────────────────────────────── @@ -686,7 +676,6 @@ begin check "fzf-update: git clone network drop returns non-zero" true (test $status -ne 0; and echo true; or echo false) end - # ───────────────────────────────────────────────────────────────────────────── # Teardown and Final Report # ───────────────────────────────────────────────────────────────────────────── -- 2.54.0 From d926a632d5e29e3cbb7fbcc2cec1e7d00eb15c6b Mon Sep 17 00:00:00 2001 From: Rootiest Date: Wed, 9 Sep 2026 20:32:43 -0400 Subject: [PATCH 4/8] test: enforce fish_indent --check in test runner --- tests/run-tests.fish | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/tests/run-tests.fish b/tests/run-tests.fish index ace5741..2ff2f91 100755 --- a/tests/run-tests.fish +++ b/tests/run-tests.fish @@ -3,7 +3,7 @@ # SPDX-License-Identifier: AGPL-3.0-or-later # # CI test runner for this fish configuration. -# 1. Syntax-lints every tracked .fish file (fish -n). +# 1. Syntax-lints and indent-checks every tracked .fish file (fish -n, fish_indent --check). # 2. Discovers tests/test-*.fish and reads the mode each suite declares # in its own header (`# MODE: isolated` or `# MODE: in-session`). # 3. Runs each isolated suite as its own --no-config fish process with @@ -24,25 +24,32 @@ set -l script_dir (realpath (dirname (status filename))) set -l repo_root (realpath $script_dir/..) set -l overall_failed 0 -# ---- Phase 1: syntax lint ------------------------------------------------ -echo "== Syntax lint ==" +# ---- Phase 1: syntax & indent lint --------------------------------------- +echo "== Syntax & indent lint ==" set -l lint_files $repo_root/config.fish -for dir in functions conf.d completions integrations +for dir in functions conf.d completions integrations tests set -a lint_files (find $repo_root/$dir -name '*.fish' | sort) end -set -l lint_failed 0 +set -l syntax_failed 0 +set -l indent_failed 0 for f in $lint_files set -l out (fish -n $f 2>&1) if test $status -ne 0 - echo " FAIL "(string replace $repo_root/ '' $f) + echo " FAIL (syntax) "(string replace $repo_root/ '' $f) printf '%s\n' $out - set lint_failed (math $lint_failed + 1) + set syntax_failed (math $syntax_failed + 1) + end + + if not fish_indent --check $f >/dev/null 2>&1 + echo " FAIL (indent) "(string replace $repo_root/ '' $f) + set indent_failed (math $indent_failed + 1) end end set -l lint_total (count $lint_files) -echo (math $lint_total - $lint_failed)"/$lint_total files passed lint" -if test $lint_failed -ne 0 +echo (math $lint_total - $syntax_failed)"/$lint_total files passed syntax check" +echo (math $lint_total - $indent_failed)"/$lint_total files passed indent check" +if test $syntax_failed -ne 0 -o $indent_failed -ne 0 set overall_failed 1 end -- 2.54.0 From ff4f7b39099095452dd484a18073cb9740400cc8 Mon Sep 17 00:00:00 2001 From: Rootiest Date: Wed, 9 Sep 2026 20:39:46 -0400 Subject: [PATCH 5/8] test: add string and commandline expansion test suite --- tests/test-string-and-expansion.fish | 390 +++++++++++++++++++++++++++ 1 file changed, 390 insertions(+) create mode 100644 tests/test-string-and-expansion.fish diff --git a/tests/test-string-and-expansion.fish b/tests/test-string-and-expansion.fish new file mode 100644 index 0000000..6a36869 --- /dev/null +++ b/tests/test-string-and-expansion.fish @@ -0,0 +1,390 @@ +#!/usr/bin/env fish +# Copyright (C) 2026 Rootiest +# SPDX-License-Identifier: AGPL-3.0-or-later +# +# MODE: isolated + +source (realpath (dirname (status filename)))/lib.fish +set -p fish_function_path $repo_root/functions +set -gx TERM xterm-256color +set -g __fish_config_dir $repo_root 2>/dev/null; or true + +# Ensure data/words is reachable via __fish_config_dir in isolated runs +if not test -d "$__fish_config_dir/data/words" + mkdir -p "$__fish_config_dir/data" + ln -sf "$repo_root/data/words" "$__fish_config_dir/data/words" +end + +# ----------------------------------------------------------------------------- +# rand_string: Components +# ----------------------------------------------------------------------------- +section "rand_string: components" + +check "literal component" FOO (rand_string literal=FOO) +check "string component" BAR (rand_string string=BAR) + +set -l out_digits4 (rand_string digits=4) +check "digits=4 returns 4 digits" true (string match -qr '^\d{4}$' -- "$out_digits4"; and echo true; or echo false) + +set -l out_digits1 (rand_string digits=1) +check "digits=1 returns 1 digit" true (string match -qr '^\d$' -- "$out_digits1"; and echo true; or echo false) + +set -l out_digits_inv (rand_string digits=abc) +check "digits=non-numeric defaults to 1 digit" true (string match -qr '^\d$' -- "$out_digits_inv"; and echo true; or echo false) + +set -l out_color (rand_string color) +check "category color generates non-empty word" true (test -n "$out_color"; and echo true; or echo false) + +set -l out_animal (rand_string animal) +check "category animal generates non-empty word" true (test -n "$out_animal"; and echo true; or echo false) + +set -l out_noun (rand_string noun) +check "category noun generates non-empty word" true (test -n "$out_noun"; and echo true; or echo false) + +set -l out_verb (rand_string verb) +check "category verb generates non-empty word" true (test -n "$out_verb"; and echo true; or echo false) + +set -l out_adj (rand_string adjective) +check "category adjective generates non-empty word" true (test -n "$out_adj"; and echo true; or echo false) + +set -l out_name (rand_string name) +check "category name generates non-empty word" true (test -n "$out_name"; and echo true; or echo false) + +# ----------------------------------------------------------------------------- +# rand_string: Separators +# ----------------------------------------------------------------------------- +section "rand_string: separators" + +check "default separator is dash" A-B (rand_string literal=A literal=B) +check "--separator=underscore" A_B (rand_string --separator=underscore literal=A literal=B) +check "--separator=dot" "A.B" (rand_string --separator=dot literal=A literal=B) +check "--separator=none" AB (rand_string --separator=none literal=A literal=B) +check "--separator=empty" AB (rand_string --separator=empty literal=A literal=B) +check "-s dot" "A.B" (rand_string -s dot literal=A literal=B) +check "-s underscore" A_B (rand_string -s underscore literal=A literal=B) +check "-s none" AB (rand_string -s none literal=A literal=B) +check "custom literal separator ::" "A::B" (rand_string --separator=:: literal=A literal=B) + +set -l out_multi (rand_string literal=test --separator=underscore literal=part digits=2) +check "multi-component with separators" true (string match -qr '^test_part_\d{2}$' -- "$out_multi"; and echo true; or echo false) + +# ----------------------------------------------------------------------------- +# rand_string: Casing +# ----------------------------------------------------------------------------- +section "rand_string: casing" + +check "--case=upper produces uppercase" true (string match -qr '^[A-Z]+$' -- (rand_string --case=upper color); and echo true; or echo false) +check "--case=lower produces lowercase" true (string match -qr '^[a-z]+$' -- (rand_string --case=lower color); and echo true; or echo false) +check "--case=title produces titlecase" true (string match -qr '^[A-Z][a-z]+$' -- (rand_string --case=title color); and echo true; or echo false) + +check "-c upper flag" true (string match -qr '^[A-Z]+$' -- (rand_string -c upper animal); and echo true; or echo false) +check "-c lower flag" true (string match -qr '^[a-z]+$' -- (rand_string -c lower animal); and echo true; or echo false) +check "-c title flag" true (string match -qr '^[A-Z][a-z]+$' -- (rand_string -c title animal); and echo true; or echo false) + +check "literal preserves casing with --case=upper" foo (rand_string --case=upper literal=foo) +check "literal preserves casing with --case=lower" FOO (rand_string --case=lower literal=FOO) +check "literal preserves casing with --case=title" foo (rand_string --case=title literal=foo) + +# ----------------------------------------------------------------------------- +# rand_string: Errors and Help +# ----------------------------------------------------------------------------- +section "rand_string: errors and help" + +rand_string nonexistent_cat_xyz >/dev/null 2>&1 +check "missing category returns 1" 1 $status + +set -l err_msg (rand_string nonexistent_cat_xyz 2>&1 >/dev/null) +check "missing category reports error on stderr" true (string match -qr 'unknown category' -- "$err_msg"; and echo true; or echo false) + +rand_string -h >/dev/null 2>&1 +check "-h returns 0" 0 $status + +set -l help_h (rand_string -h) +check "-h displays usage" true (string match -qr 'Usage:' -- "$help_h"; and echo true; or echo false) + +rand_string --help >/dev/null 2>&1 +check "--help returns 0" 0 $status + +set -l help_long (rand_string --help) +check "--help displays usage" true (string match -qr 'Usage:' -- "$help_long"; and echo true; or echo false) + +# ----------------------------------------------------------------------------- +# Mock commandline infrastructure +# ----------------------------------------------------------------------------- +set -g mock_cmd_buffer "" +set -g mock_cmd_cursor 0 +set -g mock_cmd_token "" +set -g mock_cmd_inserted "" +set -g mock_cmd_in_search 0 + +function commandline + if test (count $argv) -eq 0 + echo -n "$mock_cmd_buffer" + return 0 + end + + switch $argv[1] + case -C + if test (count $argv) -ge 2 + set -g mock_cmd_cursor $argv[2] + else + echo -n "$mock_cmd_cursor" + end + return 0 + case -r + if test (count $argv) -ge 2 + set -g mock_cmd_buffer "$argv[2]" + else + set -g mock_cmd_buffer "" + end + return 0 + case -- + if test (count $argv) -ge 2 + set -g mock_cmd_buffer "$argv[2]" + else + set -g mock_cmd_buffer "" + end + return 0 + case -i --insert + if test (count $argv) -ge 2 + set -g mock_cmd_inserted "$argv[2]" + set -g mock_cmd_buffer "$mock_cmd_buffer$argv[2]" + end + return 0 + case --search-field + if contains -- --insert $argv + set -l idx (contains -i -- --insert $argv) + set -l val $argv[(math $idx + 1)] + set -g mock_cmd_inserted "$val" + set -g mock_cmd_buffer "$mock_cmd_buffer$val" + return 0 + else + test "$mock_cmd_in_search" -eq 1 + return $status + end + case -t --current-token + if test (count $argv) -ge 2 + set -l old_tok "$mock_cmd_token" + set -g mock_cmd_token "$argv[2]" + if test -n "$old_tok" + set -g mock_cmd_buffer (string replace -- "$old_tok" "$argv[2]" "$mock_cmd_buffer") + else + set -g mock_cmd_buffer "$mock_cmd_buffer$argv[2]" + end + else + echo -n "$mock_cmd_token" + end + return 0 + end +end + +function set_test_history --argument-names cmd + set -l session "test_hist_"(random) + set -l hist_dir "" + if test -n "$XDG_DATA_HOME" + set hist_dir "$XDG_DATA_HOME/fish" + else + set hist_dir "$HOME/.local/share/fish" + end + mkdir -p "$hist_dir" + set -l hist_file "$hist_dir/"$session"_history" + echo "- cmd: $cmd" >"$hist_file" + echo " when: "(date +%s) >>"$hist_file" + set -g fish_history "$session" + set -g _last_test_hist_file "$hist_file" +end + +function cleanup_test_history + if set -q _last_test_hist_file; and test -f "$_last_test_hist_file" + rm -f "$_last_test_hist_file" + end +end + +# ----------------------------------------------------------------------------- +# _replace_command_token +# ----------------------------------------------------------------------------- +section _replace_command_token + +set mock_cmd_buffer "git status" +set mock_cmd_cursor -1 +_replace_command_token +check "git status: buffer replaces command token" " status" "$mock_cmd_buffer" +check "git status: cursor placed at 0" 0 "$mock_cmd_cursor" + +set mock_cmd_buffer "sudo rm -rf /tmp" +set mock_cmd_cursor -1 +_replace_command_token +check "sudo rm -rf /tmp: buffer preserves sudo and replaces command" "sudo -rf /tmp" "$mock_cmd_buffer" +check "sudo rm -rf /tmp: cursor placed at 5" 5 "$mock_cmd_cursor" + +set mock_cmd_buffer "sudo systemctl status nginx" +set mock_cmd_cursor -1 +_replace_command_token +check "sudo systemctl status: buffer preserves sudo" "sudo status nginx" "$mock_cmd_buffer" +check "sudo systemctl status: cursor placed at 5" 5 "$mock_cmd_cursor" + +set mock_cmd_buffer "python -m unittest" +set mock_cmd_cursor -1 +_replace_command_token +check "python -m unittest: buffer replaces command token" " -m unittest" "$mock_cmd_buffer" +check "python -m unittest: cursor placed at 0" 0 "$mock_cmd_cursor" + +set mock_cmd_buffer ls +set mock_cmd_cursor -1 +_replace_command_token +check "single token ls: buffer becomes single space" " " "$mock_cmd_buffer" +check "single token ls: cursor placed at 0" 0 "$mock_cmd_cursor" + +set mock_cmd_buffer "sudo reboot" +set mock_cmd_cursor -1 +_replace_command_token +check "sudo reboot: buffer becomes sudo with two spaces" "sudo " "$mock_cmd_buffer" +check "sudo reboot: cursor placed at 5" 5 "$mock_cmd_cursor" + +# ----------------------------------------------------------------------------- +# __substitute_typo +# ----------------------------------------------------------------------------- +section __substitute_typo + +set_test_history 'git commit -m "feat"' +set mock_cmd_buffer "^feat^fix" +set mock_cmd_inserted "" +__substitute_typo +check "substitute ^feat^fix in git commit" 'git commit -m "fix"' "$mock_cmd_buffer" + +set_test_history "docker run -d nginx" +set mock_cmd_buffer "^nginx^redis" +set mock_cmd_inserted "" +__substitute_typo +check "substitute ^nginx^redis in docker run" "docker run -d redis" "$mock_cmd_buffer" + +set_test_history "echo foo foo" +set mock_cmd_buffer "^foo^bar" +set mock_cmd_inserted "" +__substitute_typo +check "substitute replaces all occurrences" "echo bar bar" "$mock_cmd_buffer" + +set_test_history "git checkout main feat" +set mock_cmd_buffer "^feat^" +set mock_cmd_inserted "" +__substitute_typo +check "substitute ^feat^ with empty string deletes token" "git checkout main " "$mock_cmd_buffer" + +set_test_history "git status" +set mock_cmd_buffer "^" +set mock_cmd_inserted "" +__substitute_typo +check "lone caret inserts literal caret" "^" "$mock_cmd_inserted" +check "lone caret buffer retains caret" "^^" "$mock_cmd_buffer" + +set_test_history "git status" +set mock_cmd_buffer "git log" +set mock_cmd_inserted "" +__substitute_typo +check "non-matching buffer inserts literal caret" "^" "$mock_cmd_inserted" + +cleanup_test_history + +# ----------------------------------------------------------------------------- +# _puffer_fish_expand_dot +# ----------------------------------------------------------------------------- +section _puffer_fish_expand_dot + +set mock_cmd_token ".." +set mock_cmd_inserted "" +set mock_cmd_buffer "" +set mock_cmd_in_search 0 +_puffer_fish_expand_dot +check "token .. inserts /.." "/.." "$mock_cmd_inserted" + +set mock_cmd_token "../.." +set mock_cmd_inserted "" +set mock_cmd_buffer "" +set mock_cmd_in_search 0 +_puffer_fish_expand_dot +check "token ../.. inserts /.." "/.." "$mock_cmd_inserted" + +set mock_cmd_token "../../.." +set mock_cmd_inserted "" +set mock_cmd_buffer "" +set mock_cmd_in_search 0 +_puffer_fish_expand_dot +check "token ../../.. inserts /.." "/.." "$mock_cmd_inserted" + +set mock_cmd_token "." +set mock_cmd_inserted "" +set mock_cmd_buffer "" +set mock_cmd_in_search 0 +_puffer_fish_expand_dot +check "token . inserts ." "." "$mock_cmd_inserted" + +set mock_cmd_token "..." +set mock_cmd_inserted "" +set mock_cmd_buffer "" +set mock_cmd_in_search 0 +_puffer_fish_expand_dot +check "token ... inserts ." "." "$mock_cmd_inserted" + +set mock_cmd_token foo +set mock_cmd_inserted "" +set mock_cmd_buffer "" +set mock_cmd_in_search 0 +_puffer_fish_expand_dot +check "token foo inserts ." "." "$mock_cmd_inserted" + +set mock_cmd_token ".." +set mock_cmd_inserted "" +set mock_cmd_buffer "" +set mock_cmd_in_search 1 +_puffer_fish_expand_dot +check "search field mode inserts . even if token is .." "." "$mock_cmd_inserted" + +# ----------------------------------------------------------------------------- +# _puffer_fish_expand_bang +# ----------------------------------------------------------------------------- +section _puffer_fish_expand_bang + +set_test_history "cargo test --release" + +set mock_cmd_token "!" +set mock_cmd_buffer "!" +set mock_cmd_inserted "" +set mock_cmd_in_search 0 +_puffer_fish_expand_bang +check "token ! expands to history[1]" "cargo test --release" "$mock_cmd_token" +check "token ! updates buffer" "cargo test --release" "$mock_cmd_buffer" + +set mock_cmd_token "!" +set mock_cmd_buffer "sudo !" +set mock_cmd_inserted "" +set mock_cmd_in_search 0 +_puffer_fish_expand_bang +check "sudo ! expands token to history[1]" "sudo cargo test --release" "$mock_cmd_buffer" + +set mock_cmd_token git +set mock_cmd_buffer git +set mock_cmd_inserted "" +set mock_cmd_in_search 0 +_puffer_fish_expand_bang +check "non-bang token inserts literal !" "!" "$mock_cmd_inserted" + +set mock_cmd_token "" +set mock_cmd_buffer "" +set mock_cmd_inserted "" +set mock_cmd_in_search 0 +_puffer_fish_expand_bang +check "empty token inserts literal !" "!" "$mock_cmd_inserted" + +set mock_cmd_token "!" +set mock_cmd_buffer "" +set mock_cmd_inserted "" +set mock_cmd_in_search 1 +_puffer_fish_expand_bang +check "search field mode inserts literal !" "!" "$mock_cmd_inserted" + +cleanup_test_history + +# Unshadow commandline builtin +functions -e commandline + +report -- 2.54.0 From c7d7cea8aed4f80b15fb32bded111bade55ed7e4 Mon Sep 17 00:00:00 2001 From: Rootiest Date: Wed, 9 Sep 2026 20:43:52 -0400 Subject: [PATCH 6/8] test: eliminate flakiness in rand_string casing test and improve history cleanup --- tests/test-string-and-expansion.fish | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/tests/test-string-and-expansion.fish b/tests/test-string-and-expansion.fish index 6a36869..576a3f9 100644 --- a/tests/test-string-and-expansion.fish +++ b/tests/test-string-and-expansion.fish @@ -77,9 +77,9 @@ check "--case=upper produces uppercase" true (string match -qr '^[A-Z]+$' -- (ra check "--case=lower produces lowercase" true (string match -qr '^[a-z]+$' -- (rand_string --case=lower color); and echo true; or echo false) check "--case=title produces titlecase" true (string match -qr '^[A-Z][a-z]+$' -- (rand_string --case=title color); and echo true; or echo false) -check "-c upper flag" true (string match -qr '^[A-Z]+$' -- (rand_string -c upper animal); and echo true; or echo false) -check "-c lower flag" true (string match -qr '^[a-z]+$' -- (rand_string -c lower animal); and echo true; or echo false) -check "-c title flag" true (string match -qr '^[A-Z][a-z]+$' -- (rand_string -c title animal); and echo true; or echo false) +check "-c upper flag" true (string match -qr '^[A-Z]+$' -- (rand_string -c upper color); and echo true; or echo false) +check "-c lower flag" true (string match -qr '^[a-z]+$' -- (rand_string -c lower color); and echo true; or echo false) +check "-c title flag" true (string match -qr '^[A-Z][a-z]+$' -- (rand_string -c title color); and echo true; or echo false) check "literal preserves casing with --case=upper" foo (rand_string --case=upper literal=foo) check "literal preserves casing with --case=lower" FOO (rand_string --case=lower literal=FOO) @@ -178,6 +178,8 @@ function commandline end end +set -g _test_hist_files + function set_test_history --argument-names cmd set -l session "test_hist_"(random) set -l hist_dir "" @@ -191,12 +193,17 @@ function set_test_history --argument-names cmd echo "- cmd: $cmd" >"$hist_file" echo " when: "(date +%s) >>"$hist_file" set -g fish_history "$session" - set -g _last_test_hist_file "$hist_file" + set -ga _test_hist_files "$hist_file" end function cleanup_test_history - if set -q _last_test_hist_file; and test -f "$_last_test_hist_file" - rm -f "$_last_test_hist_file" + if set -q _test_hist_files + for f in $_test_hist_files + if test -f "$f" + rm -f "$f" + end + end + set -e _test_hist_files end end -- 2.54.0 From a1cf06624ec816dd0c7b150b35c5d1749256fff7 Mon Sep 17 00:00:00 2001 From: Rootiest Date: Wed, 9 Sep 2026 20:49:16 -0400 Subject: [PATCH 7/8] fix(security): return full variable name in sponge_filter_secrets --- functions/sponge_filter_secrets.fish | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/functions/sponge_filter_secrets.fish b/functions/sponge_filter_secrets.fish index da7d374..fb192aa 100644 --- a/functions/sponge_filter_secrets.fish +++ b/functions/sponge_filter_secrets.fish @@ -33,7 +33,7 @@ # set -U -a sponge_filters sponge_filter_secrets function sponge_filter_secrets --argument-names command # Find all exported variables with security-sensitive names - set -l sensitive_vars (set --names --export | string match --regex -- \ + set -l sensitive_vars (set --names --export | string match --entire --regex -- \ '(?i)(?:TOKEN|PASSWORD|PASSWD|SECRET|API[_-]KEY|PRIVATE[_-]KEY|ACCESS[_-]KEY|AUTH[_-]KEY|CREDENTIAL|KOPIA_PASSWORD)') for var in $sensitive_vars -- 2.54.0 From 04edd6de8990b7f741732f46cdd4b2a82bfd3a0a Mon Sep 17 00:00:00 2001 From: Rootiest Date: Wed, 9 Sep 2026 20:50:21 -0400 Subject: [PATCH 8/8] test: add core utilities and security test suite --- tests/test-core-utilities.fish | 357 +++++++++++++++++++++++++++++++++ 1 file changed, 357 insertions(+) create mode 100644 tests/test-core-utilities.fish diff --git a/tests/test-core-utilities.fish b/tests/test-core-utilities.fish new file mode 100644 index 0000000..017ce0e --- /dev/null +++ b/tests/test-core-utilities.fish @@ -0,0 +1,357 @@ +#!/usr/bin/env fish +# Copyright (C) 2026 Rootiest +# SPDX-License-Identifier: AGPL-3.0-or-later +# +# MODE: isolated + +source (realpath (dirname (status filename)))/lib.fish +set -p fish_function_path $repo_root/functions +set -gx TERM xterm-256color +set -g __fish_config_dir $repo_root 2>/dev/null; or true + +# ============================================================================= +# 1. sponge_filter_secrets: Credential Leakage Prevention +# ============================================================================= +section sponge_filter_secrets + +# Export test variables matching sensitive heuristics +set -gx MY_AWS_SECRET_ACCESS_KEY wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY +set -gx GITHUB_TOKEN ghp_0123456789abcdefghijklmnopqrstuv +set -gx KOPIA_PASSWORD MyLongPassword123 + +# Secret appears in command -> returns 0 (filtered from history) +sponge_filter_secrets "aws s3 cp file.txt s3://bucket/ --key wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" +check "aws secret in command filtered" 0 $status + +sponge_filter_secrets "curl -H 'Authorization: token ghp_0123456789abcdefghijklmnopqrstuv' https://api.github.com" +check "github token in command filtered" 0 $status + +sponge_filter_secrets "kopia repository connect --password MyLongPassword123" +check "kopia password in command filtered" 0 $status + +# Clean command with no secret -> returns 1 (kept in history) +sponge_filter_secrets "git status -s" +check "clean command retained" 1 $status + +sponge_filter_secrets "echo 'Hello World'" +check "ordinary echo retained" 1 $status + +# Short secret (<= 8 chars) -> ignored, returns 1 (kept) +set -gx SHORT_API_KEY 12345678 +set -gx SHORT_SECRET short +sponge_filter_secrets "curl -H 'X-Key: 12345678' https://example.com" +check "8-char secret ignored" 1 $status + +sponge_filter_secrets "echo short" +check "short secret ignored" 1 $status + +# Path-like secret (starts with / or ~) -> ignored, returns 1 (kept) +set -gx SECRET_FILE_PATH "/home/rootiest/.token" +set -gx HOME_SECRET_PATH "~/secrets/token.txt" +sponge_filter_secrets "cat /home/rootiest/.token" +check "path-like secret starting with / ignored" 1 $status + +sponge_filter_secrets "source ~/secrets/token.txt" +check "path-like secret starting with ~ ignored" 1 $status + +# Empty or unset variable -> handled without error, returns 1 +set -gx DUMMY_API_KEY "" +sponge_filter_secrets "echo normal command" +check "empty sensitive var does not cause crash or false filter" 1 $status + +# Clean up exported test variables +set -e MY_AWS_SECRET_ACCESS_KEY +set -e GITHUB_TOKEN +set -e KOPIA_PASSWORD +set -e SHORT_API_KEY +set -e SHORT_SECRET +set -e SECRET_FILE_PATH +set -e HOME_SECRET_PATH +set -e DUMMY_API_KEY + +# ============================================================================= +# 2. _fish_mkdir_p: Directory Creation with Feedback +# ============================================================================= +section _fish_mkdir_p + +set -l mkdir_sandbox (mktemp -d) + +# Empty argument returns status 1 +_fish_mkdir_p +check "empty argument returns 1" 1 $status + +# Existing directory returns status 0 +_fish_mkdir_p $mkdir_sandbox +check "existing directory returns 0" 0 $status + +# --path: creates directory and prints single line +set -l out_path (_fish_mkdir_p --path $mkdir_sandbox/dirA/dirB) +check "--path returns 0" 0 $status +check "--path creates directory" true (test -d $mkdir_sandbox/dirA/dirB; and echo true; or echo false) +check "--path outputs 'Created directory:'" true (string match -q "*Created directory:*" -- "$out_path"; and echo true; or echo false) + +# --silent: creates directory and suppresses stdout +set -l out_silent (_fish_mkdir_p --silent $mkdir_sandbox/dirC/dirD) +check "--silent returns 0" 0 $status +check "--silent creates directory" true (test -d $mkdir_sandbox/dirC/dirD; and echo true; or echo false) +check "--silent produces empty stdout" "" "$out_silent" + +# -s short flag +set -l out_silent_short (_fish_mkdir_p -s $mkdir_sandbox/dirC/dirD/sub) +check "-s returns 0" 0 $status +check "-s creates directory" true (test -d $mkdir_sandbox/dirC/dirD/sub; and echo true; or echo false) +check "-s produces empty stdout" "" "$out_silent_short" + +# --tree: creates directory and displays tree view with branch glyph +set -l out_tree (_fish_mkdir_p --tree $mkdir_sandbox/dirE/dirF) +check "--tree returns 0" 0 $status +check "--tree creates directory" true (test -d $mkdir_sandbox/dirE/dirF; and echo true; or echo false) +check "--tree outputs 'Created directories:'" true (string match -q "*Created directories:*" -- "$out_tree"; and echo true; or echo false) +check "--tree outputs branch glyph └──" true (string match -q "*└──*" -- "$out_tree"; and echo true; or echo false) + +# Default mode without mode flag uses path mode +set -l out_default (_fish_mkdir_p $mkdir_sandbox/dirG/dirH) +check "default mode returns 0" 0 $status +check "default mode creates directory" true (test -d $mkdir_sandbox/dirG/dirH; and echo true; or echo false) +check "default mode outputs 'Created directory:'" true (string match -q "*Created directory:*" -- "$out_default"; and echo true; or echo false) + +# Cleanup +rm -rf $mkdir_sandbox + +# ============================================================================= +# 3. _scrollback_prune_junk: History Log Pruning +# ============================================================================= +section _scrollback_prune_junk + +set -l prune_sandbox (mktemp -d) + +# Create test files in prune sandbox +# Empty files (must be deleted) +touch $prune_sandbox/empty.log +touch $prune_sandbox/empty.txt + +# 1-line noise files (must be deleted) +echo "lone command prompt" >$prune_sandbox/single_line.log +echo "[exited]" >$prune_sandbox/single_line.txt +printf "\n\n \n single line with surrounding whitespace \n\n" >$prune_sandbox/whitespace_single.log + +# Kitty tab-rename UI noise captures (must be deleted) +printf "%s\n" "Prompt start" "Enter the new title for this tab below" Done >$prune_sandbox/scrollback_kitty.log +printf "%s\n" Header "Enter the new title for this tab below" Tail >$prune_sandbox/scrollback_rename.txt + +# Valid multi-line log files (>1 meaningful lines) (MUST BE PRESERVED) +printf "%s\n" "Session started at 12:00" "Command: ls -la" "Output: total 0" >$prune_sandbox/valid_session.log +printf "%s\n" "Build step 1 passed" "Build step 2 passed" >$prune_sandbox/valid_session.txt +printf "%s\n" "scrollback line 1" "scrollback line 2" "scrollback line 3" >$prune_sandbox/scrollback_valid.log + +# Run prune +_scrollback_prune_junk $prune_sandbox + +# Assertions: empty files deleted +check "empty .log deleted" false (test -f $prune_sandbox/empty.log; and echo true; or echo false) +check "empty .txt deleted" false (test -f $prune_sandbox/empty.txt; and echo true; or echo false) + +# Assertions: 1-line noise files deleted +check "single-line .log deleted" false (test -f $prune_sandbox/single_line.log; and echo true; or echo false) +check "single-line .txt deleted" false (test -f $prune_sandbox/single_line.txt; and echo true; or echo false) +check "whitespace single-line .log deleted" false (test -f $prune_sandbox/whitespace_single.log; and echo true; or echo false) + +# Assertions: kitty tab rename prompt files deleted +check "kitty tab rename scrollback_*.log deleted" false (test -f $prune_sandbox/scrollback_kitty.log; and echo true; or echo false) +check "kitty tab rename scrollback_*.txt deleted" false (test -f $prune_sandbox/scrollback_rename.txt; and echo true; or echo false) + +# Assertions: valid multi-line files preserved +check "valid multi-line .log preserved" true (test -f $prune_sandbox/valid_session.log; and echo true; or echo false) +check "valid multi-line .txt preserved" true (test -f $prune_sandbox/valid_session.txt; and echo true; or echo false) +check "valid multi-line scrollback_valid.log preserved" true (test -f $prune_sandbox/scrollback_valid.log; and echo true; or echo false) + +# Assertions: non-existent directory handled safely +_scrollback_prune_junk $prune_sandbox/nonexistent +check "nonexistent directory returns 0" 0 $status + +# Cleanup +rm -rf $prune_sandbox + +# ============================================================================= +# 4. sudo-toggle: Sudo NOPASSWD Security Toggle +# ============================================================================= +section sudo-toggle + +set -l sudo_sandbox (mktemp -d) +set -g _mock_sudoers_file "$sudo_sandbox/nofail-toggle" + +# Mock sudo function to intercept stat, truncate, and tee for /etc/sudoers.d/nofail-toggle +function sudo + set -l cmd_args $argv + while test (count $cmd_args) -gt 0; and string match -qr '^-[a-zA-Z]' -- $cmd_args[1] + set -e cmd_args[1] + end + + switch $cmd_args[1] + case stat + if test -f "$_mock_sudoers_file" + command stat -c %s "$_mock_sudoers_file" + else + return 1 + end + case truncate + if test -f "$_mock_sudoers_file" + command truncate -s 0 "$_mock_sudoers_file" + end + case tee + command tee "$_mock_sudoers_file" + case '*' + return 1 + end +end + +# Case 1: When file is missing -> writes rule, outputs DISABLED (Bypass active) +rm -f "$_mock_sudoers_file" +set -l out_missing (sudo-toggle) +check "missing file: returns 0" 0 $status +check "missing file: displays DISABLED" true (string match -q "*🔓 Sudo security: DISABLED*" -- "$out_missing"; and echo true; or echo false) +check "missing file: creates sudoers file" true (test -f "$_mock_sudoers_file"; and echo true; or echo false) +check "missing file: writes content to file" true (test -s "$_mock_sudoers_file"; and echo true; or echo false) + +# Case 2: When file has size > 0 (bypass active) -> truncates to 0, outputs ENABLED +set -l out_active (sudo-toggle) +check "active bypass: returns 0" 0 $status +check "active bypass: displays ENABLED" true (string match -q "*🔒 Sudo security: ENABLED*" -- "$out_active"; and echo true; or echo false) +check "active bypass: truncates file to 0 bytes" false (test -s "$_mock_sudoers_file"; and echo true; or echo false) + +# Case 3: When file exists but has size 0 -> writes rule, outputs DISABLED +set -l out_empty (sudo-toggle) +check "empty file: returns 0" 0 $status +check "empty file: displays DISABLED" true (string match -q "*🔓 Sudo security: DISABLED*" -- "$out_empty"; and echo true; or echo false) +check "empty file: writes content to file" true (test -s "$_mock_sudoers_file"; and echo true; or echo false) + +# Case 4: --help flag displays header documentation +set -l out_help (sudo-toggle --help) +check "sudo-toggle --help returns 0" 0 $status +check "sudo-toggle --help contains USAGE" true (string match -q "*USAGE*" -- "$out_help"; and echo true; or echo false) + +# Cleanup +functions -e sudo +set -e _mock_sudoers_file +rm -rf $sudo_sandbox + +# ============================================================================= +# 5. spark: Sparkline Bar Chart Generation +# ============================================================================= +section spark + +# Number array 1 2 3 4 5 produces sparkline characters +set -l out_seq (spark 1 2 3 4 5) +check "spark 1 2 3 4 5 generates sparkline" "▁▃▄▆█" "$out_seq" + +# Numbers via stdin +set -l out_stdin (printf "%s\n" 1 2 3 4 5 | spark) +check "spark via stdin generates sparkline" "▁▃▄▆█" "$out_stdin" + +# Clamping with --min and --max +set -l out_clamped (spark --min=0 --max=10 0 5 10) +check "spark clamped with --min and --max" "▁▄█" "$out_clamped" + +# Version flag +set -l out_ver (spark --version) +check "spark --version contains version 1.1.0" true (string match -q "*spark, version 1.1.0*" -- "$out_ver"; and echo true; or echo false) + +set -l out_ver_s (spark -v) +check "spark -v contains version 1.1.0" true (string match -q "*spark, version 1.1.0*" -- "$out_ver_s"; and echo true; or echo false) + +# Help flag +set -l out_spark_help (spark --help) +check "spark --help contains Usage:" true (string match -q "*Usage:*" -- "$out_spark_help"; and echo true; or echo false) + +set -l out_spark_help_s (spark -h) +check "spark -h contains Usage:" true (string match -q "*Usage:*" -- "$out_spark_help_s"; and echo true; or echo false) + +# ============================================================================= +# 6. pkg: System Package Manager Abstraction +# ============================================================================= +section pkg + +# Test missing package manager detection +function _fish_deps_detect_pm + return 1 +end + +set -l err_no_pm (pkg somepkg 2>&1 >/dev/null) +check "missing package manager returns 1" 1 $status +check "missing package manager reports error on stderr" true (string match -q "*error: no supported package manager found*" -- "$err_no_pm"; and echo true; or echo false) + +# Test pacman integration with mock detection and mock commands +function _fish_deps_detect_pm + echo pacman +end + +set -g _pacman_log +function pacman + set -ga _pacman_log (string join -- " " $argv) + if test "$argv[1]" = -Qi + if test "$argv[2]" = installed-pkg + return 0 + else + return 1 + end + end + return 0 +end + +function sudo + $argv +end + +# Auto mode with installed package -> invokes pacman -Qi and then removes (-Rns) +set -g _pacman_log +set -l out_installed (pkg installed-pkg) +check "pkg installed-pkg returns 0" 0 $status +check "pkg installed-pkg outputs Removing" true (string match -q "*Removing*installed-pkg*" -- "$out_installed"; and echo true; or echo false) +check "pkg installed-pkg runs pacman -Qi query" true (string match -q "*-Qi installed-pkg*" -- "$_pacman_log"; and echo true; or echo false) +check "pkg installed-pkg runs pacman -Rns removal" true (string match -q "*-Rns installed-pkg*" -- "$_pacman_log"; and echo true; or echo false) + +# Auto mode with uninstalled package -> invokes pacman -Qi and then installs (-S) +set -g _pacman_log +set -l out_uninstalled (pkg uninstalled-pkg) +check "pkg uninstalled-pkg returns 0" 0 $status +check "pkg uninstalled-pkg outputs Installing" true (string match -q "*Installing*uninstalled-pkg*" -- "$out_uninstalled"; and echo true; or echo false) +check "pkg uninstalled-pkg runs pacman -Qi query" true (string match -q "*-Qi uninstalled-pkg*" -- "$_pacman_log"; and echo true; or echo false) +check "pkg uninstalled-pkg runs pacman -S installation" true (string match -q "*-S uninstalled-pkg*" -- "$_pacman_log"; and echo true; or echo false) + +# Explicit install mode (-i) -> directly installs +set -g _pacman_log +set -l out_force_install (pkg -i new-pkg) +check "pkg -i returns 0" 0 $status +check "pkg -i outputs Installing" true (string match -q "*Installing*new-pkg*" -- "$out_force_install"; and echo true; or echo false) +check "pkg -i runs pacman -S" true (string match -q "*-S new-pkg*" -- "$_pacman_log"; and echo true; or echo false) + +# Explicit uninstall mode (-u) -> directly removes +set -g _pacman_log +set -l out_force_uninstall (pkg -u old-pkg) +check "pkg -u returns 0" 0 $status +check "pkg -u outputs Removing" true (string match -q "*Removing*old-pkg*" -- "$out_force_uninstall"; and echo true; or echo false) +check "pkg -u runs pacman -Rns" true (string match -q "*-Rns old-pkg*" -- "$_pacman_log"; and echo true; or echo false) + +# Help flag +set -l out_pkg_help (pkg --help) +check "pkg --help returns 0" 0 $status +check "pkg --help outputs Usage" true (string match -q "*Usage:*pkg*" -- "$out_pkg_help"; and echo true; or echo false) + +# No arguments -> prints usage and returns 0 +set -l out_pkg_empty (pkg) +check "pkg with no arguments returns 0" 0 $status +check "pkg with no arguments outputs Usage" true (string match -q "*Usage:*pkg*" -- "$out_pkg_empty"; and echo true; or echo false) + +# Unknown flag -> returns 1 and prints error to stderr +set -l err_pkg_flag (pkg --unrecognized-option 2>&1 >/dev/null) +check "pkg unknown flag returns 1" 1 $status +check "pkg unknown flag outputs error on stderr" true (string match -q "*error:*unknown flag*--unrecognized-option*" -- "$err_pkg_flag"; and echo true; or echo false) + +# Cleanup +functions -e _fish_deps_detect_pm pacman sudo +set -e _pacman_log + +# ============================================================================= +# Report +# ============================================================================= +report -- 2.54.0