系列笔记链接

Ch00,Ch01
Ch02:Buffers, Windows and Tabs

Ch03 Searching Files

这一章介绍在Vim中进行搜索的相关操作,掌握搜索功能有助于进一步熟练使用Vim。

Vim中打开和编辑文件

在终端启动Vim之后,要在当前窗口打开一个新的文件(buffer),可以在命令行模式下执行:

:edit file.txt

当file.txt不存在于当前目录,Vim会新建一个对应文件的buffer。
同时,:edit支持通配符*的使用,*可以代表0个或多个字符,比如以下命令会匹配当前目录中后缀为.txt的所有文件:

:edit *.txt<Tab>

:edit命令每次只能打开一个buffer,因此通配符需要配合Tab键使用,输入:edit *.txt后按Tab键,就能在后缀为.txt的文件中选择需要打开的文件。
Vim学习笔记【Ch03】-LMLPHP
若不确定待搜索的文件位于哪个文件夹,还可以使用**进行递归搜索,比如要打开某个.md文件:

:edit **/*.md<Tab>

结合Tab键,就能列出当前目录下,所有子文件夹(包括嵌套的子文件夹)包含的.md文件。
当给:edit传入一个文件夹作为参数,Vim会调用其内置的文件资源管理插件,以浏览的形式选择要打开的文件。通过上下移动光标可以选择不同的子文件夹和文件,按下Enter键进入子文件夹或选择对应文件。其中,选择../时代表返回上一级目录。
Vim学习笔记【Ch03】-LMLPHP

用Find进行文件搜索

通过:find命令可以在Vim中查找指定文件并打开对应的buffer,看上去和:edit命令功能相同。但实际上,:find的搜索范围会比:edit大,:edit一般只能在当前目录下搜索,而:find可在path包含的所有路径中搜索对应文件。通过以下命令可以获取当前path的值:

set path?   # 默认输出path=.,/usr/include,,

默认情况下,:find对应的搜索路径是path=.,/usr/include,,,第一个.表示当前打开的文件对应的目录,第二部分/usr/include是Linux系统中C的头文件存储路径,第三部分是一个,,代表Vim当前所处的目录,也就是说,:find会在以上三个目录查找文件。可以通过设置path的值,增加find的搜索范围:

:set path+=d1/dd1/
:set path+=d1/dd1/**   # 可搜索嵌套的子目录
:set path+=$PWD/**   # 将当前工作目录及其子目录都加入搜索范围

在命令行模式下执行:set操作都是临时性的,当前Vim操作结束后,所有设置都会还原为默认值,因此如果需要使加入的搜索路径永久生效,就需要在vimrc文件中进行设置(参考Ch01)。

用grep进行文件内搜索

在各文件内部搜索指定内容时需要用到grep,在Vim中,可以通过:vim或者:grep两种途径进行模式匹配搜索。

:vim搜索

:vim:vimgrep的缩写,是内置于Vim内部的grep组件,其搜索方式如下:

:vim /pattern/ file   # 在file文件中查找pattern
:vim /pattern/ d1/dd1/**/*.txt   # 在d1/dd1及其子目录下的所有后缀为.txt的文件中查找

两个斜杠之间的pattern是目标模式串,vim会在后面的文件file(支持多个文件和通配符*)中查找是否存在与pattern一致的段落。
执行:vim命令后,会展示其第一个搜索结果,其搜索命令使用quickfix操作管理搜索结果,比如通过:copen可以看到当前搜索的所有结果:
Vim学习笔记【Ch03】-LMLPHP
其他常用的quickfix命令如下:

:copen   # 打开quickfix窗口
:cclose   # 关闭quickfix窗口
:cnext   # 前往下一个搜索结果
:cprevious   # 前往上一个搜索结果
:colder  # 前往上一次vim搜索的quickfix窗口
:cnewer  # 前往下一次vim搜索的quickfix窗口

next和previous是在当前搜索中切换不同的搜索结果,而older和newer则是在多次搜索中切换,说明vim的搜索会一直保留直到当前Vim任务终止。同时,:vim会将每个匹配的文件都加载到内存中,如果一次搜索中有较多匹配的文件,会造成比较大的内存消耗,使得Vim的速度变慢。进行模式串搜索时应尽量精确,避免无效搜索占用过多内存。

:grep搜索

:grep是另一种模式匹配搜索命令,调用外部的grep终端命令进行搜索,其搜索方式为:

:grep "pattern" file/directory

此时,目标模式串用双引号修饰,参数可以是文件或目录,传入目录时在该目录中的所有文件中搜索。:grep同样使用quickfix管理搜索结果。Vim中定义了grepprg参数,指定了运行:grep时具体调用的外部程序(默认是终端的grep),在不关闭Vim的情况下进行终端的grep命令,后续会介绍如何修改默认调用的grep程序。

通过Netrw浏览文件

之前介绍:edit时提到,给它传入一个目录时会启动netrw,从而浏览该目录,:edit是在Vim内部使用Netrw的命令,在进入Vim终端时给vim传入一个目录作为参数,同样可以启动netrw进行文件浏览。
运行netrw需要配置文件.vimrc中包含以下设置:

set nocp
filetype plugin on

在Vim内部(命令行模式),不传入目录参数启动netrw的其他方式:

:Explore  # 对当前文件启动netrw
:Sexplore  # 在窗口的上半部分启动netrw
:Vexplore  # 在窗口的左半部分启动netrw

以及一些增删改文件/目录的netrw命令(在netrw窗口中直接输入%,不用带参数,窗口底部会提示输入文件名):

%   # 创建新文件
d   # 创建新目录
R   # 重命名文件或目录
D   # 删除文件或目录

作者还推荐了其他的文件浏览插件,如vim-vinegarNERDTree

Fzf

前面几部分是通过Vim内置工具进行文件和文件内容搜索以及文件管理,作者还介绍了使用如何使用fzf.vim这个插件进行简单又高效的搜索。

fzf和ripgrep安装

fzf是一个通用的命令行模糊查询工具,可以根据其github仓库安装和使用fzf,个人的系统属于Ubuntu18.04,不满足apt安装条件,因此是通过git安装的,一些安装设置都选择yes。
Vim学习笔记【Ch03】-LMLPHP
ripgrep和grep一样(从名字上也看得出),也是一个模式匹配搜索工具,缩写为rg。它比grep搜索更快,并且具备许多有用的功能,fzf中也可以使用ripgrep。ripgrep的安装同样可以参考github的仓库,有比较详细的介绍。根据个人系统配置,通过.deb文件安装:
Vim学习笔记【Ch03】-LMLPHP

fzf配置

fzf默认情况下并不会使用ripgrep,需要在配置文件中指定一个FZF_DEFAULT_COMMAND变量。我用的是bash,因此打开.bashrc,在文件最后加入:

if type rg &> /dev/null; then
  export FZF_DEFAULT_COMMAND='rg --files'
  export FZF_DEFAULT_OPTS='-m'
fi

FZF_DEFAULT_COMMAND指定默认命令为rg,FZF_DEFAULT_OPTS的设置是为了使fzf支持用Tab键。

先安装一个vim-plug,用于Vim的插件安装:

curl -fLo ~/.vim/autoload/plug.vim --create-dirs https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim

然后还在.vimrc中添加以下配置:

call plug#begin()
Plug 'junegunn/fzf.vim'
Plug 'junegunn/fzf', { 'do': { -> fzf#install() } }
call plug#end()

通过vim安装fzf的插件,而这个过程又需要借助fzf.vim,因此还需要先安装fzf.vim这个插件。
随后打开vim,运行:PlugInstall,安装.vimrc中指定的插件。
Vim学习笔记【Ch03】-LMLPHP
需要注意的是,即便是安装完成,call这部分的配置也不能删掉(或注释掉),否则将无法在Vim使用这些插件。

fzf语法

fzf匹配的一些基础语法有以下5个:

^ :表示前缀的准确匹配,如匹配一个以"welcome"开头的短语:^welcome
$ :表示后缀的准确匹配,如匹配一个以"welcome"结尾的短语:welcome$
' :表示某个短语的准确匹配
| :表示逻辑或的匹配
! :表示逻辑非的匹配,不包含!之后的字符串

这些语法可以组合使用,构成更复杂的模式串。

查找文件

安装fzf.vim插件以后,可以在Vim中使用:Files查找文件,如下所示,通过输入目录/文件的前几个字符,fzf自动匹配满足条件的目录或文件。
Vim学习笔记【Ch03】-LMLPHP
在.vimrc中添加以下命令,可以将:Files映射到键盘的快捷键Ctrl+F

nnoremap <silent> <C-f> :Files<CR>

PS:home目录下的.vimrc修改完就能生效,不需要source!

在文件内部查找

在文件内部进行查找(模式匹配)使用:Rg,每次敲这个命令都比较麻烦,同样可以将它映射到键盘的快捷键,比如将其映射为<Leader>+f,其中,<Leader>对应键盘上的\

nnoremap <silent> <Leader>f :Rg<CR>

其他搜索命令可以参考fzf.vim的命令,作者提供了对应的快捷键映射参考,可以根据个人偏好进行调整。

nnoremap <silent> <Leader>b :Buffers<CR>
nnoremap <silent> <C-f> :Files<CR>
nnoremap <silent> <Leader>f :Rg<CR>
nnoremap <silent> <Leader>/ :BLines<CR>
nnoremap <silent> <Leader>' :Marks<CR>
nnoremap <silent> <Leader>g :Commits<CR>
nnoremap <silent> <Leader>H :Helptags<CR>
nnoremap <silent> <Leader>hh :History<CR>
nnoremap <silent> <Leader>h: :History:<CR>
nnoremap <silent> <Leader>h/ :History/<CR>

实践时发现,<C-f>这种形式表示Ctrl和F两个键需要同时按,而<Leader>f表示先按\键,再按F,才能正确调用对应指令,果然不能纸上谈兵!

用Rg代替Grep

通过在.vimrc中添加以下命令,可以指定前面提到的:grep具体使用的外部搜索工具:

set grepprg=rg\ --vimgrep\ --smart-case\ --follow

其中的选项可以修改,不过我现在也不知道具体怎么修改,详细要看man rg,这些选项的配置可以令Vim中:grep的用法更简洁。重新指定的:grep依旧用的是quickfix展示搜索结果。

在多个文件中搜索和替换

VSCode一类的文本编辑器支持多个文件的内容搜索和替换。Vim进行这项操作有两种方式。
假设要将文件中的"pizza"替换为"donut",
方式一:

:grep "pizza"
:cfdo %s/pizza/donut/g | update

第一行用:grep查找所有"pizza",第二行中,:cfdo会对quickfix中的所有结果执行其后的命令,%s/pizza/donut/g是一个全局替换命令,然后|表示管道,位于其前后的命令顺序执行,update则是保存每个文件,由此完成所有文件对应单词的替换。
方式二:
方式一属于无差别替换(:grep会搜索当前目录下的所有文件),方式二只对打开的buffer(文件)做替换。
1.首先需要清空buffers,可以选择重启Vim,或者通过:%bd | e#删除所有buffers,然后打开最近一次查看/编辑的文件;
2.然后运行:Files(或者设置的快捷键Ctrl+f),上下移动光标选择参与替换的文件,当光标停留在某文件上,按Tab键可以将该文件多选(之前设置了FZF_DEFAULT_OPTS为-m即可通过Tab键多选),被选中的文件左侧会多出一个>>>表示该文件被选中,同时光标在该文件上),选择完成后Enter即可打开所有选中文件的buffer。
Vim学习笔记【Ch03】-LMLPHP
3.对打开的buffer执行替换命令

:bufdo %s/pizza/donut/g | update

和方式一的差别就是bufdo只对当前所有buffers中的目标进行替换,不会影响未打开的文件。

小结

至此,这一章结束了。边看教程边实践,加上记录下来花了不少精力,但实践起来才发现教程也不会事无巨细,很多都需要自己去试,或者去看他提供的拓展资料。目前为止算是初步入门了Vim,但离掌握和熟练使用还有很长一段路。

06-04 23:57