From 5d8d9df7d1b95c1a6a7bf25ddf57652c86f110b3 Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Tue, 19 May 2026 12:24:40 -0500 Subject: feat(tmux-util): add script skeleton and reap subcommand A new utility in dotfiles/common/.local/bin/ for managing tmux sessions. The eventual plan covers six subcommands (go, pick, ls, find, reap, rename). This commit ships the skeleton, the dispatch + help, and the first subcommand: reap. reap walks every unattached tmux session whose name doesn't match $TMUX_UTIL_REAP_SKIP (default `^aiv-`), sends SIGHUP to each pane's PID (the same signal that fires when you close a terminal window), waits up to three seconds for the session to wind down, and falls back to `tmux kill-session` if anything's still alive. Tests live under tests/tmux-util/ with the same fake-binary-on-PATH pattern layout-navigate uses. fake-tmux reads canned session state from a file and records every invocation. fake-kill records signal calls without sending them. fake-sleep is a no-op so tests don't actually wait. 14 tests cover Normal / Boundary cases for dispatch + reap. Run them with: cd tests && python3 -m unittest tmux-util.test_tmux_util The other five subcommands stub out for now and exit non-zero with "not implemented yet" so future TDD turns can drop them in one at a time. --- tests/tmux-util/fake-tmux | 95 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100755 tests/tmux-util/fake-tmux (limited to 'tests/tmux-util/fake-tmux') diff --git a/tests/tmux-util/fake-tmux b/tests/tmux-util/fake-tmux new file mode 100755 index 0000000..f6217bb --- /dev/null +++ b/tests/tmux-util/fake-tmux @@ -0,0 +1,95 @@ +#!/bin/sh +# Fake tmux for testing tmux-util. +# +# State file: $FAKE_TMUX_DIR/sessions.txt +# One line per session, space-separated: [last_activity_epoch] +# pids_csv is a comma-separated list of pane PIDs (or '-' for none) +# +# Log file: $FAKE_TMUX_DIR/calls.log +# Each invocation appended as a single line: tmux + +: "${FAKE_TMUX_DIR:?FAKE_TMUX_DIR must be set}" + +STATE="$FAKE_TMUX_DIR/sessions.txt" +LOG="$FAKE_TMUX_DIR/calls.log" + +# Log every invocation +printf 'tmux %s\n' "$*" >> "$LOG" + +cmd="$1" +shift + +# Helper: read state file, ignoring blank lines +read_state() { + [ -f "$STATE" ] || return 0 + grep -v '^[[:space:]]*$' "$STATE" || true +} + +case "$cmd" in + list-sessions) + # Format string is ignored; we always emit " " because + # that's the only format tmux-util uses against list-sessions. + read_state | while IFS=' ' read -r name attached pids _rest; do + [ -n "$name" ] || continue + echo "$name $attached" + done + ;; + list-panes) + session="" + while [ "$#" -gt 0 ]; do + case "$1" in + -t) shift; session="$1"; shift ;; + -F) shift; [ "$#" -gt 0 ] && shift ;; + -s) shift ;; + *) shift ;; + esac + done + read_state | while IFS=' ' read -r name attached pids _rest; do + if [ "$name" = "$session" ]; then + [ "$pids" = "-" ] || echo "$pids" | tr ',' '\n' + exit 0 + fi + done + ;; + has-session) + session="" + while [ "$#" -gt 0 ]; do + case "$1" in + -t) shift; session="$1"; shift ;; + *) shift ;; + esac + done + while IFS=' ' read -r name attached pids _rest; do + if [ "$name" = "$session" ]; then + exit 0 + fi + done < "$STATE" + exit 1 + ;; + kill-session) + session="" + while [ "$#" -gt 0 ]; do + case "$1" in + -t) shift; session="$1"; shift ;; + *) shift ;; + esac + done + tmp="$STATE.tmp" + : > "$tmp" + while IFS=' ' read -r name attached pids rest; do + [ -n "$name" ] || continue + if [ "$name" != "$session" ]; then + if [ -n "$rest" ]; then + printf '%s %s %s %s\n' "$name" "$attached" "$pids" "$rest" >> "$tmp" + else + printf '%s %s %s\n' "$name" "$attached" "$pids" >> "$tmp" + fi + fi + done < "$STATE" + mv "$tmp" "$STATE" + ;; + *) + echo "fake-tmux: unknown command '$cmd'" >&2 + exit 1 + ;; +esac -- cgit v1.2.3