Move mail configuration back to emacs

This commit is contained in:
Benson Chu 2022-12-05 14:45:02 -06:00
parent 14e97bee65
commit 8042beb9b2
4 changed files with 449 additions and 1 deletions

View file

@ -80,7 +80,7 @@
user-emacs-directory))
(when (file-exists-p "~/.config/emacs-mail.el")
(load-file "~/.config/emacs-mail.el"))
(require 'emacs-mail))
(org-babel-load-file
(expand-file-name "config-ext.org"

30
lisp/emacs-mail.el Normal file
View file

@ -0,0 +1,30 @@
;;; emacs-mail.el --- -*- lexical-binding: t -*-
;; Copyright (C) 2022 Benson Chu
;; Author: Benson Chu <bensonchu457@gmail.com>
;; Created: [2022-12-05 14:47]
;; This file is not part of GNU Emacs
;; This program is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
;;; Commentary:
;;; Code
(require 'notmuch-config)
(provide 'emacs-mail)
;;; emacs-mail.el ends here

View file

@ -0,0 +1,294 @@
;;; notmuch-config.el --- -*- lexical-binding: t -*-
;; Copyright (C) 2022 Benson Chu
;; Author: Benson Chu <bensonchu457@gmail.com>
;; Created: [2022-12-05 14:42]
;; This file is not part of GNU Emacs
;; This program is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
;;; Commentary:
;;; Code:
(require 'use-package)
(setq user-mail-address "bensonchu457@fastmail.com"
user-full-name "Benson Chu")
(setq smtpmail-smtp-server "smtp.fastmail.com"
smtpmail-smtp-service 465
smtpmail-stream-type 'ssl
send-mail-function 'smtpmail-send-it
message-send-mail-function 'smtpmail-send-it)
(mailcap-add "text/html" "/usr/bin/xdg-open %s ")
(setq mail-specify-envelope-from t
message-sendmail-envelope-from 'header
mail-envelope-from 'header)
(use-package notmuch
:commands notmuch
:bind (;; :map notmuch-message-mode-map
;; ("C-c C-c" . #'my/choose-email-address-and-send)
:map notmuch-search-mode-map
("z" . #'notmuch-search-tree-current-thread)
("A" . #'notmuch-search-show-all)
("D" . #'notmuch-search-delete-all)
("f" . #'notmuch-search-filter-for-domain)
("F" . #'notmuch-search-filter-for-sender)
("d" . #'my/notmuch-search-delete-mail)
("<mouse-1>" . nil)
:map notmuch-tree-mode-map
("<tab>" . #'notmuch-tree-explore-here)
("<down>" . #'notmuch-tree-next-message)
("<up>" . #'notmuch-tree-prev-message)
("U" . #'notmuch-tree-unfold-all)
("u" . #'notmuch-tree-up-thread)
("N" . #'notmuch-tree-next-sibling)
("P" . #'notmuch-tree-prev-sibling)
("t" . #'notmuch-tree-toggle-folding-thread)
("/" . #'notmuch-tree-undo-read)
("F" . #'notmuch-tree-focus)
("S-SPC" . #'notmuch-tree-scroll-message-window-back))
:config
(require 'notmuch-mode-line)
(require 'notmuch-nav)
(require 'notmuch-tree-hide)
(require 'notmuch-tree)
;; (require 'notmuch-fold)
(defun my/notmuch-search-delete-mail ()
(interactive)
(cond ((string-match-p "tag:inbox" notmuch-search-query-string)
(notmuch-search-add-tag '("-inbox" "+archive")))
(t
(user-error "Haven't designated a tag for deleting mail in this folder")))
(next-line))
(setq notmuch-draft-tags '("+draft"))
(custom-set-faces
'(notmuch-tree-match-tree-face ((t (:family "Source Code Pro"))) t)
'(notmuch-tree-no-match-tree-face ((t (:family "Source Code Pro"))) t))
(set-face-attribute 'notmuch-search-unread-face nil :foreground "white")
(set-face-attribute 'notmuch-message-summary-face nil :background "steel blue" :foreground "snow")
(add-to-list 'notmuch-search-line-faces
'("deleted" . font-lock-comment-face))
(defun notmuch-search-show-all ()
(interactive)
(let* ((query (replace-regexp-in-string "date:[^ ]+" "" notmuch-search-query-string))
(noand (replace-regexp-in-string "^ *and +" "" query))
(noand2 (replace-regexp-in-string " +and *" "" query)))
(notmuch-search noand2)))
(defun notmuch-search-tree-current-thread (arg)
(interactive "P")
(let* ((thread-id (notmuch-search-find-thread-id))
(input (notmuch-read-query (concat "Notmuch tree: " thread-id " and "))))
(notmuch-tree thread-id (unless (zerop (length input)) input) nil nil nil nil nil (unless arg #'notmuch-tree-hide-dead-trees))))
(defun notmuch-tree-focus (arg)
(interactive "P")
(notmuch-tree notmuch-tree-basic-query (notmuch-tree-get-message-id) nil nil nil nil nil (if (not arg) #'notmuch-tree-hide-dead-trees #'notmuch-tree-show-trail-and-alive-children)))
(defun notmuch-tree-undo-read (arg)
(interactive "P")
(if arg
(save-excursion
(beginning-of-buffer)
(while (text-property-search-forward
'face 'notmuch-tag-deleted
#'(lambda (value prop)
(if (consp prop)
(member value prop)
(eq value prop))))
(notmuch-tree-add-tag '("+unread"))))
(notmuch-tree-add-tag '("+unread"))
(next-line)))
(setq notmuch-search-oldest-first nil
notmuch-saved-searches
'((:name "inbox" :query "tag:inbox" :key "i")
;; (:name "inbox today" :query "date:2020-07-25.. and tag:inbox" :key "t")
(:name "migration" :query "tag:migration" :key "m")
(:name "work" :query "tag:work" :key "w")
(:name "emacs-devel" :query "tag:lists/emacs-devel and date:6M.." :key "e" :sort-order newest-first)
(:name "emacs bugs" :query "tag:lists/bug-gnu-emacs and date:30d.." :key "E")
(:name "emacs help" :query "tag:lists/help-gnu-emacs and date:30d.." :key "h")
(:name "org-mode" :query "tag:lists/emacs-orgmode and date:6M.." :key "o" :sort-order newest-first)
(:name "unread" :query "tag:unread" :key "u")
(:name "flagged" :query "tag:flagged" :key "f")
(:name "cs" :query "tag:cs" :key "c")
(:name "receipts" :query "tag:receipts" :key "R")
(:name "tracking" :query "tag:tracking" :key "t")
(:name "voicemail" :query "from:vm@italkbb.com" :key "v")
;; (:name "sent" :query "tag:sent" :key "s")
;; (:name "drafts" :query "tag:draft" :key "d")
(:name "all mail" :query "*" :key "a")))
(defun my/choose-email-address-and-send ()
(interactive)
(let ((resp (completing-read "Which email? " '("bensonchu457@fastmail.com" "bensonchu457@gmail.com") nil t "^")))
(setq smtpmail-smtp-server
(pcase resp
("bensonchu457@gmail.com" "smtp.gmail.com")
("bensonchu457@fastmail.com" "smtp.fastmail.com")))
(notmuch-mua-send-and-exit)))
(add-to-list 'notmuch-tagging-keys
'("R" ("-inbox" "+recruiting") "Recruiting"))
(setf (cdr (assoc "d" notmuch-tagging-keys))
'(("+deleted") "Delete"))
(advice-add #'notmuch-tag-jump :after #'(lambda (&rest args) (next-line)))
(defun notmuch-search-filter-for-domain ()
(interactive)
(notmuch-search-show-thread)
(let* ((author (notmuch-show-get-from)))
(notmuch-bury-or-kill-this-buffer)
(string-match (rx (and (group (+ (not (any "." "@" "<")))
"."
(+ (not (any "." "@" "<" ">"))))
(or ">" eol)))
author)
(notmuch-search-filter (format "from:%s" (match-string 1 author)))))
(defun notmuch-search-filter-for-sender ()
(interactive)
(notmuch-search-show-thread)
(let ((author (notmuch-show-get-from)))
(notmuch-bury-or-kill-this-buffer)
(notmuch-search-filter (format "from:%s" author))))
(defun notmuch-search-delete-all ()
(interactive)
(let ((line-count (save-excursion (end-of-buffer) (line-number-at-pos (point)))))
(when (or (< line-count 20)
(y-or-n-p "Are you sure? There seems to be a lot of emails... "))
(notmuch-search-tag-all '("+deleted")))))
(defun notmuch-add-child (child-id)
(interactive (list (read-string (format "Message id of new child (default: %s): "
(current-kill 0))
nil nil (current-kill 0))))
(let ((parent-id (notmuch-show-get-message-id t))
(child-file
(save-window-excursion
(notmuch-show (format "id:%s" child-id))
(notmuch-show-get-filename))))
(with-current-buffer (find-file-noselect child-file)
(beginning-of-buffer)
(if (save-excursion (re-search-forward "^In-Reply-To: " nil t))
(error "File already has reply message")
(save-excursion
(re-search-forward "^Date: ")
(end-of-line)
(insert (format "\nIn-Reply-To: <%s>"
parent-id))
(save-buffer)))
(re-search-forward "^Message-ID: <\\(.*\\)>$")
(message (match-string 1)))
(notmuch-refresh-file child-id)))
(defun notmuch-refresh-file (id)
(interactive (list (read-string "Which id? ")))
(let ((thread-id
(replace-regexp-in-string
"\\n" ""
(shell-command-to-string
(format "notmuch search --output=threads id:%s" id)))))
(shell-command (format "notmuch reindex %s" thread-id)))
(shell-command (format "notmuch reindex id:%s" id)))
(defun notmuch-show-goto-file ()
(interactive)
(find-file (notmuch-show-get-filename))))
(use-exwm
:config
;; (defvar offlineimap-timer nil)
;; (defvar offlineimap-process nil)
;; (defun run-offlineimap ()
;; (interactive)
;; (if (and (processp offlineimap-process)
;; (process-live-p offlineimap-process))
;; (message "offlineimap already running...")
;; (message "offlineimap starting...")
;; (when (and (timerp offlineimap-timer)
;; (not (timer--triggered offlineimap-timer)))
;; (cancel-timer offlineimap-timer))
;; (call-process-shell-command "timedatectl" nil "*offlineimap-output*")
;; (set-process-sentinel
;; (setq offlineimap-process
;; (start-process-shell-command "offlineimap" "*offlineimap-output*" "offlineimap"))
;; #'(lambda (process event)
;; (when (string-match-p "exited abnormally with code 1" event)
;; (with-current-buffer (process-buffer offlineimap-process)
;; (when (string-match-p "get_password_emacs"(buffer-string))
;; (erase-buffer)
;; (message "Oops, didn't grab a password. ")
;; (setq offlineimap-timer (run-with-timer 300 nil #'run-offlineimap)))))
;; (when (string-match-p "^finished" event)
;; (message "Offlineimap finished")
;; (setq offlineimap-timer (run-with-timer 300 nil #'run-offlineimap)))))))
;; (defun stop-offlineimap ()
;; (interactive)
;; (when (timerp offlineimap-timer)
;; (cancel-timer offlineimap-timer))
;; (when (processp offlineimap-process)
;; (set-process-sentinel offlineimap-process
;; nil)))
;; (add-to-list 'exwm-init-hook
;; #'run-offlineimap
;; t)
(defvar mbsync-timer nil)
(defvar mbsync-process nil)
(defun run-mbsync ()
(interactive)
(if (and (processp mbsync-process)
(process-live-p mbsync-process))
(message "mbsync already running...")
(message "mbsync starting...")
(when (and (timerp mbsync-timer)
(not (timer--triggered mbsync-timer)))
(cancel-timer mbsync-timer))
(call-process-shell-command "timedatectl" nil "*mbsync-output*")
(set-process-sentinel
(setq mbsync-process
(start-process-shell-command "mbsync" "*mbsync-output*"
"~/bin/update-mail.sh"))
#'(lambda (process event)
(when (string-match-p "exited abnormally with code 1" event)
(with-current-buffer (process-buffer mbsync-process)
(when (string-match-p "get_password_emacs"(buffer-string))
(erase-buffer)
(message "Oops, didn't grab a password. ")
(setq mbsync-timer (run-with-timer 300 nil #'run-mbsync)))))
(when (string-match-p "^finished" event)
(message "mbsync finished")
(setq mbsync-timer (run-with-timer 300 nil #'run-mbsync))))))))
(provide 'notmuch-config)
;;; notmuch-config.el ends here

View file

@ -0,0 +1,124 @@
;;; notmuch-mode-line.el --- -*- lexical-binding: t -*-
;; Copyright (C) 2022 Benson Chu
;; Author: Benson Chu <bensonchu457@gmail.com>
;; Created: [2022-12-05 14:42]
;; This file is not part of GNU Emacs
;; This program is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
;;; Commentary:
;;; Code:
(defvar notmuch-unread-mode-line-string "")
(defvar notmuch-unread-inbox-count -1)
(defun notmuch-open-inbox ()
(interactive)
(notmuch-search "tag:inbox" t))
(defvar mode-line-notmuch-keymap
(let ((map (make-sparse-keymap)))
(define-key map [mode-line mouse-1] 'notmuch-open-inbox)
map))
(defconst my-mode-line-map
(let ((map (make-sparse-keymap)))
(define-key map [mode-line down-mouse-1]
(make-sparse-keymap))
map))
(defun notmuch-count-query (query)
(->> query
(notmuch-command-to-string "count")
(replace-regexp-in-string "\n" "")
(string-to-number)))
(defun notmuch-get-unread-string ()
(let ((count
(setq notmuch-unread-inbox-count
(notmuch-count-query "tag:inbox AND tag:unread")))
(all (notmuch-count-query "tag:inbox")))
(format " %s [%d/%d] "
(if (zerop count) "" "")
count all)))
(defun notmuch-update-mode-string ()
(setq notmuch-unread-mode-line-string
(notmuch-get-unread-string)))
(add-to-list 'global-mode-string
;; TODO: switch to using :propertize
`(:eval (propertize notmuch-unread-mode-line-string
'help-echo ,(purecopy "mouse-1: Open inbox")
'face (when (not (zerop notmuch-unread-inbox-count)) font-lock-warning-face)
'mouse-face 'mode-line-highlight
'local-map mode-line-notmuch-keymap))
t)
;; (setq global-mode-string (butlast global-mode-string))
(run-at-time nil 10 'notmuch-update-mode-string)
;; (defcustom doom-modeline-notmuch nil
;; "Whether display the notmuch notifications."
;; :type 'boolean
;; :group 'doom-modeline)
;; (doom-modeline-def-segment notmuch
;; "Show notifications of any unread emails in `notmuch'."
;; (when (and doom-modeline-notmuch
;; (doom-modeline--segment-visible 'notmuch)
;; (bound-and-true-p notmuch-unread-inbox-count)
;; (numberp notmuch-unread-inbox-count)
;; ;; don't display if the unread mails count is zero
;; ;; (> notmuch-unread-inbox-count 0)
;; )
;; (concat
;; doom-modeline-spc
;; (propertize
;; (concat
;; (doom-modeline-icon 'material "email" "📧" "#"
;; :face 'doom-modeline-notification
;; :height 1.1 :v-adjust -0.2)
;; doom-modeline-vspc
;; (propertize
;; (if (> notmuch-unread-inbox-count doom-modeline-number-limit)
;; (format "%d+" doom-modeline-number-limit)
;; (number-to-string notmuch-unread-inbox-count))
;; 'face '(:inherit
;; (doom-modeline-unread-number doom-modeline-notification))))
;; 'mouse-face 'doom-modeline-highlight
;; 'keymap '(mode-line keymap
;; (mouse-1 . notmuch-open-inbox))
;; 'help-echo (concat (if (= notmuch-unread-inbox-count 1)
;; "You have an unread email"
;; (format "You have %s unread emails" notmuch-unread-inbox-count))
;; "\nClick here to view "
;; (if (= notmuch-unread-inbox-count 1) "it" "them")))
;; doom-modeline-spc)))
;; (doom-modeline-def-segment notmuch
;; "Display battery status."
;; (when (and (doom-modeline--segment-visible 'notmuch)
;; (bound-and-true-p notmuch-unread-mode-line-string))
;; (concat doom-modeline-spc
;; notmuch-unread-mode-line-string
;; doom-modeline-spc)))
(provide 'notmuch-mode-line)
;;; notmuch-mode-line.el ends here