#!/usr/bin/env bash # Drive the agent popup from the windows of the session underneath it rather # than from the agent session's own window list. Bound inside the agent session # to C-n and C-p ("next"/"prev") and to 0..9 (a window # index) -- see tmux.conf. # # Root windows with no agent window are stepped over when cycling and do nothing # when named by index, so the popup and the session below it never disagree. # Neither ever creates an agent window; only A does. Both the client # underneath and the popup move, so closing the popup leaves you where you went. set -e target=${1:-next} us=$'\x1f' # Fall back to plain window cycling within the agent session when there is no # usable client underneath (popup opened before this was configured, or the # recorded client has gone away). fallback() { case $target in next) tmux next-window -t agent ;; prev) tmux previous-window -t agent ;; *) tmux select-window -t "agent:$target" 2>/dev/null || true ;; esac exit 0 } popup_client=$(tmux display-message -p '#{@agent_popup_client}' 2>/dev/null || true) [ -n "$popup_client" ] || fallback # The session and window underneath the popup, as the client sees them now. client_info=$(tmux list-clients -F "#{client_name}$us#{session_name}$us#{window_id}" \ | awk -v FS="$us" -v client="$popup_client" '$1 == client { print $2 FS $3; exit }') [ -n "$client_info" ] || fallback IFS=$us read -r client_session client_window <<<"$client_info" # Every agent window, with what it is keyed to. agent_ids=() agent_dirs=() agent_origins=() while IFS=$us read -r window_id dir origin; do [ -n "$dir" ] || continue agent_ids+=("$window_id") agent_dirs+=("$dir") agent_origins+=("$origin") done < <(tmux list-windows -t agent \ -F "#{window_id}$us#{@agent_dir}$us#{@agent_origin_window}" 2>/dev/null || true) [ ${#agent_ids[@]} -gt 0 ] || exit 0 # The agent window serving a root window: by recorded origin first, then by the # directory its first pane sits in. Mirrors agent-sync.sh's resolution, reversed. agent_window_for() { local root_window=$1 start_path=$2 current_path=$3 i for i in "${!agent_ids[@]}"; do if [ -n "${agent_origins[$i]}" ] && [ "${agent_origins[$i]}" = "$root_window" ]; then printf '%s\n' "${agent_ids[$i]}" return 0 fi done for i in "${!agent_ids[@]}"; do if [ "${agent_dirs[$i]}" = "$start_path" ] || [ "${agent_dirs[$i]}" = "$current_path" ]; then printf '%s\n' "${agent_ids[$i]}" return 0 fi done return 1 } # Move the client underneath and the popup together. The agent window is re-keyed # to the window being moved to, so the sync hook that the select below fires # agrees with what happened here instead of dragging the client back to wherever # that window was last opened from. move_to() { # move_to tmux set-window-option -t "$2" @agent_origin_window "$1" tmux select-window -t "$1" tmux select-window -t "$2" exit 0 } # The underlying session's windows, in its own order, each with its index and its # first pane's directory. Panes are listed grouped by window in index order. root_windows=() root_indexes=() root_paths=() seen_window="" while IFS=$us read -r window_id window_index start_path current_path; do [ "$window_id" != "$seen_window" ] || continue seen_window=$window_id root_windows+=("$window_id") root_indexes+=("$window_index") root_paths+=("$start_path$us$current_path") done < <(tmux list-panes -s -t "$client_session" \ -F "#{window_id}$us#{window_index}$us#{pane_start_path}$us#{pane_current_path}" \ 2>/dev/null || true) count=${#root_windows[@]} [ "$count" -gt 0 ] || exit 0 # 0..9: the window with that index in the session underneath. Doing # nothing when it has no agent window keeps the popup and that session in step. if [ -n "$target" ] && [ -z "${target//[0-9]/}" ]; then for i in "${!root_windows[@]}"; do [ "${root_indexes[$i]}" = "$target" ] || continue IFS=$us read -r start_path current_path <<<"${root_paths[$i]}" target_agent=$(agent_window_for "${root_windows[$i]}" "$start_path" "$current_path") || exit 0 move_to "${root_windows[$i]}" "$target_agent" done exit 0 fi # Where the client sits in that order. current=0 for i in "${!root_windows[@]}"; do if [ "${root_windows[$i]}" = "$client_window" ]; then current=$i break fi done if [ "$target" = prev ]; then step=-1; else step=1; fi # Walk in that direction for the first window with an agent window, wrapping. # Landing back on the starting window means nothing else has one: selecting it # again is a no-op, which is the wanted behaviour. i=$current for _ in $(seq 1 "$count"); do i=$(( (i + step + count) % count )) target_root=${root_windows[$i]} IFS=$us read -r start_path current_path <<<"${root_paths[$i]}" target_agent=$(agent_window_for "$target_root" "$start_path" "$current_path") || continue move_to "$target_root" "$target_agent" done