From 5e75e4044f7f5a70d743af6b47009354ab518c4e Mon Sep 17 00:00:00 2001 From: Tom Ryder Date: Thu, 6 Dec 2018 11:26:14 +1300 Subject: Refactor bd()/sd() completion a lot Avoid very many forks; and work around Bash 3.0 bugs with array behaviour: bash-3.0$ nodes=(a b c) bash-3.0$ printf '%s\n' "${nodes[@]:1}" b c bash-3.0$ nodes=(a b) bash-3.0$ printf '%s\n' "${nodes[@]:1}" bash-3.0 Compare: bash-5.0$ nodes=(a b c) bash-5.0$ printf '%s\n' "${nodes[@]:1}" b c bash-5.0$ nodes=(a b) bash-5.0$ printf '%s\n' "${nodes[@]:1}" b bash-5.0$ --- bash/bash_completion.d/bd.bash | 50 +++++++++++++++++++++++++++--------------- bash/bash_completion.d/sd.bash | 47 ++++++++++++++++++++++++++++----------- 2 files changed, 66 insertions(+), 31 deletions(-) (limited to 'bash') diff --git a/bash/bash_completion.d/bd.bash b/bash/bash_completion.d/bd.bash index 32969522..f6ca3a6b 100644 --- a/bash/bash_completion.d/bd.bash +++ b/bash/bash_completion.d/bd.bash @@ -12,38 +12,52 @@ _bd() { COMPREPLY[ci++]=$comp done < <( - # Build an array of path nodes, leaf to root + # Build an array of path ancestors path=$PWD while [[ -n $path ]] ; do - node=${path##*/} + + # Peel off the leaf of the path + ancestor=${path##*/} path=${path%/*} - [[ -n $node ]] || continue - nodes[ni++]=$node + + # Skip if this is a null string; root, trailing/double slash... + [[ -n $ancestor ]] || continue + + # Skip the first non-null element (current dir) + ((generation++)) || continue + + # Push node onto ancestry list + ancestors[ai++]=$ancestor done - # Continue if we have at least two nodes, counting the leaf - ((ni > 1)) || return + # Continue if we have at least one non-root ancestor + ((ai)) || return - # Shift off the leaf, since it is not meaningful to go "back to" the - # current directory - nodes=("${nodes[@]:1}") + # Add quoted ancestors to new array; for long paths, this is faster than + # forking a subshell for `printf %q` on each item + while read -d / -r ancestor ; do + ancestors_quoted[aqi++]=$ancestor + done < <(printf '%q/' "${ancestors[@]}") # Make matching behave appropriately if _completion_ignore_case ; then shopt -s nocasematch 2>/dev/null fi - # Iterate through the nodes and print the ones that match the word - # being completed, with a trailing slash as terminator - for node in "${nodes[@]}" ; do - node_quoted=$(printf '%q' "$node") - # Check the quoted and unquoted word for matching - for match in "$node" "$(printf '%q' "$node")" ; do - # Print any match, slash-terminated + # Iterate through keys of the ancestors array + for ai in "${!ancestors[@]}" ; do + + # Get ancestor and associated quoted ancestor + ancestor=${ancestors[ai]} + ancestor_quoted=${ancestors_quoted[ai]} + + # If either the unquoted or quoted ancestor matches, print the + # unquoted one as a completion reply + for match in "$ancestor" "$ancestor_quoted" ; do case $match in ("$2"*) - printf '%s/' "$node" - continue + printf '%s/' "$ancestor" + break ;; esac done diff --git a/bash/bash_completion.d/sd.bash b/bash/bash_completion.d/sd.bash index 4dc72f31..8adc9810 100644 --- a/bash/bash_completion.d/sd.bash +++ b/bash/bash_completion.d/sd.bash @@ -14,25 +14,46 @@ _sd() { # Make globs expand appropriately shopt -s dotglob nullglob + + # Get list of siblings; use trailing slashes to limit to directories + # There should always be at least one (self) + siblings=(../*/) + + # Strip leading dot-dot-slash and trailing slash + siblings=("${siblings[@]#../}") + siblings=("${siblings[@]%/}") + + # Add quoted siblings to new array; for large directories, this is + # faster than forking a subshell for `printf %q` on each item + while read -d / -r sibling ; do + siblings_quoted[sqi++]=$sibling + done < <(printf '%q/' "${siblings[@]}") + + # Make matching work appropriately if _completion_ignore_case ; then shopt -s nocasematch 2>/dev/null fi - # Print matching sibling dirs that are not the current dir - for sib in ../*/ ; do - # Strip leading ../ - sib=${sib#../} - # Strip trailing slash - sib=${sib%/} - # Skip self - [[ $sib != "${PWD##*/}" ]] || continue - # Check the quoted and unquoted word for matching - for match in "$sib" "$(printf '%q' "$sib")" ; do - # Print any match, slash-terminated + # Get current dir + self=${PWD##*/} + + # Iterate through keys of the siblings array + for si in "${!siblings[@]}" ; do + + # Get sibling and associated quoted sibling + sibling=${siblings[si]} + sibling_quoted=${siblings_quoted[si]} + + # Skip if this sibling looks like the current dir + [[ $sibling != "$self" ]] || continue + + # If either the unquoted or quoted sibling matches, print the + # unquoted one as a completion reply + for match in "$sibling" "$sibling_quoted" ; do case $match in ("$2"*) - printf '%s/' "$sib" - continue + printf '%s/' "$sibling" + break ;; esac done -- cgit v1.2.3