Чем особенно хорош Vim/Neovim? Тем, что твой инструментарий — это не только редактор (который сам по себе сильно расширяем плагинами и имеет богатый базовый функционал и очень гибок в области кастомизации), но и всё ваше рабочее окружение, со всем юникс-вейным прилагающимся инструментарием из gnu/coreutils и не только. Можно не уходя из редактора взять любую программу или интерпретатор ЯП и использовать его прямо в редакторе.
Предисловие
Этот пост писался на скорую руку для приватного круга лиц, но я решил что его вполне можно запостить и на Хабр. Для кого-то может стать вдохновением, кому-то поможет лучше понять филосовию Vim, а кто-то приметит для себя пару трюков. Сразу на всякий случай оговорюсь, что не следует ожидать, что я стану кому-то что-то доказывать в комментариях, например убеждать что вам нужно определённо бросить вашу разжиревшую IDE и начать пользоваться Vim-ом, мне это совершенно не интересно.
К делу
Вот к примеру возьмём такой кусок кода (из конфига Haskell проекта), список зависимостей пакета (пример в вакууме):
build-depends:
X11
, base
, directory
, extra
, GLFW-b
, safe
, aeson
, containers
, data-default
, text
, process
, time
, dbus
Что мы хотим?
- Отсортировать зависимости по алфавиту, по-возрастанию
- Отсортировать регистро-независимо (
X11
иGLFW-b
не должны уходить вверх над всем) - Восстановить запятые (
aeson
уйдёт в самый вверх и у него уже не должно быть запятой слева, а вот уX11
должна добавиться запятая слева) - Восстановить отступы (чтобы можно было и в другом конфиге с другим уровнем вложенности просто достать команду из истории и переиспользовать её, или вообще забиндить команду на хоткей в конфиге Vim-а)
Решение
В первую очередь выделим (визуальным выделением) список зависимостей кроме первой строки build-depends
. Можно конечно просто нажать V
(визуальный режим с построчным выделением) и через jk
или стрелочками вверх-вниз выделить нужные строки. В своём случае я это делаю одним взмахом руки с помощью кастомного хоткея для визуального режима:
xn iz <esc>[zV]z
Находясь например в середине списка зависимостей я просто жму viz
и уменя выделены все зависимости, т.к. выделен весь fold, который в свою очередь — текущий блок вложенности (т.к. foldmethod
у меня задан как indent
). Но можно и вручную набирать последовательно [zV]z
без кастомного хоткея ([z
прыгает в начало fold-а, а ]z
в конец), но т.к. для меня такая операция часто-употребляемая, то я укоротил её до viz
— тут нет модификаторов вроде шифта и прожимается на рефлексах в одно мгновение (наиболее близкий стандартный аналог — vip
для выделения блока до ближайших пустых строк).
Далее жмётся :
(двоеточие) для перехода в командный режим для выполнения команды относительно текущего визуального выделения. По сути обычный командный режим, но с дописанными сразу маркерами выделения, т.е. будет выглядеть как :'<,'>
где '<,'>
— это диапазон выделения, где '<
— первая строка визуального выделения, а '>
— последняя.
После нажимаем !
(восклицательный знак) на клавиатуре, это будет означать что всё, что идёт дальше — это shell/bash (в зависимости от настроек) команда. Будет выглядеть как :'<,'>!
. На самом деле после выделения можно сразу нажать !
и получим тот же результат — :'<,'>!
.
Данная операция перенаправит выделенные строки в STDIN команды и заменит выделенные строки на STDOUT выхлоп от этой команды. Для примера можно использовать команду sort
, чисто для проверки, результат пока не тот, что нам нужен — '<,'>!sort
и жмём Enter, получим:
build-depends:
, aeson
, base
, containers
, data-default
, dbus
, directory
, extra
, GLFW-b
, process
, safe
, text
, time
X11
Способ с coreutils и вообще башем
Восстановим предыдущее выделение (можно нажать gv
для восстановления последнего выделения) и нажмём !
и далее стрелку вверх — это восстановит из истории последнюю команду, таким образом нам не надо писать заново, просто достаём из истории предыдущую команду и изменяем её. Для более комфортного редактирования команды можно нажать Ctrl
+f
— это откроет доп. окно с нормальлным стандартным редактированием команды, со всеми возможностями Vim-а, кстати там будут видны и все предудщие команды из истории в качестве отдельных строк, которые также можно выбрать, отредактировать и выполнить.
Как тут правильно поступить — можно выдумать находу, мой же поинт такой: сначала удаляем запятые, сортируем без них (регистро-независимо), потом возвращаем запятые, кроме самой первой строки.
Сначала удаляем запятые (а у первой строки доп. отступ, чтобы у всех строк был одинаковый отступ) используя команду sed
с регулярным выражением ([, ]
— запятая или пробел, а затем ещё пробел, (w)
экранированные скобки для выделения блока для подстановки, чтобы он потом был доступен как 1
, w
— первый буквенный символ, в замене мы востанавливаем буквенный символ подстановкой 1
):
:'<,'>!sed 's/[, ] (w)/1/'
Получим следующее:
build-depends:
X11
base
directory
extra
GLFW-b
safe
aeson
containers
data-default
text
process
time
dbus
Далее пайпим (через символ |
— это фича баша) в команду сортировки sort
передавая ключ -f
для регистро-нечувствительности:
:'<,'>!sed 's/[, ] (w)/1/' | sort -f
Получаем:
build-depends:
aeson
base
containers
data-default
dbus
directory
extra
GLFW-b
process
safe
text
time
X11
Почти готово! Осталось только добавить запятые, а первой строке — пару пробелов. Воспользуемся всё тем же sed
, в синтаксисе его операций можно указывать строки и диапазоны строк (как и в самом Vim-е, синтаксис такой же, ну или почти такой же). Префикс 1
будет означать первую строку, 2,$
означает диапазон со 2-ой строки и до конца ($
, как и ^
означает начало файла, по аналогии с такими же символами в регулярных выражениях, которые означают конец и начало строки). Будем использовать w
чтобы скипнуть отступ и сразу выделить первый буквенный символ: 1s/w/ &/
— тут мы делаем замену для первой строки, восстанавливаем первый буквенный символ через &
(по аналогии с 1
, только &
означает всё, что попало под регулярку целиком, в то время как 1
означает первый блок, завёрнутый в круглые экранированные скобки), добавив перед ним пару пробелов. Для остальных строк вместо двух пробелов добавим запятую + пробел следом: 2,$s/w/, &/
, целиком команда будет такой: sed -e '1s/w/ &/' -e '2,$s/w/, &/'
, — -e
мы используем чтобы отделить 2 операции друг от друга. В Vim вся операция целиком будет выглядеть как:
:'<,'>!sed 's/[, ] ([^, ])/1/' | sort -f | sed -e '1s/w/ &/' -e '2,$s/w/, &/'
Применяем и получаем:
build-depends:
aeson
, base
, containers
, data-default
, dbus
, directory
, extra
, GLFW-b
, process
, safe
, text
, time
, X11
Готово! Писать второй раз её уже не нужно, просто набираем первые несколько символов, напр: :'<,'>!se
(фактически нужно нажать лишь !se
), — и стрелкой вверх достаём нужную команду из истории. Так или иначе рекомендую почаще тренироваться писать такие штуки сходу. Таким образом вы как прокачаете навыки повседневной работы в bash, так и в самом Vim-е, т.к. по сути вы делаете тоже самое.
В конце-концов, всю эту команду можно назначить на хоткей, или абстрагировать в функцию, и переиспользовать всюду на раз-два.
Используя сторонний ЯП
Вместо запуска чего-то из coreutils можно запустить интерпретатор какого-нибудь удобного для вас ЯП, мне вот нравится такие штуки делать через Perl6 (он же недавно переименовался в Raku):
:'<,'>!perl6 -e 'my @x=lines.map(*.subst(/<[,s]>s(w)/,{$0})).sort(*.lc); @x.shift.subst(/w/,{q/ /~$_}).say; .subst(/w/,{q/, /~$_}).say for @x'
Да хоть на жопоскрипте (node.js):
:'<,'>!node -e 'let fs=require("fs"), x=fs.readFileSync(process.stdin.fd).toString().replace(/n$/,'').split(/n/).map(x=>x.replace(/[, ] (w)/,"$1")).sort((a,b)=>a.toLowerCase().localeCompare(b.toLowerCase())); console.log(x.shift().replace(/(w)/," $1")); process.stdout.write(x.map(x=>x.replace(/(w)/,", $1")).join("n"))'
Такое можно проделать и на VimL/Vimscript внутри самого Vim, без вызова внешних команд. Но этот пост не об этом.
Естественно, как уже можно было догадаться, вы можете легко сохранить свой скрипт в отдельный файл, или даже скомпилировать собственную программу, которая что-то берёт на вход в STDIN и выдаёт что-то обработанное в STDOUT и использовать это в Vim просто вызывая (что, опять же, можно назначить на хоткей):
:'<,'>!~/my-program-or-script
Таким образом, когда вы пишите код в Vim, в вашем распоряжении не только сам Vim, но и всё ваше рабочее окружение.
Один из самых простых примеров, отпреттифаить JSON-файл:
:%!jq
Всего несколько нажатий клавишь, зачем переизобретать AST-парсер и преттифаер для JSON для любого нового редактора/IDE/whatever, когда можно просто взять и пропустить файл через jq
никуда не уходя из Vim-а? Я уж не говорю о том, что вы можете через jq
таким образом обработать ваш большой JSON файл, никуда не уходя из Vim-а, найти например нужный ключ в дереве, отсортировать, оставить только нужные данные, etc.
Автор: Viacheslav Lotsmanov