Vim 内置不少用于编辑 C 语言源码的功能。以下提供概览及相应跳转标签:
usr_29.txt 用户手册中关于代码跳转的章节。
usr_30.txt 用户手册中关于代码程序的章节。
C-indenting 文本输入时自动设置行缩进。
= 对若干行重新缩进。
format-comments 格式化注释。
:checkpath 列出所有被 (嵌套) 包含的头文件。
[i 在当前文件及头文件内搜索光标所在标识符。
[_CTRL-I 跳到 [i 的匹配位置。
[I 列出在当前文件及头文件内,所有匹配光标所在标识符的行。
[d 在当前文件及头文件内,搜索光标所在标识符的宏定义。
CTRL-] 跳转到光标所在标签 (例如: 函数的定义处)。
CTRL-T 跳回 CTRL-] 跳转前的位置。
:tselect 从多个匹配标签中选择一个。
gd 跳转到光标所在局部变量的声明处。
gD 跳转到光标所在全局变量的声明处。
gf 跳转到光标所在文件名的对应文件。
% 跳转到配对的 ()、{}、[]、/* */、#if、#else、#endif。
[/ 跳转到上一处注释起始位置。
]/ 跳转到下一处注释结束位置。
[# 反向跳转到未闭合的 #if、#ifdef 或 #else。
]# 正向跳转到未闭合的 #else 或 #endif。
[( 反向跳转到未闭合的 '('。
]) 正向跳转到未闭合的 ')'。
[{ 反向跳转到未闭合的 '{'。
]} 正向跳转到未闭合的 '}'。
v_ab 选中 "一个块" ("a block"),从 [( 至 ]) ,含括号
v_ib 选中 "内含块" ("inner block"),从 [( 至 ])
v_aB 选中 "一个大块" ("a Block"),从 [{ 至 ]} ,含括号
v_iB 选中 "内含大块" ("inner Block"),从 [{ 至 ]}
用户应该已经熟知 tags 可用于跳转到函数或变量的定义位置。但有时可能希望跳转到
函数和变量所有的使用位置。有两种实现方式:
1. 使用 :grep 命令。在大多数 Unix 系统上可用,但速度较慢 (需要读取全部文件),
且仅可在单个目录内搜索。
2. 使用 ID 工具集。速度快,支持多目录。依靠数据库存储定位信息。需要额外安装程
序,并且要维护数据库保持更新。
使用 GNU id 工具集 (id-tools):
所需环境:
- 安装 GNU id 工具集 (mkid 用于生成 ID 数据库,lid 用于调用相关宏)。
- 当前目录下存在名为 "ID" 的标识符数据库文件。可通过 "mkid file1 file2 .." 外
壳命令创建。
在 .vimrc 文件里加入:
map _u :call ID_search()<Bar>execute "/\\<" .. g:word .. "\\>"<CR>
map _n :n<Bar>execute "/\\<" .. g:word .. "\\>"<CR>
function! ID_search()
let g:word = expand("<cword>")
let x = system("lid --key=none " .. g:word)
let x = substitute(x, "\n", " ", "g")
execute "next " .. x
endfun
使用方法是,将光标放在单词上,按下 "_u",vim 会加载包含这单词的文件。在相同文
件中按 "n" 查找单词下一处匹配。按 "_n" 切换到下一个文件。
该方案已在 id-utils-3.2 版本 (最近的 gnu ftp 镜像服务器上的归档包名) 测试通
过。
[本思路来自 Andreas Kutschera]
(来源: comp.editors,Juergen Weigert 对一个问题的回复)
:> 另一个问题就是退出 vim 后,屏幕保持原样,也就是说: 我正在查看 (编辑) 的内容
:> 留在屏幕上。之前执行的命令 (如 "ls") 的输出丢失了,在滚动缓冲区里也找不到。
:> 我知道有办法在退出 vim 或其他类 vi 编辑器后恢复屏幕,只是不知道怎么做。请帮
:> 助我,谢谢。
:
: 我想其他人可以回答这个问题。但我猜想在特定的 xterm 配置下, vim 和 vi 的行为
: 应该一致。
二者行为未必一致,这可能是 termcap 和 terminfo 的差异导致。要知道描述终端属性
有两套数据库: termcap 和 terminfo。当两套数据库的条目有出入,且一个程序使用
terminfo,而另一个使用 termcap 时,就会产生行为差异 (请参见: +terminfo )。
针对此问题,重点是以下控制序列: ^[[?47h 和 ^[[?47l。它们用来在 xterm 备用和主
屏幕缓冲区之间切换。一个简易临时解决方案是使用以下命令序列
echo -n "^[[?47h"; vim ... ; echo -n "^[[?47l"
(符号 ^[ 代表 ESC 字符,后文会看到数据库里写作 \E)。
Vim 启动时,会向终端输出 termcap 的 ti 项目 (terminfo 中名为 smcup)。退出时输
出 te 项目 (terminfo 中名为 rmcup)。因此,这两项应是上述控制序列正确的存放位
置。
可将系统实际 xterm termcap 项目 (位于 /etc/termcap) 和 xterm terminfo 项目 (用
"infocmp -C xterm" 获取) 进行对比。两者都应包含类似以下的形式:
:te=\E[2J\E[?47l\E8:ti=\E7\E[?47h:
附: 如果发现二者存在差异,可建议系统管理员彻底检查整套 termcap 和 terminfo 数
据库的一致性。
备注 1: 在 feature.h 中定义 FEAT_XTERM_SAVE 后重新编译 Vim,则内置 xterm 数据
库就会包含上述的 "te" 和 "ti" 条目。
备注 2: 要禁止屏幕切换,又不想修改 termcap,可在 .vimrc 中加入:
:set t_ti= t_te=
处于插入模式时,要查看屏幕外的内容,可用 CTRL-X CTRL-E 和 CTRL-X CTRL-Y 滚屏。
i_CTRL-X_CTRL-E
可用以下映射方便操作:
:inoremap <C-E> <C-X><C-E>
:inoremap <C-Y> <C-X><C-Y>
(按本义输入,确保 'cpoptions' 选项不含 '<' 标志位 cpo-< )。
不过映射后,将无法使用 i_CTRL-E 复制光标上一行/下一行的文本。
也可将 'scrolloff' 调大,确保看到光标附近更多的上下文。如果 'scrolloff' 取值大
于窗口高度的一半,在上下移动光标时,光标会始终保持在窗口中间,而文本自动滚动。
要使滚动更平滑,可用以下映射:
:map <C-U> <C-Y><C-Y><C-Y><C-Y><C-Y><C-Y><C-Y><C-Y><C-Y><C-Y><C-Y><C-Y><C-Y><C-Y><C-Y><C-Y>
:map <C-D> <C-E><C-E><C-E><C-E><C-E><C-E><C-E><C-E><C-E><C-E><C-E><C-E><C-E><C-E><C-E><C-E>
(按本义输入,确保 'cpoptions' 选项不含 '<' 标志位 cpo-< )。
对于反复输错的单词,可定义缩写自动修正。示例:
:ab teh the
:ab fro for
要统计当前缓冲区中任意模式的匹配出现频率,可用 :substitute 命令并加 'n' 标志
位以避免实际替代。Vim 报告的替代次数就是模式匹配次数。例如:
:%s/./&/gn 字符数
:%s/\i\+/&/gn 单词数
:%s/^//n 行数
:%s/the/&/gn 任意位置的 "the" 出现次数
:%s/\<the\>/&/gn 作为单词的 "the" 出现次数
可复位 'hlsearch' 或执行 :nohlsearch 关闭高亮。
无匹配时如果不想报错,加上标志位 'e'。
适用于可视模式的另一种统计数据的方法是 v_g_CTRL-G 。
多文件查找匹配可用 :vimgrep 。
count-bytes
要统计字节数,可用以下方法:
可视选中所需字符 (也可用列块选择)
用 y 复制文本
调用 strlen() 函数:
:echo strlen(@")
这里换行符会被计为一个字节。
有时需要编写映射时,能够修改文件任意位置的内容,同时不改变光标位置也不导致文本
滚动。修改文件中的日期标记的示例:
:map <F2> msHmtgg/Last [cC]hange:\s*/e+1<CR>"_D"=strftime("%Y %b %d")<CR>p'tzt`s
保存位置命令的拆解:
ms 将光标位置存入位置标记 's'
H 跳转到窗口的顶端
mt 将光标位置存入位置标记 't'
恢复位置命令的拆解:
't 跳回先前到达的窗口顶部行
zt 滚屏,将该行滚动到窗口的顶端
`s 跳回光标初始位置
更高级的方法可见 winsaveview() 和 winrestview() 。
假如目录下有如下文件 (目录为随机选取):
buffer.c
charset.c
digraph.c
...
要将所有 *.c 统一重命名为 *.bla。步骤如下:
$ vim
:r !ls *.c
:%s/\(.*\).c/mv & \1.bla
:w !sh
:q!
使用脚本文件在多个文件中批量替换文本的示例:
创建文件 "subs.vim",写入 :substitute 命令和 :update 命令:
:%s/Jones/Smith/g
:%s/Allen/Peter/g
:update
启动 Vim 打开所有目标文件,然后为每个参数文件执行该脚本:
vim *.let
argdo source subs.vim
参见 :argdo 。
某些情况下,外部命令执行会很慢。Unix 上通配符展开也会随之变慢。以下是提速建议:
.cshrc 文件 (文件名依所用外壳而定) 过长时,可以拆成两段: 交互配置、非交互配置
(后者常被称作二级外壳)。在 Vim 中执行一个 ":!ls" 等外部命令时,不需要交互配置
部分 (如提示符设置)。可将无关 (交互配置) 部分放到以下判断句之后:
if ($?prompt == 0) then
exit 0
endif
另一种办法是在 'shell' 选项中包含 "-f" 参数,例如:
:set shell=csh\ -f
(反斜杠用于在选项值中保留空格)。
该设置会使 csh 完全跳过 .cshrc 文件加载。但这可能造成部分功能失效。
下面是部分用户常用的映射。
map-backtick >vim
:map ' `
使单引号的行为同 ',光标会移动到位置标记所在列,而非该行首个非空白字符。
emacs-keys
要在命令行上使用 Emacs 风格的编辑操作: >vim
" 至行首
:cnoremap <C-A> <Home>
" 后退一个字符
:cnoremap <C-B> <Left>
" 删除光标所在字符
:cnoremap <C-D> <Del>
" 至行尾
:cnoremap <C-E> <End>
" 前进一个字符
:cnoremap <C-F> <Right>
" 调取较新的命令行
:cnoremap <C-N> <Down>
" 调取以前 (较旧的) 命令行
:cnoremap <C-P> <Up>
" 后退一个单词
:cnoremap <Esc><C-B> <S-Left>
" 前进一个单词
:cnoremap <Esc><C-F> <S-Right>
备注: 需要 'cpoptions' 选项不含 '<' 标志位 cpo-< 。 <>
format-bullet-list
以下映射用于排版项目符号 (bullet) 列表,它要求每个项目上下各有一个空行。使用表
达式命令、以便对映射各段添加注释: >vim
:let m = ":map _f :set ai<CR>" " 需要置位 'autoindent'
:let m ..= "{O<Esc>" " 在项目上面新建空行
:let m ..= "}{)^W" " 跳转到项目符号之后的文本
:let m ..= "i <CR> <Esc>" " 添加缩进空格
:let m ..= "gq}" " 排版项目符号之后的文本
:let m ..= "{dd" " 删除空行
:let m ..= "5lDJ" " 将文本放到项目符号后方
:execute m |" 定义映射
(使用 <> 记法 <> 。注意 必须按本义输入。^W 是 "^" 和 "W",不是 CTRL-W。如果
'cpoptions' 选项不含 '<' 标志位 cpo-< ,可直接复制/粘贴上述代码到 Vim 执行。)
注意 最后注释以 |" 开头,因为 :execute 不能直接附带注释。
同时需要将 'textwidth' 设为非 0 值,例如: >vim
:set tw=70
以下映射效果类似,但从首行读取列表缩进 (备注: 本映射其实只有一行,但带大量空
格): >vim
:map _f :set ai<CR>}{a <Esc>WWmmkD`mi<CR><Esc>kkddpJgq}'mJO<Esc>j
<
collapse
以下两个映射可将连续空行 (;b) 或空白行 (;n) 合并为单行 >vim
:map ;b GoZ<Esc>:g/^$/.,/./-j<CR>Gdd
:map ;n GoZ<Esc>:g/^[ <Tab>]*$/.,/[^ <Tab>]/-j<CR>Gdd
磁盘空间紧张时,可压缩帮助文档。帮助文档仍可正常查看,但速度稍慢,且需依赖
"gzip" 程序。
(1) 压缩全部帮助文档: "gzip doc/*.txt"。
(2) 编辑文件 "doc/tags",用 ".txt.gz" 替换 ".txt":
:%s=\(\t.*\.txt\)\t=\1.gz\t=
(3) 在 vimrc 文件中加入:
set helpfile={dirname}/help.txt.gz
{dirname} 为帮助文档所在目录。 gzip 插件负责解压文件。
压缩后的 "doc" 目录和其他 Vim 文件不在同一位置时,要确保 $VIMRUNTIME 设为其他
Vim 文件所在目录。参见 $VIMRUNTIME 。
参见 terminal 。
另一种方案是用 "splitvt" 工具程序,分割终端屏幕或显示窗口。可在部分 ftp 服务器
获取。Sam Lantinga <slouken@cs.ucdavis.edu> 对此了解颇多。
还有一种方案是使用 BSD Unix 系统自带的 "window" 命令,支持多个重叠窗口。也可用
www.uni-erlangen.de 网站提供的 "screen" 程序,支持窗口栈。
参见用户手册第 23.4 一节。
二进制文件有专属扩展名时 (诸如 exe、bin 等),可在 <.vimrc> 文件中使用以下自动
命令实现读写自动化。可将 "*.bin" 替换为实际所需的扩展名列表 (以逗号分隔): >vim
" vim -b : 以 xxd-格式编辑二进制文件!
augroup Binary
autocmd!
autocmd BufReadPre *.bin set binary
autocmd BufReadPost *.bin
\ if &binary
\ | execute "silent %!xxd -c 32"
\ | set filetype=xxd
\ | redraw
\ | endif
autocmd BufWritePre *.bin
\ if &binary
\ | let s:view = winsaveview()
\ | execute "silent %!xxd -r -c 32"
\ | endif
autocmd BufWritePost *.bin
\ if &binary
\ | execute "silent %!xxd -c 32"
\ | set nomodified
\ | call winrestview(s:view)
\ | redraw
\ | endif
augroup END
自动命令使用的参数不识别 <> 记法。为避免使用特殊字符,可用一次性自销毁映射来使
用 <> 记法,再从自动命令调用这个映射。示例:
map-self-destroy >vim
" 此命令自动将文件名加入菜单列表。
" 使用自销毁映射!
" 1. 在缓冲区一行中,将文件名中的点号 ('dots') 替换成 \.
" 2. 将结果存入寄存器 '"'
" 3. 将该文件名加入缓冲区菜单列表
" 警 告: 存在副作用,如会覆盖当前寄存器内容,并删除 "i" 命令原有映射
"
autocmd BufNewFile,BufReadPre * nmap i :nunmap i<CR>O<C-R>%<Esc>:.g/\./s/\./\\./g<CR>0"9y$u:menu Buffers.<C-R>9 :buffer <C-R>%<C-V><CR><CR>
autocmd BufNewFile,BufReadPre * normal i
另一个可能更好的办法是通过 :execute 命令。在字符串中,可通过前导反斜杠使用
<> 记法。原有反斜杠数量要翻倍,'"' 前也要加上反斜杠。 >vim
autocmd BufNewFile,BufReadPre * exe "normal O\<C-R>%\<Esc>:.g/\\./s/\\./\\\\./g\<CR>0\"9y$u:menu Buffers.\<C-R>9 :buffer \<C-R>%\<C-V>\<CR>\<CR>"
真正缓冲区菜单的实现需要用户函数 (参见 :function ),但那里不需要 <> 记法,不
适合作为本例演示。
本例演示若干高级技巧:
- 使用 CursorMoved 自动命令事件
- 使用 searchpairpos() 查找匹配括号
- 使用 synID() 检测光标是否位于字符串或注释内
- 使用 :match 高亮内容
- 使用 pattern 匹配文件内特定位置。
脚本需要放在 Vim 脚本文件中,因为用到脚本局部变量。除非光标已经位于字符串或注
释内部,否则它需要先跳过字符串或注释内的括号匹配。这需要语法高亮支持。
matchparen 插件使用一个稍有改进的版本。
let s:paren_hl_on = 0
function s:Highlight_Matching_Paren()
if s:paren_hl_on
match none
let s:paren_hl_on = 0
endif
let c_lnum = line('.')
let c_col = col('.')
let c = getline(c_lnum)[c_col - 1]
let plist = split(&matchpairs, ':\|,')
let i = index(plist, c)
if i < 0
return
endif
if i % 2 == 0
let s_flags = 'nW'
let c2 = plist[i + 1]
else
let s_flags = 'nbW'
let c2 = c
let c = plist[i - 1]
endif
if c == '['
let c = '\['
let c2 = '\]'
endif
let s_skip ='synIDattr(synID(line("."), col("."), 0), "name") ' ..
\ '=~? "string\\|comment"'
execute 'if' s_skip '| let s_skip = 0 | endif'
let [m_lnum, m_col] = searchpairpos(c, '', c2, s_flags, s_skip)
if m_lnum > 0 && m_lnum >= line('w0') && m_lnum <= line('w$')
exe 'match Search /\(\%' .. c_lnum .. 'l\%' .. c_col ..
\ 'c\)\|\(\%' .. m_lnum .. 'l\%' .. m_col .. 'c\)/'
let s:paren_hl_on = 1
endif
endfunction
autocmd CursorMoved,CursorMovedI * call s:Highlight_Matching_Paren()
autocmd InsertEnter * match none
缺省在分割窗口打开帮助文档。有些场合可能适合直接使用当前窗口。为此可用
helpcurwin 可选包。可手工加载,也可在 vimrc 里加入: >vim
packadd helpcurwin
加载该软件包后:
- 命令 :HelpCurwin {subject} 可用于在当前窗口里打开帮助。
- 如果当前窗口的缓冲区已修改,插件会先提示确认再替换内容。确认后,原有缓冲区
会被隐藏 hidden-buffer 。
- 帮助文档 helpcurwin.txt 会详细介绍插件功能。
vim:tw=78:ts=8:noet:ft=help:norl: