Neovim 快捷键:移动与跳转

Neovim 快捷键:不用鼠标的移动与跳转

这是 Neovim 的第二部分,专门讨论普通模式下的移动。目标不是背下一张很长的按键表,而是建立一条可靠的路线:到达目标 → 阅读或修改 → 回到原处

本文默认使用 Linux,并且只介绍 Neovim/Vim 内置的行为。先按 Esc 回到普通模式;下文的按键都在普通模式执行。<C-d> 表示同时按下 Ctrld

先记住:移动有不同的“距离”

在长文件里,按 j 很多次通常不是问题的解法。先判断目标离自己多远,再选合适的工具:

距离或目标 优先按键 用途
当前行附近 hjkl 左、下、上、右
一个单词 wbe 下个词首、上个词首、词尾
当前行的端点 0^$ 行首、首个非空白字符、行尾
文件中的某个词 /关键词nN 搜索、下一个、上一个匹配
文件另一处 ggG{数字}G 文件开头、结尾、指定行
看过后返回 <C-o> ``、'' 跳转历史、精确返回、按行返回
改过后返回 `.g; 最近一次修改、较早的修改位置

数字可以放在多数移动命令前。例如 5j 向下五行,3w 向后移动三个单词,20G 到第 20 行。先把这套“距离感”用熟,比记更多命令更重要。

让光标和屏幕各司其职

jk 移动的是光标;滚动屏幕时,最好不要每一行都带着光标走。

按键 作用 常见场景
<C-d> / <C-u> 向下 / 向上滚动半屏,光标随之移动 连续阅读函数或调用链
<C-f> / <C-b> 向下 / 向上翻整屏 快速略过大段内容
<C-e> / <C-y> 屏幕向下 / 向上滚动一行,光标不动 想看相邻上下文,又不离开当前编辑点
H / M / L 光标跳到当前屏幕的顶部 / 中间 / 底部 在已看见的代码中快速换一个阅读落点
zz 让当前行置于屏幕中央 到达目标后开始阅读,最常用
zt / zb 让当前行置顶 / 置底 需要为下方 / 上方内容留出空间

HML 移动的是光标,依据的是当前看得见的屏幕区域;ztzzzb 则固定光标,并调整屏幕的位置。一个很顺手的习惯是:搜索或跳转到目标后按 zz。光标不会贴着屏幕边缘,阅读代码的上下文也更完整。

在一行和一个代码块中跳

单词、字符与行内查找

w 到下一个单词开头,b 回到上一个单词开头,e 到当前或下一个单词结尾。它们适合编辑标识符、参数和短语。

已知目标字符在当前行时,f{字符} 比搜索整份文件更快:f, 跳到下一个逗号,F, 反向跳到上一个逗号;t, 则停在逗号前一个字符,T, 是反向版本。按 ; 重复上一次 f/t 查找,按 , 反向重复。

例如在下面一行中,光标在 formatInvoice 的开头,输入 f( 能到第一个左括号;输入 t, 能停在第一个逗号前,方便继续操作。

const text = formatInvoice(invoice, locale, options)

括号、段落与文件边界

% 在成对的 ()[]{} 之间跳转。在嵌套的函数或对象中,这是理解范围最快的内置命令之一:站在 { 上按 %,立刻看到代码块的结尾;再按一次就回来。

{} 在段落之间向前、向后跳。在有空行分隔的说明文字、注释或函数块中很有用。gg 到文件开头,G 到文件末尾,{数字}G 到指定行;例如 120G

[[]] 会在文件的“章节”或代码块边界间移动。它们对有明确结构的文件特别方便,但具体识别方式会受文件类型和缩进习惯影响;不确定时,用 / 搜索函数名更可靠。

搜索:最通用的文件内跳转

输入 /renderInvoice 再按回车,跳到下一个匹配;?renderInvoice 则向上搜索。随后:

按键 作用
n / N 同方向 / 反方向的下一个匹配
* / # 搜索光标下的完整单词,并向后 / 向前跳
g* / g# 搜索光标下的文本,可匹配单词的一部分
:noh 清除搜索高亮,不会改变当前位置

搜索本身是一种跳转。搜索一个函数名、看完实现后,可以用 <C-o> 回到搜索之前的位置;不需要重新猜测自己从哪里来。

标记:给重要位置命名

跳转历史会变化;当某个位置之后还要多次回来时,用标记(mark)更稳妥。ma 表示把当前位置记为小写标记 a

按键 去向
ma 在当前位置设置标记 a
`a 精确跳到标记 a,保留列位置
'a 跳到标记 a 所在行的首个非空白字符
:marks 查看已有标记

小写标记只在当前文件有效。大写标记可以跨文件:例如在 api.ts 中用 mA 记住入口,再在任何文件输入 `A 回来。常用做法是:阅读一个复杂函数时用 ma 标出入口,跳去检查几个调用点,最后用 `a 精确回到刚才的光标列。

跳转历史:去了哪里,就能怎么回来

Neovim 会记录许多会跨越较大距离的移动,例如搜索、ggG、使用标记和跳到定义。这个记录叫作跳转列表(jumplist)。

按键 作用
<C-o> 跳到较旧的位置,也就是“后退”
<C-i> 跳到较新的位置,也就是“前进”
:jumps 查看跳转列表
`` 回到上一次跳转前的精确位置
'' 回到上一次跳转前的位置所在行

这里有两套互补的返回方式:

  • 刚做完一次跳转,想立刻原路回去,用 ``;它保留列位置。
  • 已经跳了好几次,想沿着来路逐步回退,用 <C-o>;如果退过头了,用 <C-i> 前进。

'' `` 的区别只在落点:前者落到目标行的首个非空白字符,后者回到原来的列。编辑代码时通常优先使用反引号版本。

修改历史:回到“刚才改过的地方”

跳转列表回答的是“我去过哪里”,修改列表(changelist)回答的是“我改过哪里”。两者不要混用。

按键 作用
`. 精确跳到最近一次修改的位置
'. 跳到最近一次修改所在行的首个非空白字符
g; 前往更早的一次修改位置
g, 前往更晚的一次修改位置
:changes 查看修改列表

例如你在文件开头改完一个类型定义,随后 G 去文件末尾阅读。此时输入 `.,就能回到刚才改动的精确位置。若今天修了多个地方,用 g; 连续回溯;看完后用 g, 沿反方向前进。

注意 `.. 很像,但含义完全不同:`. 是跳转到最近修改处,.重复上一次修改操作。前者用于定位,后者用于编辑。

一个完整的工作路线

假设你正在阅读一个很长的 invoice.ts,要确认 renderInvoice 的实现,并修改其中一处格式化逻辑。

  1. /renderInvoice<Enter> 搜索函数名;到达后按 zz,把函数放到屏幕中央。
  2. 在函数体的 { 上按 %,先确认这个函数在哪里结束;再按 % 回到开头。
  3. 输入 ma,为这个阅读起点建立一个临时标记。
  4. <C-d> 向下阅读半屏;遇到同名字段时,按 *nN 在各个匹配处切换。
  5. 修改某一行后,如果又跳到别处,按 `. 回到这次修改;想回到最初阅读点,则按 `a
  6. 最后若要检查搜索之前的位置,按 <C-o>;若只需要撤回刚刚那一次跳转,则按 ``。

这条路线的关键是:临时目标用搜索,固定锚点用标记,来回路径用跳转列表,修改点用修改列表。

五分钟练习

新建一个临时 TypeScript 文件,粘贴下面的内容。不要追求一次按对;每一步都观察状态栏和光标位置的变化。

export function renderInvoice(invoice: Invoice, locale: string) {
  const amount = formatAmount(invoice.total, locale)
  const title = `Invoice: ${invoice.id}`

  return `${title}${amount}`
}

export function formatAmount(amount: number, locale: string) {
  return new Intl.NumberFormat(locale, {
    style: 'currency',
    currency: 'CNY',
  }).format(amount)
}

export function sendInvoice(invoice: Invoice) {
  return renderInvoice(invoice, 'zh-CN')
}
  1. /formatAmount<Enter> 找到函数定义,按 zz
  2. new Intl.NumberFormat(...) 的左括号上按 %,观察跳到哪里,再按 % 返回。
  3. currency: 'CNY' 一行按 ma;随后 gg 到文件开头,再用 `a 回来。
  4. CNY 改成另一种货币,随后 G 到文件结尾,再按 `. 回到改动处。
  5. g; 回顾更早的修改位置,再用 g, 回到较新的位置。
  6. 输入 :jumps:changes,亲眼比较两份历史记录。

速查表

目的 按键
半屏阅读 <C-d> / <C-u>
跳到当前屏幕顶部 / 中间 / 底部 H / M / L
当前行居中 / 置顶 / 置底 zz / zt / zb
搜索与继续搜索 /文本nN
搜索光标下单词 * / #
成对括号间跳转 %
文件开头 / 结尾 / 指定行 gg / G / {数字}G
行内找字符并重复 f{字符};,
设置 / 精确跳到标记 ma / `a
跳转历史后退 / 前进 <C-o> / <C-i>
回到上次跳转前 ``
回到最近修改处 `.
在修改历史中后退 / 前进 g; / g,
查看跳转 / 修改历史 :jumps / :changes

快捷键忘记时不必离开编辑器:在 Neovim 中输入 :help {命令},例如 :help jumplist:help changelist:help g;,可以直接查看内置说明。