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
- JIT Compiler: Compile fennel source at nvim runtime.
- Rollbacks: Safely roll back to the last successfully compiled backups if compilation fails.
- Integrations:
Evaluate fennel code in
cmdlineandkeymapwith the following features:- Colorful output on the builtin treesitter.
- Implicit paren-completions on parinfer: Evaluate
(+ 1 2as 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.
- To cut down startuptime, checking Fennel should be skipped at startup if possible.
- I don't like to mess up
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 of fnl/
directory. The relevant options are fnl-dir and macro-path.
โฆand more features! So, this project started from scratch.
- Neovim v0.11.1+
- Fennel on your
&runtimepath, in short,&rtp.
(not embedded unlike the alternative plugins. See Installation.) make(or please locate a compiledfennel.luain alua/directory on&rtpby yourself)
luajitorlua5.1(to compilefennelon&rtponmake)
If none of them is available,nvim --clean --headless -lwill be used as aluafallback.- A tree-sitter parser for fennel like tree-sitter-fennel,
or via nvim-treesitter on
&rtp. - The parinfer-rust on
&rtp(to improve UX on the commands and keymaps)
- Install
nvim-thymewith 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 the init setup 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
It's recommended to define a bootstrap function for simplicityโฆ
(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")-- 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)-- 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.Caution
Please make sure to disable the lazy.nvim's performance.rtp.reset
option. (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.
With folke/lazy.nvim,
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,
})If 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 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.setup but 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}- (important) Rename
lua/atvim.fn.stdpath('config'), likemv lua/ lua.bk/.
Otherwise, there's some chances that nvim would unquestionably load lua files under thelua/directory apart fromnvim-thyme. - Add codes to enable thyme's auto-compile system. See the Installation section above.
- Start
nvim. You will be asked to generate.nvim-thyme.fnlat the directoryvim.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- Unlike tangerine.nvim,
nvim-thymedoes not compile$XDG_CONFIG_HOME/nvim/init.fnl. - Unlike hotpot.nvim,
nvim-thymedoes not loadplugin/*.fnl,ftplugin/*.fnl,lsp/*.fnland so on;nvim-thymedoes not support Vim commands (e.g.,:sourceand:runtime) to load your Fennel files.nvim-thymeonly supports Lua/Fennel loader likerequire. - Unlike nfnl,
nvim-thymedoes 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:- Define some
autocmds in your config or in .nvim.lua. - Use another compiler plugin together like nfnl.
- Use a task runner like overseer.nvim.
- Use git hooks. (See the .githooks in this project as a WIP example. Help wanted.)
- Define some
- As you may have noticed, the term of Zero overhead only means it does not affect startup time once compiled at an nvim runtime.
- As you may have noticed, the term of JIT (Just-in-time) might be a bit
misleading due to the convention.
The JIT in this project is more like JIT in JIT Manufacturing than in JIT Compilation: it compiles missing modules, and optionally recompiles them onBufWritePostandFileChangedShellPost.
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.
Q. Does the rollback system help me avoid starting in nearly mother-naked nvim due to some misconfigurations?
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.
The collapse illustrates how to merge `fnl/` into the `lua/` directory as safely as possible.
(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.
nvimIf 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.