BufWritePost not working?

Hey guys! I’ve started using Kakoune more seriously and I love it.
I have some quick questions, if someone could help me out it would be awesome.

First, I’m trying to make a (shell script) buffer be linted and formatted when I save it. So I’m using this:

hook global BufWritePost filetype=sh %{
    lint-buffer
    format-buffer
}

But it doesn’t work, when I :write on the buffer, nothing happens. Running :lint-buffer or :format-buffer works, though. Since I’ve configured correctly the options lintcmd and formatcmd.

And by the way, for setting those options (and other similar type of things) for a filetype, how come people, online, use the WinSetOption? It works for me, but I’m not sure why. The documentation says:

an option was set in a window context.

What does this exactly mean? For me it would make more sense to use something like BufCreate, but it doesn’t work for that, apparently.

My Kakoune version is v2020.09.01.

Thanks a lot in advance!

About your hook: perhaps it is not configured properly. :doc hooks says that hooks accept a filtering regex that must match your entire parameter string. In the case of BufWritePost, the regex must match your whole filename. Try rewriting your hook command as:

hook global BufWritePost .*[.]sh %{
    lint-buffer
    format-buffer
}

and please tell us whether things get better.

1 Like

There’s two concerns here:

When a buffer is created, Kakoune guesses what the filetype might be, but its guess isn’t always correct. If the user sets the filetype option after the buffer is created, the highlighting and other settings should update to match, so the convention is to hook *SetOption rather than BufCreate.

Kakoune supports the same buffer being displayed in multiple windows simultaneously. It’s conceivable that somebody might want different filetype options (generally syntax highlighting, but who knows) in different windows, so the convention is to hook WinSetOption rather than BufSetOption.

Thanks a lot for the answer!
Your solution does indeed work. I hadn’t noticed that in :doc hooks, for BufWritePost, the argument is filename instead of <option_name>=<new_value>. Although it’s kind of lacking, since in a lot of places, shell scripts don’t have a filename extension. And some tools like shellcheck, more intelligently, rely on the shebang for determining the syntax that the file needs (e.g. posix, bash, etc.) instead of relying on the filename extension. But I guess I can work with this for now.

Thanks a lot for the explanation, makes sense. Now I understand it!

You can hook global BufOpenFile and check for shebangs using something like

exec -draft gk<a-x>s#!/bin/(ba|z)?sh<ret>
set window filetype sh

And use try blocks if you want to include other filetypes, eg python.

How do you set lintcmd and formatcmd options? From a WinSetOption filetype=sh hook, right? So instead of declaring a global BufWritePost hook for shell files, you can do this:

hook global WinSetOption filetype=sh %{
    set-option buffer lintcmd 'your-lint-cmd'
    set-option buffer formatcmd 'your-format-cmd'
    hook buffer BufWritePost .* %{
        lint-buffer
        format-buffer
    }
}

Now the hook is only installed when you already know it’s a shell file, so you don’t need to match anything in the filename.

As an additional hint, you might want to use BufWritePre instead of BufWritePost, at least for formatting: right now, you’re writing the unformatted version of the buffer, then formatting it, potentially introducing changes you’ll have to write again. If you use BufWritePre, the formatting happens before the write, so the formatted version gets written out.

Also, you might recall I talked about using window scope instead of buffer scope so you can have multiple windows with different filetypes, but in my example above I’m using buffer scope anyway. This is because the Buf* hooks always execute in buffer scope, so hooking BufWritePost at window scope will never trigger. Since we’re running lint-buffer and format-buffer in buffer scope, the lintcmd and formatcmd options must also be set at buffer scope so the commands can see them.

It is possible to automatically clean up these options and hooks when the filetype option changes value, but that’s a bit more complex and you can figure it out from reading the file-format plugins in the Kakoune standard library.

1 Like