跳到主要内容

第3章 打开和搜索文件

本章的目的是向您介绍如何在Vim中快速搜索,能够快速搜索是提高您的Vim工作效率的重要途径。当我解决了如何快速搜索文件这个问题后,我就决定改为完全使用Vim来工作。

本章划分为两个部分:一是如何不依赖插件搜索;二是使用fzf插件搜索。让我们开始吧!

打开和编辑文件

要在Vim中打开一个文件,您可以使用:edit

:edit file.txt

如果file.txt已经存在,就会打开file.txtbuffer。如果file.txt不存在,会创建一个新buffer名为file.txt

:edit命令支持使用<Tab>进行自动补全。比如,如果您的文件位于Rails应用控制器的用户控制器目录./app/controllers/users_controllers.rb内,您可以使用<Tab>对文件路径名进行快速扩展。

:edit a<Tab>c<Tab>u<Tab>

:edit可以接收通配符参数。*匹配当前目录下的任意文件。如果您只想查找当前目录下后缀名为.yml的文件:

:edit *.yml<Tab>

Vim将列出当前目录下所有.yml文件供您选择。

您可以使用**进行递归的搜索。如果您想查找当前项目文件夹下所有*.md文件,但您不知道在哪个目录,您可以这样做:

:edit **/*.md<Tab>

:edit可以用于运行netrw(Vim的内置文件浏览器)。使用方法是,给:edit一个目录参数而不是文件名就行了:

:edit .
:edit test/unit/

使用find命令搜索文件

您可以使用:find命令搜索文件。比如:

:find package.json
:find app/controllers/users_controller.rb

:find命令同样支持自动补全:

:find p<Tab>                " to find package.json
:find a<Tab>c<Tab>u<Tab> " to find app/controllers/users_controller.rb

您可能注意到:find:edit看起来很像。它们的区别是什么呢?

Find 和 Path

两者的区别在于,:find命令根据path选项配置的路径查找文件,而:edit不会。让我们了解一点关于path选项的知识。一旦您学会如何修改您的路径,:find命令能变成一个功能强大的搜索工具。先查看一下您的path是什么:

:set path?

默认情况下,您的path内容很可能是这样的:

path=.,/usr/include,,
  • . 意思是在当前文件所在目录下搜索。(译者注:注意不是命令行输入pwd返回的当前目录,而是 当前所打开的文件 所在的目录)
  • , means to search in the current directory.(译者注:此处貌似作者有点小错误,逗号,应该是表示路径之间的分割符。连续的两个,,(两个逗号之间为空)才表示当前目录)
  • /usr/include 表示在C编译器头文件目录下搜索。

前两个配置非常重要,第3个现在可以被忽略。您这里应该记住的是:您可以修改您自己的路径。让我们假设您的项目结构是这样的:

app/
assets/
controllers/
application_controller.rb
comments_controller.rb
users_controller.rb
...

如果您想从根目录跳到users_controller.rb,您将不得不经过好几层目录(按好几次<Tab>)。一般说来,当您处理一个framework时,90%的时间您都在某个特定的目录下。在这种情况下,您只关心如何用最少的按键跳到controllers/目录。那么path设置可以减少这个途程。

您只需要将app/controllers/添加到当前path选项。以下是操作步骤:

:set path+=app/controllers/

现在您的路径已经更新,当您输入:find u<Tab>时,Vim将会在app/controllers/目录内搜索所有以"u"开头的文件。

如果您有一个嵌套的目录controllers/,比如app/controllers/account/users_controller.rb,Vim就找不到users_controllers了。您必须改为添加:set path+=app/controllers/**,这样自动补全才会找到users_controller.rb。这太棒了!您现在可以只需要按1次键就可找到users controller。

您可能会想将整个项目文件夹添加到path中,这样当您按<Tab>,Vim将在所有文件夹内搜索您要找的文件,就像这样:

:set path+=$PWD/**

$PWD 表示的是当前工作目录。如果您尝试将整个项目路径加到path中,并希望让所有文件名可以用<Tab>补全,虽然对于小项目没问题,但如果您的项目中包含大量文件时,这会显著降低您的搜索速度。我建议仅仅将您最常访问的文件/目录添加到path。(译者注:不知道是不是因为系统环境不一样的原因,译者使用的是windows下的Vim8.2,$PWD 这个环境变量在译者的vim中不起作用,必须在vimrc文件中添加一句let $PWD=getcwd()才行)。

您可以将set path+={您需要添加的目录}添加到您的vimrc文件中。更新path仅花费几秒钟,但可以为您的工作节省很多时间。

使用Grep命令在文件中搜索

如果您想在文件内部查找(搜索文件中的词句),您可以使用grep。Vim有两个方法可以完成这个工作:

  • 内置grep (:vim。没错,就是:vim,它是:vimgrep的简写)。
  • 外部grep (:grep)。

让我们首先仔细看看内置grep。:vim有以下语法:

:vim /pattern/ file
  • /pattern/ 是您要搜索的内容的正则表达式。
  • file 是文件参数。您可以传入多个参数。Vim将在文件中搜索所有匹配正则表达式的内容。类似于:find,您可以传入*和**通配符。

比如,要在app/controllers/目录下所有ruby文件(.rb)中,查找所有的"breakfast"字符串:

:vim /breakfast/ app/controllers/**/*.rb

输入上面的命令后,您将会被导航到第一个结果。Vim的vim搜索命令使用quickfix进行处理。要查看所有搜索结果,运行:copen会打开一个quickfix窗口。下面有一些有用的quickfix命令,可以让您提高效率:

:copen        打开quickfix窗口
:cclose 关闭quickfix窗口
:cnext 跳到下一个错误
:cprevious 跳到前一个错误
:colder 跳到旧的错误列表
:cnewer 跳到新的错误列表

要了解更多关于quickfix的信息,使用:h quickfix查看帮助信息。

您可能注意到运行内置grep(:vim)命令时,如果匹配结果数量较多时系统速度会变慢。这是因为Vim将每一个搜索结果读入内存。Vim加载每一个匹配的文件就像它们被编辑一样。如果Vim查到大量文件,它将消耗很多内存。

让我们谈谈外置grep。默认情况下,它使用终端命令grep。要想在app/controllers/目录中搜索字符串"lunch",您可以这样做:

:grep -R "lunch" app/controllers/

注意这里不是使用/pattern/,它遵循的是终端grep的语法"pattern",它同样使用'quickfix'来显示所有的匹配结果。

Vim使用grepprg变量来决定运行:grep时,应该使用哪个外部程序。所以您并不是非得使用终端的grep命令。稍后我将为您演示如何改变外部grep程序的默认值。

用Netrw浏览文件

netrw是Vim的内置文件浏览器,当查看项目的目录结构时很有用。要运行netrw,您需要在您的.vimrc中做以下设置:

set nocp
filetype plugin on

由于netrw是一个很宽泛的话题,我将紧紧介绍它的基本用法,这应该已经足够了。您可以在启动Vim时运行netrw,只需要传给Vim一个目录参数(而不是文件参数)就行了。比如:

vim .
vim src/client/
vim app/controllers/

要想从Vim内部运行netrw,您可以使用:edit命令,传给他一个目录而不是文件名:

:edit .
:edit src/client/
:edit app/controllers/

也有其他方法,不需要传递目录参数就运行netrw窗口:

:Explore     从当前文件启动netrw。
:Sexplore Sex_Plore?不是开玩笑:),在顶部水平分割的窗口打开netrw。
:Vexplore 在左侧垂直分割的窗口打开netrw。

您可以使用Vim动作(motions,在后面的章节中将详细讲述)在netrw中导航。如果您要创建、删除、重命名文件或文件夹,下面有一些关于netrw的有用命令:

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

:h netrw 的信息非常复杂,如果您有时间可以看看。

如果您觉得 netrw 过于单调乏味,vim-vinegar是netrw的一个改进插件。如果您想找一个不同的文件浏览器,NERDTree 是一个很好的选择。去看看吧。

Fzf插件

您现在已经学会了如何使用Vim的内置工具去搜索文件,那么让我们学习一下如何用插件实现这些功能吧。

现代文本编辑器相比Vim,有一个功能设计得很好,那就是文件搜索和模糊搜索的简化。在本章的下半部分,我将向您演示如何使用fzf.vim插件,在Vim中轻松实现功能强大的搜索功能。

安装

首先,确保您下载了fzfripgrep。按照它们在github仓库上的指令一步步做。成功安装后,命令fzfrg应该就可以用了。

Ripgrep是一个类似grep(从名字上就看得出)的搜索工具。一般说来,它比grep要快,而且还有很多有用的特性。Fzf是一个多用途的命令行模糊搜索工具,您可以讲它与其他命令联合起来使用,包括ripgrep。联合起来后,它们成为一个功能强大的搜索工具。

Fzf默认并不使用ripgrep,所以我们需要设置FZF_DEFAULT_COMMAND变量告诉fzf使用ripgrep命令。在我的.zshrc(如果您用bash,应该是.bashrc)文件内,我有以下设置:

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

注意FZF_DEFAULT_OPTS变量中的-m。这个设置允许我们按下<Tab<Shift-Tab>后进行多重选择。如果仅想让fzf在Vim中能够工作,这个设置不是必须的,但我认为这是一个有用的设置。当您想在多个文件中执行搜索和替换,进行少量修改时,它会很方便。fzf命令可以接收很多标志,但我不会再这里讲。要想了解更多信息,可以查看fzf's 仓库,或者使用man fzf。要想让fzf使用ripgrep,您至少得有这个设置export FZF_DEFAULT_COMMAND='rg'

安装好了fzf和ripgrep后,让我们再安装fzf的插件。在这个例子中,我使用的是vim-plug插件管理器,当然您可以使用其他插件管理器。

将下列配置添加到您的.vimrc中。因为您需要使用fzf.vim插件。(同样是由fzf的作者在维护)

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

添加后,您需要打开vim,运行:PlugInstall。这条命令将会安装所有您在vimrc文件中定义了但尚未安装的插件。 在我的例子中,将会安装fzf.vimfzf

要了解更多关于此插件的信息,您可以查看fzf.vim 的仓库

Fzf的语法

要想高效的使用fzf,您首先得了解一些fzf的基础语法。幸运的是,这个列表比较短:

  • ^ 表示前缀精确匹配。要搜索一个以"welcome"开头的短语:^welcom
  • $ 表示后缀精确匹配。要搜索一个以"my friends"结尾的短语:friends$
  • ' 表示精确匹配。要搜索短语"welcom my friends":'welcom my friends
  • | 表示"或者"匹配。要搜索"friends"或"foes":friends | foes
  • ! 表示反向匹配。要搜索一个包含"welcome"但不包含"friends"的短语:welcome !friends

您可以混合起来使用。比如,^hello | ^welcome friends$将搜索以"welcome"或"hello"开头,并且以"friends"结束的短语。

查找文件

要想在Vim内使用fzf.vim插件搜索文件,您可以使用:Files方法。在Vim中运行:Files,您将看到fzf搜索提示符。

因为您将频繁地使用这个命令,最好建立一个键盘映射,我把它映射到Ctrl-f。在我的vimrc配置中,有这个设置:

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

在文件中查找

要想在文件内部搜索,您可以使用:Rg命令。

同样,因为您可能将频繁的使用这个命令,让我们给它一个键盘映射。我的映射在<Leader>f

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

其他搜索

Fzf.vim提供了许多其他命令。这里我不会一个个仔细讲,您可以去这里查看更多信息。

这是我的fzf键盘映射:

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>

将Grep替换为Rg

正如前面提到的,Vim有两种方法在文件内搜索::vim:grep。您可以使用grepprg这个关键字重新指定:grep使用的外部搜索工具。我将向您演示如何设置Vim,使得当运行:grep命令时,使用ripgrep代替终端的grep。

现在,让我们设置grepprg来使:grep使用ripgrep。将下列设置添加到您的vimrc:

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

上面的一些选项可以随意修改!要想了解更多关于这些选项的含义,请使用man rg了解详情。

当您更新grepprg选项后,现在当您运行:grep,它将实际运行rg --vimgrep --smart-case --follow而不是grep。如果您想使用ripgrep搜索"donut",您可以运行一条更简洁的命令:grep "donut",而不是:grep "donut" . -R

就像老的:grep一样,新的:grep同样使用quickfix窗口来显示结果。

您可能好奇,“很好,但我从没在Vim中使用过:grep,为什么我不能直接使用:Rg命令在文件中搜索呢?究竟什么时候我必须使用:grep?”。

这个问题问得很好。在Vim中,当您需要在多个文件中执行搜索和替换时,您可能必须使用:grep这个命令。我马上就会讲这个问题。

在多文件中搜索和替换

现代文本编辑器,比如VSCode中,在多个文件中搜索和替换一个字符串是很简单的事情。在这一节,我将向您演示如何在Vim中轻松实现这个。

第一个方法是在您的项目中替换 所有 的匹配短句。您得使用:grep命令。如果您想将所有"pizza"替换为"donut",下面是操作方法:

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

让我们来分析一下这条命令:

  1. :grep pizza使用ripgrep去搜索所有"pizza"(顺带说一句,就算您不给grepprg重新赋值让它使用ripgrep,这条命令依然有效,但您可能不得不使用:grep "pizza" . -R命令,而不是:grep "pizza")。
  2. :cfdo会在您的quickfix列表中所有文件里,执行您传递给它的命令。在这个例子中,您的命令是一条替换命令%s/pizza/donut/g。管道符号(|)是一个链接操作符。命令update在每个文件被替换后,立刻保存。在后面的章节中,我将深入介绍替换命令。

第二个方法是在您选择文件中执行搜索和替换。用这个方法,您可以手动选择您想执行搜索和替换的文件。下面是操作方法:

  1. 首先清空您的buffer。让您的buffer列表仅包含您所需要的文件,这一点很有必要。您可以重启Vim,也可以运行:%bd | e#命令(%bd关闭所有buffer,而e#打开您当前所在的文件)。
  2. 运行:Files
  3. 选择好您想搜索-替换的文件。要选择多个文件,使用<Tab><Shift-Tab>。当然,您必须使多文件标志(-m)位于FZF_DEFAULT_OPTS中。
  4. 运行:bufdo %s/pizza/donut/g | update。命令:bufdo %s/pizza/donut/g | update看起来和前面的:cfdo %s/pizza/donut/g | update很像,区别在于,(:cfdo)替换所有quickfix中的实体,而(:bufdo)替换所有buffer中的实体。

用聪明的方法学习搜索

在文本编辑时,搜索是一个很实用的技巧。学会在Vim中如何搜索,将显著提高您的文本编辑工作流程效率。

Fzf.vim插件就像一个游戏规则改变者。我无法想象使用Vim没有它的情景。当最开始使用Vim时,如果有一个好的搜索工具,我想是非常重要的。我看见很多人过渡到Vim时的艰难历程,就是因为Vim缺少了现代编辑器所拥有的一些关键功能特性,比如简单快捷且功能强大的搜索功能。我希望本章将帮助您更轻松地向Vim过渡。

您同时也看到了Vim的扩展性,即使用插件或外部程序扩展搜索功能的能力。将来,记住您想在Vim中拓展的功能。很有可能已经有人写好了相关插件,已经有现成的程序了。下一章,您将学习Vim中非常重要的主题:Vim语法。