🍲 Hotpot
May 8, 2026 · View on GitHub
🍲 Hotpot

You take this home, throw it in a pot, add some broth, some neovim... baby, you got a stew going!
~ Fennel Programmers (probably)
Hotpot is a Fennel compiler plugin for Neovim that allows you to write your Neovim config and plugins in Fennel.
Version 2
Important
Hotpot version 2's configuration is incompatible with version 1 (née version 0). For most users, the migration should be simple, see migrating from version 1.
The most dramatic change to all users is the requirement that all macro
files must use the extension .fnlm.
If you are unable or do not want to update your configuration, pin your
plugin manager version to the v1.0.0 tag.
Version 2 has a simpler configuration, better support for directories such as
lsp as well as improved support working in multiple project directories
with isolated configuration.
Warning
Again, The most dramatic change to all users is the requirement that all macro
files must use the extension .fnlm.
Requirements
- Neovim 0.11.6+ to run Hotpot
- The compiled output will run on any version of Neovim that your code is written to support.
Fanatical devotion to parentheses.
You may also want an LSP $/progress notification renderer, eg
fidget.nivm for event notifications, or
otherwise enable verbose mode.
Installation
Hotpot provides semantic-version releases via git tags. It's recommended you
use your package managers version tracking system if available, instead of
tracking the main branch which provides no stability guarantees.
Installing with vim.pack
-- init.lua
vim.pack.add({
{src = "https://github.com/rktjmp/hotpot.nvim",
version = vim.version.range("^2.0.0")}
})
require("hotpot")
-- Most users will then require their "config" module stored
-- somewhere like `fnl/config/init.fnl`...
require("config")
Avoid lazy-loading Hotpot unless you are only using it for plugin development.
Hotpot already performs the minimum amount of work on demand. Users wanting to
configure vim.pack.add's behaviour via its options table should read the
advanced vim.pack configuration
notes. Most users should use the above instructions.
Installing with Lazy.nvim
Lazy.nvim
You likely will want to bootstrap Hotpot in the same way you bootstrap
Lazy.nvim itself. See Lazy.nvim's own install instructions or the example
ensure_installed function below.
It's recommended you check the version in the script below matches the latest tagged version release, or run Lazy.nvim's update function immediately after your initial install.
-- init.lua
local function ensure_installed(plugin, branch)
local user, repo = string.match(plugin, "(.+)/(.+)")
local repo_path = vim.fn.stdpath("data") .. "/lazy/" .. repo
if not (vim.uv or vim.loop).fs_stat(repo_path) then
vim.notify("Installing " .. plugin .. " " .. branch)
local repo_url = "https://github.com/" .. plugin .. ".git"
local out = vim.fn.system({
"git",
"clone",
"--filter=blob:none",
"--branch=" .. branch,
repo_url,
repo_path
})
if vim.v.shell_error ~= 0 then
vim.api.nvim_echo({
{ "Failed to clone " .. plugin .. ":\n", "ErrorMsg" },
{ out, "WarningMsg" },
{ "\nPress any key to exit..." },
}, true, {})
vim.fn.getchar()
os.exit(1)
end
end
return repo_path
end
-- Install hotpot in the same manner as lazy.nvim, into Lazy's own plugin directory.
local lazy_path = ensure_installed("folke/lazy.nvim", "stable")
local hotpot_path = ensure_installed("rktjmp/hotpot.nvim", "v2.1.1")
-- As per Lazy's install instructions, but also include hotpot as
-- we have installed it to lazy.nvim's managed directory outside of Neovim's
-- runtimepath.
vim.opt.runtimepath:prepend({hotpot_path, lazy_path})
-- Important! When using Lazy.nvim you *must* require the hotpot module
-- before Lazy.nvim to ensure the module is loaded into memory prior to
-- lazy.nvim altering neovims behaviour.
require("hotpot")
-- Most users will then require their "config" module stored
-- somewhere like `fnl/config/init.fnl`...
require("config")
You must also include Hotpot in your plugins list for Lazy.nvim to correctly manage updates. It is recommended you do not apply any lazy loading methods to Hotpot.
;; fnl/config/init.fnl
(let [lazy (require :lazy)
api (require :hotpot.api)
context (assert (api.context (vim.fn.stdpath :config)))]
(lazy.setup
;; Define your package specs in any way you like, this is just an example.
;; Be sure to include hotpot, as calling `lazy.setup` interferes with lua module
;; loading, which will cause hotpot to malfunction when loads further parts
;; of itself on demand.
{:spec [{:url "https://github.com/folke/lazy.nvim" :branch :stable}
{:url "https://github.com/rktjmp/hotpot.nvim" :version "^2.0.0"}]
;; You must include hotpots output directory in `performance.rtp.paths`!
:performance {:rtp {:paths [(context.locate :destination)]}}}))
;; If you wish to use `Hotpot fennel update` to download fennel new versions of
;; fennel from the internet, you will also have to add the `target directory`
;; path, as listed in `:checkhealth hotpot` under the `Hotpot fennel update`
;; section.
;;
;; If you have configured your config directory to use `:target :colocate`
;; (which is *not* the default), you may skip the `performance.rtp.paths` step.
Usage
~/.config/nvim
Just as any Lua code can be put in lua/**/*.lua, you can now put Fennel code
in fnl/**/*.fnl and require it. You can add as little or as much Fennel as
you would like. Your existing lua/**/*.lua code will continue to work as
before and Lua and Fennel modules are interoperable.
You can also put .fnl files in any standard runtime directories such as
lsp/, plugin/ or ftplugin/.
Saving any .fnl files will cause Hotpot to sync any changes needed to the
associated .lua files.
;; ~/.config/nvim/fnl/my-config/hello.fnl
(print :hello)
;; ~/.config/nvim/lsp/my-lang.fnl
(print :setup-some-lsp)
Hotpot's default configuration for Neovim's config directory stores any
compiled .lua files in a separate location to maintain a clean directory
tree, so you won't see any .lua files.
There is one special exception to the above: .config/nvim/init.fnl will
always compile to .config/nvim/init.lua.
That's all you need to know to write your config in Fennel. You might also want to see commands for evaluating or compiling Fennel snippets or interacting with Hotpot.
To adjust aspects of Hotpot's behaviour such as how notifications are
delivered, colocating .lua files in-tree, ignoring files or configuring the
Fennel compiler, see configuration.
To interact with Hotpot from your own commands, keybinds or functions, see the API.
Use .fnlm Extension for Macros
Important
Hotpot requires that Fennel macro files (those that you call import-macros
for) must use the modern .fnlm extension. Regular Fennel modules (those
that you call require for) should use the .fnl extension.
Plugins
To enable Fennel compilation for a plugin, you must place a .hotpot.fnl file
in the root of the plugin directory. At a minimum, this file must specify the
schema and target keys, as shown below.
;; projects/my-plugin/.hotpot.fnl
{:schema :hotpot/2
;; Plugins must ship `.lua` code to users.
;; Setting the `target` to `colocate` will keep any `.lua` files "in-tree".
;; It is an error to set `target` to `cache` for a plugin.
:target :colocate}
Tip
Before saving any changes to your .hotpot.fnl file, you might want to run
:trust, to save yourself being prompted later.
After creating your .hotpot.fnl file, open any .fnl file and save it to
trigger a build, or use the sync command.
See configuration for details on customising Hotpot's
behaviour, ignoring files .fnl or .lua files or configuring the Fennel
compiler.
Commands
Use :Hotpot to interact with Hotpot, :Fnl and its
related commands to evaluate or compile Fennel code from the buffer or command
line.
:Hotpot
The :Hotpot command exposes the following subcommands:
sync: manually trigger a compile & clean cycle.locate: find or open a files.luaor.fnlcounterpart.watch: enable or disable compile-on-save.fennel: update the bundled fennel version with the latest from fennel-lang.org.
:Hotpot sync
Sync a given context's .fnl and .lua files. This is the same operation that
occurs when you save a .fnl or .fnlm file.
Supports the following parameters:
context=<path>: sets the context for the command, if not given, the current working directory is used.force: force compilation of all files in the context, even if the.luais up to date.atomic: allow writing successfully compiled files even if others have compilation errors.verbose: output additional compilation messages.
See also the context.sync function exposed by the API.
:Hotpot locate
Find or open a counterpart file, supports the following invocations:
:Hotpot locate <path> -- <commands ...>
Find counterpart file path for <path> and append it to <commands ...>, eg:
:Hotpot locate fnl/my-file.fnl -- vnew would open the counterpart .lua file
in a vnew split. You may use %% to subsitute the located path in commands
where simply appending the path wont work (eg: echo '%%' where echo
requires the path be quoted).
If path is not given, the current buffer path is used instead, eg: :Hotpot locate -- <commands ...> is equivalent to :Hotpot locate % -- <commands ...>.
:Hotpot locate <path>
Prints the counterpart path, again, if no <path> is given, the current buffer
path is used.
See also the context.locate function exposed by the API.
:Hotpot watch
Enable or disable the compile-on-save behaviour.
Supports the following (mutually exclusive) parameters:
enable: enable syncing on save for all contexts in this session.disable: disable syncing on save for all contexts in this session.
:Hotpot fennel
Update or rollback fennel.lua to the latest version from fennel-lang.org.
Requires curl to be installed.
Important
Running this is not without some risk, as an updated version of Fennel may
be incompatible with Hotpot. This is pretty unlikely unless the API to
evaluate or compile fennel code is changed. If a release is only adding new
"forms" (eg: (accumulate ...)) the update should be safe.
Exposes the following sub commands:
:Hotpot fennel version
Reports the currently loaded and used version of Fennel.
:Hotpot fennel update
Supports the following parameters:
url=<url>: use given URL instead of finding the latest from fennel-lang.org.force: do not ask whether to update.
:Hotpot fennel rollback
Remove downloaded Fennel file and use version shipped with Hotpot.
:Fnl
Operate on either the command line provided Fennel, or a range from the current buffer either specified on the command line or by selection.
You may always provide a range (:'<,'>Fnl, :%Fnl, etc) or source
string (:Fnl (+ 1 1)). If given both, range always takes precedence.
This command is analogous to Neovim's built in :lua command, and supports the
following flags:
:Fnl
Evaluate the input range or string. Outputs nothing unless the source itself does.
:Fnl=
Evaluate the input range or string and vim.print the result of the expressions.
:Fnl-
Compile the input range or string and vim.print the result of the compilation.
Note that allowedGlobals = false when compling source via :Fnl-, as most
often it's used to compile small snippets for inspection where its common to
reference out-of-selection variables. This means you will not see any warnings
when referencing misspelled or unknown variable as you would during normal
compilation.
:FnlEval
Alias for :Fnl=
:FnlCompile
Alias for :Fnl-
:Fnlfile {file.fnl}
Evaluates the given file, also supports :Fnlfile= file (output evaluation) and
:Fnlfile- file (output compilation).
:source {file.fnl}
Sources given .fnl file. See :h :source
Configuration
The majority of Hotpot's behaviour and the Fennel compiler is configured via a
.hotpot.fnl file. Some specific integration between Hotpot and
Neovim is configured via the setup() function.
Tip
For most users who just want to write their configuration in Fennel, you can ignore this section and use the default settings.
.hotpot.fnl
Hotpot's behaviour, along with the Fennel compiler, is configured by a
.hotpot.fnl file, placed in the root of your config or plugin directory.
These files define a context for operations in that directory tree. These
contexts are independent of one another and only alter behaviour in the
same tree.
If there is no .hotpot.fnl file in Neovim's config directory, a default
configuration is loaded. This is not the case for plugins, which must have a
.hotpot.fnl file.
Tip
Before saving any changes to your .hotpot.fnl file, you might want to run
:trust, to save yourself being prompted later.
;; .hotpot.fnl
{
;; Required, string, valid: hotpot/2
;; Describes expected schema for table.
:schema :hotpot/2
;; Required, string, valid: cache|colocate
;; Describes target location of lua files. `cache` places lua files "out of
;; tree" in a directory loadable by neovim, `colocate` places lua files "in
;; tree", next to their fennel counterparts.
;;
;; When no `.hotpot.fnl` file is present in your config directory,
;; the target defaults to :cache. You may set it to :colocate by adding a
;; .hotpot.fnl file.
;; Be aware that its the users responsibility to remove previously
;; generated lua files when swapping targets in either direction.
;;
;; For plugins, the only valid value is `colocate`.
:target :cache
;; All other keys are optional.
;; Optional, boolean
;; If true (default), any single compilation error will prevent any changes
;; from being written.
:atomic? true
;; Optional, boolean
;; If true (default: false), output messages after every successful
;; compilation instead of just on error.
:verbose? true
;; Optional, function
;; If provided, all compiled fennel source is passed to the function, along
;; with its path, relative to `.hotpot.fnl`. The function must return the
;; modified source.
;; Transform is not called automatically when using the compile and eval API.
:transform (fn [src path] src)
;; Optional, list of strings
;; Glob patterns to ignore when performing compile and clean operations,
;; relative to the .hotpot.fnl file.
;;
;; Files matching `.lua` patterns are never considered orphans and never removed.
;; Files matching `.fnl` patterns are never compiled.
;; Files matching `.fnlm` patterns are never considered when performing stale checks.
:ignore [:some/lib/**/*.lua :junk/*.fnl]
;; Optional, table
;; Fennel compiler options, passed directly to `fennel.compile-string`.
;;
;; Hotpot enables strict global checking by default to prevent referencing
;; unknown or misspelled variables. To restore Fennel's default
;; behaviour, you can set `allowedGlobals` to `false`.
;;
;; If you wish to reference `vim` in your macros, set `:extra-compiler-env {: vim}`.
;;
;; Note that `error-pinpoint` is always forced to false and `filename` is
;; always set to the correct value.
;;
;; See Fennel's own API documentation and --help for further details.
:compiler {:allowedGlobals (icollect [k _ (pairs _G)] k)
:extra-compiler-env {: vim}
:error-pinpoint false}
}
setup()
Some advanced configuration options are supported by calling setup({...})
after requiring Hotpot. You do not have to call setup() if you are
satisfied with the default behaviour.
Note: you may provide the keys in fennel-style or lua_style.
sync-report-handler
A function to override the default sync event reporter.
If you provide this function it is your responsibility to correctly output compiliation errors.
The function receives 3 arguments, a context object, report table and an
invocation-metadata table. Inspect the report table for available fields.
invocation-metadata contains reason which may be command, autocommand
or api depending on what initiated the sync event.
The default handler generates LSP $/progress messages by default or
nvim_echo messages when verbose? = true.
API
Hotpot provides an API to compile and evaluate arbitrary Fennel code, as well
as compiling files in a project. All interaction is done via a context object.
context(path|nil)
Creates a context object. Returns context or nil, error.
All other API interactions are performed through the context object.
(let [api (require :hotpot.api)
ctx (api.context (vim.fn.stdpath :config))]
(ctx.eval "(+ 1 1)"))
path may be:
- A path to a file or directory that has a
.hotpot.fnlin its file tree, - your Neovim config directory, even if that does not have a
.hotpot.fnlfile, - or
nil.
If given a valid path, the context is loaded for use. If given nil, a
default "api" context is created which does not support some operations that
require disk paths, such as sync.
context.compile(string, compiler-options)
Compiles the given string, using the context compiler options. Returns true, compiled string or false, error.
Does not automatically apply any transform specified for by the context
configuration. If one was specified, it can be called by context.transform.
context.eval(string, compiler-options)
Evaluates the given string, using the context compiler options. Returns true, ...evaluated values or false, error.
Does not automatically apply any transform specified for by the context
configuration. If one was specified, it can be called by context.transform.
context.sync(options|nil)
Syncs the context by compiling files in the context. Returns report table.
Supports the following options:
force?: force compilation of all files in the context, even if the.luais up to date.atomic?: allow writing successfully compiled files even if others have compilation errors.verbose?: output additional compilation messages.compiler: additional Fennel compiler options.
Not available for "api" contexts, eg: those created without any path given.
context.transform(string, filename|nil)
Apply context transform to string, as defined in the context .hotpot.fnl.
Not available if no transform has been defined.
context.locate(string)
Convert given path into its counterpart, eg: given a .fnl file path inside
the context source, convert it to the .lua file path in the context
destination.
Accepts .fnl or .lua file paths. Will construct paths for files that do not
exist if desired.
Not available for "api" contexts, eg: those created without any path given.
context.metadata()
Returns a table of metadata related to the given context.
Migrating from Version 1
Important
You must change all macro files to use the the .fnlm extension. This
does not require any code changes.
For many users who have not explicitly configured Hotpot, after renaming any
macro files to .fnlm, version 2 should work without drama.
If you are using Lazy.nvim, you should re-check the
installation instructions to correctly set the rtp option.
If you have specifically configured compiler options (via the
compiler.macros/compiler.modules setup options) or use a .hotpot.lua
file, you will need to migrate to a .hotpot.fnl file. See
configuration for details on .hotpot.fnl. You no longer
need to provide separate macros and modules compiler tables. All compiler
options are specified in the compiler key in your .hotpot.fnl file.
The API has been simplified but with simplification, some previously
provided functions have been removed, eg: there is now only eval(source, options), no eval-buffer, eval-file, etc.
Previously Hotpot provided in-editor diagnostics. These have been removed in favour of more complete solutions provided by LSP servers.
Changes from Version 1
- No more Just in Time, now an Ahead of Time compiler
- Hotpot now compiles all Fennel files that require compiling on save,
instead of on-demand (i.e. on
require()). This change was made for better compatibility with things such as thelsp/runtime directory. - You can still "hide"
.luafiles from your config, this is still the default behaviour.
- Hotpot now compiles all Fennel files that require compiling on save,
instead of on-demand (i.e. on
- Better support for different compiler contexts
.hotpot.fnlsupports configuring different directories with different compiler options and editing files in one plugin directory while your current working directory is elsewhere works better.
- Macro files must use the extension
.fnlm- This is the modern Fennel way, no concessions are currently made to support
init-macros.fnlfilenames.
- This is the modern Fennel way, no concessions are currently made to support
.hotpot.luais now.hotpot.fnl- See configuration, most of the same features exist, providing
direct access to the compiler options, transforming source code and
including/ignoring files. You can no longer redirect files on a case-by-case
basis, either all are in cache or colocated (special case for
<config>/init.fnlwhich is always colocated).
- See configuration, most of the same features exist, providing
direct access to the compiler options, transforming source code and
including/ignoring files. You can no longer redirect files on a case-by-case
basis, either all are in cache or colocated (special case for
- Diagnostics (in-editor compiler warnings) removed
- LSP (eg:
fennel-language-server) provides a richer feature set.
- LSP (eg:
- Configuring Fennel compiler options via
setup()- Removed, use
.hotpot.fnlfile +compilerkey if needed.
- Removed, use
hotpot.api.compile|evaluate_[file|selection|...]- Simplified and context aware, see API.
- Removed
:Fnldocommands- This seem to have little value personally, if you really do have a use for it please open an issue.
Disable or Uninstall
To disable Hotpot's compile-on-save behaviour, see :Hotpot watch.
To disable Hotpot temporarily, remove the require('hotpot') call from your
init.lua|fnl file.
To uninstall Hotpot and remove any files, first run :checkhealth hotpot and note
destination directory associated with the Neovim configuration context. This
will contain any compiled lua files. You can delete this directory and then
remove Hotpot via your plugin manager.
Licenses
Hotpot embeds fennel.lua, see lua/hotpot/vendor/fennel.lua for licensing
information.