感受Vim的强大: 进阶技巧
Vim是从vi发展出来的一个文本编辑器. 代码补全, 编译及错误跳转等方便编程的功能特别丰富, 在程序员中被广泛使用. 和Emacs并列成为类Unix系统用户最喜欢的编辑器. 如果你还没有使用过Vim, 建议你先去看这个: Vim初级: 配置和使用. 本文介绍一些Vim的高级特性. 包括块编辑, 宏录制, 语法高亮, 键盘映射, 函数定义, 文件类型识别与对应插件加载等.
模式
在开始尝试复杂操作前务必要了解 Vim 的工作模式:
- 普通模式(Normal): 进入 Vim 后的默认模式, 可输入编辑命令.
- 插入模式(Insert): 在 Normal 模式中按下
i/I/a/A等键后进入插入模式, 这时输入的字符会插入到光标处. - 可视模式(Visual): 在 Normal 模式下按下
v可进入. 用于选中某个区域, 后续的编辑命令作用于这个区域. - 选择模式(Select): 设置好
selectmode和mouse后鼠标选区即可进入. 类似于可视模式, 但键入的字符对选择区进行替换 (类似 Microsoft Windows 下的编辑方式) . - 命令行模式(Command-line): 在 Normal 模式下按下 ":" 或 "/" 即可进入, 用来输入 Ex 命令.
- Ex 模式(Ex): 在 Normal 模式按下
Q进入. 类似命令行模式, 只是每个命令结束后不会自动回到普通模式 (vimrc 脚本即处于这一模式) .
选中编辑
v 可进入 可视模式 (visual) , 使用标准快捷键移动光标 可进行文本选区, 之后的编辑命令对整个选中区域生效.
例如把选中区域的所有 author 替换为 harttle:
- 进入 Visual 模式并选三行文本:
vjjj. - 对选中部分进行替换:
:s/author/harttle/g.
块编辑
按下 <Ctrl>v 进入列编辑模式 列编辑 模式 (block Visual mode) , 移动光标可跨行选择矩形的文本块, 之后的编辑命令对整个块生效.
例如注释 N 行代码 (添加 //) :
Normal 模式下 <Ctrl>v 进入列编辑模式, jjj 选中下面三行, I 进入插入模式, 输入 // 注释字符, 按 <Esc> 完成.
如何让矩形块选中参差不齐的行尾呢, 这时需要一些特殊的移动命令,
比如 ^ 移动到行首 (或 | 移动到当前行的第一个字符) , $ 光标移到行尾, A 在行尾追加, I 在行首插入.
例如在每行行尾都加入句号: Normal 模式下 gg 到文件头, <Ctrl-v>进入块编辑模式,
G 选择所有内容; A 进入行尾插入模式, 输入 . , 按下 <Esc>完成.
filetype on
Vim可针对特定的文件, 加载指定插件. 以此来实现文件类型的特殊配置以及语法高亮.
首先要在 vimrc 中通过 filetype on 开启文件类型识别, Vim 会在载入时做如下工作:
- 执行
$RUNTIMEPATH/filetype.vim, 尝试得到filetype. - 如果没有得到
filetype, 继续执行$RUNTIMEPATH/scripts.vim
比如我们可以在 ~/.vim/filetype.vim 中进行 filetype:
" 在读取和创建 *.cpp 时设置 filetype 为 cpp
au BufRead,BufNewFile *.cpp setfiletype cpp调试时我们可以手动设置 :set filetype=cpp.
RUNTIMEPATH 变量可通过 :set rtp 打印出来, 详情请参考: http://vimcdoc.sourceforge.net/doc/options.html#'runtimepath'
filetype plugin on
filetype plugin on 允许 Vim 通过 filetype 的当前值加载对应的 $RUNTIMEPATH/ftplugin/<filetype>.vim.
如果你按照上一小节配置了 filetype 变量, 在你打开 cpp 文件时 Vim 会尝试载入 ~/.vim/ftplugin/cpp.vim 文件 (如果有的话) .
事实上 Vim 会尝试载入 $RUNTIMEPATH 下的所有 ftplugin/cpp.vim.
比如 ~/.vim/ftplugin/cpp.vim 之后还会执行 ~/.vim/after/ftplugin/cpp.vim,
因此它里面的配置生效会更稳.
手动载入插件
filetype 只是提供一种方便的机制来定义不同文件类型的配置.
我们也可以不用这种机制, 手动载入对应文件的配置, 例如:
" 创建或读取 *.plt 文件时执行 plt.vim
au BufNewFile,BufRead *.plt source ~/.vim/after/ftplugin/plt.vim`参考: http://vimcdoc.sourceforge.net/doc/filetype.html#filetype-plugins
缩进设置
缩进的设置也可以通过不同的文件类型来载入, 需要在 ~/.vimrc 中设置 filetype indent on.
与 ftplugin 机制类似在 Vim 加载一个有 filetype 的文件时会自动载入对应的缩进配置文件.
比如打开 *.cpp (filetype 已按上述设置为 cpp) 时, Vim 会尝试载入 ~/.vim/indent/cpp.vim.
其中的内容可以是:
set shiftwidth=4
set tabsize=4
set expandtab语法高亮
syntax on 允许 Vim 加载文件类型的语法高亮配置, Vim会在载入时寻找并加载 RUNTIMEPATH/syntax/<filetype>.vim. 例如: ~/.vim/syntax/markdown.vim 将会对文件类型markdown 进行语法高亮.
当然语法高亮最好引入插件, 请参考这些文章:
键盘映射
Vim支持定义键盘映射来完成快捷键的功能, 也就是将特定的按键映射为一系列按键与函数的序列.
例如将 F7 映射为编译当前 java 文件:
map <F7> <Esc>:!javac %<<CR>: 为进入Ex模式, ! 指定下面的命令在vim外执行, % 为当前文件名, %< 为不带扩展名的当前文件名, <CR> 为回车.
此外设置 map 时可以指定它在何种情况 (Vim 模式) 下生效, 这些模式包括:
| map 模式 | 描述 |
|---|---|
| n | 普通 |
| v | 可视和选择 |
| s | 选择 |
| x | 可视 |
| o | 操作符等待 |
| ! | 插入和命令行 |
| i | 插入 |
| l | 插入, 命令行和 Lang-Arg 模式的 "" 映射 |
| c | 命令行 |
定义 map 时添加上述模式名作为前缀, 即可指定 map 命令在哪些模式生效:
| 命令 | 左边 | 右边 | 模式 |
|---|---|---|---|
| {lhs} | {rhs} | mapmode-nvo | |
ap | {lhs} | {rhs} | mapmode-n |
ap | {lhs} | {rhs} | mapmode-v |
ap | {lhs} | {rhs} | mapmode-x |
| {lhs} | {rhs} | mapmode-s | |
ap | {lhs} | {rhs} | mapmode-o |
| ! | {lhs} | {rhs} | mapmode-ic |
ap | {lhs} | {rhs} | mapmode-i |
ap | {lhs} | {rhs} | mapmode-l |
ap | {lhs} | {rhs} | mapmode-c |
此外还可添加nore 指定非递归方式 (取消传递性) .
例如 inoremap 为插入模式下工作的 map, 并且它的值不会被递归映射.
函数
现在我们可以自定义快捷键了, 如果希望在键盘映射中执行更复杂的功能, 我们需要定义Vim函数.
- 函数名必须以大写字母开始
- 函数可以有返回值:
return something - 函数可以有范围前缀. 定义:
function s:Save(), 调用:call s:Save()
下面是函数调用的例子, 按键F8时, 进入拷贝模式 (取消行号, 鼠标进入visual模式) .
" key mapping
map <F8> <Esc>:call ToggleCopy()<CR>
" global variable
let g:copymode=0
" function
function ToggleCopy()
if g:copymode
set number
set mouse=a
else
set nonumber
set mouse=v
endif
let g:copymode=!g:copymode
endfunction录制宏
用户录制的宏保存在寄存器中, 我们先来看看什么是寄存器. vim的寄存器分为数字寄存器和字母寄存器.
- 数字寄存器为
0-9,0保存着上次复制的内容,1-9按照最近的顺序保存着最近删除的内容. - 字母寄存器为
a-z, 拷贝3行到寄存器c:c3yy.
现在开始录制宏. 假如有如下的文件, 我们希望将第二列的类型用`分隔起来.
1 | BOOL | Boolean
2 | SINT | Short integer
3 | INT | Integer
4 | DINT | Double integer
5 | LINT | Long integer
6 | USINT | Unsigned short integer
7 | UINT | Unsigned integer - 首先按几次
<Esc>进入normal模式, 光标移到第一行, 开始录制并存入m寄存器qm. - 光标到行首
^, 到第二列词首ww, 进入插入模式i, 插入分隔符`, 退出到normal模式<Esc>, 到词尾e, 进入插入模式i, 插入分隔符`, 退出到normal模式<Esc>, 光标到下一行j. - 结束录制
q. - 光标到第二行, 在normal模式执行100次寄存器m中的宏
100@m.
宏会在 j 执行错误后自动结束, 得到如下文件:
1 | `BOOL` | Boolean
2 | `SINT` | Short integer
3 | `INT` | Integer
4 | `DINT` | Double integer
5 | `LINT` | Long integer
6 | `USINT` | Unsigned short integer
7 | `UINT` | Unsigned integer