Zero-overhead Fennel JIT compiler for Neovim
Also welcome, non-lispers
How about trying :Fnl (vim.tbl_extend :force {:foo :bar} {:foo :qux
(uhβ¦, typos? Β―\_(γ)_/Β―),
or :=vim.tbl_extend("force", {foo = "bar"}, {foo = "baz"})?
Welcome Aboard β’ Installation β’ Usage β’ Reference β’ FAQ
cmdline and keymap with the following features:(+ 1 2
as if (+ 1 2)![!CAUTION] Please note that undocumented features are subject to change without notice, regardless of semantic versioning.
| Feature | nvim-thyme | hotpot.nvim | nfnl | tangerine.nvim |
|---|---|---|---|---|
| Zero Startup Overhead | β | β | β | β |
| Runtime Compiler | β | β | β | β |
| (Compile in lua/ at runtime) | β (optional) | β
(with :source) |
β | β |
| Safety Rollbacks | β | β | β | β |
| Parinfer Integration in Cmdline mode | β | β | β | β |
| Fennel Dependency | Not embedded (Any compatible version should be on &rtp.) |
Embedded | Embedded | Embedded |
See also Migration Guide and Ex Command Comparisons below.
lua/ as I still write Lua when it seems to be more
comfortable than Fennel. (Type annotation helps us very much.)[!TIP] Optionally, you can manage your Fennel files under
lua/instead offnl/directory. The relevant options are fnl-dir and macro-path.
β¦and more features! So, this project started from scratch.
&runtimepath,
in short, &rtp.make
(or please locate a compiled fennel.lua
in a lua/ directory on &rtp by yourself)luajit or lua5.1
(to compile fennel on &rtp on make)nvim --clean --headless -l will be used as a lua fallback.&rtp.&rtp
(to improve UX on the commands and keymaps)nvim-thyme with lazy.nvim.(If you've decided to go along with Fennel, please skip to the Installation section below.)
require("lazy").setup({
---@type LazySpec
{
"aileot/nvim-thyme",
version = "^v1.6.0",
dependencies = {
{ "https://git.sr.ht/~technomancy/fennel" },
},
lazy = false,
priority = 1000,
build = ":lua require('thyme').setup(); vim.cmd('ThymeCacheClear')",
init = function()
-- Make your Fennel modules loadable.
table.insert(package.loaders, function(...)
return require("thyme").loader(...)
end)
local thyme_cache_prefix = vim.fn.stdpath("cache") .. "/thyme/compiled"
vim.opt.rtp:prepend(thyme_cache_prefix)
end,
config = function()
-- Create the helper interfaces.
require("thyme").setup()
end,
},
-- Optional
{
"aileot/nvim-laurel",
build = ":lua require('thyme').setup(); vim.cmd('ThymeCacheClear')",
},
{
"eraserhd/parinfer-rust",
build = "cargo build --release",
},
-- and other plugin specs...
})
[!WARNING] With the config above, you cannot load Fennel modules before the setup of
lazy.nvim, but only load Fennel modules after theinitsetup is done. Please follow the Installation section below if you'd like to write Fennel more!
:Fnl (+ 1 2 3) " Evaluate Fennel expression
:Fnl (vim.notify "Hello, Fennel!") " Call nvim APIs
:FnlBuf % " Evaluate Fennel expression in the current buffer
&runtimepath(The collapse shows a snippet for folke/lazy.nvim.)
local function bootstrap(url)
-- To manage the version of repo, the path should be where your plugin manager will download it.
local name = url:gsub("^.*/", "")
local path = vim.fn.stdpath("data") .. "/lazy/" .. name
if not vim.loop.fs_stat(path) then
vim.fn.system({
"git",
"clone",
"--filter=blob:none",
url,
path,
})
end
vim.opt.runtimepath:prepend(path)
end
-- Given the `bootstrap` function defined above,
bootstrap("https://git.sr.ht/~technomancy/fennel")
bootstrap("https://github.com/aileot/nvim-thyme")
-- (Optional) Install your favorite plugin manager.
bootstrap("https://github.com/folke/lazy.nvim")
-- (Optional) Install some Fennel macro plugins before the setup of the plugin manager...
bootstrap("https://github.com/aileot/nvim-laurel")
require("thyme").loader to package.loaders-- Wrapping the `require` in `function-end` is important for lazy-load.
table.insert(package.loaders, function(...)
return require("thyme").loader(...) -- Make sure to `return` the result!
end)
&runtimepath-- Note: Add a cache path to &rtp. The path MUST include the literal substring "/thyme/compile".
local thyme_cache_prefix = vim.fn.stdpath("cache") .. "/thyme/compiled"
vim.opt.rtp:prepend(thyme_cache_prefix)
-- Note: `vim.loader` internally cache &rtp, and recache it if modified.
-- Please test the best place to `vim.loader.enable()` by yourself.
vim.loader.enable() -- (optional) before the `bootstrap`s above, it could increase startuptime.
nvim-thyme with Plugin Manager[!CAUTION] Please make sure to disable the
lazy.nvim'sperformance.rtp.resetoption. (The option is enabled by default.) Otherwise, you would get into "loop or previous error," or would be complained that the literal substring"/thyme/compile"is missing in&runtimepath.
require("lazy").setup({
spec = {
{
"aileot/nvim-thyme",
version = "^v1.6.0",
dependencies = {
{ "https://git.sr.ht/~technomancy/fennel" },
},
build = ":lua require('thyme').setup(); vim.cmd('ThymeCacheClear')",
-- For config, see the "Setup Optional Interfaces" section
-- and "Options in .nvim-thyme.fnl" below!
-- config = function()
-- end,
},
-- If you also manage macro plugin versions, please clear the Lua cache on the updates!
{
"aileot/nvim-laurel",
build = ":lua require('thyme').setup(); vim.cmd('ThymeCacheClear')",
-- and other settings
},
-- Optional dependency plugin.
{
"eraserhd/parinfer-rust",
build = "cargo build --release",
},
-- and other plugin specs...
},
performance = {
rtp = {
reset = false, -- Important! It's unfortunately incompatible with nvim-thyme.
},
},
})
(If you also manage macro plugin versions, please clear the Lua cache on the
updates! You can automate it either on spec hook like above, on user event hook
like below; otherwise, please run :ThymeCacheClear manually.)
-- If you also manage other Fennel macro plugin versions, please clear the Lua cache on the updates!
vim.api.nvim_create_autocmd("User", {
pattern = "LazyUpdate", -- for lazy.nvim
callback = function()
require("thyme").setup()
vim.cmd("ThymeCacheClear")
end,
})
To optimize the nvim startuptime, nvim-thyme suggests you to define the Ex command
interfaces and its fnl file state checker some time after VimEnter. For example,
-- In init.lua,
vim.api.nvim_create_autocmd("VimEnter", {
once = true,
callback = function() -- You can substitute vim.schedule_wrap if you don't mind its tiny overhead.
vim.schedule(function()
require("thyme").setup()
end)
end,
})
nvimIf you don't have .nvim-thyme.fnl at vim.fn.stdpath('config'), generally
$XDG_CONFIG_HOME/nvim, you will be asked to generate .nvim-thyme.fnl there
with recommended config. See the Configuration section below.
Ensure the setup by :checkhealth thyme.
Please read the reference for the details and additional features.
.nvim-thyme.fnlnvim-thyme manages all the configurations in a separate config file .nvim-thyme.fnl
instead of thyme.setup.
[!NOTE] This is a point to optimize the nvim startuptime with the JIT compiler. Apart from
thyme.setupbut with.nvim-thyme.fnl, the configurations can be lazily evaluated only by need.
Here is a sample config:
{:max-rollback 5
:compiler-options {:correlate true
;; :compilerEnv _G
:error-pinpoint ["|>>" "<<|"]}
:fnl-dir "fnl"
:macro-path "./fnl/?.fnlm;./fnl/?/init-macros.fnlm;./fnl/?.fnl;./fnl/?/init-macros.fnl;./fnl/?/init.fnl"}
However, you don't have to prepare it by yourself!
If .nvim-thyme.fnl is missing at vim.fn.stdpath('config') on nvim startup,
you will be asked for confirmation. Once you agree, a new .nvim-thyme.fnl will
be generated to vim.fn.stdpath('config') with recommended settings there. The
generated file is a copy of .nvim-thyme.fnl.example.
For all the available options, see Options in the reference.
require("hotpot").setup({
compiler = {
macros = {
env = "_COMPILER",
correlate = true,
},
modules = {
correlate = true,
},
},
})
;; in .nvim-thyme.fnl at stdpath('config')
;; The thyme's searchers always set "_COMPILER" at "env" in evaluating macro modules.
{:compiler-options {:correlate true}
lua/ at vim.fn.stdpath('config'),
like mv lua/ lua.bk/.lua/ directory apart from nvim-thyme.nvim. You will be asked to generate .nvim-thyme.fnl at the
directory vim.fn.stdpath('config').require([[tangerine]]).setup({})
;; in .nvim-thyme.fnl at stdpath('config')
{:compiler-options {:compilerEnv _G
:useBitLib true}
Note: nvim-thyme only provides user commands after you call
thyme.setup for
performance.
With parinfer-rust,
" nvim-thyme
:Fnl (+ 1 1
" hotpot.nvim
:Fnl= (+ 1 1)
" tangerine.nvim
:Fnl (print (+ 1 1))
" nvim-thyme
:silent Fnl (+ 1 1
" hotpot.nvim
:Fnl (+ 1 1)
" tangerine.nvim
:Fnl (+ 1 1)
" nvim-thyme
:FnlFile %
" hotpot.nvim
:Fnlfile %
" nfnl.nvim
:NfnlFile (vim.fn.expand "%:p")
" tangerine.nvim
:FnlFile %:p
nvim-thyme does not compile
$XDG_CONFIG_HOME/nvim/init.fnl.nvim-thyme does not load
plugin/*.fnl, ftplugin/*.fnl, lsp/*.fnl and so on; nvim-thyme does
not support Vim commands (e.g., :source and :runtime) to load your
Fennel files. nvim-thyme only supports Lua/Fennel loader like
require.nvim-thyme does not compile Fennel files which is
not loaded in nvim runtime by default. If you still need to compile Fennel
files in a project apart from nvim runtime, you have several options:autocmds in your config or in .nvim.lua.BufWritePost and
FileChangedShellPost.vim.loader?A. Yes, it is. vim.loader.enable() optimizes the nvim-thyme loader.
A. nvim-thyme is incompatible the option performance.rtp.reset of lazy.nvim.
Make sure you've disabled the lazy.nvim's performance.rtp.reset option.
(The option is enabled by default.)
A. Yes, you can. Just set the variable vim.g.parinfer_enabled to false.
A. Yes, but only for the modules written in Fennel.
Rollbacks are automatically applied when errors are detected at compile time. In addition to that, with the combinations of :ThymeRollbackSwitch and :ThymeRollbackMount, you can also roll back for runtime errors in compiled Lua.
However, it is recommended to put your configuration files under git management first
in case nvim even fail to reach the lines that defines the rollback helper commands.
A. By default, or with the recommended config,
nvim-thyme will make nvim load Fennel modules in lua/ directory
as the default nvim loads the other Lua modules
unless fnl/ exists at the directory that stdpath("config") returns (usually ~/.config/nvim).
Note that, if both foo.lua and foo.fnl exist at the lua/ directory, foo.lua is always loaded.
The relevant options are only fnl-dir and macro-path.
(Assume your nvim config files are managed by git, at ~/.config/nvim.)
# Commit current status
git add -A
git commit -m 'save states before merging fnl/ into lua/'
# Note the current branch name (main or master, maybe)
git branch --show-current
# Create and switch a new branch. (The branch name is an example.)
git switch -c merge-fnl-into-lua
cd ~/.config/nvim
# Check the results with `--dry-run`.
git mv --dry-run fnl lua
git mv --verbose fnl lua
# Make sure your nvim can start without issues.
nvim
If you have any issues,
reset to the previous states where fnl/ and lua/ have co-existed
by the following command.
# Assume your default branch is `main`.
git reset --hard main
git switch main
# Put aside the previous cache directory.
mv ~/.cache/nvim/thyme{,.bk}
Thanks to Shougo for
dein.vim the legendary. The design heavily
inspires nvim-thyme.
Thanks to harrygallagher4 for
nvim-parinfer-rust. The integration of nvim-thyme with
parinfer is based in part on copy extracted from the project, so the
file on parinfer is also on the license
CC0-1.0.