Configurar Neovim para Zig — LSP, Tree-sitter e Plugins

Configurar Neovim para Zig

O Neovim é uma excelente escolha para desenvolvimento com Zig, oferecendo LSP nativo, Tree-sitter para syntax highlighting preciso e um ecossistema rico de plugins. Este guia mostra como configurar o Neovim para ter uma experiência de desenvolvimento completa com Zig, incluindo autocompletar, diagnósticos, formatação e navegação de código.

Antes de configurar o Neovim, certifique-se de que o Zig está instalado. Consulte o guia para sua plataforma no guia completo de instalação.


Pré-requisitos

  • Neovim 0.9+ (recomendado 0.10+)
  • Zig instalado e acessível no PATH
  • ZLS instalado (Zig Language Server)
  • Git para clonar plugins

Verificar Versões

nvim --version | head -1
zig version
zls --version

Instalar o ZLS

Se ainda não instalou o ZLS:

# Linux (x86_64)
curl -LO "https://github.com/zigtools/zls/releases/download/0.14.0/zls-x86_64-linux.tar.xz"
tar -xf "zls-x86_64-linux.tar.xz"
sudo mv zls /usr/local/bin/

# macOS
brew install zls

# Arch Linux
sudo pacman -S zls

Configuração com lazy.nvim (Recomendado)

O lazy.nvim é o gerenciador de plugins mais popular para Neovim. Vamos configurar tudo do zero.

Estrutura de Arquivos

~/.config/nvim/
├── init.lua
├── lua/
│   ├── plugins/
│   │   ├── lsp.lua
│   │   ├── cmp.lua
│   │   └── treesitter.lua
│   └── config/
│       └── keymaps.lua

init.lua Base

Crie ou edite ~/.config/nvim/init.lua:

-- Bootstrap lazy.nvim
local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
if not vim.loop.fs_stat(lazypath) then
  vim.fn.system({
    "git", "clone", "--filter=blob:none",
    "https://github.com/folke/lazy.nvim.git",
    "--branch=stable", lazypath,
  })
end
vim.opt.rtp:prepend(lazypath)

-- Opções gerais
vim.g.mapleader = " "
vim.opt.number = true
vim.opt.relativenumber = true
vim.opt.tabstop = 4
vim.opt.shiftwidth = 4
vim.opt.expandtab = true
vim.opt.signcolumn = "yes"
vim.opt.termguicolors = true

-- Carregar plugins
require("lazy").setup("plugins")

-- Carregar keymaps
require("config.keymaps")

Configuração do LSP (ZLS)

Crie ~/.config/nvim/lua/plugins/lsp.lua:

return {
  {
    "neovim/nvim-lspconfig",
    dependencies = {
      "williamboman/mason.nvim",
      "williamboman/mason-lspconfig.nvim",
    },
    config = function()
      -- Configurar Mason (gerenciador de LSP servers)
      require("mason").setup()
      require("mason-lspconfig").setup({
        ensure_installed = { "zls" },
      })

      local lspconfig = require("lspconfig")

      -- Função para configurar keymaps quando o LSP conecta
      local on_attach = function(client, bufnr)
        local opts = { noremap = true, silent = true, buffer = bufnr }

        -- Navegação
        vim.keymap.set("n", "gd", vim.lsp.buf.definition, opts)
        vim.keymap.set("n", "gD", vim.lsp.buf.declaration, opts)
        vim.keymap.set("n", "gi", vim.lsp.buf.implementation, opts)
        vim.keymap.set("n", "gr", vim.lsp.buf.references, opts)
        vim.keymap.set("n", "K", vim.lsp.buf.hover, opts)
        vim.keymap.set("n", "<C-k>", vim.lsp.buf.signature_help, opts)

        -- Ações
        vim.keymap.set("n", "<leader>rn", vim.lsp.buf.rename, opts)
        vim.keymap.set("n", "<leader>ca", vim.lsp.buf.code_action, opts)
        vim.keymap.set("n", "<leader>f", function()
          vim.lsp.buf.format({ async = true })
        end, opts)

        -- Diagnósticos
        vim.keymap.set("n", "[d", vim.diagnostic.goto_prev, opts)
        vim.keymap.set("n", "]d", vim.diagnostic.goto_next, opts)
        vim.keymap.set("n", "<leader>e", vim.diagnostic.open_float, opts)
        vim.keymap.set("n", "<leader>q", vim.diagnostic.setloclist, opts)

        -- Formatar ao salvar
        if client.supports_method("textDocument/formatting") then
          vim.api.nvim_create_autocmd("BufWritePre", {
            buffer = bufnr,
            callback = function()
              vim.lsp.buf.format({ bufnr = bufnr })
            end,
          })
        end
      end

      -- Configurar ZLS
      lspconfig.zls.setup({
        on_attach = on_attach,
        settings = {
          zls = {
            enable_snippets = true,
            enable_autofix = true,
            enable_import_detection = true,
            warn_style = true,
            highlight_global_var_declarations = true,
          },
        },
      })

      -- Configuração de diagnósticos
      vim.diagnostic.config({
        virtual_text = true,
        signs = true,
        underline = true,
        update_in_insert = false,
        severity_sort = true,
        float = {
          border = "rounded",
          source = true,
        },
      })
    end,
  },
}

Autocompletar com nvim-cmp

Crie ~/.config/nvim/lua/plugins/cmp.lua:

return {
  {
    "hrsh7th/nvim-cmp",
    dependencies = {
      "hrsh7th/cmp-nvim-lsp",
      "hrsh7th/cmp-buffer",
      "hrsh7th/cmp-path",
      "L3MON4D3/LuaSnip",
      "saadparwaiz1/cmp_luasnip",
    },
    config = function()
      local cmp = require("cmp")
      local luasnip = require("luasnip")

      cmp.setup({
        snippet = {
          expand = function(args)
            luasnip.lsp_expand(args.body)
          end,
        },
        mapping = cmp.mapping.preset.insert({
          ["<C-b>"] = cmp.mapping.scroll_docs(-4),
          ["<C-f>"] = cmp.mapping.scroll_docs(4),
          ["<C-Space>"] = cmp.mapping.complete(),
          ["<C-e>"] = cmp.mapping.abort(),
          ["<CR>"] = cmp.mapping.confirm({ select = true }),
          ["<Tab>"] = cmp.mapping(function(fallback)
            if cmp.visible() then
              cmp.select_next_item()
            elseif luasnip.expand_or_jumpable() then
              luasnip.expand_or_jump()
            else
              fallback()
            end
          end, { "i", "s" }),
          ["<S-Tab>"] = cmp.mapping(function(fallback)
            if cmp.visible() then
              cmp.select_prev_item()
            elseif luasnip.jumpable(-1) then
              luasnip.jump(-1)
            else
              fallback()
            end
          end, { "i", "s" }),
        }),
        sources = cmp.config.sources({
          { name = "nvim_lsp" },
          { name = "luasnip" },
        }, {
          { name = "buffer" },
          { name = "path" },
        }),
        formatting = {
          format = function(entry, vim_item)
            vim_item.menu = ({
              nvim_lsp = "[LSP]",
              luasnip = "[Snip]",
              buffer = "[Buf]",
              path = "[Path]",
            })[entry.source.name]
            return vim_item
          end,
        },
      })
    end,
  },
}

Tree-sitter para Syntax Highlighting

Crie ~/.config/nvim/lua/plugins/treesitter.lua:

return {
  {
    "nvim-treesitter/nvim-treesitter",
    build = ":TSUpdate",
    config = function()
      require("nvim-treesitter.configs").setup({
        ensure_installed = { "zig", "lua", "vim", "vimdoc", "c", "markdown" },
        auto_install = true,

        highlight = {
          enable = true,
          additional_vim_regex_highlighting = false,
        },

        indent = {
          enable = true,
        },

        incremental_selection = {
          enable = true,
          keymaps = {
            init_selection = "<C-space>",
            node_incremental = "<C-space>",
            scope_incremental = false,
            node_decremental = "<bs>",
          },
        },
      })
    end,
  },
}

Keymaps Específicos para Zig

Crie ~/.config/nvim/lua/config/keymaps.lua:

-- Atalhos gerais
local map = vim.keymap.set

-- Compilar e executar Zig
map("n", "<leader>zb", ":!zig build<CR>", { desc = "Zig build" })
map("n", "<leader>zr", ":!zig build run<CR>", { desc = "Zig build run" })
map("n", "<leader>zt", ":!zig build test<CR>", { desc = "Zig build test" })

-- Executar o arquivo atual
map("n", "<leader>zx", function()
  local file = vim.fn.expand("%")
  vim.cmd("!zig run " .. file)
end, { desc = "Zig run current file" })

-- Testar o arquivo atual
map("n", "<leader>zT", function()
  local file = vim.fn.expand("%")
  vim.cmd("!zig test " .. file)
end, { desc = "Zig test current file" })

-- Terminal integrado
map("n", "<leader>tt", ":terminal<CR>", { desc = "Abrir terminal" })
map("t", "<Esc>", "<C-\\><C-n>", { desc = "Sair do modo terminal" })

Plugins Adicionais Recomendados

Para uma experiência mais completa, adicione estes plugins ao diretório lua/plugins/:

Telescope (Fuzzy Finder)

-- lua/plugins/telescope.lua
return {
  {
    "nvim-telescope/telescope.nvim",
    dependencies = { "nvim-lua/plenary.nvim" },
    keys = {
      { "<leader>ff", "<cmd>Telescope find_files<cr>", desc = "Buscar arquivos" },
      { "<leader>fg", "<cmd>Telescope live_grep<cr>", desc = "Buscar texto" },
      { "<leader>fb", "<cmd>Telescope buffers<cr>", desc = "Listar buffers" },
      { "<leader>fh", "<cmd>Telescope help_tags<cr>", desc = "Ajuda" },
      { "<leader>fd", "<cmd>Telescope diagnostics<cr>", desc = "Diagnósticos" },
      { "<leader>fr", "<cmd>Telescope lsp_references<cr>", desc = "Referências LSP" },
    },
  },
}

Lualine (Status Line)

-- lua/plugins/lualine.lua
return {
  {
    "nvim-lualine/lualine.nvim",
    config = function()
      require("lualine").setup({
        sections = {
          lualine_c = {
            { "filename", path = 1 },
          },
          lualine_x = { "encoding", "fileformat", "filetype" },
        },
      })
    end,
  },
}

Verificação da Configuração

Após configurar tudo, reinicie o Neovim e verifique:

  1. LSP ativo: Abra um arquivo .zig e execute :LspInfo para verificar se o ZLS está conectado
  2. Tree-sitter: Execute :TSInstallInfo e verifique se zig está instalado
  3. Autocompletar: Comece a digitar std. em um arquivo .zig e veja as sugestões

Teste com Código Zig

Crie um arquivo main.zig e abra no Neovim:

const std = @import("std");

pub fn main() !void {
    const allocator = std.heap.page_allocator;
    var list = std.ArrayList(u32).init(allocator);
    defer list.deinit();

    try list.append(42);
    try list.append(100);

    const stdout = std.io.getStdOut().writer();
    for (list.items) |item| {
        try stdout.print("{} ", .{item});
    }
    try stdout.print("\n", .{});
}

Verifique se:

  • A sintaxe é colorida corretamente (Tree-sitter)
  • Ao digitar std., aparecem sugestões (autocompletar)
  • Erros são destacados em tempo real (diagnósticos)
  • gd sobre main vai para a definição (navegação LSP)

Problemas Comuns

ZLS não conecta

Execute :LspLog para ver os logs do LSP. Causas comuns:

  • ZLS não está no PATH
  • Versão do ZLS incompatível com a versão do Zig
  • Falta a dependência nvim-lspconfig

Tree-sitter não funciona para Zig

Execute :TSInstall zig manualmente e reinicie o Neovim.

Autocompletar lento

Verifique se não há muitos sources configurados no nvim-cmp e se o ZLS está na versão correta.

Para mais soluções, visite nossa página de erros comuns.


Próximos Passos

Com o Neovim configurado para Zig:

  1. Crie seu primeiro projetoPrimeiro projeto Zig
  2. Aprenda a linguagemIntrodução ao Zig
  3. Explore outros editoresVS Code ou Sublime/Emacs
  4. Configure CI/CDGitHub Actions com Zig
  5. Veja exemplosReceitas

Continue aprendendo Zig

Explore mais tutoriais e artigos em português para dominar a linguagem Zig.