Pierre
Pierre

Reputation: 2090

Is it possible to define a completion specification for menu-complete?

I have a folder ~/builds that contains the following sub-directories:

> tree ~/builds
~/builds
├── projectA
│   ├── build_2020_04_10_ok
│   ├── build_2020_04_11_ko
│   ├── build_2020_04_12_ok
│   └── ...
└── projectB
    ├── build_2020_04_10_ok
    ├── build_2020_04_11_ok
    ├── build_2020_04_12_ok
    └── ...

Currently, when I write a command that take one of the sub-folder as an argument and I use the auto-completion, bash lists all the candidates:

> mycmd ~/builds/projectA/[TAB]
> mycmd ~/builds/projectA/build_2020_04_1[TAB][TAB]
build_2020_04_10_ok build_2020_04_11_ko build_2020_04_12_ok ...

That is not the behavior I want.

What I want is more like a Windows-like auto-completion. I know I can use it by modifying my .bashrc that way:

bind '"\C-g": menu-complete'

Now, here is what happens when I press Ctrl+g:

> mycmd ~/builds/projectA/[Ctrl+g]
> mycmd ~/builds/projectA/build_2020_04_10_ok[Ctrl+g]
> mycmd ~/builds/projectA/build_2020_04_11_ko[Ctrl+g]
> mycmd ~/builds/projectA/build_2020_04_12_ok

It is almost what I want. I would like to change in which order the sub-folders appear (the newest at first, then older to older). Also, I would like to discard all the ko builds. In other words, I want the order defined by this command:

ls -dr build*ok
build_2020_04_12_ok/  build_2020_04_10_ok/

Therefore, I would like to define a completion specification for menu-complete to perform the behavior I want only on the sub-directories of ~/builds. I saw it is possible to do it for complete and I found no information to do it on menu-complete.

Is it possible?

Upvotes: 1

Views: 237

Answers (1)

dimo414
dimo414

Reputation: 48864

As @chepner said, menu-complete delegates to the same completions that complete does (see man readline). So initially I didn't think it would be possible to configure completions differently based on whether it's going through complete or menu-complete. But I checked to see if any different values are set depending on which hook is invoked and something stood out:

# Get the current set of environment variables
# the sed expression strips functions to just print variables
# There's probably a better way to do this...
bash-5.0$ get_env() {
  set | sed -n '/^[^=]*$/q;p' | sort
}

# Define a function to use as a completion that simply prints the changes
# in the environment between the normal shell and from within a completion
bash-5.0$ diff_env() {
  echo # extra echo's for readability
  sdiff -s /tmp/env.txt <(get_env)
}
# Register it as a completion (the command doesn't actually need to exist)
bash-5.0$ complete -F diff_env foo

bash-5.0$ bind '"\C-g": menu-complete'

# Persist the initial environment
bash-5.0$ get_env > /tmp/env.txt

bash-5.0$ foo [TAB]
BASH_LINENO=([0]="12")            |    BASH_LINENO=([0]="1" [1]="13")
BASH_SOURCE=([0]="main")          |    BASH_SOURCE=([0]="main" [1]="main")
                                  >    COMP_CWORD=1
                                  >    COMP_KEY=9
                                  >    COMP_LINE='foo '
                                  >    COMP_POINT=4
                                  >    COMP_TYPE=9
                                  >    COMP_WORDS=([0]="foo" [1]="")
                                  >    _=echo
FUNCNAME=([0]="get_env")          |    FUNCNAME=([0]="get_env" [1]="diff_env")
_=get_env                         <

^C
bash-5.0$ foo [Ctrl+g]
BASH_LINENO=([0]="12")            |    BASH_LINENO=([0]="1" [1]="14")
BASH_SOURCE=([0]="main")          |    BASH_SOURCE=([0]="main" [1]="main")
                                  >    COMP_CWORD=1
                                  >    COMP_KEY=9
                                  >    COMP_LINE='foo '
                                  >    COMP_POINT=4
                                  >    COMP_TYPE=37
                                  >    COMP_WORDS=([0]="foo" [1]="")
                                  >    _=echo
FUNCNAME=([0]="get_env")          |    FUNCNAME=([0]="get_env" [1]="diff_env")
_=get_env                         <
^C

Spot it? COMP_TYPE is different! man bash explains that it's set to an integer depending on the completion behavior; the description is a bit opaque but the value is the ASCII-code-point (9 for tab, 37 for %) for the given type of completion.

So we can define a completion function that checks this value in order to change its behavior:

bash-5.0$ clever_complete() {
  case "$COMP_TYPE" in
    9) COMPREPLY=("tab!") ;;
    37) COMPREPLY=("ctrl-g!") ;;
    # should probably behave the same as [TAB], but I split it out to demonstrate
    *) COMPREPLY=("IDK... $COMP_TYPE") ;;
  esac
}
bash-5.0$ complete -F clever_complete bar
bash-5.0$ bar [TAB]tab! ^C
bash-5.0$ bar [Ctrl+g]ctrl-g! ^C

Hopefully that's enough to go on :) if you need help actually writing the complete function with the behavior you described I'd suggest starting a separate question with the current implementation (use complete -p [command] to see the current completion, and type [function-name] to see the completion's implementation), it should be straightforward to adjust the existing behavior from there.

Upvotes: 5

Related Questions