diff --git a/bash-abbr b/bash-abbr index fcfbf80..14f7136 100644 --- a/bash-abbr +++ b/bash-abbr @@ -111,15 +111,21 @@ # Therefore, it might interfere with other plugins/custom bindings. # Some bindings may be changed (e.g. \e\C-a...) while others should not (e.g. \C-m) -# Regexes for heuristically determining whether we're abbreviating the first word. +# Setting IFS to null so we can use "${var[*]}" syntax to make a string out of the meta char array +_bash_base_meta_chars=('|' '&' ';' '(' ')') +_bash_meta_chars_without_left_right_angle_brackets=("${_bash_base_meta_chars[@]}" ' ' $'\t' $'\n') +_bash_meta_chars_without_tab_and_nl=("${_bash_base_meta_chars[@]}" '<' '>' ' ') +_bash_meta_chars=("${_bash_base_meta_chars[@]}" '<' '>' ' ' $'\t' $'\n') + +# Regexes for heuristically determining whether we're doing expansion on the first word. # Construction put into functions to prevent global namespace pollution # and to increase readability. _control_op () { declare -r _newline=$'\n' \ - _bg_proc_or_and_and='[^&;]&&?' \ - _pipe_or_or_or='[^&|;]\|\|?' \ - _semicolon_or_case_operators='[^;|];;?&?' \ - _opening_paren='(^|[^[:alnum:]])\(' + _bg_proc_or_and_and='[^\\](\\\\)*&&?' \ + _pipe_or_or_or='[^\\](\\\\)*\|\|?' \ + _semicolon_or_case_operators='[^\\](\\\\)*;;?&?' \ + _opening_paren='(^|[^[:alnum:]\\])(\\\\)*\(' declare _control_op="^" _control_op+="|$_newline" _control_op+="|$_bg_proc_or_and_and" @@ -190,11 +196,14 @@ _try_abbrev_internal () { # set a variable defined in the calling context. This level is just appended to the identifier to make it unique # across all the recursive calls. + # IFS must be null so "${var[*]}" concatenates without space + declare -r IFS= + # If the self-insertion character is null (i.e. when pressing enter/accepting a line), # then we also want to expand aliases if the cursor/point is *on* the word instead of just *after* the word. if [[ -z $self_insertion ]]; then line_from_point=${line:point} - trailing_part_of_word=${line_from_point%%[<>&;()|[:space:]]*} + trailing_part_of_word=${line_from_point%%["${_bash_meta_chars[*]}"]*} # Move point forward to right after the word. Keep mark where it is. point=$((point+${#trailing_part_of_word})) fi @@ -202,13 +211,13 @@ _try_abbrev_internal () { if # Check if the character at point is a shell metacharacter or EOL - # [[ $point -eq ${#line} || "${line:point:1}" = [\<\>\&\;\(\)\|[:space:]] ]] && + # [[ $point -eq ${#line} || "${line:point:1}" = ["${_bash_meta_chars[*]}"] ]] && # NOTE: Above line commented out, it's actually not that useful. # Determine word to complete # Uses the same word separators as bash (| & ; ( ) space tab newline) (aka "shell metacharacters") # But < and > are ignored, since they always expect a file/delimiter/string afterwards and not a command - declare -r word_at_point=${line_until_point##*[&;()|[:space:]]} && + declare -r word_at_point=${line_until_point##*["${_bash_meta_chars_without_left_right_angle_brackets[*]}"]} && # Ensure that word does not contain either < or > [[ ! $word_at_point =~ [\<\>] ]] && @@ -260,7 +269,7 @@ _try_abbrev_internal () { # Recursively expand replacement if we haven't expanded it already declare tmp=" $replacement" # Prepend space, makes the logic easier [[ $tmp =~ ^([[:blank:]]*$is_first_word_prefix) ]] && tmp=${tmp#"${BASH_REMATCH[1]}"} # Strip matching prefix - declare -r word_to_expand=${tmp%%[<>&;()|[:space:]]*} + declare -r word_to_expand=${tmp%%["${_bash_meta_chars[*]}"]*} if [[ -z ${_expanded_aliases[$word_to_expand]-} ]]; then declare -r point_for_replacement=$((${#BASH_REMATCH[1]}+${#word_to_expand})) declare -g "ret_$level" @@ -307,20 +316,23 @@ _try_abbrev () { _try_abbrev_internal "$READLINE_LINE" "$READLINE_POINT" "$READLINE_MARK" READLINE_LINE READLINE_POINT READLINE_MARK "$@" # Don't pollute global namespace - unset -v _expanded_aliases + declare -gA _expanded_aliases=() } bash_abbr () { + # IFS must be null so "${var[*]}" concatenates without space + declare -r IFS= + # From the bash man page: # metacharacter # A character that, when unquoted, separates words. One of the following: # | & ; ( ) < > space tab newline # Note: double quote should be escaped, if added to the list of metacharacters declare ch - for ch in ' ' '|' '&' ';' '(' ')' '<' '>'; do - bind -x '"\e\C-a'"$ch"'": _try_abbrev "'"$ch"'"' - bind '"'"$ch"'": "\e\C-a'"$ch"'"' - # bind '"'"$ch"'": "\e\C-a'"$ch"'\e^"' # TODO fix. Moves point weirdly atm. + for ch in "${_bash_meta_chars_without_tab_and_nl[@]}"; do + bind_if -x '"\e\C-a'"$ch"'": _try_abbrev "'"$ch"'"' + bind_if '"'"$ch"'": "\e\C-a'"$ch"'"' + # bind_if '"'"$ch"'": "\e\C-a'"$ch"'\e^"' # TODO fix. Moves point weirdly atm. done # TODO To get it fully working in all edge cases (as far as we can parse),