Статья-продолжение первой части, в которой не уместилось всё что хотел описать. Напомню, что в ней я начал описывать своё рабочее окружение и dotfiles.
Чаще всего less-ом приходится смотреть вывод результатов поиска GNU grep:
Все опции говорят сами за себя. Отображать название файлов, показывать номера строк, включать цвета и перенаправлять всё в пейджер. Самый часто используемый pipe-aware алиас G: f bar G foo W будет, грубо говоря, аналогом find. -name "*bar*" | grep foo | wc -l. GS бывает используется для некоторых задач где важен порядок следования численно отсортированных результатов. А написан он полностью заглавными буквами, так как gs это Ghostscript, а набирая gS часто ошибаюсь в моменте нажатия вовремя Shift-а для заглавной «S».
Почему GNU grep, ведь у меня же есть grep из коробки в самой FreeBSD? Как бы не уважал GNU проект за его следование идеям свободного ПО, как бы ненавидел GNU проект за умение создавать монструозный по размерам, пухлый и переусложнённый софт, да ещё и регулярно с GNU-специфичными расширениями и форматами, но GNU grep на порядки быстрее *BSD версий. Когда-то его производительность была не такой и использовался популярный ack, написанный на Perl. По точно такой же причине использую GNU sed по умолчанию — он на порядок быстрее может отрабатывать.
В ~/.zshenv файле находятся многочисленные настройки переменных окружения. Большую его часть уже показал. Но пока ещё остаются белые пятна:
Всё это настройка путей до исполняемых файлов, man, info, pkg-config, и прочего. zsh умеет делать «связанные» переменные массивов со строковыми переменными с сериализованные представлениями этих массивов. Ведь PATH=foo:bar это, по сути, массив из foo и bar. Некоторые переменные априори встроены и известны (path, manpath), а на некоторые явно создаю binding через export -TU. Работа с массивами более удобна и error prone.
Так уж получилось, но практически всё что делает freedesktop.org, мне противет всем Unix, DJB-way и suckless мировоззрениям. Хочу, чтобы всё что касается одной программы, находилось сосредоточенным в одном месте. В Unix мире плюс-минус так и было принято. XDG же делает совершенно противоположно. Благо, везёт, что почти все программы что по умолчанию уважают XDG переменные окружения — мне не интересны в плане хранения их состояний. Поэтому в tmpfs создаю директории для них, где оседают кэши и default конфиги.
Оставшаяся часть переменных окружения:
Когда перехожу в директорию рабочего проекта, то хочу подготовить с ним работу. Для Python проектов это может означать включение virtualenv. Для проектов на Си это добавление/переопределение PATH, LD_LIBRARY_PATH. Когда-то создавал PROJ/.init файл, на который делал source (.-команда). Бывает что в него добавлял и cd в рабочую директорию проекта. То есть, сначала мне надо сделать source, а потом ещё возможно и cd в нужную поддиректорию. С «отменой» произведённых изменений переменных не парился — просто запуская очередной shell с чистыми переменными.
Нашёл готовые решения для этой задачи: direnv (который мне не нравится из-за громоздкости, сложности и негибкости) и zsh-autoenv. Но в исходном коде последнего много лишнего. Поэтому почти полностью переписал этот плагин, в основном с оптимизациями производительности и убиранием огромной части кода.
Теперь, если перехожу, например, в свой PyDERASN проект, даже в одну из его поддиректорий (cd ~pyd/tests), то в нём автоматически запустится:
Активирующий virtualenv. Если покину иерархию ~pyd директорий, то автоматически запустится:
Но очень часто нужно изменять переменные окружения, которые бы неплохо потом восстанавливать до оригинального значения:
autostash является единственной командой оставшейся в моей версии zsh-autoenv, запоминающей оригинальное значение переменных. Главное удобство autostash в том, что в .autoenv_leave.zsh не нужно вообще ничего писать для восстановления значений! Поэтому в данном arbeit проекте нет никакого .autoenv_leave.zsh. В отличии от direnv, всё эти файлы являются обычными zsh скриптами, а не очередным языком со своими командами. Можно и цвета поменять и приглашение командной строки и всё что придёт в голову.
А для пущей безопасности, zsh-autoenv требует явного одобрения запуска этих скриптов, сохраняя контрольную сумму в перманентном файле. Подсунуть .autoenv.zsh в git коммит для компрометации компьютера не выйдет.
Замечу, что для удобной работы в zsh не обязательно иметь кучу конфигурационных файлов на сотни строк. Конфиг для root-а состоит всего-лишь из:
А чтобы научиться пользоваться множеством фич из серии !$:h:s/m/f/ (подстановка последнего аргумента предыдущей команды, у которого отрезан хвост (как правило, имя файла до директории) и в нём замена «m» на «f» — отнюдь не надуманный пример) — нужно читать документацию zsh. Я это делаю просто время от времени, со случайной позиции, находя что-то новое и интересное, пытаясь его использовать. Как правило, прочитать и узнать то можно о дюжине новых трюков, но в голове останется только пара — поэтому процесс регулярно надо повторять. Оно стоит того!
Последнее что покажу про zsh это процесс запуска и выхода:
Изначально zsh читает .zshenv, затем во время логина .zprofile, затем .zshrc, а при выходе .zlogout. Всё что касается интерактивной сессии должно находится в .zshrc. Каждый раз при логине делается очистка домашней директории от возможно появляющегося мусора.
Для напоминаний о датах/событиях использую remind утилиту. Прежде пользовался родным calendar, но в нём нельзя настроить период за который необходимо заранее предупреждать о датах. Какие-то требуют предупреждения за месяц, а какие-то за 1-2 дня. Последняя строка конфигурации заставляет показывать через сколько дней произойдёт то или иное событие.
Почему некоторые команды у меня сделаны в виде алиасов, а некоторые в виде вынесенных в отдельный файл скриптов? Исключительно вопрос частоты их использования. Команды работы с git и ssh — очень частые, поэтому потребовали бы ощутимого overhead-а на ОС и ФС считывая и интерпретируя код из ~/bin/XXX скрипта. А загромождать namespace и память zsh редко используемыми алиасами тоже не гоже.
Календарь используется только для отдалённых дат, для ежемесячных вещей, типа «оплатить квартиру». А в качестве блокнота у меня t скрипт. Когда-то это была Python программа tnote. Но пришлось переписать, ибо запуск Python — достаточно медленная операция. Переписал на POSIX shell, а недавно на zsh, дабы почти полностью избавиться от вызовов внешних команд.
Это одна из самых часто используемых команд ежечасно! Она может сохранить заметку, показать их краткий список, удалить и отредактировать. Как только у меня появляется в голове хоть что-то, что нужно бы куда то записать, то сразу ввожу в любом свободном терминале, например, t a купить хлеба. И регулярно просматриваю и проверяю что у меня в заметках. Вот буквально сейчас у меня:
Когда посмотрю «один из самых страшных фильмов» (как гласит пресса), то сделаю t d 3. t e 4 отредактирует заметку под номером 4. А t 4 её полностью покажет (в данном случае она на восемь строчек). Кроме того, добавив N=XXX переменную окружения можно вести несколько namespace-ов, в которых веду эфемерные заметки по разным рабочим проектам, типа текущих IP-адресов и административных задач не требующих занесения в трэкер.
Когда у меня был Palm PDA, то с ним не расставался, записывая абсолютно всё что надо бы было не забыть или проверить. Без него у меня бы был блокнот с ручкой. t утилита именно для подобных задач. Если что-то не записал ручкой или клавиатурой, то могу забыть через полчаса — а так хотя бы помню факт записи. Это не долговременные заметки типа:
Когда-то прилично использовал vimwiki плагин, дающий Wiki систему внутри Vim и даже рендерящую её в HTML файлы, на основе чего делал многие свои сайты. Но потом пришёл к выводу, что всё это переусложнение и мне достаточно простых текстовых файлов в ~/notes, календаря и «горячих» короткоживущих t заметок. А для сайтов лучше что-то другое использовать.
Одна из утилит заслуживающая особое внимание — GNU parallel. Это скрипт на Perl, в котором использую наверное только 5% из всех возможностей, но простота его применения влюбляет. Множество CPU-hungry программ не распараллеливаются: например, мультимедиа конвертеры. А музыку обработать хочется побыстрее. Делаем parallel flac -d ::: **.flac чтобы, заняв все процессоры, запустить кучу flac декодеров. parallel opusdec {} {.}.wav ::: **.opus чтобы сделать из них Opus. Можно аудиокниги пределать в MP3 чтобы слушать на плеере:
Мультимедиа файлы частенько надо массово переименовать. Например, скачал с YouTube-а разные клипы от RetroAhoy и все они имеют общий ненужный (мне) префикс. Используем встроенный в zsh плагин zmv:
Все .jpeg переименовать в .jpg: zmv -W '*.jpeg' '*.jpg'
Во всей иерархии директорий, во всех именах заменить пробелы на подчёркивания:
Все эти примеры взял из своего ~/secure/.history, не надуманные.
Как у меня устроена музыкальная коллекция? Примитивно: каждый альбом в своей директории, каждый трэк в отдельном файле. Исполнитель-Год-Название альбома. Все пробелы в именах заменены на подчёркивания. Если это откуда то извне пришедшие MP3, то в них удаляю все IDv2 тэги: id3v2 -D *.
Я требую lossless вариант музыки! Очень даже запросто откажусь слушать её lossy версию, ибо с моими неплохими наушниками, усилителем и ЦАПом могу отличать 320Kbps -q 9 MP3 файл от lossless. И я получаю дикое удовольствие от хорошего звука. К сожалению, некоторые мною любимые жанры (лютый порно/копро грайндкор с харш нойзом) бывает невозможно нигде достать, даже нелегально скачивая, кроме как в этом MP3 (ё моё, XXI век, а всё до сих пор люди используют этот формат, ведь есть и свободные кодеки, в разы лучше сжимающие!).
Бывает, приходится доставать запись с пластинки, даже современных альбомов, так как на них бывает отличающееся сведение с более широким динамическим диапазоном. Это ценнее чем полнота частот и присутствующие потрескивания.
Кириллические имена могут быть неудобны из-за всяких архаичных ОС. Для транслитерации их имён использую один из своих старейших скриптов:
Для lossless сжатия использую WavPack. Судя по всему (я тут диванный эксперт), он лучше спроектирован и проще чем FLAC. Плюс на несколько процентов получше жмёт. Плюс не имеет ограничений на формат входных данных, типа поддержки 32-bit float, которые было мне встречались. На объёме в несколько сотен гигабайт музыки он мне дал несколько десятков гигабайт свободного места (преобразование из FLAC) — тоже приятно.
На работе, где не может идти речь про открытые наушники, а значит и хорошего качества звучания, вполне годится и lossy. Использую Opus кодек. Прежде был Vorbis, но Opus на десятки процентов лучше может сжать при схожем на слух качестве.
Когда ко мне попадают архивы с одним .flac и .cue файлами, то декодирую, cuebreakpoints *.cue | shnsplit *.wav командой разбиваю получившийся .wav на трэки, далее древнейшим скриптом (который бы надо переписать, но раз работает — не тронь) генерирую команды переименования на основе названий песен из CUE файла:
После чего, сжимаю parallel wavpack -hh ::: **.wav командой. Иногда применяю нормализацию по пиковому уровню для всего альбома: normalize --peak *.wav. cdparanoia используется для считывания купленных аудио дисков.
В качестве проигрывателя, с самого момента рождения и до последних лет использовал mplayer+mencoder. Но mpv имеет лучшую поддержку аппаратного декодирования, существенно быстрее делает перемотку, плюс умеет gapless playback, подходя в качестве музыкального проигрывателя.
Настройки клавиш ввода связаны с моей 20-летней привычкой использования MPlayer. Проще перебить клавиши, чем перепривыкать. В начале статье упоминал про ~/.Xmodmap файл, в котором как раз были binding-и мультимедиа клавиш моей клавиатуры.
В основном конфиге mpv настройки аппаратного декодирования, буферизации, gapless проигрывания и разные профили связанные с выходными источниками звука и применением нормализации громкости. Почему эти профили имеют такие короткие названия?
Потому что запускаю mp скрипт, преобразующий двухбуквенные слова-аргументы в --profile=XX. mp AM VN ... сделает вывод звука на HDMI монитора с нормализацией звука.
Для кодирования/перекодирования в mencoder уже стало не хватать возможностей. Всегда сторонился FFmpeg проекта, видя его непонятные аргументы, считая что любое действие с ним это сплошная боль. Но познакомившись поближе, всё же потеплел. Со всеми задачами справляется, хотя и не без помощи самого большого ~/notes/cmd/ffmpeg. mpv меня напрягал ещё тем после mplayer, что в нём нельзя было делать такие вещи как dump звукового потока (если ничего не путаю) — теперь много подобных задач переехало на ffmpeg.
Применений у ffmpeg у меня тьма. Одно из самых частых это «перекомпоновка» контейнера, убирая из него как можно больше метаинформации, оставляя только определённые аудио/видео дорожки:
Контейнер AVI оставляю в покое. А вот никаких MP4 не позволю себе иметь, переконвертируя в Matroska. Хочу поддерживать форматы и кодеки созданные людьми для людей, а не корпорациями для DRM-а. Частенько Matroska/WebM контейнеры надо создавать из отдельных аудио и видео файлов (отдельно сжал Opus-ом аудио, отдельно закодировал в VP8/VP9 vpxenc-ом видео) — для этого использую MKVToolNix инструментарий. Например очистка Matroska контейнера от метаинформации, которая или должна быть заполнена идеально (нереальный вариант) или пускай её не будет вовсе, делается так:
Как смотрю 4K видео? Которое в сжатом видео гарантированно не сможет у меня быть декодировано в real-time? Перекодирую как можно быстрее с уменьшением разрешения в MPEG2, имеющим (относительно) малые расходы на CPU и экономно тратит дисковое пространство:
Для работы с изображениями использую NetPBM набор Unix-friendly утилит. Для сканированных книжек DjVu формат (DjVuLibre программа) незаменим. cjpeg -optimize -progressive для получения JPEG. Для оптимизации уже существующих JPEG: jpegtran -optimize -progressive -perfect -copy none. pngcrush -rem alla -rem allb для попыток сжатия PNG. Для снимков экрана lossless WebP (cwebp -pass 10 -alpha_filter best -m 6 -mt -q 100 -lossless) формат может дать на 50-60% лучшее сжатие!
JPEG полученные из сторонних источников конечно же стараюсь не пересжимать, ибо ещё бОльшая потеря будет, но удаляю EXIF информацию из них: exiftool -all= -overwrite_original.
А недавно стал поклонником JPEG2000 формата, особенно с учётом иссякания патентов на него. Все отсканированные фотографии храню в OpenJPEG lossless сжатом виде: opj_compress -t 1024,1024 -i $i -o $i:r.jp2. Во-первых, отсканированные изображения с большим DPI даже не уместятся в какой-нибудь WebP, так как в нём ограничение размеров картинки. Во-вторых, .jp2 файл на треть меньше места занимает чем PNG. Да и в целом wavelet сжатие мне импонирует. Но приходится ограничивать размер tile, ибо будет уж слишком большое потребление оперативной памяти. Из-за его ресурсоёмкости на огромных по размерам отсканированных изображениях, рядом делаю .webp preview небольшого разрешения.
Просматриваю изображения sxiv программой: минималистична, быстра, поддерживает rotate, zoom, и показ множества картинок в виде таблицы из preview. Удовлетворяет всем, кроме поддержки JPEG2000 формата (но это претензия к Imlib2).
Для чтения PDF, лучше чем Zathura ничего не видел! Удобнейшие vi-клавиши, возможность показа презентаций, копирования и поиска текста. Стоят плагины для просмотра EPUB и DjVu форматов. Крайне важно для меня использовать MuPDF backend для PDF файлов, так как он может работать, без преувеличений, на порядок быстрее Poppler! Речь про то, что в Poppler можно секунд десять ждать когда отобразится жирная иллюстрация, тогда как в MuPDF она появится без ощутимых задержек. Плюс MuPDF умеет конвертировать FictionBook2 в формат пригодный для чтения внутри Zathura.
Zathura не идеальна — она тянет по зависимости GTK3 и DBus. Но… пока это меньшее из зол с которым готов смириться, ибо уж очень удобный просмотрщик.
Из коробки в ней нет никаких tab-ов. А иногда приходится читать параллельно с десяток документов. Стэковое представление окон в dwm будет не удобно. Zathura поддерживает XEmbed расширение X11, позволяющее одним программам («tab manager») включать в себя дочерние embeddable окна. Unix-way! Использую tabbed suckless менеджер для этой задачи. На данный момент, кроме Zathura, больше GUI программ для tab-ов у меня нет, хотя изначально туда встраивал и Lynx (когда не догадался что tmux достаточно).
~/bin/start-tabbed.sh выглядит сложным для простого запуска Zathura, но когда-то он использовался и для других. Его задача либо добавить окно в уже запущенный менеджер, либо запустить его с нуля.
zsh кстати поддерживает функционал по запуску программ на основе расширений файлов. Можно «исполнить» .pdf и автоматически откроется Zathura. Но что-то у меня не появилось привычки открывать программы таким образом. А всё что не начинает активно мною используется — удаляется, дабы не мозолить глаза и не усложнять конфиги на пустом месте.
Блокировку экрана делаю вызовом mylock скрипта из dmenu:
Он «отключает» электропитание монитора и очищает ключницы GnuPG и SSH агента, блокируя экран suckless slock утилитой.
Для форматов архивов у меня есть чёткое предпочтение: POSIX.1-2001 pax формат, который обратно совместим с ustar. Чисто технически это ustar, внутри которого есть особые директории с keyvalue атрибутами, хранящими длинные имена файлов, точные временные штампы и другую метаинформацию. К тому же, этот формат является стандартом, а не каким-нибудь GNU-специфичным расширением.
Я понимаю что есть задачи где потоковая природа tar будет мешать. В принципе, ничего не имею против ZIP64 для этого, у которого есть индекс файлов. Но в нём не сохранить Unix-специфичные атрибуты файла.
Но вот беда: tar API не сказать что Unix-way friendly, сравнивая с cpio, в котором работа с путями и поиск файлов это задача сторонней программы (find). POSIX стандарт описывает как сам pax формат, так и pax архиватор, имеющий cpio-like интерфейс. cpio имеет существенные ограничения формата, не позволяя многое заархивировать (длинные имена файлов, размеры файлов), а pax выглядит как лучшее из двух миров (API и формат). Но почему то исторически так сложилось, что pax нету в большинстве дистрибутивах. А даже если и сама команда имеется, то не факт что она поддерживает pax format.
Благо, всё же tar можно использовать в cpio-like режиме, читая файл со списком файлов из stdin: find… | tar cI — .... Более того, по умолчанию libarchive, используемый в FreeBSD для архиваторов из коробки, делает ustar формат архива, если никакие метаданные не будут потеряны, и только в случае потерь он будет делать pax.
tar используется мной и для записи/чтения данных на LTO5 стример. В курсе, что LTO5 уже поддерживает LTFS, но даже не пытался пробовать использовать ленты в этом режиме. Мне действительно хватает обычного потокового чтения заранее сжатых (и зашифрованных) архивов с диска.
Для сжатия я ярый поклонник Zstandard алгоритма. Работая существенно быстрее чем zlib/gzip, он ещё и лучше сжимает. Это если самый банальный простой вызов zstd команды через pipe. Он может из коробки распараллеливаться, занимая все CPU. А указывая высокие степени сжатия (--ultra -22), можно лишь на считанные проценты сжать похуже чем xz, который использовал прежде.
Но основная причина моей любви к Zstandard — умопомрачительная скорость декомпрессии. До zstd, даже не подозревал как часто упирался, при распаковке какой-нибудь программы, в CPU. Фактически CPU всегда был бутылочным горлышком, тогда как с zstd упираюсь в диски. Кроме того, с параметрами по умолчанию не упираюсь в CPU даже при созданий резервных копий: zfs send -R… | zstd | gpg -e -r offline -z 0 --force-aead, а только в скорость своей SATA SSD. FreeBSD уже уйму лет имеет zstd из коробки, поддерживая это сжатие даже в демоне ротации логов.
По сути, остаётся либо gzip ради совместимости, либо zstd для всего остального: и для быстрого сжатия и для архивного хранения с сильной степенью сжатия.
Кроме почтовых рассылок, личной почтовой переписки, иногда синхронного общения по IRC, получаю более 400+ новостных рассылок в RSS и Atom форматах. Много лет использовал rss2email программу, которая отправляла новости в виде почтовых сообщений, попадающих в отдельный почтовый ящик. Но всё же вернулся на Newsboat.
Знание о всех своих подписках храню в recfile формате, также выложенном на домашней страницы. Пример записей:
Тут хранятся и ссылки на сами «домашние» страницы, так и ссылки на feed-ы, которые могут жить на совершенно разных доменах. Я стал поклонником recfile формата (и recutils утилит для работы с ним) из-за простоты как для компьютера, так и удобства для человека. Плюс возможностью задавать простую схему валидации записей. Можно тривиально сделать выборку всех feed URL-ов для вставки в ~/.newsboat/urls: recsel -P Feed < links.rec | sed /^$/d. Кроме того, на Go написал программу преобразующую этот .rec файл в XBEL и OPML форматы обмена ссылками. Мне так понравился формат, что применяю его для хранения состояния зависимостей в goredo, для журналов NNCP и некоторых рабочих проектах.
Раз уж идёт речь про форматы, то не могу не упомянуть про любовь к Hjson. Это вечная holywar тема о том какой формат лучше для конфигурации ПО. Всё сильно зависит от сложности данных внутри. Где-то может и .ini подойти, и CSV, и termcap-like формат (много где используемый в FreeBSD).
Я точно ярый противник YAML: чрезвычайно переусложнён и библиотеки для работы с ним чаще превосходят по размерам саму основную программу. Мог бы привести вагон примеров когда чёрт ногу сломит человеку понять что же там написано и попытаться интерпретировать все эти структуры с кучей способов записи.
Я и противник TOML, опять же, на практике пробовав в своих проектах. Очень простые структуры в нём хорошо читаются. Но чуть более сложные, типа списков внутри словарей — увольте, но я, даже зная наизусть структуры своего NNCP, не мог самостоятельно корректно прописать это всё с первого раза.
А чем так плох JSON, если забыть про сложность (относительно termcap, ini) его парсинга? Невозможностью добавить комментарии, унылыми кавычками в ключах словарей, запятой в конце списков. Хочется тривиального небольшого синтаксического сахара. Hjson именно его и добавляет. Библиотека парсинга Hjson убирает этот сахар, превращая Hjson в обычный JSON, который можно отпарсить родной библиотекой. Золотая середина для конфигов со сложными структурами!
Что думаю про формат документации? Однозначно GNU Texinfo и Info! man подходит только для простых страниц — для кратких сводок опций. Попробуйте попутешествовать по man-у zsh! Вариант с man -P «less +/^EXAMPLES» считаю костылём. Отсутствие ссылок, разделения на секции по которым можно было бы перемещаться. Info — гипертекстовый документ, с разделением на секции/ноды. Плюс .info файл можно смотреть даже без специализированного броузера. Можно иметь ссылки и на картинки, которые в GUI Emacs бы отобразились. Похожая идея в CHM файлах Windows.
Чуть больше синтаксической подсветки, отключение автоматического перехода на следующие секции (это предоставляется как фича, но мне ужасно неудобное поведение), работоспособность колеса прокрутки и немного vi-like клавиш управления.
Это всё речь про форматы которые просматриваются. А формат исходного текста уже может отличаться. И man (mdoc) и и HTML тоже можно набирать сразу же от руки. А можно использовать какой-нибудь reStructured Text (reST), который и HTML и даже .info в состоянии сгенерировать. Уйму лет web-сайты делал на reST.
Как отношусь к Markdown? Никак не могу относиться к тому чего нет: нету никакого Markdown формата — есть только куча диалектов, частично совместимых между собой. И их общая часть — крайне скудна и не достаточна. К AsciiDoc у меня негативного отношения нет, но и каких-то плюсов относительно reST не вспомню — оба даже написаны на Python.
Но от reST в итоге отошёл (применяю разве что для Python проектов, ибо это родной формат в этой экосистеме). Не всегда хватает возможностей «самовыражения», типа «сделать жирную ссылку». Чуть более сложные вложенные структуры уже проблематично прописывать и генерировать автоматизированно, ибо требуется быть очень аккуратным с отступами и это становится сложно воспринимать с экрана.
Texinfo оказался золотой серединой между по человечески выглядящему reST и полного SGML тэгами HTML! Легко читать, легко писать, низкий порог входа, легко генерировать, свобода самовыражения и гибкость. Он действительно более близок и похож на TeX. Плюс написан на Си/Perl — мало зависимостей и отличная скорость работы. Почти все web-сайты генерирую из Texinfo кода. Единственное, что иногда создаёт неудобства, так это его заточенность под иерархичную структуру документов.
Ещё есть свойство спорной значимости: Texinfo может генерировать Docbook, очень приличного качества форматирования. Открыв каким-нибудь LibreOffice, можно сохранить в Office Open XML документ, почти без потерь форматирования задуманного автором .texi.
reST из коробки умеет читать docstring-и Python кода. Doxygen умеет из Си кода. Texinfo из коробки нет, но не проблема: docstringer.pl Perl скрипт находит в файлах комментарии такого вида, в которых есть пометка TEXINFO: XXX:
А в исходном Texinfo коде, содержащим @DOCSTRING CrickAppSignVerify@, будет сделана подстановка тела комментария.
Как бы много чего монструозного не было в GNU проекте, но их обязательное требование использования Texinfo/Info всецело одобряю.
Рабочие отчёты для печати создаю в LaTeX, используя TeX Live дистрибутив, чтобы не было раздумий какой CTAN пакет и как мне надо устанавливать. В нём же писал все курсовые, диплом и всё подобное. Схемы, картинки ни разу в жизни не рисовал «от руки» (мышкой что то там расставлять на экране) — только PGF/TikZ, GraphViz и Gnuplot. Только чертежи выполнял в QCAD. Почти все презентации выполняю в Beamer пакете LaTeX.
У меня стоит OpenJDK исключительно ради запуска PlantUML. Меня коробит от столь тяжёлой зависимости (Java), но PlantUML настолько хорош и прост в использовании, создавая и прекрасные растровые и Unicode текстовые диаграммы! Созданные им диаграммы у меня встречаются практически в каждой программе.
Бывают неэтично сделанные проекты где документация только в online виде. Проблему можно решить через зеркалирование сайта. Для каждого ресурса приходится какие-то параметры подправлять, но в общем случае команда:
wget умеет создавать и Web ARChive .warc файлы, которые затем можно просматривать через WARC-прокси:
Если Web-сервер предоставляет WebDAV, или есть FTP зеркало (не говоря про rsync), то использую LFTP, как например:
ZFS тут может здорово помочь в создании атомарных обновлений раздаваемых директорий. Сам HTTP/FTP сервер может смотреть на snapshot по /path/.zfs/snapshot/SNAPNAME пути, а долгое rsync/lftp обновление идёт просто в /path. Когда зеркалирование завершено, то новую версию snapshot над обновлённым состоянием можно переименовать в /path/.zfs/snapshot/SNAPNAME.
Ещё бы рекомендовал aria2 менеджер скачивания. Использую его как BitTorrent клиент, а также когда нужно скачивать множество файлов, с возможностью докачки, в несколько потоков.
Важно переопределить file-allocation, который по умолчанию полностью создаёт пустой файл перед скачиванием, что бессмысленно на современных copy-on-write файловых системах.
Из BitTorrent клиентов пробовал ctorrent — нравился, но нет DHT и UDP поддержки. Transmission — ресурсоёмкий, не нравился. Rtorrent долго использовал, пока не наткнулся на его hard-coded ограничения на максимальные размеры файлов/торрентов. К aria2 у меня вообще никаких нареканий. С ней использую немного изменённый (добавил команды просмотра подключённых peer-ов и их прогресс) diana frontend.
Последние лет десять, играю время от времени (отдых же нужен) в:
Всё написанное выше: про базовую работу в ОС, общение и развлечения. Про саму работу речи ещё не было. А это написание кучи кода, тестов, скриптов и документации. А также работа с СУБД:
Из интересного тут можно отметить только функции pretty printing JSONB полей PostgreSQL. Для редактирования SQL запросов активно применяю внешний редактор через \e вызов.
Начинал программировать с Perl, имел многолетний опыт с Ruby, PHP, Lua. Но основная работа была проделана на Python. А знакомство с Go коренным образом поменяло моё отношение ко всему остальному. Считаю этот язык неким Святым Граалем: продуманный до мелочей, крайне простой и лёгкий в поддержке и понимании кода — это самое главное. Всё остальное, если не имеет простоты и понятности — не играет роли. Это лучший язык для промышленного программирования из существующих, заточенный не для выпендрёжа, а для решения задач. Go (и его авторы) даёт не то что люди хотят, а то что им действительно нужно.
Недавно заметил, что на Python перестал писать даже простейшие скрипты типа преобразования моего .rec файла с ссылками в другие форматы: всё равно без раздумий использую Go. И на серверах у меня полностью исчезли интерпретаторы Python.
К Ruby, PHP и Lua уже не притрагиваюсь. Ничего против них не имею, но просто нет задач. Python до сих пор используется из-за большой кодовой базы на нём написанной. Мне не нравится путь развития и самого интерпретатора и в целом всей Python экосистемы, где даже просто установить пакет из PyPI может быть тем ещё приключением из-за несовместимых версий зависимостей и ОС-специфичных setup.py файлах.
Чуть более года назад, начал программировать на Си. И это дело мне очень нравится! Go, безусловно, остаётся «Си каким он должен был бы быть».
Не проходит и недели чтобы не было бы что-то написано на Perl. Крайне удручает существование множества негативных мифов о нём. Насколько понимаю, рождённых исключительно из голов не сильно квалифицированных системных администраторов, которым чаще всего проходилось иметь с ним дело в 90-х годах, так как не больно много других интерпретируемых языков было. Человечество ещё не придумало что могло бы занять его нишу. Писать большие проекты наверное я бы не рекомендовал, но программы до 2-х экранов кода, особенно связанных с обработкой текста — самое оно.
Я негативно отношусь к использованию awk. Во-первых, как и в случае с sed и grep — есть разные диалекты: код написанный для gawk не будет работать на *BSD системах, где его нет и не будет из коробки. Проблема совместимости и портируемости. Во-вторых, в 99.99% случаев люди используют 0.1% его возможностей, не уходящих дальше {print $NF, $1}. awk люди не знают. Знаком только с парой человек умеющих на нём писать полноценные программы. Зависеть от полноценного ЯП только для печати столбцов?
Perl же, для преобладающего большинства случаев, везде будет одинаково работать. Работа с текстом, сокетами, файлами и процессами (fork/exec, как минимум) не требует внешних .pm библиотек и встроена в интерпретатор. Который занимает несколько сот килобайт и безболезненно встраивается даже в OpenWRT дистрибутивы.
Он имеет и все средства из коробки для удобной замены awk oneliner задач. Например awk пример выше, на Perl мог бы быть таким: perl -lane 'print "@F[$#F, 0]"' — работающим и в GNU и BSD и OpenWRT системе, требуя довольно компактный единственный исполняемый файл. К тому же, работающий очень и очень шустро с текстом. Достаточно знать только основы Perl, вместо нескольких диалектов (чтобы помнить разницу между ними) sed, awk, cut (тоже имеющий некоторые полезные опции (-w) не везде присутствующие), tr и подобных.
Кроме Vim-специфичных вещей, для Python имеется только мизерный конфиг pdb отладчика:
hare алиас используется для убийства текущего процесса. Так уж вышло, что частенько быстрее прибить процесс, чем нажимать кучу Ctrl-D/C. А pp1 используется для pretty printing-а ASN.1 структур.
Позволит не создавать .py[co] файлы, смысла в которых не вижу, только мешаясь на диске.
Я не использую PyPI в PIP напрямую. Написал GoCheese PyPI-совместимый сервер для кэширующего проксирования и отдачи локально загруженных пакетов. Удивительно, но на момент его написания (до сих пор?), на Python не было ни одного удовлетворившего меня простого прокси. pyshop катастрофически медленный. Благодаря GoCheese абсолютно всё, что когда либо устанавливал — надёжно сохранено на диске, не требуя Интернет. И работает стремглав быстро. А для хранения состояния использует директории и файлы, проверяя и запоминая контрольные суммы.
Ещё одна программа исключительно для Python исходного кода, написанная не на Python:
Она преобразует import-ы, имеющие кучу вариаций написания:
к единому «каноничному» виду, очень дружелюбному для разрешения git конфликтов, и простому для работы в редакторах. Чёткий и детерминированный:
Кэш для промежуточных файлов сборки не хочу хранить на диске, так как это занимает много места, быстро становясь не актуальным, плюс потенциально несущим ценную информацию о коде. Не использую ни sum.golang.org, ни proxy.golang.org, так как это утечка приватных данных. Текущая GOPROXY=off настройка вообще отключает лазанье в Интернет — например вызов go doc частенько туда хочет. Когда мне надо явно разрешить скачивание кода, то добавляю GOPROXY=direct перед командой go get.
Так как Go штатно может иметь множество версий одной и той же библиотеки в $GOPATH/pkg/mod, то размещаю $GOPATH директории на ZFS dataset с включённой дедупликацией. Результат: dedupratio=1.26x.
Из must-have утилит для работы с Go могу отметить:
Я яростный поклонник Go модулей! Не скажу что мне их идея сразу же понравилась и что всё шло как по маслу и было понятно. Но в Go всегда так: спустя время ты понимаешь гениальную простоту и продуманность. Очень много людей, судя по блогам, не понимают как использовать vendor директорию и что $GOPATH, действительно, становится не нужен для сборки софта из чётко предоставленного исходного кода в tarball-е. Сам сторонник того, что в tarball всё должно быть, никакой зависимости от Интернета. Но $GOPATH deprecation ни капли не мешает.
Для Си разработки применяю LLVM, Clang, LLDB, clang-analyzer, clang-tidy, clangd LSP сервер, clang-format и include-what-you-use (IWYU). Сплошная LLVM/Clang экосистема. LSP сервер действительно помогает для linting-а. Статические анализаторы уйму раз ткнули меня носом в проблемы которые бы заметил неизвестно когда. А с IWYU забыл про геморрой корректного включения нужных #include-ов, хотя приходится рядом иметь mapping-файл с коррекциями его решений.
Из-за clang-format не форматировал ни единого кусочка (ну кроме таблиц констант) исходного кода, вызывая cfmt.sh. Стиль формата не полностью меня удовлетворяет, но пока наименее раздражающий:
Опыта на Си у меня совсем немного, но среди всех отладчиков с которыми работал в разных языках: pdb, ipdb, delve, gdb — LLDB показался самым удобным и в нём хочется путешествовать по всему что происходит в дебрях ОС. Единственный алиас для печати участка памяти:
Во время разработки на Си, ощутил и понял удобство DTrace. И прежде что-то на нём иногда запускал и смотрел, но USDT (userspace defined tracing) пробы встраиваются очень просто и с минимальным overhead-ом для программы. Позволяя потом трассировать что в ней происходит. Причём USDT код может как для включения DTrace проб использоваться, так и SystemTap в GNU/Linux, далее используя eBPF framework для трассировки. Таким образом, исходный код пригоден без изменений под разные системы трассировки.
Есть три небольшие HTML странички, которые существенно изменили всё в моей жизни программиста. Это предложение DJB о системе слежения за зависимостями, о системе сборки redo. Гениальная простота этого предложения, да и сама реализация, которую можно на Go написать за один день (подтверждено на практике), впечатлили меня как никогда. Об этом я уже писал. Сейчас у меня не осталось ни одного личного или рабочего проекта, где бы использовался Make или autoconf. У них просто нет никакого смысла существования.
Я был не совсем удовлетворён redo-c реализацией и был удручён скоростью работы apenwarr/redo реализации на Python. В итоге написал свою версию на Go, используя десятки и сотни раз на дню. Но в рабочих проектах проверяю совместимость .do файлов и с другими реализациями, где их может быть под сотню, включая default*.do.
Здесь разрешаю распараллеливать задачи сборки и не делать fsync вызов, дабы потенциально ещё больше ускорить работу.
Ещё я поклонник TAI64 формата времени, познакомившись с ним в daemontools утилитах. В UTC время всегда идёт вперёд, но не монотонно из-за високосных секунд (leap seconds). TAI время их учитывает и идёт равномерно. А TAI64 это предложение от DJB для формата хранения этого TAI значения: с сохранением секунд, наносекунд или аттосекунд. В своих программах начал использовать TAI64N. Хотя это и потребует наличия обновляемой базы данных високосных секунд, чтобы точно переводить TAI в UTC.
Перехожу к самому важному в своей системе: текстовому редактору. В нём провожу преобладающую часть всего времени пока не сплю. Всё что касается работы, ввода чего бы то ни было на web-сайтах, когда-то переписки с девушками (много тысяч писем!), выполнение работ в институте, создание статей/документов/отчётов и презентаций — всё делалось и делается в редакторе. Работа с SQL в клиенте СУБД, редактирование длинных строк командного интерпретатора, полей ввода броузера — всё использует внешний редактор (в моём случае это Vim). Это самая важная программа на компьютере, без которой он и не нужен.
Есть люди спрашивающие почему Vim, а не «модное Electron-driven творение или монстр от Microsoft/Apple»? Не знаю что им ответить. Подозреваю что там могут быть лучше интегрированы отладчики и подсказки связанные с языком программирования, но какой в этом смысл, когда сама правка кода там выполняется редактором другой весовой категории? «Почему ты используешь Unix или Windows NT, а не MS-DOS?» — для меня вопрос такого же уровня. В Vim несколько сотен команд редактирования и движения, сотни команд для командной строки, сотни переменных для настроек мельчайших нюансов поведения, плюс ещё возможность и скриптовать это всё. О каком сравнении может идти речь?
Есть люди считающие, что скорость редактирования не важна, ибо они бОльшую часть времени тратят «на раздумья». Не спорю что сама работа руками может занимать меньше времени, вот только частенько проще что-то попробовать сделать, попробовать изменить, порефакторить и посмотреть что выйдет на деле, чем в «виртуальной машине» в голове. Возможно преувеличивая лишь самую малость, мне со стороны кажется, глядя на людей работающих в чём-то простом типа Блокнота/Word/Nano/mcedit, что они могли бы 75% времени, ими проводимым за экраном, сэкономить просто более эффективно работая в редакторе и shell-е. Да зачастую всё программирование это сплошное редактирование уже имеющихся кусков текста! Убеждён, что люди просто не понимают и не замечают как много времени они тратят на «материализацию» своих мыслей (что они хотят увидеть на экране).
Я очень уважаю Emacs, но всё же как редактор он будет чуть менее эффективным. Но признаю что разница между ними настолько несущественна, что profit от единой «экосистемы» Emacs будет запросто перевешивать эффективность Vim, даже обвешанного скриптами и интегрированного с терминалами. Но Emacs не буду пробовать: его постоянная долбёжка по Ctrl вредна для здоровья (статистика говорит о большем количестве проблем с руками у Emacs-еров) и я не сторонник функционального программирования, так как оно требует другого склада ума.
В Vim плохо одно: постоянное обучение и изучение (no pain, no gain!). 20+ лет с ним работаю, но до пор раз в пару месяцев узнаю какую-нибудь очередную интересную команду или трюк. Нет, всё есть в документации, во встроенном :help, но понять значимость, понять use-case-ы и ценность команды или способа совмещения команд между собой не выходит сразу. Время от времени, беру и читаю его документацию со случайной позиции, стараясь что-то новое отметить и начать это отрабатывать на практике. Кажется что вершина мастерства Vim недостижима, но уже не первый год замечаю, что из 99% статей про Vim, что открываю с заголовками «ultra advanced powerful tips and tricks», не узнаю ничего нового, ни бита полезной информации.
С Vim может быть другая проблема: вредные привычки. Самое убийственное для производительности труда в Vim что наблюдал со стороны — использование визуального режима (v) не к месту. Я это называю «блокнот-mode», когда можно и рукоятью электрической дрели забивать гвозди. Визуальный режим, означает что штатные motion-ы не выполняются (motion будет на визуальное выделение), а это приводит к невозможности создания макросов, использования normal команд в какой-нибудь :g команде, банального повторения (.) действия к отличающемуся куску текста. Это как пытаться бегать, но связав себе ноги вместе — передвижение будет, но не выше заданной планки.
Есть ещё мнение что Vim требует кучи плагинов. Только если для совершенно специфичных задач, как и любому бы другому редактору надо было. А в общем случае: полнейшая чушь, говорящая только о непонимании родных возможностей. И сам грешил этим: когда-то ставя себя пару десятков плагинов и удивляясь почему они не встроены из коробки. С годами, количество плагинов только уменьшалось. Либо оставались совсем task-specific, либо несколько must-have творений Tim Pope (об этом ниже).
И ещё бытует миф что для работы в Vim нужно много строчек в .vimrc. Пухлые конфиги, как мне кажется, возникают по двум причинам:
Рассказывать про команды и трюки в Vim не смогу, как и в случае с zsh: это слишком объёмная тема. Так что ограничусь только рассмотрением своих конфигов.
Включение синтаксической подсветки, filetype-специфичных плагинов, отключение совместимости с vi (без этого куча всего не будет работать), UTF-8 как кодировка по умолчанию и не пытаться перехватывать управление мышкой (чтобы в терминале выделять текст можно было).
Я форсированно ограничиваю количество цветов до 16 — это делает их насыщенными и контрастными, а не блёклыми и бледными, еле отличимыми друг от друга. Никогда не использовал цветовую схему отличную от default. На чёрном фоне с 16 цветами всё очень хорошо различимо.
relativenumber — ультраважнейшая штука, ради которой когда-то обновлял Vim из исходников, минуя пакеты. Существенно повышающая эффективность работы штука. Суть тривиальна: на каждой строчке показывается смещение относительно текущей. Строка которая на десять строк выше/ниже от текущей — будет иметь номер 10. Может ли человек быстро удалить две строки в Vim? 2dd — без проблем. А пять или десять? Уже нет, так как глазом он уже не в состоянии быстро посчитать и оценить сколько тут строк и какое число надо ввести. Относительный номер строки буквально покажет расстояние. Хочется прыгнуть «вот на эту» строку где-то на полэкрана выше? Относительный номер покажет что надо ввести например 23k.
Я часто хочу скопировать, переместить или удалить строку из какого-то далёкого места на экране: можно туда прыгнуть, нажать копирование/удаление, потом вернутся назад и вставить её. Я же сделаю :-27t. чтобы 27-ую строку выше скопировать под текущую. Это возможно сделать и без относительной нумерации, но абсолютные адреса строк чаще длиннее, просто потому что относительных на экране много не влезает и они состоят из 2-х цифр.
numberwidth уменьшает ширину столбца для отображения номеров строк. Так как используется относительная нумерация, то значений из трёх цифр там не будет и ширина в 4 символа (по умолчанию) излишня.
Тэги — архиважнейшая функция! И удручает как мало людей даже не знают об их существовании. Python-исты часто используют сторонние плагины для навигации по исходному коду и ни разу не видел чтобы это было так же эффективно как работа с тэгами. Физический смысл тэгов банален: это база данных ключейзначений, где ключами являются эти самые тэги, а значениями информация об их местоположении. Текстовый индекс. Как правило, каждая запись ещё сопровождается информацией о типе тэга (переменная, функция, класс, и т.д.) и возможно полной сигнатурой функции.
Названия тэгов (название переменных, функций) могут пересекаться, поэтому везде имеется функционал чтобы перемещаться между тэгами одного названия. Вот такими могут быть предложения тэгов для Go, Си и Python кода:
Даже nvi (не Vim!) в FreeBSD имеет навигацию по тэгам, как и less пейджер. Самое главное (для меня) что это очень быстрый способ навигации, в отличии от систем которые парсят исходный код на лету. ctags генератор файлов с тэгами даже идёт из коробки в FreeBSD. Но использую Universal Ctags генератор, так как он знает про Go язык, создавая vi/less-совместимые tags файлы.
Работая с проектом который использует сторонние библиотеки, хочется чтобы были тэги и по самому проекту и по зависимым библиотекам. Недавно просто добавлял символические ссылки к исходному коду зависимых библиотек в код проекта. Время от времени файл с тэгами необходимо обновлять, чтобы в нём отразились изменения. И с огромными сторонними библиотеками (например OpenSSL) это может занять ощутимое время, тогда как сама зависимая библиотека не обновляется вовсе.
Сейчас использую такой удобный подход: в проекте создаётся .tags директория:
В которой находится tags файл и возможно поддиректории со своими tags файлами. Проект с LibreSSL и libtasn1 зависимостями будет иметь следующую .tags директорию:
.tags/tags отвечает за тэги только локального проекта. У меня не вышло (возможно это бага моей версии Vim) сделать так, чтобы Vim воспринимал тэги сторонних проектов без поддиректорий.
Ну и ладно! tags настройка в .vimrc отлично работает с этим use-case. Для генерирования файла с тэгами использую vimscript :Ctags команду, которой передаю языки программирования для индексирования. Она самостоятельно находит местоположение .tags/tags файла, находящегося в корне проекта, от которого и будет запущена uctags команда индексирования:
Чтобы подготовить к работе новый проект, делаю mktags, а далее внутри Vim, например :Ctags C,C++. Зависимые библиотеки, интересующие меня, руками добавляю через mkdir и ln -s.
exrc опция позволяет Vim-у автоматически исполнять .vimrc/.exrc файл в текущей директории. Например в корне рабочего проекта туда можно заносить какие-то project/language специфичные настройки. secure строже будет относится к дозволенным действиям из этого файла, на всякий пожарный (мало ли кто подложит этот файл в git коммите). Когда-то планировал в этом .exrc файле указывать пути до файлов тэгов к другим проектам, но решил что хранить эту информацию в состоянии ФС будет проще.
Включение автоматического folding-а по отступам, с отображением в колонке слева уровня fold-а. При открытии окна, все fold-ы будут раскрыты (выполняется zR команда). Folding (схлопывание множества строк в одну) бывает очень полезным, когда хочется лицезреть кучу кода, но убирая тела циклов или целых функций. Часто схлопываю все fold-ы чтобы видеть в файле только названия функций/классов.
Настройка строки состояния, которая выглядеть может вот так:
Тут нельзя передать цвета, поэтому в местах с "^" указал границу между двумя разными цветами, между которыми нет пробела. То есть «HELP» и «utf...» написаны слитно. Стараюсь экономить место, которого не хватало бы для всего что хочется отобразить когда открыто много split-ов.
Для Python использую самописный асинхронный вызов linter-а. Его код тут не привожу, ибо занимает больше экрана. Суть работы проста: в фоне запустить pylint/pyflake, получить весь вывод, отпарсить и преобразовать в формат quickfix списка, загрузить в quickfix окно. Пока он работает, то LintStatus() возвращает строку LN, предупреждая что linter сейчас работает.
В Go у меня есть скрипт вызывающий внешнюю gogetdoc команду, которая в LintStatus() напишет GD. Строка состояния — динамичная штука.
Эти hook-и создают view-файлы при выходе из буфера и загружают их при открытии, если есть имя файла (?). Вообще в Vim есть .viminfo, но view является куда более полноценной информацией о состоянии рабочей «сессии», вплоть до последнего местоположения курсора.
Плюс не стоит забывать, что мой Vim запоминает и список буферов при выходе. Если просто запущу vim, то увижу пустой экран, однако на самом деле все буферы загруженные при выходе будут уже присутствовать. По сути не сохраняется только местоположение и размеры окон смотрящих на эти буферы. Но это можно сделать через штатный функционал сессий (:mksession, vi -S Session.vim).
Эти настройки заставляют Vim посылать escape последовательности изменения заголовка окон (в tmux и X11). В качестве заголовка используется последний элемент пути (название файла).
Один из примеров за что так люблю Vim. Настройка поведения форматирования комментариев и списков. Удовлетворяющего всех поведения быть не может — ничего не бывает сразу удобного для всех.
Тут указывается какие словари использовать для проверки орфографии. Настройки цветов изменены из-за моих правок для создания inverse цветов и форсированного ограничения палитры.
По возможности стараюсь использовать британские слова, поэтому и en_gb словарь. А русский словарь (~/.vim/spell/ru.utf-8.spl) генерировал самостоятельно, так как противник противников использования буквы «ё», а дистрибутивы из коробки подкладывают словари без неё. Поэтому пришлось конвертировать rus-myspell-yo-0.99f7 в формат словаря Vim.
Раздражающие опечатки:
Как часто люди перемещаются по «большим» словам (W)? Очень! Но также очень часто хочется чтобы курсор находился не на самом слове, а на пробеле перед ним. А штатно на пробеле нет никакой команды. У меня теперь есть. Плюс «большие» прыжки с большими словами (W/E) выполняются с нажатием Shift-а, что медленнее. Один из тех самых трюков которые люблю: просты и эффективны. А "_" заставляет перемещаться по подчёркиваниям, которых полно в Python коде бывает, чтобы прыгать по словам внутри названий функций и переменных.
\] копирует в X11 буфер обмена слово под курсором. \p заменяет слово под курсором, не загрязняя регистры, значением регистра по умолчанию. Удалили/скопировали где-то слово и им хочется заменить другое, но чтобы от прошлого не осталось и следа.
\d производит удаляет в "/dev/null" регистр. После, конечно же, нужно указать motion. Очень часто использую.
Всё остальное у меня вынесено в директории и отдельные скрипты. В Vim8 появились packages: возможность подключения дополнительных «chroot»-ов Vim директорий. Причём как с автоматическим запуском, так и опциональным. Например плагины Tim Pope у меня установлены так:
А мой изредка используемый (поэтому не нужно его загружать и тратить ресурсы) плагин Codecomm включается когда нужно командой :packadd codecomm:
Смысла в пакетных менеджерах после этого нет никакого. Фичи типа «пропиши github/foo/bar строчку, автоматически всё скачаю и запущу» ужаснут любого кто задумывается о безопасности и компрометации своего компьютера. И не только я так считаю: все плагины Tim Pope тоже советуют именно такой способ установки. Но стоит давать скидку на то, что многие плагины были написаны до Vim8, поэтому они с чистой совестью могут упоминать «альтернативные» пакетные менеджеры.
Vim разработка регулярно славится тем, что она годами ничего не привносит существенного, как мне показалось. Потом появляются какие-нибудь сторонние проекты типа NeoVim. После чего Мууленаар в новой версии выкатывает и асинхронные задачи, и каналы, и пакетную систему. Так было и во времена nvi, в какой-той момент перевешивавший по возможностям Vim. Вот только, на мой взгляд, в Vim всё гораздо проще и продуманнее делается в итоге.
Сразу расскажу про несколько убер-полезнейших плагинов. Всё творение Tim Pope:
Отдельно скажу своё фи про NERD* семейство плагинов: это пример того, что не надо пытаться делать с Vim-ом. NERDTree только скрывает, и так достаточно удобный и хороший, функционал, не давая пользователю его познать. А в NERDCommenter достаточно увидеть hard-coded знания о символах комментариях для разных языков, в противовес commentary, использующим штатную commentstring настройку. Честно, если в статье про Vim вижу совет использования NERD*, то сразу закрываю.
А также другие плагины:
Особняком стоит мой самописный CodeComm. История его началась почти десять лет назад, когда, работая в одной компании, надо было делать code review и специализированного инструментария для этого не было — review писался в виде комментариев к задаче в трэкере. Открывая код для review в fugitive, визуально выделял интересующие куски кода, нажимал \cc, открывалось окно с этим куском кода, где оставлял свои замечания. Всё это агрегировалось в файле на диске, который затем вставлял в трэкер.
На другой работе использовался Gerrit. Но не буду же сидеть в броузере для review!? Модифицировал плагин: он в итоге создавал JSON, пригодный для вставки в API Gerrit-а. А дальше вынес основной функционал комментирования в CodeComm, не привязанный к какой-либо системе.
Выделив какой-то блок внутри fugitive (чтобы был виден хэш коммита и путь), нажав \cc, будет создаваться такой вот блок:
Номера строк, номер комментария (чтобы сослаться), хэш, путь. Агрегирование комментариев в файл на диске. Для ревью кода использую сейчас только его.
По сути вся ~/.vim/plugin директория может находится внутри ~/.vimrc, как и наоборот. Принципа разделения у меня нет никакого — просто ощущения что надо выносить, а что по мелочам оставить в общей куче. Только один раз вставлю тут строки проверяющие загружен ли плагин — все они могут чуть-чуть ускорить загрузку Vim, но интереса более не представляют. Рассмотрю содержание директории без какого-либо порядка:
Одна из немногих фич которую совершенно не использую в Vim это tab-ы. Не сложилась дружба с ними, даже не пытаюсь более. Но раз для них выделена целая строка tabline, то почему бы не использовать её для информации? Вот её и заполняю списком буферов, аналогично выдаче :buffers, только в одну строку. Сейчас она выглядит вот так:
Она буквально перехватывает вывод :buffers и парсит его. Почему нельзя использовать функции vimscript? Потому что не нашёл возможностей для получения ряда интересующих меня атрибутов буферов! Или плохо искал.
End клавиша показывает список :changes (вынес на отдельную клавишу, потому что часто использую) и подготавливает :Chng команду к вводу. Ей можно передать номер change на который надо прыгнуть назад. Указав отрицательное значение, можно прыгнуть «вперёд». Использую это всё когда нужно совершить прыжок не на 2-3 шага назад/вперёд, а на сколько именно не помню.
Defsplit плагин изначально писался для тривиальной задачи: разбить сигнатуру Python функции по строчкам:
То ли совсем не нашёл готовых средств для этого, то ли все они занимали тьму кода. Если для такой (простой) задачи вижу плагин на несколько экранов — что-то тут не так, иду пробовать писать своё решение. :Defsplit делает разбиение сигнатуры по строчкам. Можно указать порядковый номер скобочки с которой начать перенос. :Undefsplit схлопнет всё выражение в одну строчку: например уменьшилось количество аргументов или надо переразбить по другой скобочке. Далее появился Brsplit на том же самом «движке» для разбиения любых скобочек. Во время разработки на Python это всё использовалось сотни раз в час! Для Go, очевидно, Defsplit не применяется, так как в нём собственная жёсткая система автоматического форматирования.
Отключение загрузки кучи родных плагинов, которые мне штатно не нужны. Сокращает время запуска Vim.
Ee h позволяет отредактировать файл с таким же названием как и текущий, но «h» расширением. Использую для переключения между .c и .h.
Это must-have возможность открытия файла с указанием номера строки через двоеточие. foo:12 имя файла откроет foo файл на 12-ой строке. Мой вариант это упрощённый file:line, в котором происходила замена текущих открытых окон на новое, что мне было не удобно. Огромное количество утилит и linter-ов выдаёт адреса строк в файлах именно в этом формате.
Очередная must-have вещь, которая встроена в NeoVim по умолчанию: поддержка bracketed paste вставки, про которую писал выше. Тут чётко видно, что когда Vim встречает escape-последовательность начинающую bracketed paste, то он переключает paste опцию.
\w команда показывает и копирует в X11 буфер обмена путь к редактируемому файлу и номер строки где находится курсор. Используется в первую очередь для работы с отладчиками. Нужно установить breakpoint вот на этом участке кода: \w, переходим в отладчик, Shift-Insert, Enter. Но формат у LLDB и GDB команд отличается, поэтому для LLDB используется \W.
Начало скрипта очень важно: при запуске Vim он запоминает значение «физической» и «логической» текущей директории. У меня есть проекты в которых символические ссылки библиотеки ведут во вне дерева исходных кодов (например на зашифрованный раздел). Go при сборке программы видит путь ~/work/go/src/XXX, а Vim при редактировании файла в этом XXX будет видеть его физический путь до /somewhere/outside/XXX. И я не нашёл способа получить «логический» путь до файла/директории средствами самого Vim. И засада в том, что при нажатии \w передам в отладчик физический путь, про который ни он, ни компилятор ничего не знают. В своём плагине проверяю отличаются ли физические и логические пути и делаю замену физической части пути на логическую. Выглядит грязно, но работает is good enough.
В Vim есть встроенный поиск по файловой системе: vimgrep. Мне почти всегда нужно указать рекурсивный поиск по всему дереву, добавляя **/* путь. А также не игнорировать регистр. Писать каждый раз :vimgrep /whatever/ **/*, да ещё и с запоминанием ignorecase настройки — не вариант. Поэтому используется :Vim whatever команда.
Позже познал скорость git grep и начал использовать :Ggrep команду из fugitive. Но мне не нравится что quickfix список с результатами поиска не открывается автоматом. Кроме того, нужно помнить про экранирование искомого запроса. Тут родилась :Vmg обёртка, схожая по названию с :Vim, чтобы меньше запоминать.
Часто хочу открыть результат grep, запущенного из командной строки zsh, уже постфактум. Да, Vim поддерживает file:line формат файлов, но это если мне надо открыть один файл. qq скрипт сохраняет tmux экран, находит всё что похоже на file:line и запускает Vim, загружая в него quickfix список. Выглядит грязно, но вполне себе сносно работает! Ведь не всегда же я сижу в Vim и провожу поиск через :Vim и :Vmg.
Я так привык к fuzzy-like дополнению путей в zsh (когда f/b/baz раскрывается в foo/2bar/somebaz), что хотел бы и в Vim задавать пути к открываемым файлам в этом же виде. Не могу описать как к этому пришёл, но лучше чем запускать zsh, внутри которого эмулировать pty терминал, отсылать нажатия клавиш (ввод пути) и ловить вывод, не нашёл.
\eПУТЬ выполняет :Fe ПУТЬ команду, которая откроет найденный ПУТЬ (zsh-like completed) в текущем окне. \v сделает это в вертикальном split, а \ПРОБЕЛ в горизонтальном.
Filetype-специфичные настройки, не требующие комментариев:
Во время написания ответа на письмо, эта функция вырезает подпись из процитированного письма. К сожалению, бывают неопытные пользователи, не знающие что подпись отделяется тремя символами — .
Ввод framedo, itemdo или cnter с последующим Ctrl-O отобразят на экране:
где курсор сразу будет находятся в КУРСОР позиции в режиме ввода. Создал это из-за презентаций (в Beamer), где чаще всего применяется. Знаю что есть отдельные плагины для создания, так называемых, code snippet. Пробовал SnipMate (может быть что-то ещё), но не нашёл где бы мне не хватило штатного функционала.
Вызов @e макроса копирует название переменной под курсором на следующую строку, оборачивая в assert(XXX != NULL); вызов. А :Fmt команда: запоминает текущую позицию, переходит в начало файла, применяет к нему команду выравнивания, возвращается назад, центрирует экран. Команда «выравнивания» тут применяется исключительно как способ прогона всего исходного кода через внешнюю утилиту, форматирующую код. Для Go применяется fmt.vim плагин, поставляемый, если не путаю, в Go дистрибутиве. Он тоже использует:Fmt (на самом деле это Си сделан по образу и подобию).
Насколько помню, всё это было взято из Python filetype файла из дистрибутива Vim и переделано для Go. Команды [[/]] в vi позволяли перемещаться между функциями (поэтому в Си коде важно чтобы открывающая фигурная скобка начала функции располагалась в начале строки), а [m/]m между методами. Здесь [[/]] используются, аналогично, для прыжков между функциями и объявлением типов. А [m/]m разрешают иметь indentation, позволяя прыгать на анонимные функции, объявляемые внутри других функций.
@e макрос при применении на foo(bar) строчке сделает:
А @r добавит на следующей строке:
Изначально это был просто proof-of-concept работы popup окон, но в итоге стало функцией показа документации (используя внешнюю gogetdoc утилиту) по слову под курсором. Просто нажимаем на нём Enter и асинхронная задача в фоне будет получать документацию, отобразив в popup окне.
Код из серии «хак на хаке». Но при этом вполне работающий в преобладающем большинстве случаев. Занимается автоматической вставкой import-ов. Повторюсь: экосистема Python меня удручает и, на момент написания, не встретил удовлетворивших меня инструментов (тогда как в Go вон всё прекрасно).
Например я набрал слово Certificate и нажал на нём F3. Будет запущен git grep с поиском import-ов связанных с Certificate по всему проекту и сортировкой по самой длинной строке. Будет предложено меню с найденными предложениями. Конечно это не сработает когда проект совсем голый и import ещё ни разу не был написан. А ещё это будет работать только с «каноничными» import-ами (уже писал о них для pyimportcan.pl скрипта).
Позже был рождён ещё больший хак в виде :AllImportCompl функции. Предварительно нужно запустить Pylint или Pyflakes linter-ы, загрузив результат их работы в quickfix список (это автоматом делает мой pylint.vim). Функция найдёт в этом списке ошибок все предупреждения об отсутствующих import-ах и для каждого из них она запустит аналог F3-функции. Дальше останется только применить сортировку и разбиение по группам. До сих пор удивительно для меня, но этот огромный хак отрабатывает очень и очень хорошо: с ростом проекта покрывая (автоматически успешно добавляя) преобладающее большинство всех import-ов.
Примеры кода тут привожу чтобы продемонстрировать что в vimscript нет ничего сложного и страшного. Это штука которую может использовать обычный пользователь, а не hardcore разработчик.
Это дополнение к хаку :AllImportCompl — функция удаляющая неиспользуемые import-ы (тоже на основе quickfix данных linter-а).
Функция создания Python-пути до теста в котором мы сейчас находимся. На удивление не маленькая, так как у нас только текст, а не AST дерево кода. Находясь в теле теста, нажимаем \t и в буфер обмена будет скопирована строка типа foo.bar.tests:TestBaz.test_whatever. Часто использую когда нужно быстренько запустить только этот метод: что происходит постоянно при написании новых тестов.
Вот и всё! Надеюсь что кто-нибудь да почерпнёт что-нибудь полезное для себя!
grep
Чаще всего less-ом приходится смотреть вывод результатов поиска GNU grep:
----- ~/.zshrc ----- GREP_ARGS=( --colour=always --with-filename --line-number --dereference-recursive --binary-files=without-match --exclude-dir=.git --exclude-dir=.tags ) g() { grep $GREP_ARGS $@ | less } GS() { grep $GREP_ARGS $@ | sort --numeric-sort | less } alias -g G="| grep --colour=always"
Все опции говорят сами за себя. Отображать название файлов, показывать номера строк, включать цвета и перенаправлять всё в пейджер. Самый часто используемый pipe-aware алиас G: f bar G foo W будет, грубо говоря, аналогом find. -name "*bar*" | grep foo | wc -l. GS бывает используется для некоторых задач где важен порядок следования численно отсортированных результатов. А написан он полностью заглавными буквами, так как gs это Ghostscript, а набирая gS часто ошибаюсь в моменте нажатия вовремя Shift-а для заглавной «S».
Почему GNU grep, ведь у меня же есть grep из коробки в самой FreeBSD? Как бы не уважал GNU проект за его следование идеям свободного ПО, как бы ненавидел GNU проект за умение создавать монструозный по размерам, пухлый и переусложнённый софт, да ещё и регулярно с GNU-специфичными расширениями и форматами, но GNU grep на порядки быстрее *BSD версий. Когда-то его производительность была не такой и использовался популярный ack, написанный на Perl. По точно такой же причине использую GNU sed по умолчанию — он на порядок быстрее может отрабатывать.
.zshenv
В ~/.zshenv файле находятся многочисленные настройки переменных окружения. Большую его часть уже показал. Но пока ещё остаются белые пятна:
----- ~/.zshenv ----- path=(~/bin ~/local/bin /usr/local/bin /usr/local/sbin /usr/bin /usr/sbin /bin /sbin) manpath=( ~/local/share/man /usr/local/lib/perl5/5.26/perl/man /usr/local/lib/perl5/site_perl/man /usr/share/openssl/man /usr/local/share/man /usr/local/man /usr/share/man ) export -TU INFOPATH infopath infopath=(~/local/share/info /usr/local/share/info /usr/local/info) manpath=(~/src/suckless/tabbed $manpath) path=(~/src/suckless/tabbed $path) path=(~/work/goredo $path) export -TU PKG_CONFIG_PATH pkg_config_path pkg_config_path=(~/local/lib/pkgconfig ~/local/libdata/pkgconfig) # C related {{{ export CFLAGS="-I$HOME/local/include -I/usr/local/include" export LDFLAGS="-L$HOME/local/lib -L/usr/local/lib" export -TU LD_LIBRARY_PATH ld_library_path ld_library_path=(~/local/lib) # }}} # Go related {{{ export GOPATH=$HOME/work/gopath path=($GOPATH/bin ~/work/go/bin $path) # }}}
Всё это настройка путей до исполняемых файлов, man, info, pkg-config, и прочего. zsh умеет делать «связанные» переменные массивов со строковыми переменными с сериализованные представлениями этих массивов. Ведь PATH=foo:bar это, по сути, массив из foo и bar. Некоторые переменные априори встроены и известны (path, manpath), а на некоторые явно создаю binding через export -TU. Работа с массивами более удобна и error prone.
----- ~/.zshenv ----- export XDG_CACHE_HOME=/tmp/stargrave-xdg export XDG_CONFIG_HOME=/tmp/stargrave-xdg export XDG_DATA_HOME=/tmp/stargrave-xdg export XDG_RUNTIME_DIR=/tmp/stargrave-xdg
Так уж получилось, но практически всё что делает freedesktop.org, мне противет всем Unix, DJB-way и suckless мировоззрениям. Хочу, чтобы всё что касается одной программы, находилось сосредоточенным в одном месте. В Unix мире плюс-минус так и было принято. XDG же делает совершенно противоположно. Благо, везёт, что почти все программы что по умолчанию уважают XDG переменные окружения — мне не интересны в плане хранения их состояний. Поэтому в tmpfs создаю директории для них, где оседают кэши и default конфиги.
Оставшаяся часть переменных окружения:
----- ~/.zshev ----- export SHELL=/bin/zsh export EDITOR=vim export TZ=Europe/Moscow export IFCONFIG_FORMAT=inet:cidr,inet6:cidr export SSH_AUTH_SOCK=$HOME/.ssh/agent export MAILDIR=$HOME/mail export DBUS_SESSION_BUS_ADDRESS=disabled: export MYSQL_HISTFILE=/tmp/.mysql_history export SHARNESS_TEST_SRCDIR=$HOME/local/stow/sharness/share/sharness
- IFCONFIG_FORMAT заставляет выводить адреса ifconfig команды в CIDR формате, привычном и удобном для меня.
- DBUS_SESSION_BUS_ADDRESS — запрет автоматического включения DBus демона. Очередное противящее мне freedesktop.org творение.
autoenv
----- ~/.zshrc ----- export AUTOENV_AUTH_FILE=~/.zautoenv-auth . ~/work/zsh-autoenv/autoenv.zsh
Когда перехожу в директорию рабочего проекта, то хочу подготовить с ним работу. Для Python проектов это может означать включение virtualenv. Для проектов на Си это добавление/переопределение PATH, LD_LIBRARY_PATH. Когда-то создавал PROJ/.init файл, на который делал source (.-команда). Бывает что в него добавлял и cd в рабочую директорию проекта. То есть, сначала мне надо сделать source, а потом ещё возможно и cd в нужную поддиректорию. С «отменой» произведённых изменений переменных не парился — просто запуская очередной shell с чистыми переменными.
Нашёл готовые решения для этой задачи: direnv (который мне не нравится из-за громоздкости, сложности и негибкости) и zsh-autoenv. Но в исходном коде последнего много лишнего. Поэтому почти полностью переписал этот плагин, в основном с оптимизациями производительности и убиранием огромной части кода.
Теперь, если перехожу, например, в свой PyDERASN проект, даже в одну из его поддиректорий (cd ~pyd/tests), то в нём автоматически запустится:
----- ~pyd/.autoenv.zsh ----- venv workon pyderasn3
----- ~/.zshrc ----- venv() { . /usr/local/bin/virtualenvwrapper.sh }
Активирующий virtualenv. Если покину иерархию ~pyd директорий, то автоматически запустится:
----- ~pyd/.autoenv_leave.zsh ----- deactivate
Но очень часто нужно изменять переменные окружения, которые бы неплохо потом восстанавливать до оригинального значения:
----- ~arbeit/.autoenv.zsh ----- autostash LD_LIBRARY_PATH MANPATH TESSERACT=ALTERED_STATE ld_library_path=( ~/work/arbeit.ru/deps/libressl/lib ~/work/arbeit.ru/local/lib $ld_library_path ) manpath=(~/work/arbeit.ru/deps/libressl/share/man $manpath)
autostash является единственной командой оставшейся в моей версии zsh-autoenv, запоминающей оригинальное значение переменных. Главное удобство autostash в том, что в .autoenv_leave.zsh не нужно вообще ничего писать для восстановления значений! Поэтому в данном arbeit проекте нет никакого .autoenv_leave.zsh. В отличии от direnv, всё эти файлы являются обычными zsh скриптами, а не очередным языком со своими командами. Можно и цвета поменять и приглашение командной строки и всё что придёт в голову.
А для пущей безопасности, zsh-autoenv требует явного одобрения запуска этих скриптов, сохраняя контрольную сумму в перманентном файле. Подсунуть .autoenv.zsh в git коммит для компрометации компьютера не выйдет.
.zprofile
Замечу, что для удобной работы в zsh не обязательно иметь кучу конфигурационных файлов на сотни строк. Конфиг для root-а состоит всего-лишь из:
----- /root/.zshrc ----- bindkey -v bindkey "^[[1~" beginning-of-line bindkey "^[[4~" end-of-line export PS1="%2~%# " export PAGER=less export IFCONFIG_FORMAT=inet:cidr,inet6:cidr export LESSHISTFILE=- export XDG_CACHE_HOME=/tmp/root-xdg export XDG_CONFIG_HOME=/tmp/root-xdg export XDG_DATA_HOME=/tmp/root-xdg export XDG_RUNTIME_DIR=/tmp/root-xdg mkdir -p /tmp/root-xdg umask 077
А чтобы научиться пользоваться множеством фич из серии !$:h:s/m/f/ (подстановка последнего аргумента предыдущей команды, у которого отрезан хвост (как правило, имя файла до директории) и в нём замена «m» на «f» — отнюдь не надуманный пример) — нужно читать документацию zsh. Я это делаю просто время от времени, со случайной позиции, находя что-то новое и интересное, пытаясь его использовать. Как правило, прочитать и узнать то можно о дюжине новых трюков, но в голове останется только пара — поэтому процесс регулярно надо повторять. Оно стоит того!
Последнее что покажу про zsh это процесс запуска и выхода:
----- ~/.zprofile ----- umask 077 [[ -r $LESSKEY ]] || lesskey -o $LESSKEY [[ -e $XDG_DATA_HOME ]] || mkdir -p $XDG_DATA_HOME /tmp/stargrave-lldb /tmp/stargrave-flags [[ -n "$XAUTHORITY" ]] || ~/bin/rem ~/bin/cleanup.sh
----- ~/.zlogout ----- ~/bin/cleanup.sh
----- ~/bin/cleanup.sh ----- #!/bin/sh rm -fr \ ~/.*_history \ ~/.cache ~/.config ~/.local* ~/.thumbnails \ ~/.java \ ~/zathura.core
Изначально zsh читает .zshenv, затем во время логина .zprofile, затем .zshrc, а при выходе .zlogout. Всё что касается интерактивной сессии должно находится в .zshrc. Каждый раз при логине делается очистка домашней директории от возможно появляющегося мусора.
----- ~/bin/rem ----- remind -g ~/rem/index.rem
----- ~/rem/index.rem ----- BANNER % INCLUDE /home/stargrave/rem/monthly.rem INCLUDE /home/stargrave/rem/payments.rem INCLUDE /home/stargrave/rem/birthdays.rem INCLUDE /home/stargrave/rem/domains.rem INCLUDE /home/stargrave/rem/peryear.rem INCLUDE /home/stargrave/rem/onetime.rem FSET sortbanner(x) iif(x == today(), "***** Heute *****", "===== %b =====")
Для напоминаний о датах/событиях использую remind утилиту. Прежде пользовался родным calendar, но в нём нельзя настроить период за который необходимо заранее предупреждать о датах. Какие-то требуют предупреждения за месяц, а какие-то за 1-2 дня. Последняя строка конфигурации заставляет показывать через сколько дней произойдёт то или иное событие.
Почему некоторые команды у меня сделаны в виде алиасов, а некоторые в виде вынесенных в отдельный файл скриптов? Исключительно вопрос частоты их использования. Команды работы с git и ssh — очень частые, поэтому потребовали бы ощутимого overhead-а на ОС и ФС считывая и интерпретируя код из ~/bin/XXX скрипта. А загромождать namespace и память zsh редко используемыми алиасами тоже не гоже.
t
Календарь используется только для отдалённых дат, для ежемесячных вещей, типа «оплатить квартиру». А в качестве блокнота у меня t скрипт. Когда-то это была Python программа tnote. Но пришлось переписать, ибо запуск Python — достаточно медленная операция. Переписал на POSIX shell, а недавно на zsh, дабы почти полностью избавиться от вызовов внешних команд.
Это одна из самых часто используемых команд ежечасно! Она может сохранить заметку, показать их краткий список, удалить и отредактировать. Как только у меня появляется в голове хоть что-то, что нужно бы куда то записать, то сразу ввожу в любом свободном терминале, например, t a купить хлеба. И регулярно просматриваю и проверяю что у меня в заметках. Вот буквально сейчас у меня:
% t [0] принтер (1) [1] отсканировать фотографии (1) [2] скопировать фотографии с фотоаппарата (1) [3] ведьма блэр (1) [4] nncp ck wait (8) [5] forth book (1)
Когда посмотрю «один из самых страшных фильмов» (как гласит пресса), то сделаю t d 3. t e 4 отредактирует заметку под номером 4. А t 4 её полностью покажет (в данном случае она на восемь строчек). Кроме того, добавив N=XXX переменную окружения можно вести несколько namespace-ов, в которых веду эфемерные заметки по разным рабочим проектам, типа текущих IP-адресов и административных задач не требующих занесения в трэкер.
Когда у меня был Palm PDA, то с ним не расставался, записывая абсолютно всё что надо бы было не забыть или проверить. Без него у меня бы был блокнот с ручкой. t утилита именно для подобных задач. Если что-то не записал ручкой или клавиатурой, то могу забыть через полчаса — а так хотя бы помню факт записи. Это не долговременные заметки типа:
----- ~/notes/go-build-no-optimization ----- go build -gcflags="all=-N -l" go build -ldflags=-s
Когда-то прилично использовал vimwiki плагин, дающий Wiki систему внутри Vim и даже рендерящую её в HTML файлы, на основе чего делал многие свои сайты. Но потом пришёл к выводу, что всё это переусложнение и мне достаточно простых текстовых файлов в ~/notes, календаря и «горячих» короткоживущих t заметок. А для сайтов лучше что-то другое использовать.
Музыка
Одна из утилит заслуживающая особое внимание — GNU parallel. Это скрипт на Perl, в котором использую наверное только 5% из всех возможностей, но простота его применения влюбляет. Множество CPU-hungry программ не распараллеливаются: например, мультимедиа конвертеры. А музыку обработать хочется побыстрее. Делаем parallel flac -d ::: **.flac чтобы, заняв все процессоры, запустить кучу flac декодеров. parallel opusdec {} {.}.wav ::: **.opus чтобы сделать из них Opus. Можно аудиокниги пределать в MP3 чтобы слушать на плеере:
parallel 'opusdec {} {.}.wav && lame --abr 128 {.}.wav && rm {.}.wav' ::: **.opus
Мультимедиа файлы частенько надо массово переименовать. Например, скачал с YouTube-а разные клипы от RetroAhoy и все они имеют общий ненужный (мне) префикс. Используем встроенный в zsh плагин zmv:
zmv 'RetroAhoy - (*)' '$1'
Все .jpeg переименовать в .jpg: zmv -W '*.jpeg' '*.jpg'
zmv '(*).MetalBand-AlbumName-(*)-(*).wv' '$1-$3-$2.wv'
Во всей иерархии директорий, во всех именах заменить пробелы на подчёркивания:
cur=`pwd` ; for i (**(/)) { cd "$i" ; zmv '*' '$f:gs/ /_' ; cd $cur }
Все эти примеры взял из своего ~/secure/.history, не надуманные.
Как у меня устроена музыкальная коллекция? Примитивно: каждый альбом в своей директории, каждый трэк в отдельном файле. Исполнитель-Год-Название альбома. Все пробелы в именах заменены на подчёркивания. Если это откуда то извне пришедшие MP3, то в них удаляю все IDv2 тэги: id3v2 -D *.
Я требую lossless вариант музыки! Очень даже запросто откажусь слушать её lossy версию, ибо с моими неплохими наушниками, усилителем и ЦАПом могу отличать 320Kbps -q 9 MP3 файл от lossless. И я получаю дикое удовольствие от хорошего звука. К сожалению, некоторые мною любимые жанры (лютый порно/копро грайндкор с харш нойзом) бывает невозможно нигде достать, даже нелегально скачивая, кроме как в этом MP3 (ё моё, XXI век, а всё до сих пор люди используют этот формат, ведь есть и свободные кодеки, в разы лучше сжимающие!).
Бывает, приходится доставать запись с пластинки, даже современных альбомов, так как на них бывает отличающееся сведение с более широким динамическим диапазоном. Это ценнее чем полнота частот и присутствующие потрескивания.
Кириллические имена могут быть неудобны из-за всяких архаичных ОС. Для транслитерации их имён использую один из своих старейших скриптов:
----- ~/bin/torn ----- #!/usr/bin/env perl [...] binmode STDOUT, ":utf8"; my $VERSION = "0.10"; my $src; my $dst; my $src_filename; opendir DIR, "." or die "Can not open directory\n"; foreach (sort readdir DIR) { # Skip directory itself next if /^\.{1,2}$/; next if -d and not $ENV{FORCE_DIR}; $src_filename = $_; $src = decode "utf-8", $src_filename; $dst = $src; # Basic corrections for music files $dst =~ s/ /_/g; $dst =~ s/_-_/-/g; $dst =~ s/_\(/\(/g; $dst =~ s/-\(/-/g; $dst =~ s/\)_/-/g; $dst =~ s/\,_/\,/g; $dst =~ s/\[//g; $dst =~ s/\]//g; $dst =~ s/\(_/\(/g; $dst =~ s/_\)/\)/g; $dst =~ s/\&/and/g; $dst =~ y/абвгдеёзийклмнопрстуфхцьъыэ/abvgdeezijklmnoprstufhcjjye/; $dst =~ y/АБВГДЕЁЗИЙКЛМНОПРСТУФХЦЬЪЫЭ/ABVGDEEZIJKLMNOPRSTUFHCJJYE/; $dst =~ s/ж/zh/g; $dst =~ s/ч/ch/g; $dst =~ s/ш/sh/g; $dst =~ s/щ/sch/g; $dst =~ s/я/ja/g; $dst =~ s/ю/ju/g; $dst =~ s/Ж/Zh/g; $dst =~ s/Ч/Ch/g; $dst =~ s/Ш/Sh/g; $dst =~ s/Щ/Sch/g; $dst =~ s/Я/Ja/g; $dst =~ s/Ю/Ju/g; # Lowercase file extensions if ($dst =~ /^(.*)\.([^\.]+)$/) { $dst = $1 . "." . lc $2; }; # Change looking of track numbers if ($dst =~ /^(\d+)[-.]_*(.+)$/) { $dst = "$1.$2"; }; next if ($src_filename eq $dst); print "$src -> $dst\n"; die "\"$dst\" exists" if -e $dst; rename $src_filename, $dst; };
Для lossless сжатия использую WavPack. Судя по всему (я тут диванный эксперт), он лучше спроектирован и проще чем FLAC. Плюс на несколько процентов получше жмёт. Плюс не имеет ограничений на формат входных данных, типа поддержки 32-bit float, которые было мне встречались. На объёме в несколько сотен гигабайт музыки он мне дал несколько десятков гигабайт свободного места (преобразование из FLAC) — тоже приятно.
На работе, где не может идти речь про открытые наушники, а значит и хорошего качества звучания, вполне годится и lossy. Использую Opus кодек. Прежде был Vorbis, но Opus на десятки процентов лучше может сжать при схожем на слух качестве.
Когда ко мне попадают архивы с одним .flac и .cue файлами, то декодирую, cuebreakpoints *.cue | shnsplit *.wav командой разбиваю получившийся .wav на трэки, далее древнейшим скриптом (который бы надо переписать, но раз работает — не тронь) генерирую команды переименования на основе названий песен из CUE файла:
----- ~/bin/cueparser.sh ----- #!/bin/sh c=1 iconv -f latin1 -t utf-8 "$1" | sed -n 's/^ *TITLE .\(.*\)".*$/\1/p' | while read trackname; do v=`printf "%02d\n" $c` echo mv split-track$v.wav $v.\"$trackname\".wav c=$(( $c + 1 )) done
После чего, сжимаю parallel wavpack -hh ::: **.wav командой. Иногда применяю нормализацию по пиковому уровню для всего альбома: normalize --peak *.wav. cdparanoia используется для считывания купленных аудио дисков.
mpv
В качестве проигрывателя, с самого момента рождения и до последних лет использовал mplayer+mencoder. Но mpv имеет лучшую поддержку аппаратного декодирования, существенно быстрее делает перемотку, плюс умеет gapless playback, подходя в качестве музыкального проигрывателя.
----- ~/.mpv/config ----- vo=gpu profile=gpu-hq hwdec=vaapi hwdec-codecs=h264,hevc,vp8,mpeg2video,vc1 scale=ewa_lanczossharp cscale=ewa_lanczossharp cache=yes cache-secs=60 display-tags=* ytdl=no gapless-audio=yes autosync=30 oss-mixer-device=/dev/mixer oss-mixer-channel=vol [AO] audio-device=oss//dev/dsp3 [AM] audio-device=oss//dev/dsp2 oss-mixer-device=/dev/mixer2 oss-mixer-channel=pcm [VN] af=lavfi=loudnorm [NV] video=no
----- ~/.mpv/input.conf ----- PGUP seek 600 PGDWN seek -600 Shift+PGUP add chapter 1 Shift+PGDWN add chapter -1 ENTER playlist-next force BS set speed 1.0 VOLUME_UP run "mixer" "-f" "${oss-mixer-device}" "${oss-mixer-channel}" "+2" VOLUME_DOWN run "mixer" "-f" "${oss-mixer-device}" "${oss-mixer-channel}" "-2"
Настройки клавиш ввода связаны с моей 20-летней привычкой использования MPlayer. Проще перебить клавиши, чем перепривыкать. В начале статье упоминал про ~/.Xmodmap файл, в котором как раз были binding-и мультимедиа клавиш моей клавиатуры.
В основном конфиге mpv настройки аппаратного декодирования, буферизации, gapless проигрывания и разные профили связанные с выходными источниками звука и применением нормализации громкости. Почему эти профили имеют такие короткие названия?
----- ~/bin/mp ----- #!/usr/bin/env perl exec "mpv", map { s/^([A-Z][A-Z])$/--profile=$1/ ; $_ } @ARGV;
Потому что запускаю mp скрипт, преобразующий двухбуквенные слова-аргументы в --profile=XX. mp AM VN ... сделает вывод звука на HDMI монитора с нормализацией звука.
Для кодирования/перекодирования в mencoder уже стало не хватать возможностей. Всегда сторонился FFmpeg проекта, видя его непонятные аргументы, считая что любое действие с ним это сплошная боль. Но познакомившись поближе, всё же потеплел. Со всеми задачами справляется, хотя и не без помощи самого большого ~/notes/cmd/ffmpeg. mpv меня напрягал ещё тем после mplayer, что в нём нельзя было делать такие вещи как dump звукового потока (если ничего не путаю) — теперь много подобных задач переехало на ffmpeg.
Применений у ffmpeg у меня тьма. Одно из самых частых это «перекомпоновка» контейнера, убирая из него как можно больше метаинформации, оставляя только определённые аудио/видео дорожки:
ffmpeg -i IN -map 0:v:0 -map 0:a:0 -sn -map_metadata -1 -codec copy -y OUT
Контейнер AVI оставляю в покое. А вот никаких MP4 не позволю себе иметь, переконвертируя в Matroska. Хочу поддерживать форматы и кодеки созданные людьми для людей, а не корпорациями для DRM-а. Частенько Matroska/WebM контейнеры надо создавать из отдельных аудио и видео файлов (отдельно сжал Opus-ом аудио, отдельно закодировал в VP8/VP9 vpxenc-ом видео) — для этого использую MKVToolNix инструментарий. Например очистка Matroska контейнера от метаинформации, которая или должна быть заполнена идеально (нереальный вариант) или пускай её не будет вовсе, делается так:
----- ~/bin/mkvclean.sh ----- #!/bin/sh mkvpropedit \ --edit info \ --delete title \ --delete date \ --set muxing-application=- \ --set writing-application=- \ --chapters "" \ --tags all: $@
Как смотрю 4K видео? Которое в сжатом видео гарантированно не сможет у меня быть декодировано в real-time? Перекодирую как можно быстрее с уменьшением разрешения в MPEG2, имеющим (относительно) малые расходы на CPU и экономно тратит дисковое пространство:
----- ~/bin/4k2mpeg2.sh ----- #!/bin/sh ffmpeg -i "$1" -vf scale=w=iw/4:h=ih/4 -c:v mpeg2video -b:v 10000k -y "$1".mpg
Картинки
Для работы с изображениями использую NetPBM набор Unix-friendly утилит. Для сканированных книжек DjVu формат (DjVuLibre программа) незаменим. cjpeg -optimize -progressive для получения JPEG. Для оптимизации уже существующих JPEG: jpegtran -optimize -progressive -perfect -copy none. pngcrush -rem alla -rem allb для попыток сжатия PNG. Для снимков экрана lossless WebP (cwebp -pass 10 -alpha_filter best -m 6 -mt -q 100 -lossless) формат может дать на 50-60% лучшее сжатие!
JPEG полученные из сторонних источников конечно же стараюсь не пересжимать, ибо ещё бОльшая потеря будет, но удаляю EXIF информацию из них: exiftool -all= -overwrite_original.
А недавно стал поклонником JPEG2000 формата, особенно с учётом иссякания патентов на него. Все отсканированные фотографии храню в OpenJPEG lossless сжатом виде: opj_compress -t 1024,1024 -i $i -o $i:r.jp2. Во-первых, отсканированные изображения с большим DPI даже не уместятся в какой-нибудь WebP, так как в нём ограничение размеров картинки. Во-вторых, .jp2 файл на треть меньше места занимает чем PNG. Да и в целом wavelet сжатие мне импонирует. Но приходится ограничивать размер tile, ибо будет уж слишком большое потребление оперативной памяти. Из-за его ресурсоёмкости на огромных по размерам отсканированных изображениях, рядом делаю .webp preview небольшого разрешения.
Просматриваю изображения sxiv программой: минималистична, быстра, поддерживает rotate, zoom, и показ множества картинок в виде таблицы из preview. Удовлетворяет всем, кроме поддержки JPEG2000 формата (но это претензия к Imlib2).
Для чтения PDF, лучше чем Zathura ничего не видел! Удобнейшие vi-клавиши, возможность показа презентаций, копирования и поиска текста. Стоят плагины для просмотра EPUB и DjVu форматов. Крайне важно для меня использовать MuPDF backend для PDF файлов, так как он может работать, без преувеличений, на порядок быстрее Poppler! Речь про то, что в Poppler можно секунд десять ждать когда отобразится жирная иллюстрация, тогда как в MuPDF она появится без ощутимых задержек. Плюс MuPDF умеет конвертировать FictionBook2 в формат пригодный для чтения внутри Zathura.
----- ~/.zathurarc ----- set sandbox strict set dbus-service false
Zathura не идеальна — она тянет по зависимости GTK3 и DBus. Но… пока это меньшее из зол с которым готов смириться, ибо уж очень удобный просмотрщик.
Из коробки в ней нет никаких tab-ов. А иногда приходится читать параллельно с десяток документов. Стэковое представление окон в dwm будет не удобно. Zathura поддерживает XEmbed расширение X11, позволяющее одним программам («tab manager») включать в себя дочерние embeddable окна. Unix-way! Использую tabbed suckless менеджер для этой задачи. На данный момент, кроме Zathura, больше GUI программ для tab-ов у меня нет, хотя изначально туда встраивал и Lynx (когда не догадался что tmux достаточно).
----- ~/bin/zat ----- #!/bin/sh export PATH=$HOME/local/bin:$PATH export XDG_DATA_DIRS=$HOME/local export LD_LIBRARY_PATH=$HOME/local/lib:$LD_LIBRARY_PATH winid=`start-tabbed.sh zathura -c` bin.zathura -e $winid "$1" rm -f zathura.core # ага, zathura любит делать coredump
----- ~/bin/start-tabbed.sh ----- #!/bin/sh tabname=$1 shift w=/tmp/tabbed-$tabname [ -r "$w" ] && { read winid < $w wmclass=`xprop -id $winid WM_CLASS 2>/dev/null` echo "$wmclass" | grep -q tabbed-$tabname && { echo $winid exit } } [ -z "$NOTABSTART" ] || exit 1 tabbed -n tabbed-$tabname -d $@ > $w exec $0 $tabname $@
~/bin/start-tabbed.sh выглядит сложным для простого запуска Zathura, но когда-то он использовался и для других. Его задача либо добавить окно в уже запущенный менеджер, либо запустить его с нуля.
zsh кстати поддерживает функционал по запуску программ на основе расширений файлов. Можно «исполнить» .pdf и автоматически откроется Zathura. Но что-то у меня не появилось привычки открывать программы таким образом. А всё что не начинает активно мною используется — удаляется, дабы не мозолить глаза и не усложнять конфиги на пустом месте.
Блокировку экрана делаю вызовом mylock скрипта из dmenu:
----- ~/bin/mylock ----- #!/bin/sh xset dpms force off gpgconf --reload gpg-agent SSH_AUTH_SOCK=$HOME/.ssh/agent ssh-add -D slock
Он «отключает» электропитание монитора и очищает ключницы GnuPG и SSH агента, блокируя экран suckless slock утилитой.
Архивы
Для форматов архивов у меня есть чёткое предпочтение: POSIX.1-2001 pax формат, который обратно совместим с ustar. Чисто технически это ustar, внутри которого есть особые директории с keyvalue атрибутами, хранящими длинные имена файлов, точные временные штампы и другую метаинформацию. К тому же, этот формат является стандартом, а не каким-нибудь GNU-специфичным расширением.
Я понимаю что есть задачи где потоковая природа tar будет мешать. В принципе, ничего не имею против ZIP64 для этого, у которого есть индекс файлов. Но в нём не сохранить Unix-специфичные атрибуты файла.
Но вот беда: tar API не сказать что Unix-way friendly, сравнивая с cpio, в котором работа с путями и поиск файлов это задача сторонней программы (find). POSIX стандарт описывает как сам pax формат, так и pax архиватор, имеющий cpio-like интерфейс. cpio имеет существенные ограничения формата, не позволяя многое заархивировать (длинные имена файлов, размеры файлов), а pax выглядит как лучшее из двух миров (API и формат). Но почему то исторически так сложилось, что pax нету в большинстве дистрибутивах. А даже если и сама команда имеется, то не факт что она поддерживает pax format.
Благо, всё же tar можно использовать в cpio-like режиме, читая файл со списком файлов из stdin: find… | tar cI — .... Более того, по умолчанию libarchive, используемый в FreeBSD для архиваторов из коробки, делает ustar формат архива, если никакие метаданные не будут потеряны, и только в случае потерь он будет делать pax.
tar используется мной и для записи/чтения данных на LTO5 стример. В курсе, что LTO5 уже поддерживает LTFS, но даже не пытался пробовать использовать ленты в этом режиме. Мне действительно хватает обычного потокового чтения заранее сжатых (и зашифрованных) архивов с диска.
Для сжатия я ярый поклонник Zstandard алгоритма. Работая существенно быстрее чем zlib/gzip, он ещё и лучше сжимает. Это если самый банальный простой вызов zstd команды через pipe. Он может из коробки распараллеливаться, занимая все CPU. А указывая высокие степени сжатия (--ultra -22), можно лишь на считанные проценты сжать похуже чем xz, который использовал прежде.
Но основная причина моей любви к Zstandard — умопомрачительная скорость декомпрессии. До zstd, даже не подозревал как часто упирался, при распаковке какой-нибудь программы, в CPU. Фактически CPU всегда был бутылочным горлышком, тогда как с zstd упираюсь в диски. Кроме того, с параметрами по умолчанию не упираюсь в CPU даже при созданий резервных копий: zfs send -R… | zstd | gpg -e -r offline -z 0 --force-aead, а только в скорость своей SATA SSD. FreeBSD уже уйму лет имеет zstd из коробки, поддерживая это сжатие даже в демоне ротации логов.
По сути, остаётся либо gzip ради совместимости, либо zstd для всего остального: и для быстрого сжатия и для архивного хранения с сильной степенью сжатия.
Feeds
Кроме почтовых рассылок, личной почтовой переписки, иногда синхронного общения по IRC, получаю более 400+ новостных рассылок в RSS и Atom форматах. Много лет использовал rss2email программу, которая отправляла новости в виде почтовых сообщений, попадающих в отдельный почтовый ящик. Но всё же вернулся на Newsboat.
----- ~/.newsboat/config ----- history-limit 0 text-width 80 bind-key ^Y up bind-key ^E down notify-beep yes cleanup-on-quit yes max-items 100 auto-reload yes reload-time 120 reload-threads 10 suppress-first-reload yes feed-sort-order lastupdated browser "~/bin/www %u &"
- Binding-и колеса прокрутки.
- Beep оповещения когда появляются новости.
- Проверка всех feed-ов каждые два часа в десять HTTP потоков.
- Хранение только сотни последних новостей. Когда надолго уезжаю (оставаясь без компьютера), то эту опцию комментирую, ибо за неделю на некоторых ресурсах гораздо больше новостей приходит.
- Открывается www броузер: в зависимости от флагового WG файла это либо Lynx, либо Xombrero (об этом писал выше).
Знание о всех своих подписках храню в recfile формате, также выложенном на домашней страницы. Пример записей:
----- ~hp/links.rec ----- Title: ImperialViolet Note: Adam Langley's blog URL: https://www.imperialviolet.org/ Category: Crypto Category: IT Category: Personal Feed: https://www.imperialviolet.org/iv-rss.xml Title: Few thoughts of cryptographic engineering Note: Matthew Green's blog URL: https://blog.cryptographyengineering.com/ Category: Crypto Feed: https://blog.cryptographyengineering.com/feed/
Тут хранятся и ссылки на сами «домашние» страницы, так и ссылки на feed-ы, которые могут жить на совершенно разных доменах. Я стал поклонником recfile формата (и recutils утилит для работы с ним) из-за простоты как для компьютера, так и удобства для человека. Плюс возможностью задавать простую схему валидации записей. Можно тривиально сделать выборку всех feed URL-ов для вставки в ~/.newsboat/urls: recsel -P Feed < links.rec | sed /^$/d. Кроме того, на Go написал программу преобразующую этот .rec файл в XBEL и OPML форматы обмена ссылками. Мне так понравился формат, что применяю его для хранения состояния зависимостей в goredo, для журналов NNCP и некоторых рабочих проектах.
Hjson
Раз уж идёт речь про форматы, то не могу не упомянуть про любовь к Hjson. Это вечная holywar тема о том какой формат лучше для конфигурации ПО. Всё сильно зависит от сложности данных внутри. Где-то может и .ini подойти, и CSV, и termcap-like формат (много где используемый в FreeBSD).
Я точно ярый противник YAML: чрезвычайно переусложнён и библиотеки для работы с ним чаще превосходят по размерам саму основную программу. Мог бы привести вагон примеров когда чёрт ногу сломит человеку понять что же там написано и попытаться интерпретировать все эти структуры с кучей способов записи.
Я и противник TOML, опять же, на практике пробовав в своих проектах. Очень простые структуры в нём хорошо читаются. Но чуть более сложные, типа списков внутри словарей — увольте, но я, даже зная наизусть структуры своего NNCP, не мог самостоятельно корректно прописать это всё с первого раза.
А чем так плох JSON, если забыть про сложность (относительно termcap, ini) его парсинга? Невозможностью добавить комментарии, унылыми кавычками в ключах словарей, запятой в конце списков. Хочется тривиального небольшого синтаксического сахара. Hjson именно его и добавляет. Библиотека парсинга Hjson убирает этот сахар, превращая Hjson в обычный JSON, который можно отпарсить родной библиотекой. Золотая середина для конфигов со сложными структурами!
*tex*
Что думаю про формат документации? Однозначно GNU Texinfo и Info! man подходит только для простых страниц — для кратких сводок опций. Попробуйте попутешествовать по man-у zsh! Вариант с man -P «less +/^EXAMPLES» считаю костылём. Отсутствие ссылок, разделения на секции по которым можно было бы перемещаться. Info — гипертекстовый документ, с разделением на секции/ноды. Плюс .info файл можно смотреть даже без специализированного броузера. Можно иметь ссылки и на картинки, которые в GUI Emacs бы отобразились. Похожая идея в CHM файлах Windows.
----- ~/.infokey ----- #info j next-line k prev-line ^E next-line ^Y prev-line ^W split-window ^N next-window #var link-style=yellow active-link-style=yellow,bold match-style=underline,bold,nocolour scroll-behavior=Page Only
Чуть больше синтаксической подсветки, отключение автоматического перехода на следующие секции (это предоставляется как фича, но мне ужасно неудобное поведение), работоспособность колеса прокрутки и немного vi-like клавиш управления.
Это всё речь про форматы которые просматриваются. А формат исходного текста уже может отличаться. И man (mdoc) и и HTML тоже можно набирать сразу же от руки. А можно использовать какой-нибудь reStructured Text (reST), который и HTML и даже .info в состоянии сгенерировать. Уйму лет web-сайты делал на reST.
Как отношусь к Markdown? Никак не могу относиться к тому чего нет: нету никакого Markdown формата — есть только куча диалектов, частично совместимых между собой. И их общая часть — крайне скудна и не достаточна. К AsciiDoc у меня негативного отношения нет, но и каких-то плюсов относительно reST не вспомню — оба даже написаны на Python.
Но от reST в итоге отошёл (применяю разве что для Python проектов, ибо это родной формат в этой экосистеме). Не всегда хватает возможностей «самовыражения», типа «сделать жирную ссылку». Чуть более сложные вложенные структуры уже проблематично прописывать и генерировать автоматизированно, ибо требуется быть очень аккуратным с отступами и это становится сложно воспринимать с экрана.
Texinfo оказался золотой серединой между по человечески выглядящему reST и полного SGML тэгами HTML! Легко читать, легко писать, низкий порог входа, легко генерировать, свобода самовыражения и гибкость. Он действительно более близок и похож на TeX. Плюс написан на Си/Perl — мало зависимостей и отличная скорость работы. Почти все web-сайты генерирую из Texinfo кода. Единственное, что иногда создаёт неудобства, так это его заточенность под иерархичную структуру документов.
Ещё есть свойство спорной значимости: Texinfo может генерировать Docbook, очень приличного качества форматирования. Открыв каким-нибудь LibreOffice, можно сохранить в Office Open XML документ, почти без потерь форматирования задуманного автором .texi.
reST из коробки умеет читать docstring-и Python кода. Doxygen умеет из Си кода. Texinfo из коробки нет, но не проблема: docstringer.pl Perl скрипт находит в файлах комментарии такого вида, в которых есть пометка TEXINFO: XXX:
----- .../app.h ----- [...] // TEXINFO: CrickAppSignVerify // @deftypefun CrickErr CrickAppSignVerify @ // (CrickApp *app, const CrickFileId fileId, @ // const uint8_t *pub, const size_t pubLen, @ // const uint8_t *dgst, const size_t dgstLen, @ // const uint8_t *sign, const size_t signLen) // Verify @var{sign} signature made on @var{dgst} digest with @var{pub} // 34.10-2012-256 public key. @ref{CrickErrInvalidMACSignature} error is // returned if signature is wrong. // @end deftypefun CrickErr CrickAppSignVerify( CrickApp *, const CrickFileId, const uint8_t *pub, const size_t pubLen, const uint8_t *dgst, const size_t dgstLen, const uint8_t *sign, const size_t signLen); [...]
А в исходном Texinfo коде, содержащим @DOCSTRING CrickAppSignVerify@, будет сделана подстановка тела комментария.
Как бы много чего монструозного не было в GNU проекте, но их обязательное требование использования Texinfo/Info всецело одобряю.
Рабочие отчёты для печати создаю в LaTeX, используя TeX Live дистрибутив, чтобы не было раздумий какой CTAN пакет и как мне надо устанавливать. В нём же писал все курсовые, диплом и всё подобное. Схемы, картинки ни разу в жизни не рисовал «от руки» (мышкой что то там расставлять на экране) — только PGF/TikZ, GraphViz и Gnuplot. Только чертежи выполнял в QCAD. Почти все презентации выполняю в Beamer пакете LaTeX.
У меня стоит OpenJDK исключительно ради запуска PlantUML. Меня коробит от столь тяжёлой зависимости (Java), но PlantUML настолько хорош и прост в использовании, создавая и прекрасные растровые и Unicode текстовые диаграммы! Созданные им диаграммы у меня встречаются практически в каждой программе.
File transfer
Бывают неэтично сделанные проекты где документация только в online виде. Проблему можно решить через зеркалирование сайта. Для каждого ресурса приходится какие-то параметры подправлять, но в общем случае команда:
----- ~/bin/mirror-site.sh ----- #!/bin/sh user_agent="Mozilla/5.0 ..." # некоторые Wget считают роботом и не дают доступа name=$1 shift mkdir -p $name cd $name wget \ --page-requisites \ --convert-links \ --adjust-extension \ --restrict-file-names=ascii \ --span-hosts \ --random-wait \ --execute robots=off \ --recursive \ --timestamping \ -l inf \ --no-remove-listing \ --no-parent \ --user-agent "$user_agent" \ --reject '*.woff*,*.ttf,*.eot,*.js' \ --tries 10 \ $@
wget умеет создавать и Web ARChive .warc файлы, которые затем можно просматривать через WARC-прокси:
----- ~/bin/mirror-site-warc.sh ----- #!/bin/sh mirror-site.sh "$1" \ --warc-file $name-$(date '+%Y%M%d%H%m%S') \ --no-warc-compression --no-warc-keep-log $@
Если Web-сервер предоставляет WebDAV, или есть FTP зеркало (не говоря про rsync), то использую LFTP, как например:
echo "mirror --delete --continue . if-archive" | lftp -d ftp.ifarchive.org
ZFS тут может здорово помочь в создании атомарных обновлений раздаваемых директорий. Сам HTTP/FTP сервер может смотреть на snapshot по /path/.zfs/snapshot/SNAPNAME пути, а долгое rsync/lftp обновление идёт просто в /path. Когда зеркалирование завершено, то новую версию snapshot над обновлённым состоянием можно переименовать в /path/.zfs/snapshot/SNAPNAME.
Ещё бы рекомендовал aria2 менеджер скачивания. Использую его как BitTorrent клиент, а также когда нужно скачивать множество файлов, с возможностью докачки, в несколько потоков.
----- ~/.aria2/aria2.conf ----- max-concurrent-downloads=200 ca-certificate=/etc/ssl/cert.pem bt-max-open-files=1000 bt-max-peers=0 bt-save-metadata=true enable-dht=true enable-dht6=true seed-ratio=0.0 disk-cache=0 file-allocation=trunc force-save=true
Важно переопределить file-allocation, который по умолчанию полностью создаёт пустой файл перед скачиванием, что бессмысленно на современных copy-on-write файловых системах.
Из BitTorrent клиентов пробовал ctorrent — нравился, но нет DHT и UDP поддержки. Transmission — ресурсоёмкий, не нравился. Rtorrent долго использовал, пока не наткнулся на его hard-coded ограничения на максимальные размеры файлов/торрентов. К aria2 у меня вообще никаких нареканий. С ней использую немного изменённый (добавил команды просмотра подключённых peer-ов и их прогресс) diana frontend.
Games
Последние лет десять, играю время от времени (отдых же нужен) в:
- FreeCiv стратегию.
- NetHack RPG, в которой только недавно узнал про существенное удобство curses интерфейса:
----- ~/games/nethack/.nethackrc ----- OPTIONS=gender:male OPTIONS=windowtype:curses,color,menucolors OPTIONS=boulder:0 OPTIONS=!autoopen,autopickup,pickup_types:$ OPTIONS=hitpointbar,statushilites=3,showexp,force_invmenu,perm_invent,statuslines:3 OPTIONS=hilite_pet,hilite_pile,lit_corridor OPTIONS=pettype:none MENUCOLOR="blessed"=green MENUCOLOR=" cursed"=red OPTION=hilite_status: gold/up/yellow/down/brown OPTION=hilite_status: characteristics/up/green/down/red OPTION=hilite_status: hitpoints/100%/gray&normal OPTION=hilite_status: hitpoints/<100%/green&normal OPTION=hilite_status: hitpoints/<66%/yellow&normal OPTION=hilite_status: hitpoints/<50%/orange&normal OPTION=hilite_status: hitpoints/<33%/red&bold OPTION=hilite_status: hitpoints/<15%/red&inverse OPTION=hilite_status: condition/major/orange&inverse OPTION=hilite_status: condition/lev+fly/red&inverse WIZKIT=/home/stargrave/games/nethack/.wizkit # да, ни разу честно не победил
Эта игра мне не нравится только одним: на неё не получится отвлечься на пару часов — придётся убить хотя бы день, даже с учётом использования wizard-режима и wizkit. - ScummVM машина в которой идут сотни потрясающих игр! Все квесты от LucasArts (поклонник именно их творений, а не Sierra) прошёл в этом эмуляторе. А недавно в него встроили ResidualVM и в этом году прошёл The Longest Journey. Одни из самых лучших моментов жизни связаны именно с ScummVM и ResidualVM.
- Не так часто, как хотелось бы, но запускаю interactive fiction игры, почти всегда в Frotz Z-машине.
Одним из первых шагов, при прохождении больших Infocom-овских игр, было рисование карт от руки. Через какое-то время сдался (рисовать/чертить не люблю) и попробовал это сделать через GraphViz. Далее написал Perl скрипт который декларативные инструкции по рисованию переводил в Dot язык. И только после задался вопросом о существовании готовых инструментов для этой задачи. С тех пор использую Interactive Fiction Mapper.
БД
Всё написанное выше: про базовую работу в ОС, общение и развлечения. Про саму работу речи ещё не было. А это написание кучи кода, тестов, скриптов и документации. А также работа с СУБД:
----- ~/.sqliterc ----- .bail on .mode column .headers on
----- ~/.psqlrc ----- CREATE OR REPLACE FUNCTION ppj(jsonb) RETURNS TEXT AS $$ SELECT jsonb_pretty($1); $$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION ppj(jsonb[]) RETURNS SETOF TEXT AS $$ SELECT jsonb_pretty(unnest) FROM (SELECT unnest($1)) AS dummy; $$ LANGUAGE SQL; \pset linestyle unicode \pset footer off \timing on \set HISTFILE ~/secure/.psql_history
Из интересного тут можно отметить только функции pretty printing JSONB полей PostgreSQL. Для редактирования SQL запросов активно применяю внешний редактор через \e вызов.
ЯП
Начинал программировать с Perl, имел многолетний опыт с Ruby, PHP, Lua. Но основная работа была проделана на Python. А знакомство с Go коренным образом поменяло моё отношение ко всему остальному. Считаю этот язык неким Святым Граалем: продуманный до мелочей, крайне простой и лёгкий в поддержке и понимании кода — это самое главное. Всё остальное, если не имеет простоты и понятности — не играет роли. Это лучший язык для промышленного программирования из существующих, заточенный не для выпендрёжа, а для решения задач. Go (и его авторы) даёт не то что люди хотят, а то что им действительно нужно.
Недавно заметил, что на Python перестал писать даже простейшие скрипты типа преобразования моего .rec файла с ссылками в другие форматы: всё равно без раздумий использую Go. И на серверах у меня полностью исчезли интерпретаторы Python.
К Ruby, PHP и Lua уже не притрагиваюсь. Ничего против них не имею, но просто нет задач. Python до сих пор используется из-за большой кодовой базы на нём написанной. Мне не нравится путь развития и самого интерпретатора и в целом всей Python экосистемы, где даже просто установить пакет из PyPI может быть тем ещё приключением из-за несовместимых версий зависимостей и ОС-специфичных setup.py файлах.
Чуть более года назад, начал программировать на Си. И это дело мне очень нравится! Go, безусловно, остаётся «Си каким он должен был бы быть».
Не проходит и недели чтобы не было бы что-то написано на Perl. Крайне удручает существование множества негативных мифов о нём. Насколько понимаю, рождённых исключительно из голов не сильно квалифицированных системных администраторов, которым чаще всего проходилось иметь с ним дело в 90-х годах, так как не больно много других интерпретируемых языков было. Человечество ещё не придумало что могло бы занять его нишу. Писать большие проекты наверное я бы не рекомендовал, но программы до 2-х экранов кода, особенно связанных с обработкой текста — самое оно.
Я негативно отношусь к использованию awk. Во-первых, как и в случае с sed и grep — есть разные диалекты: код написанный для gawk не будет работать на *BSD системах, где его нет и не будет из коробки. Проблема совместимости и портируемости. Во-вторых, в 99.99% случаев люди используют 0.1% его возможностей, не уходящих дальше {print $NF, $1}. awk люди не знают. Знаком только с парой человек умеющих на нём писать полноценные программы. Зависеть от полноценного ЯП только для печати столбцов?
Perl же, для преобладающего большинства случаев, везде будет одинаково работать. Работа с текстом, сокетами, файлами и процессами (fork/exec, как минимум) не требует внешних .pm библиотек и встроена в интерпретатор. Который занимает несколько сот килобайт и безболезненно встраивается даже в OpenWRT дистрибутивы.
Он имеет и все средства из коробки для удобной замены awk oneliner задач. Например awk пример выше, на Perl мог бы быть таким: perl -lane 'print "@F[$#F, 0]"' — работающим и в GNU и BSD и OpenWRT системе, требуя довольно компактный единственный исполняемый файл. К тому же, работающий очень и очень шустро с текстом. Достаточно знать только основы Perl, вместо нескольких диалектов (чтобы помнить разницу между ними) sed, awk, cut (тоже имеющий некоторые полезные опции (-w) не везде присутствующие), tr и подобных.
Python
Кроме Vim-специфичных вещей, для Python имеется только мизерный конфиг pdb отладчика:
----- ~/.pdbrc ----- import os alias hare os.system("kill %d" % os.getpid()) alias hare9 os.system("kill -9 %d" % os.getpid()) alias pp1 import pyderasn ;; print(pyderasn.pprint(%1, \ oid_maps=(locals().get("OID_STR_TO_NAME", {}),)))
hare алиас используется для убийства текущего процесса. Так уж вышло, что частенько быстрее прибить процесс, чем нажимать кучу Ctrl-D/C. А pp1 используется для pretty printing-а ASN.1 структур.
----- ~/.zshenv ----- export PYTHONDONTWRITEBYTECODE=1
Позволит не создавать .py[co] файлы, смысла в которых не вижу, только мешаясь на диске.
Я не использую PyPI в PIP напрямую. Написал GoCheese PyPI-совместимый сервер для кэширующего проксирования и отдачи локально загруженных пакетов. Удивительно, но на момент его написания (до сих пор?), на Python не было ни одного удовлетворившего меня простого прокси. pyshop катастрофически медленный. Благодаря GoCheese абсолютно всё, что когда либо устанавливал — надёжно сохранено на диске, не требуя Интернет. И работает стремглав быстро. А для хранения состояния использует директории и файлы, проверяя и запоминая контрольные суммы.
Ещё одна программа исключительно для Python исходного кода, написанная не на Python:
----- ~/work/pyimportcan/pyimportcan.pl ----- my $buf; my $con; my @imports; my %parsed; # Collect strings and aggregate the splitted ones while(<>){ next if /^[<=>]{2,}/; next if /^\s*#/; chop; $buf = ($con ? $buf : "") . $_; $con = /[\\,\(]\s*$/ ? 1 : 0; next if /^\s*\)*\s*$/; push @imports, $buf; }; # Consolidate information from where what is imported foreach (@imports) { s/[\\\(\)]//g; s/ */ /g; next if /import\s*$/; /^(.*)\s*import\s*(.*)$/; my ($where, $what) = ($1, $2); map { $parsed{$where}->{$_}++ } split /\s*,\s*/, $what; }; foreach my $where (sort keys %parsed){ map { print $where . "import $_\n" } sort keys %{$parsed{$where}}; };
Она преобразует import-ы, имеющие кучу вариаций написания:
from foo import bar from foo import bar, baz from foo import ( bar, baz ) from foo import ( baz, bar, ) from foo import bar, \ baz
к единому «каноничному» виду, очень дружелюбному для разрешения git конфликтов, и простому для работы в редакторах. Чёткий и детерминированный:
from foo import bar from foo import baz
Go
----- ~/.zshenv ----- export GOCACHE=/tmp/go-cache export GOPROXY=off export GOSUMDB=off export GOPATH=$HOME/work/gopath
Кэш для промежуточных файлов сборки не хочу хранить на диске, так как это занимает много места, быстро становясь не актуальным, плюс потенциально несущим ценную информацию о коде. Не использую ни sum.golang.org, ни proxy.golang.org, так как это утечка приватных данных. Текущая GOPROXY=off настройка вообще отключает лазанье в Интернет — например вызов go doc частенько туда хочет. Когда мне надо явно разрешить скачивание кода, то добавляю GOPROXY=direct перед командой go get.
Так как Go штатно может иметь множество версий одной и той же библиотеки в $GOPATH/pkg/mod, то размещаю $GOPATH директории на ZFS dataset с включённой дедупликацией. Результат: dedupratio=1.26x.
Из must-have утилит для работы с Go могу отметить:
- golang.org/x/tools/cmd/goimports, автоматически форматирующую код и обновляющую список import-ов, экономя кучу времени.
- golang.org/x/tools/gopls LSP сервер. Самый приятный и положительный опыт работы с LSP у меня только с Go, в котором он работает с невероятной скоростью. Что в Python, что в Си (на больших проектах) — можно ждать по несколько секунд когда обновится linter или получу подсказку дополнения.
- github.com/goware/modvendor помощник для создания vendor директории, включающейся в release tarball.
Я яростный поклонник Go модулей! Не скажу что мне их идея сразу же понравилась и что всё шло как по маслу и было понятно. Но в Go всегда так: спустя время ты понимаешь гениальную простоту и продуманность. Очень много людей, судя по блогам, не понимают как использовать vendor директорию и что $GOPATH, действительно, становится не нужен для сборки софта из чётко предоставленного исходного кода в tarball-е. Сам сторонник того, что в tarball всё должно быть, никакой зависимости от Интернета. Но $GOPATH deprecation ни капли не мешает.
C
Для Си разработки применяю LLVM, Clang, LLDB, clang-analyzer, clang-tidy, clangd LSP сервер, clang-format и include-what-you-use (IWYU). Сплошная LLVM/Clang экосистема. LSP сервер действительно помогает для linting-а. Статические анализаторы уйму раз ткнули меня носом в проблемы которые бы заметил неизвестно когда. А с IWYU забыл про геморрой корректного включения нужных #include-ов, хотя приходится рядом иметь mapping-файл с коррекциями его решений.
Из-за clang-format не форматировал ни единого кусочка (ну кроме таблиц констант) исходного кода, вызывая cfmt.sh. Стиль формата не полностью меня удовлетворяет, но пока наименее раздражающий:
----- ~/bin/cfmt.sh ----- #!/bin/sh clang-format -style="`cat ~/.clang-format`" $@
----- ~/.clang-format ----- { BasedOnStyle: llvm, ColumnLimit: 88, IndentWidth: 4, AlignAfterOpenBracket: AlwaysBreak, AlignConsecutiveAssignments: true, AllowAllParametersOfDeclarationOnNextLine: false, AllowShortBlocksOnASingleLine: true, AlwaysBreakAfterReturnType: TopLevel, BinPackArguments: false, BinPackParameters: false, BreakBeforeTernaryOperators: false, BreakStringLiterals: false, BreakBeforeBraces: Custom, BraceWrapping: { AfterFunction: true, }, }
Опыта на Си у меня совсем немного, но среди всех отладчиков с которыми работал в разных языках: pdb, ipdb, delve, gdb — LLDB показался самым удобным и в нём хочется путешествовать по всему что происходит в дебрях ОС. Единственный алиас для печати участка памяти:
----- ~/.lldbinit ----- command alias mr memory read --size 1 --format x --count %1 --
Во время разработки на Си, ощутил и понял удобство DTrace. И прежде что-то на нём иногда запускал и смотрел, но USDT (userspace defined tracing) пробы встраиваются очень просто и с минимальным overhead-ом для программы. Позволяя потом трассировать что в ней происходит. Причём USDT код может как для включения DTrace проб использоваться, так и SystemTap в GNU/Linux, далее используя eBPF framework для трассировки. Таким образом, исходный код пригоден без изменений под разные системы трассировки.
redo
Есть три небольшие HTML странички, которые существенно изменили всё в моей жизни программиста. Это предложение DJB о системе слежения за зависимостями, о системе сборки redo. Гениальная простота этого предложения, да и сама реализация, которую можно на Go написать за один день (подтверждено на практике), впечатлили меня как никогда. Об этом я уже писал. Сейчас у меня не осталось ни одного личного или рабочего проекта, где бы использовался Make или autoconf. У них просто нет никакого смысла существования.
Я был не совсем удовлетворён redo-c реализацией и был удручён скоростью работы apenwarr/redo реализации на Python. В итоге написал свою версию на Go, используя десятки и сотни раз на дню. Но в рабочих проектах проверяю совместимость .do файлов и с другими реализациями, где их может быть под сотню, включая default*.do.
----- ~/.zshenv ----- export REDO_JOBS=0 REDO_NO_SYNC=1
Здесь разрешаю распараллеливать задачи сборки и не делать fsync вызов, дабы потенциально ещё больше ускорить работу.
TAI64
Ещё я поклонник TAI64 формата времени, познакомившись с ним в daemontools утилитах. В UTC время всегда идёт вперёд, но не монотонно из-за високосных секунд (leap seconds). TAI время их учитывает и идёт равномерно. А TAI64 это предложение от DJB для формата хранения этого TAI значения: с сохранением секунд, наносекунд или аттосекунд. В своих программах начал использовать TAI64N. Хотя это и потребует наличия обновляемой базы данных високосных секунд, чтобы точно переводить TAI в UTC.
Vim
Перехожу к самому важному в своей системе: текстовому редактору. В нём провожу преобладающую часть всего времени пока не сплю. Всё что касается работы, ввода чего бы то ни было на web-сайтах, когда-то переписки с девушками (много тысяч писем!), выполнение работ в институте, создание статей/документов/отчётов и презентаций — всё делалось и делается в редакторе. Работа с SQL в клиенте СУБД, редактирование длинных строк командного интерпретатора, полей ввода броузера — всё использует внешний редактор (в моём случае это Vim). Это самая важная программа на компьютере, без которой он и не нужен.
Есть люди спрашивающие почему Vim, а не «модное Electron-driven творение или монстр от Microsoft/Apple»? Не знаю что им ответить. Подозреваю что там могут быть лучше интегрированы отладчики и подсказки связанные с языком программирования, но какой в этом смысл, когда сама правка кода там выполняется редактором другой весовой категории? «Почему ты используешь Unix или Windows NT, а не MS-DOS?» — для меня вопрос такого же уровня. В Vim несколько сотен команд редактирования и движения, сотни команд для командной строки, сотни переменных для настроек мельчайших нюансов поведения, плюс ещё возможность и скриптовать это всё. О каком сравнении может идти речь?
Есть люди считающие, что скорость редактирования не важна, ибо они бОльшую часть времени тратят «на раздумья». Не спорю что сама работа руками может занимать меньше времени, вот только частенько проще что-то попробовать сделать, попробовать изменить, порефакторить и посмотреть что выйдет на деле, чем в «виртуальной машине» в голове. Возможно преувеличивая лишь самую малость, мне со стороны кажется, глядя на людей работающих в чём-то простом типа Блокнота/Word/Nano/mcedit, что они могли бы 75% времени, ими проводимым за экраном, сэкономить просто более эффективно работая в редакторе и shell-е. Да зачастую всё программирование это сплошное редактирование уже имеющихся кусков текста! Убеждён, что люди просто не понимают и не замечают как много времени они тратят на «материализацию» своих мыслей (что они хотят увидеть на экране).
Я очень уважаю Emacs, но всё же как редактор он будет чуть менее эффективным. Но признаю что разница между ними настолько несущественна, что profit от единой «экосистемы» Emacs будет запросто перевешивать эффективность Vim, даже обвешанного скриптами и интегрированного с терминалами. Но Emacs не буду пробовать: его постоянная долбёжка по Ctrl вредна для здоровья (статистика говорит о большем количестве проблем с руками у Emacs-еров) и я не сторонник функционального программирования, так как оно требует другого склада ума.
В Vim плохо одно: постоянное обучение и изучение (no pain, no gain!). 20+ лет с ним работаю, но до пор раз в пару месяцев узнаю какую-нибудь очередную интересную команду или трюк. Нет, всё есть в документации, во встроенном :help, но понять значимость, понять use-case-ы и ценность команды или способа совмещения команд между собой не выходит сразу. Время от времени, беру и читаю его документацию со случайной позиции, стараясь что-то новое отметить и начать это отрабатывать на практике. Кажется что вершина мастерства Vim недостижима, но уже не первый год замечаю, что из 99% статей про Vim, что открываю с заголовками «ultra advanced powerful tips and tricks», не узнаю ничего нового, ни бита полезной информации.
С Vim может быть другая проблема: вредные привычки. Самое убийственное для производительности труда в Vim что наблюдал со стороны — использование визуального режима (v) не к месту. Я это называю «блокнот-mode», когда можно и рукоятью электрической дрели забивать гвозди. Визуальный режим, означает что штатные motion-ы не выполняются (motion будет на визуальное выделение), а это приводит к невозможности создания макросов, использования normal команд в какой-нибудь :g команде, банального повторения (.) действия к отличающемуся куску текста. Это как пытаться бегать, но связав себе ноги вместе — передвижение будет, но не выше заданной планки.
Есть ещё мнение что Vim требует кучи плагинов. Только если для совершенно специфичных задач, как и любому бы другому редактору надо было. А в общем случае: полнейшая чушь, говорящая только о непонимании родных возможностей. И сам грешил этим: когда-то ставя себя пару десятков плагинов и удивляясь почему они не встроены из коробки. С годами, количество плагинов только уменьшалось. Либо оставались совсем task-specific, либо несколько must-have творений Tim Pope (об этом ниже).
И ещё бытует миф что для работы в Vim нужно много строчек в .vimrc. Пухлые конфиги, как мне кажется, возникают по двум причинам:
- Новички пытаются изменить штатное поведение Vim чтобы он стал похож на других. Не понимают как надо использовать. И таких конфигов видел большинство. На своём опыте убедился что .vimrc со временем сильно похудел.
- У опытных пользователей накапливается огромный багаж привычек и более дружелюбного поведения по умолчанию именно для их задач и, так сказать, особенностей анатомии (рабочие пальцы, восприятие цветов). Пользователям не «настоящих» редакторов это сложно понять, так как в них мало чего настраивается.
Рассказывать про команды и трюки в Vim не смогу, как и в случае с zsh: это слишком объёмная тема. Так что ограничусь только рассмотрением своих конфигов.
----- ~/.vimrc ----- set t_Co=16 syntax on filetype on filetype plugin on set nocompatible set encoding=utf-8 set mouse=""
Включение синтаксической подсветки, filetype-специфичных плагинов, отключение совместимости с vi (без этого куча всего не будет работать), UTF-8 как кодировка по умолчанию и не пытаться перехватывать управление мышкой (чтобы в терминале выделять текст можно было).
Я форсированно ограничиваю количество цветов до 16 — это делает их насыщенными и контрастными, а не блёклыми и бледными, еле отличимыми друг от друга. Никогда не использовал цветовую схему отличную от default. На чёрном фоне с 16 цветами всё очень хорошо различимо.
----- ~/.vimrc ----- set viminfo-=h set viminfo+=f1,%,n~/secure/vim/info set viewdir=~/secure/vim/view set directory=~/secure/vim/tmp set undodir=~/secure/vim/undo set undofile
- Из viminfo (управление информацией сохраняемой в .viminfo файле) убираю забывание о hlsearch и требую сохранять метки на файлы (заглавные 'A-'Z) и список буферов. .viminfo находится на зашифрованном разделе, дабы в открытом виде нечаянно не осела важная информация из регистров.
- Директории для временных файлов, undo-файлов, view (сохраняющих информацию о текущей «сессии») тоже находятся на зашифрованном разделе.
----- ~/.vimrc ----- set autoindent set tabstop=4 set shiftwidth=4 set smarttab set expandtab set nojoinspaces set scrolloff=2 set backspace=indent,eol set shortmess=aoOtI set highlight-=v:Visual set highlight+=vr set highlight+=sr set cpoptions+=$ set showcmd set showmatch set completeopt-=preview set diffopt+=indent-heuristic,algorithm:histogram
- autoindent включает автоматическую вставку отступов во время ввода текста.
- tabstop, shiftwidth, expandtab говорят о размерах табуляции и о том, чтобы табуляцию превращать в пробелы.
Я сторонник использования символов табуляции для indenation. Но только
для indentation! Пробелы должны использоваться для всего остального:
например выравнивания относительно скобок. С этим не будет проблем даже когда
размеры табуляции у людей отличаются.
Но, к сожалению, люди (из за редакторов?) не в состоянии это делать грамотно и сдался и смирился с тем, что проще и надёжнее везде использовать пробелы. Это просто меньшее из зол. К тому же всё равно для indentation-а нажимаю tab, и команды изменения indent тоже прекрасно работают и с пробелами. - nojoinspaces предотвращает вставку лишних пробелов после символов пунктуации во время объединения строк (J). У нас не принято так писать, да и в программировании это точно не нужно.
- scrolloff показывает две строки над/под курсором когда он находится на самом верху/низу страницы. Так проще понять контекст где нахожусь.
- backspace опция говорит где может работать backspace. Может ли он «удалять» отступ и удалять перевод между строками (объединять их). У меня убрано значение start: это значит что не могу удалять ничего перед местом где начал вставку. К такому поведению у меня привычка со времён (n)vi.
- shortmess настраивает использование коротких информационных сообщений и отключение таких вещей как приветствие при запуске Vim. Например "[noeol]" вместо "[Incomplete last line]", «999L, 888C» вместо «999 lines, 888 characters», и т.д..
- highlight заменяет стиль подсветки визуальных выделений с блёклого серого подсвечивания на инвертирование цветов. Просто сильно заметнее и ярче.
- В cpoptions добавлено указание на то, что когда изменяю (change действие) кусок текста, то не удалять всё что было написано, а просто пометить $ в конце редактируемого куска. Гораздо удобнее видеть что за текст там был. Это же поведение по умолчанию в (n)vi.
- showcmd показывает в углу частично введённую, но ещё не законченную команду. Во-первых, это полезно при парном программировании (коллега видит что ты вбиваешь). Во-вторых, чётко даёт понять в каком ты сейчас режиме: ввёл ли ты что или ещё нет.
- showmatch при закрытии скобочки быстро прыгает курсором на соответствующую открывающую скобку, показывая что конкретно мы закрываем.
- Из completeopt опций регулирующих поведение предложения completion убран показ preview окна. Оно меня сбивает и визуально раздражает.
- diffopt настраивает более ресурсоёмкий, но приятный для человека алгоритм вычисления diff.
----- ~/.vimrc ----- set cursorline set cursorcolumn set colorcolumn=80
- cursorline, cursorcolumn включают вертикальную и горизонтальную подсветку текущего положения курсора. Делая некое перекрестие. Позволяют визуально моментально найти где находится курсор.
- cursorcolumn рисует вертикальную черту на границе 80-го символа, чтобы было понятно когда захожу за неё при наборе. 80 (±) символов это отличная ширина для восприятия человеком текста, не заставляющая горизонтально бегать глазами по ширине экрана.
----- ~/.vimrc ----- set listchars=trail:·,tab:>→,nbsp:% set list highlight ExtraWhitespace ctermbg=green ctermfg=blue match ExtraWhitespace /\s\+$/
- listchars, list подсвечивают символы, штатно невидимые, типа пустующих whitespace-ов в конце строки, символов табуляции и неразделяемого пробела (бывает появляется при вставке) указанным символом. Очень полезно лицезреть лишний невидимый мусор.
- ExtraWhitespace highlight ярко подсвечивает whitespace-ы в конце строки, которые в 99.99% случаев не нужны, являясь мусором.
----- ~/.vimrc ----- set relativenumber set numberwidth=3
relativenumber — ультраважнейшая штука, ради которой когда-то обновлял Vim из исходников, минуя пакеты. Существенно повышающая эффективность работы штука. Суть тривиальна: на каждой строчке показывается смещение относительно текущей. Строка которая на десять строк выше/ниже от текущей — будет иметь номер 10. Может ли человек быстро удалить две строки в Vim? 2dd — без проблем. А пять или десять? Уже нет, так как глазом он уже не в состоянии быстро посчитать и оценить сколько тут строк и какое число надо ввести. Относительный номер строки буквально покажет расстояние. Хочется прыгнуть «вот на эту» строку где-то на полэкрана выше? Относительный номер покажет что надо ввести например 23k.
Я часто хочу скопировать, переместить или удалить строку из какого-то далёкого места на экране: можно туда прыгнуть, нажать копирование/удаление, потом вернутся назад и вставить её. Я же сделаю :-27t. чтобы 27-ую строку выше скопировать под текущую. Это возможно сделать и без относительной нумерации, но абсолютные адреса строк чаще длиннее, просто потому что относительных на экране много не влезает и они состоят из 2-х цифр.
numberwidth уменьшает ширину столбца для отображения номеров строк. Так как используется относительная нумерация, то значений из трёх цифр там не будет и ширина в 4 символа (по умолчанию) излишня.
----- ~/.vimrc ----- set ignorecase set smartcase set hlsearch set incsearch set gdefault map <F4> :nohlsearch<CR>:MarkClear<CR>
- ignorecase и smartcase игнорируют регистр символов при поиске, если запрос был введён исключительно маленькими буквами. Если появляется хоть одна заглавная, то поиск будет с учётом регистра. Крайне удобная штука: не отвлекаешься на корректный регистр при поиске, и не теряешь возможность точно поиска.
- hlsearch подсвечивает на экране все найденные элементы. А incsearch будет их подсвечивать по мере ввода запроса. Кроме того, вас могут и переместить на совершенно другое место в файле, показывая где и что найдено в первую очередь. Очень удобное поведение!
- gdefault инвертирует поведение флага /g в командах замены :s/.../.... Это флаг глобальной замены во всей строке, требующийся чаще всего. С этой опцией отсутствие флага означает глобальную замену, а присутствие — единичную. Must-have!
- F4 клавиша отключает подсветку найденных слов. Когда всё нашлось, то подсветка может мешать. Набирать :nohl? Долго! Кроме того, использую плагин Mark для множественной разноцветной подсветки слов — F4 уберёт подсветку и для его результатов.
tags
----- ~/.vimrc ----- set wildmode=list:longest set tags=.tags/**/tags; set wildignore=**/.git,**/.tags
- wildmode задаёт поведение дополнения путей и имён файлов. longest режим дополняет только до наибольшей общей части строки, как это происходит в shell-е. Более привычно. list покажет список из возможных дополнений.
- wildignore игнорирует при поиске всё что касается файлов с тэгами (они могут быть большого размера и регулярно включаться в результат поиска) и git-а.
- tags указывает где искать файлы с тэгами.
Тэги — архиважнейшая функция! И удручает как мало людей даже не знают об их существовании. Python-исты часто используют сторонние плагины для навигации по исходному коду и ни разу не видел чтобы это было так же эффективно как работа с тэгами. Физический смысл тэгов банален: это база данных ключейзначений, где ключами являются эти самые тэги, а значениями информация об их местоположении. Текстовый индекс. Как правило, каждая запись ещё сопровождается информацией о типе тэга (переменная, функция, класс, и т.д.) и возможно полной сигнатурой функции.
Названия тэгов (название переменных, функций) могут пересекаться, поэтому везде имеется функционал чтобы перемещаться между тэгами одного названия. Вот такими могут быть предложения тэгов для Go, Си и Python кода:
:tselect Addr # Prio Art Tag Datei 1 F m Addr /home/stargrave/work/nncp/src/call.go struct:nncp.Call typeref:typename:*string Addr *string 2 F m Addr /home/stargrave/work/nncp/src/cfg.go struct:nncp.CallJSON typeref:typename:*string Addr *string `json:"addr,omitempty"` :tselect ASN1_BIT_STRING_free # Prio Art Tag Datei 1 F f ASN1_BIT_STRING_free .../libressl-2.7.4/crypto/asn1/tasn_typ.c typeref:typename:void ASN1_BIT_STRING_free(ASN1_BIT_STRING *a) 2 F p ASN1_BIT_STRING_free .../libressl-2.7.4/include/openssl/asn1.h typeref:typename:void void ASN1_BIT_STRING_free(ASN1_BIT_STRING *a); 3 F p ASN1_BIT_STRING_free .../libressl/include/openssl/asn1.h typeref:typename:void void ASN1_BIT_STRING_free(ASN1_BIT_STRING *a); :tselect Certificate # Prio Art Tag Datei 1 F c Certificate .../x509/cert.py class Certificate(Sequence): 2 v certificate .../xx/__init__.py class:XXEntryType certificate = 'certificate'
Даже nvi (не Vim!) в FreeBSD имеет навигацию по тэгам, как и less пейджер. Самое главное (для меня) что это очень быстрый способ навигации, в отличии от систем которые парсят исходный код на лету. ctags генератор файлов с тэгами даже идёт из коробки в FreeBSD. Но использую Universal Ctags генератор, так как он знает про Go язык, создавая vi/less-совместимые tags файлы.
Работая с проектом который использует сторонние библиотеки, хочется чтобы были тэги и по самому проекту и по зависимым библиотекам. Недавно просто добавлял символические ссылки к исходному коду зависимых библиотек в код проекта. Время от времени файл с тэгами необходимо обновлять, чтобы в нём отразились изменения. И с огромными сторонними библиотеками (например OpenSSL) это может занять ощутимое время, тогда как сама зависимая библиотека не обновляется вовсе.
Сейчас использую такой удобный подход: в проекте создаётся .tags директория:
----- ~/bin/mktags ----- #!/bin/sh -e mkdir -p .tags touch .tags/tags
В которой находится tags файл и возможно поддиректории со своими tags файлами. Проект с LibreSSL и libtasn1 зависимостями будет иметь следующую .tags директорию:
.tags/tags .tags/libressl/tags => /path/to/libressl/.tags/tags .tags/tasn1/tags => /path/to/tasn1/.tags/tags
.tags/tags отвечает за тэги только локального проекта. У меня не вышло (возможно это бага моей версии Vim) сделать так, чтобы Vim воспринимал тэги сторонних проектов без поддиректорий.
Ну и ладно! tags настройка в .vimrc отлично работает с этим use-case. Для генерирования файла с тэгами использую vimscript :Ctags команду, которой передаю языки программирования для индексирования. Она самостоятельно находит местоположение .tags/tags файла, находящегося в корне проекта, от которого и будет запущена uctags команда индексирования:
----- ~/.vim/plugin/ctags.vim ----- function! s:ctags(lang) let dst = tagfiles() if len(dst) == 0 echohl WarningMsg | echomsg "No tagfiles" | echohl None return endif let dst = sort(dst, {a, b -> len(a) > len(b) ? 1 : len(a) == len(b) ? 0 : -1})[0] let src = "/" . join(split(dst, "/")[:-3], "/") if dst[0] != "/" let dst = getcwd() . "/.tags/tags" let src = getcwd() endif let cmdline = [ \"uctags", \"--languages=" . a:lang, \"--python-kinds=-i", \"--c++-kinds=+p", \"--recurse", \"-f", dst, src, \] execute "!" . join(cmdline, " ") redraw! endfunction command! -nargs=1 Ctags silent call s:ctags(<f-args>)
Чтобы подготовить к работе новый проект, делаю mktags, а далее внутри Vim, например :Ctags C,C++. Зависимые библиотеки, интересующие меня, руками добавляю через mkdir и ln -s.
.vimrc
----- ~/.vimrc ----- set exrc set secure
exrc опция позволяет Vim-у автоматически исполнять .vimrc/.exrc файл в текущей директории. Например в корне рабочего проекта туда можно заносить какие-то project/language специфичные настройки. secure строже будет относится к дозволенным действиям из этого файла, на всякий пожарный (мало ли кто подложит этот файл в git коммите). Когда-то планировал в этом .exrc файле указывать пути до файлов тэгов к другим проектам, но решил что хранить эту информацию в состоянии ФС будет проще.
----- ~/.vimrc ----- set foldmethod=indent set foldnestmax=9 set foldenable set foldcolumn=1 set foldlevel=0 autocmd BufWinEnter * normal zR highlight FoldColumn ctermfg=cyan ctermbg=black highlight CursorColumn ctermfg=cyan ctermbg=red
Включение автоматического folding-а по отступам, с отображением в колонке слева уровня fold-а. При открытии окна, все fold-ы будут раскрыты (выполняется zR команда). Folding (схлопывание множества строк в одну) бывает очень полезным, когда хочется лицезреть кучу кода, но убирая тела циклов или целых функций. Часто схлопываю все fold-ы чтобы видеть в файле только названия функций/классов.
----- ~/.vimrc ----- set laststatus=2 set statusline=%F\ %m%r%h%w%k set statusline+=%{len(getqflist())?'[Q]':''} set statusline+=%{len(getloclist(0))?'[L]':''} set statusline+=\ %= set statusline+=%n\ %#Question#%Y set statusline+=%#DiffChange#%{strlen(&fenc)?&fenc:&enc}%{&bomb?'-bom':''}\ %{&ff} set statusline+=%#DiffAdd#%l/%L set statusline+=%#Todo#%c%V:0x%B set statusline+=%#Comment#%o set statusline+=%#Error#%{LintStatus()} set statusline+=%*\ %P function! LintStatus() " it is overrided in ftplugins return "" endfunction
Настройка строки состояния, которая выглядеть может вот так:
options.txt [-][RO][Hilfe] ^^^^2 HELP utf-8 unix 6370/8966 7-49:0x2A 272384 71% ^ ^ ^ ^ ^
Тут нельзя передать цвета, поэтому в местах с "^" указал границу между двумя разными цветами, между которыми нет пробела. То есть «HELP» и «utf...» написаны слитно. Стараюсь экономить место, которого не хватало бы для всего что хочется отобразить когда открыто много split-ов.
- В начале пишется имя файла.
- Затем вереница из флагов: есть ли изменения в файле ([+]/[-]), только ли он для чтения ([RO]), является ли он help-буфером ([help], но у меня Vim перевёл на немецкий), является ли он preview-окном ([preview]), существует ли сейчас в принципе quickfix или location списки ([Q], [L]).
- Далее отображается множество "^"-символов если текущее окно активно. Игрался с цветами строки состояния чтобы подсветить активное окно, но так и не нашёл которые бы одновременно и были заметны и не сбивали восприятие. Вереница из "^" хорошо заметна, чтобы понять в каком сейчас окне нахожусь.
- Далее пишется номер буфера, за который это окно отвечает. Может быть полезно если хочется удалить буфер и сразу виден его номер для :bd X команды.
- Далее показывается тип файла (filetype).
- Далее его кодировка и наличие BOM (:set bomb!).
- Далее «формат» файла, а если точнее, то способ перевода строк: CRLF (dos), LF (unix) или CR (mac). Довольно часто полезно видеть это, чтобы понимать что за файл под рукой. Как и видеть кодировку отличную от UTF-8.
- Номер текущей строки и всего строк.
- Байтовая позиция курсора в строке и символьная. Например точка в этом предложении сейчас имеет значение 91-51, означая, что курсор на ней находится на 91-ом байте, но 51-ом символе на экране. Кириллический символ в UTF-8 занимает два байта, но один символ экрана. Табуляция может занимать 8 символов экрана, но один байт. Оба этих значения полезны.
- Unicode значение символа под курсором. Очень полезно, как минимум, чтобы понимать что у нас Unicode символ похожий на ASCII.
- Абсолютная байтовая позиция в файле где сейчас находится курсор.
- Ярко красное отображение вывода функции LintStatus().
- Процентаж того где мы находимся относительно всего файла.
Для Python использую самописный асинхронный вызов linter-а. Его код тут не привожу, ибо занимает больше экрана. Суть работы проста: в фоне запустить pylint/pyflake, получить весь вывод, отпарсить и преобразовать в формат quickfix списка, загрузить в quickfix окно. Пока он работает, то LintStatus() возвращает строку LN, предупреждая что linter сейчас работает.
В Go у меня есть скрипт вызывающий внешнюю gogetdoc команду, которая в LintStatus() напишет GD. Строка состояния — динамичная штука.
----- ~/.vimrc ----- autocmd BufWinLeave ?* silent! if &ft !=# "gitcommit" | mkview | endif autocmd BufWinEnter ?* silent loadview
Эти hook-и создают view-файлы при выходе из буфера и загружают их при открытии, если есть имя файла (?). Вообще в Vim есть .viminfo, но view является куда более полноценной информацией о состоянии рабочей «сессии», вплоть до последнего местоположения курсора.
Плюс не стоит забывать, что мой Vim запоминает и список буферов при выходе. Если просто запущу vim, то увижу пустой экран, однако на самом деле все буферы загруженные при выходе будут уже присутствовать. По сути не сохраняется только местоположение и размеры окон смотрящих на эти буферы. Но это можно сделать через штатный функционал сессий (:mksession, vi -S Session.vim).
----- ~/.vimrc ----- nnoremap <C-b> <C-W> noremap <C-j> <C-w>j noremap <C-k> <C-w>k noremap <C-l> <C-w>l noremap <C-h> <C-w>h nmap <leader>- <C-w>\|<C-w>_ nmap <leader>= <C-w>= nmap <Del> :close<CR> autocmd VimResized * wincmd =
- Я аллергичен к Ctrl-сочетаниям клавиш для частых действий. Это всё для Emacs оставьте. С редкой вставкой слова под курсором (C-R+C-W) ещё готов мириться, но не частым перемещением между окнами. Ctrl+[hjkl] клавишами сразу перемещаемся между окнами.
- Так как Ctrl-B могу послать одним нажатием Menu клавиши, и в Vim оно ни с чем не конфликтует, то Ctrl-B (одно!) нажатие является и C-W. Почему не оставить только Ctrl-B алиас с которым переход между окнами тоже можно осуществить двумя нажатиями? Потому что при использовании Ctrl+[hjkl], Ctrl можно оставить зажатым.
- \- (корректнее писать «leader»-клавиша + "-", но у меня leader является обратным слэшом (как по умолчанию), так что буду уж писать настоящую клавишу) делает «максимизирование» размеров окна: заполняет всё вертикальное и горизонтальное пространство. От остальных окон остаются только строки состояния. Как бы аналог Prefix+Z из Tmux. Частенько бывает нужно временно удобно посмотреть на текст не ютясь в маленьком окне. А \= выравнивает размеры всех окон обратно. Есть отдельные плагины для Vim позволяющие полностью запоминать все точные размеры окон чтобы их восстановить обратно — но либо иметь ровно одну строчку алиаса, либо скрипт на десятки строк? Меня удовлетворяют и окна равного размера и остающийся statusline.
- Клавиша Del закрывает окно. Сверхчасто используемая штука. Ну не набирать же :bd!
- Из-за tiling-ового оконного менеджера dwm, любое появление новых окон изменяет и размер уже существующих на экране. Поэтому размеры X11 окон на практике может часто «колбасить», а в Vim при этом сбиваются размеры внутренних окон. VimResized событие вызывает команду выравнивания размеров.
----- ~/.vimrc ----- set titleold = "" set title autocmd BufEnter * let &titlestring = expand("%:t")
Эти настройки заставляют Vim посылать escape последовательности изменения заголовка окон (в tmux и X11). В качестве заголовка используется последний элемент пути (название файла).
----- ~/.vimrc ----- set comments-=mb:* set formatlistpat=^\\s*\\*\ \\s* set formatoptions+=onj
Один из примеров за что так люблю Vim. Настройка поведения форматирования комментариев и списков. Удовлетворяющего всех поведения быть не может — ничего не бывает сразу удобного для всех.
- В formatoptions добавляется: требование добавлять символ комментария (оценивая текущий контекст и известные символы комментариев) при добавлении новой строки командами o/O; включение распознавания нумерованных списков; отбрасывание символов комментариев во время объединения строк (J) (Vim начнёт воспринимать текст в комментариев как будто в нём нет лидирующих символов комментариев).
- По умолчанию звёздочки в начале строк распознаются как символы комментариев (в Си языках часто используются). Но я не использую /*… */ комментарии совсем, а звёздочки у меня для списков. Соответственно, хочу чтобы к звёздочкам применялись «правила» работы со списками. Из comments убираю знание о звёздочках как символах комментариев. А в formatlistpat добавлен шаблон распознавания звёздочек как элементов списка.
----- ~/.vimrc ----- set spelllang=ru,en_gb highlight SpellBad cterm=inverse ctermfg=red ctermbg=black highlight SpellLocal term=reverse ctermfg=red ctermbg=cyan
Тут указывается какие словари использовать для проверки орфографии. Настройки цветов изменены из-за моих правок для создания inverse цветов и форсированного ограничения палитры.
По возможности стараюсь использовать британские слова, поэтому и en_gb словарь. А русский словарь (~/.vim/spell/ru.utf-8.spl) генерировал самостоятельно, так как противник противников использования буквы «ё», а дистрибутивы из коробки подкладывают словари без неё. Поэтому пришлось конвертировать rus-myspell-yo-0.99f7 в формат словаря Vim.
Раздражающие опечатки:
----- ~/.vimrc ----- map q: : command! W w nmap <F1> :help!<CR> imap <F1> <C-O>:help!<CR> iabbrev итд и т.д. iabbrev итп и т.п.
- q: запускает редактирование истории командной строки. Очень часто опечатываюсь, набирая :q, после чего приходится выходить из появившегося окна и снова набирать :q. Когда редактирование истории мне действительно нужно, то можно вызвать командую строку и нажать Ctrl-F.
- Для сохранения файла чаще всего использую :w, где при наборе двоеточия могу не успеть отпустить Shift при наборе «w» — ещё одна частая опечатка.
- Бывает нажимаю F1, в том числе из-за привычки проверки ею почты в zsh. И по умолчанию это откроет мне помощь. Just for fun вызываю при этом встроенную пасхалку из «Автостопом по галактике», показывающую мне «Nur keine Panik!». Зато не открывающую назойливого окна помощи.
----- ~/.vimrc ----- set keywordprg= let g:sh_no_error = 1 set termwinkey=<C-B>
- При нажатии K, по умолчанию, вызывается man для слова под курсором. Бывает нечаянно нажимаю эту клавишу, получая выговор о том что такого man нет. Для конкретных языков программирования эта функция полезна, но не в общем случае (пустой keywordprg).
- sh_no_error заставляет игнорировать ошибки (с точки зрения syntax/sh.vim) shell-скриптов. Конкретику уже не помню, но sh.vim бывает ошибается в понимании что написано в скрипте, сильно раздражая безумной подсветкой на экране.
- termwinkey задаёт клавишу управления окнами терминала (встроенный терминал в Vim… который тоже запускается под мультиплексором терминалов Tmux, в свою очередь запускаемый в эмуляторе терминала X11). Опять же, просто использую Ctrl-B, посылаемый у меня одним нажатием Menu клавиши. Но терминалы, сколько не пробовал, не выходит использовать для пользы совсем.
----- ~/.vimrc ----- nmap <space> f<space> nmap _ f_l
Как часто люди перемещаются по «большим» словам (W)? Очень! Но также очень часто хочется чтобы курсор находился не на самом слове, а на пробеле перед ним. А штатно на пробеле нет никакой команды. У меня теперь есть. Плюс «большие» прыжки с большими словами (W/E) выполняются с нажатием Shift-а, что медленнее. Один из тех самых трюков которые люблю: просты и эффективны. А "_" заставляет перемещаться по подчёркиваниям, которых полно в Python коде бывает, чтобы прыгать по словам внутри названий функций и переменных.
----- ~/.vimrc ----- nmap <leader>] "*yiw nmap <leader>p "_diwP
\] копирует в X11 буфер обмена слово под курсором. \p заменяет слово под курсором, не загрязняя регистры, значением регистра по умолчанию. Удалили/скопировали где-то слово и им хочется заменить другое, но чтобы от прошлого не осталось и следа.
----- ~/.vimrc ----- nnoremap <leader>d "_d vnoremap <leader>d "_d
\d производит удаляет в "/dev/null" регистр. После, конечно же, нужно указать motion. Очень часто использую.
----- ~/.vimrc ----- map Q gq cmap ][ '[,'] imap <C-b> <C-x><C-o> nmap <Tab> :buffers<CR>:b<Space> nmap <silent> <Home> :registers<CR> nmap <leader>' yiwciw"<C-r>""<ESC> nmap <leader>h1 yypVr=o nmap <leader>sc 024i-<ESC>a >8 <ESC>24a-<ESC> nmap <leader><C-]> :vertical wincmd ]<CR> nnoremap <C-P> <C-I>
- Q более быстро позволяет написать qg, который используется, как минимум, для переформатирования параграфа gqap.
- ][ набор в командной строке раскрывается в указание диапазона от метки [ до метки ] — они автоматически ставятся после вставки текста. Архичасто используемый функционал, так как после вставки какого-то куска ему хочется изменить indentation: ][< и Enter!
- Ctrl-B в режиме ввода вызывает LSP completion функционал. В чужих статьях вроде бы видел, что часто используют Shift+Space комбинацию, но Ctrl-B у меня нажимается одной клавишей.
- Когда-то для переключения буферов ставил плагины типа bufexplorer, в которых Tab использовался для показа списка буферов и их выбора. Позже дошёл до простейшего тривиального решения: показать список буферов и набрать :b с пробелом, чтобы оставалось только указать название/номер буфера для переключения и нажать Enter. Причём Vim из коробки может понять указание буфера через часть его имени.
- Home клавиша покажет содержимое всех регистров. Частенько проще их посмотреть, чем понять по косвенным признакам (вставке, применению макроса) что там за значение.
- \' одна из самых частых команд в Python. Просто обрамляет слово под курсором в двойные кавычки. Прежде она использовала функционал surround плагина (о нём ниже), но независимость всё же лучше. А если хочется обрамить слово в одинарные? Или скобочки? Обрамляю в двойные, а дальше использую surround возможности для смены двойных кавычек на нужные мне. Звучит геморройно, но на деле всё это всё делается достаточно быстро: \'cs"'.
- \h1 подчёркивает всю текущую строку "=" символом. Использовалось, в первую очередь, в reST файлах для создания заголовка (отсюда и «h1», как header в HTML). Интересно как она это делает: копирует всю строку, вставляет ниже (чтобы появилась строка нужно длины), визуально полностью выделяет, делает замену (автоматически применяя ко всем символам) на "=". Если нужно использовать другой символ для заголовка, то проще и быстрее выполнить изменение "=" символов, например \h1:s/=/-. Вижу колоссальное количество коммитов в разных репозиториях на исправление некорректной длины подчёркивания заголовка — явно людям редактор не помогает.
- \sc создаёт длинный "-- >8 --" разделитель. Сочетание символов похожих на ножницы увидел в git. Очень часто в письмах ставлю этот хорошо визуально различимый разделитель.
- \+Ctrl+] переходит на тэг (слово под курсором) в вертикально открытом новом окне. Аналог встроенного Ctrl-W+Ctrl-], но не в горизонтально разбитом окне.
- По причинам которых не помню, Tab является и Ctrl-I. Tab перебил на выбор буфера. Соответственно и Ctrl-I перестаёт работать для перехода по jump list-у «вперёд» (Ctrl-O работает «назад»). Чтобы не терять функционал хождения по jump list-у в любую сторону, использую Ctrl-P, который тоже удобно находится рядом с Ctrl-O, симметрично его дополняя.
----- ~/.vimrc ----- command! E Explore command! Ch cd %:p:h command! -bar -nargs=? -bang Tmp :silent vnew<bang>| setlocal buftype=nofile bufhidden=hide noswapfile buflisted filetype=<args> modifiable
- В старых версиях Vim :E команда открывала файловый броузер на текущей директории. В новых версиях пришлось бы набирать :Ex. А я привык к короткой записи.
- :Ch команда меняет текущую рабочую директорию Vim на ту, в которой находится текущий файл.
- :Tmp XXX команда создаёт окно для временных данных. Без хранения чего бы то ни было на файловой системе. XXX позволяет задавать тип файла в нём, чтобы хотя бы синтаксическая подсветка работала.
~/.vim/pack
Всё остальное у меня вынесено в директории и отдельные скрипты. В Vim8 появились packages: возможность подключения дополнительных «chroot»-ов Vim директорий. Причём как с автоматическим запуском, так и опциональным. Например плагины Tim Pope у меня установлены так:
~/.vim/pack/tpope/start/fugitive # установлено из .zip ~/.vim/pack/tpope/start/vim-abolist/.git # git репозиторий смотрящий на github [...]
А мой изредка используемый (поэтому не нужно его загружать и тратить ресурсы) плагин Codecomm включается когда нужно командой :packadd codecomm:
~/.vim/pack/codecomm/opt/codecomm
Смысла в пакетных менеджерах после этого нет никакого. Фичи типа «пропиши github/foo/bar строчку, автоматически всё скачаю и запущу» ужаснут любого кто задумывается о безопасности и компрометации своего компьютера. И не только я так считаю: все плагины Tim Pope тоже советуют именно такой способ установки. Но стоит давать скидку на то, что многие плагины были написаны до Vim8, поэтому они с чистой совестью могут упоминать «альтернативные» пакетные менеджеры.
Vim разработка регулярно славится тем, что она годами ничего не привносит существенного, как мне показалось. Потом появляются какие-нибудь сторонние проекты типа NeoVim. После чего Мууленаар в новой версии выкатывает и асинхронные задачи, и каналы, и пакетную систему. Так было и во времена nvi, в какой-той момент перевешивавший по возможностям Vim. Вот только, на мой взгляд, в Vim всё гораздо проще и продуманнее делается в итоге.
Сразу расскажу про несколько убер-полезнейших плагинов. Всё творение Tim Pope:
- surround. Первое что однозначно бы советовал ставить. Позволяет добавлять, удалять и изменять «обрамления» участков текста. Заменять одни кавычки/скобочки на другие и всё в таком духе. Это всё чрезвычайно эффективно: проще набрать слово, а потом просто добавить к нему нужное обрамление. Это настолько популярная и полезная штука, что даже для zsh делают плагины повторяющие этот функционал в vi-режиме редактирования.
- repeat. Явно этот плагин никак не виден, но он позволяет повторять команды surround плагина (и с полдюжины других творений Tim Pope) штатной командой повторения (.). Обязательное дополнение к surround.
- unimpaired. Даже сам Tim Pope пишет что этот плагин просто является выжимкой mapping-ов из его .vimrc. Но так уж вышло, что и у меня многие mapping-и были похожи и часто используемы, типа ]q/[q для перехода к следующему/предыдущему элементу quickfix списка (не набирать же :cnext!).
Этот плагин полон подобных быстрых переходов, а также переключателей орфографии, wrapping и прочих мелочей. Плагин сконсолидировал множество всего что у меня было в .vimrc и я переделал переключение textwidth чтобы оно походило на unimpaired:
----- ~/.vimrc ----- nmap [ob :set textwidth=72<CR> nmap ]ob :set textwidth=0<CR>
- abolish много чего умеет на тему похожих и схожих замен. Команда замены :Subvert бесценна во время программирования и рефакторинга. Например у вас есть строчка FooBar foo_bar FOO_BAR. Применив :S/Foo/Baz/ вы получите BazBar baz_bar BAZ_BAR. Одной :S командой можно делать глобальный рефакторинг для всего файла!
А также abolish умеет менять «coercion» у слова: применив cr_ к FooBarBaz, он изменится на foo_bar_baz. Применив crm («m» от «mixed» case) к этому, вы получите FooBarBaz снова. В языке, где регулярно присутствует мешанина из mixed, camel, kebab, snake case написания одного и того же слова — это бесценнейший плагин. - commentary позволяет gc командой добавлять или убирать символы комментариев. Крайне просто и эффективно работает.
- tbone позволяет выполнять команды в tmux. Использую частенько :Tyank и :Tpaste для вставки текста из буферов tmux или копирования в них. Удобная интеграция.
- Ну и конечно же fugitive. Как пишет сам Tim Pope в описании, "(этот) git wrapper настолько крутой, что должен быть вне закона" (отсюда и fugitive название). И он не преувеличивает: выполняю тьму действий в git-е только через него. В первую очередь это :Gdiff — только ради него можно бы было ставить этот плагин.
----- ~/.vimrc ----- nmap <F7> :Gvsplit <C-R><C-W><CR>zR nmap <F9> :diffupdate<CR>:syntax off<CR>:syntax on<CR>
F7 позволяет открыть окно с просмотром (в fugitive) коммита находящегося под курсором. Постоянно использую во время интерактивного rebase. А F9 «передёргивает» синтаксическую подсветку, которая (вина Vim) может иногда съезжать, особенно во время работы с diff-ом. Костыль, но терпимый.
Отдельно скажу своё фи про NERD* семейство плагинов: это пример того, что не надо пытаться делать с Vim-ом. NERDTree только скрывает, и так достаточно удобный и хороший, функционал, не давая пользователю его познать. А в NERDCommenter достаточно увидеть hard-coded знания о символах комментариях для разных языков, в противовес commentary, использующим штатную commentstring настройку. Честно, если в статье про Vim вижу совет использования NERD*, то сразу закрываю.
А также другие плагины:
- Mark позволяет подсвечивать разными цветами разные слова. Просто нажимая \m будет другой цвет очередного подсвечиваемого слова. Помню что мне это сильно помогало в огромных SQL запросах, где полно слов отличающихся только окончанием и тяжело визуально ориентироваться и дифференцировать их.
- traces в реальном времени показывает какое изменение будет производить набираемая команда :s. На деле оказалось очень и очень удобным: сразу виден корявый результат!
- IndentWise добавляет motion-ы по уровням indent-ов. С ним можно «переместиться на ближайшую строку с меньшим/большим indent-ом». Можно и другие команды (например удаление) применять к indent-блокам. Очень часто использую как в Python, так и в Си для навигации. Как мне сказать чтобы я переместился на начало внешнего for-цикла где-то за пределами экрана? Indent motion-ом это делается мгновенно.
- ViewPort выглядит как хак, но вполне себе неплохой. Он позволяет участок буфера редактировать в отдельном окне. Он запоминает начало/конец редактируемой части и вешает hook-и чтобы, при сохранении ViewPort-буфера, его содержимое вставилось назад между метками. В Python коде это очень часто помогало, когда есть масса визуально похожих частей кода и можно ошибиться, начав менять что-то не то. Или поиск будет регулярно выходить на пределы редактируемой функции/класса, сбивая с толку.
- vim-lsp LSP клиент. Корявый, регулярно сбивающий :Changes плагин. Но… всё же приносящий пользу. LSP идея мне нравится, но лишь бы оно быстро работало. С Go вообще никаких проблем. clangd LSP сервер для Си кода медленно работает с completion на больших проектах. С Python польза LSP мне ещё не ясна, когда и так есть асинхронный linter, заполняющий, к тому же, quickfix.
----- ~/.vim/plugin/lsp.vim ----- let g:lsp_auto_enable = 1 let g:lsp_diagnostics_echo_cursor = 1 let g:lsp_diagnostics_echo_delay = -1 let g:lsp_signature_help_enabled = 0 function! s:on_lsp_buffer_enabled() abort setlocal omnifunc=lsp#complete nmap <buffer> [g <Plug>(lsp-previous-diagnostic) nmap <buffer> ]g <Plug>(lsp-next-diagnostic) nmap <buffer> gd <plug>(lsp-definition) nmap <buffer> K <plug>(lsp-hover) endfunction augroup lsp_install autocmd! autocmd User lsp_buffer_enabled call s:on_lsp_buffer_enabled() augroup END
Вот показ диагностических сообщений (linter) полезен: пускай даже и не быстро работает, но не отвлекает и подсказывает о примитивных проблемах (невалидный синтаксис). [g и ]g прыгают по ближайшим диагностическим проблемам. gd (goto definiton) из-за тэгов не особо нужен, но пускай будет. А K команда показывает hover окно с сигнатурой функций. Причём автоматически показ сигнатуры (g:lsp_signature_help_enabled) во время ввода отключён — слишком уж мелькает на экране всё при этом. Плюс отключено автоматическое omni-дополнение из-за тормозов (явно вызываю только когда нужно через Ctrl-B).
Особняком стоит мой самописный CodeComm. История его началась почти десять лет назад, когда, работая в одной компании, надо было делать code review и специализированного инструментария для этого не было — review писался в виде комментариев к задаче в трэкере. Открывая код для review в fugitive, визуально выделял интересующие куски кода, нажимал \cc, открывалось окно с этим куском кода, где оставлял свои замечания. Всё это агрегировалось в файле на диске, который затем вставлял в трэкер.
На другой работе использовался Gerrit. Но не буду же сидеть в броузере для review!? Модифицировал плагин: он в итоге создавал JSON, пригодный для вставки в API Gerrit-а. А дальше вынес основной функционал комментирования в CodeComm, не привязанный к какой-либо системе.
Выделив какой-то блок внутри fugitive (чтобы был виден хэш коммита и путь), нажав \cc, будет создаваться такой вот блок:
-----# 2 [ ec77b1f6b | src/pyderasn.py ]----- 79 def tag_encode(num, klass=TagClass.universal, form=TagForm.primitive): 80 if num < 31: 81 # [XX|X|.....] 82 return int2byte(klass.value | form.value | num) 83 # [XX|X|11111][1.......][1.......] ... [0.......] 84 return int2byte(klass.value | form.value | 31) + ... 85 ---------------------------------- >8 ---------------------------------- Here goes my comment.
Номера строк, номер комментария (чтобы сослаться), хэш, путь. Агрегирование комментариев в файл на диске. Для ревью кода использую сейчас только его.
~/.vim/plugin
По сути вся ~/.vim/plugin директория может находится внутри ~/.vimrc, как и наоборот. Принципа разделения у меня нет никакого — просто ощущения что надо выносить, а что по мелочам оставить в общей куче. Только один раз вставлю тут строки проверяющие загружен ли плагин — все они могут чуть-чуть ускорить загрузку Vim, но интереса более не представляют. Рассмотрю содержание директории без какого-либо порядка:
----- ~/.vim/plugin/buftabline.vim ----- if exists('*<SID>BufTabLine') | finish | endif function! BufTabline() redir => bufsRaw silent buffers redir END let bufs = [] for buf in split(bufsRaw, "\n")[:16] let leftIdx = stridx(buf, '"') let rightIdx = strridx(buf, '"') let filename = substitute(buf[leftIdx + 1 : rightIdx - 1], "%", "%%", "g") let linenum = split(buf[rightIdx:], " ")[-1] let attrs = split(buf[:leftIdx-1], '\s\+') let attrs = map(attrs, 'substitute(v:val, "%", "%%", "g")') let attrs = (len(attrs) == 1) ? attrs[0] : attrs[0] . ":" . join(attrs[1:], "") let attrs = "%#TabLine#" . attrs . "%*" if len(filename) > 20 | let filename = "<" . filename[-(20-1):] | endif let hl = (match(attrs, "%%") == -1) ? "%#TabLine#:" : "%#TabLineSel#" let bufs = add(bufs, attrs . hl . filename . ":" . linenum . "%*") endfor return join(bufs, " ") endfunction set showtabline=2 set tabline=%!BufTabline()
Одна из немногих фич которую совершенно не использую в Vim это tab-ы. Не сложилась дружба с ними, даже не пытаюсь более. Но раз для них выделена целая строка tabline, то почему бы не использовать её для информации? Вот её и заполняю списком буферов, аналогично выдаче :buffers, только в одну строку. Сейчас она выглядит вот так:
1:a:mysetup.html:6040 2:~/.vimrc:118 17:<mm/doc/codecomm.txt:34 20:%a<ugin/buftabline.vim:1
Она буквально перехватывает вывод :buffers и парсит его. Почему нельзя использовать функции vimscript? Потому что не нашёл возможностей для получения ряда интересующих меня атрибутов буферов! Или плохо искал.
----- ~/.vim/plugin/chnglstnav.vim ----- function! s:Chng(...) if a:0 == 0 | return | endif execute "normal " . a:1 . (a:1 > 0 ? "g;" : "g,") endfunction command! -nargs=? Chng call s:Chng(<args>) nmap <End> :changes<CR>:Chng<Space>
End клавиша показывает список :changes (вынес на отдельную клавишу, потому что часто использую) и подготавливает :Chng команду к вводу. Ей можно передать номер change на который надо прыгнуть назад. Указав отрицательное значение, можно прыгнуть «вперёд». Использую это всё когда нужно совершить прыжок не на 2-3 шага назад/вперёд, а на сколько именно не помню.
Defsplit плагин изначально писался для тривиальной задачи: разбить сигнатуру Python функции по строчкам:
def foobar(self, foo: str, bar: Some[thing, too]) -> None: превращается в: def foobar( self, foo: str, bar: Some[thing, too], ) -> None: foo(bar, baz)[0] превращается в: foo( bar, baz, )[0]
То ли совсем не нашёл готовых средств для этого, то ли все они занимали тьму кода. Если для такой (простой) задачи вижу плагин на несколько экранов — что-то тут не так, иду пробовать писать своё решение. :Defsplit делает разбиение сигнатуры по строчкам. Можно указать порядковый номер скобочки с которой начать перенос. :Undefsplit схлопнет всё выражение в одну строчку: например уменьшилось количество аргументов или надо переразбить по другой скобочке. Далее появился Brsplit на том же самом «движке» для разбиения любых скобочек. Во время разработки на Python это всё использовалось сотни раз в час! Для Go, очевидно, Defsplit не применяется, так как в нём собственная жёсткая система автоматического форматирования.
----- ~/.vim/plugin/disables.vim ----- let g:loaded_2html_plugin = 1 let g:loaded_getscriptPlugin = 1 let g:loaded_logipat = 1 let g:loaded_rrhelper = 1 let g:loaded_spellfile_plugin = 1 let g:loaded_vimballPlugin = 1 let g:loaded_vimball = 1
Отключение загрузки кучи родных плагинов, которые мне штатно не нужны. Сокращает время запуска Vim.
----- ~/.vim/plugin/exted.vim ----- function! s:exted(ext) execute "edit %<." . a:ext endfunction command! -nargs=1 Ee silent call s:exted(<f-args>)
Ee h позволяет отредактировать файл с таким же названием как и текущий, но «h» расширением. Использую для переключения между .c и .h.
----- ~/.vim/plugin/fileline.vim ----- function! s:gotoline() let file = bufname("%") if filereadable(file) | return | endif let names = matchlist(file, '\(.\{-1,}\):\%(\(\d\+\)\%(:\(\d*\):\?\)\?\)\?$') if empty(names) | return | endif let file_name = names[1] let line_num = names[2] == "" ? "0" : names[2] let col_num = names[3] == "" ? "0" : names[3] if !filereadable(file_name) | return | endif let nr = bufnr("%") exec "keepalt edit +" . line_num . " " . file_name exec "normal! " . col_num . "|" exec "bdelete " . nr endfunction autocmd! BufNewFile *:* nested call s:gotoline() autocmd! BufRead *:* nested call s:gotoline()
Это must-have возможность открытия файла с указанием номера строки через двоеточие. foo:12 имя файла откроет foo файл на 12-ой строке. Мой вариант это упрощённый file:line, в котором происходила замена текущих открытых окон на новое, что мне было не удобно. Огромное количество утилит и linter-ов выдаёт адреса строк в файлах именно в этом формате.
----- ~/.vim/plugin/pastemode.vim ----- if &term =~ "screen.*" let &t_ti = &t_ti . "\e[?2004h" let &t_te = "\e[?2004l" . &t_te function! XTermPasteBegin(ret) set pastetoggle=<Esc>[201~ set paste return a:ret endfunction map <expr> <Esc>[200~ XTermPasteBegin("i") imap <expr> <Esc>[200~ XTermPasteBegin("") cmap <Esc>[200~ <nop> cmap <Esc>[201~ <nop> endif
Очередная must-have вещь, которая встроена в NeoVim по умолчанию: поддержка bracketed paste вставки, про которую писал выше. Тут чётко видно, что когда Vim встречает escape-последовательность начинающую bracketed paste, то он переключает paste опцию.
----- ~/.vim/plugin/whereami.vim ----- function! s:pwdLoad() let g:mein_pwdL=trim(system("pwd -L")) let g:mein_pwdP=trim(system("pwd -P")) endfunction autocmd VimEnter * call s:pwdLoad() function! WhereAmI(fmt) let fullpath = expand("%:p") if fullpath[:len(g:mein_pwdP)-1] ==# g:mein_pwdP let fullpath = g:mein_pwdL . fullpath[len(g:mein_pwdP):] endif if a:fmt == "gnu" let where = fullpath . ":" . line(".") elseif a:fmt == "lldb" let where = "breakpoint set --file " . fullpath . " --line " . line(".") else let where = "unknown fmt" endif let @* = where echomsg where endfunction nmap <leader>w :call WhereAmI("gnu")<CR> nmap <leader>W :call WhereAmI("lldb")<CR>
\w команда показывает и копирует в X11 буфер обмена путь к редактируемому файлу и номер строки где находится курсор. Используется в первую очередь для работы с отладчиками. Нужно установить breakpoint вот на этом участке кода: \w, переходим в отладчик, Shift-Insert, Enter. Но формат у LLDB и GDB команд отличается, поэтому для LLDB используется \W.
Начало скрипта очень важно: при запуске Vim он запоминает значение «физической» и «логической» текущей директории. У меня есть проекты в которых символические ссылки библиотеки ведут во вне дерева исходных кодов (например на зашифрованный раздел). Go при сборке программы видит путь ~/work/go/src/XXX, а Vim при редактировании файла в этом XXX будет видеть его физический путь до /somewhere/outside/XXX. И я не нашёл способа получить «логический» путь до файла/директории средствами самого Vim. И засада в том, что при нажатии \w передам в отладчик физический путь, про который ни он, ни компилятор ничего не знают. В своём плагине проверяю отличаются ли физические и логические пути и делаю замену физической части пути на логическую. Выглядит грязно, но работает is good enough.
----- ~/.vim/plugin/grep.vim ----- function! s:Vim(pattern) let ignorecase_bak=&ignorecase set noignorecase execute "vimgrep /" . a:pattern . "/ **/*" copen let &ignorecase=ignorecase_bak let g:pylint_disable=1 endfunction command! -nargs=* -complete=file Vim call s:Vim(<q-args>)
В Vim есть встроенный поиск по файловой системе: vimgrep. Мне почти всегда нужно указать рекурсивный поиск по всему дереву, добавляя **/* путь. А также не игнорировать регистр. Писать каждый раз :vimgrep /whatever/ **/*, да ещё и с запоминанием ignorecase настройки — не вариант. Поэтому используется :Vim whatever команда.
----- ~/.vim/plugin/ggrep.vim ----- function! s:Vmg(pattern) silent execute 'Ggrep "' . a:pattern . '"' copen redraw! let g:pylint_disable=1 endfunction command! -nargs=* -complete=file Vmg call s:Vmg(<q-args>)
Позже познал скорость git grep и начал использовать :Ggrep команду из fugitive. Но мне не нравится что quickfix список с результатами поиска не открывается автоматом. Кроме того, нужно помнить про экранирование искомого запроса. Тут родилась :Vmg обёртка, схожая по названию с :Vim, чтобы меньше запоминать.
----- ~/bin/qq ----- #!/bin/sh tmp=`mktemp` tmux capture-pane -J tmux save-buffer $tmp tmux delete-buffer perl -ne '/^([^:]+:\d+:.*[^\s])\s*$/ and print "$1\n"' < $tmp > $tmp.err rm $tmp vim -c 'let g:pylint_disable=1' -c copen -q $tmp.err rm $tmp.err
Часто хочу открыть результат grep, запущенного из командной строки zsh, уже постфактум. Да, Vim поддерживает file:line формат файлов, но это если мне надо открыть один файл. qq скрипт сохраняет tmux экран, находит всё что похоже на file:line и запускает Vim, загружая в него quickfix список. Выглядит грязно, но вполне себе сносно работает! Ведь не всегда же я сижу в Vim и провожу поиск через :Vim и :Vmg.
----- ~/.vim/plugin/zshfe.vim ----- if !exists("g:zshfe_path") | let g:zshfe_path=expand("~/.vim/plugin/zshfe.zsh") | endif function! s:zshfe(query, opencmd) silent let result = systemlist(g:zshfe_path . " " . a:query) if len(result) == 0 | return | endif exec a:opencmd . " " . result[0][:-2] endfunction command! -nargs=1 Fe call s:zshfe(<f-args>, "edit") command! -nargs=1 Fsp call s:zshfe(<f-args>, "split") command! -nargs=1 Fvs call s:zshfe(<f-args>, "vsplit") nmap <Leader>e :Fe <- тут пробел в конце строки nmap <Leader><space> :Fsp <- тут пробел в конце строки nmap <Leader>v :Fvs <- тут пробел в конце строки
----- ~/.vim/plugin/zshfe.zsh ----- #!/usr/bin/env zsh set -e zmodload zsh/zpty zpty zshfe zsh zpty -w zshfe "print ZSHFE $*"$'\t EFHSZ\nexit\n' zpty -r zshfe zshfe "*exit" print -- $zshfe | sed -n "s/^ZSHFE \(.*\) EFHSZ/\1/p"
Я так привык к fuzzy-like дополнению путей в zsh (когда f/b/baz раскрывается в foo/2bar/somebaz), что хотел бы и в Vim задавать пути к открываемым файлам в этом же виде. Не могу описать как к этому пришёл, но лучше чем запускать zsh, внутри которого эмулировать pty терминал, отсылать нажатия клавиш (ввод пути) и ловить вывод, не нашёл.
\eПУТЬ выполняет :Fe ПУТЬ команду, которая откроет найденный ПУТЬ (zsh-like completed) в текущем окне. \v сделает это в вертикальном split, а \ПРОБЕЛ в горизонтальном.
~/.vim/ftplugin
Filetype-специфичные настройки, не требующие комментариев:
----- ~/.vim/ftdetect/conf.vim ----- autocmd BufRead,BufNewFile *.conf setlocal noexpandtab ----- ~/.vim/ftdetect/dtrace.vim ----- autocmd BufRead,BufNewFile *.d setlocal filetype=dtrace ----- ~/.vim/ftdetect/hjson.vim ----- autocmd BufNewFile,BufRead *.hjson setlocal shiftwidth=2 autocmd BufNewFile,BufRead *.hjson setlocal commentstring=#\ %s ----- ~/.vim/ftdetect/python.vim ----- autocmd BufRead,BufNewFile *.pyi setlocal filetype=python ----- ~/.vim/ftdetect/redo.vim ----- autocmd BufNewFile,BufRead *.do setlocal filetype=sh ----- ~/.vim/ftdetect/sql.vim ----- autocmd BufRead /tmp/psql.edit* setlocal filetype=sql ----- ~/.vim/ftplugin/gitcommit/autos.vim ----- setlocal spell spelllang=ru,en_gb setlocal textwidth=72 ----- ~/.vim/ftplugin/yaml/autos.vim ----- setlocal shiftwidth=2 ----- ~/.vim/ftplugin/texinfo/autos.vim ----- abbreviate \t @tab setlocal commentstring=@c\ %s ----- ~/.vim/ftplugin/python/ignores.vim ----- set wildignore+=**/_build/*,**/.hypothesis ----- ~/.vim/ftplugin/c/lsp.vim ----- au User lsp_setup call lsp#register_server({ \ "name": "clangd", \ "cmd": ["clangd"], \ "allowlist": ["c", "cpp", "objc", "objcpp"], \}) ----- ~/.vim/ftplugin/go/lsp.vim ----- au User lsp_setup call lsp#register_server({ \ "name": "gopls", \ "cmd": ["gopls"], \ "allowlist": ["go"], \}) ----- ~/.vim/ftplugin/python/lsp.vim ----- if executable("pyls") " pip install 'python-language-server[all]' au User lsp_setup call lsp#register_server({ \ "name": "pyls", \ "cmd": {server_info->["pyls"]}, \ "allowlist": ["python"], \}) endif
----- ~/.vim/ftdetect/mutt.vim ----- function! s:KillSignature() call cursor(1, 1) call search('^[>|] \?-- \?$') if getpos(".")[1] != 1 normal d} endif endfunction autocmd BufRead /tmp/mutt-* call s:KillSignature() autocmd BufRead /tmp/mutt-* setlocal textwidth=72
Во время написания ответа на письмо, эта функция вырезает подпись из процитированного письма. К сожалению, бывают неопытные пользователи, не знающие что подпись отделяется тремя символами — .
----- ~/.vim/ftdetect/tex.vim ----- autocmd BufNewFile,BufRead *.tex abbreviate framedo \begin{frame}<CR>\frametitle{TODO}<CR>\end{frame}<ESC>k>>wwlcw autocmd BufNewFile,BufRead *.tex abbreviate itemdo \begin{itemize}<CR>\item <CR>\end{itemize}<ESC>k>>$a autocmd BufNewFile,BufRead *.tex abbreviate cnter \begin{center}<CR>\end{center}<ESC>k$o
Ввод framedo, itemdo или cnter с последующим Ctrl-O отобразят на экране:
\begin{frame} \frametitle{КУРСОР} \end{frame} \begin{itemize} \item КУРСОР \end{itemize} \begin{center} КУРСОР \end{center}
где курсор сразу будет находятся в КУРСОР позиции в режиме ввода. Создал это из-за презентаций (в Beamer), где чаще всего применяется. Знаю что есть отдельные плагины для создания, так называемых, code snippet. Пробовал SnipMate (может быть что-то ещё), но не нашёл где бы мне не хватило штатного функционала.
----- ~/.vim/ftplugin/c/autos.vim ----- setlocal commentstring=//\ %s abbreviate UCC unsigned char abbreviate u8 uint8_t * abbreviate U8 (uint8_t *) let @e = "ywoassert(^[pA!= NULL);^[" setlocal equalprg=cfmt.sh command! -buffer Fmt normal mtgg=G'tz.
Вызов @e макроса копирует название переменной под курсором на следующую строку, оборачивая в assert(XXX != NULL); вызов. А :Fmt команда: запоминает текущую позицию, переходит в начало файла, применяет к нему команду выравнивания, возвращается назад, центрирует экран. Команда «выравнивания» тут применяется исключительно как способ прогона всего исходного кода через внешнюю утилиту, форматирующую код. Для Go применяется fmt.vim плагин, поставляемый, если не путаю, в Go дистрибутиве. Он тоже использует:Fmt (на самом деле это Си сделан по образу и подобию).
----- ~/.vim/ftplugin/go/motion.vim ----- nnoremap <silent> <buffer> ]] :call <SID>Go_jump('/^\(func\\|type\)')<cr> nnoremap <silent> <buffer> [[ :call <SID>Go_jump('?^\(func\\|type\)')<cr> nnoremap <silent> <buffer> ]m :call <SID>Go_jump('/^\s*\(func\\|type\)')<cr> nnoremap <silent> <buffer> [m :call <SID>Go_jump('?^\s*\(func\\|type\)')<cr> fun! <SID>Go_jump(motion) range let cnt = v:count1 let save = @/ " save last search pattern mark ' while cnt > 0 silent! exe a:motion let cnt = cnt - 1 endwhile call histdel('/', -1) let @/ = save " restore last search pattern endfun
Насколько помню, всё это было взято из Python filetype файла из дистрибутива Vim и переделано для Go. Команды [[/]] в vi позволяли перемещаться между функциями (поэтому в Си коде важно чтобы открывающая фигурная скобка начала функции располагалась в начале строки), а [m/]m между методами. Здесь [[/]] используются, аналогично, для прыжков между функциями и объявлением типов. А [m/]m разрешают иметь indentation, позволяя прыгать на анонимные функции, объявляемые внутри других функций.
----- ~/.vim/ftplugin/go/autos.vim ----- set noexpandtab let g:defsplit_shift="ТАБУЛЯЦИЯ" let @e = "^iif err = ^[A; err != nil {^[oТАБУЛЯЦИЯ" let @r = "oif err != nil {^M}^[OТАБУЛЯЦИЯ"
@e макрос при применении на foo(bar) строчке сделает:
if err = foo(bar); err != nil { КУРСОР
А @r добавит на следующей строке:
if err != nil { КУРСОР }
----- ~/.vim/ftplugin/go/gogetdoc.vim ----- function! LintStatus() if exists("b:gogetdoc_job") && job_status(b:gogetdoc_job) == "run" | return "GD" | endif return "" endfunction function! GoGetDocGot(ch) let msgs = [] while ch_status(a:ch) == "buffered" let msgs = add(msgs, ch_read(a:ch)) endwhile if exists("b:godocid") | call popup_close(b:godocid) | endif if len(msgs) == 0 echohl WarningMsg | echomsg "No go doc" | echohl None return endif let msgs = msgs[2:] let b:godocid = popup_atcursor(msgs[2:-2], {"wrap": 0, "title": msgs[0], "move": "word"}) endfunction function! s:GoGetDoc() if exists("b:gogetdoc_job") && job_status(b:gogetdoc_job) == "run" return endif let pos = line2byte(line(".")) + col(".") - 2 let cmdline = "gogetdoc -pos " . expand("%p") . ":#" . pos echomsg cmdline let b:gogetdoc_job = job_start(cmdline, { \"in_mode": "nl", \"err_io": "null", \"close_cb": "GoGetDocGot", \}) endfunction nmap <buffer> <silent> <CR> :call <SID>GoGetDoc()<CR>
Изначально это был просто proof-of-concept работы popup окон, но в итоге стало функцией показа документации (используя внешнюю gogetdoc утилиту) по слову под курсором. Просто нажимаем на нём Enter и асинхронная задача в фоне будет получать документацию, отобразив в popup окне.
----- ~/.vim/ftplugin/python/autos.vim ----- iabbrev #u # coding: utf-8 iabbrev tt # type: iabbrev tti # type: ignore iabbrev trace import pdb ; pdb.set_trace()<CR>pass iabbrev embed import code ; code.interact(local=locals()) iabbrev kargs *args, **kwargs iabbrev pyldis # pylint: disable= iabbrev deff def () -> None:<ESC>F(i iabbrev """ """<ESC>o"<ESC>2i"<ESC>kA nmap <leader>ss :set lazyredraw<CR>vip:sort u<CR>:'<,'>sort i<CR>:set nolazyredraw<CR> let @b = ">gvctry:^[<<oexcept Exception as err:^Mimport pdb ; pdb.set_trace()^Mpass^[>>k>>kP" let @n = "ddV/except.*:^M<n3dd"
- Большая часть аббревиатур и так ясна. deff с последующим Ctrl-O создаёт: def (КУРСОР) -> None:. Ввод тройных открывающих docstring кавычек сразу создаёт и закрывающие.
- \ss используется для сортировки абзаца с import-ами и удаления дублей. Почему нельзя делать :sort ui? Потому что бывают случаи, когда игнорирование регистра приведёт к схлопыванию, ставших «одинаковыми», import-ов. А lazyredraw исключительно для производительности.
- @b и @n макросы — самое ценное что есть у меня для Python. Визуально выделяем какой-то участок кода (например пару строчек с foo и bar), применяем @b и получаем:
try: foo bar except Exception as err: import pdb ; pdb.set_trace() pass
И это всё будет работать независимо от начального indentation. Применяем @n на try: и вся эта обёртка убирается назад. Колоссальнейшая экономия времени во время отладки тестов и кода. И всё без vimscript.
----- ~/.vim/ftplugin/python/importcompl.vim ----- let s:git_grep_cmd = "git grep -H --line-number --ignore-case --no-color " function! SortByLen(s1, s2) if len(a:s1) == len(a:s2) | return a:s1 > a:s2 | endif return 1 ? len(a:s1) > len(a:s2) : -1 endfunction function! ImportCompl() normal diw let output = system(s:git_grep_cmd . '"^from .* import .*' . @" . '" -- "*.py" "**/*.py"') let suggestions = [] for line in split(output, "\n") if stridx(line, "unused-import") != -1 | continue | endif let m = matchlist(line, '^.*:\d\+:\(.*\)$') if len(m) == 0 | continue | endif call insert(suggestions, m[1]) endfor call sort(suggestions, "SortByLen") call uniq(suggestions) call reverse(suggestions) call complete(col('.'), suggestions) return '' endfunction inoremap <F3> <C-R>=ImportCompl()<CR> function! AllImportCompl() let output = system(s:git_grep_cmd . '"^from .* import" -- "*.py" "**/*.py"') let imports = {} for line in split(output, "\n") if stridx(line, "unused-import") != -1 | continue | endif for regexp in [ \'^.*:\d\+:\(from .* import \(\w\+\).*\)$', \'^.*:\d\+:\(from .* import \w\+ as \(\w\+\).*\)$', \] let m = matchlist(line, regexp) if len(m) == 0 | break | endif let imports[m[2]] = m[1] endfor endfor let lines = getloclist(winnr()) if len(lines) == 0 | let lines = getqflist() | endif let result = [] for line in lines let m = matchlist(line.text, '\(E0602\|F821\).*' . "'" . '\(\w\+\)' . "'$") if len(m) == 0 || !has_key(imports, m[2]) | continue | endif call insert(result, imports[m[2]]) endfor call sort(result, "i") call uniq(result) call append(".", result) endfunction
Код из серии «хак на хаке». Но при этом вполне работающий в преобладающем большинстве случаев. Занимается автоматической вставкой import-ов. Повторюсь: экосистема Python меня удручает и, на момент написания, не встретил удовлетворивших меня инструментов (тогда как в Go вон всё прекрасно).
Например я набрал слово Certificate и нажал на нём F3. Будет запущен git grep с поиском import-ов связанных с Certificate по всему проекту и сортировкой по самой длинной строке. Будет предложено меню с найденными предложениями. Конечно это не сработает когда проект совсем голый и import ещё ни разу не был написан. А ещё это будет работать только с «каноничными» import-ами (уже писал о них для pyimportcan.pl скрипта).
Позже был рождён ещё больший хак в виде :AllImportCompl функции. Предварительно нужно запустить Pylint или Pyflakes linter-ы, загрузив результат их работы в quickfix список (это автоматом делает мой pylint.vim). Функция найдёт в этом списке ошибок все предупреждения об отсутствующих import-ах и для каждого из них она запустит аналог F3-функции. Дальше останется только применить сортировку и разбиение по группам. До сих пор удивительно для меня, но этот огромный хак отрабатывает очень и очень хорошо: с ростом проекта покрывая (автоматически успешно добавляя) преобладающее большинство всех import-ов.
Примеры кода тут привожу чтобы продемонстрировать что в vimscript нет ничего сложного и страшного. Это штука которую может использовать обычный пользователь, а не hardcore разработчик.
----- ~/.vim/ftplugin/python/unused_remover.vim ----- function! UnusedImportsRemover() call setqflist(filter(getqflist(), "stridx(v:val.text, \"unused-import\") != -1 || stridx(v:val.text, \"imported but unused\") != -1")) cdo d endfunction
Это дополнение к хаку :AllImportCompl — функция удаляющая неиспользуемые import-ы (тоже на основе quickfix данных linter-а).
----- ~/.vim/ftplugin/python/testname.vim ----- function! TestName() normal mm normal ?.*\s*def .*[Tt]est^M normal ^f(Byw let postfix = @" normal [[f(Byw let postfix = @" . "." . postfix normal `m let base = join([""] + split(getcwd(), "/")[:-1], "/") let prefix = substitute(expand("%:p:r")[len(base)+1:], "/", ".", "g") let name = prefix . ":" . postfix let @* = name echomsg name endfunction nmap <leader>t :call TestName()<CR>
Функция создания Python-пути до теста в котором мы сейчас находимся. На удивление не маленькая, так как у нас только текст, а не AST дерево кода. Находясь в теле теста, нажимаем \t и в буфер обмена будет скопирована строка типа foo.bar.tests:TestBaz.test_whatever. Часто использую когда нужно быстренько запустить только этот метод: что происходит постоянно при написании новых тестов.
Вот и всё! Надеюсь что кто-нибудь да почерпнёт что-нибудь полезное для себя!