These are your father's parentheses.
Elegant weapons for a more... civilized age.
— xkcd/297
An opinionated library of macros that aims to streamline the process of configuring neovim with fennel, a lisp that compiles to lua.
For a full config example, see my dotfiles.
- Provide a syntactically sweet way of interacting with select parts of lua api
- Seamlessly integrate lua functions into keymaps, autocmds, etc
- Be primarily a library of macros, do as much as possible at compile time
- Output code that is readable and efficient
- Remain compatible with everything, yet standalone
WIP If you have any feedback or ideas on how to improve zest, please share them with me! You can reach me in an issue or at @tsbohc on the conjure discord.
Deprecation notice I'll be overhauling the macros some time later this month. The old macros will stay for a while though. The -fn
macros will be merged into regular ones.
If you're already using a plugin that integrates fennel into neovim, such as aniseed or hotpot, follow these instructions:
- Install with your favourite package manager
(use :tsbohc/zest.nvim)
- Before using any of the macros, run
zest.setup
with no arguments
(let [zest (require :zest)]
(zest.setup))
- Import and alias the macros you wish to use in the current file
(import-macros
{:opt-prepend opt^} :zest.macros)
When installed on its own, zest can be configured to mirror the source
directory tree to target
. When a relevant file is saved, zest will display a message and recompile it.
Unless configured, zest will not initialise its compiler.
Show an example of a standalone configuration
(let [zest (require :zest)
h vim.env.HOME]
(zest.setup
{:target (.. h "/.garden/etc/nvim.d/lua")
:source (.. h "/.garden/etc/nvim.d/fnl")
:verbose-compiler true
:disable-compiler false}))
In each example, the top block contains the fennel code written in the configuration, while the bottom one shows the lua code that neovim will execute.
The examples are refreshed with every change to zest and are always up to date.
- Store a function and return its
v:lua
, excluding the parentheses
(local v (vlua my_fn))
show lua
local v
do
local ZEST_N_0_ = _G._zest.v["#"]
local ZEST_ID_0_ = ("_" .. ZEST_N_0_)
_G._zest["v"][ZEST_ID_0_] = my_fn
_G._zest["v"]["#"] = (ZEST_N_0_ + 1)
v = ("v:lua._zest.v." .. ZEST_ID_0_)
end
- A
string.format
wrapper forvlua
(vim.cmd
(vlua-format
":com -nargs=* Mycmd :call %s(<f-args>)"
(fn [...]
(print ...))))
show lua
local function _0_(...)
local ZEST_N_0_ = _G._zest.v["#"]
local ZEST_ID_0_ = ("_" .. ZEST_N_0_)
local function _1_(...)
return print(...)
end
_G._zest["v"][ZEST_ID_0_] = _1_
_G._zest["v"]["#"] = (ZEST_N_0_ + 1)
return ("v:lua._zest.v." .. ZEST_ID_0_)
end
vim.cmd(string.format(":com -nargs=* Mycmd :call %s(<f-args>)", _0_(...)))
- A complete
vim.opt
wrapper
(opt-local-append completeopt ["menuone" "noselect"])
show lua
do end (vim.opt_local.completeopt):append({"menuone", "noselect"})
Full list of opt-
macros:
opt-set opt-local-set opt-global-set
opt-get opt-local-get opt-global-get
opt-append opt-local-append opt-global-append
opt-prepend opt-local-prepend opt-global-prepend
opt-remove opt-local-remove opt-global-remove
- Map literals
(def-keymap :H [nv] "0")
show lua
do
local ZEST_OPTS_0_ = {noremap = true}
vim.api.nvim_set_keymap("n", "H", "0", ZEST_OPTS_0_)
vim.api.nvim_set_keymap("v", "H", "0", ZEST_OPTS_0_)
end
- Map lua expressions
(each [_ k (ipairs [:h :j :k :l])]
(def-keymap (.. "<c-" k ">") [n] (.. "<c-w>" k)))
show lua
for _, k in ipairs({"h", "j", "k", "l"}) do
vim.api.nvim_set_keymap("n", ("<c-" .. k .. ">"), ("<c-w>" .. k), {noremap = true})
end
- Map pairs
(def-keymap [n]
{:<ScrollWheelUp> "<c-y>"
:<ScrollWheelDown> "<c-e>"})
show lua
do
local ZEST_OPTS_0_ = {noremap = true}
vim.api.nvim_set_keymap("n", "<ScrollWheelUp>", "<c-y>", ZEST_OPTS_0_)
vim.api.nvim_set_keymap("n", "<ScrollWheelDown>", "<c-e>", ZEST_OPTS_0_)
end
To disable noremap
, include :remap
after the modes.
- Define a function and map it to a key
(def-keymap-fn :<c-m> [n]
(print "hello from fennel!"))
show lua
do
local ZEST_VLUA_0_
do
local ZEST_ID_0_ = "_60_99_45_109_62_110_"
local function _0_()
return print("hello from fennel!")
end
_G._zest["keymap"][ZEST_ID_0_] = _0_
ZEST_VLUA_0_ = ("v:lua._zest.keymap." .. ZEST_ID_0_)
end
local ZEST_RHS_0_ = (":call " .. ZEST_VLUA_0_ .. "()<cr>")
vim.api.nvim_set_keymap("n", "<c-m>", ZEST_RHS_0_, {noremap = true})
end
- Define an expression as a function
(def-keymap-fn :k [nv :expr]
(if (> vim.v.count 0) "k" "gk"))
show lua
do
local ZEST_VLUA_0_
do
local ZEST_ID_0_ = "_107_110_118_"
local function _0_()
if (vim.v.count > 0) then
return "k"
else
return "gk"
end
end
_G._zest["keymap"][ZEST_ID_0_] = _0_
ZEST_VLUA_0_ = ("v:lua._zest.keymap." .. ZEST_ID_0_)
end
local ZEST_RHS_0_ = (ZEST_VLUA_0_ .. "()")
local ZEST_OPTS_0_ = {expr = true, noremap = true}
vim.api.nvim_set_keymap("n", "k", ZEST_RHS_0_, ZEST_OPTS_0_)
vim.api.nvim_set_keymap("v", "k", ZEST_RHS_0_, ZEST_OPTS_0_)
end
- Define an augroup with
autocmd!
included
(def-augroup :my-augroup)
show lua
do
vim.cmd("augroup my-augroup")
vim.cmd("autocmd!")
vim.cmd("augroup END")
end
- Define an autocommand
(def-autocmd [:BufNewFile my_event] [:*.html :*.xml]
"setlocal nowrap")
show lua
vim.cmd(("au " .. table.concat({"BufNewFile", my_event}, ",") .. " *.html,*.xml setlocal nowrap"))
- Define a function and bind it as an autocommand
(def-augroup :restore-position
(def-autocmd-fn :BufReadPost "*"
(when (and (> (vim.fn.line "'\"") 1)
(<= (vim.fn.line "'\"") (vim.fn.line "$")))
(vim.cmd "normal! g'\""))))
show lua
do
vim.cmd("augroup restore-position")
vim.cmd("autocmd!")
do
local ZEST_VLUA_0_
do
local ZEST_N_0_ = _G._zest.autocmd["#"]
local ZEST_ID_0_ = ("_" .. ZEST_N_0_)
local function _0_()
if ((vim.fn.line("'\"") > 1) and (vim.fn.line("'\"") <= vim.fn.line("$"))) then
return vim.cmd("normal! g'\"")
end
end
_G._zest["autocmd"][ZEST_ID_0_] = _0_
_G._zest["autocmd"]["#"] = (ZEST_N_0_ + 1)
ZEST_VLUA_0_ = ("v:lua._zest.autocmd." .. ZEST_ID_0_)
end
vim.cmd(("autocmd BufReadPost * :call " .. ZEST_VLUA_0_ .. "()"))
end
vim.cmd("augroup END")
end
- Define an augroup without
autocmd!
(def-augroup-dirty :my-dirty-augroup)
show lua
do
vim.cmd("augroup my-dirty-augroup")
vim.cmd("augroup END")
end
- Assign a function to an ex command
(def-command-fn :MyCmd [...]
(print ...))
show lua
do
local ZEST_VLUA_0_
do
local ZEST_ID_0_ = "_77_121_67_109_100_"
local function _0_(...)
return print(...)
end
_G._zest["command"][ZEST_ID_0_] = _0_
ZEST_VLUA_0_ = ("v:lua._zest.command." .. ZEST_ID_0_)
end
vim.cmd(("command -nargs=* MyCmd :call " .. ZEST_VLUA_0_ .. "(<f-args>)"))
end
Arguments are handled automatically like so:
[] -nargs=0 --
[x] -nargs=1 <q-args>
[...] -nargs=* <f-args>
[x ...] -nargs=* <f-args>
[x y] -nargs=* <f-args>
At compile time, there is no good way of knowing if a variable contains a function or a string. I think so, at least (enlighten me!). This means that the type of the argument has to be supplied to the macro explicitly.
This is the reason for the having both def-keymap
and def-keymap-fn
, for example.
That said, def-keymap
and others can accept functions if they have been wrapped in vlua
:
(fn my-fn []
(print "dinosaurs"))
(def-keymap :<c-m> [n]
(vlua-format
":call %s()<cr>"
my-fn))
When it comes to defining text objects, they can be considered fancy keymaps. Here're the definitions of inner line
and around line
:
(def-keymap :il [xo :silent]
(string.format ":<c-u>normal! %s<cr>"
"g_v^"))
(def-keymap :al [xo :silent]
(vlua-format ":<c-u>call %s()<cr>"
(fn [] (vim.cmd "normal! $v0"))))
Text operators are the fanciest of keymaps. Here's a minimal example:
(fn def-operator [k f]
(let [v-lua (vlua f)]
(def-keymap k [n :silent] (string.format ":set operatorfunc=%s<cr>g@" v-lua))
(def-keymap k [v :silent] (string.format ":<c-u>call %s(visualmode())<cr>" v-lua))
(def-keymap (.. k k) [n :silent] (string.format ":<c-u>call %s(v:count1)<cr>" v-lua))))
(def-operator :q
(fn [x] (print x))
If you want to create complex autocmds, use vlua
:
(vim.cmd
(vlua-format
(.. ":autocmd " ponder " * <buffer=42> ++once :call %s()")
print-answer))
- bakpakin for fennel, a wonderful dialect for a wonderful language
- Olical for aniseed and being awesome
- ElKowar for sharing his thoughts and his discord status
- Hauleth for this post, which sparked my interest in fennel
zest embeds
fennel.lua
-- I do not claim any ownership over this file