《Vim实用技巧》笔记
📕《Vim实用技巧》, 以前想看只是因为评分挺高的,没想到居然写的这么好
.
命令可以用来重现上次的修改
一次修改的定义:
- 普通模式 : 普通模式下的一次修改"操作"
- 插入模式 : 进入插入模式使用的操作符 + 直到退出插入模式为止所输入的内容
进入插入模式的一些语法糖有特殊好处, 比如想要在每一行的末尾添加一个分号, 如果使用普通的$a;<ESC>
, .
命令重现的上次修改就是操作符+输入内容=a;
, 但使用A;<ESC>
, .
命令重现的上次修改就是A;
基础模式
普通模式
操作符 + 动作命令 = 操作
其中动作命令(motion)用来指定操作的范围, dl
(删除字符l), daw
(一个完整单词aw), dap
(一整个段落ap).
常用操作符:
命令 | 用途
- | -
c
| 修改d
| 删除y
| 复制到寄存器g~
| 翻转大小写gu
/gU
| 转换大小写!
| 使用外部程序过滤 motion 所跨越的行
vim 的语法只有一条额外规则, 即当一个操作符命令被连续执行两次时, 他会作用于当前行.
cc
,dd
,yy
等
插入模式
进入插入模式的一些语法糖:
s
=cl
S
=^c
C
=c$
I
=^i
A
=$a
o
=A<CR>
O
=ko
WARNING
照理说 D
= d$
, Y
应该等于 y$
才对, 但实际 Y
= yy
...
插入模式下可以使用以下组合键
按键操作 | 用途 |
---|---|
<C-h> |
删除前一个字符 |
<C-w> |
删除前一个单词 |
<C-u> |
删至行首 |
<C-j> |
下一行 |
这些命令不是插入模式独有的, 在vim 的命令行模式中, 甚至 shell 中都可以使用
<C-o>
: 切换到插入-普通模式(执行完一个普通模式操作后再次进入插入模式)<C-r>{register}
: 插入寄存器中的文本
在 vim 中使用/<C-r>"<CR>
可以搜索复制的字符串, 对于需要转义的文本后面会介绍完整方案
TIP
表达式寄存器=
, 可以用来执行一段 vim 脚本, <C-r>=4\*60\*60
插入模式下写入14400
可视模式
可视模式用于选中一块文本区域, 可在后续对其执行操作
激活可视模式的三种子模式:
v
面向字符的可视模式V
面向行的可视模式<C-v>
面向列的可视模式
按键操作 | 用途 |
---|---|
gv |
重选上次的可视区域 |
o |
切换高亮选区的活动端 |
关于书中所讲的可视模式.
执行的问题, 简单来说就是vitU
命令中是先选择范围再执行转换, .
只复制了转换过程只能改变选中字符相同的长度. 而普通模式下的gUit
是一条完整指令使用.
可以完全重现.
选择修改列文本, vim 基本的
c
r
A
I
可以用, 更复杂的功能还是推荐插件吧 https://github.com/mg979/vim-visual-multi
实战
将下列文本转成 markdown 格式的表格
Chapter Page
Normal mode 15
Insert mode 31
Visual mode 44
首先将光标放在首行合适的分割位置再执行:
<C-v>jjjr|
将当前列替换成|字符yyp
复制一个首行的副本- 接下来需要用到命令行模式:
:s/[^|]/-/g
将副本行的非|字符替换成-
Chapter | Page
-------------|---------
Normal mode | 15
Insert mode | 31
Visual mode | 44
TIP
<C-v>jjjr|yyp:s/[^|]/-/g
这段命令只是模仿书上的, 但例子我改了.
简单的方法应该是先写第二行的分隔符yypVr-
, 再将光标移动到想要分割的列后: <C-v>jjjokr|
命令行模式
vi 的前身是名为 ex 的行编辑器
使用:
可以执行各 ex 命令, 插入模式下的各种按键操作在命令行模式下同样试用
执行范围
print 简写p
, 直接执行将会打印当前行内容
很多 ex 命令都可以指定一个执行范围, :2,3p
将会打印2-3行的内容.
可用于指定范围的特殊符号:
符号 | 地址 |
---|---|
1 |
文件的第一行 |
$ |
文件的最后一行 |
0 |
虚拟行,位于文件第一行上方 |
. |
光标所在行 |
'm |
包含位置标记m的行 |
'< |
高亮选区的起始行 |
'> |
高亮选区的结束行(可视模式下将自动附加) |
% |
整个文件(:1,$ 的简写形式) |
copy 简写 t
:6t.
将第6行的内容复制到当前行下:t6
将当前行的内容复制到第6行下'<,'>t0
将选中块中的文本复制到文件开头
move 简写 m
操作如上
@:
可以重复执行上次的 ex 命令
normal 简写 norm
在指定范围上执行普通模式命令
:%norm A;
向文件中所有行末尾附加分号:'<,'>norm I//;
选中行添加//注释, 在可视模式下使用:
将会自动附加'<,'>
范围
normal
指令还是很常用的(可惜 ideaVim 暂不支持...)
在 vim 命令行下执行<C-r><C-w>
将会复制光标下的单词并将其插入到命令行中, 普通模式下常用的*
指令将等效于/\<<C-r><C-w>\><CR>
WARNING
<C-r>
说明是个寄存器操作但<C-w>
这是啥...
历史命令
<C-p>
上一个命令<C-n>
下一个命令
这个和 shell 是一样的
q:
ex 命令行窗口:h cmdwin
关键是命令行窗口, 相信很多用 vim 的小伙伴都误入过这种模式:
普通模式下执行q:
后在左下角将会打开这个记录历史命令的窗口, 在该窗口内可以执行完整的 vim 指令, 这样就可以方便我们编辑/合并历史命令了
退出方法有两种:
- 使用 vim 的
:q
- 在任一行按下
<CR>
即回车键将会自动退出并执行该行的命令, 而且初始行为空也方便我们误入该模式后直接<CR>
安全退出
IMPORTANT
后面会介绍一个也很有用的查找记录命令行窗口 q/
, 操作都是一样的
执行 shell
在命令行模式中, 加一个!
前缀就可以调用外部命令 :!go run %
read
/write
前缀可以将当前 buffer 内容作为外部命令的标准输出/输入
最常用的是直接用外部命令过滤缓冲区内容, 只要在调用外部命令前给出指定范围就行了
%!cat a.txt
: 使用 a.txt 的内容重写当前文件
john,smith,john@example.com
drew,neil,drew@vimcasts.org
jane,doe,jane@example.com
:%!sort -t',' -k2
: 使用外部 sort 根据姓氏重新排序上面的内容
书中文件一章跳着看的, 自己全是用插件来做 buffer 管理
更快的移动和跳转
实际行与屏幕行, 默认的 hjkl 是根据实际行来移动的, 所以在超出屏幕宽度显示为多行中使用 jk 移动会出现反直觉的情况, 可以使用gj
, gk
命令按照屏幕行移动光标或直接 nnoremap 映射过去
单词移动:
将大写的W
,E
,B
理解为快速移动就好了
F
,T
理解为反向查找
在执行d/nnore
,y/nnore
这类查找操作时候, 删除和拷贝的都是当前位置到查找位置nnore
之前的字符不包括查找字符, 可以在后面接/e
后缀来表示执行到查找位置末尾
个人最离不开的插件就是vim-easymotion(ideaVim 也有的), 并且将其指令前缀设置为
;
,;
原生是用来重复上一次行内字符搜索的, 需要多次查找的情况强制自己用 easymotion 慢慢就熟悉了
文本对象选区
i 可以理解为 inside, 指文本选区内部, a 理解为 around
文本对象 | 选择区域 |
---|---|
a) /ab |
(parentheses) |
i) /ib |
(parentheses ) |
a} /aB |
{parentheses} |
i} /iB |
{parentheses } |
at |
<xml>tags</xml> |
it |
<xml>tags </xml> |
aw |
当前词及一个空格 |
iw |
当前词 |
ap |
当前段落及一个空行 |
ip |
当前段落 |
前面接操作符c
=>ciw
,v
=>vip
等组成想要的操作
i 和 a 的选区算是我最喜欢并且入坑 vim 的操作了
位置标记
m
命令后接选定字母可以用该字母标记当前位置, 比如mm
可以记录当前位置到 m 标记上
跳转标记:
`m
跳转 m 标记的准确位置'm
跳转 m 标记所在行的第一个字符处
因为自己用的是60%键盘, ` 需要组合键触发, 更倾向于将这两个键互换下
vim 的自动标记位置:
位置标记 | 跳转到 |
---|---|
`` |
上次跳转动作之前的位置(当前文件中) |
`. |
上次修改的地方 |
`[ |
上次修改或复制的开始位置 |
`] |
上次修改或复制的结束位置 |
%
在匹配括号之间跳转
<C-o>
,<C-i>
文件级的位置来回跳转
快速跳转(语法糖)
g;
跳转到上次修改的位置gi
恢复上次的插入模式
寄存器
vim 中复制粘贴命令xp
,yyp
等默认使用的都是无名寄存器"
指定寄存器 a 使用"
前缀: "ax
: 复制字符到 a 寄存器, "ap
: 粘贴字符
无名寄存器总是缺省的, 常常会被一些意外的内容覆盖, 比如可视模式下粘贴, 总之用多了就后知后觉然后会设置复制专用寄存器 0 的快捷键, 比如: nnoremap <Leader>p "0p
特殊寄存器:
寄存器 | 内容 |
---|---|
"= |
表达式寄存器 |
"% |
当前文件名 |
". |
上次插入的文本 |
": |
上次执行的 ex 命令 |
"/ |
上次查找的模式 |
在可视模式下使用p
命令时, vim 将用我们指定寄存器的内容来替换高亮选中的文本, 这里无名寄存器 " 将会被原高亮区文本覆盖.
在插入模式下使用<C-r>{register}
可以插入指定寄存器的内容
<C-r>0
: 插入复制专用寄存器的内容.
宏
宏允许我们把一段修改序列录制下来, 用于之后的回放
qa
开始录制保存到 寄存器 a 的宏q
结束录制@a
播放宏
宏的使用虽然非常简单, 书中主要大篇幅讲了宏录制的规范
实战
将以下文本转成 markdown 的有序列表
partridge in a pear tree
turtle doves
French hens
calling birds
golden rings
使用let
关键字可以在 ex 命令中创建变量:
:let i=0
:let i+=1
:echo i
前面提到在插入模式下引入表达式寄存器, 执行<C-r>=i<CR>
可以插入变量 i 的值
:let i=1<CR>
定义编号变量 iqa
开始录制宏并保存到寄存器 a 中I<C-r>=i<CR>. <ESC>
在行开头插入编号.:let i+=1<CR>
递增编号q
结束录制宏
播放宏可以使用两种方式:
- 使用可视模式选中后面的行
jVjjj
:
这里自动附加 ex 执行范围为选中区'<,'>norm @a
使用 norm 对选中的每行播放宏 - 或者录制宏的时候在第4步干脆把
j
(即移动到下一行)也录制进去, 然后使用4@a
手动播放
1. partridge in a pear tree
2. turtle doves
3. French hens
4. calling birds
5. golden rings
编辑宏内容
:put a
可以将寄存器 a 中的内容粘贴至当前光标行下方
0"ay$dd
可以将编辑后的宏安全复制回寄存器 a 并删除当前副本
模式
vim 中/
命令按正则表达式查找时, 使用\v
前缀可以开启 very magic 模式, 使得正则运作更像是一般编程语言所用的 Perl 风格
- vim 原生正则风格(类似POSIX):
/\(a\|b\)
查找 a 或 b 字符 - very magic 风格(类似Perl):
/\v(a|b)
查找 a 或 b 字符
捕获子匹配 /\v(a|b)\1
匹配 aa 或 bb, 这是编程中的常用操作, 后面介绍的用子匹配内容作为其他命令的执行范围非常有用
[warning] 不捕获子匹配内容用
%()
而不是一般编程的(?:)
匹配的边界
前面提到普通模式下的\*
指令等效于/\<<C-r><C-w>\><CR>
, 其中包裹着当前光标单词的<>
符号是单词界定符表示这是一个单词的边界, 使用g*
将不使用单词界定符
模式与匹配的区别:
- 模式指查找域中输入的正则表达式
- 匹配是文档中被高亮显示的文本
我们可以用\zs
与\ze
对匹配进行裁剪, /v"\zs[^"]+\ze"
将会查找引号所包裹的字符且不匹配包裹的引号
\zs
,\ze
都是零宽字符, 其功能和 Perl 正则中的"环视"即零宽断言类似. 上面例子的正则在一般编程语言中的写法是:(?<=")\[^"]+(?=")
, 术语叫做 零宽正向先行断言 和 零宽正向后发断言
vim 脚本中的常用函数escape
(参见:h escape
)可以帮助转换字符, escape(@u, '\/')
: 为寄存器 u 中所有 \ / 字符加上反斜杠前缀
查找
:set hls
highlight search 开启高亮搜索
以前是看别人文章推荐将Backspace
按键映射到:se nohls
用来关闭高亮搜索, 书中推荐的方案是用<C-l>
, 在原有的清屏重绘功能上加入关闭搜索, 也更符合键盘本位...推荐尽量用<C-j>
来代替<CR>
更别说<Backspace>
了
nnoremap <silent> <C-l> :<C-u>nohlsearch<CR><C-l>
默认搜索的跳转位置光标会停留在搜索词的开始, 在某些脚本中想要搜索光标位置停留在搜索词末尾可以使用/lang/e
, 这也是为什么需要转义 / 字符, / 可以用来标识结束匹配并追加自定义操作
q/
命令行窗口(查找历史)
实战
查找所有单引号字符串并换成双引号
/\v'(([^']|'\w)+)'
如果是代码防止的应该是反斜杠转义%s//"\1"/g
替换子匹配内容, substitute 命令后面会介绍
查找当前选中文本
vim 中没有默认提供该功能, 在可视模式下*
命令还是查找当前光标下的单词, 我们可以自定义脚本提取高亮选区的文本后将其转义后再用于搜索
书中提供的配置:
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()
let temp = @s
norm! gv"sy
let @/ = '\V' . substitute(escape(@s, '/\'), '\n', '\\n', 'g')
let @s = temp
endfunction
替换
substitute 语法 :[range]s[ubstitute]/{pattern}/{string}/[flags]
标志位 flags:
g
全局替换, 而不是默认的只替换第一个c
手动确认是否替换(confirm)
替换域中的特殊字符
符号 | 描述 |
---|---|
\r |
换行符 |
\\\ |
反斜杠 |
\1 |
插入第一个子匹配 |
\0 |
插入匹配模式的所有内容 |
\={vim script} |
执行表达式 |
:%s/going/rolling/gc
全局替换整个页面的 going 为 rolling 并手动确认每一处
在上面例子中有用到%s//"\1"/g
,替换前一次搜索子匹配的内容, 可以发现其中的查找域留空了, 当查找域为空时 vim 默认使用当前的模式, 所以上面的/\v'(([^']|'\w)+)'
+%s//"\1"/g
等价于:%s/\v'(([^']|'\w)+)'/"\1"/g
:&&
执行上一次的替换行为, &
快捷键将执行忽略标志位的上一次替换行为
global 命令
:[range] global[!] /{pattern}/ [cmd]
:global
简写 :g
, 在某个指定模式的所有匹配行上分别运行 ex 命令
:g/re/p
其中的 re 指 regular expression(正则表达式), 这条命令的意思就是将该正则匹配的所有行打印出来(print), 这也是grep
这个词的由来, ex 的前身 ed 首次使用了 grep 程序, 该程序最早由肯·汤普逊写成(没错就是那个设计 B语言, unix 和参与 Golang 开发的)
:v/re/d
v
代表 inverse 反选, 该命令用来删除所有不匹配 re 模式的行
两个简单的例子:
:g/TODO/yank A
将包含 TODO 字符串的行添加至寄存器 a, 注意寄存器用大写表示追加写入:g/TODO/t$
将包含 TODO 字符串的行添加至文件末尾
来看一个匹配 css 文件中所有大括号内的属性并排序的例子
一个复杂的例子:
g/{/ .+1,/}/-1 sort
选中所有大括号包裹的行并排序
global 广义命令形式如下:
:g/{start}/ .,{finish} [cmd]
其中 +1 -1 都是偏移量, .+1,/}/-1
表示从当前行+1到查询}
所在行-1位置
IMPORTANT
在 vim 中遇到需要区分特定的某些行并执行操作时候就用 global
指令