3

I'm using the following in my zshrc:

zstyle ':completion:*' matcher-list 'm:{[:lower:]}={[:upper:]} r:|?=**'

(which is thanks to Marlon)

But I'm finding some odd behaviour. e.g. take the following test case:


% tree zsh-test 
zsh-test
├── a-xx
├── b-xx
└── somefile

Then typing ls zsh-test/xx and hitting Tab I might have expected to see a-xx and b-xx listed as possiblities. Instead, nothing is listed and the xx part of what I typed is removed!

I think what's happening is xx is generating two options, then zsh is looking for a common prefix, not finding one and so replacing my text with nothing. Which is not very helpful. I've RTM but that's not helped me with the goal of finding an intuitive fuzzy matcher pattern.

Is there a way I can get it to show the candidates that match the pattern, without removing my pattern?

The weird thing about this behaviour is that I can't even get used to it because if I'm inside the zsh-test dir and I type ls xx Tab then it works as expected - showing the two candidates!

2 Answers 2

2

This happens because Zsh sees zsh-test/ as an unambiguous prefix in the first case, whereas in the second case, there is no unambiguous prefix. (Yes, there's an unambiguous suffix, but Zsh doesn't do anything with that, unfortunately.) And by default, if Zsh sees an unambiguous prefix, then the first press of Tab will simply insert that prefix, possibly deleting characters you've already typed.

You can test this by entering the following into the shell:

autoload -Uz compinit && compinit
zstyle ':completion:*' matcher-list 'm:{[:lower:]}={[:upper:]} r:|?=**'
bindkey '^I' complete-word
zle -C complete-word complete-word complete-word

complete-word() {
  _main_complete;
  compadd -x "unambiguous prefix: '${compstate[unambiguous]}'";
  compstate[list]='list force';
}

Then, try your two cases again. For the first case, you will see unambiguous prefix: 'zsh-test/'. For the second case, you will see unambiguous prefix: ''

There is, unfortunately, no way to configure Zsh to get the behavior you want. However, if you are willing to go a bit deeper down the rabbit hole, then the behavior you desire can be achieved by adding the following script to your .zshrc file:

zmodload -i zsh/complist
autoload -Uz compinit && compinit
zstyle ':completion:*' matcher-list 'm:{[:lower:]}={[:upper:]} r:|?=**'
bindkey '^I' complete-word
zle -C complete-word menu-select complete-word

complete-word() {
  _main_complete;
  compstate[list]='list';
  local word=$PREFIX$SUFFIX
  (( compstate[unambiguous_cursor] <= ${#word} )) && compstate[insert]='menu';
}
  • compstate[list]='list' makes sure that completions are automatically listed when there's more than one match available.
  • The last line checks whether the default behavior would delete any part of the current word that the cursor is on and, if so, instead inserts the first item from the list.
  • The menu-select (provided by zsh/complist) in zle -C complete-word menu-select complete-word then lets you use your choice of Tab or the arrow keys to select a different item from the list.

Tuning Zsh's completion system this way can get fairly complex rather quickly and there are many parts of it that are not all that well-documented. I highly encourage you to check out my zsh-autocomplete plugin, which customizes the Zsh completion system to do out-of-the-box what most people would expect it to do. 🙂

7
  • Thanks, I thought I'd try your plugin. I installed with antigen (first plugin manager I duckduckgo'ed) sourcing from the default ohmyzsh repo and it doesn't do anything; I installed from marlonrichert/zsh-autocomplete and I get floods of ` _autocomplete.async-list-choices:30: command not found: zpty` every key press! (pls say if this is not the right place to have this conversation) Jun 3, 2020 at 8:30
  • Ah, adding antigen bundle mafredri/zsh-async before antigen bundle marlonrichert/zsh-autocomplete fixes that. (but the ohmyzsh bundled one did nothing with or without that). Jun 3, 2020 at 8:35
  • Re "I installed with antigen (first plugin manager I duckduckgo'ed)": If you're not committed to any plugin manager yet, I would highly recommend using Zinit: github.com/zdharma/zinit Jun 3, 2020 at 10:45
  • Re "sourcing from the default ohmyzsh repo": zsh-autocomplete is not included with oh-my-zsh. So, I'm not sure what you mean by that? Jun 3, 2020 at 10:48
  • 1
    @artfulrobot I changed the < in my answer to <=, because I noticed there's an edge where the widget would do nothing. Now it should work for all cases. 🙂 Jun 4, 2020 at 8:03
0

With menu completion you do get two, zsh-test/a-xx and zsh-test/b-xx. Quite why the xx is removed I can't tell you. But from what you're saying it seems you trying to get fuzzy matching and there may be easier ways.

'r:|?=**' is quite aggressive. In terms of fuzzyness, it allows extra characters between every single typed character but doesn't handle things like transposed characters. Just 'b:=*' will allow substrings and the _approximate completer handles approximations in a somewhat more controlled manner, preferring matches that require fewer corrections.

3
  • The _approximate completer is really slow. I prefer not to use it. Also, it tends to generate too many false positives. Jun 3, 2020 at 10:53
  • 1
    b:=* does not give you fuzzy matching. It does not let you type bky and get bindkey. r:|?=** does. Jun 3, 2020 at 10:55
  • As I stated, b:=* does substrings - I was suggesting it be combined with _approximate.. You can configure _approximate, a common setup is one error for every 3 characters typed. zstyle -e ':completion:*:approximate:*' max-errors 'reply=( $(( ($#PREFIX+$#SUFFIX)/3 )) numeric )'
    – okapi
    Jun 4, 2020 at 21:22

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .