Configurando Neovim com Fennel
Tags: neovim, fennel, lua, vim
Published: 2022-05-20 19:26:38

---
title: Configurando Neovim com Fennel
published: true
description: 
tags: #neovim #fennel #lua #vim
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0togmu896suwya4h7lxe.png
---

## Tudo começa na Lua

Uma das funcionalidades mais aguardadas e amadas pelos usuários do Neovim é o suporte à [lua](https://www.lua.org/).

Esse suporte veio, oficialmente, na versão 0.5 do Neovim, o que passou a permitir que os usuários pudessem ~~jogar no lixo~~ deixar de usar seus `init.vim` e configurar o Neovim usando um belo `init.lua`.

E uma feliz consequência disso é que, não só podemos usar lua, mas podemos usar [pacotes do ecossistema lua](https://luarocks.org/) e **linguagens que compilam
para lua**

## Hello, ~~Peter~~ Fennel

Fennel é uma dessas linguagens que compilam para lua, o que significa que você vai escrever código fennel, o compilador vai gerar código lua, que vai ser lido e executado pelo Neovim.

```
Fennel -> Lua -> Neovim
```

### Mas, por que Fennel?

Essa é uma pergunta bastante comum que as pessoas fazem quando eu digo que uso Fennel.

Na verdade, é uma pergunta comum que as pessoas fazem para todos que usam Fennel, pelo que parece, porque no site oficial do Fennel [tem a resposta para, exatamente, essa pergunta](https://fennel-lang.org/rationale).

Vou resumir a resposta deles em alguns pontos.

#### Fennel é menos propenso à alguns erros

Lua é uma ótima linguagem, porém existem alguns pontos que podem facilitar a ocorrência de erros.

Um dos pontos é a facilidade com que você acessa ou altera uma variável global.
Se você criar uma variável sem a palavra chave `local` ela já é uma variável global. E para acessar o valor dessa variável, basta digitar o nome da variável, o que pode causar comportamentos inesperados, por exemplo:
```lua
-- settings.lua
myVar = 'this is global'


-- init.lua
local myVal = 'local'
print('this is the value of local: ' .. myVar)
```

Repare que, por um erro de digitação da palavra `myVal`, trocando o `l` pelo `r` acabamos acessando o valor de uma variável global definida em outro lugar.
Erros como esse podem ser difíceis de descobrir.

Fennel previne erros como esse permitindo que o acesso à variáveis globais sejam feitos apenas através da tabela `_G`.
Ao tentar simular o caso acima em Fennel, o compilador nos alertará que a variável `myVar` não existe.

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

Outro ponto, que pode facilitar a ocorrência de erros em lua, é a falta de validação do número de argumentos de uma função.

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

Repare que eu apenas passei 1 argumento, sendo que a função trabalha com 2 parâmetros, o código em lua rodou sem me avisar que esqueci de passar outro argumento para a função.

Em Fennel, podemos usar a palavra chave `lambda` para criar funções que validam os parâmetros:

```fennel
(lambda my-and [x y]
  (and x y))

(print (my-and true)) ; Error [...] Missing argument y
```

*Nota: Em fennel, `;` é o caractere usado para iniciar um comentário*

#### (Sintaxe (do (Lisp!)))

Esse é um ponto um pouco polêmico porque algumas pessoas não gostam da sintaxe do Lisp, mas ela traz alguns benefícios:

- Tudo é uma expressão, ou seja, não temos _statements_
- Quando lidamos com operadores, não há ambiguidade do que vem primeiro, não temos "precedência de operadores". (Em lua, por exemplo, `A or B and C or D`)

Esses pontos tornam Fennel uma linguagem muito simples de se programar e de se dar manutenção.

#### Modernidade e facilidades

Além dos pontos mencionados acima, vale a pena ressaltar algumas funcionalidades interessantes que o Fennel traz para facilitar a nossa vida.

Com Fennel, temos _desestruturação_, _pattern matching_, _macros_ e mais.

##### Desestruturação

Enquanto em lua fazemos:
```lua
-- Lua
local var = require'module'.var
local var2 = require'module'.var2
```
Em fennel, podemos, simplesmente, fazer:
```fennel
; Fennel
(local {: var : var2} (require :module))
```

##### Pattern matching

Quando queremos testar o valor de uma variável várias vezes, em lua, fazemos uma sequência de `if`:
```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
```
Enquanto, em fennel, podemos usar o `match`:
```fennel
; Fennel
(lambda get-desc [key]
  (match key
    :k1 "Key 1"
    :k2 "Key 2"
    :k3 "Key 3"))
```

### Como começar a usar

Agora que eu te convenci (pelo menos espero) a usar fennel, vamos ver como começar a usa-lo para configurar o Neovim!

Vamos usar dois plugins para isso:

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

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

O tangerine integra de maneira bem transparente o Fennel com o Neovim, compilando os arquivos fennel para lua e trazendo umas ferramentas interessantes.
O hibiscus traz várias macros relacionadas ao ecossistema do Neovim que nos ajudam a escrever menos

O primeiro passo é criar o arquivo `~/.config/nvim/plugin/0-tangerine.lua` com o conteúdo:

```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" }
  }
}
```

> Essa configuração supõe que você usa o _Packer_ para gerenciar seus plugins, se você não usa, verifique no repositório do tangerine como instalar no seu gerenciador.

Com isso, ao reiniciar o Neovim, o tangerine e o hibiscus vão ser baixados e inicializados.

Isso significa que você já pode começar a configurar o Neovim em Fennel, criando um arquivo `~/.config/nvim/init.fnl` 🎉

Quando você salvar esse arquivo, o tangerine já vai compila-lo e gerar o arquivo lua para ser carregado pelo Neovim, não sendo necessário fazer nenhuma configuração adicional para isso.

### Dicas para começar com Fennel

A documentação é sua amiga!

As duas melhores fontes para entender como o Fennel funciona é o [tutorial](https://fennel-lang.org/tutorial) e a [referência](https://fennel-lang.org/reference) do Fennel.

Vou adiantar algumas coisas simples para você já entender o básico.

#### Parênteses

Você vai ver muitos parênteses no Fennel, eles servem para delimitar onde uma expressão começa e termina.

Por exemplo, para declarar uma variável no escopo local, em lua, você usa a palavra chave `local`, já em fennel, você chama a **função** `local`:

```fennel
(local myVar "myValue") ; myVar = "myValue"
```

Se o valor da variável é resultado de uma concatenação, não vamos usar o operador `..`, mas sim, a **função** `..`:

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

Resumindo, toda a função que você chamar, você vai colocar entre parênteses.

#### API do Neovim

Tudo o que você faz com lua, você faz com fennel, então a mesma chamada que você faz para uma API do Neovim em lua, você vai fazer em Fennel.
Isso, em lua:
```lua
-- Lua
print(vim.fn.stdpath"config")
```
é isso, em fennel:
```fennel
(print (vim.fn.stdpath :config))
```

#### :symbol

Você já deve ter reparado que, em alguns casos, eu escrevi algumas strings em lua usando `:` em fennel (se não reparou, basta olhar para o último exemplo)

Isso, basicamente, é outro jeito de escrever uma string. No entanto, para escrever nesse formato, a string não pode conter espaços.

```fennel
(= :str "str") ; true
```

### Mapeamentos do tangerine

O plugin que usamos para integrar o Fennel com o Neovim tem uns mapeamentos e comandos que nos ajudam a garantir que estamos escrevendo um código que vai gerar o que queremos. Vou listar abaixo os que eu mais uso:

#### gL

Pode ser executado tanto no modo normal, quando no modo visual.
Esse mapeamento mostra o código lua que o seu código em Fennel vai gerar.
Por exemplo, se eu apertar `gL` após selecionar o trecho:

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

Ele abre uma janela contendo o código em 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
```
Bem útil para checar rapidamente se o código fennel que você está escrevendo vai gerar o código lua que você espera.

*Nota: Esse mapeamento não funciona muito bem no visual mode se você selecionar um trecho que usa alguma macro, a não ser que no trecho tenha a importação da macro.*

#### gO

Esse mapeamento abre o arquivo lua compilado pelo arquivo fennel que está aberto, ou seja, se você está com o arquivo `plugins.fnl` aberto e aperta `gO`, ele vai abrir o arquivo `plugins.lua` que foi gerado pela compilação do `plugins.fnl`.
Muito útil para fazer debug.

#### :Fnl

Com o comando `:Fnl` você consegue executar qualquer código em fennel, da mesma forma que `:lua`.

`:Fnl (print "hey")`

Vai imprimir `hey`, equivalente à `:lua print("hey")`

## Agora é contigo

A partir daqui, você já está preparado para se divertir usando Fennel (e é divertido mesmo).

Qualquer dúvida, pergunta aqui nos comentários!

Happy Vimming!