dotfiles/.doom.d/config.org

1288 lines
50 KiB
Org Mode

#+TITLE: DOOM Emacs Configuration
#+STARTUP: indent
#+PROPERTY: header-args :tangle yes
* Custom settings
Store options set via =customize-*= in a separate file (Emacs stores
them in =init.el= by default).
#+BEGIN_SRC emacs-lisp
(setq custom-file "~/.emacs.d/custom.el")
(if (file-exists-p custom-file)
(load custom-file))
(setf custom-safe-themes t)
#+END_SRC
* UX Improvements
** Disable C-z backgrounding when the GUI is loaded
In OSX, this minimizes the window. It's incredibly annoying, and I accidentally
hit it too much. I also can't think of any reason I'd want to background Emacs
outside of a terminal session.
#+begin_src emacs-lisp
(if (display-graphic-p)
(global-unset-key (kbd "C-z")))
#+end_src
** Mixed Pitch
Facilitates mixing monospace and proportional fonts. I'm using an MIT-licensed
version of the [[https://github.com/edwardtufte/et-book][Edward Tufte book font]] because it's /gorgeous/.
#+begin_src emacs-lisp
(use-package! mixed-pitch
:hook ((org-mode markdown-mode rst-mode) . mixed-pitch-mode))
(setq doom-variable-pitch-font (font-spec :family "ETBookOT" :size 19))
#+end_src
* Dashboard
#+begin_src emacs-lisp
(defun my/choice (&rest things)
(let ((index (random (length things))))
(nth index things)))
(defun my/dashboard-randomize ()
(interactive)
(let ((logo (my/choice "~/Pictures/Patreon/Jon Bliss/phoenix-and-catgirl-500.png"
"~/Pictures/Patreon/Jon Bliss/Bassist-final-transparent-500.png"
"~/Pictures/Patreon/Jon Bliss/catgirl-final-transparent-500.png"
"~/Pictures/Patreon/Jon Bliss/lapiz-final-transparent-500.png"
"~/Pictures/Patreon/Jon Bliss/Poledancer-final-transparent-500.png"
"~/Pictures/Patreon/Jon Bliss/Hacker-final-transparent-500.png"
"~/Pictures/Patreon/Bee/IMG_3666_transparent_500.png")))
(setq fancy-splash-image logo)
(if (called-interactively-p)
(+doom-dashboard-reload))))
(my/dashboard-randomize)
(map!
:map +doom-dashboard-mode-map
"g" #'my/dashboard-randomize)
#+end_src
* BibTeX
#+begin_src emacs-lisp
(setq my/bibliographies
'("~/Documents/bibliography/references.bib"
"~/Documents/bibliography/calibre.bib"))
#+end_src
** Helm BibTeX
#+begin_src emacs-lisp
(setq bibtex-completion-bibliography
my/bibliographies)
(setq bibtex-completion-pdf-field "File")
#+end_src
* Org
:PROPERTIES:
:header-args: :tangle no :noweb-ref org
:END:
#+begin_src emacs-lisp :noweb yes :tangle yes :noweb-ref org-all
(after! org
<<org>>)
#+end_src
** Override DOOM indentation behavior
#+begin_src emacs-lisp
(defun my/org-init-babel ()
(setq org-src-preserve-indentation nil))
(add-hook! 'org-mode-hook #'my/org-init-babel)
#+end_src
** Disable DOOM's centralized attachment system
It's incompatible with all of the org files I already have using the standard
setup.
#+begin_src emacs-lisp
(setq org-attach-directory "data/")
(remove-hook! 'org-load-hook
#'(+org-init-centralized-attachments-h))
#+end_src
** Agenda
#+begin_src emacs-lisp
(after! org-agenda
(require 'f)
(setq my/agenda-files '((personal . ("~/Nextcloud/org/personal.org"))
(work . ("~/Nextcloud/org/aweber.org")))
org-agenda-files (-filter #'f-exists?
(-concat
(if (string-equal (system-name) "s1069.ofc.lair")
(cdr (assoc 'work my/agenda-files))
(cdr (assoc 'personal my/agenda-files))))))
(setq org-stuck-projects
'("+LEVEL=1/-DONE" ("TODO" "NEXT" "NEXTACTION") nil ""))
;; https://www.tompurl.com/2015-12-29-emacs-eisenhower-matrix.html
(setq org-tag-alist '(("important" . ?i)
("urgent" . ?u)))
(setq org-agenda-custom-commands
'(("n" "Agenda and all TODOs"
((agenda "" ((org-agenda-span 'week)))
(tags-todo "DEADLINE<=\"<+7d>\""
((org-agenda-overriding-header "Due soon")))
(todo ""))
((org-agenda-start-with-log-mode t)
(org-agenda-start-day nil)
(org-agenda-span 'day)
(org-agenda-log-mode-items '(clock state closed)))
("~/Public/org/agenda.html"
"~/Public/org/agenda.ics"))
("l" "Log"
agenda ""
((org-agenda-span 'fortnight)
(org-agenda-start-day "-1w")
(org-agenda-start-with-log-mode t)
(org-agenda-log-mode-items '(clock state closed))
(org-agenda-include-deadlines nil)
(org-agenda-skip-scheduled-delay-if-deadline t))
("~/Public/org/agenda-log.html"))
("e" "Eisenhower Matrix"
((tags-todo "+important+urgent"
((org-agenda-overriding-header "Do")))
(tags-todo "+important-urgent"
((org-agenda-overriding-header "Decide")))
(tags-todo "-important+urgent"
((org-agenda-overriding-header "Delegate")))
(tags-todo "-important-urgent"
((org-agenda-overriding-header "Delete"))))
((org-agenda-start-with-log-mode t)
(org-agenda-span 'day)
(org-agenda-log-mode-items '(clock state closed))))))
(defun my/org-agenda-timeline ()
(interactive)
(let ((org-agenda-files (list (buffer-file-name))))
(org-agenda)))
(setq org-agenda-start-on-weekday nil)
(setq org-agenda-span 'fortnight)
(setq org-agenda-todo-ignore-scheduled 'future)
(setq org-agenda-tags-todo-honor-ignore-options t)
(setq org-agenda-skip-deadline-prewarning-if-scheduled t)
(add-hook 'org-agenda-finalize-hook (lambda () (hl-line-mode)))
(setq
org-icalendar-use-scheduled '(todo-start event-if-todo)
org-icalendar-combined-agenda-file (expand-file-name "~/Documents/org.ics")))
#+end_src
** LaTeX Export
*** Document Classes
#+BEGIN_SRC emacs-lisp
(use-package! ox-latex
:config
(seq-map (apply-partially #'add-to-list 'org-latex-classes)
'(("koma-letter"
"\\documentclass{scrlttr2}"
("\\section{%s}" . "\\section*{%s}")
("\\subsection{%s}" . "\\subsection*{%s}")
("\\subsubsection{%s}" . "\\subsubsection*{%s}")
("\\paragraph{%s}" . "\\paragraph*{%s}")
("\\subparagraph{%s}" . "\\subparagraph*{%s}"))
("koma-article"
"\\documentclass{scrartcl}"
("\\section{%s}" . "\\section*{%s}")
("\\subsection{%s}" . "\\subsection*{%s}")
("\\subsubsection{%s}" . "\\subsubsection*{%s}")
("\\paragraph{%s}" . "\\paragraph*{%s}")
("\\subparagraph{%s}" . "\\subparagraph*{%s}"))
("koma-book"
"\\documentclass{scrbook}"
("\\section{%s}" . "\\section*{%s}")
("\\subsection{%s}" . "\\subsection*{%s}")
("\\subsubsection{%s}" . "\\subsubsection*{%s}")
("\\paragraph{%s}" . "\\paragraph*{%s}")
("\\subparagraph{%s}" . "\\subparagraph*{%s}"))
("koma-book-chapters"
"\\documentclass{scrbook}"
("\\chapter{%s}" . "\\chapter*{%s}")
("\\section{%s}" . "\\section*{%s}")
("\\subsection{%s}" . "\\subsection*{%s}")
("\\subsubsection{%s}" . "\\subsubsection*{%s}")
("\\paragraph{%s}" . "\\paragraph*{%s}")
("\\subparagraph{%s}" . "\\subparagraph*{%s}"))
("koma-report"
"\\documentclass{scrreprt}"
("\\chapter{%s}" . "\\chapter*{%s}")
("\\section{%s}" . "\\section*{%s}")
("\\subsection{%s}" . "\\subsection*{%s}")
("\\subsubsection{%s}" . "\\subsubsection*{%s}")
("\\paragraph{%s}" . "\\paragraph*{%s}")
("\\subparagraph{%s}" . "\\subparagraph*{%s}"))
("memoir"
"\\documentclass{memoir}"
("\\section{%s}" . "\\section*{%s}")
("\\subsection{%s}" . "\\subsection*{%s}")
("\\subsubsection{%s}" . "\\subsubsection*{%s}")
("\\paragraph{%s}" . "\\paragraph*{%s}")
("\\subparagraph{%s}" . "\\subparagraph*{%s}"))
("hitec"
"\\documentclass{hitec}"
("\\section{%s}" . "\\section*{%s}")
("\\subsection{%s}" . "\\subsection*{%s}")
("\\subsubsection{%s}" . "\\subsubsection*{%s}")
("\\paragraph{%s}" . "\\paragraph*{%s}")
("\\subparagraph{%s}" . "\\subparagraph*{%s}"))
("paper"
"\\documentclass{paper}"
("\\section{%s}" . "\\section*{%s}")
("\\subsection{%s}" . "\\subsection*{%s}")
("\\subsubsection{%s}" . "\\subsubsection*{%s}")
("\\paragraph{%s}" . "\\paragraph*{%s}")
("\\subparagraph{%s}" . "\\subparagraph*{%s}"))
("letter"
"\\documentclass{letter}"
("\\section{%s}" . "\\section*{%s}")
("\\subsection{%s}" . "\\subsection*{%s}")
("\\subsubsection{%s}" . "\\subsubsection*{%s}")
("\\paragraph{%s}" . "\\paragraph*{%s}")
("\\subparagraph{%s}" . "\\subparagraph*{%s}"))
("tufte-handout"
"\\documentclass{tufte-handout}"
("\\section{%s}" . "\\section*{%s}")
("\\subsection{%s}" . "\\subsection*{%s}")
("\\subsubsection{%s}" . "\\subsubsection*{%s}")
("\\paragraph{%s}" . "\\paragraph*{%s}")
("\\subparagraph{%s}" . "\\subparagraph*{%s}"))
("tufte-book"
"\\documentclass{tufte-book}"
("\\section{%s}" . "\\section*{%s}")
("\\subsection{%s}" . "\\subsection*{%s}")
("\\subsubsection{%s}" . "\\subsubsection*{%s}")
("\\paragraph{%s}" . "\\paragraph*{%s}")
("\\subparagraph{%s}" . "\\subparagraph*{%s}"))
("tufte-book-chapters"
"\\documentclass{tufte-book}"
("\\chapter{%s}" . "\\chapter*{%s}")
("\\section{%s}" . "\\section*{%s}")
("\\subsection{%s}" . "\\subsection*{%s}")
("\\subsubsection{%s}" . "\\subsubsection*{%s}")
("\\paragraph{%s}" . "\\paragraph*{%s}")
("\\subparagraph{%s}" . "\\subparagraph*{%s}"))
("labbook"
"\\documentclass{labbook}"
("\\chapter{%s}" . "\\chapter*{%s}")
("\\section{%s}" . "\\section*{%s}")
("\\subsection{%s}" . "\\labday{%s}")
("\\subsubsection{%s}" . "\\experiment{%s}")
("\\paragraph{%s}" . "\\paragraph*{%s}")
("\\subparagraph{%s}" . "\\subparagraph*{%s}")))))
#+END_SRC
*** DnD
#+begin_src emacs-lisp
(use-package! ox-dnd
:after ox)
#+end_src
** Capture templates
#+begin_src emacs-lisp
(setq org-capture-templates
`(
;; Personal
("j" "Journal Entry" plain
(file+datetree "~/org/journal.org")
"%U\n\n%?" :empty-lines-before 1)
("t" "TODO" entry
(file+headline "~/Nextcloud/org/personal.org" "Unsorted")
"* TODO %^{Description}\n%?")
("n" "Note" entry
(file+headline "~/Nextcloud/org/personal.org" "Notes")
"* %^{Description}\n%U\n\n%?")
;; Org-Protocol
("b" "Bookmark" entry
(file+headline "~/org/bookmarks.org" "Unsorted")
"* %^{Title}\n\n Source: %u, %c\n\n %i")
("p" "Webpage" entry
(file "~/org/articles.org")
"* %a\n\n%U %?\n\n%:initial")
;; Email
;; https://martinralbrecht.wordpress.com/2016/05/30/handling-email-with-emacs/
("r" "respond to email (mu4e)"
entry (file+headline "~/org/todo.org" "Email")
"* REPLY to [[mailto:%:fromaddress][%:fromname]] on %a\nDEADLINE: %(org-insert-time-stamp (org-read-date nil t \"+1d\"))\n%U\n\n"
:immediate-finish t
:prepend t)
;; Work
("w" "Work")
("wt" "Work TODO" entry
(file+headline "~/Nextcloud/org/aweber.org" "Unsorted")
"* TODO %^{Description}\n%?")
("wl" "Log Work Task" entry
(file+datetree "~/org-aweber/worklog.org")
"* %^{Description} %^g\nAdded: %U\n\n%?"
:clock-in t
:clock-keep t)
("wL" "Log Work Task (no clock)" entry
(file+datetree "~/org-aweber/worklog.org")
"* %^{Description} %^g\nAdded: %U\n\n%?")
("wj" "Log work on JIRA issue" entry
(file+datetree "~/org-aweber/worklog.org")
,(concat
"* %?\n"
":PROPERTIES:\n"
":JIRA_ID: %^{JIRA_ID}\n"
":END:\n"
"Added: %U\n\n"
"[[jira:%\\1][%\\1]]")
:clock-in t
:clock-keep t)
("wr" "respond to email (mu4e)"
entry (file+headline "~/Nextcloud/org/aweber.org" "Unsorted")
"* REPLY to [[mailto:%:fromaddress][%:fromname]] on %a\nDEADLINE: %(org-insert-time-stamp (org-read-date nil t \"+1d\"))\n%U\n\n"
:immediate-finish t
:prepend t)))
#+end_src
** Custom ID generation
Because I'm all kinds of crazy, I like the custom IDs of my work log entries to
be based on their headings.
#+begin_src emacs-lisp
(use-package! org-id
:after org
:config
;; https://writequit.org/articles/emacs-org-mode-generate-ids.html#automating-id-creation
(defun eos/org-custom-id-get (&optional pom create prefix)
"Get the CUSTOM_ID property of the entry at point-or-marker POM.
If POM is nil, refer to the entry at point. If the entry does
not have an CUSTOM_ID, the function returns nil. However, when
CREATE is non nil, create a CUSTOM_ID if none is present
already. PREFIX will be passed through to `org-id-new'. In any
case, the CUSTOM_ID of the entry is returned."
(interactive)
(org-with-point-at pom
(let ((id (org-entry-get nil "CUSTOM_ID")))
(cond
((and id (stringp id) (string-match "\\S-" id))
id)
(create
(setq id (org-id-new (concat prefix "h")))
(org-entry-put pom "CUSTOM_ID" id)
(org-id-add-location id (buffer-file-name (buffer-base-buffer)))
id)))))
(defun eos/org-add-ids-to-headlines-in-file ()
"Add CUSTOM_ID properties to all headlines in the current
file which do not already have one. Only adds ids if the
`auto-id' option is set to `t' in the file somewhere. ie,
,#+OPTIONS: auto-id:t"
(interactive)
(save-excursion
(widen)
(goto-char (point-min))
(when (re-search-forward "^#\\+OPTIONS:.*auto-id:t" (point-max) t)
(org-map-entries (lambda () (eos/org-id-get (point) 'create)))))
(save-excursion
(widen)
(goto-char (point-min))
(when (re-search-forward "^#\\+OPTIONS:.*auto-id:worklog" (point-max) t)
(let ((my/org-worklog-id-depth 2))
(org-map-entries (lambda () (my/org-worklog-id-get (point) 'create))))))
(save-excursion
(widen)
(goto-char (point-min))
(when (re-search-forward "^#\\+OPTIONS:.*auto-id:readable" (point-max) t)
(let ((my/org-worklog-id-depth 0))
(org-map-entries (lambda () (my/org-worklog-id-get (point) 'create)))))))
;; automatically add ids to saved org-mode headlines
(add-hook 'org-mode-hook
(lambda ()
(add-hook 'before-save-hook
(lambda ()
(when (and (eq major-mode 'org-mode)
(eq buffer-read-only nil))
(eos/org-add-ids-to-headlines-in-file))))))
(defun my/org-remove-all-ids ()
(interactive)
(save-excursion
(widen)
(goto-char (point-min))
(org-map-entries (lambda () (org-entry-delete (point) "CUSTOM_ID")))))
(defvar my/org-worklog-id-depth 2)
(defun my/org-worklog-id-new (&optional prefix)
(let ((path (or (-drop my/org-worklog-id-depth (org-get-outline-path t))
(last (org-get-outline-path t)))))
(mapconcat
(lambda (s)
(->> s
(s-downcase)
(s-replace-regexp "[^[:alnum:]]+" "-")))
path
"-")))
(defun my/org-worklog-id-get (&optional pom create prefix)
(interactive)
(org-with-point-at pom
(let ((id (org-entry-get nil "CUSTOM_ID")))
(cond
((and id (stringp id) (string-match "\\S-" id))
id)
(create
(setq id (my/org-worklog-id-new prefix))
(org-entry-put pom "CUSTOM_ID" id)
id))))))
#+end_src
** Publish projects
#+begin_src emacs-lisp
(require 'org-attach)
(setq org-html-mathjax-options
'((path "https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.2/MathJax.js?config=TeX-AMS-MML_HTMLorMML")))
(setq org-re-reveal-root "https://cdn.jsdelivr.net/reveal.js/3.0.0/")
(defun my/org-work-publish-to-html (plist filename pub-dir)
(message "Publishing %s" filename)
(cond ((string-match-p "slides.org$" filename)
(org-re-reveal-publish-to-reveal plist filename pub-dir))
(t (let ((org-html-head
(concat
;; Tufte
;; "<link rel=\"stylesheet\" href=\"" my/org-base-url "styles/tufte-css/tufte.css\"/>"
;; "<link rel=\"stylesheet\" href=\"" my/org-base-url "styles/tufte-css/latex.css\"/>"
;; Org-Spec
;; "<link href=\"https://fonts.googleapis.com/css?family=Roboto+Slab:400,700|Inconsolata:400,700\" rel=\"stylesheet\" type=\"text/css\" />"
;; "<link rel=\"stylesheet\" href=\"" my/org-base-url "styles/org-spec/style.css\"/>"
;; "<link rel=\"stylesheet\" type=\"text/css\" href=\"" my/org-base-url "css/info.css\" />"
;; ReadTheOrg
"<link rel=\"stylesheet\" type=\"text/css\" href=\"" my/org-base-url "styles/readtheorg/css/htmlize.css\"/>"
"<link rel=\"stylesheet\" type=\"text/css\" href=\"" my/org-base-url "styles/readtheorg/css/readtheorg.css\"/>"
"<link rel=\"stylesheet\" type=\"text/css\" href=\"" my/org-base-url "css/info.css\" />"
"<script src=\"https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js\"></script>"
"<script src=\"https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/js/bootstrap.min.js\"></script>"
"<script type=\"text/javascript\" src=\"" my/org-base-url "styles/lib/js/jquery.stickytableheaders.min.js\"></script>"
"<script type=\"text/javascript\" src=\"" my/org-base-url "styles/readtheorg/js/readtheorg.js\"></script>"
;; Bigblow
;; "<link rel=\"stylesheet\" type=\"text/css\" href=\"" my/org-base-url "styles/bigblow/css/htmlize.css\"/>"
;; "<link rel=\"stylesheet\" type=\"text/css\" href=\"" my/org-base-url "styles/bigblow/css/bigblow.css\"/>"
;; "<link rel=\"stylesheet\" type=\"text/css\" href=\"" my/org-base-url "styles/bigblow/css/hideshow.css\"/>"
;; "<script type=\"text/javascript\" src=\"" my/org-base-url "styles/bigblow/js/jquery-1.11.0.min.js\"></script>"
;; "<script type=\"text/javascript\" src=\"" my/org-base-url "styles/bigblow/js/jquery-ui-1.10.2.min.js\"></script>"
;; "<script type=\"text/javascript\" src=\"" my/org-base-url "styles/bigblow/js/jquery.localscroll-min.js\"></script>"
;; "<script type=\"text/javascript\" src=\"" my/org-base-url "styles/bigblow/js/jquery.scrollTo-1.4.3.1-min.js\"></script>"
;; "<script type=\"text/javascript\" src=\"" my/org-base-url "styles/bigblow/js/jquery.zclip.min.js\"></script>"
;; "<script type=\"text/javascript\" src=\"" my/org-base-url "styles/bigblow/js/bigblow.js\"></script>"
;; "<script type=\"text/javascript\" src=\"" my/org-base-url "styles/bigblow/js/hideshow.js\"></script>"
;; "<script type=\"text/javascript\" src=\"" my/org-base-url "styles/lib/js/jquery.stickytableheaders.min.js\"></script>"
)))
(save-excursion
(save-restriction
(org-html-publish-to-html plist filename pub-dir)))))))
;; (setq my/org-base-url (concat "/~" (getenv "USER") "/org/"))
(setq my/org-base-url "/")
(setq my/org-base-url "https://correlr.gitlab.aweber.io/org/")
(setq org-publish-project-alist
`(
;; ("work-common"
;; :base-directory "~/org/common"
;; :publishing-directory "~/Public/org"
;; :base-extension "css\\|gif\\|jpe?g\\|png\\|svg"
;; :recursive t
;; :publishing-function org-publish-attachment)
("work-themes"
:base-directory "~/.emacs.local.d/org-html-themes/styles"
:publishing-directory "~/Public/org/styles"
:base-extension "js\\|css\\|gif\\|jpe?g\\|png\\|svg\\|ogv"
:recursive t
:publishing-function org-publish-attachment)
("work-html"
:base-directory "~/org-aweber"
:base-extension "org"
;; :exclude "\\(^knowledge-transfer.org$\\|-archive.org$\\)"
:exclude "\\(^README.org$\\|roam/.*\\)"
:publishing-directory "~/Public/org"
:publishing-function (my/org-work-publish-to-html
org-org-publish-to-org
org-babel-tangle-publish)
;; :htmlized-source t
;; :html-head "<link rel=\"stylesheet\" type=\"text/css\" href=\"http://thomasf.github.io/solarized-css/solarized-dark.min.css\" />"
;; :html-head-extra "<link rel=\"stylesheet\" type=\"text/css\" href=\"/~croush/org/css/org.css\" />"
;; :setup-file "~/.emacs.local.d/org-html-themes/setup/theme-readtheorg-local.setup"
:html-link-home ,my/org-base-url
:html-doctype "html5"
:html-html5-fancy t
:with-sub-superscript nil
:section-numbers nil
;; :infojs-opt "path:http://thomasf.github.io/solarized-css/org-info.min.js view:showall"
:auto-sitemap t
:sitemap-filename "index.org"
:sitemap-title "Correl Roush's Org Documents"
:sitemap-sort-folders last
:recursive t)
("work-roam-html"
:base-directory "~/org-aweber/roam"
:base-extension "org"
:publishing-directory "~/Public/org/roam"
:recursive t
:with-toc nil
:section-numbers nil
:auto-sitemap t
:sitemap-filename "index.org"
:sitemap-title "Correl Roush's Org Roam Notes"
:publishing-function org-html-publish-to-html
:html-head "<link rel=\"stylesheet\" href=\"https://gongzhitaao.org/orgcss/org.css\"/>")
("work-assets"
:base-directory "~/org-aweber"
:base-extension "css\\|gif\\|jpe?g\\|png\\|svg\\|pdf\\|ogv\\|py\\|html\\|ya?ml"
:include (".gitlab-ci.yml")
:publishing-directory "~/Public/org"
:publishing-function org-publish-attachment
:display-custom-times t
:recursive t)
("work-todo"
:base-directory "~/Nextcloud/org"
:exclude ".*"
:include ("aweber.org")
:html-head "<link rel=\"stylesheet\" href=\"styles/tufte-css/tufte.css\"/>"
:html-head-extra "<link rel=\"stylesheet\" href=\"styles/tufte-css/latex.css\"/>"
:publishing-directory "~/Public/org"
:publishing-function org-html-publish-to-tufte-html)
("work" :components ("work-html" "work-roam-html" "work-todo" "work-assets" "work-themes"))
("dotfiles-common"
:base-directory "~/dotfiles"
:publishing-directory "~/Public/dotfiles"
:base-extension "css\\|gif\\|jpe?g\\|png\\|svg"
:recursive t
:publishing-function org-publish-attachment)
("dotfiles-html"
:base-directory "~/dotfiles"
:base-extension "org"
:publishing-directory "~/Public/dotfiles"
:publishing-function (org-html-publish-to-html
org-babel-tangle-publish)
:htmlized-source t
:html-head "<link rel=\"stylesheet\" type=\"text/css\" href=\"http://thomasf.github.io/solarized-css/solarized-dark.min.css\" />"
:html-head-extra "<link rel=\"stylesheet\" type=\"text/css\" href=\"/~croush/org/css/org.css\" />"
:html-link-home "/~croush/dotfiles/"
:html-doctype "html5"
:html-html5-fancy t
:with-sub-superscript nil
:infojs-opt "path:http://thomasf.github.io/solarized-css/org-info.min.js view:showall"
:auto-sitemap t
:sitemap-filename "index.org"
:sitemap-title "Correl Roush's Dotfiles"
:sitemap-sort-folders last
:recursive t)
("dotfiles-assets"
:base-directory "~/dotfiles"
:base-extension "css\\|gif\\|jpe?g\\|png\\|svg"
:publishing-directory "~/Public/dotfiles"
:publishing-function org-publish-attachment
:recursive t)
("dotfiles" :components ("dotfiles-common" "dotfiles-html" "dotfiles-assets"))
("personal-themes"
:base-directory "~/.emacs.local.d/org-html-themes/styles"
:publishing-directory "~/Public/personal/styles"
:base-extension "js\\|css\\|gif\\|jpe?g\\|png\\|svg"
:recursive t
:publishing-function org-publish-attachment)
("personal-html"
:base-directory "~/org"
:base-extension "org"
:publishing-directory "~/Public/personal"
:recursive t
:with-toc t
:auto-sitemap t
:sitemap-title "Correl Roush's Org Files"
:sitemap-filename "index.org"
:publishing-function org-html-publish-to-tufte-html
:html-head ,(concat
;; Tufte
"<link rel=\"stylesheet\" href=\"" my/org-base-url "styles/tufte-css/tufte.css\"/>"
"<link rel=\"stylesheet\" href=\"" my/org-base-url "styles/tufte-css/latex.css\"/>"))
;; Org-Spec
;; "<link href=\"http://fonts.googleapis.com/css?family=Roboto+Slab:400,700|Inconsolata:400,700\" rel=\"stylesheet\" type=\"text/css\" />"
;; "<link href=\"http://demo.thi.ng/org-spec/css/style.css\" rel=\"stylesheet\" type=\"text/css\" />"
("personal-files"
:base-directory "~/org"
:base-extension "css\\|gif\\|jpe?g\\|png\\|svg"
:publishing-directory "~/Public/personal"
:recursive t
:publishing-function org-publish-attachment)
("personal-assets"
:base-directory "~/org"
:base-extension "css\\|gif\\|jpe?g\\|png\\|svg\\|pdf"
:publishing-directory "~/Public/personal"
:publishing-function org-publish-attachment
:recursive t)
("personal" :components ("personal-themes" "personal-html" "personal-files" "personal-assets"))
("journal"
:base-directory "~/org"
:exclude ".*"
:include ("journal.org")
:publishing-directory "~/journal"
:publishing-function (org-html-publish-to-html
org-latex-export-to-pdf))
("roam-html"
:base-directory "~/org/roam"
:base-extension "org"
:publishing-directory "~/Public/roam"
:recursive t
:with-toc nil
:section-numbers nil
:auto-sitemap nil
:publishing-function org-html-publish-to-html
:html-head "<link rel=\"stylesheet\" href=\"https://gongzhitaao.org/orgcss/org.css\"/>")
("roam-assets"
:base-directory "~/org/roam"
:base-extension "css\\|gif\\|jpe?g\\|png\\|svg\\|pdf"
:publishing-directory "~/Public/roam"
:publishing-function org-publish-attachment
:recursive t)
("roam" :components ("roam-html" "roam-assets"))
("sicp-html"
:base-directory "~/code/sicp"
:base-extension "org"
:publishing-directory "~/Public/sicp"
:publishing-function (org-html-publish-to-html
org-org-publish-to-org
org-babel-tangle-publish)
:htmlized-source t
:html-head "<link rel=\"stylesheet\" type=\"text/css\" href=\"http://thomasf.github.io/solarized-css/solarized-light.min.css\" />"
:html-link-home "/"
:html-doctype "html5"
:html-html5-fancy t
:with-sub-superscript nil
:auto-sitemap t
:sitemap-filename "index.org"
:sitemap-title "SICP Exercises and Notes"
:sitemap-sort-folders last
:recursive t)
("sicp-assets"
:base-directory "~/code/sicp"
:base-extension "css\\|gif\\|jpe?g\\|png\\|svg\\|scheme\\|pl"
:publishing-directory "~/Public/sicp"
:publishing-function org-publish-attachment
:recursive t)
("sicp" :components ("sicp-html" "sicp-assets"))))
;; Don't prompt for babel evaluation, ever.
(setq org-confirm-babel-evaluate nil)
(require 'ox-confluence)
(defun my/org-publish ()
(interactive)
(org-publish "work")
(let ((org-link-abbrev-alist (seq-concatenate 'list org-link-abbrev-alist
'(("jira" . "https://jira.aweber.io/browse/")
("gitlab" . "https://gitlab.aweber.io/")))))
(org-store-agenda-views))
(shell-command "org-publish"))
(bind-key "C-c o p" #'my/org-publish)
#+end_src
** Enhanced Confluence export
#+begin_src emacs-lisp
(use-package! ox-confluence-en
:after ox
:commands ox-confluence-en-export-as-confluence)
#+end_src
** Reload images on source execution
#+begin_src emacs-lisp
(defun my/redisplay-org-images ()
(when org-inline-image-overlays
(org-redisplay-inline-images)))
(add-hook 'org-babel-after-execute-hook
'my/redisplay-org-images)
#+end_src
** Sticky headers
#+begin_src emacs-lisp
(use-package! org-sticky-header
:hook (org-mode . org-sticky-header-mode)
:config (setq org-sticky-header-full-path 'full))
#+end_src
** Library of Babel
Load shared code snippets to be used in org documents.
#+begin_src emacs-lisp
(let ((org-dirs '("~/org" "~/org-aweber")))
(seq-map #'org-babel-lob-ingest
(seq-filter #'f-exists?
(seq-map (lambda (path) (f-join path "library-of-babel.org"))
org-dirs))))
#+end_src
** Nicer looking timestamps
#+begin_src emacs-lisp
(setq org-time-stamp-custom-formats '("<%A, %B %d %Y>" . "<%A, %B %d %Y %H:%M>"))
(defun org-export-filter-timestamp-remove-brackets (timestamp backend info)
"removes relevant brackets from a timestamp"
(cond
((org-export-derived-backend-p backend 'latex)
(replace-regexp-in-string "[<>]\\|[][]" "" timestamp))
((org-export-derived-backend-p backend 'ascii)
(replace-regexp-in-string "[<>]\\|[][]" "" timestamp))
((org-export-derived-backend-p backend 'html)
(replace-regexp-in-string "&[lg]t;\\|[][]" "" timestamp))))
(after! ox
(add-to-list
'org-export-filter-timestamp-functions
'org-export-filter-timestamp-remove-brackets))
#+end_src
** Tufte HTML
#+begin_src emacs-lisp
(use-package! ox-tufte
:after ox)
#+end_src
** Journal
#+begin_src emacs-lisp
(use-package org-journal
:if (f-dir? "~/org-aweber")
:custom
(org-journal-date-prefix "#+title: ")
(org-journal-file-format "%Y-%m-%d.org")
(org-journal-dir "~/org-aweber")
(org-journal-date-format "%A, %d %B %Y"))
#+end_src
** Ref
#+begin_src emacs-lisp
(use-package! org-ref
:config
(setq reftex-default-bibliography my/bibliographies)
;; see org-ref for use of these variables
(setq org-ref-bibliography-notes "~/Documents/bibliography/notes.org"
org-ref-default-bibliography my/bibliographies
org-ref-pdf-directory "~/Documents/bibliography/bibtex-pdfs/"))
#+end_src
** Roam
*** Add backlinks to org-roam exports
Adapted from https://org-roam.readthedocs.io/en/master/org_export/.
#+begin_src emacs-lisp
(defun my/org-roam--rewrite-backlink-content-links (path content)
"Re-write the links in backlink CONTENT to be relative to PATH."
(with-temp-buffer
(insert content)
(org-mode)
(let ((ast (org-element-parse-buffer)))
(org-element-map ast 'link
(lambda (link)
(when (string= (org-element-property :type link) "file")
(org-element-put-property
link :path
(f-relative (org-element-property :path link)
path)))))
(org-no-properties (org-element-interpret-data ast)))))
(defun my/org-roam--backlinks-list-with-content (file)
"Generate a list of backlinks for FILE with content."
(when (and (stringp file) (f-file? file))
(with-temp-buffer
(cd (f-dirname file))
(hack-dir-local-variables-non-file-buffer)
(if-let* ((backlinks (org-roam--get-backlinks file))
(grouped-backlinks (--group-by (nth 0 it) backlinks)))
(progn
(dolist (group grouped-backlinks)
(let ((file-from (car group))
(bls (cdr group)))
(insert (format "** [[file:%s][%s]]\n"
(f-relative file-from (f-dirname file))
(org-roam--get-title-or-slug file-from)))
(dolist (backlink bls)
(pcase-let* ((`(,file-from _ ,props) backlink)
(content (my/org-roam--rewrite-backlink-content-links
(f-dirname file)
(plist-get props :content))))
(insert (s-trim (s-replace "\n" " " content)))
(insert "\n\n")))))))
(buffer-string))))
(defun my/org-roam--bibtex ()
(if-let* ((citekey (cdr (org-roam--extract-ref)))
(entry (org-ref-get-bibtex-entry citekey))
(parsed (reftex-parse-bibtex-entry entry)))
(concat (org-list-to-org
(list 'descriptive
(list
(concat "Author :: " (cdr (assoc "author" parsed))))
(list
(concat "Calibre :: https://calibre.phoenixinquis.is-a-geek.org/#library_id=library&panel=book_details&book_id=" (cdr (assoc "calibreid" parsed))))))
"\n\n")))
(defun my/org-export-preprocessor (backend)
"Append org-roam backlinks with content when applicable before
passing to the org export BACKEND."
(let ((links (my/org-roam--backlinks-list-with-content (buffer-file-name)))
(bibtex (my/org-roam--bibtex)))
(unless (or (not (stringp bibtex)) (string= bibtex ""))
(save-excursion
(goto-char (point-max))
(insert (concat "\n* Bibliography\n") bibtex)))
(unless (or (not (stringp links)) (string= links ""))
(save-excursion
(goto-char (point-max))
(insert (concat "\n* Backlinks\n") links)))))
(add-hook 'org-export-before-processing-hook 'my/org-export-preprocessor)
#+end_src
*** Org Roam Bibtex
#+begin_src emacs-lisp
(use-package! org-roam-bibtex
:after org-roam
:hook (org-roam . org-roam-bibtex-mode)
:bind (:map org-mode-map
(("C-c n r a" . orb-note-actions))))
#+end_src
*** Org Roam Server
#+begin_src emacs-lisp
(use-package! org-roam-server
:commands org-roam-server-mode)
#+end_src
* Eshell
** Change directory in the context of a remote host
#+begin_src emacs-lisp
(defun eshell/lcd (&optional directory)
(interactive)
(if (file-remote-p default-directory)
(with-parsed-tramp-file-name default-directory nil
(eshell/cd (tramp-make-tramp-file-name
(tramp-file-name-method v)
(tramp-file-name-user v)
(tramp-file-name-domain v)
(tramp-file-name-host v)
(tramp-file-name-port v)
(or directory "")
(tramp-file-name-hop v))))
(eshell/cd directory)))
#+end_src
* MU4E
#+begin_src emacs-lisp
(use-package! mu4e
:bind (("<f9>" . mu4e))
:config
(require 'f)
(setq mu4e-maildir "~/Mail")
(setq user-full-name "Correl Roush")
(setq mu4e-contexts
(list (make-mu4e-context
:name "work"
:vars `((user-mail-address . "correlr@aweber.com")
(mu4e-drafts-folder . "/Work/[Gmail].Drafts")
(mu4e-sent-folder . "/Work/[Gmail].Sent Mail")
(mu4e-trash-folder . "/Work/[Gmail].Trash")
(mu4e-maildir-shortcuts . (("/Work/INBOX" . ?i)
("/Work/[Gmail].All Mail" . ?a)
("/Work/[Gmail].Starred" . ?S)
("/Work/[Gmail].Sent Mail" . ?s)
("/Work/[Gmail].Trash" . ?t)))
(mu4e-compose-signature . ,(with-temp-buffer
(insert-file-contents "~/.signature-aweber")
(buffer-string)))))))
(when (f-exists?
(f-join mu4e-maildir "Personal"))
(add-to-list
'mu4e-contexts
(make-mu4e-context
:name "personal"
:vars `((user-mail-address . "correl@gmail.com")
(mu4e-drafts-folder . "/Personal/[Gmail].Drafts")
(mu4e-sent-folder . "/Personal/[Gmail].Sent Mail")
(mu4e-trash-folder . "/Personal/[Gmail].Trash")
(mu4e-maildir-shortcuts . (("/Personal/INBOX" . ?i)
("/Personal/[Gmail].All Mail" . ?a)
("/Personal/[Gmail].Starred" . ?S)
("/Personal/[Gmail].Sent Mail" . ?s)
("/Personal/[Gmail].Trash" . ?t)))
(mu4e-compose-signature . ,(with-temp-buffer
(insert-file-contents "~/.signature")
(buffer-string)))))))
(setq mu4e-context-policy 'pick-first)
(setq mu4e-compose-dont-reply-to-self t)
(setq mu4e-user-mail-address-list '("correlr@aweber.com"
"correl@gmail.com")))
#+end_src
* Prodigy
Manage background services
#+begin_src emacs-lisp
(use-package! prodigy
:defer 2
:config
(global-set-key (kbd "<f7>") 'prodigy)
(prodigy-define-tag
:name 'work)
(prodigy-define-tag
:name 'personal)
;; https://martinralbrecht.wordpress.com/2016/05/30/handling-email-with-emacs/
(when (executable-find "imapnotify")
(prodigy-define-tag
:name 'email
:ready-message "Checking Email using IMAP IDLE. Ctrl-C to shutdown.")
(prodigy-define-service
:name "imapnotify-work"
:command "imapnotify"
:args (list "-c" (expand-file-name "~/.config/imap_inotify/work.js"))
:tags '(email work autostart)
:kill-signal 'sigkill)
(unless (string-equal "croush" (user-login-name))
(prodigy-define-service
:name "imapnotify-personal"
:command "imapnotify"
:args (list "-c" (expand-file-name "~/.config/imap_inotify/personal.js"))
:tags '(email personal autostart)
:kill-signal 'sigkill)))
(when (f-exists? (expand-file-name "~/code/elm-dashboard"))
(prodigy-define-service
:name "elm-dashboard"
:command "python"
:args '("-m" "SimpleHTTPServer" "3000")
:cwd (expand-file-name "~/code/elm-dashboard")
:tags '(personal elm)
:stop-signal 'sigkill
:kill-process-buffer-on-stop t))
(when (f-exists? (expand-file-name "~/git/www"))
(prodigy-define-service
:name "AWeber WWW"
:command "npm"
:args '("start")
:cwd (expand-file-name "~/git/www")
:tags '(work)))
(when (f-exists? (expand-file-name "~/Public/org"))
(prodigy-define-service
:name "Org Documents"
:command "python"
:args '("-m" "http.server" "3001")
:cwd (expand-file-name "~/Public/org")
:tags '(work autostart)
:kill-signal 'sigkill))
(mapcar
#'prodigy-start-service
(-concat (prodigy-services-tagged-with 'autostart))))
#+end_src
* Projectile
#+begin_src emacs-lisp
(after! projectile
(require 'dash)
(require 'f)
(setq projectile-switch-project-action #'magit-status)
(let ((project-directories (-filter #'f-directory?
'("~/code"
"~/git"))))
(-map
(lambda (directory)
(-map (lambda (project)
(-> (concat project "/") ;; Projectile likes trailing slashes
(projectile-add-known-project)))
(-filter (lambda (f) (and (not (s-ends-with? "." f))
(f-directory? f)))
(-map (lambda (f) (concat directory "/" f))
(directory-files directory)))))
project-directories))
(projectile-cleanup-known-projects))
#+end_src
* Elfeed
#+begin_src emacs-lisp
(use-package! elfeed
:commands (elfeed my/elfeed my/elfeed-emacs my/elfeed-blogs)
:bind
(("<f2>" . elfeed)
("C-c n n" . my/elfeed)
("C-c n a" . my/elfeed-all)
("C-c n e" . my/elfeed-emacs)
("C-c n b" . my/elfeed-blogs))
:init
(global-set-key [f2] 'elfeed)
:config
(use-package! elfeed-org
:config (progn (elfeed-org)
(setq rmh-elfeed-org-files '("~/org/elfeed.org"))))
(defun my/elfeed-with-filters (filters)
(elfeed)
(setq elfeed-search-filter
(if (listp filters) (mapconcat #'identity filters " ")
filters))
(elfeed-search-update :force))
(defun my/elfeed ()
(interactive)
(my/elfeed-with-filters "@6-months-ago +unread"))
(defun my/elfeed-all ()
(interactive)
(my/elfeed-with-filters "@6-months-ago"))
(defun my/elfeed-emacs ()
(interactive)
(my/elfeed-with-filters "@6-months-ago +emacs +unread"))
(defun my/elfeed-blogs ()
(interactive)
(my/elfeed-with-filters "@6-months-ago +unread +blog")))
#+end_src
* Paredit
#+begin_src emacs-lisp
(use-package! paredit
:hook ((emacs-lisp-mode . enable-paredit-mode)))
#+end_src
* UUID Generation
#+begin_src emacs-lisp
(use-package! uuidgen
:commands (uuidgen))
#+end_src
* Eval and Replace
Taken from [[http://emacsredux.com/blog/2013/06/21/eval-and-replace/][Emacs Redux]]
#+begin_src emacs-lisp
(defun eval-and-replace ()
"Replace the preceding sexp with its value."
(interactive)
(backward-kill-sexp)
(condition-case nil
(prin1 (eval (read (current-kill 0)))
(current-buffer))
(error (message "Invalid expression")
(insert (current-kill 0)))))
(global-set-key (kbd "C-)") 'eval-and-replace)
#+end_src
* Unfill
#+begin_src emacs-lisp
(use-package! unfill
:commands (unfill-paragraph
unfill-region)
:bind ("M-Q" . unfill-paragraph))
#+end_src
* EMMS
#+begin_src emacs-lisp
(use-package! emms
:commands (emms
emms-play-file
emms-play-directory
emms-smart-browse)
:config
(let ((emms-player-base-format-list
;; Add some VGM formats to the list for VLC to play
(append emms-player-base-format-list '("nsf" "spc" "gym"))))
(require 'emms-player-vlc))
(require 'emms-setup)
(emms-all)
(setq emms-player-list '(emms-player-vlc))
;; Use the installed VLC app if we're in OSX
(if (f-exists? "/Applications/VLC.app/Contents/MacOS/VLC")
(setq emms-player-vlc-command-name
"/Applications/VLC.app/Contents/MacOS/VLC")))
(map! :leader
(:prefix-map ("x" . "EMMS")
:desc "Play file" "f" #'emms-play-file
:desc "Play directory" "d" #'emms-play-directory
:desc "Smart Browser" "b" #'emms-smart-browse))
#+end_src
* Kubernetes
#+begin_src emacs-lisp
(use-package! kubernetes
:commands (kubernetes-overview)
:config)
(set-popup-rule! "^\\*kubernetes" :ignore t)
(use-package! kubernetes-tramp
:config
(setq tramp-remote-shell-executable "sh"))
#+end_src
* Twitter
#+begin_src emacs-lisp
(define-key! twittering-mode-map
"f" #'twittering-favorite
"F" #'twittering-unfavorite)
#+end_src
* Kerl
#+begin_src emacs-lisp
(use-package! kerl
:commands (kerl-use))
#+end_src
* Jira
#+begin_src emacs-lisp
(use-package jira-api
:config (setq jira-api-host "jira.aweber.io"
jira-api-user "correlr"))
(defun my/org-clock-last-time-in-seconds ()
(save-excursion
(let ((end (save-excursion (org-end-of-subtree))))
(when (re-search-forward (concat org-clock-string
".*\\(\\[[^]]+\\]\\)--\\(\\[[^]]+\\]\\)")
end t)
(let* ((start (match-string 1))
(end (match-string 2)))
(floor (- (org-time-string-to-seconds end)
(org-time-string-to-seconds start))))))))
(defun my/org-jira-add-worklog-latest ()
(interactive)
(let ((jira-id (org-entry-get (point) "JIRA_ID"))
(seconds (my/org-clock-last-time-in-seconds)))
(when (and jira-id seconds)
(jira-api-log-work jira-id seconds)
(message
(format "Logged %d minutes to %s on JIRA"
(/ seconds 60)
jira-id)))))
(defun my/org-jira-add-worklog-total ()
(interactive)
(let ((jira-id (org-entry-get (point) "JIRA_ID"))
(seconds (* 60 (org-clock-sum-current-item))))
(when (and jira-id seconds)
(jira-api-log-work jira-id seconds)
(message
(format "Logged %d minutes to %s on JIRA"
(/ seconds 60)
jira-id)))))
(defun my/org-clock-add-jira-worklog-last ()
"Add a work log entry to a JIRA.
To log work to JIRA, set a property named JIRA_ID on the entry to be
logged to a JIRA issue ID."
(interactive)
(save-excursion
(save-window-excursion
(org-clock-goto)
(my/org-jira-add-worklog-latest))))
(defun my/org-jira-browse ()
(interactive)
(-if-let (jira-id (org-entry-get (point) "JIRA_ID"))
(let ((protocol (if jira-api-use-ssl "https" "http")))
(browse-url
(concat
protocol "://" jira-api-host "/browse/" jira-id)))))
(defun my/org-jira-list ()
(interactive)
(let ((buffer (generate-new-buffer "*org-jira*")))
(switch-to-buffer buffer)
(org-mode)
(insert "ohai")
(setq-local buffer-read-only t)
(display-buffer buffer)))
;; (add-hook 'org-clock-out-hook 'my/org-clock-add-jira-worklog-last)
(map! :map org-mode-map
"C-c j t" #'my/org-jira-add-worklog-total
"C-c j l" #'my/org-jira-add-worklog-latest
"C-c j b" #'my/org-jira-browse
"C-c j c" #'jira-api-create-issue-from-heading
"C-c j u" #'jira-api-update-issue-from-heading)
#+end_src
* Kanji Mode
Minor mode for displaying Japanese characters' stroke orders.
#+begin_src emacs-lisp
(use-package! kanji-mode
:commands kanji-mode)
#+end_src
* Kanji Glasses Mode
Study kanji by overlaying hiragana readings.
#+begin_src emacs-lisp
(use-package! kanji-glasses-mode
:commands kanji-glasses-mode)
#+end_src
* Nov Epub reader
#+begin_src emacs-lisp
(use-package! nov
:mode ("\\.epub\\'" . nov-mode)
:config
(setq nov-save-place-file (concat doom-cache-dir "nov-places")))
#+end_src
* Editing binary-compressed plist files in OSX
From https://www.emacswiki.org/emacs/MacOSXPlist#toc1
#+begin_src emacs-lisp
;; Allow editing of binary .plist files.
(add-to-list 'jka-compr-compression-info-list
["\\.plist$"
"converting text XML to binary plist"
"plutil"
("-convert" "binary1" "-o" "-" "-")
"converting binary plist to text XML"
"plutil"
("-convert" "xml1" "-o" "-" "-")
nil nil "bplist"])
;;It is necessary to perform an update!
(jka-compr-update)
#+end_src
* Miscellaneous Nonsense
** BRING ON THE ...
A silly interactive method for generating horizontal and vertical text.
#+CAPTION: M-x bring-on-the RET cats RET
#+begin_example
B R I N G O N T H E C A T S
R
I
N
G
O
N
T
H
E
C
A
T
S
#+end_example
#+begin_src emacs-lisp
(defun bring-on-the (thing)
(interactive "sBring on the: ")
(let ((upthing (seq-into (s-upcase (s-concat "bring on the " thing)) 'list)))
(insert
(s-concat
(seq-into
(-interleave upthing (-repeat (length upthing) 32))
'string)
"\n"
(seq-into
(-interleave (rest upthing) (-repeat (1- (length upthing)) ?\n))
'string)))))
#+end_src
** OwO Mode
Make reading an open buffer an exercise in insanity.
#+begin_src emacs-lisp
(use-package! owo-mode
:commands owo-mode)
#+end_src