diff options
author | mms <michal@sapka.me> | 2023-12-24 22:57:00 +0100 |
---|---|---|
committer | mms <michal@sapka.me> | 2023-12-24 22:57:00 +0100 |
commit | 10bf749a75bb711d960dcce7e8e40078c9ce5eb3 (patch) | |
tree | 25edc39ce745dbb932e84429c43abbad36b06c76 /content/emacs | |
parent | fdb420bd62a4ffe3fdc2ff4e3f5932fcddebc32b (diff) |
feat: emacs as a section
Diffstat (limited to 'content/emacs')
-rw-r--r-- | content/emacs/elfeed-literate-config.md | 77 | ||||
-rw-r--r-- | content/emacs/emacs-as-a-shell.md | 44 | ||||
-rw-r--r-- | content/emacs/home.md | 28 | ||||
-rw-r--r-- | content/emacs/input-completition-in-emacs.md | 151 | ||||
-rw-r--r-- | content/emacs/moving-my-rss-reading-to-emacs-with-elfeed.md | 82 | ||||
-rw-r--r-- | content/emacs/notmuch.md | 355 |
6 files changed, 464 insertions, 273 deletions
diff --git a/content/emacs/elfeed-literate-config.md b/content/emacs/elfeed-literate-config.md index 4876c6e..a8b5b7a 100644 --- a/content/emacs/elfeed-literate-config.md +++ b/content/emacs/elfeed-literate-config.md @@ -1,54 +1,66 @@ ---- -title: "Literate configuration of Elfeed" -category: emacs -abstract: Setting up config inside an org file -date: 2023-06-02T22:26:24+02:00 -year: 2023 -draft: false -tags: -- elfeed -- org-mode -- literate-programming ---- -Recently I've been toying with a literate configuration[^literate] of Emacs. My init.el took a straightforward form: -[^literate]: https://en.wikipedia.org/wiki/Literate_programming - -{{<highlight lisp "linenos=table">}} ++++ +title = "Literate configuration of Elfeed" +author = ["Michał Sapka"] +date = 2023-06-02T23:00:00+02:00 +lastmod = 2023-12-24T22:56:11+01:00 +categories = ["emacs"] +draft = false +weight = 2003 +abstract = "Setting up config inside an org file" +[menu] + [menu.emacs-guides] + weight = 2003 + identifier = "literate-configuration-of-elfeed" ++++ + +Recently I've been toying with a literate configuration[^fn:1] of Emacs. +My init.el took a straightforward form: + +```emacs-lisp (org-babel-load-file "~/.emacs.d/config.org") -{{</highlight>}} +``` -And all of my configuration lives in an org-file. This alone gives me very little benefit for now, but in the future, it will make it easier to have it as a dedicated page on this site. +And all of my configuration lives in an org-file. +This alone gives me very little benefit for now, but in the future, it will make it easier to have it as a dedicated page on this site. -However, one thing completely surprised me: the elfeed configuration. Until now, I used the standard way of configuring feeds with Elisp. It works, but what it is not is readable. A list of: +However, one thing completely surprised me: the elfeed configuration. +Until now, I used the standard way of configuring feeds with Elisp. +It works, but what it is not is readable. +A list of: -{{<highlight lisp "linenos=true,linenostart=199">}} +```emacs-lisp ("https://gideonwolfe.com/index.xml" blog imporant) ("https://fabiensanglard.net/rss.xml" blog important) ("https://protesilaos.com/master.xml" emacs) -{{</highlight>}} +``` is far from being manageable and requires constant manual labor. -### Elfeed-org -One of the packages expanding capabilities of elfeed is elfeed-org[^elf-org]. It allows configuring the list of feeds with a standard org tree. Since my config is now also an org file, nothing stops me from adding the list as an org-tree inside my config org-file! I set it up via: -[^elf-org]: https://github.com/remyhonig/elfeed-org +## Elfeed-org {#elfeed-org} -{{<highlight lisp "linenos=table,linenostart=199,hl_lines=7">}} +One of the packages expanding capabilities of elfeed is elfeed-org[^fn:2]. +It allows configuring the list of feeds with a standard org tree. +Since my config is now also an org file, nothing stops me from adding the list as an org-tree inside my config org-file! I set it up via: + +{{<highlight lisp "linenos=table,linenostart=199,hl_lines=7">}} + +```org *** elfeed-org #+BEGIN_SRC emacs-lisp - (use-package elfeed-org +(use-package elfeed-org :ensure t :config (setq rmh-elfeed-org-files (list "~/.emacs.d/config.org")) (elfeed-org)) #+END_SRC -{{</highlight>}} +``` -Therefore, I am now pointing at the same file to become the data source for elfeed-org as the rest of my config. Just a few lines down, I start to define my list of subscriptions: +Therefore, I am now pointing at the same file to become the data source for elfeed-org as the rest of my config. +Just a few lines down, I start to define my list of subscriptions: -{{<highlight org "linenos=table,linenostart=207">}} +```org *** Feeds :elfeed: **** Blogs @@ -59,6 +71,9 @@ Therefore, I am now pointing at the same file to become the data source for elfe **** Emacs ***** https://protesilaos.com/master.xml :important: -{{</highlight>}} +``` + +Much more readable! Elfeed-org will ignore the entire outer tree and extract the feeds from leaves under the \`:elfeed:\` tag. -Much more readable! Elfeed-org will ignore the entire outer tree and extract the feeds from leaves under the `:elfeed:` tag. +[^fn:1]: : <https://en.wikipedia.org/wiki/Literate_programming> +[^fn:2]: : <https://github.com/remyhonig/elfeed-org>
\ No newline at end of file diff --git a/content/emacs/emacs-as-a-shell.md b/content/emacs/emacs-as-a-shell.md index 2376613..d7bd651 100644 --- a/content/emacs/emacs-as-a-shell.md +++ b/content/emacs/emacs-as-a-shell.md @@ -1,30 +1,32 @@ ---- -title: "Emacs as a Shell" -category: emacs -abstract: My current understanding of Emacs -date: 2023-04-13T23:38:38+02:00 -year: 2023 -draft: false -tags: -- emacs -- shell -- unix ---- -Pavel Korytov writes in his [recent post](https://sqrtminusone.xyz/posts/2023-04-13-emacs/): ++++ +title = "Emacs as a Shell" +author = ["Michał Sapka"] +date = 2023-04-13T23:00:00+02:00 +lastmod = 2023-12-24T22:56:14+01:00 +categories = ["emacs"] +draft = false +weight = 2001 +abstract = "My current understanding of Emcs" +[menu] + [menu.emacs-varia] + weight = 2001 + identifier = "emacs-as-a-shell" ++++ + +Pavel Korytov writes in his [recent post](https://sqrtminusone.xyz/posts/2023-04-13-emacs/); > So over time, Emacs has become my programming environment, email client, window manager, knowledge base, and a lot more. I think I ended up using Emacs for almost as many things as possible; -This is where I want to be in the near future. So far I've moved my development environment and email to Emacs. Next up are notes, RSS reading, and music listening. +This is where I want to be in the near future. So far I've moved my development environment and email to Emacs. Next up are notes, RSS reading, and music listening. -What I love about Emacs is the consistency between modes/packages. They accomplish widely different things, but the general control scheme is the same. It's great since all TUI programs I use tend to support Vim's way of doing things. Having it all inside Emacs changes the dynamic. I'm trying to think of Emacs as a shell rather than an editor. +What I love about Emacs is the consistency between modes/packages. They accomplish widely different things, but the general control scheme is the same. It's great since all TUI programs I use tend to support Vim's way of doing things. Having it all inside Emacs changes the dynamic. I'm trying to think of Emacs as a shell rather than an editor. What Emacs really is, is a virtual machine running LISP code. Some say that Emacs violates Linux philosophy. I don't see it this way. Does shell violate it? It's also a way to run different programs. Emacs is an abstraction over real shell which adds some calm to it. It's a way to have an interactive layer over OS... which also does text editing. So, when you look at it this way, Emacs makes a lot of sense: -- It runs programs. Bigger packages, like Magit, are nothing short of real programs. -- It's scriptable. Elisp all the way! -- It allows for interoperability between programs. -- It runs above basic OS. You can replace your window manager with Emacs, but you need some sort of kernel. -- You can live entirely inside Emacs, just like you can live entirely inside a terminal. - +- It runs programs. Bigger packages, like Magit, are nothing short of real programs. +- It's scriptable. Elisp all the way! +- It allows for interoperability between programs. +- It runs above basic OS. You can replace your window manager with Emacs, but you need some sort of kernel. +- You can live entirely inside Emacs, just like you can live entirely inside a terminal. diff --git a/content/emacs/home.md b/content/emacs/home.md new file mode 100644 index 0000000..f863ff2 --- /dev/null +++ b/content/emacs/home.md @@ -0,0 +1,28 @@ ++++ +title = "C-X Emacs" +author = ["Michał Sapka"] +date = 2023-12-24T20:00:00+01:00 +lastmod = 2023-12-24T22:56:09+01:00 +categories = ["emacs"] +draft = false +weight = 1001 +hardback = true ++++ + +I am new to Emacs (a little over a year, with a break), so I am in outsider's position. +Most users of Emacs I've met have been using this editor for decades, and since it's not that popular, there are not many noobs out there. +This website is a recording of my findings, as Emacs is a completely different best compared to any other editor I've ever used. + + +## Guides {#guides} + +{{< menu "emacs-guides" >}} +Guides +{{< /menu >}} + + +## Varia {#varia} + +{{< menu "emacs-varia" >}} +Varia +{{< /menu >}} diff --git a/content/emacs/input-completition-in-emacs.md b/content/emacs/input-completition-in-emacs.md index 11e86c1..04f610f 100644 --- a/content/emacs/input-completition-in-emacs.md +++ b/content/emacs/input-completition-in-emacs.md @@ -1,91 +1,118 @@ ---- -title: "Input Completion in Emacs" -category: "emacs" -abstract: Icomplete, IDO and FIDO -date: 2023-05-26T21:10:08+02:00 -year: 2023 -draft: false -tags: -- IComplete -- IDO-mode -- FIDO-mode -- Emacs ---- -Emacs consists of a massive set of tools with a long history. Therefore, whatever the problem is, someone likely has already created a package for it. In many cases, this package is already baked in Emacs. This is great for veterans but very confusing to newcomers - like me. - -## The three modes - -Emacs comes with three modes for input completion: Icomplete, IDO, and FIDO. Input completion works with whatever you select in the Minibuffer section. For text competition, you must use a different solution, like Company mode[^company]. -[^company]: [Company-mode website](https://company-mode.github.io/) - -The oldest one of those is *icomplete*[^icomplete] mode. It allows you to select from a list of choices incrementally[^hist], so you need to type the beginning, and icomplete will narrow the list of propositions. For example, when searching for "magit", the user needs to type "m" first as omitting it and starting with "i" will instead narrow to options beginning with "i". Similarly, "mgt" will not limit to "magit" as we're missing"a". Newer Emacs versions allowallow us to use "flex" matching with icomplete, but more on this later. Incomplete work for all lists using a mini buffer, like filenames or imenu. -[^hist]: According to a [Reddit comment](https://www.reddit.com/r/emacs/comments/13szol7/comment/jltmaud/?utm_source=reddit&utm_medium=web2x&context=3) to this article, this behavior is relatively new. Until recently, Icompelete only showed the narrowed selection in a dedicated buffer, similar to using `Tab-Tab` now. The actual input was still up to the user to type in. Thanks for the tidbit! -[^icomplete]: [Icomplete documentation](https://www.gnu.org/software/emacs/manual/html_node/emacs/Icomplete.html) - -Then *IDO*[^ido] mode (Interactively Do) came in. It uses the aforementioned "flex" matching, where you can search for any part of the word. "Magit" would be found with "agit", "mgt" or just "git". IDO, however, works only with files and buffers, so `M-x` would still fall back to icomplete. -[^ido]: [IDO documentation](https://www.gnu.org/software/emacs/manual/html_mono/ido.html) - -Starting with Emacs 27, we've got *fido* mode (Fake Ido). It combines the best things about icomplete (works anywhere) with flex matching. In fact, all Fido does under the hood is to enable Icomplete with flex matching. - -There are also other solutions for competition, not baked into emacs, with the most popular being Helm[^helm] and Ivy[^ivy]. Personally, I always try the default option, and only if their limits become annoying do I look at the alternatives. So, I am now a happy FIDO mode user. -[^helm]: [Helm website](https://emacs-helm.github.io/helm/) -[^ivy]: [Ivy website](https://oremacs.com/swiper/) - -### Using FIDO ++++ +title = "Input Completiton in Emacs" +author = ["Michał Sapka"] +date = 2023-05-26T23:00:00+02:00 +lastmod = 2023-12-24T22:56:09+01:00 +categories = ["emacs"] +draft = false +weight = 2001 +abstract = "Icomplete, IDO and FIDO" +[menu] + [menu.emacs-guides] + weight = 2001 + identifier = "input-completiton-in-emacs" ++++ + +Emacs consists of a massive set of tools with a long history. +Therefore, whatever the problem is, someone likely has already created a package for it. +In many cases, this package is already baked in Emacs. +This is great for veterans but very confusing to newcomers - like me. + + +## The three modes {#the-three-modes} + +Emacs comes with three modes for input completion: Icomplete, IDO, and FIDO. +Input completion works with whatever you select in the Minibuffer section. +For text competition, you must use a different solution, like Company mode[^fn:1]. + +The oldest one of those is _icomplete_[^fn:2] mode. +It allows you to select from a list of choices incrementally[^fn:3], so you need to type the beginning, and icomplete will narrow the list of propositions. +For example, when searching for "magit", the user needs to type "m" first as omitting it and starting with "i" will instead narrow to options beginning with "i". +Similarly, "mgt" will not limit to "magit" as we're missing"a". +Newer Emacs versions allowallow us to use "flex" matching with icomplete, but more on this later. +Incomplete work for all lists using a mini buffer, like filenames or imenu. + +Then _IDO_[^fn:4] mode (Interactively Do) came in. +It uses the aforementioned "flex" matching, where you can search for any part of the word. +"Magit" would be found with "agit", "mgt" or just "git". +IDO, however, works only with files and buffers, so \`M-x\` would still fall back to icomplete. + +Starting with Emacs 27, we've got _fido_ mode (Fake Ido). +It combines the best things about icomplete (works anywhere) with flex matching. +In fact, all Fido does under the hood is to enable Icomplete with flex matching. + +There are also other solutions for competition, not baked into emacs, with the most popular being Helm[^fn:5] and Ivy[^fn:6]. +Personally, I always try the default option, and only if their limits become annoying do I look at the alternatives. +So, I am now a happy FIDO mode user. + + +## Using FIDO {#using-fido} To start FIDO mode run -{{<highlight lisp>}} +```emacs-lisp fido-mode -{{</highlight>}} +``` or, if you find a vertical list to be more readable, use -{{<highlight lisp>}} +```emacs-lisp fido-veritcal-mode -{{</highlight>}} +``` -You can also customize variables to have it auto-load in your `init.el`: +You can also customize variables to have it auto-load in your \`init.el\`: -{{<highlight lisp>}} +```emacs-lisp (fido-mode t) ;; or (fido-vertical-mode t) -{{</highlight>}} +``` -As I've stated above, FIDO is nothing more than flex-enabled Icomplete, so all keybindings work the same. For vertical mode: +As I've stated above, FIDO is nothing more than flex-enabled Icomplete, so all keybindings work the same. +For vertical mode: -- Typing a string will narrow the selection -- `C-n` and `C-p` will select the next/prev element on the list -- `Tab` will display a buffer with a list of narrowed elements -- `enter` or `C-j` will select the option +- Typing a string will narrow the selection +- \`C-n\` and \`C-p\` will select the next/prev element on the list +- \`Tab\` will display a buffer with a list of narrowed elements +- \`enter\` or \`C-j\` will select the option -One cool thing you can do with FIDO is file deletion (when selecting files) or closing buffers (when selecting buffers) using `C-k`. +One cool thing you can do with FIDO is file deletion (when selecting files) or closing buffers (when selecting buffers) using \`C-k\`. -### Customizing the Minibuffer + +## Customizing the Minibuffer {#customizing-the-minibuffer} We can, of course, customize how Icomplete looks works[^savanah]. -- *icompleteatches-format* (default "%s/%s") - how the number of filtered and total options is presented -- *complete-first-match* (default (t :weight bold)) - how the first filtered option is presented -- *icomplete-selected-match* (default (t :inherit highlight)) - the same as above, but for vertical mode -- *icomplete-section* (default (t :inherit shadow :slant italic)) - how the section title for vertical mode is presented -- *icomplete-compute-delay* (default .15) - how fast the filtering will start after a keypress when a larger number of options is presented -- *icomplete-delay-completions-threshold* (default 400) - defines the "large number" for icomplete-compute-delay -- *icomplete-max-delay-chars* (default 2) - maximum number of initial characters before applying *icomplete-compute-delay*. I have no idea what it means, nor have I the knowledge of Elisp to dig into it. -- *icomplete-in-buffer* (default nil) - enables usage of Icomplete outside of the Minibuffer. I have not tested it. +- _icompleteatches-format_ (default "%s/%s") - how the number of filtered and total options is presented +- _complete-first-match_ (default (t :weight bold)) - how the first filtered option is presented +- _icomplete-selected-match_ (default (t :inherit highlight)) - the same as above, but for vertical mode +- _icomplete-section_ (default (t :inherit shadow :slant italic)) - how the section title for vertical mode is presented +- _icomplete-compute-delay_ (default .15) - how fast the filtering will start after a keypress when a larger number of options is presented +- _icomplete-delay-completions-threshold_ (default 400) - defines the "large number" for icomplete-compute-delay +- _icomplete-max-delay-chars_ (default 2) - maximum number of initial characters before applying _icomplete-compute-delay_. I have no idea what it means, nor have I the knowledge of Elisp to dig into it. +- _icomplete-in-buffer_ (default nil) - enables usage of Icomplete outside of the Minibuffer. I have not tested it. -We can also use *icomplete-minibuffer-setup-hook* hook if needed. +We can also use _icomplete-minibuffer-setup-hook_ hook if needed. [^savanah]: list based on [Icomplete source code](https://git.savannah.gnu.org/cgit/emacs.git/tree/lisp/icomplete.el) -### Using Completions from Elisp -The great thing about FIDO is that it, like Icomplete, uses Minibuffer API[^minubuffer], so you can simply: -[^minibuffer]: [Guide on Minibuffer completition](https://www.gnu.org/software/emacs/manual/html_node/elisp/Minibuffer-Completion.html) +## Using Completions from Elisp {#using-completions-from-elisp} + +The great thing about FIDO is that it, like Icomplete, uses Minibuffer API[^fn:7], so you can simply: -{{<highlight lisp>}} +```emacs-lisp (completing-read "prompt: " '(("option1" 1) ("option" 2)) nil t "default query") -{{</highlight>}} +``` + +[^fn:1]: [Company-mode website](https://company-mode.github.io/) +[^fn:2]: [Icomplete documentation](https://www.gnu.org/software/emacs/manual/html_node/emacs/Icomplete.html) +[^fn:3]: According to a [Reddit comment](https://www.reddit.com/r/emacs/comments/13szol7/comment/jltmaud/?utm_source=reddit&utm_medium=web2x&context=3) this behavior is relatively new. + Until recently, Icompelete only showed the narrowed selection in a dedicated buffer, similar to using \`Tab-Tab\` now. + The actual input was still up to the user to type in. Thanks for the tidbit +[^fn:4]: [IDO documentation](https://www.gnu.org/software/emacs/manual/html_mono/ido.html) +[^fn:5]: [Helm website](https://emacs-helm.github.io/helm/) +[^fn:6]: [Ivy website](https://oremacs.com/swiper/) +[^fn:7]: [Guide on Minibuffer completition](https://www.gnu.org/software/emacs/manual/html_node/elisp/Minibuffer-Completion.html)
\ No newline at end of file diff --git a/content/emacs/moving-my-rss-reading-to-emacs-with-elfeed.md b/content/emacs/moving-my-rss-reading-to-emacs-with-elfeed.md index a7f8687..5d80d9b 100644 --- a/content/emacs/moving-my-rss-reading-to-emacs-with-elfeed.md +++ b/content/emacs/moving-my-rss-reading-to-emacs-with-elfeed.md @@ -1,44 +1,56 @@ ---- -title: "Moving My RSS Reading to Emacs With Elfeed" -category: emacs -abstract: It's even better now than it newsboat. -date: 2023-05-06T19:44:40+02:00 -year: 2023 -draft: false -tags: -- Emacs -- elfeed -- RSS -- newsboat ---- -Since Emacs became my shell of choice[^1], I am abandoning more and more dedicated applications in favor of different packages. As it turns out, Emacs packages are very feature rich. This time: I moved my RSS reading from newsboat[^2] to elfeed[^3]. ++++ +title = "Moving My RSS Reading to Emacs With Elfeed" +author = ["Michał Sapka"] +date = 2023-05-19T23:00:00+02:00 +lastmod = 2023-12-24T22:56:09+01:00 +categories = ["emacs"] +draft = false +weight = 2002 +abstract = "Setting up config inside an org file" +[menu] + [menu.emacs-guides] + weight = 2002 + identifier = "moving-my-rss-reading-to-emacs-with-elfeed" ++++ + +Since Emacs became my shell of choice[^fn:1], I am abandoning more and more dedicated applications in favor of different packages. +As it turns out, Emacs packages are very feature rich. +This time: I moved my RSS reading from newsboat[^fn:2] to elfeed[^fn:3]. Elfeed has very simple keybindings: -- g will refresh the items list -- G will refresh the items list and fetch new items -- r will mark currently selected item is read (remove unread tag)[^4] -- b will open item in the browser +- g will refresh the items list +- G will refresh the items list and fetch new items +- r will mark currently selected item is read (remove unread tag)[^4] +- b will open item in the browser -One huge upside of elfeed compared to newsboat is image support. Emacs is a GUI application, so all images are present in their glory! +One huge upside of elfeed compared to newsboat is image support. +Emacs is a GUI application, so all images are present in their glory! -{{<img-center "elfeed-details.png" "Images!">}} +{{< img-c "elfeed-details.png" >}} +Images! +{{< /img-c >}} -My setup is near stock. I have a few dozen feeds that are auto-tagged. Three essential tags are "important", "news", and "company". I want to read each "important", then I want to see all normal, and finally I can just skim "news" and "company". Adding auto-tagging is very simple: just define the tag when defining the RSS feed list: +My setup is near stock. +I have a few dozen feeds that are auto-tagged. +Three essential tags are "important", "news", and "company". +I want to read each "important", then I want to see all normal, and finally I can just skim "news" and "company". +Adding auto-tagging is very simple: just define the tag when defining the RSS feed list: -{{<highlight lisp>}} +```emacs-lisp ("https://rubenerd.com/feed/" blog important) ("https://www.pine64.org/feed/" company) -{{</highlight>}} +``` -Now, each new article will be tagged with matching tags. Elfeed allows to define of custom faces that will be applied to items matching tag[^6]: +Now, each new article will be tagged with matching tags. +Elfeed allows to define of custom faces that will be applied to items matching tag[^fn:4]: -{{<highlight lisp>}} +```emacs-lisp (defface important-elfeed-entry '((t :foreground "#f77")) "Marks an important Elfeed entry." :group 'elfeed) - + (defface nonimportant-elfeed-entry '((t :foreground "#C0C0C0")) "Marks an nonimportant Elfeed entry." @@ -50,17 +62,17 @@ Now, each new article will be tagged with matching tags. Elfeed allows to define elfeed-search-face-alist) (push '(news nonimportant-elfeed-entry) elfeed-search-face-alist) -{{</highlight>}} +``` -Now important items will be dark red, while company & news will be dark gray +Now important items will be dark red, while company & news will be dark gray -{{<img-center "elfeed-list.png" "No important things to read at this moment.">}} +{{< img-c "elfeed-list.png" >}} +No important things to read at this moment. +{{< /img-c >}} Elfeed has a few packages expanding its functionality, but I found the default behavior to be exactly right. -[^1]: [Emacs as a Shell](/2023/emacs-as-a-shell/) -[^2]: [Newsboat homepage](https://newsboat.org/) -[^3]: [Elfeed repository on Github](https://github.com/skeeto/elfeed) -[^4]: The "r" key messes with my muscle memory, as notmuch[^5] uses "ku" for the same action while "r" will start composing a reply. -[^5]: [Posts tagged about notmuch](https://d-s.sh/tags/notmuch) -[^6]: my elisp-fu not good +[^fn:1]: [Emacs as a Shell](/2023/emacs-as-a-shell/) +[^fn:2]: [Newsboat homepage](https://newsboat.org/) +[^fn:3]: [Elfeed repository on Github](https://github.com/skeeto/elfeed) +[^fn:4]: my elisp-fu not good
\ No newline at end of file diff --git a/content/emacs/notmuch.md b/content/emacs/notmuch.md index 15a6257..ab18801 100644 --- a/content/emacs/notmuch.md +++ b/content/emacs/notmuch.md @@ -1,32 +1,40 @@ ---- -title: "Managing email with Notmuch and Emacs" -category: "emacs" -abstract: My email based workflow for GitHub Pull Review Requests -date: 2023-07-03T11:00:54+02:00 -year: 2023 -draft: false -tags: -- notmuch -- email -- github -- tutorial -- workflow ---- - -Web email interfaces have taken over the world a long time ago. Except for Outlook users, only a few people even consider using an actual application for it. This is one of the primary reasons for our low opinion of electronic mail today, since with limited tools, you get limited capabilities. While there are options for automatic filtering, tagging, or folding with most clients, those capabilities are barebones. ++++ +title = "Managing email with Notmuch and Emacs" +author = ["Michał Sapka"] +date = 2023-07-03T23:00:00+02:00 +lastmod = 2023-12-24T22:56:14+01:00 +categories = ["emacs"] +draft = false +weight = 2004 +abstract = "My email based workflow for GitHub Pull Review Requests" +[menu] + [menu.emacs-guides] + weight = 2004 + identifier = "managing-email-with-notmuch-and-emacs" ++++ + +Web email interfaces have taken over the world a long time ago. +Except for Outlook users, only a few people even consider using an actual application for it. +This is one of the primary reasons for our low opinion of electronic mail today, since with limited tools, you get limited capabilities. +While there are options for automatic filtering, tagging, or folding with most clients, those capabilities are barebones. Luckily there are still compelling alternatives. -## Managing email locally -Back in the day of POP, this was the standard: your computer downloaded new messages from the server, and you had local copies from this point. Then IMAP came, and after it, Exchange. Now you have a fast and convenient option to sync all changes with other clients and not bother with hard drive space. But this also puts us at the mercy of email providers. If a given service doesn't support tags, folders, or saved searches, you cannot use it. I tried to reverse this direction and never looked back. I want all my email to be on my machine, available at all times, and accessible instantly. -Mbsync(1)[^mbs] is a popular application to populate local ~/Mail folder with remote messages fetched via IMAP. The config is quite simple. For Gmail[^gmail][^frosty]: +## Managing email locally {#managing-email-locally} -[^mbs]: [mbsync on sourceforge](https://isync.sourceforge.io/mbsync.html) -[^gmail]: I have my gripes with Gmail, but it is still the standard. While this article assumes Gmail, you can easily use mbsync(1) with any IMAP service. -[^frosty]: Originally I took the config from [Frosty](https://frostyx.cz/posts/synchronize-your-2fa-gmail-with-mbsync) and it worked flawlessly. +Back in the day of POP, this was the standard: your computer downloaded new messages from the server, and you had local copies from this point. +Then IMAP came, and after it, Exchange. +Now you have a fast and convenient option to sync all changes with other clients and not bother with hard drive space. +But this also puts us at the mercy of email providers. +If a given service doesn't support tags, folders, or saved searches, you cannot use it. +I tried to reverse this direction and never looked back. +I want all my email to be on my machine, available at all times, and accessible instantly. -{{<highlight shell "linenos=inline">}} +Mbsync(1)[^fn:1] is a popular application to populate local ~/Mail folder with remote messages fetched via IMAP. +The config is quite simple. For Gmail[^fn:2]<sup>, </sup>[^fn:3]: + +```shell { linenos=true, linenostart=1, anchorlinenos=true, lineanchors=org-coderef--c9c8f8 } MaildirStore gmail-local Path ~/Mail/gmail/ Inbox ~/Mail/gmail/INBOX @@ -46,121 +54,179 @@ Create Both Expunge Both Patterns * !"[Gmail]/All Mail" !"[Gmail]/Important" !"[Gmail]/Starred" !"[Gmail]/Bin" SyncState * -{{</highlight>}} +``` + +Then type \`mbsybc -a\`, wait for a few (hundred? depending on the mailbox size) minutes, and voila. +Your \`~/Mail/gmail/\` is now populated with all your messages. Let's break down the config. -Then type `mbsybc -a`, wait for a few (hundred? depending on the mailbox size) minutes, and voila. Your `~/Mail/gmail/` is now populated with all your messages. Let's break down the config. +Mbsync(1) assumes two stores exist: local (on your computer) and remote (the IMAP server). +A store is a place where mail exists. +We have then configured in lines [1](#org-coderef--c9c8f8-1)-[4](#org-coderef--c9c8f8-4) and [6](#org-coderef--c9c8f8-6)-[11](#org-coderef--c9c8f8-11). -Mbsync(1) assumes two stores exist: local (on your computer) and remote (the IMAP server). A store is a place where mail exists. We have then configured in lines 1-4 and 6-11. +The remote one is self-explanatory. +One thing to remember: some providers will require you to use an app-specific password and reject auth attempts with the normal one. +Our password in line 11 can be either a string with the password or an arbitrary command (think \`cat ~/my-secret-password\` or a CLI password manager). -The remote one is self-explanatory. One thing to remember: some providers will require you to use an app-specific password and reject auth attempts with the normal one. Our password in line 11 can be either a string with the password or an arbitrary command (think `cat ~/my-secret-password` or a CLI password manager). +The local store is just a definition of local folders to use. +It can be anywhere, but \`~/Mail\` is the standard, anxd many mail clients will assume that you store your email there. -The local store is just a definition of local folders to use. It can be anywhere, but `~/Mail` is the standard, anxd many mail clients will assume that you store your email there. +On line [13](#org-coderef--c9c8f8-13), we start to define a channel, which is how mbsync(1) works. +One store is \`far\` (remote), while the other is \`near\` (on your machine). +The rest of the config defines behavior. +Refer to the manual, but in my case: -On line 13, we start to define a channel, which is how mbsync(1) works. One store is `far` (remote), while the other is `near` (on your machine). The rest of the config defines behavior. Refer to the manual, but in my case: -- it will create non-matching mailboxes -- it will delete messages in a store if a message was deleted on the other -- it will sync all messages except a few matching the pattern. -- it will store the synchronization state file in the Mail dir. +- it will create non-matching mailboxes +- it will delete messages in a store if a message was deleted on the other +- it will sync all messages except a few matching the pattern. +- it will store the synchronization state file in the Mail dir. -One last thing to add is a simple cron rule. You can force mail fetching manually, but I opted for the automatic option. Therefore, my crontab(1) has: +One last thing to add is a simple cron rule. +You can force mail fetching manually, but I opted for the automatic option. +Therefore, my crontab(1) has: -{{<highlight crontab "linenos=table">}} +```cron 1/5 * * * * killall mbsync &>/dev/null; <msync_dir>/mbsync -a -V 2>&1 > ~/.mbsync_log -{{</highlight>}} +``` We will now fetch new messages every 5 minutes. -## Local clients -Now we need to choose a local email client to use. There is a lot to choose from! Thunderbird[^thunderbrid] seems to be the most popular option for GUI users. For terminal users, we have Mutt(1)[^mutt], its successor Neomutt(1)[^neomutt], and many, many more. I used Neomutt(1)[^luke] for a while, and it was a pleasure compared to web clients. -[^thunderbrid]: [Thunderbird website](https://www.thunderbird.net/) -[^mutt]: [Mutt website](https://www.mutt.org/) -[^neomutt]: [Neomutt website](https://neomutt.org/) -[^luke]: Luke Smith has a few excellent vlogs about Mutt/NeoMutt like [Email on the terminal with mutt](https://www.youtube.com/watch?v=2jMInHnpNfQ&t=683s)". +## Local clients {#local-clients} + +Now we need to choose a local email client to use. +There is a lot to choose from! Thunderbird[^fn:4] seems to be the most popular option for GUI users. +For terminal users, we have Mutt(1)[^fn:5], its successor Neomutt(1)[^fn:6], and many, many more. +I used Neomutt(1)[^fn:7] for a while, and it was a pleasure compared to web clients. + +However, ever since I started to use Emacs more, I wanted to move my Email inside Emacs. +And to little surprise, we also have a lot to choose from. +By default, Emacs comes with Gnus[^fn:8], a newsgroups reader with an email client capability. +However, the two most popular packages are Mu4e[^fn:9] and notmuch(1)[^fn:10] -However, ever since I started to use Emacs more, I wanted to move my Email inside Emacs. And to little surprise, we also have a lot to choose from. By default, Emacs comes with Gnus[^gnus], a newsgroups reader with an email client capability. However, the two most popular packages are Mu4e[^mu4e] and notmuch(1)[^notmuch] -[^gnus]: [Gnus on gnu.org](https://www.gnu.org/software/emacs/manual/html_node/gnus/) -[^mu4e]: [mu4e website](https://www.djcbsoftware.nl/code/mu/) -[^notmuch]: [notmuch website](https://notmuchmail.org/) +The last two are based on fast email indexing and searching but assume different workflows. +Mu4e is based on filtering, while notmuch around tagging. -The last two are based on fast email indexing and searching but assume different workflows. Mu4e is based on filtering, while notmuch around tagging. +A friend[^fn:11] recommended Notmuch(1) to me. +I have never tried mu4e as notmuch fully satisfies my needs. -A friend[^alex] recommended Notmuch(1) to me. I have never tried mu4e as notmuch fully satisfies my needs. -[^alex]: Thanks, [Alex](https://se30.xyz/) -## Setting up notmuch(1) +### Setting up notmuch(1) {#setting-up-notmuch--1} -Notmuch(1) is not a client but an indexer. It indexes and tags existing email and allows one to search messages. It should be present on most systems package management, so install it. They run `notmuch`, answer a few questions, and you've got yourself a ready notmuch. +Notmuch(1) is not a client but an indexer. +It indexes and tags existing email and allows one to search messages. +It should be present on most systems package management, so install it. +They run \`notmuch\`, answer a few questions, and you've got yourself a ready notmuch. -Whenever a new mail arrives, it won't be known to notmuch before indexing. You can manually run `notmuch new` or make a cron definition for it. +Whenever a new mail arrives, it won't be known to notmuch before indexing. +You can manually run \`notmuch new\` or make a cron definition for it. -One killer feature of notmuch is its sheer speed. The name comes from the fact that it can work on gigantic mailboxes very swiftly - oh, you have one million messages? That's not much! +One killer feature of notmuch is its sheer speed. +The name comes from the fact that it can work on gigantic mailboxes very swiftly - oh, you have one million messages? That's not much! Let's try a simple search: -{{<highlight shell>}} +```shell $ notmuch search 'from:*@github.com' -{{</highlight>}} +``` -You can search based on sender, receiver, dates, attachments, contents, titles, etc. Refer to man pages for `notmuch-search-terms(7)`. However, to get the most out of notmuch, we need to learn about tags. +You can search based on sender, receiver, dates, attachments, contents, titles, etc. +Refer to man pages for \`notmuch-search-terms(7)\`. +However, to get the most out of notmuch, we need to learn about tags. Let's add a "gh" tag for messages from Git Hub. -{{<highlight shell>}} +```shell +$ notmuch search 'from:*@github.com' $ notmuch tag "+gh" -- "from:@github.com" -{{</highlight>}} +``` Now, we can search for such messages -{{<highlight shell>}} +```shell $ notmuch search "tag:gh" -{{</highlight>}} +``` + +We can also join multiple search criteria with "and", "or" and other boolean operators. +We now have a fully working local email reader - however we can not send email. +I will not discuss sending email here as it's a separate subject. Notmuch(1) is not for sending email. -We can also join multiple search criteria with "and", "or" and other boolean operators. We now have a fully working local email reader - however we can not send email. I will not discuss sending email here as it's a separate subject. Notmuch(1) is not for sending email. +Using CLI for reading email is far from pleasant. +All those commands will come in handy, but first, let's add a user interface. -Using CLI for reading email is far from pleasant. All those commands will come in handy, but first, let's add a user interface. -### Notmuch(1) in Emacs(1) +### Notmuch(1) in Emacs(1) {#notmuch--1--in-emacs--1} -Notmuch(1) can be used with different UIs, like Mutt(1). However, it comes with an Emacs(1) package, so let's enable it. +Notmuch(1) can be used with different UIs, like Mutt(1). +However, it comes with an Emacs(1) package, so let's enable it. -{{<highlight elisp>}} +```emacs-lisp (use-package notmuch :commands notmuch-hello :bind (("C-c m" . notmuch-hello))) -{{</highlight>}} +``` The key binding "C-c m" is very popular, but you can use whatever you want. -After running `notmuch-hello`, we are not greeted with a list of messages but with a search interface. You've got access to saved searches, recent searches, and a list of tags. This tells us that we are dealing with a completely different beast than webmail, and the user needs to think of new workflows. +After running \`notmuch-hello\`, we are not greeted with a list of messages but with a search interface. +You've got access to saved searches, recent searches, and a list of tags. +This tells us that we are dealing with a completely different beast than webmail, and the user needs to think of new workflows. + +The way we are thought of thinking of email is a list of messages. +Some clients can mark messages that are more important, favorite, tag them, move them into folders, etc. +But everywhere I know, the primary interface is just a list - unread email on top, read on the bottom. +The behavior I always expect is to open (or mark as read) all incoming messages and then ignore most of them. +If you spent some time on configuration, maybe you have automatic rules - like moving all newsletters to a "newsletter" folder and removing them from your inbox. +Hey[^hey] is even based on separating all incoming messages into three tiers: important mail, newsletters, and paper trial. +[^hey]: [Hey email website](<https://www.hey.com/>). It's the best email service I know, but it doesn't allow any external clients, and the pricing is unacceptable for me. -The way we are thought of thinking of email is a list of messages. Some clients can mark messages that are more important, favorite, tag them, move them into folders, etc. But everywhere I know, the primary interface is just a list - unread email on top, read on the bottom. The behavior I always expect is to open (or mark as read) all incoming messages and then ignore most of them. If you spent some time on configuration, maybe you have automatic rules - like moving all newsletters to a "newsletter" folder and removing them from your inbox. Hey[^hey] is even based on separating all incoming messages into three tiers: important mail, newsletters, and paper trial. -[^hey]: [Hey email website](https://www.hey.com/). It's the best email service I know, but it doesn't allow any external clients, and the pricing is unacceptable for me. +But back to notmuch. +Look at saved searches - we will use them later. +Open "unread," and we see a semi-normal list of messages. +Use "n" and "p" to select email, enter to open it, and so on - standard Emacs stuff. +One thing to remember is that by default, reading an email will not mark it as read. +You need to manually remove the tag via \`k u\` - either one by one, or on all messages in a selected region (C-spc, it's still a buffer, after all). +"Unread" here is just another tag. +We can be much smarter with marking actionable items. -But back to notmuch. Look at saved searches - we will use them later. Open "unread," and we see a semi-normal list of messages. Use "n" and "p" to select email, enter to open it, and so on - standard Emacs stuff. One thing to remember is that by default, reading an email will not mark it as read. You need to manually remove the tag via `k u` - either one by one, or on all messages in a selected region (C-spc, it's still a buffer, after all). "Unread" here is just another tag. We can be much smarter with marking actionable items. -## Automatic Github Pull Review workflow +## Automatic Github Pull Review workflow {#automatic-github-pull-review-workflow} -What we've seen before is nothing more than a normal email client with extra steps. We read emails in Emacs(1), which is great, but we don't get anything extra. It's time for a real-world example. +What we've seen before is nothing more than a normal email client with extra steps. +We read emails in Emacs(1), which is great, but we don't get anything extra. +It's time for a real-world example. -I am a software engineer forced to work with GitHub. One thing I do is to review pull requests. The primary problem here is knowing that someone wants me to review something. The review itself is the easy part :-) +I am a software engineer forced to work with GitHub. +One thing I do is to review pull requests. +The primary problem here is knowing that someone wants me to review something. +The review itself is the easy part :-) I rely solely on email for this information, ostensibly ignoring all nudges on Slack[^slack]. -[^slack]: Back when Slack was first sold, it was proposed not only as a chat tool but also as a single place for all information. We see it now: we connect Slack to everything - GitHub, jira, Data Dog, or pager duty. The general idea is great, but Slack is a pretty mediocre application. The only way to manage what you receive is to leave a channel. But then you lose all other messages sent there, so the price may be high. +[^slack]: Back when Slack was first sold, it was proposed not only as a chat tool but also as a single place for all information. +We see it now: we connect Slack to everything - GitHub, jira, Data Dog, or pager duty. +The general idea is great, but Slack is a pretty mediocre application. +The only way to manage what you receive is to leave a channel. +But then you lose all other messages sent there, so the price may be high. -First of all, we need to enable email notifications from GitHub. Remember to mark that you want to get emails about your own actions. +First of all, we need to enable email notifications from GitHub. +Remember to mark that you want to get emails about your own actions. -Now, let's think about what we want to achieve. For me it is "I want to know about all the pull requests I should look at without opening browser[^gh-cli]". This means I want to see all the review requests I was assigned (personally or by being part of a team) that I have not yet reviewed. -[^gh-cli]: There is the GitHub CLI which is amazing by itself - one of the best things that GitHub has done in the last few years. And it can achieve the same. However, let's ignore it for now, as the same model of email-based dashboards can be expanded to many other things. +Now, let's think about what we want to achieve. +For me it is "I want to know about all the pull requests I should look at without opening browser[^fn:12]". +This means I want to see all the review requests I was assigned (personally or by being part of a team) that I have not yet reviewed. +And it can achieve the same. +However, let's ignore it for now, as the same model of email-based dashboards can be expanded to many other things. Luckily, GitHub allows us to get that from email: -1. When you are first assigned a review, you get a dedicated email -2. When you approve or reject a PR, you get an email -3. When someone asks you to re-review an email, you get the same email as it was the first request for this PR. -We now know that we can use Notmuch(1). There are two ways: we can use `notmuch-hooks(5)` and place a shell script in `~/Mail/.notmuch/hooks/post-new, but it never worked reliably for me. Instead, I have a cron job that runs a script: +1. When you are first assigned a review, you get a dedicated email +2. When you approve or reject a PR, you get an email +3. When someone asks you to re-review an email, you get the same email as it was the first request for this PR. -{{<highlight shell "linenos=inline">}} +We now know that we can use Notmuch(1). +There are two ways: we can use \`notmuch-hooks(5)\` and place a shell script in \`~/Mail/.notmuch/hooks/post-new, but it never worked reliably for me. +Instead, I have a cron job that runs a script: + +```shell { linenos=true, linenostart=1 } #!/usr/bin/env sh notmuch tag +gh -unread -- '(from:notifications@github.com)' @@ -168,78 +234,119 @@ notmuch tag +gh -unread -- '(from:notifications@github.com)' # Mark new review requests for thread in $(notmuch search --sort=oldest-first --output=threads -- "\"requested your review on\" and tag:gh and -tag:gh-pr-done"); do for msg in $(notmuch search --sort=oldest-first --output=messages -- "$thread"); do - txt=$(notmuch show "$msg") + txt=$(notmuch show "$msg") - (echo "$txt" | grep "requested your review on") && notmuch tag +gh-pr-todo -- "$thread" - (echo "$txt" | grep "@michalsapka approved this pull request") && notmuch tag -gh-pr-todo -- "$thread" - (echo "$txt" | grep "@michalsapka requested changes on this pull request") && notmuch tag -gh-pr-todo -- "$thread" - (echo "$txt" | grep "Merged.*into") && notmuch tag -gh-pr-todo +gh-pr-done -- "$thread" + (echo "$txt" | grep "requested your review on") && notmuch tag +gh-pr-todo -- "$thread" + (echo "$txt" | grep "@michalsapka approved this pull request") && notmuch tag -gh-pr-todo -- "$thread" + (echo "$txt" | grep "@michalsapka requested changes on this pull request") && notmuch tag -gh-pr-todo -- "$thread" + (echo "$txt" | grep "Merged.*into") && notmuch tag -gh-pr-todo +gh-pr-done -- "$thread" done done -{{</highlight>}} +``` Let's break it down: -1. First, we add a "gh" tag to all notification emails from GitHub and remove the "unread" tag. I don't need to be notified about all such emails, but I can still look at the "gh" tag if needed. -2, Then we search for threads where an email informs me about a review request. I limit the search to emails from GitHub via the tag from #1 and those without "gh-pr-done" tag. More on the second one in a moment -3. Then I search for all messages in such threads. I force order as oldest-first to make it possible to reason about. In normal PR, all actions happen with a significant delay between them, so this should be enough not to get lost in the timeframe. If I ask for a change in review, the re-request will not happen instantly. Note that I get the email body as a variable on line 8. -4. Then comes the meat. I will tag the entire thread multiple times based on the body of the message. When a request comes, a "gh-pr-todo" is added. I need to look at it. When I approve or reject a PR, the tag is removed. If someone asks for a review, logic from line 10 will be triggered, and the tag will be added again. This means that I want to handle all email threads with the "gh-pr-todo" tag. -6. Lastly, when a PR is merged, I ensure that the "gh-pr-todo" tag is removed, and I add the "gh-pr-done" tag so this thread will not be found in step 2 in the future. - -There are other ways to tag, like afew(1)[^afew], but keeping it to simple shell script working with notmuch(1) directly gives us the greatest amount of freedom and made it easier for me to tell this story. -[^afew]: [Afew on Github](https://github.com/afewmail/afew) - -## Making it more visible - -This alone would be a challenge to manage. An email with a tag would be easily missed. Notmuch has us covered yet again! My emacs config has a few dedicated lines: -{{<highlight elisp "linenos=table">}} +1. First, we add a "gh" tag to all notification emails from GitHub and remove the "unread" tag. + I don't need to be notified about all such emails, but I can still look at the "gh" tag if needed. +2. Then we search for threads where an email informs me about a review request. + I limit the search to emails from GitHub via the tag from #1 and those without "gh-pr-done" tag. More on the second one in a moment +3. Then I search for all messages in such threads. + I force order as oldest-first to make it possible to reason about. + In normal PR, all actions happen with a significant delay between them, so this should be enough not to get lost in the timeframe. + If I ask for a change in review, the re-request will not happen instantly. + Note that I get the email body as a variable on line 8. +4. Then comes the meat. + I will tag the entire thread multiple times based on the body of the message. + When a request comes, a "gh-pr-todo" is added. + I need to look at it. + When I approve or reject a PR, the tag is removed. + If someone asks for a review, logic from line 10 will be triggered, and the tag will be added again. + This means that I want to handle all email threads with the "gh-pr-todo" tag. +5. Lastly, when a PR is merged, I ensure that the "gh-pr-todo" tag is removed, and I add the "gh-pr-done" tag so this thread will not be found in step 2 in the future. + +There are other ways to tag, like afew(1)[^fn:13], but keeping it to simple shell script working with notmuch(1) directly gives us the greatest amount of freedom and made it easier for me to tell this story. + + +### Making it more visible {#making-it-more-visible} + +This alone would be a challenge to manage. +An email with a tag would be easily missed. +Notmuch has us covered yet again! My emacs config has a few dedicated lines: + +```emacs-lisp { linenos=true, linenostart=1 } (setq notmuch-search-line-faces '(("gh-pr-todo" . ((t :foreground "#f77")))) notmuch-saved-searches ;; other saved searches omitted ( :name "GitHub[reviews]" :query "tag:gh-pr-todo" - :sort-order newest-first)))) -{{</highlight>}} + :sort-order newest-first)) +``` This makes two changes. -1. Firstly, all messages with "gh-pr-todo" will be shown in red in any email list. All red items are actionable since we remove this tag in the workflow. -2. Secondly, amongst other saved searches, I have one dedicated to PRs. +1. Firstly, all messages with "gh-pr-todo" will be shown in red in any email list. + All red items are actionable since we remove this tag in the workflow. +2. Secondly, amongst other saved searches, I have one dedicated to PRs. -With those two things, every time I enter `notmuch-hello` screen, I get instant information about the work I need to do. +With those two things, every time I enter \`notmuch-hello\` screen, I get instant information about the work I need to do. -## Making it extra visible -But we can go one step further. Prot's[^prot] excellent notmuch-indicator[^not-ind] allows us to add saved searches to the mode line. After installing it, the configuration is straightforward: -[^prot]: [Prot's website](https://protesilaos.com/) -[^not-ind]: [notmuch-indicator repository](https://git.sr.ht/~protesilaos/notmuch-indicator) +### Making it extra visible {#making-it-extra-visible} -{{<highlight elisp "linenos=table">}} +But we can go one step further. +Prot's[^fn:14] excellent notmuch-indicator[fn: not-ind] allows us to add saved searches to the mode line. +After installing it, the configuration is straightforward: + +```emacs-lisp (notmuch-indicator-mode 1) (setq notmuch-indicator-refresh-count 60 notmuch-indicator-hide-empty-counters t notmuch-indicator-args - '((:terms "tag:gh-pr-todo":label "pr:")) -{{</highlight>}} + '((:terms "tag:gh-pr-todo":label "pr:"))) +``` -This adds a "pr:<count>" to the mode line. The count is the number of messages, not threads, but frankly, I want it to be 0. The counter will be refreshed every 60 seconds. And lastly, if the count is 0, the label will not be added to the mode line. +This adds a "pr:<count>" to the mode line. +The count is the number of messages, not threads, but frankly, I want it to be 0. +The counter will be refreshed every 60 seconds. +And lastly, if the count is 0, the label will not be added to the mode line. -## The downsides -Notmuch comes with one significant downside: lack of multi-device support. It's 2023, and most of us have more than one computer and those pesky mobile phones. +## The downsides {#the-downsides} -As for the mobile - I have no solution. The read statuses will sync via mbsync(1), but not much else. I try to purge myself from phone addiction, so maybe that's a plus? +Notmuch comes with one significant downside: lack of multi-device support. +It's 2023, and most of us have more than one computer and those pesky mobile phones. -As for the other computers, we have muchsync(1)[^muchsync]. It's an external application designed to sync entire mailboxes and tags between devices over ssh. I have not tried it yet[^boundries], but it looks promising. -[^muchsync]: [Muchsync homepage](https://www.muchsync.org/) -[^boundries]: my work computer gets all work messages, and my private one gets all private ones—complete separation. When I get a second personal machine, I will set it up, but for now, there is no use case for me. +As for the mobile - I have no solution. +The read statuses will sync via mbsync(1), but not much else. +I try to purge myself from phone addiction, so maybe that's a plus? +As for the other computers, we have muchsync(1)[^fn:15]. +It's an external application designed to sync entire mailboxes and tags between devices over ssh. +I have not tried it yet[^fn:16], but it looks promising. -## Summary -With local email and tools like notmuch(1), we are not at the mercy of external tools for even sophisticated workflows. If you get transactional emails, you can extract actionable data. It can be JIRA tickets, Pager Duty alerts, heck - even Amazon deliveries. Here I have demonstrated how easy it is to leverage notmuch(1), simple shell script, and emacs(1) to have a fully automated notification setup. It does not try to hijack your attention (like mobile notifications do) and is not hidden on some webpage (like GitHub notification), but it still gives actionable results. And all that without leaving the comfort of Emacs. +## Summary {#summary} -One cool thing I plan to apply soon is integrating notmuch(1) with Org-mode with the ol-notmich[oln] package. But for now, I am in the process of moving as many external services to a similar workflow as possible. -[^oln]: [ol-notmuch on sourcehut](https://git.sr.ht/~tarsius/ol-notmuch) +With local email and tools like notmuch(1), we are not at the mercy of external tools for even sophisticated workflows. If you get transactional emails, you can extract actionable data. It can be JIRA tickets, Pager Duty alerts, heck - even Amazon deliveries. Here I have demonstrated how easy it is to leverage notmuch(1), simple shell script, and emacs(1) to have a fully automated notification setup. It does not try to hijack your attention (like mobile notifications do) and is not hidden on some webpage (like GitHub notification), but it still gives actionable results. And all that without leaving the comfort of Emacs. +One cool thing I plan to apply soon is integrating notmuch(1) with Org-mode with the ol-notmich[oln] package. But for now, I am in the process of moving as many external services to a similar workflow as possible. +[^oln]: [ol-notmuch on sourcehut](<https://git.sr.ht/~tarsius/ol-notmuch>) + +[^fn:1]: [mbsync on sourceforge](https://isync.sourceforge.io/mbsync.html) +[^fn:2]: I have my gripes with Gmail, but it is still the standard. + While this article assumes Gmail, you can easily use mbsync(1) with any IMAP service. +[^fn:3]: Originally I took the config from [Frosty](https://frostyx.cz/posts/synchronize-your-2fa-gmail-with-mbsync) +[^fn:4]: [Thunderbird website](https://www.thunderbird.net/) +[^fn:5]: [Mutt website](https://www.mutt.org/) +[^fn:6]: [Neomutt website](https://neomutt.org/) +[^fn:7]: Luke Smith has a few excellent vlogs about Mutt/NeoMutt like [Email on the terminal with mutt](https://www.youtube.com/watch?v=2jMInHnpNfQ&t=683s)%22) +[^fn:8]: [Gnus on gnu.org](https://www.gnu.org/software/emacs/manual/html_node/gnus/) +[^fn:9]: [mu4e website](https://www.djcbsoftware.nl/code/mu/) +[^fn:10]: [notmuch website](https://notmuchmail.org/) +[^fn:11]: Thanks, [Alex](https://se30.xyz/) +[^fn:12]: There is the GitHub CLI which is amazing by itself - one of the best things that GitHub has done in the last few years. +[^fn:13]: [Afew on Github](https://github.com/afewmail/afew) +[^fn:14]: [Prot's website](<https://protesilaos.com/>) +[^fn:15]: [Muchsync homepage](https://www.muchsync.org/) +[^fn:16]: my work computer gets all work messages, and my private one gets all private ones—complete separation. When I get a second personal machine, I will set it up, but for now, there is no use case for me.
\ No newline at end of file |