rep
rep copied to clipboard
Add REPL buffer to kakoune
I've extended rep to have it write repl I/O to a dedicated *rep*
buffer in kakoune. This is necessary for how our clojure build system works at my job for... reasons. But it also has some advantages, like being able to interact with repl output with the full facilities of kakoune.
I'd be happy to clean this up and submit a PR if you think it's in scope for this project.
To be honest, I have mixed feelings which I've never quite sorted out about adding a REPL buffer: Basically, as common as it is for nice lisp editors to have REPL buffers, the REPL buffer is basically a terminal, and we should already have a terminal.
Granted, this doesn't work as smoothly as I'd like.
I'm curious to see what you currently have though.
Unfortunately after some days using it, I'm noticing some serious deficiencies in my implementation.
I'm currently creating a scratch buffer and writing the output of rep
to it using execute-keys
and some escaping/scrolling hacks. Works fine, but you only get output after rep
actually exits, so anything that takes a long time to evaluation gives no feedback to the user.
My initial approach was using a fifo buffer, but I gave up on that after I realized that apparently I don't completely know what I'm doing with fifos.
This is what I've got though:
declare-option -hidden str rep_buffer false
declare-option -hidden str rep_selection
hook global WinSetOption ^filetype=rep$ %{
add-highlighter window/clojure ref clojure
set-option buffer readonly true
}
define-command -override -hidden rep-find-namespace %{
evaluate-commands -draft %{
set-option buffer rep_namespace ''
# Probably will get messed up if the file starts with comments
# containing parens.
execute-keys 'gkm'
evaluate-commands %sh{
ns=$(rep --port="@.nrepl-port@${kak_buffile-.}" -- "(second '$kak_selection)" 2>/dev/null)
if [ $? -ne 0 ]; then
printf 'fail "could not parse namespace"\n'
else
printf 'set-option buffer rep_namespace %s\n' "$ns"
rep --port="@.nrepl-port@${kak_buffile-.}" -- "(require '$kak_selection)" 1>/dev/null 2>/dev/null
fi
}
}
}
define-command -hidden create-rep-buffer %{
evaluate-commands %sh{
if [ "$kak_opt_rep_buffer" = true ]; then
printf '%s\n' "
evaluate-commands -try-client '$kak_opt_toolsclient' %{
buffer *rep*
}
"
else
printf '%s\n' "
set-option global rep_buffer true
evaluate-commands -try-client '$kak_opt_toolsclient' %{
edit -scratch *rep*
set-option buffer filetype rep
}
"
fi
}
}
define-command \
-params 0.. \
-docstring %{rep-evaluate-selection: Evaluate selected code in REPL and write the result to *rep*.
Switches:
-namespace <ns> Evaluate in <ns>. Default is the current file's ns or user if not found.} \
rep-evaluate-selection-fifo %{
evaluate-commands %{
set-option global rep_evaluate_output ''
set-option global rep_selection %val{selection}
try %{ rep-find-namespace }
evaluate-commands -itersel -draft %{
evaluate-commands %sh{
add_port() {
if [ -n "$kak_buffile" ]; then
rep_command="$rep_command --port=\"@.nrepl-port@$kak_buffile\""
fi
}
add_file_line_and_column() {
anchor="${kak_selection_desc%,*}"
anchor_line="${anchor%.*}"
anchor_column="${anchor#*.}"
cursor="${kak_selection_desc#*,}"
cursor_line="${cursor%.*}"
cursor_column="${cursor#*.}"
if [ $anchor_line -lt $cursor_line ]; then
start="$anchor_line:$anchor_column"
elif [ $anchor_line -eq $cursor_line ] && [ $anchor_column -lt $cursor_column ]; then
start="$anchor_line:$anchor_column"
else
start="$cursor_line:$cursor_column"
fi
rep_command="$rep_command --line=\"$kak_buffile:$start\""
}
add_namespace() {
ns="$kak_opt_rep_namespace"
while [ $# -gt 0 ]; do
case "$1" in
-namespace) shift; ns="$1";;
esac
shift
done
if [ -n "$ns" ]; then
rep_command="$rep_command --namespace=$ns"
fi
}
error_file=$(mktemp)
rep_command='value=$(rep'
add_port
add_file_line_and_column
add_namespace "$@"
pprint="(set! nrepl.middleware.print/*print-fn* clojure.pprint/pprint)"
rep_command="$rep_command"' -- "$pprint $kak_selection" 2>"$error_file" |sed -e "s/'"'"'/'"''"'/g")'
printf '%s\n' "echo -debug %{[rep] $rep_command}"
eval "$rep_command"
error=$(sed "s/'/''/g" <"$error_file")
rm -f "$error_file"
printf "set-option -add global rep_evaluate_output '%s'\n" "$value"
[ -n "$error" ] && printf "set-option -add global rep_evaluate_output '\n%s'\n" "$error"
}
}
create-rep-buffer
evaluate-commands -draft -no-hooks -buffer *rep* %sh{
in="$(printf '%s\n' "$kak_opt_rep_selection" |
sed 's/</<lt>/g' |
sed 's/{/⸨/g' |
sed 's/}/⸩/g'
)"
out="$(printf '%s\n' "$kak_opt_rep_evaluate_output" |
tail -n+2
sed 's/</<lt>/g' |
sed 's/{/⸨/g' |
sed 's/}/⸩/g'
)"
printf 'echo -debug %%{[rep] in=%s}\n' "$in" | kak -p "$kak_session"
printf 'echo -debug %%{[rep] out=%s}\n' "$out" | kak -p "$kak_session"
printf '%s\n' "
set-option buffer readonly false
execute-keys -draft %{gj"\\"o$in<ret>$out<ret><esc>}
try %{ execute-keys -draft '%s⸨<ret>c{<esc>%s⸩<ret>c}<esc>' }
set-option buffer readonly true
"
}
try %{ execute-keys -client %opt{toolsclient} gj }
}
}
map -docstring 'evaluate the selection in the FIFO REPL' global rep e ': rep-evaluate-selection-fifo<ret>'
map -docstring 'evaluate the selection in the echo REPL' global rep E ': rep-evaluate-selection<ret>'
You remind me that when I was first thinking about connecting Kakoune to a REPL, I was thinking of some daemonized, persistent process (to support a buffer). The rep
command being a one-shot was partly a change of thinking, and that makes the REPL buffer thing harder.
However, to get incremental output, you could do something like (following is completely untested):
( rep --print="out,1,printf 'rep-append-text %%∑%n%{out}%n∑' |kak -p $kak_session%n" \
--print="val,1,printf 'rep-append-text %%∑%n%{val}%n∑' |kak -p $kak_session%n" \
--print="err,1,printf ' ... ' | $SHELL ) </dev/null >/dev/null 2>&1 &
The idea is that each incremental response packet is translated into shell for asynchronously updating the buffer. Shell instead of just making Kakoune commands is because I'm pretty sure that kak -p
collects all its input before processing any command, and it would still wait.
EDIT: That clearly has a number of shell quoting issues, if double quotes or single quotes appear in the output. I would be up for adding modifiers to the print formats to shell-quote the result.
Actually, maybe a --shell-interpret
option that, for each reply message, converts the whole packet into correctly quoted shell variables and runs a user-supplied shell function, e.g. "out='foo' err='bar' rep_reply_received". This seems like an obvious way I should have implemented formats in the first place.
My initial approach to getting incremental output working was to create a
temp directory with a normal file (call it rep/in) and a fifo (rep/out) and
spawn a background process tail -f rep/in > rep/out
that lives for as
long as the repl buffer (which is a fifo buffer reading from rep/out)
exists.
Then each invocation of rep would append it's output to rep/in.
However this approach did not work, and I'm not sure why. If it's possible to get working then it is rather convenient, since it doesn't involve any complicated escaping hacks to communicate with kakoune over execute-keys.
On Wed, May 5, 2021, 12:17 Jason Felice @.***> wrote:
Actually, maybe a --shell-interpret option that, for each reply message, converts the whole packet into correctly quoted shell variables and runs a user-supplied shell function, e.g. "out='foo' err='bar' rep_reply_received". This seems like an obvious way I should have implemented formats in the first place.
— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/eraserhd/rep/issues/8#issuecomment-832825186, or unsubscribe https://github.com/notifications/unsubscribe-auth/AABMYDJL57CCBODIGN7FSULTMFVSDANCNFSM433TM4YQ .