diff --git a/org/config/emacs.org b/org/config/emacs.org index c551ca6..ac626ed 100644 --- a/org/config/emacs.org +++ b/org/config/emacs.org @@ -576,6 +576,27 @@ prefix them with a comma (I’ve taken this habit from Spacemacs). (general-auto-unbind-keys)) #+end_src +#+name: general-keybindings-gen +#+header: :tangle no :exports none :results value +#+begin_src emacs-lisp :var table=mu4e-keybindings-view-tbl + (mapconcat (lambda (line) + (let ((key (car line)) + (function (cadr line)) + (comment (caddr line))) + (format "\"%s\" %s" key + (if (string= "" comment) + (if (string= "nil" function) + "nil" + (concat "#'" function)) + (format "'(%s :wk \"%s\")" + comment + (if (string= "" function) + "nil" + comment)))))) + table + "\n") +#+end_src + ** Evil #+begin_src emacs-lisp (use-package evil @@ -729,12 +750,28 @@ grow the ~mu4e-headers~ buffer when viewing an email. #+end_src *** Elfeed -*** Email - Mu4e +*** Email +**** Basic configuration +As seen below, I use ~org-msg~ to compose my emails, which includes by +default my signature. Therefore, there is no need for Emacs itself to +know about it since I don’t want it to include it a second time after +~org-msg~ already did. #+begin_src emacs-lisp (setq message-signature nil mail-signature nil) #+end_src +**** Mu4e +Mu4e is a very eye-pleasing email client for Emacs, built around ~mu~ +and which works very well with ~mbsync~ (found in Arch’s ~isync~ package). +For me, the main advantage of mu4e is it has a modern interface for +emailing, and quite straightforward. I tried a couple of other email +clients for Emacs, and I even was for some time a Gnus user, but in +the end, mu4e really works best for me. Below you’ll find my +configuration for the ~mu4e~ package itself. + +Quick sidenote: on ArchLinux, you’ll need to install either ~mu~ or +~mu-git~ from the AUR in order to use mu4e. #+begin_src emacs-lisp (use-package mu4e :after all-the-icons @@ -754,7 +791,11 @@ grow the ~mu4e-headers~ buffer when viewing an email. :config (progn - <> + <> + <> + <> + <> + (setq mu4e-compose-signature nil) (when (fboundp 'imagemagick-register-types) @@ -771,100 +812,50 @@ grow the ~mu4e-headers~ buffer when viewing an email. (add-hook 'dired-mode-hook 'turn-on-gnus-dired-mode) (add-hook 'mu4e-compose-mode-hook 'mml-secure-message-sign-pgpmime) - (setq mu4e-get-mail-command "mbsync -a" - mu4e-maildir "~/.mail" - mu4e-trash-folder "/Trash" - mu4e-refile-folder "/Archive" - mu4e-sent-folder "/Sent" - mu4e-drafts-folder "/Drafts" - mu4e-update-interval 60 - mu4e-compose-format-flowed t - mu4e-view-show-addresses t - mu4e-sent-messages-behaviour 'sent - mu4e-hide-index-messages t - ;; try to show images - mu4e-view-show-images t - mu4e-view-image-max-width 600 - ;; configuration for sending mail - message-send-mail-function #'smtpmail-send-it - smtpmail-stream-type 'starttls - message-kill-buffer-on-exit t ; close after sending - ;; start with the first (default) context - mu4e-context-policy 'pick-first - ;; compose with the current context, or ask - mu4e-compose-context-policy 'ask-if-none - ;; use ivy - mu4e-completing-read-function #'ivy-completing-read - ;; no need to ask - mu4e-confirm-quit t - - - mu4e-header-fields - '((:account . 12) - (:human-date . 12) - (:flags . 4) - (:from . 25) - (:subject))) + (setq mu4e-get-mail-command "mbsync -a" + mu4e-maildir "~/.mail" + mu4e-trash-folder "/Trash" + mu4e-refile-folder "/Archive" + mu4e-sent-folder "/Sent" + mu4e-drafts-folder "/Drafts" + mu4e-change-filenames-when-moving t + mu4e-update-interval 60 + mu4e-compose-format-flowed t + mu4e-view-show-addresses t + mu4e-sent-messages-behaviour 'sent + mu4e-hide-index-messages t + mu4e-view-show-images t ; try to show images + mu4e-view-image-max-width 600 + message-send-mail-function #'smtpmail-send-it ; how to send an email + smtpmail-stream-type 'starttls + message-kill-buffer-on-exit t ; close after sending + mu4e-context-policy 'pick-first ; start with first (default) context + mu4e-compose-context-policy 'ask-if-none ; compose with current context, or ask + mu4e-completing-read-function #'ivy-completing-read ; use ivy + mu4e-confirm-quit t ; no need to ask + mu4e-header-fields '((:account . 12) + (:human-date . 12) + (:flags . 4) + (:from . 25) + (:subject))) ;; set mail user agent (setq mail-user-agent 'mu4e-user-agent) ;; Use fancy icons - (setq mu4e-use-fancy-chars t - mu4e-headers-draft-mark `("D" . ,(all-the-icons-faicon "pencil":height 0.8)) - mu4e-headers-flagged-mark `("F" . ,(all-the-icons-faicon "flag":height 0.8)) - mu4e-headers-new-mark `("N" . ,(all-the-icons-faicon "rss":height 0.8)) - mu4e-headers-passed-mark `("P" . ,(all-the-icons-faicon "check":height 0.8)) - mu4e-headers-replied-mark `("R" . ,(all-the-icons-faicon "reply":height 0.8)) - mu4e-headers-seen-mark `("S" . ,(all-the-icons-faicon "eye":height 0.8)) - mu4e-headers-unread-mark `("u" . ,(all-the-icons-faicon "eye-slash":height 0.8)) - mu4e-headers-trashed-mark `("T" . ,(all-the-icons-faicon "trash":height 0.8)) - mu4e-headers-attach-mark `("a" . ,(all-the-icons-faicon "paperclip":height 0.8)) - mu4e-headers-encrypted-mark `("x" . ,(all-the-icons-faicon "lock":height 0.8)) - mu4e-headers-signed-mark `("s" . ,(all-the-icons-faicon "certificate":height 0.8))) + <> + ;; Set bookmarks + <> - (setq mu4e-bookmarks - `((,(s-join " " - '("NOT flag:trashed" - "AND (maildir:/Inbox OR maildir:/Junk)" - "AND NOT to:CONLANG@LISTSERV.BROWN.EDU" - "AND NOT to:AUXLANG@LISTSERV.BROWN.EDU" - "AND NOT to:ateliers-emacs@framalistes.org" - "AND NOT to:ateliers-paris@emacs-doctor.com" - "AND NOT list:ateliers-emacs.framalistes.org" - "AND NOT list:ateliers-paris.emacs-doctor.com")) - "Inbox" ?i) ;; Inbox without the linguistics mailing lists - (,(s-join " " - '("NOT flag:trashed" - "AND (maildir:/Inbox OR maildir:/Junk)" - "AND (f:/.*up8\.edu|.*univ-paris8.*/" - "OR c:/.*up8\.edu|.*univ-paris8.*/" - "OR t:/.*up8\.edu|.*univ-paris8.*/)")) - "University" ?u) ;; University-related emails - (,(s-join " " - '("to:CONLANG@LISTSERV.BROWN.EDU" - "OR to:AUXLANG@LISTSERV.BROWN.EDU")) - "Linguistics" ?l) ;; linguistics mailing lists - (,(s-join " " - '("list:ateliers-emacs.framalistes.org" - "OR to:ateliers-paris@emacs-doctor.com" - "OR list:ateliers-paris.emacs-doctor.com")) - "Emacs" ?e) ;; Emacs mailing list - ("maildir:/Sent" "Sent messages" ?s) - ("flag:unread AND NOT flag:trashed" "Unread messages" ?U) - ("date:today..now AND NOT flag:trashed" "Today's messages" ?t) - ("date:7d..now AND NOT flag:trashed" "Last 7 days" ?w) - ("date:1m..now AND NOT flag:trashed" "Last month" ?m) - ("date:1y..now AND NOT flag:trashed" "Last year" ?y) - ("flag:trashed AND NOT flag:trashed" "Trash" ?T) - ("mime:image/* AND NOT flag:trashed" "Messages with images" ?p))) + ;; mu4e-headers-mode config + <> ;; Add a column to display what email account the email belongs to. (add-to-list 'mu4e-header-info-custom '(:account - :name "Account" - :shortname "Account" - :help "Which account this email belongs to" + :name "Phundrak Main" + :shortname "Phundrak" + :help "Main email of phundrak" :function (lambda (msg) (let ((maildir (mu4e-message-field msg :maildir))) @@ -888,23 +879,237 @@ grow the ~mu4e-headers~ buffer when viewing an email. (add-to-list 'mu4e-view-actions '("PDF view" . mu4e-action-open-as-pdf) t))) #+end_src -#+name: mu4e-keybindings +***** Bookmarks +In mu4e, the main focus isn’t really mail directories such as your +inbox, your sent messages and such, but instead you manipulate +bookmarks which will show you emails depending on tags. This mean you +can create some pretty customized bookmarks that go way beyound your +simple inbox, outbox and all. Actually, four of my bookmarks have a +couple of filtering: +- anything in my inbox linked to my university +- the [[https://emacs-doctor.com/lists/listinfo][emacs-doctor mailing list]] (French Emacs mailing list) +- the conlang mailing lists +- and my inbox for any mail not caught by any of these filters +And all of them will have the requirement not to display any trashed +email. Actually, all of my bookmarks will have this requirement, +except for the bookmark dedicated to them as well as my sent emails. +I’ll add these latter requirements later. + +Here are the requirements for my university bookmark. The regex +matches any email address which contains either ~up8.edu~ or +~univ-paris8~, which can be found in email addresses from the University +Paris 8 (my university). +#+name: mu4e-bookmarks-filter-uni +#+headers: :tangle no :exports both :cache yes +#+begin_src emacs-lisp + (string-join '("f:/.*up8\.edu|.*univ-paris8.*/" + "c:/.*up8\.edu|.*univ-paris8.*/" + "t:/.*up8\.edu|.*univ-paris8.*/") + " OR ") +#+end_src + +#+RESULTS[55041e7ce5b7c7b228c9fd6e1c9715f677094c8e]: mu4e-bookmarks-filter-uni +: f:/.*up8.edu|.*univ-paris8.*/ OR c:/.*up8.edu|.*univ-paris8.*/ OR t:/.*up8.edu|.*univ-paris8.*/ + +As for the Emacs-doctor list, I need to match both the current, modern +mailing list address but also its old address. +#+name: mu4e-bookmarks-filter-emacs-list +#+headers: :tangle no :exports both :cache yes +#+begin_src emacs-lisp + (mapconcat (lambda (address) + (mapconcat (lambda (flag) + (concat flag ":" address)) + '("list" "to" "from") + " OR ")) + '("ateliers-emacs.framalistes.org" "ateliers-paris.emacs-doctor.com") + " OR ") +#+end_src + +#+RESULTS[463132dbde653749ac07ee8e1263733ee15b5847]: mu4e-bookmarks-filter-emacs-list +: list:ateliers-emacs.framalistes.org OR to:ateliers-emacs.framalistes.org OR from:ateliers-emacs.framalistes.org OR list:ateliers-paris.emacs-doctor.com OR to:ateliers-paris.emacs-doctor.com OR from:ateliers-paris.emacs-doctor.com + +When it comes to the conlang mailing list, let’s not match anything +from or to them. I’ll also include the auxlang mailing list –I’m not +subscribed anymore, but it’ll keep my inbox clean. +#+name: mu4e-bookmarks-filter-conlang-list +#+headers: :tangle no :exports both :cache yes +#+begin_src emacs-lisp + (mapconcat (lambda (address) + (mapconcat (lambda (flag) + (concat flag ":" address)) + '("from" "to" "list") + " OR ")) + '("CONLANG@LISTSERV.BROWN.EDU" "AUXLANG@LISTSERV.BROWN.EDU") + " OR ") +#+end_src + +#+RESULTS[5565a39c69d99277cffbf4e4be88211ab463543b]: mu4e-bookmarks-filter-conlang-list +: from:CONLANG@LISTSERV.BROWN.EDU OR to:CONLANG@LISTSERV.BROWN.EDU OR list:CONLANG@LISTSERV.BROWN.EDU OR from:AUXLANG@LISTSERV.BROWN.EDU OR to:AUXLANG@LISTSERV.BROWN.EDU OR list:AUXLANG@LISTSERV.BROWN.EDU + +As I said earlier, something that will often come back in my bookmarks +is the emails must not be trashed to appear. I want also to display +junk emails, so I end up with the following rule: +#+name: mu4e-bookmarks-default-filter +#+headers: :tangle no :exports both :cache yes +#+begin_src emacs-lisp + (string-join `("NOT flag:trashed" + ,(format "(%s)" (mapconcat (lambda (maildir) (concat "maildir:" maildir)) + '("/Inbox" "/Junk") + " OR "))) + " AND ") +#+end_src + +#+RESULTS[88f8a5401e240f98fd64fe227699f5ddfe6d5730]: mu4e-bookmarks-default-filter +: NOT flag:trashed AND (maildir:/Inbox OR maildir:/Junk) + +And for the last string-generating code, let’s describe my main inbox: +#+name: mu4e-bookmarks-inbox-filters +#+headers: :exports both :tangle no :cache yes +#+begin_src emacs-lisp + (string-join (cons + <> + `( + ,(format "(%s)" + <>) + ,(format "(%s)" + <>) + ,(format "(%s)" + <>))) + " AND NOT ") +#+end_src + +#+RESULTS[96218e66b6554a2b8b1f09f016d9af1d238bb79b]: mu4e-bookmarks-inbox-filters +: NOT flag:trashed AND (maildir:/Inbox OR maildir:/Junk) AND NOT (from:CONLANG@LISTSERV.BROWN.EDU OR to:CONLANG@LISTSERV.BROWN.EDU OR list:CONLANG@LISTSERV.BROWN.EDU OR from:AUXLANG@LISTSERV.BROWN.EDU OR to:AUXLANG@LISTSERV.BROWN.EDU OR list:AUXLANG@LISTSERV.BROWN.EDU) AND NOT (list:ateliers-emacs.framalistes.org OR to:ateliers-emacs.framalistes.org OR from:ateliers-emacs.framalistes.org OR list:ateliers-paris.emacs-doctor.com OR to:ateliers-paris.emacs-doctor.com OR from:ateliers-paris.emacs-doctor.com) AND NOT (f:/.*up8.edu|.*univ-paris8.*/ OR c:/.*up8.edu|.*univ-paris8.*/ OR t:/.*up8.edu|.*univ-paris8.*/) + +We can finally define our bookmarks! The code reads as follows: +#+name: mu4e-bookmarks +#+begin_src emacs-lisp :tangle no :cache yes + (setq mu4e-bookmarks + `((:name "Inbox" + :key ?i + :query ,(format "%s" + <>)) + (:name "Linguistics" + :key ?l + :query ,(format "%s AND %s" + <> + <>)) + (:name "Emacs" + :key ?e + :query ,(format "%s AND %s" + <> + <>)) + (:name "University" + :key ?u + :query ,(format "%s AND %s" + <> + <>)) + (:name "eshell-info-banner" :key ?E :query ,(format "%s AND %s" + <> + "list:eshell-info-banner.el.Phundrak.github.com")) + (:name "Sent" :key ?s :query "maildir:/Sent") + (:name "All Unread" :key ?U :query "flag:unread AND NOT flag:trashed") + (:name "Today" :key ?t :query "date:today..now AND NOT flag:trashed") + (:name "This Week" :key ?w :query "date:7d..now AND NOT flag:trashed") + (:name "This Month" :key ?m :query "date:1m..now AND NOT flag:trashed") + (:name "This Year" :key ?y :query "date:1y..now AND NOT flag:trashed"))) +#+end_src + +#+RESULTS[3e4e7f607d9f961a27594ddb98037ec25b2c94f7]: mu4e-bookmarks +| :name | Inbox | :key | 105 | :query | NOT flag:trashed AND (maildir:/Inbox OR maildir:/Junk) AND NOT (from:CONLANG@LISTSERV.BROWN.EDU OR to:CONLANG@LISTSERV.BROWN.EDU OR list:CONLANG@LISTSERV.BROWN.EDU OR from:AUXLANG@LISTSERV.BROWN.EDU OR to:AUXLANG@LISTSERV.BROWN.EDU OR list:AUXLANG@LISTSERV.BROWN.EDU) AND NOT (list:ateliers-emacs.framalistes.org OR to:ateliers-emacs.framalistes.org OR from:ateliers-emacs.framalistes.org OR list:ateliers-paris.emacs-doctor.com OR to:ateliers-paris.emacs-doctor.com OR from:ateliers-paris.emacs-doctor.com) AND NOT (f:/.*up8.edu | .*univ-paris8.*/ OR c:/.*up8.edu | .*univ-paris8.*/ OR t:/.*up8.edu | .*univ-paris8.*/) | | | | +| :name | Linguistics | :key | 108 | :query | NOT flag:trashed AND (maildir:/Inbox OR maildir:/Junk) AND NOT (from:CONLANG@LISTSERV.BROWN.EDU OR to:CONLANG@LISTSERV.BROWN.EDU OR list:CONLANG@LISTSERV.BROWN.EDU OR from:AUXLANG@LISTSERV.BROWN.EDU OR to:AUXLANG@LISTSERV.BROWN.EDU OR list:AUXLANG@LISTSERV.BROWN.EDU) AND NOT (list:ateliers-emacs.framalistes.org OR to:ateliers-emacs.framalistes.org OR from:ateliers-emacs.framalistes.org OR list:ateliers-paris.emacs-doctor.com OR to:ateliers-paris.emacs-doctor.com OR from:ateliers-paris.emacs-doctor.com) AND NOT (f:/.*up8.edu | .*univ-paris8.*/ OR c:/.*up8.edu | .*univ-paris8.*/ OR t:/.*up8.edu | .*univ-paris8.*/) AND from:CONLANG@LISTSERV.BROWN.EDU OR to:CONLANG@LISTSERV.BROWN.EDU OR list:CONLANG@LISTSERV.BROWN.EDU OR from:AUXLANG@LISTSERV.BROWN.EDU OR to:AUXLANG@LISTSERV.BROWN.EDU OR list:AUXLANG@LISTSERV.BROWN.EDU | | | | +| :name | Emacs | :key | 101 | :query | NOT flag:trashed AND (maildir:/Inbox OR maildir:/Junk) AND NOT (from:CONLANG@LISTSERV.BROWN.EDU OR to:CONLANG@LISTSERV.BROWN.EDU OR list:CONLANG@LISTSERV.BROWN.EDU OR from:AUXLANG@LISTSERV.BROWN.EDU OR to:AUXLANG@LISTSERV.BROWN.EDU OR list:AUXLANG@LISTSERV.BROWN.EDU) AND NOT (list:ateliers-emacs.framalistes.org OR to:ateliers-emacs.framalistes.org OR from:ateliers-emacs.framalistes.org OR list:ateliers-paris.emacs-doctor.com OR to:ateliers-paris.emacs-doctor.com OR from:ateliers-paris.emacs-doctor.com) AND NOT (f:/.*up8.edu | .*univ-paris8.*/ OR c:/.*up8.edu | .*univ-paris8.*/ OR t:/.*up8.edu | .*univ-paris8.*/) AND list:ateliers-emacs.framalistes.org OR to:ateliers-emacs.framalistes.org OR from:ateliers-emacs.framalistes.org OR list:ateliers-paris.emacs-doctor.com OR to:ateliers-paris.emacs-doctor.com OR from:ateliers-paris.emacs-doctor.com | | | | +| :name | University | :key | 117 | :query | NOT flag:trashed AND (maildir:/Inbox OR maildir:/Junk) AND NOT (from:CONLANG@LISTSERV.BROWN.EDU OR to:CONLANG@LISTSERV.BROWN.EDU OR list:CONLANG@LISTSERV.BROWN.EDU OR from:AUXLANG@LISTSERV.BROWN.EDU OR to:AUXLANG@LISTSERV.BROWN.EDU OR list:AUXLANG@LISTSERV.BROWN.EDU) AND NOT (list:ateliers-emacs.framalistes.org OR to:ateliers-emacs.framalistes.org OR from:ateliers-emacs.framalistes.org OR list:ateliers-paris.emacs-doctor.com OR to:ateliers-paris.emacs-doctor.com OR from:ateliers-paris.emacs-doctor.com) AND NOT (f:/.*up8.edu | .*univ-paris8.*/ OR c:/.*up8.edu | .*univ-paris8.*/ OR t:/.*up8.edu | .*univ-paris8.*/) AND f:/.*up8.edu | .*univ-paris8.*/ OR c:/.*up8.edu | .*univ-paris8.*/ OR t:/.*up8.edu | .*univ-paris8.*/ | +| :name | eshell-info-banner | :key | 69 | :query | NOT flag:trashed AND (maildir:/Inbox OR maildir:/Junk) AND NOT (from:CONLANG@LISTSERV.BROWN.EDU OR to:CONLANG@LISTSERV.BROWN.EDU OR list:CONLANG@LISTSERV.BROWN.EDU OR from:AUXLANG@LISTSERV.BROWN.EDU OR to:AUXLANG@LISTSERV.BROWN.EDU OR list:AUXLANG@LISTSERV.BROWN.EDU) AND NOT (list:ateliers-emacs.framalistes.org OR to:ateliers-emacs.framalistes.org OR from:ateliers-emacs.framalistes.org OR list:ateliers-paris.emacs-doctor.com OR to:ateliers-paris.emacs-doctor.com OR from:ateliers-paris.emacs-doctor.com) AND NOT (f:/.*up8.edu | .*univ-paris8.*/ OR c:/.*up8.edu | .*univ-paris8.*/ OR t:/.*up8.edu | .*univ-paris8.*/) AND list:eshell-info-banner.el.Phundrak.github.com | | | | +| :name | Sent | :key | 115 | :query | maildir:/Sent | | | | | | | +| :name | All Unread | :key | 85 | :query | flag:unread AND NOT flag:trashed | | | | | | | +| :name | Today | :key | 116 | :query | date:today..now AND NOT flag:trashed | | | | | | | +| :name | This Week | :key | 119 | :query | date:7d..now AND NOT flag:trashed | | | | | | | +| :name | This Month | :key | 109 | :query | date:1m..now AND NOT flag:trashed | | | | | | | +| :name | This Year | :key | 121 | :query | date:1y..now AND NOT flag:trashed | | | | | | | + +***** Getting Fancy +I’m not a huge fan of mu4e’s default icons marking my emails, so I’ll +redefine them as follows. Be aware the name of these icons are from +/faicon/ in the package ~all-the-icons~. +#+name: mu4e-fancy-marks-tbl +| Mark | Flag | Icon | +|-----------+------+-------------| +| draft | D | pencil | +| flagged | F | flag | +| new | N | rss | +| passed | P | check | +| replied | R | reply | +| seen | S | eye | +| unread | u | eye-slash | +| trashed | T | trash | +| attach | a | paperclip | +| encrypted | x | lock | +| signed | s | certificate | + +#+name: mu4e-fancy-marks-gen +#+header: :tangle no :exports none :results value +#+begin_src emacs-lisp :var table=mu4e-fancy-marks-tbl + (mapconcat (lambda (line) + (let ((mark (car line)) + (flag (cadr line)) + (icon (caddr line))) + (format "mu4e-headers-%s-mark `(\"%s\" . ,(all-the-icons-faicon \"%s\" :height 0.8))" + mark + flag + icon))) + table + "\n") +#+end_src + +#+RESULTS: +#+begin_example +mu4e-headers-draft-mark `("D" . ,(all-the-icons-faicon "pencil" :height 0.8)) +mu4e-headers-flagged-mark `("F" . ,(all-the-icons-faicon "flag" :height 0.8)) +mu4e-headers-new-mark `("N" . ,(all-the-icons-faicon "rss" :height 0.8)) +mu4e-headers-passed-mark `("P" . ,(all-the-icons-faicon "check" :height 0.8)) +mu4e-headers-replied-mark `("R" . ,(all-the-icons-faicon "reply" :height 0.8)) +mu4e-headers-seen-mark `("S" . ,(all-the-icons-faicon "eye" :height 0.8)) +mu4e-headers-unread-mark `("u" . ,(all-the-icons-faicon "eye-slash" :height 0.8)) +mu4e-headers-trashed-mark `("T" . ,(all-the-icons-faicon "trash" :height 0.8)) +mu4e-headers-attach-mark `("a" . ,(all-the-icons-faicon "paperclip" :height 0.8)) +mu4e-headers-encrypted-mark `("x" . ,(all-the-icons-faicon "lock" :height 0.8)) +mu4e-headers-signed-mark `("s" . ,(all-the-icons-faicon "certificate" :height 0.8)) +#+end_example + +Let’s enable them and set them: +#+name: mu4e-fancy-marks #+begin_src emacs-lisp :tangle no - ;; Unbinding some stuff + (setq mu4e-use-fancy-chars t + <>) +#+end_src + +***** Headers mode +#+name: mu4e-headers-mode +#+begin_src emacs-lisp + (add-hook 'mu4e-headers-mode-hook (lambda () (visual-line-mode -1))) + (add-hook 'mu4e-headers-mode-hook (lambda () (toggle-truncate-lines -1))) +#+end_src + +***** Keybindings +By default, Evil has some pretty annoying keybindings for users of the +bépo layout: ~hjkl~ becomes ~ctsr~ for us. Let’s undefine some of these: +#+name: mu4e-keybindings-undef +#+begin_src emacs-lisp (general-define-key :keymaps '(mu4e-headers-mode-map mu4e-view-mode-map) "s" nil) (general-define-key :states 'normal :keymaps '(mu4e-headers-mode-map mu4e-view-mode-map) - "s" nil) + "s" nil + "," nil) (general-define-key :keymaps 'mu4e-view-mode-map "SPC" nil "S" nil "r" nil "c" nil) - (general-define-key :keymaps 'mu4e-view-mode-map :states 'normal @@ -913,119 +1118,184 @@ grow the ~mu4e-headers~ buffer when viewing an email. "r" nil "c" nil "gu" nil) +#+end_src - ;; View - (general-define-key - :keymaps 'mu4e-view-mode-map - :states 'normal - ) +#+RESULTS: mu4e-keybindings-undef - (general-define-key - :states 'motion - :keymaps 'mu4e-view-mode-map - :prefix "," - "|" #'mu4e-view-pipe - "." '(mu4e-headers-split-adjust-width/body :wk "mu4e-headers width") +Now, let’s define some keybindings for mu4e’s view mode, that is when +we are viewing an email. All these keybindings will reside between the +major-mode specific leader key ~,~ and most of these keybindings can be +described with a simple function: +#+name: mu4e-keybindings-view-tbl +| Keybinding | Function | Description | +|------------+--------------------------------------+--------------------| +| & | mu4e-view-pipe | | +| . | mu4e-headers-split-adjust-width/body | mu4e-headers width | +| a | nil | attachments | +| a& | mu4e-view-pipe-attachment | | +| aa | mu4e-view-attachment-action | | +| ao | mu4e-view-open-attachment | | +| aO | mu4e-view-open-attachment-with | | +| c | nil | compose | +| cc | mu4e-compose-new | | +| ce | mu4e-compose-edit | | +| cf | mu4e-compose-forward | | +| cr | mu4e-compose-reply | | +| cR | mu4e-compose-resend | | +| g | nil | go to | +| gu | mu4e-view-go-to-url | | +| gX | mu4e-view-fetch-url | | +| l | mu4e-show-log | | +| m | nil | mark | +| md | mu4e-view-mark-for-trash | | +| mD | mu4e-view-mark-for-delete | | +| mm | mu4e-view-mark-for-move | | +| mr | mu4e-view-mark-for-refile | | +| mR | mu4e-view-mark-for-read | | +| mu | mu4e-view-mark-for-unread | | +| mU | mu4e-view-mark-for-unmark | | +| t | nil | thread | +| T | nil | toggle | +| Tc | mu4e-view-toggle-hide-cited | | +| Th | mu4e-view-toggle-html | | +| n | mu4e-view-headers-next | | +| N | mu4e-view-headers-next-unread | | +| p | mu4e-view-headers-prev | | +| P | mu4e-view-headers-prev-unread | | - "a" '(nil :wk "attachments") - "a|" #'mu4e-view-pipe-attachment - "aa" #'mu4e-view-attachment-action - "ao" #'mu4e-view-open-attachment - "aO" #'mu4e-view-open-attachment-with +Some functions are however lambdas. They all are written as actions on +the current thread. They all begin with ~,t~, and below you can see each +one of the possible following key can act on a thread: +#+name: mu4e-keybindings-view-lambdas-tbl +| Key | Mark thread as | +|-----+----------------| +| td | trash | +| tD | delete | +| tm | move | +| tr | refile | +| tR | read | +| tu | unread | +| tU | unmark | - "c" '(nil :wk "compose") - "cc" #'mu4e-compose-new - "ce" #'mu4e-compose-edit - "cf" #'mu4e-compose-forward - "cr" #'mu4e-compose-reply - "cR" #'mu4e-compose-resend - - "g" '(nil :wk "go to") - "gu" #'mu4e-view-go-to-url - "gX" #'mu4e-view-fetch-url - - "l" #'mu4e-show-log - - "m" '(nil :wk "mark") - "md" #'mu4e-view-mark-for-trash - "mD" #'mu4e-view-mark-for-delete - "mm" #'mu4e-view-mark-for-move - "mr" #'mu4e-view-mark-for-refile - "mR" #'mu4e-view-mark-for-read - "mu" #'mu4e-view-mark-for-unread - "mU" #'mu4e-view-mark-for-unmark - - "t" '(nil :wk "thread") - "td" '((lambda () - (interactive) - (mu4e-view-mark-thread '(trash))) - :wk "Mark as trash") - "tD" '((lambda () - (interactive) - (mu4e-view-mark-thread '(delete))) - :wk "Mark as delete") - "tm" '((lambda () - (interactive) - (mu4e-view-mark-thread '(move))) - :wk "Mark as move") - "tr" '((lambda () - (interactive) - (mu4e-view-mark-thread '(refile))) - :wk "Mark as refile") - "tR" '((lambda () - (interactive) - (mu4e-view-mark-thread '(read))) - :wk "Mark as read") - "tu" '((lambda () - (interactive) - (mu4e-view-mark-thread '(unread))) - :wk "Mark as unread") - "tU" '((lambda () - (interactive) - (mu4e-view-mark-thread '(unmark))) - :wk "Mark as unmark") - - "T" '(nil :wk "toggle") - "Tc" #'mu4e-view-toggle-hide-cited - "Th" #'mu4e-view-toggle-html - - - "n" #'mu4e-view-headers-next - "N" #'mu4e-view-headers-next-unread - "p" #'mu4e-view-headers-prev - "P" #'mu4e-view-headers-prev-unread) - - ;; Headers - (general-define-key - :prefix "," - :keymaps 'mu4e-headers-mode-map - :states 'normal - "s" '(nil :wk "search") - "ss" #'swiper) - - (general-define-key - :keymaps 'mu4e-headers-mode-map - :states 'motion - "t" #'evil-next-line - "s" #'evil-previous-line - "T" '((lambda () +#+name: mu4e-keybindings-view-lambdas-gen +#+header: :tangle no :exports none :results value +#+begin_src emacs-lisp :var table=mu4e-keybindings-view-lambdas-tbl mode="view" + (mapconcat (lambda (line) + (let ((key (car line)) + (mark (cadr line))) + (format "\"%s\" '((lambda () (interactive) - (mu4e-headers-mark-thread nil '(read))) - :wk "Mark as read")) + (mu4e-%s-mark-thread '%s)) + :wk \"Mark as %s\")" + key mode mark mark))) + table + "\n") +#+end_src - ;; Message +#+RESULTS: mu4e-keybindings-view-lambdas-gen +#+begin_example +"td" '((lambda () + (interactive) + (mu4e-view-mark-thread 'trash)) + :wk "Mark as trash") +"tD" '((lambda () + (interactive) + (mu4e-view-mark-thread 'delete)) + :wk "Mark as delete") +"tm" '((lambda () + (interactive) + (mu4e-view-mark-thread 'move)) + :wk "Mark as move") +"tr" '((lambda () + (interactive) + (mu4e-view-mark-thread 'refile)) + :wk "Mark as refile") +"tR" '((lambda () + (interactive) + (mu4e-view-mark-thread 'read)) + :wk "Mark as read") +"tu" '((lambda () + (interactive) + (mu4e-view-mark-thread 'unread)) + :wk "Mark as unread") +"tU" '((lambda () + (interactive) + (mu4e-view-mark-thread 'unmark)) + :wk "Mark as unmark") +#+end_example + +#+name: mu4e-keybindings-view +#+begin_src emacs-lisp + (general-define-key + :states 'normal + :keymaps 'mu4e-view-mode-map + :prefix "," + <> + <>) +#+end_src + +Some simple keybindings are defined for mu4e’s header mode: +#+name: mu4e-keybindings-headers-tbl +| Key | Function | Description | +|-----+----------+-------------| +| s | swiper | | +| t | nil | thread | + +The keybindings from table [[mu4e-keybindings-view-lambdas-tbl]] will also +be reused for this mode. + +#+name: mu4e-keybindings-header +#+begin_src emacs-lisp + (general-define-key + :states 'normal + :keymaps 'mu4e-headers-mode-map + :prefix "," + <> + <>) +#+end_src + +#+RESULTS: mu4e-keybindings-header + +I will also redefine without a leader key ~ctsr~ in order to be able to +move freely (remember, bépo layout for me). +#+name: mu4e-keybindings-header-no-leader +#+begin_src emacs-lisp + (general-define-key + :keymaps 'mu4e-headers-mode-map + :states 'normal + "c" #'evil-backward-char + "t" #'evil-next-line + "s" #'evil-previous-line + "r" #'evil-forward-char) +#+end_src + +#+RESULTS: mu4e-keybindings-header-no-leader + +Finally, let’s declare a couple of keybindings for when we are +composing a message. This time, all my keybindings are prefixed with +the major-mode leader and call a simple function. +#+name: mu4e-keybindings-message-tbl +| Key | Function | Description | +|-----+-----------------------+-------------| +| , | message-send-and-exit | | +| c | message-send-and-exit | | +| a | message-kill-buffer | | +| k | message-kill-buffer | | +| s | message-dont-send | | +| f | mml-attach-file) | | + +#+name: mu4e-keybindings-message +#+begin_src emacs-lisp :tangle no (general-define-key :states 'normal :keymaps 'message-mode-map :prefix "," - "," #'message-send-and-exit - "c" #'message-send-and-exit - "a" #'message-kill-buffer - "k" #'message-kill-buffer - "s" #'message-dont-send - "f" #'mml-attach-file) + <> #+end_src +#+RESULTS: mu4e-keybindings-message + +**** Composing messages #+begin_src emacs-lisp (use-package org-msg :after (org mu4e)