Last active
April 19, 2026 13:36
-
-
Save stephenfeather/49fb5f6dfdf379d5e545fef74a8f187a to your computer and use it in GitHub Desktop.
tmux-msg + tmux-name-pane: send messages to tmux panes by name, with sender attribution and opt-in broadcast modes
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| # tmux-msg — send a message to a named tmux window or pane with sender attribution. | |
| # | |
| # A wrapper around `tmux send-keys` that solves four practical problems: | |
| # | |
| # 1. tmux's send-keys needs TWO calls to submit a command: one for the payload, | |
| # one for Enter. Forgetting the second call leaves text sitting un-submitted | |
| # in the target pane. This script always sends both. | |
| # | |
| # 2. send-keys addresses windows only. If a window has multiple panes, only the | |
| # active one receives the keystrokes. This script resolves a friendly label | |
| # to a specific pane via pane titles (`tmux select-pane -T <title>`). | |
| # | |
| # 3. Typos silently drop messages. This script verifies the target exists | |
| # and prints the list of available windows/panes if it doesn't. | |
| # | |
| # 4. Recipients can't tell who sent a message. This script prepends a sender | |
| # tag — `[PANE-TITLE ...]` from inside tmux, `[USER ...]` from outside — | |
| # so the receiving pane's capture-pane history is self-explanatory. | |
| # | |
| # Optional broadcast modes let you fan a single message out to every pane in a | |
| # window, session, or every pane whose title marks it as "interesting." | |
| # | |
| # ----------------------------------------------------------------------------- | |
| # | |
| # TARGET SYNTAX (single-target mode) | |
| # | |
| # <label> Window named <label> (any session), | |
| # else pane titled <label> (any session). | |
| # <session>:<label> Window <label> in <session>, else pane titled <label> | |
| # in <session>. | |
| # <session>:<win>:<pt> Pane titled <pt> inside window <win> in <session>. | |
| # <session>:<w>.<p> Raw tmux target (passed through unchanged). | |
| # | |
| # Matching is case-insensitive. Pane titles are matched on a whole-word basis | |
| # with a leading non-word-character strip, so titles like `● build` or | |
| # `⠂ worker-1` (common with tmux statusline plugins that prefix spinners) still | |
| # match `build` / `worker-1`. | |
| # | |
| # USAGE | |
| # | |
| # tmux-msg.sh <target> <message...> | |
| # tmux-msg.sh -s <session> <target> <message...> | |
| # | |
| # NOTE: quote messages that contain shell metacharacters (? * [ ] ! ; & | > <). | |
| # With zsh's default NOMATCH option, a bare `?` triggers a "no matches | |
| # found" error before your script ever runs. Example: | |
| # tmsg architect "is the build stable?" # quoted — works | |
| # tmsg architect is the build stable? # unquoted — zsh errors | |
| # | |
| # tmux-msg.sh --all <session> <message...> Broadcast: every pane in <session>. | |
| # tmux-msg.sh --window <target> <message...> Broadcast: every pane in a window. | |
| # tmux-msg.sh --roles [<session>] <message...> Broadcast: every pane whose title | |
| # differs from the hostname and from | |
| # the pane's current command. | |
| # | |
| # tmux-msg.sh --list List windows. | |
| # tmux-msg.sh --list-panes List panes with titles. | |
| # tmux-msg.sh --resolve <target> Dry-run resolution. | |
| # tmux-msg.sh -h | --help Show help. | |
| # | |
| # SENDER ATTRIBUTION (prepended automatically) | |
| # | |
| # Inside tmux → "[AGENT <source>]" | |
| # <source> is chosen in this order: | |
| # 1. $TMUX_AGENT_NAME (explicit override) | |
| # 2. Current pane title (if non-default: not hostname, not user@host) | |
| # 3. Current window name (#W) | |
| # | |
| # Outside tmux → "[HUMAN <name>]" | |
| # <name> defaults to $TMUX_MSG_SENDER, else $USER. | |
| # | |
| # Both tags are labels only — rename them in your shell if you prefer: | |
| # export TMUX_MSG_TAG_INSIDE=PANE | |
| # export TMUX_MSG_TAG_OUTSIDE=USER | |
| # | |
| # SETTING PANE TITLES | |
| # | |
| # From inside the pane: tmux select-pane -T <label> | |
| # From another pane: tmux select-pane -t <session>:<w>.<p> -T <label> | |
| # | |
| # A companion helper `tmux-name-pane.sh` wraps this and can also persist the | |
| # title so shell prompt updates don't clobber it. | |
| # | |
| # EXIT CODES | |
| # | |
| # 0 success (message sent, or listing produced) | |
| # 1 usage error | |
| # 2 target not found | |
| # 127 tmux not installed | |
| # | |
| # LICENSE: MIT. No warranty. Script does what it says; review before use. | |
| set -euo pipefail | |
| usage() { | |
| sed -n '2,72p' "$0" | sed 's/^# \{0,1\}//' | |
| exit "${1:-0}" | |
| } | |
| SESSION="" | |
| LIST=0 | |
| LIST_PANES=0 | |
| DRY_RUN=0 | |
| MODE="single" | |
| MODE_ARG="" | |
| TAG_INSIDE="${TMUX_MSG_TAG_INSIDE:-AGENT}" | |
| TAG_OUTSIDE="${TMUX_MSG_TAG_OUTSIDE:-HUMAN}" | |
| while [ $# -gt 0 ]; do | |
| case "$1" in | |
| -h|--help) usage 0 ;; | |
| --list|-l) LIST=1; shift ;; | |
| --list-panes|-L) LIST_PANES=1; shift ;; | |
| -s|--session) SESSION="$2"; shift 2 ;; | |
| -n|--dry-run|--resolve) DRY_RUN=1; shift ;; | |
| --all) MODE="all"; MODE_ARG="$2"; shift 2 ;; | |
| --window|--broadcast) MODE="window"; MODE_ARG="$2"; shift 2 ;; | |
| --roles) | |
| MODE="roles"; shift | |
| if [ $# -gt 0 ] && [[ "$1" != *" "* ]] && tmux has-session -t "$1" 2>/dev/null; then | |
| MODE_ARG="$1"; shift | |
| fi | |
| ;; | |
| --) shift; break ;; | |
| -*) echo "unknown flag: $1" >&2; usage 1 ;; | |
| *) break ;; | |
| esac | |
| done | |
| if ! command -v tmux >/dev/null 2>&1; then | |
| echo "tmux not installed" >&2 | |
| exit 127 | |
| fi | |
| # ----- Listing helpers -------------------------------------------------------- | |
| list_windows() { | |
| local scope=() | |
| if [ -n "$SESSION" ]; then scope=(-t "$SESSION"); else scope=(-a); fi | |
| tmux list-windows "${scope[@]}" -F '#{session_name}:#{window_index} #{window_name}' | |
| } | |
| list_panes() { | |
| local scope=() | |
| if [ -n "$SESSION" ]; then scope=(-s -t "$SESSION"); else scope=(-a); fi | |
| tmux list-panes "${scope[@]}" \ | |
| -F '#{session_name}:#{window_index}.#{pane_index} #{window_name} #{pane_title} #{pane_current_command}' | |
| } | |
| if [ "$LIST" -eq 1 ]; then | |
| list_windows | |
| exit 0 | |
| fi | |
| if [ "$LIST_PANES" -eq 1 ]; then | |
| printf '%-30s %-18s %-22s %s\n' 'TARGET' 'WINDOW' 'PANE_TITLE' 'COMMAND' | |
| list_panes | awk -F'\t' '{printf "%-30s %-18s %-22s %s\n", $1, $2, $3, $4}' | |
| exit 0 | |
| fi | |
| # ----- Pane-title matcher (shared awk) ---------------------------------------- | |
| # | |
| # Matches <title> against <label>: | |
| # 1. exact case-insensitive equality | |
| # 2. whole-word case-insensitive substring (so "build" matches "● build" | |
| # but "api" does NOT match "apidev") | |
| # 3. after stripping leading non-word characters (spinners, emoji, punctuation), | |
| # exact case-insensitive equality | |
| # | |
| # Uses tolower() rather than IGNORECASE so it works with BSD awk (macOS default). | |
| MATCH_AWK=' | |
| function match_label(title, lbl, norm, t, l) { | |
| t = tolower(title); l = tolower(lbl) | |
| if (t == l) return 1 | |
| if (t ~ "(^|[^[:alnum:]_])" l "([^[:alnum:]_]|$)") return 1 | |
| norm = t | |
| sub(/^[^[:alnum:]_]+/, "", norm) | |
| if (norm == l) return 1 | |
| return 0 | |
| } | |
| ' | |
| # ----- Resolvers -------------------------------------------------------------- | |
| # Resolve a friendly target string to a tmux-native "session:window.pane" form. | |
| # Writes the resolved target on stdout. Returns 1 on failure (empty stdout). | |
| resolve_target() { | |
| local target="$1" | |
| local session="" window="" pane_label="" | |
| # If --session is set and target has no session part, prepend it. | |
| if [ -n "$SESSION" ] && [[ "$target" != *:* ]]; then | |
| target="${SESSION}:${target}" | |
| fi | |
| # Pass-through: "<...>.<pane>" is already a tmux-native target. | |
| if [[ "$target" == *.* ]]; then | |
| echo "$target" | |
| return 0 | |
| fi | |
| local IFS=':' | |
| read -r -a parts <<< "$target" | |
| case "${#parts[@]}" in | |
| 1) window="${parts[0]}" ;; | |
| 2) session="${parts[0]}"; window="${parts[1]}" ;; | |
| 3) session="${parts[0]}"; window="${parts[1]}"; pane_label="${parts[2]}" ;; | |
| *) return 1 ;; | |
| esac | |
| unset IFS | |
| # Three-part form: explicit pane label inside a window. | |
| if [ -n "$pane_label" ]; then | |
| local match | |
| match=$(tmux list-panes -t "${session}:${window}" \ | |
| -F '#{session_name}:#{window_index}.#{pane_index} #{pane_title} #{window_name}' 2>/dev/null \ | |
| | awk -F'\t' -v lbl="$pane_label" "$MATCH_AWK"' | |
| { if (match_label($2, lbl) || match_label($3, lbl)) { print $1; exit } }') | |
| [ -n "$match" ] && echo "$match" && return 0 | |
| return 1 | |
| fi | |
| # Try window-name first (exact, case-insensitive). | |
| local win_match | |
| win_match=$(tmux list-windows $( [ -n "$session" ] && printf -- '-t %s' "$session" || printf -- '-a' ) \ | |
| -F '#{session_name}:#{window_name}' 2>/dev/null \ | |
| | awk -F: -v sess="$session" -v w="$window" ' | |
| (sess == "" || tolower($1) == tolower(sess)) && tolower($2) == tolower(w) { print $1":"$2; exit }') | |
| if [ -n "$win_match" ]; then echo "$win_match"; return 0; fi | |
| # Fall back to pane-title search (loose match via MATCH_AWK). | |
| local pane_match | |
| pane_match=$(tmux list-panes -a \ | |
| -F '#{session_name}:#{window_index}.#{pane_index} #{session_name} #{pane_title}' 2>/dev/null \ | |
| | awk -F'\t' -v sess="$session" -v lbl="$window" "$MATCH_AWK"' | |
| (sess == "" || tolower($2) == tolower(sess)) { if (match_label($3, lbl)) { print $1; exit } }') | |
| [ -n "$pane_match" ] && echo "$pane_match" && return 0 | |
| return 1 | |
| } | |
| # Resolve <target> to a "session:window" (strip .pane or look up by name). | |
| resolve_window() { | |
| local target="$1" | |
| if [ -n "$SESSION" ] && [[ "$target" != *:* ]]; then | |
| target="${SESSION}:${target}" | |
| fi | |
| if [[ "$target" == *.* ]]; then | |
| echo "${target%.*}" | |
| return 0 | |
| fi | |
| local session="" window="$target" | |
| if [[ "$target" == *:* ]]; then | |
| session="${target%%:*}"; window="${target#*:}" | |
| fi | |
| tmux list-windows $( [ -n "$session" ] && printf -- '-t %s' "$session" || printf -- '-a' ) \ | |
| -F '#{session_name}:#{window_name}' 2>/dev/null \ | |
| | awk -F: -v sess="$session" -v w="$window" ' | |
| (sess == "" || tolower($1) == tolower(sess)) && tolower($2) == tolower(w) { print $1":"$2; exit }' | |
| } | |
| # List panes whose titles look like meaningful labels (skip bare shells). | |
| # Heuristic "role pane" = title is set, not equal to hostname, not user@host form, | |
| # not a hostname.local/.lan, and not equal to the pane's current command. | |
| # Callers can extend this by filtering stdout further. | |
| list_role_panes() { | |
| local scope=() | |
| if [ -n "${1:-}" ]; then scope=(-s -t "$1"); else scope=(-a); fi | |
| local host | |
| host="$(tmux display-message -p '#h' 2>/dev/null || hostname -s 2>/dev/null || echo '')" | |
| tmux list-panes "${scope[@]}" \ | |
| -F '#{session_name}:#{window_index}.#{pane_index} #{pane_title} #{pane_current_command}' 2>/dev/null \ | |
| | awk -F'\t' -v host="$host" ' | |
| { | |
| title = $2; cmd = $3 | |
| if (title == "") next | |
| if (title == host) next | |
| if (title ~ "@") next | |
| if (title ~ /\.(local|lan)$/) next | |
| if (title == cmd) next | |
| print $1 "\t" title | |
| }' | |
| } | |
| # ----- Argument parse for target + message ------------------------------------ | |
| TARGET="" | |
| MSG="" | |
| if [ "$DRY_RUN" -eq 1 ]; then | |
| if [ $# -lt 1 ]; then echo "usage: tmux-msg.sh --resolve <target>" >&2; exit 1; fi | |
| TARGET="$1" | |
| MSG="(dry run)" | |
| else | |
| case "$MODE" in | |
| single) | |
| if [ $# -lt 2 ]; then usage 1; fi | |
| TARGET="$1"; shift | |
| MSG="$*" | |
| ;; | |
| all|window|roles) | |
| if [ $# -lt 1 ]; then | |
| echo "${MODE} mode needs a message" >&2; usage 1 | |
| fi | |
| MSG="$*" | |
| ;; | |
| esac | |
| fi | |
| # ----- Sender attribution ----------------------------------------------------- | |
| if [ -n "${TMUX:-}" ]; then | |
| SRC_TITLE="$(tmux display-message -p '#{pane_title}' 2>/dev/null || true)" | |
| SRC_WIN="$(tmux display-message -p '#W' 2>/dev/null || echo unknown)" | |
| SRC_HOST="$(tmux display-message -p '#h' 2>/dev/null || true)" | |
| if [ -n "${TMUX_AGENT_NAME:-}" ]; then | |
| SRC="$TMUX_AGENT_NAME" | |
| elif [ -n "$SRC_TITLE" ] && [ "$SRC_TITLE" != "$SRC_HOST" ] && [[ "$SRC_TITLE" != *@* ]]; then | |
| SRC="$SRC_TITLE" | |
| else | |
| SRC="$SRC_WIN" | |
| fi | |
| SENDER="[${TAG_INSIDE} ${SRC}]" | |
| else | |
| SENDER="[${TAG_OUTSIDE} ${TMUX_MSG_SENDER:-${USER:-unknown}}]" | |
| fi | |
| PAYLOAD="${SENDER} ${MSG}" | |
| # ----- Send helper ------------------------------------------------------------ | |
| send_to() { | |
| local tgt="$1" | |
| tmux send-keys -t "$tgt" -- "$PAYLOAD" | |
| tmux send-keys -t "$tgt" Enter | |
| echo "→ ${tgt}: ${PAYLOAD}" | |
| } | |
| # ----- Dispatch --------------------------------------------------------------- | |
| if [ "$DRY_RUN" -eq 1 ]; then | |
| FULL_TARGET="$(resolve_target "$TARGET" || true)" | |
| if [ -z "$FULL_TARGET" ]; then | |
| echo "tmux-msg: could not resolve target '$TARGET'" >&2 | |
| exit 2 | |
| fi | |
| echo "resolved: ${TARGET} → ${FULL_TARGET}" | |
| echo "sender: ${SENDER}" | |
| exit 0 | |
| fi | |
| case "$MODE" in | |
| single) | |
| FULL_TARGET="$(resolve_target "$TARGET" || true)" | |
| if [ -z "$FULL_TARGET" ]; then | |
| echo "tmux-msg: could not resolve target '$TARGET'" >&2 | |
| echo "" >&2 | |
| echo "windows:" >&2 | |
| list_windows | sed 's/^/ /' >&2 | |
| echo "" >&2 | |
| echo "panes (session:w.p window pane_title command):" >&2 | |
| list_panes | awk -F'\t' '{printf " %-28s %-16s %-20s %s\n", $1, $2, $3, $4}' >&2 | |
| echo "" >&2 | |
| echo "tip: set a pane title with tmux select-pane -T <label>" >&2 | |
| exit 2 | |
| fi | |
| send_to "$FULL_TARGET" | |
| ;; | |
| all) | |
| if ! tmux has-session -t "$MODE_ARG" 2>/dev/null; then | |
| echo "tmux-msg: no session named '$MODE_ARG'" >&2 | |
| exit 2 | |
| fi | |
| targets=$(tmux list-panes -s -t "$MODE_ARG" \ | |
| -F '#{session_name}:#{window_index}.#{pane_index}' 2>/dev/null) | |
| if [ -z "$targets" ]; then | |
| echo "tmux-msg: session '$MODE_ARG' has no panes" >&2 | |
| exit 2 | |
| fi | |
| count=0 | |
| while IFS= read -r t; do send_to "$t"; count=$((count+1)); done <<< "$targets" | |
| echo "broadcast: ${count} panes in session '${MODE_ARG}'" | |
| ;; | |
| window) | |
| win="$(resolve_window "$MODE_ARG" || true)" | |
| if [ -z "$win" ]; then | |
| echo "tmux-msg: could not resolve window '$MODE_ARG'" >&2 | |
| exit 2 | |
| fi | |
| targets=$(tmux list-panes -t "$win" \ | |
| -F '#{session_name}:#{window_index}.#{pane_index}' 2>/dev/null) | |
| if [ -z "$targets" ]; then | |
| echo "tmux-msg: window '$win' has no panes" >&2 | |
| exit 2 | |
| fi | |
| count=0 | |
| while IFS= read -r t; do send_to "$t"; count=$((count+1)); done <<< "$targets" | |
| echo "broadcast: ${count} panes in window '${win}'" | |
| ;; | |
| roles) | |
| agents=$(list_role_panes "$MODE_ARG" 2>/dev/null || true) | |
| if [ -z "$agents" ]; then | |
| echo "tmux-msg: no role-titled panes found${MODE_ARG:+ in session '$MODE_ARG'}" >&2 | |
| echo "(role panes are ones whose title differs from the hostname and current command)" >&2 | |
| exit 2 | |
| fi | |
| count=0 | |
| while IFS=$'\t' read -r t title; do | |
| [ -z "$t" ] && continue | |
| send_to "$t" | |
| count=$((count+1)) | |
| done <<< "$agents" | |
| echo "broadcast: ${count} role panes${MODE_ARG:+ in session '$MODE_ARG'}" | |
| ;; | |
| esac |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| # tmux-name-pane — set a tmux pane's title so it can be addressed by name. | |
| # | |
| # tmux panes have a `pane_title` attribute that tools can query (`#T` in format | |
| # strings) and that can be displayed in the status line. Setting a meaningful | |
| # title lets other tools — notably the companion `tmux-msg.sh` script — route | |
| # messages to a pane by role rather than by numeric index. | |
| # | |
| # This helper does three things: | |
| # | |
| # 1. Runs `tmux select-pane -T <title>` on the requested pane. | |
| # 2. Optionally persists the title by setting the `allow-rename` and | |
| # `automatic-rename` options off for that pane, so a shell prompt or | |
| # program doesn't overwrite the title via terminal escape sequences. | |
| # 3. Optionally emits a shell-escape sequence you can `echo` inside the pane | |
| # to set the title from within the pane's own shell. | |
| # | |
| # ----------------------------------------------------------------------------- | |
| # | |
| # USAGE | |
| # | |
| # tmux-name-pane.sh <title> Set title of current pane. | |
| # tmux-name-pane.sh -t <target> <title> Set title of a specific pane | |
| # (<target> is any tmux pane | |
| # target, e.g. "session:0.1"). | |
| # tmux-name-pane.sh --persist <title> Also disable auto-rename so | |
| # the title sticks. | |
| # tmux-name-pane.sh --escape <title> Print the ANSI escape that | |
| # sets the title from within | |
| # the pane's own shell (for | |
| # use in a PS1/precmd hook). | |
| # tmux-name-pane.sh --clear Clear the current pane's title. | |
| # tmux-name-pane.sh --list List all panes with titles. | |
| # tmux-name-pane.sh -h | --help Show help. | |
| # | |
| # EXAMPLES | |
| # | |
| # # Inside the pane you want to name: | |
| # tmux-name-pane.sh build | |
| # | |
| # # From elsewhere, naming a specific pane: | |
| # tmux-name-pane.sh -t work:0.2 worker-1 | |
| # | |
| # # Make it stick across shell prompts that would otherwise retitle the pane: | |
| # tmux-name-pane.sh --persist api-server | |
| # | |
| # # Emit an escape you can put in zsh's precmd to keep the name set: | |
| # # precmd() { printf '\033]2;%s\033\\' api-server } | |
| # eval "$(tmux-name-pane.sh --escape api-server)" | |
| # | |
| # PERSISTENCE NOTES | |
| # | |
| # tmux sets a pane's title from two sources: | |
| # a) `tmux select-pane -T <title>` (what this script uses) | |
| # b) OSC 2 / OSC 0 escape sequences printed inside the pane (what most | |
| # shells do via their prompt) | |
| # | |
| # If your shell's prompt emits OSC 2, it will override (a) on every prompt. | |
| # `--persist` turns off tmux's `allow-rename` option for the target pane, | |
| # which tells tmux to ignore those escape sequences. This is the correct | |
| # fix for most cases; if you also want your own shell-driven title, use | |
| # `--escape` to generate the sequence instead of relying on the default | |
| # prompt. | |
| # | |
| # EXIT CODES | |
| # | |
| # 0 success | |
| # 1 usage error | |
| # 2 tmux operation failed | |
| # 127 tmux not installed | |
| # | |
| # LICENSE: MIT. No warranty. Review before use. | |
| set -euo pipefail | |
| usage() { | |
| sed -n '2,57p' "$0" | sed 's/^# \{0,1\}//' | |
| exit "${1:-0}" | |
| } | |
| TARGET="" | |
| PERSIST=0 | |
| ESCAPE_ONLY=0 | |
| CLEAR=0 | |
| LIST=0 | |
| while [ $# -gt 0 ]; do | |
| case "$1" in | |
| -h|--help) usage 0 ;; | |
| -t|--target) TARGET="$2"; shift 2 ;; | |
| --persist) PERSIST=1; shift ;; | |
| --escape) ESCAPE_ONLY=1; shift ;; | |
| --clear) CLEAR=1; shift ;; | |
| --list|-l) LIST=1; shift ;; | |
| --) shift; break ;; | |
| -*) echo "unknown flag: $1" >&2; usage 1 ;; | |
| *) break ;; | |
| esac | |
| done | |
| if ! command -v tmux >/dev/null 2>&1; then | |
| echo "tmux not installed" >&2 | |
| exit 127 | |
| fi | |
| if [ "$LIST" -eq 1 ]; then | |
| tmux list-panes -a \ | |
| -F '#{session_name}:#{window_index}.#{pane_index} #{window_name} #{pane_title}' \ | |
| | awk -F'\t' 'BEGIN { printf "%-28s %-18s %s\n", "TARGET", "WINDOW", "PANE_TITLE" } | |
| { printf "%-28s %-18s %s\n", $1, $2, $3 }' | |
| exit 0 | |
| fi | |
| if [ "$CLEAR" -eq 1 ]; then | |
| if [ -n "$TARGET" ]; then | |
| tmux select-pane -t "$TARGET" -T "" | |
| else | |
| [ -n "${TMUX:-}" ] || { echo "--clear needs -t <target> when run outside tmux" >&2; exit 1; } | |
| tmux select-pane -T "" | |
| fi | |
| exit 0 | |
| fi | |
| if [ "$ESCAPE_ONLY" -eq 1 ]; then | |
| if [ $# -lt 1 ]; then echo "usage: tmux-name-pane.sh --escape <title>" >&2; exit 1; fi | |
| TITLE="$1" | |
| # OSC 2 sets the window title; tmux reads it as the pane title when allow-rename is on. | |
| # Emit a printf command the caller can eval or drop into precmd. | |
| printf "printf '\\\\033]2;%%s\\\\033\\\\\\\\' %q\n" "$TITLE" | |
| exit 0 | |
| fi | |
| if [ $# -lt 1 ]; then | |
| usage 1 | |
| fi | |
| TITLE="$1" | |
| if [ -n "$TARGET" ]; then | |
| tmux select-pane -t "$TARGET" -T "$TITLE" | |
| SCOPE=(-t "$TARGET") | |
| else | |
| if [ -z "${TMUX:-}" ]; then | |
| echo "no --target specified and not running inside tmux" >&2 | |
| exit 1 | |
| fi | |
| tmux select-pane -T "$TITLE" | |
| SCOPE=() | |
| fi | |
| if [ "$PERSIST" -eq 1 ]; then | |
| # allow-rename: if set (default), OSC escapes from the pane re-title it. | |
| # Turning it off pins the title we just set. | |
| tmux set-option -p "${SCOPE[@]}" allow-rename off 2>/dev/null || true | |
| tmux set-window-option "${SCOPE[@]}" automatic-rename off 2>/dev/null || true | |
| fi | |
| echo "set pane title${TARGET:+ on $TARGET}: $TITLE" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment