Bash Autocomplete – Disable Tab Completion in Bash for Large Directories

autocompletebash

I'm working with large number of files, which I keep in a directory. Every time I go in to that directory and accidentally press Tab twice, it takes too long (may be over a minutes) to show files that match the pattern, and I'm quite annoyed with this behavior.

For example, my directory structure is:

my-project/
├── docs/
├── data/        <---- contains around 200k files.
└── analyser/

Since I still love completion, is there anyway to disable this feature only at the data/ directory? Like set timeout for 5 sec, or a script that automatically switch off completion when inside specific directory?

Best Answer

This isn't perfect, but then again bash completion is quite a tricky thing...

The very simplest way is on a per-command basis, it's slightly more flexible than FIGNORE, you can do:

 complete -f -X "/myproject/data/*" vi

This instructs autocomplete that completion for vi is for files, and to remove patterns matching the -X filter. The downside is that the pattern isn't normalised, so ../data and variations won't match.

The next best thing might be a custom PROMPT_COMMAND function:

# associative arrays of non-autocomplete directories
declare -A noacdirs=([/myproject/data]=1 )

function _myprompt {
    [[ -n "${noacdirs[$PWD]}" ]] && {
       echo autocomplete off
       bind 'set disable-completion on'
    } || {
       echo autocomplete on
       bind 'set disable-completion off'
    }
} 

PROMPT_COMMAND=_myprompt

This disables completion (completely) when you are in the directory, but it disables it for every path not just files in that directory.

It would be more generally useful to selectively disable this for defined paths, but I believe the only way is to use a default completion function (bash-4.1 and later with complete -D) and a lot of messing about.

This should work for you, but it may have unintended side effects (i.e. changes to the expected completion in some cases):

declare -A noacdirs=([/myproject/data]=1 )

_xcomplete() {
    local cur=${COMP_WORDS[COMP_CWORD]} # the current token

    name=$(readlink -f "${cur:-./}")  # poor man's path canonify
    dirname=$(dirname "$name/.")

    [[ -n "${noacdirs[$dirname]}" ]] && {
        COMPREPLY=( "" )   # dummy to prevent completion
        return
    }
    # let default kick in
    COMPREPLY=()
}

complete -o bashdefault -o default -F _xcomplete vi

This works for completion of vi, other commands can be added as needed. It should stop completion for files in the named directories regardless of path or working directory.

I believe the general approach with complete -D is to dynamically add completion functions for each command as it is encountered. One might also need to add complete -E (completion of command name when input buffer is empty).


Update Here's a hybrid version of the PROMPT_COMMAND and completion function solutions, it's a little easier to understand and hack I think:

declare -A noacdirs=([/myproject/data]=1 [/project2/bigdata]=1)

_xcomplete() {
    local cmd=${COMP_WORDS[0]}
    local cur=${COMP_WORDS[COMP_CWORD]} # the current token

    [[ -z "$cur" && -n "$nocomplete" ]] && {
        printf "\n(restricted completion for $cmd in $nocomplete)\n"  
        printf "$PS2 $COMP_LINE"
        COMPREPLY=( "" )   # dummy to prevent completion
        return
    }
    COMPREPLY=()       # let default kick in
}

function _myprompt {
    nocomplete=
    # uncomment next line for hard-coded list of directories
    [[ -n "${noacdirs[$PWD]}" ]] && nocomplete=$PWD
    # uncomment next line for per-directory ".noautocomplete"
    # [[ -f ./.noautocomplete ]] && nocomplete=$PWD
    # uncomment next line for size-based guessing of large directories
    # [[ $(stat -c %s .) -gt 512*1024 ]] && nocomplete=$PWD
} 

PROMPT_COMMAND=_myprompt
complete -o bashdefault -o default -F _xcomplete vi cp scp diff

This prompt function sets the nocomplete variable when you enter one of the configured directories. The modified completion behaviour only kicks in when that variable is non-blank and only when you try completing from an empty string, thus allowing completion of partial names (remove the -z "$cur" condition to prevent completion altogether). Comment out the two printf lines for silent operation.

Other options include a per-directory .noautocomplete flag file that you can touch in a directory as needed; and guessing of directory size using GNU stat. You can use any or all of those three options.

(The stat method is only a guess, the reported directory size grows with its contents, it's a "high water mark" that won't usually shrink when files are deleted without some administrative intervention. It's cheaper than determining the real contents of a potentially large directory. Precise behaviour and increment per file depends on underlying filesystem. I find it a reliable indicator on linux ext2/3/4 systems at least.)

bash adds an extra space even when an empty completion is returned (this only occurs when completing at the end of a line). You can add -o nospace to the complete command to prevent this.

One remaining niggle is that if you back up the cursor to the start of a token and hit tab, the default completion will kick in again. Consider it a feature ;-)

(Or you could futz around with ${COMP_LINE:$COMP_POINT-1:1} if you like over-engineering, but I find bash itself fails to set the completion variables reliably when you back up and attempt completion in the middle of a command.)

Related Question