跳转到内容

Vim 使用指南

在服务器上改配置文件、在终端里写 Git 提交信息,经常会进入 Vim。Vim 的操作方式和普通编辑器不同:大部分时间不是在“打字”,而是在用按键组合“操作文本”。这篇从模式讲起,整理最常用的移动、编辑、搜索替换和多窗口操作,再介绍寄存器和宏,最后给出一份基础配置。

终端窗口
vim file.txt # 打开文件,不存在则新建
vim +25 file.txt # 打开并跳到第 25 行
vim -O a.txt b.txt # 左右分屏打开两个文件
vimtutor # Vim 自带的交互式教程,约半小时,强烈推荐

退出相关命令(在普通模式下输入):

命令 作用
:w 保存
:q 退出(有未保存修改时会拒绝)
:wq 或 :x 保存并退出(:x 只在有修改时写入)
:q! 放弃修改强制退出
:wa / :qa 保存全部 / 退出全部
:w !sudo tee % 忘了用 sudo 打开只读文件时,借助 tee 以 root 身份写入
模式 进入方式 用途
普通模式(Normal) 启动后默认;在其他模式按 Esc 返回 移动光标、删除、复制、粘贴等操作
插入模式(Insert) i、a、o 等 输入文本
可视模式(Visual) v、V、Ctrl-v 选中文本后再操作
命令行模式(Command-line) :、/、? 执行命令、搜索

进入插入模式的几种方式:

按键 位置
i / a 光标前 / 光标后插入
I / A 行首(第一个非空字符) / 行尾插入
o / O 在下方 / 上方新开一行插入

可视模式:v 按字符选择,V 按行选择,Ctrl-v 按列(矩形块)选择。选中后可以用 d 删除、y 复制、> 缩进等。块选择模式下按 I 输入文字再按 Esc,可以同时在多行前插入相同内容,常用于批量加注释。

普通模式下:

按键 移动到
h j k l 左、下、上、右
w / b / e 下一个单词开头 / 上一个单词开头 / 单词结尾
W / B / E 同上,但以空白分隔“单词”(跳过标点)
0 / ^ / $ 行首 / 行首第一个非空字符 / 行尾
gg / G 文件开头 / 文件结尾
25G 或 :25 第 25 行
Ctrl-d / Ctrl-u 向下 / 向上翻半页
Ctrl-f / Ctrl-b 向下 / 向上翻一页
% 跳到匹配的括号
f{字符} / t{字符} 在当前行向右跳到某字符上 / 该字符前;; 重复,, 反向重复
* / # 向下 / 向上搜索光标下的单词
Ctrl-o / Ctrl-i 跳回上一个位置 / 跳到下一个位置

大多数移动命令前可以加数字表示重复次数,如 5j 向下移动 5 行,3w 向后移动 3 个单词。

Vim 编辑命令的核心语法是 操作符 + 动作(或文本对象):

操作符 作用
d 删除(删除的内容会进入寄存器,相当于剪切)
c 修改:删除后进入插入模式
y 复制(yank)
> / < 增加 / 减少缩进
= 自动缩进
gu / gU 转小写 / 转大写

把操作符和前面的移动组合起来:

dw 删除到下一个单词开头
d$ 删除到行尾(等同于 D)
dgg 删除到文件开头
c3w 修改接下来 3 个单词
yG 复制到文件结尾
>j 当前行和下一行增加缩进

操作符连按两次表示作用于整行:dd 删除一行,yy 复制一行,cc 修改一行,>> 缩进一行。前面加数字,如 3dd 删除 3 行。

文本对象让你不必把光标移到开头就能操作一整块内容,格式是 i(inner,不含边界)或 a(around,含边界)加对象类型:

命令 作用
diw / daw 删除当前单词 / 删除单词及其后的空格
ci" 修改双引号内的内容
da( 或 dab 删除包括括号在内的整个括号块
yi{ 复制花括号内的内容
cit 修改 HTML 标签内的内容
dip 删除当前段落

比如光标在 fetch("/api/user") 的引号内任意位置,输入 ci" 就能直接改写 URL。

按键 作用
x 删除光标下的字符
r{字符} 把光标下的字符替换为指定字符
p / P 在光标后 / 前粘贴
u 撤销
Ctrl-r 重做
. 重复上一次修改操作
J 把下一行合并到当前行
~ 切换光标下字符的大小写

. 是 Vim 中最有用的命令之一:比如用 ciw 改了一个单词,移动到另一个单词上按 . 就能做同样的修改。

/pattern 向下搜索
?pattern 向上搜索
n / N 跳到下一个 / 上一个匹配

搜索默认区分大小写,在模式后加 \c 可以忽略大小写,如 /error\c。

替换命令的格式是 :[范围]s/查找/替换/[标志]:

:s/foo/bar/ 当前行第一个 foo 替换为 bar
:s/foo/bar/g 当前行所有 foo
:%s/foo/bar/g 整个文件
:%s/foo/bar/gc 整个文件,每处替换前确认(y 替换,n 跳过,a 全部,q 退出)
:10,20s/foo/bar/g 第 10 到 20 行
:'<,'>s/foo/bar/g 可视模式选中的范围(选中后按 : 会自动填入 '<,'>)
:%s/\s\+$//e 删除所有行尾空白,e 表示没有匹配时不报错

查找内容中包含 / 时,可以换用其他分隔符,比如 :%s#/old/path#/new/path#g。

:g 命令对匹配的行执行命令:

:g/console.log/d 删除所有包含 console.log 的行
:v/TODO/d 删除所有不包含 TODO 的行

Vim 中每个打开的文件都是一个缓冲区(buffer):

:e path/to/file 打开文件
:ls 列出缓冲区
:bn / :bp 下一个 / 上一个缓冲区
:b 2 切换到 2 号缓冲区
:bd 关闭当前缓冲区
:sp file 水平分屏打开(上下排列)
:vs file 垂直分屏打开(左右排列)
Ctrl-w h/j/k/l 移动到左 / 下 / 上 / 右侧的窗口
Ctrl-w w 在窗口之间循环切换
Ctrl-w = 所有窗口等宽等高
Ctrl-w q 关闭当前窗口
:only 只保留当前窗口
:tabnew file 在新标签页打开
gt / gT 下一个 / 上一个标签页
:tabclose 关闭标签页

复制和删除的内容存放在寄存器中,用 " 加寄存器名指定:

寄存器 说明
"" 默认寄存器,最近一次删除或复制的内容
"0 最近一次复制(yank)的内容,不会被删除操作覆盖
"a 到 "z 命名寄存器,供自己使用
"+ 系统剪贴板(需要 Vim 编译时支持 clipboard)
"_ 黑洞寄存器,写入的内容直接丢弃
"ayy 把当前行复制到寄存器 a
"ap 粘贴寄存器 a 的内容
"+y 复制到系统剪贴板
"_dd 删除一行但不影响默认寄存器
"0p 粘贴最近一次复制的内容(即使之后又删除过东西)
:reg 查看所有寄存器的内容

一个常见困扰:复制了一段文字,接着删除了别的内容想替换,结果粘贴出来的是刚删掉的内容。这时用 "0p 就能粘贴之前复制的内容。

插入模式下按 Ctrl-r 再按寄存器名,可以插入寄存器的内容。

宏就是把一串按键录制到寄存器中,然后重复播放,适合处理大量格式相同的行:

qa 开始录制到寄存器 a
... 执行一系列操作
q 停止录制
@a 播放一次
@@ 重复上一次播放的宏
10@a 播放 10 次

示例:把下面几行改成 JS 对象属性格式。

name
age
email

把光标放在第一行,依次输入:qa(开始录制)、I(行首插入)、输入 、Esc、A(行尾插入)、输入 : '',、Esc、j(移到下一行)、q(结束录制)。然后 2@a 处理剩下两行,结果:

name: '',
age: '',
email: '',

录制宏时尽量使用相对移动(w、f、0、$),让宏在不同长度的行上都能正确执行。

用户配置文件是 ~/.vimrc(Neovim 使用 ~/.config/nvim/init.vim 或 init.lua)。系统级配置的位置因发行版而异,比如 Debian/Ubuntu 是 /etc/vim/vimrc,一般不建议修改系统级配置,写在自己的 ~/.vimrc 中即可。

" 基础
set nocompatible " 不兼容旧版 vi 的行为
syntax on " 语法高亮
filetype plugin indent on " 按文件类型加载插件和缩进规则
set encoding=utf-8
" 界面
set number " 显示行号
set relativenumber " 显示相对行号,方便 5j 这类跳转
set cursorline " 高亮当前行
set showcmd " 右下角显示正在输入的命令
set scrolloff=5 " 光标上下保留 5 行
" 缩进
set expandtab " Tab 转为空格
set tabstop=2 " Tab 显示宽度
set shiftwidth=2 " 自动缩进宽度
set autoindent
" 搜索
set hlsearch " 高亮搜索结果
set incsearch " 边输入边搜索
set ignorecase " 搜索忽略大小写
set smartcase " 搜索词含大写字母时区分大小写
" 其他
set backspace=indent,eol,start " 允许退格删除缩进、换行
set clipboard=unnamedplus " 默认使用系统剪贴板(需 clipboard 支持)
set mouse=a " 启用鼠标
set undofile " 持久化撤销历史
" 快捷键:空格作为 leader 键
let mapleader = " "
nnoremap <leader>w :w<CR>
nnoremap <leader>h :nohlsearch<CR>

nnoremap 定义普通模式下的非递归映射,比 nmap 更安全,推荐总是使用 noremap 系列。修改配置后可以在 Vim 中执行 :source ~/.vimrc 立即生效。

需要插件时,可以使用 Vim 8 起自带的 package 机制,或 vim-plug 等插件管理器。如果想把 Vim 当成日常主力编辑器,可以考虑 Neovim,或在 VS Code 中安装 Vim 插件,在熟悉的环境里使用 Vim 的按键方式。

  • 普通模式用来操作,插入模式用来输入,可视模式用来选择,命令行模式用来执行命令;不确定在哪个模式就按 Esc。
  • 编辑命令遵循“操作符 + 动作/文本对象”,如 dw、ci"、yip;操作符连按两次作用于整行。
  • . 重复上一次修改,u 撤销,Ctrl-r 重做。
  • 替换用 :%s/查找/替换/gc,:g 对匹配行批量执行命令。
  • 分屏用 :sp、:vs 和 Ctrl-w 系列;"0p 粘贴最近复制的内容,qa ... q 录制宏,@a 播放。
  • 个人配置写在 ~/.vimrc,映射优先使用 noremap 系列。