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
: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
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:
…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.