jvinhit//lab

Search posts

Type to search across journal entries.

navigate open esc close

Neovim · Part 2 — Cài đặt, cấu trúc Lua config và nền móng

Dựng cấu hình Neovim sạch từ đầu: init.lua, options, keymaps, autocommands, leader key, backup config và health check.

Phần này dựng nền móng: một config Neovim sạch, nhỏ, đọc được, đủ để mở project và không bị lạc.

Ta chưa cài plugin. Nếu nền không vững, plugin chỉ làm lỗi khó debug hơn.


Cấu trúc config

Neovim đọc config ở:

~/.config/nvim/

Trên Windows thường là:

~/AppData/Local/nvim/

Kiểm tra chính xác trong Nvim:

:echo stdpath('config')
:echo stdpath('data')

Tạo cấu trúc ban đầu:

~/.config/nvim/
├─ init.lua
└─ lua/
   └─ config/
      ├─ options.lua
      ├─ keymaps.lua
      └─ autocmds.lua

init.lua chỉ nên là cửa vào:

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

Quy tắc: file nào làm việc nấy. Đừng để init.lua thành một bức tường 1000 dòng.


Options nền tảng

Tạo 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.list = true
opt.listchars = { tab = "» ", trail = "·", nbsp = "␣" }
opt.inccommand = "split"
opt.cursorline = true
opt.scrolloff = 8

opt.expandtab = true
opt.shiftwidth = 2
opt.tabstop = 2
opt.smartindent = true

Giải thích nhanh:

OptionVì sao bật
numberbiết dòng hiện tại
relativenumbermotion theo số dòng dễ hơn
undofileundo còn sau khi đóng file
signcolumn=yesdiagnostic/git sign không làm layout nhảy
splitright/splitbelowsplit giống trực giác hiện đại
ignorecase/smartcasesearch thường thông minh hơn
clipboard=unnamedpluscopy/paste với system clipboard

Nếu clipboard không hoạt động, chạy :checkhealth provider. Trên Linux bạn có thể cần xclip, xsel, wl-clipboard, hoặc OSC 52 tùy terminal/SSH.


Keymaps đầu tiên

Tạo 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>q", "<cmd>quit<CR>", { desc = "Quit window" })

map("n", "<C-h>", "<C-w><C-h>", { desc = "Move to left window" })
map("n", "<C-j>", "<C-w><C-j>", { desc = "Move to lower window" })
map("n", "<C-k>", "<C-w><C-k>", { desc = "Move to upper window" })
map("n", "<C-l>", "<C-w><C-l>", { desc = "Move to right window" })

map("v", "J", ":m '>+1<CR>gv=gv", { desc = "Move selection down" })
map("v", "K", ":m '<-2<CR>gv=gv", { desc = "Move selection up" })

desc quan trọng. Các plugin như which-key, picker, hoặc help UI có thể đọc mô tả này.

Leader key nên đặt trước khi plugin load. Series này dùng Space.


Autocommands nhỏ nhưng đáng tiền

Tạo lua/config/autocmds.lua:

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

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

vim.api.nvim_create_autocmd("BufReadPost", {
  group = group,
  desc = "Jump to last edit position",
  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,
})

Autocmd là nơi gắn hành vi theo sự kiện: sau khi yank, sau khi đọc file, trước khi save, khi FileType đổi.


Lệnh sống còn

LệnhÝ nghĩa
:source %reload file config đang mở
:messagesxem lỗi/message gần đây
:checkhealthkiểm tra môi trường
:verbose set option?biết option được set ở đâu
:mapxem keymap
:verbose map <leader>wkeymap này đến từ đâu
:lua =vim.opt.shiftwidth:get()inspect Lua value
:h option-listmở docs option

Khi config lỗi, đừng xóa bừa. Dùng :messages, :checkhealth, và :verbose.


Backup trước khi phá

Nếu bạn đã có config cũ:

mv ~/.config/nvim ~/.config/nvim.bak
mv ~/.local/share/nvim ~/.local/share/nvim.bak
mv ~/.local/state/nvim ~/.local/state/nvim.bak
mv ~/.cache/nvim ~/.cache/nvim.bak

Sau đó tạo config mới. Nếu muốn quay lại, đổi tên .bak về chỗ cũ.

Đừng xóa thẳng khi chưa hiểu. Config editor là công cụ làm việc hằng ngày; đối xử với nó như source code.


Bài tập

  1. Tạo cấu trúc config như trên.
  2. Mở nvim ~/.config/nvim/init.lua, chạy :source %.
  3. Bật/tắt relativenumber bằng :set relativenumber!.
  4. Tạo keymap <leader>x để đóng buffer hoặc quit window.
  5. Dùng :verbose map <leader>w để xem keymap đến từ đâu.
Lời giải gợi ý
vim.keymap.set("n", "<leader>x", "<cmd>quit<CR>", { desc = "Close window" })

Nếu muốn đóng buffer sau này, khi đã có plugin bufferline hoặc mini.bufremove, ta sẽ map lại. Giai đoạn đầu cứ giữ đơn giản.


Điều cốt lõi

Một config Neovim tốt bắt đầu từ options ít, keymaps rõ, autocmds có tên, và mọi thứ chia file vừa đủ. Plugin chưa cần xuất hiện cho tới khi bạn biết nền này đang làm gì.