Pull to refresh

Минимум один трюк Vim, про который вы не знали

Reading time 10 min
Views 35K
Original author: Hillel Wayne
Я уже восемь лет работаю в 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. Надеюсь, вы узнали что-то полезное!
Tags:
Hubs:
+30
Comments 16
Comments Comments 16

Articles