Criando plugins Lua para Neovim - Parte 3
Tags: neovim, vim, plugins, lua
Published: 2022-05-09 17:20:28

> Migrando do init.vim para o init.lua

---

Para fechar nossa série de criação de plugins em Lua para o Neovim, vamos mudar um pouco o foco na criação de plugins para um tutorial de como podemos migrar toda nossa configuração do Neovim usando o tradicional Vimscript, para Lua.

> Mas por que Lua?

A decisão tomada pelo _core team_ de desenvolvimento do Neovim tem várias respostas diversas, que vão desde velocidade a facilidade de escrita, pelo fato de Lua ser uma linguagem bem extensível. Para entender as principais motivações, sugiro dar uma pausa no post e apreciar a talk “_We can have nice things_”, do Justin M. Keyes, um dos atuais líderes de desenvolvimento do Neovim.

{% embed https://www.youtube.com/watch?v=Bt-vmPC_-Ho %}

## A migração

Caso você ainda não conheça a estrutura de diretórios do Neovim, toda configuração vai em:

Unix: `~/.config/nvim/init.vim`
Windows: `~/AppData/Local/nvim/init.vim`
ou, caso você queria um path de config customizado, onde `$XDG_CONFIG_HOME` é definido, fica assim:

`$XDG_CONFIG_HOME/nvim/init.vim`

Esse arquivo é o ponto de entrada para as configurações customizadas do editor, desde cores, tamanhos de fonte, a instalação de plugins.

No nosso caso, começaremos renomeando o arquivo `init.vim` para `init.old` e criando um novo arquivo chamado init.lua.

> Por quê precisamos renomear o init.vim?

No neovim não é possível existir 2 pontos de entrada para as configurações, então você só poderá usar o `init.vim` ou o mais recente, `init.lua`

Para o nosso tutorial não ficar bastante extenso, iremos usar um `init.vim` relativamente pequeno, explicando as principais configurações e como portá-las para a versão em lua.

Mas primeiro precisamos relembrar como a API em Lua se comporta.

## O namespace `vim`

Na implementação da API Lua para o Neovim, existe uma variável chamada `vim`, que é o ponto de entrada para todas as funções da chamada “biblioteca padrão” Lua para o Neovim. Essas funções servem diferentes propósitos, mas o principal sendo a ponte entre o código lua e o editor. Algumas APIs incluem:

- `vim.inspect` - uma função de debug para inspecionar resultados. Geralmente em lua, os resultados vem em tables, e esses são difíceis de ser expostos em formato de “print”. Essa função “disseca” a váriável que você gostaria de expor de uma forma mais amigável.

- `vim.regex` - Módulo que possibilita o uso de regexes diretamente no código Lua. Para mais info `:h vim.regex`

- `vim.api` - Provavelmente o módulo mais importante dessa API. Aqui encontramos todas a funções para trabalhar com buffers, windows, chamar comandos do neovim, etc.

- `vim.loop` - Módulo que funciona como uma camada de abstração da `LibUV`, a camada de _event-loop_ do Neovim. Pra quem quer usar concorrência, timers, etc, esse módulo é o caminho.

- `vim.lsp` - Pra quem trabalhar diretamente na fantástica implementação nativa de um cliente LSP dentro do Neovim.

- `vim.treesitter` - O módulo que expõe algumas funções para se trabalhar diretamente com o tree-sitter.

- `vim.keymap` - Inclua ou remova keymaps do vim

Para este post, focaremos apenas na `vim.api` e `vim.keymap`

## Nosso arquivo init.lua

Levando em consideração que nosso `init.vim` tinha a seguinte cara:

```vim
" Parte 1
set colorcolumn=80
set expandtab
set tabstop=4
set autoindent
set incsearch
set hlsearch
set termguicolors

" Parte 2
let g:minha_config = 'valor'
let g:algumnamespace#teste#variavel = "valor"

" Parte 3
nmap <leader>, <Cmd>:noh<CR>
nnoremap <leader>h <Cmd>split<CR>
vnoremap < <gv

" Parte 4
autocmd BufEnter * echo "hello nvim"

" Parte 5
function! SayHello()
  echohl WarningMsg
  echo "Hello World"
  echohl
endfunction`
```

separamos a migração em 4 partes:

- options
- vars
- mappings
- autocmds
- functions

Para a parte 1, temos as seguintes funções da `vim.api`:

- `vim.api.nvim_set_option`
- `vim.api.nvim_buf_set_option`
- `vim.api.nvim_win_set_option`

Para facilitar a escrita, temos o atalho `vim.opt`:

```lua
vim.opt.termguicolors = true   -- set termguicolors
vim.opt.colorcolumn = 80       -- set colorcolumn=80
vim.opt.expandtab              -- set expandtab
vim.opt.tabstop=4              -- set tabstop=4
vim.opt.autoindent = true      -- set autoindent
vim.opt.incsearch = true       -- set incsearch
vim.opt.hlsearch = true        -- set hlsearch
```


Continuando com a **parte 2**, temos uma declaração de uma variável global (escopo `g:`). Na API, podemos utilizar as seguintes funções:

`vim.api.nvim_set_var` e suas variações (`vim.api.nvim_get_var` e `vim.api.nvim_del_var`)
Aqui também temos uma versão “minimalista” com seus respectivo _meta-acessor_ (ver `:h lua-vim-variables` para mais detalhes):

`vim.g` - Também funciona como um getter/setter

Temos então nosso código lua, com a parte 2:

```lua
-- parte 1
vim.opt.termguicolors = true   -- set termguicolors
vim.opt.colorcolumn = 80       -- set colorcolumn=80
vim.opt.expandtab = true       -- set expandtab
vim.opt.tabstop = 4            -- set tabstop=4
vim.opt.autoindent = true      -- set autoindent
vim.opt.incsearch = true       -- set incsearch
vim.opt.hlsearch = true        -- set hlsearch

-- parte 2
vim.g.minha_config = "valor"
 -- o `meta-acessor` é uma table, então podemos atribuir valores usando também os colchetes
vim.g["algumnamespace#teste#variavel"] = "valor"
```

Para a **parte 3** temos os _mappings_ e para isso temos 2 funções que podem ser utilizadas:

- `vim.api.nvim_set_keymap` - mappings que serão atribuídos para todos os buffers
- `vim.api.nvim_buf_set_keymap` - mappings que serão exclusivos de algum buffer em particular

Para facilitar ainda mais, na versão 0.7 foi introduzida a API `vim.keymap` (`:h lua-keymap`), que deixa as coisas bem mais fáceis quando precisamos criar os mappings.

Temos então, no nosso arquivo lua:

```lua
-- parte 1
vim.opt.termguicolors = true   -- set termguicolors
vim.opt.colorcolumn = 80       -- set colorcolumn=80
vim.opt.expandtab = true       -- set expandtab
vim.opt.tabstop = 4            -- set tabstop=4
vim.opt.autoindent = true      -- set autoindent
vim.opt.incsearch = true       -- set incsearch
vim.opt.hlsearch = true        -- set hlsearch

-- parte 2
vim.g.minha_config = "valor"
 -- o `meta-acessor` é uma table, então podemos atribuir valores usando também os colchetes
vim.g["algumnamespace#teste#variavel"] = "valor"

-- parte 3
vim.keymap.set("n", "<leader>,", "<Cmd>:noh<CR>", nil) -- nmap <leader>, <Cmd>:noh<CR>
vim.keymap.set("n", "<leader>h", "<Cmd>split<CR>", {noremap = true}) -- nnoremap <leader>h <Cmd>split<CR>
vim.keymap.set("v", "<", "<gv", {noremap = true}) -- vnoremap < <gv
```

Já para a parte 4, vamos adicionar os autocmd e augroups. Para isso, iremos usar a função `vim.api.nvim_create_autocmd`, introduzida também na versão 0.7. Adicionando a parte 4 na nossa configuração, temos:

```lua
-- parte 1
vim.opt.termguicolors = true   -- set termguicolors
vim.opt.colorcolumn = 80       -- set colorcolumn=80
vim.opt.expandtab = true       -- set expandtab
vim.opt.tabstop = 4            -- set tabstop=4
vim.opt.autoindent = true      -- set autoindent
vim.opt.incsearch = true       -- set incsearch
vim.opt.hlsearch = true        -- set hlsearch

-- parte 2
vim.g.minha_config = "valor"
 -- o `meta-acessor` é uma table, então podemos atribuir valores usando também os colchetes
vim.g["algumnamespace#teste#variavel"] = "valor"

-- parte 3
vim.keymap.set("n", "<leader>,", "<Cmd>:noh<CR>", nil) -- nmap <leader>, <Cmd>:noh<CR>
vim.keymap.set("n", "<leader>h", "<Cmd>split<CR>", {noremap = true}) -- nnoremap <leader>h <Cmd>split<CR>
vim.keymap.set("v", "<", "<gv", {noremap = true}) -- vnoremap < <gv

-- parte 4
vim.api.nvim_create_autocmd("BufEnter", {
  callback = function()
    print("hello nvim")
  end
})
```

Para a última parte, temos uma função vimscript que poderá ser portada utilizando a função vim.api.nvim_exec, que avalia um código vimscript contendo várias linhas. A assinatura da função é da seguinte forma:

```lua
vim.api.nvim_exec(bloco_de_codigo, retorno)
```

Onde:

- `bloco_de_codigo`: A string multiline ou não do bloco de código vimscript que será executado
- `retorno` - `true` caso deseje retornar o resultado string da execução do bloco, `false` para retornar uma string vazia

Temos então a versão final da config lua:

 ```lua
-- parte 1
vim.opt.termguicolors = true   -- set termguicolors
vim.opt.colorcolumn = 80       -- set colorcolumn=80
vim.opt.expandtab = true       -- set expandtab
vim.opt.tabstop = 4            -- set tabstop=4
vim.opt.autoindent = true      -- set autoindent
vim.opt.incsearch = true       -- set incsearch
vim.opt.hlsearch = true        -- set hlsearch

-- parte 2
vim.g.minha_config = "valor"
 -- o `meta-acessor` é uma table, então podemos atribuir valores usando também os colchetes
vim.g["algumnamespace#teste#variavel"] = "valor"

-- parte 3
vim.keymap.set("n", "<leader>,", "<Cmd>:noh<CR>", nil) -- nmap <leader>, <Cmd>:noh<CR>
vim.keymap.set("n", "<leader>h", "<Cmd>split<CR>", {noremap = true}) -- nnoremap <leader>h <Cmd>split<CR>
vim.keymap.set("v", "<", "<gv", {noremap = true}) -- vnoremap < <gv
```

Já para a parte 4, vamos adicionar os autocmd e augroups. Para isso, iremos usar a função `vim.api.nvim_create_autocmd`, introduzida também na versão 0.7. Adicionando a parte 4 na nossa configuração, temos:

```lua
-- parte 1
vim.opt.termguicolors = true   -- set termguicolors
vim.opt.colorcolumn = 80       -- set colorcolumn=80
vim.opt.expandtab = true       -- set expandtab
vim.opt.tabstop = 4            -- set tabstop=4
vim.opt.autoindent = true      -- set autoindent
vim.opt.incsearch = true       -- set incsearch
vim.opt.hlsearch = true        -- set hlsearch

-- parte 2
vim.g.minha_config = "valor"
 -- o `meta-acessor` é uma table, então podemos atribuir valores usando também os colchetes
vim.g["algumnamespace#teste#variavel"] = "valor"

-- parte 3
vim.keymap.set("n", "<leader>,", "<Cmd>:noh<CR>", nil) -- nmap <leader>, <Cmd>:noh<CR>
vim.keymap.set("n", "<leader>h", "<Cmd>split<CR>", {noremap = true}) -- nnoremap <leader>h <Cmd>split<CR>
vim.keymap.set("v", "<", "<gv", {noremap = true}) -- vnoremap < <gv

-- parte 4
vim.api.nvim_create_autocmd("BufEnter", {
  callback = function()
    print("hello nvim")
  end
})

-- parte 5
vim.api.nvim_exec([[
function! SayHello()
  echohl WarningMsg
  echo "Hello World"
  echohl
endfunction
]], false)
```

## Conclusão

Mostramos que, apesar de algumas limitações, portar uma configuração em vimscript para Lua está cada dia mais fácil com a API vigente. Para um exemplo de configuração mais extensa usando mais elementos da API Lua, visite a [minha configuração do neovim](https://github.com/ellisonleao/neovimfiles), deixa seu _star_ por lá também e caso tenha alguma dúvida, manda mensagem por aqui

Lembrando que faço lives esporádicas na [Twitch](https://twitch.tv/ellisonleao)! Caso deseje ver esse tipo de conteúdo ao vivo, não deixe de me seguir por lá e também acompanhar meu [Twitter](https://twitter.com/ellisonleao)

## Para ouvir

{% embed https://www.youtube.com/watch?v=Rv_a6rlRjZk %}