读书笔记:《Vim实用技巧》第二版
[TOC]
前言
记得在刚学 Vim 的时候,就把 《Vim实用技巧》(英文名:Practical Vim)通读了一遍(当时读的应该是第一版),可以说就是这本书让我真正进入了 Vim 的世界。
也因为在很早期就系统学习了 Vim 的相关技巧,因此使用 Vim 还是挺得心应手的。只是 Vim 的功能实在是繁多,日常使用也就只是用了 Vim 特性的十之一二,很多的东西不常用,慢慢地也就忘记了。
刚好看到 《Vim实用技巧》 第二版,就想着温故知新,重新再读一遍,把自己不常用但感觉好用的技巧给记录下来,方便以后查阅。
注:据网上介绍,《Vim实用技巧》 第二版与第一版内容上区别不大,主要改动为:
- 技巧36:批处理运行Ex命令
- 技巧84:对完整的查找匹配进行操作
- 技巧86:统计当前模式的匹配个数
- 技巧97:在多个文件中执行查找与替换
- 技巧111:使用Vim内置正则表达式引擎的Grep
- 技巧117:自动补全单词序列
摘抄
注:以下只会记录本人不熟悉或觉得需要记录的内容,不会事无巨细记录完整内容。
-
Vim 在线文档:vimhelp.org
Vim 在线中文文档 1:vimdoc
Vim 在线中文文档 2:vimdoc_github -
查看按键符号:
key-notationh key-notation
-
使用 Vim 的出厂配置:
vim -u NONE -N
其中:
-u NONE
:该标志让 Vim 在启动时不加载.vimrc
文件
-N
:该标志使能nocompatible
选项,防止进入 vi 兼容模式
注:-u
标识可在 Vim 启动时加载指定配置文件,如:
" 只加载 code/essential.vim 配置文件
vim -u code/essential.vim
-
查看 Vim 版本:
:version
-
S
:删除整行并进入插入模式,相当于^C
。 -
当使用
f{char}
/t{char}
时,;
正向寻找下一个,,
反向回到上一个。 -
i{insert some text}<Esc>
称为一次修改,也即进入insert
模式到退出回到normal
模式,则完成一次修改。因此通过合适的控制一次修改的粒度,就可以控制撤销u
的粒度。 -
vu
:转成小写
vU
:转成大写
~
:反转大小写
注:gxx
:转换当前行。比如:
guu " 当前行转成小写
gUU " 当前行转成大写
g~~ " 当前行反转大小写
- Vim 模式:
-
normal mode
:正常模式 -
insert mode
:插入模式 -
command-line mode
:命令行模式,即按下:
键时,Vim 就会切换到命令行模式。
▪ 出于历史原因,在命令行模式中执行的命令又被称为 Ex 命令。
▪ 在按/
调出查找提示符或用<C-r>=
访问表达式寄存器时,命令行模式也会被激活。
▪ 更多命令行模式提供的命令,请查看::h ex-cmd-index
-
visual mode
:可视模式 -
visual-select-modeselect mode
:选择模式,与其他的文本编辑器类似的功能,在文本选中状态下,按键会直接替换选中区域。
注:在选中状态下,按<C-g>
可切换select mode
和visual mode
,下方提示栏可查看当前模式:
-
operator-pending mode
:操作符待决模式,即在输入操作符(比如c
/d
/y
,具体查看::h operator
)后,进入只接受动作命令的状态。比如在执行动作命令dw
时,当按下操作符d
时,该模式立即激活,此时 Vim 会记录d
这个按键并等待后续命令动作,当按下w
时,该模式立即结束,并执行dw
操作。
更多模式,请查看::h mode
-
Vim 在
insert mode
时插入寄存器内的文本时(比如,<C-r>{register}
),其插入方式相当于键盘一个一个字符输入插入的文本,因此,如果激活了textwidth
或者autoindent
选项的话,则可能会出现不必要的换行或额外的缩进。
如果想按原义进行插入,可使用<C-r><C-p>{register}
或者直接回到normal mode
进行粘贴。 -
gv
:高亮上一次选中区域。 -
对于列块可视模式(
<C-v>
/<C-q>
),I
/A
命令会分别置于当前光标之前/之后,而不是跳转到开头/结尾。
在可视模式以及操作符待决模式中,i
/a
命令会被当做一个文本对象的组成成分,而不是直接插入。 -
command-line mode
中,%
代表所有行/当前文件名(参见::h cmdline-special
)。因此:
:%d " 删除所有行
:%s/target/replace " 对所有行执行替换操作
:echo expand('%') " 显示当前文件名
-
:/<html>/,/<\/html>/p
:打印文中<html>
和</html>
标签的所有内容。 -
:/<html>/+1,/<\/html>/-1p
:打印文中<html>
和</html>
标签内的所有内容。 -
命令行模式使用
normal
命令结合.
命令更加强大
" 注释所有行
:%normal I//
" 指定宏命令
:normal @q
" 执行点命令
:%normal .
-
.
:重复上次的普通模式命令
@:
:重复上次的 Ex 命令
▪ 执行一次 Ex 命令后,后续相同命令可使用@@
命令进行重复
▪ 寄存器:
总是保存着最后一次执行的命令行命令(参见::h quote
)
注:.
/:
都是寄存器,可直接使用:register
查看所有寄存器内容
:reg . " 查看 . 寄存器
:reg / " 查看 / 寄存器
:reg 0 " 查看复制专用寄存器
:reg a " 查看寄存器 a
-
bn[next]
:打开缓冲区列表下一个
bp[revious]
:打开缓冲区列表上一个 -
命令行模式下,
<C-d>
命令可以显示所有可用的补全列表::col<C-d>
-
把当前单词插入到命令行:
<C-r><C-w>
把当前字符串插入到命令行:<C-r><C-a>
-
命令行窗口:命令行历史记录,可进行选择与修改。
-
q:
:打开命令行历史窗口
q/
:打开搜索历史窗口 -
如果在 bash shell 中运行 Vim 时,可通过
Ctrl-z
挂起 Vim 进程回到 bash;
在 bash 中,可以用fg
命令回到后台 Vim 进程。 -
把 bash 命令的结果读入到 Vim 缓冲区:
:read !{cmd}
把 Vim 缓冲区内容作为{cmd}
命令的标准输入::write !{cmd}
:read !ping www.baidu.com
ping www.baidu.com
:w !sh
注:可以通过范围指定传递给{cmd}
的缓冲区内容:
ping www.baidu.com
echo "only run this command"
" 高亮选择 echo 语句,然后输入以下命令,就只会执行 echo 这句的命令
:'<,'>w !sh
-
bufdo
可以对:ls
列出的所有缓冲区上执行 Ex 命令:
:bufdo echo expand('%')
-
通配符
*
用于匹配 0 个或多个字符,仅限当前指定目录,不递归子目录。
通配符**
用于匹配 0 个或多个字符,仅会递归子目录。
:args *.* " 将当前目录所有文件都加入到参数列表中,不包含子目录文件
:args **/*.* " 将当前目录及其子目录的所有文件都加入到参数列表中
:args **/*.js " 将当前目录及其子目录的所有 .js 文件都加入到参数列表中
:args **/*.js **/*.css " 将当前目录及其子目录的所有 .js 和 .css 文件都加入到参数列表中
注:使用args
添加文件到参数列表时,会同时将该文件添加到缓冲区中。
-
参数列表比缓冲区列表更容易管理,这使其成为对缓冲区进行分组的理想方式。
▪args
:打印数列表。
▪args {arglist}
:设置参数列表。
▪arga[dd] {names}
:添加文件参数列表。
▪argd[elete] {pattern}
:删除参数列表文件。
▪%argd[elete]
:删除参数列表所有文件。
▪:next
:跳转到下一个参数列表文件。
▪:prev
:跳转到上一个参数列表文件。
▪:argdo
:对参数列表中的每个缓冲区都执行同一条 Ex 命令。
注:参数列表是缓冲区的强烈补充。
更多详细信息,请查看::h args
-
:lcd {path}
命令让我们可以设置当前窗口的本地工作目录。 -
:e %<TAB>
会展开%
代表的含义,即文件名
同理::e %:p<TAB>
/:e %:h<TAB>
··· -
find
命令允许通过文件名查找打开文件,查找目录由path
决定。因此可以通过设置path
选项,结合find
命令可快速打开某个文件:
" ** 表示当前文件夹及其子文件夹内所有文件
set path+=**,app/**,/usr/local/include
:find nameoffile.vim
" 或 tab键自动补全路径
:find nameof<TAB>
-
e
:正向移动到当前单词/下一单词的结尾
ge
:反向移动到上一单词的结尾 -
word:单词(跨单词移动使用:
w
/b
/e
/ge
)
WORD:字串(跨字串移动使用:W
/B
/E
/gE
) -
b
:表示圆括号()
B
:表示花括号{}
-
Vim 的自动位置标记:
位置标记 | 跳转位置 |
---|---|
'' |
当前文件中上次跳转动作之前的位置 |
'. |
上次修改的地方 |
'^ |
上次插入的地方 |
'[ |
上次修改或复制的起始位置 |
'] |
上次修改或复制的结束位置 |
'< |
上次高亮选区的起始位置 |
'> |
上次高亮选取的结束位置 |
-
gi
:回到上次插入模式离开的位置,并再次进入插入模式。此命令使用'^
标记恢复光标位置,并切换进入插入模式。
gf
:跳转到光标所在的文件。如果文件缺少后缀名,可通过set suffixesadd+=.js
进行指定。
-
Vim 不区分
<C-i>
和<Tab>
键,因此当<Tab>
被重新映射时,<C-i>
也会被重映射,不会再有跳转功能。 -
无名寄存器
""
:Vim 默认使用的寄存器。
黑洞寄存器_
:即直接删除:_d{motion}
详情参考::h quote_quote
/:h quote_
-
串行执行宏:
10@{reg}
并行执行宏:1,$ normal @{reg}
,对全部行分别同时执行宏。
注:这里的 并行 指定不是同时执行,而是对每个行单独执行,某一行操作失败也不会导致其余行的宏停止。 -
\V
原义查找:
1)正向查找时,需转义字符/
。
2)方向查找时,需转义字符?
。
3)任何方向查找,都需转义反斜杠字符\
。
注:可用编程方式转义字符:
escape({string}, {chars})
" {chars} 参数指定哪些字符串需要进行转义:
" 正向查找:escape({string},'/\')
" 反向查找:escape({string},'?\')
getcmdtype() " 该函数在正向查找时,返回'/',方向查找时,返回'?'
" 正向查找
:/\V<C-r>=escape("https://www.baidu.com?key=whyn",getcmdtype().'\')
" 反向查找
:?\V<C-r>=escape("https://www.baidu.com?key=whyn",getcmdtype().'\')
- 使能
*
/#
支持可视模式搜索全部高亮字串:
xnoremap * :<C-u>call <SID>VSetSearch('/')<CR>/<C-R>=@/<CR><CR>
xnoremap # :<C-u>call <SID>VSetSearch('?')<CR>?<C-R>=@/<CR><CR>
function! s:VSetSearch(cmdtype)
let temp = @s
norm! gv"sy
let @/ = '\V' . substitute(escape(@s, a:cmdtype.'\'), '\n', '\\n', 'g')
let @s = temp
endfunction
-
substitute
替换命令:其格式如下:
:[range]s[ubstitue]/{pattern}/{string}/[flags]
以下介绍substitute
常用的标志位:flags
▪ g
:修改一行内所有匹配。
▪ c
:交互式确认。
▪ n
:不进行替换,只是显示匹配个数。
▪ e
:匹配不到时,不显示错误信息。
▪ &
:重用上一次substitute
的所使用的标志位。
更多详细信息,请查看::h s_flags
/
以下介绍替换域一部分常用特殊符号:
符号 | 描述 |
---|---|
\r |
插入一个换行符 |
\t |
插入一个制表符 |
\\ |
插入一个反斜杠 |
\1 |
插入第一个子匹配 |
\2 |
插入第二个子匹配(以此类推,最多到\9
|
\0 |
插入匹配模式pattern 的所有内容 |
& |
插入匹配模式pattern 的所有内容 |
~ |
使用上一次substitute 的{string}
|
\={Vim script} |
执行{Vim script} 表达式,并将其结果作为替换{string}
|
▪ 如果substitute
命令使用了寄存器内容,而该寄存器内容存在特殊字符(如&
或~
)时,则需要手动进行转义,当然还有更简单的方法,就是直接引用寄存器内容即可:
:%s//\=@{register}/g
" 比如,替换域直接引用复制专用寄存器
:%s//\=@0/g
▪ *
:当执行替换:s/target/replacement
后,后续使用&
可重复执行上一次的substitue
命令,normal
模式按下&
或Ex
模式输入:&
均可起作用。
注:正常模式下使用g&
会在全局执行上一次的substitute
命令,相当于执行了::%s//~/&
。
- 对
quickfix
列表中的每个条目文件执行命令:
:vimgrep /\Vleader/ **/*.vim " 递归查找当前文件夹所有拥有字符串 leader 的 .vim 文件,存储到 quickfix 列表中
:set hidden " 对 quickfix 列表进行命令执行前,需要设置该选项
:cfdo %s//Leader/gc " 使用 cfdo 命令对 quickfix 列表各条目文件进行替换命令操作
:cfdo update " update 用于保存文件(当文件有改动时,才进行保存)
对quickfix
列表中的每个条目文本执行命令::cfdo {cmd}
更多信息,请查看::h quickfix
-
global
命令:该模式结合了 Ex 命令和 Vim 的模式匹配这两方面的能力,即该命令可以让我们在符合模式匹配的那些行上执行 Ex 命令。其格式如下:
:[range] g[lobal][!] /{pattern}/[cmd]
▪ global
命令缺省作用范围为整个文件(%
),其他大多数命令(如::delete
/:substitute
/:normal
)的缺省作用范围为当前行。
▪ [cmd]
命令可以是除:global
命令之外的任何 Ex 命令。缺省则默认为:print
。
▪ global
命令的执行机制:global
命令在指定的[range]
内的文本上执行时通常分为两轮操作:
第一轮:Vim 标记匹配[pattern]
的所有行
第二轮:在所有标记的文本行上执行[cmd]
举个栗子:将含有TODO
注释的行复制到寄存器中
:qaq " 清空寄存器 a
:g/\VTODO\yank A " 将匹配内容追加到寄存器 a 中。注:不能使用小写,否则最后一条会覆盖前面内容
:reg a " 此时查看下寄存器 a 的内容,应该可以看到操作成功的内容
可以结合t
命令将匹配文本行复制到文本结尾:
:g/\VTODO/t$
注:global
命令后的[cmd]
也可以带一个范围,此时[cmd]
命令的范围是基于global
范围之上的,其格式为:
:[range1] g[lobal][!] /{pattern}/[range2][cmd] " range2 基于 range1,即 cmd 在匹配行内在进行范围选择
举个栗子:对以下 css 属性按字母进行排序,即排序{}
内的所有内容:
html {
border: 0;
font-size: 100%;
margin: 0;
font: inherit;
vertical-align: baseline;
padding: 0;
}
body {
background: white;
color: black;
line-height: 1.5;
}
执行使用以下命令即可完成:
:g/\V{/ .+1,/\V}/-1 sort
上述代码中:
▪ /\V{/
:表示global
命令的匹配
▪ .+1,/\V}/-1
:表示cmd
的范围,此处范围基于global
的匹配范围
上述代码其真实形式如下:
:g/\V{/ (.+1,/\V}/-1 sort) " 括号()范围内即为 cmd
-
定制
grep
程序:Vim 中:grep
命令可以调用外部grep
命令,并对外部grep
命令结果进行解析,然后输出到quickfix
窗口进行查看。
Vim 通过配置grepprg
和grepformat
两个选项对外部grep
命令进行包装:
▪grepprg
:指定调用的 shell 程序(参见::h 'grepprg'
)
▪grepformat
:配置 Vim 解析外部grep
命令输出结果的格式(参见::h 'grepformat'
)。
▪ Vim 在 Unix 系统中的缺省设置如下:
grepprg="grep -n $* /dev/null"
grepformat="%f:%l:%m,%f:%l%m,%f %l%m
其中:
▪ grepprg
中,$*
表示占位符,最终会被:grep
命令的参数代替。
▪ grepformat
中,各占位符的意思如下:
占位符 | 描述 |
---|---|
%f |
文件名 |
%l |
行号 |
%c |
列号 |
%m |
匹配行的文本 |
grepformat
字符串可以使用,
分割指定多组解析格式,比如 Vim 缺省的grepformat
:
grepformat="%f:%l:%m,%f:%l%m,%f %l%m
即表示对格式为%f:%l:%m
或%f:%l%m
或%f %l%m
的行可以进行解析,比如,外部grep
的输出如下所示:
department-store.txt:1:Waldo is beside the boot counter.
goldrush.txt:6:Waldo is studying his clipboard.
goldrush.txt:9:The penny farthing is 10 paces ahead of Waldo.
可以看到是符合%f:%l:%m
的解析格式的,因此 Vim 可以对外部命令grep
进行解析并输出到quickfix
窗口中。
因此,自定义一个外部grep
命令传递给 Vim 简直不要太容易了。
举个栗子:比如我们使用rgrep
(号称当前最快的文本搜索神器)进行文本搜索,外部命令如下:
rg --line-number --column --no-heading --smart-case {word}
上述命令输出如下:
basic\settings.vim:2:5:let {word}=' '
根据命令与输出,我们就可以对 Vim 内置的:grep
命令进行配置了:
set grepprg=rg\ --line-number\ --column\ --no-heading\ --smart-case\ $*
set grepformat=%f:%l:%c:%m
配置完成后,在 Vim 中输入::grep! {word}
,然后使用命令:copen
打开quickfix
窗口,就可以看到搜索结果了。
-
查找参数列表文件内容:
vimgrep
中,##
会被扩展为参数列表中的所有文件:
:args *.txt " 把当前目录所有 .txt 文件加到参数列表中(不递归子目录)
:vimgrep /\Vgoing/g ## " 搜索参数列表中所有出现 going 内容的文件
其他
以下是对 Vim 的一些补充内容。
-
Vim 提供了
<Leader>
键,用于作为用户自定义命令的命名空间。 -
书签:
m{char}
跳转到书签(光标到书签所在行首):'{char}
跳转到书签(具体到定义书签时的光标位置):`{char}
-
搜索后光标停留在匹配单词的第一个字符上(默认):
/word/s
搜索后光标停留在匹配单词的最后一个字符上:/word/e
搜索后光标停留在匹配单词的最后一个字符的下一个字符上:/word/e+1
搜索完成后,可通过命令行输入://e
或//s
改变匹配单词字符位置。 -
%s/{pattern}//gn
:统计pattern
出现的次数。
:vimgrep /{pattern}/g %
:统计pattern
出现的位置。需要使用:copen
打开quickfix
窗口进行查看。 -
gn
:代表当前位置的高亮区域。比如搜索时,当前光标所在的匹配单词即可使用gn
进行操作:dgn
,删除当前高亮单词。 -
/Practical \zsVim
:查找Practical Vim
,但只高亮Vim
/Practical \zsVi\zem
:查找Practical Vim
,但只高亮Vi
-
查看加载的插件:
:scriptnames
-
更改到当前文件所在的目录:
:lcd %:p:h
注:lcd
:改变当前窗口的工作路径
%
:代表当前文件的文件名
:p
:当前文件名全路径
:h
:取出当前文件所在的目录 -
查看按键绑定信息:
:verbose map {keys}
:verbose map <leader>q " <leader>q按键绑定信息
-
H
:移动光标到屏幕的首行
M
:移动光标到屏幕的中间一行
L
:移动光标到屏幕的尾行
zz:让光标所在的行居屏幕中央
zt:让光标所在的行居屏幕最上一行
zb:让光标所在的行居屏幕最下一行
-
删除空行:
:g/^\s*$/d
-
删除行尾空白:
:%s/ *$//
-
删除DOS方式的回车
^M
::%s/\r//g
-
移动光标到上一次的修改行:
'.
移动光标到上一次的修改点:`.
列出你跳转的足迹::ju(mps)
依次沿着你的跳转记录向回跳:<C-o>
依次沿着你的跳转记录向前跳:<C-i>
-
缓冲区转成标签页:
:tab sb[uffer] {num}
-
为每个窗口都设置选项:
:windo setlocal scrollbind
-
好像有很多
g
命令啊:
▪gv
:高亮上一次选中区域。
▪ge
:反向移动到上一单词的结尾
▪gi
:回到上次插入模式离开的位置,并再次进入插入模式。此命令使用'^
标记恢复光标位置,并切换进入插入模式。
▪gI
:跳转到开头并进行插入。
注:gI
效果与I
类似,但是I
是插入到第一个单词前面,而gI
是插入到行起始位置(无论行起始位是否有单词)。
▪gf
:跳转到光标所在的文件。如果文件缺少后缀名,可通过set suffixesadd+=.js
进行指定。
▪gn
:代表当前位置的高亮区域。比如搜索时,当前光标所在的匹配单词即可使用gn
进行操作:dgn
,删除当前高亮单词。
▪g$
:移动到当前行末尾位置。
注:当set nowrap
设置时,g$
效果相当于$
,此时 屏幕行(screenline) 等于 文本行(text line)。
当set wrap
时,g$
只会移动到当前 屏幕行 的末尾,而$
会移动到 文本行末尾。
▪g0
:效果与g$
一致,不过是移动到当前行最前面。
▪g_
:跳转到行末尾单词位置(文本行)。
注:g_
命令与$
类似,但是$
会跳转到行末尾(无论行末尾是否有单词)。
▪g^
:跳转到行起始单词位置。
注:g^
命令与0
类似,但是0
会跳转到行起始位置(无论行起始是否有单词)。
▪gm
:移动到屏幕行中间位置。
▪gM
:移动到文本行中间位置。
▪g;
:依次跳转到上次更改的位置(新到旧跳转)。
▪g,
:依次跳转到上次更改的位置(旧到新跳转)。
▪g<
:回显上次命令行的输出内容。
▪gJ
:拼接上下行,不插入空格分隔符。
注:J
拼接上下行会插入一个空格分隔符。
更多g
命令,请参考:help g