Vim学习笔记
系列笔记链接
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的文件中选择需要打开的文件。
若不确定待搜索的文件位于哪个文件夹,还可以使用**
进行递归搜索,比如要打开某个.md文件:
:edit **/*.md<Tab>
结合Tab键,就能列出当前目录下,所有子文件夹(包括嵌套的子文件夹)包含的.md文件。
当给:edit
传入一个文件夹作为参数,Vim会调用其内置的文件资源管理插件,以浏览的形式选择要打开的文件。通过上下移动光标可以选择不同的子文件夹和文件,按下Enter
键进入子文件夹或选择对应文件。其中,选择../
时代表返回上一级目录。
用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
可以看到当前搜索的所有结果:
其他常用的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-vinegar和NERDTree。
Fzf
前面几部分是通过Vim内置工具进行文件和文件内容搜索以及文件管理,作者还介绍了使用如何使用fzf.vim这个插件进行简单又高效的搜索。
fzf和ripgrep安装
fzf是一个通用的命令行模糊查询工具,可以根据其github仓库安装和使用fzf,个人的系统属于Ubuntu18.04,不满足apt安装条件,因此是通过git安装的,一些安装设置都选择yes。
ripgrep和grep一样(从名字上也看得出),也是一个模式匹配搜索工具,缩写为rg。它比grep搜索更快,并且具备许多有用的功能,fzf中也可以使用ripgrep。ripgrep的安装同样可以参考github的仓库,有比较详细的介绍。根据个人系统配置,通过.deb文件安装:
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中指定的插件。
需要注意的是,即便是安装完成,call这部分的配置也不能删掉(或注释掉),否则将无法在Vim使用这些插件。
fzf语法
fzf匹配的一些基础语法有以下5个:
^ :表示前缀的准确匹配,如匹配一个以"welcome"开头的短语:^welcome
$ :表示后缀的准确匹配,如匹配一个以"welcome"结尾的短语:welcome$
' :表示某个短语的准确匹配
| :表示逻辑或的匹配
! :表示逻辑非的匹配,不包含!之后的字符串
这些语法可以组合使用,构成更复杂的模式串。
查找文件
安装fzf.vim插件以后,可以在Vim中使用:Files
查找文件,如下所示,通过输入目录/文件的前几个字符,fzf自动匹配满足条件的目录或文件。
在.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。
3.对打开的buffer执行替换命令
:bufdo %s/pizza/donut/g | update
和方式一的差别就是bufdo
只对当前所有buffers中的目标进行替换,不会影响未打开的文件。
小结
至此,这一章结束了。边看教程边实践,加上记录下来花了不少精力,但实践起来才发现教程也不会事无巨细,很多都需要自己去试,或者去看他提供的拓展资料。目前为止算是初步入门了Vim,但离掌握和熟练使用还有很长一段路。