macros to configure neovim in fennel
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.
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:
(use :tsbohc/zest.nvim)
zest.setup
with no arguments(let [zest (require :zest)]
(zest.setup))
(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.
(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.
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
string.format
wrapper for vlua
(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_(...)))
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
(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
(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
(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.
(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
(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
autocmd!
included(def-augroup :my-augroup)
show lua
do
vim.cmd("augroup my-augroup")
vim.cmd("autocmd!")
vim.cmd("augroup END")
end
(def-autocmd [:BufNewFile my_event] [:*.html :*.xml]
"setlocal nowrap")
show lua
vim.cmd(("au " .. table.concat({"BufNewFile", my_event}, ",") .. " *.html,*.xml setlocal nowrap"))
(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
autocmd!
(def-augroup-dirty :my-dirty-augroup)
show lua
do
vim.cmd("augroup my-dirty-augroup")
vim.cmd("augroup END")
end
(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))
zest embeds
fennel.lua
-- I do not claim any ownership over this file