Редактирование кода - это не только правки в одном месте. Вы постоянно "прыгаете": посмотрел определение функции на 400 строк выше, вернулся к месту, где писал; открыл другой файл за конфигом, вернулся обратно; нашёл ошибку через /, исправил, продолжил. Если каждое такое перемещение делать заново - листать Ctrl-D, искать глазами, заново вводить номера строк - вы тратите внимание на навигацию, а не на сам код.
Vim запоминает, где вы были, что правили и куда сознательно поставили закладку. Три механизма закрывают эти задачи:
- Метки (marks) - именованные закладки на конкретные позиции, которые вы ставите сами.
- Jumplist - автоматическая история "больших" прыжков, по которой можно ходить вперёд-назад.
- Changelist - автоматическая история мест, где вы вносили правки.
Метки: именованные закладки
Метка - это сохранённая позиция (файл + строка + столбец) под однобуквенным именем. Вы ставите метку командой m и переходите к ней апострофом ' или обратным апострофом `.
Постановка и переход
m{буква} ставит метку. Чтобы вернуться, есть две команды, и разница между ними важна:
Код: Выделить всё
Команда | Куда переходит
---------+---------------------------------------------------------
a | Точно на позицию метки a (та же строка и столбец)
'a | На первый непробельный символ строки, где стоит метка a
Эта пара - ' против ` - единообразна во всём Vim: она же различает построчные и точные переходы для автометок, которые мы разберём ниже.
Код: Выделить всё
Было (курсор на строке 1, столбец 10):
function calculate(items) { <- курсор здесь, ставим: ma
...100 строк...
} <- сюда ушли поработать
Нажимаем: `a
Стало: курсор вернулся ровно на столбец 10 строки 1.
Нажимаем: 'a
Стало: курсор на 'f' (function) - первый непробельный символ строки 1.
Метки в нижнем регистре (a-z) локальны для буфера. Метка a, поставленная в main.go, не имеет отношения к метке a в utils.go - в каждом файле свой набор a-z. Это удобно для расстановки опорных точек внутри одного файла: "начало функции" на f, "то место, к которому надо вернуться" на r.
Подводный камень: уйдя в другой файл, вы "теряете" нижнерегистровые метки текущего - 'a в новом буфере сошлётся уже на его собственную метку a (или ни на что, если её там нет). Для перемещения между файлами нужны глобальные метки.
Глобальные метки A-Z
Метки в верхнем регистре (A-Z) глобальны: метка A указывает на конкретный файл и позицию в нём, где бы вы ни находились. Нажав `A из любого буфера, вы откроете нужный файл (если он закрыт) и прыгнете в запомненное место.
Это превращает заглавные метки в лёгкие закладки по проекту:
- C - на главный конфиг,
- T - на тест, который сейчас чинится,
- M - на main/точку входа.
Нумерованные метки '0-'9На практике 2026 заглавные метки часто используют как нативную, беззависимостную альтернативу плагину Harpoon для быстрой навигации по "активным" файлам проекта: один маппинг ставит следующую свободную метку A..Z, picker показывает их списком. Реализация опирается на nvim_get_mark() / nvim_buf_get_mark(). Учтите, что отдают они разное: nvim_get_mark() (глобальные метки A-Z) возвращает 4-кортеж [row, col, buffer, buffername] -> (0,0,0,''), если метка не задана; а nvim_buf_get_mark() (метки текущего буфера) возвращает 2-кортеж [row, col] -> (0,0), если метка не задана. Подробнее о пикерах - в главе 28.
Помимо a-z и A-Z, Vim/Neovim ведут нумерованные метки '0-'9 - их вы не ставите вручную, они приходят из shada/viminfo и отмечают позиции при выходе из недавних сессий:
- `0 - место курсора при последнем выходе из Vim/Neovim;
- `1 - то же для предыдущего сеанса, `2 - ещё раньше, и так до `9 (самый старый запомненный файл).
Просмотр и удаление меток
Команда :marks показывает таблицу всех меток с номерами строк, колонок и текстом строки:
Код: Выделить всё
:marks
mark line col file/text
a 12 4 const router = express.Router()
A 3 0 ~/proj/config.lua
' 8 0 (позиция до последнего прыжка)
. 45 12 (последняя правка)
^ 45 18 (последняя позиция вставки)
Удалять метки можно нативно:
Код: Выделить всё
Команда | Действие
-----------------+-----------------------------------------------------------------------------------------------------------------
dm a | Удалить метку a - команда нормального режима только Neovim (без перехода в командную строку)
dm A | Удалить глобальную метку A - тоже только Neovim
:delmarks a b c | Удалить метки a, b, c (работает и в Vim, и в Neovim)
:delmarks! | Удалить все метки буфера, кроме глобальных A-Z и нумерованных 0-9 (т.е. a-z и спецметки . ^ [ ] < > " ' и т.д.)
Автоматические метки: Vim ставит их за вас
Помимо именованных меток, Vim постоянно поддерживает набор автоматических меток - позиций, которые он запоминает сам. Они адресуются через ` / ' со спецсимволами вместо буквы. Это, пожалуй, самая недооценённая часть навигации.
Код: Выделить всё
Метка | Что означает
-------+------------------------------------------------------------------
| Позиция до последнего прыжка (две обратные кавычки)
. | Позиция последней правки
^ | Позиция, где последний раз были в режиме вставки
[ | Начало последнего изменённого/скопированного (yank/paste) текста
] | Конец последнего изменённого/скопированного текста
< | Начало последнего визуального выделения
> | Конец последнего визуального выделения
" | Позиция курсора при последнем выходе из буфера
Двойной обратный апостроф ` - одна из самых частых команд за день. Он переключает между текущей позицией и той, что была до последнего прыжка. Прыгнули в начало файла через gg, посмотрели, нажали ` - вернулись. Снова ` - снова наверх. Это "туда-обратно" одной клавишей.
Прыжком, который обновляет `, считается любое "большое" перемещение: gg, G, /поиск, переход по метке, {/} и т. п. - то же, что попадает в jumplist (см. ниже).
Код: Выделить всё
Курсор на строке 250. Хотим глянуть импорты наверху.
Нажимаем: gg -> курсор на строке 1
Читаем импорты.
Нажимаем: `` -> курсор вернулся на строку 250, в ту же колонку
Нажимаем: `` -> снова строка 1 (toggle)
`. прыгает туда, где вы последний раз что-то изменили, независимо от того, сколько вы успели полистать. Классический сценарий: написали строку, ушли проверить что-то в другом конце файла, и хотите вернуться ровно к тому месту, где остановились, - `. доставит точно.
Код: Выделить всё
Дописали строку 80, ушли читать строку 5.
Нажимаем: `. -> мгновенно обратно на строку 80, на место правки.
`^ - позиция последней вставки. Напрямую ей пользуются редко, но на ней построена очень полезная команда gi: она ставит курсор в `^ и сразу входит в режим вставки. То есть "продолжить печатать там, где я в прошлый раз остановился" - одна команда. Полистали код, нажали gi - и снова дописываете.
`[ `] - границы yank/paste
После любой операции копирования, вставки или изменения Vim выставляет `[ на начало затронутого текста и `] на конец. Это позволяет, например, мгновенно выделить только что вставленный блок:
Код: Выделить всё
Вставили блок: p
Нажимаем: `[v`] -> выделили ровно вставленный текст
(можно сразу = для переотступа, gu для нижнего регистра и т.д.)
`< `> - границы визуального выделения
`< и `> хранят границы последнего визуального выделения. Именно на них основан диапазон '<,'>, который Vim автоматически подставляет в командную строку, когда вы нажимаете : из визуального режима. Подробнее о визуальном режиме - в главе 17, а о диапазонах команд - в главах 11-12.
Jumplist: история прыжков
Vim ведёт jumplist - список "больших" перемещений по каждому окну. По нему можно ходить как по истории браузера: назад и вперёд.
Код: Выделить всё
Клавиша | Действие
---------+------------------------------------------------------------
Ctrl-O | Назад по jumplist (к предыдущему прыжку) - мнемоника Older
Ctrl-I | Вперёд по jumplist (то же, что Tab) - In / новее
:jumps | Показать jumplist
В jumplist попадают только прыжки, а не построчные перемещения. Запись добавляется при:
- gg, G, {count}G - переход по номеру строки;
- /, ?, n, N, *, # - поиск (глава 10);
- {, }, (, ), % - абзацы, предложения, парная скобка;
- 'a / `a - переход по метке;
- L, M, H - переход по экрану;
- переход в другой файл.
Код: Выделить всё
:jumps
jump line col file/text
3 12 0 src/app.lua
2 240 4 src/router.lua
1 88 10 src/router.lua
> 0 45 12 src/router.lua <- текущая позиция (>)
1 305 0 src/handlers.lua
Размер jumplist фиксирован - до 100 записей на окно, и у каждого окна свой список.
Ручная запись и подавление прыжка
Иногда хочется управлять jumplist явно - не полагаясь на то, что Vim сам решит, "большое" ли это перемещение.
- m' (или эквивалентное m`) - добавить текущую позицию в jumplist, ничего никуда не двигая. По сути это запись "закладки прыжка" без именованной метки: поставили m', ушли мелкими движениями (j/w/f) и вернулись Ctrl-O. Так в историю прыжков попадает место, которое иначе бы в неё не записалось.
- :keepjumps {команда} - наоборот, выполнить команду, не записывая позицию в jumplist (и в changelist). Незаменимо при написании маппингов и функций: если ваш маппинг внутри делает G или /, без :keepjumps он замусорит пользователю историю прыжков ложными записями. Обернув служебное перемещение в :keepjumps, вы оставляете jumplist чистым.
Код: Выделить всё
" в маппинге/функции: прыгнуть в конец файла для служебной обработки,
" не засоряя пользовательский jumplist
:keepjumps normal! G
Режим stack - jumplist как у браузера
По умолчанию у jumplist есть нелогичная для многих особенность: если вы вернулись Ctrl-O в середину списка и прыгнули в новое место, новая запись добавляется, а "хвост" (то, что было впереди) сохраняется - и Ctrl-I/Ctrl-O начинают вести себя запутанно при ветвлении.
В Neovim это лечится опцией:
Код: Выделить всё
vim.o.jumpoptions = 'stack'
Дополнительно в Neovim есть значение view:
Код: Выделить всё
vim.o.jumpoptions = 'stack,view'
Changelist: история ваших правок
Changelist - отдельный список, в который попадают позиции, где вы вносили изменения. В отличие от jumplist (он про навигацию), changelist - про редактирование.
Код: Выделить всё
Клавиша | Действие
----------+---------------------------------------------
g; | Назад по changelist (к более старой правке)
g, | Вперёд по changelist (к более новой правке)
:changes | Показать changelist
Код: Выделить всё
:changes
change line col text
3 120 4 let total = 0
2 122 8 total += item.price
1 140 0 return total
> 0
Размер changelist такой же фиксированный, как у jumplist, и список тоже свой на буфер.
Лучшие практики 2026
Глобальные метки вместо тяжёлых плагинов закладок. Прежде чем ставить Harpoon или похожий плагин, попробуйте A-Z: они нативны, переживают перезапуск через shada и не добавляют зависимостей. Достаточно повесить маппинг, который ставит следующую свободную заглавную метку, и показывать их пикером (snacks.picker/Telescope, глава 28). Для одного-двух "активных" файлов хватает и голых `T/`M без всякой обвязки.
`. и gi - в ежедневный обиход. Связка "полистал - gi - продолжил печатать" экономит больше времени, чем кажется. А `. - первая команда, к которой стоит тянуться после "куда я только что писал?".
jumpoptions=stack почти всем подходит лучше дефолта. Поведение "как у браузера" интуитивнее: один раз настроив stack (и при желании view), вы перестаёте удивляться, почему Ctrl-O/Ctrl-I водят не туда. Это нативная опция, плагины не нужны.
Метки как диапазоны для команд. Поставьте ma в начале блока, спуститесь к концу - и любая Ex-команда :'a,.команда отработает по диапазону "от метки a до текущей строки". Это особенно мощно с :substitute, :global и :normal (главы 11-12). Метка избавляет от подсчёта номеров строк.
Визуализация меток - по желанию. Если вы плотно работаете с метками, плагины вроде marks.nvim или GutterMarks.nvim показывают их в знаковой колонке (signcolumn), а portal.nvim рисует превью целевых позиций для Ctrl-O/Ctrl-I и changelist - лечит "слепые" прыжки. Но это надстройка над уже рабочим нативным механизмом, а не замена ему.
Частые ошибки
- Путать 'a и `a. 'a ставит курсор на первый непробельный символ строки, а не на запомненную колонку. Если вам нужно точное место (например, чтобы продолжить выражение) - берите обратный апостроф `.
- Ставить нижнерегистровую метку и ждать её в другом файле. a-z локальны для буфера: уйдя в другой файл, вы к ним не вернётесь. Для межфайловых закладок - только A-Z.
- Думать, что Ctrl-O отменяет действие. Ctrl-O - это "назад по jumplist", навигация. Отмена правок - это u (глава 04). Их легко перепутать по мышечной памяти.
- Ожидать, что Ctrl-O вернёт после j/k/w. Мелкие движения в jumplist не пишутся. Назад через jumplist вы попадёте к последнему прыжку (поиск, G, метка), а не к каждому шагу курсора.
- Удивляться "раздвоению" истории. Без jumpoptions+=stack прыжок из середины jumplist сохраняет хвост, и Ctrl-I/Ctrl-O ведут себя неинтуитивно. Настройте stack.
- Терять метки после :%s или перезагрузки буфера. Массовое изменение строк сдвигает позиции - Vim старается двигать метки вместе с текстом, но при перезагрузке файла извне или серьёзной перетряске метки могут "съехать". Заглавные метки спасает shada между сессиями, но в рамках сессии лучше не полагаться на точность после разрушительных операций.
- Туда-обратно. Откройте файл на 100+ строк. Поставьте ma где-нибудь в середине. Прыгните gg, затем G, затем `a. Теперь отработайте ` (двойной апостроф): прыгните /слово, вернитесь `, снова перейдите `. Почувствуйте toggle-поведение.
- `. и gi. Допишите строку в одном месте файла, уведите курсор в другой конец (gg или G). Вернитесь к месту правки через `.. Затем уведите курсор снова и вернитесь через gi - обратите внимание, что во втором случае вы сразу в режиме вставки.
- Границы вставки. Скопируйте абзац (yap), вставьте ниже (p) и сразу выделите вставленное через `[v`]. Затем попробуйте =`] для переотступа вставленного блока. Сравните с ручным подсчётом строк.
- Глобальные закладки. В проекте из нескольких файлов поставьте mC на конфиг и mT на тест. Поработайте в третьем файле, затем прыгните `C и `T. Закройте файл с меткой C (:bd) и убедитесь, что `C его снова откроет.
- Jumplist как стек. Добавьте vim.o.jumpoptions = 'stack' (или в Vim: :set jumpoptions=stack). Сделайте серию прыжков (G, /x, gg), пройдите назад парой Ctrl-O, прыгните в новое место и понаблюдайте через :jumps, как хвост отбросился. Сравните с поведением без stack.
- Changelist-тур. Внесите правки в 4-5 разных мест функции. Затем пройдите по ним g; от новой к старой и g, обратно. Откройте :changes и сопоставьте записи с тем, что видите. Попробуйте 999g;.
- Метки m{a-z} - локальные закладки в буфере; m{A-Z} - глобальные между файлами, переживают перезапуск через shada.
- 'a прыгает на строку (первый непробельный символ), `a - точно на строку и колонку. Это различие действует всюду.
- Автометки работают без вашего участия: ` - до прыжка, `. - последняя правка, `^ - вставка (и gi чтобы продолжить печатать), `[/`] - границы yank/paste, `</`> - визуальное выделение.
- Jumplist (Ctrl-O/Ctrl-I, :jumps) - история "больших" прыжков; мелкие движения в неё не пишутся. Опция jumpoptions=stack (Neovim/Vim) делает её предсказуемой, view восстанавливает вид окна.
- Changelist (g;/g,, :changes) - история мест, где вы правили код; точнее jumplist, когда нужно вернуться к редактированию.
- Нумерованные метки `0-`9 приходят из shada: `0 - место выхода из прошлой сессии, `1-`9 - более ранние файлы; готовый "список недавнего".
- m' добавляет текущую позицию в jumplist вручную, а :keepjumps выполняет команду без записи в jumplist/changelist (для маппингов и функций).
- :marks показывает всё разом; метки удаляют :delmarks (Vim и Neovim) и команда нормального режима dm{x} (только Neovim).
- Метки отлично работают как диапазоны для Ex-команд (:'a,.s/...) - об этом подробнее в главах 11-12.