Neovim · Part 6 — LSP, Diagnostics và Completion chuẩn 2026
Setup LSP hiện đại với vim.lsp.config, vim.lsp.enable, nvim-lspconfig, mason-lspconfig, diagnostics, keymaps và blink.cmp stable.
Đây là bài quan trọng nhất nếu bạn chuyển từ VSCode.
VSCode cho bạn IntelliSense, go to definition, hover, rename, code action, diagnostics. Trong Neovim, lớp đó là LSP.
Năm 2025-2026, điều cần nhớ:
- Neovim có LSP client built-in.
- Neovim 0.11 thêm
vim.lsp.config()vàvim.lsp.enable(). nvim-lspconfigcung cấp config mẫu cho server, nhưng stylerequire("lspconfig").server.setup()là lối cũ.mason.nvimcài executable.mason-lspconfig.nvimnối Mason vớinvim-lspconfigvà có thể tựvim.lsp.enable()server đã cài.
Mental model
Neovim buffer
-> LSP client built-in
-> language server executable
-> project files
Ví dụ TypeScript:
- Neovim là editor.
- LSP client là code trong Neovim.
typescript-language-serverlà process ngoài.ts_lslà tên config LSP trong Neovim.- Project root được tìm bằng marker như
package.json,.git,tsconfig.json.
Nếu LSP không chạy, hãy hỏi theo thứ tự:
- server executable đã cài chưa?
- server có trong PATH của Neovim không?
- filetype đúng không?
- root marker có tìm được không?
- config đã enable chưa?
Mason và lspconfig
Tạo lua/plugins/lsp.lua:
return {
{
"mason-org/mason.nvim",
opts = {},
},
{
"mason-org/mason-lspconfig.nvim",
dependencies = {
"mason-org/mason.nvim",
"neovim/nvim-lspconfig",
},
opts = {
ensure_installed = {
"lua_ls",
"ts_ls",
"eslint",
},
automatic_enable = true,
},
},
}
Mở Nvim:
:Mason
:LspInstall lua_ls ts_ls eslint
:checkhealth vim.lsp
mason-lspconfig chỉ tự enable server được cài qua Mason. Nếu bạn tự cài server bằng Homebrew/npm/system package, bạn có thể cần vim.lsp.enable() thủ công.
Custom config với API mới
Tạo lua/config/lsp.lua và require nó trước plugin hoặc trong lsp.lua.
vim.lsp.config("lua_ls", {
settings = {
Lua = {
runtime = { version = "LuaJIT" },
diagnostics = {
globals = { "vim" },
},
workspace = {
library = vim.api.nvim_get_runtime_file("", true),
checkThirdParty = false,
},
telemetry = { enable = false },
},
},
})
vim.lsp.config("ts_ls", {
settings = {
typescript = {
inlayHints = {
includeInlayParameterNameHints = "literal",
includeInlayFunctionParameterTypeHints = true,
},
},
},
})
Nếu không dùng mason-lspconfig automatic enable:
vim.lsp.enable("lua_ls")
vim.lsp.enable("ts_ls")
vim.lsp.enable("eslint")
Điểm mấu chốt: vim.lsp.config() định nghĩa/cập nhật config; vim.lsp.enable() bật config.
Keymaps LSP
Neovim mới đã có nhiều mapping mặc định khi LSP attach:
| Key | Ý nghĩa |
|---|---|
grn | rename |
gra | code action |
grr | references |
gri | implementation |
gO | document symbols |
K | hover |
[d ]d | diagnostic trước/kế |
Bạn vẫn có thể thêm keymap quen tay trong autocmd LspAttach:
vim.api.nvim_create_autocmd("LspAttach", {
group = vim.api.nvim_create_augroup("user_lsp", { clear = true }),
callback = function(event)
local map = function(keys, fn, desc)
vim.keymap.set("n", keys, fn, { buffer = event.buf, desc = desc })
end
map("gd", vim.lsp.buf.definition, "Go to definition")
map("gD", vim.lsp.buf.declaration, "Go to declaration")
map("gt", vim.lsp.buf.type_definition, "Go to type definition")
map("<leader>rn", vim.lsp.buf.rename, "Rename symbol")
map("<leader>ca", vim.lsp.buf.code_action, "Code action")
map("<leader>lf", function()
vim.lsp.buf.format({ async = true })
end, "Format with LSP")
end,
})
Nếu bạn thích default mới, giữ ít keymap hơn. Ít keymap nghĩa là ít xung đột.
Diagnostics
Cấu hình diagnostics:
vim.diagnostic.config({
virtual_text = {
current_line = true,
},
virtual_lines = false,
underline = true,
update_in_insert = false,
severity_sort = true,
float = {
border = "rounded",
source = true,
},
})
vim.keymap.set("n", "<leader>e", vim.diagnostic.open_float, { desc = "Line diagnostics" })
vim.keymap.set("n", "<leader>q", vim.diagnostic.setloclist, { desc = "Diagnostics to loclist" })
Neovim 0.11 tắt diagnostic virtual text mặc định theo release notes. Vì vậy nếu muốn thấy text inline, bật rõ trong config.
Completion với blink.cmp
blink.cmp có v2 đang phát triển mạnh, nhiều breaking changes. Cho setup ổn định, dùng v1:
return {
{
"saghen/blink.cmp",
version = "1.*",
dependencies = {
"rafamadriz/friendly-snippets",
},
opts = {
keymap = { preset = "default" },
appearance = {
nerd_font_variant = "mono",
},
completion = {
documentation = {
auto_show = true,
auto_show_delay_ms = 300,
},
},
sources = {
default = { "lsp", "path", "snippets", "buffer" },
},
},
},
}
Nếu bạn muốn tối giản tuyệt đối, có thể dùng completion built-in của Neovim. Nhưng người chuyển từ VSCode thường cần UI completion giàu hơn, nên blink.cmp là lựa chọn hợp lý.
Commands debug LSP
| Lệnh | Dùng khi |
|---|---|
:checkhealth vim.lsp | xem server/config/buffer attach |
:LspInfo | alias/trạng thái LSP |
:lsp | quản lý LSP tương tác trên Nvim mới |
:Mason | xem tool đã cài |
:MasonLog | debug install lỗi |
:set filetype? | kiểm tra filetype |
:lua =vim.lsp.get_clients() | inspect clients |
Bài tập
- Cài
lua_lsvà mở file Lua config. - Gõ sai
vim.opt.nonexist = true, xem diagnostic. - Dùng
Kđể hover trênvim.keymap.set. - Dùng
grnrename một biến trong file TypeScript. - Chạy
:checkhealth vim.lspvà đọc phần active clients.
Lỗi thường gặp
No active clients: server chưa attach, thường do root marker hoặc filetype.cmd not executable: server chưa cài hoặc PATH sai.- TypeScript LSP không format như ý: dùng
conform.nvim+ Prettier ở phần sau. - ESLint không chạy: project thiếu config hoặc package ESLint.
Điều cốt lõi
LSP trong Neovim hiện đại không còn là một plugin magic. Hãy nghĩ theo 3 lớp: cài executable bằng Mason, lấy config bằng lspconfig/native API, enable bằng vim.lsp.enable(). Khi model này rõ, lỗi LSP không còn đáng sợ.