What if Kakoune was turned into a Lisp machine?
I modified this code to connect Kakoune to a guile script that sets up a repl on startup. So the basic setup in Kakoune is the following:
declare-option str guile_socket
declare-option -hidden str guile_pid
declare-option -hidden str guile_output_file
eval %sh{
# kak_client
# kak_session
guile_output=$(mktemp)
printf %s%s\\n "set-option global guile_output_file " "$guile_output"
~/kakoune.scm >> $guile_output 2>&1 &
pid=$!
printf %s\\n "set-option global guile_pid '$pid'
hook -once -group guile global KakEnd .* 'guile-stop-repl $pid'"
}
eval %{
edit -fifo %opt{guile_output_file} guile-compile-log
}
eval %{
edit -scratch guile
}
define-command -params 0..1 guile-stop-repl %{
evaluate-commands %sh{
if [[ -z $1 ]] ;then
kill $kak_opt_guile_pid
else
kill $1
fi
rm $kak_opt_guile_socket
printf %s\\n "echo $kak_opt_guile_pid
set-option global guile_pid ''
set-option global guile_socket ''"
}
}
define-command guile-evaluate -params 1 -docstring \
"Evaluates the given string in the context of the current guile session" %{
guile-write-to-buffer %sh{
# kak_client
# Allow us to expand variables like $kak_client.
escaped=$(echo "$1" | sed 's/"/\\"/g')
# Expand and preserve quotes
cmd=$(eval "printf \"%s\" \"$escaped\"")
printf '%s\n' "$cmd" | socat - UNIX-CLIENT:$kak_opt_guile_socket |
tail +9 | sed 's/\$[0-9]* = \(.*\)/\1/g'
}
}
define-command guile-write-to-buffer -params 1 %{
evaluate-commands -buffer guile -save-regs '"' %{
set-register '"' %arg(1)
execute-keys 'gep'
}
}
define-command guile-evaluate-selection %{
guile-evaluate %val{selection}
}
This script invokes a scheme script on startup named ~/kakoune.scm
, what we want now is to create that script and have it start a repl server that we can communicate with. Here is a base to get started
#!/usr/bin/env guile
!#
;; Access variables
(use-modules (srfi srfi-98))
;; Pipes
(use-modules (ice-9 popen))
(use-modules (ice-9 ports))
(use-modules (ice-9 rdelim))
(use-modules (srfi srfi-28))
(use-modules (srfi srfi-98))
(use-modules (ice-9 threads))
(use-modules (system repl server))
(define kak-session
(getenv "kak_session"))
(define (send-to-kakoune msg)
(let* ((port (open-pipe (string-append "kak -p " kak-session) OPEN_WRITE)))
(display (format "msg: ~a" msg))
(display msg port)
(newline port)
(close-port port)))
(define (echo-debug str)
(send-to-kakoune (format "echo -debug GUILE: ~a" str))
(newline))
(define (echo str)
(send-to-kakoune (format "echo ~a" str))
(newline))
(define (main)
(define socket-name (tmpnam))
(send-to-kakoune (format "set-option global guile_socket ~a" socket-name))
(echo-debug (format "Running for session ~a" kak-session))
(run-server (make-unix-domain-server-socket #:path socket-name)))
(main)
You’ll need to chmod +x
this script to be able to execute it. The script stores the kak session so the repl is able to communicate back with Kakoune. send-to-kakoune
is the basic function we can use, for example:
(send-to-kakoune "execute-keys -client $kak_client ghihello")
When evaluated will prepend hello to the string, $kak_client gets expanded with the escaping we do earlier.
From here, we can set up a DSL to run commands:
(define (kak! . args)
;; Flatten everything into strings and join with spaces
(let ((cmd (string-join (map kakoune-arg->string args) " ")))
(send-to-kakoune cmd)))
(define (kak-block lst)
;; Wraps content in `%{ ... }`
`(%block ,lst))
(define (raw val)
`(%raw ,val))
(define (kakoune-arg->string arg)
(cond
;; #:option => -option
((keyword? arg) (string-append "-" (keyword->string arg)))
;; Block content
((and (pair? arg) (eq? (car arg) '%block))
(string-append "%{ "
(string-join (map kakoune-arg->string (cadr arg)) " ")
" }"))
;; (raw "hello") => hello
((and (pair? arg) (eq? (car arg) '%raw))
(cadr arg))
;; "hello" => "hello"
((string? arg) (string-append "\"" arg "\""))
;; 'echo => "echo"
((symbol? arg) (symbol->string arg))
;; 5 => "5"
((number? arg) (number->string arg))
(else (format #f "~a" arg))))
And now we can define commands directly in guile
(kak! 'echo #:debug "Hello from Guile")
(kak! 'face 'global 'Default (raw "rgb:000000,default"))
(kak! 'define-command
#:override #:params 0
'hello
(kak-block (list 'echo "Hello from Guile!")))
These can then be evaluated with guile-evaluate-selection
.