Nfnl
Enhance your Neovim with Fennel
Install / Use
/learn @Olical/NfnlREADME
nfnl
Enhance your [Neovim][neovim] experience through [Fennel][fennel] with zero overhead. Write Fennel, run Lua, nfnl will not load unless you're actively modifying your Neovim configuration or plugin source code ([nfnl-plugin-example][nfnl-plugin-example], [my Neovim configuration][config]).
- Only loads when working in directories containing a
.nfnl.fnlconfiguration file. - Automatically compiles
*.fnlfiles to*.luawhen you save your changes. - Can be used for your Neovim configuration or [plugins][nfnl-plugin-example] with no special configuration, it just works for both.
- Includes a Clojure inspired [standard library][apidoc] (based on [Aniseed][aniseed]).
- Compiles your Fennel code and then steps out of the way leaving you with plain Lua that doesn't require nfnl to load in the future.
- Displays compilation errors as you save your Fennel code to keep the feedback loop as tight as possible.
Usage
First, you must create the configuration file at the root of your project or configuration, this can be blank if you wish to rely on the defaults for now.
echo "{}" > .nfnl.fnl
The first time you open a Fennel file under this directory you'll be prompted to trust this configuration file since it's Fennel code that's executed on your behalf. You can put any Fennel code you want in this file, just be sure to return a table of configuration at the end.
(print "Hello, World! From my nfnl configuration!")
{:fennel-path "..."}
By default, writing to any Fennel file with Neovim under the directory
containing .nfnl.fnl will automatically compile it to Lua. If there are
compilations errors they'll be displayed using vim.notify and the Lua will not
be updated.
nfnl will refuse to overwrite any existing Lua at the destination if nfnl was not the one to compile it, this protects you from accidentally overwriting existing Lua with compiled output. To bypass the warning you must delete or move the Lua file residing at the destination yourself.
Now you may use the compiled Lua just like you would any other Lua files with Neovim. There's nothing special about it, please refer to the abundant documentation on the topic of Neovim configuration and plugins in Lua.
You must commit the Lua into your configuration or plugin so that it can be loaded by native Neovim Lua systems, with absolutely no knowledge of the Fennel it originated from.
Configuration
nfnl is configured on a per directory basis using .nfnl.fnl files which also
signify that the plugin should operate on the files within this directory.
Without the file the plugin is inert, meaning even if you don't lazy load it you
won't see any performance impact at startup time.
Any configuration you don't provide (an empty file or just {} is absolutely
fine!) will default to these values that should work fine for most people.
{;; Enables verbose notifications from nfnl, including notifications about
;; when it starts up and when it compiles successfully. Useful for debugging
;; the plugin itself and checking that it's running when you expect it to.
:verbose false
;; When false, will not write the protective `-- [nfnl] ...` header to the resulting .lua files.
;; It will also prevent nfnl from checking for this header before overwriting .lua files.
;; Intended for users who wish to write shebang comments at the top of their .lua for easier execution.
:header-comment true
:orphan-detection
{;; Automatically invoke :NfnlFindOrphans whenever you write to a .fnl file. This acts as a passive
;; garbage collection check and will warn you about dangling .lua files left over from .fnl deletions or renames.
;; If an orphan is found, you can use :NfnlDeleteOrphans to delete all of the files listed
;; automatically. The deletion command will NOT prompt you, so make sure you check the list first!
:auto? true
;; If you embed nfnl, for example, in your Lua directory then nfnl will detect those files as orphans.
;; You can add Lua patterns here to tell nfnl which Lua paths are okay and should not be counted as orphans.
;; Example: ["lua/nfnl/"]
:ignore-patterns []}
;; Passed to fennel.compileString when your code is compiled.
;; See https://fennel-lang.org/api for more information.
:compiler-options {;; Disables ansi escape sequences in compiler output.
:error-pinpoint false}
;; Warning! In reality these paths are absolute and set to the root directory
;; of your project (where your .nfnl.fnl file is). This means even if you
;; open a .fnl file from outside your project's cwd the compiler will still
;; find your macro files. If you use relative paths like I'm demonstrating here
;; then macros will only work if your cwd is in the project you're working on.
;; They also use OS specific path separators, what you see below is just an example really.
;; I'm not including nfnl's directory from your runtimepath, but it would be there by default.
;; See :rtp-patterns below for more information on including other plugins in your path.
;; String to set the compiler's fennel.path to before compilation.
:fennel-path "/home/.../?.fnlm;/home/.../?/init.fnlm;/home/.../fnl/?.fnlm;/home/.../fnl/?/init.fnlm;/home/.../?.fnl;/home/.../?/init.fnl;/home/.../?/init-macros.fnl;/home/.../fnl/?.fnl;/home/.../fnl/?/init.fnl;/home/.../fnl/?/init-macros.fnl"
;; String to set the compiler's fennel.macro-path to before compilation.
:fennel-macro-path "/home/.../?.fnlm;/home/.../?/init.fnlm;/home/.../fnl/?.fnlm;/home/.../fnl/?/init.fnlm;/home/.../?.fnl;/home/.../?/init.fnl;/home/.../?/init-macros.fnl;/home/.../fnl/?.fnl;/home/.../fnl/?/init.fnl;/home/.../fnl/?/init-macros.fnl"
;; A list of glob patterns (autocmd pattern syntax) of files that
;; should be compiled. This is used as configuration for the BufWritePost
;; autocmd, so it'll only apply to buffers you're interested in.
;; Will use backslashes on Windows.
;; Defaults to compiling all .fnl files, you may want to limit it to your fnl/ directory.
:source-file-patterns [".*.fnl" "*.fnl" "**/*.fnl"]
;; A function that is given the absolute path of a Fennel file and should return
;; the equivalent Lua path, by default this will translate `fnl/foo/bar.fnl` to `lua/foo/bar.lua`.
;; See the "Writing Lua elsewhere" tip below for an example function that writes to a sub directory.
:fnl-path->lua-path (fn [fnl-path] ...)}
As an example, if you only want to compile .fnl files under the fnl/
directory of your Neovim configuration (so nothing in the root directory) you
could use this .nfnl.fnl file instead.
{:source-file-patterns ["fnl/**/*.fnl"]}
And since this is a Fennel file that's executed within Neovim you can actually load nfnl's modules to access things like the default config values.
(local core (require :nfnl.core))
(local config (require :nfnl.config))
(local default (config.default))
{:source-file-patterns (core.concat default.source-file-patterns ["custom-dir/*.fnl"])}
config.default accepts a table of arguments ([docs][config-default-doc]) to
change how it builds the default configuration. You can call
(config.default {...}) on the last line of your .nfnl.fnl file in order to
return a modified default configuration table. You also then have the option to
call config.default, modify that table with extra values and then return that.
By providing a different rtp-patterns value (defaults to ["/nfnl$"]) we can
include other plugins you have installed in your search paths when requiring Lua
modules or macros.
;; Configuration that includes nfnl _and_ your-cool-plugin in the search paths.
(local config (require :nfnl.config))
(config.default {:rtp-patterns ["/nfnl$" "/your-cool-plugin$"]})
;; Configuration that includes ALL of your installed plugins in your search paths.
;; This might slow down compilation on some machines, so it's not the default.
(local config (require :nfnl.config))
(config.default {:rtp-patterns [".*"]})
;; Searching all of your plugins _and_ merging in some other custom configuration.
(local core (require :nfnl.core))
(local config (require :nfnl.config))
(core.merge
(config.default {:rtp-patterns [".*"]})
{:source-file-patterns ["fnl/**/*.fnl"]})
Installation
- [Lazy][lazy]:
{ "Olical/nfnl", ft = "fennel" } - [Plug][plug]:
Plug 'Olical/nfnl' - [Packer][packer]:
use "Olical/nfnl"
[Lazy][lazy] will lazily load the plugin when you enter a Fennel file for the
first time. There is no need to call require("nfnl").setup() right now, it's
currently a noop but it may be used eventually. Some plugin managers support
this function and will call it automatically.
- Requires Neovim > v0.9.0.
- Add the dependency to your plugin manager.
- Add lazy loading rules on the Fennel filetype if you want.
Standard library
nfnl ships with a standard library used mostly for it's internal implementation, but it can also be used by your own Neovim configuration or plugins. This is based on [Aniseed's][aniseed] standard library but without the module system that prevented you from using it in standard, non-Neovim, Fennel projects.
Full API documentation powered by [fenneldoc][fenneldoc] can be found in the [api][apidoc] directory.
The documentation is regenerated by executing
./script/render-api-documentation. One limitation of using this tool is that
all top level values in a module should really be functions, if we do work
inside (local) for example we'll end up incurring side effects at
documentation rendering time that we may not want.
Macros
Fennel allows you to write inline macros with the (macro ...) form but they're restricted to only being used in that one file. If you wish to have a macro module shared by the rest of your codebase you need to mark that file as a macro module either by giving it the .fnlm suffix or by placing ;; [nfnl-macro] somewhere within the source code. The exact amount of
