Sometimes, a new Kakoune user drops by the IRC channel to ask about some equivalent of Vim’s !
command that runs a launches a shell command in the same terminal Vim runs in. Kakoune does not support that, for good reasons*, but it does support reading FIFOs, which in some ways is better - it works more portably, the output isn’t lost when you return to the editor, etc.
In fact, Kakoune already has some plugins that do something similar: the :lint
, :make
and :grep
commands all run a shell command, capture the output into a new buffer, and do some post-processing. Wouldn’t it be neat to have a Kakoune command that runs a shell command and captures the output into a buffer - then those other tools could be built on top? Heck, Kakoune even allows !
in command names — we could add a !
alias for maximum Vim compatibility!
So, I decided to try it. Actually creating the command to capture shell output into a buffer was easy, and it was still fairly simple after adding error-handling and some features like “overriding the buffer name” and “extra commands to run in the new buffer”. Then I started trying to port other plugins to use it.
First up was lint.kak
. It turns out this plugin is deceiving - it looks like it runs a lint command and captures the output into a buffer, but in fact it does a whole bunch of post-processing into a temporary file, and eventually constructs a buffer from the results. I decided to just skip this as Not Worth The Effort.
Next I tried make.kak
. This was straight-forward — I built my command by stripping down :make
, so I just had to put it back together. Along the way, I discovered that :make
does not actually pass $kak_opt_makecmd
directly to the shell, it passes eval "$kak_opt_makecmd"
. This makes sense, because you might want to add options to makecmd
(like make -j2 CXX=clang++
).
Last, I tried grep.kak
, and this is where I got stuck. When you run :grep
, what it actually executes is:
$kak_opt_grepcmd "$@"
…which is to say, the contents of grepcmd
are interpreted by the shell, but the arguments are protected. That makes sense - like :make
, it should be possible add extra default arguments, but the arguments the user adds are probably going to be regexes full of shell-metacharacters and which need to be kept as-is.
My shiny new capture-output command can protect all arguments (if it just uses "$@"
) or it can expand all arguments (if it uses eval "$@"
) but it can’t easily protect some of the arguments, without the caller having to do shell-quoting which is kind of a pain. And so, I set this experiment aside - “unifying” these commands actually creates a lot more code and complexity than it solves, so I’ll leave them alone.
*: For example, it’s really difficult to support that feature for UIs that do not run in a terminal, and if you are in a terminal you can usually just hit <c-z>
and run your command to get the same effect.