aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-05-19 13:09:23 -0500
committerCraig Jennings <c@cjennings.net>2026-05-19 13:09:23 -0500
commit331d9421f1ca56afdaf0d238082f83d82103a795 (patch)
tree4e1f0b568f6fcfa89053da8dc55846afc39ddb0e /tests
parent685272399d7dbc35aea6028d6741963399d84e3f (diff)
downloadarchsetup-331d9421f1ca56afdaf0d238082f83d82103a795.tar.gz
archsetup-331d9421f1ca56afdaf0d238082f83d82103a795.zip
feat(tmux-util): add find subcommand
tmux-util find <pattern> walks every pane across every session, queries each pane's foreground command (`#{pane_current_command}`), and prints the location of any pane whose command matches the pattern. Output format: `<session>:<window>.<pane> <command>`, one per line. Pattern is a basic ERE (passed through `grep -E`), so anchors and alternation work. Substring matching is the common case. Exit code: - 0 with output: matches found - 1 with no output: no matches (lets you script around it) - 2 with usage on stderr: missing or empty pattern 7 new tests cover Normal cases (single match, multi-match across sessions, format verification) and Boundary cases (no matches, no sessions, missing pattern, empty pattern). fake-tmux now parses pid:cmd entries in the state file so panes can carry a synthetic command name. Total suite: 39 tests, all green.
Diffstat (limited to 'tests')
-rwxr-xr-xtests/tmux-util/fake-tmux36
-rw-r--r--tests/tmux-util/test_tmux_util.py72
2 files changed, 103 insertions, 5 deletions
diff --git a/tests/tmux-util/fake-tmux b/tests/tmux-util/fake-tmux
index 163ea24..b5a1e61 100755
--- a/tests/tmux-util/fake-tmux
+++ b/tests/tmux-util/fake-tmux
@@ -38,6 +38,19 @@ render_format() {
echo "$out"
}
+# Render a tmux format string against one pane's fields.
+# Args: format, name, pid, cmd, idx, cwd
+render_pane_format() {
+ local out="$1" name="$2" pid="$3" cmd="$4" idx="$5" cwd="$6"
+ out="${out//\#\{pane_pid\}/$pid}"
+ out="${out//\#\{pane_current_command\}/$cmd}"
+ out="${out//\#\{pane_current_path\}/$cwd}"
+ out="${out//\#\{session_name\}/$name}"
+ out="${out//\#\{window_index\}/0}"
+ out="${out//\#\{pane_index\}/$idx}"
+ echo "$out"
+}
+
case "$cmd" in
list-sessions)
fmt=""
@@ -54,20 +67,33 @@ case "$cmd" in
done
;;
list-panes)
+ all=0
+ fmt=""
session=""
while [ "$#" -gt 0 ]; do
case "$1" in
-t) shift; session="$1"; shift ;;
- -F) shift; [ "$#" -gt 0 ] && shift ;;
+ -F) shift; fmt="${1:-}"; shift ;;
-s) shift ;;
+ -a) all=1; 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
+ [ -n "$fmt" ] || fmt='#{pane_pid}'
+ read_state | while IFS=' ' read -r name attached pids activity windows cwd; do
+ [ -n "$name" ] || continue
+ if [ "$all" -eq 0 ] && [ "$name" != "$session" ]; then
+ continue
fi
+ [ "$pids" = "-" ] && continue
+ idx=0
+ for entry in $(echo "$pids" | tr ',' ' '); do
+ pid="${entry%%:*}"
+ cmd="${entry##*:}"
+ [ "$cmd" = "$entry" ] && cmd="shell"
+ render_pane_format "$fmt" "$name" "$pid" "$cmd" "$idx" "${cwd:-/tmp}"
+ idx=$((idx + 1))
+ done
done
;;
display)
diff --git a/tests/tmux-util/test_tmux_util.py b/tests/tmux-util/test_tmux_util.py
index 24cc335..e313af6 100644
--- a/tests/tmux-util/test_tmux_util.py
+++ b/tests/tmux-util/test_tmux_util.py
@@ -477,5 +477,77 @@ class TestGoErrors(TmuxUtilHarness):
self.assertIn("missing session name", result.stderr)
+# -----------------------------------------------------------------------------
+# find — Normal cases
+# -----------------------------------------------------------------------------
+
+class TestFindNormal(TmuxUtilHarness):
+
+ def test_find_matches_pane_running_named_command(self):
+ # Two sessions, one has a pane running vim; find should locate it.
+ self.set_sessions([
+ ("work", 1, ["101:zsh", "102:vim"], 950, 2, "/tmp/work"),
+ ("idle", 0, ["201:zsh"], 900, 1, "/tmp/idle"),
+ ])
+ result = self.run_script("find", "vim")
+ self.assertEqual(result.returncode, 0, msg=result.stderr)
+ self.assertIn("work:", result.stdout)
+ self.assertIn("vim", result.stdout)
+ # idle session pane shouldn't be in the output
+ self.assertNotIn("idle:", result.stdout)
+
+ def test_find_matches_multiple_panes(self):
+ self.set_sessions([
+ ("a", 1, ["101:zsh", "102:vim"], 950, 1, "/tmp/a"),
+ ("b", 1, ["201:vim"], 900, 1, "/tmp/b"),
+ ])
+ result = self.run_script("find", "vim")
+ self.assertEqual(result.returncode, 0)
+ self.assertIn("a:", result.stdout)
+ self.assertIn("b:", result.stdout)
+ # Two matches, two output lines
+ lines = [ln for ln in result.stdout.strip().split("\n") if ln]
+ self.assertEqual(len(lines), 2, msg=f"expected 2 matching lines, got {lines!r}")
+
+ def test_find_output_includes_session_window_pane_location(self):
+ self.set_sessions([
+ ("work", 1, ["101:zsh", "102:vim"], 950, 1, "/tmp/work"),
+ ])
+ result = self.run_script("find", "vim")
+ self.assertEqual(result.returncode, 0)
+ # Expected format: work:0.1 vim (window 0, pane index 1)
+ self.assertRegex(result.stdout, r"work:0\.1\s+vim")
+
+
+# -----------------------------------------------------------------------------
+# find — Boundary / Error cases
+# -----------------------------------------------------------------------------
+
+class TestFindBoundary(TmuxUtilHarness):
+
+ def test_find_no_matches_exits_nonzero(self):
+ self.set_sessions([
+ ("work", 1, ["101:zsh"], 950, 1, "/tmp/work"),
+ ])
+ result = self.run_script("find", "nothing-matches-this")
+ self.assertNotEqual(result.returncode, 0)
+ self.assertEqual(result.stdout.strip(), "")
+
+ def test_find_no_sessions_exits_nonzero(self):
+ self.set_sessions([])
+ result = self.run_script("find", "vim")
+ self.assertNotEqual(result.returncode, 0)
+
+ def test_find_no_pattern_exits_nonzero(self):
+ result = self.run_script("find")
+ self.assertNotEqual(result.returncode, 0)
+ self.assertIn("missing pattern", result.stderr)
+
+ def test_find_empty_pattern_exits_nonzero(self):
+ result = self.run_script("find", "")
+ self.assertNotEqual(result.returncode, 0)
+ self.assertIn("missing pattern", result.stderr)
+
+
if __name__ == "__main__":
unittest.main()