diff options
author | Tom Ryder <tom@sanctum.geek.nz> | 2018-12-02 18:03:15 +1300 |
---|---|---|
committer | Tom Ryder <tom@sanctum.geek.nz> | 2018-12-02 18:03:15 +1300 |
commit | 693fc13bb98b17938f2208fbadaec1996822fc5e (patch) | |
tree | 4a9cc6179f521528c663c8ab5f3bbc59dc57eaa0 | |
parent | Merge branch 'release/v2.6.0' (diff) | |
parent | Bump VERSION (diff) | |
download | dotfiles-693fc13bb98b17938f2208fbadaec1996822fc5e.tar.gz dotfiles-693fc13bb98b17938f2208fbadaec1996822fc5e.zip |
Merge branch 'release/v2.7.0'v2.7.0
* release/v2.7.0: (22 commits)
Bump VERSION
Make separate install-bash-completion target
Overhaul Bash completion scripts
Reduce ud() completion to just dirnames
Upgrade uncap_ex.vim plugin to v0.3.0
Apply syntax fixes to last _text_filenames specs
Rearrange _text_filenames completion a little
Remove prompt() completion
Throw away chgrp completion
Throw away Git Bash completion
Remove mysql(1) completion
Use consistent temp names for shell subfile vars
Overhaul pass(1) completion
Adjust syntax of two more completion loads
Remove ftp(1) completion
Remove `kill` completion
Use the positional parameter aliases for words
Overhaul bd() completion again
Remove unneeded -q option to shopt -s commands
Don't include dotfiles in keep() names
...
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 |