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
cmdline
andkeymap
with the following features:- Colorful output on the builtin treesitter.
- Implicit paren-completions on parinfer: Evaluate
(+ 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.
- 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.lua
in alua/
directory on&rtp
by yourself)
luajit
orlua5.1
(to compilefennel
on&rtp
onmake
)
If none of them is available,nvim --clean --headless -l
will be used as alua
fallback.- 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-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 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.fnl
at 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-thyme
does not compile$XDG_CONFIG_HOME/nvim/init.fnl
. - Unlike hotpot.nvim,
nvim-thyme
does not loadplugin/*.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 likerequire
. - Unlike nfnl,
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:- Define some
autocmd
s 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 onBufWritePost
andFileChangedShellPost
.
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.
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.