summaryrefslogtreecommitdiff
path: root/scripts/tmux-edit-send
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/tmux-edit-send')
-rwxr-xr-xscripts/tmux-edit-send246
1 files changed, 246 insertions, 0 deletions
diff --git a/scripts/tmux-edit-send b/scripts/tmux-edit-send
new file mode 100755
index 0000000..05fe62d
--- /dev/null
+++ b/scripts/tmux-edit-send
@@ -0,0 +1,246 @@
+#!/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"
+ local pane_id
+ if [ -n "$file_path" ] && [ -f "$file_path" ]; then
+ pane_id="$(sed -n '1p' "$file_path" | tr -d '[:space:]')"
+ # Ensure pane ID has % prefix
+ if [ -n "$pane_id" ] && [[ "$pane_id" != %* ]]; then
+ pane_id="%${pane_id}"
+ fi
+ printf '%s' "$pane_id"
+ fi
+}
+
+# Read the target pane id from tmux environment if present.
+read_target_from_env() {
+ local env_line pane_id
+ env_line="$(tmux show-environment -g TMUX_EDIT_TARGET 2>/dev/null || true)"
+ case "$env_line" in
+ TMUX_EDIT_TARGET=*)
+ pane_id="${env_line#TMUX_EDIT_TARGET=}"
+ # Ensure pane ID has % prefix
+ if [ -n "$pane_id" ] && [[ "$pane_id" != %* ]] && [[ "$pane_id" =~ ^[0-9]+$ ]]; then
+ pane_id="%${pane_id}"
+ fi
+ printf '%s' "$pane_id"
+ ;;
+ 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:-<empty>}"
+
+ # Ensure candidate has % prefix if it's a pane ID
+ if [ -n "$candidate" ] && [[ "$candidate" =~ ^[0-9]+$ ]]; then
+ candidate="%${candidate}"
+ log "normalized candidate to $candidate"
+ fi
+
+ 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)"
+ log "using last pane as fallback: $candidate"
+ 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"
+ log "candidate was current, using last pane: $candidate"
+ 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
+ log "validate: looking for target='$target' in all panes:"
+ for pane in $(tmux list-panes -a -F "#{pane_id}" 2>/dev/null || true); do
+ log "validate: checking pane='$pane'"
+ if [ "$pane" = "$target" ]; then
+ target_found=1
+ log "validate: MATCH FOUND!"
+ 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
+ log "validate: target pane validated successfully"
+}
+
+# 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
+ log "send_content: target=$target, prompt_text='$prompt_text'"
+ while IFS= read -r line || [ -n "$line" ]; do
+ log "send_content: read line='$line'"
+ if [ "$first_line" -eq 1 ] && [ -n "$prompt_text" ]; then
+ if [[ "$line" == "$prompt_text"* ]]; then
+ local old_line="$line"
+ line="${line#"$prompt_text"}"
+ log "send_content: stripped prompt, was='$old_line' now='$line'"
+ fi
+ fi
+ first_line=0
+ log "send_content: sending line='$line'"
+ 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
+
+ log "=== tmux-edit-send starting ==="
+ log "target_file=$target_file"
+ log "EDITOR=$editor"
+
+ target="$(read_target_from_file "$target_file" || true)"
+ if [ -n "$target" ]; then
+ log "file target=${target:-<empty>}"
+ rm -f "$target_file"
+ fi
+ if [ -z "$target" ]; then
+ target="${TMUX_EDIT_TARGET:-}"
+ fi
+ log "env target=${target:-<empty>}"
+ if [ -z "$target" ]; then
+ target="$(read_target_from_env || true)"
+ fi
+ log "tmux env target=${target:-<empty>}"
+ target="$(resolve_target_pane "$target")"
+ log "fallback target=${target:-<empty>}"
+
+ tmpfile="$(mktemp)"
+ log "created tmpfile=$tmpfile"
+ if [ ! -f "$tmpfile" ]; then
+ log "ERROR: mktemp failed to create file"
+ echo "ERROR: mktemp failed" >&2
+ exit 1
+ fi
+ mv "$tmpfile" "${tmpfile}.md" 2>&1 | while read -r line; do log "mv output: $line"; done
+ tmpfile="${tmpfile}.md"
+ log "renamed to tmpfile=$tmpfile"
+ if [ ! -f "$tmpfile" ]; then
+ log "ERROR: tmpfile does not exist after rename"
+ echo "ERROR: tmpfile rename failed" >&2
+ exit 1
+ fi
+ trap 'rm -f "$tmpfile"' EXIT
+
+ log "capturing prompt text from target=$target"
+ prompt_text="$(capture_prompt_text "$target")"
+ log "captured prompt_text='$prompt_text'"
+ prefill_tmpfile "$tmpfile" "$prompt_text"
+ log "prefilled tmpfile"
+
+ log "launching editor: $editor $tmpfile"
+ "$editor" "$tmpfile"
+ local editor_exit=$?
+ log "editor exited with status $editor_exit"
+
+ if [ ! -s "$tmpfile" ]; then
+ log "empty file, nothing sent"
+ exit 0
+ fi
+
+ log "tmpfile contents:"
+ log "$(cat "$tmpfile")"
+
+ log "validating target pane"
+ validate_target_pane "$target"
+ log "sending content to target=$target"
+ send_content "$target" "$tmpfile" "$prompt_text"
+ log "=== tmux-edit-send completed ==="
+}
+
+main "$@"