A failed experiment: unifying lint.kak, grep.kak, make.kak

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.

3 Likes

If it’s just about a “capture-output” command I really like :fifo from connect.kak. But I am not sure if one can use that to unify lint.kak, grep.kak and make.kak.


Unifying grep.kak and make.kak with a common underlying fifo.kak is one of the first thing I did when we landed the module system, and I did hit issues, not sure if I identified the same you did but I remember coming out of that experiment thinking that the first issue is how easy it is to implement :grep and :make in the first place, they both take around 10 lines. It is really hard to unify those two with a solution that permit the slight difference in behaviour we want whilst preserving the total complexity.

In other words, it is very hard to get
common fifo complexity + new make complexity + new grep complexity < old make complexity + old grep complexity because the right hand side of that comparison is already very low complexity.

6 Likes