Vim 小技巧
情景一:自动写入文件头
在编写 C++ 程序时,总有一些东西会在每个头文件中出现,比如:
#ifndef <_File_Name_MACRO_>
#define <_File_Name_MACRO_>
// ...
#endif // <_File_Name_MACRO_>
每次键入这些信息会非常枯燥。但只要熟练使用 Vim ,就可以逃避这些烦人的操作。如有需要,直接拉到最后复制粘贴。
autocmd
vim 中自带的自动命令,会在指定事件发生时自动执行。巧妙地利用自动命令就可以迅速完成上面场景的要求,将重复的操作自动化,提高编辑效率并减少人为操作的差错。
举例来说,先定义以下函数,用于在文件中插入当前日期:
:function DateInsert()
: $read !date
:endfunction
使用以下命令,可以手动调用此函数:
:call DateInsert()
而若使用自动命令,则可以在保存文件时自动执行函数,其中 FileWritePre
是 vim 中的内置事件:
:autocmd FileWritePre * :call DateInsert()
也可以用简写 :au
代替 :autocmd
。内置事件有很多种,参考知乎专栏罗列在此,官方文档可见 Vim Doc:
再回到情景一的问题中,如果要想在创建新的 .hpp
文件后,让 vim 自动添加文件头,应当在 vimrc
中添加:
# 自定义命令,发生在编辑新的文件时,且文件名后缀为hpp,需要执行"call AddHPPHeader"
autocmd BufNewFile *.hpp exec ":call AddHPPHeader()"
关于函数
那么,AddHPPHeader
函数应该怎么写呢?首先,在 vimrc
中,需要定义函数 AddHPPHeader
:
func AddHPPHeader()
# ...
endfunc
函数内,第一步就是判断文件类型是否真的为 .hpp
。使用 expand()
内置函数可以对当前文件进行分类。expand("%:e")
中,%
表示当前文件名,而 :e
表示只有扩展名,于是该函数返回的是当前文件名的扩展名。如果文件是 hpp
,就添加文件头:
if expand("%:e") == 'hpp'
# 先限定在文件的第一行
call setline(1, "/** ")
# 再一行一行地添加内容
添加文件头内容时,有很多办法,最简单的就是使用 setline
函数一行一行地写:
call setline(line('.')+1, "#ifndef XXXX")
call setline(line('.')+2, "#define XXXX")
call setline(line('.')+5, "#endif // XXXX")
最简单的方法往往不够灵活,试想若换一个文件,宏定义的具体内容必然需要改变,那岂不是又要修改 vimrc
了?
使用变量才能保证宏定义可以随着文件名等条件变化。
关于变量
vim 中,变量使用 let
进行赋值,通过 unlet
销毁变量 ,可用 echo
打印变量的值。对于 Vim 选项还可用 set
来更方便地操作,比如 set {option}
, set no{option}
, set {option}?
。
与 bash 中的变量类似,普通变量可以直接引用,环境变量要加前缀 $
、寄存器变量要加前缀 @
、Vim 选项要加前缀 &
。如,在正常模式下:
:let str = "Hello World"
:echo str
:let $PATH .= ':/foo:/bar'
:echo $PATH
# 打印当前文件名
echo @%
# 把刚才拷贝的内容放到 a 寄存器中
let @a = @"
.=
运算符在 Vim 中用来做字符串拼接并赋值。
Vim 选项是控制着 Vim 编辑器行为的一些变量,比如是否显示行号,使用哪种剪切板。 引用选项变量时需要添加 &
前缀。例如:
# 显示行号
:let &number = 1
# 不显示行号
:let &number = 1
Vim 提供了 set
命令来更方便地操作选项变量,网上相关内容很多,这里不再赘述。
变量作用域值得一提,变量默认作用域取决于定义的位置,函数内则为函数作用域,外部则为全局变量。 赋值和引用变量时可以使用 b:
,g:
,l:
,t:
等前缀来指定要操作哪个作用域的变量。
变量作用域 | 简写 | 描述 |
---|---|---|
buffer-var | b: | Local to the current buffer |
window-var | w: | Local to the current window |
tabpage-var | t: | Local to the current tab page |
global-var | g: | Global variable |
local-var | l: | Local to a function |
vim-var | v: | Global, predefined by Vim |
function-arg | a: | Function argument (only inside function) |
回到我们要解决的问题上来,先定义一个 Vim 变量,通常宏定义都是大写,因此需要用到 toupper
函数将小写的文件名变为大写。
let str = "Hello, Vim"
echo toupper(str)
# 输出为 HELLO, VIM
考虑到,文件名路径通常包含 /
和 .
等符号,这对宏定义来说是非法的。要使用 substitute
将非法符号替换为 _
。
substitute(expand('%'), "[/.]", "_", "g")
第一个参数是要修改的字符串,第二个是 pattern,第三个是替换的子串,第四个是替换的 flags,上面这句话将文件名路径中的 /
和 .
都替换为 _
。
综合以上几点,可以如下定义 Vim 变量:
let macro= "_DF_".toupper(substitute(expand("%"), "[/.]", "_", "g"))."_"
其中,.
在 Vim 中充当字符串连接符。如果当前文件名为 process.hpp
,那么 macro
变量的值为 _DF_PROCESS_HPP_
。
总结
最后,我们得到了一个简短有用的 Vim 自动化脚本,可以有效地解决情景一提出的问题。
autocmd BufNewFile *.hpp exec ":call AddHPPHeader()"
func AddHPPHeader()
if expand("%:e") == 'hpp'
# 先限定在文件的第一行
call setline(1, "/** ")
# 再一行一行地添加内容
let macro= "_DF_".
toupper(substitute(expand("%"), "[/.]", "_", "g"))."_"
normal o
call setline(line('.'), "#ifndef ".macro)
call setline(line('.')+1, "#define ".macro)
call setline(line('.')+4, "#endif // ".macro)
endfunc
情景二:vundle工具和nerd文件树
vundle 插件管理器
许多 vim 的第三方插件可以让工作变得更加简单。但首先,我们需要安装一个管理插件的插件 vundle,其安装方式很简单,把它的 git 仓库存在 .vim/bundle
下就行了,执行:
git clone https://github.com/gmarik/vundle.git ~/.vim/bundle/vundle
随后,我们需要在 .vimrc
上使用这个插件管理器,使用方法很简单:
set rtp+=~/.vim/bundle/Vundle.vim
call vundle#begin()
" VundleVim/Vundle.vim
Plugin 'VundleVim/Vundle.vim'
" The Nerd Tree
Plugin 'preservim/nerdtree'
" Vim airline
Plugin 'vim-airline/vim-airline'
call vundle#end()
好,复制完上面这段代码后,我们来挨个看看他们的意思。第一句 set
命令是在指定 vundle 插件的运行路径,方便进行初始化操作。随后,call vundle#begin()
启动 vundle ,开始进行插件管理。之后紧跟的三句话表示我们的 vim 会使用到如下三个插件:Vundle.vim、nerdtree 和vim-airline ,请求 vundle 对这三个插件进行管理。最后 call vundle#end()
表示 vundle 管理结束。
nerd 文件树
使用 nerd 文件树目录可以方便我们在打开 vim 的同时看到我们目前所处的文件路径和文件树结构。其安装方法与 vundle 一样简单便捷,只需执行:
git clone https://github.com/preservim/nerdtree.git ~/.vim/bundle/nerdtree
即可完成下载和安装。但此时你打开 vim ,你仍无法看到文件树,需要输入:NERDTreeToggle
才能打开。我们可以利用在情景一中的小知识,使用 autocmd
来自动完成打开 vim 即打开文件树目录的操作:
autocmd vimenter * NERDTree
受限于 vim 平台本身,nerdtree 的使用有些小门槛,需要使用它提供的环境变量与它更好地配合,下表分享一下常用的 nerdtree 变量:
变量名 | 描述 | 值 |
---|---|---|
NERDTreeDirArrowExpandable | 设置树的显示图标,该图标表示这层目录可以被展开 | 字符 |
NERDTreeDirArrowCollapsible | 设置树的显示图标,该图标表示这层目录已经被展开 | 字符 |
NERDTreeIgnore | 用于过滤所有列表中的文件 | 例如[‘\.tmp ′ , ′ . g i t ', '\\.git ′,′.git’] 表示所有扩展名为 .tmp 或 .git 的文件都不会显示在上面 |
NERDTreeShowLineNumbers | 指示 nerd 的窗口是否需要显示行号 | 1 为需要 |
NERDTreeMinimalUI | 是否需要最小化 UI | 为 1 时不显示 ‘Press ? for help’ |
NERDTreeWinSize | 指定文件树占用的窗口宽度 | 宽度值 |
顺便秀一下我的配置吧:
关于 nerdtree 的使用,还有许多有趣的小技巧分享给大家:
- nerdtree 的刷新:打开的 vim 中,Nerdtree 目录结构是不会自动刷新的,需要按 r 手动进行刷新
- 恢复显示隐藏的文件:在 nerdtree 中按 Ctrl-I(大写)