Hey friends, so I’m attempting to write a plugin that providers a nu %{}
command in Kakoune so one can write Nushell code in their Kakoune scripts. POSIX sh is fine, but sometimes you need something more powerful.
So far, I have the following for the Nushell module:
# knu mod.nu - Main module file for internal Kakoune usage
# This file exports all functions available within nu %{} blocks in Kakoune
module buffer {
# Buffer-related functions for internal Kakoune usage
export def "buffer file" [] {
if ($env.KAK_BUFFILE? | is-empty) { null } else { $env.KAK_BUFFILE }
}
export def "buffer name" [] {
if ($env.KAK_BUFNAME? | is-empty) { null } else { $env.KAK_BUFNAME }
}
export def "buffer line count" [] {
if ($env.KAK_BUF_LINE_COUNT? | is-empty) { 0 } else { $env.KAK_BUF_LINE_COUNT | into int }
}
export def "buffer timestamp" [] {
if ($env.KAK_BUF_TIMESTAMP? | is-empty) { null } else { $env.KAK_BUF_TIMESTAMP }
}
export def "buffer modified-p" [] {
if ($env.KAK_BUF_MODIFIED? | is-empty) { false } else { $env.KAK_BUF_MODIFIED | into bool }
}
}
module client {
# Client-related functions for internal Kakoune usage
export def "client curr" [] {
if ($env.KAK_CLIENT? | is-empty) { null } else { $env.KAK_CLIENT }
}
export def "client pid" [] {
if ($env.KAK_CLIENT_PID? | is-empty) { 0 } else { $env.KAK_CLIENT_PID | into int }
}
export def "client name" [] {
if ($env.KAK_CLIENT_NAME? | is-empty) { null } else { $env.KAK_CLIENT_NAME }
}
export def "client session" [] {
if ($env.KAK_SESSION? | is-empty) { null } else { $env.KAK_SESSION }
}
export def "client command-fifo" [] {
if ($env.KAK_COMMAND_FIFO? | is-empty) { null } else { $env.KAK_COMMAND_FIFO }
}
}
module selections {
export def "selections curr" [] {
if ($env.KAK_SELECTIONS? | is-empty) { [] } else { $env.KAK_SELECTIONS | lines }
}
export def "selections quoted" [] {
if ($env.KAK_QUOTED_SELECTIONS? | is-empty) { [] } else { $env.KAK_QUOTED_SELECTIONS | split row ' ' }
}
}
# Cursor and selection position functions for internal Kakoune usage
module cursor {
export def "cursor line" [] {
if ($env.KAK_CURSOR_LINE? | is-empty) { 0 } else { $env.KAK_CURSOR_LINE | into int }
}
export def "cursor column" [] {
if ($env.KAK_CURSOR_COLUMN? | is-empty) { 0 } else { $env.KAK_CURSOR_COLUMN | into int }
}
export def "cursor selection anchor-line" [] {
if ($env.KAK_SELECTION_ANCHOR_LINE? | is-empty) { 0 } else { $env.KAK_SELECTION_ANCHOR_LINE | into int }
}
export def "cursor selection anchor-column" [] {
if ($env.KAK_SELECTION_ANCHOR_COLUMN? | is-empty) { 0 } else { $env.KAK_SELECTION_ANCHOR_COLUMN | into int }
}
export def "cursor selection cursor-line" [] {
if ($env.KAK_SELECTION_CURSOR_LINE? | is-empty) { 0 } else { $env.KAK_SELECTION_CURSOR_LINE | into int }
}
export def "cursor selection cursor-column" [] {
if ($env.KAK_SELECTION_CURSOR_COLUMN? | is-empty) { 0 } else { $env.KAK_SELECTION_CURSOR_COLUMN | into int }
}
}
module external {
export def sessions [] {
^kak -l | lines | where ($it | str length) > 0
}
export def session-exists [session: string] {
sessions | where $it == $session | length | $in > 0
}
export def current-session [] {
$env.KAKOUNE_SESSION?
}
export def send [session: string, command: string] {
$command | ^kak -p $session
}
export def eval [session: string, code: string] {
let tmpfile = (mktemp)
send $session $"echo -to-file ($tmpfile) ($code)"
let result = (open $tmpfile)
rm $tmpfile
$result
}
# TODO, flush this out
export def fifo [session: string, code: string, buf_name: string = "*fifo*"] {
let fifo: string = (mktemp | str trim)
^mkfifo $fifo
send $session $"edit -fifo ($fifo) ($buf_name)"
let line = (input "fifo> ")
open $fifo --raw | while $line {
if $line == "exit" {
break
}
$line | save --append --raw $fifo
}
rm $fifo
}
export def get-option [session: string, option: string] {
eval $session $"echo %opt{($option)}"
}
export def set-option [session: string, option: string, value: string] {
send $session $"set-option global ($option) $value"
}
export def get-register [session: string, reg: string] {
eval $session $"echo %reg{($reg)}"
}
export def set-register [session: string, reg: string, value: string] {
send $session $"set-register ($reg) ($value)"
}
export def get-value [session: string, val: string] {
eval $session $"echo %val{($val)}"
}
export def quote [input: string] {
# Simple quoting: wrap in single quotes and escape existing single quotes
$input | str replace "'" "'\\''" | $"'($in)'"
}
export def unquote [input: string] {
$input | str trim -c "'" | str replace "'\\''" "'"
}
}
export use external *
export use buffer *
export use client *
export use selections *
export use cursor *
And the following for the Kakoune script file:
# knu.kak - Kakoune + Nushell integration
#
# Provides a 'nu' command to execute Nushell code from within Kakoune.
# Usage: nu %{ echo "Hello from Nushell!" }
# Declare options for configuration
# TODO Make this more generic
declare-option -hidden str knu_path %sh{ echo "$HOME/.dotfiles/nushell/.config/nushell/modules/knu.nu" }
declare-option -docstring "The Nushell interpreter used to execute Nushell code" str knu_interpreter nu
# Define the nu command that executes Nushell code
define-command nu -params 1.. -docstring %{
nu [<switches>] [args...] code: Execute provided Nushell code as a script.
Switches:
-debug Print Nushell commands to *debug* buffer instead of executing them.
} %{
evaluate-commands %sh{
# Check if we have `nu` in our path
if ! command -v "$kak_opt_knu_interpreter" >/dev/null 2>&1; then
echo "echo -debug Error: $kak_opt_knu_interpreter not found in PATH" | kak -p "$kak_session"
exit 1
fi
# Check if the knu module file exists
if [ ! -f "$kak_opt_knu_path" ]; then
echo "echo -debug Error: knu module not found at $kak_opt_knu_path" | kak -p "$kak_session"
exit 1
fi
# Export important Kakoune variables as environment variables
export KAK_CLIENT="$kak_client"
export KAK_SESSION="$kak_session"
export KAK_BUFFILE="$kak_buffile"
export KAK_BUFNAME="$kak_bufname"
export KAK_SELECTIONS="$kak_selections"
export KAK_QUOTED_SELECTIONS="$kak_quoted_selections"
export KAK_COMMAND_FIFO="$kak_command_fifo"
# Buffer-related variables
export KAK_BUF_LINE_COUNT="$kak_buf_line_count"
export KAK_BUF_TIMESTAMP="$kak_buf_timestamp"
export KAK_BUF_MODIFIED="$kak_buf_modified"
# Client-related variables
export KAK_CLIENT_PID="$kak_client_pid"
export KAK_CLIENT_NAME="$kak_client_name"
# Cursor and selection variables
export KAK_CURSOR_LINE="$kak_cursor_line"
export KAK_CURSOR_COLUMN="$kak_cursor_column"
export KAK_SELECTION_ANCHOR_LINE="$kak_selection_anchor_line"
export KAK_SELECTION_ANCHOR_COLUMN="$kak_selection_anchor_column"
export KAK_SELECTION_CURSOR_LINE="$kak_selection_cursor_line"
export KAK_SELECTION_CURSOR_COLUMN="$kak_selection_cursor_column"
# Common options
export KAK_OPT_FILETYPE="$kak_opt_filetype"
export KAK_OPT_TABSTOP="$kak_opt_tabstop"
export KAK_OPT_EXPANDTAB="$kak_opt_expandtab"
export KAK_OPT_INDENTWIDTH="$kak_opt_indentwidth"
export KAK_OPT_ALIGNWIDTH="$kak_opt_alignwidth"
export KAK_OPT_SCROLLOFF="$kak_opt_scrolloff"
export KAK_OPT_WRAP="$kak_opt_wrap"
export KAK_OPT_MODELINE="$kak_opt_modeline"
export KAK_OPT_HIGHLIGHT="$kak_opt_highlight"
export KAK_OPT_AUTOSCROLL="$kak_opt_autoscroll"
export KAK_OPT_AUTOCOMPLETE="$kak_opt_autocomplete"
export KAK_OPT_AUTOINFO="$kak_opt_autoinfo"
export KAK_OPT_AUTOINDENT="$kak_opt_autoindent"
export KAK_OPT_AUTOSELECT="$kak_opt_autoselect"
export KAK_OPT_AUTOSELECTCOMPLETE="$kak_opt_autoselectcomplete"
export KAK_OPT_AUTOSELECTCOMPLETEINSERT="$kak_opt_autoselectcompleteinsert"
export KAK_OPT_AUTOSELECTCOMPLETESELECT="$kak_opt_autoselectcompleteselect"
export KAK_OPT_AUTOSELECTCOMPLETESELECTINSERT="$kak_opt_autoselectcompleteselectinsert"
export KAK_OPT_AUTOSELECTCOMPLETESELECTINSERTCOMPLETE="$kak_opt_autoselectcompleteselectinsertcomplete"
# Get the Nushell code (last argument)
code="${!#}"
# Build the Nushell command
nu_cmd="use '$kak_opt_knu_path' *; $code"
# Check for the debug switch
if [ "$1" = "-debug" ]; then
shift
tmpfile=$(mktemp)
if [ $# -gt 0 ]; then
printf %s "$1" | "$kak_opt_knu_interpreter" --commands "$nu_cmd" >"$tmpfile" 2>&1
else
"$kak_opt_knu_interpreter" --commands "$nu_cmd" >"$tmpfile" 2>&1
fi
# Send output to debug buffer
while IFS= read -r line; do
echo "echo -debug $line" | kak -p "$kak_session"
done < "$tmpfile"
rm -f "$tmpfile"
exit 0
fi
# Check if we have arguments to pipe (like %reg{'})
if [ $# -gt 0 ]; then
printf %s "$1" | "$kak_opt_knu_interpreter" --commands "$nu_cmd" >/dev/null 2>&1
else
"$kak_opt_knu_interpreter" --commands "$nu_cmd" >/dev/null 2>&1
fi
}
}
And so far so good. Is there anything here to fix with this? I’m potentially thinking about providing something like PyKak does with a server to prevent opening a new shell process each time a nu %{}
block is called.