Created
October 8, 2025 12:45
-
-
Save gitmpr/063a084eb3c69247db25df703b58ff4f to your computer and use it in GitHub Desktop.
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
| # ===== Bash: edit current command line in (Neo)Vim with cursor round-trip ===== | |
| # Key: F12 -> edit without executing, then return to prompt with cursor restored. | |
| # Pick an editor (prefer VISUAL, then EDITOR, then nvim, vim, vi) | |
| __bash_edit__pick_editor() { | |
| if [[ -n "$VISUAL" ]]; then echo "$VISUAL"; return; fi | |
| if [[ -n "$EDITOR" ]]; then echo "$EDITOR"; return; fi | |
| if command -v nvim >/dev/null 2>&1; then echo nvim; return; fi | |
| if command -v vim >/dev/null 2>&1; then echo vim; return; fi | |
| echo vi | |
| } | |
| # Convert READLINE_POINT to 1-based (line, col) within READLINE_LINE | |
| __bash_edit__point_to_lc() { | |
| local buf="$READLINE_LINE" point="$READLINE_POINT" | |
| local before="${buf:0:point}" | |
| # count newlines in "before" for line number | |
| local line=$(( $(printf '%s' "$before" | grep -o $'\n' | wc -l) + 1 )) | |
| # column is bytes since last newline, 1-based | |
| local lastnl="${before##*$'\n'}" | |
| local col=$(( ${#lastnl} + 1 )) | |
| printf '%d %d\n' "$line" "$col" | |
| } | |
| # Clamp integer to [0, len] | |
| __bash_edit__clamp() { | |
| local val="$1" len="$2" | |
| (( val < 0 )) && val=0 | |
| (( val > len )) && val=$len | |
| printf '%d\n' "$val" | |
| } | |
| # Core: edit without executing, with cursor round-trip | |
| __bash_edit_command_buffer_safe() { | |
| local tmpfile curfile editor line col | |
| tmpfile=$(mktemp /tmp/bash-edit-buffer.XXXXXX) || return | |
| curfile=$(mktemp /tmp/bash-edit-cursor.XXXXXX) || { rm -f "$tmpfile"; return; } | |
| # dump current buffer | |
| printf '%s' "$READLINE_LINE" > "$tmpfile" | |
| read -r line col < <(__bash_edit__point_to_lc) | |
| editor=$(__bash_edit__pick_editor) | |
| # pass sidecar path via env var | |
| BASH_EDIT_CURSOR_FILE="$curfile" | |
| if command -v nvim >/dev/null 2>&1 && [[ "$editor" == *nvim* ]]; then | |
| BASH_EDIT_CURSOR_FILE="$curfile" \ | |
| "$editor" \ | |
| -c "augroup BashEditBuffer | autocmd! | autocmd CursorMoved,InsertLeave,BufLeave,VimLeavePre * call writefile([string(line2byte(line('.')) + col('.') - 2)], getenv('BASH_EDIT_CURSOR_FILE')) | augroup END" \ | |
| "+call cursor($line, $col)" \ | |
| -- "$tmpfile" | |
| elif command -v vim >/dev/null 2>&1 && [[ "$editor" == *vim* ]]; then | |
| BASH_EDIT_CURSOR_FILE="$curfile" \ | |
| "$editor" \ | |
| -c "augroup BashEditBuffer | autocmd! | autocmd CursorMoved,InsertLeave,BufLeave,VimLeavePre * call writefile([string(line2byte(line('.')) + col('.') - 2)], expand('\$BASH_EDIT_CURSOR_FILE')) | augroup END" \ | |
| "+call cursor($line, $col)" \ | |
| -- "$tmpfile" | |
| else | |
| # unknown editor: position by line only, no cursor return | |
| "$editor" "+$line" -- "$tmpfile" | |
| fi | |
| # bring edited text back | |
| READLINE_LINE=$(cat -- "$tmpfile") | |
| # restore cursor if sidecar has a numeric index | |
| if [[ -s "$curfile" ]]; then | |
| local idx len | |
| idx=$(tr -d '\n\r' < "$curfile") | |
| if [[ "$idx" =~ ^[0-9]+$ ]]; then | |
| len=${#READLINE_LINE} | |
| idx=$(__bash_edit__clamp "$idx" "$len") | |
| READLINE_POINT=$idx | |
| else | |
| READLINE_POINT=${#READLINE_LINE} | |
| fi | |
| else | |
| READLINE_POINT=${#READLINE_LINE} | |
| fi | |
| rm -f -- "$tmpfile" "$curfile" | |
| } | |
| # Key bindings: | |
| # F12 is typically unbound in readline. If your terminal differs, adjust the escape. | |
| bind -x '"\e[24~":__bash_edit_command_buffer_safe' # F12 -> edit without running | |
| # Alt+e to edit current READLINE_LINE in $EDITOR. | |
| bind -x '"\ee":__bash_edit_command_buffer_safe' | |
| # Optional: edit then execute directly, we don't bind this to prevent accidental command execution | |
| # This mimics what bash control+x, control+e does: the edit-and-execute readline function, which is a bad bash design quirk | |
| __bash_edit_command_buffer_exec() { | |
| __bash_edit_command_buffer_safe | |
| local cmd="$READLINE_LINE" | |
| READLINE_LINE= | |
| READLINE_POINT=0 | |
| history -s "$cmd" | |
| builtin eval "$cmd" | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment