aboutsummaryrefslogtreecommitdiff
path: root/bash
diff options
context:
space:
mode:
authorTom Ryder <tom@sanctum.geek.nz>2018-12-02 17:59:29 +1300
committerTom Ryder <tom@sanctum.geek.nz>2018-12-02 17:59:29 +1300
commit7d6fe8b1886f902f8ffbec2a9985fae9f91121cb (patch)
tree282fac0bb1809e726a373916c90260832ab23ced /bash
parentReduce ud() completion to just dirnames (diff)
downloaddotfiles-7d6fe8b1886f902f8ffbec2a9985fae9f91121cb.tar.gz
dotfiles-7d6fe8b1886f902f8ffbec2a9985fae9f91121cb.zip
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.
Diffstat (limited to 'bash')
-rw-r--r--bash/bash_completion.d/_abook_addresses.bash31
-rw-r--r--bash/bash_completion.d/_completion_ignore_case.bash12
-rw-r--r--bash/bash_completion.d/_ssh_config_hosts.bash33
-rw-r--r--bash/bash_completion.d/bd.bash39
-rw-r--r--bash/bash_completion.d/eds.bash52
-rw-r--r--bash/bash_completion.d/find.bash64
-rw-r--r--bash/bash_completion.d/gpg.bash21
-rw-r--r--bash/bash_completion.d/keep.bash45
-rw-r--r--bash/bash_completion.d/mail.bash4
-rw-r--r--bash/bash_completion.d/make.bash93
-rw-r--r--bash/bash_completion.d/man.bash95
-rw-r--r--bash/bash_completion.d/mex.bash60
-rw-r--r--bash/bash_completion.d/openssl.bash44
-rw-r--r--bash/bash_completion.d/pass.bash62
-rw-r--r--bash/bash_completion.d/path.bash101
-rw-r--r--bash/bash_completion.d/sd.bash60
-rw-r--r--bash/bash_completion.d/td.bash51
17 files changed, 437 insertions, 430 deletions
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 .<section> 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