I got tired of `cd` (and `ls`, and `vim`, and...) always suggesting .svn directories on tab completion, so I found a way to filter them.
* Before we go any further, switching to `git` was not an option ;)
Luckily, bash's tab completion is entirely scriptable, and the out-of-the-box completions are all editable functions housed in the `/etc/bash_completion` file.
- Install the bash_completion package, as necessary (on OSX: `brew install bash_completion`)
- Save the code below to /etc/bash_completion.d/cdexclude (/usr/local/etc/bash_completion.d/cdexclude, if you're on OSX and installed using Homebrew)
- Edit ~/.cdexclude and add any directory fragments you want to exclude from tab completion, one per line (eg, in my file I have ".svn" as one line)
- `source /etc/bash_completion` to enable the changes
# Install bash_completion (using brew or whatnot) and shove
# this into /usr/local/etc/bash_completion.d
#
# All the overrides below do is prevent directories that match
# a line in your $HOME/.cdexclude from showing up in tab
# completions.
#
# e.g. If you put ".svn" in ~/.cdexclude your subversion directories
# won't show up in tab completion.
#
CDEXCLUDE_FILE=~/.cdexclude
CDEXCLUDE=( $( cat "$CDEXCLUDE_FILE" 2>/dev/null ) )
if [ ${#CDEXCLUDE[@]} -gt 0 ]; then
echo "Loaded '${#CDEXCLUDE[@]}' entries from $CDEXCLUDE_FILE"
else
echo "HAS NO ENTRIES"
fi
# Find an element in the CDEXCLUDE array
has_element() {
local hay needle=$1
for hay in "${CDEXCLUDE[@]}"; do
[[ "$hay" == "$needle" ]] && echo "found" && return 0
done
return 1
}
_filedir()
{
local i IFS=$'\n' xspec
_tilde "$cur" || return 0
local -a toks
local quoted tmp
_quote_readline_by_ref "$cur" quoted
toks=( ${toks[@]-} $(
compgen -d -- "$quoted" | {
while read -r tmp; do
# If the directory is in your .cdexclude file, skip it
[[ `has_element "\`basename $tmp\`"` ]] && continue
# TODO: I have removed a "[ -n $tmp ] &&" before 'printf ..',
# and everything works again. If this bug suddenly
# appears again (i.e. "cd /b<TAB>" becomes "cd /"),
# remember to check for other similar conditionals (here
# and _filedir_xspec()). --David
printf '%s\n' $tmp
done
}
))
if [[ "$1" != -d ]]; then
# Munge xspec to contain uppercase version too
[[ ${BASH_VERSINFO[0]} -ge 4 ]] && \
xspec=${1:+"!*.@($1|${1^^})"} || \
xspec=${1:+"!*.@($1|$(printf %s $1 | tr '[:lower:]' '[:upper:]'))"}
toks=( ${toks[@]-} $( compgen -f -X "$xspec" -- $quoted) )
fi
[ ${#toks[@]} -ne 0 ] && _compopt_o_filenames
COMPREPLY=( "${COMPREPLY[@]}" "${toks[@]}" )
} # _filedir()
_filedir_xspec()
{
local IFS cur xspec
IFS=$'\n'
COMPREPLY=()
_get_comp_words_by_ref cur
_expand || return 0
# get first exclusion compspec that matches this command
xspec=$( awk "/^complete[ \t]+.*[ \t]${1##*/}([ \t]|\$)/ { print \$0; exit }" \
"$BASH_COMPLETION" )
# prune to leave nothing but the -X spec
xspec=${xspec#*-X }
xspec=${xspec%% *}
local -a toks
local tmp
toks=( ${toks[@]-} $(
compgen -d -- "$(quote_readline "$cur")" | {
while read -r tmp; do
# If the directory is in your .cdexclude file, skip it
[[ `has_element "\`basename $tmp\`"` ]] && continue
# see long TODO comment in _filedir() --David
printf '%s\n' $tmp
done
}
))
# Munge xspec to contain uppercase version too
eval xspec="${xspec}"
local matchop=!
if [[ $xspec == !* ]]; then
xspec=${xspec#!}
matchop=@
fi
[[ ${BASH_VERSINFO[0]} -ge 4 ]] && \
xspec="$matchop($xspec|${xspec^^})" || \
xspec="$matchop($xspec|$(printf %s $xspec | tr '[:lower:]' '[:upper:]'))"
toks=( ${toks[@]-} $(
eval compgen -f -X "!$xspec" -- "\$(quote_readline "\$cur")" | {
while read -r tmp; do
# If the directory is in your .cdexclude file, skip it
[[ `has_element "\`basename $tmp\`"` ]] && continue
[ -n $tmp ] && printf '%s\n' $tmp
done
}
))
[ ${#toks[@]} -ne 0 ] && _compopt_o_filenames
COMPREPLY=( "${toks[@]}" )
}
Recent Comments