В прошлых главах мы разобрали философию модального редактирования и научились выживать: запускать редактор, переключать режимы и не паниковать при выходе. Теперь начинается настоящая работа. Если модальность - это сердце Vim, то движения (motions) - его кровеносная система. Без беглого перемещения по тексту все остальные возможности редактора остаются недосягаемыми.Часть I. Основы
Эта глава целиком посвящена нормальному режиму и одному вопросу: как переместить курсор туда, куда нужно, минимальным числом нажатий. Звучит скромно, но именно навигация отделяет человека, который "знает Vim", от человека, который думает на Vim. И что важнее всего: движения, которые вы выучите здесь, - это не просто способ ходить по тексту. В главе 05 они станут аргументами для операторов (d, c, y), и каждое движение превратится в команду редактирования. Выучив навигацию сейчас, вы закладываете фундамент под всю грамматику Vim.
Зачем это нужно: эффективность измеряется в нажатиях
Представьте, что курсор находится в начале строки, а вам нужно изменить слово в её середине. В обычном редакторе вы тянетесь к мыши или зажимаете стрелку. В Vim вы говорите редактору: "к третьему слову" или "к открывающей скобке" - и оказываетесь там за один-два символа. Разница не в скорости набора как таковой, а в том, что вы перестаёте отвлекаться на механику перемещения и думаете о смысле правки.
Ключевая идея главы: в Vim не "двигают курсор по чуть-чуть", а выражают намерение - к слову, к строке, к символу, к структуре. Чем точнее ваш словарь движений, тем меньше вы зажимаете клавиши.
hjkl и почему не стрелки
Базовое перемещение на один символ выполняется четырьмя клавишами домашнего ряда:
Код: Выделить всё
Клавиша | Направление
---------+-------------
h | влево
j | вниз
k | вверх
l | вправо
Почему не стрелки, ведь они интуитивнее? Три причины, и все они про эффективность.
- Руки остаются на домашнем ряду. Стрелки находятся в правом нижнем углу клавиатуры. Каждый раз, когда вы тянетесь к ним, правая рука покидает основную позицию, а возврат стоит времени и сбивает ритм. hjkl доступны без единого движения кисти.
- hjkl комбинируются со счётчиками и операторами. Вы можете написать 5j (вниз на пять строк) или d3l (удалить три символа справа). Со стрелками такие конструкции либо не работают, либо работают неуклюже.
- Стрелки - это "обезболивающее", которое мешает учиться. Пока стрелки под рукой, мозг не запоминает hjkl. Поэтому общепринятая практика - на старте сознательно отключить стрелки (об этом в разделе про лучшие практики).
Движения по словам: w / b / e / ge
Перемещение посимвольно слишком мелкое. Чаще нужно прыгать словами:
Код: Выделить всё
Клавиша | Действие
---------+--------------------------------------------
w | в начало следующего слова (word - forward)
b | в начало предыдущего слова (back)
e | в конец текущего/следующего слова (end)
ge | в конец предыдущего слова
- word (строчные w/b/e/ge) - последовательность букв, цифр и подчёркиваний, либо последовательность знаков пунктуации. Граница между ними считается границей слова. То есть в foo.bar() курсор остановится отдельно на foo, на ., на bar, на (, на ).
- WORD (заглавные W/B/E) - последовательность любых непробельных символов, разделённая только пробелами/табуляцией/переводами строк. В foo.bar() всё это - одна WORD.
Код: Выделить всё
Клавиша | Действие
---------+--------------------------------
W | в начало следующей WORD
B | в начало предыдущей WORD
E | в конец текущей/следующей WORD
Код: Выделить всё
Было (| - курсор):
|const path = config.get("user.name");
Нажимаем w:
const |path = config.get("user.name");
Нажимаем w ещё раз:
const path |= config.get("user.name");
Из начала строки нажимаем W три раза:
const path = |config.get("user.name");
Чтобы увидеть настоящую разницу, нужен фрагмент без пробелов между сегментами - например config.get("user.name"). Встанем на его начало:
Код: Выделить всё
Было:
const path = |config.get("user.name");
Жмём w несколько раз - курсор останавливается на каждом сегменте:
config|.get(...) -> config.|get(...) -> config.get|(...)
-> config.get(|"...) -> config.get("|user.name") -> ...
А вот W из той же точки проскакивает весь блок целиком:
const path = config.get("user.name")|;
Движения по строке: 0 / ^ / $ / g_
Внутри одной строки есть четыре опорные точки:
Код: Выделить всё
Клавиша | Действие
---------+---------------------------------------------------+------------------------------------+--------------------
0 | в самый первый столбец строки (нулевая позиция)
^ | в первый непробельный символ строки
$ | в конец строки (последний символ)
g_ | в последний непробельный символ строки
{n}\ | | к столбцу номер n (например, 17\ | - на 17-й столбец)
Код: Выделить всё
Было:
return |computeTotal(items);
Нажимаем 0 (на столбец 1, на пробел):
| return computeTotal(items);
Нажимаем ^ (на первый значимый символ):
|return computeTotal(items);
Нажимаем $ (в конец):
return computeTotal(items);|
inclusive против exclusive: почему это важно уже сейчас
Эта глава постоянно ссылается на главу 05, где движения станут аргументами операторов (d, c, y). Чтобы предсказывать результат d{motion}, нужно знать одну вещь заранее: движения делятся на exclusive и inclusive.
- exclusive - целевой символ под курсором не попадает в выделение/удаление. Такие: w, b, 0, ^, ), } и большинство "вперёд по словам".
- inclusive - целевой символ включается. Такие: e, f, t, $.
Пока просто запомните деление; в главе 05 мы вернёмся к нему уже в контексте редактирования. Если хоть раз d{motion} отработал "на символ не так, как ожидалось", причина почти всегда здесь.
Поиск по строке: f / F / t / T / ; / ,
Это одни из самых недооценённых движений, и именно они отличают опытного пользователя. Они позволяют прыгнуть к конкретному символу в текущей строке:
Код: Выделить всё
Клавиша | Действие
---------+---------------------------------------------------------
f{char} | вперёд на символ {char} (find)
F{char} | назад на символ {char}
t{char} | вперёд до символа {char} (остановиться перед ним, till)
T{char} | назад до символа {char} (остановиться после него)
; | повторить последний f/F/t/T в том же направлении
, | повторить в противоположном направлении
f/t работают только в пределах текущей строки и принимают счётчик: 3fa - к третьей букве a справа.
Код: Выделить всё
Было:
|let result = items.filter(x => x.active).map(toDto);
Нажимаем f. (к первой точке):
let result = items|.filter(x => x.active).map(toDto);
Нажимаем ; (к следующей точке - повтор f.):
let result = items.filter(x => x.active)|.map(toDto);
Нажимаем , (вернуться к предыдущей - повтор в обратную сторону):
let result = items|.filter(x => x.active).map(toDto);
Движения по экрану: H / M / L, zz / zt / zb, прокрутка
Когда нужно переместиться в пределах видимой области, помогают три "якоря" экрана:
Код: Выделить всё
Клавиша | Действие
---------+----------------------------------
H | к верхней строке экрана (High)
M | к средней строке экрана (Middle)
L | к нижней строке экрана (Low)
Отдельный частый источник путаницы у новичков - визуально перенесённые (wrapped) строки. Когда одна логическая строка не помещается по ширине и переносится на несколько экранных строк, j/k/0/$ работают по логическим строкам и могут "перепрыгнуть" весь видимый перенос разом. Чтобы двигаться по тому, что видно на экране, есть "display-line" движения с префиксом g:
Код: Выделить всё
Клавиша | Действие
---------+-------------------------------
gj | вниз на одну экранную строку
gk | вверх на одну экранную строку
g0 | в начало экранной строки
g$ | в конец экранной строки
Часто требуется не двигать курсор по экрану, а наоборот - подвинуть экран под курсор, оставив курсор на месте по тексту:
Код: Выделить всё
Клавиша | Действие
---------+-------------------------------------------------------------
zz | прокрутить так, чтобы текущая строка стала по центру экрана
zt | текущая строка - наверх экрана (top)
zb | текущая строка - вниз экрана (bottom)
Для прокрутки на бо́льшие расстояния:
Код: Выделить всё
Клавиша | Действие
---------+---------------------------------
Ctrl-d | вниз на полэкрана (down)
Ctrl-u | вверх на полэкрана (up)
Ctrl-f | вперёд на целый экран (forward)
Ctrl-b | назад на целый экран (back)
Код: Выделить всё
Курсор где-то в середине большого файла.
Нажимаем zt - текущая строка уезжает наверх,
ниже открывается следующий блок кода для чтения.
Нажимаем Ctrl-d - спустились на полэкрана,
половина старого контекста ещё видна сверху.
Перемещение по всему файлу:
Код: Выделить всё
Клавиша | Действие
---------+---------------------------------------------------
gg | в начало файла (первая строка)
G | в конец файла (последняя строка)
{n}G | к строке номер n (например, 42G - на 42-ю строку)
{n}gg | то же, что {n}G
Отдельная звезда - команда %:
Код: Выделить всё
Клавиша | Действие
---------+-------------------------------------
% | прыжок к парной скобке - (), [], {}
Код: Выделить всё
Было (курсор явно на открывающей скобке):
function process|(items) {
// ... много строк ...
}
Нажимаем % - прыжок на парную закрывающую:
function process(items|) {
Перешли на { и нажимаем %:
function process(items) {
// ...
}| <- курсор на закрывающей фигурной скобке
Для навигации по структуре прозы и кода есть движения по предложениям и абзацам:
Код: Выделить всё
Клавиша | Действие
---------+--------------------------------------------------
) | к началу следующего предложения
( | к началу предыдущего предложения
} | к началу следующего абзаца (или к пустой строке)
{ | к началу предыдущего абзаца
Абзац - блок текста, отделённый пустыми строками. В коде { и } особенно удобны: они прыгают между логическими блоками (функциями, методами), разделёнными пустыми строками, и при этом гораздо "крупнее", чем построчное движение. Зажатой j тут не место - используйте }.
Ещё одна важная для главы 05 деталь: }/{, а также gg/G - это linewise-движения (они оперируют целыми строками, а не диапазоном символов). Поэтому d} удалит абзац построчно (целыми строками), а dG - от текущей строки до конца файла целыми строками, а не "от позиции курсора". Это отличает их от character-wise движений вроде w или f и прямо влияет на то, что именно вырежет оператор.
Прыжки по поискуНе путайте movement-команды {/}/(/) с текстовыми объектами i{/a( и т. п. - это разные вещи. Текстовые объекты ("внутри скобок", "вокруг абзаца") разбираются подробно в главе 06.
Поиск - это тоже движение, и одно из самых мощных. Беглый обзор (полноценно - в главе 10):
Код: Выделить всё
Клавиша | Действие
-----------+------------------------------------------------
/{шаблон} | искать {шаблон} вперёд
?{шаблон} | искать назад
n | к следующему совпадению (в направлении поиска)
N | к предыдущему совпадению
* | искать слово под курсором вперёд
# | искать слово под курсором назад
Поскольку поиск - это движение, он тоже комбинируется с операторами: d/end<Enter> удалит всё от курсора до слова end. Но об операторах - в главе 05.
После "дальних" прыжков (gg, G, /, %, переход по метке) Vim запоминает позицию в jumplist. Вернуться по истории прыжков можно через Ctrl-o (назад) и Ctrl-i (вперёд). Важный практический нюанс: Ctrl-i - это та же физическая клавиша, что и <Tab> (на уровне терминала они шлют один и тот же код), поэтому во многих терминалах их нельзя замапить независимо друг от друга. Это отдельная большая тема - навигации по истории перемещений посвящена глава 09.
Тесно связаны с jumplist и метки позиций. Vim сам ведёт несколько автоматических меток, к которым удобно возвращаться: ` (две обратные кавычки) - к позиции до последнего прыжка, '' (две одинарные кавычки) - к строке до последнего прыжка, а `. - к месту последней правки. Эти возвраты часто экономят время: например, прыгнули поиском "посмотреть объявление функции", а ` мгновенно вернул туда, где вы редактировали. Подробно метки разбираются в главе 09.
Лучшие практики 2026
Думайте движениями, а не нажатиями. Прежде чем нажать клавишу перемещения, спросите себя: "куда мне нужно - к слову, к символу, к строке, к структуре?" - и выберите соответствующее движение. Привычка зажимать j/k/h/l или стрелки - антипаттерн номер один. Почти всегда есть более выразительная альтернатива: счётчик (8j), абзац (}), полэкрана (Ctrl-d), точечный прыжок (f/t), поиск (/).
Включите относительную нумерацию строк. Опции number и relativenumber вместе дают "гибридный" режим: текущая строка показывает абсолютный номер, остальные - расстояние до неё. Это превращает счётные прыжки в рефлекс: видите слева 6, нажимаете 6j - и вы там. В Neovim это настраивается в init.lua так (конфигурация подробно разбирается в главе 20):
Код: Выделить всё
vim.opt.number = true
vim.opt.relativenumber = true
Код: Выделить всё
set number
set relativenumber
Код: Выделить всё
nnoremap <Up> <Nop>
nnoremap <Down> <Nop>
nnoremap <Left> <Nop>
nnoremap <Right> <Nop>
Центрируйте экран после дальних прыжков. Возьмите за рефлекс связку "прыжок + zz": после G, /, * или перехода по номеру строки нажмите zz, чтобы цель оказалась в центре с контекстом. Многие идут дальше и делают n и N автоцентрирующими через маппинги nnoremap n nzz и nnoremap N Nzz.
Соберите "лестницу" движений по дальности. Полезная ментальная модель - выбирать движение под расстояние: символ -> h/l, слово -> w/b/e, символ в строке -> f/t, строка целиком -> 0/^/$, абзац/блок -> {/}, полэкрана -> Ctrl-d/Ctrl-u, файл -> gg/G/{n}G, произвольное содержимое -> /. Чем больше расстояние, тем "крупнее" движение.
Частые ошибки
- Зажимать j/k вместо счётчика или абзаца. Если вы держите j, чтобы спуститься на десяток строк, остановитесь и подумайте: 10j, } или Ctrl-d сделают это точнее и без потери позиции глаза.
- Не различать 0 и ^. В коде с отступами 0 встаёт на пробел, а вам почти всегда нужен первый значимый символ - ^. Привыкайте к ^.
- Путать f и t. f{char} встаёт на символ, t{char} - перед ним. Для простой навигации разница невелика, но как только движение станет аргументом оператора (dt) против df)), ошибка изменит результат на один символ.
- Не пользоваться ; и ,. Многие нажимают f. снова и снова с нуля, забыв, что повтор поиска по строке - это ; (и , в обратную сторону). Это удваивает число нажатий на пустом месте.
- Игнорировать % при чтении вложенного кода. Считать парные скобки глазами - медленно и ошибкоопасно. % делает это мгновенно.
- Думать, что hjkl - основной способ перемещения. Это аварийный минимум. Если ваша навигация на 90% состоит из hjkl, вы ещё не "думаете движениями".
- Считать {/} текстовыми объектами. Как movement они прыгают по абзацам; текстовые объекты (i{, ap) - отдельная грамматическая конструкция (глава 06).
- Домашний ряд. Откройте любой файл и поставьте себе правило на 20 минут: перемещаться только клавишами нормального режима, без стрелок и мыши. Каждый раз, когда рука потянется к стрелке, останавливайтесь и ищите подходящее движение.
- word против WORD. Возьмите строку вида obj.method(arg1, arg2).chain["key"]. Пройдите её слева направо сначала клавишей w (считая остановки), затем W. Сравните, на сколько меньше нажатий потребовала W, и сформулируйте, когда какое движение уместнее.
- Стрельба по символам. В строке с несколькими запятыми и точками потренируйте f, плюс ; для перехода к каждой следующей запятой и , для возврата. Затем повторите с t и сравните, где встаёт курсор.
- Парные скобки. Найдите в коде функцию с вложенными блоками. Встаньте на открывающую ( или { и нажмите %, чтобы прыгнуть к парной. Пройдитесь так по нескольким уровням вложенности.
- Лестница дальности. Откройте файл на 200+ строк. Доберитесь до строки 150 четырьмя разными способами: 150G; повторными Ctrl-d; через / по уникальному слову на этой строке; через гибридную нумерацию и счётный прыжок {n}j. Отметьте, какой способ оказался быстрее для этого конкретного случая.
- Рефлекс центрирования. Включите relativenumber. Несколько раз прыгните по файлу через G, gg и /, каждый раз добавляя zz. Прочувствуйте, насколько комфортнее читать, когда цель в центре экрана.
- hjkl - базовое посимвольное движение и причина держать руки на домашнем ряду; стрелки уводят руку с позиции и не комбинируются со счётчиками/операторами. Но hjkl - аварийный минимум, а не основной инструмент.
- По словам: w/b/e/ge (с учётом пунктуации) и W/B/E (WORD - только по пробелам). Строчные "мельче", заглавные "крупнее".
- По строке: 0 (столбец 1), ^ (первый значимый), $ (конец), g_ (последний значимый), {n}| (к столбцу N). Для перенесённых строк - display-line движения gj/gk/g0/g$.
- Движения делятся на inclusive (e, f, t, $ - включают целевой символ) и exclusive (w, b, 0, ^ - не включают), а }/{/gg/G - linewise. Это критично для результата d{motion} в главе 05.
- Точечные прыжки по строке: f/F/t/T плюс ;/, для повтора - мощнейший и недооценённый инструмент.
- По экрану: H/M/L - якоря экрана; zz/zt/zb - позиционирование экрана под курсор; Ctrl-d/Ctrl-u (полэкрана, предпочтительно) и Ctrl-f/Ctrl-b (экран).
- По файлу: gg/G/{n}G; % - к парной скобке; (/) и {/} - по предложениям и абзацам.
- Поиск (/, ?, n, N, *, #) - это тоже движение, часто самый быстрый способ преодолеть расстояние (подробно - глава 10).
- Возврат по истории: Ctrl-o/Ctrl-i (jumplist; Ctrl-i == <Tab>), автоматические метки `/'' (предыдущая позиция) и `. (последняя правка) - подробно в главе 09.
- Практики 2026: думать движениями под расстояние, включить гибридный number+relativenumber, отключить стрелки и/или поставить hardtime.nvim, центрировать экран через zz после дальних прыжков.
- Всё, что вы выучили здесь, в главе 05 станет аргументами для операторов - навигация и есть фундамент грамматики Vim.