把 GVIM 装好,双击打开一个.v文件,光标停在第一行,你想删掉一行,按了 Delete,屏幕没动静;想退出,按下 Esc 敲:q,底部弹出一串提示。这个场景我在团队里见过太多次了,尤其从其他编辑器转过来做 RTL 的同事,前三天基本都在跟“为什么按键不生效”较劲。GVIM 这东西的尴尬之处在于:它的门槛不在功能多少,而在你必须先接受一套完全不同的交互逻辑,然后才谈得上效率。
这篇东西我按“边学边用”的思路来写,不打算给你一张命令清单让你背。清单背完就忘,真正留下来的是那些嵌进你日常工作流里的动作。内容会覆盖 GVIM 官网下载之后的初始配置、_vimrc的写法、模式与动作的组合逻辑、verilog-mode在 Vim 语境下到底指什么(这个词被搜得很乱)、例化与端口对齐这类 RTL 日常操作、ctags 加 quickfix 的工程导航、中文与换行符这几个经典坑的排查链路,最后聊聊怎么在赶项目的状态下持续学而不崩盘。适合刚转 GVIM 的硬件工程师、写脚本的软件同学,以及任何想给自己的编辑器做减法的人。
1. 先把 GVIM 的定位摆正:它是 Vim 外面套了一层 GUI
很多人第一次搜 “gvim 官网”,其实心里想的是“我要不要装一个和 Vim 不一样的东西”。这里先把关系理清楚:GVIM 就是 Vim 的图形界面版本,编译时开启了 GUI 支持,核心引擎、命令集、配置语言完全一致。你在 GVIM 里敲的每一条 Ex 命令,在终端里跑vim一样能用;反过来,你在终端里积累的.vimrc拷到_vimrc里也照样生效。它们不是两个软件,是同一个软件的两个入口。
1.1 GUI 外壳带来的实际差异
差异看着小,实际用起来分歧点很集中。鼠标、菜单栏、系统剪贴板、字体渲染、多窗口形态这几块是 GVIM 的优势区。我在 Windows 上写 Verilog 的那几年,几乎全部时间都用 GVIM 而不是终端 Vim,原因很实在:终端里的字体渲染和中文显示在旧版本上问题太多,而 GVIM 直接调系统字体,换个 JetBrains Mono 或者 Consolas 就完事,眼睛舒服很多。
另一个容易被忽略的点是剪贴板。终端 Vim 想和系统剪贴板互通,得看编译选项里有没有+clipboard,很多精简版是没有的;GVIM 基本都带,配一行set clipboard=unnamed就能让你平时用的y、p直接对接系统剪贴板,和外部工具之间来回贴代码非常顺。
代价也有。GVIM 启动比终端 Vim 慢一点,远程登录服务器的时候没法用(除非用 X 转发),而且鼠标一开容易让人退回到“点菜单”的习惯里,反而拖慢学习进度。我现在配置里会刻意保留鼠标,但把菜单栏和工具栏关掉,逼自己用命令。
1.2 什么时候我会建议直接用终端 Vim
判断标准很简单:如果你的代码全在本地、用 GUI 工具链做综合和仿真、需要经常和文档、波形工具来回切,那 GVIM 更顺手。如果你的编译、仿真、回归全在远端服务器上,本地只是敲代码,那你迟早会切到终端 Vim 加 tmux,因为让远端环境复用本地配置这件事,终端方案要干净得多。
| 场景 | 推荐入口 | 主要原因 |
|---|---|---|
| 本地 RTL 开发,IDE 混用 | GVIM | 字体、剪贴板、鼠标、多窗口体验好 |
| 远端服务器编译仿真 | 终端 Vim | 无需图形转发,配合 tmux 会话稳定 |
| 只想练基本功 | 终端 Vim | 干扰少,被迫用命令 |
| 需要频繁贴代码给外部工具 | GVIM | 剪贴板互通配置成本低 |
我自己的做法是两套都在用,但配置文件是同一份,通过软链接或者直接在_vimrc里判断平台差异。这样迁移的时候只维护一处,不会出现“公司电脑会用的命令,家里电脑不认”。
2. 从 gvim 官网拿到安装包之后的头半小时
下载安装部分没什么可讲的,Windows 上一般找自解压的安装程序,Linux 上多数发行版仓库里搜vim-gtk3之类的包名就能装到带 GUI 的版本。真正值得花时间的是装完之后的那半小时,因为这段时间里的几个决定,会直接影响你后面几个月的手感。
2.1 Windows 上的 _vimrc 和 _gvimrc 到底放哪
Windows 版本和 Linux 的一个明显区别是配置文件名前面带下划线。用户级配置通常放在%USERPROFILE%\_vimrc,GUI 专属的配置放在%USERPROFILE%\_gvimrc。加载顺序是_vimrc先,_gvimrc后,所以字体、窗口尺寸这类只在图形界面里才成立的东西,我建议全部丢进_gvimrc,这样同一份配置拿到终端里跑也不会因为不认识guifont而报错。
不想记路径也有偷懒办法:在 GVIM 里敲:echo $MYVIMRC,它会直接告诉你当前正在用的是哪个文件。如果这个变量是空的,说明你还没创建过配置文件,敲:e $MYVIMRC新建一个保存就行。还有个更快的入口是:edit $MYVIMRC,我把它映射成了<leader>ev,改配置这件事顺手了,你才会真的去改。
2.2 字体、编码、剪贴板:新机必改的三项
字体只影响观感,但观感影响你愿不愿意长期待在这个编辑器里。Windows 上写set guifont=Consolas:h11,字体名里有空格要用下划线转义。写代码我推荐等宽字体,字符区分度高的那种,0和O、1和l分不清的字体在 Verilog 里是灾难。
编码这块是重灾区。默认配置在纯英文环境没问题,一旦文件里有中文注释,或者同事用 GBK 存的文件流到你手里,就开始了。我现在的写法是这样的:
" 编码三件套 set encoding=utf-8 set fileencodings=ucs-bom,utf-8,gb18030,gbk,latin1 set fileformat=unixencoding决定 Vim 内部怎么表示字符,这个必须是 utf-8,改它要重启才生效。fileencodings是读文件时的探测顺序,Vim 会从前往后试,第一个能成功解码的就用哪个。注意这个顺序不能乱放,gbk放在utf-8前面会导致部分 UTF-8 文件被误判成 GBK 然后显示乱码——GBK 的字节范围太宽松,很多合法 UTF-8 序列在它眼里也“合法”。
剪贴板的配置我一般写set clipboard=unnamed,在 Windows 上就是让匿名寄存器直接指向系统剪贴板。Linux 上如果编译带+clipboard,效果类似,但要注意unnamed和unnamedplus的区别:前者对*寄存器,后者对+寄存器,桌面环境里通常选unnamedplus更符合直觉。
3. 模式与动作:把“我在哪个模式”变成肌肉记忆
GVIM 最难跨的坎不是命令多,而是模式切换这件事反直觉。你在别的编辑器里养成的习惯是“选中再操作”,在 Vim 里是“先说要干什么,再说要作用在哪”。这个顺序反过来,是所有动作组合能成立的基础。
3.1 正常模式下的移动,从抛弃方向键开始
我建议第一周就做一件有点痛苦的事:把方向键彻底不用。原因是方向键逼着你的手在键盘主区和右下角之间来回跑,而hjkl就在主键位上。更重要的是,用方向键的时候你没法配合数字前缀,比如5j往下走五行,3w往后走三个词,这种“带计数的移动”是后面所有高效操作的基石。
比字符级移动更值钱的是几类大跨度移动。w、b、e按词跳,0、^、$按行跳,gg、G到文件首尾,{、}按空行分块跳。在 Verilog 里按空行跳特别爽,因为我们写代码一般会在always块、模块声明之间留空行,}一下就能跳过一个逻辑段。还有%在括号之间跳,写例化端口列表的时候非常有用。
搜索是最省力的移动方式。看到某个信号名,光标放上去按*,直接跳到下一个同名信号,#是往上找。搜索完想取消高亮敲:noh,我嫌麻烦就映射成了<leader>h。这个动作我在读别人写的 RTL 时用得最多,比滚动轮快一个量级。
3.2 操作符加文本对象:d、c、y 是动词,不是快捷键
理解这一层,GVIM 的学习曲线会突然变平滑。d、c、y不是三个“删除、修改、复制”的快捷键,它们是动词,后面要接一个动作或者一个对象。dw是删除到下一个词,d$是删除到行尾,di{是删除花括号内部的内容,ci"是替换双引号里面的内容。
文本对象是这套语法里最优雅的部分,i表示 inside,a表示 around。di(删掉括号里所有内容,da(连括号一起删;在 Verilog 里di{处理begin...end是不行的(因为end不是括号),但处理拼接{}、端口列表()、数组下标[]都是直接可用的。
| 组合 | 含义 | Verilog 里的典型用途 |
|---|---|---|
ciw | 替换当前词 | 改信号名 |
di( | 删除括号内内容 | 清空端口列表 |
yi{ | 复制花括号内内容 | 复制拼接表达式 |
ca[ | 连同方括号一起改 | 改位宽声明 |
>i{ | 缩进花括号内容 | 整理代码块 |
我个人的经验是只记十几个组合就够用了,剩下的靠用的时候现查。硬背一百个组合的效果,远不如把ciw、di(、yi{这三个练到不用思考。
3.3 插入模式里的两个逃生出口和一个原地返回
插入模式里最值得记的是Ctrl-o,它让你临时执行一条正常模式命令再回到插入模式。比如你在写一行代码,想跳到行尾补个分号,不用退出来再进去,Ctrl-o加$加a就完事了。另一个是Ctrl-r加寄存器名,可以插入某个寄存器内容,Ctrl-r "就是插入上次复制的内容,写例化的时候反复贴同一段很有用。
还有个位置记忆的技巧:gi直接跳回你上次离开插入模式的位置并进入插入模式。改了别处的代码想回到刚才写的地方,这个命令比翻 jumplist 还快。
4. .vimrc 不是收藏夹,按层拆开维护
我见过太多人的配置文件是从网上抄了三百行,注释全是英文,自己也不知道哪行是干嘛的。这种配置一开始很爽,出问题的时候就是噩梦。更麻烦的是,赶项目的时候配置崩了,你连回退到哪个版本都不知道。
4.1 目录与加载顺序
给配置定一个能长期活下去的结构,比多写几个映射重要得多。我现在的分法大致是这样:主配置文件只放平台无关的设置,平台相关的差异用条件判断放在文件末尾;插件配置单独放进plugin-config目录,通过runtime命令加载;键位映射单独一个文件,因为这块改得最勤。
" $MYVIMRC 结构示意 " 1. 基础行为 set nocompatible set backspace=indent,eol,start set hidden set updatetime=300 " 2. 显示 set number relativenumber set showcmd set laststatus=2 set list listchars=tab:>-,trail:- " 3. 搜索 set ignorecase smartcase set incsearch hlsearch " 4. 缩进(Verilog 团队常用 2 或 4 空格) set expandtab set shiftwidth=2 set softtabstop=2 set autoindent " 5. 持久化 set undofile set undodir=~/.vim/undo// set directory=~/.vim/swap//set hidden这行看着不起眼,但它决定了你切换缓冲区时需不需要先保存。写代码的时候经常要在模块头部和末尾之间来回切,每次都被拦住保存一次是很烦的。updatetime=300影响的是光标停多久触发某些自动行为,默认 4000 毫秒太长了,改成 300 后体验会好很多。
undofile加undodir是我强烈建议开启的一项。它把撤销历史写到磁盘上,意味着你关掉 GVIM 明天再打开同一个文件,还能一路u回去。我有一次误删了一个always块,靠这个功能救了回来,因为文件已经保存过了,普通撤销历史早没了。目录末尾那两个斜杠是必须的,它告诉 Vim 用文件的完整路径生成交换文件名,避免同名文件互相覆盖。
4.2 十几条真正每天都用到的设置
number加relativenumber组合是很多人忽略的甜点:当前行显示绝对行号,其他行显示相对行号。这样你想“往下删五行”,看一眼左边就知道敲5dd,不用心算。
list加listchars是个细节但很有价值。团队代码里混进 Tab 和行尾空格是常态,特别是多人协作的项目。把 Tab 显示成>-、行尾空格显示成-,一眼就能看出来哪里不干净。清理行尾空格我用:%s/\s\+$//e,加在映射里,提交前跑一次。
wildmenu和wildmode影响命令行补全的体验。配置成longest:full,full之后,你敲:e加 Tab,会先补到最长公共前缀,再按一次列出所有候选。搭配wildignore排除掉.git、simv、*.log这些目录,补全列表会干净很多。
4.3 映射设计:别把常用键位送给低频操作
映射的取舍原则我用一句话概括:高频操作配好按的键,低频操作配难按的键,破坏性操作不要配单键。<leader>默认是反斜杠,几乎所有人第一件事就是把它改成逗号或者空格,因为反斜杠在键盘上位置太偏。
let mapleader = "," " 保存与退出(注意:不要映射成单键 Q) nnoremap <leader>w :update<CR> nnoremap <leader>q :q<CR> " 配置与重载 nnoremap <leader>ev :edit $MYVIMRC<CR> nnoremap <leader>sv :source $MYVIMRC<CR> " 取消搜索高亮 nnoremap <leader>h :nohlsearch<CR> " 清理行尾空白 nnoremap <leader>s :%s/\s\+$//e<CR>这里有个细节值得说:nnoremap和nmap的区别在于是否递归展开右边的映射。日常自定义一律用nnoremap、vnoremap、inoremap,只有在你明确想串接别的映射时才用非递归以外的形式。这个习惯能帮你躲掉一大批莫名其妙的映射冲突。
5. 在 GVIM 里说 verilog-mode,说的其实是三样不同的东西
“gvim verilog-mode”这个搜索词背后有个长期存在的误会。搜的人里有一部分是从 Emacs 那边过来的,他们听说 Emacs 的 verilog-mode 有 AUTOINST、AUTOWIRE、AUTOARG 这类能自动生成例化、自动补线网声明的东西,就想知道 Vim 里对应的在哪。答案容易让人失望:Vim 里没有对应的一套东西,名字撞车而已。
5.1 Vim 自带的那套运行时文件做了什么
Vim 安装目录下的runtime目录里,是自带 Verilog 支持的。跟 Verilog 相关的主要是三类文件:syntax/verilog.vim负责关键字高亮,把module、always、assign、wire、reg这些染上颜色;indent/verilog.vim负责缩进规则,也就是自动缩进时该退几格;ftplugin/verilog.vim负责文件类型相关的局部设置,比如注释符号。
这三件事听起来基础,但少了任何一个都会明显难受。尤其缩进,Verilog 的begin...end、case...endcase嵌套结构靠手调缩进非常费时间。至于 SystemVerilog,class、interface、covergroup、constraint这些结构的支持在不同版本上差异不小,如果发现高亮和缩进对不上,多半是需要额外的语法包来补齐。
如果拿到的文件扩展名不常见,比如.v、.sv、.svh、.vh,自动识别偶尔会失灵。手动指定就行:
:set filetype=verilog :set filetype=systemverilog :set syntax=verilog:set filetype会连带触发缩进和 ftplugin 的加载,:set syntax只管高亮。碰到“文件类型对了但高亮不对”的情况,通常是这两个没对齐。
5.2 它不做 Emacs verilog-mode 的 AUTOINST 和 AUTOWIRE
这一节是我最想写清楚的部分,因为踩过这个坑的人太多了。Vim 自带的 verilog 支持不包含以下能力:自动把所有子模块的端口抓出来生成例化模板、自动把没声明的信号补成wire、自动提取模块的端口列表生成module声明。这些恰恰是 Emacs verilog-mode 最出名的功能。
那 Vim 用户怎么办?我知道的路线有三条,各有取舍。
第一条是插件路线。社区里有专门针对 Verilog 和 SystemVerilog 的语法与工具插件,有的能提供更完整的语法高亮、更聪明的缩进,以及基于 ctags 或自定义解析的例化辅助。这条路配置成本中等,但插件质量参差不齐,选之前最好看更新时间。
第二条是外部脚本路线。用 Python 或 Perl 写个小脚本解析模块端口,然后在 Vim 里用:read !把输出读进来。这条路最灵活,因为你可以完全控制格式,比如按公司代码规范生成带注释的例化,或者一次性生成.sig_name(sig_name)这样的连接。
第三条是文本处理路线,也就是不追求“全自动”,用 Vim 自己的替换和宏解决 80% 的情况。我日常用得最多的其实是第三条,因为它不依赖任何额外环境,换台电脑照样能用。下一节会具体展开。
5.3 关键字补全、行补全与字典补全的实际用法
插入模式下Ctrl-n和Ctrl-p是最常用的补全,它们基于当前缓冲区里出现过的词做补全。写 RTL 的时候,信号名一般都会在声明处出现过,所以这个补全的命中率不低。
比它更实用的是行补全Ctrl-x Ctrl-l。它按整行匹配,输入端口列表或者重复的always块开头时,敲几个字符然后触发,整行就出来了,接着改改信号名就行。我写模块例化的时候,端口连接那一段基本靠这个命令省时间。
再往上一步是字典补全Ctrl-x Ctrl-k。你可以把所有 Verilog 关键字、常用宏、UVM 类名做成一个纯文本字典文件,在配置里指过去:
set dictionary+=~/.vim/dict/verilog.dict set completeopt=menu,menuone,noinsertnoinsert这个选项值得加,它让补全菜单只预览不自动插入第一个候选,避免你还没看清楚就被塞了一个错的进去。
6. RTL 日常里最费手的三件事:例化、对齐、批量重命名
写 RTL 的时间构成很反直觉:真正在思考电路逻辑的时间可能只占三成,剩下七成花在例化、改端口、对齐格式、批量重命名信号这些机械劳动上。GVIM 的价值就在这七成里体现。
6.1 从端口声明直接生成例化连接
假设子模块端口是这样声明的:
module data_path #( parameter WIDTH = 8 ) ( input wire clk, input wire rst_n, input wire [WIDTH-1:0] din, output reg [WIDTH-1:0] dout, output wire valid );选中这几行端口声明,然后跑一条替换,就能直接变成例化连接列表:
:'<,'>s/\v^\s*(input|output|inout)\s+.*[^\w](\w+)\s*[;,]\s*$/\2(\2),/这条命令拆开看有三部分。^\s*(input|output|inout)\s+锚定端口方向关键字,保证只处理端口行,不会误伤普通信号声明;.*[^\w](\w+)\s*[;,]\s*$负责抓出最后一个标识符,正向贪婪匹配会一路吃到行尾附近再回退,正好落在端口名上;替换部分\2(\2),把捕获到的名字用两次,一次当连接名,一次当信号名。
跑完得到这样的结果:
clk(clk), rst_n(rst_n), din(din), dout(dout), valid(valid),想要带点号的形式,把替换部分改成.\2(\2),就行。想要端口名和方法名不一样,那还是得手改,别指望一条正则包打天下。
注意:这条替换依赖端口名的位置规律。如果团队代码里端口声明后面跟着注释,比如
input wire clk, // 主时钟,正则里的$锚点就匹配不上了。先把注释临时删掉,或者把锚点改成宽松版本再跑。
6.2 对齐与折叠,把模块看成一页纸
对齐这件事在没有插件的环境里也能做,只是手动一点。选中连续几行,用:left、:right、:center可以调对齐方式,配合:normal加编辑命令能做更细的调整:
:'<,'>normal! f=2w不过说实话,对齐这种事交给插件更省心,社区里有专门做列对齐的插件,配置一条触发指令,选中端口列表一按,等号和信号名就整整齐齐。我更想聊的是折叠,因为它直接影响你读代码的方式。
Verilog 的一个模块动辄几百行,翻起来很累。开语法折叠:
set foldmethod=syntax set foldlevel=1 set foldcolumn=2foldmethod=syntax依赖语法文件的折叠定义,对大多数 Verilog 文件效果不错,能按module、always、function折叠。如果语法折叠不够准,就退回手动标记折叠,在代码里埋{{{和}}}注释,配foldmethod=marker。foldlevel=1表示默认只展开最外层,打开文件时能看到模块骨架,需要的时候再zo展开。foldcolumn=2在左边留一条窄栏显示折叠状态,鼠标点着也方便。
我读别人代码的流程基本固定:先zM全部折叠看结构,再逐个zo展开感兴趣的块,比从头滚到尾快很多。
6.3 宏录制处理重复结构
宏是那种“平时不用,用一次省一小时”的功能。录制逻辑是三下:q加寄存器名开始录,操作一遍,再按q停止。回放是@加寄存器名,重复上一次用@@,带次数就是100@a。
举个真实例子:手头有一堆信号需要加前缀。假设要把某个区块里所有信号名改成u_dut_开头,可以先用:g//加替换处理一部分,剩下的不规则情况用宏逐个来。更聪明的用法是配合范围执行:
" 对选中的每一行执行寄存器 a 里的宏 :'<,'>normal @a这个写法把宏从“手动一行行点”变成了“对整块区域批量跑”。批量注释也能这么干:选中一段代码,跑:'<,'>normal I//,每行行首都插入双斜杠;取消注释就是:'<,'>s/\v^\/\///。
有个小坑要注意:录制宏的时候尽量用“可重复的移动方式”。如果你录的时候用了鼠标点击或者方向键,回放时位置就飘了。用j、w、f、/这些确定性的移动,宏才能稳定复现。
7. 工程级跳转:tags、:vimgrep 与 quickfix 的组合拳
单文件操作熟练之后,瓶颈会转移到工程层面:这个信号在哪个文件里定义的?这个模块在哪儿被例化的?改了一个参数,哪些文件受影响?GVIM 本身不带索引,但它的命令行能力配上外部工具,组合起来相当能打。
7.1 用 Universal Ctags 生成 Verilog 索引
第一步是装 ctags,建议用 Universal Ctags 那个分支,它对 SystemVerilog 的支持明显更好,能识别module、class、interface、package这些结构。在工程根目录跑:
ctags -R --languages=Verilog,SystemVerilog --extras=+q .生成的tags文件里记录每个符号所在文件和行号。然后在配置里告诉 Vim 去哪找:
set tags=./tags;,tags那个;是关键,它让 Vim 从当前文件所在目录一路往上找,直到找到tags为止。这样你在深层子目录里编辑文件也能正常跳转。--extras=+q是给标签加限定名,比如模块里的信号可以用模块名.信号名的形式索引,避免不同模块里同名信号互相干扰。
ctags 有个使用习惯上的细节:它不会自动更新。你新加了模块,得重新生成一次。我在 Makefile 里加了个tags目标,跑编译之前顺手生成了。如果懒得手动,也可以配个自动命令在保存时增量更新,但大工程上增量更新偶尔会出问题,我倾向于手动控制时机。
7.2 跳转、回跳与 jumplist
有了 tags,Ctrl-]就是主力跳转键,光标放在模块名或信号名上按下去,直接跳到定义处。跳错了或者看完了,Ctrl-t往回跳。这两个键你会用到吐。
跳了几层之后,用Ctrl-o和Ctrl-i在跳转历史里前后穿梭。Ctrl-o是往回,Ctrl-i是往前,这个历史就是 jumplist。:jumps命令能看到完整的跳转记录,我在迷失方向的时候会打开看一眼,比乱按回去快。
还有个组合是g]和:tselect。当同一个名字有多个定义(比如不同模块里有同名信号),Ctrl-]会跳到第一个,而g]会弹出一个候选列表让你选。这个在大型工程里很有用,因为重名是常态。
顺手提一个细节:Ctrl-]对 ctags 生成的标签依赖是强绑定,如果光标下的名字是个局部变量或者 ctags 没索引的符号,它不会跳转,会直接报“tag not found”。这时候用*搜索反而更快,因为*是基于文件内容搜索的,不依赖索引。
7.3 全局搜索替换:vimgrep、cdo 与 argdo
cscope 在 Verilog 上支持一直不太好,所以我的全局搜索基本全靠:vimgrep。它的好处是纯 Vim 内置,不用装任何东西:
:vimgrep /\<data_valid\>/ **/*.v **/*.sv :copen\<和\>是词边界,避免把data_valid_dly也给搜进来。**/*.v是递归匹配所有子目录,这是 Vim 自带的通配符语法。搜完之后:copen打开 quickfix 窗口,所有命中结果列成一张表,用:cn、:cp前后翻,回车跳到对应位置。
quickfix 真正强大的地方在于它可以批量执行命令。:cdo对 quickfix 里每一条结果执行命令,:cfdo是按文件执行:
" 对每个命中行做替换 :cdo s/\<data_valid\>/data_valid_q/ " 保存所有被修改的文件 :cfdo update:cfdo update这个组合很值得记。:cdo改完之后文件是脏的但没保存,一个个切过去存太慢,一条:cfdo update全部搞定。
另外:argdo是对参数列表执行命令,用法是先:args **/*.v把文件加进列表,然后:argdo %s/old/new/ge | update。这里的e标志是“没匹配到也不报错”,批量操作时必加,否则第一个不匹配的文件就中断整个流程。| update是执行完顺便保存,注意是update不是write,前者只在文件有改动时才真正写盘,能省不少磁盘写入。
8. 那几个让你怀疑人生的坑:中文、剪贴板、换行符、崩溃恢复
GVIM 用久了,你会发现真正让人抓狂的从来不是不会用某个命令,而是那些看起来什么都对、结果就是不对的问题。下面这几个是我在不同项目里反复遇到的。
8.1 中文乱码与 fileencodings 的探测顺序
乱码分两种。一种是打开就是乱码,另一种是打开正常,但 Vim 判断错了编码,你保存的时候把文件内容改了。第二种更可怕,因为悄无声息。判断当前文件被识别成什么编码,敲:set fileencoding?就能看到。
排查链路我会按这个顺序走。第一,确认:set encoding?是utf-8,如果不是,先改它再重启,这个选项改晚了会影响已经读进来的缓冲区。第二,确认fileencodings的顺序,utf-8必须在gb18030和gbk前面。第三,如果文件已经识别错了,用:e ++enc=gbk强制按指定编码重新读,或者:e! ++enc=utf-8。第四,确认要保存的目标编码,用:set fileencoding=gbk再:w,就能把文件转存成 GBK。
注意:转码保存之后建议用外部工具(比如文件管理器里的编码检测,或者
file命令)确认一下,不要直接覆盖原文件,先存成副本验证。
还有个坑是fileformats和fileformat的区别,前者是读取时的候选列表,后者是当前文件的实际格式。这两个选项名字差一个 s,作用差很远,我在配置里写过好几次才发现。
8.2 剪贴板和寄存器的对应关系
寄存器和剪贴板这一块,是新手上手最容易被绕晕的点。Vim 的寄存器用双引号加字符表示,""是匿名寄存器,"0是最近复制的内容,"1到"9是最近删除的内容(会滚动覆盖),"a到"z是你可以主动用的具名寄存器。
想明确用某个寄存器,在命令前加"加寄存器名。"ayy是把当前行复制到a寄存器,"ap是粘贴a寄存器的内容。这个机制在你想暂存几段不同代码的时候特别有用,比如一边整理端口列表一边保留信号声明。
系统剪贴板和寄存器的桥接是*和+两个寄存器。配了set clipboard=unnamed之后,默认的复制粘贴就直接对接系统剪贴板了。但我建议不要无脑开这个选项,因为它会让每次dd都覆盖系统剪贴板,有时候你删了一行,回头想粘贴刚在浏览器里复制的东西,发现被那行代码顶掉了。我现在的做法是保持默认,需要和系统剪贴板交互时显式用"+y和"+p,把这两个映射成顺手的键位。
| 寄存器 | 内容 | 典型用途 |
|---|---|---|
"" | 匿名,最近一次操作 | 普通复制粘贴 |
"0 | 最近复制的内容 | 删了东西后还想贴原来复制的 |
"1-"9 | 最近删除的历史 | 找回前几次删掉的整行 |
"a-"z | 手动指定 | 暂存多段代码 |
"+/"* | 系统剪贴板 | 和外部工具互通 |
8.3 CRLF 和 LF 在团队协作里的麻烦
Windows 上的编辑器默认写 CRLF 行尾,Linux 和 Git 默认 LF。混在一起的结果是各种诡异现象:Git diff 显示整个文件都改了、脚本报\r: command not found、某些编译器的某些版本会警告。
GVIM 里的处理方式很直接。看当前文件格式敲:set fileformat?,返回dos是 CRLF,unix是 LF。转换就是设置加保存:
" 转成 LF :set fileformat=unix :w " 转成 CRLF :set fileformat=dos :wfileformats这个选项我一般配成unix,dos,让读取时优先按 LF 解释。另外配置里加set nobomb,避免某些场景下 Vim 给 UTF-8 文件加上 BOM 头,有些工具对 BOM 处理得很糟糕。
还有set ff这种简写在普通模式下敲起来很快,养成习惯每次提交前确认一下文件格式,能省掉一堆无意义的 diff。
8.4 swap 文件、undofile 与配置排查的硬手段
GVIM 崩溃或者被强杀之后,再打开同一个文件会弹出 swap 文件的选择菜单,问你是恢复、删除还是只读打开。这里的选择要谨慎:如果你确定那份没保存的内容很重要,选恢复,然后在 GVIM 里手动检查内容,确认无误后把 swap 文件删掉。如果选错了“删除”,那份内容就真没了。
undofile是另一层保险,它和 swap 是两套独立机制。swap 是崩溃时保住未保存的内容,undofile 是跨会话保留修改历史。两个我都开着,因为丢过一次代码之后,这类保险再多也不嫌多。
最后说一个排查配置问题的硬手段,我觉得每个 GVIM 用户都该知道。当你发现某个选项的行为和你预期不一致,想知道它是被谁改的:
:verbose set expandtab? :verbose set foldmethod? :verbose map <leader>w:verbose前缀会让 Vim 报告“最后修改这个设置的文件和行号”。配置一多,光看配置文件根本找不到是哪行生效的,这个命令能直接把源头指出来。
另一种更粗暴的排查方式是vim -u NONE启动,完全不加载任何配置。如果问题还在,那说明跟配置无关,是环境问题;如果问题消失了,那就是你的某个配置干的,用二分法注释掉一半试试,几轮就能定位。
9. 边学边用的节奏:每次只往回路里塞一个新动作
写了这么多具体的东西,最后想聊聊方法。GVIM 的学习最容易走进两个极端:一种是从网上抄一大套配置,用几天发现处处别扭,然后放弃;另一种是抱着命令手册啃,啃完之后发现一个都没记住,因为脑子和手指是两套系统。
我自己的做法是给自己定一个很低的配额:每周只往日常工作流里塞两到三个新动作。选法是从当前任务里最烦的那一步倒推。比如这周你频繁在做端口例化,那就把替换命令练熟;下周你在反复跳转查信号,那就把Ctrl-]和Ctrl-o用到不用想。动作嵌在真实任务里,才会变成肌肉记忆;脱离了任务去背,就是白费力气。
9.1 把 :help 当成查字典而不是教科书
:help最容易被当成一本需要通读的书,其实它是一本字典。用法上我希望你掌握两件事。一是精确查询,:help 'expandtab'查选项要带单引号,:help :vimgrep查命令要带冒号,:help i_CTRL-N查插入模式下的按键要带模式前缀。二是模糊搜索,:helpgrep 关键词会在所有帮助文件里全文搜索,然后把结果扔进 quickfix 窗口,翻起来和代码搜索一个体验。
还有个很实用的小命令是:help index,它会列出某个模式下的所有命令索引。我在想不起来某个键是干什么的时候会翻一下,比搜索引擎快。
9.2 用 q: 和宏把重复劳动压平
命令历史窗口是我最晚才用的功能之一,用过之后回不去了。正常模式下按q:打开命令行历史,里面是你敲过的所有 Ex 命令,可以像编辑普通文本一样编辑其中某一条,回车即执行。当你需要把一条长替换命令微调之后重跑,这个比重新敲一遍舒服太多。搜索历史是q/。
配合宏和:normal,很多机械操作可以压缩成一次按键。我的判断标准是:如果一个操作我今天做了超过十遍,就值得花十分钟把它变成宏或者映射。
9.3 什么时候该停下来别再折腾配置
配置这东西有很强的自我膨胀倾向。我见过把配置文件写到一千多行的,加载慢得像启动一个 IDE,结果每天用的还是那二十个命令。
给自己设一条线:如果某个配置项你已经两周没意识到它存在,那它要么删掉,要么说明你没在用。插件也一样,装之前先问自己“我现在的痛点是什么”,如果答不上来,就是跟着别人的配置清单在堆东西。
我个人在实际操作中的体会是,GVIM 真正带来的效率提升,八成来自那十几个核心动作,剩下两成才是配置和插件的功劳。所以与其花周末折腾一套看起来很专业的配置,不如在下一个真实任务里,故意逼自己用一个新命令做完那件事。做完之后你就会发现,这个动作已经留在手上了,比看十篇教程都管用。