mirror of
https://github.com/cheetahlou/CategoryResourceRepost.git
synced 2025-11-16 22:23:45 +08:00
mod
This commit is contained in:
270
极客时间专栏/Vim 实用技巧必知必会/基础篇/01|各平台下的 Vim 安装方法:上路前准备好你的宝马.md
Normal file
270
极客时间专栏/Vim 实用技巧必知必会/基础篇/01|各平台下的 Vim 安装方法:上路前准备好你的宝马.md
Normal file
@@ -0,0 +1,270 @@
|
||||
<audio id="audio" title="01|各平台下的 Vim 安装方法:上路前准备好你的宝马" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/da/75/da0dc32784aef0362c76fc127f4ca275.mp3"></audio>
|
||||
|
||||
你好,我是吴咏炜。
|
||||
|
||||
今天第一讲,我们先来讨论一下 Vim 在 Linux、macOS、Windows 系统下的安装和配置问题。
|
||||
|
||||
Vim 在 Linux 和 macOS 上一般是默认安装的,在 Windows 上不是。不过 Vim 的网站上提供了 Windows 下的安装包,自己安装也很容易。所以,今天的课程我不会手把手、一步步地讲,而是挑选一些重点。对于默认安装的情况,主要讨论的是版本老旧或功能不全的问题。对于其他情况,我则会给出一个基本指引,减少你走弯路的可能性。
|
||||
|
||||
好了,下面我们就分各个不同的平台,一一来看。
|
||||
|
||||
## Linux 下的安装
|
||||
|
||||
### Red Hat 和 CentOS 系列
|
||||
|
||||
在 Red Hat Linux 和 CentOS Linux 上,默认安装的 Vim 可能是一个最小功能的版本。虽然这个版本启动速度很快,但它缺少了很多对开发有用的功能,如语法加亮、Python 集成和图形界面。一般情况下,应至少安装更全功能版本的 Vim;如果能使用 X Window 的话,则应安装图形界面版本。
|
||||
|
||||
你可以通过下面的命令来查看已经安装的 Vim 版本:
|
||||
|
||||
```
|
||||
yum list installed | grep vim
|
||||
|
||||
```
|
||||
|
||||
如果输出只有下面这样的内容的话,就说明安装的 Vim 版本只有基本功能:
|
||||
|
||||
>
|
||||
`vim-minimal.x86_64 2:8.0.1763-13.el8 @System`
|
||||
|
||||
|
||||
此时,我建议使用 `sudo yum install vim-X11` 来安装图形界面的 Vim,或至少使用 `sudo yum install vim-enhanced` 来安装增强版本的 Vim(如果你不在这台机器上进行图形界面登录的话)。
|
||||
|
||||
只要你使用图形界面,一般而言,你都应该安装有图形界面支持的 Vim。总体而言,图形界面 Vim 的功能更丰富,并且即使你只在终端里使用 Vim,含图形界面支持的 Vim 会带剪贴板支持,跟整个图形环境的交互也就比较容易。当然,如果你只是远程通过 SSH 使用 Vim 的话,那确实图形界面支持就没有意义了。
|
||||
|
||||
### Debian 和 Ubuntu 系列
|
||||
|
||||
在 Debian、Ubuntu 等使用 apt 的 Linux 发行版上,Vim 同样有着不同功能版本的区别,而且选择更多。我们可能会看到:
|
||||
|
||||
- vim
|
||||
- vim-athena
|
||||
- vim-gnome
|
||||
- vim-gtk
|
||||
- vim-gtk3
|
||||
- vim-nox
|
||||
- vim-tiny
|
||||
|
||||
它们中有编译进最小功能的 Vim 包(vim-tiny),有较全功能的文本界面 Vim 包(vim-nox),有适用于老的 X-Window 界面的版本(vim-athena),有适用于 KDE 环境的 GTK2 版本(vim-gtk),等等。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/dc/94/dc83899514bd661b41d1e8a902d47294.png" alt="Fig1.1" title="一个估计你不会去用的古老界面的 Vim(Athena 界面)">
|
||||
|
||||
如果你不确定要装什么版本的话,那可以遵循我下面的建议:
|
||||
|
||||
- 如果你使用标准的 GNOME 桌面环境的话(大部分的情况),安装 vim-gtk3 或 vim-gnome。
|
||||
- 如果你使用 KDE 桌面的话,安装 vim-gtk。
|
||||
- 如果你只使用文本界面的话,安装 vim-nox。
|
||||
- 都不是?那你是个爱自己定制的家伙,也就不需要我告诉你该安装什么了。
|
||||
|
||||
你可以通过下面的命令来查看已经安装的 Vim 版本:
|
||||
|
||||
```
|
||||
apt list --installed | grep vim
|
||||
|
||||
```
|
||||
|
||||
我们先执行 `sudo apt update` 来确保更新环境,然后使用 `sudo apt install vim-gtk3` 安装 GTK3 版本的 Vim(或者其他你需要的版本)。如果你安装了图形界面的版本,不必单独再另外安装其他版本的 Vim,因为图形版本的 Vim 也是可以纯文本启动的。事实上,在 Ubuntu 上,`vim` 和 `gvim` 都是指向同一个应用程序的符号链接,且 `gvim` 的执行效果和 `vim -g` 相同。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/39/5a/39d5f8bef475c6b223cee68de8376f5a.png" alt="Fig1.2" title="在终端中运行 vim.gtk3,执行 :version">
|
||||
|
||||
### 手工编译
|
||||
|
||||
如果你用的 Linux 发行版较老的话,你可能会希望手工编译 Vim 来得到最新的版本。此时需要注意的是,Vim 有很多的编译配置选项,有些缺省是不打开的。对于这个课程来讲,我们会希望至少加上 Python 支持和图形界面支持。
|
||||
|
||||
你首先需要确保自己已经安装了所需的开发包(可以参考[这个网上的回答](https://superuser.com/a/749760/270697))。然后,我们可以使用下面的命令来配置 Vim 和编译(根据需要,“auto”也可以替换成“gtk3”等其他需要的数值):
|
||||
|
||||
```
|
||||
./configure --enable-pythoninterp \
|
||||
--enable-python3interp \
|
||||
--enable-gui=auto
|
||||
make -j
|
||||
sudo make install
|
||||
|
||||
```
|
||||
|
||||
如果上述步骤正常没有出错,Vim就被成功安装到 /usr/local 下了。你可以用 `which vim` 来检查系统是否会自动优先选择你的 vim:如果不是的话,你可能需要调整 PATH 里的顺序,或者设置别名来优先启动 /usr/local/bin/vim)。然后,你可以使用 `vim --version` 命令来输出 vim 的版本信息。我们希望能在输出里看到:
|
||||
|
||||
>
|
||||
<p>Huge version with … GUI<br>
|
||||
+python/dyn<br>
|
||||
+python3/dyn</p>
|
||||
|
||||
|
||||
目前 Python 2 已经停止支持,所以你最好可以确保上面的输出中包含“+python3”(很多 Vim 的插件已经开始要求 Python 3、不再支持 Python 2 了);没有“+python”(即 Python 2)则没什么关系(有没有“dyn”关系也不大)。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/f0/30/f05a0a02cbea3b6412790ab7a1aa6d30.png" alt="Fig1.3" title="我在 CentOS 7 上编译的 gvim">
|
||||
|
||||
好了,关于 Linux 环境下的Vim安装和配置要点我们就讲完了,接下来继续看在 macOS 上如何安装。
|
||||
|
||||
## macOS 下的安装
|
||||
|
||||
在 macOS 中一般已经内置了 vim,并提供了除图形界面外的较完整功能集。如果你希望使用图形界面,则需要自行安装 MacVim,一个跟现代 macOS 融合较好的独立 Vim 版本。安装 MacVim 有两种常用方式:
|
||||
|
||||
- 使用 Homebrew。我推荐你使用这种方式,这样的话,以后升级也会比较容易。
|
||||
- 使用 MacVim 的独立安装包。如果你之前没有在用 Homebrew 的话,或处于不方便使用 Homebrew 的网络环境中,这种方式也可以。
|
||||
|
||||
由于使用 Homebrew 已经足够简单,日后升级也非常方便,我个人觉得我们没必要自己去编译 MacVim。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/f4/75/f49cb907c0b34b9c2b8f7579de9d5075.png" alt="Fig1.4" title="MacVim 的界面">
|
||||
|
||||
### 使用 Homebrew 安装 MacVim
|
||||
|
||||
首先,如果你没有 Homebrew,那你需要先安装 Homebrew。安装信息可以在 [Homebrew 的主页](https://brew.sh/)上找到(这个网站是支持中文的)。
|
||||
|
||||
在安装了 Homebrew 之后,一般情况下,你需要修改你的 .bash_profile(如果使用 Bash 的话)、.zprofile(如果使用 Zsh 的话)或是相应的 shell 的配置文件,调整 PATH,把 /usr/local/bin 放到 /usr/bin 前面。我个人在 .bash_profile 里是这样配置的:
|
||||
|
||||
```
|
||||
if [[ $PATH != "$HOME/bin"* ]]; then
|
||||
PATH=~/bin:/usr/local/bin:/usr/local/sbin:`echo $PATH|sed -e "s!:$HOME/bin!!" -e 's!:/usr/local/bin!!'`
|
||||
fi
|
||||
|
||||
```
|
||||
|
||||
这样,可以确保个人的路径优先于 /usr/local,而 /usr/local 下的路径又优先于系统的路径。
|
||||
|
||||
如果你这样配置的话,那只要执行 `brew install macvim`,然后在等待安装完成之后,你用 `vim` 启动的就是 MacVim 了。缺省用 `vim` 运行的仍然是纯文本界面的 Vim,但跟 Linux 一样,你可以用 `vim -g` 或 `gvim`(还有仅用在 Mac 上的 `mvim`)来启动 Vim 的图形界面。
|
||||
|
||||
跟 Homebrew 里的其他软件一样,你以后要升级 MacVim 的话,只需要输入命令 `brew upgrade macvim` 即可。是不是很简单?这就是为什么我比较推荐这种安装方式,后期升级真的更容易。不过我下面还是会介绍下安装包的方式,以满足我们不同的应用需求。
|
||||
|
||||
### 使用安装包安装 MacVim
|
||||
|
||||
跟大部分的 Mac 软件一样,你也可以直接使用 DMG 安装包来安装 MacVim。目前可从以下网址下载 MacVim 的安装包:
|
||||
|
||||
[https://github.com/macvim-dev/macvim/releases](https://github.com/macvim-dev/macvim/releases)
|
||||
|
||||
等待下载完成后,双击下载的文件,然后会打开一个访达(Finder)窗口。你只需要把 MacVim 拖拽复制到应用程序文件夹即可。
|
||||
|
||||
在这种安装方式下,手工键入 `vim`、`gvim` 或 `mvim` 命令是无法启动 MacVim 的。你需要手工创建这些命令的符号链接(symlink)或别名(alias)才行。假设你的 MacVim 是直接安装在应用程序文件夹里的话,这些命令本身可以在 /Applications/MacVim.app/Contents/bin 文件夹里找到;使用下面的命令可以在你自己的 bin 目录下创建这些命令的符号链接:
|
||||
|
||||
```
|
||||
[ -d ~/bin ] || mkdir ~/bin
|
||||
ln -s /Applications/MacVim.app/Contents/bin/* ~/bin/
|
||||
|
||||
```
|
||||
|
||||
## Windows 下的安装
|
||||
|
||||
最后,我们来看在 Windows 下怎么安装。课程开头我提到了,Windows 上缺省是没有 Vim 的。我们可以从 Vim 的网站下载 Windows 下的安装包:
|
||||
|
||||
[https://www.vim.org/download.php#pc](https://www.vim.org/download.php#pc)
|
||||
|
||||
在 Linux 和 macOS 上,64 位应用程序已经成为主流。而与此不同的是,在 64 位Windows上,32 位应用程序仍然很常见。默认的 Vim 8 的安装包安装的仍然是一个 32 位的应用程序。不过,32 位的 Vim 也足够满足一般需求了,除非你需要编辑 2 GB 以上的大文件。
|
||||
|
||||
安装界面会有一个选择组件的步骤,如下图所示:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/3a/80/3a9291c280033a71f80d924467546580.png" alt="Fig1.5" title="Vim 的组件选择界面">
|
||||
|
||||
这个界面中,下面几项我们可以关注一下:
|
||||
|
||||
- “安装批处理文件”(Create .bat files):对于用 Vim 的开发者来说,通常命令行是刚需,所以我们一般需要勾上这项。
|
||||
- “创建图标”(Create icons for Vim):根据你自己的需要进行选择,通常我会去掉展开子项里的“桌面图标”(On the Desktop),不在桌面上创建 Vim 的图标。
|
||||
- “创建默认配置文件”(Create Default Config):去掉这项——我们马上会创建配置文件。
|
||||
- “安装多语言支持”(Native Language Support):这项功能使得 Vim 的菜单可以显示中文的命令,但实际上还是有点鸡肋,因为 Vim 的主要功能不是靠菜单驱动的,安装程序安装的帮助文件也只有英文版。所以,这项选和不选关系不大,你可以自由选择。
|
||||
|
||||
然后我们点“下一步”(Next),不需要修改安装目标文件夹,完成安装即可。
|
||||
|
||||
完成安装后,Vim 会缺省打开一个 README 文件。在这个窗口中,我们应当键入“`:e ~\_vimrc`”,回车键,然后把下面的内容粘贴进去(这些配置项的意义我们以后会讨论):
|
||||
|
||||
```
|
||||
set enc=utf-8
|
||||
set nocompatible
|
||||
source $VIMRUNTIME/vimrc_example.vim
|
||||
|
||||
```
|
||||
|
||||
然后键入“`ZZ`”(大写)存盘退出即可。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/07/17/07ebd8b7d535e516e8d68517a6338717.gif" alt="Fig1.6" title="创建基本的 _vimrc 配置文件">
|
||||
|
||||
注意由于历史上的文件系统限制,在 Windows 下 Vim 的配置文件名称是 _vimrc 而不是 .vimrc(虽然 Windows 命令行不支持像 Unix 一样用“~”代表用户的主目录,在 Vim 里我们仍然可以使用“~\_vimrc”或“~/_vimrc”这样的写法)。这是 Unix 和 Windows 下的 Vim 配置的区别之一。其他的主要区别是以下两点:
|
||||
|
||||
- 点打头的 Vim 文件都成了“_”打头,如 .viminfo 也成了 _viminfo
|
||||
- 点打头的 Vim 配置目录 .vim 在 Windows 下则成了 vimfiles
|
||||
|
||||
除此之外,Vim 的配置在 Windows 下和 Unix 下(如 Linux 和 macOS)并没有根本不同。Windows 上的主要麻烦在于,由于 Vim 的生态主要在 Unix 上,某些 Vim 的插件在 Windows 上安装配置需要花费更大的力气。但就一般的文本和程序编辑而言,Vim 在 Windows 下和 Linux 下没有本质的不同。甚至 Windows 下还有一个小小的优势:Unix 下虽然 Vim 可以编译成支持 Python 2 和 Python 3,但在 Vim 里一旦执行了 Python 2 的代码,就不能再执行 Python 3 的代码了;反之亦然。Windows 下则没有这个限制。
|
||||
|
||||
有没有注意到我只在 Windows 的安装部分讨论了配置?这是因为 Unix 下主流的缺省编码已经是 UTF-8 了,而 Vim 只能在内码是 UTF-8 的情况下才能处理多语言的文本。而我们有自己的配置文件,是为了确保启用一些最为基本的配置选项,来保证基本行为的一致性。
|
||||
|
||||
Windows 上可以把 Vim 配置成跟普通的编辑器行为差不多,包括支持 Ctrl-A 全选,选择内容后输入任何内容替换选择的内容,等等。但是,这种行为跟 Vim 的标准行为是冲突的。我们要学习 Vim,还是忘了这些 Windows 特有的功能为好,去学习掌握 Vim 的跨平台标准功能。上面的配置文件也同样没有启用 Windows 下的特有行为。
|
||||
|
||||
### Cygwin/MSYS2
|
||||
|
||||
Windows 有 [Cygwin](http://cygwin.org/) 和 [MSYS2](https://www.msys2.org/),可以提供类似于 Linux 的 POSIX shell。在这些环境里,Vim 都是标准组件,按这些环境的标准方式来安装 Vim 就行。如果你使用 [Git Bash](https://gitforwindows.org/) 的话,里面就直接包含了 MSYS2 的终端、Bash 和 Vim。唯一需要提一句的是,这些类 POSIX 环境里面的 Vim 配置应当参照 Linux 终端来,而不是 Windows 下的标准方式(也就是说,个人配置目录和配置文件是 .vim 和 .vimrc,而非 vimfiles 和 _vimrc)。我以后对这种情况就不再单独描述了。
|
||||
|
||||
### 远程使用 Vim
|
||||
|
||||
还有一种常用的环境恐怕是使用 mintty、PuTTY、SecureCRT 之类的软件在 Windows 上远程连接到 Linux 机器上。在这种情况下,需要特别注意的,是远程终端软件的远程字符集(如 PuTTY 中的“Windows > Translation > Remote character set”)应当设置成 UTF-8。这个设定跟具体的软件及其版本有关,我就不详细说明了;请自行查看你所使用的远程终端软件的设定和相关文档。
|
||||
|
||||
## 学习 Vim
|
||||
|
||||
上面我们讲解了 Vim 的安装。如果安装过程中遇到了什么问题,可以留言提问。接下来,我会给你提供一些 Vim 的学习资料,帮助你进入 Vim 的世界。你应该仔细看一下你所使用的平台上的 Vim 安装信息(其他平台的可以略过),并且应该自己打开 Vim 教程练习一遍(除非这些基础知识你都了解了)。键盘配置相关信息属于可选,可以根据自己的兴趣和需要决定是否了解一下。
|
||||
|
||||
### 中文帮助文件
|
||||
|
||||
Vim 内置了完整的英文帮助文件。如果你想要中文帮助文件的话,有个好消息是,有网友同步翻译了最新的帮助文件,而且安装过程在 Vim 8 (或将来的版本)里是非常简单的。以 Unix 下为例(Windows 下类似,但路径 .vim 需要修改为 vimfiles):
|
||||
|
||||
```
|
||||
cd ~/.vim
|
||||
mkdir -p pack/my/start
|
||||
git clone https://github.com/yianwillis/vimcdoc.git pack/my/start/vimcdoc
|
||||
|
||||
```
|
||||
|
||||
如果你不需要以后利用 Git 来快速升级文档的话, 也可以在这个 [Vim 中文文档计划](https://github.com/yianwillis/vimcdoc)的[下载页面](https://github.com/yianwillis/vimcdoc/releases)下载 tar 包,然后自行解压到 ~/.vim/pack/my/start 目录下(或 Windows 用户目录下的 vimfiles\pack\my\start 目录下)。
|
||||
|
||||
Windows 用户有一个简单的安装程序(当前为 [vimcdoc-2.3.0-setup-unicode.exe](https://github.com/yianwillis/vimcdoc/releases/download/v2.3.0/vimcdoc-2.3.0-setup-unicode.exe)),可以自动帮你完成中文帮助文件的安装任务。如果你的机器上没有 `git` 和 `tar` 可执行程序的话,那这个方式最简单。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/fe/a7/fe5d4b5cbf08c939feb8d453f919d1a7.png" alt="Fig1.7" title="中文 Vim 帮助">
|
||||
|
||||
### Vim 教程
|
||||
|
||||
Vim 在安装中自带了一个教程,可供快速入手使用。如果你对 Vim 的基本操作不熟的话,建议你完整学习一下,我也就不必多费笔墨介绍一些最基础的用法了。
|
||||
|
||||
Vim 教程支持多语言,可使用命令 `vimtutor` 来启动。如果启动的教程的语言不是你希望的,你可以使用环境变量 LANG 来设定希望的语言。比如,下面的命令可以在 Unix 环境中启动一个中文的 Vim 教程:
|
||||
|
||||
```
|
||||
LANG=zh_CN.UTF-8 vimtutor
|
||||
|
||||
```
|
||||
|
||||
Windows 下你可以在开始菜单里找到 Vim tutor。但我测试下来[它有一个问题](https://github.com/vim/vim/issues/5756)。虽然我提交的解决方法已经作为补丁(8.2.0412)合并,但目前(Vim 8.2)安装程序安装的文件多半仍然是有问题的,你会无法成功地创建一个 tutor 文件的副本供编辑使用。我建议手工创建一个这个教程的副本。可以在命令提示符下输入:
|
||||
|
||||
```
|
||||
vim --clean -c "e $VIMRUNTIME/tutor/tutor.zh_cn.utf-8" -c "w! TUTORCOPY" -c "q"
|
||||
|
||||
```
|
||||
|
||||
这样即可在当前目录下创建一个教程的副本。然后我们可以用 `gvim TUTORCOPY` 来打开这个副本进行学习。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/20/c5/208992f818376e6010066c4a544286c5.png" alt="Fig1.8" title="Vim 教程">
|
||||
|
||||
### 键盘重配置
|
||||
|
||||
最后,有些重度的 Vim 用户会重新配置键盘,把使用频度较低的大写锁定键(Caps Lock)重新映射成 Esc 或 Ctrl 键。对于这个问题,如果你需要的话,网上很容易就能找到攻略,如:
|
||||
|
||||
- [Linux下将大写锁定键(CapsLock)键映射为Ctrl键(Ubuntu, Manjaro,CentOS)](https://blog.csdn.net/daerzei/article/details/89414610)
|
||||
- [mac book更改caps lock键为esc键/ctrl键](https://blog.csdn.net/tbestcc/article/details/52287622)
|
||||
- [windows交换大写锁定键与ESC键(注册表修改)](https://blog.csdn.net/P_LarT/article/details/72829425)
|
||||
- [在任何操作系统上,如何禁用或者重新分配 Caps Lock键](https://www.kutu66.com/Mac/article_11233)
|
||||
|
||||
这当然是一件非常个人化的事情,而且有一个风险,你一旦跑到别人的机器上操作,你的“肌肉记忆”可能会让你常常按错键。鉴于你目前可能只是个 Vim 的初学者,现在不一定需要这么去做。等到你觉得按 Esc 太麻烦了,再想起这个可能性去修改键盘配置也来得及。
|
||||
|
||||
## 内容小结
|
||||
|
||||
今天我们讨论了 Vim 在常见平台上的安装过程。顺便说一句,以后在牵涉到环境问题时,我一般也会以上面提到的几种典型情况为例来进行讲解:
|
||||
|
||||
- Linux(CentOS 和 Ubuntu)
|
||||
- macOS
|
||||
- Windows
|
||||
|
||||
你可能看着多个平台的安装过程有点晕,这却是我的实际使用环境了——我就是在各个平台下都安装、配置、使用着 Vim 的,这也就是 Vim 的全平台、跨平台优势了。
|
||||
|
||||
当然,必须得承认,Vim 还是最适合类 Unix 环境,它的生态系统也是在类 Unix 环境下最好。鉴于在 Windows 下已经越来越容易接触到类 Unix 环境(像 Git Bash、Docker 和 Windows Subsystem for Linux),服务器开发上 Linux 也已经成了主流,在 Windows 上熟悉 Vim 的完整环境对你也应该是件好事——尤其如果你是做服务器或嵌入式开发的话。
|
||||
|
||||
下一讲,我们就会进一步学习一下 Vim 的基本概念和配置。
|
||||
|
||||
## 课后练习
|
||||
|
||||
如果你之前不常使用 Vim ,请务必花点时间看一下 Vim 教程。在下一讲开始时,我将会假设你已经掌握了 Vim 教程里的基本用法。
|
||||
|
||||
当然,如果有任何问题的话,可以在讨论区留言和我进行交流。
|
||||
|
||||
我是吴咏炜,我们下一讲再见。
|
||||
323
极客时间专栏/Vim 实用技巧必知必会/基础篇/02|基本概念和基础命令:应对简单的编辑任务.md
Normal file
323
极客时间专栏/Vim 实用技巧必知必会/基础篇/02|基本概念和基础命令:应对简单的编辑任务.md
Normal file
@@ -0,0 +1,323 @@
|
||||
<audio id="audio" title="02|基本概念和基础命令:应对简单的编辑任务" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/45/18/45cf6d0e1a9e302b2f467c0771890f18.mp3"></audio>
|
||||
|
||||
你好,我是吴咏炜。
|
||||
|
||||
这一讲,我们会讨论 Vim 的基本概念和配置。先强调一下,请务必确保你在学习这一讲之前,已经通过 Vim 教程熟悉了 Vim 的基本用法。
|
||||
|
||||
## Vim 教程的内容概要
|
||||
|
||||
上节课我给你留的作业,就是花时间学习一下 Vim 教程,下面我们就来检验一下。只有你自己先对照着教程操作了一遍,今天我再带着你过一遍里面的基本概念和配置,你才能查漏补缺,发现自己遇到的问题,明确自己需要多加练习的地方。
|
||||
|
||||
好,现在请查看下面的键盘图。简单说明一下,这张图上展示了一个键盘。图中的“•”表示,单个字母不是完整的命令,必须再有进一步的输入。比如,单个“g”没有意义,而“gg”表示跳转到文件开头。(对于命令后面明确跟一个动作的,如“c”,我们不使用“•”。)一个键最多有三排内容:最底下是直接按键的结果,中间是按下 Shift 的结果(变大写),上面偏右的小字是按下 Ctrl 的结果。我们还用了一些特殊符号来表示操作的位置,如果你已经了解了这些命令的功能,你也自然就明白它们的意义了。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/76/a1/76910d1a16ed737c42078dd1255124a1.png" alt="Fig2.1" title="Vim 命令速查(教程版)">
|
||||
|
||||
请检查一下有颜色的那些键,看看你是否有任何不熟悉的地方。如果看下来有让你感到陌生的内容,请复习 Vim 教程。
|
||||
|
||||
这张图里没有写出 Vim 的命令行命令。你现在应该已经掌握了以下这些:
|
||||
|
||||
- “:q!”:退出 Vim
|
||||
- “:wq”:存盘退出
|
||||
- “:s”:执行替换
|
||||
- “:!”:执行外部命令
|
||||
- “:edit”(一般缩写为 “:e”):编辑文件
|
||||
- “:w”:写文件
|
||||
- “:r”:读文件
|
||||
- “:help”:查看帮助
|
||||
- 使用键 Ctrl-D 和 Tab 来进行命令行补全
|
||||
|
||||
同样,如果你发现上面列举的命令有你不熟悉的,也请重新打开 Vim 教程复习一下——这些属于 Vim 的最基本功能,一定要能熟练运用才行。
|
||||
|
||||
## Vim 的模式
|
||||
|
||||
接下来我们进入本讲的正题,讲述 Vim 的四种主要模式、键描述的体例和 Vim 需要的基本配置选项。掌握了这些内容之后,我们就能应对基本的编辑任务了。下面我们一一来看。
|
||||
|
||||
Vim 最特别的地方就是它的模式了。与其他大部分编辑器不同,进入 Vim 后,缺省状态下键入的字符并不会插入到所编辑的文件之中。 Vim 的模式(mode,可以简单地理解为“状态”)是它的麻烦所在,但同时也是它的威力所在。
|
||||
|
||||
我们需要知道,Vim 有以下四种主要模式:
|
||||
|
||||
- 正常(normal)模式(也称为普通模式),缺省的编辑模式;如果不加特殊说明,一般提到的命令都直接在正常模式下输入;在任何其他模式中,都可以通过键盘上的 Esc 键回到正常模式。
|
||||
- 插入(insert)模式,输入文本时使用;比如在正常模式下键入 i(insert)或 a(append)即可进入插入模式。
|
||||
- 可视(visual)模式,用于选定文本块;教程中已经提到可以用键 v(小写)来按字符选定,Vim 里也提供其他不同的选定方法,包括按行和按列块。
|
||||
- 命令行(command-line)模式,用于执行较长、较复杂的命令;在正常模式下键入冒号(:)即可进入该模式;使用斜杠(/)和问号(?)开始搜索也算作命令行模式。命令行模式下的命令要输入回车键(Enter)才算完成。
|
||||
|
||||
此外,Vim 也有个选择(select)模式,与普通的 Windows 编辑器行为较为接近,选择内容后再输入任何内容,将会替换选择的内容。在以可视模式和选择模式之一选定文本块之后,可以使用 Ctrl-G 切换到另一模式。这个模式主要是为了模拟 Windows 编辑器的行为,并不是 Vim 的主要用法,使用它反而会给 Vim 里的自动化带来麻烦,所以我们也就不多作介绍了。
|
||||
|
||||
关于 Vim 的模式,我们重点掌握正常模式就可以了,刚刚也说过,Vim 里的大部分操作会在正常模式下完成。如果你做编辑工作时有超过几秒的停顿,就应当考虑按下 Esc 键,回到正常模式。记住,正常模式就是正常情况下你应当处于的模式。😄
|
||||
|
||||
## Vim 的键描述体例
|
||||
|
||||
清楚了 Vim 模式之后,我们来对 Vim 里的按键作一下清晰的体例描述,毕竟,Vim 里的键真的有点多。
|
||||
|
||||
从现在开始,我会使用 Vim 里的标准键描述方式来讲解。根据 Vim 的一般习惯,我们使用尖括号来描述特殊的输入序列。下面我会提供一个列表,给出常用键的表示方式及在动图中的显示方式。这部分内容不需要记住,你用的时候作为参考就行。
|
||||
|
||||
- `<Esc>` 表示 Esc 键;显示为“⎋”
|
||||
- `<CR>` 表示回车键;显示为“↩”
|
||||
- `<Space>` 表示空格键;显示为“␣”
|
||||
- `<Tab>` 表示 Tab 键;显示为“⇥”
|
||||
- `<BS>` 表示退格键;显示为“⌫”
|
||||
- `<Del>` 表示删除键;显示为“⌦”
|
||||
- `<lt>` 表示 < 键;显示为“<”
|
||||
- `<Up>` 表示光标上移键;显示为“⇡”
|
||||
- `<Down>` 表示光标下移键;显示为“⇣”
|
||||
- `<Left>` 表示光标左移键;显示为“⇠”
|
||||
- `<Right>` 表示光标右移键;显示为“⇢”
|
||||
- `<PageUp>` 表示 Page Up 键;显示为“⇞”
|
||||
- `<PageDown>` 表示 Page Down 键;显示为“⇟”
|
||||
- `<Home>` 表示 Home 键;显示为“↖”
|
||||
- `<End>` 表示 End 键;显示为“↘”
|
||||
- `<F1>` - `<F12>` 表示功能键 1 到 12;显示为“F1”到“F12”
|
||||
- `<S-…>` Shift 组合键;显示为“⇧”(较少使用,因为我们需要写 `!` 而不是 `<S-1>`;和特殊键组合时仍然有用)
|
||||
- `<C-…>` Control 组合键;显示为“⌃”
|
||||
- `<M-…>` Alt 组合键;显示为“⌥”(对于大部分用户,它的原始键名 Meta 应该只具有历史意义)
|
||||
- `<D-…>` Command 组合键;显示为“⌘”(Mac 键盘)
|
||||
|
||||
现在回到前面的模式部分,我们提到的 Esc、Enter、v、V 和 Ctrl-V,按我们现在的描述惯例,以后就会写成 `<Esc>`、`<CR>`、`v`、`V` 和 `<C-V>`。这也是以后在 Vim 里对键进行重映射的写法——如果你还不了解重映射是什么也没关系,我们很快就会讨论到。
|
||||
|
||||
这里我要强调一下,对“`<`”的特殊解释仅在描述输入时生效。在描述命令行和代码时,我们写“`<CR>`”仍表示四个字符,而非回车键。特别是,如果我们描述的命令行首是“:”,表示这是一个输入 `:` 开始的 Vim 命令行模式命令(以回车键结束);如果行首是“/”或“?”,表示这是一个输入 `/` 或 `?` 开始的搜索命令(以回车键结束);如果行首是“`$`”,表示这是一个在 shell 命令行上输入的命令(以回车键结束),“`$`”(和后面的空格)不是命令的一部分,通常后续行也不是命令的一部分,除非行尾有“\”或“^”字符,或行首有“`$`”字符。
|
||||
|
||||
也就是说,下面的命令是在 Vim 里输入“`:set ft?<CR>`”(用来显示当前编辑文件的文件类型):
|
||||
|
||||
```
|
||||
:set ft?
|
||||
|
||||
```
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/41/81/411b4byyfbd7717f2b52246ccyy5b481.gif" alt="Fig2.2" title="Vim 命令示例">
|
||||
|
||||
下面的命令则是在 shell 里输入“`which vim<CR>`”(用来检查 `vim` 命令的位置):
|
||||
|
||||
```
|
||||
$ which vim
|
||||
/usr/bin/vim
|
||||
|
||||
```
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/80/f3/80c1875197457b86f94f1216e334dcf3.gif" alt="Fig2.3" title="Shell 命令示例">
|
||||
|
||||
此外,当我用“[`:help`](https://yianwillis.github.io/vimcdoc/doc/help.html)”描述帮助命令时,你不仅可以在 Vim 里输入这个命令来得到帮助,也可以点击这个帮助的链接,直接在线查看相应的中文帮助页面。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/f5/cc/f58469ab7dbf281bb72896a7a7d8e0cc.jpeg" alt="Fig2.4" title="Vim 的内置帮助功能">
|
||||
|
||||
这节内容不需要死记。建议使用“收藏”功能,这样,你可以在以后碰到不认识的符号标记的时候,返回来查看这一节的内容。
|
||||
|
||||
## Vim 的选项和配置
|
||||
|
||||
了解了 Vim 模式和键描述,我们对 Vim 的认识又多了一些,第一步的学习成就达成。要想更好地使用 Vim,下一个关键点就是配置了,接下来我就带你看看 Vim 配置都有哪些需要注意的点。
|
||||
|
||||
作为一个可以越用越顺手的应用程序,Vim 是需要配置的。我们才刚开始学习,所以目前我们的配置文件是相当简单的,但随着课程的进展和你使用 Vim 越来越多,你的 Vim 配置文件必然会越变越复杂。我们今天就先来做一些初步的讨论,看看能实际使用的一个最基本 Vim 配置文件是什么样子。
|
||||
|
||||
我们上节课已经讨论过,根据 Unix 下的惯例,Vim 的配置文件放在用户的主目录下,文件名通常是 .vimrc;而它在 Windows 下名字是 _vimrc。我们前面给出最基本的配置文件是这个样子的:
|
||||
|
||||
```
|
||||
set enc=utf-8
|
||||
set nocompatible
|
||||
source $VIMRUNTIME/vimrc_example.vim
|
||||
|
||||
```
|
||||
|
||||
如果你熟悉 shell 语法,你肯定能看到不少熟悉的影子在里面。这三行完成了下列功能:
|
||||
|
||||
- 设置编辑文件的内码是 UTF-8(非所有平台缺省,但为编辑多语言文件所必需)
|
||||
- 设置 Vim 不需要和 vi 兼容(仅为万一起见,目前大部分情况下这是缺省情况)
|
||||
- 导入 Vim 的示例配置(这会打开一些有用的选项,如语法加亮、搜索加亮、命令历史、记住上次的文件位置,等等)
|
||||
|
||||
对于现代 Unix 系统上的 Vim 8,实际上只需要最后一句就足够了。对于现代 Windows 系统上的 Vim 8,中间的这句 `set nocompatible` 也可以删除。如果你在较老的 Vim 版本上进行配置,那么把这三行全放进去会比较安全。
|
||||
|
||||
接下来,我会讲一些基本的配置项,保证你的日常工作流顺畅。它们是:备份和跨会话撤销、鼠标支持、中文支持及图形界面的字体支持。除了字体支持主要牵涉到美观性,其他三项都是对编辑至关重要的基本功能。我们一一来看。
|
||||
|
||||
### 备份和撤销文件
|
||||
|
||||
上面的基本设置会产生一个有人喜欢、但也有很多人感到困惑的结果:你修改文件时会出现结尾为“~”的文件,有文件名后面直接加“~”的,还有前面加“.”后面加“.un~”的。这是因为在示例配置里,Vim 自动设置了下面两个选项:
|
||||
|
||||
```
|
||||
set backup
|
||||
set undofile
|
||||
|
||||
```
|
||||
|
||||
前一个选项使得我们每次编辑会保留上一次的备份文件,后一个选项使得 Vim 在重新打开一个文件时,仍然能够撤销之前的编辑(undo),这就会产生一个保留编辑历史的“撤销文件”(undofile)了。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/a2/5e/a2ae45ebdf188cfbe11e9dfd0fb1245e.gif" alt="Fig2.5" title="有撤销文件时,再次打开文件时仍然可以撤销上次的编辑">
|
||||
|
||||
我的通常做法是,不产生备份文件,但保留跨会话撤销编辑的能力;因为有了撤销文件,备份其实也就没有必要了。同时,把撤销文件放在用户个人的特定目录下,既保证了安全,又免去了其他目录下出现不必要文件的麻烦。
|
||||
|
||||
要达到这个目的,我在 Linux/macOS 下会这么写:
|
||||
|
||||
```
|
||||
set nobackup
|
||||
set undodir=~/.vim/undodir
|
||||
|
||||
```
|
||||
|
||||
在 Windows 下这么写:
|
||||
|
||||
```
|
||||
set nobackup
|
||||
set undodir=~\vimfiles\undodir
|
||||
|
||||
```
|
||||
|
||||
无论哪种环境,你都需要创建这个目录。我们可以用下面的命令来让 Vim 在启动时自动创建这个目录:
|
||||
|
||||
```
|
||||
if !isdirectory(&undodir)
|
||||
call mkdir(&undodir, 'p', 0700)
|
||||
endif
|
||||
|
||||
```
|
||||
|
||||
如果我告诉你, `&undodir` 代表 `undodir` 这个选项的值,那么其他代码的基本作用,相信你也一定能看出来了吧?我们暂时就不做进一步分析了。如果你好奇的话,可以提前看一下下面各项的 Vim 帮助文档:
|
||||
|
||||
- [`:help isdirectory()`](https://yianwillis.github.io/vimcdoc/doc/eval.html#isdirectory%28%29)
|
||||
- [`:help mkdir()`](https://yianwillis.github.io/vimcdoc/doc/eval.html#mkdir%28%29)
|
||||
- [`:help :call`](https://yianwillis.github.io/vimcdoc/doc/eval.html#:call)
|
||||
|
||||
这个跨会话撤销的能力,我还真不知道其他哪个编辑器也有。更妙的是,Vim 还有撤销树的概念,可以帮助你回到任一历史状态。这个我们以后会和相关的插件一起讨论。
|
||||
|
||||
### 鼠标支持
|
||||
|
||||
我不知道你会不会像某些资深 Vim 用户一样,只用键盘不用鼠标。我反正是做不到的,也没有动力去那样做——毕竟,浪费计算机界一项伟大的发明并不那么有必要😂。手一直在键盘上的本位排(home row)打字当然会更快,但一个程序员看代码的时间比写代码的时间要多得多,而在非线性的跳转任务上,鼠标比键盘更加快,也更加有效。
|
||||
|
||||
在 Vim 的终端使用场景下,鼠标的选择有一定的歧义:你希望是使用 Vim 的可视模式选择内容,并且只能在 Vim 里使用呢,还是产生 Vim 外的操作系统的文本选择,用于跟其他应用程序的交互呢?这是一个基本的使用问题,两种情况都可能发生,都需要照顾。
|
||||
|
||||
如果你使用 xterm 兼容终端的话,通常的建议是:
|
||||
|
||||
- 在不按下修饰键时,鼠标选择产生 Vim 内部的可视选择。
|
||||
- 在按下 Shift 时,鼠标选择产生操作系统的文本选择。
|
||||
|
||||
对于不兼容 xterm、不支持对 Shift 键做这样特殊处理的终端,我们一般会采用一种“绕过”方式,让 Vim 在某种情况下暂时不接管鼠标事件。通常的选择是在命令行模式下不使用鼠标。下面,我们就分这两种情况来配置。
|
||||
|
||||
虽然最新的 Vim 缺省配置文件(示例配置文件会包含缺省配置),在大部分情况下已经可以自动设置合适的鼠标选项了,不过为照顾我们课程的三种不同平台,我们还是手工设置一下:
|
||||
|
||||
```
|
||||
if has('mouse')
|
||||
if has('gui_running') || (&term =~ 'xterm' && !has('mac'))
|
||||
set mouse=a
|
||||
else
|
||||
set mouse=nvi
|
||||
endif
|
||||
endif
|
||||
|
||||
```
|
||||
|
||||
上面代码说的是,如果 Vim 有鼠标支持的话,那在以下任一条件满足时:
|
||||
|
||||
- 图形界面正在运行
|
||||
- 终端是 xterm 兼容,并且不是 Mac(Mac 上的终端声称自己是 xterm,但行为并不完全相同)
|
||||
|
||||
我们将启用完全的鼠标支持(mouse=a)。特别是,此时鼠标拖拽就会在 Vim 里使用可视模式选择内容(只能在 Vim 里使用)。而当用户按下 Shift 键时,窗口系统接管鼠标事件,用户可以使用鼠标复制 Vim 窗口里的内容供其他应用程序使用。
|
||||
|
||||
否则(非图形界面的的终端,且终端类型不是 xterm),就只在正常模式(n)、可视模式(v)、插入模式(i)中使用鼠标。这意味着,当用户按下 `:` 键进入命令行模式时,Vim 将不对鼠标进行响应,这时,用户就可以使用鼠标复制 Vim 窗口里的内容到其他应用程序里去了。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/e6/df/e6d582405e3c7e4cbf26f3821yy242df.gif" alt="Fig2.6" title="可视模式的选取和按“:”后的选取">
|
||||
|
||||
非 xterm 的鼠标支持在 macOS 和 Windows 下都有效。但在 Windows 下需要注意的一点是,如果使用非图形界面的 Vim 的话,应当在命令提示符(Command Prompt)的属性里关闭“快速编辑模式”(QuickEdit Mode),否则 Vim 在运行时将无法对鼠标事件进行响应。
|
||||
|
||||
鉴于命令提示符的行为有很多怪异和不一致之处,强烈建议你在 Windows 下,要么使用图形界面的 Vim,要么使用 Cygwin/MSYS2 里、运行在 mintty 下的 Vim。
|
||||
|
||||
### 中文支持
|
||||
|
||||
接下来我和你讲讲中文支持的问题。如果你一直在 UTF-8 下使用中文的话,那这一小节的内容可以跳过。对于大部分在 Unix 下工作的人员,应该是这样的情况。而如果你在 Windows 上工作,或者有需要跟别人交换 GB2312、GBK、GB18030 编码的文本文件,那这部分的内容还是需要看一下的。
|
||||
|
||||
完整的 Unicode 历史和原理可以讲上整整一讲,但从实用的角度,我们就简化成下面几条吧:
|
||||
|
||||
- 整个世界基本上在向 UTF-8 编码靠拢。
|
||||
- 微软由于历史原因,内部使用 UTF-16;UTF-16 可以跟 UTF-8 无损转换。
|
||||
- GB2312、GBK、GB18030 是一系列向后兼容的中文标准编码方式,GB2312 编码的文件是合法的 GBK 文件,GBK 编码的文件是合法的 GB18030 文件。但除了 GB18030,都不能做到跟 UTF-8 无损转换;目前非 UTF-8 的简体中文文本基本上都用 GBK/GB18030 编码(繁体中文文本则以 Big5 居多)。鉴于 GB18030 是国家标准,其他两种编码也和 GB18030 兼容,我们就重点讲如何在 Vim 中支持 GB18030 了。
|
||||
|
||||
举一个具体的例子,“你好😄”这个字符串,在 UTF-8 编码下是下面 10 个字节(我按字符进行了分组):
|
||||
|
||||
`e4bda0 e5a5bd f09f9884`
|
||||
|
||||
如果使用 GB18030 编码(GB2312/GBK 不能支持表情字符)的话,会编码成 8 个字节:
|
||||
|
||||
`c4e3 bac3 9439fd30`
|
||||
|
||||
这么看起来,GB18030 处理中文在存储效率上是优势的。但它也有缺点:
|
||||
|
||||
- GBK 外的 Unicode 字符一般需要四字节编码(非中文情况会劣化)
|
||||
- GBK 外的 Unicode 字符跟 Unicode 码点需要查表才能转换(UTF-8 则可以用非常简单的条件判断、移位、与、或操作来转换)
|
||||
- 一旦出现文件中有单字节发生损毁,后续的所有中文字符都可能发生紊乱(而 UTF-8 可以在一个字符之后恢复)
|
||||
|
||||
因此,GB18030 在国际化的软件中不会作为内码来使用,只会是读取/写入文件时使用的转换编码。我们要让 Vim 支持 GB18030 也同样是如此。由于 UTF-8 编码是有明显规律的,并非任意文件都能成功地当成 UTF-8 来解码,我们一般使用的解码顺序是:
|
||||
|
||||
- 首先,检查文件是不是有 Unicode 的 BOM(字节顺序标记)字符,有的话按照 BOM 字符来转换文件内容。
|
||||
- 其次,检查文件能不能当作 UTF-8 来解码;如果可以,就当作 UTF-8 来解释。
|
||||
- 否则,尝试用 GB18030 来解码;如果能成功,就当作 GB18030 来转换文件内容。
|
||||
- 最后,如果上面的解码都不成功,就按 Latin1 字符集来解码;由于这是单字节的编码,转换必定成功。
|
||||
|
||||
事实上,Vim 缺省差不多就是按这样的顺序,但第三步使用何种编码跟系统配置有关。如果你明确需要处理中文,那在配置文件里最好明确写下下面的选项设定:
|
||||
|
||||
```
|
||||
set fileencodings=ucs-bom,utf-8,gb18030,latin1
|
||||
|
||||
```
|
||||
|
||||
### 图形界面的字体配置
|
||||
|
||||
图形界面的 Vim 可以自行配置使用的字体,但在大部分环境里,这只是起到美化作用,而非必需项。不过,对于高分辨率屏幕的 Windows,这是一个必需项:Vim 在 Windows 下缺省使用的不是 TrueType 字体,不进行配置的话,字体会小得没法看。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/0a/98/0a8f9854d5d4ca317b10e9b1753f3c98.png" alt="Fig2.7" title="高分辨率屏下的 Windows 图形界面 Vim">
|
||||
|
||||
在 Windows 的缺省字体里,一般而言,Consolas 和 Courier New 还比较合适。以 Courier New 为例,在 _vimrc 里可以这样配置(Windows 上的基本写法是字体名称加冒号、“h”加字号;用“_”取代空格,否则空格需要用“\”转义):
|
||||
|
||||
```
|
||||
if has('gui_running')
|
||||
set guifont=Courier_New:h10
|
||||
endif
|
||||
|
||||
```
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/b9/82/b9c3a28656a45260a94b4099eab96282.png" alt="Fig2.8" title="设置了 10 磅 Consolas 字体的 Vim">
|
||||
|
||||
字体名称如何写是件平台相关的事(可参见帮助文档“[`:help gui-font`](https://yianwillis.github.io/vimcdoc/doc/gui.html#gui-font)”)。如果你不确定怎么写出你需要的字体配置,或者你怎么写都写不对的话,可以先使用图形界面的菜单来选择(通常是“编辑 > 选择字体”;在 MacVim 里是“Edit > Font > Show Fonts”),然后使用命令“`:set guifont?`”来查看。
|
||||
|
||||
注意,Vim 在设置选项时,空格需要用“\”进行转义。比如,如果我们要在 Ubuntu 下把字体设成 10 磅的 DejaVu Sans Mono,就需要写:
|
||||
|
||||
```
|
||||
" Linux 和 Windows 不同,不能用 '_' 取代空格
|
||||
set guifont=DejaVu\ Sans\ Mono\ 10
|
||||
|
||||
```
|
||||
|
||||
此外,宽字符字体(对我们来讲,就是中文字体了)是可以单独配置的。这可能就更是一件仁者见仁、智者见智的事了。对于纯中文的操作系统,这一般反而是不需要配置的;但如果你的语言设定里,中文不是第一选择的话,就有可能在显示中文时出现操作系统误用日文字体的情况。这时你会想要手工选择一个中文字体,比如在 Ubuntu 下,可以用:
|
||||
|
||||
```
|
||||
set guifontwide=Noto\ Sans\ Mono\ CJK\ SC\ 11
|
||||
|
||||
```
|
||||
|
||||
注意,在不同的中英文字体搭配时,并不需要字号相同。事实上,在 Windows 和 Linux 上我通常都是使用不同字号的中英文字体的。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/07/b5/0794053ac4fa28bf9595bfee8a202cb5.gif" alt="Fig2.9" title="Ubuntu 下的 gvim 设置中文字体">
|
||||
|
||||
在上面的动图中,你可以观察到设了中文字体之后,不仅中文字变大,更美观了,“将”、“适”、“关”、“复”、“启”等字的字形也同时发生了变化。
|
||||
|
||||
由于字体在各平台上差异较大,字体配置我就不写到 Vim 的参考配置中去了,只把如何选择和配置的方法写出来供你参考。
|
||||
|
||||
## 内容小结
|
||||
|
||||
好了,这一讲我就讲到这里,我们来做个内容小结。
|
||||
|
||||
今天我给出了一张键盘图,带你复习了 Vim 教程的内容,这里我要再强调一遍,这部分的内容如果你还有不熟悉的,一定要再去学习一下 Vim 教程,这段时间我们一定要多花点时间和精力来练习,把这一步跨过去。
|
||||
|
||||
掌握了 Vim 教程里的基础信息还远远不够,我们还得了解 Vim 的四种主要模式,你只要记住最重要的就是正常模式就可以了。
|
||||
|
||||
最后我带你学习了 Vim 的几个基本配置选项,包括对撤销、鼠标、中文和字体的支持,来满足最基本的编辑需要。最终的 Vim 配置文件可以在 GitHub 上找到:
|
||||
|
||||
[https://github.com/adah1972/geek_time_vim](https://github.com/adah1972/geek_time_vim)
|
||||
|
||||
关于这个配置文件,我这里做个备注说明:主(master)分支可以用在类 Unix 平台上,windows 分支则用在 Windows 上。适用于今天这一讲的内容标签是 `l2-unix` 和 `l2-windows`:你可以用 `git checkout l2-unix` 或 `git checkout l2-windows` 来得到相应平台对应本讲的配置文件。
|
||||
|
||||
好了,掌握了今天的内容,你就可以用 Vim 做最基本的编辑了。
|
||||
|
||||
## 课后练习
|
||||
|
||||
请使用本讲的配置文件,并尝试以下操作:
|
||||
|
||||
- 退出 Vim 然后重新打开文件,仍然可以撤销上次的编辑。
|
||||
- 使用终端的 Vim,在终端里用鼠标复制 Vim 里的文本到另外一个文本编辑器中(仅 Unix 下,可选)。
|
||||
- 在 Vim 中使用“`:help`”命令(大部分环境下也可以使用 `<F1>` 功能键),尝试查看命令说明,以及使用键盘和鼠标在帮助主题中跳转。
|
||||
|
||||
如果遇到任何问题,欢迎留言和我讨论。我们下一讲见。
|
||||
162
极客时间专栏/Vim 实用技巧必知必会/基础篇/03|更多常用命令:应对稍复杂的编辑任务.md
Normal file
162
极客时间专栏/Vim 实用技巧必知必会/基础篇/03|更多常用命令:应对稍复杂的编辑任务.md
Normal file
@@ -0,0 +1,162 @@
|
||||
<audio id="audio" title="03|更多常用命令:应对稍复杂的编辑任务" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/93/8b/93ab75041eb1a396fd218c5f19b5d78b.mp3"></audio>
|
||||
|
||||
你好,我是吴咏炜。
|
||||
|
||||
上一讲我们通过 Vim 教程学习了 Vim 的基本命令,我还给你讲解了 Vim 的基本配置,现在你就已经可以上手基本的编辑工作了。
|
||||
|
||||
今天,我们将学习更多 Vim 的常用命令,以便更高效地进行编辑。我会先带你过一下**光标移动**命令和**文本修改**命令,然后重点讲解**文本对象**,随后快速讨论一下不能搭配文本修改的光标移动命令,最后讨论如何**重复**命令。
|
||||
|
||||
## 光标移动
|
||||
|
||||
我们先来讨论一下可以跟文本修改、复制搭配的光标移动命令。
|
||||
|
||||
通过前面的课程,你已经知道,Vim 里的基本光标移动是通过 `h`、`j`、`k`、`l` 四个键实现的。之所以使用这四个键,是有历史原因的。你看一下 Bill Joy 开发 vi 时使用的键盘就明白了:这个键盘上没有独立的光标键,而四个光标符号直接标注在 H、J、K、L 四个字母按键上。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/05/fd/052101c70044c4cab529cc8678a9fefd.png" alt="Fig3.1" title="Lear Siegler ADM-3A 终端键盘的排布(图片源自维基百科)">
|
||||
|
||||
当然,除了历史原因外,这四个键一直使用至今,还是有其合理性的。它们都处于打字机的本位排(home row)上,这样打字的时候,手指基本不用移动就可以敲击到。因此,即使到了键盘上全都有了光标移动键的今天,很多 Vim 的用户仍然会使用这四个键来移动光标。
|
||||
|
||||
不过,标准的光标移动键可以在任何模式下使用,而这四个键并不能在插入模式下使用,因此,它们并不构成完全的替代关系。
|
||||
|
||||
顺便提一句,你有没有注意到 ADM-3A 键盘上的 Esc 键在今天 Tab 的位置?在 Bill Joy 决定使用 Esc 来退出插入模式的时候,Esc 在键盘上的位置还没像今天那样跑到遥远的左上角去……
|
||||
|
||||
Vim 跳转到行首的命令是 `0`,跳转到行尾的命令是 `$`,这两个命令似乎没什么特别的原因,一般用 `<Home>` 和 `<End>` 也没什么不方便的,虽然技术上它们有一点点小区别。如果你感兴趣、想进一步了解的话,可以参考帮助 [`:help <Home>`](https://yianwillis.github.io/vimcdoc/doc/motion.html#%3CHome%3E)。此外,我们也有 `^`,用来跳转到行首的第一个非空白字符。
|
||||
|
||||
对于一次移动超过一个字符的情况,Vim 支持使用 `b`/`w` 和 `B`/`W`,来进行以单词为单位的跳转。它们的意思分别是 words Backward 和 Words forward,用来向后或向前跳转一个单词。小写和大写命令的区别在于,小写的跟编程语言里的标识符的规则相似,认为一个单词是由字母、数字、下划线组成的(不严格的说法),而大写的命令则认为非空格字符都是单词。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/b5/ff/b5180a6be6e04a4d486b159a265b69ff.gif" alt="Fig3.2" title="小写 w 和大写 W 的区别">
|
||||
|
||||
根据单个字符来进行选择也很常见。比如,现在光标在 `if (frame->fr_child != NULL)` 第五个字符上,如果我们想要修改括号里的所有内容,需要仔细考虑 `w` 的选词规则,然后输入 `c5w` 吗?这样显然不够方便。
|
||||
|
||||
这种情况下,我们就需要使用 `f`(find)和 `t`(till)了。它们的作用都是找到下一个(如果在输入它们之前先输入数字 **n** 的话,那就是下面第 **n** 个)紧接着输入的字符。两者的区别是,`f` 会包含这个字符,而 `t` 不会包含这个字符。在上面的情况下,我们用 `t` 就可以了:`ct)` 就可以达到目的。如果需要反方向搜索的话,使用大写的 `F` 和 `T` 就可以。
|
||||
|
||||
对于写文字的情况,比如给开源项目写英文的 README,下面的光标移动键也会比较有用:
|
||||
|
||||
- `(` 和 `)` 移到上一句和下一句
|
||||
- `{` 和 `}` 移到上一段和下一段
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/ae/57/aeaba065b958a9424c861fdf0f154857.gif" alt="Fig3.3" title="整句和整段的移动">
|
||||
|
||||
在很多环境(特别是图形界面)里,Vim 支持使用 `<C-Home>` 和 `<C-End>` 跳转到文件的开头和结尾。如果遇到困难,则可以使用 vi 兼容的 `gg` 和 `G` 跳转到开头和结尾行(小区别:`G` 是跳转到最后一行的第一个字符,而不是最后一个字符)。
|
||||
|
||||
光标移动咱们就讲到这里。你需要重点掌握的就是 Vim 里除了简单的光标移动,还有“小词”、“大词”、句、段的移动,以及字符的搜索;每种方式都分向前和向后两种情况。
|
||||
|
||||
## 文本修改
|
||||
|
||||
接着,我们来看文本修改。
|
||||
|
||||
在 Vim 的教程里,我们已经学到,`c` 和 `d` 配合方向键,可以对文本进行更改。本质上,我们可以认为 `c`(修改)的功能就是执行 `d`(删除)然后 `i`(插入)。在 Vim 里,一般的原则就是,常用的功能,按键应尽可能少。因此很多相近的功能在 Vim 里会有不同的按键。不仅如此,大写键也一般会重载一个相近但稍稍不同的含义:
|
||||
|
||||
- `d` 加动作来进行删除(`dd` 删除整行);`D` 则相当于 `d$`,删除到行尾。
|
||||
- `c` 加动作来进行修改(`cc` 修改整行);`C` 则相当于 `c$`,删除到行尾然后进入插入模式。
|
||||
- `s` 相当于 `cl`,删除一个字符然后进入插入模式;`S` 相当于 `cc`,替换整行的内容。
|
||||
- `i` 在当前字符前面进入插入模式;`I` 则相当于 `^i`,把光标移到行首非空白字符上然后进入插入模式。
|
||||
- `a` 在当前字符后面进入插入模式;`A` 相当于 `$a`,把光标移到行尾然后进入插入模式。
|
||||
- `o` 在当前行下方插入一个新行,然后在这行进入插入模式;`O` 在当前行上方插入一个新行,然后在这行进入插入模式。
|
||||
- `r` 替换光标下的字符;`R` 则进入替换模式,每次按键(直到 `<Esc>`)替换一个字符。
|
||||
- `u` 撤销最近的一个修改动作;`U` 撤销当前行上的所有修改。
|
||||
|
||||
熟练掌握这些按键需要一定的记忆和练习。但是,当你熟练掌握之后,大部分编辑操作只需要按一两个按键就能完成;而在你还没有做到熟练掌握之前,记住最简单、最有逻辑的按键也可以让你至少能够完成需要的编辑任务。
|
||||
|
||||
## 文本对象选择
|
||||
|
||||
好,接下来就是我们今天的重点内容,文本对象的选择了。我之所以把这部分内容作为这节课的重点,是因为这是一个很方便很强大的功能,并且特别适合程序中的逻辑块的编辑。
|
||||
|
||||
到现在,我们已经学习过,可以使用 `c`、`d` 加动作键对这个动作选定的文本块进行操作,也可以使用 `v` 加动作键来选定文本块(以便后续进行操作),我们也学习了好些移动光标的动作。不过,还有几个动作只能在 `c`、`d`、`v`、`y` 这样命令之后用,我们也需要学习一下。
|
||||
|
||||
这些选择动作的基本附加键是 `a` 和 `i`。其中,`a` 可以简单理解为英文单词 a,表示选定后续动作要求的完整内容,而 `i` 可理解为英文单词 inner,代表后续动作要求的内容的“内部”。这么说,还是有点抽象,我们来看一下具体的例子。
|
||||
|
||||
假设有下面的文本内容:
|
||||
|
||||
```
|
||||
if (message == "sesame open")
|
||||
|
||||
```
|
||||
|
||||
我们进一步假设光标停在“sesame”的“a”上,那么(和一般的行文惯例不同,下面在命令外面也加上了引号,避免可能的歧义):
|
||||
|
||||
- ‘`dw`’(理解为 delete word)会删除“`ame␣`”,结果是“`if (message == "sesopen")`”
|
||||
- ‘`diw`’(理解为 delete inside word)会删除“`sesame`”,结果是“`if (message == " open")`”
|
||||
- ‘`daw`’(理解为 delete a word)会删除“`sesame␣`”,结果是“`if (message == "open")`”
|
||||
- ‘`diW`’会删除“`"sesame`”,结果是“`if (message == open")`”
|
||||
- ‘`daW`’会删除“`"sesame␣`”,结果是“`if (message == open")`”
|
||||
- ‘`di"`’会删除“`sesame open`”,结果是“`if (message == "")`”
|
||||
- ‘`da"`’会删除“`"sesame open"`”,结果是“`if (message ==)`”
|
||||
- ‘`di(`’或‘`di)`’会删除“`message == "sesame open"`”,结果是“`if ()`”
|
||||
- ‘`da(`’或‘`da)`’会删除“`(message == "sesame open")`”,结果是“`if␣`”
|
||||
|
||||
上面演示了 `a`、`i` 和 `w`、双引号、圆括号搭配使用,这些对于任何语言的代码编辑都是非常有用的。实际上,可以搭配的还有更多:
|
||||
|
||||
- 搭配 `s`(sentence)对句子进行操作——适合西文文本编辑
|
||||
- 搭配 `p`(paragraph)对段落进行操作——适合西文文本编辑,及带空行的代码编辑
|
||||
- 搭配 `t`(tag)对 HTML/XML 标签进行操作——适合 HTML、XML 等语言的代码编辑
|
||||
- 搭配 ``` 和 `'` 对这两种引号里的内容进行操作——适合使用这些引号的代码,如 shell 和 Python
|
||||
- 搭配方括号(“[”和“]”)对方括号里的内容进行操作——适合各种语言(大部分都会用到方括号吧)
|
||||
- 搭配花括号(“{”和“}”)对花括号里的内容进行操作——适合类 C 的语言
|
||||
- 搭配角括号(“<”和“>”)对角括号里的内容进行操作——适合 C++ 的模板代码
|
||||
|
||||
再进一步,在 `a` 和 `i` 前可以加上数字,对多个(层)文本对象进行操作。下面图中是一个示例:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/16/bd/16a886bf009e689bdbaf3e3fd4ca69bd.gif" alt="Fig3.4" title="修改往上第 2 层花括号内的所有内容">
|
||||
|
||||
你看,无论你使用什么语言,这些快捷的文本对象选择方式是不是总会有一种可以适用?我个人觉得这些功能绝对是 Vim 的强项了,所以,我再敲一次黑板,这部分内容是重点,不要嫌内容多,挨个儿用一用、练一练,你会发现这个功能非常实用,在写代码的时候常常会用得上。
|
||||
|
||||
## 更快地移动
|
||||
|
||||
除了这讲开头提到的光标移动功能外,还有一些通常不和操作搭配的光标和屏幕移动功能。我们在这节里会快速描述一下。
|
||||
|
||||
我们仍然可以使用 `<PageUp>` 和 `<PageDown>` 来翻页,但 Vim 更传统的用法是 `<C-B>` 和 `<C-F>`,分别代表 Backward 和 Forward。
|
||||
|
||||
除了翻页,Vim 里还能翻半页,有时也许这种方式更方便,需要的键是 `<C-U>` 和 `<C-D>`,Up 和 Down。
|
||||
|
||||
如果你知道出错位置的行号,那你可以用数字加 `G` 来跳转到指定行。类似地,你可以用数字加 `|` 来跳转到指定列。这在调试代码的时候非常有用,尤其适合进行自动化。
|
||||
|
||||
下图中展示了 iTerm2 中[捕获输出](https://www.iterm2.com/documentation-captured-output.html)并执行 Vim 命令的过程(用 `vim -c 'normal 5G36|'` 来执行跳转到出错位置第 5 行第 36 列):
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/7e/c1/7e3a8ffc7yy73b31e7d9ebea6c9b3dc1.gif" alt="Fig3.5" title="捕获错误信息并自动通过 Vim 命令行来跳转到指定位置">
|
||||
|
||||
(如果你用 iTerm2 并对这个功能感兴趣,我设置的正则表达式是 `^([_a-zA-Z0-9+/.-]+):([0-9]+):([0-9]+): (?:fatal error|error|warning|note):`,捕获输出后执行的命令是 `echo "vim -c 'normal \2G\3|' \1"`。)
|
||||
|
||||
你只关心当前屏幕的话,可以快速移动光标到屏幕的顶部、中间和底部:用 `H`(High)、`M`(Middle)和 `L`(Low)就可以做到。
|
||||
|
||||
顺便提一句,vimrc_example 有一个设定,我不太喜欢:它会设 `set scrolloff=5`,导致只要屏幕能滚动,光标就移不到最上面的 4 行和最下面的 4 行里,因为一移进去屏幕就会自动滚动。这同样也会导致 `H` 和 `L` 的功能发生变化:本来是移动光标到屏幕的最上面和最下面,现在则变成了移动到上数第 6 行和下数第 6 行,和没有这个设定时的 `6H` 与 `6L` 一样了。所以我一般会在 Vim 配置文件里设置 `set scrolloff=1`(你也可以考虑设成 0),减少这个设置的干扰。
|
||||
|
||||
只要光标还在屏幕上,你也可以滚动屏幕而不移动光标(不像某些其他编辑器,Vim 不允许光标在当前屏幕以外)。需要的按键是 `<C-E>` 和 `<C-Y>`。
|
||||
|
||||
另外一种可能更实用的滚动屏幕方式是,把当前行“滚动”到屏幕的顶部、中部或底部。Vim 里的对应按键是 `zt`、`zz` 和 `zb`。和上面的几个滚动相关的按键一样,它们同样受选项 `scrolloff` 的影响。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/bf/07/bf231095714cdfc9417b0c1ceed7b307.gif" alt="Fig3.6" title="光标移动和屏幕滚动">
|
||||
|
||||
## 重复,重复,再重复
|
||||
|
||||
今天的最后,我来带你解决一个你肯定会遇到的问题,那就是如何更高效地解决重复的操作。
|
||||
|
||||
我们已经看到,在 Vim 里有非常多的命令,而且很多命令都需要敲好几个键。如果你要重复这样的命令,每次都要再手敲一遍,这显然是件很费力的事。作为追求高效率的编辑器,这当然是不可接受的。除了我们以后要学到的命令录制、键映射、自定义脚本等复杂操作外,Vim 对很多简单操作已经定义了重复键:
|
||||
|
||||
- `;` 重复最近的字符查找(`f`、`t` 等)操作
|
||||
- `,` 重复最近的字符查找操作,反方向
|
||||
- `n` 重复最近的字符串查找操作(`/` 和 `?`)
|
||||
- `N` 重复最近的字符串查找操作(`/` 和 `?`),反方向
|
||||
- `.` 重复执行最近的修改操作
|
||||
|
||||
有了这些,重复操作就非常简单了。要掌握它们的方法就是多练习,多用几次自然就会了。
|
||||
|
||||
## 内容小结
|
||||
|
||||
好了,今天的内容就讲完了,我们来做个小结。我们讨论了更多的一些常用 Vim 命令,包括:
|
||||
|
||||
- 基本光标移动命令(可配合 `c`、`d` 和 `v`)
|
||||
- 文本修改命令小汇总
|
||||
- 文本对象命令(`c`、`d`、`v` 后的`a` 和 `i`)
|
||||
- 更快的光标和屏幕移动功能
|
||||
- 重复功能
|
||||
|
||||
今天讲的内容不难,重点是文本对象。你知道吗?我见到的 Vim 命令速查表里通常也没有它们,因而连很多 Vim 的老用户都不知道这些功能呢。所以,掌握了这部分内容,我们就已经走在很多 Vim 用户的前面了。请一定要多加练习,用好这个功能会大大提升你的代码编辑效率。
|
||||
|
||||
最后,提醒你去 GitHub 上看配置文件。配置文件我们有一处改动。类似地,适用于本讲的内容标签是 `l3-unix` 和 `l3-windows`。
|
||||
|
||||
## 课后练习
|
||||
|
||||
请把本讲里面描述的 Vim 功能自己练习一下,尤其需要重点掌握的是文本修改命令、文本对象命令和重复功能。其他某些功能可能只对部分人和某些场景有用,如果一个功能你觉得用不上,不用去强记。毕竟,不用的功能,即使一时死记硬背可以记住,也很快会遗忘的。
|
||||
|
||||
欢迎你在留言区分享自己的学习收获和心得,有问题也要及时反馈,我们一起交流讨论。我们下一讲见!
|
||||
300
极客时间专栏/Vim 实用技巧必知必会/基础篇/04|初步定制:让你的 Vim 更顺手.md
Normal file
300
极客时间专栏/Vim 实用技巧必知必会/基础篇/04|初步定制:让你的 Vim 更顺手.md
Normal file
@@ -0,0 +1,300 @@
|
||||
<audio id="audio" title="04|初步定制:让你的 Vim 更顺手" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/8c/e2/8c4e3a2a53399e3970f5501edf5619e2.mp3"></audio>
|
||||
|
||||
你好,我是吴咏炜。
|
||||
|
||||
在前几讲,我已经介绍了不少 Vim 的常用命令,我想你已经略有心得了吧。今天我们转换一下视角,来讲一下 Vim 这个软件本身。
|
||||
|
||||
作为一个 Vim 的使用者,光熟悉命令是不够的,你还需要定制 Vim。因为每个人的习惯和需求都是不一样的,一个高度定制化的 Vim 环境能大大提高你的工作效率。
|
||||
|
||||
今天,我会先带你了解一下 Vim 的运行支持文件目录结构,然后我们再一起探索 Vim 8 带来的新功能,及如何对 Vim 进行初步配置来使得 Vim 更加好用。
|
||||
|
||||
## Vim 的目录结构
|
||||
|
||||
Vim 的工作环境是由运行支持文件来设定的。如果你想要定制 Vim,就要熟知 Vim 有哪些不同类型的运行支持文件,分别存放在哪里,怎样能快捷地找到它们。Vim 比较有意思的一点的是,虽然运行支持文件是在 Vim 的安装目录下,但用户自己是可以“克隆”这个目录结构的。也就是说,你自己目录下的用户配置,到你深度定制的时候,也有相似的目录结构。所以,我就先从这些文件的目录结构开始讲起。
|
||||
|
||||
### 安装目录下的运行支持文件
|
||||
|
||||
Vim 的运行支持文件在不同的平台上有着相似的目录结构。以 Vim 8.2 为例,它们的标准安装位置分别在:
|
||||
|
||||
- 大部分 Unix 下面:/usr/share/vim/vim82
|
||||
- macOS Homebrew 下:/usr/local/opt/macvim/MacVim.app/Contents/Resources/vim/runtime
|
||||
- Windows 下:C:\Program Files (x86)\Vim\vim82
|
||||
|
||||
在这个目录下面,你可以看到很多子目录,如 autoload、colors、doc、pack、plugin、syntax 等等。这些子目录下面就是分类放置的 Vim 支持文件。最常用的子目录应该是下面这几个:
|
||||
|
||||
- syntax:Vim 的语法加亮文件
|
||||
- doc:Vim 的帮助文件
|
||||
- colors:Vim 的配色方案
|
||||
- plugin:Vim 的“插件”,即用来增强 Vim 功能的工具
|
||||
|
||||
以 syntax 目录为例,当前我在下面看到有 617 个文件,也就是说,Vim 对 617 种不同的文件类型提供了语法加亮支持!这里面的文件去掉“.vim”后缀后,就是文件类型的名字,你可以用类似 `:setfiletype java` 这样的命令来设置文件的类型,从而进行语法加亮。目录下我们可以看到大家都很熟悉的语言,也有很多我从来都没听说过的东西。
|
||||
|
||||
只要有正当的理由,你就可以向 Vim 的作者 Bram 提交改进版本,或是对全新的语言的支持。我就对若干种文件类型提交过补丁,新增了对《计算机编程艺术》中的 MIX 汇编语言的语法支持,并维护着微软宏汇编(MASM)的语法文件。
|
||||
|
||||
在图形界面的 Vim 里,你可以通过“语法 > 在菜单中显示文件类型”(“Syntax > Show File Types in Menu”)来展示 Vim 的所有文件类型,然后可以选择某一类型来对当前文件进行设置。这儿的菜单项,跟 syntax 目录下的文件就基本是一一对应的了。
|
||||
|
||||
在菜单中显示文件类型这个额外的步骤,可能是因为很久很久以前,加载所有文件类型的菜单是一个耗时的操作吧。在 menu.vim 里,目前有这样的代码:
|
||||
|
||||
```
|
||||
" Skip setting up the individual syntax selection menus unless
|
||||
" do_syntax_sel_menu is defined (it takes quite a bit of time).
|
||||
if exists("do_syntax_sel_menu")
|
||||
runtime! synmenu.vim
|
||||
else
|
||||
…
|
||||
endif
|
||||
|
||||
```
|
||||
|
||||
不知道这段注释是什么年代加上的……但显然,我们的电脑已经不会再在乎加载几百个菜单项所占的时间了。即使我不怎么用菜单,我也找不出不直接展示这个菜单的理由;我可不想在需要使用的时候再多点一次鼠标。
|
||||
|
||||
所以,我会在我的 vimrc 文件里写上:
|
||||
|
||||
```
|
||||
let do_syntax_sel_menu = 1
|
||||
|
||||
```
|
||||
|
||||
同理,我会加载其他一些可能会被 Vim 延迟加载的菜单,减少需要在菜单上点击的次数:
|
||||
|
||||
```
|
||||
let do_no_lazyload_menus = 1
|
||||
|
||||
```
|
||||
|
||||
上面两个设置在我的机器上会让我打开 Vim 的速度下降大概 20 毫秒。我想我不在乎这点时间差异……
|
||||
|
||||
我们用“`:help`”命令查看的帮助文件就放在 doc 目录下。我们可以用菜单“编辑 > 配色方案”(“Edit > Color Scheme”)浏览配色方案,相应的文件就在 colors 目录下。
|
||||
|
||||
在 plugin 目录下的系统内置插件不多,我们下面就快速讲解一下:
|
||||
|
||||
- getscriptPlugin:获得最新的 Vim 脚本的插件(在目前广泛使用 Git 的年代,这个插件过时了,我们不讲)
|
||||
- gzip:编辑 .gz 压缩文件(能在编辑后缀为 .gz 的文件时自动解压和压缩,你会感觉不到这个文件是压缩的)
|
||||
- logiPat:模式匹配的逻辑运算符(允许以逻辑运算、而非标准正则表达式的方式来写模式匹配表达式)
|
||||
- manpager:使用 Vim 来查看 man 帮助(强烈建议试一下,记得使用 Vim 的跳转键 `C-]` 和 `C-T`)
|
||||
- matchparen:对括号进行高亮匹配(现代编辑器基本都有类似的功能)
|
||||
- netrwPlugin:从网络上编辑文件和浏览(远程)目录(支持多种常见协议如 ftp 和 scp,可直接打开目录来选择文件)
|
||||
- rrhelper:用于支持 `--remote-wait` 编辑(Vim 的多服务器会用到这一功能)
|
||||
- spellfile:在拼写文件缺失时自动下载(Vim 一般只安装了英文的拼写文件)
|
||||
- tarPlugin:编辑(压缩的)tar 文件(注意,和 gzip 情况不同,这儿不支持写入)
|
||||
- tohtml:把语法加亮的结果转成 HTML(自己打开个文件,输入命令“`:TOhtml`”就知道效果了)
|
||||
- vimballPlugin:创建和解开 .vba 文件(这个目前也略过时了,我们不讲)
|
||||
- zipPlugin:编辑 zip 文件(和 tar 文件不同,zip 文件可支持写入)
|
||||
|
||||
除了 rrhelper 和 spellfile 属于功能支持插件,没有自己的帮助页面,其他功能都可以使用“`:help`”命令来查看帮助。查看帮助时,插件名称中的“Plugin”后缀需要去掉:查看 zip 文件编辑的帮助时,应当使用“[`:help zip`](https://yianwillis.github.io/vimcdoc/doc/pi_zip.html#zip)”而不是“`:help zipPlugin`”。
|
||||
|
||||
从这些插件当中,我们已经可以看到 Vim 的一些特殊威力了吧。下面的动图里,我们可以看到部分插件功能的展示:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/40/7b/40016fac28fd6b3dd1361c4a9a39a07b.gif" alt="Fig4.1" title="浏览远程目录,打开一个 tar.gz 文件">
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/f8/c2/f8675428065868331a7376a64f2143c2.gif" alt="Fig4.2" title="使用 Vim 查看 man 帮助">
|
||||
|
||||
### 用户的 Vim 配置目录
|
||||
|
||||
Vim 的安装目录你是不应该去修改的。首先,你可能没有权限去修改这个目录;其次,即使你有修改权限,这个目录会在 Vim 升级时被覆盖,你做的修改也会丢失。用户自己的配置应当放在自己的目录下,这也就是用户自己的主目录下的 Vim 配置目录(Unix 下的 .vim,Windows 下的 vimfiles)。这个目录应和 Vim 安装目录下的运行支持文件目录有相同的结构,但下面的子目录你在需要修改 Vim 的相关行为时才有必要创建。如果一个同名文件出现用户自己的 Vim 配置目录里和 Vim 的安装目录里,用户的文件优先。
|
||||
|
||||
换句话说,修改 Vim 行为最简单的一种方式,就是把一个系统的运行支持文件复制到自己的 Vim 配置目录下的相同位置,然后修改其内容。我自己常常用这种方式来精调 Vim 的语法加亮。
|
||||
|
||||
显然,这种方式的缺点(在适当的时候也是优点)是,如果 Vim 的运行支持文件后来被修改/更新了,你也会继续使用你自己目录下的老版本修改版。如果你的修改不只是你自己的临时方案、同时也适合他人的话,最佳做法还是给 Vim 项目提交补丁,让其他所有人都能用上你的修改,这样才是开源的最佳使用方式。
|
||||
|
||||
关于 Vim 的公用脚本,这儿再多说几句。Vim 的网站过去是用来集中获取各种脚本——如插件和配色方案——的地方,而 getscriptPlugin 可以帮助简化这个过程。今天你仍然可以使用这个方法,但 Git 和 GitHub 的广泛使用已经改变了人们获取和更新脚本的方式。现在,最主流的分发 Vim 脚本的方式是利用 GitHub,而用户则使用包管理器来调用 Git 从 GitHub(或类似的 Git 库)获取和更新脚本,下面我们很快就会讲到。
|
||||
|
||||
理解了 Vim 的目录结构,我们接着来看 Vim 8 的新功能。
|
||||
|
||||
## Vim 8 新功能
|
||||
|
||||
Vim 是一个持续改进中的应用程序。从 Vim 8.1(2018 年 5 月 17 日)到 Vim 8.2(2019 年 12 月 12日),Vim 有 2424 个补丁,也就是说,平均每天超过 4 个补丁。很多 Vim 8 里的大功能,并不是一次性引入,而是在补丁中慢慢引入的。比如,Vim 里现在有终端支持,这个功能从 Vim 8.0.0693 开始引入,到了 Vim 8.1,成为一个正式的大功能。
|
||||
|
||||
站在我个人的角度看,从 Vim 7.4 到 Vim 8.2,最大的新功能是:
|
||||
|
||||
- Vim 软件包的支持(“[`:help packages`](https://yianwillis.github.io/vimcdoc/doc/repeat.html#packages)”)
|
||||
- 异步任务支持(“[`:help channel`](https://yianwillis.github.io/vimcdoc/doc/channel.html)”、“[`:help job`](https://yianwillis.github.io/vimcdoc/doc/channel.html#job)”和“[`:help timers`](https://yianwillis.github.io/vimcdoc/doc/eval.html#timers)”)
|
||||
- 终端支持(“[`:help terminal`](https://yianwillis.github.io/vimcdoc/doc/terminal.html)”)
|
||||
|
||||
今天我们就重点讲一下 Vim 软件包。这是 Vim 8 里带来的一个重要功能,也让我们在扩展 Vim 的时候变得更方便了。
|
||||
|
||||
### Vim 软件包
|
||||
|
||||
Vim 的目录结构有点传统 Unix 式:一个功能用到的文件可能会分散在多个目录下。就像传统 Unix 上 Vim 的文件可能分散在 /usr/bin、/usr/share/man、/usr/share/vim 等目录下一样,一个 Vim 的插件(严格来讲,应该叫包)通常也会分散在多个目录下:
|
||||
|
||||
- 插件的主体通常在 plugin 目录下
|
||||
- 插件的帮助文件在 doc 目录下
|
||||
- 有些插件只对某些文件类型有效,会有文件放在 ftplugin 目录下
|
||||
- 有些插件有自己的文件类型检测规则,会有文件放在 ftdetect 目录下
|
||||
- 有些插件有特殊的语法加亮,会有文件放在 syntax 目录下
|
||||
- ……
|
||||
|
||||
以前我们安装插件,一般是一次性安装后就不管了。安装过程基本上就是到 .vim 目录(Windows 上是 vimfiles 目录)下,解出压缩包的内容,然后执行 `vim -c 'helptags doc|q'` 生成帮助文件的索引。到了“互联网式更新”的年代,这种方式就显得落伍了。尤其糟糕的地方在于,它是按文件类型来组织目录的,而不是按相关性,这就没法用 Git 来管理了。
|
||||
|
||||
Vim 上后来就出现了一些包管理器,它们的基本模式都是相通的:每个包有自己的目录,然后这些目录会被加到 Vim 的运行时路径(`runtimepath`)选项里。最早的 `runtimepath` 较为简单,在 Unix 上缺省为:
|
||||
|
||||
>
|
||||
<p>`$HOME/.vim,`<br>
|
||||
`$VIM/vimfiles,`<br>
|
||||
`$VIMRUNTIME,`<br>
|
||||
`$VIM/vimfiles/after,`<br>
|
||||
`$HOME/.vim/after`</p>
|
||||
|
||||
|
||||
而在有了包管理器之后,`runtimepath` 就会非常复杂,每个包都会增加一个自己的目录进去。但是,好处也是非常明显的,包的管理变得非常方便。从 Vim 8 开始,Vim 官方也采用了类似的体系。Vim 会在用户的配置目录(Unix 下是 `$HOME/.vim` ,Windows 下是 `$HOME/vimfiles` )下识别名字叫 pack 的目录,并在这个目录的子目录的 start 和 opt 目录下寻找包的目录。
|
||||
|
||||
听着有点绕吧?我们看一个实际的 Vim 配置目录的结构就清楚了:
|
||||
|
||||
```
|
||||
.
|
||||
├── colors
|
||||
├── doc
|
||||
├── pack
|
||||
│ ├── minpac
|
||||
│ │ ├── opt
|
||||
│ │ │ ├── minpac
|
||||
│ │ │ ├── vim-airline
|
||||
│ │ │ └── vimcdoc
|
||||
│ │ └── start
|
||||
│ │ ├── VimExplorer
|
||||
│ │ ├── asyncrun.vim
|
||||
│ │ ├── fzf.vim
|
||||
│ │ ├── gruvbox
|
||||
│ │ ├── killersheep
|
||||
│ │ ├── nerdcommenter
|
||||
│ │ ├── nerdtree
|
||||
│ │ ├── tagbar
|
||||
│ │ ├── undotree
|
||||
│ │ ├── vim-fugitive
|
||||
│ │ ├── vim-matrix-screensaver
|
||||
│ │ ├── vim-rainbow
|
||||
│ │ ├── vim-repeat
|
||||
│ │ ├── vim-rhubarb
|
||||
│ │ └── vim-surround
|
||||
│ └── my
|
||||
│ ├── opt
|
||||
│ │ ├── YouCompleteMe
|
||||
│ │ ├── ale
|
||||
│ │ ├── clang_complete
|
||||
│ │ ├── cvsmenu
|
||||
│ │ └── syntastic
|
||||
│ └── start
|
||||
│ ├── vim-gitgutter
|
||||
│ └── ycmconf
|
||||
├── plugin
|
||||
├── syntax
|
||||
└── undodir
|
||||
|
||||
```
|
||||
|
||||
可以看到,pack 目录下有 minpac 和 my 两个子目录(这些名字 Vim 不关心),每个目录下面又有 opt 和 start 两个子目录,再下面就是每个包自己的目录了,里面又可以有自己的一套 colors、doc、plugin 这样的子目录,这样就方便管理了。Vim 8 在启动时会加载所有 pack/*/start 下面的包,而用户可以用 `:packadd` 命令来加载某个 opt 目录下的包,如 `:packadd vimcdoc` 命令可加载 vimcdoc 包,来显示中文帮助信息。
|
||||
|
||||
有了这样的目录结构,用户要自己安装、管理包就方便多了。不过,我们还是推荐使用一个包管理器。包管理器可以带来下面的好处:
|
||||
|
||||
- 根据文本的配置(一般写在 vimrc 配置文件里)决定要安装哪些包
|
||||
- 自动化安装、升级和卸载,包括帮助文件的索引生成
|
||||
|
||||
在我们这门课程里,我会使用 minpac,一个利用 Vim 8 功能的小巧的包管理器。如果你已经在使用其他包管理器,我接下来讲的两个小节你可以考虑跳过。
|
||||
|
||||
### 安装 minpac
|
||||
|
||||
根据 minpac 网页上的说明,我们在 Windows 下可以使用下面的命令:
|
||||
|
||||
```
|
||||
cd /d %USERPROFILE%
|
||||
git clone https://github.com/k-takata/minpac.git ^
|
||||
vimfiles\pack\minpac\opt\minpac
|
||||
|
||||
```
|
||||
|
||||
在 Linux 和 macOS 下则可以使用下面的命令:
|
||||
|
||||
```
|
||||
git clone https://github.com/k-takata/minpac.git \
|
||||
~/.vim/pack/minpac/opt/minpac
|
||||
|
||||
```
|
||||
|
||||
然后,我们在 vimrc 配置文件中加入以下内容(先不用理解其含义):
|
||||
|
||||
```
|
||||
if exists('*minpac#init')
|
||||
" Minpac is loaded.
|
||||
call minpac#init()
|
||||
call minpac#add('k-takata/minpac', {'type': 'opt'})
|
||||
|
||||
" Other plugins
|
||||
endif
|
||||
|
||||
if has('eval')
|
||||
" Minpac commands
|
||||
command! PackUpdate packadd minpac | source $MYVIMRC | call minpac#update('', {'do': 'call minpac#status()'})
|
||||
command! PackClean packadd minpac | source $MYVIMRC | call minpac#clean()
|
||||
command! PackStatus packadd minpac | source $MYVIMRC | call minpac#status()
|
||||
endif
|
||||
|
||||
```
|
||||
|
||||
存盘、重启 Vim 之后,我们就有了三个新的命令,可以用来更新(安装)包、清理包和检查当前包的状态。
|
||||
|
||||
### 通过 minpac 安装扩展包
|
||||
|
||||
下面我们就来试验一下通过 minpac 来安装扩展包。我们在“Other plugins”那行下面加入以下内容:
|
||||
|
||||
```
|
||||
call minpac#add('tpope/vim-eunuch')
|
||||
|
||||
```
|
||||
|
||||
保存文件,然后我们使用 `:PackUpdate` 命令。略微等待之后,我们就能看到类似下面的界面:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/10/4c/10885cf1c7406883c70838015971fb4c.png" alt="Fig4.3" title="插件安装之后的状态界面">
|
||||
|
||||
这就说明安装成功了。我们可以按 `q` 来退出这个状态窗口。
|
||||
|
||||
我们也可以使用 `:PackStatus` 来重新打开这个状态窗口。要删除一个插件,在 vimrc 中删除对应的那行,保存,然后使用 `:PackClean` 命令就可以了。愿意的话,你现在就可以试一下。
|
||||
|
||||
### 最近使用的文件
|
||||
|
||||
安装好 Vim 软件包之后,我们进一步来实现一个小功能。
|
||||
|
||||
Vim 的缺省安装缺了一个很多编辑器都有的功能:最近使用的文件。
|
||||
|
||||
我们就把这个功能补上吧。你只需要按照上一节的步骤安装 yegappan/mru 包就可以了(MRU 代表 most recently used)。安装完之后,重新打开 vimrc 文件,你就可以在图形界面里看到下面的菜单了:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/2c/d6/2c44030349043755a98f9e237ae534d6.png" alt="Fig4.4" title="最近文件的菜单">
|
||||
|
||||
估计你很可能会问:如果是远程连接,没有图形界面怎么办?
|
||||
|
||||
我们仍可以在文本界面上唤起菜单,虽然美观程度会差点。你需要在 vimrc 配置文件中加入以下内容(同样,我们暂时先不用去理解其意义):
|
||||
|
||||
```
|
||||
if !has('gui_running')
|
||||
" 设置文本菜单
|
||||
if has('wildmenu')
|
||||
set wildmenu
|
||||
set cpoptions-=<
|
||||
set wildcharm=<C-Z>
|
||||
nnoremap <F10> :emenu <C-Z>
|
||||
inoremap <F10> <C-O>:emenu <C-Z>
|
||||
endif
|
||||
endif
|
||||
|
||||
```
|
||||
|
||||
在增加上面的配置之后,你就可以使用键 `<F10>`(当然你也可以换用其他键)加 `<Tab>` 来唤起 Vim 的文本菜单了。如下图所示:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/f4/d8/f4435e4c42a7cf2be74f563e4f0b0fd8.gif" alt="Fig4.5" title="文本菜单的使用">
|
||||
|
||||
## 内容小结
|
||||
|
||||
本讲我们讨论了 Vim 8 下的基本目录结构和 Vim 8 的软件包。
|
||||
|
||||
你需要知道,Vim 的运行支持目录和用户配置目录有相似的结构,用户配置目录下的文件优先于 Vim 安装目录下的文件。因此,在用户配置目录里进行修改,可以在 Vim 版本有变化时保留自己的定制行为。
|
||||
|
||||
而 Vim 8 的软件包使得维护 Vim 的扩展变得更为容易,每一个 Vim 软件包都是独立的目录,可以单独安装、修改和升级。包管理器,如 minpac,则可以帮助我们更方便地安装和管理 Vim 软件包。
|
||||
|
||||
对于配置文件,适用于本讲的内容标签是 `l4-unix` 和 `l4-windows`。
|
||||
|
||||
## 课后练习
|
||||
|
||||
请仔细查看一下你的 Vim 安装目录下面的目录结构,对 Vim 的目录结构有一个直观的理解。你也可以打开一些 .vim 文件来看看,初步感受一下 Vim 脚本:如 filetype.vim 里有着 Vim 如何识别文件类型的逻辑,值得大致了解一下——Vim 并不仅仅是用后缀来判断文件类型的!
|
||||
|
||||
另外,一定要根据文中的说明,实践一下安装 minpac,及使用 minpac 来安装其他 Vim 软件包(除非你已经在使用另外一个包管理器了)。我们后面会经常性地安装新的软件包,这一部分操作一定需要牢牢掌握。
|
||||
|
||||
如有任何问题,请留言和我交流、讨论。我们下一讲见!
|
||||
212
极客时间专栏/Vim 实用技巧必知必会/基础篇/05|多文件打开与缓冲区:复制粘贴的正确姿势.md
Normal file
212
极客时间专栏/Vim 实用技巧必知必会/基础篇/05|多文件打开与缓冲区:复制粘贴的正确姿势.md
Normal file
@@ -0,0 +1,212 @@
|
||||
<audio id="audio" title="05|多文件打开与缓冲区:复制粘贴的正确姿势" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/9d/80/9da336597c9e732028d244089e8fb880.mp3"></audio>
|
||||
|
||||
你好,我是吴咏炜。
|
||||
|
||||
在前面的几讲里,我们介绍了 Vim 的基本命令和配置。有了这些基本功,单个文件的基本编辑对你来说应该已经不成问题了。不过,显然我们在工作和生活中不可能只用一个文件包打天下,你肯定还会遇到需要同时编辑多个文件的情况。今天,我们就来细细讨论一下这个话题,什么是编辑多个文件的正确姿势。
|
||||
|
||||
先来假设一个简单的使用场景,我们现在需要在某个目录下的所有 .cpp 和 .h 文件开头贴入一段版权声明,该如何操作?
|
||||
|
||||
## 单文件的打开方式
|
||||
|
||||
### 图形界面
|
||||
|
||||
使用图形界面的话,我们可以在操作系统的资源管理器里进入到合适的目录,然后逐个使用 Vim 来打开文件。我们可以使用右键菜单(“Edit with Vim”、“Open with…”等),也可以直接把文件拖拽到 Vim 里。使用“文件 > 打开”(File > Open)菜单当然也是一种选择,但这需要你记住上次打开到第几个文件,并不如使用资源管理器方便。
|
||||
|
||||
使用这几种编辑方式的话,你可以把需要粘贴的内容放到操作系统的剪贴板里,然后在图形界面的 Vim 里用以下方法之一粘贴进去(当然,如果光标不在开头的话,先用鼠标或用 `gg` 命令跳转到开头):
|
||||
|
||||
- 正常模式 Vim 命令 `"+P`(意义我们后面再解释)
|
||||
- 快捷键 `<D-V>`(提醒:这是我们对 ⌘V 的标记方式;仅适用于 macOS)或 `<S-Insert>`(PC 键盘)
|
||||
- 鼠标右键加“粘贴”(Paste)
|
||||
- 菜单“编辑 > 粘贴”(Edit > Paste)
|
||||
|
||||
注意,如果你通常使用 Ctrl-V 键粘贴的话,这个快捷键在 Vim 里并不适用。即使你使用的是图形界面的 Vim 也是如此,因为这个键在 Vim 里有其他用途。顺便说一句,这个键在 Unix 终端上也一样是不能用作粘贴的。
|
||||
|
||||
显然,在远程连接到服务器上时,以上方法不可用,我们得考虑终端 Vim 的用法。
|
||||
|
||||
### 终端 Vim
|
||||
|
||||
如果直接把图形界面下的基本步骤,翻译成终端 Vim(非图形界面)的用法的话,应该是这样子的:
|
||||
|
||||
1. 在终端里进入到目标目录下
|
||||
1. 使用 `vim 文件名` 来逐一打开需要编辑的文件
|
||||
1. 如果光标不在开头的话,用鼠标或 `gg` 命令跳转到开头
|
||||
1. 使用命令 `i` 进入插入模式
|
||||
1. 使用终端窗口的粘贴命令或快捷键(如 `<S-Insert>`)来粘贴内容
|
||||
1. 按 `<Esc>` 回到正常模式并用 `ZZ` 存盘退出
|
||||
|
||||
或者,我们还可以采用下面的不退出 Vim 的处理方法:
|
||||
|
||||
- 打开文件使用 `:e 文件名`;可以使用 `<C-D>` 来查看有哪些文件,及用 `<Tab>` 进行自动完成
|
||||
- 存盘使用 `:w`
|
||||
|
||||
但是如果粘贴的内容含缩进、而 Vim 又不够新的话,我们还会有特殊的麻烦。请继续往下看。
|
||||
|
||||
#### Vim 老版本的特殊处理
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/4b/0c/4b68d006f7f3b2003700b3001fb48f0c.png" alt="Fig5.1" title="老版本 Vim 下直接粘贴可能出现的错误结果">
|
||||
|
||||
上面的图片展示了 Vim 用户可能遇到的一种错误情况。这是因为对于终端 Vim 来说,一般而言,它是没法分辨用户输入和粘贴的。因此,在粘贴内容时,Vim 的很多功能,特别是和自动缩进相关的,就会和输入打架,导致最后的结果不对。
|
||||
|
||||
要解决这个问题,你就得让 Vim 知道,你到底是在输入还是在粘贴。Vim 有一个 `paste` 选项,就是用来切换输入/粘贴状态的。如果这个选项打开的话(`:set paste`),Vim 就认为你在粘贴,智能缩进、制表符转换等功能就不会修改粘贴的内容。
|
||||
|
||||
不过,手工设置该选项(及事后用 `set nopaste` 取消)是件烦人的事。所幸 xterm 里有一个“括号粘贴模式”(bracketed paste mode)可以帮 Vim 判断目前是输入还是粘贴。这个模式启用后,终端在发送剪贴板的内容之前和之后都会发送特殊的控制字符序列,来通知应用程序进行特殊的处理。
|
||||
|
||||
启用括号粘贴模式需要向 xterm 发送启用序列 `<Esc>[?2004h`,关闭括号粘贴模式需要向 xterm 发送关闭序列 `<Esc>[?2004l`;在启用了括号粘贴模式后,xterm 在发送剪贴板内容时会在前后分别加上开始粘贴序列 `<Esc>[200~` 和结束粘贴序列 `<Esc>[201~`。
|
||||
|
||||
Vim 8.0.0210 开始引入了对括号粘贴模式的支持。在兼容 xterm 的终端里进行粘贴时,你不再需要使用 `paste` 这个选项了。更棒的是,目前你甚至都不需要进入插入模式就可以粘贴了——这是不是就方便多了?
|
||||
|
||||
如果你使用的是 Vim 8.0.0210 之前的版本的话,那我们至少也可以通过代码来使得手工设置 `paste` 选项变得不必要。你可以在 vimrc 里加入下面的代码:
|
||||
|
||||
```
|
||||
if !has('patch-8.0.210')
|
||||
" 进入插入模式时启用括号粘贴模式
|
||||
let &t_SI .= "\<Esc>[?2004h"
|
||||
" 退出插入模式时停用括号粘贴模式
|
||||
let &t_EI .= "\<Esc>[?2004l"
|
||||
" 见到 <Esc>[200~ 就调用 XTermPasteBegin
|
||||
inoremap <special> <expr> <Esc>[200~ XTermPasteBegin()
|
||||
|
||||
function! XTermPasteBegin()
|
||||
" 设置使用 <Esc>[201~ 关闭粘贴模式
|
||||
set pastetoggle=<Esc>[201~
|
||||
" 开启粘贴模式
|
||||
set paste
|
||||
return ""
|
||||
endfunction
|
||||
endif
|
||||
|
||||
```
|
||||
|
||||
这个功能虽然小,但解决了在远程连接上使用 Vim 粘贴代码的一个常见烦恼。因此,我认为你需要了解一下。
|
||||
|
||||
#### “已经存在交换文件!”
|
||||
|
||||
对每个文件单独使用一个 Vim 会话来编辑,很容易出现冲突的情况,所以你迟早会遇到“已经存在交换文件!”(Swap file “…” already exists!)的错误提示。出现这个提示,有两种可能的原因:
|
||||
|
||||
1. 你上次编辑这个文件时,发生了意外崩溃。
|
||||
1. 你已经在使用另外一个 Vim 会话编辑这个文件了。
|
||||
|
||||
原因不同,我们处理的策略自然也不相同。当进程 ID(process ID)后面没有“STILL RUNNING”这样的字样时,那就是情况 1;否则,就是情况 2 了。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/d0/27/d09c693ab664af0853139d8f04eedd27.png" alt="Fig5.2" title="上次编辑这个文件时发生了意外崩溃的错误提示">
|
||||
|
||||
上图中没有“STILL RUNNING”的字样,说明是情况 1。这时你需要按 `r` 来恢复上次的编辑状态——Vim 支持即使在你没有存盘的情况下仍然保存你的编辑状态,因而这种方法可以恢复你上次没有存盘的内容。
|
||||
|
||||
需要注意的是,在恢复之后,Vim 仍然不会删除崩溃时保留下来的那个交换文件。因此,在确定内容无误、保存文件之后,你需要重新再打开文件,并按 `d` 键把交换文件删除。当然,如果你确定目前保存的文件版本就是你想要的,也可以直接按 `d` 把交换文件删除、重新编辑文件。
|
||||
|
||||
反过来,如果你已经在另一个 Vim 会话里编辑文件的话,我们就会在进程 ID 后面看到“STILL RUNNING”的字样;同时,Vim 界面上也没有了删除(Delete)交换文件这一选项。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/a4/6f/a45521128650b2824381de6f84911b6f.png" alt="Fig5.3" title="文件正在其他地方被编辑的错误提示">
|
||||
|
||||
这时,大部分情况侠我们应当使用 `q` 或 `a`(绝大部分情况下没有区别)放弃编辑,并找到目前已经打开的 Vim 窗口,从那里继续。少数情况下,我们只是要查看文件,那也可以选择 `o` 只读打开文件。需要使用 `e` 强行编辑的情况很少,需要非常谨慎——比如,你确认另外有 Vim 会话,但里面不会去做任何修改,这是我目前想得出来的唯一的合理需求。
|
||||
|
||||
如果我们使用图形界面 Vim 8 的话,Vim 支持在文件已经打开时自动切换到已经打开的 Vim 窗口上。这个功能在文件处于一个不活跃的标签页(下一讲会讨论标签页支持)时特别有用,因为 Vim 能把这个标签页自动切到最前面。不过,这个功能不是默认激活的,我们需要在 vimrc 中加入以下内容:
|
||||
|
||||
```
|
||||
if v:version >= 800
|
||||
packadd! editexisting
|
||||
endif
|
||||
|
||||
```
|
||||
|
||||
好了,目前我们已经讨论了最简单、无聊、低效的工作方式。可以明显看到,不管是使用图形界面 Vim,还是终端 Vim,上面的方法本质上把 Vim 当成了记事本来用,完全没有体现出任何高效性或方便性。
|
||||
|
||||
既然使用号称“高效”的 Vim,我们当然就得有更加高效的做法。下面,我们以多文件打开为例加以说明。
|
||||
|
||||
## 多文件的打开方式
|
||||
|
||||
首先,我们需要知道,Vim 支持一次性打开多个文件,你只需要在命令行上写出多个文件即可,或者使用通配符。比如,就我们刚才所说的编辑场景,我们可以使用 `vim *.cpp *.h`。
|
||||
|
||||
有可能让你吃惊的是,输入这个命令之后,Vim 只打开了一个文件,那就是所有文件中的第一个。
|
||||
|
||||
原来,为了确保在配置较差的环境里仍然能够正常工作,Vim 绝对不会不必要地消耗内存,包括打开不必要立即打开的文件。所以在上面的命令后,Vim 建立了一个文件列表,并且暂时只打开其中的第一个文件。接下来,用户可以决定,要编辑哪个文件,或者查看列表,或者提前退出,等等。
|
||||
|
||||
为此,Vim 提供了以下命令:
|
||||
|
||||
- `:args`:可以显示“参数”,即需要编辑的多个文件的列表
|
||||
- `:args 文件名`:使用新的文件名替换参数列表
|
||||
- `:next`(可缩写为 `:n`):打开下一个文件;如当前文件修改(未存盘)则会报错中止,但如果命令后面加 `!` 则会放弃修改内容,其他命令也类似
|
||||
- `:Next`(缩写 `:N`)或 `:previous`(缩写 `:prev`):打开上一个文件
|
||||
- `:first` 或 `:rewind`:回到列表中的第一个文件
|
||||
- `:last`:打开列表中的最后一个文件
|
||||
|
||||
使用这些命令,我们的工作流当然就会发生变化了:
|
||||
|
||||
1. 在终端里进入到目标目录下
|
||||
1. 使用 `vim *.cpp *.h` 或 `gvim *.cpp *.h` 来打开需要编辑的文件
|
||||
1. 对于第一个文件,使用之前的方法贴入所需的文本
|
||||
1. 使用 `V` 进入行选择的可视模式,移动光标选中所需的文本,然后使用 `y` 复制选中的各行
|
||||
1. 执行命令 `:set autowrite`,告诉 Vim 在切换文件时自动存盘
|
||||
1. 执行命令 `:n|normal ggP`,切换到下一个文件并执行正常模式命令 `ggP`,跳转到文件开头并贴入文本
|
||||
1. 确认修改无误后,键入 `:`、上箭头和回车,重复执行上面的命令
|
||||
1. 待 Vim 报错说已经在最后一个文件里,使用 `:w` 存盘,或 `:wq`(抑或更快的 `ZZ`)存盘退出
|
||||
|
||||
注意,第 6 步可以拆成 `:n` 和 `ggP` 两步,但文件数量较多时,反复手工敲 `ggP` 也挺累的。因此,我这儿使用了 `normal` 命令,在命令行模式下执行正常模式命令,下面就可以直接重复切换命令加粘贴命令,我们的编辑效率也得以大大提升。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/7b/cd/7b2341087d8a34471b9c1979acdcddcd.gif" alt="Fig5.4" title="第 4 步到第 7 步的演示(注意倒数第二行的变化)">
|
||||
|
||||
这种编辑方式,是不是就比之前的优越多了?
|
||||
|
||||
另外,Vim 还能解决一个 shell 相关的不一致性问题。如果我们要编辑的文件除了当前目录下的,还有所有子目录下的,在大部分 shell 下,包括 Linux 上缺省的 Bash,我们需要使用“*.cpp *.h **/*.cpp **/*.h”来挑选这些文件,重复、麻烦。Vim 在此处采用了类似于 Zsh 的简化语法,“**”也包含了当前目录。这样,我们只需把上面第 2 步改成下面这样即可:
|
||||
|
||||
- 键入 `vim` 进入 Vim,然后使用 `:args **/*.cpp **/*.h` 来打开需要编辑的文件
|
||||
|
||||
### 缓冲区的管理和切换
|
||||
|
||||
跟多文件相关又略微不同的一个概念是缓冲区(buffer)。它是 Vim 里的一个基本概念,和今天讲的很多其他内容有相关性和相似性,你也或迟或早终究会遇到它,我今天也一起概要描述一下。
|
||||
|
||||
Vim 里会对每一个已打开或要打开的文件创建一个缓冲区,这个缓冲区就是文件在 Vim 中的映射。在多文件编辑的时候你也会有同样数量的缓冲区。不过,缓冲区的数量常常会更高,因为你用 `:e` 等命令打开的文件不会改变“命令行参数”(只被命令行或 `:args` 命令修改),但同样会增加缓冲区的数量。
|
||||
|
||||
此外,`:args` 代表参数列表/文件列表,真的只是文件的列表而已。缓冲区中有更多信息的,最最基本的就是记忆了光标的位置。在 Vim 里,除了切换到下一个文件这样的批处理操作外,操作缓冲区的命令比简单操作文件的命令更为方便。
|
||||
|
||||
作为对比,我们来看一下文件列表和缓冲区列表的命令的结果。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/0f/a4/0ff5249a78179117dfcda70715dc3aa4.png" alt="Fig5.5" title="文件列表命令 :args 的结果">
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/33/08/3395a72ee4776c49748243fac411e008.png" alt="Fig5.6" title="缓冲区列表命令 :ls 的结果">
|
||||
|
||||
可以看到,两者都展示了文件,都标示出了当前编辑的文件(分别使用方括号和“%a”)。不过,缓冲区列表中明显有更多的信息:
|
||||
|
||||
- 文件名前面有编号;我们也马上就会说到利用编号的命令。
|
||||
- 除了当前活跃文件的标记“%a”,还有个文件被标成了“#”,这表示最近的缓冲区;缓冲区列表里还可能有其他标记,如“+”表示缓冲区已经被修改。
|
||||
- 文件名后面有行号,表示光标在文件中的位置。
|
||||
|
||||
常用的缓冲区命令跟前面文件列表相关的命令有很大的相似性,因此我在这儿一起讲,可以帮助你记忆:
|
||||
|
||||
- `:buffers` 或 `:ls`:可以显示缓冲区的列表
|
||||
- `:buffer 缓冲区列表里的编号`(`:buffer` 可缩写为 `:b`):跳转到编号对应的缓冲区;如当前缓冲区已被修改(未存盘)则会报错中止,但如果命令后面加 `!` 则会放弃修改内容;其他命令也类似
|
||||
- `:bdelete 缓冲区列表里的编号`(`:bdelete` 可缩写为 `:bd`):删除编号对应的缓冲区;编号省略的话删除当前缓冲区
|
||||
- `:bnext`(缩写 `:bn`):跳转到下一个缓冲区
|
||||
- `:bNext`(缩写 `:bN`)或 `:bprevious`(缩写 `:bp`):跳转到上一个缓冲区
|
||||
- `:bfirst` 或 `:brewind`:跳转到缓冲区列表中的第一个文件
|
||||
- `:blast`:跳转到缓冲区列表中的最后一个文件
|
||||
|
||||
还有很常见的一种情况是,我们需要在两个文件之间切换。Vim 对最近编辑的文件(上面提到的列表里标有“#”的文件)有特殊的支持,使用快捷键 `<C-^>` 可以在最近的两个缓冲区之间来回切换。这个快捷键还有一个用法是在前面输入缓冲区的编号:比如,用 `1<C-^>` 可以跳转到第一个缓冲区(跟命令行模式的命令 `:bfirst` 或 `:b1` 效果相同)。
|
||||
|
||||
从实际使用的角度,使用缓冲区列表有点像打开最近使用的文件菜单(但缓冲区列表不会存盘),可以当作一种快速切换到最近使用的文件的方式。
|
||||
|
||||
缓冲区是文件在某个 Vim 会话里的映射。这意味着,如果某个 Vim 会话里不同的窗口或标签页(下一讲里会讨论)编辑的是同一个文件,它们对应到的也会是同一个缓冲区。更重要的是,文件/缓冲区的修改在同一个 Vim 会话里是完全同步的——这就不会像在多会话编辑时那样发生冲突和产生错误了。
|
||||
|
||||
## 内容小结
|
||||
|
||||
本讲通过讨论使用 Vim 在多个文件里粘贴代码的多种方法,我们学习了以下知识:
|
||||
|
||||
- 在图形界面和终端里,粘贴系统剪贴板的内容需要使用不同的方法:前者使用 Vim 命令,后者则需进入插入模式,使用终端的粘贴命令进行粘贴
|
||||
- Vim 能在崩溃后恢复未存盘的内容,也能在多会话编辑同一个文件时检测到这种冲突
|
||||
- 在 Vim 里我们可以使用通配符“*.后缀”和“**/*.后缀”来打开多个文件
|
||||
- 使用 `:args` 命令我们可以展示或替换参数列表,使用 `:next` 等命令我们可以在这些参数指定的文件中切换
|
||||
- 使用 `:buffers` 或 `:ls` 命令我们可以展示缓冲区列表,即所有已编辑和将编辑的文件,使用 `:b` 和 `:bnext` 等命令我们可以在这些缓冲区中进行切换
|
||||
|
||||
今天讲到了一些命令行模式的命令,你应该可以看到,它们都是非常有规律的,最基本的操作就是“first”、“last”、“next”、“Next” 或 “previous”等英文单词,以及它们与前缀的组合。把命令行模式的命令记住,就能完成基本的编辑任务;至于像 `<C-^>` 这样的正常模式命令,万一记不住,也可以用命令行模式的命令来替代。但是,正常模式的命令更加高效,有助于提高你的编辑效率,所以最好通过多加练习来形成“肌肉记忆”。
|
||||
|
||||
对于配置文件,本讲只有很小的更改,对应的标签是 `l5-unix` 和 `l5-windows`。
|
||||
|
||||
## 课后练习
|
||||
|
||||
请在课后进行以下练习,熟悉今天所讲的内容:
|
||||
|
||||
1. 用 Vim 打开一个文件,进行编辑(不存盘),然后将这个 Vim 进程 kill 掉;重新打开文件,恢复其中内容并存盘;再次打开文件,删除交换文件。
|
||||
1. 用 Vim 打开一个文件,然后在另外一个终端窗口里再次打开这个文件,阅读冲突信息,然后退出编辑。
|
||||
1. 使用 Vim 打开多个文件,逐个查看,然后退出。
|
||||
|
||||
我是吴咏炜,我们下一讲再见!
|
||||
168
极客时间专栏/Vim 实用技巧必知必会/基础篇/06|窗口和标签页:修改、对比多个文件的正确姿势.md
Normal file
168
极客时间专栏/Vim 实用技巧必知必会/基础篇/06|窗口和标签页:修改、对比多个文件的正确姿势.md
Normal file
@@ -0,0 +1,168 @@
|
||||
<audio id="audio" title="06|窗口和标签页:修改、对比多个文件的正确姿势" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/bf/82/bf3bc525d9346989fa1025d98a47f482.mp3"></audio>
|
||||
|
||||
你好,我是吴咏炜。
|
||||
|
||||
上一讲我们讨论了多文件的编辑。不过,迄今为止,我们即使编辑多个文件,也是在单个窗口里进行的。这样做的局限在于,我们既不能同时修改两个文件,也不能在单个 Vim 会话里对比显示两个文件。当然了,在两个 Vim 会话里倒是可以做到,但有很多不足之处,其中之一就是容易出现“已经存在交换文件”这样的冲突。
|
||||
|
||||
所以,这一讲我们就来讨论一下如何利用多窗口、多标签页编辑来实现这些功能。
|
||||
|
||||
## 多窗口编辑
|
||||
|
||||
Vim 有窗口的概念。事实上,如果你使用过 Vim 的帮助功能的话,那你就已经见过 Vim 的多窗口界面了。在那种情况下,Vim 自动打开了一个水平分割的帮助窗口。
|
||||
|
||||
那如果我们想要自己同时查看、编辑多个文件呢?最基本的命令就是 `:split`(缩写 `:sp`)了。这个命令后面如果有文件名,表示分割窗口并打开指定的文件;如果没有文件名,那就表示仅仅把当前窗口分割开,当前编辑的文件在两个窗口里都显示。跟显示帮助文件一样,`:split` 默认使用水平分割的方式。
|
||||
|
||||
既然我说了“水平分割”,聪明如你,一定想到了还有竖直分割。
|
||||
|
||||
确实如此。由于 Vim 经常是在终端窗口里打开,而终端宽度常常不能允许在竖直分割时显示两个文件,所以 Vim 默认分割是水平方式。竖直分割要求屏幕比较宽,但如果你想对比两个文件时,竖直分割就会更方便了。
|
||||
|
||||
我们可以在会产生分割的命令(如 `help` 和 `split`)之前加上 `vertical`(缩写 `vert`),来进行竖直分割。对于最常见的竖直分割操作,我们则可以直接写成 `:vsplit`(缩写 `:vs`)。
|
||||
|
||||
下面的动画展示了我们进行一次竖直分割后,再进行水平分割的过程:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/e0/db/e0ea398be76e787d9f06b22d62756bdb.gif" alt="Fig6.1" title="窗口分割演示">
|
||||
|
||||
多窗口编辑是一个比较适宜使用鼠标的情况。你可以使用鼠标来激活想要使用的窗口,也可以使用鼠标来拖拉窗口的大小——只要启用了鼠标支持,终端窗口(包括远程连接的 mintty、PuTTY 等)里的 Vim 的窗口分割线也是可以拖动的(上面动画里的分割线拖动就是在一个终端窗口里)。
|
||||
|
||||
当然,作为 Vim 用户,基本的键盘使用肯定是少不了的:
|
||||
|
||||
- `<C-W>` 加方向键(`h`、`j`、`k`、`l`、`<Left>` 等等)可以在窗口之间跳转
|
||||
- `<C-W>w` 跳转到下一个(往右和往下)窗口,如果已经是右下角的窗口,则跳转到左上角的窗口
|
||||
- `<C-W>W` 跳转到上一个(往左和往上)窗口,如果已经是左上角的窗口,则跳转到右下角的窗口
|
||||
- `<C-W>n` 或 `:new` 打开一个新窗口
|
||||
- `<C-W>c` 或 `:close` 关闭当前窗口;当前窗口如果已经是最后一个则无效
|
||||
- `<C-W>q` 或 `:quit` 退出当前窗口,当最后一个窗口退出时则退出 Vim
|
||||
- `<C-W>o` 或 `:only` 只保留当前窗口,关闭其他所有窗口
|
||||
- `<C-W>s` 和 `:split` 作用相同,把当前窗口横向一分为二
|
||||
- `<C-W>v` 和 `:vsplit` 作用相同,把当前窗口纵向一分为二
|
||||
- `<C-W>=` 使得所有窗口大小相同(当调整过终端或图形界面 Vim 的窗口大小后特别有用)
|
||||
- `<C-W>_` 设置窗口高度,命令前的数字表示高度行数,默认为纵向占满(想专心编辑某个文件时很有用)
|
||||
- `<C-W>|` 设置窗口宽度,命令前的数字表示宽度列数,默认为横向占满
|
||||
- `<C-W>+` 增加窗口的高度,命令前的数字表示需要增加的行数,默认为 1
|
||||
- `<C-W>-` 减少窗口的高度,命令前的数字表示需要减少的行数,默认为 1
|
||||
- `<C-W>>` 增加窗口的宽度,命令前的数字表示需要增加的列数,默认为 1
|
||||
- `<C-W><lt>`(提醒,我们用 `<lt>` 表示“<”键)减少窗口的宽度,命令前的数字表示需要增加的列数,默认为 1
|
||||
|
||||
由于切换窗口是一个非常常见的操作,我通常会映射一下快捷键。为了跟一般的图形界面程序一致,我使用了 Ctrl-Tab 和 Ctrl-Shift-Tab:
|
||||
|
||||
```
|
||||
nnoremap <C-Tab> <C-W>w
|
||||
inoremap <C-Tab> <C-O><C-W>w
|
||||
nnoremap <C-S-Tab> <C-W>W
|
||||
inoremap <C-S-Tab> <C-O><C-W>W
|
||||
|
||||
```
|
||||
|
||||
简单解释一下:`nnoremap` 命令映射正常模式下的键盘,`inoremap` 命令映射插入模式下的键盘;正常模式的映射简单直白,应该不需要解释,插入模式的映射使用了临时模式切换键 `<C-O>`([`:help i_CTRL-O`](https://yianwillis.github.io/vimcdoc/doc/insert.html#i_CTRL-O)),在正常模式下执行相应的窗口命令,然后返回插入模式。使用这样的键盘映射之后,这两个快捷键在正常模式和插入模式下就都可以使用了。
|
||||
|
||||
### 双窗口比较
|
||||
|
||||
多窗口编辑中有一个非常有用的使用方式,那就是比较两个文件,Vim 对此也有特殊的支持。使用 `vimdiff` 或 `gvimdiff` 命令,后面跟两个文件名,我们就可以对这两个文件进行比较。在比较时,Vim 会自动折叠相同的代码行,并加亮两边文本的不同部分。窗口的滚动也是联动的。一个实际的截图如下所示:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/fc/08/fc9a2b82ece7eaeeb940806bfcae9b08.png" alt="Fig6.2" title="比较两个 C++ 文件">
|
||||
|
||||
顺便说一句,因为使用双窗口比较功能要求 Vim 的宽度是平时的两倍左右,所以我通常都会对 Vim 窗口使用最大化、拖拉之类的操作。这些操作一般只影响右边的窗口的大小,因此,在放大窗口的操作后,我通常紧跟着就会执行 `<C-W>=` 来使两个窗口的宽度相同——事实上,我使用 `<C-W>=` 主要就在这种场合。你也可以试试。
|
||||
|
||||
当然了,在 Vim 内部也可以发起这样的比较。你需要做的是打开第一个文件,然后使用命令 `:vert diffsplit 第二个文件`。这一点只要了解一下就好,毕竟大部分情况下你不需要这样去做。
|
||||
|
||||
## 多标签页编辑
|
||||
|
||||
接下来我们继续讨论和多窗口编辑构成互补的另外一种方式,也就是多标签页。
|
||||
|
||||
这里我先给你一个结论:**单窗口多文件编辑最适合的场景是批量修改具有相似性质的文件,多窗口编辑最适合的场景是需要对多个文件进行对比编辑,而其他的一些同时编辑多个文件的场景,就可以考虑多标签页的编辑方式。**
|
||||
|
||||
我把多标签页编辑归为“其他”,但仍然还是有其特殊性质的。如果你熟悉现代多标签页的其他编辑器的话,你应该已经熟悉它的基本特性了。我们这儿再温习一下:
|
||||
|
||||
- 多标签页编辑允许在编辑器里同时修改多个(未存盘的)文件
|
||||
- 多标签页编辑一次只展示一个文件
|
||||
- 通过选择标签页(或使用键盘)可以方便地在多个标签页中进行切换
|
||||
|
||||
Vim 中的标签页在图形界面或终端模式下都能支持上面描述的这些特性。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/7a/71/7a8a884f991d412fc2dyy41ef5ab7971.png" alt="Fig6.3" title="终端里运行的有两个标签页的 Vim">
|
||||
|
||||
鉴于你在实际使用中对标签页这种方式应当已经相当熟悉,如何使用标签页图形界面我就不讲了。有一点需要注意一下,和某些图形界面应用程序不同,Vim 里标签页可包含多个窗口(一个标签页里默认有一个窗口),而不是窗口可包含多个标签页——这也意味着,在标签页里关闭最后一个窗口就关闭了整个标签页。
|
||||
|
||||
此外,Vim 的标签页在纯文本的终端模式里也是可用的。在存在多个标签页的情况下,即使在终端里,你也可以用鼠标点击标签页来进行切换,双击标签栏的空白处添加新标签页,以及点击“X”标记来关闭标签页。
|
||||
|
||||
当然,Vim 用户更经常会使用键盘:
|
||||
|
||||
- 在已有命令行模式命令前加 `tab␣` 可以在新标签页中展示命令的结果,如 `:tab help` 可以在新标签页中打开帮助,`:tab split` 可以在新标签页中打开当前缓冲区
|
||||
- `:tabs` 展示所有标签页的列表
|
||||
- `:tabnew` 或 `:tabedit` 可以打开一个空白的新标签页,后面有文件名的话则打开该文件
|
||||
- `:tabclose` 可以关闭当前标签页(如果标签页里只有一个窗口,使用窗口关闭命令 `<C-W>c` 应该更快)
|
||||
- `:tabnext`、`gt` 或 `<C-PageDown>` 可以切换到下一个标签页
|
||||
- `:tabNext`、`:tabprevious` 、`gT` 或 `<C-PageUp>` 可以切换到上一个标签页
|
||||
- `:tabfirst` 或 `:tabrewind` 切换到第一个标签页
|
||||
- `:tablast` 切换到最后一个标签页
|
||||
|
||||
这些命令跟多文件、多缓冲区的命令有诸多相似之处,我就不需要再多加描述了。
|
||||
|
||||
如果一开始用多窗口编辑,后来发现不需要一直参照这个文件了,或者屏幕空间不足了,该怎么办呢?Vim 提供了一个命令,可以把当前窗口转变成一个新标签页:按下 `<C-W>T` 即可(仅当当前屏幕上有多个窗口时有效)。
|
||||
|
||||
这讲我们对多窗口和多标签页编辑的基本讨论到这儿就暂告一个段落。不知道你记不记得,上一讲我们说过,如果某个 Vim 会话里不同的窗口(或标签页;以下略)编辑的是同一个文件,它们对应到的也会是同一个缓冲区。这意味着多个窗口编辑同一个文件不会有冲突,同时,如果缓冲区被修改了,但只要当前关闭的窗口不是包含这个缓冲区的唯一窗口,那关闭窗口不会有任何问题,也不会影响文件的状态。在任何一个瞬间,任何一个窗口都指向一个缓冲区,而任何一个缓冲区都属于一个或多个窗口。(例外情况是你使用了一个不那么常用的功能,隐藏缓冲区;这个功能在本课程中不会讨论。)
|
||||
|
||||
## NERDTree 插件
|
||||
|
||||
讨论了多窗口和多标签页之后,我们来看几个利用这些特性的插件。我们讨论的第一个插件就是 NERDTree。
|
||||
|
||||
我们上一讲开始提出的问题就是对多个文件进行编辑。对于找文件这件事,NERDTree 就是你知道文件大概在哪里、但不知道文件具体名字时的一个好选择。跟很多 Vim 插件一样,NERDTree 会利用多窗口(少数情况下利用标签页)的特性。
|
||||
|
||||
拿我们上一讲提到的在文件开头插入版权声明的例子来说,至少在文件在一个目录下的情况下,使用一个文件浏览插件也能解决问题。NERDTree 就是最为著名的一个文件浏览/管理插件。下面是一个功能展示截图,先给你一个直观的印象:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/77/1f/776ac4fc034b3556f4639c42c1ed1b1f.png" alt="Fig6.4" title="NERDTree 的使用界面">
|
||||
|
||||
### 安装
|
||||
|
||||
如果使用 minpac 的话,我们需要在 vimrc 中“Other plugins”那行下面加入下面的语句,并运行 `:PackUpdate` 来安装一下:
|
||||
|
||||
```
|
||||
call minpac#add('preservim/nerdtree')
|
||||
|
||||
```
|
||||
|
||||
NERDTree 缺省就会抢占 netrw 使用的路径形式,所以我们可以用 `:e .` 来打开 NERDTree。不过,更常用的方式仍然是使用 `:NERDTreeToggle`,NERDTree 窗口的切换命令。我们使用这个命令可以打开上面左侧的那个 NERDTree 窗口,也可以关闭。这样,我们如果频繁需要浏览文件系统的话,就可以把这个命令映射到一个快捷键,免得每次都要打这么长的命令。鉴于功能键只有 12 个,映射其他键容易忘记,我暂时就不帮你在 vimrc 中映射了。对我来说,这个插件是需要安装的,但使用并不那么高频。
|
||||
|
||||
### 使用
|
||||
|
||||
在打开 NERDTree 窗口之后,使用还是相当直观的,并且按下 `?` 就可以查看帮助信息,所以我也不必一一列举所有功能了。在这里,我就概要提一下最重要的几个功能点:
|
||||
|
||||
- 顾名思义,这个插件以树形方式展示文件系统,在目录上敲回车或双击即可打开或关闭光标下的目录树。
|
||||
- 在文件上敲回车或双击立即打开该文件,并且光标跳转到文件窗口中,这样你就可以立即开始编辑了。
|
||||
- 在文件上使用 `go` 会预览该文件,也就是光标不会跳转到文件所在的窗口中,方便快速查看多个文件的内容。
|
||||
- 按 `i` 会打开文件到一个新的水平分割的窗口中,按 `s` 会打开文件到一个新的竖直分割的窗口中,按 `t` 会打开文件到一个新的标签页中。
|
||||
- NERDTree 会自动过滤隐藏文件和目录,但如果你需要看到它们的话,也可以用 `I` 来开启和关闭隐藏文件的显示。
|
||||
- 按 `m` 会出现一个菜单,允许添加、删除、更名等操作。
|
||||
|
||||
这些命令不需要死记硬背。从使用的角度,知道回车、双击就可以使用这个插件了,其他命令可以根据需要,在使用中慢慢掌握。
|
||||
|
||||
### 类似插件
|
||||
|
||||
Vim 里预置了 netrw 插件,其功能包含多个网络文件协议,同时也包含了对本地文件系统的支持,使用 `:e .` 或 `:vs .` 这样的命令就可以直接启用。不过,它在本地目录浏览相关功能上比较简单,没有 NERDTree 好用。如果有条件安装 NERDTree 的话,你一定会更喜欢 NERDTree 的。
|
||||
|
||||
我的朋友明白(mbbill)写了个叫 [VimExplorer](https://github.com/mbbill/VimExplorer) 的插件,我也挺喜欢的,一直在用。它可以把 Vim 转变成一个双面板的资源管理器,设计上也更侧重于管理,而不是纯粹的文本编辑。不过,这个插件没有像 NERDTree 一样处于积极开发状态,我就不详细介绍了。有兴趣的可以自己试一下。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/f8/44/f8f1e7a095594a52c170677cd3357944.png" alt="Fig6.5" title="VimExplorer 的界面和右键菜单">
|
||||
|
||||
## 内容小结
|
||||
|
||||
好了,这一讲到这里我就全部讲完了。我来简单做一下小结:
|
||||
|
||||
今天我们讲了 Vim 里的窗口概念和标签页概念,并讨论了相关命令。窗口的命令主要是以 `<C-W>` 开始的双键命令,而标签页的命令则和上一讲的文件操作命令和缓冲区操作命令非常相似,也用了“first”、“last”、“next”、“Next”、“previous”等英文单词,但前缀得使用“tab”。
|
||||
|
||||
多窗口适合你同时参照多个文件的内容,或者同一个文件的不同部分。多标签页适合其他你希望同时编辑多个文件的情况。在同一个 Vim 会话的多窗口和多标签页里编辑同一个文件不会发生任何冲突。
|
||||
|
||||
NERDTree 是一个利用多窗口和多标签页的流行插件,可以方便地在 Vim 里浏览文件系统并打开文件进行编辑。
|
||||
|
||||
本讲的配置文件更改也不多,对应的标签是 `l6-unix` 和 `l6-windows`。
|
||||
|
||||
## 课后练习
|
||||
|
||||
请在今天的课后进行以下的练习,来熟悉今天讲解的内容:
|
||||
|
||||
1. 尝试在打开文件后使用 `:split` 和 `:vsplit` 分割窗口,改变窗口大小,在窗口里跳转,及关闭窗口。
|
||||
1. 使用 `vimdiff` 比较两个相似的文件。
|
||||
1. 运行 NERDTree,在多个标签页里打开当前目录下的文件。
|
||||
1. 尝试在标签页里切换,及关闭标签页。
|
||||
|
||||
我是吴咏炜,我们下一讲再见。
|
||||
190
极客时间专栏/Vim 实用技巧必知必会/基础篇/07|正则表达式:实现文件内容的搜索和替换.md
Normal file
190
极客时间专栏/Vim 实用技巧必知必会/基础篇/07|正则表达式:实现文件内容的搜索和替换.md
Normal file
@@ -0,0 +1,190 @@
|
||||
<audio id="audio" title="07|正则表达式:实现文件内容的搜索和替换" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/fe/a4/fe86f08a35cc7f51f6696046c06c12a4.mp3"></audio>
|
||||
|
||||
你好,我是吴咏炜。
|
||||
|
||||
上面两讲里我们讨论了如何找到你想要查看/编辑的文件,及如何处理多个文件。今天我们来看一下如何在一个文件中搜索和替换内容,其核心主题就是正则表达式。
|
||||
|
||||
## 正则表达式搜索
|
||||
|
||||
通过 Vim 教程,你已经学到了搜索命令 `/` 和替换命令 `:s` 的基本用法。教程里没有提到的是,你输入的待查找的内容是被 Vim 当成正则表达式来看待的。正则表达式的学习资料很多(极客时间上就有专门的课程),完整学习也相当复杂,我们就不从头学习了。下面我们会简单讨论的,是 Vim 里的正则表达式,重点是它和其他常用正则表达式(正则表达式还是有很多种不同的风格的)的区别之处。如果你之前对正则表达式完全没有了解,建议你这儿暂停一下,先在网上搜索一下关于正则表达式的资料,了解它的基本概念和用法,然后继续阅读。
|
||||
|
||||
在一个搜索表达式里,或者称为模式(pattern;注意不要和 Vim 的 mode 混淆)里,`.`、`*`、`^`、`$`、`~`、`[]`、`\` 是有特殊含义的字符:
|
||||
|
||||
- `.` 可以匹配除换行符外的任何字符:如 `a.` 可以匹配“aa”、“ab”、“ac”等,但不能匹配“a”、“b”或“ba”。如果需要匹配换行符(跨行匹配)的话,则需要使用 `\_.`。
|
||||
- `*` 表示之前的匹配原(最普通的情况为单个字符)重复零次或多次:如 `aa*` 可以匹配“a”、“aa”或“aaa”,`a.*` 可以匹配“a”、“aa”、“abc”等等,但两者均不能匹配“b”。
|
||||
- `^` 匹配一行的开头,如果出现在模式的开头的话;在其他位置代表字符本身。
|
||||
- `$` 匹配一行的结尾,如果出现在模式的结尾的话;在其他位置代表字符本身。
|
||||
- `~` 匹配上一次替换的字符串,即如果上一次你把“foo”替换成了“bar”,那 `~` 就匹配“bar”。
|
||||
- `[…]` 匹配方括号内的任一字符;方括号内如果第一个字符是 `^`,表示对结果取反;除开头之外的 `-` 表示范围:如 `[A-Za-z]` 表示任意一个拉丁字母,`[^-+*/]` 表示除了“+”、“-”、“*”、“/”外的任意字符。
|
||||
- `\` 的含义取决于下一个字符,在大部分的情况下,包括上面的这几个(`.`、`*`、`\`、`^`、`$`、`~`、`[` 和 `]`),代表后面这个字符本身;在跟某些字符时则有特殊含义(后面我们会讨论最重要的那些)。
|
||||
|
||||
除此之外的字符都是普通字符,没有特殊含义。不过,需要注意的是,如果使用 `/` 开始一个搜索命令,或者在替换命令(`:s`)中使用 `/` 作为模式的分隔符,那模式中的 `/` 必须写作 `\/` 才行,否则 Vim 看到 `/` 就会以为模式结束了,导致错误发生。
|
||||
|
||||
为了避免写模式的困扰,如果模式中使用“/”作为路径的分隔符,在替换命令中可以使用其他模式中没有的符号作为分隔符。比如,想把“/image/”全部替换成“/images/”的话,不要用 `:%s/\/image\//\/images\//g`,而应该用类似于 `:%s!/image/!/images/!g` 的写法。这只能适用于替换命令,而在使用 `/` 命令搜索时我们就没什么好办法了,只能把模式里的 `/` 写作 `\/`。不过我们也可以取巧一下,用 `?` 向上、也就是反向搜索,只要记得 `n`、`N` 反过来用找下一个就行。
|
||||
|
||||
通过 `\` 开始的特殊表达式有不少,如果你需要完整了解的话,可以去看看参考文档([`:help pattern-overview`](https://yianwillis.github.io/vimcdoc/doc/pattern.html#pattern-overview))。我们下面先学习一下最基本的 6 个特殊模式项:
|
||||
|
||||
- `\?` 表示之前的匹配原重复零次或一次:如 `aa\?` 可以匹配“a”、“aa”,但不能完整匹配“aaa”(可以匹配其前两个字符、后两个或最后一个字符)。
|
||||
- `\+` 表示之前的匹配原重复一次或多次:如 `aa\+` 可以匹配“aa”、“aaa”,但不能匹配“a”或“b”。
|
||||
- `\{n,m}` 表示之前的匹配原重复 n 到 m 遍之间,两个数字可以省略部分或全部:如 `a\{3}`(可读作:3 个“a”)可以匹配“aaa” ,`a\{,3}`(可读作:最多 3 个“a”)可以匹配“”、“a”、“aa”和“aaa”;两个数字都省略时等价于 `*`,也就是之前的匹配原可以重复零次或多次。
|
||||
- `\(` 和 `\)` 括起一个模式,将其组成为单个匹配原:如 `\(foo\)\?` 可以表示单词“foo”出现零次或一次。`\(` 和 `\)` 还有一个附加作用,是捕获匹配的内容,按 `\(` 出现的先后顺序,可以用 `\1`、`\2` 到 `\9` 来引用。如果你不需要捕获匹配内容的话,用 `\%(` 和 `\)` 的性能更高。
|
||||
- `\&` 是分支内多个邻接(concat)的分隔符,概念上可以和**与**操作相比,表示每一项都需要匹配成功,然后取最后一项的结果返回:如 `.*foo.*\&.*bar.*` 匹配同时出现了“foo”和“bar”的完整行。相对来讲,`\&` 没那么常用。
|
||||
- `\|` 是多个分支的分隔符,概念上可以和**或**操作相比,表示任意一项匹配成功即可:如 `foo\|bar` 可匹配“foo”或“bar”两单词之一。
|
||||
|
||||
接下来,我再和你分享 13 个特殊模式项。虽然它们相对来说不那么必需,但掌握它们可以大大地提高程序员的编辑效率。
|
||||
|
||||
- `\<` 匹配单词的开头
|
||||
- `\>` 匹配单词的结尾
|
||||
- `\s` 匹配空白字符 `<Space>` 和 `<Tab>`
|
||||
- `\S` 匹配非空白字符
|
||||
- `\d` 匹配数字,相当于 `[0-9]`
|
||||
- `\D` 匹配非数字,相当于 `[^0-9]`
|
||||
- `\x` 匹配十六进制数字,相当于 `[0-9A-Fa-f]`
|
||||
- `\X` 匹配非十六进制数字,相当于 `[^0-9A-Fa-f]`
|
||||
- `\w` 匹配单词字符,相当于 `[0-9A-Za-z_]`
|
||||
- `\W` 匹配非单词字符,相当于 `[^0-9A-Za-z_]`
|
||||
- `\h` 匹配单词首字符,相当于 `[A-Za-z_]`
|
||||
- `\H` 匹配非单词首字符,相当于 `[^A-Za-z_]`
|
||||
- `\c` 忽略大小写进行匹配
|
||||
|
||||
以上我们讨论的实际上是 Vim 缺省设置下的正则表达式。通过选项([`:help /magic`](https://yianwillis.github.io/vimcdoc/doc/pattern.html#%2Fmagic)),我们可以对哪些字符有特殊意义进行一定程度的调整。不过一般情况下,我认为修改这个选项只会造成混乱、增加心智负担,因此我也就不在这儿展开了。
|
||||
|
||||
### 搜索实例
|
||||
|
||||
抽象地讨论正则表达式恐怕你也不容易记住,我们还是拿一些具体的例子来看一下吧。
|
||||
|
||||
首先,如果**我们要查找某个函数,该怎么做呢?**简单,按下 `/`,然后输入函数名,回车,不就行了?
|
||||
|
||||
错。这种方式对函数名是部分匹配,你搜 `begin` 还会得到 `begin1`、`_begin` 之类的结果。正确的方法是,要在前后加上匹配单词头尾的标记,如,`\<begin\>`。
|
||||
|
||||
顺便说一句,被誉为最有用的 Vim 提示,是把光标移到希望搜索的关键字上,然后按下 `*` 键。Vim 会提取光标下的关键字,并自动添加 `\<` 和 `\>` 进行搜索。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/d0/4e/d037c4d999ccb36edc8886f1f507c14e.gif" alt="Fig7.1" title="使用 * 搜索的示例;注意“unexpected”并没有被高亮">
|
||||
|
||||
如果我要搜索 `begin` 或 `end` 呢?我想,你应该已经知道了,是:`/\<\(begin\|end\)\>`。注意,写成 `/\<begin\|end\>` 可是不对的。(为什么?你想明白了吗?)
|
||||
|
||||
对于 HTML,你应该多多少少有些了解。**如果我们想匹配一下 HTML 标签的话,该怎么做呢?**
|
||||
|
||||
一个标签以 `<` 开始,以 `>` 结束。所以,最简单的模式应该是 `<.\+>`,对吗?
|
||||
|
||||
不对,这个写法忽略了一行里可能有多个标签的事实:对于“<h1>title</h1>”这样一个字符串,上面这个简单的模式会匹配整个字符串,而不是“<h1>”和“</h1>”……
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/c8/1d/c80f8930b2934cd315c6560dd31c661d.png" alt="Fig7.2" title="HTML 标签的错误匹配">
|
||||
|
||||
有一种解决方案是,排除不应该匹配的字符,把模式写成 `<[^>]\+>`:一对尖括号里有一个或多个不是“>”的字符。不过,这样的写法会让像 `>` 这样的结尾字符在模式中重复出现,因此这并不是最理想的写法。更好的方式是,使用最短匹配。
|
||||
|
||||
### 最长匹配和最短匹配
|
||||
|
||||
我们上面学到的 `*`、`\?`、`\+` 和 `\{}` 都属于最长匹配(也叫贪婪匹配),也就是说,当模式既可以匹配一个较长的字符串,也可以匹配一个较短的字符串时,结果会是那个较长的字符串。
|
||||
|
||||
相应地,还有一种匹配叫做最短匹配,也就是在同时可以匹配较长的字符串和较短的字符串时,产生较短的匹配。在 Vim 里,最短匹配只有一种形式,`{-n,m}`,其意义和之前说的 `{n,m}` 基本相同,但结果是较短而非较长的字符串。
|
||||
|
||||
以上面的 HTML 标签匹配为例,使用最短匹配的话,我们可以把模式写成 `<.\{-1,}>`,要求在一对尖括号里至少有一个字符,但越短越好。
|
||||
|
||||
### 搜索加亮和取消
|
||||
|
||||
如果你一边学一边在试验的话,就会发现,Vim 缺省在你输入搜索模式时就会高亮跟你输入的模式匹配的文本。这对验证你输入的模式是否正确,以及进行进一步的编辑,都是非常方便和重要的。用惯了 Vim,就会把它当成是一件理所当然的事——直到你被迫使用其他编辑器时才发现,一边输入正则表达式一边就能看到匹配的结果,原来不是谁都这样做的啊……
|
||||
|
||||
但也有些时候,我们已经做完了搜索或替换,和模式匹配的文本内容仍然还高亮着,非常碍眼。有些人就会随便搜索一个不存在的字符串来取消加亮,但这显然不是一种高效的处理方式。事实上,Vim 有一个专门命令来取消搜索加亮,这个命令就是 `:nohlsearch`,不要高亮搜索。
|
||||
|
||||
鉴于这个命令使用的频度实在是太高了,我们需要给它专门分配一个快捷键。请在 vimrc 中加入:
|
||||
|
||||
```
|
||||
" 停止搜索高亮的键映射
|
||||
nnoremap <silent> <F2> :nohlsearch<CR>
|
||||
inoremap <silent> <F2> <C-O>:nohlsearch<CR>
|
||||
|
||||
```
|
||||
|
||||
这样一来,在搜索或替换工作完成之后,只要按下 `<F2>` 就可以取消搜索加亮了。
|
||||
|
||||
好,关于正则表达式的搜索部分,我们暂时就先学到这里。下面我们来看一下替换。
|
||||
|
||||
## 正则表达式替换
|
||||
|
||||
你可能要说了:替换不就是找到跟模式匹配的字符串,然后把它换成另外一个字符串么,有什么复杂的?
|
||||
|
||||
事实上,还真是有些复杂情况的。你在看下面这些复杂的替换情况时,也可以同时考虑下自己有没有解决方案:
|
||||
|
||||
- 你可能要保留匹配中的某些字符,而替换另外一些字符
|
||||
- 你可能要对匹配出的内容做大小写转换
|
||||
- 你可能需要“计算”出替换结果
|
||||
- 你可能需要决定一行里要替换单次还是多次,是自动替换还是要一一确认,等等
|
||||
|
||||
接下来,我们就分别看看这些复杂情况。
|
||||
|
||||
在这些情况里,最常用的显然就是**在替换结果中保留匹配出的字符串**了。前面说到 `\(\)` 除了将一个模式转变成匹配原外,还有一个作用是捕捉匹配的内容,按 `\(` 的出现顺序依次编号为 1 到 9,并可以在模式和替换字符串中用 `\1` 到 `\9` 来访问。如果要在替换字符串中完整使用匹配内容的话,则可以使用 `\0` 或 `&`(字符“&”也因此要在替换字符串中写成 `\&`)。
|
||||
|
||||
从搜索的角度,我们一般只关心匹配与否,而不关心匹配的大小。举个例子,如果我想找出作为函数调用的 `begin`,那我可以写成 `\<begin(`,虽然 `(` 不是我想匹配的内容(函数名称)的一部分。但从替换的角度,我需要在替换时再处理一下多匹配的内容,也是件麻烦事;在非匹配的内容比较复杂或者会变化的时候,尤其会是这样。所以 Vim 里还有专门标识匹配开始和结束的匹配原,分别是 `\zs` 和 `\ze`。对于这个例子,搜索模式就应该是 `\<begin\ze(`。为了巩固前面学到的知识,你应该知道,这个模式也可以啰嗦地写成 `\<begin(\&begin` 或 `\<begin(\&.....`。
|
||||
|
||||
Vim 里还有一些**大小写转换的特殊替换字符串**。它们是:
|
||||
|
||||
- `\U` 把下面的字符变成大写,直到 `\E` 出现
|
||||
- `\u` 把下一个字符变成大写
|
||||
- `\L` 把下面的字符变成小写,直到 `\E` 出现
|
||||
- `\l` 把下一个字符变成小写
|
||||
- `\E` 结束大小写转换
|
||||
|
||||
Vim 还能用 `\=` 开始一个返回字符串的表达式,用来**计算出一个替换结果**。鉴于我们目前还没有讨论 Vim 脚本,这个我们就留到后面第 14 讲再说了。
|
||||
|
||||
跟常用的编程语言一样,Vim 的正则表达式中支持 `\t`、`\r`、`\n` 等特殊转义字符,但在替换表达式中,由于一些技术原因([`:help NL-used-for-Nul`](https://yianwillis.github.io/vimcdoc/doc/pattern.html#NL-used-for-Nul)),`\n` 插入的是空字符(NUL 或“\0”),而非在模式中出现时代表的 LF。如果要插入正常的行尾符 LF 的话,我们得使用 `\r`。这意味着如果想把一个回车变成两个的话,我们得别扭地写 `:s/\n/\r\r/`,略遗憾。如果有特殊需要得插入 CR 的话,就要更别扭地输入 `\<C-V><CR>` 才行。还好,我们基本不会在替换时遇到要插入 CR 的情况……
|
||||
|
||||
**Vim 有很多用来控制替换的标志**,你可以通过 [`:help s_flags`](https://yianwillis.github.io/vimcdoc/doc/change.html#:s_flags) 查看详细的介绍,我就不一一列举了。今天这一讲中,我们只会用到最常用的一个标志,`g`,代表可以在一行内进行多次替换;没有这个标志的话,Vim 在一行里只会对第一个成功的匹配进行替换。
|
||||
|
||||
### 替换实例
|
||||
|
||||
同样,我们还是通过例子来巩固一下对正则表达式替换的理解。
|
||||
|
||||
先来看一个简单的,删除行尾的“//”注释。我们可以用这个命令
|
||||
|
||||
```
|
||||
:%s!\s*//.*$!!
|
||||
|
||||
```
|
||||
|
||||
把零到多个空白字符后面出现的“//”直到行尾全部删除。
|
||||
|
||||
如果要删除“/* */”注释,那就复杂多了。首先,匹配内容可以跨行;其次,有跟 HTML 标签类似的问题,需要使用最短匹配。我们需要使用的命令是:
|
||||
|
||||
```
|
||||
:%s!/\*\_.\{-}\*/!!g
|
||||
|
||||
```
|
||||
|
||||
由于一行里可以有多个“/* */”注释,我们在替换命令的尾部还加上了 `g` 标志,允许一行里进行多次替换。
|
||||
|
||||
假设我们目前的编码规范规定,所有的函数名应该首字母大写(简单起见,我们假设所有的类名已经是首字母大写了,因而构造函数自动符合该要求,不会发生冲突;但其他很多函数名称仍然是小写字母开头),我们能不能用 Vim 的替换命令做到呢?答案也是肯定的。所有需要的知识点我们都已经讲过了,我就直接公布答案了:
|
||||
|
||||
```
|
||||
:%s/\<\(_*\)\([a-z]\w*\)\ze(/\1\u\2/g
|
||||
|
||||
```
|
||||
|
||||
这个命令比较长,请你慢慢体会一下,尝试去理解每一部分的意图。如果你有哪个点卡住了,可以留言给我,我再帮你详细分析一下。
|
||||
|
||||
## 内容小结
|
||||
|
||||
好了,今天的内容就讲到这里了。内容有点密集,我把要点再总结一下:
|
||||
|
||||
Vim 支持用 `/` 进行搜索和用 `:s` 进行替换,它们都用到了正则表达式。
|
||||
|
||||
在搜索的模式里,`.`、`*`、`^`、`$`、`~`、`[]`、`\` 是有特殊含义的字符,你一定要记住它们的含义。在 `\` 开始的特殊表达式中,最重要的是 `\?`、`\+`、`\(\)`、`\|` 和 `\{n,m}`。对于程序员来说,`\<`、`\>` 等匹配原对于提高编辑效率也非常重要。Vim 中的常用搜索命令 `*` 则会自动在搜索的关键字前后加上 `\<` 和 `\>`。
|
||||
|
||||
在替换时,我们需要特别记住 `\1`、`\2` 到 `\9` 可以用来引用前面用 `\(` 和 `\)` 括起来的内容,字符“&”出现在替换内容中需要使用反斜杠转义成 `\&`,否则代表完整的被匹配字符串。
|
||||
|
||||
正则表达式就可以算是一种独立的语言了,靠死记硬背是不行的。最后我还要建议你再把这一讲中的例子仔细看一下、尝试一下,多多练习是掌握正则表达式搜索和替换的必经之路。如果你日后遇到了这一讲没有覆盖的问题,可以再去查阅 Vim 的帮助文档 [`:help regexp`](https://yianwillis.github.io/vimcdoc/doc/pattern.html#regexp)。
|
||||
|
||||
本讲我们在配置文件中只更改了一处,对应的标签是 `l7-unix` 和 `l7-windows`。
|
||||
|
||||
## 课后练习
|
||||
|
||||
有两道练习题,请你在课后尝试一下。练习对于掌握正则表达式是非常重要的。
|
||||
|
||||
<li>
|
||||
如果我要搜索“/* */”注释的话,搜索命令应该是什么样的?
|
||||
</li>
|
||||
<li>
|
||||
例子里只说了首字母大写,但实际的编码规范是要求把 begin_search_nocase 这样的函数名称转变成 BeginSearchNocase。请用 Vim 的替换命令完成这一任务。**提示:**可能需要一条以上的替换命令。
|
||||
</li>
|
||||
|
||||
我是吴咏炜,我们下一讲再见。
|
||||
433
极客时间专栏/Vim 实用技巧必知必会/基础篇/08|基本编程支持:规避、解决编程时的常见问题.md
Normal file
433
极客时间专栏/Vim 实用技巧必知必会/基础篇/08|基本编程支持:规避、解决编程时的常见问题.md
Normal file
@@ -0,0 +1,433 @@
|
||||
<audio id="audio" title="08|基本编程支持:规避、解决编程时的常见问题" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/ab/66/abc3c6c69eff0e21c70c08693bddyy66.mp3"></audio>
|
||||
|
||||
你好,我是吴咏炜。
|
||||
|
||||
在前面的几讲里,我们已经学了很多使用 Vim 的基本知识。今天是编程专题,我来专门介绍一下 Vim 对编程的特别支持。学了这一讲之后,你会进一步了解 Vim 编辑程序时的重要特性,并能够规避、解决编程时的常见问题。
|
||||
|
||||
## 文件类型和关联设定
|
||||
|
||||
程序源代码通常由文件组成,每个文件都有一个关联的文件类型。这个文件类型决定了 Vim 对其进行处理的一些基本设定,可能包括:
|
||||
|
||||
- 如何对文件进行高亮
|
||||
- 制表符(tab)的宽度(空格数)
|
||||
- 是否在键入 `<Tab>` 时扩展为空格字符
|
||||
- 每次缩进的空格数(是的,可以和制表符宽度不同)
|
||||
- 采用何种自动缩进方法
|
||||
- 其他可适用的选项
|
||||
|
||||
**文件高亮**通常需要一套相当复杂的规则,我们今天就只把它当成一个既成事实了,不讨论这些规则的细节。其他各项在 Vim 里一般以选项的形式出现。这些选项都是文件本地(local)选项,即可以在一个文件里修改其数值而不影响其他文件。对于这样的选项,可以用 `:setlocal` 和 `:setglobal` 命令分别访问本地值和全局值。一般的 `:set` 命令在读取数值时(如 `:set tabstop?`)返回本地值,在写入数值时(如 `:set tabstop=4`)同时设置本地值和全局值。
|
||||
|
||||
**制表符宽度**对应的选项是 `tabstop`。这在不同的语言里可能有不同的惯例,自然不必多说。它的缺省值是 8,但在不同的文件里可以不一样。不同的文件类型也可能会自动设定不同的数值。
|
||||
|
||||
是否**扩展 `<Tab>` 为空格**由 `expandtab` 选项控制。我们前面看到过,但没有讲过,Vim 选项有些是用等号赋值的,也有些不用等号,而只用选项名称或选项名称前面加 `no`,表示否定。这些就是布尔类型选项,`expandtab` 也是其中之一。如果打开了 `expandtab` 选项,那输入中的 tab 会被转变成空格;如果关闭的话,则 tab 字符会被保留。
|
||||
|
||||
让事情变得更复杂的是,Vim 还有个 `softtabstop` 选项,**软制表符宽度**。一旦设置了这个选项为非零值,再键入 `<Tab>` 和 `<BS>`(退格键),你就感觉像设置了这个宽度的 `tabstop` 一样,有相应数量的缩进或取消缩进,但实际插入的字符仍然受 `expandtab` 和 `tabstop` 两个选项控制。在设置软制表符宽度时,一种最常用的用法是同时设置 `expandtab`,这样,编辑时你感觉像使用了这个宽度的制表符一样,但你输入的内容里实际被保存的仍然是空格字符。
|
||||
|
||||
这些还不是 Vim 真正使用的“缩进”值。以 C 语言为例,当 Vim 看到你输入“{”和回车键时,会自动产生一个缩进,而这个缩进值跟 `tabstop` 和 `softtabstop` 都无关,是一个独立的选项 `shiftwidth`。
|
||||
|
||||
最后,Vim 还有很多精细的选项来控制如何进行**缩进**。默认情况下,Vim 没有特殊缩进,回车键回到行首。一般而言,使用选项 `autoindent` 可以使 Vim 至少记住上一行的缩进位置;而对于特定语言,Vim 可以设置更合适的选项,达到更佳的缩进效果——如对类 C 语言 Vim 会设置 `cindent` 选项,达到最优的缩进效果。我们下面还会提到,Vim 支持对类 C 语言的缩进有一些精调选项,你也可以自己进一步进行调整。
|
||||
|
||||
我之前提到过,Vim 会根据文件类型来设置选项。所以,相关问题就是,Vim 如何判断文件类型,如何根据文件类型来设置选项,以及我们该如何定制这些行为。我们下面就来一一作答。
|
||||
|
||||
### 文件类型判断
|
||||
|
||||
Vim 的文件类型判断是在 filetype.vim 中执行的。我们可以用下面的命令来打开这个文件:
|
||||
|
||||
```
|
||||
:e $VIMRUNTIME/filetype.vim
|
||||
|
||||
```
|
||||
|
||||
这个文件相当复杂,但有编程功底的你,应该可以看出大概的意思吧?其中最主要的逻辑仍然是通过后缀来进行判断,如:
|
||||
|
||||
```
|
||||
" C++
|
||||
au BufNewFile,BufRead *.cxx,*.c++,*.hh,*.hxx,*.hpp,*.ipp,*.moc,*.tcc,*.inl setf cpp
|
||||
|
||||
```
|
||||
|
||||
其中 `au` 是 `autocmd` 的缩写,代表 Vim 在发生某事件时触发某一动作。上面说的就是在创建(`BufNewFile`)或读入(`BufRead`)跟指定文件名模式匹配的文件时,把文件类型设为 C++(`setf cpp`, `setf` 是 `setfiletype` 的缩写)。
|
||||
|
||||
但在后缀不足以唯一判断时,Vim 可以进一步执行代码,如:
|
||||
|
||||
```
|
||||
au BufNewFile,BufRead *.h call dist#ft#FTheader()
|
||||
|
||||
```
|
||||
|
||||
上面函数的定义在文件 $VIMRUNTIME/autoload/dist/ft.vim 里:
|
||||
|
||||
```
|
||||
func dist#ft#FTheader()
|
||||
if match(getline(1, min([line("$"), 200])), '^@\(interface\|end\|class\)') > -1
|
||||
if exists("g:c_syntax_for_h")
|
||||
setf objc
|
||||
else
|
||||
setf objcpp
|
||||
endif
|
||||
elseif exists("g:c_syntax_for_h")
|
||||
setf c
|
||||
elseif exists("g:ch_syntax_for_h")
|
||||
setf ch
|
||||
else
|
||||
setf cpp
|
||||
endif
|
||||
endfunc
|
||||
|
||||
```
|
||||
|
||||
它的大概意思是,如果在头 200 行里找到某行以 `@interface` 等内容开始,那就认为这是 Objective-C/C++,否则认为是 C/C++。具体是 C 还是 C++,则由全局变量 `g:c_syntax_for_h` 控制(我们忽略 Ch 这种小众情况)。详细语法我们就不展开讲述了,留待讨论 Vim 脚本的时候再看。
|
||||
|
||||
上面讲的是 Vim 的缺省行为。我们当然也可以定制 Vim的行为。按照惯例,一般把定制放在用户 Vim 配置目录里的 filetype.vim 里。我的定制如下所示:
|
||||
|
||||
```
|
||||
if exists("did_load_filetypes")
|
||||
finish
|
||||
endif
|
||||
|
||||
function! s:CheckCPP()
|
||||
if expand('%:t') !~ '\.'
|
||||
setfiletype cpp
|
||||
endif
|
||||
endfunction
|
||||
|
||||
augroup filetypedetect
|
||||
au! BufRead,BufNewFile *.asm setfiletype masm
|
||||
au! BufRead proxy.pac setfiletype javascript
|
||||
au! BufRead */c++/* call s:CheckCPP()
|
||||
au! BufRead */include/* call s:CheckCPP()
|
||||
augroup END
|
||||
|
||||
```
|
||||
|
||||
我们可以跳过一些语法方面的细节,只讨论代码里的意图。上面这段代码主要做了以下事情:
|
||||
|
||||
- 当读入或创建后缀为“.asm”的文件时,设置文件类型为微软宏汇编(默认为 GNU 的汇编格式)。
|
||||
- 当读入名字为“proxy.pac”的文件时,把内容当成 JavaScript 解释。
|
||||
- 当读入路径含“c++”或“include”的文件时,调用脚本内部函数 `CheckCPP`,检查文件名(`%` 代表文件名,`:t` 代表尾部,即去掉路径部分)是否不含“.”,是的话当成 C++ 文件类型。这是为了处理像“memory”这样的无后缀 C++ 头文件。
|
||||
- 随后 Vim 会继续载入自带的 filetype.vim;如果文件类型还未确定的话,则继续使用 Vim 自带的规则进行判断。
|
||||
|
||||
### 文件类型选项
|
||||
|
||||
一旦确定了文件类型,Vim 会从运行支持文件目录下载入同名的文件。以 Python 为例:
|
||||
|
||||
- syntax/python.vim 包含了如何对 Python 进行语法加亮的设置
|
||||
- indent/python.vim 包含了如何对 Python 代码进行缩进的设置(如在用户输入 `if` 时进行缩进等)
|
||||
- ftplugin/python.vim 是文件类型插件,包含了其他跟文件类型相关的设置
|
||||
|
||||
文件类型插件中包含我们上面提到的制表符宽度方面的设定,具体来说,是下面这几行:
|
||||
|
||||
```
|
||||
if !exists("g:python_recommended_style") || g:python_recommended_style != 0
|
||||
" As suggested by PEP8.
|
||||
setlocal expandtab shiftwidth=4 softtabstop=4 tabstop=8
|
||||
endif
|
||||
|
||||
```
|
||||
|
||||
默认情况下,该文件使用 [PEP 8](https://www.python.org/dev/peps/pep-0008/) 推荐的设置:
|
||||
|
||||
- 把用户输入的制表符扩展成空格
|
||||
- 缩进和软制表符宽度设为 4
|
||||
- 如果文件中包含制表符的话,仍按宽度为 8 来解释
|
||||
|
||||
缩进和软制表符宽度设成 4 估计不需要解释,这应该是最常用的缩进值了。使用空格而不是制表符的最大好处是,在无论何种环境下,展示效果都可以完全一致,不会在 diff 时或制表符宽度不符合预期时代码就乱了。至于“硬”制表符宽度仍然是 8,则是为了确保显示文件的兼容性,尤其是在终端里 cat 文件时和在浏览器中浏览源代码时;这两种情况下,制表符宽度一般都是 8。
|
||||
|
||||
跟 Python 不同,很多其他文件类型没有推荐的风格设定,这时就应该用户自己进行设定了。我推荐在 vimrc 配置文件里进行设置,因为比较集中、容易管理。如:
|
||||
|
||||
```
|
||||
au FileType c,cpp,objc setlocal expandtab shiftwidth=4 softtabstop=4 tabstop=4 cinoptions=:0,g0,(0,w1
|
||||
au FileType json setlocal expandtab shiftwidth=2 softtabstop=2
|
||||
au FileType vim setlocal expandtab shiftwidth=2 softtabstop=2
|
||||
|
||||
```
|
||||
|
||||
上面设置了几种不同文件类型的编辑选项。大部分我们都已经知道了,下面这个则是新的:
|
||||
|
||||
- `cinoptions` 可以精调 C 风格缩进的方式;上面 `:0` 表示 `switch` 下面的 `case` 语句不进行额外缩进,`g0` 代表作用域声明(`public:`、`private:` 等)不额外缩进,`(0` 和 `w1` 配合代表没结束的圆括号里的内容折行时不额外缩进。
|
||||
|
||||
我们也可以根据文件类型以外的条件来进行设定,如下面设定是要把 /usr/include 目录下的文件按 [GNU 编码风格](https://www.gnu.org/prep/standards/)来解释:
|
||||
|
||||
```
|
||||
function! GnuIndent()
|
||||
setlocal cinoptions=>4,n-2,{2,^-2,:2,=2,g0,h2,p5,t0,+2,(0,u0,w1,m1
|
||||
setlocal shiftwidth=2
|
||||
setlocal tabstop=8
|
||||
endfunction
|
||||
|
||||
au BufRead /usr/include/* call GnuIndent()
|
||||
|
||||
```
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/32/2e/327950d78fafccce8aab31db624aaf2e.png" alt="Fig8.1" title="GNU 风格的 C++ 代码(注意大括号的缩进风格和高亮的 tab 宽度)">
|
||||
|
||||
当然,除了设定选项,我们也可以做其他事情,比如下面的代码是在 Vim 帮助文件中,将 `q` 设定为关闭窗口的按键,映射中的 `<buffer>` 表示该映射只对这个缓冲区有效。
|
||||
|
||||
```
|
||||
au FileType help nnoremap <buffer> q <C-W>c
|
||||
|
||||
```
|
||||
|
||||
## Tags 支持
|
||||
|
||||
Vim 对一种叫 tags 的文本索引格式有特殊支持。事实上,Vim 自己的帮助文件都是用 tags 来索引的。我们用过了 Vim 帮助,也就用过了 tags 文件。下面展示了 $VIMRUNTIME/doc/tags 文件中的一部分:
|
||||
|
||||
```
|
||||
? pattern.txt /*?*
|
||||
?<CR> pattern.txt /*?<CR>*
|
||||
@ repeat.txt /*@*
|
||||
@/ change.txt /*@\/*
|
||||
@: repeat.txt /*@:*
|
||||
@= change.txt /*@=*
|
||||
@@ repeat.txt /*@@*
|
||||
@r eval.txt /*@r*
|
||||
A insert.txt /*A*
|
||||
ACL editing.txt /*ACL*
|
||||
ANSI-C develop.txt /*ANSI-C*
|
||||
|
||||
```
|
||||
|
||||
我们可以清楚地看到,其中内容分为三列:第一列是关键字,第二列是文件名,第三列是在目标文件中的匹配文本。当你在 Vim 的帮助文件中使用双击或 `<C-]>` 等命令跳转时,Vim 就会在 tags 文件中搜索,寻找到匹配项的时候就跳转到指定的文件,并利用匹配文本跳转到指定的位置。
|
||||
|
||||
注意我们有不止一个 tags 文件。单单从 Vim 帮助的角度,个人 Vim 配置目录下的 doc 目录里有一个 tags 文件;每当你装了一个新的带帮助文件的 Vim 插件时,你都需要到这个 doc 目录下运行 `helptags .` 来重新生成索引。每个 Vim 软件包的 doc 目录下也同样需要有 tags 文件,不过包管理器能够在安装、更新时自动帮我们在 doc 目录下生成 tags 文件。Vim 在你使用 `:help` 命令查帮助时,会自动在你的所有运行时目录(可以使用 `:set runtimepath?` 查看)下的 doc/tags 里查找第一个匹配项。
|
||||
|
||||
### 生成 tags 文件的工具
|
||||
|
||||
如果 tags 文件只支持 Vim 帮助文件的话,那我就没必要对其进行详细讨论了。之所以在这里讨论 tags,是因为它可以用在编程语言上。要生成 Vim 可以使用的支持常用编程语言的 tags 文件,我们需要使用下列两个工具之一:
|
||||
|
||||
- [Exubertant Ctags](http://ctags.sourceforge.net/)
|
||||
- [Universal Ctags](https://ctags.io/)
|
||||
|
||||
Exuberant Ctags 是已经存在了好多年的老牌工具。Windows 下可直接下载可执行程序,而 Linux 和 macOS 上的包管理器一般也直接支持。如:
|
||||
|
||||
- Ubuntu 下可使用 `sudo apt install exuberant-ctags`
|
||||
- CentOS 下可使用 `sudo yum install ctags`
|
||||
- macOS Homebrew 可使用 `brew install ctags`(但需要注意 macOS 本身提供了一个功能较简单的 ctags 命令,你可能需要将 /usr/local/bin 在路径里移到 /usr/bin 前面,或自己设置 alias,确保优先使用 /usr/local/bin/ctags)
|
||||
|
||||
Universal Ctags 还比较新,目前各操作系统的包管理器里多半还没有它,所以安装会麻烦一点。你需要自己查看文档,找到在你的操作系统上的安装方式。(我是直接从源代码编译了一个版本。)
|
||||
|
||||
我之所以要推荐 Universal Ctags,是因为虽然 Exuberant Ctags 和 Universal Ctags 都支持超过 40 种的常见编程语言,但 Exuberant Ctags 的最后一个版本 5.8,发布于 2009 年,之后就一直没有更新了。Universal Ctags 是基于 Exuberant Ctags 代码的改进版本,并把开发移到了 GitHub 上,项目一直处于活跃状态。想偷懒的话,可以直接使用 Exuberant Ctags;如果愿意折腾一下,或者明确遇到 Exuberant Ctags 的问题,则可以试试 Universal Ctags。
|
||||
|
||||
对于现代 C++ 代码,使用 Universal Ctags 还是挺重要的。老的 Exuberant Ctags 不能处理 C++11 以来的新语法——这当然也是件显而易见的事。
|
||||
|
||||
### 生成 tags 文件的命令
|
||||
|
||||
要生成 tags 文件时,你可以简单地进入到一个目录下,然后执行下面的语句对该目录及子目录下的程序源文件生成一个 tags 文件:
|
||||
|
||||
```
|
||||
ctags -R .
|
||||
|
||||
```
|
||||
|
||||
但根据场景和语言不同,你可能需要使用更多的选项。比如,对于 C++,我一般使用:
|
||||
|
||||
```
|
||||
ctags --fields=+iaS --extra=+q -R .
|
||||
|
||||
```
|
||||
|
||||
如果是对系统的头文件生成 tags 文件——可以用来查找函数的原型信息——那我们一般还需要加上 `--c-kinds=+p` 选项。为了一次性地对系统头文件简单地生成 tags 文件,我还专门写了个脚本 [gen_systags](https://github.com/adah1972/gen_systags) 来自动化这项工作。你如果感兴趣的话,也可以点进去看一下。
|
||||
|
||||
鉴于我们主要讲 Vim 而不是 Ctags,这个话题我暂时就点到为止、不展开了。你可以通过我给出的链接,以及 `man ctags` 或 `ctags --help` 的输出,自己进一步学习一下。讲到 C 的工作环境时,我们会再回到 Ctags。
|
||||
|
||||
### 使用 tags 文件
|
||||
|
||||
如果当前目录下或当前文件所在目录下存在 tags 文件,Vim 会自动使用这个文件,不需要你做额外的设定。你所需要做的就是在待搜索的关键字上(也可以在可视模式下选中需要的关键字)使用正常模式命令 `<C-]>`,或者按 `g`(`g` 可理解成 go)键加鼠标单击。你愿意的话,也可以手工输入命令 `:tag` 后面跟空格和待搜索的符号加回车键。这样 Vim 即会跳转到该符号的定义或声明位置。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/d2/5e/d205869f3312918f8e39a40c2592325e.gif" alt="Fig8.2" title="标签跳转示例">
|
||||
|
||||
如果待搜索的符号找不到,Vim 会报错“E426: tag not found”。如果存在一个或多个匹配项,Vim 会跳转到第一个匹配的位置。下面我列举一下其他相关的常用命令:
|
||||
|
||||
- `:tnext`(缩写 `:tn`)跳转到下一个标签匹配位置
|
||||
- `:tNext`(缩写 `:tN`)或 `:tprevious`(缩写 `:tp`)跳转到上一个标签匹配位置
|
||||
- `:tfirst` 或 `:trewind` 跳转到第一个标签匹配位置
|
||||
- `:tlast` 跳转到最后一个标签匹配位置
|
||||
- `:tselect 名称`(`:tselect` 可缩写为 `:ts`)跟 `:tag` 类似,但会列举可能的匹配项,让你自己选择(而非跳转到第一个匹配位置)
|
||||
- `g]` 跟 `<C-]>` 类似,但跟 `:tselect` 一样会给出一个列表而非直接跳转
|
||||
- `:tjump 名称`(`:tjump` 可缩写为 `:tj`)跟 `:tselect` 类似,但在只有一个匹配项的时候会直接跳转到匹配位置
|
||||
- `g<C-]>` 跟 `g]` 类似,但跟 `:tjump` 一样在只有一个匹配项时会直接跳转到匹配位置
|
||||
- `:stselect 名称`(`:stselect` 可缩写为 `:sts`)跟 `:tselect` 类似,但结果会打开到一个新分割的窗口中
|
||||
- `:stjump 名称`(`:stjump` 可缩写为 `:stj`)跟 `:tjump` 类似,但结果会打开到一个新分割的窗口中
|
||||
|
||||
我们的标签跳转分为 `:tag`、`:tselect` 和 `:tjump` 三种不同方法,正常模式和可视模式的命令 `<C-]` 也同样有后两种方法的变体,对应的命令分别是 `g]` 和 `g<C-]>`。这三个命令前面也都可以额外加上 `<C-W>`,表示结果打开到新窗口中而非当前窗口。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/10/ec/1017481027bdbca5862acc8b1356e9ec.gif" alt="Fig8.3" title="展示在新窗口中选择并打开 printf 的声明">
|
||||
|
||||
Vim 默认只在当前目录下和文件所在目录下寻找 tags 文件。对于含多层目录的项目,这个设定就不合适了。解决方法是使用 Vim 的选项 `tags`。一个小技巧是根据项目的可能深度,检查上层存在的 tags 文件:
|
||||
|
||||
```
|
||||
" 加入记录系统头文件的标签文件和上层的 tags 文件
|
||||
set tags=./tags,../tags,../../tags,tags,/usr/local/etc/systags
|
||||
|
||||
```
|
||||
|
||||
`tags` 选项的默认值是 `./tags,tags`,即检查文件所在目录下的 tags 文件和当前目录下的 tags 文件。上面这样的写法还会额外检查父目录下的 tags 文件,祖父目录下的 tags 文件,以及我们上面用 gen_systags 生成的 systags 文件。这对一个有不超过三层目录结构的项目来讲就足够了。如果你的项目目录层次更深,也只需要在 `tags` 选项里添加 `../../../tags` 这样的内容即可。
|
||||
|
||||
### Tagbar 插件
|
||||
|
||||
根据上面的描述,我们可以看到 Ctags 是一个可以从源代码中提取符号的工具。事实上,这个工具在我们不生成 tags 文件也都是有用的。Vim 的插件 tagbar 就可以利用 Ctags 来提取符号,生成源代码的结构图。只要 Ctags 能支持这种语言,插件就能“识别” 这种语言,来生成结构图;识别的好坏程度也视 Ctags 对其的支持程度而定。下面是一个示例:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/01/c8/01c7599a78c335d5279f4ec261bdc8c8.png" alt="Fig8.4" title="Tagbar 示例">
|
||||
|
||||
跟之前类似,假设使用 minpac 的话,我们需要在 vimrc 中“Other plugins”那行下面加入下面的语句,并运行 `:PackUpdate` 来安装一下:
|
||||
|
||||
```
|
||||
call minpac#add('majutsushi/tagbar')
|
||||
|
||||
```
|
||||
|
||||
我给它映射了快捷键 `<F9>`,可以快速打开和关闭 Tagbar 的窗口:
|
||||
|
||||
```
|
||||
" 开关 Tagbar 插件的键映射
|
||||
nnoremap <F9> :TagbarToggle<CR>
|
||||
inoremap <F9> <C-O>:TagbarToggle<CR>
|
||||
|
||||
```
|
||||
|
||||
## Quickfix 窗口
|
||||
|
||||
Vim 里有一种特殊类型的窗口,被称作 quickfix(快速修复)。这个窗口中会展示外部命令的结果,并可以通过这个窗口中的内容直接跳转到特定文件的特定位置。这个设计最初是用来加速“编辑-编译-编辑”这个循环的,但它的实际用处并不只是用来编译程序。
|
||||
|
||||
我们先来看一下 Vim 的 `:make` 命令。如果你的代码可以简单执行 `make` 来编译的话(也就是说,你已经写了或者生成了合适的 Makefile),你可以尝试直接在 Vim 里执行 `:make`。你会看到正常的执行过程。唯一不一样的地方是,如果编译失败了,Vim 会自动跳转到第一个出错的位置!
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/f5/d0/f515a16bcb32114edab186520b2a47d0.gif" alt="Fig8.5" title="演示 :make 失败时的行为">
|
||||
|
||||
如果使用 `:copen` 命令,我们就可以打开 quickfix 窗口。在里面我们可以看到完整的出错信息,并能通过颜色看出 Vim 解析了文件名和行号。我们在带文件名的行上双击即可跳转到对应位置。另外,我们在 quickfix 窗口中也有跟之前类似的“next”类命令:
|
||||
|
||||
- `:cnext`(缩写 `:cn`)跳转到下一个出错位置
|
||||
- `:cNext`(缩写 `:cN`)或 `:cprevious`(缩写 `:cp`)跳转到上一个出错位置
|
||||
- `:cfirst` 或 `:crewind` 跳转到第一个出错位置
|
||||
- `:clast` 跳转到最后一个出错位置
|
||||
|
||||
事实上,在这些下一个、上一个的命令中,我用得最多的就是这个快速修复里的跳转了。为了方便记忆,我对它们都映射了相似的快捷键。
|
||||
|
||||
```
|
||||
" 用于 quickfix、标签和文件跳转的键映射
|
||||
nmap <F11> :cn<CR>
|
||||
nmap <F12> :cp<CR>
|
||||
nmap <M-F11> :copen<CR>
|
||||
nmap <M-F12> :cclose<CR>
|
||||
nmap <C-F11> :tn<CR>
|
||||
nmap <C-F12> :tp<CR>
|
||||
nmap <S-F11> :n<CR>
|
||||
nmap <S-F12> :prev<CR>
|
||||
|
||||
```
|
||||
|
||||
这是我的映射,你可以根据自己的需要进行调整。另外要留意的一点是,取决于环境,不是所有的快捷键都能被 Vim 接收到,尤其在使用终端和远程连接的时候。比如,在 Mac 上有些快捷键已经被系统占用,并且终端基本不接受修饰键;在 Windows 的远程连接客户端里,PuTTY 不支持使用 Alt 的快捷键,但 mintty 就可以。
|
||||
|
||||
### `:make` 命令的其他细节
|
||||
|
||||
Vim 里的 `:make` 命令缺省会执行 make 命令,并且这是可以通过选项 `makeprg` 来进行配置的。比如,如果你希望启用四路并发编译,你就可以设置 `:set makeprg=make\ -j4`。你也可以使用 GNU Make 之外的构建工具,但需要注意的是,如果发现 Vim 不能识别你使用的构建工具产生的错误信息,你可能需要利用 `errorformat`([`:help errorformat`](https://yianwillis.github.io/vimcdoc/doc/quickfix.html#errorformat))选项来告诉 Vim 如何处理错误信息。
|
||||
|
||||
### `:grep` 命令
|
||||
|
||||
对我而言,跟构建使用频度至少一样高的命令是搜索,也就是根据关键字找到相关的源代码。这就可以使用 Vim 的 `:grep` 命令。跟 `:make` 命令相似,Vim 会调用一个合适的外部程序(可通过 `grepprg` 选项来进行配置)来进行搜索,并从结果中找到文件名、行号等信息。注意:在 Windows 上如果 Vim 没找到 grep 的话,它会调用 Windows 自带的 findstr 命令行工具;为了获得跟其他平台相同的体验和跟 Vim 本身相似的正则表达式,我强烈推荐你在 Windows 上也安装 grep 工具。我们上一讲讲到的搜索模式,大部分在 grep 里可以原封不动地使用,尤其是对 `\?`、`\+`、`\<` 和 `\>` 的解释。考虑到 vi 源自 Bill Joy,grep 源自 Ken Thompson,两者的老祖宗都是 ed,这自然也不是件令人意外的事。
|
||||
|
||||
如果使用 grep 命令的话,我们的命令大致如下所示:
|
||||
|
||||
```
|
||||
:grep '要查找的符号' 文件名列表
|
||||
|
||||
```
|
||||
|
||||
当然,grep 支持的复杂参数我们都可以用上。比如,下面的命令可以在所有的子目录里查找用到了 `printf` 的 .c 和 .h 文件:
|
||||
|
||||
```
|
||||
:grep -R --include='*.c' --include='*.h' '\<printf\>' .
|
||||
|
||||
```
|
||||
|
||||
**小提示:**在查看搜索结果时,适时使用 `zz`(或 `zt`、`zb`)重定位当前行在屏幕上的位置,可能可以更清晰地查看前后的相关代码。
|
||||
|
||||
### 异步支持
|
||||
|
||||
上面这些命令,都有一个缺点:在执行过程中你干不了其他事情。对于执行过程可能较慢的 make,这个问题尤其严重。幸好,在 Vim 8 支持异步任务之后,这个问题也得到了解决。我们利用一个插件,就可以获得类似在一些集成开发环境中的体验,在构建过程中仍然可以继续做其他事情。
|
||||
|
||||
我们首先需要安装一个插件 asyncrun.vim。跟前面类似,假设我们使用 minpac 的话,我们需要在 vimrc 中的合适位置加入下面这行:
|
||||
|
||||
```
|
||||
call minpac#add('skywind3000/asyncrun.vim')
|
||||
|
||||
```
|
||||
|
||||
我们还需要一个跟 `:make` 相似的命令。我使用下面的命令定义(今天我们重点看使用,定义的细节就不讨论了):
|
||||
|
||||
```
|
||||
" 和 asyncrun 一起用的异步 make 命令
|
||||
command! -bang -nargs=* -complete=file Make AsyncRun -program=make @ <args>
|
||||
|
||||
```
|
||||
|
||||
这个命令同样会使用 `makeprg` 选项。不过,还有个问题是默认情况下屏幕上看不到执行过程的信息。我们可以让 asyncrun 在执行命令时立即打开 quickfix 窗口:
|
||||
|
||||
```
|
||||
" 异步运行命令时打开 quickfix 窗口,高度为 10 行
|
||||
let g:asyncrun_open = 10
|
||||
|
||||
```
|
||||
|
||||
对于 C/C++ 程序员来讲,启动和停止构建应该是一个很频繁的操作吧。所以,我也给它分配了一个快捷键:
|
||||
|
||||
```
|
||||
" 映射按键来快速启停构建
|
||||
nnoremap <F5> :if g:asyncrun_status != 'running'<bar>
|
||||
\if &modifiable<bar>
|
||||
\update<bar>
|
||||
\endif<bar>
|
||||
\exec 'Make'<bar>
|
||||
\else<bar>
|
||||
\AsyncStop<bar>
|
||||
\endif<CR>
|
||||
|
||||
```
|
||||
|
||||
上面的代码通过判断异步任务状态和窗口是否可修改,还会自动执行保存文件和终止构建等操作。建议你自己尝试一下。鉴于我们本讲内容已经很多了,我们暂时就不讲解了。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/12/65/12a729849d86ef08263622929ed05a65.gif" alt="Fig8.6" title="演示异步启动构建和使用快捷键跳转">
|
||||
|
||||
## 查看文档
|
||||
|
||||
Vim 里快捷键 `K` 可以用来查看光标下关键字的相关文档。它的行为是由选项 `keywordprg`([`:help 'keywordprg'`](https://yianwillis.github.io/vimcdoc/doc/options.html#'keywordprg'))控制的。这个选项的缺省值是 `man`,表示查看 Unix 的 man 手册,很多文件类型插件会对当前缓冲区设置一个更合适的值,如 Vim 脚本就会直接把行为改成调用 `:help` 命令。
|
||||
|
||||
查看 man 手册的默认行为通常只在终端工作良好,而在图形界面 Vim 里会出现显示问题。我推荐使用 Vim 内置的 man 插件,并把全局的 `keywordprg` 设成 `:Man`:
|
||||
|
||||
```
|
||||
" 启用 man 插件
|
||||
source $VIMRUNTIME/ftplugin/man.vim
|
||||
|
||||
set keywordprg=:Man
|
||||
|
||||
```
|
||||
|
||||
这样,我们在使用 `K` 命令时,将在 Vim 里直接打开 man 手册,效果如下所示:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/yy/08/yy9c761e5cbaa5ff91d5f2c0952f6208.png" alt="Fig8.8" title="使用 K 在 Vim 里查看 man 手册">
|
||||
|
||||
你看,是不是就方便多了?
|
||||
|
||||
## 内容小结
|
||||
|
||||
今天我们讨论了 Vim 中对编程的基本支持,包括:
|
||||
|
||||
- Vim 使用编程规则来判断文件类型,逻辑放在文件 filetype.vim 里。
|
||||
- Vim 里有很多设置文件格式的选项,自动设置一般在 ftplugin 和 indent 目录下;我们可以简单地在 vimrc 配置文件中进行定制。Vim 通过 `cindent` 和 `cinoptions` 选项,对类 C 的语言提供了相当细颗粒的缩进风格支持。
|
||||
- Vim 对 tags 文件提供了完整的支持,Ctags 工具可以对超过 40 种主流编程语言生成 tags 文件,供 Vim 和 Tagbar 使用。
|
||||
- Vim 里通过 quickfix 窗口,对构建和搜索提供了内置支持;从 Vim 8 开始,我们可以使用异步支持,在构建时继续进行编辑。
|
||||
- Vim 里通过 `K` 命令,可以快速地查阅文档;通过 man 插件,我们可以直接在 Vim 里查阅 man 手册。
|
||||
|
||||
本讲我们的配置文件更改较多,请仔细检查一下其中内容。对应的标签是 `l8-unix` 和 `l8-windows`。
|
||||
|
||||
## 课后练习
|
||||
|
||||
今天的内容不难,但较多较杂。请你务必自己试验一下我上面总结的这些功能,来加深对这些功能的理解。要提高编辑的效率,熟悉基本功能一定是必要的。
|
||||
|
||||
- 不管你用什么语言编程,找出 Vim 是如何判断你的源代码文件类型的。
|
||||
- 看看你能不能找出 Vim 对你使用的编程语言有没有附加的设置(可选)。
|
||||
- 尝试一下 Ctags,并用 `<C-]>` 来跳转到某个符号的定义。
|
||||
- 如果你的语言有构建过程,尝试异步的构建过程。
|
||||
- 尝试使用 `:grep` 命令来在你的源代码中搜索某一符号。
|
||||
- 对于 C 系语言,尝试使用 `K` 命令来查阅文档。
|
||||
|
||||
我是吴咏炜,我们下一讲再见。
|
||||
200
极客时间专栏/Vim 实用技巧必知必会/基础篇/09|七大常用技巧:让编辑效率再上一个台阶.md
Normal file
200
极客时间专栏/Vim 实用技巧必知必会/基础篇/09|七大常用技巧:让编辑效率再上一个台阶.md
Normal file
@@ -0,0 +1,200 @@
|
||||
<audio id="audio" title="09|七大常用技巧:让编辑效率再上一个台阶" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/1a/a2/1a4d664f89c5b39c0f6edf15fbb00ea2.mp3"></audio>
|
||||
|
||||
你好,我是吴咏炜。
|
||||
|
||||
学到这里,你应该已经初步掌握 Vim 了。我们这一讲来重点看一下 Vim 里的七大常用编辑技巧。有些技巧你直接就可以用,有些则需要安装第三方插件。但无论是哪种情况,它们都可以大大提高你的编辑效率。
|
||||
|
||||
从这一讲开始,我们将不再讨论插件的安装过程,而只是给出像 skywind3000/asyncrun.vim 这样的名称。相信你学到现在应该已经不需要我再详细讲述这样的基础知识了。如果你对安装插件还不太熟练的话,请复习第 4 讲。接下来,我们正式开始今天的内容。
|
||||
|
||||
## 自动完成
|
||||
|
||||
自动完成是一个编辑器中很主流的功能了。通常,我们希望编辑器能在我们输入一部分内容时就能猜到我们希望输入的是什么,并能够予以提示。自动完成可以节约我们输入的工作量,是一件编辑中非常必要的利器。
|
||||
|
||||
Vim 内置有自动完成功能。最基本的自动完成功能有两种:
|
||||
|
||||
- 基于当前文件文本的自动完成
|
||||
- 基于文件系统的自动完成
|
||||
|
||||
我们先说**基于当前文件文本**的自动完成。在当前文件里,或当前文件用 `#include`(C 类语言的情况)包含的文件里包含某个关键字时,你可以输入头若干个字母并按下 `<C-P>`(表示 previous)或 `<C-N>`(表示 next)来进行自动完成。这两者的区别是,`<C-P>` 是从当前位置往前找,而 `<C-N>` 是从当前位置往后找。当只有一个匹配项时,Vim 直接给出完成结果,再次按下 `<C-P>` 或 `<C-N>` 则取消自动完成。当存在多个匹配项时,Vim 会根据搜索顺序给出匹配项列表并使用第一个匹配项;再次按下 `<C-P>` 或 `<C-N>` 则可以在列表里进行选择。
|
||||
|
||||
Vim 的缺省选项能帮你在 Unix 系统上找到系统的头文件,利用里面出现的关键字来完成。想要在其他语言或平台里找到当前文件“包含”的文件里的关键字,请参考下列选项帮助:
|
||||
|
||||
- [`:help include`](https://yianwillis.github.io/vimcdoc/doc/options.html#'include')
|
||||
- [`:help includeexpr`](https://yianwillis.github.io/vimcdoc/doc/options.html#'includeexpr')
|
||||
- [`:help isfname`](https://yianwillis.github.io/vimcdoc/doc/options.html#'isfname')
|
||||
- [`:help path`](https://yianwillis.github.io/vimcdoc/doc/options.html#'path')
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/72/68/7231db664f67cb5403c287d5e4b70c68.gif" alt="Fig9.1" title="自动完成的示例">
|
||||
|
||||
我们再看一下**基于文件系统**的自动完成。当你在插入模式下输入一个绝对路径或者当前目录下的文件/目录名称的一部分时,你可以使用 `<C-X><C-F>` 来启动文件自动完成。在此之后,操作就和前面一样了,你可以使用 `<C-P>` 和 `<C-N>` 在匹配项中跳转和取消。
|
||||
|
||||
Vim 里还有其他一些以 `<C-X>` 开始的自动完成功能。比如,你可以用 `<C-X><C-K>` 从配置的词典中选择合适的单词,可以用 `<C-X><C-O>` 进行“代码自动完成”。但这些功能要么不常用,要么在缺省配置下工作得并不好。所以,今天我就暂时不讨论其他自动完成功能了。等到了提高篇和拓展篇,我们再来看英文文本编辑和代码自动完成这两个话题。
|
||||
|
||||
最后,要注意任何自动完成功能都可能会重复你的错误。如果你一开始拼错了,后面又拼对了,很可能会发现前面的错误。而一旦使用自动完成,你要是一开始就拼错了,后面可能就会不断重复之前的错误。这当然不是编辑器的错,但作为我曾经见到发生过的问题,我觉得值得提醒你一下。
|
||||
|
||||
## 文本目标跳转
|
||||
|
||||
如果光标下面是一个计算机可以找到的文件,你一定希望我们有办法可以一下子打开这个文件吧。这正是我们这一节要讨论的技巧。
|
||||
|
||||
当光标下的文件名可以在 `path` 选项标识的目录下找到时,我们可以很方便地跳转过去。你需要的是正常模式命令 `gf` 和 `<C-W>f`。估计你很容易猜到,前者是直接跳转到文件(理解为“goto file”),后者则会打开一个新窗口(window),在新窗口里打开该文件。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/9c/33/9cd42ac85567641079dd781cd51fa733.gif" alt="Fig9.2" title="文件跳转的演示">
|
||||
|
||||
如果光标下面是一个链接,或者非文本文件,那我们该怎么办呢?显然,即使 Vim 可以打开这个文件,看到的内容也多半不是你想要的(你想看图片,还是把图片当成文本的乱码?)。这时候,最简单的解决方式是使用 netrw 插件提供的 `gx` 命令。它的缺省行为是使用操作系统提供的机制来打开光标下的文件或链接。
|
||||
|
||||
比较让人伤心的是,最新版本的 netrw 插件在打开链接时的行为不正常。[这个问题已经报告有一年了,还没有解决。](https://github.com/vim/vim/issues/4738)作为临时方案,我在 Vim 配置的目录放了一个可以工作的老版本,你可以把这个文件复制到你的 Vim 配置目录下的 plugin 子目录下来绕过这个问题。此外,`gx` 只适合本机,不适合在远程连接上使用。
|
||||
|
||||
## Vim 寄存器/剪贴板
|
||||
|
||||
我们已经学到,Vim 的删除和复制命令(如 `d` 和 `y`)会把内容存起来,以供粘贴命令(如 `p` 和 `P`)使用。我们还没有讨论这种内容存储有什么特别的地方。
|
||||
|
||||
首先,估计你已经知道的是,Vim 把要粘贴的内容存在 Vim 内部的“寄存器”(register)里,而非系统的剪贴板。你不一定知道的是,Vim 里的寄存器有好多个。事实上,Vim 有超过 40 个不同的寄存器!我们挨个来看一下:
|
||||
|
||||
- 首先是无名寄存器。当操作没有用 `"` 加寄存器名称指定寄存器时,我们默认使用无名寄存器。不过,我们仍可以使用 `""` 来指定使用无名寄存器,也就是说,`""p` 和 `p` 效果相同。
|
||||
- 其次是数字寄存器 `0` 到 `9`。`0` 号寄存器中放的永远是最近一次复制(yank)的内容。这和无名寄存器很不一样,它里面放的是最近操作的结果,也包括了 `d`、`x`、`c` 等命令,特别是包括了粘贴命令所替换的内容。`1` 到 `9` 号寄存器中放的则是上一次、倒数第二次、直到倒数第九次被删除或修改命令删除的文本。在做少量的用一个名字替换另一个名字、而又懒得使用替换命令时,`"0p` 是一个接近图形界面里的粘贴命令的常用选择。
|
||||
- 然后有小删除寄存器 `-`。上面我说得不全,删除内容进入 `1` 到 `9` 号寄存器的前提条件是被删除的内容至少有一行,或者使用了移动命令 `%`、`(`、`)`、```、`/`、`?`、`n`、`N`、`{` 和 `}` 进行删除。否则,删除的内容只会进入 `-` 而不是 `1` 到 `9` 号寄存器。
|
||||
- 常用的有名寄存器 `a` 到 `z`。这些寄存器仅在用户手工指定时才会使用,内容在下一次打开 Vim 时仍然存在。比如,我们可以用 `"ayy` 代替 `yy` 把当前行复制到 `a` 寄存器中,以后就一直可以用 `"ap` 来进行粘贴了,直到 `a` 寄存器的内容被替换为止。
|
||||
- 不常用的特殊寄存器 `.` 、`:`、`#` 和 `%`。这些相对来说不那么常用,请自行查看帮助文件 [`:help ".`](https://yianwillis.github.io/vimcdoc/doc/change.html#quote.) 等。
|
||||
- 黑洞寄存器 `_`。专门用来删除,目的就是不要影响无名寄存器的内容。
|
||||
- 搜索寄存器 `/`。存放是上一次搜索使用的模式。
|
||||
- 表达式寄存器 `=`。可以把 Vim 表达式估值的结果作为寄存器的内容。这个我们以后讲 Vim 脚本编程的时候再探讨。
|
||||
- 最后是图形界面剪贴板寄存器 `+`、`*` 和 `~`。一般而言,`+` 寄存器代表操作系统的剪贴板,和图形界面应用程序交互用这个就好;你用图形界面 Vim 菜单里的拷贝和粘贴访问的也是系统剪贴板。`*` 和 `~` 在 X11 和 GTK 环境下有一些特殊用途,我们目前就不展开了。想深入钻研的话,可以查看帮助文档 [`:help "+`](https://yianwillis.github.io/vimcdoc/doc/gui_x11.html#quoteplus)、[`:help "*`](https://yianwillis.github.io/vimcdoc/doc/gui.html#quotestar) 和 [`:help "~`](https://yianwillis.github.io/vimcdoc/doc/change.html#quote%7E)。
|
||||
|
||||
寄存器在正常模式下可以用 `d`、`y`、`p` 等命令来访问,你现在应当已经很清楚了。它们在插入模式和命令行模式下也可以用 `C-R` 加寄存器名来访问,这经常也会省去你很多打字的麻烦。
|
||||
|
||||
这些寄存器当然不是每个都常用。具体你是否会用到它们,取决你的工作方式。下面我说几个我自己编辑时的常用场景。
|
||||
|
||||
### 常用的寄存器使用场景
|
||||
|
||||
如果要**交换两行内容**,可以直接利用删除命令会把删除的内容放到无名寄存器这个特性。我们在第一行上面按下 `dd`,然后直接按 `p` 粘贴即可。
|
||||
|
||||
如果要**交换两处文本内容**,可以类似地使用删除和粘贴替换都会把内容放到无名寄存器这个特性。我们选中第一处文本,按下 `d` 进行删除;然后选中第二处文本,按下 `p` 进行粘贴;最后回到第一处文本的原来位置,使用 `P` 把文本粘贴回去即可。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/fe/b8/fe8e4ee739357cecdcd2a3f1f354e1b8.gif" alt="Fig9.3" title="两处文本交换的演示">
|
||||
|
||||
如果要**少量修改某一变量名称**(多的话使用 `:s` 替换命令更合适),可以把光标移到变量名称上,用 `*` 进行开启自动搜索,然后编辑变量名称到合适;随后复制新的变量名称,反复使用 `n` 命令搜索,并用 `ve"0p` 进行替换即可。
|
||||
|
||||
当然,反复打 `ve"0p` 真的会感觉这个命令有点长。鉴于这个组合键使用的频度还挺高,我觉得映射一个更短的按键比较好,我的选择是 `\v`,同时,我做了点更通用的处理:
|
||||
|
||||
```
|
||||
" 替换光标下单词的键映射
|
||||
nnoremap <Leader>v viw"0p
|
||||
vnoremap <Leader>v "0p
|
||||
|
||||
```
|
||||
|
||||
关于 `<Leader>` 的含义,可查看帮助文档 [`:help <Leader>`](https://yianwillis.github.io/vimcdoc/doc/map.html#mapleader),里面说得很清楚,我就不重复了。如果你忘了 `viw` 的意义,请复习一下第 3 讲里的文本对象。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/fd/cc/fd28ec0d43a977c62dc2b4dcacd40fcc.gif" alt="Fig9.4" title="使用复制、自动选择、粘贴修改变量名">
|
||||
|
||||
### 宏的录制和播放
|
||||
|
||||
Vim 里可以用 `q` 把动作记录到寄存器里,然后使用 `@` 来播放这些动作。上面这个变量更名,如果用宏来做也可以:
|
||||
|
||||
- 用 `*` 开启搜索
|
||||
- 键入 `qa` 开始录制宏到 `a` 寄存器;当然我们可以使用其他寄存器,只要被录制的命令不会修改这个寄存器即可,所以一般使用 `a` 到 `z` 这 26 个有名寄存器
|
||||
- 键入 `n` 进行搜索;先行搜索的目的是,如果搜索不到内容,命令出错,宏的剩余部分就不会被执行
|
||||
- 键入 `eabar<Esc>` 把 `foo` 修改为 `foobar`
|
||||
- 键入 `q` 结束宏录制
|
||||
- 键入 `@a` 播放录制的宏
|
||||
- 重复上一步直到 Vim 报告找不到 `foo` 为止
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/58/1d/588f5bd4416945d442924ac24ba0261d.gif" alt="Fig9.5" title="使用宏修改变量名">
|
||||
|
||||
关于宏的进一步细节可以查看帮助文件([`:help q`](https://yianwillis.github.io/vimcdoc/doc/repeat.html#q)),我就不展开了。
|
||||
|
||||
从上一节的 `\v` 到宏再到 `:s` 命令,对我们当前的任务而言,自动化程度逐步上升,但交互性逐步下降,“僵硬”性也逐步上升。对于重复遍数较多、信心较高的修改,我们应当偏向使用更自动化的方式,对于重复遍数较少或信心较低的修改,我认为使用不那么自动化的方式更有助于实时检查修改的效果。
|
||||
|
||||
今天关于寄存器和复制粘贴我们就讲到这里。我们以后还会有讨论到寄存器的时候。
|
||||
|
||||
## 文本对象增强
|
||||
|
||||
Vim 对文本对象的支持我已经在第 3 讲里讨论过了。那些当然是很不错的功能,不过,能不能在那些功能的基础上再进一步,做出更有用的功能呢?对于写了多个 Vim 插件的 Tim Pope 来说,答案是肯定的。
|
||||
|
||||
具体来说,如果你安装了他的 tpope/vim-surround 插件,你可以实现下面这些功能:
|
||||
|
||||
- 在一个单词的外面加上引号,如把 `word` 变成 `"word"`,可以使用命令 `ysiw"`
|
||||
- 把一个单词的外面的双引号变成单引号(有强迫症的 Python 程序员很可能有这样的需求),如把 `"word"` 变成 `'word'`,可以使用命令 `cs"'`
|
||||
- 把外面的引号或括号变成 HTML 标签也没有问题,如把 `[my choice]` 变成 `<em>my choice</em>`,可以使用命令 `cs[<em>`
|
||||
- 可视模式也有类似的命令,如可以在选中 `my choice` 后,输入 `S<em>` 把文本变成 `<em>my choice</em>`
|
||||
- 当然,你也可以把加上的包围符号移除,命令是 `ds` 后面跟包围符号,如 `ds"` 可以移除外围的双引号;要移除 HTML 标签则使用 `t` 来表达,即使用 `dst` 来移除文本外面的第一个 HTML 标签
|
||||
|
||||
注意 Vim 命令 `.` 只能用来重复 Vim 的内置命令,而不能用来重复上面这样的用户自定义命令。为了解决这个问题,我也会安装 tpope/vim-repeat 插件,使得重复命令对上面这样的情况依然能够生效。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/27/2a/270e5c5a2cc9ccccf295b7f59db99d2a.gif" alt="Fig9.6" title="Vim-surround 和 vim-repeat 的演示">
|
||||
|
||||
## 撤销树
|
||||
|
||||
Vim 不仅支持多级撤销,而且有撤销树的概念。利用撤销树,你可以转回到编辑中的任何一个历史状态。不过,问题是,Vim 用来管理撤销树的命令不那么直观。在使用撤销树的图形化插件之前,我自己也没有把相关的命令真正用好。
|
||||
|
||||
著名的撤销树插件我知道两个,一个是 mbbill/undotree,一个是 sjl/gundo.vim。两者功能相似,界面风格和快捷键有所不同。鉴于 undotree 功能更加丰富,我就以它为例来介绍一下。
|
||||
|
||||
从下图中可以看到,undotree 可以展示完整修改历史。你可以用 `J` 和 `K` 在历史中跳转,左下角的预览窗口中就会显示修改的内容,右侧文件直接会回到相应的历史状态,并加亮最近的那次修改。一旦用上这个插件,就真的回不到没有这个插件的环境了。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/3c/18/3c99c626290b302139e086ce05eebb18.png" alt="Fig9.7" title="Undotree 插件的效果">
|
||||
|
||||
另外需要稍加注意的一点是,一旦这个文件在其他编辑器里修改了,Vim 发现内容对不上,就无法保留编辑的历史。有一个绕过方法是,当你需要使用其他编辑器修改前,确保你在 Vim 里打开了该文件并且所有修改已保存;这样,在修改完成之后,只要在 Vim 里用 `:e` 命令重新载入该文件,Vim 就可以把外部的修改也保存在撤销历史记录里,保留完整的编辑历史。此外要注意的是,**最后得在 Vim 里使用 `:w` 存盘一次**,才能把编辑历史真正保存下来——即使你在 Vim 里没有进行任何修改,也需要这样做一下才能保存修改的历史。
|
||||
|
||||
## 对当前缓冲区的更名和移动
|
||||
|
||||
你肯定遇见过文件需要更名或者移动吧。这当然很简单,你可以通过图形界面或命令行进行操作。但这样操作之后,有一个问题是 Vim 的撤销历史跟文件就再也对不上了,你也没法再继续撤销更名或移动前的编辑操作了。有一个 Vim 插件,也是 Tim Pope 写的 tpope/vim-eunuch,可以解决这个问题。
|
||||
|
||||
事实上,这个插件的功能远不止更名和移动。它实际上是把 Unix 的很多命令行工具搬到了 Vim 里(比较一下 Unix 和 eunuch 的发音你就知道这个插件的名字是什么意思了)。对我来说,最重要的就是它提供的 `:Rename` 和 `:Move` 命令,后面跟的参数就是新的名字或路径。这样操作之后,以后再打开这个更名或移动后的文件,仍然能够访问它的一切编辑历史。
|
||||
|
||||
## 模糊文件查找
|
||||
|
||||
使用 NERDTree 的话,你可以通过浏览目录来打开文件。这种方式,对于你知道文件在哪个目录下、但不知道文件名的时候特别有用。另外一种可能的情况是,你知道文件名或其中的关键部分,但你不知道或不关心文件在哪里。这种情况下,Fzf 的模糊匹配就非常有用了。我们先来看一下动画演示,有一个初步的印象:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/da/36/da779a8796a771f3cbbce130c13a1036.gif" alt="Fig9.8" title="Fzf 的动态文件筛选演示">
|
||||
|
||||
从动画可以看到,插件使用的是模糊匹配的方式,可以动态展示搜索的结果,并能直接预览当前选中的文件内容(在窗口足够宽的情况下)。因而这种方式不仅快,而且非常直观。
|
||||
|
||||
跟其他插件不同的是,fzf.vim 插件依赖于 fzf 命令行工具。在 [fzf](https://github.com/junegunn/fzf) 的页面上列出了具体安装方式,支持各个平台。但由于这个软件比较新,老一点的 Linux 发布版(如 Ubuntu 18.04 和 CentOS 7)在包管理器里还没有 fzf。所以我的安装建议是:
|
||||
|
||||
- 对于 macOS,使用 Homebrew 命令 `brew install fzf` 安装。
|
||||
- 对于 Ubuntu 19.10 或更新版本,使用 `sudo apt-get install fzf` 安装。
|
||||
- 对于较老的 Ubuntu、CentOS 7 和其他页面中没有列出的 Linux 发布版,直接使用[二进制发布版本](https://github.com/junegunn/fzf-bin/releases),一般使用后缀为“linux_amd64.tgz”的文件。
|
||||
- 对于 Windows,也使用[二进制发布版本](https://github.com/junegunn/fzf-bin/releases),使用后缀为“windows386.zip”(32 位)或“windows_amd64.zip”(64 位)的文件。
|
||||
|
||||
在安装了 fzf 后(可以执行 `fzf` 来验证一下,它会枚举当前目录下的所有文件,并在你输入字符时缩小匹配;按 `<CR>` 选择文件,按 `<Esc>` 取消选择),就可以安装插件了。使用 minpac 的话,我们需要在 vimrc 中加入下面两行:
|
||||
|
||||
```
|
||||
call minpac#add('junegunn/fzf', {'do': {-> fzf#install()}})
|
||||
call minpac#add('junegunn/fzf.vim')
|
||||
|
||||
```
|
||||
|
||||
在安装完成之后,你就可以像我前面展示的那样使用 `:Files` 命令了。更多高级用法可以查看 [fzf.vim](https://github.com/junegunn/fzf.vim) 的页面。
|
||||
|
||||
顺便说一句,如果你对安装一个可执行文件有点发怵的话,插件也可以自动帮你下载 `fzf` 命令。但这样做的缺点是,fzf 就只能在 Vim 里面使用了。如果你使用包管理器安装或手工安装,fzf 可以在 Vim 里使用,也可以在 Bash 等其他地方使用——fzf 的 Bash 集成是可以大大提升 shell 的使用体验的,不过这不属于我们 Vim 课程要讨论的话题,就请你自行参阅文档了。
|
||||
|
||||
这个插件可以跟其他工具进一步配合。如果你安装了 [ripgrep](https://github.com/BurntSushi/ripgrep) 和 [bat](https://github.com/sharkdp/bat) 的话,可以获得更好的效果。动图中右下角文件预览的语法加亮的效果就依赖于系统里有 bat。如果你装了 ripgrep 的话,可以考虑设置下面的环境变量:
|
||||
|
||||
```
|
||||
export FZF_DEFAULT_COMMAND='rg --files --sortr modified'
|
||||
|
||||
```
|
||||
|
||||
这样的话,fzf 可以利用 ripgrep 来自动过滤掉被 Git 忽略的文件、隐藏文件、二进制文件等程序员通常不关心的内容,并将结果以修改时间倒排,确保最新修改的文件在最下面,大大提高了迅速找到你需要的文件的概率。
|
||||
|
||||
## 内容小结
|
||||
|
||||
今天我们讲述了不少编辑中的技巧。鉴于这些内容比较散、单项内容又比较小,我在这儿只对它们适用的场合作一下快速总结:
|
||||
|
||||
- Vim 里有自动完成功能,可以让你只输入文本或文件名/路径的一部分,让 Vim 来帮你完成剩余部分。
|
||||
- 反过来,对于文件中出现的文件名和超链接,Vim 也支持打开它们。
|
||||
- Vim 里的寄存器相当于几十个不同用途的自动剪贴板,用好它们,能更加高效地完成常见的编辑动作。
|
||||
- Vim 里的文本对象是个特色功能,vim-surround 和 vim-repeat 插件又对其进行了进一步增强。我觉得这对前端程序员和 Python 程序员会特别有用。
|
||||
- 跨会话撤销已经很强大了,而撤销树则让你能够充分发挥这个强大功能的潜力。
|
||||
- Vim-eunuch 插件可以让你在对文件进行更名和移动时仍然保留其编辑/撤销历史。
|
||||
- Fzf.vim 插件提供若干快速查找文件的工具,它的最基本命令 `:Files` 可以让你使用部分匹配的文件名快速地在当前目录或指定的目录下面的任一目录里找到你需要的文件。
|
||||
|
||||
本讲我们的配置文件修改也不少,包含了我们今天讲到的这些插件。对应的标签是 `l9-unix` 和 `l9-windows`。
|
||||
|
||||
## 课后练习
|
||||
|
||||
今天讲的主要是技巧,而非理论知识,所以最主要的课后练习,就是需要自己实践一下,把需要安装的插件也全部都装起来。如果有任何问题,可以留言和我讨论。
|
||||
|
||||
希望你根据你的实际使用场景,可以举一反三、融会贯通。比如,我讲到了交换两行,你能不能也能做到交换两列的内容呢?学以致用是掌握 Vim 这样的工具的唯一方法。
|
||||
|
||||
我是吴咏炜,我们下一讲再见。
|
||||
224
极客时间专栏/Vim 实用技巧必知必会/基础篇/10|代码重构实验:在实战中提高编辑熟练度.md
Normal file
224
极客时间专栏/Vim 实用技巧必知必会/基础篇/10|代码重构实验:在实战中提高编辑熟练度.md
Normal file
@@ -0,0 +1,224 @@
|
||||
<audio id="audio" title="10|代码重构实验:在实战中提高编辑熟练度" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/52/11/52f5ee20d2721fb7c381a27059b42d11.mp3"></audio>
|
||||
|
||||
你好,我是吴咏炜。
|
||||
|
||||
在前几讲中,我们已经学了很多关于 Vim 的知识,现在需要好好消化一下。今天是基础篇的最后一讲,我们就基本上不学新的内容了,而是通过一个假想的代码重构实验,来复习、巩固已经学到的编辑技能。
|
||||
|
||||
## 开始前的准备工作
|
||||
|
||||
这是一堂实验课,你需要跟着我一步步地操作。跟只学习文字内容相比,实践操作能让你收获更多。所以,就请你现在把电脑准备好,跟我来吧。
|
||||
|
||||
今天我们将要做的是,签出我为极客时间写的 C++ 示例程序,并对其中的代码进行重构。别紧张,你不需要精通 C++,因为我会在必要的时候对代码进行解释。你学习的重点在于,我是如何进行编辑的,而不是我写的代码是什么意思。
|
||||
|
||||
首先,你需要先为工作代码找一个合适的父目录,然后用下面的命令签出代码(Windows 下面去掉“\”全部写一行,或者把“\”换成“^”)):
|
||||
|
||||
```
|
||||
git clone --recurse-submodules \
|
||||
--shallow-submodules \
|
||||
https://github.com/adah1972/geek_time_cpp.git
|
||||
|
||||
```
|
||||
|
||||
万一我以后更改代码的话,就有可能造成内容或路径发生变化。所以,请把我们今天编辑的 commit id 记下来:632b067。如果你用 `git log` 看到 HEAD 的 commit id 不是它,可使用 `git checkout 632b067` 这个命令来签出跟今天完全相同的版本。
|
||||
|
||||
下面,我们就开始了!
|
||||
|
||||
## 类模板 smart_ptr 更名
|
||||
|
||||
我们第一步要做的,是把示例的 `smart_ptr` 类模板更名为 `shared_ptr`。同时,为了避免跟标准的 `shared_ptr` 发生冲突,我们要把它放到名空间 `gt`里面去(当然,你可以用其他名字;这只是我们的示例)。
|
||||
|
||||
大体思路是,先需要找到 `shared_ptr` 定义所在的文件,对其进行修改;然后找到使用该文件的地方,也进行相应的修改。下面我们就来做一下。
|
||||
|
||||
### 修改类定义
|
||||
|
||||
首先,我们需要进入 `geek_time_cpp` 所在的目录。如果你前面的命令就是 `git clone` 的话,那现在使用 `cd geek_time_cpp` 就可以了。
|
||||
|
||||
然后,我们当然是启动 Vim 了。假设我们知道 `smart_ptr` 被定义在 smart_ptr.h 头文件里,那我们最快的打开方式就是使用 `:Files` 命令,然后输入“sm”,即可看到“common/smart_ptr.h”成了第一选择。我们此时按下回车键即可打开文件。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/da/5e/daa9009fe8f7c74a2c14fb64da06155e.gif" alt="Fig10.1" title="使用 fzf.vim 插件打开文件的界面">
|
||||
|
||||
进入文件后,我们先来看一下文件的结构。根据目前的 Vim 配置,我们可以使用 `<F9>` 打开 tagbar 插件。注意,这个文件使用了 C++11,Exuberant Ctags 会有错误的识别。下面的截图是安装了 Universal Ctags 之后的结果:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/bc/34/bc206cdd5b2094d6676a8ba283410c34.png" alt="Fig10.2" title="使用 tagbar 查看文件结构">
|
||||
|
||||
我们可以看到这个文件比较简单,里面主要就是两个类的定义和一些全局函数。不过,我们还是要确认一下,文件中没有任何会被错误匹配替换的内容。我们可以在右侧窗口里双击“smart_ptr”,这样左侧窗口就会跳转到 `smart_ptr` 的定义上,并且光标停留在类名上面。这样,我们只需使用 `*` 启动搜索和加亮即可。使用 `n` 继续搜索,我们很快就能确认文件中确实没有冲突的内容。
|
||||
|
||||
下面,我们进行替换操作,需要键入的是 `:%s/<C-R>//shared_ptr/g<CR>`(`<C-R>` 和 `<CR>` 都是按键,而非小于符号后面跟其他字符)。我们不需要手工输入 `\<smart_ptr\>`,因为搜索寄存器 `/` 中已经有我们要的内容了。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/fb/98/fbcc22dd7a2424afbfac32045b3e1e98.gif" alt="Fig10.3" title="键入替换命令">
|
||||
|
||||
最后,我们在第一个类定义的前面加上 `namespace gt {`、在最后的 `#endif` 前面加上 `} /* namespace gt */`,就完成了定义的修改。
|
||||
|
||||
不过,现在文件名还没有更改,文件里的包含保护(即宏 `SMART_PTR_H`)也没有更改。包含保护需要简单的重命名,就请你用我们目前介绍的任一方法自己完成了。随后,我们用命令 `:Rename shared_ptr.h` 即可完成更名和存盘操作。
|
||||
|
||||
### 修改使用 smart_ptr 的地方
|
||||
|
||||
我们先试着用下面的命令搜索一下:
|
||||
|
||||
```
|
||||
:grep -R --include="*.cpp" --include="*.h" "\<smart_ptr\>" .
|
||||
|
||||
```
|
||||
|
||||
(小提示:在查看搜索结果的时候,适时使用 `zz`、`zt` 和 `zb` 命令,可以把周边的代码看得更清楚。)
|
||||
|
||||
使用 `:cn` (或我们定义的快捷键)仔细检查搜索出来的结果,我们会发现有一些误匹配:有 `smart_ptr` 是 `unique_ptr` 的情况,也有 `smart_ptr` 是策略类的情况。
|
||||
|
||||
我们稍微改换一下方法,搜索对 `smart_ptr.h` 的使用:
|
||||
|
||||
```
|
||||
:grep -R --include="*.cpp" --include="*.h" "\<smart_ptr\.h\>" .
|
||||
|
||||
```
|
||||
|
||||
这样的话,我们会发现结果只有一个匹配,那就简单了。
|
||||
|
||||
在上一讲里,我们已经讨论了在这种情况下进行修改的三种不同方法(忘了?请回过去复习一下)。今天,我们用第四种方法。这种方法的每一步我们实际上都讲过,但串起来用,你可能就没有试过了。我们使用的基本命令是 `cw`、`n` 和 `.`。
|
||||
|
||||
由于之前搜索过 `smart_ptr`,我们现在仍然可以继续使用 `n` 找到需要修改的地方。我们随即需要键入的,是 `cwgt::shared_ptr<Esc>`。这样输入虽然有点长、有点啰嗦,但它的好处是整个修改会被 Vim 看作是一步,因而可以用 `.` 命令来重复。这样,下面我们只需要反复利用 `n` 和 `.` 命令,把除了 `#include` 那行之外的所有 `smart_ptr` 都改成 `gt::shared_ptr` 即可。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/d4/20/d4bb5a96e3951b71c9ecb1c00191b020.gif" alt="Fig10.4" title="使用 cw、n 和 . 来进行替换">
|
||||
|
||||
很显然,这并不是唯一的方法,也不一定是最好的方法。所以,我建议你在这里暂停一下,用 `:e!` 重新载入这个文件,试试使用上一讲提到的其他方法。我这里就仅仅再给你展示一下如何使用替换命令,同时又不会误匹配文件名:
|
||||
|
||||
```
|
||||
:%s/\<smart_ptr\>\ze\%([^.]\|$\)/gt::shared_ptr/g
|
||||
|
||||
```
|
||||
|
||||
这个匹配模式说的是,我要查找完整的单词“smart_ptr”(这就是要替换的内容了),但是,在匹配结束(`\ze`)后,我还有两个额外的匹配要求(用 `\%(` 和 `\)` 括起来),要么不是句点(`[^.]`),要么(`\|`)是行尾(`$`)。
|
||||
|
||||
我们最后把唯一残留的 `smart_ptr.h` 修改成 `shared_ptr.h`,就完成了 `smart_ptr` 的更名任务。
|
||||
|
||||
## 编译执行(可选)
|
||||
|
||||
如果你懂 C++,并且有 geek_time_cpp 的 [README](https://github.com/adah1972/geek_time_cpp/blob/master/README.md) 文件里要求的执行环境的话,可以选择体验一下编译执行。
|
||||
|
||||
我们需要先在 02 目录下创建并进入 build 子目录,然后运行 `cmake ..`。随后,在 Unix 环境下,一般可立即使用快捷键 `<F5>` 进行编译;想要在 Windows 下也能正常进行编译,我们则应当设置 `set makeprg=cmake\ --build\ .\ -j`(老版本的 cmake 可能不支持 `-j` 命令行参数的话,这样的话,我们会没法用 cmake 进行并发编译;不过对于我们的小例子没啥关系)。
|
||||
|
||||
另外一个要注意的地方是,Vim 在缺省配置下不能识别 Visual C++ 的错误输出格式 。为了能进行识别,并在发生错误时跳转到文件的指定位置,我们需要设置下面的选项:
|
||||
|
||||
```
|
||||
set errorformat=\ %#%f(%l\\\,%c):\ %m
|
||||
|
||||
```
|
||||
|
||||
目前来讲,环境没问题的话,我们就会……遇到编译错误。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/23/a2/2379fd36a653d5725129d0ecaebcb3a2.png" alt="Fig10.5" title="Windows 下遇到编译错误的界面">
|
||||
|
||||
原因是 `dynamic_pointer_cast` 前面也需要加上 `gt::`。做了这个修改之后,我们就应该可以顺利编译出可执行文件了。在 Windows 下使用命令 `:!.\Debug\sp_test02_shared_ptr`,或在 Unix 平台下使用命令 `:!./sp_test02_shared_ptr`,我们即可在终端看到下面的输出:
|
||||
|
||||
>
|
||||
<p>`circle()`<br>
|
||||
`use count of ptr1 is 1`<br>
|
||||
`use count of ptr2 was 0`<br>
|
||||
`use count of ptr2 is now 2`<br>
|
||||
`ptr1 is not empty`<br>
|
||||
`use count of ptr3 is 3`<br>
|
||||
`~circle()`</p>
|
||||
|
||||
|
||||
同时,如果愿意的话,我们也可以使用 AsyncRun 提供的机制,在 Windows 下使用命令 `:AsyncRun .\Debug\sp_test02_shared_ptr`,或在 Unix 平台下使用命令 `:AsyncRun ./sp_test02_shared_ptr`,异步运行程序并把输出重定向到 quickfix 窗口里。
|
||||
|
||||
## 添加跟踪语句
|
||||
|
||||
假设我们对这个代码执行过程有些疑问,想添加些跟踪语句,该怎么做呢?
|
||||
|
||||
我们首先需要在一个新窗口中打开 common/smart_ptr.h。由于我们第一个打开的文件就是它,所以它的缓冲区编号为 1,我们可在用 `<C-W>n` 打开一个新窗口后,使用 `1<C-^>` 飞速地重新打开文件。
|
||||
|
||||
我们希望对引用计数的增、减、删除等操作进行跟踪。最简单的方式,当然就是执行对应操作的时候,把执行的语句也输出一下。像这样简单的机械化操作,显然就是宏的天下了。我们来试一下。
|
||||
|
||||
我们先来改造一下 `smart_ptr` 析构函数里面的第一个 `delete ptr_`。一个可能的操作步骤是:
|
||||
|
||||
1. 复制当前行
|
||||
1. 粘贴当前行
|
||||
1. 选中行首缩进后、结尾分号前的内容,套上双引号
|
||||
1. 在这个新对象前后插入输出所必须的命令
|
||||
|
||||
我们需要录制的宏的内容是 `yyPv$hS"gvS)iputs<Esc>l%a;<Esc>`,而你把这一串东西用 `nmap` 命令映射给某个按键上也完全可行(注意,此处不能用 `nnoremap`,因为我们需要使用 vim-surround 插件带来的新的 `S` 按键的定义)。当然,在交互的环境中,录制按键会比眼睛看这个字符串容易理解多了。Vim 的宏,就其本质而言,可算是一种只写不读的简单过程式语言。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/b7/73/b7e972e2c310b493f31bf18d16264873.gif" alt="Fig10.6" title="录制把一行语句变成调试输出的宏">
|
||||
|
||||
我们用到的命令里,只有 `gv` 是之前没有学过的。我们当然也有其他方法来选中行中的内容,但 `gv` 的作用是重新选中刚才选中的内容,最快,也最方便。
|
||||
|
||||
利用这个宏,我们可以把添加调试语句变成按两个键。哦,对了,宏一旦执行过后,第二次执行同一个宏只需要键入 `@@` 即可,这样还能更快些。
|
||||
|
||||
在我们把所有的 `delete` 语句和 `add_count` 函数调用行上执行了这个宏之后,我们运行程序可以得到下面的结果:
|
||||
|
||||
>
|
||||
<p>`circle()`<br>
|
||||
`use count of ptr1 is 1`<br>
|
||||
`use count of ptr2 was 0`<br>
|
||||
`other.shared_count_->add_count();`<br>
|
||||
`use count of ptr2 is now 2`<br>
|
||||
`ptr1 is not empty`<br>
|
||||
`other.shared_count_->add_count();`<br>
|
||||
`use count of ptr3 is 3`<br>
|
||||
`delete ptr_;`<br>
|
||||
`~circle()`<br>
|
||||
`delete shared_count_;`</p>
|
||||
|
||||
|
||||
如果想对这个代码作进一步调整,类似操作即可,相当容易吧?
|
||||
|
||||
## 调整测试用例
|
||||
|
||||
我们现在使用鼠标点击或者 `<C-W>j` 等命令跳转到测试代码 test02_shared_ptr.cpp 中。我们随即使用 `<C-W>_` 命令来最大化窗口,因为似乎暂时用不着编辑 smart_ptr.h(但还不那么确定,否则就可以直接关闭那个窗口了)。
|
||||
|
||||
我们打算在 `ptr1` 不为空的那个条件判断下面再加点内容。那行输出看着也挺无聊的,我们就直接把它干掉了。我们可以在那组大括号内的任意地方点击后,使用 `ci{` 开始编辑,然后输入以下内容:
|
||||
|
||||
```
|
||||
printf("ptr1 %s ptr2\n",
|
||||
ptr1 == ptr2 ? '==' : '!=');
|
||||
|
||||
```
|
||||
|
||||
代码编译居然有奇怪的告警出现……我是 Python 写多了,脑子没转回来吗?没关系,在第一处单引号内部键入 `cs'"`,然后在第二处单引号内部键入 `.` 重复一下就好,现在代码应该是正确的了:
|
||||
|
||||
```
|
||||
printf("ptr1 %s ptr2\n",
|
||||
ptr1 == ptr2 ? "==" : "!=");
|
||||
|
||||
```
|
||||
|
||||
再次编译,完美,没有问题了!运行程序,我们得到:
|
||||
|
||||
>
|
||||
<p>`circle()`<br>
|
||||
`use count of ptr1 is 1`<br>
|
||||
`use count of ptr2 was 0`<br>
|
||||
`other.shared_count_->add_count();`<br>
|
||||
`use count of ptr2 is now 2`<br>
|
||||
`ptr1 == ptr2`<br>
|
||||
`other.shared_count_->add_count();`<br>
|
||||
`use count of ptr3 is 3`<br>
|
||||
`delete ptr_;`<br>
|
||||
`~circle()`<br>
|
||||
`delete shared_count_;`</p>
|
||||
|
||||
|
||||
## 内容小结
|
||||
|
||||
今天我们尝试对一小段 C++ 代码进行了简单的重构。在这个过程中,我们使用和复习了下面这些编辑技巧:
|
||||
|
||||
- 使用 fzf.vim 来根据部分文件名迅速打开文件
|
||||
- 使用 tagbar 来浏览文件的结构
|
||||
- 使用 vim-eunuch 来进行文件更名
|
||||
- 使用替换命令来进行批量代码更名
|
||||
- 使用 `.` 命令技巧来进行批量代码更名
|
||||
- 使用 `<C-R>` 在插入模式和命令行模式中使用寄存器的内容
|
||||
- 使用 `:grep` 命令在文件中进行文本搜索
|
||||
- 使用异步的构建命令,并设置选项使得错误信息解析在 Visual Studio 工具里也能工作
|
||||
- 使用文本对象命令对用括号、引号等符号包起来的文本进行统一的修改
|
||||
- 使用宏,在一次操作之后,在遇到类似场景时可以快速修改
|
||||
|
||||
虽然今天的代码是 C++ 的,但这些编辑方式适用于任何语言。请你一定要牢牢掌握。我们也应该慢慢看到了,编辑的一个要点,在于把需要重复的工作自动化和简单化。Vim 作为一个程序员的编辑器,提供了灵活而强大的编辑机制——最终用户,或扩展包的开发者,都可以利用这些底层机制,使编辑变得更加高效。
|
||||
|
||||
本讲我们对 Windows 下的 vimrc 配置文件有一处小修改,对应的标签是“l10-windows”。
|
||||
|
||||
## 课后练习
|
||||
|
||||
实验课中的内容你已经一一尝试了吧?请你再向前一步,想一想我们的每次编辑是否可以有不同的执行方式,及哪种方式对你最顺手。Vim 的命令一定是在使用中才能熟练应用的。你不一定要记住所有可能的编辑方式,但每一种最好都至少尝试一次,然后找出最适合自己的、最能牢牢掌握的编辑方式。
|
||||
|
||||
我是吴咏炜,我们下一讲再见!
|
||||
Reference in New Issue
Block a user