Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Bash completion: compgen a wordlist as if they were paths - Only suggest up until next slash

I'm working on a bash completion script for a dot file management utility. The tool has a command dots diff [filename] that will show the difference between the installed dot file and the source dot file. It also has a command dots files which lists the paths of all managed dot files (relative to the source directory). I would like to complete the diff command with the output of the files command.

Here's an example of the files output

X11/xkb/symbols/evan-custom
X11/xorg.conf.d/10-dual-monitors.conf
X11/xorg.conf.d/10-keylayout.conf
bash/aliases
bash/bashrc
bash/completion.d/dots
bash/profiles/standard-user
bash/profiles/systemd-user
bspwm/bspwmrc
compton/compton.conf
fontconfig/fonts.conf
git/config
git/ignore
gtk-2.0/gtkrc
gtk-3.0/settings.ini
mysql/config
mysql/grcat
ncmpcpp/config
pulse/client.conf
pulse/daemon.conf
pulse/default.pa
ssh/config
sublime-text-3/Packages/User/Preferences.sublime-settings
sxhkd/sxhkdrc
termite/config
transmission-daemon/settings.json
vim/vimrc

Using something like this

COMPREPLY=( $(compgen -W "$(dots files)" -- $cur) )

Works, however when readline lists the available options it lists out the full paths (The list above).

I would like for it to treat the words as if they were file paths and when listing suggestions only list up to the first forward slash.

For example, if I typed dots diff [tab][tab] the following should be printed

X11/
bash/
bspwm/
compton/
fontconfig/
git/
gtk-2.0/
gtk-3.0/
mysql/
ncmpcpp/
pulse/
ssh/
sublime-text-3/
sxhkd/
termite/
transmission-daemon/
vim/

If for example I then typed dots diff bash/[tab][tab] then it would show

aliases
bashrc
completion.d/
profiles/

Ideally I would like it to actually treat it as a path so that changing the readline option mark-directories to off would exclude the trailing slashes.

I've tried setting compopt -o filenames but this instead gives suggestions for the file names, instead of the paths initially.

Here is the completion script I have so far

like image 295
Evan Purkhiser Avatar asked Nov 03 '13 11:11

Evan Purkhiser


People also ask

How do you autocomplete in bash?

Bash completion is a bash function that allows you to auto complete commands or arguments by typing partially commands or arguments, then pressing the [Tab] key. This will help you when writing the bash command in terminal.

What does bash completion do?

Bash completion is a functionality through which Bash helps users type their commands more quickly and easily. It does this by presenting possible options when users press the Tab key while typing a command.

How can I tell if I have bash completion?

You can use the complete command with the -p option to get a list of all or specific completions.

Where do I put bash completion scripts?

Running make install will place the scripts to $(sysconfdir)/bash_completion. d/ , thus, the user should specify --sysconfdir=/etc at configuration. If OVS is installed from packages, the scripts will automatically be placed inside /etc/bash_completion.


1 Answers

I've solved this.

The trick was to use compopt -o filename and then slice off the portion of the path being completed that is a sub-directory of the directory being completed.

Here's the code

# Do completion from a passed list of paths
#
# Accepts 2 arguments
# 1. The list of paths to complete from
# 2. The current word being completed
__dots_path_comp()
{
    # This forces readline to only display the last item separated by a slash
    compopt -o filenames

    local IFS=$'\n'
    local k="${#COMPREPLY[@]}"

    for path in $(compgen -W "$1" -- $2)
    do
        local trailing_trim

        # Determine what to trim from the end
        trailing_trim="${path#${2%/*}/}/"
        trailing_trim="${trailing_trim#*/}"
        trailing_trim="${trailing_trim%/}"

        # Don't add a space if there is more to complete
        [[ "$trailing_trim" != "" ]] && compopt -o nospace

        # Remove the slash if mark-directories is off
        if ! _rl_enabled mark-directories
        then
            # If The current typed path doesnt have a slash in it yet check if
            # it is the full first portion of a path and ignore everything after
            # if it is. We don't have to do this once the typed path has a slash
            # in it as the logic above will pick up on it
            [[ "$2" != */* && "$path" == ${2}/* ]] && path="$2/$trailing_trim"    

            trailing_trim="/$trailing_trim"
        fi

        COMPREPLY[k++]="${path%%${trailing_trim}}"
    done
}
like image 181
Evan Purkhiser Avatar answered Jan 02 '23 11:01

Evan Purkhiser