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.nvimlà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ệc | Công cụ |
|---|---|
| Syntax highlight | Treesitter |
| Indent/fold theo AST | Treesitter |
| Go to definition | LSP |
| Rename symbol | LSP |
| Type error | LSP hoặc compiler linter |
| Format code | Prettier/Stylua qua Conform |
| ESLint rules | ESLint qua LSP hoặc nvim-lint |
| Import organize | LSP 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
- Cài
styluavà format file Lua config. - Cài
prettier, format file.tsxhoặc.mdx. - Cố ý tạo lỗi ESLint, xem diagnostic.
- Chạy
:ConformInfo. - Mở file JavaScript, kiểm tra
:InspectTreenế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_ftvà 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.