Setting Up Neovim with Fennel
Tags: neovim, fennel, lua, vim
Published: 2023-06-06 09:34:56

---
title: Setting Up Neovim with Fennel
published: true
description: Let's find out what's fennel, why you may want to use it to configure your Neovim, and how you do it
tags: #neovim #fennel #lua #vim
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0togmu896suwya4h7lxe.png
---

## It all starts on the ~~moon~~ Lua

One of the most anticipated and loved features by Neovim users is the [lua](https://www.lua.org/) 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](https://luarocks.org/) 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.

It seems that's a common question that people ask everyone who uses Fennel because the official Fennel website [has the answer to exactly this question](https://fennel-lang.org/rationale ).

I will summarize their response in a few points.

#### Fennel is less error-prone

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:

```lua
-- settings.lua
myVar = 'this is global'


-- init.lua
local myVal = '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.

```fennel
(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.

```lua
local function myAnd(x, y)
  return x and y
end
print(myAnd(true)) -- nil
```

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:

```fennel
(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.

##### Destructuring

While on Lua we do:
```lua
-- Lua
local var = require'module'.var
local var2 = require'module'.var2
```
In fennel, we can simply do:
```fennel
; Fennel
(local {: var : var2} (require :module))
```

##### Pattern matching

When we want to test the value of a variable several times, in Lua, we make an `if` sequence:
```lua
-- Lua
local function get_desc(key)
  if (key == "k1") then
    return "Key 1"
  elseif (key == "k2") then
    return "Key 2"
  elseif (key == "k3") then
    return "Key 3"
  else
    return nil
end
```
Whereas, in fennel, we can use `match`:
```fennel
; Fennel
(lambda get-desc [key]
  (match key
    :k1 "Key 1"
    :k2 "Key 2"
    :k3 "Key 3"))
```

### How to get started

Now that I've convinced you (at least I hope) to use Fennel, let's see how to start using it to configure Neovim!

We will use two plugins for this:

1. tangerine {% embed https://github.com/udayvir-singh/tangerine.nvim %}

2. hibiscus {% embed https://github.com/udayvir-singh/hibiscus.nvim %}

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:

```lua
local function bootstrap (name, url, path)
	if vim.fn.isdirectory(path) == 0 then
		print(name .. ": installing in data dir...")

		vim.fn.system {"git", "clone", "--depth", "1", url, path}

		vim.cmd [[redraw]]
		print(name .. ": finished installing")
	end
end

bootstrap (
  "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](https://fennel-lang.org/tutorial) and [reference](https://fennel-lang.org/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 **function** `local`:

```fennel
(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** `..`:

```fennel
(local name "Fennel")
(local myVar (.. "Hello, " name)) ; myVar = "Hello, Fennel"
```

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:
```lua
-- Lua
print(vim.fn.stdpath"config")
```
this is it, in fennel:
```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.

```fennel
(= :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:

```fennel
(lambda add [x y]
  (+ x y))
```

It opens a window containing the code in Lua:
```lua
local function add(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)
end
return add
```
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).

Any questions, ask here in the comments!

Happy Vimming!