# How to Run Terminal Command on the Active File on Write?

**URL:** <https://discuss.kakoune.com/t/how-to-run-terminal-command-on-the-active-file-on-write/2298>\
**Category:** Help\
**Tags:** language\
**Created:** [September 29, 2023, 8:49pm UTC](https://discuss.kakoune.com/t/how-to-run-terminal-command-on-the-active-file-on-write/2298 "2023-09-29T20:49:39Z")\
**Posts on this page:** 10\
**Page:** 1

<div class="post-metadata">

**Author:** ![samtcifihi](https://discuss.kakoune.com/letter_avatar_proxy/v4/letter/s/e99b99/32.png) [@samtcifihi](https://discuss.kakoune.com/u/samtcifihi)\
**Post date:** [September 29, 2023, 8:49pm UTC](https://discuss.kakoune.com/t/how-to-run-terminal-command-on-the-active-file-on-write/2298/1 "2023-09-29T20:49:39Z")

</div>

I want Kakoune to run `go fmt this-file` when I write to a file ending in `.go`.

I’m currently trying this:

```auto
hook global BufWritePre .*\.go %{
	evaluate-commands %sh{ go fmt $kak_buffile }
}

```

but getting this in `:buffer *debug*` when I do `:w` (I’m testing it on `./src/main.go`):

 ![image](https://discuss.kakoune.com/uploads/default/original/2X/c/cc6d89991187633a7fb276e5282390fcea686c76.png)

Things I’m unsure on:

1. Would `buffer` scope be more appropriate? It seems more fitting, but I’ve only been using `global` scope in my kakrc so far.
2. Should I use `evaluate-commands`? I’m not sure what it’s doing that the `%sh` isn’t.
3. Should I use `$kak_buffile` or `%val{buffile}`? I was using the second, but switched to the first, but I’m not sure on the difference.
4. If none of that is the problem, what is the problem?

---

<div class="post-metadata">

**Author:** ![Screwtapello](https://discuss.kakoune.com/user_avatar/discuss.kakoune.com/screwtapello/32/12_2.png) [@Screwtapello](https://discuss.kakoune.com/u/Screwtapello)\
**Post date:** [September 30, 2023, 1:53am UTC](https://discuss.kakoune.com/t/how-to-run-terminal-command-on-the-active-file-on-write/2298/2 "2023-09-30T01:53:13Z")

</div>

> Would `buffer` scope be more appropriate?

The conventional approach is to hook `BufSetOption filetype=go`, and set your hooks and options at buffer-scope within that hook. That way, you get Go-like behaviour whenever you edit a file that is Go-like, not just specifically `.go` files.

> Should I use `evaluate-commands`? I’m not sure what it’s doing that the `%sh` isn’t.

Probably not. What’s going on here is:

- when the hook fires, its body is evaluated
- when the body is evaluated, Kakoune sees a `%sh{}` block, runs the content of that block as a shell-script, and replaces the `%sh{}` block with the output of that script
- apparently, that command prints `src/main.go` possibly followed by some other text
- `evaluate-commands` tries to evaluate its arguments (`src/main.go ...`) as a Kakoune command, but that _isn’t_ a valid Kakoune command, it’s a filename, so it prints an error

A better choice here might be `nop %sh{ go fmt $kak_buffile }` since `nop` is a command that ignores its arguments and does nothing - you don’t want it to do anything, you just want the side-effect of running a shell-script.

> Should I use `$kak_buffile` or `%val{buffile}`?

Generally, you use `$kak_buffile` inside a `%sh{}` block, and `%val{buffile}` outside one. There’s more specific rules (for example, if you’re using a shell-script to build up a command that will be evaluated _outside_ the shell-script, such as by `evaluate-commands` or `kak -p`) but those don’t apply here.

Also, when you use `$kak_buffile` (or any other shell variable) it’s a good idea to wrap it in double-quotes so that unexpected space characters don’t make things go haywire.

There’s one more problem with your solution: you’re running `go fmt` on the file on disk, _before_ Kakoune writes out the content of the buffer. So whatever work `go fmt` does, will be immediately overwritten.

All that aside, though, Kakoune comes with [a format plugin](https://github.com/mawww/kakoune/blob/master/rc/tools/format.kak) that can be used to run a formatter on the current buffer.

Gluing together my personal configuration for Python files, and the wiki’s [suggestion for formatting Go](https://github.com/mawww/kakoune/wiki/Format#Go), I’d recommend something like:

```auto
# When we edit a Go-like file,
# either auto-detected
# or because the user overrode the filetype manually....
hook global BufSetOption filetype=go %{
    # Tell the format plugin
    # to use "gofmt" to format this buffer
    set-option buffer formatcmd "gofmt"
    # Automatically format the buffer
    # before writing it to disk
    hook buffer -group go-auto-format BufWritePre .* format
    # If, after we've set these options and hooks,
    # the user overrides the filetype to something else...
    hook buffer BufSetOption filetype=.* %{
        # remove the auto-format hook,
        # since it probably won't be
        # appropriate for the new filetype
        remove-hooks buffer go-auto-format
        # We leave the formatcmd option alone,
        # since the user might have manually overridden it
        # and we don't have anything better to set it to.
    }
}

```

---

<div class="post-metadata">

**Author:** ![samtcifihi](https://discuss.kakoune.com/letter_avatar_proxy/v4/letter/s/e99b99/32.png) [@samtcifihi](https://discuss.kakoune.com/u/samtcifihi)\
**Post date:** [October 1, 2023, 12:17am UTC](https://discuss.kakoune.com/t/how-to-run-terminal-command-on-the-active-file-on-write/2298/3 "2023-10-01T00:17:18Z")

</div>

> [@Screwtapello](#):
>
> The conventional approach is to hook `BufSetOption filetype=go`, and set your hooks and options at buffer-scope within that hook. That way, you get Go-like behaviour whenever you edit a file that is Go-like, not just specifically `.go` files.

Cool; I’ll go with that, then.

> [@Screwtapello](#):
>
> when the body is evaluated, Kakoune sees a `%sh{}` block, runs the content of that block as a shell-script, and replaces the `%sh{}` block with the output of that script

This is helpful.

> [@Screwtapello](#):
>
> - `evaluate-commands` tries to evaluate its arguments (`src/main.go ...`) as a Kakoune command, but that _isn’t_ a valid Kakoune command, it’s a filename, so it prints an error

Ah, for some reason I was under the mistaken impression that it was a terminal command, not a Kakoune command. Now that error makes more sense.

> [@Screwtapello](#):
>
> A better choice here might be `nop %sh{ go fmt $kak_buffile }` since `nop` is a command that ignores its arguments and does nothing - you don’t want it to do anything, you just want the side-effect of running a shell-script.

That makes sense; I wasn’t sure exactly when and where to use `nop`, but it makes a lot more sense now in conjunction with what `%sh{}` and `evaluate-commands` are doing.

> [@Screwtapello](#):
>
> Generally, you use `$kak_buffile` inside a `%sh{}` block, and `%val{buffile}` outside one.

Cool; I wasn’t sure if it was that or if one was deprecated or something.

> [@Screwtapello](#):
>
> Also, when you use `$kak_buffile` (or any other shell variable) it’s a good idea to wrap it in double-quotes so that unexpected space characters don’t make things go haywire.

Good to know; I’m not used to variables being good things to surround in `"`, lol.

> [@Screwtapello](#):
>
> There’s one more problem with your solution: you’re running `go fmt` on the file on disk, _before_ Kakoune writes out the content of the buffer. So whatever work `go fmt` does, will be immediately overwritten.

Good catch; that was a total blind spot. I must have been conflating the buffer and the disk in my mind.

> [@Screwtapello](#):
>
> All that aside, though, Kakoune comes with [a format plugin](https://github.com/mawww/kakoune/blob/master/rc/tools/format.kak) that can be used to run a formatter on the current buffer.

Alright; looks like it’s time for me to install the plugin manager (Is [GitHub - andreyorst/plug.kak: Plugin manager for Kakoune](https://github.com/andreyorst/plug.kak) the one everyone uses?).

> [@Screwtapello](#):
>
> `hook buffer -group go-auto-format BufWritePre .* format`

Why does formatting before writing work here? Is it because the format plugin is giving the formatting tool the buffer to edit instead of the file on disk? At least conceptually.

> [@Screwtapello](#):
>
> `go-auto-format`

Is this just an arbitrary name to reference below?

PS: The above post is super thorough; thanks for taking the time to put it together.

---

<div class="post-metadata">

**Author:** ![Screwtapello](https://discuss.kakoune.com/user_avatar/discuss.kakoune.com/screwtapello/32/12_2.png) [@Screwtapello](https://discuss.kakoune.com/u/Screwtapello)\
**Post date:** [October 1, 2023, 3:10am UTC](https://discuss.kakoune.com/t/how-to-run-terminal-command-on-the-active-file-on-write/2298/4 "2023-10-01T03:10:07Z")

</div>

> [@samtcifihi](#):
>
> Alright; looks like it’s time for me to install the plugin manager

To be clear, `format.kak` is part of Kakoune’s standard library, you don’t need to install it separately. Also, a plugin manager is mostly just automation for `git clone` into the autoload directory; some people find that really valuable, but if you’re happy to run `git clone` and `git pull` yourself you don’t really need a plugin manager.

> [@samtcifihi](#):
>
> Is it because the format plugin is giving the formatting tool the buffer to edit instead of the file on disk?

Yes, it pipes the buffer through the formatting tool, just before Kakoune writes the buffer to disk.

> [@samtcifihi](#):
>
> Is this just an arbitrary name to reference below?

Yes.

> [@samtcifihi](#):
>
> PS: The above post is super thorough; thanks for taking the time to put it together.

Glad to help!

---

<div class="post-metadata">

**Author:** ![Nasmekva](https://discuss.kakoune.com/user_avatar/discuss.kakoune.com/nasmekva/32/1238_2.png) [@Nasmekva](https://discuss.kakoune.com/u/Nasmekva)\
**Post date:** [October 1, 2023, 11:10pm UTC](https://discuss.kakoune.com/t/how-to-run-terminal-command-on-the-active-file-on-write/2298/5 "2023-10-01T23:10:06Z")

</div>

Hi @Screwtapello !

> Yes, it pipes the buffer through the formatting tool, just before Kakoune writes the buffer to disk.

That’s the issue with go format tool and why your snippet won’t work.  
`gofmt/gofumpt` do not use stdin as their format source, but need a filepath 🙁

To make those work with kakoune `:format` command, I created a small script that acts just like `xargs` but for files.

```bash
usage() { printf 'usage: fargs [-h/--help] [-q/--quiet] CMD...\n'; exit 1; }

file=$(mktemp fargs.tmp.XXXX)
trap 'rm "$file"' EXIT

case "$1" in
    -h|--help) usage ;;
    -q|--quiet) shift; exec 2>/dev/null ;;
esac

[$# -le 0] && usage

cat - > "$file"
"$@" "$file"
cat "$file"

```

I then used this script in my `formatcmd` for go and python 😊

```kak
# Need fargs to work
hook -group languagecmd global WinSetOption filetype=go %{
    set-option buffer formatcmd 'fargs gofumpt -w'
}

hook -group languagecmd global WinSetOption filetype=python %{
    set-option buffer formatcmd 'fargs black --line-length 150'
}

```

* * *

@samtcifihi, you can use the above solution, or some similar script, to format go code using the standard kakoune `:format` command.  
As @Screwtapello and I mentioned previously, kakoune **PIPES** the whole buffer to `formatcmd`, it does not append the buffer path as an argument (whereas `lintcmd` does receive the buffer name as an argument).

* * *

You can always create a small custom command just for go, which will pass the current buffer filepath to `gofmt/gofumpt` if the external script is bothersome.

```auto
define-command -params 0 -docstring "Format go code, passing the buffer filepath to gofmt" \
    go-format nop %sh{ gofmt "$kak_buffile" }

```

---

<div class="post-metadata">

**Author:** ![Screwtapello](https://discuss.kakoune.com/user_avatar/discuss.kakoune.com/screwtapello/32/12_2.png) [@Screwtapello](https://discuss.kakoune.com/u/Screwtapello)\
**Post date:** [October 2, 2023, 3:53am UTC](https://discuss.kakoune.com/t/how-to-run-terminal-command-on-the-active-file-on-write/2298/6 "2023-10-02T03:53:36Z")

</div>

That seems like a very useful script, and I’m sure it comes in handy. However, although I’m not a Go programmer myself, but [this `gofmt` documentation I found](https://pkg.go.dev/cmd/gofmt) says:

> Without an explicit path, it processes the standard input.

`gofumpt` doesn’t seem to have corresponding documentation, but it says it was forked from `gofmt` and is intended as a drop-in replacement, so I assume it probably behaves similarly.

Meanwhile, the Python `black` formatting tool _does_ require a path, but you can give it `-` to mean “read from stdin” and it works quite nicely as a Kakoune `formatcmd`.

---

<div class="post-metadata">

**Author:** ![Nasmekva](https://discuss.kakoune.com/user_avatar/discuss.kakoune.com/nasmekva/32/1238_2.png) [@Nasmekva](https://discuss.kakoune.com/u/Nasmekva)\
**Post date:** [October 2, 2023, 6:36am UTC](https://discuss.kakoune.com/t/how-to-run-terminal-command-on-the-active-file-on-write/2298/7 "2023-10-02T06:36:24Z")

</div>

@Screwtapello my bad… Don’t know where I got the idea stdin wasn’t supported, but I’m clearly wrong here. Thanks for correcting me !

---

<div class="post-metadata">

**Author:** ![samtcifihi](https://discuss.kakoune.com/letter_avatar_proxy/v4/letter/s/e99b99/32.png) [@samtcifihi](https://discuss.kakoune.com/u/samtcifihi)\
**Post date:** [October 6, 2023, 1:14pm UTC](https://discuss.kakoune.com/t/how-to-run-terminal-command-on-the-active-file-on-write/2298/8 "2023-10-06T13:14:48Z")

</div>

> [@Screwtapello](#):
>
> To be clear, `format.kak` is part of Kakoune’s standard library, you don’t need to install it separately. Also, a plugin manager is mostly just automation for `git clone` into the autoload directory; some people find that really valuable, but if you’re happy to run `git clone` and `git pull` yourself you don’t really need a plugin manager.

Ah; thanks.

> [@Screwtapello](#):
>
> Yes, it pipes the buffer through the formatting tool, just before Kakoune writes the buffer to disk.

Cool.

> [@Screwtapello](#):
>
> ```auto
> # If, after we've set these options and hooks,
> # the user overrides the filetype to something else...
> hook buffer BufSetOption filetype=.* %{
> # remove the auto-format hook,
> # since it probably won't be
> # appropriate for the new filetype
> remove-hooks buffer go-auto-format
> # We leave the formatcmd option alone,
> # since the user might have manually overridden it
> # and we don't have anything better to set it to.
> }
> 
> ```

Is this right? It works when I comment it out, but otherwise it doesn’t do anything. Either way; thanks for the help; this is way more convenient than running `gofmt` manually all the time.

---

<div class="post-metadata">

**Author:** ![samtcifihi](https://discuss.kakoune.com/letter_avatar_proxy/v4/letter/s/e99b99/32.png) [@samtcifihi](https://discuss.kakoune.com/u/samtcifihi)\
**Post date:** [October 6, 2023, 1:17pm UTC](https://discuss.kakoune.com/t/how-to-run-terminal-command-on-the-active-file-on-write/2298/9 "2023-10-06T13:17:25Z")

</div>

I’ll keep this post in mind if I run into trouble in the future using Screwtapello’s solution for other languages.

---

<div class="post-metadata">

**Author:** ![Screwtapello](https://discuss.kakoune.com/user_avatar/discuss.kakoune.com/screwtapello/32/12_2.png) [@Screwtapello](https://discuss.kakoune.com/u/Screwtapello)\
**Post date:** [October 7, 2023, 5:02am UTC](https://discuss.kakoune.com/t/how-to-run-terminal-command-on-the-active-file-on-write/2298/10 "2023-10-07T05:02:19Z")

</div>

> [@samtcifihi](#):
>
> Is this right? It works when I comment it out, but otherwise it doesn’t do anything.

Go may require all its source files to have the `.go` file extension, but it’s possible to have other filetypes with that extension, like a shell-script called `give.me.a.fair.go` or whatever. If you open such a file in Kakoune, it may be autodetected as a Go file (and so the autoformat hook will be installed) even though it’s not appropriate.

If that happens, you should be able to say `:set buffer filetype=sh` (or whatever the real filetype is) and then the autoformat hook will automatically be removed, since you wouldn’t get anything useful trying to pass a shell-script through `gofmt`.

That is to say, you won’t see that code do anything in normal operation, it’s just there to cover an unusual corner-case.
