熟练使用 Vim

Vim 小技巧

情景一:自动写入文件头

在编写 C++ 程序时,总有一些东西会在每个头文件中出现,比如:

1
2
3
4
#ifndef <_File_Name_MACRO_>
#define <_File_Name_MACRO_>
// ...
#endif // <_File_Name_MACRO_>

每次键入这些信息会非常枯燥。但只要配置好 Vim ,就可以逃避这些烦人的操作。我们可以在 .vimrc 中将需要每次键入的信息写在一个函数内,每次在创建一个特定文件时(比如 .cpp),vim 会帮我们自动把这些信息写好。下面我来介绍一下使用方法,如有需要,直接拉到最后复制粘贴

autocmd

vim 中自带的自动命令,会在指定事件发生时自动执行。我们正打算利用这一特性,迅速完成上面场景的要求,将重复的操作自动化,提高编辑效率并减少人为操作的差错。

先从最简单的开始举例,vim 中可定义以下函数,用于在文件中插入当前日期:

1
2
3
:function DateInsert()
: $read !date
:endfunction

而使用以下命令,可以手动调用此函数:

1
:call DateInsert()

但每次调用函数有点太过麻烦,我们使用自动命令,在保存文件时自动执行函数,其中 FileWritePre 是 vim 中的内置事件:

1
:autocmd FileWritePre * :call DateInsert()

上一句话可以简单地翻译为:“在写入文件前,需要自动执行命令 call DateInsert()”。可用简写 :au 代替 :autocmd。内置事件有很多种,参考知乎专栏罗列在此,官方文档可见 Vim Doc


简单的例子介绍完了,再回到情景一的问题中,结合上面给出的表格,如果要想在创建新的 .hpp 文件后,让 vim 自动添加文件头,那么应当在 vimrc 中添加:

1
2
# 自定义命令,发生在编辑新的文件时,且文件名后缀为hpp,需要执行"call AddHPPHeader"
autocmd BufNewFile *.hpp exec ":call AddHPPHeader()"

现在,每次打开一个新的 .hpp 文件后,vim 都会帮我们自动执行命令 call AddHPPHeader()

关于函数

那么,AddHPPHeader 函数应该怎么写呢?首先,在 vimrc 中,需要定义函数 AddHPPHeader

1
2
3
func AddHPPHeader()
# ...
endfunc

函数内,第一步就是判断文件类型是否为 .hpp。这里使用 expand() 内置函数可以对当前文件进行分类。expand("%:e") 中,% 表示当前文件名,而 :e 表示只有扩展名,于是该函数返回的是当前文件名的扩展名。如果文件是 hpp,就添加文件头:

1
2
3
4
if expand("%:e") == 'hpp'
# 先限定在文件的第一行
call setline(1, "/** ")
# 再一行一行地添加内容

添加文件头内容时,有很多办法,最简单的就是使用 setline 函数一行一行地写:

1
2
3
call setline(line('.')+1, "#ifndef XXXX")
call setline(line('.')+2, "#define XXXX")
call setline(line('.')+5, "#endif // XXXX")

这里的XXXX被我们写死在了 .vimrc 中,这样不够灵活,试想若换一个文件,宏定义的具体内容必然需要改变,那岂不是又要修改 .vimrc 了?

使用变量才能保证宏定义可以随着文件名等条件变化。

关于变量

vim 中,变量使用 let 进行赋值,通过 unlet 销毁变量 ,可用 echo 打印变量的值。对于 Vim 选项还可用 set 来更方便地操作,比如 set {option}, set no{option}, set {option}?

与 bash 中的变量类似,普通变量可以直接引用,环境变量要加前缀 $、寄存器变量要加前缀 @、Vim 选项要加前缀 &。如,在正常模式下:

1
2
3
4
5
6
7
8
:let str = "Hello World"
:echo str
:let $PATH .= ':/foo:/bar'
:echo $PATH
# 打印当前文件名
echo @%
# 把刚才拷贝的内容放到 a 寄存器中
let @a = @"

.= 运算符在 Vim 中用来做字符串拼接并赋值。

Vim 选项是控制着 Vim 编辑器行为的一些变量,比如是否显示行号,使用哪种剪切板。 引用选项变量时需要添加 & 前缀。例如:

1
2
3
4
# 显示行号
: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 函数将小写的文件名变为大写。

1
2
3
let str = "Hello, Vim"
echo toupper(str)
# 输出为 HELLO, VIM

考虑到,文件名路径通常包含 /. 等符号,这对宏定义来说是非法的。要使用 substitute 将非法符号替换为 _

1
substitute(expand('%'), "[/.]", "_", "g")

substitute 的第一个参数是要修改的字符串,第二个是 pattern,第三个是替换的子串,第四个是替换的 flags,上面这句话将文件名路径中的 /. 都替换为 _

综合以上几点,可以如下定义 Vim 变量:

1
let macro= "_DF_".toupper(substitute(expand("%"), "[/.]", "_", "g"))."_"

其中,. 在 Vim 中充当字符串连接符。如果当前文件名为 process.hpp ,那么 macro 变量的值为 _DF_PROCESS_HPP_

总结

最后,我们得到了一个简短有用的 Vim 自动化脚本,可以有效地解决情景一提出的问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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 下就行了,执行:

1
git clone https://github.com/gmarik/vundle.git ~/.vim/bundle/vundle

随后,我们需要在 .vimrc 上使用这个插件管理器,使用方法很简单:

1
2
3
4
5
6
7
8
9
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 一样简单便捷,只需执行:

1
git clone https://github.com/preservim/nerdtree.git ~/.vim/bundle/nerdtree

即可完成下载和安装。但此时你打开 vim ,你仍无法看到文件树,需要输入:NERDTreeToggle 才能打开。我们可以利用在情景一中的小知识,使用 autocmd 来自动完成打开 vim 即打开文件树目录的操作:

1
autocmd vimenter * NERDTree

受限于 vim 平台本身,nerdtree 的使用有些小门槛,需要使用它提供的环境变量与它更好地配合,下表分享一下常用的 nerdtree 变量:

变量名 描述
NERDTreeDirArrowExpandable 设置树的显示图标,该图标表示这层目录可以被展开 字符
NERDTreeDirArrowCollapsible 设置树的显示图标,该图标表示这层目录已经被展开 字符
NERDTreeIgnore 用于过滤所有列表中的文件 例如[‘\.tmp,.git', '\\.git’] 表示所有扩展名为 .tmp 或 .git 的文件都不会显示在上面
NERDTreeShowLineNumbers 指示 nerd 的窗口是否需要显示行号 1 为需要
NERDTreeMinimalUI 是否需要最小化 UI 为 1 时不显示 ‘Press ? for help’
NERDTreeWinSize 指定文件树占用的窗口宽度 宽度值

顺便秀一下我的配置吧:

img

关于 nerdtree 的使用,还有许多有趣的小技巧分享给大家:

  • nerdtree 的刷新:打开的 vim 中,Nerdtree 目录结构是不会自动刷新的,需要按 r 手动进行刷新
  • 恢复显示隐藏的文件:在 nerdtree 中按 Ctrl-I(大写)

熟练使用 Vim
https://dingfen.github.io/2021/08/22/2021-8-22-VimTech/
作者
Bill Ding
发布于
2021年8月22日
更新于
2024年4月9日
许可协议