From 80153a4c39f96628de81f7f4aa385454c721330d Mon Sep 17 00:00:00 2001 From: Tom Ryder Date: Sat, 1 Dec 2018 02:54:03 +1300 Subject: Fix backwards test for gpg(1) completion --- bash/bash_completion.d/gpg.bash | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bash/bash_completion.d/gpg.bash b/bash/bash_completion.d/gpg.bash index 697e4a65..a47b23d7 100644 --- a/bash/bash_completion.d/gpg.bash +++ b/bash/bash_completion.d/gpg.bash @@ -6,7 +6,8 @@ _gpg() { # Bail if not completing an option case ${COMP_WORDS[COMP_CWORD]} in - --*) return 1 ;; + --*) ;; + *) return 1 ;; esac # Generate completion reply from gpg(1) options -- cgit v1.2.3 From 74439641d750caa0081f1318fa57251cb365b580 Mon Sep 17 00:00:00 2001 From: Tom Ryder Date: Sat, 1 Dec 2018 11:12:07 +1300 Subject: Use consistent comments for version numbers --- bash/bashrc.d/completion.bash | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bash/bashrc.d/completion.bash b/bash/bashrc.d/completion.bash index d938275c..bdcdfe57 100644 --- a/bash/bashrc.d/completion.bash +++ b/bash/bashrc.d/completion.bash @@ -102,7 +102,7 @@ if ((BASH_VERSINFO[0] >= 4)) ; then 'readarray' fi -# If we have dynamic completion loading (Bash>=4.0), use it +# If we have dynamic completion loading (Bash >= 4.0), use it if ((BASH_VERSINFO[0] >= 4)) ; then # Handler tries to load appropriate completion for commands -- cgit v1.2.3 From ada478997c5429520a1b3d0519db9ddea4d205cb Mon Sep 17 00:00:00 2001 From: Tom Ryder Date: Sat, 1 Dec 2018 11:13:47 +1300 Subject: Don't include dotfiles in keep() names Variable and function names in Bash can't start with a period, so it's not appropriate to use the `dotglob` shell option to include dotfiles. --- bash/bashrc.d/keep.bash | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bash/bashrc.d/keep.bash b/bash/bashrc.d/keep.bash index ab89288e..a39d2fa7 100644 --- a/bash/bashrc.d/keep.bash +++ b/bash/bashrc.d/keep.bash @@ -131,7 +131,7 @@ EOF # Otherwise the user must want us to print all the NAMEs kept ( - shopt -s dotglob nullglob + shopt -s nullglob declare -a keeps keeps=("$bashkeep"/*.bash) keeps=("${keeps[@]##*/}") -- cgit v1.2.3 From 38d3783a1db89c0520cb98f9341465c99dcc4dc9 Mon Sep 17 00:00:00 2001 From: Tom Ryder Date: Sat, 1 Dec 2018 11:15:16 +1300 Subject: Remove unneeded -q option to shopt -s commands --- bash/bash_completion.d/_ssh_config_hosts.bash | 2 +- bash/bash_completion.d/_text_filenames.bash | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bash/bash_completion.d/_ssh_config_hosts.bash b/bash/bash_completion.d/_ssh_config_hosts.bash index c26457cf..dc75d4bc 100644 --- a/bash/bash_completion.d/_ssh_config_hosts.bash +++ b/bash/bash_completion.d/_ssh_config_hosts.bash @@ -16,7 +16,7 @@ _ssh_config_hosts() { while read -r _ setting ; do case $setting in ('completion-ignore-case on') - shopt -qs nocasematch 2>/dev/null + shopt -s nocasematch 2>/dev/null break ;; esac diff --git a/bash/bash_completion.d/_text_filenames.bash b/bash/bash_completion.d/_text_filenames.bash index 9cc1c722..4d576374 100644 --- a/bash/bash_completion.d/_text_filenames.bash +++ b/bash/bash_completion.d/_text_filenames.bash @@ -32,7 +32,7 @@ _text_filenames() { # Check the filename extension to know what to exclude ( # Case-insensitive matching available since 3.1-alpha - shopt -qs nocasematch 2>/dev/null + shopt -s nocasematch 2>/dev/null # Match against known binary patterns case $item in -- cgit v1.2.3 From 2f59b3c6a7931de14e837ad3c8b969327fda89ff Mon Sep 17 00:00:00 2001 From: Tom Ryder Date: Sat, 1 Dec 2018 13:06:03 +1300 Subject: Overhaul bd() completion again I forgot that the second positional parameter $2 to these completion functions is the word currently being completed. That's going to make things a bit less verbose. --- bash/bash_completion.d/bd.bash | 59 ++++++++++++++++++++++++++++++------------ 1 file changed, 43 insertions(+), 16 deletions(-) diff --git a/bash/bash_completion.d/bd.bash b/bash/bash_completion.d/bd.bash index e67cdd09..59f4718f 100644 --- a/bash/bash_completion.d/bd.bash +++ b/bash/bash_completion.d/bd.bash @@ -1,25 +1,52 @@ # Completion setup for bd() _bd() { - # Only makes sense for the first argument + # The function accepts only one argument, so it doesn't make sense to + # complete anywhere else ((COMP_CWORD == 1)) || return - # Build a list of dirnames in $PWD - local -a dirnames - IFS=/ read -rd '' -a dirnames < <(printf '%s\0' "${PWD#/}") + # Iterate through slash-delimited matching parent path elements, as piped + # in by the subshell + local ci word + while IFS= read -rd/ word ; do + COMPREPLY[ci++]=$word + done < <( - # Remove the last element in the array (the current directory) - ((${#dirnames[@]})) || return - dirnames=("${dirnames[@]:0:${#dirnames[@]}-1}") + # Build an array of path nodes, leaf to root + path=$PWD + while [[ -n $path ]] ; do + node=${path##*/} + path=${path%/*} + [[ -n $node ]] || continue + nodes[ni++]=$node + done - # Add the matching dirnames to the reply - local dirname - for dirname in "${dirnames[@]}" ; do - case $dirname in - "${COMP_WORDS[COMP_CWORD]}"*) - COMPREPLY[${#COMPREPLY[@]}]=$(printf %q "$dirname") - ;; - esac - done + # Continue if we have at least two nodes, counting the leaf + ((${#nodes} > 1)) || return + + # Shift off the leaf, since it's not meaningful to go "back to" the + # current directory + nodes=("${nodes[@]:1}") + + # Turn on case-insensitive matching, if configured to do so + while read -r _ setting ; do + case $setting in + ('completion-ignore-case on') + shopt -s nocasematch 2>/dev/null + break + ;; + esac + done < <(bind -v) + + # 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 + case $node in + ("$2"*) + printf '%s/' "$node" + ;; + esac + done + ) } complete -F _bd bd -- cgit v1.2.3 From b6c540ded9a527f19b8bff7888e330ba2786f552 Mon Sep 17 00:00:00 2001 From: Tom Ryder Date: Sat, 1 Dec 2018 13:15:28 +1300 Subject: Use the positional parameter aliases for words The current word is available in $2, and the previous word in $3. That's easier (and maybe a bit less expensive) to dig out, so let's use it. --- bash/bash_completion.d/_abook_addresses.bash | 2 +- bash/bash_completion.d/_ssh_config_hosts.bash | 4 ++-- bash/bash_completion.d/_text_filenames.bash | 2 +- bash/bash_completion.d/chgrp.bash | 2 +- bash/bash_completion.d/eds.bash | 2 +- bash/bash_completion.d/find.bash | 14 +++++++------- bash/bash_completion.d/ftp.bash | 2 +- bash/bash_completion.d/git.bash | 16 ++++++++-------- bash/bash_completion.d/gpg.bash | 4 ++-- bash/bash_completion.d/keep.bash | 4 ++-- bash/bash_completion.d/kill.bash | 2 +- bash/bash_completion.d/make.bash | 2 +- bash/bash_completion.d/man.bash | 14 +++++--------- bash/bash_completion.d/mysql.bash | 2 +- bash/bash_completion.d/openssl.bash | 2 +- bash/bash_completion.d/pass.bash | 4 ++-- bash/bash_completion.d/path.bash | 6 +++--- bash/bash_completion.d/sd.bash | 2 +- bash/bash_completion.d/td.bash | 2 +- bash/bash_completion.d/ud.bash | 2 +- 20 files changed, 43 insertions(+), 47 deletions(-) diff --git a/bash/bash_completion.d/_abook_addresses.bash b/bash/bash_completion.d/_abook_addresses.bash index e79eef42..e1a94bc7 100644 --- a/bash/bash_completion.d/_abook_addresses.bash +++ b/bash/bash_completion.d/_abook_addresses.bash @@ -24,7 +24,7 @@ _abook_addresses() { # Generate list of email addresses from abook(1) while IFS=$'\t' read -r address _ ; do case $address in - ("${COMP_WORDS[COMP_CWORD]}"*) + ("$2"*) printf '%s\n' "$address" ;; esac diff --git a/bash/bash_completion.d/_ssh_config_hosts.bash b/bash/bash_completion.d/_ssh_config_hosts.bash index dc75d4bc..0c1eb379 100644 --- a/bash/bash_completion.d/_ssh_config_hosts.bash +++ b/bash/bash_completion.d/_ssh_config_hosts.bash @@ -2,7 +2,7 @@ _ssh_config_hosts() { # Don't complete anything that wouldn't be in a valid hostname - case ${COMP_WORDS[COMP_CWORD]} in + case $2 in *[!a-zA-Z0-9.-]*) return 1 ;; esac @@ -37,7 +37,7 @@ _ssh_config_hosts() { (*'*'*) ;; # Found a match; print it - ("${COMP_WORDS[COMP_CWORD]}"*) + ("$2"*) printf '%s\n' "$value" ;; esac diff --git a/bash/bash_completion.d/_text_filenames.bash b/bash/bash_completion.d/_text_filenames.bash index 4d576374..caf05e8d 100644 --- a/bash/bash_completion.d/_text_filenames.bash +++ b/bash/bash_completion.d/_text_filenames.bash @@ -155,5 +155,5 @@ _text_filenames() { # Complete everything else; some of it will still be binary COMPREPLY[${#COMPREPLY[@]}]=$item - done < <(compgen -A file -- "${COMP_WORDS[COMP_CWORD]}") + done < <(compgen -A file -- "$2") } diff --git a/bash/bash_completion.d/chgrp.bash b/bash/bash_completion.d/chgrp.bash index 5e93ccee..e63651c5 100644 --- a/bash/bash_completion.d/chgrp.bash +++ b/bash/bash_completion.d/chgrp.bash @@ -9,6 +9,6 @@ _chgrp() { done while read -r group ; do COMPREPLY[${#COMPREPLY[@]}]=$group - done < <(compgen -A group -- "${COMP_WORDS[COMP_CWORD]}") + done < <(compgen -A group -- "$2") } complete -F _chgrp -o bashdefault -o default chgrp diff --git a/bash/bash_completion.d/eds.bash b/bash/bash_completion.d/eds.bash index 1c5b2aa2..da6bb879 100644 --- a/bash/bash_completion.d/eds.bash +++ b/bash/bash_completion.d/eds.bash @@ -22,7 +22,7 @@ _eds() { done < <(bind -v) declare -a files - files=("${EDSPATH:-"$HOME"/.local/bin}"/"${COMP_WORDS[COMP_CWORD]}"*) + files=("${EDSPATH:-"$HOME"/.local/bin}"/"$2"*) declare -a executables for file in "${files[@]}" ; do ! [[ -d $file ]] || continue diff --git a/bash/bash_completion.d/find.bash b/bash/bash_completion.d/find.bash index 7e2ae9c3..7593c594 100644 --- a/bash/bash_completion.d/find.bash +++ b/bash/bash_completion.d/find.bash @@ -36,7 +36,7 @@ _find() { # If the word being completed starts with a dash, just complete it as # an option; crude, but simple, and will be right the vast majority of # the time - case ${COMP_WORDS[COMP_CWORD]} in + case $2 in (-*) compgen -W ' -atime @@ -58,32 +58,32 @@ _find() { -type -user -xdev - ' -- "${COMP_WORDS[COMP_CWORD]}" + ' -- "$2" ;; esac # Otherwise, look at the word *before* this one to figure out what to # complete - case ${COMP_WORDS[COMP_CWORD-1]} in + case $3 in # Args to -exec and -execdir should be commands (-exec|-execdir) - compgen -A command -- "${COMP_WORDS[COMP_CWORD]}" + compgen -A command -- "$2" ;; # Args to -group should complete group names (-group) - compgen -A group -- "${COMP_WORDS[COMP_CWORD]}" + compgen -A group -- "$2" ;; # Legal POSIX flags for -type (-type) - compgen -W 'b c d f l p s' -- "${COMP_WORDS[COMP_CWORD]}" + compgen -W 'b c d f l p s' -- "$2" ;; # Args to -user should complete usernames (-user) - compgen -A user -- "${COMP_WORDS[COMP_CWORD]}" + compgen -A user -- "$2" ;; esac ) diff --git a/bash/bash_completion.d/ftp.bash b/bash/bash_completion.d/ftp.bash index a584dd81..f174b5ff 100644 --- a/bash/bash_completion.d/ftp.bash +++ b/bash/bash_completion.d/ftp.bash @@ -26,7 +26,7 @@ _ftp() { # Generate completion reply local machine for machine in "${machines[@]}" ; do - [[ $machine == "${COMP_WORDS[COMP_CWORD]}"* ]] || continue + [[ $machine == "$2"* ]] || continue COMPREPLY[${#COMPREPLY[@]}]=$machine done } diff --git a/bash/bash_completion.d/git.bash b/bash/bash_completion.d/git.bash index a2edb468..a05655c3 100644 --- a/bash/bash_completion.d/git.bash +++ b/bash/bash_completion.d/git.bash @@ -15,7 +15,7 @@ _git() { ref=${ref#refs/*/} case $ref in '') continue ;; - "${COMP_WORDS[COMP_CWORD]}"*) + "$2"*) COMPREPLY[${#COMPREPLY[@]}]=$ref ;; esac @@ -29,7 +29,7 @@ _git() { while IFS= read -r remote ; do case $remote in '') continue ;; - "${COMP_WORDS[COMP_CWORD]}"*) + "$2"*) COMPREPLY[${#COMPREPLY[@]}]=$remote ;; esac @@ -45,7 +45,7 @@ _git() { alias=${alias%% *} case $alias in '') continue ;; - "${COMP_WORDS[COMP_CWORD]}"*) + "$2"*) COMPREPLY[${#COMPREPLY[@]}]=$alias ;; esac @@ -58,7 +58,7 @@ _git() { local execpath execpath=$(git --exec-path) || return local path - for path in "$execpath"/git-"${COMP_WORDS[COMP_CWORD]}"* ; do + for path in "$execpath"/git-"$2"* ; do ! [[ -d $path ]] || continue [[ -e $path ]] || continue [[ -x $path ]] || continue @@ -121,7 +121,7 @@ _git() { set-url show update - ' -- "${COMP_WORDS[COMP_CWORD]}") + ' -- "$2") else "${FUNCNAME[0]}" remotes fi @@ -146,7 +146,7 @@ _git() { save show store - ' -- "${COMP_WORDS[COMP_CWORD]}") + ' -- "$2") return ;; @@ -166,7 +166,7 @@ _git() { summary sync update - ' -- "${COMP_WORDS[COMP_CWORD]}") + ' -- "$2") return ;; @@ -187,7 +187,7 @@ _git() { # I normally only want a refspec for "reset" if I'm using the --hard or # --soft option; otherwise, files are fine reset) - case ${COMP_WORDS[COMP_CWORD-1]} in + case $3 in --hard|--soft) "${FUNCNAME[0]}" refs ;; diff --git a/bash/bash_completion.d/gpg.bash b/bash/bash_completion.d/gpg.bash index a47b23d7..bfa2d1a9 100644 --- a/bash/bash_completion.d/gpg.bash +++ b/bash/bash_completion.d/gpg.bash @@ -5,7 +5,7 @@ _gpg() { hash gpg 2>/dev/null || return # Bail if not completing an option - case ${COMP_WORDS[COMP_CWORD]} in + case $2 in --*) ;; *) return 1 ;; esac @@ -14,7 +14,7 @@ _gpg() { local option while read -r option ; do case $option in - "${COMP_WORDS[COMP_CWORD]}"*) + "$2"*) COMPREPLY[${#COMPREPLY[@]}]=$option ;; esac diff --git a/bash/bash_completion.d/keep.bash b/bash/bash_completion.d/keep.bash index 00b1469e..6829db9c 100644 --- a/bash/bash_completion.d/keep.bash +++ b/bash/bash_completion.d/keep.bash @@ -25,7 +25,7 @@ _keep() { # Keepable names: all functions and variables (keep) compgen -A function -A variable \ - -- "${COMP_WORDS[COMP_CWORD]}" + -- "$2" ;; # Kept names: .bash-suffixed names in keep dir @@ -43,7 +43,7 @@ _keep() { # Build list of kept names dir=${BASHKEEP:-"$HOME"/.bashkeep.d} - cword=${COMP_WORDS[COMP_CWORD]} + cword=$2 kept=("$dir"/"$cword"*.bash) kept=("${kept[@]##*/}") kept=("${kept[@]%.bash}") diff --git a/bash/bash_completion.d/kill.bash b/bash/bash_completion.d/kill.bash index dccc926b..bdb42ec1 100644 --- a/bash/bash_completion.d/kill.bash +++ b/bash/bash_completion.d/kill.bash @@ -4,7 +4,7 @@ _kill() { local pid while read -r pid ; do case $pid in - "${COMP_WORDS[COMP_CWORD]}"*) + "$2"*) COMPREPLY[${#COMPREPLY[@]}]=$pid ;; esac diff --git a/bash/bash_completion.d/make.bash b/bash/bash_completion.d/make.bash index 0f39ef4b..8ca36529 100644 --- a/bash/bash_completion.d/make.bash +++ b/bash/bash_completion.d/make.bash @@ -40,7 +40,7 @@ _make() { *[^[:word:]./-]*) ;; # Add targets that match what we're completing - "${COMP_WORDS[COMP_CWORD]}"*) + "$2"*) COMPREPLY[${#COMPREPLY[@]}]=$target ;; esac diff --git a/bash/bash_completion.d/man.bash b/bash/bash_completion.d/man.bash index ffef48ec..b5ecaa3e 100644 --- a/bash/bash_completion.d/man.bash +++ b/bash/bash_completion.d/man.bash @@ -4,13 +4,9 @@ _man() { # Don't even bother if we don't have manpath(1) hash manpath 2>/dev/null || return - # Snarf the word - local word - word=${COMP_WORDS[COMP_CWORD]} - # Don't bother if the word has slashes in it, the user is probably trying # to complete an actual path - case $word in + case $2 in */*) return 1 ;; esac @@ -18,9 +14,9 @@ _man() { # we'll assume that's the section to search local section subdir if ((COMP_CWORD > 1)) ; then - case ${COMP_WORDS[COMP_CWORD-1]} in + case $3 in [0-9]*) - section=${COMP_WORDS[COMP_CWORD-1]} + section=$3 subdir=man${section%%[^0-9]*} ;; esac @@ -59,12 +55,12 @@ _man() { [[ -n $manpath ]] || continue if [[ -n $section ]] ; then for page in \ - "$manpath"/"$subdir"/"$word"*."$section"?(.[glx]z|.bz2|.lzma|.Z) + "$manpath"/"$subdir"/"$2"*."$section"?(.[glx]z|.bz2|.lzma|.Z) do pages[${#pages[@]}]=$page done else - for page in "$manpath"/man[0-9]*/"$word"*.* ; do + for page in "$manpath"/man[0-9]*/"$2"*.* ; do pages[${#pages[@]}]=$page done fi diff --git a/bash/bash_completion.d/mysql.bash b/bash/bash_completion.d/mysql.bash index 3ff97090..b6c3ce93 100644 --- a/bash/bash_completion.d/mysql.bash +++ b/bash/bash_completion.d/mysql.bash @@ -31,7 +31,7 @@ _mysql() { # Collect all the config file names, strip off leading path and .cnf local -a cnfs - cnfs=("$dirname"/"${COMP_WORDS[COMP_CWORD]}"*.cnf) + cnfs=("$dirname"/"$2"*.cnf) cnfs=("${cnfs[@]#"$dirname"/}") cnfs=("${cnfs[@]%.cnf}") diff --git a/bash/bash_completion.d/openssl.bash b/bash/bash_completion.d/openssl.bash index 86650770..3396eb9f 100644 --- a/bash/bash_completion.d/openssl.bash +++ b/bash/bash_completion.d/openssl.bash @@ -7,7 +7,7 @@ _openssl() { while read -r subcmd ; do case $subcmd in '') ;; - "${COMP_WORDS[COMP_CWORD]}"*) + "$2"*) COMPREPLY[${#COMPREPLY[@]}]=$subcmd ;; esac diff --git a/bash/bash_completion.d/pass.bash b/bash/bash_completion.d/pass.bash index 176886dc..c1246757 100644 --- a/bash/bash_completion.d/pass.bash +++ b/bash/bash_completion.d/pass.bash @@ -35,8 +35,8 @@ _pass() # Gather the entries and remove their .gpg suffix declare -a entries - entries=("$passdir"/"${COMP_WORDS[COMP_CWORD]}"*/**/*.gpg \ - "$passdir"/"${COMP_WORDS[COMP_CWORD]}"*.gpg) + entries=("$passdir"/"$2"*/**/*.gpg \ + "$passdir"/"$2"*.gpg) entries=("${entries[@]#"$passdir"/}") entries=("${entries[@]%.gpg}") diff --git a/bash/bash_completion.d/path.bash b/bash/bash_completion.d/path.bash index 7143b448..8db6a74a 100644 --- a/bash/bash_completion.d/path.bash +++ b/bash/bash_completion.d/path.bash @@ -17,7 +17,7 @@ _path() { pop remove shift - ' -- "${COMP_WORDS[COMP_CWORD]}") + ' -- "$2") # Complete with either directories or $PATH entries as all other words else @@ -46,7 +46,7 @@ _path() { # Collect directory names, strip trailing slash local -a dirnames - dirnames=("${COMP_WORDS[COMP_CWORD]}"*/) + dirnames=("$2"*/) dirnames=("${dirnames[@]%/}") # Print quoted entries, null-delimited @@ -62,7 +62,7 @@ _path() { local part for part in "${promptarr[@]}" ; do case $part in - "${COMP_WORDS[COMP_CWORD]}"*) + "$2"*) COMPREPLY[${#COMPREPLY[@]}]=$(printf '%q' "$part") ;; esac diff --git a/bash/bash_completion.d/sd.bash b/bash/bash_completion.d/sd.bash index e7e82f80..d6e93c78 100644 --- a/bash/bash_completion.d/sd.bash +++ b/bash/bash_completion.d/sd.bash @@ -31,7 +31,7 @@ _sd() { # Collect directory names, strip leading ../ and trailing / local -a dirnames - dirnames=(../"${COMP_WORDS[COMP_CWORD]}"*/) + dirnames=(../"$2"*/) dirnames=("${dirnames[@]#../}") dirnames=("${dirnames[@]%/}") diff --git a/bash/bash_completion.d/td.bash b/bash/bash_completion.d/td.bash index 92927c28..38dc51a3 100644 --- a/bash/bash_completion.d/td.bash +++ b/bash/bash_completion.d/td.bash @@ -26,7 +26,7 @@ _td() { done < <(bind -v) declare -a fns - fns=("$dir"/"${COMP_WORDS[COMP_CWORD]}"*) + fns=("$dir"/"$2"*) fns=("${fns[@]#"$dir"/}") # Print quoted entries, null-delimited diff --git a/bash/bash_completion.d/ud.bash b/bash/bash_completion.d/ud.bash index c7dee582..a9912f5a 100644 --- a/bash/bash_completion.d/ud.bash +++ b/bash/bash_completion.d/ud.bash @@ -25,7 +25,7 @@ _ud() { done < <(bind -v) # Collect directory names, strip trailing slashes - dirnames=("${COMP_WORDS[COMP_CWORD]}"*/) + dirnames=("$2"*/) dirnames=("${dirnames[@]%/}") # Print results null-delimited -- cgit v1.2.3 From 280d471d84156e4a491b626c89ff7ea43fb7dd82 Mon Sep 17 00:00:00 2001 From: Tom Ryder Date: Sat, 1 Dec 2018 14:19:12 +1300 Subject: Remove `kill` completion Completing PIDs is stupid, in retrospect. --- bash/bash_completion.d/kill.bash | 16 ---------------- bash/bashrc.d/completion.bash | 3 ++- 2 files changed, 2 insertions(+), 17 deletions(-) delete mode 100644 bash/bash_completion.d/kill.bash diff --git a/bash/bash_completion.d/kill.bash b/bash/bash_completion.d/kill.bash deleted file mode 100644 index bdb42ec1..00000000 --- a/bash/bash_completion.d/kill.bash +++ /dev/null @@ -1,16 +0,0 @@ -# Complete kill builtin with jobspecs (prefixed with % so it will accept them) -# and this user's PIDs (requires pgrep(1)) -_kill() { - local pid - while read -r pid ; do - case $pid in - "$2"*) - COMPREPLY[${#COMPREPLY[@]}]=$pid - ;; - esac - done < <( { - compgen -A job -P% - pgrep -u "$USER" . - } 2>/dev/null ) -} -complete -F _kill kill diff --git a/bash/bashrc.d/completion.bash b/bash/bashrc.d/completion.bash index bdcdfe57..ff3a95c1 100644 --- a/bash/bashrc.d/completion.bash +++ b/bash/bashrc.d/completion.bash @@ -79,7 +79,8 @@ complete -A helptopic \ complete -P '%' -A job \ 'disown' \ 'fg' \ - 'jobs' + 'jobs' \ + 'kill' complete -P '%' -A stopped \ 'bg' -- cgit v1.2.3 From 7b6f3076ea485181060707e04945c3c4b27e1e89 Mon Sep 17 00:00:00 2001 From: Tom Ryder Date: Sat, 1 Dec 2018 15:17:05 +1300 Subject: Remove ftp(1) completion I don't remember the last time I used a .netrc file. --- README.md | 1 - bash/bash_completion.d/ftp.bash | 33 --------------------------------- 2 files changed, 34 deletions(-) delete mode 100644 bash/bash_completion.d/ftp.bash diff --git a/README.md b/README.md index ae9b4319..de4e8618 100644 --- a/README.md +++ b/README.md @@ -264,7 +264,6 @@ files, for things I really do get tired of typing repeatedly: * Bash builtins: commands, help topics, shell options, variables, etc. * `find(1)`'s more portable options -* `ftp(1)` hostnames from `~/.netrc` * `git(1)` subcommands, remotes, branches, tags, and addable files * `gpg(1)` long options * `make(1)` targets read from a `Makefile` diff --git a/bash/bash_completion.d/ftp.bash b/bash/bash_completion.d/ftp.bash deleted file mode 100644 index f174b5ff..00000000 --- a/bash/bash_completion.d/ftp.bash +++ /dev/null @@ -1,33 +0,0 @@ -# Completion for ftp(1) with .netrc machines -_ftp() { - - # Bail if the .netrc file is illegible - local netrc - netrc=$HOME/.netrc - [[ -r $netrc ]] || return - - # Tokenize the file - local -a tokens - read -a tokens -d '' -r < "$netrc" - - # Iterate through tokens and collect machine names - local -a machines - local -i nxm - local token - for token in "${tokens[@]}" ; do - if ((nxm)) ; then - machines[${#machines[@]}]=$token - nxm=0 - elif [[ $token == machine ]] ; then - nxm=1 - fi - done - - # Generate completion reply - local machine - for machine in "${machines[@]}" ; do - [[ $machine == "$2"* ]] || continue - COMPREPLY[${#COMPREPLY[@]}]=$machine - done -} -complete -F _ftp -o bashdefault -o default ftp -- cgit v1.2.3 From 254faa3fe34486e0ab8def59561c1de211b85f77 Mon Sep 17 00:00:00 2001 From: Tom Ryder Date: Sat, 1 Dec 2018 21:46:55 +1300 Subject: Adjust syntax of two more completion loads These were missed for 65e47bf, somehow. --- bash/bash_completion.d/sed.bash | 3 ++- bash/bash_completion.d/source.bash | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/bash/bash_completion.d/sed.bash b/bash/bash_completion.d/sed.bash index 7957ebe2..3137412e 100644 --- a/bash/bash_completion.d/sed.bash +++ b/bash/bash_completion.d/sed.bash @@ -1,4 +1,5 @@ # Completion for sed(1) with files that look editable -declare -F _text_filenames >/dev/null || +if ! declare -F _text_filenames >/dev/null ; then source "$HOME"/.bash_completion.d/_text_filenames.bash +fi complete -F _text_filenames -o filenames sed diff --git a/bash/bash_completion.d/source.bash b/bash/bash_completion.d/source.bash index de608813..8f40e9e2 100644 --- a/bash/bash_completion.d/source.bash +++ b/bash/bash_completion.d/source.bash @@ -1,4 +1,5 @@ # Completion for `source` with files that look like plain text -declare -F _text_filenames >/dev/null || +if ! declare -F _text_filenames >/dev/null ; then source "$HOME"/.bash_completion.d/_text_filenames.bash +fi complete -F _text_filenames -o filenames source -- cgit v1.2.3 From 1351b3f831a93f78665ee39c5a5b66d14a169d3d Mon Sep 17 00:00:00 2001 From: Tom Ryder Date: Sat, 1 Dec 2018 21:47:45 +1300 Subject: Overhaul pass(1) completion Remove Bash 4.0 requirement by using globstar dynamically if found. --- bash/bash_completion.d/pass.bash | 46 ++++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/bash/bash_completion.d/pass.bash b/bash/bash_completion.d/pass.bash index c1246757..af7926d9 100644 --- a/bash/bash_completion.d/pass.bash +++ b/bash/bash_completion.d/pass.bash @@ -1,6 +1,3 @@ -# Requires Bash >= 4.0 for globstar -((BASH_VERSINFO[0] >= 4)) || return - # Custom completion for pass(1), because I don't like the one included with the # distribution _pass() @@ -10,37 +7,40 @@ _pass() passdir=${PASSWORD_STORE_DIR:-"$HOME"/.password-store} [[ -r $passdir ]] || return - # Iterate through list of .gpg paths, extension stripped, null-delimited, - # and filter them down to the ones matching the completing word (compgen - # doesn't seem to do this properly with a null delimiter) - local entry - while IFS= read -rd '' entry ; do - [[ -n $entry ]] || continue - COMPREPLY+=("$entry") + # Iterate through completions produced by subshell + local ci comp + while IFS= read -d '' -r comp ; do + COMPREPLY[ci++]=$comp done < <( # Set shell options to expand globs the way we expect shopt -u dotglob - shopt -s globstar nullglob + shopt -s nullglob - # Make globbing case-insensitive if appropriate + # Check Readline settings for case-insensitive matching while read -r _ setting ; do - case $setting in - ('completion-ignore-case on') - shopt -s nocaseglob - break - ;; - esac + if [[ $setting == 'completion-ignore-case on' ]] ; then + shopt -s nocaseglob + break + fi done < <(bind -v) - # Gather the entries and remove their .gpg suffix - declare -a entries - entries=("$passdir"/"$2"*/**/*.gpg \ - "$passdir"/"$2"*.gpg) + # Gather the entries, use ** for depth search if we can + entries=("$passdir"/"$2"*.gpg) + if shopt -s globstar 2>/dev/null ; then + entries=("${entries[@]}" "$passdir"/"$2"**/*.gpg) + else + entries=("${entries[@]}" "$passdir"/"$2"*/*.gpg) + fi + + # Bail out if there are no entries + ((${#entries[@]})) || exit + + # Strip leading path and .gpg suffix from entry names entries=("${entries[@]#"$passdir"/}") entries=("${entries[@]%.gpg}") - # Print quoted entries, null-delimited + # Print entries, quoted and null-delimited printf '%q\0' "${entries[@]}" ) } -- cgit v1.2.3 From d0edad20fb5efc2dfc493345e1e7419c39a5558f Mon Sep 17 00:00:00 2001 From: Tom Ryder Date: Sun, 2 Dec 2018 12:14:22 +1300 Subject: Use consistent temp names for shell subfile vars --- bash/bashrc | 8 ++++---- bash/bashrc.d/completion.bash | 32 +++++++++++++++++++++++++------- ksh/kshrc | 6 +++--- zsh/zshrc | 8 ++++---- 4 files changed, 36 insertions(+), 18 deletions(-) diff --git a/bash/bashrc b/bash/bashrc index 3b4c91bd..a05526f2 100644 --- a/bash/bashrc +++ b/bash/bashrc @@ -95,8 +95,8 @@ if ((BASH_VERSINFO[0] >= 4)) ; then fi # Load Bash-specific startup files -for sh in "$HOME"/.bashrc.d/*.bash ; do - [[ -e $sh ]] || continue - source "$sh" +for bash in "$HOME"/.bashrc.d/*.bash ; do + [[ -e $bash ]] || continue + source "$bash" done -unset -v sh +unset -v bash diff --git a/bash/bashrc.d/completion.bash b/bash/bashrc.d/completion.bash index ff3a95c1..5161a0bf 100644 --- a/bash/bashrc.d/completion.bash +++ b/bash/bashrc.d/completion.bash @@ -108,19 +108,37 @@ if ((BASH_VERSINFO[0] >= 4)) ; then # Handler tries to load appropriate completion for commands _completion_loader() { - [[ -n $1 ]] || return + + # Check completed command for validity + case $1 in + # Not empty + '') return 1 ;; + # Not starting with an underscore + _*) return 1 ;; + esac + + # Build expected path for the command completion local compspec compspec=$HOME/.bash_completion.d/$1.bash - [[ -f $compspec ]] || return - source "$compspec" >/dev/null 2>&1 && return 124 + + # Skip directories and nonexistent files + [[ -e $compspec ]] || return + ! [[ -d $compspec ]] || return + + # Try to read the file, return 124 if it worked + if source "$compspec" ; then + return 124 + fi } + + # Set completion loader to use the above function complete -D -F _completion_loader -o bashdefault -o default # If not, load all of the completions up now else - for sh in "$HOME"/.bash_completion.d/*.bash ; do - [[ -e $sh ]] || continue - source "$sh" + for bash in "$HOME"/.bash_completion.d/[^_]*.bash ; do + [[ -e $bash ]] || continue + source "$bash" done - unset -v sh + unset -v bash fi diff --git a/ksh/kshrc b/ksh/kshrc index 43ac14da..1aea4b9d 100644 --- a/ksh/kshrc +++ b/ksh/kshrc @@ -20,7 +20,7 @@ set -o trackall HISTFILE=$HOME/.ksh_history # Load any supplementary scripts -for kshrc in "$HOME"/.kshrc.d/*.ksh ; do - [[ -e $kshrc ]] && . "$kshrc" +for ksh in "$HOME"/.kshrc.d/*.ksh ; do + [[ -e $ksh ]] && . "$ksh" done -unset -v kshrc +unset -v ksh diff --git a/zsh/zshrc b/zsh/zshrc index 2977c4d9..0e111364 100644 --- a/zsh/zshrc +++ b/zsh/zshrc @@ -16,8 +16,8 @@ HISTFILE=$HOME/.zsh_history SAVEHIST=$((1 << 12)) # Load Zsh-specific startup files -for sh in "$HOME"/.zshrc.d/*.zsh ; do - [[ -e $sh ]] || continue - source "$sh" +for zsh in "$HOME"/.zshrc.d/*.zsh ; do + [[ -e $zsh ]] || continue + source "$zsh" done -unset -v sh +unset -v zsh -- cgit v1.2.3 From cc7f884a0900456ab7eb9ee24d134ea47b615895 Mon Sep 17 00:00:00 2001 From: Tom Ryder Date: Sun, 2 Dec 2018 12:24:36 +1300 Subject: Remove mysql(1) completion It would be better to refactor this as just listing databases. --- bash/bash_completion.d/mysql.bash | 42 --------------------------------------- 1 file changed, 42 deletions(-) delete mode 100644 bash/bash_completion.d/mysql.bash diff --git a/bash/bash_completion.d/mysql.bash b/bash/bash_completion.d/mysql.bash deleted file mode 100644 index b6c3ce93..00000000 --- a/bash/bash_completion.d/mysql.bash +++ /dev/null @@ -1,42 +0,0 @@ -# Completion setup for MySQL for configured databases -_mysql() { - - # Only makes sense for first argument - ((COMP_CWORD == 1)) || return - - # Bail if directory doesn't exist - local dirname - dirname=$HOME/.mysql - [[ -d $dirname ]] || return - - # Return the names of the .cnf files sans prefix as completions - local db - while IFS= read -rd '' db ; do - [[ -n $db ]] || continue - COMPREPLY[${#COMPREPLY[@]}]=$db - done < <( - - # Set options so that globs expand correctly - shopt -s dotglob nullglob - - # Make globbing case-insensitive if appropriate - while read -r _ setting ; do - case $setting in - ('completion-ignore-case on') - shopt -s nocaseglob - break - ;; - esac - done < <(bind -v) - - # Collect all the config file names, strip off leading path and .cnf - local -a cnfs - cnfs=("$dirname"/"$2"*.cnf) - cnfs=("${cnfs[@]#"$dirname"/}") - cnfs=("${cnfs[@]%.cnf}") - - # Print quoted entries, null-delimited - printf '%q\0' "${cnfs[@]}" - ) -} -complete -F _mysql -o bashdefault -o default mysql -- cgit v1.2.3 From cc6082bfc04db1f95187fa996118e2bd53f06283 Mon Sep 17 00:00:00 2001 From: Tom Ryder Date: Sun, 2 Dec 2018 12:38:24 +1300 Subject: Throw away Git Bash completion Too complicated, for too little benefit. What would probably be better would be bindings in Bash specifically for completing Git remotes, tags, etc. --- README.md | 1 - bash/bash_completion.d/git.bash | 198 ---------------------------------------- 2 files changed, 199 deletions(-) delete mode 100644 bash/bash_completion.d/git.bash diff --git a/README.md b/README.md index de4e8618..99bc9d7d 100644 --- a/README.md +++ b/README.md @@ -264,7 +264,6 @@ files, for things I really do get tired of typing repeatedly: * Bash builtins: commands, help topics, shell options, variables, etc. * `find(1)`'s more portable options -* `git(1)` subcommands, remotes, branches, tags, and addable files * `gpg(1)` long options * `make(1)` targets read from a `Makefile` * `man(1)` page titles diff --git a/bash/bash_completion.d/git.bash b/bash/bash_completion.d/git.bash deleted file mode 100644 index a05655c3..00000000 --- a/bash/bash_completion.d/git.bash +++ /dev/null @@ -1,198 +0,0 @@ -# Some simple completion for Git -_git() { - - # Subcommands for this function to stack words onto COMPREPLY; if the first - # argument is not given, the rest of the function is reached - case $1 in - - # No argument; continue normal completion - '') ;; - - # Symbolic references, remote or local - refs) - local ref - while IFS= read -r ref ; do - ref=${ref#refs/*/} - case $ref in - '') continue ;; - "$2"*) - COMPREPLY[${#COMPREPLY[@]}]=$ref - ;; - esac - done < <(git for-each-ref --format '%(refname)' 2>/dev/null) - return - ;; - - # Remote names - remotes) - local remote - while IFS= read -r remote ; do - case $remote in - '') continue ;; - "$2"*) - COMPREPLY[${#COMPREPLY[@]}]=$remote - ;; - esac - done < <(git remote 2>/dev/null) - return - ;; - - # Git aliases - aliases) - local alias - while IFS= read -r alias ; do - alias=${alias#alias.} - alias=${alias%% *} - case $alias in - '') continue ;; - "$2"*) - COMPREPLY[${#COMPREPLY[@]}]=$alias - ;; - esac - done < <(git config --get-regexp '^alias\.' 2>/dev/null) - return - ;; - - # Git subcommands - subcommands) - local execpath - execpath=$(git --exec-path) || return - local path - for path in "$execpath"/git-"$2"* ; do - ! [[ -d $path ]] || continue - [[ -e $path ]] || continue - [[ -x $path ]] || continue - COMPREPLY[${#COMPREPLY[@]}]=${path#"$execpath"/git-} - done - return - ;; - esac - - # Try to find the index of the Git subcommand - local -i sci i - for ((i = 1; !sci && i <= COMP_CWORD; i++)) ; do - case ${COMP_WORDS[i]} in - - # Skip --option=value - --*=*) ;; - - # These ones have arguments, so bump the index up one more - -C|-c|--exec-path|--git-dir|--work-tree|--namespace) ((i++)) ;; - - # Skip --option - --?*) ;; - - # We have hopefully found our subcommand - *) ((sci = i)) ;; - esac - done - - # Complete initial subcommand or alias - if ((sci == COMP_CWORD)) ; then - "${FUNCNAME[0]}" subcommands - "${FUNCNAME[0]}" aliases - return - fi - - # Test subcommand to choose completions - case ${COMP_WORDS[sci]} in - - # Help on real subcommands (not aliases) - help) - "${FUNCNAME[0]}" subcommands - return - ;; - - # Complete with remote subcommands and then remote names - remote) - if ((COMP_CWORD == 2)) ; then - local word - while IFS= read -r word ; do - [[ -n $word ]] || continue - COMPREPLY[${#COMPREPLY[@]}]=$word - done < <(compgen -W ' - add - get-url - prune - remove - rename - set-branches - set-head - set-url - show - update - ' -- "$2") - else - "${FUNCNAME[0]}" remotes - fi - return - ;; - - # Complete with stash subcommands - stash) - ((COMP_CWORD == 2)) || return - local word - while IFS= read -r word ; do - [[ -n $word ]] || continue - COMPREPLY[${#COMPREPLY[@]}]=$word - done < <(compgen -W ' - apply - branch - clear - create - drop - list - pop - save - show - store - ' -- "$2") - return - ;; - - # Complete with submodule subcommands - submodule) - ((COMP_CWORD == 2)) || return - local word - while IFS= read -r word ; do - [[ -n $word ]] || continue - COMPREPLY[${#COMPREPLY[@]}]=$word - done < <(compgen -W ' - add - deinit - foreach - init - status - summary - sync - update - ' -- "$2") - return - ;; - - # Complete with remotes and then refs - fetch|pull|push) - if ((COMP_CWORD == 2)) ; then - "${FUNCNAME[0]}" remotes - else - "${FUNCNAME[0]}" refs - fi - ;; - - # Commands for which I'm likely to want a ref - branch|checkout|merge|rebase|tag) - "${FUNCNAME[0]}" refs - ;; - - # I normally only want a refspec for "reset" if I'm using the --hard or - # --soft option; otherwise, files are fine - reset) - case $3 in - --hard|--soft) - "${FUNCNAME[0]}" refs - ;; - esac - ;; - esac -} -complete -F _git -o bashdefault -o default git -- cgit v1.2.3 From 1f55ea284b7af62630de2883b6d6671a77a39a95 Mon Sep 17 00:00:00 2001 From: Tom Ryder Date: Sun, 2 Dec 2018 12:43:46 +1300 Subject: Throw away chgrp completion I almost never run this as my own user. --- bash/bash_completion.d/chgrp.bash | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 bash/bash_completion.d/chgrp.bash diff --git a/bash/bash_completion.d/chgrp.bash b/bash/bash_completion.d/chgrp.bash deleted file mode 100644 index e63651c5..00000000 --- a/bash/bash_completion.d/chgrp.bash +++ /dev/null @@ -1,14 +0,0 @@ -# Complete group names for first non-option chgrp(1) argument -_chgrp() { - local i - for ((i = 1; i < COMP_CWORD; i++)) ; do - case ${COMP_WORDS[i]} in - -*) ;; - *) return 1 ;; - esac - done - while read -r group ; do - COMPREPLY[${#COMPREPLY[@]}]=$group - done < <(compgen -A group -- "$2") -} -complete -F _chgrp -o bashdefault -o default chgrp -- cgit v1.2.3 From 50959224f30f2c21921e7f4fa9a13b067c85dd55 Mon Sep 17 00:00:00 2001 From: Tom Ryder Date: Sun, 2 Dec 2018 12:52:57 +1300 Subject: Remove prompt() completion This is just overkill in retrospect. --- bash/bash_completion.d/prompt.bash | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 bash/bash_completion.d/prompt.bash diff --git a/bash/bash_completion.d/prompt.bash b/bash/bash_completion.d/prompt.bash deleted file mode 100644 index d73d77ec..00000000 --- a/bash/bash_completion.d/prompt.bash +++ /dev/null @@ -1,2 +0,0 @@ -# Complete subcommands for the prompt wrapper -complete -W 'on off git svn vcs ret job' prompt -- cgit v1.2.3 From ce12911d25cfeee03459833d736f964c2a7bf37a Mon Sep 17 00:00:00 2001 From: Tom Ryder Date: Sun, 2 Dec 2018 12:57:21 +1300 Subject: Rearrange _text_filenames completion a little Use an integer index for adding values. --- bash/bash_completion.d/_text_filenames.bash | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/bash/bash_completion.d/_text_filenames.bash b/bash/bash_completion.d/_text_filenames.bash index caf05e8d..b6e035ad 100644 --- a/bash/bash_completion.d/_text_filenames.bash +++ b/bash/bash_completion.d/_text_filenames.bash @@ -8,24 +8,26 @@ # the thing I want, and I want it to stay fast. # _text_filenames() { - local item - while IFS= read -r item ; do + + # Iterate through completions produced by subshell + local ci comp + while IFS= read -r comp ; do # Exclude blanks - [[ -n $item ]] || continue + [[ -n $comp ]] || continue # Exclude nonexistent (some sort of error) - [[ -e $item ]] || continue + [[ -e $comp ]] || continue # Exclude files with block, character, pipe, or socket type - ! [[ -b $item ]] || continue - ! [[ -c $item ]] || continue - ! [[ -p $item ]] || continue - ! [[ -S $item ]] || continue + ! [[ -b $comp ]] || continue + ! [[ -c $comp ]] || continue + ! [[ -p $comp ]] || continue + ! [[ -S $comp ]] || continue # Accept directories - if [[ -d $item ]] ; then - COMPREPLY[${#COMPREPLY[@]}]=$item + if [[ -d $comp ]] ; then + COMPREPLY[ci++]=$comp continue fi @@ -35,7 +37,7 @@ _text_filenames() { shopt -s nocasematch 2>/dev/null # Match against known binary patterns - case $item in + case $comp in # Archives (*.7z) ;; @@ -153,7 +155,7 @@ _text_filenames() { ) || continue # Complete everything else; some of it will still be binary - COMPREPLY[${#COMPREPLY[@]}]=$item + COMPREPLY[ci++]=$comp done < <(compgen -A file -- "$2") } -- cgit v1.2.3 From 8aaece1c0d36a43941f0c75a8b20160bc915288d Mon Sep 17 00:00:00 2001 From: Tom Ryder Date: Sun, 2 Dec 2018 12:57:44 +1300 Subject: Apply syntax fixes to last _text_filenames specs --- bash/bash_completion.d/tail.bash | 3 ++- bash/bash_completion.d/vi.bash | 3 ++- bash/bash_completion.d/view.bash | 3 ++- bash/bash_completion.d/vim.bash | 3 ++- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/bash/bash_completion.d/tail.bash b/bash/bash_completion.d/tail.bash index e80f40a5..6fe56e29 100644 --- a/bash/bash_completion.d/tail.bash +++ b/bash/bash_completion.d/tail.bash @@ -1,4 +1,5 @@ # Completion for tail(1) with files that look editable -declare -F _text_filenames >/dev/null || +if ! declare -F _text_filenames >/dev/null ; then source "$HOME"/.bash_completion.d/_text_filenames.bash +fi complete -F _text_filenames -o filenames tail diff --git a/bash/bash_completion.d/vi.bash b/bash/bash_completion.d/vi.bash index 728be438..5dd35dc1 100644 --- a/bash/bash_completion.d/vi.bash +++ b/bash/bash_completion.d/vi.bash @@ -1,4 +1,5 @@ # Completion for vi(1) with files that look editable -declare -F _text_filenames >/dev/null || +if ! declare -F _text_filenames >/dev/null ; then source "$HOME"/.bash_completion.d/_text_filenames.bash +fi complete -F _text_filenames -o filenames vi diff --git a/bash/bash_completion.d/view.bash b/bash/bash_completion.d/view.bash index e228500f..7709b068 100644 --- a/bash/bash_completion.d/view.bash +++ b/bash/bash_completion.d/view.bash @@ -1,4 +1,5 @@ # Completion for view(1) with files that look editable -declare -F _text_filenames >/dev/null || +if ! declare -F _text_filenames >/dev/null ; then source "$HOME"/.bash_completion.d/_text_filenames.bash +fi complete -F _text_filenames -o filenames view diff --git a/bash/bash_completion.d/vim.bash b/bash/bash_completion.d/vim.bash index 02d085d1..7b9ba027 100644 --- a/bash/bash_completion.d/vim.bash +++ b/bash/bash_completion.d/vim.bash @@ -1,4 +1,5 @@ # Completion for vim(1) with files that look editable -declare -F _text_filenames >/dev/null || +if ! declare -F _text_filenames >/dev/null ; then source "$HOME"/.bash_completion.d/_text_filenames.bash +fi complete -F _text_filenames -o filenames vim -- cgit v1.2.3 From 2cd9458a9a87bae9e6818cb0a82c7f1f5a737397 Mon Sep 17 00:00:00 2001 From: Tom Ryder Date: Sun, 2 Dec 2018 17:43:57 +1300 Subject: Upgrade uncap_ex.vim plugin to v0.3.0 --- vim/bundle/uncap_ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vim/bundle/uncap_ex b/vim/bundle/uncap_ex index 8e50627f..4e745cc7 160000 --- a/vim/bundle/uncap_ex +++ b/vim/bundle/uncap_ex @@ -1 +1 @@ -Subproject commit 8e50627fda3f774de4bafa064772566348da4266 +Subproject commit 4e745cc73bc24ab0e0898f13040308cdc8a60759 -- cgit v1.2.3 From 2d3f134c870f8a1a279f2ade4dfbef57b75f6ad9 Mon Sep 17 00:00:00 2001 From: Tom Ryder Date: Sun, 2 Dec 2018 17:58:13 +1300 Subject: Reduce ud() completion to just dirnames This was really overdoing it. --- bash/bash_completion.d/ud.bash | 37 ++----------------------------------- 1 file changed, 2 insertions(+), 35 deletions(-) diff --git a/bash/bash_completion.d/ud.bash b/bash/bash_completion.d/ud.bash index a9912f5a..e6c79716 100644 --- a/bash/bash_completion.d/ud.bash +++ b/bash/bash_completion.d/ud.bash @@ -1,35 +1,2 @@ -# Completion setup for ud -_ud() { - - # Only makes sense for the second argument - ((COMP_CWORD == 2)) || return - - # Iterate through directories, null-separated, add them to completions - local dirname - while IFS= read -rd '' dirname ; do - [[ -n "$dirname" ]] || continue - COMPREPLY[${#COMPREPLY[@]}]=$dirname - done < <( - - # Set options to glob correctly - shopt -s dotglob nullglob - - # Make globbing case-insensitive if appropriate - while read -r _ setting ; do - case $setting in - ('completion-ignore-case on') - shopt -s nocaseglob - break - ;; - esac - done < <(bind -v) - - # Collect directory names, strip trailing slashes - dirnames=("$2"*/) - dirnames=("${dirnames[@]%/}") - - # Print results null-delimited - printf '%s\0' "${dirnames[@]}" - ) -} -complete -F _ud -o filenames ud +# Completie ud() with directory names +complete -A directory ud -- cgit v1.2.3 From 7d6fe8b1886f902f8ffbec2a9985fae9f91121cb Mon Sep 17 00:00:00 2001 From: Tom Ryder Date: Sun, 2 Dec 2018 17:59:29 +1300 Subject: Overhaul Bash completion scripts Some general changes: * Apply case sensitivity switching in more contexts, using a dynamically loaded helper function * Use array counters for appending to COMPREPLY where possible * Lots more short-circuiting to limit structural depth These changes are expansive and there will definitely be bugs. --- bash/bash_completion.d/_abook_addresses.bash | 31 +++---- .../bash_completion.d/_completion_ignore_case.bash | 12 +++ bash/bash_completion.d/_ssh_config_hosts.bash | 33 ++----- bash/bash_completion.d/bd.bash | 39 ++++---- bash/bash_completion.d/eds.bash | 52 +++++------ bash/bash_completion.d/find.bash | 64 +++---------- bash/bash_completion.d/gpg.bash | 21 +++-- bash/bash_completion.d/keep.bash | 45 ++++----- bash/bash_completion.d/mail.bash | 4 +- bash/bash_completion.d/make.bash | 93 +++++++++++-------- bash/bash_completion.d/man.bash | 95 ++++++++++--------- bash/bash_completion.d/mex.bash | 60 +++++++++--- bash/bash_completion.d/openssl.bash | 44 +++++---- bash/bash_completion.d/pass.bash | 62 +++++++------ bash/bash_completion.d/path.bash | 101 +++++++++++---------- bash/bash_completion.d/sd.bash | 60 ++++-------- bash/bash_completion.d/td.bash | 51 +++++------ 17 files changed, 437 insertions(+), 430 deletions(-) create mode 100644 bash/bash_completion.d/_completion_ignore_case.bash diff --git a/bash/bash_completion.d/_abook_addresses.bash b/bash/bash_completion.d/_abook_addresses.bash index e1a94bc7..6f7e226f 100644 --- a/bash/bash_completion.d/_abook_addresses.bash +++ b/bash/bash_completion.d/_abook_addresses.bash @@ -1,32 +1,29 @@ +# Load _completion_ignore_case helper function +if ! declare -F _completion_ignore_case >/dev/null ; then + source "$HOME"/.bash_completion.d/_completion_ignore_case.bash +fi + # Email addresses from abook(1) _abook_addresses() { # Needs abook(1) hash abook 2>/dev/null || return - # Iterate through words produced by subshell - local word - while read -r word ; do - [[ -n $word ]] || continue - COMPREPLY[${#COMPREPLY[@]}]=$word + # Iterate through completions produced by subshell + local ci comp + while read -r comp ; do + COMPREPLY[ci++]=$comp done < <( - # Set case-insensitive matching if appropriate - while read -r _ setting ; do - case $setting in - ('completion-ignore-case on') - shopt -s nocasematch 2>/dev/null - break - ;; - esac - done < <(bind -v) + # Make matches behave appropriately + if _completion_ignore_case ; then + shopt -s nocasematch 2>/dev/null + fi # Generate list of email addresses from abook(1) while IFS=$'\t' read -r address _ ; do case $address in - ("$2"*) - printf '%s\n' "$address" - ;; + ("$2"*) printf '%s\n' "$address" ;; esac done < <(abook --mutt-query \@) ) diff --git a/bash/bash_completion.d/_completion_ignore_case.bash b/bash/bash_completion.d/_completion_ignore_case.bash new file mode 100644 index 00000000..fe8208fc --- /dev/null +++ b/bash/bash_completion.d/_completion_ignore_case.bash @@ -0,0 +1,12 @@ +# Return whether to ignore case for filename completion +_completion_ignore_case() { + + # Check Readline settings for case-insensitive matching + while read -r _ set ; do + [[ $set == 'completion-ignore-case on' ]] || continue + return 0 + done < <(bind -v) + + # Didn't find it, stay case-sensitive + return 1 +} diff --git a/bash/bash_completion.d/_ssh_config_hosts.bash b/bash/bash_completion.d/_ssh_config_hosts.bash index 0c1eb379..3f937a2a 100644 --- a/bash/bash_completion.d/_ssh_config_hosts.bash +++ b/bash/bash_completion.d/_ssh_config_hosts.bash @@ -1,45 +1,28 @@ # Complete ssh_config(5) hostnames _ssh_config_hosts() { - # Don't complete anything that wouldn't be in a valid hostname - case $2 in - *[!a-zA-Z0-9.-]*) return 1 ;; - esac - # Iterate through words from a subshell - while read -r word ; do - [[ -n $word ]] || continue - COMPREPLY[${#COMPREPLY[@]}]=$word + local ci comp + while read -r comp ; do + COMPREPLY[ci++]=$comp done < <( - # Check bind settings to see if we should match case insensitively - while read -r _ setting ; do - case $setting in - ('completion-ignore-case on') - shopt -s nocasematch 2>/dev/null - break - ;; - esac - done < <(bind -v) - # Iterate through SSH client config paths for config in "$HOME"/.ssh/config /etc/ssh/ssh_config ; do [[ -e $config ]] || continue - # Read Host options and their first value from file + # Read 'Host' options and their first value from file while read -r option value _ ; do [[ $option == Host ]] || continue # Check host value case $value in - - # Don't complete with wildcard characters + # No empties + ('') ;; + # No wildcards (*'*'*) ;; - # Found a match; print it - ("$2"*) - printf '%s\n' "$value" - ;; + ("$2"*) printf '%s\n' "$value" ;; esac done < "$config" diff --git a/bash/bash_completion.d/bd.bash b/bash/bash_completion.d/bd.bash index 59f4718f..09134e6a 100644 --- a/bash/bash_completion.d/bd.bash +++ b/bash/bash_completion.d/bd.bash @@ -1,15 +1,15 @@ +# Load _completion_ignore_case helper function +if ! declare -F _completion_ignore_case >/dev/null ; then + source "$HOME"/.bash_completion.d/_completion_ignore_case.bash +fi + # Completion setup for bd() _bd() { - # The function accepts only one argument, so it doesn't make sense to - # complete anywhere else - ((COMP_CWORD == 1)) || return - - # Iterate through slash-delimited matching parent path elements, as piped - # in by the subshell - local ci word - while IFS= read -rd/ word ; do - COMPREPLY[ci++]=$word + # Iterate through completions produced by subshell + local ci comp + while IFS= read -d / -r comp ; do + COMPREPLY[ci++]=$comp done < <( # Build an array of path nodes, leaf to root @@ -22,29 +22,22 @@ _bd() { done # Continue if we have at least two nodes, counting the leaf - ((${#nodes} > 1)) || return + ((${#nodes[@]} > 1)) || return - # Shift off the leaf, since it's not meaningful to go "back to" the + # Shift off the leaf, since it is not meaningful to go "back to" the # current directory nodes=("${nodes[@]:1}") - # Turn on case-insensitive matching, if configured to do so - while read -r _ setting ; do - case $setting in - ('completion-ignore-case on') - shopt -s nocasematch 2>/dev/null - break - ;; - esac - done < <(bind -v) + # 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 case $node in - ("$2"*) - printf '%s/' "$node" - ;; + ("$2"*) printf '%s/' "$node" ;; esac done ) diff --git a/bash/bash_completion.d/eds.bash b/bash/bash_completion.d/eds.bash index da6bb879..371962ca 100644 --- a/bash/bash_completion.d/eds.bash +++ b/bash/bash_completion.d/eds.bash @@ -1,38 +1,34 @@ -# Complete args to eds(1df) with existing executables in $EDSPATH, defaulting -# to ~/.local/bin +# Load _completion_ignore_case helper function +if ! declare -F _completion_ignore_case >/dev/null ; then + source "$HOME"/.bash_completion.d/_completion_ignore_case.bash +fi + +# Complete args to eds(1df) _eds() { - local edspath - edspath=${EDSPATH:-"$HOME"/.local/bin} - [[ -d $edspath ]] || return - local executable - while IFS= read -rd '' executable ; do - [[ -n $executable ]] || continue - COMPREPLY[${#COMPREPLY[@]}]=$executable + + # Iterate through completions produced by subshell + local ci comp + while IFS= read -d / -r comp ; do + COMPREPLY[ci++]=$comp done < <( - shopt -s dotglob nullglob - # Make globbing case-insensitive if appropriate - while read -r _ setting ; do - case $setting in - ('completion-ignore-case on') - shopt -s nocaseglob - break - ;; - esac - done < <(bind -v) + # Make globs expand appropriately + shopt -u dotglob + shopt -s nullglob + if _completion_ignore_case ; then + shopt -s nocaseglob + fi - declare -a files - files=("${EDSPATH:-"$HOME"/.local/bin}"/"$2"*) - declare -a executables - for file in "${files[@]}" ; do + # Iterate through files in local binaries directory + edspath=${EDSPATH:-"$HOME"/.local/bin} + for file in "$edspath"/"$2"* ; do + # Skip directories ! [[ -d $file ]] || continue - [[ -e $file ]] || continue + # Skip non-executable files [[ -x $file ]] || continue - executables[${#executables[@]}]=${file##*/} + # Print entry, null-terminated + printf '%q\0' "${file##*/}" done - - # Print quoted entries, null-delimited - printf '%q\0' "${executables[@]}" ) } complete -F _eds eds diff --git a/bash/bash_completion.d/find.bash b/bash/bash_completion.d/find.bash index 7593c594..f87029e7 100644 --- a/bash/bash_completion.d/find.bash +++ b/bash/bash_completion.d/find.bash @@ -1,41 +1,13 @@ -# compopt requires Bash >=4.0, and I don't think it's worth making a compatible -# version -((BASH_VERSINFO[0] >= 4)) || return - # Semi-intelligent completion for find(1); nothing too crazy _find() { - # Backtrack through words so far; if none of them look like options, we're - # still completing directory names - local i - local -i opts - for ((i = COMP_CWORD; i >= 0; i--)) ; do - case ${COMP_WORDS[i]} in - -*) - opts=1 - break - ;; - esac - done - if ! ((opts)) ; then - compopt -o dirnames - return - fi - - # For the rest of this, if we end up with an empty COMPREPLY, we should - # just do what Bash would normally do - compopt -o bashdefault -o default - - # Iterate through whatever the subshell gives us; don't add blank items, though - local item - while read -r item ; do - [[ -n $item ]] || continue - COMPREPLY+=("$item") + # Iterate through completions produced by subshell + local ci comp + while IFS= read -r comp ; do + COMPREPLY[ci++]=$comp done < <( - # If the word being completed starts with a dash, just complete it as - # an option; crude, but simple, and will be right the vast majority of - # the time + # Complete POSIX-specified options case $2 in (-*) compgen -W ' @@ -59,33 +31,27 @@ _find() { -user -xdev ' -- "$2" + return ;; esac - # Otherwise, look at the word *before* this one to figure out what to + # Look at the word *before* this one to figure out what to # complete case $3 in # Args to -exec and -execdir should be commands - (-exec|-execdir) - compgen -A command -- "$2" - ;; - - # Args to -group should complete group names - (-group) - compgen -A group -- "$2" - ;; + (-exec|-execdir) compgen -A command -- "$2" ;; # Legal POSIX flags for -type - (-type) - compgen -W 'b c d f l p s' -- "$2" - ;; + (-type) compgen -W 'b c d f l p s' -- "$2" ;; + + # Args to -group should complete group names + (-group) compgen -A group -- "$2" ;; # Args to -user should complete usernames - (-user) - compgen -A user -- "$2" - ;; + (-user) compgen -A user -- "$2" ;; + esac ) } -complete -F _find find +complete -F _find -o bashdefault -o default find diff --git a/bash/bash_completion.d/gpg.bash b/bash/bash_completion.d/gpg.bash index bfa2d1a9..c6f92676 100644 --- a/bash/bash_completion.d/gpg.bash +++ b/bash/bash_completion.d/gpg.bash @@ -1,7 +1,7 @@ # Completion for gpg(1) with long options _gpg() { - # Bail if no gpg(1) + # Needs gpg(1) hash gpg 2>/dev/null || return # Bail if not completing an option @@ -11,13 +11,16 @@ _gpg() { esac # Generate completion reply from gpg(1) options - local option - while read -r option ; do - case $option in - "$2"*) - COMPREPLY[${#COMPREPLY[@]}]=$option - ;; - esac - done < <(gpg --dump-options 2>/dev/null) + local ci comp + while read -r comp ; do + COMPREPLY[ci++]=$comp + done < <( + gpg --dump-options 2>/dev/null | + while read -r option ; do + case $option in + ("$2"*) printf '%s\n' "$option" ;; + esac + done + ) } complete -F _gpg -o bashdefault -o default gpg diff --git a/bash/bash_completion.d/keep.bash b/bash/bash_completion.d/keep.bash index 6829db9c..c7144684 100644 --- a/bash/bash_completion.d/keep.bash +++ b/bash/bash_completion.d/keep.bash @@ -1,3 +1,8 @@ +# Load _completion_ignore_case helper function +if ! declare -F _completion_ignore_case >/dev/null ; then + source "$HOME"/.bash_completion.d/_completion_ignore_case.bash +fi + # Complete calls to keep() with variables and functions, or if -d is given with # stuff that's already kept _keep() { @@ -7,16 +12,17 @@ _keep() { mode=keep if ((COMP_CWORD > 1)) ; then case ${COMP_WORDS[1]} in + # Help; no completion -h) return 1 ;; + # Deleting; change mode -d) mode=delete ;; esac fi # Collect words from an appropriate type of completion - local word - while read -r word ; do - [[ -n $word ]] || continue - COMPREPLY[${#COMPREPLY[@]}]=$word + local ci comp + while read -r comp ; do + COMPREPLY[ci++]=$comp done < <( # Switch on second word; is it a -d option? @@ -24,32 +30,27 @@ _keep() { # Keepable names: all functions and variables (keep) - compgen -A function -A variable \ - -- "$2" + compgen -A function -A variable -- "$2" ;; # Kept names: .bash-suffixed names in keep dir (delete) + # Make globs behave correctly + shopt -u dotglob shopt -s nullglob - while read -r _ setting ; do - case $setting in - ('completion-ignore-case on') - shopt -s nocaseglob - break - ;; - esac - done < <(bind -v) + if _completion_ignore_case ; then + shopt -s nocaseglob + fi # Build list of kept names - dir=${BASHKEEP:-"$HOME"/.bashkeep.d} - cword=$2 - kept=("$dir"/"$cword"*.bash) - kept=("${kept[@]##*/}") - kept=("${kept[@]%.bash}") - - # Print kept names - printf '%s\n' "${kept[@]}" + bashkeep=${BASHKEEP:-"$HOME"/.bashkeep.d} + for keep in "$bashkeep"/"$2"*.bash ; do + ! [[ -d $keep ]] || continue + keep=${keep##*/} + keep=${keep%.bash} + printf '%s\n' "$keep" + done ;; esac ) diff --git a/bash/bash_completion.d/mail.bash b/bash/bash_completion.d/mail.bash index 5d1cdec0..0f6d60d4 100644 --- a/bash/bash_completion.d/mail.bash +++ b/bash/bash_completion.d/mail.bash @@ -1,5 +1,5 @@ # Completion for mail(1) with abook(1) email addresses -if ! declare -F _text_filenames >/dev/null ; then - source "$HOME"/.bash_completion.d/_text_filenames.bash +if ! declare -F _abook_addresses >/dev/null ; then + source "$HOME"/.bash_completion.d/_abook_addresses.bash fi complete -F _abook_addresses -o bashdefault -o default mail diff --git a/bash/bash_completion.d/make.bash b/bash/bash_completion.d/make.bash index 8ca36529..2527d145 100644 --- a/bash/bash_completion.d/make.bash +++ b/bash/bash_completion.d/make.bash @@ -1,3 +1,8 @@ +# Load _completion_ignore_case helper function +if ! declare -F _completion_ignore_case >/dev/null ; then + source "$HOME"/.bash_completion.d/_completion_ignore_case.bash +fi + # Completion setup for Make, completing targets _make() { @@ -5,48 +10,56 @@ _make() { # first, then "Makefile"). You may want to add "GNU-makefile" after this. local mf for mf in makefile Makefile '' ; do - [[ -e $mf ]] || continue - break + ! [[ -e $mf ]] || break done [[ -n $mf ]] || return - # Iterate through the Makefile, line by line - local line - while IFS= read -r line ; do - case $line in - - # We're looking for targets but not variable assignments - \#*) ;; - $'\t'*) ;; - *:=*) ;; - *:*) - - # Break the target up with space delimiters - local -a targets - IFS=' ' read -rd '' -a targets < \ - <(printf '%s\0' "${line%%:*}") - - # Iterate through the targets and add suitable ones - local target - for target in "${targets[@]}" ; do - case $target in - - # Don't complete special targets beginning with a - # period - .*) ;; - - # Don't complete targets with names that have - # characters outside of the POSIX spec (plus slashes) - *[^[:word:]./-]*) ;; - - # Add targets that match what we're completing - "$2"*) - COMPREPLY[${#COMPREPLY[@]}]=$target - ;; - esac - done - ;; - esac - done < "$mf" + # Iterate through completions produced by subshell + local ci comp + while read -r comp ; do + COMPREPLY[ci++]=$comp + done < <( + while IFS= read -r line ; do + + # Match expected format + case $line in + # Has no equals sign anywhere + (*=*) continue ;; + # First char not a tab + ($'\t'*) continue ;; + # Has a colon on the line + (*:*) ;; + # Skip anything else + (*) continue ;; + esac + + # Break the target up with space delimiters + local -a targets + IFS=' ' read -a targets -r \ + < <(printf '%s\n' "${line%%:*}") + + # Short-circuit if there are no targets + ((${#targets[@]})) || exit + + # Make matches behave correctly + if _completion_ignore_case ; then + shopt -s nocasematch 2>/dev/null + fi + + # Examine each target for completion suitability + local target + for target in "${targets[@]}" ; do + case $target in + # Not .PHONY, .POSIX etc + (.*) ;; + # Nothing with metacharacters + (*[^[:word:]./-]*) ;; + # Match! + ("$2"*) printf '%s\n' "$target" ;; + esac + done + + done < "$mf" + ) } complete -F _make -o bashdefault -o default make diff --git a/bash/bash_completion.d/man.bash b/bash/bash_completion.d/man.bash index b5ecaa3e..274f663a 100644 --- a/bash/bash_completion.d/man.bash +++ b/bash/bash_completion.d/man.bash @@ -1,39 +1,32 @@ # Autocompletion for man(1) _man() { - # Don't even bother if we don't have manpath(1) - hash manpath 2>/dev/null || return - - # Don't bother if the word has slashes in it, the user is probably trying - # to complete an actual path + # Don't interfere with a user typing a path case $2 in */*) return 1 ;; esac - # If this is the second word, and the previous word started with a number, - # we'll assume that's the section to search - local section subdir - if ((COMP_CWORD > 1)) ; then - case $3 in - [0-9]*) - section=$3 - subdir=man${section%%[^0-9]*} - ;; - esac - fi + # If previous word started with a number, we'll assume that's a section to + # search + case $3 in + [0-9]*) sec=$3 ;; + esac + + # Cut completion short if we have neither section nor word; there will + # probably be too many results + [[ -n $sec ]] || [[ -n $2 ]] || return # Read completion results from a subshell and add them to the COMPREPLY # array individually - local page - while IFS= read -rd '' page ; do - [[ -n $page ]] || continue - COMPREPLY[${#COMPREPLY[@]}]=$page + local ci comp + while IFS= read -d '' -r comp ; do + COMPREPLY[ci++]=$comp done < <( # Do not return dotfiles, give us extended globbing, and expand empty # globs to just nothing shopt -u dotglob - shopt -s extglob nullglob + shopt -s nullglob # Make globbing case-insensitive if appropriate while read -r _ setting ; do @@ -45,34 +38,54 @@ _man() { esac done < <(bind -v) - # Break manpath(1) output into an array of paths - declare -a manpaths - IFS=: read -a manpaths -r < <(manpath 2>/dev/null) - - # Iterate through the manual page paths and add every manual page we find - declare -a pages - for manpath in "${manpaths[@]}" ; do - [[ -n $manpath ]] || continue - if [[ -n $section ]] ; then - for page in \ - "$manpath"/"$subdir"/"$2"*."$section"?(.[glx]z|.bz2|.lzma|.Z) - do - pages[${#pages[@]}]=$page + # Figure out the manual paths to search + if hash amanpath 2>/dev/null ; then + + # manpath(1) exists, run it to find what to search + IFS=: read -a manpaths -r \ + < <(manpath 2>/dev/null) + else + + # Fall back on some typical paths + manpaths=( \ + "$HOME"/.local/man \ + "$HOME"/.local/share/man \ + /usr/man \ + /usr/share/man \ + /usr/local/man \ + /usr/local/share/man \ + ) + fi + + # Add pages from each manual directory + local pages pi + for mp in "${manpaths[@]}" ; do + [[ -n $mp ]] || continue + + # Which pattern? Depends on section specification + if [[ -n $sec ]] ; then + + # Section requested; quoted value in glob + for page in "$mp"/man"${sec%%[!0-9]*}"/"$2"*."$sec"* ; do + pages[pi++]=${page##*/} done else - for page in "$manpath"/man[0-9]*/"$2"*.* ; do - pages[${#pages[@]}]=$page + + # No section; + for page in "$mp"/man[0-9]*/"$2"*.[0-9]* ; do + pages[pi++]=${page##*/} done fi done - # Strip paths, .gz suffixes, and finally .
suffixes - pages=("${pages[@]##*/}") - pages=("${pages[@]%.@([glx]z|bz2|lzma|Z)}") + # Bail if there are no pages + ((pi)) || exit + + # Strip section suffixes pages=("${pages[@]%.[0-9]*}") - # Print quoted entries, null-delimited - printf '%q\0' "${pages[@]}" + # Print entries, null-delimited + printf '%s\0' "${pages[@]}" ) } complete -F _man -o bashdefault -o default man diff --git a/bash/bash_completion.d/mex.bash b/bash/bash_completion.d/mex.bash index bc3d2c7b..b1e0e1a7 100644 --- a/bash/bash_completion.d/mex.bash +++ b/bash/bash_completion.d/mex.bash @@ -1,16 +1,54 @@ +# Load _completion_ignore_case helper function +if ! declare -F _completion_ignore_case >/dev/null ; then + source "$HOME"/.bash_completion.d/_completion_ignore_case.bash +fi + # Completion setup for mex(1df), completing non-executable files in $PATH _mex() { - local -a path - IFS=: read -ra path < <(printf '%s\n' "$PATH") - local dir name - for dir in "${path[@]}" ; do - [[ -d $dir ]] || continue - for name in "$dir"/* ; do - [[ -e $name ]] || continue - ! [[ -d $name ]] || continue - ! [[ -x $name ]] || continue - COMPREPLY[${#COMPREPLY[@]}]=${name##*/} + + # Iterate through completions produced by subshell + local ci comp + while IFS= read -d / -r comp ; do + COMPREPLY[ci++]=$comp + done < <( + + # Make globs expand appropriately + shopt -u dotglob + shopt -s nullglob + if _completion_ignore_case ; then + shopt -s nocaseglob + fi + + # Break $PATH up into an array + declare -a paths + IFS=: read -a paths -r \ + < <(printf '%s\n' "$PATH") + + # Iterate through each path, collecting non-executable filenames + for path in "${paths[@]}" ; do + for name in "$path"/"$2"* ; do + + # Skip anything that is not a plain file + [[ -f $name ]] || continue + # Skip files that are already executable + ! [[ -x $name ]] || continue + + # Chop off leading path + name=${name##*/} + + # Skip certain filename patterns + case $name in + # DOS batch file + (*.bat) continue ;; + # README files + (README*) continue ;; + esac + + # Print name of the file + printf '%s/' "${name##*/}" + + done done - done + ) } complete -F _mex mex diff --git a/bash/bash_completion.d/openssl.bash b/bash/bash_completion.d/openssl.bash index 3396eb9f..1cb4bd07 100644 --- a/bash/bash_completion.d/openssl.bash +++ b/bash/bash_completion.d/openssl.bash @@ -1,26 +1,32 @@ # Some simple completion for openssl(1ssl) _openssl() { + # Needs openssl(1ssl) + hash openssl 2>/dev/null || return + # Only complete the first word: OpenSSL subcommands - case $COMP_CWORD in - 1) - while read -r subcmd ; do - case $subcmd in - '') ;; - "$2"*) - COMPREPLY[${#COMPREPLY[@]}]=$subcmd - ;; - esac - done < <( - for arg in \ - list-cipher-commands \ - list-standard-commands \ - list-message-digest-commands ; do - printf '%s\n' "$arg" - openssl "$arg" + ((COMP_CWORD == 1)) || return + + # Iterate through completions produced by subshell + local ci comp + while read -r comp ; do + COMPREPLY[ci++]=$comp + done < <( + + # Run each of the command-listing commands; read each line into an + # array of subcommands (they are printed as a table) + for list in commands digest-commands cipher-commands ; do + openssl list -"$list" + done | { + declare -a subcmds + while read -a subcmds -r ; do + for subcmd in "${subcmds[@]}" ; do + case $subcmd in + ("$2"*) printf '%s\n' "$subcmd" ;; + esac done - ) - ;; - esac + done + } + ) } complete -F _openssl -o bashdefault -o default openssl diff --git a/bash/bash_completion.d/pass.bash b/bash/bash_completion.d/pass.bash index af7926d9..5a6e0b6c 100644 --- a/bash/bash_completion.d/pass.bash +++ b/bash/bash_completion.d/pass.bash @@ -1,11 +1,11 @@ +# Load _completion_ignore_case helper function +if ! declare -F _completion_ignore_case >/dev/null ; then + source "$HOME"/.bash_completion.d/_completion_ignore_case.bash +fi + # Custom completion for pass(1), because I don't like the one included with the # distribution -_pass() -{ - # If we can't read the password directory, just bail - local passdir - passdir=${PASSWORD_STORE_DIR:-"$HOME"/.password-store} - [[ -r $passdir ]] || return +_pass() { # Iterate through completions produced by subshell local ci comp @@ -13,35 +13,43 @@ _pass() COMPREPLY[ci++]=$comp done < <( - # Set shell options to expand globs the way we expect + # Make globs expand appropriately shopt -u dotglob shopt -s nullglob + if _completion_ignore_case ; then + shopt -s nocaseglob + fi + + # Set password store path + pass_dir=${PASSWORD_STORE_DIR:-"$HOME"/.password-store} - # Check Readline settings for case-insensitive matching - while read -r _ setting ; do - if [[ $setting == 'completion-ignore-case on' ]] ; then - shopt -s nocaseglob - break - fi - done < <(bind -v) + # Gather the entries + for entry in "$pass_dir"/"$2"*.gpg ; do + entries[ei++]=$entry + done - # Gather the entries, use ** for depth search if we can - entries=("$passdir"/"$2"*.gpg) + # Try to iterate into subdirs, use depth search with ** if available if shopt -s globstar 2>/dev/null ; then - entries=("${entries[@]}" "$passdir"/"$2"**/*.gpg) + for entry in "$pass_dir"/"$2"**/*.gpg ; do + entries[ei++]=$entry + done else - entries=("${entries[@]}" "$passdir"/"$2"*/*.gpg) + for entry in "$pass_dir"/"$2"*/*.gpg ; do + entries[ei++]=$entry + done fi - # Bail out if there are no entries - ((${#entries[@]})) || exit - - # Strip leading path and .gpg suffix from entry names - entries=("${entries[@]#"$passdir"/}") - entries=("${entries[@]%.gpg}") - - # Print entries, quoted and null-delimited - printf '%q\0' "${entries[@]}" + # Iterate through entries + for entry in "${entries[@]}" ; do + # Skip directories + ! [[ -d $entry ]] || continue + # Strip leading path + entry=${entry#"$pass_dir"/} + # Strip .gpg suffix + entry=${entry%.gpg} + # Print shell-quoted entry, null terminated + printf '%q\0' "$entry" + done ) } complete -F _pass pass diff --git a/bash/bash_completion.d/path.bash b/bash/bash_completion.d/path.bash index 8db6a74a..9234f132 100644 --- a/bash/bash_completion.d/path.bash +++ b/bash/bash_completion.d/path.bash @@ -1,3 +1,8 @@ +# Load _completion_ignore_case helper function +if ! declare -F _completion_ignore_case >/dev/null ; then + source "$HOME"/.bash_completion.d/_completion_ignore_case.bash +fi + # Completion for path _path() { @@ -5,9 +10,9 @@ _path() { if ((COMP_CWORD == 1)) ; then # Complete operation as first word - local cmd - while read -r cmd ; do - COMPREPLY[${#COMPREPLY[@]}]=$cmd + local ci comp + while read -r comp ; do + COMPREPLY[ci++]=$comp done < <(compgen -W ' append check @@ -18,62 +23,60 @@ _path() { remove shift ' -- "$2") + return + fi # Complete with either directories or $PATH entries as all other words - else - case ${COMP_WORDS[1]} in + case ${COMP_WORDS[1]} in - # Complete with a directory - insert|append|check) - local dirname - while IFS= read -rd '' dirname ; do - [[ -n $dirname ]] || continue - COMPREPLY[${#COMPREPLY[@]}]=$dirname - done < <( + # Complete with a directory + insert|append|check) + local ci comp + while IFS= read -d '' -r comp ; do + COMPREPLY[ci++]=$comp + done < <( - # Set options to glob correctly - shopt -s dotglob nullglob + # Make globs expand appropriately + shopt -s dotglob nullglob + if _completion_ignore_case ; then + shopt -s nocaseglob + fi - # Make globbing case-insensitive if appropriate - while read -r _ setting ; do - case $setting in - ('completion-ignore-case on') - shopt -s nocaseglob - break - ;; - esac - done < <(bind -v) + # Print shell-quoted matching directories, null-terminated + for dir in "$2"*/ ; do + printf '%q\0' "${dir%/}" + done + ) + ;; - # Collect directory names, strip trailing slash - local -a dirnames - dirnames=("$2"*/) - dirnames=("${dirnames[@]%/}") + # Complete with directories from PATH + remove) + local ci comp + while IFS= read -d '' -r comp ; do + COMPREPLY[ci++]=$comp + done < <( - # Print quoted entries, null-delimited - printf '%q\0' "${dirnames[@]}" - ) - ;; + # Make matches work appropriately + if _completion_ignore_case ; then + shopt -s nocasematch 2>/dev/null + fi - # Complete with directories from PATH - remove) - local -a promptarr - IFS=: read -rd '' -a promptarr < \ - <(printf '%s\0' "$PATH") - local part - for part in "${promptarr[@]}" ; do - case $part in - "$2"*) - COMPREPLY[${#COMPREPLY[@]}]=$(printf '%q' "$part") - ;; + # Break PATH into parts + declare -a paths + IFS=: read -a paths -d '' -r \ + < <(printf '%s\0' "$PATH") + + # Print shell-quoted matching parts, null-terminated + for path in "${paths[@]}" ; do + case $path in + ("$2"*) printf '%q\0' "$path" ;; esac done - ;; + ) + ;; - # No completion - *) - return 1 - ;; - esac - fi + # No completion + *) return 1 ;; + esac } complete -F _path path diff --git a/bash/bash_completion.d/sd.bash b/bash/bash_completion.d/sd.bash index d6e93c78..66dea73b 100644 --- a/bash/bash_completion.d/sd.bash +++ b/bash/bash_completion.d/sd.bash @@ -1,52 +1,32 @@ +# Load _completion_ignore_case helper function +if ! declare -F _completion_ignore_case >/dev/null ; then + source "$HOME"/.bash_completion.d/_completion_ignore_case.bash +fi + # Completion function for sd; any sibling directories, excluding the self _sd() { - # Only makes sense for the first argument - ((COMP_CWORD == 1)) || return - - # Current directory can't be root directory - case $PWD in - /) return 1 ;; - esac - # Build list of matching sibling directories - local dirname - while IFS= read -rd '' dirname ; do - [[ -n $dirname ]] || continue - COMPREPLY[${#COMPREPLY[@]}]=$dirname + local ci comp + while IFS= read -d / -r comp ; do + COMPREPLY[ci++]=$comp done < <( - # Set options to glob correctly + # Make globs expand appropriately shopt -s dotglob nullglob - - # Make globbing case-insensitive if appropriate - while read -r _ setting ; do - case $setting in - ('completion-ignore-case on') - shopt -s nocaseglob - break - ;; - esac - done < <(bind -v) - - # Collect directory names, strip leading ../ and trailing / - local -a dirnames - dirnames=(../"$2"*/) - dirnames=("${dirnames[@]#../}") - dirnames=("${dirnames[@]%/}") - - # Iterate again, but exclude the current directory this time - local -a sibs - local dirname - for dirname in "${dirnames[@]}" ; do - case $dirname in - "${PWD##*/}") ;; - *) sibs[${#sibs[@]}]=$dirname ;; + if _completion_ignore_case ; then + shopt -s nocaseglob + fi + + # Print matching sibling dirs that are not the current dir + for sibling in ../"$2"*/ ; do + sibling=${sibling%/} + sibling=${sibling#../} + case $sibling in + ("${PWD##*/}") ;; + (*) printf '%q/' "${sibling}" ;; esac done - - # Print quoted sibling directories, null-delimited - printf '%q\0' "${sibs[@]}" ) } complete -F _sd sd diff --git a/bash/bash_completion.d/td.bash b/bash/bash_completion.d/td.bash index 38dc51a3..f3735691 100644 --- a/bash/bash_completion.d/td.bash +++ b/bash/bash_completion.d/td.bash @@ -1,36 +1,31 @@ +# Load _completion_ignore_case helper function +if ! declare -F _completion_ignore_case >/dev/null ; then + source "$HOME"/.bash_completion.d/_completion_ignore_case.bash +fi + # Complete filenames for td(1df) _td() { - local dir - dir=${TODO_DIR:-"$HOME"/Todo} - local fn - while IFS= read -rd '' fn ; do - [[ -n $fn ]] || continue - COMPREPLY[${#COMPREPLY[@]}]=$fn - done < <( - shopt -s extglob nullglob - shopt -u dotglob - # Make globbing case-insensitive if appropriate; is there a cleaner way - # to find this value? - while read -r _ option value ; do - case $option in - (completion-ignore-case) - case $value in - (on) - shopt -s nocaseglob - break - ;; - esac - ;; - esac - done < <(bind -v) + # Iterate through completions produced by subshell + local ci comp + while IFS= read -d / -r comp ; do + COMPREPLY[ci++]=$comp + done < <( - declare -a fns - fns=("$dir"/"$2"*) - fns=("${fns[@]#"$dir"/}") + # Make globs expand appropriately + shopt -u dotglob + shopt -s nullglob + if _completion_ignore_case ; then + shopt -s nocaseglob + fi - # Print quoted entries, null-delimited - printf '%q\0' "${fns[@]}" + # Find and print matching file entries + for list in "${TODO_DIR:-"$HOME"/Todo}"/"$2"* ; do + # Skip directories + ! [[ -d $list ]] || continue + # Print entry, slash-terminated + printf '%q/' "${list##*/}" + done ) } complete -F _td td -- cgit v1.2.3 From 2d1c741cb65a95591e4f2066abf6e4ba607ef54b Mon Sep 17 00:00:00 2001 From: Tom Ryder Date: Sun, 2 Dec 2018 18:02:48 +1300 Subject: Make separate install-bash-completion target Don't make it a default just yet, either. --- Makefile | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index f9ed21ac..882982c6 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,7 @@ install \ install-abook \ install-bash \ + install-bash-completion \ install-bin \ install-bin-man \ install-curl \ @@ -359,11 +360,14 @@ install-abook: cp -p -- abook/abookrc $(HOME)/.abook install-bash: check-bash install-sh - mkdir -p -- $(HOME)/.bashrc.d $(HOME)/.bash_completion.d $(HOME)/.config + mkdir -p -- $(HOME)/.bashrc.d cp -p -- bash/bashrc $(HOME)/.bashrc cp -p -- bash/bashrc.d/* $(HOME)/.bashrc.d cp -p -- bash/bash_profile $(HOME)/.bash_profile cp -p -- bash/bash_logout $(HOME)/.bash_logout + +install-bash-completion: install-bash + mkdir -p -- $(HOME)/.bash_completion.d $(HOME)/.config cp -p -- bash/bash_completion $(HOME)/.config cp -p -- bash/bash_completion.d/* $(HOME)/.bash_completion.d -- cgit v1.2.3 From 08fc39608ddf01f9e7c1b9d8286bff277be084bb Mon Sep 17 00:00:00 2001 From: Tom Ryder Date: Sun, 2 Dec 2018 18:03:14 +1300 Subject: Bump VERSION --- VERSION | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/VERSION b/VERSION index 8e55d49a..1f343c3f 100644 --- a/VERSION +++ b/VERSION @@ -1,2 +1,2 @@ -tejr dotfiles v2.6.0 -Fri Nov 30 13:48:02 UTC 2018 +tejr dotfiles v2.7.0 +Sun Dec 2 05:03:14 UTC 2018 -- cgit v1.2.3