Using Neovim as Lua interpreter with Luarocks
This is a short article covering how you can use Neovim as Lua interpreter for Luarocks and busted.
Update 2023-11-05:
See nlua for a ready-to-use version
Motivation ยถ
In Testing Neovim LSP plugins I wrote:
To run tests you need a test runner. Busted is a popular choice for Lua, but for Neovim using the test harness thatโs part of plenary is currently the more convenient option. This is because Neovim embeds Lua and youโll want to run your tests using this embedded โNeovim Luaโ to have access to the vim module. If you ran busted directly it would use your system Lua, without access to the vim module.
In the future it may become possible to run the normal busted and tell it to use Neovim as Lua interpreter, if Neovim adds support for that. (See repurpose โ-lโ to execute Lua)
It turns out this future happened. @justinmk
merged a change to Neovim that
allows to use the -l
command line option to run Lua scripts and pass any
trailing arguments to the script.
Examples:
nvim -l foo.lua --arg1 --arg2
Or:
nvim -l <(echo "print(10)")
See :help -l
for more details if you’re using Neovim nightly (Or in the future, Neovim 0.9+).
Install Luarocks ยถ
First we need to install Luarocks. I recommend using a package manager. On Archlinux I use pacman
:
pacman -S luarocks
Create a nlua wrapper ยถ
Luarocks supports configuring the Lua interpreter it should use, but we can’t point it to nvim -l
because of two reasons:
- Luarocks only allows setting the interpreter to a command that must be available in
/usr/bin
(at least on Linux). You can’t have an argument included in the command. - The command line interface of a regular Lua interpreter is different then what
nvim
ornvim -l
offers:
usage: lua [options] [script [args]]
Available options are:
-e stat execute string 'stat'
-i enter interactive mode after executing 'script'
-l mod require library 'mod' into global 'mod'
-l g=mod require library 'mod' into global 'g'
-v show version information
-E ignore environment variables
-W turn warnings on
-- stop handling options
- stop handling options and execute stdin
As far as I could tell, Luarocks depends on the -e
option and otherwise
passes along the script with its arguments.
I created a Haskell script which I put into /usr/bin/nlua
to emulate that.
The script is rather simplistic and doesn’t support any other options. All it
does is rewrite -e some_lua_expression
into -c "lua some_lua_expression"
and prefix the remainder with an -l
. Here is the full script:
#!/usr/bin/env stack
{- stack script --resolver lts-20.5 -}
import System.Environment (getArgs)
import System.Process (spawnProcess, waitForProcess)
import System.Exit (exitWith)
main :: IO ()
main = do
args <- remap <$> getArgs
handle <- spawnProcess "v" ("-Es" : args)
waitForProcess handle >>= exitWith
--- >>> remap ["-e", "stat"]
-- ["-c","lua stat"]
--
--- >>> remap ["-e", "stat", "luascript_path"]
-- ["-c","lua stat","-l","luascript_path"]
--
--- >>> remap ["-e", "stat", "-l", "luascript_path"]
-- ["-c","lua stat","-l","luascript_path"]
remap :: [String] -> [String]
remap [] = []
remap ("-e" : x : xs)
| null xs = newHead
| head xs == "-l" = newHead <> xs
| otherwise = newHead <> ("-l" : xs)
where
newHead = ["-c", "lua " <> x]
remap (x : xs) = x : remap xs
(See this follow up post for a Lua version of the script)
Configure Luarocks ยถ
Next we need to configure luarocks to use the new nlua
interpreter and set the right Lua version. In a shell, run:
luarocks config lua_version 5.1
luarocks config lua_interpreter nlua
luarocks config variables.LUA_INCDIR /usr/include/luajit-2.1
To check that it works, run luarocks
. You should see something like this:
Configuration:
Lua:
Version : 5.1
Interpreter: /usr/bin/nlua (ok)
LUA_DIR : /usr (ok)
LUA_BINDIR : /usr/bin (ok)
LUA_INCDIR : /usr/include/luajit-2.1 (ok)
LUA_LIBDIR : /usr/lib (ok)
Install busted ยถ
Now install busted in a local tree:
luarocks --local install busted
If the interpreter wrapper works, you should see something like this:
โฎ luarocks --local install busted
Installing https://luarocks.org/busted-2.1.1-1.src.rock
busted 2.1.1-1 depends on lua >= 5.1 (5.1-1 provided by VM)
busted 2.1.1-1 depends on lua_cliargs 3.0 (3.0-2 installed)
busted 2.1.1-1 depends on luafilesystem >= 1.5.0 (1.8.0-1 installed)
busted 2.1.1-1 depends on luasystem >= 0.2.0 (0.2.1-0 installed)
busted 2.1.1-1 depends on dkjson >= 2.1.0 (2.6-1 installed)
busted 2.1.1-1 depends on say >= 1.4-1 (1.4.1-3 installed)
busted 2.1.1-1 depends on luassert >= 1.9.0-1 (1.9.0-1 installed)
busted 2.1.1-1 depends on lua-term >= 0.1 (0.7-1 installed)
busted 2.1.1-1 depends on penlight >= 1.3.2 (1.13.1-1 installed)
busted 2.1.1-1 depends on mediator_lua >= 1.1.1 (1.1.2-0 installed)
busted 2.1.1-1 is now installed in /home/user/.luarocks (license: MIT <http://opensource.org/licenses/MIT>)
You can take a look at the busted
executable that it created to see how it uses the nlua
interpreter:
#!/bin/sh
LUAROCKS_SYSCONFDIR='/etc/luarocks' exec \
'/usr/bin/nlua' \
-e \
'package.path="/h/u/.luarocks/share/lua/5.1/?.lua;/h/u/.luarocks/share/[....]' \
'/h/u/.luarocks/lib/luarocks/rocks-5.1/busted/2.1.1-1/bin/busted' "$@"
Running busted ยถ
As last step we verify if busted
works on tests which are using the vim
module to ensure it uses the Neovim Lua. I used the nvim-lint test suite:
โช ~/.luarocks/bin/busted tests
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
31 successes / 0 failures / 0 errors / 0 pending : 0.022985 seconds
๐