#!/usr/bin/env bash # Move the client that opened the agent popup to the root window matching the # agent session's current window. Installed by agent.sh as the agent session's # session-window-changed hook, so it runs for any window change made inside the # popup: C-n, C-p, select-window, choose-tree or the mouse. # # Set @agent_follow_session to "off" to keep the opening client in the session # it is already on, never switching it to the session owning the match. set -e # Fields are joined with a unit separator, not a tab: `read` folds runs of tabs # and spaces even when IFS names them, so an unset option in the middle would # silently shift every field after it. us=$'\x1f' # Everything recorded about the agent session's current window, in one # round-trip. Format lookups rather than show-option, so unset user options come # back empty instead of logging "invalid option" into the tmux message log. state=$(tmux display-message -p -t agent "#{session_attached}$us#{@agent_dir}\ $us#{@agent_origin_window}$us#{@agent_popup_client}$us#{@agent_follow_session}" \ 2>/dev/null || true) IFS=$us read -r attached agent_dir origin_window popup_client follow <<<"$state" # Only sync while a popup is actually attached. Without this guard the # detach-client + kill-window agent-cmd.sh performs when an agent exits would # drag the root client to whichever window tmux picks next. [ "${attached:-0}" -gt 0 ] || exit 0 [ -n "$agent_dir" ] || exit 0 # An empty session means the recorded client has since gone away. client_session="" if [ -n "$popup_client" ]; then client_session=$(tmux list-clients -F "#{client_name}$us#{session_name}" \ | awk -v FS="$us" -v client="$popup_client" '$1 == client { print $2; exit }') fi target_window="" target_session="" # Prefer the recorded origin window, as long as it still exists. if [ -n "$origin_window" ]; then while IFS=$us read -r window_id session_name; do if [ "$window_id" = "$origin_window" ] && [ "$session_name" != agent ]; then target_window=$window_id target_session=$session_name break fi done < <(tmux list-windows -a -F "#{window_id}$us#{session_name}") fi # Otherwise match the recorded directory against root windows. This covers agent # windows created before origin recording existed, and root windows that have # been recreated since. if [ -z "$target_window" ]; then seen_window="" while IFS=$us read -r window_id session_name start_path current_path; do # Panes are listed grouped by window in index order, so the first row for a # window is its first pane. [ "$window_id" != "$seen_window" ] || continue seen_window=$window_id [ "$session_name" != agent ] || continue [ "$start_path" = "$agent_dir" ] || [ "$current_path" = "$agent_dir" ] || continue # Prefer a window in the session the opening client is already on. if [ "$session_name" = "$client_session" ]; then target_window=$window_id target_session=$session_name break fi if [ -z "$target_window" ]; then target_window=$window_id target_session=$session_name fi done < <(tmux list-panes -a -F "#{window_id}$us#{session_name}\ $us#{pane_start_path}$us#{pane_current_path}") fi # No match: leave the root sessions alone rather than inventing a window. [ -n "$target_window" ] || exit 0 # Window ids are server-global, so this moves the owning session whichever # client happens to be on it. tmux select-window -t "$target_window" 2>/dev/null || exit 0 # Selecting is enough when the client is already on the target session. [ -n "$client_session" ] || exit 0 [ "$follow" != off ] || exit 0 if [ -n "$target_session" ] && [ "$target_session" != "$client_session" ]; then tmux switch-client -c "$popup_client" -t "$target_session" fi