Configurando Neovim com Fennel

dmass

Douglas Massolari

Posted on May 20, 2022

Configurando Neovim com Fennel

Tudo começa na Lua

Uma das funcionalidades mais aguardadas e amadas pelos usuários do Neovim é o suporte à lua.

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 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
Enter fullscreen mode Exit fullscreen mode

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.

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:

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


-- init.lua
local myVal = 'local'
print('this is the value of local: ' .. myVar)
Enter fullscreen mode Exit fullscreen mode

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.

(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.
Enter fullscreen mode Exit fullscreen mode

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.

local function myAnd(x, y)
  return x and y
end
print(myAnd(true)) -- nil
Enter fullscreen mode Exit fullscreen mode

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:

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

(print (my-and true)) ; Error [...] Missing argument y
Enter fullscreen mode Exit fullscreen mode

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
local var = require'module'.var
local var2 = require'module'.var2
Enter fullscreen mode Exit fullscreen mode

Em fennel, podemos, simplesmente, fazer:

; Fennel
(local {: var : var2} (require :module))
Enter fullscreen mode Exit fullscreen mode
Pattern matching

Quando queremos testar o valor de uma variável várias vezes, em lua, fazemos uma sequência de if:

-- 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
Enter fullscreen mode Exit fullscreen mode

Enquanto, em fennel, podemos usar o match:

; Fennel
(lambda get-desc [key]
  (match key
    :k1 "Key 1"
    :k2 "Key 2"
    :k3 "Key 3"))
Enter fullscreen mode Exit fullscreen mode

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

    GitHub logo udayvir-singh / tangerine.nvim

    🍊 Sweet Fennel integration for Neovim

    🍊 Tangerine 🍊

    Neovim version GNU Neovim in Emacs version

    AboutInstallationSetupCommandsAPIDevelopment

    About

    Tangerine provides a painless way to add fennel to your config.

    Features

    • 🔥 BLAZING fast, compile times in milliseconds
    • 🌊 100% support for interactive evaluation
    • 🎍 Control over when and how to compile
    • 🎀 Natively loads nvim/init.fnl

    Comparison to other plugins

    HOTPOT 🍲

    • Abstracts too much away from the user.
    • Hooks onto lua package searchers to compile [harder to debug].

    ANISEED 🌿

    • Excessively feature rich for use in dotfiles.
    • Blindly compiles all files that it founds, resulting in slow load times.

    Installation

    1. Create file plugin/0-tangerine.lua to bootstrap tangerine:

    Important

    If you are using lazy.nvim then you should create init.lua instead of plugin/0-tangerine.lua.

    Refer to #20 for more information.

    -- ~/.config/nvim/plugin/0-tangerine.lua or ~/.config/nvim/init.lua
    -- pick your plugin manager
    local pack = "tangerine" or "packer" or "paq" or "lazy
    Enter fullscreen mode Exit fullscreen mode
  2. hibiscus




    GitHub logo

    udayvir-singh
    /
    hibiscus.nvim



    🌺 Flavored Fennel Macros for Neovim




    Hibiscus.nvim

    🌺 Highly opinionated macros to elegantly write your neovim config.

    Companion library for tangerine but it can also be used standalone.

    Neovim version

    Rational

    • 🍬 Syntactic eye candy over hellscape of lua api
    • 🎋 Provides missing features in both fennel and nvim api

    Installation

    • Create file plugin/0-tangerine.lua to bootstrap hibiscus:

    NOTE: if you are using lazy plugin manager, you should create /init.lua instead.

    -- ~/.config/nvim/plugin/0-tangerine.lua or ~/.config/nvim/init.lua
    -- pick your plugin manager
    local pack = "tangerine" or "packer" or "paq" or "lazy"
    
    local function bootstrap(url, ref)
        local name = url:gsub(".*/", "")
        local path
    
        if pack == "lazy" then
            path = vim.fn.stdpath("data") .. "/lazy/" .. name
            vim.opt.rtp:prepend(path)
        else
    Enter fullscreen mode Exit fullscreen mode

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:

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" }
  }
}
Enter fullscreen mode Exit fullscreen mode

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 e a referência 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:

(local myVar "myValue") ; myVar = "myValue"
Enter fullscreen mode Exit fullscreen mode

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

(local name "Fennel")
(local myVar (.. "Hello, " name)) ; myVar = "Hello, Fennel"
Enter fullscreen mode Exit fullscreen mode

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
print(vim.fn.stdpath"config")
Enter fullscreen mode Exit fullscreen mode

é isso, em fennel:

(print (vim.fn.stdpath :config))
Enter fullscreen mode Exit fullscreen mode

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

(= :str "str") ; true
Enter fullscreen mode Exit fullscreen mode

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:

(lambda add [x y]
  (+ x y))
Enter fullscreen mode Exit fullscreen mode

Ele abre uma janela contendo o código em 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
Enter fullscreen mode Exit fullscreen mode

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!

💖 💪 🙅 🚩
dmass
Douglas Massolari

Posted on May 20, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

Setting Up Neovim with Fennel
neovim Setting Up Neovim with Fennel

June 6, 2023

Configurando Neovim com Fennel
neovim Configurando Neovim com Fennel

May 20, 2022