学习缘由

我也想成为使用 vim 写 python 代码的人,我觉得这样非常酷

历史

vim 源于 vi 但不是 vi,vi 作为计算机的文本编辑器历史极为悠远,它是由美国计算机科学家比尔·乔伊编写并于 1976 年发布的。vim 诞生得要晚一些,它的第一个版本由布莱姆·米勒在 1991 年发布,这个兄弟也是一位声名显赫的程序员,80 年代买了一台 Amiga 电脑,打开电脑一看,居然没有他最常用的 vi 编辑器,对于米勒来说这是不可接受的。愤怒的米勒决定自己开发一个文本编辑器,完全复制 vi 的功能,并起名为 vi IMitation(模拟)。随着 vim 的不断发展,更多更好的功能被加了进来,正式名称改成了 vi IMproved(增强),也就形成了现代的 vim,vim 的开发语言是 C 和 VimScript,目前最新的稳定版本是 8.2

理念

vim 是一款完全面向程序员的软件,几乎没有使用 vim 编辑文字的普通用户,这可能是因为 vim 的学习曲线陡峭导致的。但是一旦熟练使用 vim 之后,就可以不依赖鼠标,双手尽可能停留在键盘中央的区域,这样使得我们编码、 插入、移动、定位、查找都不需要产生停顿和间隙,极大的提高了我们工作的效率

安装

Red Hat 和 CentOS 系统默认的 vim 版本是 7.4,我们使用编译安装可以升级到最新版本

# 现在 vim 最新版本是 8.2
yum -y remove vim
wget -O vim-8.2.2377.tar.gz https://github.com/vim/vim/archive/v8.2.2377.tar.gz
tar xf vim-8.2.2377.tar.gz && cd vim-8.2.2377
./configure --prefix=/usr/local/vim/ --enable-pythoninterp=yes --with-python-config-dir=/usr/lib64/python2.7/config
make -j4 && make install
ln -s /usr/local/vim/bin/vim /usr/bin/vim
ln -s /usr/local/vim/bin/vimdiff /usr/bin/vimdiff

# 如果 ./configure 过程中遇到
no terminal library found
checking for tgetent()… configure: error: NOT FOUND!
      You need to install a terminal library; for example ncurses.
      Or specify the name of the library with –with-tlib.

# Ubuntu 解决方法
sudo apt install libncurses5-dev

# CentOS 解决方法
yum install ncurses-devel.x86_64

基本使用

打开文件

vim filename
# 如果文件名称存在,就直接打开文件
# 如果文件名称不存在,vim 会在退出保存时自动创建文件
# vim 命令后没有跟任何文件名称时,无法实现 ":wq" 退出保存的,需要用 ":wq filename" 定义文件名称

退出文件

":q"    # 退出
":q!"    # 强制不保存退出
":w"    # 保存编辑后的内容(将内存缓冲区的数据写到文件中)
":w!"    # 强制保存编辑后的内容
":wq"    # 保存并退出
":wq!"    # 强制保存并退出
":x"    # 保存并退出

# ":x" 和 ":wq" 的区别在于:
":wq" 强制性写入文件并退出,即使文件没有被修改也强制写入并更新文件的修改时间
":x" 仅当文件被修改时才写入并更新文件修改时间,否则不会更新文件修改时间
# ":x" 和 ":wq" 在一般情况下没有什么区别,但是进行编程时,如果修改的是源代码文件,即使文件内容没有被修改,但是文件修改时间更新了,在重新编译项目时就得重新编译,产生不必要的系统资源

关于更多的 vim 基本使用会在后面一一讲解

模式

vim 设计得最特别的地方就是它的模式,与其他大部分编辑器不同,进入 vim 后默认是正常模式,此时键入的字符并不会被插入到所编辑的文件之中。vim 的模式(mode),是它的麻烦所在,但同时也是它的厉害所在,vim 有四种主要的模式。正常模式(normal 也称普通模式),如果不加特殊的说明,一般提到的命令都直接在正常模式下输入,在其他的任何模式中,都可以通过 Esc 键返回到正常模式。插入模式(insert),输入字符时使用,例如在正常模式下键入 i (insert) 或者 a (append) 即可进入插入模式。按 v 进入可视模式(visual),用于选定文本块,vim 里还提供其他不同的选定方法,包括按行和按列块。命令行模式(command-line),用于执行较长、较复杂的命令、在正常模式下键入冒号 : 即可进入该模式,使用斜杠 / 和问号 ? 开始搜索也算作命令行模式,命令行模式的命令需要输入回车键才会执行

配置和选项

vim 是有配置文件的,我们可以根据使用习惯,配置属于自己的 vim。vim 的配置文件有三个路径,我们可以敲 vim –version 就能看到三行信息,配置文件是存在优先级的。一开始的配置文件是一个很简单的,甚至是一个空文件,随着我们的深入学习和使用,配置文件会变得越来越复杂

system vimrc file: "/etc/vimrc"        # 系统配置
     user vimrc file: "$HOME/.vimrc"    # 用户配置
 2nd user vimrc file: "~/.vim/vimrc"    # 第二用户配置

# 优先级
三个文件只要存在一个,vim 就能正常运行
如果有用户配置,第二用户配置就无法生效
相同的配置项,用户配置会覆盖系统配置
系统配置里有而用户配置里没有的配置项,以系统配置为准

鼠标支持

在 vim 里也可以使用鼠标,我们可以设置鼠标在 vim 的那个模式下生效。启用鼠标支持之后在不同的终端使用场景下,也有一定的区别,如果使用 xterm 兼容终端,在多窗口编辑的情况下可以使用鼠标进行窗口切换和拖拽窗口大小,如果使用 linux 兼容终端,那么鼠标支持是不生效的

# 鼠标模式
n 普通模式
v 可视模式
i 插入模式
c 命令行模式
h 在帮助文件里
a 以上所有模式

# 启用鼠标支持
set mouse=a

# 关闭鼠标支持
set mouse-=a

# 判断终端类型启用鼠标支持模式
if has('mouse')
  if has('gui_running') || (&term =~ 'xterm' && !has('mac'))
    set mouse=a
  else
    set mouse=nvi
  endif
endif

备份和撤销文件

set backup    # 对一个文件修改之后生成对应的备份文件
set undofile    # 对一个文件修改之后生成对应的撤销文件
set undodir=~/.vim/undodir    # 指定撤销文件的存放目录
set backupdir=~/.vim/backupdir    # 指定备份文件的存放目录
# 如果没有此目录则自动创建目录
if !isdirectory(&undodir)
  call mkdir(&undodir, 'p', 0700)
endif

if !isdirectory(&backupdir)
  call mkdir(&backupdir, 'p', 0700)
endif

set enc=utf-8    # enc 是显示文件时的编码(fenc 是当前文件编码,fencs 是打开文件时进行解码的)
set nocompatible    # 设置 vim 不需要和 vi 兼容
set backspace=indent,eol,start    # backspace 键的工作模式,indent:允许删除自动缩进的空格 eol:能够将一行删除完后合并到上一行 start:删除此次进入插入模式前的输入

光标移动

vim 里的基本光标移动是通过 h、j、k、l 四个键来实现的,之所以使用这四个键,是有历史原因的,当时的 vi 开发者的键盘上还没有我们现在独立的光标键,四个光标的符号直接标注在 h、j、k、l 四个字母按键上。因此,即使今天所有的键盘都有了光标移动键,很多资深的 vim 用户仍然使用这四个键来移动光标

vim 跳转到行首的命令是 0,跳转到行尾的命令是 $,还有使用 ^ 跳转到行首的第一个非空白字符

对于一次移动超过一个字符的情况,vim 支持使用 b/w 和 B/W,来进行以单词为单位的跳转,用来向后或向前跳转一个单词。大写个小写命令的区别在于,小写命令跟编程语言里的标识符规则相似,认为一个单词是由字母、数字、下划线组成的,而大写的命令则认为非空字符都是单词

根据单个字符来进行光标移动,分别是 f(find) 和 t(till),fa 是移动光标到下一个 a 字符,ta 是移动光标到下一个字符 a 的前一个字符,大写的 F/T 代表反向

对于使用 vim 去阅读一些文档的时候,使用 ( 和 ) 分别是移动光标到上一句和下一句,使用 { 和 } 分别是移动光标到上一段和下一段

很多环境里,vim 支持使用 <Home> 和 <End> 跳转到文件的开头行和结尾行,如果不行,还可以使用 vi 兼容的 gg 和 G 跳转到开头行和结尾行(G 是跳转到结尾行的第一个字符)

h:左
j:下
k:上
l:右
0:跳转到行首
$:跳转到行尾
^:跳转到行首的第一个非空白字符
b/B:光标移至光标当前所在单词的词首,如果光标已经在单词的词首,则会跳至前一个单词的词首
e/E:光标移至光标当前所在单词的词尾,如果光标已经在单词的词尾,则会跳至后一个单词的词尾
w/W:向前跳转一个单词或字符串,光标停留在单词词首
A:光标移至行尾并进入编辑模式
I:光标移至行首并进入编辑模式
fa:移动光标到当前行下一个 a 字符
2fa:移动光标到当前行第二个 a 字符
ta:移动光标到当前行下一个字符 a 的前一个字符
2ta:移动光标到当前行第二个字符 a 的前一个字符
F/T:代表反向
(:移动光标到上一句
):移动光标到下一句
{:移动光标到上一段
}:移动光标到下一段
%:匹配括号移动光标,包括 ( { [,需要将光标先移动到括号上(编辑 Nginx 的配置文件时非常方便)
*/#:匹配光标当前所在的单词的下一个和上一个
gg:移动光标到首行的第一个字符
G:移动光标到尾行的第一个字符
:n/ngg/nG:指定光标跳转到 n 行
<PageUp>:向上翻页
<PageDown>:向下翻页
n|:n代表数字,移动到指定的列

文本修改

在 vim 的一般原则里,常用的功能,按键应尽可能少,因此很多相近的功能在 vim 里会有不同的按键,不仅如此,大写键也一般会重载一个相近但稍稍不同的含义

dd:删除整行
d0:光标位置(不包含)删除到行首的所有字符
d$/D:光标位置(包含)删除到行尾的所有字符
db:删除光标当前位置(不包含)到单词起始处的所有字符
de:删除光标当前位置(包含)到单词结尾处的所有字符
dw:删除光标当前位置(包含)到下个单词起始处的所有字符
cc/S:删除整行并进入插入模式
c$/C:光标位置删除到行尾并进入插入模式
s/cl:删除一个字符并进入插入模式
i:在当前光标字符前面进入插入模式
I:光标移动到行首非空白字符并进入插入模式
a:在当前光标字符后面进入插入模式
$a/A:光标移动到行尾并进入插入模式
o:在当前行的下方插入新行并进入插入模式
O:在当前行的上方插入新行并进入插入模式
ra:光标下的字符替换为a
R:进入替换模式,每次按键替换一个字符(直到按下<Esc>)
u:撤销上一个修改动作(可多次撤销)
U:撤销当前行上的所有修改

文本对象

如果想要在 vim 里拥有高效编辑的能力, 必然要掌握超过单个字符编辑的能力,也就是说,要掌握词(word)、句子(sentense)、段落(paragraph)级别的编辑能力。在 Vim 里,这样以一定标准分隔符界定的概念叫做文本对象(text objects)。文本对象是一个很强大的功能,无论光标处于该文本对象的哪个字符中,我们都可以对整个文本对象进行操作,这也是为什么 vim 是世界上最快的编辑器的原因

# 文本对象常用的编辑命令
y:复制
d:删除
c:替换
v:选中

# 文本对象有以下几种(标签用 t 表示)
w s p '' "" <> [] () {} <tag>

# 文本对象的操作范围有两种
i:是inner的意思,操作时不包括单词边上的空格符或包围符号
a:是arround的意思,操作时包括单词边上的空格符或包围符号

# 文本对象操作列子
操作文本对象:<h1>Sample Title</h1>,光标在Sample单词的S上
dw:<h1>Title</h1>
diw:<h1> Title</h1>
daw:<h1>Title</h1>
dit:<h1></h1> (t代表的是<tag>文本对象)
dat:Empty    (这里因为文本对象的操作范围是a,所有连同包围符号也一并删除)

# 操作文本对象:cdmuwfon.rg("stwq jkntrc," + "opac liixisq"),光标在stwq单词的s上(对于成对的标签符号操作时,光标可以在标签内的任何位置)
di":cdmuwfon.rg("" + "opac liixisq")
da":cdmuwfon.rg(+ "opac liixisq")
di(:cdmuwfon.rg()  # (和)都可以
da(:cdmuwfon.rg
ci":cdmuwfon.rg("" + "opac liixisq")  # 并且进入了插入模式
ca":cdmuwfon.rg(+ "opac liixisq")  # 并且进入了插入模式
ci(:cdmuwfon.rg()  # 并且进入了插入模式,这在编程中非常方便
ca(:cdmuwfon.rg  # 并且进入了插入模式
vi(:进入视图模式并且选中"stwq jkntrc," + "opac liixisq"  # 可以按d进行删除
va(:进入视图模式并且选中("stwq jkntrc," + "opac liixisq")

更多的文本对象操作可以使用如下的示例进行各种各样的组合,让复杂的操作只需要几个简单的组合键就能完成,极大的提高了我们的工作效率

[y:复制 d:删除 c:替换 v:选中] [i和a 文本对象的操作范围] [w s p '' "" <> [] () {} <tag> 文本对象]

重复操作

vim 里有非常多的命令组合,如果我们需要重复这样的命令,每次都要手敲一遍,这显示不是一件容易的事情。其实 vim 已经想到了这个问题,提前定义好了一些简单的重复键

;:重复最近的字符查找操作(f t)
,:反向
n:重复最近的字符查找操作(/ ?)
N:反向
.:重复执行最近的修改操作

目录结构

vim 的工作环境是由运行支持文件来设定的,如果想要定制 vim ,就需要知道 vim 的目录结构

# 以 vim8.2 为例,标准的安装位置分别在
Unix:/usr/share/vim/vim82(如果是编译安装则取决于你的安装目录)
Windows:C:\Program Files(x86)\Vim\vim82

# 这个目录下面还有很多子目录,这些子目录下面就是分类放置的 vim 支持文件
syntax:vim的语法加亮文件
doc:vim的帮助文件
colors:vim的配色方案
plugin:vim的"插件",即用来增强vim功能的工具

以 syntax 目录为例,目录下有 628 个文件都是以 .vim 作为后缀,就代表 vim 对 628 不同的文件类型提供语法加亮支持,例如 java.vim 文件,就是对 java 类型的文件进行语法加亮,也可以用 :setfiletype java 这样的命令来设置文件的类型

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文件(tar 不支持写入)
tohtml:把语法加亮的结果转成HTML文件并保存
vimballPlugin:创建和解开.vba文件(过时)
zipPlugin:编辑zip文件(zip 可支持写入)

# 打开远程文件和浏览目录
format: protocol://[user@]hostname[:port]/[path]
vim scp://root@k8s-node1/test    # 打开root用户家目录下的test文件
vim scp://root@k8s-node1//etc/docker/    # 使用远程终端的绝对路径,要使用双斜杠

# 使用vim查看man帮助文档
export MANPAGER="vim -M +MANPAGER -"

包管理器

vim 的插件严格来说应该叫包,我们安装一个插件,就是在 .vim 目录下解压插件包,基本上是安装之后就不管了,即使这个插件有更新,我们也不能及时的更新到最新的版本。现在 git 的流行,让我们对版本的控制变得简单,而在有了包管理器之后,配合 git 的版本控制,能够让我们非常方便的安装插件和更新插件,已经一系列对插件的管理操作

安装 minpac 包管理器并通过包管理器安装插件

# 安装 minpac
git clone https://github.com/k-takata/minpac.git ~/.vim/pack/minpac/opt/minpac

# 初始化包管理器和指定需要安装的插件(写入到 vim 的配置文件)
function! PackInit() abort
  packadd minpac

  call minpac#init()
  call minpac#add('k-takata/minpac', {'type': 'opt'})

  " Additional plugins here.
  call minpac#add('vim-jp/syntax-vim-ex')
  call minpac#add('tyru/open-browser.vim')
  call minpac#add('rkulla/pydiction')    # python 补全插件
endfunction

" Plugin settings here.

" Define user commands for updating/cleaning the plugins.
" Each of them calls PackInit() to load minpac and register
" the information of plugins, then performs the task.
command! PackUpdate call PackInit() | call minpac#update()    # 自定义命令
command! PackClean  call PackInit() | call minpac#clean()
command! PackStatus packadd minpac | call minpac#status()

# 保存 .vimrc 文件,重启 vim 之后我们就有了三个自定义的命令(命令模式下)
PackUpdate
PackClean
PackStatus

# 安装插件
在 .vimrc 文件写入插件的 GitHub 项目的用户名/项目名,通过 :PackUpdate 命令更新插件
插件格式为:call minpac#add('[package-author]/[package-name]')

# 删除插件
同样需要编辑 .vimrc 文件,删除不需要的插件,通过 :PackClean 命令更新插件,插件就会被删除

# 查看插件状态
:PackStatus

插件安装成功的界面

复制粘贴

一般而言,对于终端 vim 来说,它是没法分辨用户是输入操作还是粘贴操作的。因此在粘贴内容时,Vim 的很多功能,特别是智能缩进、制表符转换等功能(这些功能是用于输入操作的),就会修改粘贴的内容,导致我们粘贴的内容显示的结果不对,或者出现乱码。要解决这个问题,我们就得让 vim 知道,我们的操作到底是输入操作还是粘贴操作,vim 有一个 paste 选项,就是用来切换输入和粘贴的状态的,如果现在处于 :set paste 状态,vim 就认为现在是粘贴操作,智能缩进、制表符转换等功能就不会修改粘贴的内容,不过每次都手动修改这个状态是非常麻烦的,下面有两个优化方法

# 方法一,通过自定义键来切换paste和nopaste的状态
set pastetoggle=<F2>  # 在插入模式下,按<F2>会切换paste状态
nnoremap <F2> :set invpaste paste?<CR>  # 在命令模式下,按<F2>会切换paste状态
imap <F2> <C-O>:set invpaste paste?<CR>  # 以nopaste状态进入插入模式后,可以按一次<F2>切换paste状态

# 方法二,进入插入模式的时候自动开启paste,退出插入模式自动关闭paste
if has('autocmd')
  augroup vimrcExtension
    autocmd!
    autocmd InsertEnter * set paste
    autocmd InsertLeave * set nopaste
    if ! has('gui_running')
      set ttimeoutlen=10
      autocmd InsertEnter * set ttimeoutlen=0
      autocmd InsertEnter * set ttimeoutlen=1000
    endif
  augroup end
endif
# 一直处于 paste 开启的状态下虽然不影响基本的功能,但是会影响其他插件的正常工作(例如 python 补全插件就无法使用了)

交换文件

对一个单独的文件使用多个 vim 会话进行编辑,很容易出现冲突的情况,所以使用 vim 时肯定会遇到过 Swap file “.filename.swp” already exists! 这个错误提示,出现这个错误提示有两种原因

  1. 上次编辑这个文件时,发生了意外崩溃,导致文件没有存盘就退出了
  2. 有另一个会话正在使用 vim 编辑这个文件

当错误提示的 process ID 后面没有 (still running) 的字样,就是第一种情况,否则就是第二种情况。第一种情况下,vim 支持即使没有存盘的情况下仍然保存编辑状态,我们可以按 r 键来恢复上次没有存盘的内容,在文件恢复之后,vim 仍然不会删除崩溃时保留下来的那个交换文件,因此我们恢复文件内容之后,确定内容无误就可以保存文件。重新打开文件,按 d 键可以删除交换文件,也可以单独使用 rm -f 删除交换文件(交换文件一般是 .filename.swp 的格式)。第二种情况下,是有另一个会话正在使用 vim 编辑这个文件,这时候是没有 delete 交换文件这一选项的,这时候一般选择 q 或者 a 放弃编辑,如果只是要查看文件,那也可以选择 o 以只读文件打开,需要用到 e 强行进行编辑的情况很少

Swap file ".nginx.conf.swp" already exists!
[O]pen Read-Only, (E)dit anyway, (R)ecover, (Q)uit, (A)bort:

编辑多个文件

vim 支持一次性打开多个文件,只需要在命令行上写出多个文件即可,还支持通配符的方式。例如我们可以使用 vim *.cpp 或者 vim *.yaml 去编辑多个文件,但是执行这个命令之后只会打开所有文件中的第一个文件,这是 vim 为了确保低配置环境也能正常工作而设计的,避免不必要的内存浪费,其实在执行上述命令的时候,vim 建立了一个文件列表,并且暂时只打开其中的第一个文件,接下来用户可以在不退出 vim 的情况下,查看文件列表,继续编辑下一个文件或者退出编辑

":args" # 显示编辑的所有文件列表,其中[]括起来的文件是你正在编辑的文件
":args **/*.yaml/filename" # 在进入 vim 之后,打开当前目录下的 *.yaml 文件和打开指定文件
":next/:n" # 编辑下一个文件,如果当前文件未存盘则会报错,命令后面加!则会放弃修改内容,可以设置 vim 在切换文件时自动存盘:set autowrite
":Next/:previous" # 打开上一个文件
":first/rewind" # 回到文件列表的第一个文件
":last" # 打开文件列表的最后一个文件
":n|normal ggp" # 切换到下一个文件并在正常模式下执行 ggp 命令

缓冲区的管理和切换

vim 里会对每一个已打开或要打开的文件创建一个缓冲区,这个缓冲区就是文件在 vim 中的映射,它是 vim 里的一个基本概念。缓冲区(buffer)就是一块内存区域,里面存储着正在编辑的文件,如果没有把缓冲区里的文件存盘,那么原始文件不会被更改。在多文件编辑的时候你也会有同样数量的缓冲区,不过缓冲区的数量常常会比编辑多文件时的文件列表数更高,因为你用 :e/:o 等命令另外打开的文件不会改变命令行参数(就是不加入到 :args 的文件列表),但同样会增加缓冲区的数量。 此外 :args 代表参数列表 / 文件列表,真的只是文件的列表而已。比起文件列表,缓冲区中有更多信息,最基本的就是记忆了光标的位置。在 vim 里,除了切换到下一个文件这样的批处理操作外,操作缓冲区的命令比简单操作文件的命令更为方便

# 使用通配符命令编辑多个文件
vim *.yaml

# :args 查看文件列表
[deployment.yaml] emptyDir.yaml     hostpath-vol.yaml ingress.yaml      nfs-vol.yaml      service.yaml

# :ls/:files/:buffers 查看缓冲区列表
  1 %a   "deployment.yaml"              line 45
  2      "emptyDir.yaml"                line 0
  3      "hostpath-vol.yaml"            line 0
  4      "ingress.yaml"                 line 0
  5      "nfs-vol.yaml"                 line 0
  6      "service.yaml"                 line 0
Press ENTER or type command to continue

可以看到,文件列表和缓冲区列表都展示了打开的所有文件,而且分别使用 [] 和 %a 标示了当前正在编辑的文件。不过缓存区列表比文件列表给出了更多的文件信息

# 缓冲区示例
1 %a   "deployment.yaml"              line 45

# 参数意义
1:代表缓冲区列表文件的编号
%a:缓冲区的状态
"deployment.yaml":文件名字
line 45:光标所在的行

缓冲区状态

%:当前缓冲区
a:活动缓存区,当前显示在屏幕上的
#:交换缓冲区(最近的缓冲区)
=:只读缓冲区
+:已经更改的缓冲区
-:非活动的缓冲区
h:隐藏的缓冲区

打开缓冲区

:buffer number # 以缓冲区列表文件编号来打开缓冲区
:buffer filename # 以缓冲区列表文件名字来打开缓冲区
:sbuffer number/filename # 分割当前窗口开始编辑另一个缓冲区,如果没有指定 number/filename,则以当前缓冲区进行窗口分割
:ball # 为每一个缓冲区打开一个窗口

切换缓冲区

:bnext # 切换到下一个缓冲区
:bprevious/:bNext # 切换到上一个缓冲区
:blast # 切换到最后一个缓冲区
:bfirst # 切换到第一个缓冲区
:set hidden # 允许缓冲区在未保存的情况下进行切换(修改由vim进行保存)

删除缓冲区

:bdelete filename/3 # 根据文件名字或者编号来删除一个缓冲区
:1,3 bdelete # 根据指定范围来删除缓冲区
:bdelete! filename # 强制删除缓冲区

卸载缓冲区

:bunload filename # 从内存中卸载一个缓冲区,这个缓冲区打开的所有窗口都会关闭,如果缓冲区被改动过,那么该命令将失败    
:bunload! # 强制卸载缓冲区,但所有的改动也将会丢失

多窗口编辑

前面所讲的编辑多个文件,也只是在单个窗口进行的,这样的局限在于,我们既不能同时修改两个文件,也不能在同一窗口对比两个文件。我们想要自己同时查看、编辑多个文件,最基本的命令就是 :split (缩写 :sp),这个命令后面如果有文件名,表示分割窗口并打开指定的文件,如果没有文件名,那就表示仅仅把当前窗口分割开,当前编辑的文件在两个窗口里都显示,:split 默认使用水平分割的方式。竖直分割的命令是 :vsplit (缩写 :vs),竖直分割要求屏幕比较宽,但如果你想对比两个文件时,竖直分割就会更方便

<Ctrl-w> s/:split/:sp # 水平分割当前窗口
<Ctrl-w> v/:vsplit/:vs # 竖直分割当前窗口
:split/:sp filename # 水平分割窗口并打开指定的文件
:vsplit/:vs filename # 竖直分割窗口并打开指定的文件
<Ctrl-w> (h j k l 方向键) # 可以在多窗口之间跳转
<Ctrl-w> w # 跳转到下一个(往右和往下跳)窗口,W反向
<Ctrl-w> n/:new # 打开一个新窗口
<Ctrl-w> c/:close # 关闭当前窗口,如果当前窗口已经是最后一个则无效
<Ctrl-w> q/:quit # 退出当前窗口,当最后一个窗口退出时则退出 vim
<Ctrl-w> o/:only # 只保留当前窗口,关闭其他所有窗口
<Ctrl-w> = # 使得所有窗口大小相同

文件比较

多窗口编辑中有一个非常有用的功能,那就是比较两个文件的内容。vim 对此有特殊的支持,使用 vimdiff 或 gvimdiff 命令,后面跟上需要比较的两个文件,就能打开两个窗口比较两个文件了。在比较时,vim 会折叠相同的代码行,并加亮两边文本不同的部分,窗口的滚动也是联动的

vimdiff emptyDir1.yaml emptyDir2.yaml # 比较两个文件的内容

比较文件的实际截图如下

NERDTree 插件

NERDTree 是最为著名的一个文件浏览 / 管理插件,就是你知道文件大概在哪里,但不知道文件具体名字时的一个好选择。跟很多 vim 插件一 样,NERDTree 会利用多窗口的特性

安装 NERDTree 插件

在vimrc的"Other plugins"下面加入以下语句
call minpac#add('preservim/nerdtree')
执行:PackUpdate命令进行安装

安装成功之后,NERDTree 缺省就会抢占 netrw 使用的路径形式,我们在 vim 中可以使用 e . 来打开 NERDTree(. 代表当前路径) ,也可以在打开一个文件之后使用 :NERDTreeToggle 命令。在打开 NERDTree 窗口之后,使用还是相当直观的,并且按下 ? 就可以查看帮助信息

在文件或目录上敲回车或双击立即打开该文件或目录

在文件上使用 go 会预览该文件,也就是光标不会跳转到文件所在的窗口中

按 i 会打开文件到一个新的水平分割的窗口中,按 s 会打开文件到一个新的竖直分割的窗口中,按 t 会打开文件到一个新的标签页中

NERDTree 会自动过滤隐藏文件和目录,但如果你需要看到它们的话,也可以用 I 来开启和关闭隐藏文件的显示

按 m 会出现一个菜单,允许添加、删除、更名等操作

正则表达式

通过前面的学习,可能你已经知道搜索命令 / 和替换命令 :s 的用法了。其实,我们输入的待查找内容是被 vim 当成正则表达式来看待的,这里我们来简单学习一下 vim 里的正则表达式,它跟其他常用的正则表达式还是有区别的。在一个搜索表达式里(或者称为模式 pattern),. 、* 、^ 、$ 、~ 、[] 、\ 是有特殊含义的字符

. 可以匹配除换行符外的任何字符: 如a.可以匹配"aa" "ab" "ac"等,但不能匹配"a" "b""ba",如果需要匹配换行符(跨行匹配)的话,则需要使用\_.
* 表示之前的匹配源(最普通的情况为单个字符,匹配源可以是一个字符串,但需要该字符串需要组成一个项,如:\(ab\)*)重复零次或多次: 如aa*可以匹配"a" "aa" "aaa"
^ 匹配一行的开头,如果出现在模式的开头的话;在其他位置代表字符本身
$ 匹配一行的结尾,如果出现在模式的结尾的话;在其他位置代表字符本身
~ 匹配上一次替换的字符串,即如果上一次你把"foo"替换成了"bar",那~就匹配"bar"
[...] 匹配方括号内的任一字符,方括号内如果第一个字符是^,表示对结果取反,除开头之外的-表示范围:如[A-Za-z]表示任意一个拉丁字母,[^-+*/] 表示除了"-" "+" "*" "/"外的任意字符
\ 的含义取决于下一个字符,在大部分的情况下把某些含有特殊意义的字符进行转义,让它们代表字符本身(. * \ ^ $ ~ [ ])

除此之外的字符都是普通字符,没有特殊含义。不过,需要注意的是,如果使用 / 开始一个搜索命令,或者在替换命令(:s)中使用 / 作为模式的分隔符,那模式中的 / 必须写作 / 才行,否则 Vim 看到 / 就会以为模式结束了,导致错误发生。为了方便书写,我们可以用其他的符号作为模式的分隔符,例如想把”/image/“全部替换成”/images/“的话,我们可以把 :%s//image///images//g 写成 :%s#/image/#/images/#g,这只能适用于替换命令,而在使用搜索命令 / 时我们就没有办法了,只能把模式里的 / 写作 /

通过 \ 开始的特殊表达式

\? 表示之前的匹配源重复零次或一次:如 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"两单词之一
\< 匹配单词的开头
\> 匹配单词的结尾
\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 忽略大小写进行匹配

抽象地讨论正则表达式恐怕你也不容易记住,还是拿一些具体的例子来练习一下吧

搜索替换实例

搜索表达式

/aae # 往后查找aae,没有使用界定符,会查找到aaes qaae等单词
/\<name\> # 搜索单词name,使用\< \>进行界定单词的开头和结尾,这样的话names是搜索不到的
/\<\(red\|blue\)\> # 搜索单词red或blue
?aae # 往前查找aae,没有使用界定符,会查找到aaes qaae等单词
* # 搜索光标下的单词(光标停留在name上,键入*会跳转到下一个name)
n # 光标跳转到后一个
N # 光标跳转到前一个

替换表达式

Example:[range]s/{pattern}/{string}/[flags]

# flags 有如下四个选项
c confirm 每次替换前询问
e error 不显示错误
g globle 不询问,整行替换,如果不加g选项,则只替换每行的第一个匹配到的字符串
i ignore 忽略大小写
这些选项可以合并使用,如cgi表示不区分大小写,整行替换,替换前询问

# 替换例子
:s!ma!am! # 把当前行的ma替换成am
:s!ma!am!g # g标记表示替换行中的所有匹配点(不加g只会替换行中的一个匹配点)
:%s!ma!am!g # 把全文的ma替换成am(1,$s!ma!am!g也是一样的效果)
:1,10s!ma!am! # 表示把第1到第10行(包含1到10行)的ma替换成am

删除替换表达式

:%s!\s\+$!!g # 删除全文行尾的空白字符(<Space> 和 <Tab>)
:%s!^\s\+!!g # 删除全文行首的空白字符(<Space> 和 <Tab>)
:g/^\s*$/d # 全局删除沒有內容的空行
:%s!^\s*$\n!!g # 把沒有內容的空行(空格 制表符 换行符)替换为空,等于删除空行的效果

文件类型和关联设定

程序源代码通常由文件组成,每个文件都有一个关联的文件类型。这个文件类型决定了 vim 对其进行处理的一些基本设定,可能受影响的设定具体有以下方面

如何对文件进行高亮(不同的语言高亮不一样,例如 .c 和 .py 文件)

制表符 <tab> 的宽度(4个空格数或者8个空格)

是否在键入 <tab> 时扩展为空格字符

每次缩进的空格数(可以和制表符宽度不同)

采用何种自动缩进方法

其他可适用的选项

# 开启文件类型检测(写入vimrc配置文件)
filetype plugin indent on 

# filetype on
打开文件类型检测功能,它相当于文件类型检测功能的开关

# filetype plugin on
允许vim加载文件类型插件,vim会根据检测到的文件类型,在runtimepath中搜索该类型的所有插件并执行

# filetype indent on 
允许vim为不同类型的文件定义不同的缩进格式

# 查看文件类型检测是否开启
:filetype

# 如果文件的类型未能被正确的检测出来,可以手动设置文件类型
:set filetype=c

插入模式自动补全

自动补全是一个编辑器中很主流的功能,编辑器能够在用户输入一部分内容时就能猜到用户希望输入的是什么,并能够予以提示。自动补全可以节约我们输入的工作量,非常值得我们去学习,vim 内置就有自动补全的功能,补全的对象如下

整行补全 CTRL-X CTRL-L
根据当前文件里关键字补全 CTRL-N/CTRL-P
根据字典补全 CTRL-X CTRL-K
根据同义词字典补全 CTRL-X CTRL-T
根据头文件内关键字补全 CTRL-X CTRL-I
根据标签补全 CTRL-X CTRL-]
补全文件名或路径 CTRL-X CTRL-F
补全宏定义 CTRL-X CTRL-D
补全vim命令 CTRL-X CTRL-V
用户自定义补全方式 CTRL-X CTRL-U
拼写建议 CTRL-X CTRL-S
停止补全并且不应用匹配项 CTRL-E
在补全菜单应用对象且离开补全模式 CTRL-Y/<Enter>

CTRL-N 和 CTRL-P 分别是对当前文件里的关键字进行补全(往下查找和往上查找),其他补全都需要先进入 CTRL-X 模式,再键入对应的命令进入对应的补全模式。使用 CTRL-N 和 CTRL-P 上下移动时,输入的文本也会随之变化。使用 <Up> 和 <Down> 上下移动时,输入的文字并不会变化。使用 <PageUp> 和 <PageDown> 键,可以在补全菜单中翻页。使用 Esc 键,将关闭弹出菜单,但会保留之前应用的对象。也就是说,如果你不希望应用任何对象完成补全时,应该使用 CTRL-E 键,而不是使用 Esc 键来取消补全操作

补全文件名字或者路径(基于文件系统)

Python 开发环境