jvinhit//lab

Search posts

Type to search across journal entries.

navigate open esc close

Neovim · Part 7 — Format, Lint và Treesitter

Tách rõ formatter, linter, LSP và Treesitter: setup conform.nvim, nvim-lint, parser/highlight, format-on-save và checklist debug.

Sau LSP, ba lớp dễ bị trộn lẫn nhất là:

  • formatter: sửa layout code;
  • linter: báo lỗi/quy tắc code;
  • Treesitter: hiểu cú pháp bằng AST để highlight/fold/textobject.

Đừng bắt một công cụ làm tất cả.


Formatter khác LSP thế nào?

LSP có thể format, nhưng không phải server nào cũng format tốt. Với frontend, Prettier thường là nguồn sự thật. Với Lua, stylua. Với Go, gofmt/goimports. Với Rust, rustfmt.

Khuyến nghị:

  • dùng conform.nvim làm coordinator;
  • formatter CLI vẫn nằm ngoài Neovim;
  • nếu không có formatter CLI, fallback sang LSP.

Setup conform.nvim

Tạo lua/plugins/format.lua:

return {
  {
    "stevearc/conform.nvim",
    event = { "BufWritePre" },
    cmd = { "ConformInfo" },
    opts = {
      formatters_by_ft = {
        lua = { "stylua" },
        javascript = { "prettier" },
        javascriptreact = { "prettier" },
        typescript = { "prettier" },
        typescriptreact = { "prettier" },
        json = { "prettier" },
        markdown = { "prettier" },
        astro = { "prettier" },
        css = { "prettier" },
        html = { "prettier" },
      },
      format_on_save = {
        timeout_ms = 800,
        lsp_format = "fallback",
      },
    },
    keys = {
      {
        "<leader>f",
        function()
          require("conform").format({ async = true, lsp_format = "fallback" })
        end,
        mode = "",
        desc = "Format buffer",
      },
    },
  },
}

Cài tool bằng Mason:

:MasonInstall stylua prettier

Debug:

:ConformInfo

Nếu formatter không chạy, :ConformInfo là nơi đầu tiên cần mở.


Lint với nvim-lint

Linter tốt cho các lỗi không nhất thiết đến từ LSP, hoặc khi bạn muốn chạy CLI tool cụ thể.

Tạo lua/plugins/lint.lua:

return {
  {
    "mfussenegger/nvim-lint",
    event = { "BufReadPost", "BufNewFile" },
    config = function()
      local lint = require("lint")

      lint.linters_by_ft = {
        javascript = { "eslint_d" },
        javascriptreact = { "eslint_d" },
        typescript = { "eslint_d" },
        typescriptreact = { "eslint_d" },
        markdown = { "markdownlint" },
      }

      local group = vim.api.nvim_create_augroup("lint", { clear = true })
      vim.api.nvim_create_autocmd({ "BufWritePost", "BufReadPost", "InsertLeave" }, {
        group = group,
        callback = function()
          lint.try_lint()
        end,
      })
    end,
  },
}

Cài tool:

:MasonInstall eslint_d markdownlint

Lưu ý: ESLint thường nên chạy theo dependency của project. Nếu team dùng eslint trong node_modules, hãy cân nhắc config để ưu tiên local binary thay vì global tool.


Treesitter năm 2026: cẩn thận style cũ

Neovim core đã có nhiều tính năng Treesitter built-in:

  • highlight;
  • folding;
  • query;
  • parser API;
  • filetype/query runtime.

Ecosystem nvim-treesitter thay đổi mạnh quanh Nvim 0.12. Đừng copy config cũ kiểu:

require("nvim-treesitter.configs").setup({
  highlight = { enable = true },
})

mà không kiểm tra README hiện tại. Với setup mới, hãy hiểu core trước.


Bật Treesitter highlight bằng core API

Tạo autocmd:

local treesitter_group = vim.api.nvim_create_augroup("treesitter", { clear = true })

vim.api.nvim_create_autocmd("FileType", {
  group = treesitter_group,
  pattern = {
    "lua",
    "javascript",
    "typescript",
    "javascriptreact",
    "typescriptreact",
    "json",
    "markdown",
    "css",
    "html",
    "astro",
  },
  callback = function()
    pcall(vim.treesitter.start)
  end,
})

Folding bằng Treesitter:

vim.api.nvim_create_autocmd("FileType", {
  group = treesitter_group,
  pattern = { "lua", "javascript", "typescript", "typescriptreact" },
  callback = function()
    vim.wo.foldmethod = "expr"
    vim.wo.foldexpr = "v:lua.vim.treesitter.foldexpr()"
    vim.wo.foldlevel = 99
  end,
})

Nếu parser thiếu, highlight sẽ không bật. Parser installation là phần cần kiểm tra theo README nvim-treesitter hiện tại hoặc package manager bạn chọn.


Cài nvim-treesitter theo hướng mới

Nếu bạn dùng bản nvim-treesitter mới:

return {
  {
    "nvim-treesitter/nvim-treesitter",
    lazy = false,
    build = ":TSUpdate",
    opts = {
      install_dir = vim.fn.stdpath("data") .. "/site",
    },
    config = function(_, opts)
      require("nvim-treesitter").setup(opts)
      require("nvim-treesitter").install({
        "lua",
        "javascript",
        "typescript",
        "tsx",
        "json",
        "markdown",
        "css",
        "html",
      })
    end,
  },
}

Đây là phần nên kiểm tra lại theo phiên bản bạn đang dùng. Nếu bạn đang ở Neovim 0.11 và config cũ vẫn chạy, đừng nâng cấp nửa vời trước deadline.


Ai chịu trách nhiệm việc gì?

ViệcCông cụ
Syntax highlightTreesitter
Indent/fold theo ASTTreesitter
Go to definitionLSP
Rename symbolLSP
Type errorLSP hoặc compiler linter
Format codePrettier/Stylua qua Conform
ESLint rulesESLint qua LSP hoặc nvim-lint
Import organizeLSP code action hoặc formatter/linter

Khi lỗi xuất hiện, phân loại đúng lớp trước. Nếu không, bạn sẽ chỉnh formatter để sửa lỗi LSP.


Bài tập

  1. Cài stylua và format file Lua config.
  2. Cài prettier, format file .tsx hoặc .mdx.
  3. Cố ý tạo lỗi ESLint, xem diagnostic.
  4. Chạy :ConformInfo.
  5. Mở file JavaScript, kiểm tra :InspectTree nếu parser có sẵn.
Checklist debug
  • Format không chạy: mở :ConformInfo.
  • Lint không chạy: kiểm tra lint.linters_by_ft và executable trong PATH.
  • Treesitter không highlight: parser thiếu hoặc filetype sai.
  • Format quá chậm: tăng/giảm timeout_ms, tránh format tool nặng trong file lớn.

Điều cốt lõi

Formatter, linter, LSP, Treesitter là bốn lớp khác nhau. Setup pro là setup biết giao đúng việc cho đúng lớp, rồi có lệnh debug cho từng lớp.