Home

Awesome

zsh-autoquoter

zsh-autoquoter is a zle widget ("zsh plugin") that will automatically put quotes around arguments to certain commands. So instead of having to decide which type of quotes to suffer through:

$ git commit -m 'we haven'\''t seen the last of this "feature"'
$ git commit -m "we haven't seen the last of this \"feature\""

You can just write English:

$ git commit -m we haven't seen the last of this "feature"

And let zsh-autoquoter do the rest.

And if you use zsh-syntax-highlighting, it'll even highlight these "autoquoted strings" distinctly, so you'll never be surprised when zsh-autoquoter fires.

Check out this blog post for a fancy asciinema demo of zsh-autquoter in action.

Behavior

Configure command prefixes that you want to be autoquoted by setting the ZAQ_PREFIXES or ZAQ_PREFIXES_GREEDY arrays in your ~/.zshrc:

ZAQ_PREFIXES=('git commit -m' 'ssh *')
ZAQ_PREFIXES_GREEDY=()

By default these arrays are empty. You need to opt into autoquote behavior.

ZAQ_PREFIXES and ZAQ_PREFIXES_GREEDY are arrays of shell patterns that will be matched against your input.

Shell patterns are kind of like goofy regular expressions, where * means .* and # means * and ## means +. So while git commit -m would work as a pattern to match a literal prefix, I would suggest this more flexible alternative:

ZAQ_PREFIXES+=('git commit( [^ ]##)# -[^ -]#m')
git commit -a -m hi hello
                 ^^^^^^^^
git commit -am hi hello
               ^^^^^^^^
git commit -m hi hello
              ^^^^^^^^

And while ssh * will "skip one argument" and then quote the rest, you should probably use a pattern that allows flags and triggers after the first positional argument:

ZAQ_PREFIXES+=('ssh( -[^ ]##)# [^ -][^ ]#')
ssh -Y user@host some 'complex' * | remote <"command"
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

ZAQ_PREFIXES_GREEDY patterns will match greedily. Since everything after the matching pattern will be autoquoted, ZAQ_PREFIXES_GREEDY will cause less of your prompt to be autoquoted. You'll probably want to stick with ZAQ_PREFIXES, but greedy patterns can be useful for matching a variable number of flags. For example:

ZAQ_PREFIXES_GREEDY=('foo( -[^ ]##)#')
foo -bar -baz hello world
              ^^^^^^^^^^^

Since # means "zero or more," a non-greedy pattern would always match zero repetitions.

Writing patterns

Wow those are complicated, huh? Yeah. And they're not even very complete: for example, you can't type:

ssh 'some hostname with a space in it' something

Because the pattern doesn't recognize that 'some hostname with a space in it' is a single positional argument. Patterns operate on characters, even though we think about command arguments in terms of words. But parsing those out would be a whole thing.

Note that [^ -][^ ]# at the end of a pattern matches exactly one positional argument because zsh-autoquoter always adds an implicit space to the end of your patterns.

You can debug a pattern using the following expression:

setopt EXTENDED_GLOB
input='ssh user@host some command'
prefix='ssh( -[^ ]##)# [^ -][^ ]#'
print ${input#$~prefix }

Or, for testing greedy patterns:

print ${input##$~prefix }

Which is exactly how zsh-autoquoter is implemented. If that prints the entire input string, your pattern didn't work. If it only prints a subset of it, then that is the subset that will be autoquoted.

See section 14.8.1 Glob Operators for a more thorough description of pattern syntax.

Special characters

zsh-autoquoter runs before your shell has a chance to expand or glob or parse the command you typed, so it works to quote any shell syntax:

$ ssh user@host * globs and | pipes and > redirects and # comments oh my

There is one exception:

Double escaping

zsh-autoquoter won't add quotes if there already are quotes, so you can still type:

$ git commit -m 'i forgot that i installed zaq'

And it will not double-escape anything.

When doing this check, zsh-autoquoter will look for a fully quoted argument string. So you can still write:

$ git commit -m 'twas the night before christmas
$ git commit -m "fix" the latest "bug"

And it will be autoquoted correctly.

I highly recommend enabling syntax highlighting (see below) to make it obvious when zsh-autoquoter is going to fire and when it is not.

History

Commands will be rewritten before they're added to history, so your ~/.zsh_history will reflect an accurate list of commands you ran, not commands you typed. But because zsh-autoquoter ignores already-quoted entries, you can always up-enter and re-run the same command.

Yes, you could imagine wanting it the other way, but I just cannot figure out how to make that work with zsh.

Installation

Manually

Download zsh-autoquoter.zsh and source it from your ~/.zshrc file. Then make sure that you add some prefixes:

source ~/src/zsh-autoquoter/zsh-autoquoter.zsh
ZAQ_PREFIXES=('git commit( [^ ]##)# -[^ -]#m' 'ssh( -[^ ]##)# [^ -][^ ]#')
# If using zsh-syntax-highlighting:
# ZSH_HIGHLIGHT_HIGHLIGHTERS+=(zaq)

Antigen:

$ antigen bundle ianthehenry/zsh-autoquoter

oh-my-zsh:

Clone this repo into your custom plugins directory:

$ git clone https://github.com/ianthehenry/zsh-autoquoter.git ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autoquoter

And then add it to the plugins list in your ~/.zshrc before you source oh-my-zsh:

plugins+=(zsh-autoquoter)
source "$ZSH/oh-my-zsh.sh"

Syntax highlighting

If you use the excellent zsh-syntax-highlighting package, you can enable the zaq highlighter by adding the following line to your ~/.zshrc:

ZSH_HIGHLIGHT_HIGHLIGHTERS+=(zaq)

You can customize how it looks by setting the zaq:string key:

ZSH_HIGHLIGHT_STYLES[zaq:string]="fg=green,underline"

If you don't use zsh-syntax-highlighting, you should check it out. It's great. You'll never typo a command again.

hey why don't you just write better commit messages

Okay yeah look I'm using git commit -m as an example a lot, but I actually wrote zsh-autoquoter so that I could add notes and todo list items from the command line more easily.

Not to say that git commit -m is bad -- it's great if you're using a patch queue workflow. But zsh-autoquoter does not condone vague one-line commit messages.