jvinhit//lab

Search posts

Type to search across journal entries.

navigate open esc close

Neovim · Part 12 — Capstone Pro Setup & Cheatsheet

Gom toàn bộ series thành một setup Neovim hoàn chỉnh: cấu trúc file, plugin specs, keymaps, checklist cài đặt và cheatsheet dùng hằng ngày.

Đây là bài capstone: gom các phần trước thành một setup Neovim đủ dùng hằng ngày cho frontend/full-stack dev chuyển từ VSCode.

Không cần copy y nguyên. Hãy dùng nó như blueprint.


Cấu trúc cuối

~/.config/nvim/
├─ init.lua
├─ lazy-lock.json
└─ lua/
   ├─ config/
   │  ├─ autocmds.lua
   │  ├─ keymaps.lua
   │  ├─ lazy.lua
   │  ├─ lsp.lua
   │  └─ options.lua
   └─ plugins/
      ├─ coding.lua
      ├─ editor.lua
      ├─ format.lua
      ├─ git.lua
      ├─ lint.lua
      ├─ lsp.lua
      ├─ search.lua
      └─ ui.lua

init.lua:

require("config.options")
require("config.keymaps")
require("config.autocmds")
require("config.lsp")
require("config.lazy")

Options

lua/config/options.lua:

vim.g.mapleader = " "
vim.g.maplocalleader = "\\"

local opt = vim.opt

opt.number = true
opt.relativenumber = true
opt.mouse = "a"
opt.clipboard = "unnamedplus"
opt.breakindent = true
opt.undofile = true
opt.ignorecase = true
opt.smartcase = true
opt.signcolumn = "yes"
opt.updatetime = 250
opt.timeoutlen = 400
opt.splitright = true
opt.splitbelow = true
opt.cursorline = true
opt.scrolloff = 8
opt.expandtab = true
opt.shiftwidth = 2
opt.tabstop = 2
opt.smartindent = true
opt.list = true
opt.listchars = { tab = "» ", trail = "·", nbsp = "␣" }
opt.inccommand = "split"

Keymaps

lua/config/keymaps.lua:

local map = vim.keymap.set

map("n", "<Esc>", "<cmd>nohlsearch<CR>", { desc = "Clear search highlight" })
map("n", "<leader>w", "<cmd>write<CR>", { desc = "Save file" })
map("n", "<leader>x", "<cmd>bdelete<CR>", { desc = "Delete buffer" })

map("n", "<C-h>", "<C-w>h", { desc = "Left window" })
map("n", "<C-j>", "<C-w>j", { desc = "Lower window" })
map("n", "<C-k>", "<C-w>k", { desc = "Upper window" })
map("n", "<C-l>", "<C-w>l", { desc = "Right window" })

map("n", "<leader>e", vim.diagnostic.open_float, { desc = "Line diagnostics" })
map("n", "<leader>q", vim.diagnostic.setloclist, { desc = "Diagnostics to loclist" })

map("t", "<Esc><Esc>", "<C-\\><C-n>", { desc = "Exit terminal mode" })

Autocmds

lua/config/autocmds.lua:

local group = vim.api.nvim_create_augroup("user_config", { clear = true })

vim.api.nvim_create_autocmd("TextYankPost", {
  group = group,
  callback = function()
    vim.highlight.on_yank()
  end,
})

vim.api.nvim_create_autocmd("BufReadPost", {
  group = group,
  callback = function()
    local mark = vim.api.nvim_buf_get_mark(0, '"')
    local line_count = vim.api.nvim_buf_line_count(0)
    if mark[1] > 0 and mark[1] <= line_count then
      pcall(vim.api.nvim_win_set_cursor, 0, mark)
    end
  end,
})

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

LSP config

lua/config/lsp.lua:

vim.diagnostic.config({
  virtual_text = { current_line = true },
  underline = true,
  update_in_insert = false,
  severity_sort = true,
  float = { border = "rounded", source = true },
})

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",
      },
    },
  },
})

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")
  end,
})

Plugin list tối thiểu

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,
    },
  },
  {
    "saghen/blink.cmp",
    version = "1.*",
    dependencies = { "rafamadriz/friendly-snippets" },
    opts = {
      keymap = { preset = "default" },
      completion = { documentation = { auto_show = true } },
      sources = { default = { "lsp", "path", "snippets", "buffer" } },
    },
  },
}

lua/plugins/format.lua:

return {
  {
    "stevearc/conform.nvim",
    event = "BufWritePre",
    cmd = "ConformInfo",
    opts = {
      formatters_by_ft = {
        lua = { "stylua" },
        javascript = { "prettier" },
        typescript = { "prettier" },
        javascriptreact = { "prettier" },
        typescriptreact = { "prettier" },
        json = { "prettier" },
        markdown = { "prettier" },
        css = { "prettier" },
        html = { "prettier" },
      },
      format_on_save = {
        timeout_ms = 800,
        lsp_format = "fallback",
      },
    },
  },
}

lua/plugins/search.lua:

return {
  {
    "nvim-telescope/telescope.nvim",
    dependencies = { "nvim-lua/plenary.nvim" },
    cmd = "Telescope",
    keys = {
      { "<leader>ff", "<cmd>Telescope find_files<CR>", desc = "Find files" },
      { "<leader>fg", "<cmd>Telescope live_grep<CR>", desc = "Live grep" },
      { "<leader>fb", "<cmd>Telescope buffers<CR>", desc = "Buffers" },
      { "<leader>fh", "<cmd>Telescope help_tags<CR>", desc = "Help" },
    },
  },
}

lua/plugins/git.lua:

return {
  {
    "lewis6991/gitsigns.nvim",
    event = { "BufReadPre", "BufNewFile" },
    opts = {},
  },
}

lua/plugins/editor.lua:

return {
  { "folke/which-key.nvim", event = "VeryLazy", opts = {} },
  {
    "stevearc/oil.nvim",
    opts = { default_file_explorer = true },
    keys = {
      { "-", "<cmd>Oil<CR>", desc = "Open parent directory" },
    },
  },
}

lua/plugins/ui.lua:

return {
  {
    "folke/tokyonight.nvim",
    lazy = false,
    priority = 1000,
    config = function()
      vim.cmd.colorscheme("tokyonight")
    end,
  },
  {
    "nvim-lualine/lualine.nvim",
    event = "VeryLazy",
    opts = {
      options = {
        globalstatus = true,
      },
    },
  },
}

External tools cần cài

Qua Mason:

:MasonInstall lua-language-server typescript-language-server eslint-lsp stylua prettier eslint_d

Qua package manager hệ thống:

brew install ripgrep fd

Tên package Mason có thể khác tên server LSP. Ví dụ lua_ls là server name trong LSP config, còn Mason package là lua-language-server.


Daily cheatsheet

ViệcKey/lệnh
Find file<leader>ff
Live grep<leader>fg
Buffers<leader>fb
Save<leader>w
Formatformat-on-save hoặc <leader>f nếu map
Definitiongd
Renamegrn hoặc <leader>rn
Code actiongra hoặc <leader>ca
Referencesgrr
Diagnostics<leader>e
Quickfix:copen, :cnext, :cprev
Parent dir-
Terminal:terminal
Exit terminal<Esc><Esc>
Lazy UI:Lazy
Mason UI:Mason
Health:checkhealth

Lộ trình nâng cấp sau capstone

Khi setup này đã ổn, hãy thêm theo nhu cầu:

Nhu cầuGợi ý
Test UIneotest
Debuggernvim-dap
Better file opsmini.files hoặc mở rộng Oil
AI codingplugin Copilot/CodeCompanion tùy workflow
Textobjects nâng caoTreesitter textobjects hoặc mini.ai
Surroundmini.surround
Commentsbuilt-in commenting hoặc plugin nếu cần

Chỉ thêm khi bạn gặp nhu cầu thật ít nhất vài lần.


Bài tập tốt nghiệp

  1. Clone dotfiles sang một máy/thư mục mới.
  2. Mở một repo TypeScript.
  3. Cài tools bằng Mason.
  4. Mở file, xác nhận LSP attach bằng :checkhealth vim.lsp.
  5. Sửa code, format, lint, search references, rename local symbol.
  6. Xem Git hunk.
  7. Chạy test trong terminal.
  8. Commit dotfiles nếu mọi thứ ổn.
Checklist đạt chuẩn dùng hằng ngày
[ ] Mở project nhanh
[ ] Find file/search project nhanh
[ ] LSP attach đúng
[ ] Completion hoạt động
[ ] Format on save ổn
[ ] Diagnostics đọc được
[ ] Git hunk hiện
[ ] Terminal dùng được
[ ] Config nằm trong Git
[ ] Có lockfile
[ ] Có cách rollback

Điều cốt lõi

Một setup Neovim pro không phải bản config dài nhất. Nó là setup mà bạn hiểu từng lớp, debug được khi hỏng, update được khi ecosystem đổi, và dùng đủ nhanh để quên rằng mình đang dùng editor nào.