Lua development in NeovimEdit
On versions
- Neovim currently embeds Lua 5.1; verify this with
:lua print(_VERSION). - But really, it's LuaJIT 2.1.0-beta3; see this with
:lua print(jit.version). - In the Neovim repo after building (which is as easy as
make), you can find a REPL at.deps/usr/bin/luajit.- The LuaJIT REPL doesn't use readline, which is annoying.
- Use
brew install rlwrapto add fake readline support; invoke withrlwrap .deps/usr/bin/luajit; I've added a crude alias for this to my dotfiles (nlua).
- Lua 5.x updates include breaking changes, so be wary of reading the documentation for the wrong version.
- An example breaking change:
...is the vararg operator for LuaJIT, but other versions use (used?)arg.
- An example breaking change:
Language quirks
-
Indices are 1-based.
- This causes confusion in Neovim because some APIs use 0-based indices (eg.
vim.api.nvim_buf_get_lines) while others use 1-based indices (eg.vim.api.nvim_win_set_cursor; details in:h api-indexing).
- This causes confusion in Neovim because some APIs use 0-based indices (eg.
-
Standard library is tiny, meaning you have to implement basic things yourself.
-
~=is the "not equals" operator. -
No regular expressions (just "patterns").
- Example: instead of
\bword boundary, you can use%f[%A](a%f"frontier" pattern, look for where%A— ie. a non-word character — starts to match). - Non-greedy matches: instead of greedy
*, non-greedy{-}(Vimscript), you can use-.
- Example: instead of
-
No ternaries:
- Can't write
a ? b : c - Can't write
if a then b else c - Can write
a and b or c(but beware, ifbis false, expression will always evaluate toc)
- Can't write
-
0is truthy, so functions in the Vim API that return 0 or 1 need an explicit==check; eg:-- Passes conditonally: if vim.fn.has('autocmd') == 1 then; end -- Always passes: if vim.fn.has('autocmd') then; end -
Some methods return tuples, which is great, but can make their use awkward in some situations; eg:
-- `gsub` returns the new string plus the index of the match. -- We want to make a new list with the result of a couple of -- `gsub` calls, but we can't do this: list = { lines[1]:gsub('a', 'b'), lines[2]:gsub('c', 'd'), } -- or this... list = { lines[1]:gsub('a', 'b')[1], lines[2]:gsub('c', 'd')[1], } -- we have to do this... list = { ({lines[1]:gsub('a', 'b')})[1], ({lines[2]:gsub('c', 'd')})[1], }
Language strengths
- Small/simple (easy to learn).
- Fast, apparently.
- Functions are first class values.
Syntax
- Line comments:
-- - Block comments:
--[[to--]] - Functions:
function optional_name()toend - Loops:
for ... dotoend:- Numbers:
for i = 1, 5 do/end(loops through 1 to 5, inclusive). - Lists:
for i, val in ipairs({'a', 'b', 'c'}) do/end
- Numbers:
- Conditionals:
if ... thentoend; also:else; andelseif ... then(else ifwill usually be a syntax error, because it needs an additionalend).
Neovim APIs
vim.gsplit(string, separator, plain): Returns an iterator; ifplainis passed andtrue,separatoris interpreted literally instead of as a pattern.vim.inspect(): eg.:lua print(vim.inspect(something))vim.split(string, separator, plain): Returns a list-like table; ifplainis passed andtrue,separatoris interpreted literally instead of as a pattern.vim.trim(): trims a string.
Shortcuts
Reading options
vim.o[option](eg.vim.o.hiddenorvim.o.hidetc) is shorthand forvim.api.nvim_get_option('hidden').vim.bo[option](eg.vim.bo.filetypeorvim.bo.ftetc) is shorthand forvim.api.nvim_buf_get_option(0, 'filetype').vim.wo[option](eg.vim.wo.listetc) is shorthand forvim.api.nvim_win_get_option(0, 'list').
Calling Vimscript functions
vim.fn[name](eg.vim.fn.existsetc) runs a Vimscript function of the same name.
Running Vim commands
vim.cmd(string)(eg.vim.cmd('highlight clear')) runs a Vim command.
String methods
-
Available methods (via
:lua print(vim.inspect(vim.tbl_keys(string)))):{ "find", "lower", "format", "rep", "gsub", "len", "gmatch", "dump", "match", "reverse", "byte", "char", "upper", "gfind", "sub" }gmatch(): returns an iterator that finds a pattern; can be used to split a string (eg. to splitwordson commas:for word in string.gmatch(words, '[^,]+') do/end) but note thatvim.gsplit()is probably easier to use.sub(startIndex, endIndex): returns a substring; indices are 1-based and inclusive; eg.("foobar"):sub(2, 5)is"ooba".len(): returns length, but not that("foo"):len()can be written as#"foo".
Table methods
table.concat(tbl, joiner): eg.table.concat(words, ' ').table.insert(tbl, value)
Pro-Tips™
- For testing, can blow away module from cache with:
:lua package.loaded['some.pack'] = nil.