aboutsummaryrefslogtreecommitdiff
path: root/tmuxrdr
diff options
context:
space:
mode:
authorIgor Soarez <igor@soarez.org>2026-10-05 15:45:17 +0300
committerIgor Soarez <igor@soarez.org>2026-10-05 15:45:17 +0300
commite953b5c4797e629e92acca7525f40f2ce03ad6e1 (patch)
tree3664d0a71ca06f66c771f1cf7bf2b7d38de15f1b /tmuxrdr
parent9a12d7fb0eae23c016d3e941a16765084fbf0eaa (diff)
tmuxrdr: tmux tab row on, session commands in the : prompt
The config turns ui.tab_style = "tmux" on, with the row at the bottom like tmux's status line. The : prompt takes tmux's session commands, on workspaces: new-session (-s, -c, -d, -A), switch-client -t/-n/-p/-l, attach-session -t, list-sessions, kill-session -t, with tmux's messages for a taken or missing name. test-e2e covers both, prints the config check's diagnostics when it fails, and finds the tab row wherever it is drawn.
Diffstat (limited to 'tmuxrdr')
-rw-r--r--tmuxrdr/config/config.toml27
-rwxr-xr-xtmuxrdr/plugin/bin/command-prompt57
-rwxr-xr-xtmuxrdr/scripts/test-e2e24
3 files changed, 94 insertions, 14 deletions
diff --git a/tmuxrdr/config/config.toml b/tmuxrdr/config/config.toml
index 76feb830..82e4bca3 100644
--- a/tmuxrdr/config/config.toml
+++ b/tmuxrdr/config/config.toml
@@ -336,9 +336,18 @@ copy_on_select = false
# default 5". herdr's default is already 3, left explicit.
mouse_scroll_lines = 3
-# # tmux's status line sits at the bottom of the screen; herdr draws its tab bar
-# # at the top by default. Bottom, like tmux.
-# tab_bar_position = "bottom"
+# tmux's status line sits at the bottom of the screen; herdr draws its tab bar
+# at the top by default. Bottom, like tmux.
+tab_bar_position = "bottom"
+
+# tmux's status line, drawn by tmuxrdr's own ui.tab_style
+# (src/client/shell/tabs_tmux.rs): "[session] 1:name* 2:name- 3:name ",
+# window-status-format "#I:#W#{?window_flags,#{window_flags}, }", plain text
+# one space apart, the session cut to status-left-length 10, tabs nobody named
+# called after their pane's directory (this tmux.conf's automatic-rename-format
+# '#{b:pane_current_path}'). herdr's own style draws padded blocks, and shows
+# a named tab's name without its number.
+tab_style = "tmux"
# tmux.conf: set-option -g status-right '#h %a %d-%b %H:%M', replaced by
# ~/.tmux.conf.local with '%d-%b %H:%M' -- day-month and time at the right edge
@@ -356,11 +365,13 @@ prompt_new_tab_name = false
# tmux.conf: automatic-rename on, automatic-rename-format
# '#{b:pane_current_path}', status-interval 5 -- windows name themselves after
-# their pane's directory. herdr names no tab (an unnamed one shows its place
-# in the tab bar). tmux-mode's bin/automatic-rename does it, a manual rename
-# switching it off for that tab as in tmux, run every 5 s as a tab_bar_right
-# command entry that prints nothing. Off while tab_bar_right is commented out
-# above; to have it, give tab_bar_right this entry:
+# their pane's directory. tab_style = "tmux" above already shows tabs that way,
+# in this client. herdr itself names no tab: everywhere else -- the navigator,
+# the CLI -- an unnamed tab is its number. tmux-mode's bin/automatic-rename
+# gives tabs real names on the server, a manual rename switching it off for
+# that tab as in tmux, run every 5 s as a tab_bar_right command entry that
+# prints nothing. Not in tab_bar_right above; to have it, add this entry
+# there:
# { type = "command", command = "sh ~/.local/share/tmuxrdr/plugin/bin/automatic-rename", interval_seconds = 5, timeout_seconds = 4 }
# tmux draws pane borders only when a window is split; a lone pane has none.
diff --git a/tmuxrdr/plugin/bin/command-prompt b/tmuxrdr/plugin/bin/command-prompt
index 8c4a4e4f..a97d6702 100755
--- a/tmuxrdr/plugin/bin/command-prompt
+++ b/tmuxrdr/plugin/bin/command-prompt
@@ -25,6 +25,15 @@ opt() { # opt -t ARGS...: the value after -t, if any
done
}
+# tmux's sessions are herdr's workspaces. A target (-t) is a workspace's name
+# or id, as tmux takes "name", "name:" or "=name"; prints the id.
+session_id() {
+ _t="${1#=}"; _t="${_t%%:*}"
+ "$H" workspace list | jq -r --arg t "$_t" \
+ '[.result.workspaces[] | select(.label == $t or .workspace_id == $t)][0].workspace_id // empty'
+}
+current_session() { pane_info "$p" workspace_id; }
+
# The case lives in a function: bash 3.2, which is macOS /bin/sh, misparses a
# case statement written inside $(...) -- its pattern parentheses end the
# substitution early.
@@ -32,7 +41,37 @@ run() {
case "$cmd" in
kill-pane | killp) "$H" pane close "$p" ;;
kill-window | killw) "$H" tab close "$(pane_info "$p" tab_id)" ;;
- kill-session) "$H" workspace close "$(pane_info "$p" workspace_id)" ;;
+ kill-session)
+ t="$(opt -t "$@")"
+ if [ -n "$t" ]; then w="$(session_id "$t")"; [ -n "$w" ] || { echo "can't find session: $t"; exit 1; }
+ else w="$(current_session)"; fi
+ "$H" workspace close "$w" ;;
+ new-session | new)
+ # new-session [-A] [-d] [-s name] [-c dir]: a workspace, switched to
+ # unless -d; -A switches to an existing one of that name instead.
+ name="$(opt -s "$@")"; dir="$(opt -c "$@")"
+ flag=-A; attach=""; has "$@" && attach=1
+ flag=-d; focus=--focus; has "$@" && focus=--no-focus
+ if [ -n "$name" ]; then
+ w="$(session_id "$name")"
+ if [ -n "$w" ]; then
+ [ -n "$attach" ] || { echo "duplicate session: $name"; exit 1; }
+ "$H" workspace focus "$w"; exit
+ fi
+ fi
+ set -- workspace create "$focus"
+ [ -n "$name" ] && set -- "$@" --label "$name"
+ [ -n "$dir" ] && set -- "$@" --cwd "$dir"
+ "$H" "$@" ;;
+ attach-session | attach | a)
+ t="$(opt -t "$@")"; w="$(session_id "${t:-}")"
+ [ -n "$w" ] || { echo "can't find session: ${t:-}"; exit 1; }
+ "$H" workspace focus "$w" ;;
+ list-sessions | ls)
+ # tmux: "name: N windows (attached)"; herdr keeps no creation time.
+ "$H" workspace list | jq -r '.result.workspaces[]
+ | "\(.label): \(.tab_count) windows\(if .focused then " (attached)" else "" end)"'
+ exit 2 ;;
kill-server) "$H" server stop ;;
new-window | neww) "$H" tab create --workspace "$(pane_info "$p" workspace_id)" --focus ;;
split-window | splitw | new-pane | newp)
@@ -48,7 +87,21 @@ run() {
else flag=-D; if has "$@"; then sh "$BIN/swap-pane" next; else action swap-marked; fi; fi ;;
rotate-window | rotatew) flag=-D; if has "$@"; then sh "$BIN/rotate-window" down; else sh "$BIN/rotate-window" up; fi ;;
last-window | last) action last-window ;;
- switch-client | switchc) flag=-l; if has "$@"; then action last-session; else echo "switch-client: only -l is supported"; exit 1; fi ;;
+ switch-client | switchc)
+ flag=-l; if has "$@"; then action last-session; exit; fi
+ t="$(opt -t "$@")"
+ if [ -n "$t" ]; then
+ w="$(session_id "$t")"; [ -n "$w" ] || { echo "can't find session: $t"; exit 1; }
+ "$H" workspace focus "$w"; exit
+ fi
+ # -n / -p: the next / previous workspace in the sidebar's order, wrapping.
+ flag=-n; step=0; has "$@" && step=1
+ flag=-p; has "$@" && step=-1
+ [ "$step" != 0 ] || { echo "switch-client: -t, -n, -p or -l"; exit 1; }
+ w="$("$H" workspace list | jq -r --argjson s "$step" '.result.workspaces as $w
+ | ($w | map(.focused) | index(true)) as $i
+ | $w[(($i + $s) % ($w | length) + ($w | length)) % ($w | length)].workspace_id')"
+ "$H" workspace focus "$w" ;;
select-pane | selectp)
flag=-m; if has "$@"; then action mark; exit; fi
flag=-M; if has "$@"; then action clear-mark; exit; fi
diff --git a/tmuxrdr/scripts/test-e2e b/tmuxrdr/scripts/test-e2e
index 7c82086e..0adaebc4 100755
--- a/tmuxrdr/scripts/test-e2e
+++ b/tmuxrdr/scripts/test-e2e
@@ -98,6 +98,8 @@ ws() { "${H[@]}" workspace list | jq -r '.result.workspaces[] | select(.focused)
ws_label() { "${H[@]}" workspace list | jq -r '.result.workspaces[] | select(.focused) | .label'; }
tab() { "${H[@]}" tab list --workspace "$(ws)" | jq -r '.result.tabs[] | select(.focused) | .tab_id'; }
tab_pos() { "${H[@]}" tab list --workspace "$(ws)" | jq -r '[.result.tabs[] | .focused] | index(true) + 1'; }
+ws_count() { "${H[@]}" workspace list | jq -r '.result.workspaces | length'; }
+prompt() { "${T[@]}" send-keys -t e2e C-b :; sleep "$delay"; type_line "$1"; }
tab_count() { "${H[@]}" tab list --workspace "$(ws)" | jq -r '.result.tabs | length'; }
tab_panes() { "${H[@]}" pane list --workspace "$(ws)" | jq -r --arg t "${1:-$(tab)}" '[.result.panes[] | select(.tab_id == $t)] | length'; }
@@ -113,8 +115,9 @@ done
[[ "$(focus 2>/dev/null)" == w1:p1 ]] || die "herdr client did not come up"
echo "herdr $("$herdr" --version | awk '{print $2}'), session $session, config copy of tmuxrdr/config/config.toml"
-check 'config check: no unknown keys or bad bindings' \
- "$(HERDR_CONFIG_PATH="$cfg" "$herdr" config check 2>&1 | head -n 1)" "config: ok"
+diagnostics="$(HERDR_CONFIG_PATH="$cfg" "$herdr" config check 2>&1)"
+check 'config check: no unknown keys or bad bindings' "$(head -n 1 <<<"$diagnostics")" "config: ok"
+[[ "$diagnostics" == "config: ok" ]] || sed 's/^/ /' <<<"$diagnostics"
# --- panes -------------------------------------------------------------------
@@ -172,6 +175,9 @@ k C-b p; check 'prefix+p previous-window' "$(tab_pos)" 2
k C-b n; check 'prefix+n next-window' "$(tab_pos)" 3
k C-b 1; check 'prefix+1 select-window' "$(tab_pos)" 1
k C-b 3; check 'prefix+3 select-window' "$(tab_pos)" 3
+# ui.tab_style = "tmux": tmux's status line, the window before last flagged "-"
+# and unnamed tabs called after their directory (/private/tmp).
+check_seen 'tab row in tmux format: [session] #I:#W#F' '\[tmp\] 1:tmp- 2:tmp 3:tmp\*'
k C-b 1
k C-b '!'
check 'prefix+! break-pane' "$(tab_count) $(tab_panes) $(tab_panes w1:t1)" "4 1 3"
@@ -195,6 +201,16 @@ k C-b '('; check 'prefix+( previous session' "$(ws_label)" tmp
k C-b L; check 'prefix+L last session' "$(ws_label)" other
k C-b L; check 'prefix+L toggles back' "$(ws_label)" tmp
+# tmux's session commands, on workspaces, from the : prompt.
+prompt 'new-session -s e2e-new'; check ':new-session -s creates and switches' "$(ws_label) $(ws_count)" "e2e-new 3"
+prompt 'switch-client -t tmp'; check ':switch-client -t' "$(ws_label)" tmp
+prompt 'new -A -s e2e-new'; check ':new -A switches to the existing one' "$(ws_label) $(ws_count)" "e2e-new 3"
+prompt 'switch-client -p'; check ':switch-client -p' "$(ws_label)" other
+prompt 'new-session -s tmp'; check_seen ':new-session on a taken name' 'duplicate session: tmp'; k x
+prompt 'ls'; check_seen ':ls lists the sessions' 'e2e-new: [0-9]+ windows'; k x
+prompt 'kill-session -t e2e-new'; check ':kill-session -t' "$(ws_count)" 2
+prompt 'switch-client -t tmp'; check ':switch-client -t back' "$(ws_label)" tmp
+
# --- prompts, choosers, modes ------------------------------------------------
"${T[@]}" send-keys -t e2e C-b :; check_seen 'prefix+: command prompt' '│:'
@@ -243,11 +259,11 @@ fi
# --- reload, then detach -----------------------------------------------------
k C-b ')' # "other": a workspace with a single tab
-check_seen 'tab row before reload' '│ *1 +'
+check_seen 'tab row before reload' '\[other\] 1:tmp\*'
sed -i.bak 's/^hide_tab_bar_when_single_tab = false/hide_tab_bar_when_single_tab = true/' "$cfg"
k C-b r
check 'prefix+r reload hides the single tab row' \
- "$("${T[@]}" capture-pane -p -t e2e | sed -n 1p | grep -c -E '│ *1 +' | tr -d ' ')" 0
+ "$("${T[@]}" capture-pane -p -t e2e | grep -c -E '\[other\] 1:tmp\*' | tr -d ' ')" 0
k C-b d; sleep 1
check 'prefix+d detach ends the client' "$("${T[@]}" has-session -t e2e 2>/dev/null && echo attached || echo detached)" detached