服务端5月27日 15:07
Vim 中删除、复制、粘贴、撤销和替换命令怎么用?Vim 的编辑效率几乎全部来自普通模式下的单键命令——删、复、粘、撤、换,五个动词覆盖了日常文本操作的大半场景。下面逐类拆解,每条命令都给出真实用法,不省略边界情况。
## 删除:x / dd / D / dw / d$ / db
删除是 Vim 里最频繁的操作,也是理解 Vim「动词+名词」语法的入口。
| 命令 | 作用 | 注意 |
|------|------|------|
| `x` | 删除光标所在字符 | 等价于 `dl`,不进入插入模式 |
| `dd` | 删除整行 | 最常用的行删除,删后下方行上移 |
| `D` | 删除到行尾 | 等价于 `d$`,保留行首到光标前的内容 |
| `dw` | 删除到下一个词首 | 如果光标在词首,整个词加尾部空格一起删 |
| `d$` | 删除到行尾 | 与 `D` 等价,但更显式 |
| `db` | 删除到上一个词首 | 向左删除到当前词或前一词的开头 |
一个容易踩的坑:`dw` 在光标位于词尾时,会删到下一个词首,连同中间空格一并删除。如果你只想删当前词而不管光标在哪,用文本对象 `diw` 或 `daw` 更可靠——后面会讲。
删除的内容不会消失,而是进入无名寄存器 `""`,随时可以粘贴回来。如果你想删除但不污染寄存器,用黑洞寄存器:`"_dd`,这行删了就彻底没了。
## 复制:yy / Y / yw / y$
Vim 把复制叫 yank,对应的动词是 `y`。它的用法和 `d` 完全对称——`d` 能接的 motion,`y` 都能接。
- `yy`:复制整行,最常用。`yy` 后按 `p` 就能在下一行粘贴出同样的内容。
- `Y`:在 Vim 中等同于 `yy`(复制整行),和 `D` 不删到行尾的对称关系不同,这里容易误解。如果你想让 `Y` 行为变成 `y$`,可以在 vimrc 里加 `nnoremap Y y$`。
- `yw`:从光标位置复制到下一个词首。
- `y$`:从光标位置复制到行尾。
复制的文本同样进入无名寄存器。此外,最近一次 yank 的内容还会存入寄存器 `"0`。这意味着即使你之后做了删除操作覆盖了无名寄存器,`"0p` 仍然能粘贴之前 yank 的内容——这个技巧在实际编辑中非常实用。
## 粘贴:p / P
粘贴只有两个键,但行为取决于你复制/删除的是行还是字符:
- `p`:粘贴到光标**之后**。如果寄存器内容是整行,粘贴到当前行下方;如果是字符片段,粘贴到光标右侧。
- `P`:粘贴到光标**之前**。行内容贴到上方,字符内容贴到左侧。
一个经典用法:先用 `yy` 复制一行,再把光标移到目标位置按 `p`,新行就出现在下方。如果想替换已有行,先 `dd` 删掉目标行再 `p`。
可视模式下的粘贴行为略有不同:选中一段文本后按 `p`,选中的内容会被寄存器内容替换,而被替换掉的文本又进入寄存器——可以用来做文本交换。
## 撤销与重做:u / U / Ctrl-r
编辑出错就要撤回,Vim 提供了三个层级的撤销:
- `u`:撤销最近一次修改。连续按 `u` 会依次往前撤,直到文件打开时的状态。
- `U`:撤销当前行的所有修改,把这一行恢复到最近一次进入时的样子。注意 `U` 本身也是一个修改,可以用 `u` 再撤销掉。
- `Ctrl-r`:重做被 `u` 撤销的操作,往前推进。
一个实际建议:如果你不确定撤销到了哪一步,可以按 `g-` 跳到更早的文件状态,按 `g+` 跳到更晚的状态。Vim 用撤销树而非线性撤销,`g-/g+` 按时间遍历,`u/Ctrl-r` 按分支遍历,两者适用场景不同。
## 替换:r / R / s / S / ~
替换命令让你在不离开普通模式的情况下修改文本:
- `r{char}`:用 `{char}` 替换光标下的单个字符。比如光标在 `a` 上,按 `rx` 就变成 `x`,然后光标停原地,仍在普通模式。
- `R`:进入替换模式(Replace mode),逐字替换,每输入一个字符就覆盖光标下的字符,直到按 `Esc` 退出。
- `s`:删除光标下的字符并进入插入模式。等价于 `cl`,适合只改一个字符的场景。
- `S`:删除当前行内容(保留缩进)并进入插入模式。等价于 `^C`,适合重写一行但保留缩进层级。
- `~`:切换光标下字符的大小写,并自动右移。连续按 `~~~` 可以翻转三个字符。
`~` 在可视模式下尤其好用:选中一段文本后按 `~`,整块内容的大小写全部翻转。
## 与文本对象结合:diw / daw / ciw
文本对象是 Vim 区别于其他编辑器的核心特性。它的语法是「操作符 + i/a + 对象」,`i` 表示 inner(不含周围空格),`a` 表示 around(含周围空格)。
以 word 对象为例:
- `diw`:删除光标所在词,不管光标在词的哪个位置。删完后留下空格。
- `daw`:删除光标所在词及其相邻空格,删完后前后词紧邻。
- `ciw`:删除光标所在词并进入插入模式,可以直接输入新词替换。
和 `dw` 的关键区别:`dw` 是从光标位置删到下一个词首,如果光标在词中间,只会删掉半个词;而 `diw` / `daw` 总是删掉完整词,不依赖光标的精确位置。
文本对象不限于词。`di"` 删除双引号内的内容,`ci(` 删除括号内内容并进入插入模式,`yi'` 复制单引号内的文本——这些组合在日常编码中极其高频。
## 与计数结合:3dd / 5x
Vim 的几乎所有命令都可以在前面加数字表示重复次数:
- `3dd`:删除 3 行(从当前行开始)。
- `5x`:删除 5 个字符。
- `2yy`:复制 2 行。
- `3p`:粘贴 3 次。
- `5r-`:用 `-` 替换光标及后面共 5 个字符,变成 `-----`,画分隔线很好用。
计数也可以和文本对象组合:`d3w` 删除 3 个词,`y2iw` 复制 2 个词(注意 `2iw` 把两个词当作一个对象)。
实际使用中,`3dd` 和 `2yy` 这类整行操作最常见,字符级计数用得相对少——毕竟你可以用可视模式更直观地选择范围。
## 寄存器指定:"ay / "ap
Vim 有超过 20 个寄存器,默认操作都走无名寄存器。当你需要保留多段文本时,命名寄存器就派上用场了:
- `"ayy`:把当前行复制到寄存器 `a` 中。
- `"ap`:粘贴寄存器 `a` 的内容。
- `"bdd`:把当前行删除并存入寄存器 `b`。
- `"bp`:粘贴寄存器 `b` 的内容。
命名寄存器用小写字母 `a-z`,共 26 个。如果用大写字母 `"Ayy`,则是追加到寄存器 `a` 而非覆盖。
几个特殊寄存器也值得了解:
- `"0`:最近一次 yank 的内容,不会被删除操作覆盖。
- `"+`:系统剪贴板,`"+yy` 复制行到系统剪贴板,`"+p` 从系统剪贴板粘贴。
- `"_`:黑洞寄存器,`"_dd` 删除且不存入任何寄存器。
## 可视模式下的操作
可视模式让你先选中、再操作,对于不规则范围的编辑非常直观:
1. 按 `v` 进入字符可视化,用方向键或 `w/b/e` 扩展选择范围。
2. 按 `V` 进入行可视化,整行整行地选。
3. 按 `Ctrl-v` 进入块可视化,可以选中矩形区域。
选中后可以执行:
- `d`:删除选中内容。
- `y`:复制选中内容。
- `p`:用寄存器内容替换选中内容。
- `r{char}`:把选中区域每个字符都替换为 `{char}`,批量注释代码时用 `r#` 非常方便。
- `~` / `U` / `u`:翻转选中内容的大小写。
块可视化还有一个隐藏技巧:选中列后按 `I`(大写 i)输入文本,再按两次 `Esc`,输入的内容会出现在选中列的每一行——这是批量在行首加缩进或注释标记的标准做法。
---
以上命令全部在普通模式下执行(可视模式的选中阶段除外)。记住一个核心规律:Vim 的命令遵循「计数 + 操作符 + 范围/对象」的语法,掌握了 `d/y/c/r` 这几个操作符,配合 motion 和文本对象,就能组合出绝大多数编辑操作。不需要死记,用两周肌肉记忆就会形成。标签
Vim
Vim 是一种高度可配置的文本编辑器,用于有效地创建和更改任何类型的文本。它是从 vi 编辑器衍生出来的,最初由 Bram Moolenaar 于 1991 年发布。Vim 是自由和开源软件,并且包含了许多增强功能,使其成为程序员和文本编辑者广泛使用的工具之一。

服务端5月27日 15:07
Vim 和 Neovim 到底有什么区别?现在该选哪个?## 从一次被拒绝的补丁说起
2014 年,巴西开发者 Thiago de Arruda 向 Vim 项目提交了一组补丁,核心诉求是给 Vim 加上异步执行能力。Vim 的作者 Bram Moolenaar 拒绝了这组补丁——理由是对一个有二十多年历史的代码库做这么大的架构改动,风险太高。Thiago 随后 fork 了 Vim,通过众筹筹到一万美元,Neovim 项目就此诞生。这不是一次意气用事的分叉,而是一场关于"编辑器该往哪走"的根本分歧。
## 历史分叉:2014 年发生了什么
Vim 的代码库从 1991 年开始积累,到 2014 年已经超过 30 万行 C 代码,包含大量对 Amiga、OS/2 等早已无人使用的平台的支持。Bram 坚持通过邮件列表接收 patch 的开发流程,合并代码的节奏非常保守。Thiago 认为这种模式已经严重阻碍了 Vim 的进化,他的目标很明确:砍掉过时代码、引入现代架构、建立更开放的社区治理。
2015 年 12 月,Neovim 第一个公开版本 0.1 发布。有意思的是,Neovim 的出现反而刺激了 Vim 自身的开发——2016 年 Vim 8.0 发布,加入了异步 job 和 timer 功能,这在一定程度上是对 Neovim 竞争的回应。2023 年 Bram Moolenaar 去世后,Vim 社区的开发节奏明显放缓,而 Neovim 仍在高速迭代。
## 配置语言:Lua 对阵 VimScript
这是用户感受最直接的区别。Vim 的配置语言是 VimScript,一门诞生于编辑器内部的脚本语言,语法松散、执行效率低、调试困难。Vim9 script 试图改善性能,但生态基本没有跟上。
Neovim 选择 Lua 作为一等配置语言。Lua 本身就是一门成熟的嵌入式脚本语言,执行速度远超 VimScript,JIT 编译后差距更大。实际写起来:
- VimScript 配置:`let g:mapleader = ','`
- Lua 配置:`vim.g.mapleader = ','`
语法差异不大,但 Lua 的优势在于模块化。你可以把配置拆成多个 `.lua` 文件,用 `require` 加载,而不是在一个巨大的 `init.vim` 里用 `source` 拼接。Neovim 仍然兼容 VimScript,所以迁移不是一步到位的,可以在 `init.lua` 里混用 `vim.cmd` 调用 VimScript 命令。
## 内置 LSP:IDE 级别的语言支持
Neovim 0.5(2021 年 7 月发布)引入了原生 LSP 客户端 `vim.lsp`。这意味着你不需要安装 coc.nvim 或 ALE 这样的第三方插件,Neovim 开箱就能连接语言服务器,获得代码补全、跳转定义、悬浮文档、重命名、诊断等能力。
Vim 至今没有内置 LSP 客户端。如果你想在 Vim 里获得类似体验,只能依赖 coc.nvim(基于 Node.js)或 vim-lsp,配置复杂度和资源占用都更高。
配合 mason.nvim 插件,Neovim 可以一键安装和管理语言服务器,整个 LSP 工作流已经和 VS Code 一样顺畅。
## Tree-sitter:语法高亮的范式转变
Vim 的语法高亮基于正则表达式,这是 90 年代的设计。正则匹配无法理解代码结构,所以高亮经常出错,尤其是嵌套模板、JSX、混合语言文件这类场景。
Neovim 集成了 Tree-sitter,一个基于抽象语法树(AST)的增量解析器。它不是在文本上跑正则,而是真正解析代码结构,然后根据语法节点类型做高亮。结果是:
- 高亮更准确,不会把字符串里的关键字标红
- 支持语义级高亮(区分函数调用、变量定义、类型注解等)
- 增量解析速度极快,编辑时几乎无延迟
- 内置 50 多种语言的 parser
Vim 社区也有 tree-sitter 的移植项目,但远不如 Neovim 的深度集成。
## 异步架构:libuv 带来的质变
Neovim 的底层用 libuv 重写了事件循环,所有 I/O 操作——文件读写、语言服务器通信、插件加载、shell 命令执行——都是异步的。这意味着你在跑测试、格式化代码、等待 LSP 响应的时候,编辑器界面不会卡顿。
Vim 8.0 也加入了 `job_start` 和 `timer_start`,提供了基本的异步能力,但深度不够。很多 Vim 插件仍然在主线程上做同步操作,因为 Vim 的 API 设计没有强制插件作者考虑异步。
实测数据:空配置启动,Neovim 约 12ms,Vim 约 28ms。配置了完整 LSP + 补全 + 文件搜索的开发环境后,Neovim 的响应优势更明显。
## 浮动窗口与弹窗
Neovim 原生支持浮动窗口(floating window),可以在编辑区上方弹出半透明的面板,用于显示补全菜单、文档预览、诊断信息等。这是现代编辑器体验的关键组件。
Vim 8.2 加入了 popup window,功能类似但 API 灵活度不如 Neovim 的实现。Neovim 的浮动窗口可以叠加、设置透明度、精确控制位置和大小,插件生态围绕这个能力构建了 telescope 的预览窗口、nvim-cmp 的文档浮窗、lspsaga 的代码操作面板等体验。
## 内置终端
Neovim 内置了终端模拟器,通过 `:terminal` 命令可以直接在编辑器里打开一个 shell。配合浮动窗口插件(如 toggleterm.nvim、FTerm.nvim),可以一键弹出/隐藏终端,不需要离开编辑器切换到外部终端。
Vim 也有 `:terminal`,但 Neovim 的终端实现和窗口系统的集成更紧密,配合浮动窗口和终端模式的键位映射,使用体验更接近 VS Code 的集成终端。
## 插件生态:两条不同的路
Neovim 的插件生态已经完全 Lua 化,形成了现代化工具链:
| 功能 | Neovim 插件 | Vim 插件 |
|------|------------|---------|
| 插件管理 | lazy.nvim | vim-plug |
| 模糊搜索 | telescope.nvim | fzf.vim |
| 自动补全 | nvim-cmp | coc.nvim |
| LSP 配置 | nvim-lspconfig | coc.nvim |
| LSP 安装 | mason.nvim | 手动安装 |
| 语法高亮 | nvim-treesitter | 正则语法文件 |
| Git 集成 | gitsigns.nvim | vim-fugitive |
关键差异不在单个插件,而在整体协同。Neovim 的 Lua 插件之间可以无缝通信——telescope 的搜索结果可以直接预览文件,nvim-cmp 的补全源可以来自 LSP、Tree-sitter 和 snippet,lazy.nvim 可以延迟加载插件到毫秒级。Vim 的插件生态更成熟但更碎片化,很多流行插件最后更新时间在 2023 年之前。
## 迁移成本:从 Vim 到 Neovim 有多难
答案是:很低。Neovim 兼容绝大部分 VimScript 配置,你可以直接把 `.vimrc` 软链到 Neovim 的配置路径,几乎不用改任何东西就能跑起来。然后按自己的节奏逐步把 VimScript 配置迁移到 Lua。
迁移路径通常是:
1. 把 `init.vim` 改名为 `init.lua`,内容不变
2. 逐个模块用 Lua 重写,通过 `require` 引入
3. 把 vim-plug 换成 lazy.nvim
4. 加入 LSP 和 Tree-sitter 配置
5. 替换旧插件为 Lua 原生替代品
整个过程可以持续几周甚至几个月,不需要一次性全换。
## 性能对比
空配置下 Neovim 启动更快(12ms vs 28ms),但空闲内存占用 Vim 略低(12MB vs 18MB)。加载完整开发配置后,Neovim 的异步优势开始显现:大文件编辑、LSP 诊断、插件操作都不会阻塞 UI。在几千行代码的文件里,Tree-sitter 的高亮刷新是增量的,而 Vim 的正则高亮需要重新扫描整个文件。
GitHub 数据也能说明趋势:Neovim 88k+ stars,Vim 35k+ stars。2024-2025 年,Neovim 的提交量是 Vim 的 4-5 倍。这不是说 Vim 不好——它仍然是最稳定的编辑器之一,但社区活力确实在向 Neovim 倾斜。
## 选型建议
**选 Vim 的情况:**
- 你是资深 Vim 用户,现有配置已经很稳定,没有改造的动力
- 你经常在远程服务器上编辑文件,Vim 几乎到处都有预装
- 你的机器资源非常有限,每兆内存都要精打细算
- 你只需要一个可靠的文本编辑器,不需要 IDE 功能
**选 Neovim 的情况:**
- 你是新用户,从零开始学,没有历史包袱
- 你想要 LSP、智能补全、代码导航等现代 IDE 功能
- 你对 Lua 配置感兴趣,或者想用 Neovim 搭建个人开发环境
- 你喜欢折腾编辑器,享受配置和优化的过程
**一个务实的策略:** 在服务器上继续用 Vim,在本地开发机上用 Neovim。两者键位操作完全一致,切换没有任何学习成本。Neovim 的配置也可以通过版本管理在多台机器间同步。
## 写在最后
Vim 和 Neovim 的区别,本质上是两种开发哲学的区别。Vim 追求稳定和向后兼容,三十年来始终如一;Neovim 追求进化和现代化,愿意为了更好的架构砍掉历史包袱。两者不是替代关系——Vim 是 Neovim 的根,Neovim 是 Vim 的一种可能未来。 Bram Moolenaar 拒绝了那组补丁,但那个决定催生了编辑器领域最有活力的开源项目之一,这大概是 2014 年没有人预料到的。服务端5月27日 15:06
Vim 的文本对象怎么用?## 为什么你一直在用 dw 而不是文本对象
很多 Vim 用户学了 `dw` 删除单词、`dd` 删除行,就觉得够用了。但当你需要删除一对括号里的内容、改写一个 HTML 标签内的文字、或者复制整个段落时,还在用 `dw` 一个个删就太慢了。文本对象(Text Object)是 Vim 里最被低估的特性——它让你不移动光标就能对"语义单元"做操作,而不只是对"字符位置"做操作。
## i 和 a:文本对象的核心逻辑
所有文本对象都遵循一个模式:`操作符 + i/a + 对象标识`。
- `i` = inner,选"内部",不包含分隔符
- `a` = a/around,选"一个",包含分隔符(有时还包含周围空格)
举一个最直观的例子,假设光标在 `hello` 上:
```
say hello world
```
- `diw` → `say world`(只删单词,留两个空格)
- `daw` → `say world`(删单词加后面空格,句子依然通顺)
记住这个区别,后面所有文本对象都是同一套逻辑。
## 单词:iw / aw / iW / aW
`iw` 选中当前单词(不含空格),`aw` 选中当前单词加相邻空格。
`w` 和 `W` 的区别在于分词规则:
- `w` 按标点和空白分词,`hello-world` 是三个单词
- `W` 只按空白分词,`hello-world` 是一个整体
实际场景:代码里改变量名用 `ciw`,删除函数参数用 `daw`。
```vim
" 光标在 foo 上
const result = foo + bar
ciw→ 输入 baz
const result = baz + bar
```
## 句子:is / as
`is` 选中当前句子内部,`as` 选中句子含后面的空格。Vim 以 `.`、`!`、`?` 后跟空格或换行来识别句子边界。
日常编辑中用得不算多,但在写文档或 Markdown 时,`cis` 可以快速重写一句话,`yas` 可以复制整句去引用。
## 段落:ip / ap
`ip` 选中当前段落(不含前后空行),`ap` 选中段落加上下面的空行。
段落在 Vim 中的定义是:由空行分隔的连续非空行。这在编辑 Markdown、邮件、纯文本文档时特别好用:
- `vip` 可视选中当前段落
- `yap` 复制整段
- `dap` 删除整段(包含段间空行,删除后不会多出空行)
- `gqip` 对当前段落重新排版
## 括号家族:i( / a( / i{ / a{ / i[ / a[
这是写代码用得最多的一组。所有成对括号都支持 i/a 变体:
| 文本对象 | 选中范围 | 典型用法 |
|---------|---------|---------|
| `i(` 或 `i)` | 括号内内容,不含括号 | `ci(` 改写函数参数 |
| `a(` 或 `a)` | 包含括号本身 | `da(` 删除整个括号及内容 |
| `i{` 或 `i}` | 花括号内内容 | `vi{` 选中函数体 |
| `a{` 或 `a}` | 包含花括号本身 | `ya{` 复制整个块 |
| `i[` 或 `i]` | 方括号内内容 | `ci[` 改写数组元素 |
| `a[` 或 `a]` | 包含方括号本身 | `da[` 删除整个数组 |
注意:光标不需要在括号上,只要在括号包围的范围内即可。所以你可以站在函数体中间,直接 `ci(` 改写参数列表。
## 引号家族:i" / a" / i' / a' / i` / a`
和括号类似,引号也有完整的 i/a 支持:
| 文本对象 | 选中范围 | 典型用法 |
|---------|---------|---------|
| `i"` | 双引号内内容 | `ci"` 改写字符串值 |
| `a"` | 包含双引号 | `da"` 删除整个字符串 |
| `i'` | 单引号内内容 | `ci"` 改写字符 |
| `a'` | 包含单引号 | `ya'` 复制含引号 |
| `i`` | 反引号内内容 | `yi`` 复制模板字符串内容 |
| `a`` | 包含反引号 | `da`` 删除模板字符串 |
前端开发时 `ci"` 改写属性值、`ci`` 改写模板字符串内容,是最高频的操作之一。
## HTML 标签:it / at
`it` 选中标签内部内容,`at` 选中包含标签本身。
```html
<p class="intro">Hello Vim</p>
```
- 光标在 `Hello` 上,`dit` → `<p class="intro"></p>`(删内容留空标签)
- 光标在 `Hello` 上,`dat` → 整行消失(连标签一起删)
- `cit` → 删内容并进入插入模式,直接输入新内容
这对编辑 HTML、XML、JSX/Vue 模板非常实用。配合 `vat` 先选中标签块再操作,比手动找开闭标签快得多。
## 与操作符组合:真正的威力所在
文本对象单独用(如 `iw`)没有意义,它必须和操作符组合才能发挥作用。常见的操作符:
- `d` delete:`diw` 删单词、`dib` 删括号内、`dit` 删标签内
- `c` change:`ciw` 改单词、`ci"` 改字符串、`cip` 改段落
- `y` yank:`yiw` 复制单词、`yi{` 复制花括号内容
- `v` visual:`viw` 选中单词、`vip` 选中段落
- `>` / `<` 缩进:`>ip` 缩进段落、`>a{` 缩进整个块
- `gU` / `gu` 大小写:`gUiw` 单词转大写
也可以加数字前缀:`2daw` 删除两个单词,`3ip` 选中三段。
一些高频组合速查:
```
ciw — 改写当前单词
ci" — 改写双引号内字符串
ci( — 改写括号内参数
cit — 改写 HTML 标签内容
diw — 删除当前单词
dib — 删除括号内内容(b 等同于 ( )
dat — 删除整个 HTML 标签
yiw — 复制当前单词
yi{ — 复制花括号内内容
vip — 选中当前段落
```
## 自定义文本对象与 vim-textobj-user
Vim 内置的文本对象已经覆盖了大部分场景,但你可能会想定义自己的——比如选中函数名、选中注释块、选中 CamelCase 的某个部分。
[vim-textobj-user](https://github.com/kana/vim-textobj-user) 是 Kana Natsuno 写的插件,提供了一个声明式的 API 来创建自定义文本对象,不用手写复杂的 `onoremap` 映射。
最简单的用法——用正则定义一个文本对象:
```vim
call textobj#user#plugin('line', {
\ '-': {
\ 'select-i': 'il',
\ 'select-a': 'al',
\ 'pattern': '.*
',
\ },
\ })
```
这样就有了 `il`(行内)和 `al`(含换行)两个文本对象,可以用 `vil`、`dal` 等操作。
社区基于 vim-textobj-user 构建了大量插件,常用的有:
- **vim-textobj-comment**:`ic`/`ac` 选中注释块
- **vim-textobj-function**:`if`/`af` 选中函数体
- **vim-textobj-entire**:`ie`/`ae` 选中整个文件
- **vim-textobj-indent**:`ii`/`ai` 选中同缩进层
- **vim-textobj-anyblock**:自动匹配最近的括号/引号对
如果你用 Neovim,还可以通过 Lua 和 treesitter 定义更智能的文本对象(如 `@function.inner`、`@class.outer`),这是另一个话题了。
## 常见实战场景
**改函数参数**:光标在参数列表中间,`ci(` 清空参数重新输入。
**改字符串值**:光标在字符串上,`ci"` 删值进插入模式,不用移光标到引号里。
**删 HTML 标签但保留内容**:`dat` 会连标签一起删。如果只想删标签保留内容,可以 `vat` 选中后手动删标签行,或者用 vim-surround 插件的 `dst`(delete surrounding tag)。
**复制整个代码块**:`yi{` 复制花括号内所有内容,比 `V{` 手动选快得多。
**重排段落**:写 Markdown 时 `gqip` 对当前段落自动换行重排。
**批量缩进**:`>ap` 缩进当前段落,`>a{` 缩进整个代码块。
**在可视化模式下确认范围**:不确定文本对象会选中什么?先用 `vi(` 看看高亮范围,确认后再换成 `di(` 或 `ci(`。
## 从今天开始用
如果你之前只用 `dw` 和 `dd`,从这几个操作开始:
1. `ciw` — 改写当前单词(日常最频繁)
2. `ci"` — 改写字符串内容
3. `ci(` — 改写括号内容
4. `dit` — 删除标签内容
5. `vip` — 选中当前段落
不用一次全记,先把 `ciw` 用熟,你会发现越来越不想回到逐字符操作了。文本对象的本质就是:告诉 Vim 你要操作"什么",而不是"从哪到哪"。服务端5月27日 15:05
Vim 命令行模式有哪些常用命令?Vim 的命令行模式(Command-line Mode)是很多新手容易忽略的一层——按下 `:` 后底部弹出的那个输入框,藏着远比 `:wq` 更多的能力。从文件操作到批量替换、从窗口分割到执行 Shell 命令,命令行模式是 Vim 编辑效率的关键倍增器。
## 如何进入命令行模式
在普通模式下,以下按键会进入命令行模式:
- **`:`** — 输入 Ex 命令,这是最常用的入口
- **`/`** — 正向搜索
- **`?` — 反向搜索
- **`!`** — 在部分场景下直接执行外部命令(如 `:!ls`)
按 `Esc` 或 `Ctrl+c` 可退出命令行模式回到普通模式。
命令行底部会显示一个输入区域,你输入的内容称为"命令行",Vim 会解析并执行。
## 文件操作命令
文件操作是命令行模式最基础也最常用的功能:
| 命令 | 作用 |
|------|------|
| `:w` | 保存当前文件 |
| `:w filename` | 另存为新文件 |
| `:q` | 退出当前缓冲区 |
| `:q!` | 强制退出,丢弃修改 |
| `:wq` 或 `:x` | 保存并退出 |
| `:e filename` | 打开文件编辑 |
| `:e!` | 重新加载文件,丢弃当前修改 |
| `:r filename` | 将文件内容插入到光标下方 |
| `:sav filename` | 相当于 `:w filename` 后切换到新文件 |
几个容易混淆的区别:
- `:x` 和 `:wq` 的差异:`:x` 仅在文件有修改时才写入,时间戳不会无谓更新。
- `:e!` 是"撤销一切"的最狠方式,比反复按 `u` 更彻底。
- `:r` 是 insert 的意思,在光标行下方插入文件内容,不是"打开"。
## 行范围与地址
Ex 命令的强大在于可以指定行范围,对指定行批量操作:
| 范围写法 | 含义 |
|---------|------|
| `:3` | 仅操作第 3 行 |
| `:1,10` | 第 1 到第 10 行 |
| `:1,$` | 第 1 行到文件末尾 |
| `:%` | 等同 `1,$`,整个文件 |
| `:.,+5` | 当前行到下面 5 行 |
| `:'a,'b` | 标记 a 到标记 b 之间的行 |
常见组合:
```vim
:1,10d " 删除第1到10行
:%d " 删除整个文件内容
:5,20m30 " 将5-20行移动到第30行之后
:1,5t10 " 将1-5行复制到第10行之后
:%normal A; " 在每一行末尾加分号(执行普通模式命令)
```
`:%` 是最高频的范围写法,尤其在配合替换命令时几乎必用。
## 搜索与替换
搜索替换是命令行模式的核心能力,也是面试常考点。
### 替换命令 `:s`
基本语法:`:[范围]s/模式/替换/标志`
```vim
:s/foo/bar/ " 当前行,替换第一个匹配
:s/foo/bar/g " 当前行,替换所有匹配
:%s/foo/bar/g " 全文替换
:%s/foo/bar/gc " 全文替换,每次确认
:5,10s/foo/bar/g " 第5-10行替换
```
常用标志:
- `g` — 替换行内所有匹配(不加则只替换第一个)
- `c` — 每次替换前确认
- `i` — 忽略大小写
- `I` — 区分大小写
分隔符不限于 `/`,如果模式本身包含 `/`,可以换用其他字符:
```vim
:%s#/usr/local#/opt/homebrew#g " 用 # 当分隔符
```
### 全局命令 `:g` 和 `:v`
`:g` 对匹配行执行命令,`:v` 对不匹配行执行命令:
```vim
:g/pattern/d " 删除所有包含 pattern 的行
:g!/pattern/d " 删除所有不包含 pattern 的行
:v/pattern/d " 同上,:v 等价 :g!
:g/^$/d " 删除所有空行
:g/pattern/s/old/new/g " 对匹配行执行替换
:g/pattern/normal @q " 对匹配行执行宏
```
`:g` 的语法是 `:[范围]g/模式/命令`,它是 Vim 里最接近"脚本"能力的东西——批量操作利器。
## 窗口命令
Vim 支持多窗口编辑,这些命令在命令行模式中输入:
| 命令 | 作用 |
|------|------|
| `:split` 或 `:sp` | 水平分割窗口 |
| `:vsplit` 或 `:vsp` | 垂直分割窗口 |
| `:sp filename` | 水平分割并打开文件 |
| `:vsp filename` | 垂直分割并打开文件 |
| `:close` | 关闭当前窗口 |
| `:only` | 关闭其他所有窗口 |
| `:resize +5` | 当前窗口高度增加5行 |
| `:resize -5` | 当前窗口高度减少5行 |
| `:vertical resize 80` | 当前窗口宽度设为80列 |
窗口间移动用 `Ctrl+w` 系列快捷键(`Ctrl+w h/j/k/l`),不属于命令行命令,但经常配合使用。
## 缓冲区管理
Vim 的缓冲区(Buffer)是比窗口更底层的概念——你可以打开多个文件但不一定要显示它们:
| 命令 | 作用 |
|------|------|
| `:ls` | 列出所有缓冲区 |
| `:b N` | 切换到第 N 号缓冲区 |
| `:bn` | 下一个缓冲区 |
| `:bp` | 上一个缓冲区 |
| `:bd` | 关闭当前缓冲区 |
| `:bd N` | 关闭第 N 号缓冲区 |
| `:bufdo %s/old/new/g` | 对所有缓冲区执行替换 |
`:ls` 的输出中,`%` 表示当前缓冲区,`#` 表示轮换缓冲区(按 `Ctrl+^` 可快速切换),`a` 表示已激活。
实际开发中,常用 `:bn` 和 `:bp` 在多个文件间快速跳转,比反复 `:e` 高效。
## 执行外部命令
Vim 可以在命令行模式中直接调用 Shell:
```vim
:!ls " 查看目录列表
:!python3 % " 用 Python 运行当前文件(% 代表当前文件名)
:!make " 执行 make
:shell " 进入一个子 Shell(exit 返回 Vim)
```
将外部命令输出插入当前文件:
```vim
:read !date " 在光标下方插入当前日期
:r !ls -la " 在光标下方插入目录列表
:5read !whoami " 在第5行后插入命令输出
```
注意 `:!cmd` 和 `:read !cmd` 的区别:前者只显示结果,后者把结果插入文件。
还有一个实用技巧——把当前缓冲区的部分内容作为外部命令的输入:
```vim
:1,10!sort " 将1-10行通过 sort 命令排序后替换原内容
:%!jq . " 用 jq 格式化整个 JSON 文件
```
这种"过滤"用法在处理日志、格式化代码时非常方便。
## 命令行补全
命令行模式下有两个关键补全快捷键:
- **`Tab`** — 补全命令名、文件名、选项名等
- **`Ctrl+d`** — 列出所有可能的补全候选项
```vim
:e<Tab> " 补全以 e 开头的命令
:e /etc/pa<Ctrl+d> " 列出 /etc 下以 pa 开头的文件
:set inc<Tab> " 补全选项名(如 incsearch)
```
Vim 还支持自定义补全来源,`:set wildmenu` 开启后,底部会显示一个可导航的补全菜单,配合方向键选择更直观。
## 命令历史
命令行模式维护了独立的历史记录:
- **`q:`** — 打开命令行历史窗口,可浏览和编辑历史命令
- **`q/`** — 打开搜索历史窗口
- **`q?`** — 打开反向搜索历史窗口
在历史窗口中,可以用 `j/k` 浏览,`Enter` 执行选中的命令,`Ctrl+c` 退出。
上下方向键也可以在命令行中逐条回溯历史,与 Shell 的体验一致。
建议配合 `:set history=200` 增大历史记录条数(默认 50),方便回溯更早的命令。
## 映射与缩写
命令行模式也用于定义快捷映射和缩写:
### 映射 `:map` 系列
```vim
:map <F5> :w<CR> " 普通/可视/选择/操作符等待模式映射
:nmap <F5> :w<CR> " 仅普通模式
:imap jj <Esc> " 仅插入模式
:vmap <C-c> y " 仅可视模式
:nnoremap <F5> :w<CR> " 非递归普通模式映射(推荐)
```
实际开发中,**始终优先使用 `noremap` 系列**(`nnoremap`/`inoremap`/`vnoremap`),避免递归映射导致的问题。
### 缩写 `:abbr` 系列
```vim
:iab adn and " 插入模式将 adn 自动展开为 and
:iab @@ user@example.com " 快速输入邮箱
:ab mainfn int main() " 缩写展开
```
缩写在输入空格或回车时触发,适合常用代码片段或容易拼错的单词。
---
Vim 命令行模式本质上是 ex 编辑器的接口——ex 是 Vi 的行编辑器前身,所有 `:` 命令都是 ex 命令。理解这一点后,你会发现 Vim 的命令行模式并不是一个"输入框"那么简单,而是一个完整的行编辑器,可以精确地对任意行范围执行操作。掌握行范围、替换和 `:g` 命令,命令行模式就能从"只会 :wq"升级为真正的文本处理工具。服务端5月27日 14:58
Vim 的寄存器到底有几种,各自用在什么场景?## 为什么你的 Vim 粘贴总是不对
你一定遇到过这种情况:复制了一行代码,删掉另一行,再粘贴时发现粘贴的是刚删掉的内容,而不是你复制的那行。这不是 bug,这是 Vim 寄存器机制在起作用——大多数操作都默认写入同一个无名寄存器,后进来的把前面的覆盖了。
Vim 并不是只有一个剪贴板,它有十几种寄存器,每种都有明确的用途。搞清楚它们,复制粘贴不再踩坑,还能用寄存器做宏录制、表达式计算、跨程序复制等高级操作。
## 无名寄存器("")——默认的垃圾桶
每次执行 `yank`、`delete`、`change` 等操作,内容都会自动写入无名寄存器 `""`。普通模式下按 `p` 粘贴,用的就是它。
问题在于,`dd` 删除一行和 `yy` 复制一行都会覆盖 `""`。所以你复制之后做了一次删除,粘贴出来的就是删除的内容。
这不代表原来的内容丢了——它还在数字寄存器 `"0` 里。所以下次遇到"粘贴不对",先试 `"0p`,大概率就是你想要的内容。
## 命名寄存器("a–"z)——手动管理的 26 个抽屉
命名寄存器是最常用的一类,用法简单:操作前加 `"寄存器名`。
```vim
"ayy " 将当前行复制到寄存器 a
"ap " 粘贴寄存器 a 的内容
"bdw " 删除一个单词并存入寄存器 b
```
这样你可以在 a 里存一段代码,b 里存另一段,随时按 `"ap` 和 `"bp` 取出来,互不干扰。
### 大写字母是追加,不是覆盖
如果寄存器 a 里已经有内容,`"ayy` 会覆盖它。但用大写 `"Ayy` 则是追加:
```vim
"ayy " 覆盖写入寄存器 a
"Ayy " 追加到寄存器 a 末尾
```
这在收集分散内容时很有用——比如把文件中多个位置的函数签名逐行追加到同一个寄存器,最后一次性粘贴。
## 数字寄存器("0–"9)——自动记录的历史栈
数字寄存器不需要手动指定,Vim 自动维护:
- `"0`:最近一次 yank 的内容,不会被 delete 覆盖
- `"1`:最近一次 delete 或 change 的内容
- `"2`:倒数第二次 delete 的内容
- …以此类推到 `"9`
注意 `"0` 是 yank 专用,只有 `y` 操作才会更新它。`dd` 和 `x` 只会更新 `"1` 到 `"9`,不会碰 `"0`。
实际场景:你 `yy` 复制了一行,然后 `dd` 删了几行,想粘贴最初复制的那行——`"0p` 就是答案。
## 只读寄存器——Vim 自动填入的元信息
四个只读寄存器,你只能读取,不能手动写入:
| 寄存器 | 内容 | 典型用法 |
|--------|------|----------|
| `"%` | 当前文件名 | 插入文件名:插入模式下 `Ctrl+r %` |
| `".` | 最后插入的文本 | 重复上次输入:插入模式下 `Ctrl+r .` |
| `":` | 最后执行的 Ex 命令 | 再次执行上条命令:`@:` |
| `"/` | 最后的搜索模式 | 替换时复用:`:%s//替换内容/g` |
其中 `"/` 在替换命令里特别实用——`:%s//new/g` 等价于 `:%s/上次搜索的词/new/g`,省去重新输入搜索内容。
## 黑洞寄存器("_)——删除但不留痕迹
`"_dd` 删除一行,但不会存入任何寄存器,无名寄存器和数字寄存器都不会被更新。
什么时候用?当你删掉的内容不需要粘贴,又不想污染寄存器历史的时候。比如清理大量注释行,用 `"_dd` 逐行删除,你的 `"0` 仍然保存着之前 yank 的内容,不受影响。
## 表达式寄存器("=)——在插入模式做计算
在插入模式下按 `Ctrl+r =`,Vim 会在命令行提示你输入一个表达式,计算结果直接插入光标处。
```vim
" 插入模式下:
Ctrl+r =3600*24↵ " 插入 86400
Ctrl=r =strftime('%Y-%m-%d')↵ " 插入当前日期
```
也可以在命令里引用变量或函数返回值,适合需要动态插入内容的场景。
## 系统剪贴板("+ 和 "*)——和外部程序互通
Vim 默认不与系统剪贴板交互,需要通过 `"+` 或 `"*` 寄存器:
- `"+yy`:复制当前行到系统剪贴板
- `"+p`:从系统剪贴板粘贴
`"+` 和 `"*` 在 Windows 和 macOS 上行为一致,都指向系统剪贴板。在 Linux 上有区别:`"+` 是 CLIPBOARD(Ctrl+C/V),`"*` 是 PRIMARY(鼠标选中即复制,中键粘贴)。
如果你希望每次 yank/paste 自动使用系统剪贴板,可以设置:
```vim
set clipboard=unnamedplus
```
这样普通 `yy` 和 `p` 就直接操作系统剪贴板了。
## :reg——查看所有寄存器内容
忘了某个寄存器里存了什么?用 `:reg` 或 `:registers` 查看全部,也可以指定只看某几个:
```vim
:reg " 查看所有非空寄存器
:reg a b 0 " 只看寄存器 a、b、0
:reg / : " 查看搜索模式和上次 Ex 命令
```
输出格式是寄存器名 + 内容,内容中的换行用 `^J` 表示。调试宏或确认寄存器状态时经常用到。
## 宏与寄存器——本质上是同一套机制
Vim 的宏录制就是把按键序列存进命名寄存器。`qa` 开始录制到寄存器 a,再按 `q` 停止,`@a` 回放。
这意味着:
1. 录制的宏可以用 `:reg a` 查看,内容就是一串按键字符
2. 你可以把宏内容粘贴出来编辑,改好再 yank 回去——修改宏不需要重新录制
3. 用大写追加可以往宏里追加指令:`qA` 追加录制到 a 宏末尾
编辑宏的流程:
```vim
:reg a " 先看看宏 a 里的内容
"ap " 把宏内容粘贴到缓冲区
" 编辑这一行按键序列
"ayy " 重新 yank 回寄存器 a
```
这种可编辑性是 Vim 宏区别于简单"录制回放"的关键——出错了不用重来,改一行就行。
## 把寄存器用起来
寄存器不是 Vim 里"知道就好"的冷知识,它直接影响日常编辑效率。几个建议:
- 复制重要内容时指定命名寄存器(`"ayy`),避免被后续删除覆盖
- 需要干净删除时用黑洞寄存器(`"_dd`),保持寄存器历史干净
- 复制粘贴跨程序时显式用 `"+y` / `"+p`,不要依赖自动剪贴板设置
- 录制宏后用 `:reg` 检查内容,复杂宏直接编辑比重新录制更高效
Vim 的寄存器体系看起来种类多,但核心逻辑就是一条:每次操作前加 `"寄存器名`,就是指定目标寄存器;不加,就是无名寄存器。记住这个规律,其他的都是在此基础上的分类和特例。服务端5月27日 14:52
Vim 的标签导航怎么用?从 ctags 到 LSP 的完整跳转方案## 为什么需要标签导航
阅读源码时,你会在函数调用处和定义处之间反复切换。如果没有标签系统,只能靠 `grep` 或 `/:function_name` 搜索,效率很低。Vim 的标签导航机制让你在光标处一键跳转到定义,再一键返回,是代码阅读的核心工作流。
## 生成 tags 文件:ctags
标签导航的前提是有 tags 文件。ctags 扫描源码,把函数、类、变量的定义位置记录到一个索引文件中。
安装 Universal Ctags(Exuberant Ctags 的活跃 fork):
```bash
# macOS
brew install universal-ctags
# Ubuntu/Debian
sudo apt install universal-ctags
```
在项目根目录生成 tags 文件:
```bash
ctags -R .
```
`-R` 表示递归扫描子目录,生成的 `tags` 文件存放在当前目录。
如果你的项目有 `node_modules` 或 `build` 目录,建议排除:
```bash
ctags -R --exclude=node_modules --exclude=build .
```
C/C++ 项目需要更详细的标签信息,可以加参数:
```bash
ctags -R --c++-kinds=+p --fields=+iaS --extra=+q .
```
- `--c++-kinds=+p`:记录函数声明和外部声明
- `--fields=+iaS`:记录继承关系、访问权限、函数签名
- `--extra=+q`:为同名函数生成额外的区分行
## 基本跳转:Ctrl-] 和 Ctrl-T
这是最常用的操作,务必记住:
- **`Ctrl-]`**:跳转到光标下标识符的定义
- **`Ctrl-T`**:沿标签栈返回上一个位置
使用流程:把光标移到函数名上,按 `Ctrl-]` 跳过去,看完按 `Ctrl-T` 回来。可以连续跳转多次,每次 `Ctrl-T` 回退一层。
在终端中,`Ctrl-]` 可能被 shell 的 telnet 快捷键拦截。解决办法是用 `:tag` 命令,或者重新映射终端快捷键。
## 用 :tag 和 :tselect 精确跳转
当光标不在目标标识符上时,可以直接用命令跳转:
```vim
:tag main
```
跳转到 `main` 的定义。支持 Tab 补全,输入 `:tag m<Tab>` 会列出所有 `m` 开头的标签。
如果一个标签有多个定义(比如不同文件中同名函数),`:tag` 只跳到第一个。这时用 `:tselect` 列出所有匹配:
```vim
:tselect parse
```
Vim 会显示一个选择列表,输入编号即可跳转。
`:tjump` 是更智能的版本:只有一个匹配时直接跳转,多个匹配时弹出选择列表。相当于 `:tselect` 和 `:tag` 的合体。
在匹配项之间浏览:
- `:tnext` — 下一个匹配
- `:tprev` — 上一个匹配
- `:tfirst` — 第一个匹配
- `:tlast` — 最后一个匹配
## g] :预览式选择跳转
`g ]` 把光标下标识符的所有匹配列出来让你选择,和 `:tjump` 效果类似,但不需要输入命令。日常使用中,`g ]` 比 `Ctrl-]` 更稳妥——遇到多个定义时不会跳错位置。
另外几个预览相关的命令:
- `Ctrl-W }` — 在预览窗口中打开定义,不离开当前位置
- `:ptag func_name` — 在预览窗口打开指定标签
- `:pclose` — 关闭预览窗口
## 标签栈::tags 查看跳转历史
每次 `Ctrl-]` 跳转都会压入标签栈。查看栈内容:
```vim
:tags
```
输出类似:
```
# TO tag FROM line in file/text
1 1 parse 12 main.c
> 2 2 process 45 parser.c
3 1 validate 78 parser.c
```
`>` 标记当前所在位置。`Ctrl-T` 每次弹出一层,也可以用数字前缀一次回退多层:`3 Ctrl-T` 回退 3 层。
注意标签栈和跳转列表(jumplist)不同。`Ctrl-O` / `Ctrl-I` 操作的是跳转列表,范围更广;`Ctrl-T` 操作的是标签栈,只追踪标签跳转。两者配合使用效果最好。
## 配置 tags 选项
Vim 通过 `tags` 选项定位 tags 文件。默认值是 `./tags,tags`,即在当前文件目录和工作目录查找。
常见配置:
```vim
" 向上级目录查找 tags 文件,直到找到为止
set tags=./tags;,tags;
" 或者指定固定路径
set tags+=/path/to/project/tags
```
`./tags;` 中的分号表示向上递归查找——Vim 会从当前文件所在目录开始,逐级向上找 `tags` 文件,直到根目录。这解决了在子目录中打开文件时找不到项目根目录 tags 文件的问题。
如果项目有多个 tags 文件,用 `+=` 追加:
```vim
set tags+=/path/to/external-lib/tags
```
## 多项目的 tags 管理
当你同时在多个项目间切换时,每个项目应该有自己的 tags 文件。几个实践建议:
1. **把 tags 文件加到 .gitignore**。tags 文件是本地生成的,不应该提交到仓库。
2. **用 `set autochdir`**。Vim 自动把工作目录切换到当前文件所在目录,配合 `./tags;` 的递归查找,基本可以覆盖大部分场景:
```vim
set autochdir
set tags=./tags;,tags;
```
3. **大项目按模块拆分 tags**。在子目录分别生成 tags 文件,Vim 会自动合并所有匹配的标签。
4. **使用 $PROJECT_HOME 环境变量**。在 vimrc 中动态设置 tags 路径:
```vim
if $PROJECT_HOME != ''
set tags+=$PROJECT_HOME/tags
endif
```
## cscope:标签之外的代码交叉引用
ctags 只能跳转到定义,无法查找"谁调用了这个函数"。cscope 补充了这个能力。
生成 cscope 数据库:
```bash
# 在项目根目录
find . -name "*.c" -o -name "*.h" > cscope.files
cscope -b
```
`-b` 表示只构建数据库,不进入交互界面。生成的 `cscope.out` 文件就是数据库。
在 Vim 中连接数据库:
```vim
:cs add cscope.out
```
验证连接:
```vim
:cs show
```
cscope 的查询类型:
| 命令 | 缩写 | 含义 |
|------|------|------|
| `:cs find s symbol` | 0 | 查找符号的所有引用 |
| `:cs find g symbol` | 1 | 查找全局定义 |
| `:cs find d func` | 2 | 查找该函数调用的函数 |
| `:cs find c func` | 3 | 查找调用该函数的函数 |
| `:cs find t text` | 4 | 查找文本字符串 |
| `:cs find e pattern` | 6 | egrep 模式搜索 |
| `:cs find f file` | 7 | 查找文件 |
| `:cs find i include` | 8 | 查找 include 该文件的文件 |
最常用的是 `:cs find c`(谁调用了这个函数)和 `:cs find d`(这个函数调用了谁),这是 ctags 做不到的。
`:cstag` 命令同时搜索 cscope 数据库和 tags 文件,建议在 vimrc 中设置:
```vim
set csto=1
set cst
```
这样 `Ctrl-]` 会优先查 cscope,再查 tags。
## gutentags:自动生成 tags 文件
手动跑 `ctags -R` 很容易忘。vim-gutentags 插件在后台自动管理 tags 文件的生成和更新。
安装(以 vim-plug 为例):
```vim
Plug 'ludovicchabant/vim-gutentags'
```
基本配置:
```vim
" 指定 tags 文件存放目录(避免污染项目根目录)
let g:gutentags_cache_dir = expand('~/.cache/vim/ctags/')
" 确保缓存目录存在
if !isdirectory(g:gutentags_cache_dir)
silent! mkdir -p g:gutentags_cache_dir
endif
" 项目根目录标记
let g:gutentags_project_root = ['.git', '.hg', '.root']
```
gutentags 的行为:
- 打开项目中的文件时,自动在后台生成 tags
- 保存文件时,增量更新 tags(不是全量重建)
- 通过 `.git` 等标记识别项目根目录
- 不依赖 Python 或 Ruby,纯 Vim 脚本 + ctags
状态栏显示生成进度:
```vim
set statusline+=%{gutentags#statusline()}
```
生成 tags 时状态栏会显示 `TAGS`,完成后自动消失。
## LSP 方案:coc.nvim 和 nvim-lspconfig
LSP(Language Server Protocol)提供了比 ctags 更精确的代码导航。LSP 的跳转基于语义分析,能区分同名函数的重载,能跳转到依赖库中的定义,还能查找所有引用。
**coc.nvim**(Vim 8+ / Neovim):
```vim
Plug 'neoclide/coc.nvim', {'branch': 'release'}
```
安装语言服务器后,用 `gd` 跳转到定义,`gr` 查找引用,`K` 查看文档。coc.nvim 还会把 LSP 结果注册为 tags,所以 `Ctrl-]` 也能用。
**nvim-lspconfig**(Neovim 0.5+):
```lua
local lspconfig = require('lspconfig')
lspconfig.pyright.setup{} -- Python
lspconfig.ts_ls.setup{} -- TypeScript
lspconfig.clangd.setup{} -- C/C++
```
快捷键映射:
```lua
vim.keymap.set('n', 'gd', vim.lsp.buf.definition)
vim.keymap.set('n', 'gr', vim.lsp.buf.references)
vim.keymap.set('n', 'K', vim.lsp.buf.hover)
```
LSP 和 ctags 不是互斥的。实际工作中很多人同时使用:LSP 覆盖有完善语言服务器的语言,ctags 作为兜底方案覆盖配置文件、Makefile、shell 脚本等 LSP 不方便覆盖的文件类型。
如果你不想为每个语言配 LSP 服务器,可以试试 ctags-lsp——一个基于 ctags 实现的轻量 LSP 服务器,开箱即用,精度介于裸 ctags 和完整 LSP 之间。
## 一份实用的 vimrc 配置参考
```vim
" tags 文件查找策略
set tags=./tags;,tags;
" cscope 优先于 tags
set csto=1
set cst
" gutentags 配置
let g:gutentags_cache_dir = expand('~/.cache/vim/ctags/')
let g:gutentags_project_root = ['.git', '.root']
" 快捷键映射
nnoremap <C-]> g] " 多匹配时选择,而非直接跳第一个
nnoremap <C-T> <C-T> " 回退保持默认
nnoremap <leader>cs :cs find s <C-R>=expand('<cword>')<CR><CR>
nnoremap <leader>cg :cs find g <C-R>=expand('<cword>')<CR><CR>
nnoremap <leader>cc :cs find c <C-R>=expand('<cword>')<CR><CR>
```
Vim 的标签导航从 ctags 起步,cscope 补足交叉引用,gutentags 解决自动化,LSP 带来语义精度。根据项目语言和规模选择合适的组合,比追单一方案更实际。
服务端5月27日 14:52
VimScript 脚本编程怎么写?从变量作用域到插件结构全讲清楚## 为什么要学 VimScript
Vim 的真正威力不在于快捷键多,而在于你可以用脚本把编辑器改造成自己想要的样子。自动格式化、批量重命名、项目专属配置——这些全靠 VimScript 驱动。即使你现在用 Neovim + Lua,读懂已有插件的 VimScript 源码依然是刚需。
## 变量与作用域
VimScript 的变量用 `let` 声明,用 `unlet` 删除。关键在于作用域前缀——每个前缀决定了变量的可见范围和生命周期:
```vim
let g:global_var = 1 " 全局变量,任何地方都能访问
let s:script_var = 2 " 脚本局部变量,只在当前 .vim 文件内可见
let l:local_var = 3 " 函数局部变量,只在当前函数内可见(函数内默认)
let b:buffer_var = 4 " 缓冲区局部变量,绑定到当前文件缓冲区
let w:window_var = 5 " 窗口局部变量,绑定到当前窗口
let t:tab_var = 6 " 标签页局部变量,绑定到当前标签页
let a:arg_var = 7 " 函数参数,只在函数体内可用
```
几个容易踩的坑:
- 函数体内不加前缀的变量默认是 `l:`,不是全局的。
- 脚本级的 `s:` 变量在文件多次 source 时不会重置,生命周期跟 Vim 进程一致。
- `v:` 是 Vim 内置变量(如 `v:count`、`v:errmsg`),只读居多,不要覆盖。
- 还可以用 `&option` 访问选项值(如 `&tabstop`),`@r` 访问寄存器,`$ENV` 访问环境变量。
```vim
echo &tabstop " 读取选项
let &tabstop = 4 " 设置选项
echo @a " 读取寄存器 a 的内容
echo $HOME " 读取环境变量
```
## 字符串操作
VimScript 的字符串有两种引号,行为不同:
```vim
let s1 = "hello world" " 双引号:支持
\ 等转义
let s2 = 'hello world' " 单引号:原样输出, 就是两个字符
```
常用字符串函数:
```vim
echo strlen("hello") " => 5
echo strpart("hello", 1, 3) " => ell(从索引1取3个字符)
echo substitute("hello", "l", "L", "g") " => heLLo
echo tolower("Hello") " => hello
echo toupper("hello") " => HELLO
echo stridx("hello", "ll") " => 2(查找子串位置)
echo split("a,b,c", ",") " => ['a', 'b', 'c']
echo join(['a', 'b'], "-") " => a-b
```
拼接字符串推荐用 `.` 运算符:
```vim
let msg = "file: " . expand("%") . " line: " . line(".")
```
## 列表与字典
列表(List)就是数组,字典(Dict)就是哈希表:
```vim
" 列表
let fruits = ["apple", "banana", "cherry"]
echo fruits[0] " => apple
echo fruits[-1] " => cherry(负索引从末尾取)
call add(fruits, "date") " 追加元素
echo len(fruits) " => 4
" 列表推导
let squares = map(range(5), 'v:val * v:val')
echo squares " => [0, 1, 4, 9, 16]
" 字典
let user = {"name": "vim", "version": 9}
echo user.name " => vim
echo user["version"] " => 9
let user.lang = "VimScript" " 添加键
call remove(user, "lang") " 删除键
echo keys(user) " => ['name', 'version']
echo values(user) " => ['vim', 9]
```
## 控制流
### if / else / endif
```vim
if &filetype ==# "python"
echo "Python file"
elseif &filetype ==# "javascript"
echo "JS file"
else
echo "Other file"
endif
```
注意 `==#` 是大小写敏感比较,`==?` 是忽略大小写。裸写 `==` 受用户 `ignorecase` 设置影响,不推荐。
### for 循环
```vim
for item in ["a", "b", "c"]
echo item
endfor
for i in range(1, 10)
echo i
endfor
```
### while 循环
```vim
let i = 0
while i < 5
echo i
let i += 1
endwhile
```
VimScript 没有 break/continue 的等价物(Vim9script 有了),传统做法是用条件变量控制循环。
## 函数定义
```vim
function! s:Greet(name)
echo "Hello, " . a:name
return a:name
endfunction
```
关键规则:
- 函数名如果不加 `s:` 前缀,必须以大写字母开头。
- `function!` 加 `!` 表示如果函数已存在则覆盖,插件开发必加。
- 函数参数用 `a:` 前缀访问,如 `a:name`、`a:1`(可变参数)。
- 函数默认返回 0,除非显式 `return`。
- `abort` 关键字让函数在出错时立即中止,而不是继续执行。
```vim
function! s:Min(num1, num2) abort
return a:num1 < a:num2 ? a:num1 : a:num2
endfunction
```
可变参数用 `...`:
```vim
function! s:Varargs(name, ...)
echo a:name
echo a:0 " 可变参数个数
echo a:1 " 第一个可变参数
echo a:000 " 可变参数列表
endfunction
```
## autocmd 编程
autocmd 是 Vim 事件驱动的核心——在特定事件发生时自动执行命令:
```vim
augroup AutoFormat
autocmd!
autocmd BufWritePre *.py call s:AutoFormatPython()
autocmd BufWritePre *.js call s:AutoFormatJS()
augroup END
```
要点:
- **必须用 augroup 包裹**,否则每次 source 文件都会追加重复的 autocmd。`autocmd!` 先清空同组旧命令。
- 常用事件:`BufWritePre`(保存前)、`BufRead`(打开文件)、`FileType`(设置文件类型)、`InsertEnter`(进入插入模式)。
- autocmd 体尽量短,复杂逻辑抽成函数调用。
```vim
function! s:AutoFormatPython()
" 复杂格式化逻辑
silent! %!autopep8 -
endfunction
```
## 自定义命令:command 与 <f-args>、<range>
用 `:command` 定义用户命令,比函数调用方便得多:
```vim
command! -nargs=1 -complete=file MyEdit :edit <args>
```
`<f-args>` 将命令参数拆分为函数参数列表,是最常用的参数传递方式:
```vim
command! -nargs=* Grep call s:Grep(<f-args>)
function! s:Grep(...) abort
let pattern = join(a:000, ' ')
silent! grep! pattern
cwindow
endfunction
```
`<range>` 让命令支持行范围:
```vim
command! -range Align call s:Align(<line1>, <line2>)
function! s:Align(line1, line2) abort
execute a:line1 . ',' . a:line2 . '!column -t'
endfunction
```
常用 command 参数:
| 参数 | 含义 |
|------|------|
| `-nargs=0` | 无参数(默认) |
| `-nargs=1` | 恰好一个参数 |
| `-nargs=*` | 零或多个参数 |
| `-nargs=?` | 零或一个参数 |
| `-nargs=+` | 至少一个参数 |
| `-range` | 允许行范围 |
| `-bang` | 允许 ! 修饰 |
| `-complete=file` | 文件名补全 |
| `-bar` | 允许 | 管道连接 |
## 插件结构:plugin 与 autoload
一个规范的 Vim 插件目录结构:
```
my-plugin/
├── plugin/
│ └── my-plugin.vim " 启动时加载,定义命令和映射
├── autoload/
│ └── my-plugin.vim " 按需加载,放函数实现
├── doc/
│ └── my-plugin.txt " 帮助文档
└── after/
└── ftplugin/
└── python.vim " 文件类型专用配置
```
**plugin/** 在 Vim 启动时执行,只放命令定义和映射,不放重逻辑。
**autoload/** 是懒加载机制。文件 `autoload/my-plugin.vim` 里的函数必须命名为 `my-plugin#FuncName`,只有第一次调用时才会加载:
```vim
" plugin/my-plugin.vim(启动时执行)
command! MyCommand call my-plugin#Run()
" autoload/my-plugin.vim(首次调用 MyCommand 时才加载)
function! my-plugin#Run()
echo "Hello from autoload"
endfunction
```
这种分离让插件启动时零开销,用到才加载。
## VimScript vs Lua(Neovim)
如果你用 Neovim,可能已经在纠结该学哪个。核心区别:
**性能**:Lua(LuaJIT)在循环和密集计算上比 VimScript 快约 56 倍。Neovim 用 Lua 加载 30 个插件只需 94ms,VimScript 配置动辄数百毫秒。
**语言设计**:VimScript 有 30 年历史包袱——隐式类型转换令人困惑、作用域规则不一致、缺少标准数据结构。Lua 是正经的编程语言,有模块、闭包、协程和成熟的生态。
**生态趋势**:2022 年之后,主流 Neovim 插件(telescope、lazy.nvim、nvim-cmp、nvim-treesitter)全部用 Lua 编写,不再接受 VimScript 贡献。
**兼容性**:Neovim 仍然支持 VimScript,`init.vim` 和 `init.lua` 可以共存。用 `vim.cmd()` 在 Lua 中执行 VimScript,用 `luaeval()` 在 VimScript 中调用 Lua。
**选择建议**:
- 用 Vim → 只能写 VimScript(或 Vim9script)。
- 用 Neovim + 新插件 → 学 Lua,用 `init.lua`。
- 需要维护老插件 → 必须读懂 VimScript。
## 从哪里开始实践
打开 Vim,输入 `:help usr_41` 阅读 Vim 官方脚本教程。把你的 `.vimrc` 里重复的配置抽成函数,给常用操作绑定 `command!`,再用 `augroup` 挂上自动命令——这就是你第一个"插件"了。不需要一步到位写 autoload 结构,先让东西跑起来,再重构不迟。服务端5月27日 14:52
Vim 的可视模式怎么用?字符、行、块选择与列编辑实战## 为什么你总在 Vim 里手忙脚乱地选中文字
很多人用 Vim 编辑文本时,还在靠 `v` 一个字符一个字符地挪,遇到多行操作就切回鼠标。问题不在你,在于你没把可视模式的三个子模式用熟。Vim 的可视模式本质上是一种"先选中,再操作"的工作流——你告诉 Vim "我要处理这段文字",然后下一条命令只作用于选区。理解了这一点,后面的操作都顺理成章。
## 三种可视模式,三种选中粒度
Vim 提供了三种可视模式,对应三种选择粒度:
- **字符可视模式**(`v`):逐字符选择,适合精确选中一行内的一小段文字。
- **行可视模式**(`V`):逐行选择,整行为单位,适合批量操作连续多行。
- **块可视模式**(`Ctrl-v`):矩形块选择,适合列编辑——这是可视模式里最强大也最容易被忽略的子模式。
按 `v`、`V`、`Ctrl-v` 进入对应模式后,再用移动命令(`hjkl`、`w`、`}`、`gg`、`G` 等)扩展选区。选区确定后,按任意操作键(`d`、`y`、`c`、`>` 等)执行。
三种模式之间可以互相切换:在字符可视模式下按 `V` 切到行模式,按 `Ctrl-v` 切到块模式,无需先按 `Esc` 退出。
## 选区端点与 o 键
进入可视模式后,选区有两个端点:起点和光标所在位置。按 `o` 可以让光标跳到另一个端点,这样你就能往反方向调整选区。这在选中了一大片区域后发现"起点选错了"时非常管用,不用退出重来。
## gv:重新选中上一次的选区
执行完操作后,选区就消失了。如果你想对同一个区域再做一次操作,按 `gv` 可以重新选中上一次的可视选区。这在连续对同一块文本执行多条命令时很实用,比如先 `V` 选中几行用 `>` 缩进,再 `gv` 重新选中用 `:s/foo/bar/g` 做替换。
## 块操作:列编辑的核心
块可视模式是 Vim 区别于其他编辑器的杀手功能。进入块选择后,你可以对矩形区域做以下操作:
| 操作 | 按键 | 说明 |
|------|------|------|
| 批量插入(左侧) | `I` | 在块左侧输入文本,按 `Esc` 后所有行同时生效 |
| 批量追加(右侧) | `A` | 在块右侧输入文本,按 `Esc` 后所有行同时生效 |
| 批量替换 | `c` | 删除选中内容并进入插入模式,输入后按 `Esc` 全部行生效 |
| 批量删除 | `d` 或 `x` | 直接删除选中块 |
| 单字符替换 | `r` | 将选中区域内每个字符替换为你输入的那个字符 |
### 实战:批量给多行加注释
假设你有以下代码,想给三行加 `//` 注释:
```
int a = 1;
int b = 2;
int c = 3;
```
操作步骤:
1. 把光标移到第一行行首,按 `Ctrl-v` 进入块可视模式。
2. 按 `jj`(或 `2j`)向下选中三行的第一个字符。
3. 按 `I`(大写)在块左侧插入,输入 `// `,然后按 `Esc`。
三行会同时变成 `// int a = 1;`、`// int b = 2;`、`// int c = 3;`。
### 实战:批量修改对齐的值
假设你有一组配置项,想把 `= true` 改成 `= false`:
```
debug = true
verbose = true
log = true
```
操作步骤:
1. 光标移到第一行的 `t` 上,按 `Ctrl-v` 进入块选择。
2. 按 `2j` 向下选中三行,再按 `e` 向右选中 `true` 整个单词。
3. 按 `c`,输入 `false`,按 `Esc`。
三行同时变成 `= false`。
## 可视模式与 . 命令配合
Vim 的 `.` 命令会重复上一次修改操作。在可视模式下执行的操作同样可以被 `.` 重复。比如你用 `V` 选中一段代码做了 `>` 缩进,之后把光标移到另一段代码按 `.`,就能重复同样的缩进操作。这在批量格式化代码时效率很高。
## 可视模式与宏配合
宏(`q` 录制)和可视模式可以组合使用。常见场景:录制一个宏,其中包含可视模式选中某段文本并执行操作,然后用 `@a` 在其他位置重复执行。你也可以先在可视模式下选中多行,然后对选区执行 `:'<,'>normal @a`,让宏在每一行上运行。
## 可视模式下的搜索与替换
在可视模式下按 `:` 会自动填充 `:'<,'>`,表示命令范围限定在当前选区。你可以直接跟 `s` 命令做替换:
```
:'<,'>s/old/new/g
```
这比手动计算行号再写 `:10,20s/old/new/g` 方便得多。你也可以用 `:!` 对选区执行外部命令,比如 `:'<,'>!sort` 对选中行排序。
另一个实用技巧:在可视模式下按 `g` 再按 `/`,可以用选中的文字作为搜索模式(某些 Vim 版本和 Neovim 支持),快速跳转到下一个匹配位置。
## Select 模式:Vim 里的"普通选中"
Vim 还有一个 Select 模式(`gh` 进入字符选择、`gH` 进入行选择、`gCtrl-h` 进入块选择),行为更接近普通编辑器:选中后直接输入文字会替换选区,不用先按 `c` 或 `d`。这个模式适合从其他编辑器刚转到 Vim 的用户做过渡,但长期来看,可视模式才是 Vim 的正道——因为可视模式下你可以先选中再决定做什么操作,更灵活。
## vim-gv 插件:可视化浏览选区历史
`gv` 插件(非内置 `gv` 命令)提供了一个弹窗,列出你本次会话中的所有可视选区历史,你可以选择任意一条重新选中。安装后按两次 `gv`(第一次触发内置 `gv`,第二次触发插件)即可打开历史列表。对于需要频繁在不同选区之间切换的复杂编辑任务,这个插件能省不少事。
## 把可视模式变成肌肉记忆
Vim 可视模式的三个子模式覆盖了文本选择的所有场景:`v` 做精确字符选择,`V` 做整行操作,`Ctrl-v` 做列编辑。配合 `o` 切换端点、`gv` 重选、块操作的 `I/A/c/d`,以及与 `.` 命令和宏的组合,你几乎不需要离开键盘就能完成任何批量编辑。从今天开始,遇到需要选中多行或多列的场景,强迫自己用可视模式而不是鼠标——一周后你会发现编辑速度上了一个台阶。服务端5月27日 14:50
Vim 搜索和替换有哪些必须掌握的高级技巧?Vim 的搜索能力远不止输入关键词然后按回车。正则元字符模式、搜索高亮策略、替换确认机制、跨文件搜索——这些才是真正拉开效率差距的地方。
## 用 / 和 ? 精准定位
`/` 向下搜索,`?` 向上搜索,这是最基本的区分。按 `n` 跳到下一个匹配,`N` 跳到上一个(在 `?` 搜索时方向反转)。
搜索当前光标下的单词有两个快捷操作:`*` 向下搜索整个单词,`#` 向上搜索。Vim 会自动给关键词加上 `\<` 和 `\>` 边界,不会匹配到包含该单词的更长字符串。
如果只想搜索光标下单词的部分匹配(不要求单词边界),用 `g*` 和 `g#`。
## 四种正则模式:\v \V \m \M
Vim 的正则语法有四种模式,这是很多人忽略的关键机制:
- **\m(magic)**:默认模式。`.`、`*`、`^`、`$` 等有特殊含义,但 `+`、`?`、`(`、`)`、`{`、`}`、`|` 需要反斜杠转义才生效。写分组是 `\(` 和 `\)`,或逻辑是 `\|`。
- **\v(very magic)**:所有元字符都直接生效,不需要反斜杠。写分组直接用 `()`,或逻辑直接用 `|`,量词直接用 `+` 和 `?`。这是最接近 Perl 正则的模式,写复杂表达式时强烈推荐。
- **\M(nomagic)**:只有 `^` 和 `$` 有特殊含义,其余字符全部当作字面量。
- **\V(very nomagic)**:只有反斜杠本身有特殊含义,搜索的就是字面文本。当你需要搜索包含大量特殊字符的字符串时,用这个模式最省心。
实际用法是在搜索模式开头加上模式修饰符:`/\v\d+\.\d+` 匹配浮点数,`/\Vfoo.bar` 搜索字面的 "foo.bar"。
## 搜索高亮:hlsearch 和 incsearch
两个选项控制搜索时的视觉反馈:
```vim
set hlsearch " 高亮所有匹配项
set incsearch " 输入时实时跳转到第一个匹配
```
`hlsearch` 打开后,最后一次搜索的所有匹配都会高亮显示。缺点是高亮会一直留在屏幕上,用 `:nohlsearch`(简写 `:noh`)临时关闭。很多人在 vimrc 中映射一个快捷键:
```vim
nnoremap <Esc><Esc> :nohlsearch<CR>
```
连按两次 Esc 清除高亮。
`incsearch` 让你在输入搜索模式的过程中就能看到当前匹配位置,不用等按回车。这对复杂正则特别有用,输入到一半就能判断模式是否正确。
## 替换命令 :s 和 :%s
基本语法:
```vim
:s/old/new/ " 当前行替换第一个匹配
:s/old/new/g " 当前行替换所有匹配
:%s/old/new/g " 全文替换
:5,20s/old/new/g " 第5行到第20行替换
```
范围除了行号,还支持这些写法:
- `.,$s` — 从当前行到文件末尾
- `1,.s` — 从第一行到当前行
- `'<,'>s` — 可视模式下选中区域(按 `:` 自动填充)
## 确认替换:c 标志
替换最怕改错地方。加 `c` 标志让每次替换前都确认:
```vim
:%s/old/new/gc
```
Vim 会逐个高亮匹配,提示你选择:
- `y` — 替换当前
- `n` — 跳过当前
- `a` — 替换剩余全部
- `q` — 退出替换
- `l` — 替换当前后退出
这个交互流程让大规模替换变得安全可控。
## 正则捕获与反向引用
用 `\( \)` 分组(magic 模式)或 `()` 分组(very magic 模式),替换时用 `\1`、`\2` 引用捕获内容:
```vim
" 将 "lastName, firstName" 改为 "firstName lastName"
:%s/\v(\w+),\s*(\w+)/\2 \1/g
```
这里 `\1` 是第一个括号匹配的内容(姓),`\2` 是第二个(名)。
另一个实用场景——给函数调用加引号:
```vim
:%s/\vfunc\(([^)]+)\)/func("\1")/g
```
## 大小写控制:\c 和 \C
在搜索模式中直接加修饰符比改设置更灵活:
- `/pattern\c` — 忽略大小写搜索
- `/pattern\C` — 强制区分大小写
也可以配合 `ignorecase` 和 `smartcase` 选项:
```vim
set ignorecase " 默认忽略大小写
set smartcase " 搜索模式中包含大写字母时自动区分大小写
```
`smartcase` 的逻辑是:如果你特意输入了大写字母,说明你要精确匹配,否则就忽略大小写。这个组合比单独用 `ignorecase` 更智能。
## 搜索历史的复用
Vim 保存搜索历史,在搜索模式下按上下方向键可以回溯之前的搜索模式。更高效的做法是先输入部分内容再按上下键,Vim 只显示以该内容开头的历史条目。
命令行窗口是另一个利器:按 `q/` 打开搜索历史窗口,`q:` 打开命令历史窗口。在这个窗口中可以用 Vim 的全部编辑能力修改历史命令,然后按回车执行。
## 跨文件搜索::vimgrep
当搜索范围需要超出当前文件:
```vim
:vimgrep /pattern/ **/*.py
```
这会递归搜索当前目录下所有 `.py` 文件。搜索结果进入 quickfix 列表。
常用操作:
- `:copen` — 打开 quickfix 窗口
- `:cnext` / `:cprev` — 在匹配项之间跳转
- `:cfirst` / `:clast` — 跳到第一个/最后一个匹配
- `:cclose` — 关闭 quickfix 窗口
也可以指定文件范围:
```vim
:vimgrep /TODO/ src/**/*.js
:vimgrep /FIXME/ *.c
```
quickfix 列表不只服务于 vimgrep,`:grep`、编译错误等都会用到同一套导航命令,值得记住。
## 从搜索到替换的实战流程
一个高效的工作流是先搜索验证,再替换执行:
1. 用 `/pattern` 搜索,`n` 逐个检查匹配是否符合预期
2. 确认无误后直接执行 `:%s//replacement/g`——注意搜索模式留空,Vim 自动使用最后一次搜索的模式
3. 不确定时加 `c` 标志逐步确认
这个流程把搜索和替换打通,避免了在替换命令中重新输入复杂正则的麻烦。
Vim 的搜索体系从单文件关键词到跨文件正则,覆盖了文本定位的完整链路。掌握这些技巧后,日常编辑中的查找替换操作会从反复试错变成一次到位。服务端5月27日 14:50
Vim 的折叠功能怎么用?打开一个上千行的配置文件或源码时,满屏文本让人无从下手。Vim 的折叠功能可以把逻辑块收成一行,让代码结构一目了然——但很多人只停留在 `zc`/`zo` 的程度,不知道 Vim 其实提供了六种折叠方式,各有适用场景。
## 六种折叠方式
Vim 通过 `foldmethod` 选项决定折叠规则,一共有六种:
**manual** — 手动折叠。用 `zf` 配合移动命令圈选范围来创建折叠,最灵活但退出后丢失(除非持久化)。适合临时阅读不熟悉的文件。
**indent** — 按缩进折叠。缩进越深,折叠层级越高,Vim 用 `shiftwidth` 的值把缩进空格数折算成折叠级别。Python、YAML 这类缩进敏感的语言用这个最省心:
```vim
set foldmethod=indent
```
**expr** — 表达式折叠。通过 `foldexpr` 指定一个 Vim 表达式,对每一行求值返回折叠级别。灵活性最强,写法也最复杂。一个常见用法——按空行分段落折叠:
```vim
set foldmethod=expr
set foldexpr=getline(v:lnum)=~'^\s*$'?'<1':1
```
返回值的含义:正整数表示折叠级别,`>N` 表示 N 级折叠从此行开始,`<N` 表示 N 级折叠到此行结束,`=` 继承上一行级别,`a1`/`s1` 分别在上一行基础上加/减一级。为了性能,建议把逻辑封装成函数:
```vim
set foldexpr=MyFoldLevel()
function MyFoldLevel()
let line = getline(v:lnum)
if line =~# '^\s*$'
return '<1'
else
return 1
endif
endfunction
```
**syntax** — 语法折叠。依赖语法高亮文件中定义的 `fold` 区域,不需要额外配置,前提是当前文件类型的语法文件支持折叠。大部分主流语言开箱即用:
```vim
set foldmethod=syntax
```
**diff** — 差异折叠。只在 diff 模式下生效,自动把未修改的连续行折叠起来,只展示差异部分。用 `vimdiff` 比较文件时自动启用,无需手动设置。
**marker** — 标记折叠。通过文本中的标记符号定义折叠边界,默认是 `{{{` 和 `}}}`。标记会写入文件内容,所以退出后依然存在,且支持撤销/重做:
```vim
set foldmethod=marker
" 代码中写:
" 函数开始 {{{
function! Example()
" ...
endfunction
" }}}
```
不同文件类型可以用对应的注释格式:Python 用 `# {{{`,HTML 用 `<!-- {{{ -->`,C 用 `/* {{{ */`。
## 折叠操作的快捷键
掌握创建、删除、打开、关闭四个维度就够了:
**创建折叠:**
- `zf` + 移动命令:创建折叠。`zf3j` 把当前行及下方三行折起来,`zf%` 折叠配对的括号块,`zfa}` 折叠当前大括号内的内容。
- `zf` + 可视选择:在 Visual 模式下选中后按 `zf`。
**删除折叠:**
- `zd`:删除光标处的一个折叠(只删折叠结构,不删内容)。
- `zD`:递归删除光标处所有嵌套折叠。
- `zE`:删除当前窗口所有折叠。
**打开/关闭折叠:**
- `zo`:打开当前折叠。
- `zc`:关闭当前折叠。
- `za`:切换开关(最常用)。
- `zO` / `zC`:递归打开/关闭所有嵌套层。
- `zR`:打开所有折叠(全局)。
- `zM`:关闭所有折叠(全局)。
在折叠行上按回车或双击也可以打开折叠,但快捷键更高效。
## 嵌套折叠
折叠可以层层嵌套。一个函数内部有 if 块,if 块内部有循环,每层都可以独立折叠。嵌套深度由 `foldnestmax` 控制,默认没有上限:
```vim
set foldnestmax=3
```
超过最大嵌套层数的折叠会被合并到允许的最深层级。对于结构复杂的代码,适当限制嵌套层级能避免过度折叠导致结构不清晰。
## 折叠相关的显示选项
**foldcolumn** — 在窗口左侧显示折叠指示列。设置为 0 隐藏,最大 12,建议设为 2 或 3:
```vim
set foldcolumn=2
```
折叠列里用 `-` 表示折叠打开的行,`+` 表示折叠关闭的位置,`|` 表示折叠层级的延续。有了折叠列,鼠标点击也可以操作折叠。
**foldlevel** — 控制初始折叠深度。设为 0 打开文件时全部折叠,设为 99 等于全部展开。设一个中间值,打开文件就能看到结构骨架:
```vim
set foldlevel=2
```
配合 `foldlevelstart` 可以单独控制打开文件时的初始折叠级别而不影响后续操作。
**foldminlines** — 折叠最少显示行数。如果一个折叠内容不足指定行数,就不允许折叠它。避免把两三行的小块也折起来:
```vim
set foldminlines=5
```
## 折叠持久化
manual 模式的折叠退出 Vim 就没了。要持久化,在 vimrc 中加:
```vim
augroup FoldPersist
autocmd!
autocmd BufWinLeave * mkview
autocmd BufWinEnter * silent loadview
augroup END
```
`mkview` 保存当前窗口的折叠状态、光标位置等信息,`loadview` 恢复。视图文件默认存在 `~/.vim/view/` 目录下。
marker 模式天然持久,因为标记写在文件内容里,但会污染文件,协作项目慎用。indent、expr、syntax 这三种是按规则实时计算的,不需要额外持久化——重新打开文件,折叠会自动重建。
## 大文件折叠的性能
折叠是有代价的。`foldmethod=expr` 和 `foldmethod=syntax` 需要对每一行求值或语法解析,文件上万行时可能出现明显的卡顿,尤其是滚动和插入时频繁重算。
几个应对方法:
- 大文件优先用 `indent`,计算量最小。
- 把 `foldexpr` 的逻辑封装成函数,Vim 对编译过的函数调用比直接求值快。
- 用 `foldnestmax` 限制嵌套层数,减少计算深度。
- 只读查看时开启折叠,编辑时临时切回 manual:`set foldmethod=manual`。
- Vim 8+ 可以用 `foldmethod=expr` 配合 `async` 插件异步计算,但原生的折叠本身是同步的。
如果你经常处理大文件,建议在 vimrc 里按文件类型设置不同的折叠策略,而不是一刀切。
## 推荐的 vimrc 折叠配置
把上面这些选项组合起来,一个实用折中的配置:
```vim
" 默认使用缩进折叠
set foldmethod=indent
set foldlevel=2
set foldcolumn=2
set foldminlines=3
set foldnestmax=6
" 按文件类型覆盖
autocmd FileType vim setlocal foldmethod=marker
autocmd FileType python setlocal foldmethod=indent
autocmd FileType json,yaml setlocal foldmethod=syntax
" 持久化 manual 折叠
augroup FoldPersist
autocmd!
autocmd BufWinLeave * mkview
autocmd BufWinEnter * silent loadview
augroup END
```
折叠不是花哨的功能——它解决的是真实问题:在有限屏幕里看清代码结构。花十分钟配好折叠策略,之后每次打开文件都能直接看到骨架而不是一片文字墙。服务端5月27日 14:49
Vim 的拼写检查怎么开启和校正?写代码的时候拼错变量名、写文档的时候拼错单词,这种事谁都遇到过。Vim 从 7.0 开始就内置了拼写检查,不需要额外装插件,配几行就能用。但很多人要么不知道这个功能,要么只知道 `:set spell` 就停了。下面把常用操作和容易踩的坑都过一遍。
## 开启拼写检查并选择语言
核心就两个选项:
```vim
set spell
set spelllang=en_us
```
`spell` 打开拼写检查,`spelllang` 指定检查哪种语言。Vim 默认只自带英语拼写文件,第一次切换到其他语言时会自动从 vim.org 下载对应的 `.spl` 文件,放到 `~/.vim/spell/` 目录下。
如果只想对当前缓冲区生效而不影响其他文件,用 `setlocal` 代替 `set`:
```vim
setlocal spell spelllang=en_us
```
关闭拼写检查则是 `:set nospell`。
## 在拼写错误之间跳转
开启之后,Vim 会用不同颜色标记出问题单词:
- **SpellBad**(红色)— 不认识的词
- **SpellCap**(蓝色)— 应该大写但没大写
- **SpellRare**(黄色)— 罕见拼写
- **SpellLocal**(绿色)— 不符合当前区域的拼写
跳转命令:
| 命令 | 作用 |
|------|------|
| `]s` | 跳到下一个拼写错误 |
| `[s` | 跳到上一个拼写错误 |
| `]S` | 跳到下一个坏词(只跳 SpellBad,忽略大小写等问题) |
| `[S` | 跳到上一个坏词 |
日常用 `]s` 和 `[s` 就够了,`]S` 和 `[S` 在你只关心"这个词不认识"的时候更精准。
## 用 z= 修正拼写
光标放在拼写错误的单词上,按 `z=`,Vim 会弹出一个建议列表,编号从 1 开始。输入对应数字回车即可替换。
如果建议列表太长,可以加计数前缀跳过前面的选项,比如 `3z=` 直接选第三个建议。
在可视模式下先选中一段文本再按 `z=`,可以对选中部分做批量替换建议。
## 用 zg 添加到词库、用 zw 标记为错误
`zg` 把光标下的词标记为"好词",写入 spellfile,以后不再报错。这在遇到专有名词、项目术语、人名时特别有用。
`zw` 则相反,把光标下的词标记为"坏词"——即使系统词典认为它合法,也会被标红。
如果手滑加错了,撤销命令是 `zug`(撤销 zg)和 `zuw`(撤销 zw)。
注意区分大小写:`zG` 和 `zW` 是会话级的,只在当前 Vim 进程内生效,退出后丢失。`zg` 和 `zw` 则写入 spellfile,持久保存。
## 自定义词盘:spellfile 配置
Vim 的 spellfile 是一个纯文本文件,每行一个单词。默认路径类似 `~/.vim/spell/en.utf-8.add`,命名规则是 `{语言}.{编码}.add`。
手动指定 spellfile:
```vim
set spellfile=~/.vim/spell/en.utf-8.add
```
这个文件可以直接编辑——想批量加词,直接往里面写就行,Vim 会在下次载入时自动编译成 `.spl` 格式。也可以把它加入版本控制,团队共享一份术语表。
想加载多个词盘,用逗号分隔:
```vim
set spellfile=~/.vim/spell/en.utf-8.add,~/.vim/spell/project.utf-8.add
```
`zg` 会把词加到第一个 spellfile 里。
另外,`set complete+=kspell` 可以让 Vim 在插入模式补全时也参考拼写词典,输入时按 Ctrl-N / Ctrl-P 即可触发。
## 多语言拼写:中英文混合场景
`spelllang` 支持逗号分隔的多语言列表:
```vim
set spelllang=en_us,zh_cn
```
这样中英文会同时检查。但有一个现实问题:Vim 对中文的拼写检查能力远不如英文,中文的 `.spl` 文件并不像英文那样有完整的词库覆盖。实际使用中,`zh` 的拼写检查价值有限,更多时候还是靠英文检查来抓 typo。
一个更务实的做法是只开英文检查,中文文本不会被误报(Vim 对不在词典语言范围内的文本默认不检查):
```vim
set spelllang=en_us
```
如果你的文档以英文为主、夹杂少量中文,这个配置就够了。
## 编程场景:只在注释和字符串中检查
写代码时,如果全局开 spell,变量名和方法名会大面积标红,很干扰。Vim 的语法系统提供了 `@Spell` 和 `@NoSpell` 两个集群(cluster),可以让拼写检查只作用于特定语法区域。
对大多数 filetype 来说,Vim 自带的语法文件已经把注释和字符串归入了 `@Spell`,其他区域归入 `@NoSpell`。所以正常情况下,在代码文件里开 `set spell`,只有注释和字符串会被检查,变量名不会报错。
如果你的某个 filetype 没有做好这个区分,可以在语法文件里手动调整:
```vim
syntax match myComment "\/\/.*" contains=@Spell
syntax match myIdent "\<\w\+\>" contains=@NoSpell
```
这样就能精确控制哪些区域参与拼写检查。
## spellfile-plugin:自动下载词盘
Vim 自带一个 `spellfile-plugin`,当 `spelllang` 设置了一个本地没有对应 `.spl` 文件的语言时,这个插件会自动从 vim.org 下载。它默认是启用的。
如果因为网络问题下载失败,可以手动从 `ftp://ftp.vim.org/pub/vim/runtime/spell/` 下载对应的 `.spl` 文件,放到 `~/.vim/spell/` 目录下。
禁用自动下载:
```vim
let g:loaded_spellfile_plugin = 1
```
## 与 coc.nvim 和 nvim-lsp 的配合
Vim 内置的 spell 是基于词典的拼写检查,不依赖 LSP。但如果你还用了 coc.nvim 或 nvim-lspconfig,两者可以并行不冲突。
**coc.nvim** 有 `coc-spell-checker` 扩展,底层基于 cspell,可以检查代码中的标识符和注释拼写。它和 Vim 原生 spell 各管各的,不会互相干扰,但也会出现同一段文本两边都报错的情况。如果觉得冗余,可以关掉其中一个。
**nvim-lsp** 方面,有几个专门的拼写检查 Language Server:
- **cspell-lsp** — 基于 cspell,支持自定义词典,对代码场景优化好
- **typos-lsp** — 轻量快速,专注抓源码中的 typo
- **ltex-ls** — 基于 LanguageTool,除了拼写还检查语法,适合写文档和 Markdown
- **harper-ls** — 隐私友好的语法检查器,离线运行
配置示例(nvim-lspconfig + cspell):
```lua
vim.lsp.enable("cspell_ls")
vim.lsp.config("cspell_ls", {
cmd = { "cspell-lsp", "--stdio" },
filetypes = { "markdown", "gitcommit", "text" },
root_markers = { ".git" },
})
```
实际使用建议:日常写 Markdown 和 commit message 用 Vim 内置 spell 就够了,轻量且零依赖。如果项目需要更严格的拼写检查(比如开源项目要求 CI 里也跑 cspell),再上 LSP 方案。
## 常用配置汇总
一段比较实用的 `.vimrc` 配置:
```vim
" 拼写检查
set spell
set spelllang=en_us
set spellfile=~/.vim/spell/en.utf-8.add
set complete+=kspell
" 只在特定文件类型开启
autocmd FileType markdown,gitcommit setlocal spell
autocmd FileType python,javascript setlocal nospell
" 快捷键
nnoremap <F5> :set spell!<CR>
nnoremap <leader>s ]s
nnoremap <leader>S [s
```
Vim 的拼写检查不算复杂,但覆盖面比很多人想象的广——从简单的英文纠错到多语言混合、代码注释定向检查、团队共享词盘,都能做。花五分钟配好,之后每次写文档和 commit message 都能少犯几个低级拼写错误。服务端5月27日 14:48
Vim 退出后如何恢复上次的工作状态?每次关闭 Vim 再重新打开,窗口布局没了,文件列表清空,折叠消失了——这种"从零开始"的体验让人抓狂。Vim 内置的会话(session)和视图(view)功能,专门解决这个问题。
## 用 :mksession 保存完整工作环境
`:mksession` 把当前 Vim 的窗口布局、标签页、缓冲区列表、折叠状态、当前目录等信息序列化成一个 Vim 脚本文件:
```vim
" 保存到当前目录的 Session.vim
:mksession
" 指定路径
:mksession ~/sessions/project-a.vim
" 文件已存在时强制覆盖
:mksession! ~/sessions/project-a.vim
```
会话文件本质上是一段 Vim 脚本,可以直接打开查看。里面记录了 `tabnew`、`split`、`edit` 等命令,Vim 逐行执行就能还原你的编辑环境。
## 用 :source 或 vim -S 恢复会话
恢复会话有两种方式:
```vim
" 在 Vim 内部加载
:source Session.vim
```
```bash
# 启动时直接加载
vim -S Session.vim
# 等价写法
vim -S ~/sessions/project-a.vim
```
`vim -S` 是最常用的方式,可以把加载会话的命令写进项目目录的 Makefile 或 shell alias 里,一条命令就能回到上次的工作状态。
## sessionoptions 控制保存范围
不是所有东西都需要保存到会话里。`sessionoptions`(缩写 `ssop`)决定了 `:mksession` 写入哪些内容:
```vim
" 查看当前设置
:set sessionoptions?
" 典型配置
:set sessionoptions=buffers,curdir,folds,help,tabpages,winsize,terminal
```
常用选项说明:
- `buffers` — 缓冲区列表(包括隐藏缓冲区)
- `tabpages` — 所有标签页,去掉则只保存当前标签页
- `winsize` — 窗口大小比例
- `folds` — 手动折叠信息
- `curdir` — 当前工作目录
- `terminal` — 终端窗口
实际使用中经常需要微调。比如你不希望会话保存终端窗口,因为重新打开时原来的 shell 进程已经不存在了:
```vim
:set sessionoptions-=terminal
```
不想保存空白窗口:
```vim
:set sessionoptions-=blank
```
## :mkview 和 :loadview 保存单个窗口的状态
会话保存的是全局状态,但有时你只想保存某个窗口的折叠、滚动位置和本地选项。这时用视图:
```vim
" 保存当前窗口的视图
:mkview
" 加载当前窗口的视图
:loadview
```
视图和会话的区别在于作用域——视图只管当前窗口,会话管整个 Vim 实例。视图默认保存在 `viewdir` 目录下,文件名由缓冲区路径编码而来:
```vim
" 查看 viewdir 位置
:set viewdir?
" 自定义 viewdir
:set viewdir=~/.vim/views
```
一个常见用法是在 `vimrc` 里自动保存和恢复视图:
```vim
autocmd BufWinLeave *.py mkview
autocmd BufWinEnter *.py loadview
```
这样每次切换 Python 文件时,之前的折叠和滚动位置都能自动恢复。
## 会话与标签页、窗口、缓冲区的关系
理解这三者的关系有助于用好会话:
- **缓冲区(buffer)**:文件在内存中的实例,会话保存的是缓冲区列表,不是文件内容
- **窗口(window)**:缓冲区的视口,一个缓冲区可以出现在多个窗口中,会话保存窗口的布局和尺寸
- **标签页(tabpage)**:窗口的容器,每个标签页有自己的窗口布局,会话保存所有标签页(如果 `sessionoptions` 包含 `tabpages`)
关键点:会话恢复时,Vim 会先加载缓冲区列表,然后按照保存的布局重建窗口和标签页。如果文件已经被移动或删除,对应的窗口会变成空缓冲区。
## 项目级会话管理
给不同项目维护独立的会话文件是最实用的做法。在项目根目录保存一个 `Session.vim`,启动时用 `vim -S` 加载:
```bash
# 在项目目录下
cd ~/projects/my-app
vim -S
```
更规范的做法是把会话文件放在统一目录,按项目名区分:
```bash
mkdir -p ~/.vim/sessions
vim -S ~/.vim/sessions/my-app.vim
```
也可以在 `vimrc` 里根据当前目录自动选择会话:
```vim
let g:session_dir = expand('~/.vim/sessions/')
let g:session_file = g:session_dir . substitute(getcwd(), '/', '_', 'g') . '.vim'
```
## 自动保存和恢复会话
手动执行 `:mksession` 容易忘记。用自动命令在退出 Vim 时自动保存:
```vim
" 退出时自动保存会话
autocmd VimLeave * if v:this_session != '' | execute 'mksession!' v:this_session | endif
```
`v:this_session` 变量保存着当前会话文件的路径。如果还没加载过会话,这个变量为空字符串,此时不执行保存,避免在随意打开文件时产生多余的 Session.vim。
配合启动时自动加载:
```vim
" 启动时加载项目会话(无参数时)
autocmd VimEnter * if argc() == 0 && filereadable(g:session_file) | execute 'source' g:session_file | endif
```
`argc() == 0` 确保只有直接输入 `vim` 不带文件参数时才加载会话,避免和 `vim file.txt` 这种用法冲突。
## tpope/vim-obsession 插件
手动管理会话的自动命令虽然能工作,但需要处理各种边界情况(比如打开多个 Vim 实例、会话文件冲突)。tpope 的 vim-obsession 插件把这些细节都封装好了:
```vim
" 安装后,在项目目录执行
:Obsess ~/.vim/sessions/my-app.vim
" 停止自动保存
:Obsess!
```
启动 Obsession 后,它会持续跟踪当前会话的变化,在合适时机自动更新会话文件。退出 Vim 时自动保存,不需要额外配置。
和手动方案相比,vim-obsession 的优势在于:
- 不会在未启动 Obsession 的情况下覆盖已有会话
- 正确处理多实例场景
- 和 vim-fugitive 等插件配合良好
如果你经常在多个项目之间切换,vim-obsession 基本上是必装的。
## viminfo 和会话的分工
会话保存的是"你在看什么"——窗口、标签页、缓冲区布局。viminfo 保存的是"你做过什么"——命令历史、搜索历史、寄存器内容、标记位置。
```vim
" 保存 viminfo
:wviminfo ~/.vim/viminfo
" 加载 viminfo
:rviminfo ~/.vim/viminfo
```
完整的恢复需要两者配合:会话还原布局,viminfo 还原操作历史。Vim 默认在退出时自动写入 viminfo,所以通常只需要手动管理会话部分即可。
---
Vim 的会话机制把"恢复工作环境"这件事从手动操作变成了可自动化的流程。从最基础的 `:mksession` / `:source` 开始,配合 `sessionoptions` 调整保存范围,再用自动命令或 vim-obsession 实现无人值守的保存恢复,这个渐进式的路径覆盖了从偶尔用到每天依赖的全部场景。服务端5月27日 14:46
Vim 的键映射怎么配置才不会踩坑?Vim 的强大很大程度上来自键映射——把任意按键组合重新定义为自定义操作。但映射命令种类繁多,`map` 和 `noremap` 的区别、各模式前缀的含义、Leader 键的用法,稍不注意就会写出互相冲突甚至递归死循环的配置。这篇文章把这些问题逐个说清楚。
## map / nmap / vmap / imap / omap / cmap 分别管哪个模式
Vim 有多种编辑模式,映射命令的前缀决定了它生效的范围:
- **`map`**:普通模式 + 可视模式 + 操作符等待模式都生效,范围最广
- **`nmap`**:仅普通模式(Normal)
- **`vmap`**:仅可视模式(Visual),包括行可视和块可视
- **`imap`**:仅插入模式(Insert)
- **`omap`**:仅操作符等待模式(Operator-pending),比如按 `d` 之后等待移动目标的那段时间
- **`cmap`**:仅命令行模式(Command-line),即按 `:` 之后输入命令时
实际配置中,`nmap` 和 `imap` 使用频率最高。`omap` 用得少但很有用——比如你可以把 `dw` 中 `w` 的行为重新定义,而不影响普通模式下直接按 `w` 的跳转。
## 为什么推荐 noremap 而不是 map
`map` 系列命令是递归映射。当你写了这样的配置:
```vim
nmap j gj
nmap gj 5j
```
按 `j` 时 Vim 会先展开为 `gj`,再把 `gj` 展开为 `5j`,最终变成按5次 `j`,而 `j` 又被映射为 `gj`——无限递归,Vim 直接报错。
`noremap` 系列命令(`nnoremap`、`inoremap`、`vnoremap`、`onoremap`、`cnoremap`)是非递归映射,右侧的内容按字面意义执行,不会再被其他映射展开:
```vim
nnoremap j gj
nnoremap gj 5j
```
这样按 `j` 执行 `gj`(按屏幕行移动),按 `gj` 执行 `5j`(向下5行),互不干扰。
**结论:除非你有明确的递归需求,否则永远用 `noremap` 系列。** 这是 Vim 配置最基本的一条纪律。
## <leader> 键:给你的快捷键加个命名空间
Leader 键是一个自定义前缀,用来把你的个人映射和 Vim 默认键位隔离开。不设置的话默认是反斜杠 `\`,但反斜杠位置偏僻,大多数人会改成逗号或空格:
```vim
let mapleader = ","
" 或者用空格
let mapleader = " "
```
之后所有以 `<leader>` 开头的映射都会把该键作为前缀:
```vim
nnoremap <leader>w :w<CR>
nnoremap <leader>q :q<CR>
nnoremap <leader>ev :vsplit $MYVIMRC<CR>
```
如果 Leader 是逗号,按 `,w` 就保存文件,按 `,q` 就退出,按 `,ev` 就垂直分割打开 vimrc。
查看当前 Leader 键:
```vim
:echo mapleader
```
还有个 `<localleader>`,专门给文件类型插件用,和全局 Leader 互不干扰,通常设为反斜杠或其他键。
## <buffer>:只对当前文件生效的映射
默认情况下映射是全局的,所有缓冲区都生效。加 `<buffer>` 后映射只在定义它的那个缓冲区里有效:
```vim
nnoremap <buffer> <F5> :!python3 %<CR>
```
这条映射只在定义时活跃的那个 Python 文件里按 F5 运行脚本,切换到别的文件就没了。FileType 插件里大量使用这个参数,确保不同文件类型有各自的快捷键而不互相覆盖。
## <silent>:不让映射污染命令行
普通映射执行时,命令行区域会闪出映射的内容。比如:
```vim
nnoremap <leader>h :nohlsearch<CR>
```
按 `,h` 时命令行会短暂显示 `:nohlsearch`。加上 `<silent>` 就不会显示:
```vim
nnoremap <silent> <leader>h :nohlsearch<CR>
```
对于切换开关类的映射(拼写检查、搜索高亮、折叠),`<silent>` 几乎是标配,避免命令行闪烁干扰注意力。
## <expr>:动态计算映射内容
`<expr>` 让映射的右侧不是一个固定的字符串,而是一个表达式,Vim 每次按键时都会求值:
```vim
nnoremap <expr> <leader>n (&number ? ':set nonumber<CR>' : ':set number<CR>')
```
按 `<leader>n` 时,Vim 先算 `&number` 的值——如果行号是开着的就执行 `set nonumber`,关着就执行 `set number`,实现切换。
更实用的例子是让 `<Tab>` 在插入模式里根据上下文决定是插入制表符还是触发补全:
```vim
inoremap <expr> <Tab> pumvisible() ? "\<C-N>" : "\<Tab>"
```
补全菜单可见时按 Tab 跳到下一项,否则插入普通 Tab。
## 映射特殊键
Vim 里有不少按键没法直接写在映射里,需要用特殊表示法:
| 特殊键 | 表示法 | 用途示例 |
|--------|--------|----------|
| 回车 | `<CR>` | `nnoremap <leader>w :w<CR>` |
| Esc | `<Esc>` | `inoremap jk <Esc>` |
| Tab | `<Tab>` | 插入模式补全 |
| 空格 | `<Space>` | `let mapleader = "\<Space>"` |
| Ctrl+X | `<C-X>` | `<C-A>` 全选、`<C-R>` 重做 |
| Alt+X | `<A-X>` 或 `<M-X>` | Meta 键 |
| 无操作 | `<NOP>` | 禁用某个键:`nnoremap <Up> <NOP>` |
| F1-F12 | `<F1>`-`<F12>` | 功能键 |
注意 `<CR>` 是回车(Carriage Return),几乎每个执行冒号命令的映射末尾都要加,否则命令只显示在命令行不会执行。
## 常见映射方案
**快速保存和退出:**
```vim
nnoremap <leader>w :w<CR>
nnoremap <leader>q :q<CR>
nnoremap <leader>x :x<CR>
```
**插入模式快速回到普通模式:**
```vim
inoremap jk <Esc>
inoremap jj <Esc>
```
**按屏幕行移动(长行折行时很好用):**
```vim
nnoremap j gj
nnoremap k gk
nnoremap 0 g0
nnoremap $ g$
```
**窗口导航:**
```vim
nnoremap <C-h> <C-w>h
nnoremap <C-j> <C-w>j
nnoremap <C-k> <C-w>k
nnoremap <C-l> <C-w>l
```
**清除搜索高亮:**
```vim
nnoremap <silent> <leader><CR> :nohlsearch<CR>
```
**可视模式快速缩进:**
```vim
vnoremap < <gv
vnoremap > >gv
```
选中文本后按 `<` 或 `>` 缩进,`gv` 重新选中,可以连续按。
## 映射冲突排查
当某个按键不按预期工作,用这几个命令诊断:
```vim
" 查看某个键的所有映射
:verbose map <leader>w
" 查看当前所有映射
:map
" 只看普通模式映射
:nmap
```
`verbose` 会显示映射定义在哪个文件的第几行,定位插件冲突的关键信息。如果发现插件覆盖了你的映射,常见处理方式:
1. 在插件加载之后再定义你的映射(vimrc 中的顺序很重要)
2. 用 Leader 键作为前缀,几乎不会和插件默认映射冲突
3. 对特定文件类型使用 `<buffer>` 局部映射,避免全局冲突
4. 用 `<nowait>` 让映射立即执行,不被更长的映射序列截获:
```vim
nnoremap <nowait> <leader>a :echo "immediate"<CR>
```
Vim 的键映射体系看起来命令很多,核心就三条:用 `noremap` 避免递归、用 Leader 组织命名空间、用 `<buffer>` 限定作用域。把这三点贯彻到配置里,其他参数都是在这个基础上按需叠加。
服务端5月27日 14:46
Vim 宏录制功能怎么用才能高效重复操作?每天在 Vim 里重复同样的编辑动作,一遍又一遍地按键、移动、修改——如果有个按钮能把这串操作"录下来,一键回放",效率会怎样?Vim 的宏录制就是这样一个功能:它不是花架子,而是真正能省下大量重复劳动的工具。
## 宏录制的基本流程:q 开始,q 结束
宏的核心逻辑很简单:**按 q 加一个寄存器名开始录制,再按 q 停止录制**。
具体步骤:
1. 在普通模式下按 `qa`——把后续操作录制到寄存器 a 中,左下角会出现 `recording @a` 提示
2. 执行你需要的编辑操作(移动、删除、插入、替换……任何普通模式命令都行)
3. 按 `q` 停止录制
仅此而已。录完后,寄存器 a 里就存好了你刚才的整个操作序列。
一个关键细节:**录制前先把光标放到一个"干净"的位置**。很多人第一次录宏时,光标在行中间就开始操作,结果回放时位置不对,整个宏就废了。养成习惯,录制前先按 `0` 回到行首,这样每次回放都从确定的位置开始。
## 回放宏:@ 和 @@
录好的宏用 `@` 加寄存器名回放:
- `@a` —— 执行寄存器 a 中的宏
- `@@` —— 重复执行上次回放的宏(不用再敲寄存器名)
- `5@a` —— 把宏执行 5 次
最实用的组合是:录好宏之后,先用 `@a` 跑一次确认效果没问题,然后直接 `10@@` 或 `50@@` 批量执行。如果中间某次执行出错(比如搜索没匹配到),宏会自动停止,不会一路错下去。
## 追加录制:用大写字母往宏里加步骤
录完宏发现漏了一步怎么办?不用重新录。**按大写字母可以往已有宏的末尾追加操作**。
假设之前用 `qa` 录了一个宏,现在想在里面加一个操作:
1. 按 `qA`(大写 A)开始追加录制
2. 执行你要补充的操作
3. 按 `q` 结束
这样寄存器 a 中的内容就是原来的操作加上新追加的操作。这在调试宏时特别有用——先录一个基础版本试跑,发现缺什么再追加。
## 宏寄存器:宏就是文本,可以查看和编辑
宏存储在 Vim 的命名寄存器(a-z)中,本质上就是一段按键序列的文本。这意味着你可以直接查看和修改它。
查看寄存器内容:
```vim
:reg a
```
把宏粘贴出来编辑:
```vim
"ap
```
这会把寄存器 a 的内容当作普通文本粘贴到当前光标位置。你直接改文本,改完再用 `"ayy` 把这一行存回寄存器 a。对于比较长的宏,这种编辑方式比重新录制快得多。
还有一个技巧:你可以在命令行直接设置寄存器内容:
```vim
:let @a = '0iHello^[
```
其中 `^[` 是 Esc 键的表示,用 `Ctrl+V` 然后 `Esc` 输入。这样你甚至可以把宏写成配置文件的一部分。
## 批量执行::normal 命令配合可视模式
逐行 `@a` 效率太低,Vim 提供了两种批量执行宏的方式。
**方式一:用次数前缀**
```vim
5@a
```
简单直接,但要确保文件确实有足够的行,否则宏在中途找不到目标行会报错停止。
**方式二:可视模式 + :normal**
1. 可视模式选中目标行(`V` 然后 `j` 或 `G`)
2. 输入 `:normal @a`
这种方式更稳妥——只在你选中的行上执行,不会跑飞。而且即使某一行执行出错,其他行照常执行,互不影响。处理大文件时,这个特性非常关键。
方式二的进阶用法:对整个文件执行宏。
```vim
:%normal @a
```
等价于先 `ggVG` 全选再 `:normal @a`,但写法更简洁。
## 复杂宏技巧:计数、搜索与递归
宏不只是一堆简单的移动和编辑命令,它完全可以包含搜索、计数等高级操作。
### 在宏中使用搜索
录制时按 `/pattern<CR>` 搜索目标位置,回放时宏会自动执行这个搜索。这对于"找到下一个符合条件的位置再操作"的场景非常有效。
但要注意:**搜索命令在不同行可能匹配到不同位置**。如果你的操作依赖精确的列位置,搜索后最好加一个 `0` 或 `^` 把光标规范到行首。
### 计数器递增
Vim 有个 `Ctrl+A` 命令可以让光标下的数字加 1。结合宏可以快速生成递增序列:
1. 在第一行输入 `1`
2. `qa` 开始录制
3. `yy` 复制当前行,`p` 粘贴到下一行
4. `Ctrl+A` 让数字加 1
5. `q` 结束录制
6. `98@a` 生成 1 到 100
### 递归宏:让宏自己调用自己
在录制宏的最后一步,输入 `@a`(调用自身),然后再按 `q` 结束录制。这样宏就会不断递归执行,直到某一步出错自动停止。
```vim
qa " 开始录制到 a
0 " 移到行首
/pattern " 搜索目标
dd " 删除该行
@a " 递归调用自身
q " 结束录制
```
递归宏适合处理"不确定有多少行需要操作"的情况——不用先数行数再决定执行几次,它会一直跑到搜索失败为止。
## 宏的持久化:让宏在重启后仍然可用
默认情况下,宏只存在于当前 Vim 会话中,退出就没了。要让宏持久化,有几种方式:
**方式一:写入 vimrc**
```vim
let @a = '0dwelp'
```
把宏内容直接写进配置文件,每次启动 Vim 自动加载。
**方式二:使用 viminfo**
Vim 默认会将寄存器内容写入 viminfo 文件,下次启动时恢复。确认你的 vimrc 中有:
```vim
set viminfo='100,<50,s10,h,rA:,rB:
```
其中 `'100` 表示保存最近 100 个文件的信息,包括寄存器。
**方式三:保存到文件**
```vim
:call writefile([@a], 'my_macro.txt')
```
下次要用时:
```vim
:let @a = readfile('my_macro.txt')[0]
```
这种方式适合在不同机器间共享宏。
## 常见应用场景
### 批量给行加引号
```vim
qa " 录制到 a
I" " 行首插入引号
Esc A" " 行尾插入引号
Esc j " 下一行
q " 结束
:%normal @a " 全文执行
```
### CSV 数据提取
从一行数据中提取特定列,删掉其余部分:
```vim
qa " 录制
0df, " 删到第一个逗号
2f,ld$ " 定位到第三列,删到行尾
j " 下一行
q " 结束
```
### 代码批量重命名
把所有 `old_method` 替换成 `new_method`,同时保留行尾注释:
```vim
qa " 录制
0/new_method" 搜索
cwnew_method " 替换
Esc j " 下一行
q " 结束
```
当然简单替换用 `:%s/old_method/new_method/g` 更快,但宏的优势在于可以同时处理更复杂的组合操作——比如替换后还要调整缩进、移动位置、插入新行等,这些 `:s` 命令做不到。
### Markdown 表格对齐
```vim
qa " 录制
0f|lxA " 删除多余空格
Esc f|lxA " 下一列同样操作
Esc j " 下一行
q " 结束
```
## 宏与点命令的区别
很多人会问:已经有了 `.`(重复上一次修改),为什么还要宏?
两者的核心区别:
| 特性 | 点命令 `.` | 宏 `@a` |
|------|-----------|---------|
| 记录范围 | 只记录一次修改 | 记录完整操作序列 |
| 是否包含移动 | 不包含 | 包含 |
| 能否保存 | 不能 | 存在寄存器中 |
| 能否编辑 | 不能 | 可以 |
| 适用场景 | 单一修改的重复 | 多步操作的重复 |
简单说,`dot` 适合"同一修改,多处应用";宏适合"同一套操作流程,多行执行"。如果你的重复操作里只有一步修改,用 `.`;如果有移动、搜索、多次修改的组合,用宏。
---
Vim 的宏录制不是什么高深技巧,但它是从"手动重复"到"自动化编辑"的关键一步。核心就三件事:q 开始录、@ 回放、可视模式批量执行。掌握这三点,大部分重复编辑场景都能应对。遇到更复杂的需求,再考虑追加录制、递归宏、寄存器编辑这些进阶手法。录宏时记住一个原则——让每一步操作都位置无关,这样回放时才不会跑偏。服务端5月27日 14:01
Vim filetype 文件类型检测怎么配置?自定义语法怎么写?## 为什么你的 Vim 没有语法高亮
打开一个 `.py` 文件,满屏灰白;编辑 `.js`,缩进全靠手敲——大概率是 filetype 没开。Vim 的文件类型检测是语法高亮、缩进、文件类型插件这三套机制的地基,没它后面全白搭。
## filetype 三件套:on / plugin / indent
在 vimrc 里加上这一行就够了:
```vim
filetype plugin indent on
```
它实际等价于三条独立命令:
- `filetype on` —— 启用文件类型检测,Vim 会根据文件名和内容自动设置 `&filetype` 选项
- `filetype plugin on` —— 检测到类型后,加载对应的 ftplugin 脚本(`$VIMRUNTIME/ftplugin/<type>.vim`)
- `filetype indent on` —— 加载对应的缩进脚本(`$VIMRUNTIME/indent/<type>.vim`)
想看当前状态,直接输入:
```vim
:filetype
```
输出类似 `filetype detection:ON plugin:ON indent:OFF`,一目了然。
很多人的 vimrc 里同时写了 `syntax on` 和 `filetype on`,其实 `syntax on` 会自动安装 filetype 检测,重复写不算错但多余。如果只需要语法高亮不需要插件和缩进,单独 `syntax on` 就行;需要完整功能就用 `filetype plugin indent on` 加 `syntax enable`(`enable` 不会覆盖已有配色方案,比 `on` 更温和)。
## 检测原理:文件名优先,内容兜底
Vim 检测文件类型分两步走:
1. **文件名匹配** —— 读取 `$VIMRUNTIME/filetype.vim`(超过 2300 行),按扩展名和文件名模式匹配。`.c` 是 c,`Makefile` 是 make,`.tsx` 是 typescriptreact,覆盖了绝大部分常见文件。
2. **内容检测** —— 文件名没匹配上时,Vim 调用 `$VIMRUNTIME/scripts.vim`,检查文件头部的 shebang、DOCTYPE、特定关键词等。比如第一行是 `#!/usr/bin/env python3`,即使文件没有 `.py` 后缀也能识别为 Python。
如果两步都失败了,`&filetype` 保持空值。这时候可以手动设置:
```vim
:set filetype=python
```
或者在文件里加 modeline(写在文件末尾的注释中):
```vim
# vim: set filetype=python:
```
## ftplugin 目录:给特定文件类型加配置
ftplugin 是 filetype 和编辑器设置之间的桥梁。当你打开一个 Python 文件且 `filetype plugin on` 已启用,Vim 自动加载 `ftplugin/python.vim`。
系统自带的 ftplugin 在 `$VIMRUNTIME/ftplugin/` 下,但你应该把自定义的放在用户目录:
```
~/.vim/ftplugin/python.vim " Linux/macOS
~/vimfiles/ftplugin/python.vim " Windows
```
一个典型的 ftplugin 长这样:
```vim
" ftplugin/python.vim
setlocal expandtab
setlocal shiftwidth=4
setlocal softtabstop=4
setlocal textwidth=88
setlocal commentstring=#\ %s
```
注意用 `setlocal` 而非 `set`,这样设置只作用于当前 buffer,不会污染其他文件类型。
如果你想覆盖系统 ftplugin 的某些设置而非完全替换,用 `after/` 目录:
```
~/.vim/after/ftplugin/python.vim
```
Vim 加载顺序是:系统 ftplugin → 用户 ftplugin → after/ftplugin,后面可以覆盖前面的设置。
## syntax 目录:自定义语法高亮
语法高亮文件放在 `~/.vim/syntax/` 下,文件名就是 filetype 值。比如为一种叫 `mylang` 的自定义语言写高亮:
```vim
" syntax/mylang.vim
syntax keyword mylangKeywords if else while for return
syntax keyword mylangTodos TODO FIXME XXX
syntax match mylangNumber "\<\d\+\>"
syntax region mylangString start=+"+ end=+"+
highlight default link mylangKeywords Statement
highlight default link mylangTodos Todo
highlight default link mylangNumber Constant
highlight default link mylangString String
```
几个要点:
- 用 `highlight default link` 而非 `highlight link`,前者不会覆盖用户配色方案里的定义,后者会强制覆盖
- 语法组按语义命名(Statement、Constant、Todo),不要按颜色命名,这样换配色方案时不会出问题
- 如果不想从零写,可以用 `syn include` 把现有语法定义嵌入进来,比如在模板文件里同时高亮 HTML 和 JavaScript
## indent 目录:控制缩进行为
缩进文件放在 `~/.vim/indent/` 下,同样以 filetype 命名。系统自带的缩进脚本已经覆盖了主流语言,你通常不需要自己写。
但如果你想微调,可以在 ftplugin 里用 `setlocal` 调整:
```vim
" ftplugin/go.vim
setlocal noexpandtab
setlocal shiftwidth=4
setlocal tabstop=4
```
或者写一个完整的 indent 脚本放在 `~/.vim/indent/` 下,利用 `indentexpr` 实现复杂的缩进逻辑。Vim 自带的 `indent/python.vim` 就是很好的参考。
## 自定义文件类型检测:ftdetect 目录
如果你在写一种 Vim 不认识的文件类型,需要告诉 Vim 怎么识别它。创建 `~/.vim/ftdetect/` 目录,在里面放检测脚本:
```vim
" ftdetect/mylang.vim
autocmd BufNewFile,BufRead *.mylang setfiletype mylang
```
用 `setfiletype` 而不是 `set filetype=`,区别在于 `setfiletype` 只在 filetype 尚未设置时生效,不会覆盖已有的检测结果。
更复杂的检测可以检查文件内容:
```vim
" ftdetect/mylang.vim
autocmd BufNewFile,BufRead * if getline(1) =~# '^#!.*/mylang'
\ | setfiletype mylang
\ | endif
```
推荐用 augroup 包裹以避免重复注册:
```vim
augroup filetypedetect_mylang
autocmd!
autocmd BufNewFile,BufRead *.mylang setfiletype mylang
augroup END
```
## 目录结构速查
把自定义的文件类型相关文件整理到一起,标准目录结构如下:
```
~/.vim/
├── ftdetect/
│ └── mylang.vim " 文件类型检测规则
├── ftplugin/
│ └── mylang.vim " 文件类型专属设置
├── indent/
│ └── mylang.vim " 缩进规则
├── syntax/
│ └── mylang.vim " 语法高亮定义
└── after/
└── ftplugin/
└── python.vim " 覆盖/补充系统 ftplugin
```
检测到 filetype → 加载 syntax → 加载 ftplugin → 加载 indent,这是 Vim 处理一个文件时自动执行的完整链路。
## 常见问题
**检测对了但没高亮?** 检查 `:syntax` 是否开启,以及配色方案是否支持你用的语法组。
**改了 ftdetect 不生效?** 改完重启 Vim,或者对当前 buffer 执行 `:filetype detect` 强制重新检测。
**想临时禁用某个 ftplugin?** 在 vimrc 里设 `let g:did_load_filetypes = 1` 可以阻止系统 ftplugin 加载,但更精准的做法是针对单个类型设 `let b:did_ftplugin = 1` 阻止加载,然后自己写替代脚本。
Vim 的文件类型系统看起来层级多,但拆开看每层职责清晰:ftdetect 负责识别,syntax 负责着色,ftplugin 负责行为,indent 负责格式。把它们配齐了,编辑体验和现代 IDE 比也不差什么。
服务端5月27日 14:01
Vim 怎么比较两个文件的差异?vimdiff 怎么用?## 两个文件摆在一起,差异一目了然
日常开发中,比较两个文件的差异是高频操作。虽然 `diff` 命令能输出结果,但纯文本的比对输出读起来很费劲。Vim 自带的 diff 模式把两个文件并排展示,差异行高亮、相同行折叠,一眼就能看清哪里不一样。更关键的是,你可以在比对的同时直接编辑——这比任何只读的 diff 工具都高效。
## 启动 diff 模式的几种方式
最直接的启动方式是在终端输入:
```bash
vimdiff file1 file2
```
它和 `vim -d file1 file2` 完全等价。默认是左右竖屏分割,如果你想上下横屏显示,加个 `-o` 参数:
```bash
vimdiff -o file1 file2
```
也支持同时比较三个甚至四个文件,不过实际使用中两个文件的场景占绝大多数。
如果你已经在 Vim 里编辑了一个文件,临时想和另一个文件比对,不需要退出重来。在 Vim 的命令模式下输入:
```vim
:vertical diffsplit another_file
```
这会在右侧打开一个新窗口,自动进入 diff 模式。如果是上下分割,用 `:diffsplit` 不加 `vertical` 即可。
还有一种情况:你已经在用 Vim 编辑了,但当前窗口不是 diff 模式,可以手动开启:
```vim
:diffthis
```
在两个窗口分别执行一次,diff 高亮和折叠就会生效。
## 看懂 diff 界面的颜色编码
进入 diff 模式后,Vim 用不同的背景色来标记差异:
- **蓝色/青色背景**:只在这个文件中存在的行
- **绿色背景**:另一侧文件有这行,但当前文件对应位置是空的
- **紫色/洋红背景**:两侧都有这行,但内容不同
- **红色背景**:差异行中具体不同的文字(在紫色行的内部进一步高亮)
同时,两侧连续相同的行会被折叠成一行显示,默认展开差异处上下各 6 行上下文。如果你想调整上下文行数:
```vim
:set diffopt=context:3
```
展开折叠用 `zo`,重新折叠用 `zc`。
## 在差异之间跳转
进入 diff 模式后最常用的操作就是在差异点之间快速跳转:
- `]c` — 跳到下一个差异
- `[c` — 跳到上一个差异
这两个快捷键省去了手动翻找的麻烦。配合窗口切换 `Ctrl-w w`(在左右窗口间轮换),可以快速检视所有差异。
## 用 dp 和 do 合并差异
这是 Vim diff 最实用的部分。`dp` 和 `do` 两个快捷键让你不用复制粘贴就能把差异从一侧搬到另一侧:
- **dp**(diff put):把当前光标处的差异推送到另一侧文件
- **do**(diff obtain):从另一侧文件拉取差异到当前文件
举个例子:左侧文件的某行写了 `version: "2.0"`,右侧对应行是 `version: "1.0"`。你把光标放在左侧那行,按 `dp`,右侧就会变成 `version: "2.0"`。反过来,如果光标在右侧,按 `do`,右侧同样会变成 `version: "2.0"`。
如果需要更精确的控制,可以用命令形式:
```vim
:diffput " 和 dp 一样
:diffget " 和 do 一样
```
命令形式还支持指定范围,比如只获取第 10 到 20 行的差异:
```vim
:10,20diffget
```
操作失误了?按 `u` 撤销就行,但要注意光标必须在被修改的那个窗口里。
## 滚动同步与 :diffupdate
默认情况下,两侧窗口的滚动是同步的(`scrollbind` 选项),你滚动一侧另一侧跟着动,方便逐行对照。如果你需要独立滚动某一边来查看上下文,可以临时关闭:
```vim
:set noscrollbind
```
看完再打开:
```vim
:set scrollbind
```
手动编辑文件后,diff 高亮可能不会立刻更新。这时候执行:
```vim
:diffupdate
```
Vim 会重新扫描两个文件,刷新差异标记。养成改完内容就 `:diffupdate` 的习惯,可以避免看着过时的高亮做出错误判断。
## 与 Git 集成:把 Vim 设为 difftool
Git 默认用命令行 `diff` 输出比对结果,但你可以配置成自动调用 vimdiff:
```bash
git config --global diff.tool vimdiff
git config --global difftool.prompt false
```
之后用 `git difftool` 代替 `git diff`,每次比较都会在 Vim 的 diff 模式中打开。`difftool.prompt false` 省去了每次确认的步骤。
处理合并冲突时更有用。配置 mergetool:
```bash
git config --global merge.tool vimdiff
```
执行 `git mergetool` 后,Vim 会打开一个四窗口布局:左侧是本地版本(LOCAL),中间是公共祖先(BASE),右侧是远程版本(REMOTE),底部是合并结果(MERGED)。在 MERGED 窗口中,你可以用 `:diffget` 选择接受哪一方的改动:
```vim
:diffget LOCAL " 采用本地版本
:diffget REMOTE " 采用远程版本
:diffget BASE " 采用祖先版本
```
更简短的写法是 `:diffg //2`(LOCAL)和 `:diffg //3`(REMOTE),这是 Git 内部的缓冲区编号。处理完所有冲突后 `:wqa` 保存退出,再 `git commit` 就完成了合并。
如果用的是 Neovim,配置方式相同,只是把工具名换成 `nvimdiff`:
```bash
git config --global diff.tool nvimdiff
git config --global merge.tool nvimdiff
```
## 几个实用技巧
比较目录时,可以先用 `vimdiff dir1/file dir2/file` 打开单个文件的比对。如果需要批量处理,`git difftool` 天然支持逐文件比对,每处理完一个文件 `:qa` 就会自动打开下一个。
临时想关闭 diff 高亮但保留分屏,执行 `:diffoff`。想重新开启,对两个窗口分别 `:diffthis`。
退出时用 `:qa` 退出所有窗口,`:wqa` 保存并退出所有窗口,`:qa!` 强制不保存退出。这些批量操作比逐个窗口 `:q` 方便得多。
---
Vim 的 diff 功能没有花哨的界面,但 `dp`/`do` 一键合并差异、`]c`/`[c` 快速跳转、与 Git 的无缝衔接,这些组合起来形成了非常高效的差异处理流程。熟练之后,你会发现很多场景下它比图形化的 diff 工具还顺手——毕竟,手指不用离开键盘,才是 Vim 的核心优势。
服务端5月27日 14:01
Vim 缓冲区怎么管理?:ls/:b/:bn/:bp 命令怎么用?## 你可能一直在误用 Vim 的多文件编辑
很多人打开多个文件时习惯开一堆 tab,或者反复 `:e` 切换,觉得 Vim 多文件编辑就是不如 VS Code 方便。其实问题不在 Vim,而在你没把 buffer 用起来。Buffer 才是 Vim 多文件编辑的核心机制,tab 和 window 只是展示方式。
## Buffer、Window、Tab 到底什么关系
这三个概念经常被混淆,理清它们是用好 buffer 的前提。
**Buffer** 是文件在内存中的副本。当你用 `:e config.yml` 打开一个文件,Vim 就创建了一个 buffer。即使你切换到别的文件,这个 buffer 依然存在,除非你主动删除它。一个 buffer 对应一个文件,但不一定显示在屏幕上。
**Window** 是 buffer 的视口。一个 window 显示一个 buffer,但同一个 buffer 可以同时在多个 window 中展示(比如用 `:sp` 水平分屏看同一个文件的不同位置)。Window 是"你看到的东西"。
**Tab** 是 window 的容器。一个 tab page 里可以放多个 window,类似于工作区布局。Tab 不包含文件,它包含的是 window 的排列方式。
简单说:buffer 是数据,window 是视图,tab 是布局。你真正需要管理的是 buffer,而不是 tab。
## 查看缓冲区列表::ls
`:ls` 是你最该记住的命令之一,它会列出当前所有 buffer:
```
:ls
1 #h "app.js" line 12
2 %a "config.yml" line 5
3 "utils.py" line 1
```
输出中每行前面的数字是 buffer 编号,后面紧跟状态标记:
- `%` — 当前 window 中显示的 buffer
- `#` — 交换缓冲区(alternate buffer),可以用 `Ctrl+^` 快速跳转
- `a` — 活跃缓冲区,光标所在
- `h` — 隐藏缓冲区(已加载但不在任何 window 中显示)
- `+` — 有未保存的修改
- `=` — 只读缓冲区
- `-` — 非活跃缓冲区,不可卸载
注意 `%` 和 `#` 的区别:`%` 是你正在编辑的,`#` 是你上一个编辑的。`Ctrl+^` 在两者间切换,比 `:bn` `:bp` 更快。
## 缓冲区切换的核心命令
### 按编号跳转::b
```
:b 2
```
直接跳到编号为 2 的 buffer。编号在 `:ls` 中可以看到。
更实用的方式是按文件名模糊匹配:
```
:b app
```
Vim 会自动匹配包含 "app" 的 buffer。如果匹配到多个,Vim 会提示你选择。用 `:b` 加 Tab 补全也很好用——输入 `:b con` 然后按 Tab,Vim 会补全为 "config.yml"。
### 前后循环切换::bn 和 :bp
- `:bn`(或 `:bnext`)— 跳到下一个 buffer
- `:bp`(或 `:bprevious`)— 跳到上一个 buffer
这两个命令按 `:ls` 中的编号顺序循环切换。如果当前是最后一个 buffer,`:bn` 会跳回第一个。
### 首尾跳转
- `:bf`(或 `:bfirst`)— 跳到第一个 buffer
- `:bl`(或 `:blast`)— 跳到最后一个 buffer
实际使用中 `:bf` 和 `:bl` 用得不多,`Ctrl+^` 在两个 buffer 间来回切换才是最高频的操作。
## 删除缓冲区::bd
```
:bd " 删除当前 buffer
:bd 3 " 删除编号为 3 的 buffer
:bd 1 3 5 " 删除多个 buffer
:1,5bd " 删除编号 1 到 5 的 buffer
```
注意 `:bd` 和 `:q` 的区别:`:q` 关闭当前 window,但 buffer 仍在列表里;`:bd` 是真正把 buffer 从列表中移除。如果你只是不想看到某个文件了,用 `:bd`;如果你只是想调整窗口布局,用 `:q`。
另外 `:bd` 删除 buffer 时,如果文件有未保存的修改,Vim 会拒绝执行并提示你先保存或放弃。加 `!` 强制删除(`:bd!`)会丢弃修改,慎用。
## 隐藏缓冲区:set hidden
这是 buffer 管理中最关键的一个设置。默认情况下,如果你修改了当前 buffer 但没保存,Vim 不允许你切换到其他 buffer。这会逼你频繁 `:w`,体验很差。
在 `.vimrc` 中加上:
```vim
set hidden
```
开启后,Vim 允许你在有未保存修改的情况下切换 buffer,被切换走的 buffer 会变成隐藏状态(`:ls` 中标记为 `h`)。文件内容还在内存里,随时可以切回来继续编辑,`:w` 保存即可。
没有 `set hidden` 的话,buffer 管理基本没法用。
## 批量操作:bufdo
如果你想对所有 buffer 执行同一个操作,用 `:bufdo`:
```vim
:bufdo %s/old_func/new_func/ge | update
```
这会在所有 buffer 中执行替换,`g` 表示全局替换,`e` 表示没匹配到时不报错,`update` 等同于 `:w` 但只在有修改时才写入。
需要注意的是,`bufdo` 执行完后当前 buffer 会停在列表中最后一个 buffer 上。如果不确定操作结果,先用 `:ls` 确认,或者先在一个 buffer 上测试。
## 缓冲区列表的实际管理技巧
把几个命令组合起来用,比单独记每个命令更有价值。
**快速在两个文件间跳转**:用 `:e` 打开第二个文件后,`Ctrl+^` 就能在两个文件间来回切换,不需要记编号。
**按文件名而非编号切换**:编号是动态分配的,不靠谱。养成 `:b <部分文件名>` 的习惯,比如 `:b conf` 跳到 config 相关文件,比 `:b 5` 更不容易出错。
**清理不需要的 buffer**:编辑过程中 buffer 列表会越来越长,定期 `:ls` 看一眼,`:bd` 清理掉不再需要的。
**给常用命令做映射**:
```vim
nnoremap <leader>b :ls<CR>:b<Space>
nnoremap <leader>bn :bn<CR>
nnoremap <leader>bp :bp<CR>
nnoremap <leader>bd :bd<CR>
```
这样 `<leader>b` 会先列出 buffer 列表,然后等待你输入编号或文件名,比纯手打命令快很多。
**用插件增强体验**:如果你觉得原生命令不够直观,fzf.vim 的 `:Buffers` 命令提供模糊搜索界面,或者 mini.bufremove 提供更精细的删除控制。但建议先熟练原生命令再上插件,不然插件出问题时你连怎么手动操作都不知道。
## 一个典型的工作流程
假设你在做一个项目,需要同时编辑路由配置、控制器和视图:
1. `vim` 启动后 `:e routes.rb` 打开路由文件
2. `:e controllers/posts_controller.rb` 打开控制器
3. `:e views/posts/index.html.erb` 打开视图
4. `:ls` 查看当前 buffer 列表,确认三个文件都在
5. `:b cont` 跳到控制器编辑
6. `Ctrl+^` 在控制器和视图之间快速切换
7. 编辑完毕后 `:bd` 逐个关闭不再需要的 buffer
整个过程中你不需要开 tab,不需要分屏,三个文件通过 buffer 命令自由切换。当你习惯了这种方式,会发现比在 tab 栏里点来点去高效得多。服务端5月27日 14:01
Vim autocmd 自动命令怎么用?语法、事件和 augroup 怎么写?## 为什么你需要 autocmd
改了半天配置,打开项目发现缩进又不对——每次手动 `setlocal shiftwidth=4` 也不现实。Vim 的 autocmd 就是为了解决这类"在特定时机自动执行特定操作"的需求而存在的。当你编辑不同类型的文件、保存文件、甚至启动 Vim 的瞬间,都可以让 Vim 替你完成预设动作。
## autocmd 的基本语法
autocmd 的完整写法是:
```vim
autocmd [group] {event} {pattern} [nested] {command}
```
逐个看:
- **group**:可选,属于哪个命令组,后面会讲
- **event**:触发时机,比如 `BufRead`、`FileType`
- **pattern**:文件名匹配模式,比如 `*.py`、`*.json`,`*` 匹配所有文件
- **nested**:可选,允许自动命令嵌套触发(默认 autocmd 执行中不会再触发其他 autocmd)
- **command**:事件触发时要执行的 Vim 命令
一个最简单的例子:
```vim
autocmd BufRead *.md setlocal spell
```
这行配置的意思是:每当打开一个 `.md` 文件,自动开启拼写检查。
## 常用事件一览
Vim 内置了上百个事件,日常用得最多的是这几个:
### BufRead 和 BufReadPost
打开一个已存在的文件并读入缓冲区后触发。两者的区别在于 `BufRead` 在处理 modeline 之前触发,`BufReadPost` 在之后。绝大多数场景用 `BufReadPost` 就够了。
```vim
autocmd BufReadPost *.xml setlocal matchpairs+=<:>
```
### BufWritePre 和 BufWritePost
保存文件前和保存文件后触发。常见的用途是在保存前自动清理行尾空格:
```vim
autocmd BufWritePre * :%s/\s\+$//e
```
### FileType
当 `filetype` 选项被设置后触发。这是做语言专属配置最常用的事件,因为它是按文件类型匹配而非文件名:
```vim
autocmd FileType python setlocal shiftwidth=4 tabstop=4 expandtab
autocmd FileType javascript setlocal shiftwidth=2 tabstop=2
```
比起按文件扩展名匹配,`FileType` 事件更可靠——Vim 的 filetype 机制会综合考虑文件名、shebang、文件内容来判断类型。
### VimEnter
Vim 完成所有初始化工作后触发,适合做启动后的收尾操作:
```vim
autocmd VimEnter * if !argc() | NERDTree | endif
```
这行让 Vim 在没有打开文件参数时自动打开目录树。
### 其他值得关注的事件
| 事件 | 触发时机 |
|---|---|
| `BufNewFile` | 新建文件时 |
| `BufEnter` | 进入缓冲区时 |
| `InsertEnter` | 进入插入模式时 |
| `CursorHold` | 光标停留一段时间不动后 |
| `TextChangedI` | 插入模式下内容变化时 |
多个事件可以用逗号合写:`autocmd BufNewFile,BufRead *.vue setfiletype html`。
## augroup:给你的自动命令分组
直接写 autocmd 有个问题:如果你的 vimrc 被 source 了两次,同一组 autocmd 就会注册两遍。执行两遍 `setlocal` 倒无所谓,但执行两遍 `:%s` 就出事了。
解决办法是用 `augroup` 把命令分组,并在组内用 `autocmd!` 清除旧定义:
```vim
augroup python_settings
autocmd!
autocmd FileType python setlocal shiftwidth=4 tabstop=4 expandtab
autocmd FileType python setlocal colorcolumn=80
augroup END
```
`autocmd!` 放在组内第一行,意思是先清除 `python_settings` 组里之前注册的所有自动命令,再重新注册。这样不管 vimrc 被 source 多少次,每个 autocmd 都只会存在一份。
一个更完整的 vimrc 结构可能是这样的:
```vim
augroup my_autocmds
autocmd!
" Python 用 4 空格
autocmd FileType python setlocal shiftwidth=4 tabstop=4 expandtab
" 前端用 2 空格
autocmd FileType javascript,typescript,html,css setlocal shiftwidth=2 tabstop=2 expandtab
" 保存时去掉行尾空格
autocmd BufWritePre * :%s/\s\+$//e
" 退出快速修复窗口按回车直接关闭
autocmd FileType qf nnoremap <buffer> <CR> <CR>:cclose<CR>
augroup END
```
## 几个实用配置示例
### 保存配置文件后自动生效
```vim
augroup vimrc_reload
autocmd!
autocmd BufWritePost init.vim source $MYVIMRC
autocmd BufWritePost .vimrc source $MYVIMRC
augroup END
```
### 恢复上次编辑位置
Vim 默认不记住你上次光标停在哪,但 autocmd 可以做到:
```vim
augroup restore_cursor
autocmd!
autocmd BufReadPost *
\ if line("'"") >= 1 && line("'"") <= line("$") |
\ exe "normal! g`"" |
\ endif
augroup END
```
### 对特定目录的文件设为只读
```vim
autocmd BufRead /var/log/* setlocal readonly
```
pattern 支持绝对路径匹配,这在管理服务器日志时很实用。
## 调试 autocmd
如果 autocmd 没按预期工作,几个排查手段:
- `:verbose autocmd BufWritePre *` — 查看某个事件上注册了哪些命令,以及定义位置
- `:autocmd` — 列出所有已注册的自动命令
- 设置 `set verbose=9` 后操作,Vim 会在执行 autocmd 时打印详细信息
另外注意 autocmd 默认不会嵌套触发:一个 autocmd 执行中引发的事件不会再触发其他 autocmd。如果确实需要嵌套,加 `nested` 关键字:
```vim
autocmd FileChangedShell * nested edit
```
## 写在最后
autocmd 是 Vim 自动化的核心机制。掌握了事件、pattern 和 augroup 的组合方式,你就能让 Vim 在各种场景下自动做正确的事——不用记、不用想、不用每次手动设置。刚开始只需要记住 `FileType` + `augroup` + `autocmd!` 这个基本模式,剩下的在实际需求中慢慢积累就行。
服务端5月27日 14:00
Vim 自动补全怎么用?内置命令和 coc.nvim 怎么配置?## Vim 的补全比你想象的好用
很多从 VS Code 转过来的开发者打开 Vim 的第一反应是:补全呢?输入一个函数名的前几个字母,什么都不会发生。其实 Vim 不是没有补全,只是它默认不自动弹出,需要你主动触发。理解这一点,是从"Vim 补全不好用"到"原来可以这样"的关键转折。
## 基本关键字补全:Ctrl+N 和 Ctrl+P
在插入模式下,输入几个字符后按 `Ctrl+N`,Vim 会在当前缓冲区、其他打开的缓冲区、以及标签文件中搜索匹配的关键字,弹出一个补全菜单。`Ctrl+P` 的作用相同,只是列表方向相反——从下往上选。
这两个快捷键是 Vim 补全的起点,也是日常编码中使用频率最高的。补全菜单弹出后,继续按 `Ctrl+N`/`Ctrl+P` 可以在候选项之间上下移动,按回车确认选择。
一个容易忽略的细节:`complete` 选项决定了 `Ctrl+N`/`Ctrl+P` 的搜索范围。默认值是 `.,w,b,u,t,i`,分别代表当前缓冲区、当前窗口的其他缓冲区、卸载的缓冲区、标签、以及包含的文件。你可以通过 `:set complete?` 查看当前设置,也可以根据需要调整。
## Ctrl+X 系列:针对性补全
`Ctrl+N`/`Ctrl+P` 是通用关键字补全,而 `Ctrl+X` 开启的是一个补全模式前缀,后面跟不同的按键触发不同类型的补全。这套组合是 Vim 内置补全的精华所在:
- `Ctrl+X Ctrl+L` — 整行补全。匹配当前文件中已有的完整行,在写重复性代码(比如结构相似的配置项)时特别省事。
- `Ctrl+X Ctrl+N` / `Ctrl+X Ctrl+P` — 当前文件的关键字补全,和前面的 Ctrl+N/Ctrl+P 类似,但限定了搜索范围。
- `Ctrl+X Ctrl+I` — 包含文件的关键字补全。搜索 `#include` 或 `import` 引入的文件中的关键字,写 C 或 Python 时很实用。
- `Ctrl+X Ctrl+]` — 标签补全。需要先用 ctags 生成 tags 文件,然后可以补全函数名、结构体成员等。
- `Ctrl+X Ctrl+F` — 文件名补全。输入路径时按这个组合,Vim 会列出当前目录下的文件和文件夹,写 `import` 或 `require` 语句时效率翻倍。
- `Ctrl+X Ctrl+K` — 字典补全。从 `dictionary` 选项指定的字典文件中匹配单词,主要用在写英文文档或注释时。
- `Ctrl+X Ctrl+V` — Vim 命令行补全,日常编码用得少,写 vimrc 时倒是可以派上用场。
在补全菜单弹出后,`Ctrl+N` 和 `Ctrl+P` 依然可以用来在候选项之间导航。按 `Ctrl+E` 可以取消补全回到原始输入,按 `Ctrl+Y` 则确认当前选中项。
## 全能补全(Omni Completion):Ctrl+X Ctrl+O
如果说前面那些补全方式是"按字面匹配",那 Omni 补全就是"按语义匹配"。它由 `omnifunc` 选项指定的函数驱动,能够理解代码结构,给出上下文相关的补全建议。
使用前需要确保 `.vimrc` 中有这两行:
```vim
filetype plugin on
```
然后在插入模式下按 `Ctrl+X Ctrl+O`,Vim 会调用当前文件类型对应的补全函数。比如在 HTML 文件中输入 `<p cl` 后触发 Omni 补全,会出现 `class=` 这样的属性建议;在 C 文件中输入结构体变量后加 `.` 或 `->`,会列出结构体成员。
Vim 自带了几种语言的 Omni 补全脚本(位于 `$VIMRUNTIME/autoload/` 目录下),覆盖 C、HTML/CSS、JavaScript、PHP、Python、Ruby、SQL、XML 等语言。如果当前文件类型没有对应的 omnifunc,可以在 vimrc 中设置一个兜底方案:
```vim
autocmd FileType *
\ if &omnifunc == '' |
\ setlocal omnifunc=syntaxcomplete#Complete |
\ endif
```
这样即使没有专门的补全脚本,也能基于语法高亮信息提供基本的补全。
Omni 补全的局限也很明显:它依赖 Vim 脚本实现,对语言的语义理解深度有限,不能做跨文件的类型推断,也不支持复杂的代码分析。这也是第三方补全插件出现的原因。
## coc.nvim:把 LSP 补全带入 Vim
coc.nvim 是目前 Vim/Neovim 生态中最主流的补全插件。它的核心思路是利用 Language Server Protocol(LSP),让 Vim 获得和 VS Code 一样的补全能力——包括类型推断、跨文件跳转、函数签名提示等。
### 安装 coc.nvim
前提条件:Vim 8.0+ 或 Neovim 0.4.4+,以及 Node.js 14+。
以 vim-plug 为例,在 `.vimrc` 中添加:
```vim
call plug#begin('~/.vim/plugged')
Plug 'neoclide/coc.nvim', {'branch': 'release'}
call plug#end()
```
然后在 Vim 中执行 `:PlugInstall`。
### 安装语言服务扩展
coc.nvim 本身不包含语言支持,需要安装对应语言的扩展,方式和 VS Code 类似:
```vim
:CocInstall coc-tsserver " JavaScript/TypeScript
:CocInstall coc-pyright " Python
:CocInstall coc-clangd " C/C++
:CocInstall coc-json " JSON
:CocInstall coc-html " HTML
:CocInstall coc-css " CSS
```
安装完成后重新打开文件,输入代码时补全菜单会自动弹出。
### 常用配置
在 `~/.vim/coc-settings.json`(Neovim 用户是 `~/.config/nvim/coc-settings.json`)中可以调整补全行为:
```json
{
"suggest.autoTrigger": "always",
"suggest.maxCompleteItemCount": 15,
"suggest.noselect": false,
"suggest.enablePreselect": true
}
```
其中 `suggest.autoTrigger` 设为 `"always"` 后,输入任何字符都会触发补全;设为 `"trigger"` 则只在特定触发字符后弹出。建议初学者先用 `"always"`,习惯后再根据偏好调整。
按键映射方面,推荐在 `.vimrc` 中加入 Tab 补全和回车确认:
```vim
" Tab 触发/切换补全
inoremap <silent><expr> <TAB>
\ pumvisible() ? "\<C-n>" :
\ <SID>check_back_space() ? "\<TAB>" :
\ coc#refresh()
inoremap <expr><S-TAB> pumvisible() ? "\<C-p>" : "\<C-h>"
function! s:check_back_space() abort
let col = col('.') - 1
return !col || getline('.')[col - 1] =~# '\s'
endfunction
" 回车确认补全
inoremap <silent><expr> <CR> coc#pum#visible() ? coc#pum#confirm()
\: "\<C-g>u\<CR>\<c-r>=coc#on_enter()\<CR>"
```
这样 Tab 键在补全菜单可见时切换候选项,不可见时正常插入缩进;回车键在有选中项时确认补全,否则正常换行。
## YouCompleteMe 和其他方案
YouCompleteMe(YCM)曾是 Vim 补全领域最有名的插件,基于 clang 和 jedi 提供语义补全,补全速度快、准确度高。但它的安装流程出了名地麻烦——需要编译 C++ 核心,加上各种语言的依赖,完整安装可能拉下来 1GB 以上的文件。配置也不直观,`.ycm_extra_conf.py` 文件的编写劝退了不少人。
在 LSP 生态成熟之后,YCM 的优势逐渐被 coc.nvim 和 Neovim 原生 LSP 客户端(如 nvim-lspconfig + nvim-cmp)替代。如果你是新用户,建议直接从 coc.nvim 起步。如果已经在用 YCM 且没有问题,也没有强求迁移的必要。
对于 Neovim 用户,nvim-cmp 是另一个值得关注的补全框架。它不绑定特定 LSP 客户端,支持多种补全源(LSP、buffer、路径、snippet 等),配合 nvim-lspconfig 使用。配置比 coc.nvim 稍复杂,但灵活度更高,适合喜欢折腾 Lua 配置的人。
## 让内置补全更好用的几项配置
即使装了 coc.nvim,Vim 内置补全依然有它的价值——在编辑配置文件、写 Markdown、或者打开一个不想装插件的服务器环境时,原生补全依然是最快的工具。以下是几个实用的配置建议:
**设置字典补全的词库路径:**
```vim
set dictionary+=/usr/share/dict/words
```
**扩大 Ctrl+N/Ctrl+P 的搜索范围:**
```vim
set complete+=k " 加入字典搜索
set complete+=t " 加入标签搜索
```
**忽略大小写匹配:**
```vim
set ignorecase
set infercase " 补全时根据已输入部分自动调整大小写
```
**补全菜单的显示优化:**
```vim
set completeopt=menuone,noinsert,noselect
```
`menuone` 确保即使只有一个匹配项也弹出菜单,`noinsert` 不自动插入文本,`noselect` 不自动选中第一项,把选择权留给你。
---
Vim 的补全体系是一条从简单到复杂的渐进路径。从最基础的 `Ctrl+N` 开始,到 `Ctrl+X` 系列的针对性补全,再到 Omni 补全的语义理解,最后到 coc.nvim 的完整 LSP 支持——每一层都能解决一部分问题,每一层也都有适用的场景。不需要一步到位装上所有插件,先把 `Ctrl+N` 和 `Ctrl+X Ctrl+F` 用起来,你会发现内置补全已经覆盖了日常编码的相当一部分需求。服务端5月27日 14:00
Vim .vimrc 常用配置有哪些?怎么设置?## 为什么你的 .vimrc 值得认真对待
很多人装完 Vim 就直接开干,全程默认配置硬扛。用了一阵子发现:中文乱码、缩进混乱、搜索结果看不清、反复敲着冗长的命令——这些都是没配置 .vimrc 的后果。.vimrc 是 Vim 的灵魂配置文件,几乎所有的使用体验都由它决定。花半小时配好它,之后每天都能省下大量重复操作的时间。
## .vimrc 文件在哪
不同系统的位置略有区别:
- Linux / macOS:`~/.vimrc`
- Windows:`%USERPROFILE%/_vimrc`
- Neovim 用户:`~/.config/nvim/init.vim`
如果文件不存在,直接创建即可。Vim 启动时会自动读取这个文件。
## 基础设置:让 Vim 不再难用
先把最基本的问题解决掉——编码、缩进、行号这些,不设置的话日常编辑会非常难受。
```vim
" 编码设置,解决中文乱码
set encoding=utf-8
set fileencoding=utf-8
set fileencodings=utf-8,gbk,gb2312,cp936,ucs-bom,latin1
set fileformats=unix,dos,mac
" 缩进和 Tab
set tabstop=4 " Tab 键显示宽度
set softtabstop=4 " 按下 Tab 键时插入的空格数
set shiftwidth=4 " 自动缩进宽度
set expandtab " 将 Tab 转为空格
set autoindent " 继承上一行缩进
set smartindent " 智能缩进(C 语言风格自动调整)
set shiftround " 缩进取整到 shiftwidth 的倍数
" 行号和光标
set number " 显示绝对行号
set relativenumber " 显示相对行号(配合 number 使用更高效)
set cursorline " 高亮当前行
set ruler " 右下角显示光标位置
" 兼容性
set nocompatible " 不兼容 vi,启用 Vim 增强特性
```
这里有个细节:`number` 和 `relativenumber` 同时开启时,当前行显示绝对行号,其余行显示相对行号。这对用 `3j`、`5k` 快速移动特别方便,因为你一眼就能看到距离。
## 搜索配置:找东西又快又准
默认的搜索功能比较原始,加上几个设置就顺手很多:
```vim
set ignorecase " 搜索忽略大小写
set smartcase " 如果搜索包含大写字母则区分大小写
set hlsearch " 高亮所有搜索结果
set incsearch " 输入时实时跳转匹配
```
`ignorecase` + `smartcase` 这对组合很实用:输入 `foo` 会匹配 `Foo`、`FOO`,但输入 `Foo` 就只匹配 `Foo`。既灵活又不会误匹配。
搜索结果高亮有时候很烦人,加一个快捷键随时清除:
```vim
nnoremap <Esc><Esc> :nohlsearch<CR>
```
连按两次 Esc 就取消高亮,不用再输入 `:noh`。
## 键映射:把重复操作变成一个按键
Vim 的键映射是提升效率的核心。这里给一些实际常用的映射:
```vim
" Leader 键设为空格(比默认的 \ 好按很多)
let mapleader = " "
" 快速保存和退出
nnoremap <leader>w :w<CR>
nnoremap <leader>q :q<CR>
nnoremap <leader>x :x<CR>
" 窗口导航(不用按 Ctrl+W)
nnoremap <leader>h <C-W>h
nnoremap <leader>j <C-W>j
nnoremap <leader>k <C-W>k
nnoremap <leader>l <C-W>l
" 缓冲区切换
nnoremap <leader>n :bnext<CR>
nnoremap <leader>p :bprevious<CR>
nnoremap <leader>d :bdelete<CR>
" 行内快速移动
nnoremap H ^
nnoremap L $
" 选中全文
nnoremap <C-a> ggVG
" 系统剪贴板复制粘贴
vnoremap <C-c> "+y
nnoremap <C-v> "+p
```
关于 Leader 键,空格键是大多数人的选择,因为它是键盘上最大的键,左手拇指随时能碰到,而且它在 Normal 模式下没有任何默认功能。
有一点要注意:映射时尽量用 `nnoremap` 而不是 `nmap`。`nnoremap` 不递归展开,避免映射冲突导致的奇怪行为。这是 Vim 配置中最常见的坑之一。
## 自动命令:让 Vim 自动干活
自动命令(autocmd)可以在特定事件触发时自动执行操作,比如保存时自动去掉行尾空格、打开文件时跳到上次编辑的位置:
```vim
" 保存时自动去除行尾空格
autocmd BufWritePre * :%s/\s\+$//e
" 打开文件时恢复上次光标位置
autocmd BufReadPost *
\ if line("'"") >= 1 && line("'"") <= line("$") |
\ exe "normal! g`"" |
\ endif
" 针对特定文件类型的缩进设置
autocmd FileType python setlocal tabstop=4 shiftwidth=4 expandtab
autocmd FileType javascript,json,html,css setlocal tabstop=2 shiftwidth=2 expandtab
autocmd FileType go setlocal tabstop=4 shiftwidth=4 noexpandtab
" 新建文件时自动插入模板
autocmd BufNewFile *.sh 0r ~/.vim/templates/shell.tpl
```
建议把自动命令放在 augroup 里,防止重复加载:
```vim
augroup MyAutoCmds
autocmd!
autocmd BufWritePre * :%s/\s\+$//e
augroup END
```
`autocmd!` 清除同组已有的自动命令,避免每次 source .vimrc 时重复注册。
## 插件配置入口:从手动管理到插件管理器
手动拷贝插件到 `~/.vim/plugin/` 的时代已经过去了。现在主流的插件管理器有 vim-plug 和 Vundle,推荐 vim-plug,因为更轻量且支持懒加载。
```vim
" 安装 vim-plug:
" curl -fLo ~/.vim/autoload/plug.vim --create-dirs " https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim
call plug#begin('~/.vim/plugged')
" 文件搜索
Plug 'junegunn/fzf', { 'do': { -> fzf#install() } }
Plug 'junegunn/fzf.vim'
" 状态栏
Plug 'vim-airline/vim-airline'
" 语法检查
Plug 'dense-analysis/ale'
" 自动补全
Plug 'neoclide/coc.nvim', {'branch': 'release'}
" Git 集成
Plug 'tpope/vim-fugitive'
call plug#end()
```
安装完插件后,在 Vim 里执行 `:PlugInstall` 就能一键安装所有声明的插件。
插件虽好,但别贪多。每加一个插件都要问自己:这个功能我真的需要吗?插件越多,启动越慢,出问题排查也越困难。
## 性能优化:让 Vim 飞起来
配置多了以后启动变慢是常有的事,几个设置可以缓解:
```vim
" 禁用交换文件和备份(用了 Git 就不太需要这些)
set noswapfile
set nobackup
set nowritebackup
" 撤销持久化,关闭文件后还能撤销
set undofile
set undodir=~/.vim/undo
" 延迟更新(减少重绘次数)
set lazyredraw
" 折叠设置
set foldmethod=marker " 使用 marker 折叠,比 syntax 快
set foldlevelstart=99 " 打开文件时不自动折叠
" 正则引擎(大文件时提速)
set regexpengine=1 " 使用老引擎,某些情况下更快
```
如果启动速度已经让你难以忍受,可以测量一下:
```vim
" 启动时测量各脚本加载时间
" vim --startuptime ~/.vimstart.log
" 然后查看 ~/.vimstart.log 找到慢的插件
```
用 `vim --startuptime vim.log` 打开 Vim,退出后检查 `vim.log`,能精确看到每个插件和脚本的加载耗时,谁慢一目了然。
## 一个可用的完整 .vimrc 模板
把上面的内容整合一下,这是一个开箱即用的配置:
```vim
" ========== 基础设置 ==========
set nocompatible
set encoding=utf-8
set fileencoding=utf-8
set fileencodings=utf-8,gbk,gb2312,cp936,ucs-bom,latin1
" ========== 编辑行为 ==========
set tabstop=4
set softtabstop=4
set shiftwidth=4
set expandtab
set autoindent
set smartindent
set shiftround
" ========== 显示设置 ==========
set number
set relativenumber
set cursorline
set ruler
set showcmd
set wildmenu
set laststatus=2
set scrolloff=5
set sidescrolloff=10
" ========== 搜索设置 ==========
set ignorecase
set smartcase
set hlsearch
set incsearch
nnoremap <Esc><Esc> :nohlsearch<CR>
" ========== 键映射 ==========
let mapleader = " "
nnoremap <leader>w :w<CR>
nnoremap <leader>q :q<CR>
nnoremap <leader>h <C-W>h
nnoremap <leader>j <C-W>j
nnoremap <leader>k <C-W>k
nnoremap <leader>l <C-W>l
nnoremap <leader>n :bnext<CR>
nnoremap <leader>p :bprevious<CR>
nnoremap H ^
nnoremap L $
" ========== 文件与性能 ==========
set noswapfile
set nobackup
set nowritebackup
set undofile
set undodir=~/.vim/undo
set lazyredraw
set hidden
set autoread
" ========== 自动命令 ==========
augroup MyAutoCmds
autocmd!
autocmd BufWritePre * :%s/\s\+$//e
autocmd BufReadPost *
\ if line("'"") >= 1 && line("'"") <= line("$") |
\ exe "normal! g`"" |
\ endif
autocmd FileType python setlocal tabstop=4 shiftwidth=4 expandtab
autocmd FileType javascript,json,html,css setlocal tabstop=2 shiftwidth=2 expandtab
augroup END
" ========== 插件 ==========
call plug#begin('~/.vim/plugged')
Plug 'junegunn/fzf', { 'do': { -> fzf#install() } }
Plug 'junegunn/fzf.vim'
Plug 'vim-airline/vim-airline'
Plug 'dense-analysis/ale'
call plug#end()
" ========== 语法和文件类型 ==========
syntax on
filetype plugin indent on
```
## 配置是迭代出来的
不要想着一次配好所有东西。最佳实践是:先用最简配置开始,遇到不顺手的地方再加设置。过一段时间回头看,你会发现 .vimrc 就是你的编辑习惯的缩影——每个配置项背后都是一次"这操作太烦了"的真实体验。定期清理不用的映射和插件,保持配置文件的精简,比堆砌功能更重要。