jvinhit//lab

Search posts

Type to search across journal entries.

navigate open esc close

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()vim.lsp.enable().
  • nvim-lspconfig cung cấp config mẫu cho server, nhưng style require("lspconfig").server.setup() là lối cũ.
  • mason.nvim cài executable.
  • mason-lspconfig.nvim nối Mason với nvim-lspconfig và 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-server là process ngoài.
  • ts_ls là 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ự:

  1. server executable đã cài chưa?
  2. server có trong PATH của Neovim không?
  3. filetype đúng không?
  4. root marker có tìm được không?
  5. 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
grnrename
gracode action
grrreferences
griimplementation
gOdocument symbols
Khover
[d ]ddiagnostic 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ệnhDùng khi
:checkhealth vim.lspxem server/config/buffer attach
:LspInfoalias/trạng thái LSP
:lspquản lý LSP tương tác trên Nvim mới
:Masonxem tool đã cài
:MasonLogdebug install lỗi
:set filetype?kiểm tra filetype
:lua =vim.lsp.get_clients()inspect clients

Bài tập

  1. Cài lua_ls và mở file Lua config.
  2. Gõ sai vim.opt.nonexist = true, xem diagnostic.
  3. Dùng K để hover trên vim.keymap.set.
  4. Dùng grn rename một biến trong file TypeScript.
  5. Chạy :checkhealth vim.lsp và đọ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ợ.