diff options
40 files changed, 550 insertions, 828 deletions
@@ -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 @@ -264,8 +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` * `man(1)` page titles @@ -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 diff --git a/bash/bash_completion.d/_abook_addresses.bash b/bash/bash_completion.d/_abook_addresses.bash index e79eef42..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 - ("${COMP_WORDS[COMP_CWORD]}"*) - 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 c26457cf..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 ${COMP_WORDS[COMP_CWORD]} 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 -qs 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 - ("${COMP_WORDS[COMP_CWORD]}"*) - printf '%s\n' "$value" - ;; + ("$2"*) printf '%s\n' "$value" ;; esac done < "$config" diff --git a/bash/bash_completion.d/_text_filenames.bash b/bash/bash_completion.d/_text_filenames.bash index 9cc1c722..b6e035ad 100644 --- a/bash/bash_completion.d/_text_filenames.bash +++ b/bash/bash_completion.d/_text_filenames.bash @@ -8,34 +8,36 @@ # 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 # 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 + 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 -- "${COMP_WORDS[COMP_CWORD]}") + done < <(compgen -A file -- "$2") } diff --git a/bash/bash_completion.d/bd.bash b/bash/bash_completion.d/bd.bash index e67cdd09..09134e6a 100644 --- a/bash/bash_completion.d/bd.bash +++ b/bash/bash_completion.d/bd.bash @@ -1,25 +1,45 @@ +# 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() { - # Only makes sense for the first argument - ((COMP_CWORD == 1)) || return + # 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 + path=$PWD + while [[ -n $path ]] ; do + node=${path##*/} + path=${path%/*} + [[ -n $node ]] || continue + nodes[ni++]=$node + done + + # Continue if we have at least two nodes, counting the leaf + ((${#nodes[@]} > 1)) || return - # Build a list of dirnames in $PWD - local -a dirnames - IFS=/ read -rd '' -a dirnames < <(printf '%s\0' "${PWD#/}") + # Shift off the leaf, since it is not meaningful to go "back to" the + # current directory + nodes=("${nodes[@]:1}") - # Remove the last element in the array (the current directory) - ((${#dirnames[@]})) || return - dirnames=("${dirnames[@]:0:${#dirnames[@]}-1}") + # Make matching behave appropriately + if _completion_ignore_case ; then + shopt -s nocasematch 2>/dev/null + fi - # 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 + # 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 diff --git a/bash/bash_completion.d/chgrp.bash b/bash/bash_completion.d/chgrp.bash deleted file mode 100644 index 5e93ccee..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 -- "${COMP_WORDS[COMP_CWORD]}") -} -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..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}"/"${COMP_WORDS[COMP_CWORD]}"*) - 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 7e2ae9c3..f87029e7 100644 --- a/bash/bash_completion.d/find.bash +++ b/bash/bash_completion.d/find.bash @@ -1,42 +1,14 @@ -# 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 - case ${COMP_WORDS[COMP_CWORD]} in + # Complete POSIX-specified options + case $2 in (-*) compgen -W ' -atime @@ -58,34 +30,28 @@ _find() { -type -user -xdev - ' -- "${COMP_WORDS[COMP_CWORD]}" + ' -- "$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 ${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]}" - ;; - - # Args to -group should complete group names - (-group) - compgen -A group -- "${COMP_WORDS[COMP_CWORD]}" - ;; + (-exec|-execdir) compgen -A command -- "$2" ;; # Legal POSIX flags for -type - (-type) - compgen -W 'b c d f l p s' -- "${COMP_WORDS[COMP_CWORD]}" - ;; + (-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 -- "${COMP_WORDS[COMP_CWORD]}" - ;; + (-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/ftp.bash b/bash/bash_completion.d/ftp.bash deleted file mode 100644 index a584dd81..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 == "${COMP_WORDS[COMP_CWORD]}"* ]] || continue - COMPREPLY[${#COMPREPLY[@]}]=$machine - done -} -complete -F _ftp -o bashdefault -o default ftp diff --git a/bash/bash_completion.d/git.bash b/bash/bash_completion.d/git.bash deleted file mode 100644 index a2edb468..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 ;; - "${COMP_WORDS[COMP_CWORD]}"*) - 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 ;; - "${COMP_WORDS[COMP_CWORD]}"*) - 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 ;; - "${COMP_WORDS[COMP_CWORD]}"*) - 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-"${COMP_WORDS[COMP_CWORD]}"* ; 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 - ' -- "${COMP_WORDS[COMP_CWORD]}") - 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 - ' -- "${COMP_WORDS[COMP_CWORD]}") - 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 - ' -- "${COMP_WORDS[COMP_CWORD]}") - 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 ${COMP_WORDS[COMP_CWORD-1]} in - --hard|--soft) - "${FUNCNAME[0]}" refs - ;; - esac - ;; - esac -} -complete -F _git -o bashdefault -o default git diff --git a/bash/bash_completion.d/gpg.bash b/bash/bash_completion.d/gpg.bash index 697e4a65..c6f92676 100644 --- a/bash/bash_completion.d/gpg.bash +++ b/bash/bash_completion.d/gpg.bash @@ -1,22 +1,26 @@ # 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 - case ${COMP_WORDS[COMP_CWORD]} in - --*) return 1 ;; + case $2 in + --*) ;; + *) return 1 ;; esac # Generate completion reply from gpg(1) options - local option - while read -r option ; do - case $option in - "${COMP_WORDS[COMP_CWORD]}"*) - 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 00b1469e..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 \ - -- "${COMP_WORDS[COMP_CWORD]}" + 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=${COMP_WORDS[COMP_CWORD]} - 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/kill.bash b/bash/bash_completion.d/kill.bash deleted file mode 100644 index dccc926b..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 - "${COMP_WORDS[COMP_CWORD]}"*) - COMPREPLY[${#COMPREPLY[@]}]=$pid - ;; - esac - done < <( { - compgen -A job -P% - pgrep -u "$USER" . - } 2>/dev/null ) -} -complete -F _kill kill 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 0f39ef4b..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 - "${COMP_WORDS[COMP_CWORD]}"*) - 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 ffef48ec..274f663a 100644 --- a/bash/bash_completion.d/man.bash +++ b/bash/bash_completion.d/man.bash @@ -1,43 +1,32 @@ # Autocompletion for man(1) _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 + # 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 ${COMP_WORDS[COMP_CWORD-1]} in - [0-9]*) - section=${COMP_WORDS[COMP_CWORD-1]} - 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 @@ -49,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"/"$word"*."$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]*/"$word"*.* ; 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/mysql.bash b/bash/bash_completion.d/mysql.bash deleted file mode 100644 index 3ff97090..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"/"${COMP_WORDS[COMP_CWORD]}"*.cnf) - cnfs=("${cnfs[@]#"$dirname"/}") - cnfs=("${cnfs[@]%.cnf}") - - # Print quoted entries, null-delimited - printf '%q\0' "${cnfs[@]}" - ) -} -complete -F _mysql -o bashdefault -o default mysql diff --git a/bash/bash_completion.d/openssl.bash b/bash/bash_completion.d/openssl.bash index 86650770..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 - '') ;; - "${COMP_WORDS[COMP_CWORD]}"*) - 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 176886dc..5a6e0b6c 100644 --- a/bash/bash_completion.d/pass.bash +++ b/bash/bash_completion.d/pass.bash @@ -1,47 +1,55 @@ -# Requires Bash >= 4.0 for globstar -((BASH_VERSINFO[0] >= 4)) || return +# 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 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 + # Make globs expand appropriately shopt -u dotglob - shopt -s globstar nullglob + shopt -s 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) + # Set password store path + pass_dir=${PASSWORD_STORE_DIR:-"$HOME"/.password-store} - # 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=("${entries[@]#"$passdir"/}") - entries=("${entries[@]%.gpg}") + # Gather the entries + for entry in "$pass_dir"/"$2"*.gpg ; do + entries[ei++]=$entry + done - # Print quoted entries, null-delimited - printf '%q\0' "${entries[@]}" + # Try to iterate into subdirs, use depth search with ** if available + if shopt -s globstar 2>/dev/null ; then + for entry in "$pass_dir"/"$2"**/*.gpg ; do + entries[ei++]=$entry + done + else + for entry in "$pass_dir"/"$2"*/*.gpg ; do + entries[ei++]=$entry + done + fi + + # 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 7143b448..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 @@ -17,63 +22,61 @@ _path() { pop remove shift - ' -- "${COMP_WORDS[COMP_CWORD]}") + ' -- "$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=("${COMP_WORDS[COMP_CWORD]}"*/) - 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 - "${COMP_WORDS[COMP_CWORD]}"*) - 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/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 diff --git a/bash/bash_completion.d/sd.bash b/bash/bash_completion.d/sd.bash index e7e82f80..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=(../"${COMP_WORDS[COMP_CWORD]}"*/) - 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/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 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/td.bash b/bash/bash_completion.d/td.bash index 92927c28..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"/"${COMP_WORDS[COMP_CWORD]}"*) - 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 diff --git a/bash/bash_completion.d/ud.bash b/bash/bash_completion.d/ud.bash index c7dee582..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=("${COMP_WORDS[COMP_CWORD]}"*/) - 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 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 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 d938275c..5161a0bf 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' @@ -102,24 +103,42 @@ 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 _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/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[@]##*/}") @@ -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/vim/bundle/uncap_ex b/vim/bundle/uncap_ex -Subproject 8e50627fda3f774de4bafa064772566348da426 +Subproject 4e745cc73bc24ab0e0898f13040308cdc8a6075 @@ -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 |