Зачем это нужноЧасть VII. Мастерство
Представьте, что вы вложили десятки часов в отработку модального редактирования: ciw, dap, f + ;, макросы, текстовые объекты. А потом открываете браузер, чтобы прочитать документацию, и ваши руки тянутся к стрелочкам и мыши. Открываете командную строку и снова печатаете строку линейно, как в блокноте. Открываете JetBrains-IDE на работе, где "так положено", и теряете половину своей продуктивности.
Главная идея этой главы: навык модального редактирования стоит дороже, если он работает везде. Мускульная память не делится на "редактор" и "не редактор" - она просто срабатывает или нет. Каждый инструмент, где доступен Vim-режим (а таких большинство), это не отдельная конфигурация, которую нужно осваивать заново, а ещё одна площадка, закрепляющая один и тот же словарь движений. Тренажёры (vim-be-good, VimGolf - подробнее в главе 33) полезны, но самое мощное упражнение - это просто прожить весь день в hjkl, не делая исключений для терминала и браузера.
Цель главы - показать, как развернуть Vim-режим в четырёх средах: IDE (JetBrains и VSCode), браузере, shell и утилитах командной строки (less, man, k9s, lazygit). И, что важнее, объяснить где у каждой реализации граница, чтобы вы не воевали с инструментом, требуя от него того, чего он не умеет.
Принцип: один набор движений везде
Прежде чем настраивать конкретные инструменты, зафиксируем философию.
Переносится грамматика, а не конфиг. Самое ценное, что вы переносите между средами, - это базовый слой: движения (w, b, e, f, t, 0, $, gg, G), операторы (d, c, y), текстовые объекты (iw, ip, i(, it), счётчики (3w, d2j) и точка-команда .. Эти вещи реализованы практически в любом Vim-эмуляторе. А вот продвинутые фичи - :global, :normal, Ex-команды, плагины normal-режима, persistent undo - поддерживаются неравномерно или не поддерживаются вовсе.
Разделение труда. Хорошая стратегия в любой IDE: оставить родному инструменту то, что он делает лучше (рефакторинг, навигацию по символам, отладку, ресайз панелей, систему контроля версий), а Vim-режим использовать для того, в чём он силён, - точечное редактирование текста, поиск, повторяемость. Попытка заставить IDE-Vim делать Ctrl-W менеджмент окон вместо родных шорткатов IDE - путь к фрустрации.
Единый leader и единые маппинги. Если вы используете <Space> как leader в Neovim, имеет смысл задать тот же leader и набор базовых маппингов в .ideavimrc. Чем меньше расхождений между средами, тем крепче привычка. Полное единообразие недостижимо (об этом ниже), но к нему стоит стремиться.
IdeaVim в JetBrains
IdeaVim - официальный плагин-эмулятор Vim для IDE на платформе IntelliJ (IntelliJ IDEA, PyCharm, WebStorm, GoLand, RubyMine, CLion и др.). Это не "лёгкая имитация", а довольно полная реализация normal/visual/insert-режимов, текстовых объектов, регистров, макросов, marks и даже части Ex-команд. В 2025 году JetBrains запустила для IdeaVim программу вознаграждений за вклад, и плагин активно развивается.
Файл .ideavimrcПро Fleet: редактор Fleet от JetBrains (публично доступен с 2023-2024 гг.) НЕ использует плагин IdeaVim. У него собственный встроенный Vim-режим на отдельном движке. Поэтому конфиг .ideavimrc и приём :action ниже относятся к IDE на платформе IntelliJ, но не к Fleet.
IdeaVim читает конфигурацию из ~/.ideavimrc (по аналогии с ~/.vimrc). Синтаксис - Vimscript. Можно даже подключить существующий ~/.vimrc, чтобы переиспользовать привычные маппинги:
Код: Выделить всё
" ~/.ideavimrc
" Переиспользуем общий конфиг, если он совместим
source ~/.vimrc
" Базовые опции
set scrolloff=8
set incsearch
set ignorecase
set smartcase
set clipboard+=unnamed
set whichwrap=b,s,<,>,h,l,[,]
" Тот же leader, что и в Neovim
let mapleader = " "
" Включаем эмуляцию популярных плагинов
set surround
set commentary
set easymotion
set highlightedyank
set NERDTree
Мост к командам IDE: :action и <Action>
Самая мощная возможность IdeaVim - вызывать любое действие IDE по его идентификатору. Это позволяет повесить родные функции JetBrains (рефакторинг, навигацию, git) на удобные leader-маппинги в Vim-стиле:
Код: Выделить всё
" Навигация и рефакторинг через действия IDE
nnoremap <leader>rn :action RenameElement<CR>
nnoremap <leader>ca :action ShowIntentionActions<CR>
nnoremap gd :action GotoDeclaration<CR>
nnoremap gr :action FindUsages<CR>
nnoremap <leader>ff :action GotoFile<CR>
nnoremap <leader>fg :action FindInPath<CR>
nnoremap [d :action GotoPreviousError<CR>
nnoremap ]d :action GotoNextError<CR>
" Современный синтаксис через <Action> работает и в map-выражениях
nmap <leader>b <Action>(ToggleLineBreakpoint)
Конфликты с системными шорткатами IDE: handlers
На практике самая частая боль - конфликт Vim-биндингов с родными Ctrl-шорткатами JetBrains. Например, Ctrl-V в Vim - визуальный блок, а в IDE - вставка из буфера; Ctrl-R в Vim - redo, в IDE - Run; Ctrl-W в Vim - окна, в IDE - расширение выделения. IdeaVim решает это через sethandler: для каждого сочетания можно указать, кто его получает - Vim (a/i/n/v), IDE (ide) или оба.
Код: Выделить всё
" sethandler <клавиша> [режим]:обработчик
" a:vim - отдать Vim во всех режимах; ide - отдать IDE
sethandler <C-V> n-v:vim i:ide " блок-визуал в normal/visual, вставка в insert
sethandler <C-R> n:ide " отдать Ctrl-R под Run в IDE
sethandler <C-C> a:ide " копирование IDE всегда
Код: Выделить всё
nnoremap <leader>vv :action VimPluginToggle<CR>
Что НЕ стоит ждать от IdeaVim
- Полноценного :global и :normal в произвольной форме (часть Ex-команд работает, но не вся экосистема).
- Управления сплитами через Ctrl-W - используйте родные шорткаты IDE для разделения и навигации по окнам.
- Внешних normal-режимных плагинов (fugitive, telescope и т.п.) - их роль закрывают встроенные функции IDE через :action.
В VSCode есть два принципиально разных подхода к Vim-режиму, и выбор между ними - это выбор между "лёгкой эмуляцией" и "настоящим Vim".
VSCodeVim - эмуляция
VSCodeVim (расширение vscodevim.vim) - самое популярное и простое решение. Оно эмулирует Vim на TypeScript: реализует normal/visual/insert-режимы, текстовые объекты, многие операторы. Опции задаются не в vimrc, а в settings.json VSCode:
Код: Выделить всё
{
"vim.leader": "<space>",
"vim.useSystemClipboard": true,
"vim.hlsearch": true,
"vim.incsearch": true,
"vim.easymotion": true,
"vim.sneak": true,
"vim.normalModeKeyBindingsNonRecursive": [
{ "before": ["<leader>", "f", "f"], "commands": ["workbench.action.quickOpen"] },
{ "before": ["g", "d"], "commands": ["editor.action.revealDefinition"] }
]
}
VSCode Neovim - настоящий бэкенд
vscode-neovim (расширение asvetliakov.vscode-neovim) идёт принципиально иначе: оно встраивает реальный процесс Neovim в качестве бэкенда. Normal-режимные нажатия обрабатывает не эмулятор, а настоящий Neovim. Это значит, что вам доступны:
- :global, :vglobal, :normal, :substitute с полной семантикой - то, чего нет в эмуляторах;
- макросы и регистры точно как в Neovim;
- normal-режимные плагины (например, vim-surround, targets.vim, плагины текстовых объектов), подключённые через ваш init.lua;
- ваша собственная конфигурация Neovim, в том числе keymaps normal-режима.
Код: Выделить всё
Критерий | VSCodeVim | VSCode Neovim
-------------------------+--------------------------+-----------------------------------
Движок | эмуляция на TS | реальный Neovim-процесс
:global / :normal | нет/частично | да
Normal-режимные плагины | нет | да (через init.lua)
Конфигурация | settings.json | init.lua + settings.json
Порог входа | низкий | выше (нужен установленный Neovim)
Кому | нужен лёгкий Vim-режим | нужна полная семантика Vim
Vim в браузере: Vimium и Tridactyl
Браузер - это место, где средний разработчик проводит уйму времени: документация, code review, поиск. Перенести сюда Vim-навигацию - значит закрепить мускульную память там, где раньше господствовала мышь.
Vimium (Chrome/Firefox)
Vimium - самое популярное расширение, добавляющее Vim-навигацию на любую страницу. Ключевая механика - link hints: нажимаете f, и поверх всех кликабельных элементов появляются буквенные метки; набираете метку - переходите по ссылке без мыши.
Код: Выделить всё
Клавиша | Действие
---------+-------------------------------------------------------
j / k | скролл вниз / вверх
h / l | скролл влево / вправо
gg / G | в начало / конец страницы
d / u | полэкрана вниз / вверх
f | подсветить ссылки метками (открыть в текущей вкладке)
F | то же, но открыть в новой вкладке
o | omnibar: открыть URL/историю/закладки
J / K | предыдущая / следующая вкладка
x | закрыть вкладку
H / L | назад / вперёд по истории
/ | поиск по странице, n/N - следующее/предыдущее
i | insert-режим (передать ввод странице)
<Esc> | выйти из insert-режима / снять фокус
Tridactyl (Firefox)
Tridactyl - более мощная альтернатива для Firefox, ближе по духу к легендарному Vimperator. Помимо link hints и hjkl-скролла, он поддерживает Ex-подобную командную строку (:), собственный конфиг с bind-командами, и - главная фишка - режим редактирования текстовых полей во внешнем Vim/Neovim: нажимаете Ctrl-I в <textarea> и правите содержимое в настоящем редакторе, после :wq текст возвращается в поле. Для тех, кто пишет длинные комментарии и посты, это снимает главное ограничение браузерного Vim-режима - слабый встроенный редактор полей.
Границы браузерного Vim-режима
Расширение - это всё-таки контент-скрипт, инжектируемый в страницу, и у него есть жёсткие пределы:
- Привилегированные страницы. Vimium (как и любое расширение под Manifest V3) НЕ работает на служебных страницах браузера: chrome://, chrome-extension://, страница Web Store, about: в Firefox, страница новой вкладки в некоторых сборках. Там контент-скрипты запрещены политикой браузера - f, j/k просто не сработают, и это не баг настройки. Аналогично расширение часто слепо на PDF-вьюере и на страницах внутри кросс-доменных iframe.
- Manifest V3. Переход Chrome на MV3 ужесточил модель расширений (service worker вместо фоновой страницы, ограничения на инжекцию) - на функциональность Vimium это в целом не повлияло, но усилило именно запрет на привилегированных страницах.
- Safari и браузеры без полноценного Vim-расширения. В Safari нет аналога уровня Vimium/Tridactyl (доступны лишь ограниченные расширения), поэтому полноценный Vim-режим там собрать не получится - это типичная граница, о которой стоит знать заранее, прежде чем пытаться "жить в hjkl" в любом браузере.
Командная строка - это тоже текст, который вы редактируете. По умолчанию bash и zsh работают в Emacs-режиме редактирования строки. Переключение в vi-режим переносит туда привычные движения.
bash и readline
bash использует библиотеку readline. Включить vi-режим можно на сессию:
Код: Выделить всё
set -o vi
Код: Выделить всё
# ~/.inputrc
set editing-mode vi
set show-mode-in-prompt on
set vi-ins-mode-string \1\e[6 q\2
set vi-cmd-mode-string \1\e[2 q\2
В readline-vi после <Esc> (переход в command-режим) работают h/l, w/b/e, 0/$, f/F/t/T, dw, cw, d$, cc, r, x, p, а также //? для поиска по истории команд и k/j для перебора истории.
zsh
zsh не использует readline, у него собственный line editor (ZLE), поэтому ~/.inputrc на него не влияет. Включается vi-режим в ~/.zshrc:
Код: Выделить всё
bindkey -v
# Уменьшаем задержку перехода в normal-режим (по умолчанию 0.4с)
export KEYTIMEOUT=1
Код: Выделить всё
bindkey -M vicmd '/' history-incremental-search-backward
bindkey -M viins '^R' history-incremental-search-backward
Код: Выделить всё
# zsh
autoload -Uz edit-command-line
zle -N edit-command-line
bindkey -M vicmd 'v' edit-command-line
Природа задержки Esc и распознавание escape-последовательностей
Пауза после Esc - не каприз zsh, а фундаментальная проблема терминала. В терминале клавиша Esc физически шлёт байт 0x1B - и ровно с него же начинаются escape-последовательности стрелок, F-клавиш, Home/End (например, Up это ESC [ A). Получив ESC, line editor не знает сразу: это одиночный Esc (выйти в command-режим) или начало многобайтовой последовательности. Поэтому он ждёт небольшой таймаут - придут ли следующие байты.
- zsh: таймаут регулируется KEYTIMEOUT (в сотых долях секунды). KEYTIMEOUT=1 делает переход почти мгновенным, но слишком малое значение может "разорвать" медленные последовательности по SSH с высокой задержкой.
- bash/readline: аналог - keyseq-timeout (в миллисекундах) в ~/.inputrc:
Код: Выделить всё
# ~/.inputrc - ускорить распознавание одиночного Esc
set keyseq-timeout 25
Vim-навигация в утилитах командной строки
Многие TUI-инструменты по умолчанию понимают Vim-движения, потому что их авторы - те же люди, что живут в Vim. Это значит, что вам не нужно ничего настраивать - навык работает сам.
Код: Выделить всё
Инструмент | Что работает "из коробки"
--------------------+-----------------------------------------------------------
less | j/k, d/u, gg/G (через g/G), /+n/N, ?, q
man | использует less как pager - те же клавиши
git log / git diff | по умолчанию через less - те же клавиши
k9s | j/k навигация, / фильтр, : команды-ресурсы, gg/G
lazygit | h/j/k/l между панелями и по спискам, / поиск
fzf | поддерживает --bind для vi-стиля; Ctrl-J/Ctrl-K навигация
tig | j/k, /, gg/G - Vim-словарь по умолчанию
htop / btop | частично (btop поддерживает vim-keys опцией)
Jupyter | есть vi-mode для редактирования ячеек
Для less стоит запомнить: g - в начало (вместо gg, хотя gg тоже сработает), G - в конец, &pattern - показать только совпадающие строки (аналог фильтрации), F - режим "следования" как tail -f. Прокрутка d/u - это пол-окна (half-window) по умолчанию, но величина настраивается (передать число перед d/u задаёт новый шаг).
Практические примеры
Пример 1. Редактирование длинной команды в shell (zsh, vi-mode).
Код: Выделить всё
Было (в командной строке):
docker run --rm -it -v $(pwd):/app -w /app -e NODE_ENV=production node:20 npm test
Хотим заменить production на development.
Важно: в shell vi-mode (readline/ZLE) НЕТ внутристрочного поиска `/` -
он биндится на поиск по ИСТОРИИ команд (см. Пример 4). И НЕТ текстовых
объектов (`iw`, `aw`, `i(`): доступны только движения + операторы
(`w`, `b`, `f`, `t`, `F`, `dw`, `cw`, `ct`, `cc`, `r`, `x`, `p`).
Поэтому позиционируемся движениями по строке, а не поиском.
Нажимаем: <Esc> (переходим в command-режим)
F= (поиск НАЗАД по строке к '=' перед значением)
l (вправо на первый символ значения - 'p')
ct<Space> (change till next space - удаляет production)
development<Esc> (печатаем новое значение)
Альтернатива, если курсор уже стоит на 'p' начала production:
cwdevelopment<Esc> (change word - слово до конца от курсора)
Стало:
docker run --rm -it -v $(pwd):/app -w /app -e NODE_ENV=development node:20 npm test
Пример 2. Переход по ссылке в браузере без мыши (Vimium).Замечание про ciw: текстовый объект iw ("inner word") есть только в
полноценном Vim/Neovim. В readline и ZLE его нет - там пришлось бы целиться
движением (cw от начала слова или ct <пробел>). Разбор ciw vs cw
уместен в редакторе, но не в командной строке.
Код: Выделить всё
Было: открыта страница документации, нужно перейти в раздел "Configuration".
Нажимаем: f (появляются буквенные метки на всех ссылках)
SA (набираем метку нужной ссылки; метки по умолчанию
показываются заглавными с домашнего ряда, но
ввод регистронезависим - sa тоже сработает)
Стало: открыт раздел Configuration, рука не покидала клавиатуру.
Код: Выделить всё
Было: курсор на переменной userCnt, хотим переименовать во всём проекте.
Нажимаем: <leader>rn (маппинг на :action RenameElement)
userCount<CR> (вводим новое имя, IDE правит все использования)
Стало: переменная переименована проектно - Vim-движение запустило IDE-рефакторинг.
Код: Выделить всё
Было: нужно найти давнюю команду с "rsync".
Нажимаем: <Esc> (command-режим readline)
/rsync<CR> (поиск назад по истории)
n (следующее совпадение, если не та)
<CR> (выполнить найденную)
- Один leader, один словарь. Задайте <Space> как leader в Neovim, IdeaVim (let mapleader = " ") и VSCode ("vim.leader": "<space>"). Расхождения в маппингах между средами стирают мускульную память - стремитесь к максимальному совпадению базового слоя.
- В IDE делегируйте "умное" через мост к действиям. В IdeaVim вешайте рефакторинг, навигацию и git на leader-маппинги через :action/<Action>; в VSCode - через commands в normalModeKeyBindings. Не пытайтесь воспроизвести IDE-функции средствами Vim - пусть Vim занимается текстом, а IDE своим.
- Выбор VSCode-расширения по потребности. Нужна полная семантика Vim (:global, :normal, normal-режимные плагины, ваш init.lua) - vscode-neovim с реальным Neovim-бэкендом. Нужен лёгкий Vim-режим без Ex-команд - VSCodeVim.
- Покажите режим в shell. В bash добавьте set show-mode-in-prompt on и смену формы курсора в ~/.inputrc; в zsh используйте KEYTIMEOUT=1 и индикатор режима. Без статусной строки визуальная подсказка о текущем режиме экономит много ошибочных нажатий.
- Используйте ~/.inputrc как единую точку. Одна строка set editing-mode vi включает vi-режим во всех readline-программах сразу (REPL Python, psql, gdb и др.) - это бесплатное распространение навыка.
- Закрепляйте навык через "Vim everywhere", а не только тренажёрами. Перенос одного и того же словаря движений во все инструменты (терминал, браузер, IDE) закрепляет мускульную память быстрее и естественнее, чем изолированные дриллы. Тренажёры (см. главу 33) - дополнение, а не замена ежедневной практике.
- Не отключайте родные шорткаты IDE ради чистоты Vim. Persistent undo в IDE-Vim ненадёжен - пользуйтесь Local History (JetBrains) и Timeline (VSCode) вместо u-"машины времени". Это не компромисс, а правильное разделение труда.
- Воевать с IDE-Vim за неподдерживаемые фичи. Попытки использовать :global, произвольные Ex-команды, Ctrl-W-менеджмент окон или persistent undo в IdeaVim/VSCodeVim приводят к фрустрации. Если нужна полная семантика - берите vscode-neovim (реальный бэкенд); иначе принимайте границы эмулятора.
- Конфликты маппингов плагинов. Классика - s одновременно у vim-surround и vim-easymotion/sneak. Решайте заранее: переназначьте surround (например, на <leader>s) или отключите конфликтующую фичу.
- clipboard=unnamedplus вслепую в IDE. В IDE-средах системный буфер обычно уже проброшен. Дублирующие глобальные настройки буфера могут конфликтовать с родным копированием IDE - проверяйте поведение, прежде чем глобально вешать unnamed.
- Забыть, что zsh не читает ~/.inputrc. Частая путаница: настроили vi-режим в ~/.inputrc, а в zsh ничего не изменилось. zsh использует ZLE - настраивается через bindkey -v в ~/.zshrc.
- Раздражающая задержка после <Esc> в shell. Без KEYTIMEOUT=1 (zsh) или keyseq-timeout (bash/readline) переход в command-режим ощущается "залипающим". Это не баг, а таймаут на распознавание escape-последовательностей: Esc и стрелки начинаются с одного байта 0x1B. Слишком агрессивное ускорение ломает стрелки/F-клавиши по медленному SSH - балансируйте значение.
- i в Vimium ловит ввод. В браузерном Vim-режиме нажатие i (или фокус на поле ввода) передаёт клавиши странице - j/k начнут печататься в текстовом поле. Выход - <Esc>.
- Ожидать единообразия там, где его нет. Полное совпадение поведения между Neovim, IdeaVim, VSCodeVim и браузером недостижимо - реализации отличаются в краях (поведение ., отдельные текстовые объекты, регистры). Совпадает базовый слой; на него и опирайтесь.
- Shell vi-mode. Включите vi-режим в своём shell (set -o vi для bash или bindkey -v для zsh). Настройте индикацию режима и форму курсора. Проживите в нём день: редактируйте команды через <Esc> + движения, ищите по истории через /. Какие три команды вы раньше переписывали целиком, а теперь правите точечно?
- ~/.inputrc для всех REPL. Добавьте set editing-mode vi в ~/.inputrc. Откройте python3, psql (или любой доступный readline-REPL) и убедитесь, что vi-движения работают и там. Запишите, в скольких разных программах навык теперь доступен.
- Браузер без мыши. Поставьте Vimium (Chrome) или Tridactyl (Firefox). В течение часа навигации по документации не касайтесь мыши: только f для ссылок, j/k/d/u для скролла, o для открытия URL, J/K для вкладок. Засеките, насколько естественно ложатся уже знакомые gg/G///n.
- IdeaVim или VSCode-мост. В своей IDE настройте минимум три leader-маппинга к родным действиям: переименование, переход к определению, поиск по проекту (:action в .ideavimrc или commands в settings.json). Убедитесь, что leader совпадает с вашим Neovim-leader.
- Сравнение VSCode-расширений. Если используете VSCode, поставьте vscode-neovim и попробуйте выполнить :%g/TODO/d (удалить все строки с TODO) на тестовом файле. Затем попробуйте то же в VSCodeVim. Зафиксируйте разницу - это наглядная демонстрация "эмуляция vs реальный бэкенд".
- Аудит единообразия. Выпишите 10 базовых движений/команд, которыми пользуетесь чаще всего. Проверьте каждое в четырёх средах: Neovim, IDE-Vim, браузер, shell vi-mode. Отметьте, где что не работает, - это карта реальных границ вашего "Vim everywhere".
- Ценность модального редактирования многократно возрастает, когда один набор движений работает везде: в IDE, браузере, shell и TUI-утилитах. Переносится грамматика (движения + операторы + текстовые объекты + .), а не конфиг целиком.
- IdeaVim - полноценный Vim-движок JetBrains; конфигурируется через ~/.ideavimrc, эмулирует часть плагинов через set <имя>, а к функциям IDE подключается через :action/<Action>. Делегируйте "умное" родной IDE.
- В VSCode есть выбор: VSCodeVim (лёгкая эмуляция, настройка в settings.json) против vscode-neovim (реальный Neovim-бэкенд с :global/:normal/плагинами и вашим init.lua). Выбирайте по тому, нужна ли полная семантика Vim.
- В браузере Vimium/Tridactyl дают f для перехода по ссылкам и hjkl-скролл; словарь почти идентичен Vim. Tridactyl умеет править поля во внешнем редакторе.
- В shell vi-режим включается через set -o vi (bash/readline, конфиг ~/.inputrc) или bindkey -v (zsh, конфиг ~/.zshrc); ~/.inputrc распространяет vi-режим на все readline-программы разом.
- Многие TUI-инструменты (less, man, git-pager, k9s, lazygit, tig, fzf) понимают Vim-движения по умолчанию - навык работает без настройки.
- Не воюйте с границами эмуляторов и решайте конфликты маппингов заранее. Эргономика и борьба с вредными привычками при этом - тема следующей главы 32.