强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

Vim / Neovim 完全指南 / 05 - 编辑命令

“Macros in Vim are just recorded sequences of keystrokes — and that’s what makes them so powerful.”

5.1 修改操作(Change)

5.1.1 修改命令详解

命令含义说明
cchange删除并进入插入模式
ccchange line修改整行内容
Cchange to end修改到行尾
ssubstitute char替换当前字符
Ssubstitute line替换整行(等同 cc
rreplace替换单个字符
Rreplace mode进入替换模式
cw      " 修改到下一词尾
c2w     " 修改 2 个单词
ciw     " 修改光标所在的单词
ci"     " 修改引号内内容
cc      " 清空当前行并输入新内容
C       " 删除光标到行尾并输入
3cc     " 修改 3 行

5.1.2 替换模式(Replace Mode)

R       " 进入替换模式(覆盖已有字符)
gR      " 虚拟替换模式(按屏幕宽度替换)
r{char} " 替换当前单个字符
gr{char} " 虚拟替换单个字符

5.2 宏(Macro)

5.2.1 宏的概念

宏是 Vim 记录的一系列按键操作,可以无限回放。宏是 Vim 自动化的核心。

录制 → 存储到寄存器 → 回放 → 重复执行

5.2.2 宏操作命令

命令功能
q{a-z}开始录制宏到寄存器
q停止录制
@{a-z}回放寄存器中的宏
@@重复上次回放的宏
{n}@{a-z}回放 n 次
@:重复上次 Ex 命令
@/重复上次搜索

5.2.3 宏录制最佳实践

关键原则:确保宏可以从任意位置安全重复执行。

好的宏开头:  0     " 回到行首(确保起始位置一致)
好的宏结尾:  j     " 移到下一行(准备重复)

坏的宏:  f,;cw  " 依赖当前光标位置,不可靠

5.2.4 宏实战

场景一:批量添加行首注释

原始:
function_a()
function_b()
function_c()
function_d()

目标:
# function_a()
# function_b()
# function_c()
# function_d()

步骤:
1. 光标在第一行
2. qa          " 开始录制到寄存器 a
3. I# <Esc>    " 行首插入 '# '
4. j           " 移到下一行
5. q           " 停止录制
6. 3@a         " 回放 3 次

场景二:批量格式化 JSON 键

原始:
{"name":"John","age":30,"city":"New York"}

目标(将冒号后加空格):
{"name": "John","age": 30,"city": "New York"}

步骤:
1. qa
2. f:         " 跳到冒号
3. l          " 右移一位
4. i <Esc>    " 插入空格
5. q
6. ;.         " 重复查找冒号并重复修改

场景三:将 CSV 行转为 SQL INSERT

原始:
1,Alice,alice@example.com
2,Bob,bob@example.com
3,Charlie,charlie@example.com

目标:
INSERT INTO users (id, name, email) VALUES (1, 'Alice', 'alice@example.com');

步骤:
qa
0
cwINSERT INTO users (id, name, email) VALUES (<Esc>
f,
x
i, '<Esc>
f,
x
i', '<Esc>
$
x
i');<Esc>
j
q
2@a

5.2.5 编辑宏寄存器

宏存储在寄存器中,可以直接编辑:

" 查看寄存器 a 的内容
:reg a

" 粘贴寄存器内容到文件中
"ap

" 编辑宏内容(在缓冲区中修改)
" 修改后选中并复制回寄存器
"ayy    " 将当前行复制到寄存器 a

5.3 寄存器(Register)

5.3.1 寄存器类型

寄存器名称用法
""无名寄存器默认寄存器
"0复制寄存器仅存放 yank 操作
"1-"9数字寄存器存放删除/修改的内容
"-小删除寄存器不跨行的删除
"a-"z命名寄存器用户自定义
"+系统剪贴板与外部程序共享
"*选择寄存器X11 主选择
".上次插入上次 Insert 模式输入的文本
":上次命令上次执行的 Ex 命令
"/上次搜索上次搜索的模式
"_黑洞寄存器删除但不影响其他寄存器

5.3.2 使用寄存器

" 指定寄存器进行操作
"ayy       " 复制当前行到寄存器 a
"ap        " 粘贴寄存器 a 的内容
"bdd       " 删除当前行到寄存器 b
"_dd       " 删除当前行(不保存到任何寄存器)

" 查看所有寄存器
:reg
:reg a b c    " 查看特定寄存器

" 系统剪贴板
"+y           " 复制到系统剪贴板
"+p           " 从系统剪贴板粘贴
"*y           " 复制到 X11 选择

5.3.3 寄存器的工作流程

dd(删除行):
  → 内容存入 "1
  → "1 的旧内容移到 "2
  → ... 最多到 "9

yy(复制行):
  → 内容存入 "0(不影响 "1-"9)

p(粘贴):
  → 从 ""(无名寄存器)读取
  → 如果刚 yy:"" = "0 = 复制的内容
  → 如果刚 dd:"" = "1 = 删除的内容

注意:这是 pdd 后的行为——它粘贴的是刚删除的内容。如果 dd 后又 dd 了另一行,第一个 dd 的内容就在 "2 了。


5.4 撤销(Undo)与重做(Redo)

5.4.1 基本操作

u         " 撤销
<C-r>     " 重做(反撤销)
U         " 撤销整行的所有修改(仅在当前行有效)
.         " 重复上次 Normal 模式的修改

5.4.2 撤销树(Undo Tree)

Vim 的撤销不是线性的,而是一棵树:

原始文本
   │
   ├── 修改 1
   │    ├── 修改 2
   │    │    └── 修改 3 (当前)
   │    └── 修改 4 (分支)
   │
   └── 修改 5 (另一个分支)
" 在撤销树中导航
g-        " 更旧的文本状态
g+        " 更新的文本状态
:earlier 5m    " 跳到 5 分钟前的状态
:later 10m     " 跳到 10 分钟后的状态
:earlier 100   " 跳到 100 次修改前
:undolist      " 查看撤销历史

5.4.3 持久化撤销

" 启用持久化撤销(关闭文件后仍可撤销)
set undofile
set undodir=~/.vim/undodir    " Vim
" Neovim 默认在 ~/.local/state/nvim/undo/
-- Neovim Lua 配置
vim.opt.undofile = true
vim.opt.undodir = vim.fn.stdpath("state") .. "/undo"

5.4.4 撤销块(Undo Block)

" 以下操作会创建独立的撤销块:
" 1. 从 Insert 模式返回 Normal
iHello<Esc>     " 撤销一次即可删除 "Hello"

" 2. 在 Insert 模式中使用 <C-u>
iHello<C-u>World<Esc>
" 撤销会删除 "World",再撤销会恢复 "Hello"

" 3. 使用 Ctrl-G u 手动分割撤销块
iHello<C-g>uWorld<Esc>
" 撤销删除 "World",再撤销删除 "Hello"

5.5 点命令(Dot Command)

5.5.1 点命令的威力

.(点命令)重复上次 Normal 模式的修改。它只重复最后一次"修改"(从进入 Insert 到返回 Normal 的完整操作)。

" 修改一个单词
ciwnew_word<Esc>

" 移到下一个位置
w

" 重复修改
.
w
.

5.5.2 点命令与操作符结合

" 范式:[操作][移动] → . 重复

" 删除每行第一个引号前的空格
f"xx       " 跳到引号 2 字符
j          " 下一行
.          " 重复删除
j
.

5.5.3 点命令的局限

" 点命令无法重复:
" - 光标移动(j, k, w, b 等)
" - Ex 命令(使用 @: 重复)
" - 搜索(使用 n/N 重复)
" - 宏录制中的命令

" 点命令可以重复:
" - 操作符 + 动作(dw, ciw 等)
" - 单字符操作(x, r{char}, ~ 等)
" - 从 Normal 进入 Insert 模式的完整输入

5.6 高级编辑技巧

5.6.1 交换字符/行

xp      " 交换当前字符与下一个字符
ddp     " 交换当前行与下一行
ddkP    " 交换当前行与上一行

5.6.2 大小写转换

~       " 翻转当前字符大小写并移动
g~iw    " 翻转单词大小写
gUiw    " 单词转大写
guiw    " 单词转小写
g~$     " 到行尾翻转大小写
gUU     " 整行转大写
guu     " 整行转小写

5.6.3 数字增减

<C-a>   " 光标下的数字加 1
<C-x>   " 光标下的数字减 1
5<C-a>  " 加 5
3<C-x>  " 减 3
" 业务场景:版本号递增
version = "1.0.3"
" 光标放在 3 上
<C-a>   " → version = "1.0.4"

5.6.4 排序

" 对选中的行排序
vip:sort<CR>        " 段落内排序
:%sort<CR>          " 全文排序
:%sort u<CR>        " 排序并去重
:%sort n<CR>        " 按数字排序
:%sort i<CR>        " 忽略大小写排序
:%sort! r<CR>       " 逆序排序

5.6.5 格式化

=ip     " 自动缩进段落
=G      " 自动缩进到文件末尾
gqip    " 格式化段落(textwidth 内换行)
gwip    " 格式化但不移动光标

5.7 练习

练习一:批量注释

# 创建练习文件
cat > /tmp/macro-practice.txt << 'EOF'
line one
line two
line three
line four
line five
EOF
nvim /tmp/macro-practice.txt
" 步骤:用宏为每行添加 # 前缀
gg        " 到文件开头
qa        " 开始录制
I# <Esc>  " 行首插入 '# '
j         " 下一行
q         " 停止录制
4@a       " 回放 4 次

练习二:点命令练习

" 任务:将所有 foo 替换为 bar
" 思路:搜索 + 修改 + 点命令

/foo<CR>       " 搜索 foo
cawbar<Esc>    " 修改为 bar
n              " 下一个匹配
.              " 重复修改
n              " 下一个
.              " 重复

5.8 业务场景

场景推荐方式
批量格式转换宏录制
重复相同修改点命令 .
临时不保存的删除黑洞寄存器 "_d
跨文件复制系统剪贴板 "+y
版本号递增<C-a> / <C-x>
批量排序:sort 命令
代码格式化=ip / =G

5.9 总结

功能核心命令
修改c cc C s S r R
q{a-z} 录制 @{a-z} 回放 @@ 重复
寄存器"{a-z} 命名 "+ 系统 "_ 黑洞
撤销/重做u 撤销 <C-r> 重做 g- g+ 树导航
点命令. 重复上次修改
大小写~ g~ gu gU
数字<C-a><C-x>

下一步第 06 章 - 可视模式 → 掌握 Visual、Visual Line、Visual Block 模式下的范围操作。


扩展阅读