#!/usr/bin/env bash ############################################################################### # Wrapper for Neovim that takes a lua (or vimscript) file as argument and # # executes it. This essentially makes it possible to use Neovim as a # # standalone interpreter and enables regular scripts to benefit from the # # Neovim API (e.g. when used as a shebang). Without any arguments this # # wrapper starts an interactive nvim lua REPL. # ############################################################################### set -o errexit -o pipefail -o nounset histfile=${XDG_DATA_HOME:-~/.local/share}/nvim/$(basename "$0")_history compfile=${XDG_DATA_HOME:-~/.local/share}/nvim/$(basename "$0")_completion COMP_WORDBREAKS="\"'><=;|&(){}+=*^/~ " function send(){ printf "%s\n" "$*" >& "${COPROC[1]}" } function hist(){ case "$1" in load) mkdir -p "$(dirname "$histfile")" touch "$histfile" history -r "$histfile" ;; append) history -s -- "$2" history -a "$histfile" esac } function repl(){ local cmd prefix while [[ -z "${cmd:-}" ]]; do read -Erp '> ' -d $'\r' || exit 0 cmd=$REPLY prefix="" # hack to find out if the line can be interpreted as an expression to print # its value, similar to what lua 5.3 does (see # http://www.lua.org/manual/5.3/manual.html#7). if luajit -e "assert(function() return $cmd end)" 2>/dev/null; then prefix="=" else # Extremely ugly hack to find out if statement should continue on next # line. Luajit error messages which expect statements to continue seem to # end with "''". while [[ $(luajit -e "$cmd" 2>&1) == *"''" ]]; do read -Erp '>> ' -d $'\r' || { cmd=''; break; } cmd+=$'\n'$REPLY done fi done hist append "$cmd" send verbose lua "${prefix:-}${cmd//$'\n'/ }" # Use SIGUSR1 to find out when vim finished processing cmd to continue the # repl send "!kill -USR1 $BASHPID" } # https://stackoverflow.com/a/52944692 function get_cursor_pos(){ IFS='[;' read -p $'\e[6n' -d R -rs _ y x _ printf '%s\n' "$x:$y" } function add_newline(){ # vim doesn't insert newlines on eol, so we have to do it ourselves if [[ $(get_cursor_pos) != 1:* ]]; then printf '\n' fi } function complete_repl(){ local word= if [[ -v COMP_WORDS ]]; then word=${COMP_WORDS[$COMP_CWORD]} fi send call "writefile([''] + getcompletion(${word@Q}, 'lua'), ${compfile@Q})" while [[ ! -s "$compfile" ]]; do :; done mapfile -ts 1 COMPREPLY <<< "$(< "$compfile")" if [[ $word == *[.:]* ]]; then local stem=${word%[.:]*} for i in "${!COMPREPLY[@]}"; do COMPREPLY[i]="${word::((${#stem}+1))}${COMPREPLY[i]}" done fi rm "$compfile" } case "${1:-}" in "") # without arguments start interactive repl hist load nvim --version | sed '/^$/,$d' coproc nvim -es -i NONE trap "add_newline && repl" SIGUSR1 complete -o nospace -IF complete_repl -EF complete_repl -DF complete_repl repl # use a loop as wait returns when a signal with a trap is received until wait; do : done ;; --help|-h) printf "%s\n%s\n %s\n" \ "usage: $(basename "$0") [options] [script [args]]" \ "Available options are:" \ "-h print this help message and exit (also --help)" ;; *) # treat arg as a file # use 'echo""' to append a final end-of-line indicator exec nvim -c 'autocmd VimLeave * :echo ""' -l "$@" esac