Russ Tokuyama

Enhance your Neovim with Fennel

Last update: 07/31/2023
Back to home
Back to Posts

This is a report about my experience using the nfnl plugin by Oliver Caldwell to create a plugin for Neovim. nfnl allows me to use Fennel instead of Vimscript (VimL). Many thanks, Oliver!

Of course, this won't work with Vim because we're using Fennel compiled to Lua to make Neovim behave the way we want it to. Neovim understands both VimL and Lua. Nice!

Truth in advertising:

You will need to learn some Vimscript and Lua and how to script Vim when you venture into writing plugins or setting up configurations. So many ways of doing things. It hard to keep things straight and know when to use what for the problem you're trying to solve.

Set Up For A Plugin

For this example, we'll use my-plugin as the plugin name.

  • Install the nfnl plugin and restart Neovim.

  • Create project directory and switch to it.

    $ mkdir my-plugin
    $ cd my-plugin
    
  • Add these subdirectories:

    • fnl/my-plugin/
    • lua/my-plugin/
    • plugin/
    $ mkdir -p lua/my-plugin fnl/my-plugin plugin
    
  • Add this configuration to .nfnl.fnl at the top of the project directory.

    {:source-file-patterns ["fnl/**/*.fnl"]}
    

    This configuration will keep the .fnl and .lua files in separate directories using a parallel directory structure. If you don't mind them being in the same directory, then don't use separate directories and just put {} in your .nfnl.fnl file.

Add The Code

For this example, we'll assume all the hard work of figuring out what to implement in code is done.

  • Create plugin/my-plugin.vim with this inside:

    if has("nvim")
      lua require("my-plugin.main").init()
    endif
    

    When this plugin loads, it will cause the init function in main.lua to be run. A deeper explanation of this is beyond this report. See :he write-plugin for more about writing plugins.

  • Create fnl/my-plugin/main.fnl with this inside:

    (local {: autoload} (require :nfnl.module))
    (local core (autoload :nfnl.core))
    
    (local mod {})
    
    (local msg "Tears began to fall from my-plugin! -- Frank Z made me do it.")
    
    (fn mod.init []
      (print "This is msg:" msg)
      (print "  Now just a part of it: >" (string.sub msg 5) "<"))
    
    mod
    

    Alternative to using a local mod variable:

    (local {: autoload} (require :nfnl.module))
    (local core (autoload :nfnl.core))
    
    (local msg "Tears began to fall from my-plugin! -- Frank Z made me do it.")
    
    (fn init []
      (print "This is msg:" msg)
      (print "  Now just a part of it: >" (string.sub msg 5) "<"))
    
    {: init} ;; Export only these functions. The rest are "local".
    

Compile The Fennel Code

Just save your Fennel file. That's it!

nfnl will recognize your .nfnl.fnl file and take it from there.

The first time that you save the first Fennel file, nfnl will prompt you to confirm that it is OK to proceed. You'll see something like this in the command output area.

.../my-plugin/.nfnl.fnl is not trusted.
[i]gnore, (v)iew, (d)eny, (a)llow: a

You will only need to have the nfnl.fnl file in the top level directory of your project. The confirmation question will only be asked the first time that you save a Fennel file anywhere under your project directory.

The project directory structure looks like this:

├── .nfnl.fnl
├── fnl
│   └── my-plugin
│       └── main.fnl
├── lua
│   └── my-plugin
│       └── main.lua
└── plugin
    └── my-plugin.vim

Install Your Plugin

So now that everything is working, we add the my-plugin plugin to our Neovim configuration. This will depend on which plugin manager you use.

Testing The Plugin

When you start Neovim without any arguments, you should see this message appear below the status line:

This is msg: Tears began to fall from my-plugin! -- Frank Z made me do it.
  Now just a part of it: > s began to fall from my-plugin! -- Frank Z made me do it. <

This is confirmation that the plugin is being loaded and called successfully. Hello, world!

Retrospective

It was much easier to create the plugin using nfnl compared to Aniseed. There's less setup and things to do to get things to work.

I recommend using this as just one way to start to create plugins.

It all appears so easy now but the truth is I fumbled around a lot before things came together. I tried setting up a plugin using Oliver's Aniseed. That experience of getting the equivalent of a Hello, world! program running was used here.

The Good News :-)

The plugin's author quickly responded to my problem with the plugin not automatically compiling a Fennel file. I was using Packer and some unknown reason (probably my unfamiliarity with Fennel, Lua, and Aniseed) the FileType autocmd wasn't created for nfnl. That's not a problem any more and things just work.