From 17b59be87bea2bd3f01526e7cf3d603bcce9a12e Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Mon, 2 Feb 2026 10:44:28 +0200 Subject: add tmux edit send --- dotfiles/scripts/tmux-edit-send | 186 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 186 insertions(+) create mode 100755 dotfiles/scripts/tmux-edit-send diff --git a/dotfiles/scripts/tmux-edit-send b/dotfiles/scripts/tmux-edit-send new file mode 100755 index 0000000..ef1030f --- /dev/null +++ b/dotfiles/scripts/tmux-edit-send @@ -0,0 +1,186 @@ +#!/usr/bin/env bash +set -u -o pipefail + +LOG_ENABLED=0 +log_file="${TMPDIR:-/tmp}/tmux-edit-send.log" +log() { + if [ "$LOG_ENABLED" -eq 1 ]; then + printf '%s\n' "$*" >> "$log_file" + fi +} + +# Read the target pane id from a temp file created by tmux binding. +read_target_from_file() { + local file_path="$1" + if [ -n "$file_path" ] && [ -f "$file_path" ]; then + sed -n '1p' "$file_path" | tr -d '[:space:]' + fi +} + +# Read the target pane id from tmux environment if present. +read_target_from_env() { + local env_line + env_line="$(tmux show-environment -g TMUX_EDIT_TARGET 2>/dev/null || true)" + case "$env_line" in + TMUX_EDIT_TARGET=*) printf '%s' "${env_line#TMUX_EDIT_TARGET=}" ;; + esac +} + +# Resolve the target pane id, falling back to the last pane. +resolve_target_pane() { + local candidate="$1" + local current_pane last_pane + + current_pane="$(tmux display-message -p "#{pane_id}" 2>/dev/null || true)" + log "current pane=${current_pane:-}" + if [ -n "$candidate" ] && [[ "$candidate" == *"#{"* ]]; then + log "format target detected, clearing" + candidate="" + fi + if [ -z "$candidate" ]; then + candidate="$(tmux display-message -p "#{last_pane}" 2>/dev/null || true)" + elif [ "$candidate" = "$current_pane" ]; then + last_pane="$(tmux display-message -p "#{last_pane}" 2>/dev/null || true)" + if [ -n "$last_pane" ]; then + candidate="$last_pane" + fi + fi + printf '%s' "$candidate" +} + +# Capture the latest multi-line prompt content from the pane. +capture_prompt_text() { + local target="$1" + tmux capture-pane -p -t "$target" -S -2000 2>/dev/null | awk ' + function trim_box(line) { + sub(/^ *│ ?/, "", line) + sub(/ *│ *$/, "", line) + sub(/[[:space:]]+$/, "", line) + return line + } + /^ *│ *→/ && index($0,"INSERT")==0 && index($0,"Add a follow-up")==0 { + if (text != "") last = text + text = "" + capture = 1 + line = $0 + sub(/^.*→ ?/, "", line) + line = trim_box(line) + if (line != "") text = line + next + } + capture { + if ($0 ~ /^ *└/) { + capture = 0 + if (text != "") last = text + next + } + if ($0 ~ /^ *│/ && index($0,"INSERT")==0 && index($0,"Add a follow-up")==0) { + line = trim_box($0) + if (line != "") { + if (text != "") text = text " " line + else text = line + } + } + } + END { + if (text != "") last = text + if (last != "") print last + } + ' +} + +# Write captured prompt text into the temp file if available. +prefill_tmpfile() { + local tmpfile="$1" + local prompt_text="$2" + if [ -n "$prompt_text" ]; then + printf '%s\n' "$prompt_text" > "$tmpfile" + fi +} + +# Ensure the target pane exists before sending keys. +validate_target_pane() { + local target="$1" + local pane target_found + if [ -z "$target" ]; then + log "error: no target pane determined" + echo "Could not determine target pane." >&2 + return 1 + fi + target_found=0 + for pane in $(tmux list-panes -a -F "#{pane_id}" 2>/dev/null || true); do + if [ "$pane" = "$target" ]; then + target_found=1 + break + fi + done + if [ "$target_found" -ne 1 ]; then + log "error: target pane not found: $target" + echo "Target pane not found: $target" >&2 + return 1 + fi +} + +# Send temp file contents to the target pane line by line. +send_content() { + local target="$1" + local tmpfile="$2" + local prompt_text="$3" + local first_line=1 + local line + while IFS= read -r line || [ -n "$line" ]; do + if [ "$first_line" -eq 1 ] && [ -n "$prompt_text" ]; then + if [[ "$line" == "$prompt_text"* ]]; then + line="${line#"$prompt_text"}" + fi + fi + first_line=0 + tmux send-keys -t "$target" -l "$line" + tmux send-keys -t "$target" Enter + done < "$tmpfile" + log "sent content to $target" +} + +# Main entry point. +main() { + local target_file="${1:-}" + local target + local editor="${EDITOR:-vi}" + local tmpfile + local prompt_text + + target="$(read_target_from_file "$target_file" || true)" + if [ -n "$target" ]; then + log "file target=${target:-}" + rm -f "$target_file" + fi + if [ -z "$target" ]; then + target="${TMUX_EDIT_TARGET:-}" + fi + log "env target=${target:-}" + if [ -z "$target" ]; then + target="$(read_target_from_env || true)" + fi + log "tmux env target=${target:-}" + target="$(resolve_target_pane "$target")" + log "fallback target=${target:-}" + + tmpfile="$(mktemp "./.tmux-edit-send.XXXXXX.md")" + trap 'rm -f "$tmpfile"' EXIT + + prompt_text="$(capture_prompt_text "$target")" + prefill_tmpfile "$tmpfile" "$prompt_text" + + "$editor" "$tmpfile" + log "editor exited with status $?" + + if [ ! -s "$tmpfile" ]; then + log "empty file, nothing sent" + exit 0 + fi + + validate_target_pane "$target" + send_content "$target" "$tmpfile" "$prompt_text" +} + +main "$@" -- cgit v1.2.3