From 0be8566dd7b40ebd8d463e948c920accde174679 Mon Sep 17 00:00:00 2001 From: Maxime Coste Date: Wed, 29 May 2013 18:57:46 +0200 Subject: [PATCH] Add interfacing.asciidoc describing how to interact with external programs --- doc/interfacing.asciidoc | 123 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 doc/interfacing.asciidoc diff --git a/doc/interfacing.asciidoc b/doc/interfacing.asciidoc new file mode 100644 index 000000000..5971582e1 --- /dev/null +++ b/doc/interfacing.asciidoc @@ -0,0 +1,123 @@ +Interfacing Kakoune with external programs +========================================== + +In order to interact with the external world, Kakoune uses the shell, mainly +through the +%sh{ ... }+ string type, and it's 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 status line using: + +[source,bash] +---- +:echo %sh{ date } +---- + +For asynchrounous operations, the kakoune unix stream socket can be used. This +is the same socket that kakoune clients connect to. It is available in the ++kak_socket+ environment variable. + +For example, we can echo a message in kakoune in 10 seconds with: + +[source,bash] +---- +:nop %sh{ ( + sleep 10 + echo "eval -client '$kak_client' 'echo sleep ended'" | + socat stdin UNIX-CONNECT:${kak_socket} +) >& /dev/null < /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 + it's +-client+ option to send commands to a specific client. + * For the command to run asynchrounously, we wrap it in a subshell + with parenthesis, redirect it's +std{in,err,out}+ to +/dev/null+, and + run it in background with +&+. Using this pattern, the shell does + not wait for this subshell to finish before quitting. + +Interactive output +------------------ + +It is a frequent interaction mode to run a program and display it's output +in a kakoune buffer. + +The common pattern to do that is to use a fifo buffer: + +[source,bash] +----- +%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 < /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 exemple, most of the time, the echo command will as +well contains + +----- +setb filetype <...> +----- + +and some hooks for this filetype will have been written + +Completion candidates +--------------------- + +Most of the time, filetype specific completion should be provided by +external programs. + +external completions are provided using the +completions+ option, which +have the following format. + +---- +line:column[+len]@timestamp;candidate1;candidate2;... +---- + +the first element of this string list specify where and when this completions +applies, the others are simply completion candidates. + +As a completion program may take some time to compute the candidates, it should +run asynchrounously. In order to do that, the following pattern may be used: + +[source,bash] +----- +# Declare the option which will store the temporary filename +decl str plugin_filename +%sh{ + # ask kakoune to write current buffer to temporary file + filename=$(mktemp -t kak-temp.XXXXXXXX) + echo "setb plugin_filename '$filename' + write '$filename'" +} +# End the %sh{} so that it's 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 an put output in semicolon separated format + 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 "setb -buffer '${kak_bufname}' completions '$completions'" | + socat stdin UNIX-SOCKET:${kak_socket} +) >& /dev/null < /dev/null & } +-----