65

I am using zsh with oh-my-zsh's rc file and there is some behavior I find particularly annoying. By default, oh-my-zsh is configured to return case-insensitive matches when auto-completing. This behavior is sometimes good, but other times it really sucks. Is there a way I can configure zsh to only use case-insenstive matching when there are no case-sensitive matches?

For instance, this case would use case-sensitive matching:

> ls
LICENSE.txt    lib/
> emacs l <-- should autocomplete to lib/

In this case, case-insensitive auto-completion would happen:

> ls
README    lib/
> emacs r <-- should autocomplete to README

Thanks!

Max
  • 15,157
  • 17
  • 82
  • 127

3 Answers3

60

Create a file ~/.oh-my-zsh/custom/better-completion.zsh (assuming you are using default paths for oh-my-zsh) with the following lines

zstyle ':completion:*' matcher-list '' 'm:{a-zA-Z}={A-Za-z}' 'r:|[._-]=* r:|=*' 'l:|=* r:|=*'

Explanation:

Rules for matches in zsh completion in general are defined in the matcher-list style. For oh-my-zsh this is defined in ~/.oh-my-zsh/lib/completion.zsh (once for case-sensitive and once for case-insensitive). You could change it there but it would probably be gone if you updated your oh-my-zsh. ~/.oh-my-zsh/custom is specifially intended for customization and files with extension .zsh are loaded from there by .oh-my-zsh/oh-my-zsh.sh at the end of the configuration.

The default (case-insensitive) settings for matcher-list in oh-my-zsh are:

zstyle ':completion:*' matcher-list 'm:{a-zA-Z}={A-Za-z}' 'r:|[._-]=* r:|=*' 'l:|=* r:|=*'

The first of which tells to handle upper and lower case interchangeable. As it is the first rule, it will be invariably used for every match.

The only change needed is to prepend '' for simple completion (it is even the first example in zshcompsys(1) for matcher-list)

zstyle ':completion:*' matcher-list '' 'm:{a-zA-Z}={A-Za-z}' 'r:|[._-]=* r:|=*' 'l:|=* r:|=*'

This tries first to complete the current word exactly as its written, before trying case-insensitive or other matches.

To be complete:

  • The second (original) rule allows for partial completion before ., _ or -, e.g. f.b -> foo.bar.
  • The third rule allows for completing on the left side of the written text, e.g. bar -> foobar)
Adaephon
  • 16,929
  • 1
  • 54
  • 71
  • 2
    You also have to set CASE_SENSITIVE to true I assume? – Didier A. Jan 26 '15 at 17:30
  • Thanks @didibus. I removed the `if`-clause, so the setting of `CASE_SENSITIVE` does not matter anymore. I am not sure what I was thinking back then. *oh-my-zsh*'s own behaviour depends on `CASE_SENSITIVE`, but as we want to change it either way, it does not make sense to even check for the original setting. – Adaephon Jan 27 '15 at 06:45
  • 1
    You forgot `matcher-list` after `':completion:*'`, didn't you? – neoascetic Dec 17 '16 at 10:33
  • @neoascetic Yes, I did. Thank you, I have fixed it now. – Adaephon Dec 17 '16 at 10:56
  • Any reason `r:|=*'` is repeated? – Tom Hale Jul 29 '18 at 06:36
  • @TomHale It is not actually repeated on its own, each one is just a part of one of the matching rules, `r:|[._-]=* r:|=*` and `l:|=* r:|=*`. The single quote character `'` is not a part of the rule but is needed in order to quote the special (to ZSH) characters in the rule string, like `|`, `*` and of course the space. – Adaephon Jul 29 '18 at 08:19
  • how do we know whether our zsh is oh-my-zsh or plain zsh? – inherithandle Nov 22 '18 at 14:48
  • 1
    @inherithandle As long as your `~/.zshrc` does not source a `oh-my-zsh.sh` (usually placed at `~/.oh-my-zsh/oh-my-zsh.sh`) you are most likely not using *Oh My Zsh*. Of course, this does not necessarily mean, that you are using "plain" zsh, as there are other frameworks around. In most cases I would say that you are not using a *Oh My Zsh* (or another framework) unless you installed it yourself. If you forgot or if it is not your own machine, the only way to be absolutely sure would be to check any and all zsh configuration files (in your `$HOME` as well as in `/etc` or `/etc/zsh`). – Adaephon Nov 26 '18 at 14:41
  • @DidierA., yes you do. At least for me it did _not_ work with only creating the `better-completioin.zsh` file. I also had to uncomment the `CASE_SENSITIVE=true` statement in `~/.zshrc` – Milkncookiez Jul 18 '19 at 11:59
  • `zstyle ':completion:*' matcher-list '' '+m:{a-zA-Z}={A-Za-z}' '+r:|[._-]=* r:|=*' '+l:|=* r:|=*'` is better and works with more edge cases. – HappyFace Jul 21 '20 at 08:29
59

Just uncomment the following line in ~/.zshrc:

# Uncomment the following line to use case-sensitive completion.
# CASE_SENSITIVE="true"

It worked for me

Jesus Garcia
  • 646
  • 6
  • 2
  • 14
    This only works with Oh My Zsh. https://github.com/robbyrussell/oh-my-zsh/blob/master/lib/completion.zsh Edit: Oops, it was about Oh My Zsh... But this answer doesn't fully satisfy the needs. – Yous Jan 14 '16 at 13:20
  • This stops case insensitive completion completely. It does not fall back to case insensitive completion when there is no case sensitive match. – maxrzaw Sep 02 '23 at 19:11
40

For those not using oh-my-zsh, you can add the following two lines to ~/.zshrc

zstyle ':completion:*' matcher-list '' 'm:{a-zA-Z}={A-Za-z}' 'r:|=*' 'l:|=* r:|=*'
autoload -Uz compinit && compinit
Ahmad Abdelghany
  • 11,983
  • 5
  • 41
  • 36
  • 1
    Didn't work for me. For instance: `cd d` doesn't even offer me "Dropbox"; it only match lowercase. BUT `cd dr` does do the matches with "Dropbox". Am I missing something? – ranemirusG Apr 23 '23 at 14:41
  • @ranemirusG You might want to add this extra line too `setopt MENU_COMPLETE`. Here is what it means: On an ambiguous completion, instead of listing possibilities or beeping, insert the first match immediately. Then when completion is requested again, remove the first match and insert the second match, etc. When there are no more matches, go back to the first one again. – Ahmad Abdelghany May 30 '23 at 09:28
  • this worked for me on macOS - thanks! was way too difficult to figure out how to configure this – fIwJlxSzApHEZIl Jun 03 '23 at 21:44