One of the most anticipated and loved features by Neovim users is the lua support.
This support officially came in version 0.5 of the neovim, which went on to allow the users to throw away stop using their init.vim and set up the neovim with a nice init.lua.
And a happy consequence of this is that we can not only use Lua, but we can use lua ecosystem packages and languages that compile to Lua
Hello, Peter Fennel
Fennel is one of these languages that compile to Lua, which means you will write Fennel code and the compiler will generate Lua code, which will be read and run by neovim.
Fennel -> Lua -> Neovim
But why Fennel?
This is a very common question that people ask when I say that I use Fennel.
Lua is a great language, but some points can facilitate the occurrence of errors.
One of the points is the ease with which you access or change a global variable.
If you create a variable without the local keyword it is already a global variable. And to access the value of that variable, you just need to type the name of the variable, which can cause unexpected behavior, for example:
-- settings.luamyVar='this is global'-- init.lualocalmyVal='local'print('this is the value of local: '..myVar)-- Oops
Note that, due to a typing error in the word myVal, replacing l with r we end up accessing the value of a global variable defined elsewhere.
Errors like this can be difficult to find out.
Fennel prevents errors like this by allowing access to global variables only through the _G table.
When trying to simulate the above case in Fennel, the compiler will alert us that the myVar variable does not exist.
(local myVal "local")
(print (.. "This is the value of local: " myVar))
:: COMPILE ERROR
xxx settings/globals.fnl
Compile error in settings/globals.fnl:42
unknown identifier in strict mode: myVar
* Try looking to see if there's a typo.
* Try using the _G table instead, eg. _G.myVar if you really want a global.
* Try moving this code to somewhere that myVar is in scope.
* Try binding myVar as a local in the scope of this code.
Another point, which can facilitate the occurrence of Lua errors, is the lack of validation of the number of arguments of a function.
Notice that I only passed 1 argument, and the function works with 2 parameters, the code in Lua ran without telling me that I forgot to pass another argument to the function.
In Fennel, we can use the lambda keyword to create functions that validate parameters:
(lambda my-and [x y]
(and x y))
(print (my-and true)) ; Error [...] Missing argument y
Note: In Fennel, ; is the character used to start a comment
(Syntax (from (Lisp!)))
This is a bit controversial because some people don't like Lisp's syntax, but it has some benefits:
Everything is an expression, i.e. we don't have statements
When dealing with operators, there is no ambiguity of what comes first, we have no "operator precedence". (In lua, for example, A or B and C or D)
These points make Fennel a very simple language to program and maintain.
Modernity and facilities
In addition to the points mentioned above, it is worth highlighting some interesting features that Fennel brings to make our lives easier.
With Fennel, we have destructuring, pattern matching, macros and more.
Tangerine integrates Fennel with Neovim very transparently, compiling Fennel files to Lua and bringing some interesting tools.
hibiscus brings several macros related to the Neovim ecosystem that help us to write less
The first step is to create the ~/.config/nvim/plugin/0-tangerine.lua file with the content:
localfunctionbootstrap(name,url,path)ifvim.fn.isdirectory(path)==0thenprint(name..": installing in data dir...")vim.fn.system{"git","clone","--depth","1",url,path}vim.cmd[[redraw]]print(name..": finished installing")endendbootstrap("tangerine.nvim","https://github.com/udayvir-singh/tangerine.nvim",vim.fn.stdpath"data".."/site/pack/packer/start/tangerine.nvim")bootstrap("hibiscus.nvim","https://github.com/udayvir-singh/hibiscus.nvim",vim.fn.stdpath"data".."/site/pack/packer/start/hibiscus.nvim")require'tangerine'.setup{compiler={verbose=false,hooks={"onsave","oninit"}}}
This setup assumes you use Packer to manage your plugins, if you don't, check the Tangerine repository for how to install it in your plugin manager.
With this, when restarting Neovim, tangerine and Hibiscus will be downloaded and initialized.
This means you can now start configuring Neovim in Fennel by creating a ~/.config/nvim/init.fnl file π
When you save this file, tangerine will already compile it and generate the lua file to be loaded by Neovim, not requiring any additional configuration for this.
Tips for getting started with Fennel
Documentation is your friend!
The two best sources for understanding how Fennel works are Fennel's tutorial and reference.
I'll advance some simple things for you to understand the basics.
Parentheses
You will see a lot of parentheses in Fennel, they serve to delimit where an expression starts and ends.
For example, to declare a variable in the local scope, in Lua, you use the keyword local, whereas in Fennel you call the functionlocal:
(local myVar "myValue") ; myVar = "myValue"
If the value of the variable is the result of a concatenation, we will not use the operator .., but the function..:
In short, every function you call will be enclosed in parentheses.
Neovim API
Everything you do with Lua, you do with Fennel, so the same call you make to a Neovim API in Lua, you'll make in Fennel.
This, on Lua:
-- Luaprint(vim.fn.stdpath"config")
this is it, in fennel:
(print (vim.fn.stdpath :config))
:symbol
You may have already noticed that, in some cases, I wrote some strings in Lua using : in fennel (if you didn't, just look at the last example)
This is another way to write a string. However, to write in this format, the string cannot contain spaces.
(= :str "str") ; true
Tangerine Mappings
The plugin we use to integrate Fennel with Neovim has some mappings and commands that help us write code that will generate what we want. I'll list the ones I use the most below:
gL
It can be run both in normal mode and in visual mode.
This mapping shows the Lua code that your Fennel code will generate.
For example, if I press gL after selecting the snippet:
(lambda add [x y]
(+ x y))
It opens a window containing the code in Lua:
localfunctionadd(x,y)_G.assert((nil~=y),"Missing argument y on globals.fnl:1")_G.assert((nil~=x),"Missing argument x on globals.fnl:1")return(x+y)endreturnadd
Very useful for quickly checking that the fennel code you are writing will generate the Lua code you expect.
Note: This mapping doesn't work very well in visual mode if you select a snippet that uses some macro unless the snippet has the macro import.
gO
This mapping opens the lua file compiled by the open fennel file, that is, if you have the plugins.fnl file open and press gO, it will open the plugins.lua file that was generated by the compilation of plugins.fnl.
Very useful for debugging.
:Fnl
You can run any code in Fennel with the :Fnl command, just like :lua.
:Fnl (print "hey")
Will print hey, equivalent to :lua print("hey")
Now it's up to you
From here, you're ready to have some fun using Fennel (and it is).