Interfacing

In order to interact with the external world, Kakoune uses the shell,
mainly through the %sh{ … } string type, and its control socket.

Basic interaction

For synchronous operations, %sh{ … } blocks are easy to use, they
behave similarly to $( … ) shell construct.

For example, one can echo the current time in Kakoune’s status line
using:

:echo %sh{ date }

For asynchronous operations, the Kakoune Unix stream socket can be used.
This is the same socket that Kakoune clients connect to. It is available
through the kak_session environment variable: the socket is

For example, we can echo a message in Kakoune in 10 seconds with:

:nop %sh{ {
    sleep 10
    echo "eval -client '$kak_client' 'echo sleep ended'" |
        kak -p ${kak_session}
} > /dev/null 2>&1 < /dev/null & }
  • The nop command is used so that any eventual output from the
    %sh{ … } is not interpreted by Kakoune

  • When writing to the socket, Kakoune has no way to guess in which
    client’s context the command should be evaluated. A temporary
    context is used, which does not have any user interface, so if we
    want to interact with the user, we need to use the eval command,
    with its -client option to send commands to a specific client.

  • For the command to run asynchronously, we wrap it in a sub shell
    with braces, redirect its std{in,err,out} to /dev/null, and run
    it in background with &. Using this pattern, the shell does not
    wait for this sub shell to finish before quitting.

Interactive output

It is a frequent interaction mode to run a program and display its
output in a Kakoune buffer.

The common pattern to do that is to use a fifo buffer:

evaluate-commands %sh{
     # Create a temporary fifo for communication
     output=$(mktemp -d -t kak-temp-XXXXXXXX)/fifo
     mkfifo ${output}
     # run command detached from the shell
     { run command here > ${output} } > /dev/null 2>&1 < /dev/null &
     # Open the file in Kakoune and add a hook to remove the fifo
     echo "edit! -fifo ${output} *buffer-name*
           hook buffer BufClose .* %{ nop %sh{ rm -r $(dirname ${output})} }"
}

This is a very simple example, most of the time, the echo command will
as well contain

set buffer filetype <...>

and some hooks for this filetype will have been written

Completion candidates

Filetype specific completion should be provided by external programs.

External completions are provided using an option to store completion,
which have the following format.

line.column[+len]@timestamp candidate1|select1|menu1 candidate2|select2|menu2 ...

the first element of this string list specify where and when this
completion applies, the others are a triplet
<completion text>|<select cmd>|<menu text>

The select command is executed whenever this menu item gets selected,
and is usually used to display an item specific documentation with
info -placement menu '<menu item description>'

The menu text is a markup string (see :doc faces markup-strings), so it can contain {face}
directives.

To effectively use that completion option, it should get added to the
completers option.

— set -add buffer completers option=my_option_name —

As a completion program may take some time to compute the candidates, it
should run asynchronously. In order to do that, the following pattern
may be used:

# Declare the option which will store the temporary filename
decl str plugin_filename
# Declare the completion option
decl completions plugin_completions
# Add plugin_completions to completers for files of good filetype
hook global BufSetOption filetype=my_filetype %{
    set -add buffer completers option=plugin_completions
}
evaluate-commands %sh{
    # ask Kakoune to write current buffer to temporary file
    filename=$(mktemp -t kak-temp.XXXXXXXX)
    echo "set buffer plugin_filename '$filename'
          write '$filename'"
}
# End the %sh{} so that its output gets executed by Kakoune.
# Use a nop so that any eventual output of this %sh does not get interpreted.
nop %sh{ { # launch a detached shell
    buffer="${kak_opt_plugin_filename}"
    line="${kak_cursor_line}"
    column="${kak_cursor_column}"
    # run completer program and format the output in a list of completions
    candidates=$(completer $buffer $line $column | completer_filter)
    # remove temporary file
    rm $buffer
    # generate completion option value
    completions="$line.$column@$kak_timestamp $candidates"
    # write to Kakoune socket for the buffer that triggered the completion
    echo "set buffer=${kak_bufname} plugin_completions $completions" |
        kak -p ${kak_session}
} > /dev/null 2>&1 < /dev/null & }
2 Likes

I was getting a shell error until I changed the “run command” part quoted above to be wrapped with parenthesis rather then curly braces:

# run command detached from the shell
     ( run command here > ${output} ) > /dev/null 2>&1 < /dev/null &

Not sure why…but just wanted to note that here for others. Thanks for the great walkthrough @robertmeta - that was so helpful for a first timer trying to integrate some long running processes into my workflows in kakoune!

With curly braces, the command needs to end with a semicolon if on a single line.

Curly braces cause the commands to be grouped and parentheses to run in a subshell.

Thank you!

@robertmeta you probably want to update your example above so shell-newbs like myself don’t get sidetracked by that.

More refactoring @robertmeta and took into account the comments left by @schickm and @alexherbo2 (bullet point three). Cool guys, as always let me know of any improvements that can be made.


Interfacing with external programs

Kakoune interfaces mainly through %sh{ ... } blocks using string types, and
its control socket by invoking the shell.

Basic interaction

Synchronous operations: %sh{ ... } blocks are easy to use, they behave
similarly to $( ... ) shell construct.

For example, one can echo the current time on Kakoune’s status line using:

:echo %sh{ date }

Asynchronous operations: using Kakoune Unix stream socket, clients connect to
this socket via the environment variable kak_session. The socket location is
/tmp/kakoune/${username}/${kak_session}

To illustrate, we can echo a message to Kakoune in 10 seconds with:

:nop %sh{ {
    sleep 10
    echo "eval -client '$kak_client' 'echo sleep ended'" |
        kak -p ${kak_session}
} > /dev/null 2>&1 < /dev/null & }
  • Use the nop command so any output from the %sh{ ... } isn’t interpreted by Kakoune.
  • When writing to the socket, Kakoune uses a temporary context which doesn’t
    have a user interface. So, user interaction is via the eval command, with its
    -client option to send commands to a specific client.
  • First, the command runs asynchronously in a sub shell when wrapping the
    command in parentheses for a single line or braces in a multiline scenario.
    Second, it inherits a file descriptor via redirection through std {in,err,out}
    to /dev/null. Lastly, conclude with the & to run the command as a background
    process. Using this pattern allows for shell termination before sub-shell
    completion.

Interactive output

Kakoune provides buffers for frequent user interaction to display a program’s
output. The fifo buffer is a common pattern to use:

evaluate-commands %sh{
     # Create a temporary fifo for communication
     output=$(mktemp -d -t kak-temp-XXXXXXXX)/fifo
     mkfifo ${output}
     # run command detached from the shell
     ( run command here > ${output} ) > /dev/null 2>&1 < /dev/null &
     # Open the file in Kakoune and add a hook to remove the fifo
     echo "edit! -fifo ${output} *buffer-name*
           hook buffer BufClose .* %{ nop %sh{ rm -r $(dirname ${output})} }"
}

This is a very simple example, frequently the echo command will contain:

set buffer filetype <...>

with hook's written for filetypes:

hook global BufSetOption filetype=(asciidoc|latex|markdown) %{
    set-option buffer formatcmd "par 102f"
    alias buffer fs format-selections
}

Completion candidates

Kakoune will interface with external programs to provide filetype completion.
The option to store completion candidates has the following format:

line.column[+len]@timestamp candidate1|select1|menu1 candidate2|select2|menu2 ...

The first element of this string list specifies where and when this completion
applies, the others are <completion text>|<select cmd>|<menu item>.

Selection of the menu item executes the select command and can display item
documentation with info -placement menu '<menu item description>'. The menu
items text markup string (see :doc faces markup-strings)
can contain {face} directives.

Add completion candidates to the completers option for effective use.

set -add buffer completers option=my_option_name

A completion program may take some time to compute the candidates, it should run
asynchronously. To do that, use the following pattern:

# Declare the option which will store the temporary filename
decl str plugin_filename
# Declare the completion option
decl completions plugin_completions
# Add plugin_completions to completers for files
hook global BufSetOption filetype=my_filetype %{
    set -add buffer completers option=plugin_completions
}
evaluate-commands %sh{
    # ask Kakoune to write current buffer to temporary file
    filename=$(mktemp -t kak-temp.XXXXXXXX)
    echo "set buffer plugin_filename '$filename'
          write '$filename'"
}
# End the %sh{} so that its output gets executed by Kakoune.
# Use a nop so that any eventual output of this %sh does not get interpreted.
nop %sh{ { # launch a detached shell
    buffer="${kak_opt_plugin_filename}"
    line="${kak_cursor_line}"
    column="${kak_cursor_column}"
    # run completer program and format the output in a list of completions
    candidates=$(completer $buffer $line $column | completer_filter)
    # remove temporary file
    rm $buffer
    # generate completion option value
    completions="$line.$column@$kak_timestamp $candidates"
    # write to Kakoune socket for the buffer that triggered the completion
    echo "set buffer=${kak_bufname} plugin_completions $completions" |
        kak -p ${kak_session}
} > /dev/null 2>&1 < /dev/null & }
3 Likes