Медленно, но верно, редактор acme становится моим основным редактором-средой в Linux. Почему это происходит, вопрос отдельный и он не сводится к утилитарности. Проще, удобнее, быстрее -- это всё категории, которые в большей степени определяются нашими привычками. А мной в IT движет любопытство и тяга к простоте.
Идея acme очень простая, но при этом мощная. Это не редактор, это прослойка между Unix средой и человеком. Когда вы работаете с acme, "редактором" становится вся ОС. В начале это очень непривычно, но потом -- затягивает.
До последнего момента в качестве основного редактора я пользовался emacs и пользовался им как средой. То-есть, кроме собственно редактирования файлов я читал в нём почту (mu4e), общался в телеграм (telega.el), читал pdf ну и так далее...
Отвыкнуть от emacs очень сложно, а мне было интересно проводить в acme больше времени, поэтому я решил попробовать перенести в него работу с почтой.
Вероятно, можно было бы завести upas, который есть в составе plan9port, но мне этот вариант не очень подходит. Потому что я параноик. Так как почтовые серверы мне не принадлежат у меня есть непреодолимое желание хранить копию своих почтовых ящиков локально на диске. Кроме того, это даёт возможность быстро искать нужное письмо. Поэтому я пошёл другим путём.
# Синхронизация почты mbsync
Для синхронизации почты между Maildir на диске и imap на сервере нашлась отличная штука: isync (или mbsync). Замечательна она тем, что синхронизация работает в обе стороны. То-есть, удаляя письмо в Maildir вы тем самым удаляете его в imap mailbox. Ну и так далее. Таким образом, вы получаете единый срез почты на многих машинах и всё это прекрасным образом синхронизируется через imap.
Конфигурация выглядит примерно так:
IMAPAccount gmail Host imap.gmail.com User user@gmail.com Pass password SSLType IMAPS CertificateFile /etc/ssl/certs/ca-certificates.crt IMAPStore gmail-remote UseUTF8Mailboxes yes Account gmail MaildirStore gmail-local SubFolders Verbatim Path ~/Mail/gmail/ Inbox ~/Mail/gmail/Inbox Channel gmail Far :gmail-remote: Near :gmail-local: Patterns * ![Gmail]* "[Gmail]/Sent Mail" "[Gmail]/9front" Create Both Expunge Both SyncState *
Правда, mbsync из апстрима создаёт каталоги на диске в кодировке UTF-7. Но в aur есть пакет с поддержкой UTF-8. Обратите внимание на UseUTF8Mailboxes в конфиге. Для других Linux можно собрать версию отсюда: https://sourceforge.net/u/shashurup/isync/ci/utf8-mailboxes/tree/
После того, как всё настроили, можно поставить задачу на таймер в systemd или cron и всё.
# Индексация
Не так давно я открыл для себя mu. Mu позволяет индексировать почту в Maildir и дальше делать выборку, показ писем, распаковку аттачей и так далее. И всё это очень быстро. Вместе с mu идёт почтовый клиент для emacs -- mu4e.
Для создания базы просто делаем что-то вроде:
$ mu init --my-address='ваш емейл'
И потом периодически делаем индексацию:
$ mu index
# Идея почтового клиента на acme
Mu -- это почти полноценный клиент, по крайней мере, для чтения почты. Практически всё можно сделать из командной строки. Например, вывести последние сообщения:
$ mu find --sortfield d --reverse "" | head -n10
Чтобы просмотреть сообщение, вы должны указать путь к конкретному файлу в Maildir. Например:
mu view `mu find "" --sortfield d --reverse -f l | head -n1`
Конечно, пользоваться в таком виде почтой малореально, но возможностей для скриптования -- масса. И когда я это понял, то решил написать свой фронтенд к mu для acme.
# Приложение на acme
Acme с помощью файловой системы предоставляет доступ к некоторым функциям по работе с своими окнами, которых оказывается достаточно для написания "приложений". Если вы работаете в Plan9, то файловая система доступна всегда. Если же вы запускаете acme в рамках plan9port, то для доступа к ней можно:
1. Использовать утилиту 9p
2. Подмонтировать ФС через fuse: 9pfuse
Допустим, у нас есть скрипт hello, который доступен по PATH. Если запустить acme и вписать hello в заголовок главного окна (там где Newcol Kill Putall Dump..), а потом нажать на hello 2-й кнопкой мыши, то скрипт запустится.
Когда скрипт запускается в рамках acme, то переменная окружения winid содержит номер текущего окна или 0, если нет никаких открытых окон, кроме главного.
Для работы с файловой системы acme пока будем пользоваться 9p.
#!/bin/sh 9p ls acme 9p read acme acme/$winid/tag
Запустите этот вариант скрипта и увидите в отдельном окне корень ФС acme и список пунктов меню вашего текущего активного окна (если такое есть).
То же самое можно сделать с fuse:
#!/bin/sh mnt=`mktemp -d /tmp/acmeXXXX` 9pfuse `namespace`/acme $mnt ls $mnt cat $mnt/$winid/tag fusermount -u "$mnt" && rmdir "$mnt"
На самом деле, в теории, fuse вариант удобнее и быстрее, но я столкнулся с проблемой. Мой ноутбук с ArchLinux не хотел уходить в сон, пока есть хоть одна подмонтированная точка fuse. Так что приходится всё время монтировать и размонтировать, что не очень удобно.
В man 4 acme (из plan9port) описана файловая система acme. Я не буду здесь пересказывать эту информацию, но отмечу только некоторые моменты с которыми столкнулся.
1. По идее, мы можем считать позицию селектора (курсор + выделение) с помощью записи в ctl строки addr=dot и и последующего чтения из addr. Однако, при открытии addr он каждый раз ресетится. Таким образом, с помощью 9p вы не сможете получить текущую позицию курсора, так как это две операции: запись addr=dot и чтение addr. А нужно, открыть (и не закрывать addr), потом записать addr=dot, потом прочитать addr. Это можно проделать при использовании 9pfuse. Например:
pos=`{ echo 'addr=dot' >> $mnt/$winid/ctl; cat; }<$mnt/$winid/addr`
2. Если добавлять в окно текст (в data), содержащий в себе переводы строк, то просто так читать из event построчно не получится. Потому что первая строка будет содержать событие, а следующая -- уже просто кусок текста и так далее. Да, число символов текстового блока тоже при этом приходит, но писать обработку такого протокола на shell неудобно. Поэтому я добавлял текст только построчно.
# Версия на shell
Первую черновую версию я написал на shell. Всё, что она делала -- показывала последние 100 писем и реагировала на нажатие средней кнопки мыши на путь к письму (при этом, нужно было сначала этот путь выделить). Она была неудобна, неполна. Но очень проста. Поэтому, в качестве иллюстраций я привожу этот вариант целиком:
#!/bin/sh mail_view() { mu view "$MAILDIR/$2" --nocolor | 9p write acme/$1/data echo -n 'clean' | 9p write acme/$1/ctl toline $1 0 } mail_ls() { mu find --nocolor -s d --reverse -f "l|d|f|s" "" | \ /usr/bin/sed -e 's|'$MAILDIR'/\([^ ]\+\)|\1|g' | \ head -n 100 | 9p write acme/$1/data echo -n 'clean' | 9p write acme/$1/ctl toline $1 0 } toline() { echo -n "$2" | 9p write acme/$1/addr echo -n 'dot=addr' | 9p write acme/$1/ctl echo -n 'show' | 9p write acme/$1/ctl } if [ -z "$winid" ]; then exit 1 fi # создаём новое окно winid=`9p read acme/new/ctl | awk '{ print $1 }'` # показываем 100 сообщений mail_ls $winid # добавляем "кнопку" Get echo -n "Get" | 9p write acme/$winid/tag # цикл обработки событий 9p read acme/$winid/event | while read a b c d e; do if echo "$a" | grep -q -e '^Mx' 2>/dev/null; then # mx if [ "$e" = "Get" ]; then mail_ls $winid continue fi elif echo "$a" | grep -q -e '^ML' 2>/dev/null; then if [ -f "$MAILDIR/$e" ]; then msgid=`9p read acme/new/ctl | awk '{ print $1 }'` mail_view $msgid $e continue fi fi echo "$a $b" | 9p write acme/$winid/event 2>/dev/null done
Кстати, этот текст был вставлен в статью так:
- ввел в tagline <cat email;
- выделил этот текст;
- нажал 2ю кнопку мыши.
Обратите внимание на цикл обработки событий. Мы просто ждём их, читая из event и реагируем нужным образом. Или снова перечитываем сообщения, или открываем текст нужного сообщения в новом окне.
Не смотря на некоторые проблемы, о которых я узнал позже, скрипт как-то работал. Однако я быстро понял, что сложную логику писать на shell не очень весело, поэтому я перешёл на Lua. Вероятно, на go было бы ещё проще, но мне хотелось сделать всё "здесь и сейчас"...
# Версия на Lua
То, что в итоге получилось, можно посмотреть тут: https://github.com/gl00my/plan9hacks/blob/master/linux/mu-query
Скрипт довольно грязный, но он работает и делает именно то, что мне нужно. Я не буду подробно описывать что именно в нём происходит, отмечу только некоторые моменты.
1. Для отправки почты используется msmtp.
defaults auth on tls on logfile ~/.msmtp.log account gmail host smtp.gmail.com port 587 tls_certcheck off from user@gmail.com user user@gmail.com password password account default : gmail
2. mu-query умеет показывать запросы mu. Например, вводим в tagline: mu-query from:ivanov, выделяем, нажимаем 2-ю кнопку мыши и видим нужный результат. Если не указывать запрос, показываются последние 100 сообщений. Можно листать по 100 сообщений командой Page.
3. При просмотре сообщения все аттачи распаковываются во временную директорию. Например, если я вижу, что это html письмо, я могу дописать firefox file:/// перед путём к файлу прямо в теле сообщения и нажать.
4. На данный момент поддерживается только Reply, составление нового сообщения, удаление и пометка сообщений как Seen. Forward и добавлений аттачей нет. Это несложно добавить, но пока я не стал этого делать. Может быть, будет третья итерация уже на go. :)
5. Чтобы удалить пачку сообщений или пометить их как прочитанные нужно выделить все нужные сообщения и нажать на соответствующую "кнопку".
На данный момент я пользуюсь mu-query на постоянной основе и очень доволен. Мне действительно это удобно. И главное, я понял что относительно просто могу писать под себя интерфейсы. Например, я уже задумываюсь о команде git-log.
# Опыт acme
Если говорить о практической пользе, то кроме "персонализации" инструментария я получаю с acme тот же эффект, что получаю от Plan9. У меня разгружается сознание. Когда я работаю в emacs, мой мозг перегружен информацией о множестве клавиатурных комбинаций и конкретных рецептах. Если брать другие редакторы, то в целом, ситуация повторяется. Acme в этом плане не похож ни на один из них. Его "базис" очень простой, единообразный и при этом всё ещё мощный. Настоящий Unix инструмент!
Когда я первый раз увидел acme, я не мог даже себе представить, что в _этом_ можно работать. Что же, ещё один повод напомнить себе, что встречать по одёжке не стоит.
Кстати, об одёжке. Уверен, у 99% людей пропадает интерес к acme как только они узнают, что в нём нет подстветки синтаксиса. Или, что в этом редакторе нельзя перемещаться по строкам вверх-вниз с помощью клавиатуры... Я зря это сейчас сказал, да? :)