#1: Recursively expand aliases and support 3 new syntaxes for alias definition & remove the ! syntax
This commit is contained in:
parent
91600446c6
commit
3780b38734
1 changed files with 85 additions and 37 deletions
94
bash-abbr
94
bash-abbr
|
|
@ -29,7 +29,7 @@ _control_op () {
|
|||
_control_op+="|$_opening_paren"
|
||||
printf %s "$_control_op"
|
||||
}
|
||||
_control_op="$(_control_op)"
|
||||
declare _control_op="$(_control_op)"
|
||||
unset -f _control_op
|
||||
|
||||
_redir_or_assign_word () {
|
||||
|
|
@ -65,19 +65,32 @@ _redir_or_assign_word () {
|
|||
declare -r _word_redirection_or_assignment="($_generic_redirection[[:space:]]*|$_identifier_equals)($_word)"
|
||||
printf %s "$_close_redirection|$_word_redirection_or_assignment"
|
||||
}
|
||||
_redir_or_assign_word="$(_redir_or_assign_word)"
|
||||
declare -r _redir_or_assign_word="$(_redir_or_assign_word)"
|
||||
unset -f _redir_or_assign_word
|
||||
|
||||
_try_abbrev () {
|
||||
# For use within bind -x.
|
||||
# Perform abbreviation lookup and interactive expansion on the shell-word left to the cursor (no quote parsing).
|
||||
# Takes a single argument which is the character to self-insert (usually space or another shell metacharacter).
|
||||
declare -r is_first_word_prefix="($_control_op)[[:space:]]*((($_redir_or_assign_word)[[:space:]])*)"
|
||||
# If the regex leads to problems/performance issues, you may try this simpler regex
|
||||
# that does no redirection/assignment word checking:
|
||||
# declare -r is_first_word_prefix="($_control_op)[[:space:]]*"
|
||||
|
||||
declare -r line_until_point=${READLINE_LINE::READLINE_POINT}
|
||||
unset -v _control_op
|
||||
|
||||
_try_abbrev_internal () {
|
||||
# Recursively perform abbrevation on the line defined by $1, taking $2 as the point and $3 as the mark.
|
||||
# Set the vars defined by $4, $5 and $6 to the updated values (line, point and mark).
|
||||
# $7 is the character/string to self-insert.
|
||||
declare -r line=$1 point=$2 mark=$3 new_line=$4 new_point=$5 new_mark=$6 \
|
||||
self_insertion=$7 \
|
||||
level=${#FUNCNAME[@]}
|
||||
# `level` is needed to keep track of the recursion, because we don't have a return value but rather
|
||||
# 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.
|
||||
|
||||
declare -r line_until_point=${line::point}
|
||||
|
||||
if
|
||||
# Check if the character at point is a shell metacharacter or EOL
|
||||
[[ $READLINE_POINT -eq ${#READLINE_LINE} || "${READLINE_LINE:READLINE_POINT:1}" = [\<\>\&\;\(\)\|[:space:]] ]] &&
|
||||
[[ $point -eq ${#line} || "${line:point:1}" = [\<\>\&\;\(\)\|[:space:]] ]] &&
|
||||
|
||||
# Determine word to complete.
|
||||
# Uses the same word separators as bash (| & ; ( ) space tab newline) (aka "shell metacharacters")
|
||||
|
|
@ -87,7 +100,9 @@ _try_abbrev () {
|
|||
# Select replacement (if any).
|
||||
declare replacement=${BASH_ALIASES["${word_at_point:-__nonexistent}"]-} &&
|
||||
|
||||
# Determine if it's the first word of a simple command, unless we're expanding a global alias.
|
||||
[[ $replacement ]] &&
|
||||
|
||||
# Determine if it's the first word of a simple command, unless we're expanding a global alias (prefix: "*" or "$").
|
||||
# Uses the same command separators as bash (|| & && ; ;; ;& ;;& ( ) | |& newline) (aka "control operators").
|
||||
# Due to complexity and performance reasons, we cannot reliably know
|
||||
# whether the cursor position (aka "point" in readline terms) is in a quoted/escaped
|
||||
|
|
@ -99,41 +114,70 @@ _try_abbrev () {
|
|||
# s.t. there's a syntax error. We also do some rudimentary redirection and assignment word
|
||||
# checking (including quoting) after the control operator. If the syntax is invalid (as far as
|
||||
# the regex can tell), then we bail and no alias expansion will happen.
|
||||
[[ ${replacement::1} = "!" || ${line_until_point%"$word_at_point"} =~ ($_control_op)[[:space:]]*((($_redir_or_assign_word)[[:space:]])*)$ ]] &&
|
||||
# If the regex leads to problems/performance issues, you may try this simpler regex
|
||||
# that does no redirection/assignment word checking:
|
||||
# [[ ${replacement::1} = "!" || ${line_until_point%"$word_at_point"} =~ ($_control_op)[[:space:]]*$ ]] &&
|
||||
[[ $replacement ]]; then
|
||||
[[ ${replacement::1} =~ [*$] || ${line_until_point%"$word_at_point"} =~ $is_first_word_prefix$ ]]; then
|
||||
|
||||
# Account for global aliases (strip "!" prefix and disable self-insertion)
|
||||
[[ ${replacement::1} = "!" ]] && replacement=${replacement#!} && set -- ''
|
||||
_expanded_aliases[$word_at_point]=1
|
||||
|
||||
# Account for inhibited self-insertion
|
||||
[[ ${replacement::1} =~ [-$] ]] && set -- ''
|
||||
|
||||
# Strip special flags (or space) from the beginning of the replacement
|
||||
replacement=${replacement#[*$ -]}
|
||||
|
||||
# Recursively expand replacement if we haven't expanded it already
|
||||
declare tmp=" $replacement"
|
||||
[[ $tmp =~ ^([[:space:]]*$is_first_word_prefix) ]] && tmp=${tmp#"${BASH_REMATCH[1]}"} || echo NO
|
||||
declare -r word_to_expand=${tmp%%[<>&;()|[:space:]]*}
|
||||
if [[ ! ${_expanded_aliases[$word_to_expand]-} ]]; then
|
||||
declare -r point_for_replacement=$((${#BASH_REMATCH[1]}+${#word_to_expand}))
|
||||
declare -g "ret_$level"
|
||||
declare -n recursively_expanded_replacement="ret_$level"
|
||||
_try_abbrev_internal " $replacement" "$point_for_replacement" "" "ret_$level" "_" "_" ""
|
||||
replacement=${recursively_expanded_replacement# }
|
||||
fi
|
||||
|
||||
# Calculate point and mark offset.
|
||||
declare -ir offset=${#replacement}-${#word_at_point}+${#1}
|
||||
declare -ir offset=${#replacement}-${#word_at_point}+${#self_insertion}
|
||||
|
||||
# Update line.
|
||||
READLINE_LINE=${line_until_point%"$word_at_point"}$replacement${1-}${READLINE_LINE:READLINE_POINT}
|
||||
eval "$new_line"'=${line_until_point%"$word_at_point"}$replacement${self_insertion}${line:point}'
|
||||
else
|
||||
declare -ir offset=${#1}
|
||||
declare -ir offset=${#self_insertion}
|
||||
|
||||
# Self-insert by default
|
||||
READLINE_LINE=${line_until_point}${1-}${READLINE_LINE:READLINE_POINT}
|
||||
eval "$new_line"'=${line_until_point}${self_insertion}${line:point}'
|
||||
fi
|
||||
|
||||
# Advance mark if it's right of (or on) the point.
|
||||
[[ $READLINE_MARK && $READLINE_POINT -le "$READLINE_MARK" ]] && READLINE_MARK=$((READLINE_MARK+offset))
|
||||
[[ $mark && $mark -ge "$point" ]] && eval "$new_mark"'=$((mark+offset))'
|
||||
|
||||
# Advance point.
|
||||
READLINE_POINT=$((READLINE_POINT+offset))
|
||||
|
||||
eval "$new_point"'=$((point+offset))'
|
||||
# NB: READLINE_ARGUMENT is deliberately ignored.
|
||||
}
|
||||
_try_abbrev () {
|
||||
# For use within bind -x.
|
||||
# Perform abbreviation lookup and interactive expansion on the shell-word left to the cursor (no quote parsing).
|
||||
# Takes a single argument which is the character to self-insert (usually space or another shell metacharacter).
|
||||
|
||||
# To prevent infinite recursion, we need to keep track
|
||||
# of which aliases have been expanded. This function re-initializes the associative array (used as a hash set)
|
||||
declare -gA _expanded_aliases=()
|
||||
|
||||
# Calls the implementation and pass necessary arguments
|
||||
_try_abbrev_internal "$READLINE_LINE" "$READLINE_POINT" "$READLINE_MARK" READLINE_LINE READLINE_POINT READLINE_MARK "$@"
|
||||
|
||||
# Don't pollute global namespace
|
||||
unset -v _expanded_aliases
|
||||
}
|
||||
|
||||
main () {
|
||||
# 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"'"'
|
||||
|
|
@ -144,3 +188,7 @@ done
|
|||
# and has the extra functionality of accepting a line.
|
||||
bind -x '"\e\C-an": _try_abbrev ""'
|
||||
bind '"\C-m": "\e\C-an\e^\C-j"'
|
||||
}
|
||||
|
||||
# Disable plugin by commenting this line
|
||||
main
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue