Advanced Vim

Posted by Cao Zihang on December 12, 2024 Word Count:

Practical Vim Notes

VIM实用技巧 第二版 笔记

.命令

上一次修改可以指代很多东西,修改的范围不仅仅是字符,还可以是整行,甚至是整个文件。

>G命令会增加从当前行到文档末尾处的缩进层级。.命令会对当前行重新执行>G命令。

进入插入模式 (i)也会形成一次修改,Vim会记录直到返回普通模式<Esc>的所有按键命令。在这样的修改后,使用.命令会重新执行所有按键操作。

在插入模式中,使用了上下左右方向键,会产生新的撤销块,等价于<Esc>+移动后重新i

.命令可以视作一个微型的宏。

*命令会查找当前光标下的单次。

.命令最佳范式 使用一次按键移动,另一次按键执行。

普通模式

[count]<Ctrl-a>:对当前光标之上或之后的数值添加[count]

[count]<Ctrl-x>:对当前光标之上或之后的数值减少[count]

0开头的数字会被Vim认为八进制。

l字符、aw单词、as句子、ap段落;与d结合删除,与g~/gu/gU结合转换大小写。

>增加缩进,<减少缩进,=自动缩进。

插入模式

插入模式下,<ctrl-w>删除前一个单词,<ctrl-u>删除至行首。

插入-普通模式

在插入模式中,使用<Ctrl-o>可暂时进入普通模式,执行一次命令后回到插入模式。

例如:<Ctrl-o>zz使当前行滚动到屏幕中心。

插入模式下使用寄存器

<Ctrl-r>{register}

常用:上次复制内容<Ctrl-r>0

其他特殊寄存器:

  • "未命名寄存器(上次复制或删除)
  • %当前文件名
  • #上次打开的文件名
  • +系统剪贴板
  • /上次搜索内容
  • .上次插入的文本
  • :上次执行的命令

<Ctrl-r>{register}对于大量文本会出现延迟,也可能出现不必要的换行或额外缩进。

(不好用) <Ctrl-r><Ctrl-p>{register}按原义插入寄存器内文本,并修正不必要的缩进。

表达式寄存器

插入模式中:<Ctrl-r>=访问表达式寄存器,可以在其中进行运算,回车键将结果插入到光标处。

可视模式

  • <Ctrl-v>激活面相列块的可视模式
  • gv重新选择上次高亮选区
  • o切换活动端点

可视化模式,特别是块模式下,擅长处理表格等数据。

<Ctrl-v>可以同时在多行进行编辑。

Vr-将整行替换为等长的-。

<Ctrl-v>多行编辑时,插入操作只会在顶行显示,但实际会修改所有行。

$可以打破<Ctrl-v>方形限制。

可视模式下,ia会被视作一个文本对象的组成部分,不会进入插入模式。

命令行模式

  • :[range]join连接指定范围内的行
  • :[range]normal {commands}对范围内每一行执行普通模式命令
  • :[range]s(ubstitute)/pattern/replacement/[flags]对范围内每一行执行替换
  • :[range]global/{pattern}/[cmd]在指定范围内匹配的所有行,在其上执行命令行命令

命令行中的特殊行:

  • .当前行
  • $最后一行
  • ^第一行
  • %所有行
  • 0文件头部虚拟行
  • 'm包含位置标记m的行
  • '<高亮选区起始行
  • '>高亮选区结束行

使用模式指定范围

例如::/<html>/,/<\/html>/[command]处理标签内所有行。

范围可以进行偏移

e.g. :/<html>/+1,/<\/html>/-1[command]处理html标签内所有行,但不包括标签本身。

常用命令简写

  • :copy -> :t
  • :move -> :m

重复上次的命令行命令:@:,使用<Ctrl-o>撤销重复。

使用:[range]normal [.]在多行中执行.命令或其他normal模式命令。

<Tab>会智能补全,多次按<Tab>会遍历候选项。

在Vim命令行模式中,在命令前添加!前缀就可以调用shell外部程序。

e.g. :!ls

不添加!执行的是Vim内置命令,如ls显示的是Vim缓冲区列表。

添加read前缀会将命令的输出写入缓冲区。

e.g.:read !{cmd}

文件

遍历缓冲区列表

  • :bp, :bn 缓冲区列表中移动
  • :bf, bl 跳转到缓冲区列表的首尾

  • :[range]db删除缓冲区(不会影响文件),其中[range]为ls中输出的buffer分配编号

选择文件可以使用占位符:

  • *匹配0个或多个字符,但仅限于指定目录,不会递归其子目录
  • **匹配0个或多个字符,包括子目录

分割窗口

  • <Ctrl-w>v垂直分割窗口
  • <Ctrl-w>s水平分割窗口
  • :sp {file_name}水平分割窗口并打开文件
  • :vs {file_name}垂直分割窗口并打开文件

切换分割窗口

  • <Ctrl-w>w窗口间循环切换 (可以直接按住<Ctrl>+ww两次)
  • <Ctrl-w>h/j/k/l切换到左右上下窗口

关闭窗口

  • :clo[se]<Ctrl-w>c关闭活动窗口
  • :on[ly]<Ctrl-w>o关闭除活动窗口外的所有窗口

调整窗口

  • <Ctrl-w>=所有窗口等宽、等高
  • <Ctrl-w>_将活动窗口调整到最大高度
  • <Ctrl-w>|将活动窗口调整到最大宽度
  • [N]<Ctrl-w>_将活动窗口的高度调整到N行
  • [N]<Ctrl-w>|将活动窗口的宽度调整到N列

打开文件

Vim具有工作目录的概念,可以通过:pwd打印当前工作目录。

使用:edit {path}基于工作目录打开文件,使用<Tab>可以补全。

  • :e(dit) %<Tab>会展开为当前活动窗口的完整文件路径
  • :e(dit) %:h<Tab>不会包含文件名

使用edit指定的目录中包含不存在的目录,仍然会打开空白文件,但不能直接保存。需要调用外部:!mkdir -p %:h程序创建目录,其中-p参数使mkdir创建任何不存在的中间目录。

快速移动

在visual模式和操作符待决模式(d, c, y)中,使用i, a结合承成对限定符选中内容。(可选中多行)

atit在可视模式下会选中XML标签。

d{motion}aw,as,ap配合起来比较好;c{motion}i类命令结合效果会更好。

%命令在一组括号内跳转,作用于(), {}, []

修改括号的技巧:首先执行一次%命令,跳转到匹配的括号,修改后执行``跳转回括号。

  • (, )跳转到上/下一句开头
  • {, }跳转到上/下一段开头
  • H/M/L跳转到屏幕顶部、中、底部
  • <Ctrl-]>跳转到定义

位置标记

Vim会自动设置一些位置标记:

位置标记 描述
`` 上次跳转之前的位置
`. 上次修改的位置
`^ 上次插入的位置
`[ 上次修改或复制的起始位置
`] 上次修改或复制的结束位置
`< 上次高亮选区的起始位置
`> 上次高亮选区的结束位置
  • m{letter}在当前光标处创建位置标记
    • 小写字母创建局部缓冲区的标记
    • 大写字母创建全局标记(跨文件)

修改位置跳转

Vim会记录每次修改的行列号,使用:changes查看修改记录。

g;g,可以正反向在changes列表中遍历修改位置。

gi可以快速回到上次退出插入模式的位置,并进入插入模式。

跨文件跳转

<Ctrl-o> & <Ctrl-i>跳转(缓冲区列表)文件

:jumps查看跳转列表

寄存器

黑洞寄存器:删除内容并且不写入无明寄存器 -> "_d{motion}

复制专用寄存器("0):使用y命令时,会同步赋值,但仅当使用y命令时才会更新,不受x, s, c, d等命令影响 -> `“0p”

表达式寄存器 -> "=

选中一段内容后,使用p命令可以替换选中文本。

当宏执行过程中存在命令报错时,宏就会停止执行,无论还剩余多少次。

以并行的方式(命令行模式)执行宏,实际上每行都是单独的宏,当特定行出错时,不会影响其他行。

e.g. :'<,'>normal @a

修改宏

追加命令

使用大写字母追加命令到宏中:ex. qA + 追加命令 + q

修改宏

首先要将宏处理为文本,建议使用:put a的方式,相较于ap该方法会在下一行插入。

此时可以像修改文本一样进行修改。

通过:d a将宏文本读取到a中。(该方法会在寄存器末尾追加^J,通常没有大问题,可通过"ay$避免)

编号递增的宏

需要使用Vim脚本。

创建变量::let i = 1

查看变量值::echo i

递增变量::let i += 1

插入模式下,使用运算寄存器插入变量值:<Ctrl+r>=i<Enter>

ex.

  • :let i = 1
  • qa
  • I
  • <Ctrl+r>=i<Enter><Esc>
  • :let i += 1
  • q

模式

  • \cfoo不限制大小写,可以匹配foo,Foo,FOO
  • foo\C区分大小写,只能匹配foo

Vim支持magic搜索模式(i.e. \v后面的所有字符均具有特殊含义,无需转义),\x指代[0-9A-Fa-f] (详情见P201)

使用/键调出查找时,Vim会进行正向扫描;若使用?键,则进行反向扫描。

在查找模式中,按<up>键可以回溯之前的查找模式。

查询匹配数量:% s/{pattern}//gn

特殊符号:

  • \r:换行符
  • \t:制表符
  • \\:反斜杠
  • \1:第一个子匹配
  • \2:第二个子匹配 (至多到9)
  • \0&:整个匹配
    • 正常模式使用g&重新执行上次模式
  • ~:上次调用:s时的替换文本
    • :%s//~/&
  • \={Vim script}执行vim script表达式

flags: (可多个同时使用)

  • g全局
  • i忽略大小写
  • c每次确认替换
  • n计数

替换两文本

要求两个文本数量一致。

1
2
/\v(<A>|<B>)/\v(<A>|<B>)
:%s//\={"B":"A","B":"A"}[submatch(0)]/g

global命令

:[range] global[!] /{pattern}/ [cmd]

不同于其他ex命令,global命令范围的省缺时默认整个文件(%),不是当前行。

[cmd]可以是除了global外的所有ex命令,包括normal。

global!vglobal表示反选,即所有不匹配的行。

  • global简写为g
  • vglobalglobal!简写为v

ex. 匹配所有HTML标签,并删除:

1
2
3
/\v\<\/?\w+>
<!-- \w 为单词字符 -->
:g//d

ex. 将TODO行复制到寄存器a中

1
2
:g/TODO/yank A
<!-- 以附加的方式依次添加到寄存器中 -->

Vim内置:sort排序命令

ex. :'<,'>sort对当前选中的行按首字母排序

可以将:g/{pattern}匹配作为参考点,动态的设置[cmd]的[range]。

:g/{pattern}/[range] [cmd]

ex. :g/{/ .+1,/}/-1 sort

其中+1和-1都是行号偏移量。

grep

使用:grep可以在vim内部调用grep命令。