Instantly share code, notes, and snippets.
Last active
May 26, 2026 13:32
-
Star
0
(0)
You must be signed in to star a gist -
Fork
0
(0)
You must be signed in to fork a gist
-
-
Save simonLeary42/594065d2beee66f74767482c5a5176b7 to your computer and use it in GitHub Desktop.
keeps the readline region active after moving the cursor -- https://dev.to/simonleary42/adding-text-selection-to-bash-4dbc
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
| #!/bin/bash | |
| # source me into your shell! | |
| _readline_region_active_navigation_activate_navigation_hook='' | |
| _readline_region_active_navigation_exchange_point_and_mark_raw='' | |
| function _readline_region_active_navigation() { | |
| # use ugly globals because variable becomes unset before _readline_setup_navigation_hook() runs | |
| declare -n activate_navigation_hook=_readline_region_active_navigation_activate_navigation_hook | |
| declare -n exchange_point_and_mark_raw=_readline_region_active_navigation_exchange_point_and_mark_raw | |
| local control='\C-' | |
| # the keybinds below can vary based on your terminal emulator | |
| # the default assumes that you have the alt/option key set to send the ESC character | |
| # for each keybind, the expected output from `cat -v` is shown in the comment | |
| # if your output does not match, these will need to be changed | |
| local alt='\e' # ^[ | |
| local left_arrow='\e[D' # ^[[D | |
| local right_arrow='\e[C' # ^[[C | |
| local control_left_arrow='\e[1;5D' # ^[[1;5D | |
| local control_right_arrow='\e[1;5C' # ^[[1;5C | |
| local alt_control_left_arrow='\e[1;7D' # ^[[1;7D | |
| local alt_control_right_arrow='\e[1;7C' # ^[[1;7C | |
| local home='\e[H' # ^[[H | |
| local end='\e[F' # ^[[F | |
| local alt_control_f12='\e[24;7~' # ^[[24;7~ | |
| local alt_control_f11='\e[23;7~' # ^[[23;7~ | |
| local alt_control_f10='\e[21;7~' # ^[[21;7~ | |
| local alt_control_f9='\e[20;7~' # ^[[20;7~ | |
| local alt_control_f8='\e[19;7~' # ^[[19;7~ | |
| local alt_control_f7='\e[18;7~' # ^[[18;7~ | |
| # the keybinds below are subject to personal preference | |
| # the default is for the emacs-style binds to be "smart" and the arrow-key/home/end binds "raw" | |
| local forward_char_raw="$right_arrow" | |
| local backward_char_raw="$left_arrow" | |
| local forward_word_raw="$control_right_arrow" | |
| local backward_word_raw="$control_left_arrow" | |
| local shell_forward_word_raw="$alt_control_right_arrow" | |
| local shell_backward_word_raw="$alt_control_left_arrow" | |
| local beginning_of_line_raw="$home" | |
| local end_of_line_raw="$end" | |
| exchange_point_and_mark_raw='C-xC-x' | |
| local forward_char_smart="${control}f" | |
| local backward_char_smart="${control}b" | |
| local forward_word_smart="${alt}f" | |
| local backward_word_smart="${alt}b" | |
| local shell_forward_word_smart="${alt}${control}f" | |
| local shell_backward_word_smart="${alt}${control}b" | |
| local beginning_of_line_smart="${control}a" | |
| local end_of_line_smart="${control}e" | |
| declare -a abort_smart=( | |
| "${control}g" | |
| "${control}x${control}g" | |
| "${alt}${control}g" | |
| ) | |
| declare -a set_mark_smart=( | |
| "${control}@" | |
| "${alt} " | |
| ) | |
| # should be unlikely to be pressed by a human by accident | |
| local set_mark_raw="$alt_control_f7" | |
| local abort_raw="$alt_control_f8" | |
| local set_mark_hook="$alt_control_f9" | |
| local abort_hook="$alt_control_f10" | |
| local setup_navigation_hook="$alt_control_f11" | |
| activate_navigation_hook="$alt_control_f12" | |
| # end config | |
| #################################################################################################### | |
| # begin procedure | |
| bind '"'"$set_mark_raw"'": set-mark' | |
| bind '"'"$exchange_point_and_mark_raw"'": exchange-point-and-mark' | |
| bind '"'"$abort_raw"'": abort' | |
| function _readline_set_mark_hook() { READLINE_KEEP_REGION_ACTIVE=1; } | |
| function _readline_abort_hook() { READLINE_KEEP_REGION_ACTIVE=0; } | |
| bind -x '"'"$set_mark_hook"'": _readline_set_mark_hook' | |
| bind -x '"'"$abort_hook"'": _readline_abort_hook' | |
| local keybind | |
| for keybind in "${set_mark_smart[@]}"; do | |
| bind '"'"$keybind"'": "'"${set_mark_hook}${set_mark_raw}"'"' | |
| done | |
| for keybind in "${abort_smart[@]}"; do | |
| bind '"'"$keybind"'": "'"${abort_hook}${abort_raw}"'"' | |
| done | |
| function _readline_setup_navigation_hook() { | |
| # use ugly globals because variable becomes unset before _readline_setup_navigation_hook() runs | |
| declare -n activate_navigation_hook=_readline_region_active_navigation_activate_navigation_hook | |
| declare -n exchange_point_and_mark_raw=_readline_region_active_navigation_exchange_point_and_mark_raw | |
| if [[ "${READLINE_KEEP_REGION_ACTIVE:-0}" == 1 ]]; then | |
| # exchange-point-and-mark also activates the region | |
| # if you run it twice, it cancels itself out and leaves the region active | |
| bind '"'"$activate_navigation_hook"'": "'"${exchange_point_and_mark_raw}${exchange_point_and_mark_raw}"'"' | |
| else | |
| bind -r "$activate_navigation_hook" | |
| fi | |
| } | |
| bind -x '"'"$setup_navigation_hook"'": _readline_setup_navigation_hook' | |
| bind '"'"$backward_char_raw"'": backward-char' | |
| bind '"'"$backward_word_raw"'": backward-word' | |
| bind '"'"$beginning_of_line_raw"'": beginning-of-line' | |
| bind '"'"$end_of_line_raw"'": end-of-line' | |
| bind '"'"$forward_char_raw"'": forward-char' | |
| bind '"'"$forward_word_raw"'": forward-word' | |
| bind '"'"$shell_backward_word_raw"'": shell-backward-word' | |
| bind '"'"$shell_forward_word_raw"'": shell-forward-word' | |
| bind '"'"$backward_char_smart"'": "'"${setup_navigation_hook}${backward_char_raw}${activate_navigation_hook}"'"' | |
| bind '"'"$backward_word_smart"'": "'"${setup_navigation_hook}${backward_word_raw}${activate_navigation_hook}"'"' | |
| bind '"'"$beginning_of_line_smart"'": "'"${setup_navigation_hook}${beginning_of_line_raw}${activate_navigation_hook}"'"' | |
| bind '"'"$end_of_line_smart"'": "'"${setup_navigation_hook}${end_of_line_raw}${activate_navigation_hook}"'"' | |
| bind '"'"$forward_char_smart"'": "'"${setup_navigation_hook}${forward_char_raw}${activate_navigation_hook}"'"' | |
| bind '"'"$forward_word_smart"'": "'"${setup_navigation_hook}${forward_word_raw}${activate_navigation_hook}"'"' | |
| bind '"'"$shell_backward_word_smart"'": "'"${setup_navigation_hook}${shell_backward_word_raw}${activate_navigation_hook}"'"' | |
| bind '"'"$shell_forward_word_smart"'": "'"${setup_navigation_hook}${shell_forward_word_raw}${activate_navigation_hook}"'"' | |
| } | |
| _readline_region_active_navigation |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment