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ệc | Key/lệnh |
|---|---|
| Find file | <leader>ff |
| Live grep | <leader>fg |
| Buffers | <leader>fb |
| Save | <leader>w |
| Format | format-on-save hoặc <leader>f nếu map |
| Definition | gd |
| Rename | grn hoặc <leader>rn |
| Code action | gra hoặc <leader>ca |
| References | grr |
| 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ầu | Gợi ý |
|---|---|
| Test UI | neotest |
| Debugger | nvim-dap |
| Better file ops | mini.files hoặc mở rộng Oil |
| AI coding | plugin Copilot/CodeCompanion tùy workflow |
| Textobjects nâng cao | Treesitter textobjects hoặc mini.ai |
| Surround | mini.surround |
| Comments | built-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
- Clone dotfiles sang một máy/thư mục mới.
- Mở một repo TypeScript.
- Cài tools bằng Mason.
- Mở file, xác nhận LSP attach bằng
:checkhealth vim.lsp. - Sửa code, format, lint, search references, rename local symbol.
- Xem Git hunk.
- Chạy test trong terminal.
- 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.