Медленно, но верно, редактор acme становится моим основным редактором-средой в Linux. Почему это происходит, вопрос отдельный и он не сводится к утилитарности. Проще, удобнее, быстрее -- это всё категории, которые в большей степени определяются нашими привычками. А мной в IT движет любопытство и тяга к простоте.
Идея acme очень простая, но при этом мощная. Это не редактор, это прослойка между Unix средой и человеком. Когда вы работаете с acme, "редактором" становится вся ОС. В начале это очень непривычно, но потом -- затягивает.
До последнего момента в качестве основного редактора я пользовался emacs и пользовался им как средой. То-есть, кроме собственно редактирования файлов я читал в нём почту (mu4e), общался в телеграм (telega.el), читал pdf ну и так далее...
Отвыкнуть от emacs очень сложно, а мне было интересно проводить в acme больше времени, поэтому я решил попробовать перенести в него работу с почтой.
Вероятно, можно было бы завести upas, который есть в составе plan9port, но мне этот вариант не очень подходит. Потому что я параноик. Так как почтовые серверы мне не принадлежат у меня есть непреодолимое желание хранить копию своих почтовых ящиков локально на диске. Кроме того, это даёт возможность быстро искать нужное письмо. Поэтому я пошёл другим путём.
Для синхронизации почты между 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
Mu -- это почти полноценный клиент, по крайней мере, для чтения почты. Практически всё можно сделать из командной строки. Например, вывести последние сообщения:
$ mu find --sortfield d --reverse "" | head -n10
Чтобы просмотреть сообщение, вы должны указать путь к конкретному файлу в Maildir. Например:
mu view `mu find "" --sortfield d --reverse -f l | head -n1`
Конечно, пользоваться в таком виде почтой малореально, но возможностей для скриптования -- масса. И когда я это понял, то решил написать свой фронтенд к mu для 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. Всё, что она делала -- показывала последние 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 было бы ещё проще, но мне хотелось сделать всё "здесь и сейчас"...
То, что в итоге получилось, можно посмотреть тут:
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 тот же эффект, что получаю от Plan9. У меня разгружается сознание. Когда я работаю в emacs, мой мозг перегружен информацией о множестве клавиатурных комбинаций и конкретных рецептах. Если брать другие редакторы, то в целом, ситуация повторяется. Acme в этом плане не похож ни на один из них. Его "базис" очень простой, единообразный и при этом всё ещё мощный. Настоящий Unix инструмент!
Когда я первый раз увидел acme, я не мог даже себе представить, что в _этом_ можно работать. Что же, ещё один повод напомнить себе, что встречать по одёжке не стоит.
Кстати, об одёжке. Уверен, у 99% людей пропадает интерес к acme как только они узнают, что в нём нет подстветки синтаксиса. Или, что в этом редакторе нельзя перемещаться по строкам вверх-вниз с помощью клавиатуры... Я зря это сейчас сказал, да? :)