GitHub
2021.04.17 07:54:52字数: 25986

vim使用指南

下面主要是针对vscode-vim的使用指南,1-9节的内容是通用的,但是后面的内容是vscode-vim特有的功能。

01.最基本操作

vim有三种模式:

  1. ESC进入正常模式(normal)
  2. 在正常模式下按i,进入插入模式(insert)
  3. 在正常模式下按:键进入命令模式

三个模式里面只有插入模式是用于输入文本的,正常模式是用于编辑文本的,包括移动光标,复制粘贴删除等 命令模式可以用来执行搜索,批量替换等

在正常模式下,移动光标可以用以下几个字幕或者方向键

           ↑
     ← h j k l →
         ↓

在插入模式下,按 <ESC>, <CTRL-[> or <CTRL-C> 返回正常模式

在vim中打开文件

  • :e <filename> 打开名为 filename 的文件,若文件不存在则创建之
  • :Ex 在 Vim 中打开目录树,光标选中后回车打开对应文件(提示:- 进入上级目录)

显示buffer列表

你可以用 :buffers:ls 命令查看

  • :bn 打开缓存中下一个文件
  • :bp 打开缓存中上一个文件
  • :b<N> 打开缓存中第 N 个文件
  • 你也可以使用 :bdelete<N> 来删除所要关闭的缓冲区,缩写 :bd<N>

保存文件

  • :w 保存当前文件

  • :wa 保存全部文件

  • :wq or ZZ 保存并退出,或者:x也是保存并退出

  • :q! or ZQ 强制退出,不保存

  • :saveas <new filename> 文件另存为

  • :w <new filename> 文件另存一份名为 <new filename> 的副本并继续编辑原文件

简单设置vim

“工欲善其事,必先利其器”。尽管 Vim 非常强大,但默认配置的 Vim 看起来还是比较朴素的,为了适合 我们的开发需求,要对 Vim 进行一些简单的配置。

  • :set number 显示行号
  • :set relativenumber 显示相对行号(这个非常重要,慢慢体会)
  • :set hlsearch 搜索结果高亮
  • :set autoindent 自动缩进
  • :set smartindent 智能缩进
  • :set tabstop=4 设置 tab 制表符所占宽度为 4
  • :set softtabstop=4 设置按 tab 时缩进的宽度为 4
  • :set shiftwidth=4 设置自动缩进宽度为 4
  • :set expandtab 缩进时将 tab 制表符转换为空格
  • :filetype on 开启文件类型检测
  • :syntax on 开启语法高亮

这里列出的是命令,你可以通过在 Vim 中输入进行设置,但这种方式设置的参数只在本次关闭 Vim 前生效, 如果你退出 Vim 再打开,之前的设置就失效了。

若要永久生效,需要修改 Vim 的一个自动配置文件,一般文件路径是 /home/<user>/.vimrc(Linux 系统)或 /Users/<user>/.vimrc(Mac OS 系统)

如果没有就新建一个,以 Mac OS 系统为例:

在控制台执行如下命令,每行结尾记得回车

cd ~
vim .vimrc

现在你已经在 Vim 中打开了你的 Vim 专属配置文件,将上面提到的配置复制到你的文件中,记得要删除 每行开头的 :

修改完成执行 :wq 或者 ZZ 保存退出,再次进入 Vim 时,你的这些配置就已经生效了

当然,机智如我也为你准备好了一份 vimrc 样本文件,你可以在控制台执行 cp vimrc.vim ~/.vimrc 直接使用,再次启动 Vim 或在 Vim 中执行 :source ~/.vimrc你的配置就 应该生效了。

[ AD ] 当然你也可以在我维护的另外一个项目 The 7th Vim 中找到一个更为完整的配置方案。

清除搜索高亮

前面提到的配置中,有一项是高亮全部搜索结果 :set hlsearch,其作用是当你执行 /?*# 搜索后高亮所有匹配结果。

如果你已经设置了这个选项,尝试执行 /set

看到效果了吧,搜索结果一目了然,但这有时候也是一种困扰,因为知道搜索结果后高亮就没用了,但高亮 本人并不这样认为,它会一直高亮下去,直到你用 :set nohlsearch 将其关闭。

但这样需要就打开,不需要就关闭也不是个办法,有没有更好的解决方案呢?当然!请看下面的终极答案:

再搜一个不存在的字符串

通常我用来清除搜索高亮的命令是 /lfw,一是因为 lfw 这个组合一般不会出现(不适用于 本文档...),二是这三个字母的组合按起来比较舒服,手指基本不需要怎么移动(你感受一下)。

重复上一次命令

Vim 有一个特殊的命令 .,你可以用它重复执行上一个命令。

缩进

  • >> 向右缩进当前行
  • << 向左缩进当前行

自动排版

  • == 自动排版当前行
  • gg=G 当前文档全文自动排版
  • <N>== 对从当前行开始的 N 行进行自动排版
  • =<N>j 对当前行以及向下 N 行进行自动排版
  • =<N>k 对当前行以及向上 N 行进行自动排版

窗口分屏

工作中经常会遇到这种情况,就是需要参照其他文档编辑当前文档(场景:翻译),或者从另外一个文档 copy 代码到当前文档(场景:复制 html 元素类名到 css 文档),这时候就是你最需要分屏的时候。

分屏方式

  • :split 缩写 :sp or Ctrl-w s 上下分屏
  • :vsplit 缩写 :vs or Ctrl-w v 左右分屏
  • :diffsplit 缩写 :diffs diff 模式打开一个分屏,后面可以加上 {filename}

窗口跳转

  • Ctrl-w w 激活下一个窗口
  • Ctrl-w j 激活下方窗口
  • Ctrl-w k 激活上方窗口
  • Ctrl-w h 激活左侧窗口
  • Ctrl-w l 激活右侧窗口

移动分屏

  • Ctrl-w L 移动到最右侧
  • Ctrl-w H 移动到最左侧
  • Ctrl-w K 移动到顶部
  • Ctrl-w J 移动到底部

注意:区分大小写。另外,可以将底部的屏幕移动到右侧,实现上下分屏到左右分屏的转换。

屏幕缩放

  • Ctrl-w = 平均窗口尺寸
  • Ctrl-w + 增加高度
  • Ctrl-w - 缩减高度
  • Ctrl-w _ 最大高度
  • Ctrl-w > 增加宽度
  • Ctrl-w < 缩减宽度
  • Ctrl-w | 最大宽度

标签页

创建标签页

  • :tabnew or :tabedit 缩写 :tabe 打开新标签页
  • Ctrl-w gf 在新标签页中打开当前光标所在位置的文件名

注意::tabnew:tabedit 后面都可以跟一个 <空格><文件名> 用以在新标签页中 打开指定文件,还可以在 : 后面加一个数字,指出新标签页在列表中的位置(从 0 开始)。

切换标签页

  • gt or :tabnext 缩写 :tabn 下一个标签页(最后一个会循环到第一个)
  • gT or :tabprevious 缩写 :tabp 上一个标签页(第一个会循环到最后一个)
  • :tabrewind 缩写 :tabr or :tabfirst 缩写 :tabfir 到第一个
  • :tablast 缩写 :tabl 到最后一个标签页

关闭标签页

  • :tabclose 缩写 :tabc 关闭当前标签页
  • :-tabc 关闭上一个标签页
  • :+tabc 关闭下一个标签页
  • :tabonly 缩写 :tabo 关闭其他标签

02.更快地移动光标 motion

w ,b ,W, B, e, g,e 以单词为间隔移动光标

  • w是后移
  • b是向前
  • W是后移包含符号的单词
  • B是前移包含符号的单词
  • e 移动到单词的尾部
  • ge 移动到上一个单词的尾部

w和W的区别是word和words的区别,word只包括a-zA-Z0-9,words还包含空字符,空格,EOL等

使用查找来跳转

  • f 移动到本行下一个特定的字母和符号,F 移动到本行前一个特定的字母
  • 你也可以使用tT,他的光标在搜索的字母之前

当你使用了f{character}之后,你可以用;向后重复查找,或者使用,向前重复查找

  • f 在同一行向后搜索第一个匹配

  • F 在同一行向前搜索第一个匹配

  • t 在同一行向后搜索第一个匹配,并停在匹配前

  • T 在同一行向前搜索第一个匹配,并停在匹配前

  • ; 在同一行重复最近一次搜索

  • , 在同一行向相反方向重复最近一次搜索

  • /{pattern} 向下搜索

  • ?{pattern} 向上搜索

  • *向后查找当前光标所在的单词

  • #向前查找当前光标所在的单词

    输入上面的按回车就进入搜索模式,可以用n(向下)和N在搜索到的词之间移动

    注意n的方向性和你之前查找的方向是一致的

    查找会高亮词汇,:nohl命令可以取消高亮

匹配查找

Vim 中可以使用 %()[]{} 进行匹配查找,当光标位于其中一个 符号上时,按下 %,光标会跳到与之匹配的另外一个符号上

0 ^ $ g_ 移动到行末或行尾

  • 0 移动到这行的最开头字母
  • ^ 移动到本行最开头的非空字母
  • $ 移动到本行最尾部
  • g_移动到本行最尾部的非空字母
  • n|跳转到本行第n列

( )以句子或段落为间隔移动

  • ( 跳到前一个句子
  • ) 跳到下一个句子
  • { 跳转到上一个段落
  • } 跳转到下一个段落

滚动页面,翻页 CTRL+D CTRL+U

  • Ctrl-y 向上滚动一行
  • Ctrl-e 向下滚动一行
  • Ctrl-u 向上滚动半屏
  • Ctrl-d 向下滚动半屏
  • Ctrl-b 向上滚动一屏
  • Ctrl-f 向下滚动一屏

也可以相对于当前行进行滚动

  • zt 将当前行置于屏幕顶部附近
  • zz 将当前行置于屏幕中央
  • zb 将当前行置于屏幕底部

按照数字移动

数字在通常模式里,可以重复执行一个命令多次。

{count}{command}

比如2w就是向下移动两个单词

5j,就是向下移动5行

2/count就是跳转到第二个搜索到的count位置

使用命令跳转

  • :<line number>跳转到第几行 ,相当于 <line number>gg
  • :+<line number>向下跳转几行 ,相当于 <line number>j
  • :-<line number>向上跳转到第几行 ,相当于 <line number>k

gd跳转到定义,gf跳转到文件名所指向的文件

{line}gg 跳转到指定行

  • gg 跳转到文件开头
  • G 跳转到文件结尾
  • {line}gg 跳转到文件指定行,或者也可以用{line}G
  • %,跳转到 匹配括号的开始和结尾({[]})
  • n%,文件百分比跳转

m标记位置

比如ma就是用a标签标记一个位置,你之后就可以用``a`来跳转到你标记的a位置

其中

  • ``a`是精确回到a标签的位置(行和列)
  • 'a 跳转到a标签的行

语法是

m{a-zA-Z}

其中小写字母是局部标签,大写字母是全局标签

使用:marks查看所有标签

窗格间跳转

  • H 跳转到屏幕的顶部
  • M 跳转到屏幕的中间
  • L 跳转到屏幕的底部
  • nH 跳转到距离顶部n行的位置
  • nL 跳转到距离底部n行的位置

代码跳转

  • ctrl+]跳转到函数或者变量定义处
  • ctrl+octrl+T返回跳转前位置
  • gi跳转到之前离开normal模式的位置(也就是上次编辑的位置了)
  • gI跳转到之前离开normal模式的那一行开头并进入normal模式
  • gd 跳转到函数或者变量定义处
  • gf 跳转到文件

03.编辑相关操作

关于编辑操作的语法是这样的

{operator}{count}{motion}
  • operator是具体的操作,比如delete,change,
  • count 是操作执行的次数
  • motion是操作执行的位置

举个例子,d2w是向下删除两个单词

这些操作可以用一些英语首字母帮助记忆

  • d for delete
  • f for find
  • c for change
  • t for until

d 剪切(删除)操作

d其实是剪切的功能,只不过剪切之后不粘贴回去相当于删除。

  • 删除当前行 dd
  • dw删除下一个单词
  • ggdG删除整个文件,拆解一下就好理解,gg跳到文件开头,然后执行删除,删到文件末尾。
  • D删除当前行光标之后的内容,等价于d$
  • s 删除当前字符,并进入 INSERT 模式
  • S 删除当前行并保存到 Vim 剪贴板,同时进入 INSERT 模式(等同于 cc
  • x 删除当前字符,相当于 insert 模式下的 Delete
  • X 删除前一个字符,相当于 insert 模式下的 `Backspace
  • cc 删除当前行并保存到 Vim 剪贴板,同时进入 INSERT 模式
  • c<X> 删除指定内容并保存到 Vim 剪贴板,同时进入 INSERT

u 撤销前一步操作 CTRL+R重做前一步

  • U撤销所有操作,使文件回到原样
  • 在插入模式下可以使用 Ctrl-G u 会生成一个断点,之后你就可以用u还原到这个断点
  • :wundo 保存一份撤销历史记录,它会创建一个**.undo**文件,里面存放的是你的历史记录。这样当你打开一个新文件的时候,可以通过 :rundo 来加载 undo 历史。
  • Vim 可以通过变量 undolevels 来选择最多可执行 undo 的次数。你可以通过 :echo &undolevels 来查看当前的配置。

时间旅行

你可以使用 :undolist 去查看之前所做的修改。 :earlier 可以加上分钟 (m), 小时 (h), and 天 (d) 作为参数。

:earlier 10s    恢复到10秒前的状态
:earlier 10m    恢复到10分钟前的状态
:earlier 10h    恢复到10小时前的状态
:earlier 10d    恢复到10天前的状态

c 删除并进入插入模式

  • cc删除一整行并进入插入模式
  • C删除本行光标后的内容并进入插入模式
  • cw删除下一个单词并进入插入模式

. 重复上次产生改变的操作

以下几种类型会被视为一个可以重复的改变

  • d{motion}

  • i{typeSomething}<ESC>

  • c{typeSomething}<ESC>

使用c带来的可重复性比d会高很多

举个例子,当我们想修改下面这句话的courteouslypolite,我们移动到单词开头执行cwpoliteESC,就可以完成替换的工作,然后我们就可以用.操作符重复这个替换操作

const courteousSalute = "I courteously salute you good person."

效率更高的是我们搜索和操作符,这样省去了我们移动光标找单词的时间

合并

  • J 将当前行与下一行合并

替换操作

  • r<X> 将当前字符替换为 X
  • gu<X> 将指定的文本转换为小写
  • gU<X> 将指定的文本转换为大写
  • :%s/<search>/<replace>/ 查找 search 内容并替换为 replace 内容
  • g~<X>,小写变成大写,大写变成小写。

文本对象

文本对象就是结构化的文本,它描述一个文档中的单词,句子,段落,被引号的文本,html的tags等,你可以用这个和操作符结合来改变一个单词,句子,段落等等

            |- `a` means around
            |- `i` means inner
           /
          /
         /
        {a|i}{text-object}
                  /
                 /
                | w - word
                | s - sentence
                | p - paragraph
                | " - quotes
                | t - html tags

和操作符结合就是这样:

{operator}{a|i}{text-object}

举个例子,假设你要修改下面的字符串,只要fR移动到R字母,然后执行ci",就可以输入你想输入的内容了。也就是修改引号内的内容。

const poem = "Roses are red";

实际上上述操作还可以优化,因为文本对象,是自动应用的和你光标的位置无关,所以第一步的移动操作不需要。所以这个操作的可重复性,会比单纯的c要高。

",'',和反引号操作符比较特殊,一般的wsp只有光标在文本对象上才会自动查找,而引号在同一行的前面和后面都没问题。

关于around的作用是,用a代替i会影响到分隔符和空格比如引号,大括号,小括号作为文本对象的时候会把分隔符和空格一起删除,而用i不会删除分割符和空格

其他操作

  • y (yank)复制
    • yy 复制一行,和d,c这些键的逻辑是一样的
    • Y 复制光标到行尾的内容
  • p (put)粘贴
  • g~ (switch case): 把字母从小写变成大写
  • > (shift right): 添加缩进
  • < (shift left): 移除缩进
  • = (format code): 格式化代码

更多简写

  • x 相当于 dl 删除光标下的字母
  • X 相当于 to dh 删除光标前的字母
  • s 相当于 ch, deletes 删除光标前的字母并且进入插入模式
  • r 将一个字母替换为另一个
  • ~切换一个字母的大小写

A nice way use case for x is to swap a couple of characters when you make a typo. You remove (and cut) a character with x and immediately paste it after the cursor with p. Try it!

04.输入模式操作(insert)

i,a,I,A四种插入:

  • i 插入到光标所在字母
  • a 插入到光标后
  • I 插入到行首
  • A 插入到行尾

o ,O开启新一行

  • o 在当前行下方开新行
  • O 在当前行上方开新行

gi 回到上次离开插入模式的地方开始输入

我们发现g这个字母,通常用作其他操作的修饰符,比如e和ge,e是到单词的结尾,ge是到上一个单词结尾。

还有一些命令

  • s 删除当前光标位置的字符并开始输入文本
  • S 删除当前行并开始输入文本
  • gI 在当前行的第一列的位置开始输入文本

插入模式的删除快捷键

这个快捷键小写也是生效的

  • CTRL-H 删除上次输入的字符并向左移动光标
  • CTRL-W 清楚上一个word
  • CTRL-U 删除上次输入的行

退出插入模式

  • <ESC>
  • CTRL-C
  • CTRL-[

很多vim用户会习惯会在输入模式中把ESC映射到jj或者jk

重复输入模式

在进入输入模式之前传递一个数字参数,比如

10i

你输入的文本将被重复10次。这个方法对任意一种进入输入模式的方式都有效

在输入模式中使用寄存器

举例:Ctrl-r a

Ctrl-r {register}

在输入模式下执行普通模式的命令

在输入模式下, 如果你按下Ctrl-o,你就会进入到insert-normal(输入-普通)子模式。如果你关注一下左下角的模式指示器,通常你将看到-- INSERT -- ,但是按下Ctrl-o后就会变为-- (insert) --。 在这一模式下,你可以执行一条普通模式的命令,比如你可以做以下这些事:

设置居中以及跳转

Ctrl-o zz       居中窗口
Ctrl-o H/M/L    跳转到窗口的顶部/中部/底部
Ctrl-o 'a       跳转到标志'a处

重复文本

Ctrl-o 100ihello    输入 "hello" 100 次

执行终端命令

Ctrl-o !! curl https://google.com    运行curl命令
Ctrl-o !! pwd                        运行pwd命令

快速删除

Ctrl-o dtz    从当前位置开始删除文本,直到遇到字母"z"
Ctrl-o D      从当前位置开始删除文本,直到行末

05.视觉模式选中文本(visual mode)

  • v 字母长度的选择
  • V 选择行
  • Ctrl-V 块选择模式

选择的语法如下

get into Visual Mode
    /
   /                  action to apply
  /                         /
---------                  /
{v|V|C-V}{count}{motion}{operator}
         ---------------
           /
          /
   bit of text over which
   to apply an action

在视觉模式中,可以用 oO切换光标的位置

ctrl+v块选择模式

块选择模式比较特殊,适合批量在前方或尾部插入的场景。

如果需要在每行末尾添加分号:

const one = "one"
const two = "two"
const three = "three"

将光标放在第一行上: -运行逐块可视模式,并向下两行(Ctrl-V jj)。 -高亮显示到行尾($)。 -附加(A) ,然后键入";"。 -退出可视模式(esc)。

总体来说在视觉模式下的操作会更加直观,相比于normal模式能清晰地看到自己想要编辑的区域。

Vim有Ctrl-XCtrl-A命令来减少和增加数字。与可视模式一起使用时,可以跨多行递增数字。

`<    转到上一个可视模式高亮显示的最后一个位置
`>    转到上一个可视模式高亮显示的第一位

您也可以从插入模式进入可视模式。在插入模式下进入字符可视模式:

Ctrl-O v

常用操作

  • ggvG全选

06.更迅速地执行搜索操作

之前我们已经学到了用/进入搜索,按下回车后,可以用n在搜索的关键词之间移动。此时我们可以执行一个修改操作,然后n到下一个用.操作复现。下面有一种更便捷的方式

gn 选中下一个搜索块

有了gn以后,我们就不需要按一下n在复现删除

我们直接搜索,然后gn选中第一个搜索关键词,在删除,之后用.就可以复现选择关键词然后删除的操作。

可以dgn也可以gnd,效果是一样的,然后 4.可以重复这个操作4词

07.复制和粘贴

  • y复制

  • pP分别是向下拷贝和向上拷贝

gpgP粘贴的时候光标位置不同,光标会在粘贴的内容往后一个字符的位置,也就是会包含换行符

dcp实现剪切和粘贴

vim寄存器 Vim Registers

  • unnamed register " 是拷贝或者剪切的内容不指定寄存器时默认存放的位置
  • named registers a-z ,命名寄存器,你可以为接下来剪切或者复制的内容指定特定的a-z的字符的寄存器
  • yank register 0 存储了你上次复制的东西
  • cut registers 1-9 存储了你之前9次剪切操作的内容,包括d和c操作

举个例子 "ayas会复制一个句子,然后存储在寄存器a,也就是说语法是"{register}后面跟你的复制剪切操作就会把东西存到指定的寄存器里,当你想粘贴的时候,"ap,也是同样的

使用寄存器的大写字母,表示继续往那个寄存器添加复制或剪切的内容。

查看寄存器的内容

:reg命令可以查看你的所有寄存器,是:register的缩写 ,你也可以查看指定寄存器的内容:reg {register}

更多寄存器相关的内容

实际上vim一共有下面10种寄存器

  1. 匿名寄存器(""
  2. 编号寄存器("0-9).
  3. 小删除寄存器 ("-).
  4. 命名寄存器 ("a-z).
  5. 只读寄存器 (":, ".,and "%).
  6. Buffer交替文件寄存器 ("#).
  7. 表达式寄存器 ("=).
  8. 选取和拖放寄存器("* and "+).
  9. 黑洞寄存器 ("_).
  10. 搜索模式寄存器 ("/).

匿名寄存器

匿名寄存器默认存储着你最近一次复制,修改或删除的文本。直接使用p,就是粘贴匿名寄存器的内容。相当于""p

编号寄存器

编号寄存器1-9是自动以升序来填充的,也就是说存放你的上9次剪切操作,0标号的寄存器只会存上次复制的内容

小删除寄存器

不足一行的修改或者删除都不会被存储在0-9号编号寄存器中,而是会被存储在小删除寄存器 ("-)中。 命名寄存器 ("a-z)

命名寄存器是Vim中用法最丰富的寄存器。a-z命名寄存器可以存储复制的,修改的和被删除的文本。不像之前介绍的3种寄存器一样,它们会自动将文本存储到寄存器中,你需要显式地告诉Vim你要使用命名寄存器,你拥有完整的控制权。

只读寄存器(":, "., "%)

  • · 存储上一个输入的文本
  • : 存储上一次执行的命令
  • % 存储当前文件的文件名

Buffer交替文件寄存器 ("#)

在Vim中,#通常代表交替文件。交替文件指的是你上一个打开的文件,想要插入交替文件的名字的话,可以使用命令"#p

表达式寄存器 ("=)

Vim有一个表达式寄存器,"=,用于计算表达式的结果。表达式是Vim中非常宏大的一个话题,所以我只会在这里介绍一些基础知识,我将会在之后的章节中进一步讲解更多关于表达式的细节。

你可以使用以下命令计算数学表达式1+1的值:

"=1+1<Enter>p

在这里,你在告诉Vim你正在使用表达式寄存器"=,你的表达式是(1+1),你还需要输入p来的到结果。正如之前所提到的,你也可以在输入模式中访问寄存器。想要在输入模式中计算数学表达式的值,你可以使用:

Ctrl-r =1+1

你可以使用@来从任何寄存器中获取表达式并用表达式寄存器计算其值。如果你希望从寄存器"a"中获取文本:

"=@a

之后输入<enter>,再输入p。类似地,想在输入模式中得到寄存器"a"中的值可以使用:

Ctrl-r =@a

你也能使用表达式寄存器来计算Vim脚本的值。如果你使用:let i = 1定义一个变量i,你可以用"=i获取到它的值,按下回车,再按下p。想在输入模式中获取到这个值的话可以运行命令Ctrl-r=i

假设你有一个方程:

function! HelloFunc()
	return "Hello Vim Script!"
endfunction

你可以通过调用这个方程获取它的值。想要在普通模式中调用这个方程,你可以使用:"=HelloFunc(), 按下回车再按下p。而在输入模式下可以使用Ctrl-r =HelloFunc()

选取和拖放寄存器 ("*, "+)

当你向从外部程序复制粘贴一些文本到vim中是,可以使用这两个寄存器,但是你的vim需要开启+clipboard才有用

如果你觉得这个操作比较麻烦,你也可以配置vim使得只用p也能粘贴外部程序复制的文本,在vimrc中加入下面一行就可以了:

set clipboard=unnamed

这样你从外部程序中复制文本时,就可以使用匿名寄存器p来进行粘贴

黑洞寄存器 ("_)

如果你删除和修改文本的时候,不希望这次修改的内容被存到寄存器里,可以用黑洞寄存器

想要删除一行并且不将其存储在任何寄存器中时,可以使用"_dd命令,它是和 /dev/null 类似的寄存器。

搜索模式寄存器 ("/)

这个寄存器里保存了你上一次搜索的内容

执行寄存器中存储的命令,命名寄存器中存放的内容可以当作宏命令来执行,使用@来执行

清除寄存器

从技术上来说,我们没有必要来清除任何寄存器,因为你下一个使用来存储文本的寄存器会自动覆盖该寄存器中之前的内容。然而,你可以通过记录一个空的宏命令来快速地清除任何命名寄存器。比如,如果你运行qaq,Vim就会在寄存器"a"中记录一个空的宏命令。还有一种方法就是运行命令:call setreg('a',''),其中'a'代表的就是寄存器"a"。还有一种清除寄存器的方法就是使用表达式:let @a = ''来将寄存器的值设为空的字符串。

获取寄存器中的内容

你可以使用:put命令来粘贴任何寄存器的内容。比如,如果你运行命令:put a,Vim就会打印出寄存器"a"的内容,这和"ap非常像,唯一的区别在于在普通模式下命令p在当前光标位置之后打印寄存器的内容,而:put新起一行来打印寄存器的内容。

08.命令行模式

命令行模式支持使用扩展命令(ex commands)的特性(:开启)

举例一些常用的扩展命令

  • :colorscheme 查看vim当前使用的主题
  • edit 编辑文件,后面跟上文件路径就可以打开一个新的buffer来编辑文件

编辑文件扩展命令

:edit {relative-path-to-file} 

执行这个命令后如果不存在文件那么就会新建一个再打开,这个命令的简写是:e

保存文件和关闭文件命令

  • :write简写是:w,用于保存文件
  • :quit简写是:q,用于关闭文件
  • :wall简写是:wa,用于保存所有文件
  • :qall简写是:qa,用于关闭所有文件
  • :wqall :wqa,用于保存并关闭所有文件
  • :qall! :qa!,强制退出所有文件不保存

如果你想强制执行忽略错误,那么就在前面或后面加上!,比如!q就是强制退出 你也可以组合两个命令,比如 wq就是保存后关闭

同时删除多行

文本扩展命令的形式如下:

:[range]command[options]

其中,我们要执行删除操作的时候,命令的形式会变成如下:

:[range]d [register]

其中寄存器是存放我们剪切的内容的 举个例子,执行:10,12d a,那么10-12行的内容就会被删除,剪切的内容存放在a寄存器,可以使用"ap粘贴就还原了。

具体还有如下的使用方式

  • 使用数字(e.g. :10,12d 会删除行 10, 11 , 12)
  • 使用偏移值 (e.g. :10,+2d 会删除行 10, 11 , 12)
  • 使用 **.**表示当前行 (e.g. :.,+2d 会删除当前行和接下来的两行)
  • 使用 **%**表示整个文件 (e.g. :%d 会删除整个文件)
  • 使用**0**表示整个文件的开始 (e.g. :0,+10d 会删除前十行)
  • 使用**$**表示文件的结尾 (e.g. :.,$d 会删除从当前行到文件结尾的内容)
  • 当使用visual mode选中文本后再输入 : :'<,'> 表示你选中的范围, 执行 :'<,'>d 就可以删除当前选中的内容

其他也有一些有用的命令包括 :yank, :put, :copy and :move

重复执行命令

使用 @: 可以重复执行你上一个命令,之后你可以用@@再次重复执行

替换文本

语法如下

:[range]s/{pattern}/{substitute}/{flags}
  • range定义了执行替换的范围
  • pattern 匹配替换内容的正则表达式
  • substitute 是我们想要替换的文本
  • flags 是用于配置替换文本的选项

举个例子

:s/led/gold 会替换当前行第一个匹配到的led为gold

如果你想要替换当前行所有的匹配,那么加一个g flag执行 :s/led/gold/g

如果你想替换整个文件的所有匹配,用%表示整个文件的范围,执行:%s/led/gold/g

出了g以外,还有其他flag:

  • i,忽略大小写
  • c,每次替换需要确认

09.宏命令

在编辑文件的时候,你会发现有时候你在反复地做一些相同的动作。如果你仅做一次,并在需要的时候调用这些动作岂不是会更好吗。通过 Vim 的宏命令,你可以将一些动作记录到 Vim 寄存器。

基本宏命令

宏命令的基本语法如下:

`qa`                     开始记录动作到寄存器 a
`q` (while recording)    停止记录

你可以使用小写字母 (a-z)去存储宏命令。并通过如下的命令去调用:

`@a`    Execute macro from register a
`@@`    Execute the last executed macros

假设你有如下的文本,你打算将每一行中的所有字母都变为大写。

hello
vim
macros
are
awesome

将你的光标移动到 “hello” 栏的行首,并执行:

qa0gU$jq

上面命令的分解如下:

  • qa 开始记录一个宏定义并存储在 a 寄存器。
  • 0 移动到行首。
  • gU$ 将从光标到行尾的字母变为大写。
  • j 移动到下一行。
  • q 停止记录。

调用 @a 去执行该宏命令。就像其他的宏命令一样,你也可以为该命令加一个计数。例如,你可以通过 3@a 去执行 a 命令3次。你也可以执行 3@@ 去执行上一次执行过的宏命令3次。

安全保护

在执行遇到错误的时候,宏命令会自动停止。假如你有如下文本:

a. chocolate donut
b. mochi donut
c. powdered sugar donut
d. plain donut

你想将每一行的第一个词变为大写,你可以使用如下的宏命令:

qa0W~jq

上面命令的分解如下:

  • qa 开始记录一个宏定义并存储在 a 寄存器。
  • 0 移动到行首。
  • W 移动到下一个单词。
  • ~ 将光标选中的单词变为大写。
  • j 移动到下一行。
  • q 停止记录。

我喜欢对宏命令进行很多次的调用,所以我通常使用 99@a 命令去执行该宏命令99次。当 Vim 在最后一行执行 j 命令的时候,会发现已经没有下一行可以继续,遇到执行的错误,因此宏命令会停止。

实际上,遇到错误自动停止运行是一个很好的特性。否则,Vim 会继续执行该命令99次,尽管它已经执行到最后一行了。

命令行执行宏

在正常模式执行 @a 并不是宏命令调用的唯一方式。你也可以在命令行执行 :normal @a:normal 会将任何用户添加的参数作为命令去执行。例如添加 @a,和在 normal mode 执行 @a 的效果是一样的。

:normal 命令也支持范围参数。你可以在选择的范围内去执行宏命令。如果你只想在第二行和第三行执行宏命令,你可以执行 :2,3 normal @a。我会在后续的章节中介绍更多关于在命令行中执行的命令。

在多个文件中执行宏命令

假如你有很多的 .txt 文件,每一个文件包含不同的内容。并且你只想将包含有 “donut” 单词的行的第一个单词变为大写。那么,该如何在很多文件中特定的行执行执行变该操作呢?

第一个文件:

# savory.txt
a. cheddar jalapeno donut
b. mac n cheese donut
c. fried dumpling

第二个文件:

# sweet.txt
a. chocolate donut
b. chocolate pancake
c. powdered sugar donut

第三个文件:

# plain.txt
a. wheat bread
b. plain donut

你可以这么做:

  • :args *.txt 查找当前目录下的所有 .txt 文件。
  • :argdo g/donut/normal @a 在所有 :args 中包含的文件里执行一个全局命令 g/donut/normal @a
  • :argdo update 在所有 :args 中包含的文件里执行 update 命令会将修改后的内容保存下来。

如果你对全局命令 :g/donut/normal @a 不是很了解的话,该命令会在包含有 /donut/ 中的所有行执行normal @a 命令。我会在后面的章节中介绍全局命令。

递归执行宏命令

你可以递归地执行宏命令,通过在记录宏命令时调用相同的宏来实现。假如你有如下文本,你希望改变第一个单词的大小写:

a. chocolate donut
b. mochi donut
c. powdered sugar donut
d. plain donut

如下命令会递归地执行:

qaqqa0W~j@aq

上面命令的分解如下:

  • qaq 记录一个空白的宏命令到 “a” 。把宏命令记录在一个空白的命令中是必须的,因为你不会想将该命令包含有任何其他的东西。
  • qa 开始录入宏命令到寄存器 “a”。
  • 0 移动到行首。
  • W 移动到下一个单词。
  • ~ 改变光标选中的单词的大小写。
  • j 移动到下一行。
  • @a 执行宏命令 “a”。当你记录该宏命令时,@a 应该是空白的,因为你刚刚调用了 qaq
  • q 停止记录。

现在,让我们调用 @a 来查看 Vim 如何递归的调用该宏命令。

宏命令是如何知道何时停止呢?当宏执行到最后一行并尝试 j 命令时,发现已经没有下一行了,就会停止执行。

增添一个已知宏

如果你想在一个已经录制好的宏定义中添加更多的操作,与其重新录入它,不如选择修改它。假设你不仅希望将第一个单词变为大写,也希望在每一行末尾添加一个句点。

假设当前寄存器“a”中有如下的命令:

0W~

你可以这样做:

qAA.<esc>q

分解如下:

  • qA 开始在寄存器 “A” 中记录宏命令。
  • A.<esc> 在行的末尾(A)假如一个句点,并且退出插入模式。
  • q 停止记录宏命令。

现在,当你执行 @a 时,它会跳到行的第一个字符(0),跳到下一个单词(W),改变光标选中的字母的大小写(~),移动到最后一行并且转到插入模式(A),写入一个句点(.),退出插入模式(<esc>)。

修改一个已知宏

在已存在的宏定义的末尾添加新的动作是一个很好的功能,但假如你希望在一个宏命令的中间添加动作该怎么做呢?

假设,在改变第一个单词的大小写和在末尾加入一个句点之间,你想要在单词 “donut” 之前加入 “deep fried”(因为唯一比甜甜圈好的东西就是炸甜甜圈)。

我会重新使用上一节使用过的文本:

a. chocolate donut
b. mochi donut
c. powdered sugar donut
d. plain donut

首先,让我们通过 :put a 调用一个已经录制好的宏命令(假设你已经有了上一节中使用过的宏命令):

0W~A.^[

^[ 是什么意思呢?不记得了吗,你之前执行过 0W~A.<esc>^[ 是 Vim 的内部指令,表示 <esc>。通过这些指定的键值组合,Vim 知道这些是内部代码的一些替代。一些常见的内部指令具有类似的替代,例如 <esc><backspace><enter>。还有一些其他的键值组合,但这不是本章的内容。

回到宏命令,在改变大小写之后的键后面(~),让我们添加($)来移动光标到行末,回退一个单词(b),进入插入模式(i),输入“deep fried ”(别忽略“fried ” 后面的这个空格),之后退出插入模式(<esc>)。

完整的命令如下:

0W~$bideep fried <esc>A.^[

这里有一个问题,Vim 不能理解 <esc>。所以你需要将其替换为内部代码的形式。在插入模式,在按下<esc>后按下 Ctrl-v,Vim 会打印 ^[Ctrl-v 是一个插入模式的操作符,可以逐字地插入一个非数字字符。你的宏命令应该如下:

0W~$bideep fried ^[A.^[

为了在寄存器“a”中添加修改后的指令,你可以通过在一个已知寄存器中添加一个新入口的方式来实现。在一行的行首,执行 "ay$。这将会告诉 Vim 你打算使用寄存器 “a” ("a) 来存储从当前位置到行末的文本(y$)。

现在,但你执行 @a 时,你的宏命令会自动改变第一个单词的大小写,在“donut”前面添加“deep fried”,之后在行末添加“.”。

另一个修改宏命令的方式是通过命令行解析。执行 :let @a=",之后执行 Ctrl-r Ctrl-r a,这会将寄存器“a”的命令逐字打印出来。最后,别忘记在闭合的引号(")。如果你希望在编辑命令行表达式时插入内部码来使用特定的字符,你可以使用 Ctrl-v

拷贝宏

你可以很轻松的将一个寄存器的内容拷贝到另一个寄存器。例如,你可以使用 :let @z = @a 将寄存器“a” 中的命令拷贝到寄存器“z”。 @a 表示寄存器“a”中存储的内容,你现在执行 @z,将会执行和 @a 一样的指令。

我发现对常用的宏命令创建冗余是很有用的。在我的工作流程中,我通常在前7个字母(a-g)上创建宏命令,并且我经常不加思索地把它们替换了。因此,如果我将很有用的宏命令移动到了字母表的末尾,就不用担心我在无意间把他们替换了。

连续执行宏命令

Vim 可以连续和同时运行宏命令,假设你有如下的文本:

import { FUNC1 } from "library1";
import { FUNC2 } from "library2";
import { FUNC3 } from "library3";
import { FUNC4 } from "library4";
import { FUNC5 } from "library5";

假如你希望把所有的 “FUNC” 字符变为小写,那么宏命令为如下:

qa0f{gui{jq

分解如下:

  • qa 开始记录宏命令到 “a” 寄存器。
  • 0移动到第一行。
  • f{ 查找第一个 “{” 字符。
  • gui{ 把括号内的文本(i{)变为小写(gu)。
  • j 移动到下一行。
  • q 停止记录宏命令。

现在,执行 99@a 在剩余的行修改。然而,假如在你的文本里有如下 import 语句会怎么样呢?

import { FUNC1 } from "library1";
import { FUNC2 } from "library2";
import { FUNC3 } from "library3";
import foo from "bar";
import { FUNC4 } from "library4";
import { FUNC5 } from "library5";

执行 99@a,会只在前三行执行。而最后两行不会被执行,因为在执行第四行(包含“foo”)时会遇到错误而停止。然而这种情况你希望继续向下执行。你可以移动到包含(“FUNC4”)的一行,并重新调用该命令。但是假如你希望仅调用一次命令就完成所有操作呢?你可以并行地执行宏命令。

如本章前面所说,可以使用 :normal 去执行宏命令,(例如: :3,5 normal @a 会在 3-5行执行 a 寄存器中的宏命令)。如果执行 :1,$ normal @a,会在所有除了包含有 “foo” 的行执行,而且它不会出错。

尽管本质上来说,Vim 并不是在并行地执行宏命令,但表面上看,它是并行运行的。 Vim 会独立地在从第一行开始(1,$)每一行执行 @a 。由于 Vim 独立地在每一行执行命令,每一行都不会知道有一行(包含“foo”)会遇到执行错误。

10.切换分屏(splits)和标签(tabs)

分屏(splits)

执行下面的命令可以实现分屏显示

  • :sp {relative-path-to-file} 水平分屏打开一个文件
  • :vsp {relative-path-to-file} 垂直分屏打开一个文件

除了命令的方式,你也可以使用快捷键

  • <CTRL-W> S 打开一个水平分屏(上下分屏)的窗口
  • <CTRL-W> V 打开一个垂直分屏(所有分屏)的窗口

在vscde的vim插件里面可以用 **CTRL-P*在分屏里打开新文件

使用 CTRL-W + **hjkl**在分屏见移动

标签(tabs)

在分屏里面也可以打开标签

使用命令打开标签的方式是

  • :tabnew {file} 在新标签中打开文件
  • :tabn (:tabnext) 切换到下一个标签
  • :tabp (:tabprevious) 切换到前一个标签
  • :tabo (:tabonly) 关闭所有其他标签

11.vim-surround插件的使用

VSCodeVim自带了这个插件,这个插件带来了一个新操作符s,surroundings值得是包裹单词句子的符号,比如引号,花括号还有html标签(quotes, parenthesis, braces, tags, etc…)等

  • ds 删除surroundings
  • cs 修改 surroundings
  • ys 添加 surroundings

具体的语法如下

ds{count}{motion}
cs{count}{motion}
ys{count}{motion}

举个例子

  • ds' 删除包围的 ' (ds{char})
  • cs'" 替换包围的 '" (cs{old}{new})
  • ysaptli> 包围一段话用 <li> 标签 (ys{motion}{char})

也可以在v视觉模式下选中然后 S{desired character},就可以用你喜欢的符号包围了。

12.自定义按键映射

自定义映射可以把按键调整到你更舒服的位置

在vscode里面的编辑步骤如下

首先找到设置

  1. 打开命令面板 CMD-SHIFT-P 或者 CTRL-SHIFT-P
  2. 输入preferences(或者在菜单栏也可以打开)
  3. 选择 Open User Settings Options
  4. 输入 vim

可以 编辑三种模式的自定义映射:

  • Normal Mode Key Bindings Non Recursive for normal mode
  • Visual Mode Key Bindings Non Recursive for visual mode
  • Insert Mode Key Bindings Non Recursive for insert mode

举个例子,自定义normal模式的映射

{
  "vim.insertModeKeyBindingsNonRecursive": [
    {
      "before": ["j", "k"],
      "after": ["<ESC>"]
    }
  ],
}
  • before 就是你输入的命令
  • after 就是你映射的值,当你输入before里的值后就会执行after中映射的键

在这个例子里,你可以在插入模式里把j和k当作ESC使用,不过这个设置就不是很实用,因为在插入模式里面j和k的输入需求也不少

关于创建自定义映射的指导方针

自定义映射比较灵活自由,但是不好的设置可能会给自己带来麻烦。

一般来说,遵循以下3个原则会比较好:

  • 自定义映射用leader key打头来触发,一般是反斜杠\,这样就不会和已有的键位发生冲突了
  • 如果你的工作流程中有经常使用的按键,那么映射替换vim默认的按键也是可以的。
  • 尽量使用更容易记忆的映射,比如vim的 c表示change,d表示delete

自定义前导键(leader key)

你也可以把leader key改成比反斜杠更容易输入的键。

比如说你想把leader key改成空格键

{
"vim.leader": "<Space>",
}

下面比较好的映射的例子

让正常模式的上下移动更快

这个改动遵循了一个vim的一个理念,就是大写字母命令会比小写版本的更强。

所以大写的J会比小写的移动更快。

但是我们在这里覆盖了一个比较有用的vim默认按键J,这个按键的作用是合并当前行和下一行。但是这个键的使用频率其实不高

K键是用作关键词搜索,不过在VSCodeVim中还没有实现这个功能

{
  "vim.normalModeKeyBindingsNonRecursive": [
    {
      "before": ["J"],
      "after": ["5", "j"]
    },
    {
      "before": ["K"],
      "after": ["5", "k"]
    },
  ]
}

为了保持合并行功能,我们把它加到leader key里

{
  "vim.normalModeKeyBindingsNonRecursive": [
    {
      "before": ["<Leader>", "j"],
      "after": ["J"]
    },
  ]
}

在分屏间快速切换

{
  "vim.normalModeKeyBindingsNonRecursive": [
    {
      "before": ["<C-h>"],
      "after": ["<C-w>", "h"]
    },
    {
      "before": ["<C-j>"],
      "after": ["<C-w>", "j"]
    },
    {
      "before": ["<C-k>"],
      "after": ["<C-w>", "k"]
    },
    {
      "before": ["<C-l>"],
      "after": ["<C-w>", "l"]
    }]
}

处理标签更方便

因为VSCodeVim中处理标签唯一的方式是命令模式输入命令,所以你可以用按键映射来代替这一点

commands键就是用来替换命令模式的扩展命令的

{
  "vim.normalModeKeyBindingsNonRecursive": [
    {
      "before": ["<Leader>", "t", "t"],
      "commands": [":tabnew"]
    },
    {
      "before": ["<Leader>", "t", "n"],
      "commands": [":tabnext"]
    },
    {
      "before": ["<Leader>", "t", "p"],
      "commands": [":tabprev"]
    },
    {
      "before": ["<Leader>", "t", "o"],
      "commands": [":tabo"]
    }]
}

清除文本高亮

当你使用搜索命令的时候(/?),匹配项会被高亮,为了清除高亮你需要扩展命令:noh(no higlighting)

这个任务很常见,我们可以创建下面的按键映射

{
  "vim.normalModeKeyBindingsNonRecursive": [
    {
      "before": ["<Leader>", "/"],
      "commands": [":noh"]
    }]
}

这里的助记法是,斜杠是被用来搜索的,所以前导键加斜杠就是和搜索最相关的撤销高亮

使用自定义映射影响vscode的action

下面的例子就是用 leader+w来触发保存文件

{
  "vim.normalModeKeyBindingsNonRecursive": [
    {
      "before": ["leader", "w"],
      "commands": [
          "workbench.action.files.save",
      ]
    }
}

找到设置Preferences: Open Keyboard Shortcuts,你就可以看到vscode里面命令的全名,然后你就可以替换你想替换的命令了。

下面是例子

CTRL-SHIFT-P是打开命令面板,我们替换成前导键+p

CTRL-TGo To Symbol in Workspace,我们替换成前导键+t

{
  "vim.normalModeKeyBindingsNonRecursive": [
    {
      "before": ["<Leader>", "p"],
      "commands": [
          "workbench.action.showCommands",
      ]
    },
    {
      "before": ["<Leader>", "t"],
      "commands": [
          "workbench.action.gotoSymbol",
      ]
    }
  ]
}

13.使用vim插件更快的移动

Vim-sneakvim-EasyMotion 是能够帮助你在vim里快速移动的插件

VSCodeVim 内置了这两个插件,不过需要你在设置里开启才能使用。

14.增强vscode的文件浏览器

<CTRL-W> h 切换到vscode的文件浏览器

  • kj 在文件浏览器的条目中上下移动
  • Expand directories with
  • l (right) 用于展开文件夹
  • h (left) 用于折叠文件夹
  • 使用 l (right)打开光标下的文件

记住vscode的其他快捷键会很有用

  • Go To File with CMD-P in Mac (or CTRL-P in Windows/Linux)
  • Go To Symbol in File with CMD-SHIFT-O in Mac (or CTRL-SHIFT-O in Windows/Linux)
  • Go To Symbol in Workspace with with CMD-T in Mac (or CTRL-T in Windows/Linux)

你在弹出的面板里面也可以用k和j或者tab选择,enter进入

目录