summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorjao <jao@gnu.org>2021-11-08 00:47:34 +0000
committerjao <jao@gnu.org>2021-11-08 00:48:11 +0000
commit66988068b79ca5677c4c7f4fed66a815a1b33d53 (patch)
tree2ef25a295f7cebf5f21bf82167b30510321e1d26
parent766c3a3b66f7af3f1d32306b8c494d2a6abe5662 (diff)
downloadelibs-66988068b79ca5677c4c7f4fed66a815a1b33d53.tar.gz
elibs-66988068b79ca5677c4c7f4fed66a815a1b33d53.tar.bz2
gnus + nnml + notmuch
-rw-r--r--email.org36
-rw-r--r--gnus.org237
-rw-r--r--init.org2
-rw-r--r--lib/net/jao-maildir.el28
-rw-r--r--lib/themes/jao-light-theme.el11
-rw-r--r--notmuch.org27
6 files changed, 202 insertions, 139 deletions
diff --git a/email.org b/email.org
index a9bc09c..8dec9ac 100644
--- a/email.org
+++ b/email.org
@@ -1,5 +1,5 @@
#+property: header-args:emacs-lisp :lexical t :tangle yes :comments yes :results silent :shebang ";; -*- lexical-binding: t; -*-" :tangle-mode (identity #o644)
-#+title: email handling (message mode, bbdb, notmuch)
+#+title: email handling (message mode, bbdb, gnus, notmuch)
#+auto_tangle: t
* message mode
@@ -209,7 +209,6 @@
#+end_src
* multipart html renderer
#+begin_src emacs-lisp
-
(defun jao-w3m-html-renderer (handle)
(let ((w3m-message-silent t)
(mm-w3m-safe-url-regexp nil))
@@ -220,7 +219,14 @@
(shr-use-colors nil))
(mm-shr handle))))))
+ (defun jao-shr-html-renderer (handle)
+ (let ((shr-use-fonts nil)
+ (shr-use-colors nil)
+ (fill-column 120))
+ (mm-shr handle)))
+
;; (setq mm-text-html-renderer #'jao-w3m-html-renderer)
+ (setq mm-text-html-renderer #'jao-shr-html-renderer)
#+end_src
* bbdb
#+begin_src emacs-lisp
@@ -346,3 +352,29 @@
(when (eq jao-afio-mail-function 'notmuch)
(jao-load-org "notmuch"))
#+end_src
+*** consult
+ #+begin_src emacs-lisp
+ (jao-load-path "consult-notmuch")
+ (require 'consult-notmuch)
+ (consult-customize consult-notmuch :preview-key 'any)
+
+ (defvar jao-consult-notmuch-history nil)
+
+ (defun jao-consult-notmuch-folder (&optional tree folder)
+ (interactive "P")
+ (let* ((root "~/var/mail/")
+ (folder (if folder
+ (file-name-as-directory folder)
+ (completing-read "Folder: "
+ jao-mailbox-folders
+ nil nil nil
+ jao-consult-notmuch-history
+ ".")))
+ (folder (replace-regexp-in-string "/\\(.\\)" ".\\1" folder))
+ (init (read-string "Initial query: "))
+ (init (format "folder:/%s/ %s" folder init)))
+ (if tree (consult-notmuch-tree init) (consult-notmuch init))))
+
+ (with-eval-after-load "notmuch-hello"
+ (define-key notmuch-hello-mode-map "f" #'jao-consult-notmuch-folder))
+ #+end_src
diff --git a/gnus.org b/gnus.org
index ae52fe7..1a3e176 100644
--- a/gnus.org
+++ b/gnus.org
@@ -11,7 +11,7 @@
(defvar jao-gnus-use-gmane nil)
(defvar jao-gnus-use-nnml nil)
(defvar jao-gnus-use-maildirs nil)
- (defvar jao-gnus-use-nnnm t)
+ (defvar jao-gnus-use-nnnm nil)
#+end_src
* Startup and kill
#+begin_src emacs-lisp
@@ -121,21 +121,32 @@
[[info:gnus#Searching][info:gnus#Searching]]
#+begin_src emacs-lisp
(setq gnus-search-use-parsed-queries t
- jao-gnus-search-prefix (expand-file-name "~/var/mail/"))
+ gnus-search-notmuch-raw-queries-p nil)
(with-eval-after-load "gnus-search"
- (add-to-list 'gnus-search-expandable-keys "list-id"))
+ (add-to-list 'gnus-search-expandable-keys "list")
+ (add-to-list 'gnus-search-expandable-keys "Newsgroups")
+
+ (cl-defmethod gnus-search-transform-expression ((engine gnus-search-notmuch)
+ (expr (head list)))
+ (message "Transforming: %S" expr)
+ (format "List:%s" (gnus-search-transform-expression engine (cdr expr)))))
+
+ (defun jao-gnus--notmuch-engine (prefix config)
+ (let ((prefix (file-name-as-directory (expand-file-name prefix "~")))
+ (config (expand-file-name config gnus-home-directory)))
+ `(gnus-search-engine gnus-search-notmuch
+ (remove-prefix ,prefix)
+ (config-file ,config))))
#+end_src
* News server
#+begin_src emacs-lisp
(setq gnus-select-method
- (cond (jao-gnus-use-leafnode
- '(nntp "localhost"
- (gnus-search-engine
- gnus-search-notmuch
- (remove-prefix "/home/jao/var/news/")
- (config-file "/home/jao/.notmuch-config-news"))))
- (jao-gnus-use-gmane '(nntp "news.gmane.io"))
- (t '(nnnil ""))))
+ (cond
+ (jao-gnus-use-leafnode
+ `(nntp "localhost"
+ ,(jao-gnus--notmuch-engine "var/news" "notmuch-news.config")))
+ (jao-gnus-use-gmane '(nntp "news.gmane.io"))
+ (t '(nnnil ""))))
(setq gnus-secondary-select-methods '())
@@ -160,47 +171,62 @@
'(nnimap "gandi" (nnimap-address "mail.gandi.net"))))
#+end_src
* Local mail servers
- #+begin_src emacs-lisp
- (setq mail-sources nil
- gnus-message-archive-group nil
- nnimap-quirks nil)
-
- (setq nnml-get-new-mail t
- nnmail-treat-duplicates 'delete
- nnmail-scan-directory-mail-source-once t
- nnmail-cache-accepted-message-ids t
- nnmail-message-id-cache-length 50000
- nnmail-cache-ignore-groups ".*\\(trove\\.\\|feeds\\.\\|spamish\\).*"
- nnmail-split-fancy-with-parent-ignore-groups nil
- nnmail-crosspost t)
-
- (setq nnmail-resplit-incoming t
- nnmail-mail-splitting-decodes t
- nnmail-split-methods 'nnmail-split-fancy)
-
- (when jao-gnus-use-nnml
- (add-to-list 'gnus-secondary-select-methods '(nnml "")))
-
- (when jao-gnus-use-maildirs
- (add-to-list 'gnus-secondary-select-methods
- `(nnmaildir "bml" (directory "~/var/mail/bigml/")
- (gnus-search-engine gnus-search-notmuch
- (remove-prefix
- "/home/jao/var/mail/"))))
- (add-to-list 'gnus-secondary-select-methods
- '(nnmaildir "jao" (directory "~/var/mail/jao/")
- (gnus-search-engine gnus-search-notmuch
- (remove-prefix
- "/home/jao/var/mail/"))))
- (add-to-list 'gnus-secondary-select-methods
- '(nnmaildir "feeds" (directory "~/var/mail/feeds/")
- (gnus-search-engine gnus-search-notmuch
- (remove-prefix
- "/home/jao/var/mail/")))))
+*** nnml
+ #+begin_src emacs-lisp
+ (setq mail-sources '((group))
+ gnus-message-archive-group nil
+ nnimap-quirks nil)
+
+ (setq nnml-get-new-mail t
+ nnmail-treat-duplicates 'delete
+ nnmail-scan-directory-mail-source-once nil
+ nnmail-cache-accepted-message-ids t
+ nnmail-message-id-cache-length 50000
+ nnmail-split-fancy-with-parent-ignore-groups nil
+ nnmail-use-long-file-names t
+ nnmail-crosspost t)
+
+ (setq nnmail-resplit-incoming nil
+ nnmail-mail-splitting-decodes t
+ nnmail-split-methods 'nnmail-split-fancy)
+
+ (when jao-gnus-use-nnml
+ (let ((mails (file-name-as-directory mail-source-directory)))
+ (add-to-list
+ 'gnus-secondary-select-methods
+ `(nnml "" ,(jao-gnus--notmuch-engine mails "notmuch.config")))))
+
+ (defun jao-gnus-nnml--add-maildir (dir)
+ (dolist (f (directory-files (concat "~/var/mail/" dir) t "[^\\.]"))
+ (add-to-list 'gnus-parameters
+ `(,(concat "nnml:" dir "." (file-name-base f))
+ (mail-source . (maildir :path ,f)))
+ t)))
- (when (and jao-gnus-use-nnnm (require 'nnnm nil t))
- (add-to-list 'gnus-secondary-select-methods '(nnnm "")))
- #+end_src
+ #+end_src
+*** maildirs
+ #+begin_src emacs-lisp
+ (when jao-gnus-use-maildirs
+ (defun jao-gnus--maildir (dir)
+ (let ((root (concat "/tmp/mboxes/" dir "/"))
+ (config (concat "~/.notmuch-config-" dir)))
+ (add-to-list
+ 'gnus-secondary-select-methods
+ `(nnmaildir ,dir (directory ,root)
+ (gnus-search-engine gnus-search-notmuch
+ (remove-prefix ,root)
+ (config-file ,config))))))
+ (jao-gnus--maildir "jao")
+ (jao-gnus--maildir "feeds")
+ (jao-gnus--maildir "bigml"))
+
+ #+end_src
+*** nnnm
+ #+begin_src emacs-lisp
+ (when (and jao-gnus-use-nnnm (require 'nnnm nil t))
+ (add-to-list 'gnus-secondary-select-methods '(nnnm "")))
+
+ #+end_src
* Demons and notifications
#+begin_src emacs-lisp
(setq mail-user-agent 'gnus-user-agent)
@@ -217,20 +243,19 @@
(setq gnus-check-new-newsgroups nil)
(defvar jao-gnus-tracked-groups
- `(("nnimap:bigml/inbox" "B" jao-themes-f00)
- ("nnselect:bigml-bugs" "b" jao-themes-error)
- ("nnimap:bigml/support" "S" default)
- ("nnimap:jao/inbox" "I" jao-themes-f01)
- ("nnimap:bigml/[^ibs]" "W" jao-themes-dimm)
- ("nnimap:jao" "J" jao-themes-dimm)
- ("nnimap:local" "l" jao-themes-dimm)
- ("nnimap:feeds/papers" "P" jao-themes-dimm)
- ("^gmane\\.emacs\\|nnimap:feeds/emacs" "E" jao-themes-dimm)
- (,(format "nnimap:feeds/%s"
- (regexp-opt (seq-difference (directory-files "~/var/mail/feeds")
- '("emacs" "papers" "trove" "." ".."))))
- "F" jao-themes-dimm)
- ("^gmane\\.[^e]\\|^gwene" "N" jao-themes-dimm)))
+ (let ((feeds (seq-difference (directory-files "~/var/mail/feeds")
+ '("emacs" "papers" "trove" "." ".."))))
+ `(("nnml:bigml.inbox" "B" jao-themes-f00)
+ ("nnselect:bigml.bugs" "b" jao-themes-error)
+ ("nnml:bigml.support" "S" default)
+ ("nnml:jao.drivel" "I" jao-themes-f01)
+ ("nnml:bigml.[^ibs]" "W" jao-themes-dimm)
+ ("nnml:jao.[^d]" "J" jao-themes-dimm)
+ ("nnml:local" "l" jao-themes-dimm)
+ ("nnml:feeds.papers" "P" jao-themes-dimm)
+ ("^gmane\\.emacs\\|nnml:feeds.emacs" "E" jao-themes-dimm)
+ (,(format "nnml:feeds.%s" (regexp-opt feeds)) "F" jao-themes-dimm)
+ ("^gmane\\.[^e]\\|^gwene" "N" jao-themes-dimm))))
(defun jao-gnus--unread-counts ()
(seq-reduce (lambda (r g)
@@ -311,15 +336,15 @@
#+begin_src emacs-lisp
(with-eval-after-load "randomsig"
(with-eval-after-load "gnus-sum"
- (define-key gnus-summary-save-map "-"
- 'gnus/randomsig-summary-read-sig)))
+ (define-key gnus-summary-save-map "-" 'gnus/randomsig-summary-read-sig)))
#+end_src
*** notmuch -> gnus
#+begin_src emacs-lisp
(defun jao-notmuch-goto-message-in-gnus ()
"Open a summary buffer containing the current notmuch article."
(interactive)
- (let ((group (jao-maildir-file-to-group (notmuch-show-get-filename)))
+ (let ((group (jao-maildir-file-to-group (notmuch-show-get-filename)
+ mail-source-directory))
(message-id (replace-regexp-in-string "^id:"
""
(notmuch-show-get-message-id))))
@@ -386,6 +411,12 @@
(define-key gnus-group-mode-map "zz" #'jao-consult-notmuch-folder)
(define-key gnus-group-mode-map "zZ" #'consult-notmuch)
+ (jao-transient-major-mode gnus-group
+ ["Search"
+ ("zc" "consult search" consult-notmuch)
+ ("zf" "consult folder search" jao-consult-notmuch-folder)
+ ("g" "gnus search" gnus-group-read-ephemeral-search-group)])
+
(defun jao-gnus--first-group ()
(when (derived-mode-p 'gnus-group-mode)
(gnus-group-first-unread-group)))
@@ -398,32 +429,42 @@
(setq gnus-permanently-visible-groups "^nnselect:.*")
(setq gnus-parameters
- `(("^nnnm:jao/.*"
- (jao-gnus--trash-group "nnnm:jao/trash")
- (jao-gnus--spam-group "nnnm:jao/spam")
- (jao-gnus--archiving-group "nnnm:jao/trove"))
- ("^nnnm:\\(jao\\|bigml\\)/\\(trash\\|spam\\)"
+ `(("nnml:local"
+ (auto-expire . t)
+ (total-expire . t)
+ (expiry-wait . 1)
+ (expiry-target . delete)
+ (mail-source . (file :path "/var/mail/jao")))
+ ("nnml:jao\\..*"
+ (jao-gnus--trash-group "nnml:jao.trash")
+ (jao-gnus--spam-group "nnml:jao.spam")
+ (jao-gnus--archiving-group "nnml:jao.trove"))
+ ("nnml:\\(jao\\|bigml\\)\\.\\(trash\\|spam\\)"
(gcc-self . nil)
(auto-expire . t)
(total-expire . t)
(expiry-wait . 1)
(jao-gnus--trash-group nil)
(expiry-target . delete))
- ("^nnnm:.*/\\(inbox\\|hacking\\)"
+ ("^nnml:.*\\.\\(inbox\\|hacking\\)"
(gcc-self . t))
- ("^nnnm:bigml/.*"
+ ("nnml:bigml\\..*"
(gcc-self . t)
(posting-style (address "jao@bigml.com"))
- (jao-gnus--archiving-group "nnnm:bigml/trove")
- (jao-gnus--spam-group "nnnm:bigml/spam"))
- ;; ("^nnnm:bigml/inbox"
- ;; (auto-expire . t)
- ;; (total-expire . t)
- ;; (expiry-wait . 7)
- ;; (expiry-target . "nnnm:bigml/trove"))
- ("^nnnm:bigml/support"
- (posting-style (address "support@bigml.com")))
- (,(format "^nnnm:bigml/%s"
+ (jao-gnus--trash-group "nnml:jao.trash")
+ (jao-gnus--archiving-group "nnml:bigml.trove")
+ (jao-gnus--spam-group "nnml:bigml.spam"))
+ ("nnml:bigml.inbox"
+ (auto-expire . t)
+ (total-expire . t)
+ (expiry-wait . 7)
+ (expiry-target . "nnml:bigml.trove"))
+ ("nnml:bigml.trove"
+ (auto-expire . t)
+ (total-expire . t)
+ (expiry-target . delete)
+ (expiry-wait . 365))
+ (,(format "^nnml:bigml\\.%s"
(regexp-opt '("reports" "deploys" "lists" "drivel")))
(jao-gnus--trash-group nil)
(gcc-self . nil)
@@ -431,21 +472,24 @@
(total-expire . t)
(expiry-wait . 3)
(expiry-target . delete))
- ("^nnnm:\\(jao/\\(think\\|drivel\\)\\|local\\|emacs.*\\)"
+ ("nnml:jao\\.drivel"
(auto-expire . t)
(total-expire . t)
- (expiry-wait . 1)
+ (expiry-wait . 3)
(expiry-target . delete))
- ("^nnnm:feeds/.*"
+ ("nnml:feeds\\..*"
(auto-expire . t)
(total-expire . t)
(expiry-wait . 7)
(expiry-target . delete)
- (jao-gnus--archiving-group "nnnm:feeds/trove"))
- ("^nnnm:feeds/\\(news\\|emacs\\|prog\\)$" (expiry-wait . 1))
- ("^nnnm:feeds/trove"
+ (jao-gnus--archiving-group "nnml:feeds/trove"))
+ ("nnml:feeds\\.\\(news\\|emacs\\|prog\\)$" (expiry-wait . 1))
+ ("nnml:feeds\\.trove"
(auto-expire . nil)
(total-expire . nil))))
+
+ (dolist (dir '("jao" "bigml" "feeds")) (jao-gnus-nnml--add-maildir dir))
+
#+end_src
* Summary buffer
*** Configuration
@@ -666,17 +710,17 @@
(shr-render-region begin (1- (point)))))))))
(add-hook 'gnus-part-display-hook 'jao-gnus-remove-anchors)
+ (defvar-local jao-gnus--images-p nil)
(defun jao-gnus-show-images ()
(interactive)
(save-window-excursion
(gnus-summary-select-article-buffer)
(save-excursion
- (let ((pos (next-single-property-change (point) 'w3m-image)))
- (if (not pos)
- (gnus-article-show-images)
- (goto-char pos)
- (w3m-toggle-inline-images))))))
+ (setq jao-gnus--images-p (not jao-gnus--images-p))
+ (if jao-gnus--images-p
+ (gnus-article-show-images)
+ (gnus-article-remove-images)))))
#+end_src
*** Follow links and enclosures
#+begin_src emacs-lisp
@@ -718,6 +762,7 @@
(define-key gnus-summary-mode-map "V" 'scroll-other-window-down)
(define-key gnus-summary-mode-map "X" 'jao-gnus-arXiv-capture)
(define-key gnus-summary-mode-map "e" 'jao-gnus-open-enclosure)
+ (define-key gnus-summary-mode-map "\C-l" nil)
(with-eval-after-load "w3m"
(define-key gnus-article-mode-map "\C-ci" 'w3m-view-image)
diff --git a/init.org b/init.org
index 9c347c7..0f17566 100644
--- a/init.org
+++ b/init.org
@@ -1593,7 +1593,7 @@
#+end_src
* Email
#+begin_src emacs-lisp
- (setq jao-afio-mail-function 'notmuch)
+ (setq jao-afio-mail-function 'gnus)
(jao-load-org "email")
#+end_src
* PDFs
diff --git a/lib/net/jao-maildir.el b/lib/net/jao-maildir.el
index d7cd4d6..18a1725 100644
--- a/lib/net/jao-maildir.el
+++ b/lib/net/jao-maildir.el
@@ -35,6 +35,7 @@
(defvar jao-maildir-tracked-maildirs nil)
(defvar jao-maildir-info-string "")
(defvar jao-maildir-home (expand-file-name "~/var/mail"))
+(defvar jao-maildir-news-home (expand-file-name "~/var/news"))
(defgroup jao-maildir-faces nil "Faces"
:group 'faces)
@@ -152,32 +153,37 @@
(jao-maildir--setup-watches cb))
;;;###autoload
-(defun jao-maildir-file-to-group (file)
+(defun jao-maildir-file-to-group (file &optional maildir newsdir)
"Calculate the Gnus group name from the given file name.
Example:
IN: /home/jao/var/mail/jao/foo/cur/1259184569.M4818P3384.localhost,W=6921:2,S
- OUT: nnimap:jao/foo
+ OUT: nnml:jao.foo
+
+ IN: /home/jao/.emacs.d/gnus/Mail/jao.trove/32570, /home/jao/.emacs.d/gnus/Mail/
+ OUT: nnml:jao.trove
IN: /home/jao/var/mail/gmane/foo/bar/100
- OUT: nntp+localhost:gmane.foo.bar
+ OUT: nntp:gmane.foo.bar
IN: /home/jao/var/mail/bigml/cur/1259176906.M17483P24679.localhost,W=2488:2,S
OUT:nnimap:bigml/inbox"
(let* ((g (directory-file-name (file-name-directory file)))
- (g (replace-regexp-in-string (file-name-as-directory jao-maildir-home)
- "" g))
+ (g (replace-regexp-in-string
+ (file-name-as-directory (or maildir jao-maildir-home)) "" g))
+ (g (replace-regexp-in-string
+ (file-name-as-directory (or newsdir jao-maildir-news-home)) "" g))
(nntp (string-match-p "^\\(gmane\\|gwene\\)/" g))
- (g (if nntp
- (concat "nntp+localhost:" g)
- (replace-regexp-in-string "^\\([^/]+\\)/" "nnimap:\\1/"
- (file-name-directory g) t)))
- (g (if nntp (replace-regexp-in-string "/" "." g) g))
+ (g (cond (nntp (concat "nntp:" g))
+ ((file-name-directory g)
+ (replace-regexp-in-string "^\\([^/]+\\)/" "nnml:\\1/"
+ (file-name-directory g) t))
+ (t (concat "nnml:" g))))
+ (g (replace-regexp-in-string "/" "." g))
(g (replace-regexp-in-string "[/.]$" "" g)))
(cond ((string-match ":$" g) (concat g "inbox"))
(nntp g)
(t (replace-regexp-in-string ":\\." ":" g)))))
-
(provide 'jao-maildir)
;;; jao-maildir.el ends here
diff --git a/lib/themes/jao-light-theme.el b/lib/themes/jao-light-theme.el
index 98c3d40..2a6c82e 100644
--- a/lib/themes/jao-light-theme.el
+++ b/lib/themes/jao-light-theme.el
@@ -87,12 +87,19 @@
(diff-hl-delete (c "white" "wheat1"))
(fill-column-indicator (c "grey80"))
(fringe (c "grey70" nil))
+ (gnus-button (p dimm) nul)
+ (gnus-cite-1 (c "darkslategray" nil))
+ (gnus-cite-2 (c "slate gray" nil))
+ (gnus-cite-3 (c "slate gray" nil))
+ (gnus-cite-4 (c "slate gray" nil))
+ (gnus-summary-selected (c green) nbf)
+ (gnus-summary-cancelled (c "sienna3" nil) st)
(header-line (c nil "#efebe7"))
(magit-diff-context-highlight (c nil yellow) ex)
(magit-diff-hunk-heading-highlight (c nil yellow) it bf)
- (mode-line (c "grey30" dimm-background-3)
+ (mode-line (c "grey30" dimm-background-2)
:box (:line-width -1 :color "grey90"))
- (mode-line-inactive (c "grey40" dimm-background-4)
+ (mode-line-inactive (c "grey40" "white")
:box (:line-width -1 :color "grey90"))
(mode-line-buffer-id (~ default) (c dark-blue-2 nil) nit)
(mode-line-emphasis (c green nil))
diff --git a/notmuch.org b/notmuch.org
index 8683045..d3d832d 100644
--- a/notmuch.org
+++ b/notmuch.org
@@ -429,33 +429,6 @@
("SPC" . jao-notmuch-tree-scroll-or-next)
("M-g" . jao-notmuch-browse-url))))
#+end_src
-* consult
- #+begin_src emacs-lisp
- (jao-load-path "consult-notmuch")
- (setq consult-notmuch-authors-width 30)
- (require 'consult-notmuch)
- (consult-customize consult-notmuch :preview-key 'any)
-
- (defvar jao-consult-notmuch-history nil)
-
- (defun jao-consult-notmuch-folder (&optional tree folder)
- (interactive "P")
- (let* ((root "~/var/mail/")
- (folder (if folder
- (file-name-as-directory folder)
- (completing-read "Folder: "
- jao-mailbox-folders
- nil nil nil
- jao-consult-notmuch-history
- ".")))
- (folder (replace-regexp-in-string "/\\(.\\)" ".\\1" folder))
- (init (read-string "Initial query: "))
- (init (format "folder:/%s/ %s" folder init)))
- (if tree (consult-notmuch-tree init) (consult-notmuch init))))
-
- (with-eval-after-load "notmuch-hello"
- (define-key notmuch-hello-mode-map "f" #'jao-consult-notmuch-folder))
- #+end_src
* org mode
Stolen and adapted from [[https://gist.github.com/fedxa/fac592424473f1b70ea489cc64e08911][Fedor Bezrukov]].
#+begin_src emacs-lisp