Я уже восемь лет работаю в Vim и постоянно открываю что-то новое. Принято считать это достоинством Vim. Как по мне, так это недостаток открытости: куча скрытых функций спрятаны слишком глубоко.
Вот говорят о красоте модального редактирования и текстовых объектах, но мне кажется, что суть Vim не в этом. Vim — это лоскутное одеяло из подсистем, под завязку забитых дополнительными инструментами. Только в обычном режиме редактирования более сотни комбинаций клавиш! Такая плотность инструментария в значительной степени объясняет, почему Vim настолько полезен. Если «показать все теги для ключевого слова» — это просто g]
, то этой командой будут пользоваться гораздо чаще.
В системах с недостатком открытости приходится полагаться на руководства. Но для Vim их не так уж много. Есть статьи для новичков, такие как ciw
(не путать с CIA, мануалом ЦРУ по Vim) и тому подобное. И есть статьи экспертов, которые погружаются в подсистемы. Но никто на самом деле не говорит об этих особых трюках, которые заставляют воскликнуть: чёрт побери, как мне это было нужно в течение последних шести лет!
Эта статья о некоторых маленьких трюках, которые я использую в Vim. Ни один из них не разобран во всех деталях, так что если что-то заинтересовало, рекомендую покопать дополнительную информацию. Они также не связаны друг с другом. Но это нормально. В общем, их более чем достаточно, чтобы реально помочь практически каждому.
Структура статьи
Очень грубо, пользователи Vim делятся на две категории. Пуристы ценят небольшие размеры и вездесущность. Как правило, они сводят конфигурацию к минимуму на случай, если придётся работать на незнакомом компьютере (например, по ssh). С другой стороны, расширяльщики наполняют Vim плагинами, функциями и доморощенными сопоставлениями в тщетной попытке притвориться, что они используют Emacs. Если забрать у них vimrc, то ребята останутся совершенно беспомощными.
Как вы, наверное, догадываетесь, мне гораздо ближе расширяльщики, чем пуристы. Я разделил трюки на два раздела в зависимости от того, требуются ли изменения в базовом Vim.
Пуристы
Для модальных команд используются стандартные представления из справки, т. е. <cr>
означает нажатие клавиши Enter. Когда нужно получить справку :h
для определённой строки, например, :h E676
, то строка будет в скобках.
Различные команды в обычном режиме
": и @:
":
является регистром, хранящим последнюю выполненную команду. Вы можете набрать ":p
, чтобы распечатать его в буфер. @:
повторяет последнюю команду.
"=
Регистр для «выражений». Здесь вы можете ввести любое выражение vimL и вставить его, использовать с ctrl-R и т. д. Таким образом, например, локальная метка времени вставляется вводом "=strftime("%c")<cr>p
.
mA, 'A
m{letter}
устанавливает метку на месте курсора. Тогда '{letter}
перейдёт к этой строке. Для строчных букв действует в пределах на буфера, поэтому подходит для навигации. Для прописных букв действует глобально: даже если вы находитесь в другом файле, 'A
перейдёт к файлу с меткой А
. Можете посмотреть все свои метки командой :marks:
.
ctrl-A и ctrl-X
Увеличивает и уменьшает следующее число в строке на месте курсора или справа от него. Поскольку сразу переходит к числу, то сочетание можно использовать из любого места. 10c-A
намного проще, чем wwwwwciw20
.
q:
Открывает историю предыдущих команд. Можете работать с ней как с любым текстом Vim, но изменения не сохраняются. Однако вы можете запустить изменённую команду с помощью <CR>
. Это позволяет очень быстро изменять и перезапускать команды или искать старые для повторного использования.
q/, q?
То же, что и q:
, за исключением поиска.
ctrl-I, ctrl-O
Перемещает к следующему или предыдущему местоположению в джамплисте. Полезно для быстрой проверки, а затем возврата назад. Очень приятно читать файлы справки.
Макросы
См. этот пост для глубокого погружения в использование макросов.
Визуальный режим
gv
Выбирает предыдущий визуальный элемент.
v_o
Переходит на другую сторону визуального блока. Полезно, если вы начали одну строку слишком низко или что-нибудь такое. В блочном режиме переходит в противоположный угол по диагонали, а для перехода в противоположный угол по горизонтали используйте v_O
.
g ctrl-A / ctrl-X
В визуальном режиме ctrl-A просто увеличивает первое число на каждой строке. С другой стороны, g ctrl-A
будет с каждой строкой наращивать увеличение на единицу. Это намного проще объяснить в таблице:
выбрано | ctrl-A |
g ctrl-A |
2 g ctrl-A |
---|---|---|---|
a 0 b 0 c d 0 |
a 1 b 1 c d 1 |
a 1 b 2 c d 3 |
a 2 b 4 c d 6 |
Операторы: v, V, c-v (:h o_v)
Вероятно, вы знаете, что в визуальном режиме можно выделять символы (v), строки (V) и блоки (ctrl-V). Но эти три сочетания можно использовать в качестве операторов движения по соответствующему фрагменту. Например, у вас есть такой текст:
abc
abc
abc
Если поместить курсор на верхнюю b
и нажать d2j
, он удалит все три строки, потому что j
двигается построчно. Если вместо этого нажать d<c-V>2j
, движение становится поблочным и удаляется только средний столбец с тремя буквами b
.
Один вариантов использования — удаление в поиске. Обычный d/
перемещается посимвольно. Поэтому я использую dV/
для построчного движения с удалением. Есть и другой способ сделать это:
/regex/{n}
Движение на n строк ниже совпадения или на столько же строк вверх, если значение отрицательно. В качестве побочного эффекта движение происходит построчно. Таким образом, если вы хотите удалить первую строку, соответствующую regex
, можете ввести d/regex//0
.
Ex-команды
Ex-команды вы вводите в командном режиме, например, команда :s
. Кроме замены, есть много других полезных команд. Для всех этих примеров требуется диапазон, такой как %
.
:g/regex/ex
Выполняет команду только в строках, соответствующих регулярному выражению. Например, вы можете ввести g/regex/d
для удаления всех строк, соответствующих regex. Команда v
похожа на g
, но работает на всех строках, которые не соответствуют регулярному выражению.
Трюки становятся более мощными с применением norm и некоторых других.
:norm {Vim}
Действует так, словно вы запустили {Vim} на каждой строке диапазона. Например, g/regex/norm f dw
удалит первое слово после первого пробела в каждой строке, соответствующей регулярному выражению regex. Это часто намного проще, чем макрос.
norm
подчиняется всем вашим сопоставлениям. Например, если вы назначили клавиши jk
на <esc>
в режиме вставки, то norm I jk$diw
добавит пробел к началу строки, покинет режим вставки, а затем удалит последнее слово в строке. Мне очень нравится эта функциональность, но если вы предпочитаете не использовать свои сопоставления, вы можете тогда применять norm!
.
:co .
Копирует диапазон в текущую строку. Можно указывать и произвольные значения вместо точки, например, +3
или 'a. mv
для перемещения.
:y {reg}
Копирует диапазон в регистр {reg}
. Если {reg}
обозначить заглавной буквой, то он добавляется к существующему регистру. т. е. такая команда
let @a = '' | %g/regex/y A
скопирует в a
все строки, соответствующие regex
во всём файле. Это помогает извлечь из файла разбитый текст и скопировать его в системный буфер обмена (с помощью let @+ = @a
).
:windo {ех}
Запускает команду во всех окнах. Например, :windo $
прокрутит все окна вниз. Есть ещё bufdo
, cdo
, tabdo
и другие.
Очень хорошо работает с g
и s
. Чтобы заменить все сочетания AA
на BB
с предварительным просмотром замен, можно ввести vimgrep AA
, загрузив все совпадения в quickfix, а затем cdo s/AA/BB/cge
для поиска/замены всех совпадений.
Vim для расширятелей
Здесь перечислены трюки, которые требуют сохранения в настройках или изменения сеанса Vim. Гипотетически их можно использовать в «пуританском» режиме, просто введя команды, но некоторые влекут довольно серьёзные изменения, противоречащие духу пуризма.
Здесь только самое необычное. Многие люди назначают H
на крышечку ^
, поэтому такие вещи не стоит упоминать. Также нет смысла говорить о vim-sensible
или vim-surround
, а лишь о более экзотических плагинах.
Если вы постоянно настраиваете свой vimrc, сделайте себе приятно и добавьте для этого отдельную команду:
command! Vimrc :vs $MYVIMRC
Настройки
У меня все настройки, привязки клавиш и функции хранятся в одном файле vimrc. Разбиение на множество файлов затрудняет поиск.
Большинство настроек на самом деле не являются какими-то «трюками». Лучше всего посмотреть на vim-sensible: почти все настройки оттуда подойдут вашему vimrc.
set lazyredraw
Не перерисовывать экран посреди макроса (для повышения производительности).
set smartcase/ignorecase
С двумя этими настройками поиск без заглавных букв становится нечувствителен к регистру, а поиск с заглавными буквами чувствителен к регистру.
set undofile
Сохранение действий, даже если вы закрываете и открываете Vim, так что всегда доступна отмена действий. Очень удобно в сочетании с плагином undotree.
set foldcolumn={n}
Боковая колонка со свёрнутыми блоками. Чем больше n
, тем больше свёрнутых блоков показано в колонке, а для остальных указано число.
set suffixesadd={str}
gf
обычно означает «перейти к файлу под курсором», но требует наличия расширения файла в строке. suffixesadd
добавляет указанное расширение. Если установить suffixesadd=.md
, то команда gf
на строке 'foo' будет искать файлы foo
и foo.md
.
set inccommand=nosplit
Только для Neovim. Настройка incommand
показывает в реальном времени, какие изменения внесёт команда. Сейчас поддерживается только s
, но даже это невероятно полезно. Если ввести :s/regex
, выделятся все соответствия. Если затем добавить /change
, он покажет все замены. Работает со всеми свойствами регулярного выражения, включая обратные ссылки и группы.
set statusline (:h statusline)
Определяет, что отображать на панели в нижней части каждого окна. Здесь форматирование намного сложнее и привередливее, чем в других настройках, так что придётся потратить время на объяснение. Есть некоторые простые трюки. Во-первых, посмотрим на строку состояния Vim по умолчанию:
:set statusline=%<%f %h%m%r%=%-14.(%l,%c%V%) %P
Тут проще всего заменить %P
(процент файла над курсором). Формат строки состояния — значение после знака процента в фигурных скобках. Поэтому для файлов Markdown можно написать такое:
:set statusline=%<%f %h%m%r%=%-14.(%l,%c%V%) %{wordcount()["words"]}
И заменить процент файла на количество слов в документе.
Или установить tabline
. Если вы не используете вкладки, то эту строку можно сделать «глобальной статусной строкой». Например,
set tabline=%{strftime('%c')}
всегда будет показывать дату сверху.
Привязки клавиш
У меня много привязок.
Очень много удобных клавиш в Vim по умолчанию назначено бестолково. Например, сохранение нажатия s
— это синоним cl
(экономия одного нажатия), а U
— то же самое, что и u
, за исключением записи undo как нового изменения, что функционально бесполезно. Q
идентично gQ
и в любом случае является колоссальной ловушкой. Z
используется только для ZZ
и ZQ
. Чёрт возьми, даже руководство Vim рекомендует переназначать клавиши _
и ,
на какие-нибудь функции, поскольку, «вероятно, вы, никогда их не используете». Я бы предпочёл не экономить одно нажатие, а добавить на клавиатуру совершенно новые функции. Вот некоторые из моих привязок:
nnoremap Q @@
Не замедляясь на переход в ex-режим, повторяет последний макрос.
nnoremap s "_d
Заставляет клавишу s
(с соответствующими назначениями для ss
и S
) работать как d, только без сохранения удалённого текста в регистре. Полезно для того, чтобы не засорять регистр.
nnoremap <c-j> <c-w>j
Перейти к окну ниже. Соответствующие назначения для h
, k
, l
. Работа с окнами становится гораздо проще.
nnoremap <leader>e :exe getline(line('.'))<cr>
Выполнить текущую строку, как будто она представляет собой команду. При экспериментах часто более удобно, чем q:
.
Специальные аргументы (:h map-arguments)
Команда map <buffer> lhs rhs
активирует назначение клавиш только для данного буфера. Это действительно удобно работает с автокомандами как временное сочетание клавиш или при определении назначений через функцию. Буферные назначения имеют приоритет над глобальными, то есть вы можете переопределить общую команду более полезной в конкретной ситуации.
Команда map <expr> {lhs} {expr}
проверяет {expr}
и использует возвращаемое значение в качестве финального переназначения клавиш. Один простой вариант использования — привязка в зависимости от условий. У меня есть такие:
nnoremap <expr> k (v:count == 0 ? 'gk' : 'k')
nnoremap <expr> j (v:count == 0 ? 'gj' : 'j')
Что заставляет j
и k
двигаться по строке до тех пор, пока не встретится число, а после этого назначение клавиши отменяется. Поэтому я могу перемещаться по длинным абзацам прозы, не нарушая такие сочетания, как 10j
.
Аргумент <silent>
помогает, если какие-то привязки запускают ex-команды.
inoremaps
Благодаря inoremap
привязки работают в режиме вставки. Там они начинают работать, поэтому inoremap ;a aaaa
введёт 'aaaa' вместо ';a'. Если вы хотите сделать что-то в обычном режиме, используйте <c-O>
. Например, если у нас есть
inoremap ;1 <c-o>ma
то ;1
установит в данной точке метку 'a
.
Я люблю указывать использовать точки с запятой в качестве ключа для переназначений, потому что в нормальных текстах после точки с запятой практически всегда идёт пробел или новая строка.
autocmd
Автокоманды отлично подходят для конфигурации. Обычно вы их настраиваете в таком виде:
augroup {name}
autocmd! " Prevents duplicate autocommands
au {events} {file regex} {command}
augroup END
Затем, если в файле {file regex} происходит какое-либо из событий {events}, то срабатывает команда {command}. События перечислены в списке :h event
. Например, если записать
augroup every
autocmd!
au InsertEnter * set norelativenumber
au InsertLeave * set relativenumber
augroup END
то Vim отключит relativenumber только для режима вставки.
Команда au {event} <buffer> {ex}
применяет автокоманду только к текущему буферу. Иногда я использую это для добавления краткосрочных обработчиков событий в конкретный файл.
BufNewFile, BufRead
BufnewFile
запускается при создании нового файла, BufRead
— при первом открытии буфера. Их обычно используют для добавления параметров и переназначений в конкретные типы файлов. У меня есть одно такое:
augroup md
autocmd!
au BufNewFile,BufRead *.md syntax keyword todo TODO
au BufNewFile,BufRead *.md inoremap <buffer> ;` ```<cr><cr>```<Up><Up>
augroup END
Только в файлах Markdown подсвечивается строка TODO, а символы ;`
в режиме вставки добавляет обозначение кода.
Автокоманды позволяют делать гораздо более сложные вещи. Например, au
для BufWriteCmd
переопределяет стандартное сохранение, позволяя реализовать нестандартную логику. Это выходит за рамки «трюков» и переходит в область «тёмной магии».
Плагины
Большинство знает о популярных плагинах, таких как vim-surround
и NERDtree
. Вот список некоторых малоизвестных, которые я считаю очень полезными.
Undotree
В большинстве текстовых редакторов отмена действий происходит линейно. Если вы внесёте изменение A, отмените его, а затем внесёте изменение B, то A потеряно навсегда. Однако Vim хранит всё дерево отменённых действий. Команда u
откатывает действие в текущей ветке дерева, а g
переходит к предыдущей хронологической версии. Можете просмотреть список отменённых действий командой :undolist
.
Но этот формат не очень наглядный. Гораздо лучше увидеть фактическое дерево. Именно это делает Undotree
: выкладывает хорошую ASCII-репрезентацию дерева отменённых действий с удобной навигацией.
vim.swap
Плагин предоставляет команды для обмена аргументами, поэтому вы можете в пару нажатий клавиш заменить (a, f(b, c))
на (f(b, c), a)
. Мне регулярно приходится делать такие правки, так что это сильное улучшение качества жизни.
Neoterm
Подключает API более высокого уровня к встроенному терминалу neo/vim. Например, :T {text}
отправляет {text} в консоль. Хорошо подходит для создания интерактивной среды.
" TODO {{{
В этой статье не охвачены многие темы, потому что они слишком технические или нуждаются в подробном объяснении, как написание функций или синтаксическая система. А ещё я многого не знаю. Хотелось бы детальнее изучить следующие темы:
Окна Preview, Quickfix и List
Я иногда использую инструменты с этими окнами, но не знаю, как ими манипулировать. Хотелось бы добавить ошибки quickfix в мой плагин TLA+. Ещё мне нравится идея поместить вспомогательную информацию и команды обратного вызова в окно предварительного просмотра. Это открывает некоторые возможности, которые трудно воспроизвести в IDE.
Neovim API
Neovim предлагает продвинутый API для интеграции Vim с внешними программами. Ваш скрипт Python может отправлять команд инстансу Neovim, а вы можете управлять редактором через сервер, например. Я видел некоторые классные концептуальные демонстрации, где автозаполнение происходит на основе информации в браузере. Кажется, это очень классно!
Текстовые объекты
Никогда таких не создавал.
Итак, это был краткий обзор некоторых неявных функций Vim. Надеюсь, вы узнали что-то полезное!
Автор: m1rko