{(MainTitle)} 想法子记忆Vi/Vim常用操作及指令-Bottle小站 {(MainTitleEnd)} {(PostTitle)}想法子记忆Vi/Vim常用操作及指令{(PostTitleEnd)} {(PostDate)}20220423{(PostDateEnd)} {(PostContent)} > 本笔记有特殊目录,点击开启: <button onclick="Catalogue.show()" id="specialCatalogue" style="border: 0;background-color: transparent;color: #AAA;font-size: 1em;cursor: pointer;">专有目录</button> > 有暗色浏览需求的话,可以去我的<a href="https://www.cnblogs.com/somebottle/p/vi_vim_memorizing_tricks.html" target="_blank">博客园小站</a>围观 在Linux系统中编辑文本总是离不开一位老帮手——**Vi**。而因为其诞生的年代有些久远,有些操作在现在看来可能有点“反直觉”。 于是我决定写这样一篇小笔记,记录一下我**记忆**Vi的这些这些 **常用** 操作和指令的方法(**主要靠的是英语和图示了**)。 当然,正如“好记性不如烂笔头”所言,多实践才是熟练掌握技能的王道。 ![002-2022-04-02](https://assets.xbottle.top/img/002-2022-04-02.png) <bblock style="display:none;"> { "title": "小喋日和", "artist": "FantasticYouth", "src": "https://api.xbottle.top/ne/url/1937930080", "cover": "https://api.xbottle.top/ne/cover/1937930080", "float": "right" } </bblock> ## 关于Vi/Vim名词本身 * ```Vi```代表的可能是```Visual Instrument(可视工具)```,```Visual Interface(可视界面)```亦或就是```Visual(可视化的)```单词本身。 * ```Vim```倒是很明显,代表的是```Vi IMproved(Vi改进版)```。 ## 编辑器模式 对```Vi```而言: 1. 命令模式 (```Command``` mode) ,刚进入Vi编辑器时的默认模式。在此模式下能输入Vi的合法指令(通常是一些字符)来进行操作。 2. 插入(输入)模式 (```Insert``` mode) ,顾名思义,是直截了当地编辑文本的模式。 3. 末行(底线)模式 (```Last-line``` mode) ,也是接受指令的一种模式,更偏向于文件的管理。 之所以叫 “末行” 是因为指令的输入在窗口最下方: ![lastLineMode-2022-04-07](https://assets.xbottle.top/img/lastLineMode-2022-04-07.jpg) ```Vim```是```Vi```的增强版,因此```Vim```完全可以兼容```Vi```的操作,不过它的模式名可能略有差别: 1. 正常模式 (```Normal``` mode) ,是启动```Vim```后的默认模式。 2. 插入模式 (```Insert``` mode) ,这个和```Vi```的一致。 3. 命令(行)模式 (```Command-line``` mode),类似```Vi```的末行模式。 4. 可视模式 (```Visual``` mode) ,和普通模式类似,不同的是可以**高光选择文字的一部分进行命令操作**。 包括上述几种模式,Vim总共有```12```种模式(这个说法来自Wikipedia)。但**最常用**的还是上面这四种,一般情况下够用了。 ![015-2022-04-06](https://assets.xbottle.top/img/015-2022-04-06.png?233) ## 模式切换 <details open> <summary>展开阅读</summary> ------ 就```Vi```的三种模式而言,要在其间互相切换,需要借默认模式——**命令模式**作为一个中转(对于```Vim```来说就是**正常模式**)。 * **命令模式** <a id="modeSwitch-normalMode" for-anchor="true" title="命令模式"></a> 作为进入```Vi```的默认模式,我们可以把这儿当作一个小家, 无论在哪个模式下**按```Esc```**(可以记成```Escape``` [v.]逃离。逃回家了)都可以回到命令模式。(家是永远的港湾啊) ![EscGoHome-2022-04-06](https://assets.xbottle.top/img/EscGoHome-2022-04-06.png) 再提一嘴,在```Vim```里这个模式称为**正常模式**。 * **插入模式** <a id="modeSwitch-insertMode" for-anchor="true" title="插入模式"></a> 通过以下按键指令能进入插入模式。这些按键通常是**大小写成对**的,**小写**对应的指令略显 **“温和”** ,**大写**对应的就比较 **“走极端”** 了。 ![InsertModeUpperAndLower-2022-04-07](https://assets.xbottle.top/img/InsertModeUpperAndLower-2022-04-07.png) 1. ```i``` / ```I``` 插入 (记作```Insert [v]插入;嵌入;(在文章中)添加``` ) <a id="modeSwitch-insertMode-i" for-anchor="true" title="i/I 插入"></a> 小写的```i```,就是在**光标所在位置**进入插入模式: ![lowerInsert-2022-04-07](https://assets.xbottle.top/img/lowerInsert-2022-04-07.gif) 大写的```I```则有点“极端”,会**跳转到当前光标所在行的开头**进入插入模式: ![upperInsert-2022-04-07](https://assets.xbottle.top/img/upperInsert-2022-04-07.gif) 2. ```a``` / ```A``` 附加 (记作```Append [v]增补,追加```) <a id="modeSwitch-insertMode-a" for-anchor="true" title="a/A 附加"></a> 小写的```a```,就是在**光标所在位置之后一位**进入插入模式,即所谓的“附加”: ![lowerAppend-2022-04-07](https://assets.xbottle.top/img/lowerAppend-2022-04-07.gif) 显而易见,大写的```A```就会**跳转到当前光标所在行的末尾**进入插入模式(在尾部附加): ![upperAppend-2022-04-07](https://assets.xbottle.top/img/upperAppend-2022-04-07.gif) 3. ```o``` / ```O``` 新增一行 (暂且记作```Open``` new line) <a id="modeSwitch-insertMode-o" for-anchor="true" title="o/O 新增一行"></a> 都是新增行,差别无非是往下新增还是往上新增了。 小写的```o```,就是在光标所在行的**之后新建一行**,然后进入插入模式: ![newLineAfter-2022-04-07](https://assets.xbottle.top/img/newLineAfter-2022-04-07.gif) 大写的```O```呢,就比较蛮横,会把光标所在行及其以下的行整体往下顶,在原位置新建一行(相对来说就是**在光标所在行上面新建一行**),然后进入插入模式: ![newLineBefore-2022-04-07](https://assets.xbottle.top/img/newLineBefore-2022-04-07.gif) 4. ```s``` / ```S``` 剪切(取代) (可以直接记删的拼音```Shan```,也可以记成```Substitute [v]取代```) <a id="modeSwitch-insertMode-s" for-anchor="true" title="s/S 剪切(取代)"></a> 小写的```s```,会**剪切光标所在位置右边的字符**,然后进入插入模式: ![subWord-2022-04-07](https://assets.xbottle.top/img/subWord-2022-04-07.gif) 大写的```S```就很暴躁了,会**剪切光标所在行**,然后进入插入模式: ![subLine-2022-04-07](https://assets.xbottle.top/img/subLine-2022-04-07.gif) > 记```Substitute```的话,可以理解为这个操作是剪切原有`字符/行`后进行插入,**取代**原有的`字符/行`。 💡 `S` 和 `cc` 指令的功能是相同的!这个在下面的[**“剪切一行”**](#normalMode-cut-singleLine)这一节中也提到了。 上面这些按键指令都是在```命令模式/正常模式```下输入的,通过**在指令前附加数字**,能重复这个指令的操作: <a id="modeSwitch-insertMode-repeat" for-anchor="true" title="切换指令的重复"></a> * ```3s``` -> 删除光标后```3```个字符 ![repeatSubstitute-2022-04-07](https://assets.xbottle.top/img/repeatSubstitute-2022-04-07.gif) * ```4a``` -> 在光标下一位附加内容,**操作结束后**(Esc)把这个内容重复```4```次 ![appendFourTimes-2022-04-07](https://assets.xbottle.top/img/appendFourTimes-2022-04-07.gif) * **替换模式** <a id="modeSwitch-replaceMode" for-anchor="true" title="替换模式"></a> 这个模式和插入模式一样是用于编辑文本的,不同的是替换模式下**输入所至之处会覆盖一切。** 记作 ```Replace [v]取代;代替。``` 输入大写```R```进入替换模式: ![replaceMode-2022-04-07](https://assets.xbottle.top/img/replaceMode-2022-04-07.gif) 有了大写R,那么小写```r```呢?小写r的功能是**将光标后面的字符替换为指定字符**。比如我们先输入```r```,再输入```a```: ![replaceChr-2022-04-07](https://assets.xbottle.top/img/replaceChr-2022-04-07.gif) 当然,这些操作一样是可以重复进行的: ![repeatReplace-2022-04-07](https://assets.xbottle.top/img/repeatReplace-2022-04-07.gif) (指令分别是`3rp`,`3R`->`hello`->`Esc`) * **末行模式(Vim中的命令行模式)** <a id="modeSwitch-commandLineMode" for-anchor="true" title="末行模式/命令行模式"></a> 输入 ```Shift```+```:``` 进入末行模式(事实上我们一般都是这样打出冒号来的) 末行模式在```Vim```中被称为**命令(行)模式**。 * **可视模式** <a id="modeSwitch-visualMode" for-anchor="true" title="可视模式"></a> 可视模式是```Vim```中新引入的模式,支持在高光选择下进行指令操作。 可视模式即 ```Visual Mode```,进入可视模式的按键则是```v``` / ```V```, 其中 ```v``` 键能普通地进入可视模式: ![visualNormal-2022-04-07](https://assets.xbottle.top/img/visualNormal-2022-04-07.gif) 而 ```V``` 在进入可视模式后**会保持高光选择到光标所在行**(**可视行**模式): ![visualLine-2022-04-07](https://assets.xbottle.top/img/visualLine-2022-04-07.gif) 还有一种便是使用 ```Ctrl+V/v``` 进入可视模式了,这种模式叫**可视块**模式,如字面所言,是选择“一块文字”: ![visuallySelectBlock-2022-04-23](https://assets.xbottle.top/img/visuallySelectBlock-2022-04-23.gif) </details> ## 命令模式/正常模式 这是进入```Vi/Vim```后所处的默认模式,和文本处理是靠的非常近的,我决定先记一下这部分。 <a id="normalMode-modeSwitch" for-anchor="true" title="模式切换"></a> 1. [**模式切换**](#模式切换) 2. **基本光标移动(上下左右)** <a id="normalMode-arrowMovement" for-anchor="true" title="基本光标移动(上下左右)"></a> 在交互式字符界面下没有能动的鼠标,只有闪烁的光标来标注位置。要编辑文本,真得先掌握移动光标的指令。 **PS:** 下方涉及的光标**上下**移动都是**针对换行符`\n`分隔的行**的,光标会从一行移动到上方或下方的行: <details> <summary>展开查看示例</summary> ------- ![jkLine-2022-04-08](https://assets.xbottle.top/img/jkLine-2022-04-08.gif) 👆 ```j```、```k```、```↓```、```↑```将光标从一行的指定位置移动到另一行的**指定位置**。 ![enterDash-re-2022-04-08](https://assets.xbottle.top/img/enterDash-re-2022-04-08.gif) 👆 ```Enter```、```-```,将光标移动到下一行或上一行的**行首**! </details> * **“经典”款** <a id="normalMode-arrowMovement-classical" for-anchor="true" title="h l k j移动"></a> ```h``` 向左移动光标 ```l``` 向右移动光标 ```k``` 向上移动光标 ```j``` 向下移动光标 之所以这样设计,因为在早期**终端机**上,键盘是这个样的: ![hjklOnTerminal-2022-04-08](https://assets.xbottle.top/img/hjklOnTerminal-2022-04-08.jpg) **记忆方法**:现代键盘上```↓```、```↑```键在中间,而```←```、```→```键在两侧,而HJKL也正是两侧的```K```、```L```代表横向移动,中间的```J```、```K```代表纵向移动(HJKL四个键连在一起的设计一直保留到了今天)。 ![hjkl-remarked-2022-04-08](https://assets.xbottle.top/img/hjkl-remarked-2022-04-08.gif) * **现代款** <a id="normalMode-arrowMovement-modern" for-anchor="true" title="↑ ↓ ← →移动"></a> ```←``` 向左移动光标 ```→``` 向右移动光标 ```↑``` 向上移动光标 ```↓``` 向下移动光标 * **“隐藏”款** <a id="normalMode-arrowMovement-others" for-anchor="true" title="其他款式"></a> ```Backspace(退格键)``` 向左移动光标 ```Space(空格键)``` 向右移动光标 > **👆记忆方法**:在插入模式下,`Backspace`和`Space`是分别用于删去字符和增加空格的,与之对应的光标分别是**往左移动**和**往右移动**的。 ------- ```Enter``` 把光标移动到**下一行行首** ```+``` 把光标移动到**下一行行首** ```-``` 把光标移动到**上一行行首** > **👆记忆方法**:插入模式下 ```Enter``` 起到了换行作用,换行后光标会处于行首。 > ```-``` 就记成“减去一行”(老实说我也不太知道这个怎么记了,JK其实也够用了), > ```+``` 和 ```-``` 记在一起就行。 ------- **PS:** 光标移动是在```命令模式/正常模式```下的指令,当然是支持重复的啦! 在上面所有指令的前面加上重复的次数(数字)即可,比如我要往下翻`233`行: ```233j```或```233↓```。 3. **行内光标移动** <a id="normalMode-moveInsideRow" for-anchor="true" title="行内光标移动"></a> 这里记录一下在**一行**文本中光标的移动指令: * **跳到行首** <a id="normalMode-moveInsideRow-jumpAheadOfLine" for-anchor="true" title="跳到行首"></a> ```0``` 回到行首,`列数`=`1`的地方 ```Home``` 同上👆 ```^``` 回到行首,**第一个不为空白符**的字符处(对于加了缩进的行可谓是非常有用) > **👆记忆方法**:`0`记为“归零”; > `^`的话,**正则表达式**里是用于匹配字符串首部的,可以一起记,另外其还形如一个顶部,可以记成一行中的“顶头”部分。 * **跳到行尾** <a id="normalMode-moveInsideRow-jumpEndOfLine" for-anchor="true" title="跳到行末"></a> ```$``` 回到行尾**最后一个字符**的地方 ```End``` 同上👆 ```g_``` 回到行尾**最后一个不为空白符**的字符处 > **👆记忆方法**:`$`在正则表达式里是用于匹配字符串尾部的,可以和正则表达式一起记; > `g_`可以记成`go _`,`_`形如一个输入光标,这样记成跳转到行尾最后一个非空白字符。 4. **屏幕可见行中移动光标** <a id="normalMode-moveInScreenLine" for-anchor="true" title="屏幕可见行中移动光标"></a> 上面所说的“行”取决于**换行符`\n`**的位置,但这里的**屏幕可见行**是取决于窗口大小的: ![screenLine-2022-04-10](https://assets.xbottle.top/img/screenLine-2022-04-10.jpg) (图中标注的即为**一个屏幕可见行**) <a id="normalMode-moveInScreenLine-g" for-anchor="true" title="g^ g0 gm g$ 等等"></a> 针对这样的一个行,也是有指令可用于行内移动的: * ```g^``` 跳转到行首第一个不是空白符的字符处 > **👆记忆方法**:`g^`可以记成`go ^`,去往当前的顶头部分。 * ```g0``` 跳转到行首字符处 * ```gm``` 跳转到**行中间**的字符处 > **👆记忆方法**:`gm`可以记成`go midst`,去往当前的中间部分。 * ```g$``` 跳转到行尾字符处 完全是可以结合前面的`0`,`^`,`$`一起记的。当然,`g + Home`,`g + End`也是可以的。 ----- 再展开想想,既然`g`开头的指令**在这里看的是屏幕可见行**,我是不是可以用`gj`(或`g↓`)指令从一个屏幕中的行移动到另一个行呢? ![JLOperation-2022-04-10](https://assets.xbottle.top/img/JLOperation-2022-04-10.gif) 👆这个主要是用`j`(`↓`)指令进行的光标移动 ![gjOperation-2022-04-10](https://assets.xbottle.top/img/gjOperation-2022-04-10.gif) 👆而这个主要是用`gj`(`g↓`)指令进行的光标移动。 > 利用`g`+`移动指令`,可以实现在屏幕中的行之间移动。当然,`3gj`这种移动多行的指令也是支持的哦~ ------- <a id="normalMode-moveInScreenLine-HML" for-anchor="true" title="H M L 指令"></a> 针对屏幕可见行,还有几个指令: * 大写 `H` 用于将光标移动到**屏幕可见**的**最上面一行**(这个判定有点怪,可能不太准确!) > **👆记忆方法**:`H`可以记成`Head`,去往可见区的头部。 * 大写 `M` 用于将光标移动到**屏幕可见**的**最中间行**(同样判定很怪) > **👆记忆方法**:`M`可以记成`Middle`,去往可见区的中间部分。 * 大写 `L` 用于将光标移动到**屏幕可见**的**最下面一行** > **👆记忆方法**:`L`可以记成`Last`,去往可见区的最后一行。 5. **文件内光标移动** <a id="normalMode-moveInFile" for-anchor="true" title="文件内光标移动"></a> 除了上述几种移动外,还有几个常用指令能按词、按句、按段等移动光标: * **按词移动** <a id="normalMode-moveInFile-moveByWord" for-anchor="true" title="按词移动w,b"></a> `w` 跳转到**下一个词**的开头部分(**考虑标点符号**),演示如下👇 ![wordNext-2022-04-10](https://assets.xbottle.top/img/wordNext-2022-04-10.gif) `W` 跳转到**下一个词**的开头部分(只考虑**空格分隔**) ,演示如下👇 ![wordNext-CAPS-2022-04-10](https://assets.xbottle.top/img/wordNext-CAPS-2022-04-10.gif) `b` 跳转到**上一个词**的开头部分(**考虑标点符号**)。 `B` 跳转到**上一个词**的开头部分(只考虑**空格分隔**)。 这里的`b`、`B`和`w`、`W`的操作就是**反着来的**。 > **👆记忆方法**:`w`可以记成`word next`,下一个单词; > `b`则记成`behind word`,后一个单词。 > `W`和`B`则比较“激进”,不会考虑标点符号。 ------ <a id="normalMode-moveInFile-endOfWord" for-anchor="true" title="移动到词尾e"></a> `e` 跳转到当前**光标所在词**的**词尾**,如果光标在词尾,就跳转到**下一个词的词尾** (**考虑标点符号**)。 `E` 同上👆,但是**只考虑空格分隔**。 > **👆记忆方法**:`e`可以记成`end of word`,词尾。 * **按句移动** <a id="normalMode-moveInFile-moveAheadSentence" for-anchor="true" title="移动到句首( )"></a> `(` 移动到**光标所在句子的开头部分**,如果已经在开头,就移动到**上一个句子**的开头部分 `)` 移动到**下一个句子**的开头部分 > **👆记忆方法**:写代码的时候会用半角括号`()`把表达式括起来,可以理解为“一句”。 * **按段落移动** <a id="normalMode-moveInFile-moveAheadParagraph" for-anchor="true" title="移动到段首{ }"></a> `{` 移动到**光标所在段落的开头部分**,如果已经在开头,就移动到**上一个段落**的开头部分 `}` 移动到**下一个段落**的开头部分 > **👆记忆方法**:写代码的时候会用半角大括号`{}`把代码块括起来,也就成为了一“段”代码,可以理解为一个段落。 值得注意的是,上面这些按词、句、段落移动光标的操作,主要是**针对英文句式**的。如果操作对象是中文的话,可能并不能如愿。 (最明显的就是按词移动 `W`,中文里一般是不会用空格分隔每个词的,所以会直接跳转到一段的末尾!) -------- * **在整个文件中跳转光标** <a id="normalMode-moveInFile-arrowJump" for-anchor="true" title="跳转光标gg,G"></a> ⚠️ 下面的指令全部都会使光标**跳转到行首第一个不为空白符的字符**处。 `gg` 跳转到文件**最开头的一行**,相当于`1G`。 `G` 跳转到文件**最末尾的一行**。 `[row]G` 跳转到文件**第[row]行**(row代表数字)。 ![GJump-2022-04-10](https://assets.xbottle.top/img/GJump-2022-04-10.gif) 👆 分别演示了 `gg`,`G`,`2G` 的操作。 > 记忆方法:上面三个指令本质都和`G`有关,`G`可以直接记忆为`Goto 转到,跳转`。 ![nGJump-2022-04-10](https://assets.xbottle.top/img/nGJump-2022-04-10.webp) 6. **自定义跳转标记** <a id="normalMode-makeMarks" for-anchor="true" title="自定义跳转标记"></a> 在这一坨文本中进行移动时,我经常要使用指令反复横跳。有一个地方我**经常跳转过去**,如果一步步来实在很麻烦!此时就可以用**自定义跳转标记**来解决。 * **设置标记** <a id="normalMode-makeMarks-set" for-anchor="true" title="设置标记m"></a> 指令非常简单: ```ma``` 这样就设置了一个名为`a`的标记! 经过测试发现 ```m``` 后面可以接 `a-zA-Z0-9` 之中的任意字符! > 也就是说`m1`, `mA`, `mO`, `mz`这些都是合法的指令! > 👆记忆方法:`m` 可以直接记忆为 `Mark [v]标示,标记`。 * **跳转到标记处** <a id="normalMode-makeMarks-goto" for-anchor="true" title="跳转到标记处`"></a> ``` `a ``` 这样就能跳转到`a`标记处~ 标记名是**大小写敏感的**。 > 💡 可以和下面的`y`, `c`, `d`指令联合使用。 * **跳转到标记所在行的行首** <a id="normalMode-makeMarks-goAheadLine" for-anchor="true" title="跳转到标记所在行的行首'"></a> ``` 'a ``` 使用单引号 `'` 加标记名能跳转到**该标记所在行**的行首**第一个不为空白符的**字符处! ![jump2markedLine-2022-04-12](https://assets.xbottle.top/img/jump2markedLine-2022-04-12.gif) > 这样是不是就相当于标记了一个行的行首呢? * **删除标记** <a id="normalMode-makeMarks-del" for-anchor="true" title="删除标记delm"></a> ma可以设置名为`a`的标记,删除的话其实在前面加个del即可: ```delm a``` 或 ```delmarks a``` ( `Delete Marks` ) 同时支持**删除多个标记**。比如我要删除`a`,`b`,`c`三个标记,可以写成: ```delm abc``` 或 ```delmarks a b c``` 也就是说删除标记的时候**标记名之间有没有间隔是无所谓的**。 > 💡 在进行了[**缩进操作**](#indenting)后,标记的位置仍然在文本的相应位置,无需担心。 `末行模式/命令行模式`下可以**查看标记信息**:[:marks](#commandLineMode-viewMarks) 7. **翻页** <a id="normalMode-scrollPage" for-anchor="true" title="翻页"></a> * **后空翻** <a id="normalMode-scrollPage-back" for-anchor="true" title="往回翻"></a> ```Ctrl + b``` 往回翻**一整页** ```PageUp``` 往回翻**一整页** ```Ctrl + u``` 往回翻**半页** > **👆记忆方法**:`b` 代表 `Backward`,也就是往回; > `u`则代表`Up`,也就是往上(在界面上往回翻就如同往上翻); > `PageUp`不多赘述,在现在的大多系统中都是支持的。 * **前空翻** <a id="normalMode-scrollPage-forward" for-anchor="true" title="往前翻"></a> ```Ctrl + f``` 往前翻**一整页** ```PageDown``` 往前翻**一整页** ```Ctrl + d``` 往前翻**半页** > **👆记忆方法**:`f` 代表 `Forward`,也就是往前; > `d`则代表`Down`,也就是往下(在界面上往前翻就如同往下翻)。 `Ctrl`键代表`Control`,在这里可以理解为**控制**页面滚动。 **PS:** 再记忆一下,`命令模式/正常模式`下的指令是可以重复的。在这里如果我要向前翻`3`整页:`3PageDown`或`3Ctrl+f`。 8. **复制** <a id="normalMode-yank" for-anchor="true" title="复制y"></a> 复制是文本的经典便捷操作之一。 * **复制一行** <a id="normalMode-yank-singleLine" for-anchor="true" title="复制一行"></a> `yy` 复制光标所在行(到寄存器中) `Y` 同上 > **👆记忆方法**:`yy` 中的 `y`代表`yank [v]猛拉;猛拽`,复制其实就是把对应的文本给“拽走”了。 * **多行复制** <a id="normalMode-yank-multiLines" for-anchor="true" title="复制多行"></a> `2yy` 复制**光标所在行**以及**下一行**,共`2`行 `2Y` 同上 `10yy` 复制**光标所在行**以及**下面9行**,共`10`行 > 本质上其实就是`命令模式/正常模式`下的指令重复(在指令前面加数字前缀)。 * **打一套复制组合拳!(选区复制)** <a id="normalMode-yank-withMove" for-anchor="true" title="光标移动复制"></a> `y`其实可以和很多**光标移动指令**结合使用!利用好这套“组合拳”,效率倍增有木有! ![yankBoxing-2022-04-10](https://assets.xbottle.top/img/yankBoxing-2022-04-10.png) 比如 `ye` 就能复制光标所在位置到[**单词尾部**](#normalMode-moveInFile-endOfWord)的部分, 而 `y^` 能复制光标所在位置[**到行首**](#normalMode-moveInsideRow-jumpAheadOfLine)第一个非空白符的单词部分, 再者 `y↓` 或 `yj` 能实现同`2yy`一样的功能:**复制光标所在行**以及[**下一行**](#normalMode-arrowMovement), 甚至 `y(` 都是可以的,复制**光标所在位置**到[**光标所在句开头**](#normalMode-moveInFile-moveAheadSentence)。 > 也就是说 `y{`, `y}`, `yk` ,`yl` ,`y$` ,`yg^`, ```y`a``` 等等其实都是可以的! > 另外还可以复制多行,比如`3ygj`。 ------ <a id="normalMode-yank-summary" for-anchor="true" title="光标移动复制总结"></a> 🤩 **总结一下**就是: `y` 可以和很多**光标移动指令**结合使用达成**部分复制**的效果, 通常以**光标所在位置**为起点,直到**光标移动指令执行后的光标位置**为止作为**选区**进行复制。 ![yankGoTest-2022-04-11](https://assets.xbottle.top/img/yankGoTest-2022-04-11.gif) 👆示例`ygj`的效果。光标所在位置是`语`字前面,执行`gj`后光标位于`草`字前面,所以复制选区就是: `语人曰:“此当有大手笔事。”俄而帝崩,哀册说议皆淘所`。 9. **剪切** <a id="normalMode-cut" for-anchor="true" title="剪切d,c,x..."></a> 有了复制,就不得不提到**复制并移除**——剪切了。通过这些指令移除的文本会被存进寄存器,可用于**粘贴**。 * **剪切一行** <a id="normalMode-cut-singleLine" for-anchor="true" title="剪切一行"></a> `dd` 剪切**光标所在行** `cc` 剪切**光标所在行**,并且**进入插入模式** `S` 同上👆,这个其实在[**模式切换**](#modeSwitch-insertMode-s)这一节里面写了 > **👆记忆方法**:`dd` 中的 `d`记为`delete [v]删除`; > `cc`中的`c`记为`change[v]改变`,亦可记成`Chi 吃`; > `S`可以记成`Shift [v]转移`,剪切就像是在转移文本的位置,亦可记成`Shan 删`。 * **剪切多行** <a id="normalMode-cut-multiLines" for-anchor="true" title="剪切多行"></a> 这一部分和复制多行真的相差无几,可以说会用[**多行复制**](#normalMode-yank-multiLines)了,肯定是会剪切多行的。 `2dd` 剪切**光标所在行**和**下面一行**,共`2`行 `6dd` 剪切**光标所在行**及**下面5行**,共`6`行 `3cc` 剪切光标所在行及下面2行,并进入**插入模式** `3S` 同上👆 * **剪切一个字符** <a id="normalMode-cut-chr" for-anchor="true" title="剪切一个字符x"></a> `x` 剪切**光标右边的一个字符** (相当于`dl`或`d→`) `s` 剪切**光标右边的一个字符**,并且**进入插入模式** > **👆记忆方法**:`x` 中的 `x`记为`画个叉叉删掉字符`; > `s`则可以记为`shan 删`。 * **行内剪切** <a id="normalMode-cut-inLine" for-anchor="true" title="行内剪切D,C"></a> 和复制不同的是,大写 `D` 和 `C` 的作用**并不等同于** `dd` 和 `cc`! `D` 剪切从**光标所在位置**到**光标所在行的行末**部分 `C` 完成上述操作👆,并且进入**插入模式** > `C`相对`D`仍然是多出了个**进入插入模式**的功能 * **组合剪切(选区剪切)** <a id="normalMode-cut-withMove" for-anchor="true" title="光标移动剪切"></a> 复制`y`能打组合拳,我堂堂剪切为什么不行! 确实,剪切 `d` 或 `c` 都是可以和**光标操作指令**进行结合的, 他俩不同的地方在于 `d` 只是普通的剪切,`c` 还附赠了[**切换到插入模式**](#modeSwitch-insertMode-s)服务! ![arrogantCandD-2022-04-11](https://assets.xbottle.top/img/arrogantCandD-2022-04-11.png) `dj` 或 `d↓` 的效果就等同于`2dd` `cj` 或 `c↓` 的效果就等同于`2cc`,会进入插入模式 `5x` 能剪切光标右边的5个字符 `5s` 能剪切光标右边5个字符且进入插入模式 `3cgj` 能剪切从**当前光标**到`3`个**屏幕可见行**后的相应位置部分 `dw` 剪切从**当前光标位置**到**下一个单词前**的部分 `cw` 在进行上面的操作👆后,会进入**插入模式** `d^` 剪切从**当前光标位置**到**本行头部一个不是空白符的字符**的部分 `c^` 在进行上面的操作👆后,会进入**插入模式** ```d`a``` 剪切从**当前光标**位置到`a`[**标记位置**](#normalMode-makeMarks)的部分 ![3dgj-2022-04-11](https://assets.xbottle.top/img/3dgj-2022-04-11.gif) 👆演示了一下 `3dgj` 。从光标所在位置 `语` **开始**,到执行了`3gj`之后的光标位置**为止**作为选区,进行剪切。也就是这一部分: ``` 语人曰:“此当有大手 笔事。”俄而帝崩,哀册说议皆淘所草。释义用来赞誉写作才 能极高,并用来称颂着名的作家和作品、故事东晋的文士王玖 从小才思敏捷,胆量很大,散文和诗 ``` ------ <a id="normalMode-cut-summary" for-anchor="true" title="光标移动剪切总结"></a> 🤩 **总结一下**就是: `d` / `c` 可以和很多**光标移动指令**结合使用达成**部分剪切**的效果, 通常以**光标所在位置**为起点,直到**光标移动指令执行后的光标位置**为止作为**选区**进行剪切。 > 💡 应该和**复制**结合记忆! 10. **粘贴** <a id="normalMode-paste" for-anchor="true" title="粘贴p"></a> Copy和Cut都记了,接下来咱要想办法粘贴(Paste)内容咯~ 粘贴的指令其实非常简单!( ´・ω・)ノ * `p` (向下一行/向右)粘贴文本一次(记为 `Paste [v]粘贴`) * 大写 `P` (向上一行/向左)粘贴文本一次(记为 `Upper(大写,同时也有“上边”的意思) Paste`) ------- <a id="normalMode-paste-lines" for-anchor="true" title="粘贴行文本"></a> 当**寄存器**中的内容是**一行或多行**文本(开头有换行符`\n`)时: * `p`会把文本粘贴到当前**光标所在行**的**下一行**, * 大写 `P` 会把文本粘贴到当前**光标所在行**的**上一行**! * 👇举例:先利用`yy`复制**一行**文本,然后分别用 `p` 和 `P` 进行粘贴 ![pasteAndUpperPaste-2022-04-11](https://assets.xbottle.top/img/pasteAndUpperPaste-2022-04-11.gif) > 很明显能看到使用`p`时,该行文本会被粘贴到`line2`这一行的下方; > 而使用`P`时,该行文本会被粘贴到`line2`这一行的上方。 ------- <a id="normalMode-paste-lessThanLine" for-anchor="true" title="粘贴不足一行文本"></a> 当**寄存器**中的内容是**不足一行的文本**(**开头没有**换行符`\n`)时: * `p`会把文本粘贴到当前**光标所指字符**的**右边**, * 大写 `P` 会把文本粘贴到当前**光标所指字符**的**左边**! * 👇举例:先执行`ygj`复制**开头没有换行符**的一段文本,然后分别用 `p` 和 `P` 进行粘贴 ![ygjPasteAndUpperPaste-2022-04-11](https://assets.xbottle.top/img/ygjPasteAndUpperPaste-2022-04-11.gif) > 粘贴的时候**光标指向的字符**是`n`。使用`p`粘贴时,内容粘贴在`n`的右方;而使用`P`粘贴时,内容粘贴在`n`的左方。 ------- 🌟 `命令模式/正常模式`下指令可加数字前缀重复执行,这里也是一样的。比如 `4p` 就是把寄存器的文本在光标右边/下一行**粘贴四次**。 11. **Vim上下文编辑** <a id="normalMode-contextEdit" for-anchor="true" title="Vim上下文编辑"></a> Vim在复制`y`,剪切`c`、`d`这些操作的基础上复用了`i`和`a`指令用于对上下文进行操作: > 👆记忆方法:`i` 记作 `inner 内部的`,在下面使用这个指令,操作**不会考虑周围的空格或符号**; > `a` 记作 `around 周围`,这个则**会考虑周围的空格或符号**。具体视情况而定。 * **针对单词**(`w` / `W`) <a id="normalMode-contextEdit-forWord" for-anchor="true" title="针对单词diw,daw..."></a> > ⭐ 记作`Word` `diw` 剪切**光标所在的单词**(不考虑标点符号) `diW` 剪切**光标所在的单词**(考虑周围的标点符号,也就是说剪切的时候会把周围的标点符号一并算进去) > `i` (inner) 在这里的意思是操作的部分**不包括单词周围的空格** `daw` 和 `daW` 也是剪切**光标所在的单词**(`w`和`W`区别不再多赘述) > `a` (around) 在这里的意思是操作的部分**包括单词周围的空格** ※ 下面演示一下`diw`和`daw`的区别: ![diwAnddaw-2022-04-11](https://assets.xbottle.top/img/diwAnddaw-2022-04-11.gif) 很明显能看到,使用`diw`时,剪切后遗留的文本是 `of ,`(单词左边的空格保留了); 使用`daw`时,剪切后遗留的文本是 `of,`(单词左边的空格被一同剪切了) 。 > 💡 像 `y`, `c`, `d` 一类指令都是可以这样用的,比如`yaw`, `yiW`, `ciw`, `caW` 等等... ------- * **针对句子**(`s`) <a id="normalMode-contextEdit-forSentence" for-anchor="true" title="针对句子yis,das..."></a> > ⭐ 记作`Sentence`。注意,这里**没有大写** `S` 的用法! 其上下文操作和上面单词的是一样的!比如: `yis` 复制**光标所在的句子**(`i`不考虑周围的空格) `yas` 复制**光标所在的句子**(`a`考虑周围的空格) `cas` 剪切**光标所在的句子**,并进入插入模式 `dis` 剪切**光标所在的句子** ...and so on > 💡 注意,这里只有小写`s`的用法。 ------- * **针对段落**(`p`) <a id="normalMode-contextEdit-forParagraph" for-anchor="true" title="针对段落yip,dap..."></a> > ⭐ 记作`Paragraph`。注意,这里**没有大写** `P` 的用法! 这里的用法也和上面是一样的: `cap` 剪切**光标所在的段落**,并进入插入模式(`a`考虑周围的空格) `dip` 剪切**光标所在的段落**(`i`不考虑周围的空格) `yip` 复制**光标所在的段落** ...and so on > 💡 这里同样只有小写`p`的用法。 ------- * **针对单/双引号**( `'` / `"` ) <a id="normalMode-contextEdit-forQuotes" for-anchor="true" title="针对单/双引号"></a> 很明显,这一部分操作是用来处理单/双引号**引起来的文本**的,和上面的`w`、`W`、`s`、`p`不同的是这里的 `i` / `a` 的含义: > `i` (inner) 在这里的意思是操作的部分**不包括**两侧的引号 > `a` (around) 在这里的意思是操作的部分**包括**两侧的引号 `di"` 剪切光标所在的**双引号**引起来的**同行**文本(`i`代表不包括两侧的引号) 👇 ![diQuoted-2022-04-12](https://assets.xbottle.top/img/diQuoted-2022-04-12.gif) `da'` 剪切光标所在的**单引号**引起来的**同行**文本(`a`代表包括两侧的引号) 👇 ![daQuoted-2022-04-12](https://assets.xbottle.top/img/daQuoted-2022-04-12.gif) > 💡 `y` 和 `c` 也是一样的用法 ------- * **针对括号** ( `( )`, `< >`, `[ ]`, `{ }` ) <a id="normalMode-contextEdit-forBracket" for-anchor="true" title="针对括号"></a> 这一部分用来处理**括号括起来的文本**。 > 📌 注意,**这里的操作是支持多行的!!** > `i` (inner) 在这里的意思是操作的部分**不包括**两侧的括号 > `a` (around) 在这里的意思是操作的部分**包括**两侧的括号 `di(` 或 `di)` 剪切光标所在的**小括号`( )`括起来的**文本(`i`代表不包括两侧的括号) 👇 ![diBracket-2022-04-12](https://assets.xbottle.top/img/diBracket-2022-04-12.gif) `da{` 或 `da}` 剪切光标所在的**大括号`{ }`括起来的**文本(`a`代表包括两侧的括号) 👇 ![daBracket-2022-04-12](https://assets.xbottle.top/img/daBracket-2022-04-12.gif) > 💡 `y` 和 `c` 也是一样的用法,适用于多种括号。 ![quoteAndBracketComic-2022-04-12](https://assets.xbottle.top/img/quoteAndBracketComic-2022-04-12.png) 12. **双引号指定寄存器** <a id="normalMode-specifyRegs" for-anchor="true" title="双引号指定寄存器"></a> 默认情况下使用复制粘贴似乎用的都是一个公共的寄存器(Registers),Vim实际上是支持寄存多条文本的,**只需要在指令前指定寄存器名**即可。 * **默认的寄存器**被系统记为 `"` ,一个双引号 <a id="normalMode-specifyRegs-default" for-anchor="true" title="默认寄存器"></a> 当执行`yy`时,会将目标文本行存入 `"` 寄存器,操作等同于: `""yy` > 💡 其中第一个单引号代表**引用寄存器**,第二个单引号便是**默认寄存器的名字**。 于是粘贴指令也可以改写成:`""p`,即从默认寄存器中取出文本进行粘贴。 ------ * **指定寄存器名字** <a id="normalMode-specifyRegs-setName" for-anchor="true" title="命名寄存器"></a> 把上述第二个单引号改成 `a-z` 的**小写字母**即可!这样就相当于得到了`26`个“剪贴板”呢~ 比如将当前行复制到`a`寄存器中: ```"ayy``` 那么粘贴该内容的指令也显而易见了: ```"ap``` -------- * **往已命名寄存器中附加内容** <a id="normalMode-specifyRegs-appendByName" for-anchor="true" title="往命名寄存器中附加内容"></a> 上面写到用**小写字母**对寄存器进行命名,那么**大写字母**呢? 大写字母用于往寄存器中**附加内容**: ```"Ay$``` 将光标位置到行尾的文本附加到`a`寄存器中 👇 ![AppendToRegisterA-2022-04-12](https://assets.xbottle.top/img/AppendToRegisterA-2022-04-12.gif) 这里主要记录一下复制粘贴方面的寄存器引用方法。Vim寄存器储存的内容远不止这么一点,还包括操作历史记录等信息,就不多赘述了。 > 👆记忆方法:`"` 就像个图钉📌,标明是哪一个寄存器。 `末行模式/命令行模式`下可以**查看寄存器信息**:[:reg\[isters\]](#commandLineMode-viewRegs) 13. **缩进** <a id="normalMode-indenting" for-anchor="true" title="缩进"></a> Vi/Vim 常用于编辑代码,缩进功能当然也是比较常用的了。 * **增加缩进** <a id="normalMode-indenting-add" for-anchor="true" title="增加缩进"></a> `>>` 给**光标所在行**增加缩进 `4>>` 给**光标所在行**以及下面`3`行增加缩进,共`4`行 * **减少缩进** <a id="normalMode-indenting-rmv" for-anchor="true" title="减少缩进"></a> `<<` 给**光标所在行**减少缩进 `5<<` 给**光标所在行**以及下面`4`行减少缩进,共`5`行 > 👆记忆方法:`>` 可以看作在往右边“顶”文本,而 `<` 可以看作在往左边“拉”文本。 14. **撤销与重做** <a id="normalMode-undoAndRedo" for-anchor="true" title="撤销与重做"></a> 文本编辑必不可少的便是撤销与重做功能了! * **撤销** <a id="normalMode-undoAndRedo-undo" for-anchor="true" title="撤销"></a> `u` 撤销**上一步操作** 大写 `U` 撤销**光标所在行**的**所有操作** > 👆记忆方法:`u`直接记成 `undo [v]撤销,取消` 即可。 > 一定要辨别小写`u`和大写`U`的操作区别! * **重做** <a id="normalMode-undoAndRedo-redo" for-anchor="true" title="重做"></a> `Ctrl + r` 或者 `Ctrl + R` 重做操作和小写`u`的操作是反向的!重新执行撤销掉的操作。 > 👆记忆方法:`r`直接记成 `redo [v]重做` 即可。 > 为什么多了个`Ctrl`呢?因为`r`已经被**替换模式**占用了! 15. **数字快捷增减** <a id="normalMode-numAscDesc" for-anchor="true" title="数字快捷增减"></a> 要修改一个数值的时候可以进入插入模式,移动光标到数值上进行修改,这样可能略嫌麻烦了。在`命令模式/普通模式`下提供了对**数值**进行**递增**或**递减**的操作指令: * `Ctrl + a` (`Ctrl + A`亦可) <a id="normalMode-numAscDesc-ascend" for-anchor="true" title="数字递增Ctrl+A/a"></a> 对光标指针所在数值进行**递增操作**。如果光标没有指向数值,会**向右查找**数值进行递增。 > 💡 记忆方法: `a` 可以记成 `ascend [v]上升;升高;递增`, > 亦可以记成 `accumulate [v]积累;逐渐增加`。 > 之所以有 `Ctrl` ,是因为`a`已经被用于切换**插入模式**了! ![ascendingNum-2022-04-12](https://assets.xbottle.top/img/ascendingNum-2022-04-12.gif) 👆 示例中可以看到,当光标右方没有数值时会屏幕闪烁提示指令无效。 ------- * `Ctrl + x` (`Ctrl + X`亦可) <a id="normalMode-numAscDesc-descend" for-anchor="true" title="数字递减Ctrl+X/x"></a> 对光标指针所在数值进行**递减操作**。如果光标没有指向数值,同样会**向右查找**数值进行递减。 > 💡 记忆方法: `x` 可以记成 `叉掉数字,从而让数值减小`。 > 之所以有`Ctrl`也是因为`x`已经被用于**剪切字符**。 ------- * **重复指令** <a id="normalMode-numAscDesc-repeat" for-anchor="true" title="重复递增/递减操作"></a> 设想一下,如果我要增加10亿,难不成要一直按住按键不动?当然不是这样啦! 再记忆一遍,`命令模式/普通模式`下的指令可以加**数字前缀**进行重复,在这里只需要这样输入指令即可快速递增指定数值: ```1000000000Ctrl + a``` ![ascendBillionTimes-2022-04-12](https://assets.xbottle.top/img/ascendBillionTimes-2022-04-12.gif) ```Ctrl + x``` 同理! ![rocketAscending-2022-04-12](https://assets.xbottle.top/img/rocketAscending-2022-04-12.png) * **进制的表示方法** <a id="normalMode-numAscDesc-type" for-anchor="true" title="进制的表示方法"></a> `0x` 前缀表示**十六进制** (记**hex**的**x**) `0` 前缀表示**八进制** (记**octal**的**o**,虽然这里是**0**) `0b` 或 `0B` 前缀表示 **二进制** --------- <a id="normalMode-numAscDesc-available" for-anchor="true" title="Vim可识别类型"></a> > 💡 Vim支持**进行增减**的类型有:二进制数(**bin**ary),八进制数(**octal**),十进制数(decimal,默认支持),十六进制数(**hex**adecimal),无符号数(**unsigned**),字母(**alpha**betical)。 | 类型 | `nrformats`中的表示 | |:---:|:---:| | 二进制binary | `bin` | | 八进制octal | `octal` | | 十六进制hexadecimal | `hex` | | 无符号数unsigned | `unsigned` | | 字母alphabetical | `alpha` | 👆 具体配置见`末行模式/命令(行)模式`的[nrformats配置](#commandLineMode-settings-nrformats)一节。 16. **字母大小写切换** <a id="normalMode-caseSwitch" for-anchor="true" title="字母大小写切换"></a> 数字增减有了,字母其实也是可以“随地大小变”的! * `gu后接光标移动指令` <a id="normalMode-caseSwitch-gu" for-anchor="true" title="转换为小写gu"></a> 将从光标位置开始到**执行移动指令后**光标的位置结束作为选区, 将选区内的所有字母转换为**小写**! ![guiw-2022-04-16](https://assets.xbottle.top/img/guiw-2022-04-16.gif) 👆 演示了`guiw`指令。`iw`选择的是光标所在单词`FLOOD`,执行`gu`后将单词所有字母转换为**小写**:`flood`。 * `gU后接光标移动指令` <a id="normalMode-caseSwitch-gupper" for-anchor="true" title="转换为大写gU"></a> 将选区内的所有字母转换为**大写** > 💡 记忆方法:`gu`和`gU`中一个是小写`u`,一个是大写`U`。大写`U`可以记成`UPPERCASE 大写`,而当U小写成为`u`,对应的便是`小写`了。`g`仍然可以记成`go`,`go UPPERCASE`。 * `~` (波浪符) <a id="normalMode-caseSwitch-swap" for-anchor="true" title="大小写交换~"></a> 波浪符将**光标**所在的字母进行**大小写交换**,比如`b`会转换为`B`;`B`则会转换为`b`。 这个指令可以用于转换**光标所在行**中**自光标往后**的字母**大小写**。 ![tilde-2022-04-16](https://assets.xbottle.top/img/tilde-2022-04-16.gif) 👆 `50~`,将本行中自光标往后50个字符进行大小写转换。 * `g~后接光标移动指令` <a id="normalMode-caseSwitch-swap" for-anchor="true" title="光标移动大小写交换g~"></a> 上面的`~`只能对**光标所在行**的字符进行大小写交换,如果要涉及**一个选区**呢? 于是,`g~`他来了!在`g~`后接光标移动指令,将**光标当前位置**开始到**执行指令后光标所在位置**为止之间的字符进行大小写交换。 ![gT2gj-2022-04-17](https://assets.xbottle.top/img/gT2gj-2022-04-17.gif) 👆 `g~2gj`,将从光标位置开始到光标向下移动**两个屏幕行**为止的部分字符大小写交换。 > 💡 记忆方法:波浪线可以看作是一条**波**,随着波的传播,同一个地方的**波峰波谷交替出现**,在这里就记成**大写小写的交替**。 ![caseInversion-2022-04-17](https://assets.xbottle.top/img/caseInversion-2022-04-17.gif) 17. **重复最后一次更改** <a id="normalMode-repeatLast" for-anchor="true" title="重复最后一次更改."></a> `.` ↑ 没错,指令就是一个句点! 比如,之前我输入了`3dd`剪切了三行文本,之后再按一次 `.` 会再往下剪切三行; 又比如,我先输入了`x`剪切光标所指的字符,之后输入 `3.` 就会再剪切三个字符。 **但是!**,如果我先输入`3j`让光标往下移动3行,再按 `.` 是没有效果的,因为`j`指令仅仅是移动了光标,**没有对文本造成任何更改!** > ⚠ 注意! `.` 重复的是**最后一次更改**,而不是最后一次指令! > 👆 记忆方法:硬背 18. **行连接指令** <a id="normalMode-joinRows" for-anchor="true" title="行连接"></a> * 大写 `J` <a id="normalMode-joinRows-j" for-anchor="true" title="带空格间隔"></a> 这个指令能将**光标所在行**和其下面一行**连接成一行**,之间用**一个空格间隔**: ![LinesJoin-2022-04-17](https://assets.xbottle.top/img/LinesJoin-2022-04-17.gif) > `4J` 的话则是将光标所在行和**下面3行**进行连接,每行之间用空格隔开。**一共4行**。 > 💡 记忆方法:直接记 `Join [v]加入;连接` 即可。 * `gJ` <a id="normalMode-joinRows-gj" for-anchor="true" title="无间隔"></a> 同样是连接行。和上面`J`不同的是,这个指令在连接行之后**不会在之间添加空格!**。 > 💡 记忆方法:可以记成`gross Join`,`gross`有`总的;粗鲁的`的意思,gross Join就可以记成 “**连接成一个总体**” 或者 “**粗鲁地直接连在一起**”。 ## 末行模式/命令(行)模式 在这个模式中涉及到了最基础的文件保存 / 退出编辑器等操作。 1. ```:w``` **写入** (记为```Write [v]写文件```) <a id="commandLineMode-write" for-anchor="true" title="写入(保存)文件:w"></a> 顾名思义,这个指令会将**自开始编辑以来**的更改写入文件中: ![write-2022-04-07](https://assets.xbottle.top/img/write-2022-04-07.gif) ![writeFile-2022-04-07](https://assets.xbottle.top/img/writeFile-2022-04-07.png) <a id="commandLineMode-write-as" for-anchor="true" title="另写入文件(另存为)"></a> 文件保存总让人想起桌面软件的“另存为”功能,当然```Vi```也是能实现的: ⭐ ```:w filename``` ![writeToAnother-2022-04-08](https://assets.xbottle.top/img/writeToAnother-2022-04-08.gif) ![writeAnotherFile-2022-04-08](https://assets.xbottle.top/img/writeAnotherFile-2022-04-08.png) <a id="commandLineMode-write-all" for-anchor="true" title="写入所有文件"></a> 除了上面针对单一文件的写入指令,还可以同时**写入多个文件**,下面这个指令适用于Vim编辑器中同时打开多个文件的情况: ⭐ ```:wa``` 👆 将所有打开的文件进行写入保存。(可以记成`Write All 写入所有`) ![WriteAll-2022-04-17](https://assets.xbottle.top/img/WriteAll-2022-04-17.png) <a id="commandLineMode-write-part" for-anchor="true" title="写入文件部分行"></a> 如果只想保存文本中的一部分呢?Vim还支持指定行写入文件: ⭐ ```:<line1>,<line2> w filename``` 👇 比如我想写入第`13`至第`14`行的文本(共两行):```:13,14 w filename``` ![writeSpecificLines-remake-2022-04-18](https://assets.xbottle.top/img/writeSpecificLines-remake-2022-04-18.gif) 输出文件效果: ![SpecificLines-2022-04-18](https://assets.xbottle.top/img/SpecificLines-2022-04-18.jpg) 2. ```:q``` **退出**(记为```Quit [v]离开;关闭```) <a id="commandLineMode-quit" for-anchor="true" title="退出文件:q"></a> 文本编辑器必备操作——关闭文件。 ⭐ ```:q``` 指令会退出**当前编辑的文件**(如果只打开了一个文件,这个操作同时会**退出编辑器**) ⭐ ```:qa``` 指令则会退出**所有打开的文件**,并且**退出编辑器**。 > 👆 记忆方法:`Quit All 关闭所有` > **注意**:上面这两个指令执行的前提是**当前文件自打开后没有任何更改**,也就是说文件缓冲区中没有任何新增内容,不然编辑器会警告文件尚未保存更改。 > 怎么强制退出呢?下面就将写到..... 3. **联合使用** ```w``` 与 ```q``` <a id="commandLineMode-combineWQ" for-anchor="true" title="联合使用:wq"></a> ![wCombineWithQ-2022-04-18](https://assets.xbottle.top/img/wCombineWithQ-2022-04-18.png) 既然未保存没法正常关闭文件,那么保存后关闭不就行了嘛! ⭐ ```:wq``` 👆 双剑合璧,数据无忧!这个指令会将缓冲区中的编辑内容**写入文件**,并且**关闭文件**。(针对**当前正在编辑的文件**) 当然,也有针对**编辑器所有打开的文件**的操作: ⭐ ```:wqa``` 👆 写入并关闭**所有打开的文件**,这之后会**退出编辑器**。(```Write & Quit All```) 4. 感叹号 ```!``` **强制执行指令** <a id="commandLineMode-force" for-anchor="true" title="强制执行!"></a> 有些情况下末行模式指令的执行可能会被拒绝,比如上面的`:q`在文件保存前不能直接执行。(这一点其实也是为了数据安全) ⭐ 然而,只需要在这些指令**末尾**加上感叹号 ```!``` 就可以**强制执行**了! 比如**未保存强制退出编辑器**就可以用: ```:qa!``` ![forcedQuitAll-remake-2022-04-18](https://assets.xbottle.top/img/forcedQuitAll-remake-2022-04-18.png) 此外我觉得**文件强制写入**也是值得记录一下的:```:w!``` 👆 这个指令会**尝试强制写入文件,即使文件是只读(Read-only)的**,前提是**用户得要有修改文件属性的权限**。 > ⚠ 强制执行指令时一定要再三考虑,防止数据丢失等问题。 5. ```:r``` **在下一行读入文件** (记成 ```Read [v]读取```) <a id="commandLineMode-readNextLine" for-anchor="true" title="在下一行读入文件:r"></a> 在编辑文本的时候可能需要将**另一个文件的文本**插入进来,这个时候就轮到`:r`大显身手了。 ⭐ ```:r``` 能够**读取一个文件**,并将文件内容**追加**到光标所在行的**后一行**。 ![appendFileContent-2022-04-18](https://assets.xbottle.top/img/appendFileContent-2022-04-18.gif) 👆 左边是待读取的文件,右边是正在编辑的文件。利用`:r`指令,成功将文件内容插入到光标后一行。 6. ```:e``` **开启新buffer或刷新当前buffer** (记成 ```Edit [v]编辑```) <a id="commandLineMode-newBuffer" for-anchor="true" title=":e开启或刷新当前buffer"></a> 当编辑文件编辑到一半,想要推翻当前的所有修改,该怎么办呢? 一种方法是使用 `:q!`,强制退出文件; 另一种方法则是 ```:e!```,强制刷新当前`buffer`: * 文件在`Vi/Vim`中未经修改但是在**别的软件中受到了修改**时,可以直接使用 `:e` 在编辑器里**刷新(重新载入)文件内容**。 <a id="commandLineMode-newBuffer-reload" for-anchor="true" title="强制重载文件内容"></a> * 文件做了修改后,也可以用 `:e!` 强制**重载文件内容**,也就是抛弃文件开启以来的所有修改。 ![forcedRefreshBuffer-2022-04-22](https://assets.xbottle.top/img/forcedRefreshBuffer-2022-04-22.gif) <a id="commandLineMode-newBuffer-buffer" for-anchor="true" title="简述buffer"></a> > buffer可以理解为文件的一个**缓冲区**,文件在编辑器中被编辑实际上也就是临时buffer在被编辑。当写入文件的时候就是把buffer中的内容**写入到原文件**中,以达成编辑的效果。 ---------- <a id="commandLineMode-newBuffer-newFile" for-anchor="true" title="打开新文件"></a> 除了刷新当前文件的buffer外,```:e```指令还可以用于**打开新的文件**并创建buffer。 * ```:e <filename>``` 打开文件(如果存在的话),创建buffer 👆 前提:目前在编辑的文件已经保存或者未经修改。 * ```:e! <filename>``` 无论**当前文件是否保存(写入)**,强制打开新文件 7. ```:buffers``` 和 ```:bn```, ```:bp``` 以及 ```:b<id>``` <a id="commandLineMode-buffers" for-anchor="true" title=":buffers, :bn, :bp, :b<id>"></a> * ```:buffers``` <a id="commandLineMode-buffers-all" for-anchor="true" title="查看所有:buffers"></a> **列出所有buffer**,在这里可以看到buffer的ID(最左边一列)。 ![viewBuffers-2022-04-22](https://assets.xbottle.top/img/viewBuffers-2022-04-22.jpg) * ```:b<id>``` <a id="commandLineMode-buffers-id" for-anchor="true" title=":b<id>跳转指定buffer"></a> 既然拿到了buffer的ID,我就可以在buffer之间跳转了,比如: ```:b2``` 跳转到id为2的buffer > 注意:这个操作也要求当前编辑的文件**不能有未保存**的修改。 * ```:bn``` 和 ```:bp``` <a id="commandLineMode-buffers-switch" for-anchor="true" title="buffers之间互相切换:bn,:bp"></a> 就算不知道buffer的ID,利用这两个指令也能在buffer间互相切换: ```:bn``` ---> 切换到**下一个**buffer ```:bp``` ---> 切换到**上一个**buffer > 注意:这个操作也要求当前编辑的文件**不能有未保存**的修改。 > 💡 记忆方法:`bn`即 `buffer next 下一个buffer`;`bp`即 `buffer previous 上一个buffer`。 8. ```:help [command]``` 或 ```:h [command]``` **查看帮助** <a id="commandLineMode-help" for-anchor="true" title="查看帮助:h[elp]"></a> 就像Linux中的`man`指令一样,Vi/Vim的`末行模式/命令行模式`也有个`:help`指令用来查看编辑器使用帮助。 ⭐ 直接使用 ```:help``` 时会打开帮助文件,并将光标移动到顶部。 ⭐ 指令形如```:help [command]```时,在开启文件后光标会跳转到对应的指令帮助信息。 > 比如要看寄存器指令的帮助信息:```:help reg``` 或 ```:help registers``` ( ```:h reg``` 当然也行 ) 9. ```:registers``` 或 ```:reg``` **查看寄存器** <a id="commandLineMode-viewRegs" for-anchor="true" title="查看寄存器:reg[isters]"></a> 这一部分可以结合```命令模式/正常模式```的[双引号指定寄存器](#normalMode-specifyRegs)一起记。 ⭐ 直接使用 ```:registers``` 或 ```:reg``` 时会展示**所有的寄存器** 👇 ![registers-2022-04-18](https://assets.xbottle.top/img/registers-2022-04-18.jpg) 也可以在后面**指定要查询的寄存器名**,这些名称可以用空格分开,也可以连在一起写: ⭐ 比如 ```:reg[isters] a b c``` 或 ```:reg[isters] abc``` ,可以筛选查询`a`,`b`,`c`寄存器 👇 ![specifyRegisters-2022-04-18](https://assets.xbottle.top/img/specifyRegisters-2022-04-18.gif) 关于寄存器更多用法这里就不多赘述了~ ![009-2022-04-18](https://assets.xbottle.top/img/009-2022-04-18.png) 10. ```:marks``` **查看标记信息** <a id="commandLineMode-viewMarks" for-anchor="true" title="查看标记:marks"></a> 这个和上面```:reg[isters]```的使用**很类似**,所以咱给写一起了。 这一部分可以结合```命令模式/正常模式```的[自定义跳转标记](#normalMode-makeMarks)一起记。 ⭐ 直接使用 ```:marks``` 能查询到**所有现有的标记** 和:registers一样,在后接查询的标记名,能筛选结果: ⭐ 比如 ```:marks a b c``` 或 ```:marks abc``` ,可以筛选查询`a`,`b`,`c`标记 11. ```:set``` **设置项** <a id="commandLineMode-settings" for-anchor="true" title="设置项:set"></a> 这个指令用于设置一些配置项,这里就记几个比较常用的(因为配置项有非常多,全写出来太累赘了) * **开启/关闭行号显示** <a id="commandLineMode-settings-nu" for-anchor="true" title="开启/关闭行号显示"></a> ```:set number``` 或 ```:set nu``` 可以在行首显示行号 而 ```:set nonumber``` 或 ```:set nonu``` 则可以关闭行号显示 此外 ```:set nu!``` 或 ```:set number!``` 可以在**开启行号/关闭行号**之间互相切换。 ![lineNumbers-2022-04-19](https://assets.xbottle.top/img/lineNumbers-2022-04-19.gif) > 👆 记忆方法:直接记 `Number [n]号码 [v]标号` * **开启/关闭相对行号显示** <a id="commandLineMode-settings-rnu" for-anchor="true" title="开启/关闭相对行号显示"></a> ```:set relativenumber``` 或 ```:set rnu``` 可以显示相对行号 而 ```:set nonumber``` 或 ```:set nonu``` 则可以关闭相对行号显示 此外 ```:set rnu!``` 或 ```:set relativenumber!``` 可以在**开启相对行号/关闭相对行号**之间互相切换。 ![relativeNumber-2022-04-19](https://assets.xbottle.top/img/relativeNumber-2022-04-19.gif) 👆 可以看到,相对行号以**光标所在行**为第`0`行,向上或向下标出相对的行号。 这个特性可以用于**辅助涉及多行的指令**,比如 `y3j`, `3k`, `2>>` 等。 > 💡 记忆方法:按字面记,即 `Relative Number 相对 号码` * **识别数字进制的配置** <a id="commandLineMode-settings-nrformats" for-anchor="true" title="识别数字进制的配置"></a> 上面已经写过`Ctrl+x`和`Ctrl+a`能[快捷减增数字](#normalMode-numAscDesc)。不过呢,Vim怎么识别**增减的类型**呢? 这就涉及到下面这个配置项了: * ```:set nrformats``` 或 ```:set nf``` <a id="commandLineMode-settings-nrformats-view" for-anchor="true" title="查看当前的配置"></a> 直接输入这个指令会查询**该配置项当前的设置**: ![queryNrformats-2022-04-19](https://assets.xbottle.top/img/queryNrformats-2022-04-19.gif) 上面也写过Vim[能识别的增减类型](#normalMode-numAscDesc-available)。这里的`octal,hex` 则代表会尝试识别**八进制**,**十六进制**以及**十进制**的数字。(十进制是默认支持的) * 使用 `+=` 或 `-=` 进行配置 <a id="commandLineMode-settings-nrformats-add" for-anchor="true" title="运算赋值配置±="></a> 这种写法其实可以理解为一些编程语言中的**运算赋值**操作: ```:set nrformats+=bin``` ```:set nrformats+=bin,alpha``` (或者 `set nf+= ...` ) 👆 多个可以用**逗号**分隔,会“附加”到配置项的末尾。 然而,移除的时候就要注意了,一定要按**配置中的顺序**来: ```:set nrformats-=bin``` ```:set nrformats-=bin,alpha``` 👆 这样写的话必须保证配置项中也有**bin,alpha**这个字串! ![removeNf-2022-04-19](https://assets.xbottle.top/img/removeNf-2022-04-19.gif) ↑ 没有`bin,alpha`这个字串,无法移除。 ![successfullySetNf-2022-04-19](https://assets.xbottle.top/img/successfullySetNf-2022-04-19.gif) ↑ 移除`octal,alpha`的话就很成功,因为配置项中有这个字串。 * 直接使用 `=` 进行配置 <a id="commandLineMode-settings-nrformats-eq" for-anchor="true" title="赋值配置="></a> 同样可以按编程语言里的**赋值**来理解,会直接**改变整个配置** ```:set nrformats=octal,hex``` 👆 多个值一样是用**逗号分隔** > 💡 记忆方法:`nrformats`可以看成`number recognizing formats`,也就是`数字识别格式`。取`n`和`f`即为`nf`,因此`nf`也可以简单记为`number format`! 12. **执行Shell指令** <a id="commandLineMode-shell" for-anchor="true" title="执行Shell指令"></a> 编辑文本到一半突然想执行一个Shell指令。我之前可能会利用`:wq`先退出编辑器,输入执行指令后再重新打开编辑器。 实际上在`末行模式/命令行模式`下Vi/Vim也是能快捷切换到Shell执行指令的: * ```:! <command>``` **暂时离开**编辑界面,在Shell下执行指令**并打印结果** <a id="commandLineMode-shell-temporary" for-anchor="true" title="暂时离开编辑执行语句"></a> ![temporaryShell-2022-04-19](https://assets.xbottle.top/img/temporaryShell-2022-04-19.gif) 👆 **临时**跳转到命令行执行`清屏`及`查询日期`指令并展示结果。 > 💡 `:!` 还有很多神奇的用法,比如执行指令后将返回的结果插入下一行:`:r! <command>`; > 又比如将文本`21`至`25`行进行升序排序,并替换原文本:`:21,25! sort`,这里就很像Shell中`管道符`的用法了。 ![sortByShell-2022-04-19](https://assets.xbottle.top/img/sortByShell-2022-04-19.gif) 👆 演示 `:21,25! sort` > 更多用法在这里就不多赘述了╮(╯3╰)╭ * ```:sh``` **创建一个新的Shell会话** <a id="commandLineMode-shell-new" for-anchor="true" title="创建新的Shell会话"></a> 这个指令就比较简单粗暴了,在执行后会创建一个新的Shell会话,我可以直接在Shell中执行指令! ![createNewShell-2022-04-19](https://assets.xbottle.top/img/createNewShell-2022-04-19.gif) 这个Shell是一个**非登入**Shell,所以需要使用 `exit` 指令退出。 该Shell退出后会**回到编辑器界面!** ![exitShell-2022-04-19](https://assets.xbottle.top/img/exitShell-2022-04-19.gif) > 👆 这个直接记忆 `Shell` 就行 13. **重复上一条指令** <a id="commandLineMode-repeatLast" for-anchor="true" title="重复上一条指令"></a> 在`命令模式/正常模式`下我可以使用 `.` 句点来重复上一次更改。在`末行模式/命令行模式`下也有类似的方法。 * **经典方法** <a id="commandLineMode-repeatLast-classical" for-anchor="true" title="经典方法"></a> 在`末行模式/命令行模式`下输入 `:` 后会进入`末行模式/命令行模式`,此时可以通过: ⭐ `↑`, `↓`, `PageUp`, `PageDown` 来浏览之前的输入历史 ![scrollHistory-2022-04-19](https://assets.xbottle.top/img/scrollHistory-2022-04-19.gif) 💡 可以在 `:` 后面输入一些字符以**加快检索**: ![scrollHistoryWithHint-2022-04-19](https://assets.xbottle.top/img/scrollHistoryWithHint-2022-04-19.gif) 👆 输入`:s`后,能快速浏览`:s`开头的历史指令记录;`:se`则能快速浏览`:se`开头的历史指令。 * **`@:` 方法** <a id="commandLineMode-repeatLast-at" for-anchor="true" title="@:方法"></a> 在`命令模式/正常模式`下输入: `@:` 能重新执行**上一条`末行模式/命令行模式`指令**。 > 👆 记忆方法:把 `:` 末行指令给**at**出来! 虽然我把这个方法写在末行模式这里了,但实际上其可以算是`命令模式/正常模式`的指令。 因此也是**可以重复执行的!** ![repeatLastLine-2022-04-19](https://assets.xbottle.top/img/repeatLastLine-2022-04-19.gif) 👆 演示:先用`:d`删除一行,然后再用`3@:`重复执行`:d`三次以继续删除下面三行。 ![however-2022-04-19](https://assets.xbottle.top/img/however-2022-04-19.png) 有点累了...喝口水... ## 基本搜索替换 1. **搜索** <a id="searchSub-search" for-anchor="true" title="搜索"></a> **前提**:在`命令模式/正常模式`下: ```/<搜索模式>``` 自光标**向后查找** ```?<搜索模式>``` 自光标**向前查找** > 👆 其实`末行模式/命令行模式`下也不是不行,也就多了个冒号:`:/<搜索模式>`、`:?<搜索模式>`。不过接下来这两个指令就不行了 ↓ ```n``` ---> 这个指令**会重复上一次搜索动作**。 * 比如`/....`是往**光标后**搜索,按下`n`就是**在光标后**再次搜索 * 而`?....`是往**光标前**搜索,那么按下`n`就是**在光标前**再次搜索 ```N``` ---> 这个指令和 ```n``` 的操作相反 > 👆 记忆方法:`n`可以记成 `next [a]下一个的` ,正如o和O操作相反一样,`N`和`n`操作相反。 ------------- <a id="searchSub-search-pattern" for-anchor="true" title="搜索模式(Pattern)"></a> 这里的`搜索模式`实际上就是**正则表达式**,不过和普通的正则略微有些不同! 1. 当然是可以直接搜索字串的 <a id="searchSub-search-pattern-str" for-anchor="true" title="直接搜索字串"></a> ![searchStr-2022-04-21](https://assets.xbottle.top/img/searchStr-2022-04-21.gif) 2. **大小写不敏感**搜索 <a id="searchSub-search-pattern-casei" for-anchor="true" title="大小写不敏感搜索"></a> 一般在写正则表达式时要进行大小写不敏感匹配我一般会在正则表达式末尾加上`i`标记,但是在`搜索模式`里是不行的,需要用到特殊转义标记: `\c` 这个标记写在`搜索模式`的中间(不要写在中括号`[]`里!)一般也是可以的,但我觉着还是**放在末尾**好辨别一些。 ![caseInsensitivelySearch-2022-04-21](https://assets.xbottle.top/img/caseInsensitivelySearch-2022-04-21.gif) ![caseInsensitiveSearch-2022-04-21](https://assets.xbottle.top/img/caseInsensitiveSearch-2022-04-21.png) > 👆 记忆方法:这里的 `\c` 可以记成 `case [n]大小写`。 3. 利用**正则表达式**进行搜索 <a id="searchSub-search-pattern-regex" for-anchor="true" title="利用正则表达式"></a> 这里使用正则表达式和在编程语言里有些小区别,这里简单写一下: - 采用 `<` 和 `>` 分别匹配**单词词首**和**单词词尾**,而不是`\b`。 - 部分元字符**要发挥元字符**的作用的话,需要**先转义**。这里列个表格: | 元字符 | 用途 | 在搜索模式中的用法 | |:---:|:---:|:---:| | `+` | 匹配1次或多次 | `\+` | | `?` | 匹配0或1次(非贪婪模式在Vim中另有元字符) | `\?` | | `{n,m}` | 匹配n到m次 | `\{n,m}` | | `{n,}` | 匹配n次或更多次 | `\{n,}` | | `{n}` | 匹配n次 | `\{n}` | | `{,m}` | 匹配0到m次 | `\{,m}` | | `<` | 匹配单词首部 | `\<` | | `>` | 匹配单词尾部 | `\>` | | `(` | 子模式(捕获组)开始标志 | `\(` | | `)` | 子模式(捕获组)结束标志 | `\)` | | `\|` | 两项之间任意匹配一个 | `\\|` | | `%` | 用于修饰**非捕获组** | `\%` | | `@<=`, `@<!`, `@=`, `@!` | 向后预查,向前预查 | `\@<=`, `\@<!`, `\@=`, `\@!` | > 💡 这种情况是基于Vim**默认设置**的`magic`模式的,其他模式不多赘述。 > 默认情况下这些元字符如果不转义,就代表**匹配这个字符本身**。 除此之外的元字符大多是可以直接使用的,下面是一些示例: ![regexSearch1-2022-04-21](https://assets.xbottle.top/img/regexSearch1-2022-04-21.jpg) ![regexSearch2-2022-04-21](https://assets.xbottle.top/img/regexSearch2-2022-04-21.jpg) ![regexSearch3-2022-04-21](https://assets.xbottle.top/img/regexSearch3-2022-04-21.png) ![regexSearch4-2022-04-21](https://assets.xbottle.top/img/regexSearch4-2022-04-21.jpg) ![regexSearch5-2022-04-21](https://assets.xbottle.top/img/regexSearch5-2022-04-21.jpg) 另外也是支持**引用子模式的匹配结果(分组)**的: ![regexSearch6-2022-04-22](https://assets.xbottle.top/img/regexSearch6-2022-04-22.jpg) 4. 神奇的**非贪婪模式** <a id="searchSub-search-pattern-nonGreedy" for-anchor="true" title="非贪婪匹配"></a> Vim这里的非贪婪模式用的就不是元字符 `?` 了,取而代之借用了一下大括号 ```{...,...} ``` > 官方说明可以在`末行模式/命令行模式`下输入`:help non-greedy`查看。 当 `{` 后面**紧接**了一个连字符(Hyphen) `-` 时,就相当于采用了**非贪婪匹配**,下面举些例子: `e\{2,3}` 匹配 `ee` 或 `eee`,如果有更长的`eee`就优先匹配(也就是**贪婪匹配**) `e\{-2,3}` 匹配 `ee` 或 `eee`,优先匹配更短的`ee`(也就是**非贪婪匹配**) ![non-greedyMatch-2022-04-21](https://assets.xbottle.top/img/non-greedyMatch-2022-04-21.gif) > 👆 这个例子中在`{`后紧接`-`后会往少的匹配,也就是只匹配一个`paprika `。 ------ 如果在 `-` 后面**没有指定匹配次数**,就有点类似 `*` 了。形如: ```\{-}``` 匹配`0-任意`次,但是非贪婪匹配,匹配次数尽量少。 ```\{-,3}``` 匹配`0-3`次,但是非贪婪匹配,匹配次数尽量少。 > 例:用模式 `ke\{-}` 匹配字串`keep`只匹配到了`k`,因为`\{-}`代表匹配`0-任意次`,但是**非贪婪匹配**,所以这里`e`匹配了`0`次。 5. **非捕获组与向前/向后预查** <a id="searchSub-search-pattern-groups" for-anchor="true" title="非捕获组与向前/向后预查"></a> 在编程语言使用的正则表达式中,非捕获组一般用`(?:模式)`来表示,这一个子模式不会参与分组。 --------------- 然鹅在`Vi/Vim`**搜索模式**中,非捕获组用的是: `\%(子模式\)` ( `\%\(子模式\)` 也行) 来表示的。下面就是一个示例: ![nonCapturingGroup-2022-04-22](https://assets.xbottle.top/img/nonCapturingGroup-2022-04-22.jpg) > 可以看到`accept`并未参与分组,`\1`引用的是第二个子模式的匹配结果`after` ------------- 接着,在编程语言中的向后/向前预查`(?<=)`, `(?=)`, `(?<!)`, `(?!)` 在Vi/Vim中也是有特殊的表示方法的: | 预查内容↓ \ 方向→ | 向后(左) | 向前(右) | |:---:|:---:|:---:| | 存在 | `\(子模式\)\@<=` | `\(子模式\)\@=` | | 不存在 | `\(子模式\)\@<!` | `\(子模式\)\@!` | > 💡 **预查**即预先检查子模式,看看这个子模式是否存在于待匹配的字串前/后,预查子模式是**不会出现在最终的匹配结果中的**。 ![lookBehindPositive-2022-04-22](https://assets.xbottle.top/img/lookBehindPositive-2022-04-22.jpg) > 👆 匹配` paprika`,前提是` paprika`**后面**(左边)必须要匹配到`paprika`。 ![lookAheadPositive-2022-04-22](https://assets.xbottle.top/img/lookAheadPositive-2022-04-22.jpg) > 👆 匹配`paprika`,前提是`paprika`**前面**(右边)必须要匹配到` pepper`。 ![lookAheadNegative-2022-04-22](https://assets.xbottle.top/img/lookAheadNegative-2022-04-22.jpg) > 👆 匹配`paprika`,前提是`paprika`**前面**(右边)**不能**匹配到`pepper`。 6. 搜索**光标下的单词** <a id="searchSub-search-underCursor" for-anchor="true" title="搜索光标下单词"></a> 这一小节的操作是在`命令模式/正常模式`下的: * `*` **往后**搜索**光标目前指向的单词**,只匹配**一整个单词** * `#` **向前**搜索**光标目前指向的单词**,只匹配**一整个单词** ![searchWordUnderCursor-2022-04-21](https://assets.xbottle.top/img/searchWordUnderCursor-2022-04-21.gif) > 从这个例子可以看到,实际上Vim是把光标指向的单词转换成了搜索语句。`*` 对应 `/\<accept\>`,`#` 对应 `?\<\accept\>`。 * `g*` **往后**搜索**光标目前指向的单词**,单词可作为**字串的一部分**被匹配。 * `g#` **向前**搜索**光标目前指向的单词**,单词可作为**字串的一部分**被匹配。 ![searchWordPartUnderCursor-2022-04-21](https://assets.xbottle.top/img/searchWordPartUnderCursor-2022-04-21.gif) > 可以看到 `*` 对应 `/accept`,`#` 对应 `?accept`,单词可作为**字串的一部分**被匹配。 >💡 因为这几个指令被转换为末行搜索操作了,所以在搜索中可以用的`n`、`N`这一类指令也是可以用的。 7. 开启**搜索高光显示** <a id="searchSub-search-highlight" for-anchor="true" title="开启搜索匹配高光展示"></a> 上面的图示中搜索匹配项都会“**黄的发光**”,这种**匹配结果高光显示**是可以作为配置项使用 `:set` 进行设置的: * `:set hlsearch` 或 `:set hls` 开启搜索结果**高光显示** * `:set nohlsearch` 或 `:set nohls` 关闭搜索结果**高光显示** ![highlightSearch-2022-04-21](https://assets.xbottle.top/img/highlightSearch-2022-04-21.gif) 虽然高亮显示一目了然,但是开了之后光标就不明显了,高光咱已经看够了,怎么**关掉目前结果的高光展示**呢? * `:nohlsearch` 或 `:noh` 关闭**目前的高光显示** ![nohlsearch-2022-04-21](https://assets.xbottle.top/img/nohlsearch-2022-04-21.gif) > 可以看到输入`:noh`后会取消目前的高亮,但是这并不影响重新开始搜索时高亮展示匹配。 > 💡 记忆方法:`hlsearch` 即 `Highlight Search`,`Highlight [v]突出,强调`,`Search [n]搜索`。 ![hlsearch-2022-04-21](https://assets.xbottle.top/img/hlsearch-2022-04-21.png) 2. **替换** <a id="searchSub-substitute" for-anchor="true" title="替换"></a> 替换的指令就是在`末行模式/命令行模式`下输入的了, 其完全可以结合搜索指令一起记忆: ```:[作用范围] s/<搜索模式>/<替换模式>/<替换flag>``` > 💡 很明显了,这里的`s`代表的就是`substitute [v]取代,替代` 1. **作用范围** <a id="searchSub-substitute-scope" for-anchor="true" title="作用范围"></a> 这里直接列表直观展示一下: |作用范围|说明| |:---:|:---:| |不写|**默认光标所在行**| | `.` | 同样代表**光标所在行** | | `n` (整数) | 代表**第`n`行**。比如`233`代表第233行| | `$` | 代表**最后一行** | | `'a` | 代表`a`**标记所在行**(关于设定跳转标记可以看[自定义跳转标记](#normalMode-makeMarks)) | | `上述符号±相对行数` | 比如`'a+2`就代表`a`标记所在行的下面第二行;`.-1` 就代表光标所在行的上面一行 | | `上述符号1,上述符号2` | 代表从`上述符号1`代表的行到`上述符号2`代表的行。比如`.,$`代表从光标所在行到最后一行。| | `%` | 代表**从第一行到最后一行**(整个文本),和`1,$`相同 | | `'<,'>` | 在[**可视模式**](#可视模式)下输入 `:` 进入`末行模式/命令行模式`会自动填充,代表**可视模式下选定的范围 | ![replaceInTheRange-2022-04-22](https://assets.xbottle.top/img/replaceInTheRange-2022-04-22.gif) > 👆 示例:`.,+3` 代表从光标所在行到光标所在行的下面第三行作为范围进行替换 2. **搜索模式** <a id="searchSub-substitute-searchPattern" for-anchor="true" title="搜索模式"></a> 同上面的[**搜索模式**](#searchSub-search-pattern)。 💡 值得一提的是这里可以留空。如果留空的话**默认使用**上一次[**搜索**](#searchSub-search)的匹配。 > 比如我先搜索了`/\<paprika\>`,然后我想替换全文的`/\<paprika\>/`为`pepper`,在替换时可以这样写:`:%s//pepper/g`(省略了搜索模式)。 3. **替换模式** <a id="searchSub-substitute-subPattern" for-anchor="true" title="替换模式"></a> 替换模式当然可以是**任意字符串**, 同时也可以**引用搜索结果中的部分**: ![replaceWithReference-2022-04-22](https://assets.xbottle.top/img/replaceWithReference-2022-04-22.gif) 👆 使用 `\1`, `\2` 引用**搜索结果**中的第一个和第二个**捕获组**的内容。 ![replaceWithReference2-2022-04-22](https://assets.xbottle.top/img/replaceWithReference2-2022-04-22.gif) 👆 使用 `&` 引用**搜索匹配结果** ![replaceWithReference3-2022-04-22](https://assets.xbottle.top/img/replaceWithReference3-2022-04-22.gif) 👆 很巧妙的用法,利用捕获组引用交换顺序 4. **替换flag** <a id="searchSub-substitute-flags" for-anchor="true" title="替换标识(Flag)"></a> 一般正则表达式末尾会加上修饰符以控制匹配行为。而Vim这里的**替换flag**也是类似修饰符,控制替换行为的。 下面列出几个比较常用的: | Flag | 说明 | 记忆 | | :---: | :---: | :---: | | `g` | 全局替换(在指定的**作用范围**内) | `global [a]全局的` | | `i` | 忽略大小写 | `ignore-case 忽略大小写` | | `I` | 大小写敏感 | 和`i`意义相反 | | `n` | 并不会真正替换,而是显示**匹配的数目** | `numerate [v]计算` | | `c` | 在替换前**让用户确认** | `confirm [v]确认` | > 💡 值得一提的是 `i`——忽略大小写。 > 之前在[**搜索模式**](#searchSub-search-pattern)中提到过在模式中加入 `\c` 能忽略大小写进行搜索,而这里替换也是用的搜索模式, > 因此在这里就算不用修饰符`i`,转而在搜索模式中加入`\c`也是完全可以的。 ![replaceIgnoreCase-2022-04-22](https://assets.xbottle.top/img/replaceIgnoreCase-2022-04-22.gif) > 👆 不使用修饰符而是在搜索模式中使用了`\c` ------------- ![numerateMatches-2022-04-22](https://assets.xbottle.top/img/numerateMatches-2022-04-22.gif) > 👆 使用 `n` 修饰符,并不会实际替换文本,而是**显示匹配的数量** ----------- ![confirmReplace-2022-04-22](https://assets.xbottle.top/img/confirmReplace-2022-04-22.gif) > 👆 使用 `c` 修饰符,在替换前**让用户确认**。 其中提示输入的 `(y/n/a/q/l/^E/^Y)` 中,分别代表: | 输入指令 | 说明 | 记忆 | |:---:|:---:|:---:| | `y` | 确认替换**当前光标所在匹配** | `yes [v]确认` | | `n` | 不替换**当前匹配** | `no [v]不` | | `a` | 确认替换**所有匹配** | `all [v]所有` | | `q` | 不进行替换并退出 | `quit [v]退出` | | `l` | 替换**当前匹配**并退出 | `instead [adv]顶替` | (另还有`Ctrl+E`和`Ctrl+Y`,是光标移动指令) 除了这些,搜索替换可能还有一些其他的用法尚未提到,另外还有`:g`这种末行指令。这里就主要记录一下基础的用法啦~ ![exhausted-2022-04-22](https://assets.xbottle.top/img/exhausted-2022-04-22.png) ## 简单多窗口编辑 一个文件一个文件地打开编辑老累了,幸运的是,Vim支持多窗口编辑。 * **打开新窗口** <a id="multiWin-new" for-anchor="true" title="打开新窗口"></a> 1. ```:new [filename]``` <a id="multiWin-new-new" for-anchor="true" title=":new"></a> 使用`:new`指令能在**编辑器顶部**新建一个横向窗口(原本的文件窗口会**下移**) 如果有指定**文件名**,则会在新窗口打开指定文件(文件不存在就是单纯的创建buffer) ![newWindow-2022-04-22](https://assets.xbottle.top/img/newWindow-2022-04-22.gif) 2. ```:sp[lit] [filename]``` (也就是`:sp`和`:split`都可以) <a id="multiWin-new-split" for-anchor="true" title=":sp[lit]"></a> 这个指令行为上和`:new`挺类似的,都会新建一个**横向窗口**,使原本的窗口**下移**。 不同的是,**没有指定文件名时**会在新窗口打开**当前编辑的文件**! ![splitWindow-2022-04-22](https://assets.xbottle.top/img/splitWindow-2022-04-22.gif) > 👆 split [v]划分,分割 3. ```:vs[plit] [filename]``` <a id="multiWin-new-vsplit" for-anchor="true" title=":vs[plit]"></a> 这个指令和`:sp[lit]`类似,但是`:vs[plit]`是新建一个**纵向窗口**,使原本的窗口**右移**。 和`:sp[lit]`一样的是,**没有指定文件名时**会在新窗口打开**当前编辑的文件** ![verticalSplitWindow-2022-04-22](https://assets.xbottle.top/img/verticalSplitWindow-2022-04-22.gif) > 👆 `:vsplit` 即 `vertical split [v]垂直分割` * **在窗口之间切换** <a id="multiWin-switch" for-anchor="true" title="在窗口之间切换"></a> 开了这么多窗口,得想个法子来回切换哈! 1. ```Ctrl+w``` 或 ```Ctrl+W``` ( 可以记成 `Control Window 指挥窗口` ) <a id="multiWin-switch-w" for-anchor="true" title="Ctrl+w/W循环切换"></a> 连按两次 `Ctrl+w/W` 可以在**窗口之间循环切换** ![switchWindow-2022-04-22](https://assets.xbottle.top/img/switchWindow-2022-04-22.gif) 2. ```Ctrl+w/W+方向控制键``` <a id="multiWin-switch-wMove" for-anchor="true" title="Ctrl+w+方向键切换"></a> 对于**分割成上下关系**的窗口,可以使用 `Ctrl+w/W+↑/↓` 或 `Ctrl+w/W+k/j` 来进行上下切换 ![switchSplitWindow-2022-04-22](https://assets.xbottle.top/img/switchSplitWindow-2022-04-22.gif) 而对于**分割成左右关系**的窗口,就可以用 `Ctrl+w/W+←/→` 或 `Ctrl+w/W+h/l` 来进行左右切换 ![switchVSplitWindow-2022-04-22](https://assets.xbottle.top/img/switchVSplitWindow-2022-04-22.gif) * **调整窗口尺寸** <a id="multiWin-resize" for-anchor="true" title="调整窗口尺寸"></a> 在调整终端尺寸的时候,Vim里的这些窗口可能并不会如意调整展示尺寸。这里记录一些改变窗口尺寸的指令: 1. **调整窗口高度** <a id="multiWin-resize-height" for-anchor="true" title="调整高度"></a> ```:res[ize] [±][height]``` > `:res[ize]`指令有两种调整**窗口高度**的方法。 第一种是设置**绝对高度**,比如```:res 5```,将窗口高度设置为`5`行: ![resizeWindow-2022-04-23](https://assets.xbottle.top/img/resizeWindow-2022-04-23.gif) 第二种是设置**相对高度**,比如```:res +5```,将窗口高度增加`5`行: ![resizeWindowRelatively-2022-04-23](https://assets.xbottle.top/img/resizeWindowRelatively-2022-04-23.gif) 2. **调整窗口宽度** <a id="multiWin-resize-width" for-anchor="true" title="调整宽度"></a> ```:vert[ical] res[ize] [±][width]``` 和`:res[ize]`类似,`:vert[ical] res[ize]`也是能相对/绝对地调整**窗口宽度**的: ![vresizeWindow-2022-04-23](https://assets.xbottle.top/img/vresizeWindow-2022-04-23.gif) > 💡 这个记起来可能有点反直觉, > `vertical resize 垂直方向上调整`,可以**记作**此时只有**垂直高度固定的窗口**,所以调整的是**宽度**; > 而普通的`resize`则是调整的**窗口高度**,可以**记作**此时只有**水平宽度是固定的窗口**。 * **退出窗口** <a id="multiWin-quit" for-anchor="true" title="退出窗口"></a> 同`末行模式/命令行模式`下的 [`:q 退出`](#commandLineMode-quit) ## 可视模式 前面的笔记有几个地方涉及到了**可视模式**: * [模式切换->可视模式](#modeSwitch-visualMode) * [基本搜索替换->作用范围](#searchSub-substitute-scope) 可视模式下的操作可谓是**简化版的**```命令模式/正常模式```了,显而易见,“简化”的就是“选择文本”的操作了。 ----- **在高光选择了文本后**可以进行以下操作: * **拷剪粘三件套** <a id="visualMode-ydp" for-anchor="true" title="拷剪粘三件套y,d,p..."></a> ![cutYankPaste-2022-04-23](https://assets.xbottle.top/img/cutYankPaste-2022-04-23.png) 这里和`命令模式/正常模式`的不同之处就在于文本已经选择完毕,只需要**输入一个指令**就可以完成简单的编辑操作。 * `y` 输入一次即可拷贝**选择的文本**,而`Y`则是拷贝**所有选择文本所在的行** (`Yank`) * `d` 输入一次即可剪切**选择的文本**,`D`也是会剪切**所有选择文本所在的行**的。(`Delete`) * `x` 和`d`操作一致 (给文本画叉叉) * `c` 和`d`操作一样,不过剪切完之后会**进入插入模式** * `p` 在选择的区域中**粘贴文本** ( `Paste` ) * **替换** <a id="visualMode-replace" for-anchor="true" title="替换r"></a> `r<字符>` 将所选文本替换为`<字符>` ( `Replace` ) ![visualReplace-2022-04-23](https://assets.xbottle.top/img/visualReplace-2022-04-23.gif) > 👆 `ra`将所选块文本全部替换为了`a` * **行连接** <a id="visualMode-joinRows" for-anchor="true" title="行连接J,gJ"></a> 这一部分和`命令模式/正常模式`下的[行连接指令](#normalMode-joinRows)几乎是一样了。 * `J` 将所选文本**对应的行**连接成一行,**用空格间隔** ( `Join` ) ![spaceJoinedRow-2022-04-23](https://assets.xbottle.top/img/spaceJoinedRow-2022-04-23.gif) * `gJ` 将所选文本**对应的行**连接成一行,**没有间隔** ![directlyJoinedRow-2022-04-23](https://assets.xbottle.top/img/directlyJoinedRow-2022-04-23.gif) * **执行`末行模式/命令行模式`指令** <a id="visualMode-commandLine" for-anchor="true" title="执行末行语句"></a> 在可视模式下选择文本后输入 `:` 进入末行模式,会**自动填充成** `:'<,'>`,这代表在**所选文字中进行末行语句操作**。 之前的搜索替换中就有提到过:[基本搜索替换->作用范围](#searchSub-substitute-scope) * **代码缩进** <a id="visualMode-indenting" for-anchor="true" title="调整代码缩进"></a> 在`命令模式/正常模式`下要对一行代码进行缩进操作,往往需要输入两次`>`或`<` 而在可视模式下只需要输入一次: ![visuallyIndent-2022-04-23](https://assets.xbottle.top/img/visuallyIndent-2022-04-23.gif) > 👆 `>` 将所选文本增加一次缩进 ![visuallyIndent2-2022-04-23](https://assets.xbottle.top/img/visuallyIndent2-2022-04-23.gif) > 👆 `<` 将所选文本减少一次缩进(因为选择不完全,只减少了一部分) ![visuallyIndent3-2022-04-23](https://assets.xbottle.top/img/visuallyIndent3-2022-04-23.gif) > 👆 `>` 将所选文本增加`2`次缩进 * **大小写转换** <a id="visualMode-caseSwitch" for-anchor="true" title="大小写转换u,U,~"></a> 这一部分和`命令模式/正常模式`下的[大小写转换指令](#normalMode-caseSwitch)是很类似的: * `u` 将所选文本转换为**小写** * `U` 将所选文本转换为**大写** ( `UPPERCASE` ) * `~` 将所选文本中的**大写**和**小写**分别转换为**小写**和**大写** ![caseSwitchInVisualMode-2022-04-23](https://assets.xbottle.top/img/caseSwitchInVisualMode-2022-04-23.gif) > 👆 演示了一下使用`~`进行大小写转换 可视模式还有很多用法,这里就不再写更多了(再写更多就成语法手册了喂!) ## In the end 回过头来...我又写了这么长一篇笔记...稍后我会完善一下目录功能以增加阅读体验。 这篇笔记虽然写得多,但是涉及的内容实际上还是**比较浅层**的,毕竟这篇笔记主要目的是为了记忆**常用方法**。也就是说,`Vi/Vim`的使用技巧远不止这么一点,更多高级的操作还需日常多加使用才能更加熟练! 因个人能力极其有限,可能文章中会出现部分错误。如果有写错的地方,也请大家多指教! 最后...感谢你读到这里,再会~ ![bye-2022-04-23](https://assets.xbottle.top/img/bye-2022-04-23.png) <script src="https://cdn.imbottle.com/static_files/fastfood/catalogue.js"></script> <script>/* let specialCata=document.getElementById('specialCatalogue'); if(specialCata){ Catalogue.init(document.querySelector('.markdown-body')); specialCata.addEventListener('click',()=>{ Catalogue.show(); },false); }; bblock.s(); */</script> {(PostContentEnd)} {(PostTag)}学习,小记,Vim,Linux{(PostTagEnd)} {(PostID)}282{(PostIDEnd)} {(PostCover)}none{(PostCoverEnd)} {(PubTime)}1650726560267{(PubTimeEnd)} {(EditTime)}1653044152253{(EditTimeEnd)}
{(PageType)}post.otp.html{(PageTypeEnd)}