# ⌨️ Keybinding, leader key và workflow làm việc hiệu quả

By 3T1C

Bạn đã có `init.lua` gọn gàng và một spec `lazy.nvim` với vài plugin đầu tiên. Bài trước dừng ở chỗ "cài được plugin", nhưng cài xong mà vẫn gõ `:Telescope find_files` bằng tay thì chưa phải workflow. Bài này biến phần config đó thành phản xạ của ngón tay: chọn leader key một lần, chia phím theo workflow, và viết keymap bằng Lua có mô tả để ba tháng sau bạn vẫn đọc được.

Sau bài này bạn sẽ:

- Đặt `mapleader` đúng chỗ trong `init.lua`, trước khi plugin load.
- Chia namespace phím theo hành động trong workflow, không theo từng plugin.
- Viết keymap bằng `vim.keymap.set` với `desc`, `<cmd>` và map buffer-local.
- Tự kiểm tra phím đã map bằng `:map`, `:verbose` và which-key.

## Leader key: chọn một lần và đặt ở đầu file

```lua
-- init.lua, đặt ngay trên phần require("lazy").setup
vim.g.mapleader = " "
vim.g.maplocalleader = " "
```

Vì sao Space: đây là phím lớn nhất bàn phím, cả hai ngón cái đều với tới được mà không rời home row. Leader mặc định là `\` nằm tít góc phải, mỗi lần dùng phải liếc mắt xuống. Space ở normal mode chỉ là motion sang phải, gần như không mất gì khi lấy nó làm prefix.

Điểm quan trọng hơn cả việc chọn phím nào: **vị trí đặt**. `vim.g.mapleader` phải được gán trước `require("lazy").setup(...)` và trước bất kỳ `require` nào của plugin, vì nhiều plugin đọc leader ngay lúc `setup` để bind phím của chúng. Nếu bạn set sau, keymap của plugin sẽ bind vào `\` và bạn sẽ ngồi đoán xem config sai ở đâu.

> **Warning:** 
> Triệu chứng kinh điển của leader đặt sai thời điểm: bấm `<leader>ff` không có gì xảy ra, nhưng bấm `\ff` lại chạy. Đừng sửa keymap, hãy kiểm tra thứ tự dòng trong `init.lua` trước.

**Bài tập nhanh 1:** Xác nhận leader hiện tại và xem nó đã resolve thành phím nào.

**Gợi ý**

Dùng `:lua =vim.g.mapleader` để in giá trị, rồi `:nmap <leader>` để xem Neovim đã expand `<leader>` thành gì. Nếu `:nmap <leader>` liệt kê các phím bắt đầu bằng `\`, leader của bạn đang là mặc định.

## Chia namespace theo workflow, không theo plugin

Đây là phần quyết định hệ phím của bạn sống được bao lâu. Nếu map theo plugin, bạn phải nhớ "phím này của plugin nào", và mỗi lần thay plugin bạn phải học lại phím. Nếu map theo hành động, bạn thay plugin thoải mái mà tay vẫn chạy đúng.

| Nhóm workflow | Prefix | Ví dụ |
| --- | --- | --- |
| File | `<leader>f` | `<leader>ff` mở file, `<leader>fr` file gần đây |
| Search | `<leader>s` | `<leader>sg` grep trong dự án |
| Buffer | `<leader>b` | `<leader>bd` đóng buffer hiện tại |
| Code / LSP | `<leader>c` | `<leader>cr` rename, `<leader>ca` code action |
| Git | `<leader>g` | `<leader>gs` status, `<leader>gd` diff |
| Terminal / toggle | `<leader>t` | `<leader>tt` mở terminal nổi |

Quy tắc đặt tên: **ký tự đầu là phạm vi, ký tự sau là động từ**. `<leader>s` là "tìm cái gì đó", `g` là grep. Giữ 5 đến 7 nhóm là đủ; nhiều hơn thì bạn phải nhớ hai tầng, mất lợi ích.

```mermaid
mindmap
  root((Space))
    f["📁 File"]
    s["🔍 Search"]
    b["🧱 Buffer"]
    c["🛠️ Code"]
    g["🌿 Git"]
    t["💻 Terminal"]
```

**Bài tập nhanh 2:** Chọn 3 thao tác bạn làm nhiều nhất trong một ngày làm việc và gán phím cho chúng, biết rằng `:w` đã đủ nhanh nên bạn không bắt buộc phải map save.

**Gợi ý**

Ví dụ hợp lý: `<leader>sg` cho grep, `<leader>bd` cho đóng buffer, `<leader>tt` cho terminal. Lưu ý `<leader>w` thường được dành cho nhóm window; nếu bạn muốn giữ chỗ đó thì cứ để save là `:w`. Nguyên tắc: đừng chiếm một prefix có ý nghĩa khác chỉ vì hôm nay bạn thấy tiện.

## Viết keymap bằng Lua: `desc` là bắt buộc, `<cmd>` là mặc định tốt

```lua
-- lua/config/keymaps.lua
local map = vim.keymap.set

local function nmap(lhs, rhs, desc)
  map("n", lhs, rhs, { silent = true, desc = desc })
end

nmap("<leader>w", "<cmd>write<cr>", "Ghi file hiện tại")
nmap("<leader>bd", "<cmd>bdelete<cr>", "Đóng buffer hiện tại")
nmap("<leader>sg", "<cmd>Telescope live_grep<cr>", "Grep trong dự án")
```

Các option đáng nhớ:

| Option | Dùng khi | Lý do |
| --- | --- | --- |
| `desc` | luôn luôn | nguồn dữ liệu cho `:map`, which-key, Telescope keymaps |
| `remap = true` | thật sự muốn map chồng map | `vim.keymap.set` đã mặc định `noremap` |
| `silent = true` | vế phải là command | không đẩy thông báo lên cmdline |
| `expr = true` | vế phải là hàm trả về chuỗi phím | dùng cho toggle |

Hai chi tiết nhỏ nhưng khác biệt lớn. Thứ nhất, `<cmd>write<cr>` không đi qua cmdline nên không đổi mode, không bị ảnh hưởng bởi mapping của `:`, và không nháy màn hình. Thứ hai, khi vế phải là một Lua function (như `vim.lsp.buf.rename`), bạn truyền thẳng function, không cần bọc `<cmd>`.

Map theo buffer thì để trong autocmd, vì phím LSP chỉ nên sống trong buffer có LSP:

```lua
vim.api.nvim_create_autocmd("LspAttach", {
  callback = function(ev)
    local buf = ev.buf
    map("n", "gd", vim.lsp.buf.definition, { buffer = buf, desc = "Nhảy tới định nghĩa" })
    map("n", "<leader>cr", vim.lsp.buf.rename, { buffer = buf, desc = "Rename symbol" })
  end,
})
```

Trên Windows 11, hãy tránh lấy `<C-s>`, `<C-q>` hay `<C-v>` làm phím sống còn: một số terminal giữ lại các tổ hợp này trước khi Neovim kịp nhận. Phần xử lý triệt để nằm ở Module 3, còn ở đây chỉ cần biết để không thiết kế phím dựa vào chúng.

**Bài tập nhanh 3:** Viết map `<leader>tt` mở terminal nổi, có `desc`, và giải thích trong một câu vì sao bạn dùng `<cmd>` thay `:`.

**Gợi ý**

```lua
nmap("<leader>tt", "<cmd>split | terminal<cr>", "Mở terminal trong split")
```

`<cmd>` chạy command trực tiếp, không qua cmdline, nên không bị ảnh hưởng bởi các mapping của `:` và không đổi mode khi bạn đang ở normal mode.

## Để config tự dạy bạn phím

Một hệ phím tốt phải tự giải thích được. `:map <leader>` liệt kê mọi phím bắt đầu bằng leader, `:verbose nmap <leader>ff` cho biết file nào đã định nghĩa phím đó. Nếu spec `lazy.nvim` của bạn đã có which-key, bấm leader rồi dừng lại sẽ thấy popup các nhóm phím, và nó đọc chính các `desc` bạn viết. Đó là lý do `desc` không phải trang trí.

**Bài tập nhanh 4:** Một phím bấm không có phản ứng gì. Nêu ba bước kiểm tra theo thứ tự.

**Gợi ý**

1. `:verbose nmap <phím>` xem phím có được map không và map ở file nào (nếu không có kết quả, phím chưa được map).
2. `:lua =vim.g.mapleader` xem leader có đúng không, và kiểm tra nó được set trước `lazy.setup` chưa.
3. Kiểm tra xung đột prefix: một map ngắn hơn có thể nuốt phím của bạn, hoặc plugin khác map đè sau.

## Những quy tắc giữ cho keybinding không phản bội bạn

- Chỉ map thao tác bạn đã làm thật ít nhất vài lần trong tuần. Map trước khi dùng là cách nhanh nhất để có một bộ phím bạn không nhớ.
- Không map hai tổ hợp cho cùng một việc, trừ khi đang chuyển tiếp có chủ đích.
- Giữ `keymaps.lua` phẳng cho phím global. Phím phụ thuộc filetype hoặc LSP thì để trong autocmd với `buffer`.
- Viết `desc` ngay lúc viết map. Ba tháng sau bạn sẽ không nhớ `<leader>cx` là gì, nhưng `:map` thì nhớ.
- Đừng map lại `hjkl`, `x`, `p`, `u`, `.` hay các motion bạn đã nhuyễn. Bạn đến đây để giữ tốc độ, không phải để học lại Vim.
- Khi thêm plugin mới, nhét phím của nó vào nhóm có sẵn. Chỉ tạo nhóm mới khi workflow thật sự mới.

## Chốt lại và bước kế tiếp

Bốn điều cần mang theo: leader được set trước `lazy.setup`, namespace chia theo workflow chứ không theo plugin, mọi keymap đều có `desc`, và `<cmd>` là vế phải mặc định cho command. Đây là **bộ phím phản ánh công việc của bạn, không phải bản sao config của người khác**.

Bài tiếp theo là checkpoint của Module 1: bạn sẽ tự dựng lại bộ khung `init.lua`, cài plugin qua `lazy.nvim` và chứng minh hệ phím của mình chạy đúng trước khi bước sang LSP, Telescope và Treesitter.

Front: vim.g.mapleader phải được đặt ở đâu trong init.lua?
Back: Trước require("lazy").setup(...) và trước khi bất kỳ plugin nào được load.

Front: Vì sao Space là leader key được khuyên dùng?
Back: Phím lớn nhất, hai ngón cái với tới mà không rời home row, và gần như không mất chức năng gốc.

Front: Triệu chứng của việc set leader quá muộn là gì?
Back: <leader>ff không chạy nhưng \ff chạy, vì plugin đã bind phím với leader mặc định.

Front: Quy tắc đặt tên một nhóm phím theo workflow?
Back: Ký tự đầu là phạm vi hành động, ký tự sau là động từ, ví dụ <leader>sg là grep trong nhóm search.

Front: Vì sao chia phím theo workflow thay vì theo plugin?
Back: Plugin có thể bị thay hoặc tháo, còn phím gắn với hành động thì tay vẫn chạy đúng.

Front: vim.keymap.set mặc định có bật noremap không?
Back: Có, mặc định là noremap; chỉ đặt remap = true khi thật sự muốn map chồng map.

Front: Option desc trong keymap dùng để làm gì?
Back: Mô tả phím, hiển thị trong :map, which-key và Telescope keymaps.

Front: <cmd>write<cr> khác :write<cr> ở điểm nào?
Back: <cmd> không đi qua cmdline, không đổi mode và không bị ảnh hưởng bởi mapping của dấu hai chấm.

Front: Muốn một keymap chỉ có hiệu lực trong buffer hiện tại thì dùng option nào?
Back: buffer = bufnr, thường đặt trong autocmd LspAttach với ev.buf.

Front: Lệnh nào cho biết keymap được định nghĩa ở file nào?
Back: :verbose nmap <lhs>

Front: silent = true trong keymap có tác dụng gì?
Back: Không hiển thị command hoặc thông báo trên cmdline khi phím được bấm, tránh cmdline nhấp nháy và giữ màn hình sạch.

Front: Vì sao nên map <leader>w để ghi file thay vì dựa vào :w?
Back: Một phím thay cho ba thao tác gõ, và tay không phải rời vị trí làm việc để vào cmdline.

Front: Điều gì xảy ra nếu hai keymap trùng <lhs> trong cùng mode?
Back: Cái định nghĩa sau ghi đè cái trước, và Neovim không báo lỗi, nên rất dễ mất phím mà không biết.

## Chốt lại và bước tiếp theo

Ba ý cần mang theo vào buổi làm việc thật:

- Leader key phải được set trước khi `require("lazy").setup()`, nếu không plugin sẽ bind phím với leader mặc định là `\`.
- Mỗi keymap đi qua `vim.keymap.set` với `desc`, và `<cmd>...<cr>` là dạng rhs mặc định cho lệnh Ex.
- Phím được tổ chức theo workflow (nhóm hành động), không theo tên plugin, để sau này thay Telescope bằng thứ khác thì tay vẫn chạy đúng.

Cấu hình `lua/config/keymaps.lua` bạn vừa viết chính là thứ sẽ được kiểm tra ở bài checkpoint tiếp theo, nơi bạn ráp toàn bộ `init.lua`, spec lazy.nvim và keymaps thành một bộ khung chạy được, kèm vài lỗi cố ý để bạn tự sửa.

---

## Contents

Part of [🛠️ Config neovim trên win 11](https://everwise.study/courses/config-neovim-tren-win-11.md)

- **🧱 Nền tảng cấu hình bằng Lua và plugin manager**
  - [📁 Bộ khung init.lua và cách tổ chức file cấu hình trên Windows 11](https://everwise.study/courses/config-neovim-tren-win-11/bo-khung-init-lua-tren-windows-11.md)
  - [📦 Quản lý plugin với lazy.nvim và hướng mở rộng với vim.pack](https://everwise.study/courses/config-neovim-tren-win-11/quan-ly-plugin-lazy-nvim-va-vim-pack.md) (sign-in required)
  - [⌨️ Keybinding, leader key và workflow làm việc hiệu quả](https://everwise.study/courses/config-neovim-tren-win-11/keybinding-leader-key-va-workflow.md) — this lesson
  - [✅ Checkpoint: Nền tảng cấu hình Lua và plugin manager](https://everwise.study/courses/config-neovim-tren-win-11/checkpoint-nen-tang-cau-hinh-lua.md)
- **🧠 Đưa Neovim lên mức IDE: LSP, Telescope, Treesitter**
  - [🔍 LSP, autocomplete và diagnostics](https://everwise.study/courses/config-neovim-tren-win-11/lsp-autocomplete-va-diagnostics.md)
  - [🔭 Telescope: tìm file và grep trong dự án](https://everwise.study/courses/config-neovim-tren-win-11/telescope-tim-file-va-grep.md)
  - [🌳 Treesitter: cú pháp, highlight và folding](https://everwise.study/courses/config-neovim-tren-win-11/treesitter-cu-phap-va-folding.md)
  - [✅ Checkpoint: LSP, Telescope và Treesitter](https://everwise.study/courses/config-neovim-tren-win-11/checkpoint-lsp-telescope-treesitter.md)
- **🚀 Capstone: cấu hình hoàn chỉnh dùng cho công việc thật**
  - [🌿 Tích hợp Git và terminal trong Neovim](https://everwise.study/courses/config-neovim-tren-win-11/tich-hop-git-va-terminal.md)
  - [🩺 Khắc phục lỗi đặc thù trên Windows 11](https://everwise.study/courses/config-neovim-tren-win-11/khac-phuc-loi-dac-thu-tren-windows-11.md)
  - [🏗️ Capstone: đóng gói cấu hình Neovim cho công việc thật](https://everwise.study/courses/config-neovim-tren-win-11/capstone-dong-goi-cau-hinh-neovim.md) (premium content)
  - [✅ Checkpoint: Capstone và xử lý sự cố trên Windows](https://everwise.study/courses/config-neovim-tren-win-11/checkpoint-capstone-windows.md) (premium content)
- [Course cheatsheet](https://everwise.study/courses/config-neovim-tren-win-11/cheatsheet.md) (premium content)
