Pipe `buffer-next` to kakoune session

Apparently one can send commands to a running kakoune session with commands | kak -p. So far I’ve managed to send most of the commands. For some reason I cannot change buffers though. For example I try piping the buffer-next command like so:

echo 'buffer-next' | kak -p <session_name>

But this produces the error

error running command 'buffer-next
': 1:1: 'buffer-next' no buffer in context

What is meant here by “context” and how I make the buffer-next example work?

If you start up Kakoune, you can run :buffer-next and it will switch buffers.

If you run :new, you get a new client running in a new window, and if you run :buffer-next there, the first window doesn’t switch buffers - :buffer-next must be run in the context of a particular client, and it only affects that client.

Likewise, you can run :eval -draft %{ buffer-next }, and nothing happens. The documentation for :eval says that the -draft option “runs the commands in a draft context”; Kakoune creates a new context, :buffer-next switches which buffer that context acts on, and then the context is immediately discarded, with no affect on the current client.

Unfortunately, there’s no official list of what contexts are available, or what actions are possible in which contexts. If you look at :doc expansions value-expansions you’ll see that most of the expansions are available globally (like %val{version}, the currently-running version of Kakoune), some are available for buffers and windows (like %val{buffile}, the full path of the file being edited), some are only available in windows (like %val{selections}, the currently-selected text), and some are even more restricted (like %val{count} which is only available in the right-hand side of a mapping). Those aren’t the only contexts available, but those are the kinds of things contexts can be.

To answer your question, I suspect buffer-next doesn’t work for kak -p because kak -p is running commands in a global context, much like kakrc. There’s no way for kak -p to display a buffer, so it can’t switch which buffer is being displayed. To make it work, you probably want to do something like:

echo 'eval -client SomeClient %{ buffer-next }' | kak -p <session_name>

…where SomeClient is the name of the client which should switch buffers. You can check the current name of a client with echo %val{client} (run in the context of the client in question, of course), or if you want something more deterministic, you can use :rename-client to set it however you like.

3 Likes

Thanks for your detailed explanation. The kak -p makes sense now if one keeps the global context in mind. I was also wondering why doing

echo 'edit <some_file>' | kak -p <session_name>

would open the file in the kakoune session but wouldn’t actually change the buffer to that file. I see now that given the global context of kak -p this behaviour is consistent, and doing

 echo 'eval -client <client_name> %{ edit <some_file> }' | kak -p <session_name>

works as expected and open and switches the buffer to that file.

Thank you!

@Screwtapello How to deal with escaping with blocks using the prelude?

I’m not sure what you mean.

kak_escape evaluate-commands -client "$kak_client" '%{ ... }' | kak -p "$kak_session"

How to escape the %{ ... } block, if we want to pass shell variables, etc.?

(sorry it took me a while to respond, I’ve been stressed recently)

%{ ... } is really just a kind of quoting, although it’s more pleasant to write than more traditional backslash-escaped quoting or even Kakoune’s native quote-doubling quoting. If you have a particular command you want to pass to eval, you can just use it as-is and kak_escape will quote it for you:

kak_escape eval -client "$kak_client" "info hello world" |
    kak -p "$kak_session"

If you want to build a command to pass to eval, and some of the parts of that command may need quoting… well, you just need to nest the quoting:

kak_escape eval -client "$kak_client" "$(kak_escape info You are editing "$kak_buffile")" |
    kak -p "$kak_session"

To summarise, to make shell code that produces the Kakoune code you expect:

  • Put kak_escape (or your quoting helper of choice) in front of the Kakoune command
  • Instead of writing %{ ... } to create a nested string, write "$(kak_escape ... )"

To use markolenik’s original request as an example, we start with this Kakone code:

eval -client CLIENT_NAME %{ edit SOME_FILE }

To construct that command from the shell, we prefix it with kak_escape:

kak_escape eval -client CLIENT_NAME %{ edit SOME_FILE }

…then we replace %{ ... } with "$(kak_escape ... )":

kak_escape eval -client CLIENT_NAME "$(kak_escape edit SOME_FILE )"

If we replace those placeholders with actual shell-variables (following the standard rules of shell-quoting):

kak_escape eval -client "$kak_client" "$(kak_escape edit "$some_file" )"

…we get a command we can pipe straight to kak -p and have it work reliably.

2 Likes

Don’t we lose the speed by using subshells?

The example I gave is straightforward, but the general problem is more complex: we build up a tree structure, where different parts of the tree have different levels of nesting, and then we flatten it by repeatedly escaping the innermost parts. There’s a couple of ways we could go about this.

We could write an escaping tool that can quote text multiple times in a single pass, then concatenate text quoted at different levels, maybe something like:

(
    kak_escape_once eval -client "$kak_client"
    kak_escape_twice edit "$some_file"
) | kak -p "$kak_session"

…but that would make the code quite difficult to maintain.

Alternatively, we could write an escaping tool that takes, say, an S-expression and the values we want to slot in, and builds up a tree and serialises it with all the required quoting in-place. That would be quite some work, though, and I wouldn’t like to try it in pure POSIX shell.

In practice, we want to represent a tree structure, and the simplest tree structure available to the shell is a process tree, so that’s what I went with.

Cannot it be solved by evaluate-commands forwarding its arguments the same way as sh?

Example – Kakoune:

kak_escape evaluate-commands '
  echo %arg{1}
' -- Tchou

Example – sh:

sh -c '
  echo "$1"
' -- Tchou

@mawww What do you think of this?

I don’t think that would help. The string passed to eval might contain deeply-nested commands, and if %arg{1} was used at the top level and %arg{2} was used three levels deep, how would eval know how many times each should be escaped? eval by design only ever processes a single level.

We can deep the evaluate-commands to pass the values, no?