Редактор acme: пишем почтовый клиент

[std.hugeping] Wed Oct 13 16:46:59 UTC 2021 @hugeping -> All

Медленно, но верно, редактор 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

copyleft 2021 difrex at lessmore dot pw; source code