blog/source/_posts/vim-tips.md
2021-05-13 10:19:30 +08:00

346 lines
22 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
title: VIM使用技巧
tags:
- DevOps
- VIM
- 使用技巧
categories:
- - DevOps
- 系统管理
date: 2021-05-08 15:21:06
keywords: VIM,技巧,模式,快捷键,Cheatsheet,编辑,easy motion,surround
---
VIM是Linux系统中最常用的命令行编辑器但是大多数人都会觉得VIM编辑器难以使用这是因为在没有鼠标的支持下仅仅依靠键盘来完成全部操作需要熟记大量的快捷键。而且除此之外可选择使用的数量众多的插件也带来了更多的快捷键这也使得VIM的操作更加复杂。这篇文章不是要说明VIM的使用而是要记录VIM常用的一些概念和操作方便在使用的时候进行快速的查找和助记。<!-- more -->
## 模式切换
在VIM中常用的模式有三个命令模式NORMAL、插入模式INSERT、可视模式VISUAL。此外还有一个替换模式REPLACE只是这个替换模式与插入模式基本上功能差不多只是执行替换光标所在位置的字符功能而不是向光标所在位置插入新的字符。
如果没有经过特殊的配置刚进入VIM的时候都是处于普通模式的在这个模式下任何键盘输入都是命令而不会直接转变为屏幕上的输入这是使用VIM第一个不习惯的地方。
!!! note ""
如果习惯了VIM的模式切换就会发现编辑效率还是非常高的因为在命令模式下可以有非常多的快速编辑功能可供使用前提是不要总是习惯性的使用HJKL来移动光标。
其实对于模式的切换,只有那么几个快捷键,具体可以看下表。
| 快捷键 | 进入的模式 | 助记词 |
|---|---|---|
| `i` | 在光标所在的字符之前的位置进入插入模式。 | `insert` |
| `I` | 在光标所在行的行首进入插入模式。 | `Insert` |
| `a` | 在光标所在的字符之后的位置进入插入模式。 | `append` |
| `A` | 在光标所在行的行尾进入插入模式。 | `Append` |
| `o` | 在光标所在行的下方新建一行进入插入模式。 | |
| `O` | 在光标所在行的上方新建一行进入插入模式。 | |
| `R` | 在光标所在的字符位置进入替换模式,新的输入将从光标所在的字符开始替换。 | `Replace` |
| `c` | 执行删除操作以后进入插入模式要删除的内容由其后跟随的Text Object或者Motion确定。 | `change` |
| `v` | 在光标所在的位置进入可视模式,光标所在的字符不在被选中的块中。 | `visual` |
| `V` | 在光标所在的行进入行可视模式,以进入行为起点,光标所在的行为终点,选中中间所有行。 | `Visual` |
| `:` | 进入命令行模式。 | |
## 命令格式
VIM中的命令格式其实是有固定格式的所以用起来并不难。几乎VIM中的所有命令都是遵循以下命令格式。
!!! info "VIM命令格式"
`[prefix][number]<command>[text object|motion]`
这个命令格式中开头的数字表示命令的重复次数,例如`2x`表示删除两个字符。在默认情况下VIM的命令都是以字符为单位进行操作的如果要以行为单位进行操作则需要使用以下公式。
!!! caution ""
例如使用`d3j`并不会直接删除3行内容而是会删除这3行中光标移动范围内的所有字符。
!!! info "VIM行操作命令格式"
`[prefix]<command>[number]<command>`
在这个命令格式中,前后两个`command`均为同一个命令中间的数字如果为1的话则可以省略。例如要删除3行内容则可以用`d3d`来完成。
所有VIM命令中的`prefix`内容一般都是由VIM自身或者插件定义使用的通常采用的`prefix`都是`<leader>`键或者是`<leader>`键与其他字符的组合。
> 大部分的VIM命令都十分简单易记在不考虑重新映射键位的情况下命令和动作都是相应单词的首字母例如`d2w`就是`delete 2 words`的缩写,
### 光标移动
从命令格式可以看出平时使用的HJKL快捷键实际上都是Motion。所以在移动光标的时候不要再狂按HJKL这四个键了。
> 我承认我一直也是这么狂按来着。
例如向下移动4行可以按`4j`向右移动8个字母可以按`8l`。
另外的VIM还提供了其他快速移动光标的Motion常用的可参见下表。
| 移动命令 | 效果 | 助记词 |
|---|---|---|
| `w` | 向右以单词为单位移动,光标落在单词的首字母上。 | `word` |
| `W` | 向左以单词为单位移动,光标落在单词的首字母上,只以空格作为单词的分割。 | `WORD` |
| `e` | 向右以单词为单位移动,光标落在单词的尾字母上。 | `word End` |
| `E` | 向右以单词为单位移动,光标落在单词的尾字母上,只以空格作为单词的分割。 | `WORD eND` |
| `b` | 向左以单词为单位移动,光标落在单词的首字母上。 | `word Before` |
| `B` | 向左以单词为单位移动,光标落在单词的首字母上,只以空格作为单词的分割。 | `WORD bEFORE` |
| `ge` | 向左以单词为单位移动,光标落在单词的尾字母上。 | `gOTO WORD eND` |
| `gE` | 向左以单词为单位移动,光标落在单词的尾字母上,只以空格作为单词的分割。 | `Goto word End` |
| `0` | 移动光标到行首。 | |
| `^` | 移动光标到行首第一个不是空白的字符。 | 正则表达式字符串起始符号 |
| `$` | 移动光标到行尾。 | 正则表达式字符串结束符号 |
| `%` | 移动到配对的括号位置。 | |
| `\|` | 移动到当前行指定列的位置。 | |
| `+` | 向下移动指定行数。 | |
| `-` | 向上移动指定的行数。 | |
| `_` | 向下移动指定的行数,光标会停留在目标行的上一行。 | |
| `gg` | 移动到文件首行第一个不是空白字符的位置。 | |
| `G` | 移动到文件末行第一个不是空白字符的位置,如果指定了数量,则是移动到指定行。 | |
| `f{char}` | 向右移动到第一个指定字符位置。 | `find` |
| `F{char}` | 向左移动到第一个指定字符位置。 | `find` |
| `t{char}` | 向右移动到第一个指定字符左侧的位置。 | `till` |
| `T{char}` | 向左移动到第一个指定字符右侧的位置。 | `till` |
| `;` | 顺向重复FfTt命令。 | |
| `,` | 反向重复FfTt命令。 | |
### Text Object
使用文本对象可以快速对更大的文本内容进行修改,而不用逐字符的进行编辑。文本对象都使用两个字符开头:`i`和`a`。这两个字符的区别是,`i`表示要操作的文本对象不包含指定的分隔符,而`a`则包含分割符。例如`i<`会只选中`<>`中间的内容,而`a<`会连`<>`一起选中。文本对象一般使用两个字符表示,第二个字符表示不同的文本对象类型。
可以使用的文本对象类型有以下这些。
| 类型字符 | 文本对象类型 | 助记词 |
|---|---|---|
| `w` | 一个单词,两端以空格或者标点符号分割。 | `word` |
| `W` | 一个单词,两端仅以空格进行分割,标点符号不会被认为是分割符。 | `WORD` |
| `s` | 一个句子,两端以空格和标点符号分割。 | `sentence` |
| `p` | 一组句子组成的段落。 | `paragraph` |
| `t` | HTML的标签中间的内容。 | `tag` |
| `<`、`>` | 由`<`和`>`括起来的内容。 | |
| `(`、`)` | 由`(`和`)`括起来的内容。 | |
| `[`、`]` | 由`[`和`]`括起来的内容。 | |
| `{`、`}` | 由`{`和`}`括起来的内容。 | |
| `"` | 由`"`括起来的内容。 | |
| `'` | 由`'`括起来的内容。 | |
| `` ` `` | 由 `` ` `` 括起来的内容。 | |
### Motion
其实在前面的表格中已经出现了大量的Motion动作例如基本上所有的移动命令都可以作为一个Motion来使用。比如`d$`表示从当前光标位置一直删除到本行的末尾,`dfr`表示从当前光标位置一直删除到第一个`r`字符出现的位置(这个`r`字符也会被删掉,如果要保留`r`字符,需要使用`t`命令)。
常用的Motion可以直接参考前面的光标移动一节。
## 查找替换
查找替换是文本编辑中最常用的功能在VIM中如果只使用手工删除和编辑那反而不如其他编辑器来的方便所以如果要提高VIM的编辑速度还必须熟练使用VIM自身的查找替换命令。VIM中的查找替换一般都在命令模式中使用不必进入插入模式。
### 模式搜索
模式搜索可以快速在文件中定位所需要的内容,要使用模式搜索可以直接在命令模式中按下`/`,之后开始输入所需要匹配的模式内容即可。例如`/hello`将会使文件所所有的`hello`字样高亮,并且光标会定位在距离最近的一个匹配上。
在一个文件中有多个匹配的时候,可以按`n`切换到下一个匹配,`N`切换到上一个匹配。这两个命令都可以搭配重复次数来使用以快速跳转到指定的匹配位置。
使用`/`开头的模式搜索,其完整的命令格式如下:
!!! info "模式搜索命令格式"
`/{pattern}[/{offset}]`或者`?{pattern}[?{offset}]`
模式搜索除了可以使用`/`开头,还可以使用`?`开头,区别是`/`表示在文本中从头向尾搜索,而`?`则相反。
命令中的`offset`是用来定义在寻找到匹配项以后,光标如何定位的,`offset`表达式可以使用以下格式的内容。
| Offset表达式 | 光标定位 | 助记词 |
|---|---|---|
| `n` | 定位在匹配项之下第`n`行。 | |
| `+n` | 定位在匹配项之下第`n`行。 | |
| `-n` | 定位在匹配项之上第`n`行。 | |
| `e+n` | 定位在匹配项最后一个字符右侧第`n`个字符。 | `End` |
| `e-n` | 定位在匹配项最后一个字符左侧第`n`个字符。 | `End` |
| `s+n``b+n` | 定位在匹配项第一个字符右侧第`n`个字符。 | `Start``Begin` |
| `s-n``b-n` | 定位在匹配项第一个字符左侧第`n`个字符。 | `Start``Begin` |
| `;{pattern}` | 定位在另一个模式匹配上。 | |
一个模式表达式是由一组更细的元素组成的,组成模式表达式的各级元素可参见下表。
- `pattern`是由一个或者多个`branch`组成,不同的`branch`之间形成或的关系,多个`branch`之间使用`\|`分割。
- `branch`
- `branch \| branch \| branch`
- `branch`是由一个或者多个`concat`组成,不同的`concat`之间形成与的关系,多个`concat`之间使用`\&`分割。
- `concat`
- `concat \& concat`
- `concat`是由一个或者多个`piece`组成,不同的`piece`之间表示对匹配的描述,例如`h[0-9]d`表示匹配以`f`开头,数字在中间,`d`结尾的文本。多个`piece`直接连续书写即可。
- `piece`是由一个或者多个`atom`组成的,每一个`atom`匹配文本的一个单元(可以认为是字符,因为也可能会是一组字符)。
- `atom`通常就是普通的字符,在有特殊匹配需要的情况下也可以使用`multi`表达式来扩展`atom`的匹配能力。
例如这是一个比较复杂的模式匹配命令:`/f[0-9]d\|g[0-9]\&h.\{3,}d`。大部分情况下,模式匹配都是使用普通的单词进行搜索,偶尔会用到的也只是带了`*`的模糊匹配,特别复杂的表达式并不多见。
### 替换
字符串替换在VIM中是以一条命令的形式出现的使用非常频繁。命令全称为`:substitute`,通常仅使用其简写形式`:s`。
!!! info "Substitute命令格式"
`:[range]s[ubstitute]/{pattern}/{string}/[flags] [count]`
替换命令的命令格式来自VIM的官方文档其中`[]`包裹的部分均为可选内容,`{}`包裹的内容均为必填的字符串。
!!! caution "Substitute命令的小技巧"
如果被替换的字符串或者替换成的字符串中包含`/`这个分割符,那么可以将命令的`/`分割符都替换成其他的符号,命令的不同部分之间只需要保证使用相同的分割符隔开就可以保证命令输入的正确。
命令中的`range`表示替换命令的作用范围,在省略这一项时,替换命令将只在当前行起效。`range`表达式所描述的都是行范围,其格式一般为`{start},{end}`。范围的定义除了可以使用数字直接定义行的绝对范围以外,还可以借助一些符号来定义相对范围。常用的表达式可参见下表。
| 范围表达式元素 | 功能含义 | 使用示例 | 示例含义 |
|---|---|---|---|
| `{number}` | 绝对行数。 | `4,5` | 第4行至第5行 |
| `+{number}` | 从当前行向下`number`行 | `.,+3` | 从当前行开始向下3行 |
| `-{number}` | 从当前行向上`number`行 | `-3,$` | 从当前行向上3行开始直到文件结束 |
| `.` | 当前行。| `1,.` | 第1行到当前行 |
| `$` | 文件最后一行。 | `.,$` | 当前行到最末行 |
| `%` | 整个文件 | `%` | 整个文件,相当于`1,$` |
| `'t` | 当前文件中的`t`标记 | `1,'k` | 第1行到k标记的行 |
| `/{pattern}` | 模式匹配结果的下一行 | `1,/hello` | 第一行到第一次出现`hello`的下一行 |
| `?{pattern}` | 模式匹配结果的上一行 | `1,?hello` | 第一行到第一次出现`hello`的上一行 |
| `\/` | 上一次进行模式匹配的结果的下一行 | `1,\/` | 第一行到上一次进行的模式匹配结果的下一行 |
| `\?` | 上一次进行模式匹配的结果的上一行 | `1,\?` | 第一行到上一次进行模式匹配结果的上一行 |
| `\&` | 上一次在替换中所使用的模式匹配结果的下一行 | `1,\&` | 第一行到替换中所使用模式匹配结果的下一行 |
除了可以使用`,`分割范围表达式的起止以外,还可以使用`;`分割,这两者的区别是`;`会将光标保留在起始行原地。
命令中的`flag`主要用于控制一些替换功能,常用的`flag`可以参见下表。
| 替换功能标记 | 代表功能 |
|---|---|
| `&` | 使用与上一次替换相同的功能标记。 |
| `c` | 手工确认每一次替换。 |
| `e` | 如果没有发现任何匹配,不输出错误信息。 |
| `g` | 替换一行中的所有匹配,默认情况下每一行只替换第一个匹配。 |
| `i` | 忽略大小写。 |
| `I` | 执行区分大小写的精确匹配。 |
| `n` | 不执行替换,仅输入将会发生多少次替换。 |
| `p` | 输出将被替换的行。 |
| `#` | 输出将被替换的行及其行号。 |
命令中必填的`pattern`内容与模式搜索中所使用的Pattern组成相同。替换成的目标`string`则可以是普通的字面量字符串,也可以是包含了特殊语法内容的特殊字符串。下表中是`string`中常用的特殊内容。
| 特殊替换目标 | 功能含义 |
|---|---|
| `\0` | 代表被`pattern`匹配的内容,用于将被匹配的内容带入进来。 |
| `\1` ... `\9` | 代表在`pattern`中使用`()`进行分组的内容,用于将被匹配的内容带入进来。 |
| `\u` | 下一个字符改为大写。 |
| `\U` | 后续的字符都改为大写,直到出现`\E`标记。 |
| `\l` | 下一个字符改为小写。 |
| `\L` | 后续的字符都改为小写,直到出现`\E`标记。 |
| `\r` | 回车符。 |
| `\n` | 换行符。 |
| `\b` | `<BS>`符号。 |
| `\t` | `<TAB>`符号。 |
## 重新定义键位
VIM中的快捷键不是一成不变的而且可以根据自己的需要在配置文件中自定义。配置文件一般放置在用户的家目录`$USER_HOME`)中,以`.vimrc`命名。
在配置文件中重新定义键位和在VIM的命令行中重新定义键位是一样的只是在配置文件中不需要输入`:`。定义键位使用`map`命令,格式如下。
!!! info "map命令格式"
`:map 按键 执行功能`
`map`命令拥有一系列命令,可以分别定义不同模式下的键位。但是在一般进行键位配置时,通常会使用`noremap`系列命令这是因为所要定义的按键很有可能已经被VIM或者其他插件使用了使用`noremap`系列命令就可以避免覆盖掉那些设置。综合起来可以使用的命令有以下这些。
| 命令 | 功能 |
|---|---|
| `map``noremap` | 定义Normal、Insert和Operator-pending三个模式下的键位。 |
| `vmap``vnoremap` | 定义Visual模式下的键位。 |
| `nmap``nnoremap` | 定义Normal模式下的键位。 |
| `omap``onoremap` | 定义Operator-pending模式下的键位。 |
| `map!``noremap!` | 定义Insert和Command-line模式下的键位。 |
| `imap``inoremap` | 定义Insert模式下的键位。 |
| `cmap``cnoremap` | 定义Command-line模式下的键位。 |
## 常用插件
这里推荐一个寻找VIM插件的网站{% link 'Vim Awesome' https://vimawesome.com/ %}这个网站里集成了几乎全部常用到和流行的最VIM插件。
!!! caution ""
安装VIM插件的时候千万不要贪多插件安装的越多VIM运行越慢。这对于任何一个支持插件的软件来说都是一个必然的常识。
### Easy Motion
Easy Motion插件使用另一种方式提升了在多个搜索匹配项之间进行快速跳转的能力。下面借用一张Easy Motion官方的录屏来展示一下Easy Motion的魅力。
{% oss_image vim-tips/easymotiondemo1.gif EasyMotion功能演示 650 %}
Easy Motion为了避免按键冲突特地选用了双击`<leader>`按键激活的键位设置。
!!! note ""
在VIM中`leader`通常是`\\`键,但是一般为了操作方便都会使用`let mapleader=","`将其更改为英文逗号键,毕竟逗号键按起来比较省事。
Easy Motion的搜索功能使用起来并不麻烦基本上与VIM原生的搜索功能类似。
| 功能激活键位 | 搜索功能 |
|---|---|
| `<leader><leader>s` | 搜索单个字符。 |
| `<leader><leader>f` | 顺向搜索单个字符,光标停在匹配的字符上。 |
| `<leader><leader>F` | 逆向搜索单个字符,光标停在匹配的字符上。 |
| `<leader><leader>t` | 顺向搜索单个字符,光标停在匹配字符的前一个字符上。 |
| `<leader><leader>T` | 逆向搜索单个字符,光标停在匹配字符的后一个字符上。 |
| `<leader><leader>w` | 高亮光标之后每一个单词的首字符。 |
| `<leader><leader>b` | 高亮光标之前每一个单词的首字符。 |
| `<leader><leader>l` | 顺向高亮单词、驼峰中的单词、`_`之后的单词、`#`之后的单词的首尾字符。 |
| `<leader><leader>h` | 逆向高亮单词、驼峰中的单词、`_`之后的单词、`#`之后的单词的首尾字符。 |
| `<leader><leader>e` | 高亮光标之后每一个单词的尾字符。 |
| `<leader><leader>ge` | 高亮光标之前每一个单词的尾字符。 |
| `<leader><leader>j` | 高亮光标之后每一行的首字符。 |
| `<leader><leader>k` | 高亮光表之前每一行的首字符。 |
| `<leader><leader>/` | 进行模式匹配,高亮每一个匹配的首字符。 |
| `<leader><leader><leader>bdt` | 全文搜索单个字符,光标停在匹配字符的前一个字符上。 |
| `<leader><leader><leader>bdw` | 全文高亮每一个单词的首字符。 |
| `<leader><leader><leader>bde` | 全文高亮每一个单词的尾字符。 |
| `<leader><leader><leader>bdjk` | 全文高亮每一行的第一个字符。 |
| `<leader><leader><leader>j` | 高亮全部可跳转的内容。 |
| `<leader><leader>2s` | 搜索两个字符。 |
| `<leader><leader>2f` | 顺向搜索两个字符,光标停在匹配的字符上。 |
| `<leader><leader>2F` | 逆向搜索两个字符,光标停在匹配的字符上。 |
| `<leader><leader>2t` | 顺向搜索两个字符,光标停在匹配字符的前一个字符上。 |
| `<leader><leader>2T` | 逆向搜索两个字符,光标停在匹配字符的后一个字符上。 |
| `<leader><leader><leader>bd2t` | 全文搜索两个字符,光标停在匹配字符的前一个字符上。 |
如果使用一次Easy Motion的匹配功能或者是观察之前官方的演示录屏就会发现Easy Motion会将界面变暗并高亮若干字母但是这些字母并不是文本中原有的内容。这些高亮的字母是Easy Motion提供给使用者键入完成光标跳转的所以在找到匹配项之后只需要键入Easy Motion给出的按键提示就可以直接将光标转移过去。
### Surround插件
Surround插件是用来快速编辑代码中出现的括号、引号甚至是XML标签的。通过Surround插件提供的快捷键可以快速完成对这些内容的删除、替换和创建。
Surround插件的使用一般需要结合Motion或者是Text Object来使用并且在使用的时候需要将光标定位在需要操作的文本上。
| 操作快捷键 | 功能 | 助记词 |
|---|---|---|
| `ys<text object\|motion><desired>` | 将`desired`添加到Text Object或Motion定义的位置。 | |
| `yss<desired>` | 为当前行添加`desired`包裹。 | |
| `ds<exist>` | 将`exist`删除。 | `delete` |
| `cs<exist><desired>` | 将`exist`替换成`desired`。 | `change` |
| `S<desired>` | 在Visual模式选中被包裹的内容。 | `select` |
> 快捷键描述中的`<>`都不需要输入。如果Motion使用`t`或者`<`来创建标签,那么将会启动标签输入的提示。
在向文本中插入括号(`()`、`[]`、`{}`)时,左括号和右括号所代表的内容是不一样的,如果`desired`使用左括号,那么括号将紧贴所选择的内容;如果使用右括号,那么括号和内容之间将存在一个空格。
### Sneak插件
Sneak插件是用来支持在搜索结果间快速跳转的。乍一听起来Sneak插件的功能跟EasyMotion十分的相似但是Sneak与EasyMotion相比起来更加的轻量。Sneak使用`;`和`,`在所有的搜索结果中向后和向前跳转。而与EasyMotion不同的是Sneak默认采用两字符的搜索会定位文件中所有指定的字符组合。
> 如果需要连续执行多次跳转,可以使用`<number>;`和`<number>,`。例如`3;`。
| 操作快捷键 | 功能 | 助记词 |
|---|---|---|
| `s<char><char>` | 顺向搜索文档中的两字符组合。 | `search` |
| `S<char><char>` | 逆向搜索文档中的两字符组合。 | `search` |
| `<command>z<char><char>` | 对文档中顺向首次出现的两字符组合执行指定操作。 | |
| `<command>Z<char><char>` | 对文档中逆向首次出现的两字符组合执行指定操作。 | |
> 直接按下`s`或`S`将重复上一次的搜索。
此外,还可以利用以下快捷键映射配置将`f`和`t`键重新映射给Sneak插件以使用Sneak插件的单字符搜索跳转替代VIM原有的搜索功能这个配置不会影响`f`和`t`作为Motion的功能。
```
map f <Plug>Sneak_f
map F <Plug>Sneak_F
map t <Plug>Sneak_t
map T <Plug>Sneak_T
```
## Cheatsheet
{% oss_link vim-tips/vim-cheatsheet.pdf "点此下载Vim Cheatsheet" "Vim Cheatsheet by ArchGrid.xyz" %}