dotfiles/.doom.d/config.org

64 KiB

DOOM Emacs Configuration

Look And Feel

Theme

It's Catppuccin time!

  (use-package! catppuccin-theme
    :init (setq catppuccin-flavor 'macchiato)
    :config (setq doom-theme 'catppuccin))

Default font

  (setq doom-font (font-spec :family "Inconsolata" :size 16))

Dashboard

Customize the Doom Emacs dashboard with commissioned artwork, chosen randomly. The dashboard can be reloaded with the g key.

(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)

Frame transparency

Set the background transparency to 90% so my wallpaper shows through.

  (set-frame-parameter (selected-frame) 'alpha '(98 . 98))
  (add-to-list 'default-frame-alist '(alpha . (98 . 98)))

Comic Sans Mono

Why not, it's fun 🤣

Adds a command to switch to the monospaced Comic Sans font, and make it the default font for PHP buffers.

https://dtinth.github.io/comic-mono-font/

  (defun my/buffer-face-comic-mono ()
    (interactive)
    (setq buffer-face-mode-face '(:family "Comic Mono"))
    (buffer-face-mode))

  (after! php-mode
    (add-hook 'php-mode-hook #'my/buffer-face-comic-mono))

Emoji Support

Use the Noto Emoji font.

  (setf use-default-font-for-symbols nil)
  (set-fontset-font t 'unicode "Noto Emoji" nil 'append)

Mixed Pitch

Facilitates mixing monospace and proportional fonts. I'm using an MIT-licensed version of the Edward Tufte book font because it's gorgeous.

  (use-package! mixed-pitch
    :hook ((org-mode markdown-mode rst-mode) . mixed-pitch-mode))
  (setq doom-variable-pitch-font (font-spec :family "ETBookOT"))

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.

  (if (display-graphic-p)
      (global-unset-key (kbd "C-z")))

Custom settings

Store options set via customize-* in a separate file (Emacs stores them in init.el by default).

  (setq custom-file "~/.emacs.d/custom.el")
  (if (file-exists-p custom-file)
      (load custom-file))

  (setf custom-safe-themes t)

Eval and Replace

Replace an s-expression with its value. Taken from Emacs Redux.

  (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)

Prompt for unsafe local variables

Doom sets this to :safe, logging unsafe variables for later addressing. I'd rather continue to be prompted.

  (setq enable-local-variables t)

Writing

BibTeX

Tell Emacs where to find my bibliography files. I keep most everything in my Calibre library, which I regularly export and keep synced via Nextcloud.

  (setq my/bibliographies
        '("~/Documents/bibliography/references.bib"
          "~/Documents/bibliography/calibre.bib"))

Helm BibTeX

  (setq bibtex-completion-bibliography my/bibliographies
        bibtex-completion-pdf-field "File"
        bibtex-completion-notes-path "~/org/roam")

Citar

  (setq citar-bibliography my/bibliographies
        citar-notes-paths '("~/org/roam"))

Org

Configure a variety of options and tools for Org Mode, the markup I use for everything from simple notes to task management.

  (after! org
    <<org>>)

Override DOOM indentation behavior

  (defun my/org-init-babel ()
    (setq org-src-preserve-indentation nil))

  (add-hook! 'org-mode-hook #'my/org-init-babel)

Disable DOOM's centralized attachment system

It's incompatible with all of the org files I already have using the standard setup.

  (setq org-attach-directory "data/")
  (remove-hook! 'org-load-hook
    #'(+org-init-centralized-attachments-h))

Agenda

Set up my agenda view. I use separate files for my personal TODOs and my work TODOs, synced externally using Nextcloud.

  (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) "s1326.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-overriding-header "Unscheduled")
                     (org-agenda-skip-function '(org-agenda-skip-entry-if 'scheduled 'deadline)))))

             ((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")))

LaTeX Export

Document Classes

Tell Emacs about all of the LaTeX classes I use to export documents.

  (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}"))
             ("thermal-paper"
              "\\documentclass{paper}
  \\usepackage[paperwidth=52mm]{geometry}"
              ("\\section{%s}" . "\\section*{%s}")
              ("\\subsection{%s}" . "\\subsection*{%s}")
              ("\\subsubsection{%s}" . "\\subsubsection*{%s}")
              ("\\paragraph{%s}" . "\\paragraph*{%s}")
              ("\\subparagraph{%s}" . "\\subparagraph*{%s}")))))
DnD

This adds an additional LaTeX export option that outputs documents resembling a Dungeons and Dragons manual.

  (use-package! ox-dnd
    :after ox)

Capture templates

Set up my capture templates for making new notes and journal entries.

  (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%?")
          ("c" "Cookbook Recipe" entry
           (file "~/org/cookbook/index.org")
           "%(org-chef-get-recipe-from-url)"
           :empty-lines 1)
          ;; 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)))

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.

  (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))))))

Publish projects

Tell Emacs how to build the document collections I export to HTML.

  (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-roam-html"
           :base-directory "~/roam/aweber"
           :base-extension "org"
           :publishing-directory "~/Public/roam-aweber"
           :recursive t
           :with-toc nil
           :section-numbers nil
           :auto-sitemap nil
           :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-roam-assets"
           :base-directory "~/roam/aweber"
           :base-extension "css\\|gif\\|jpe?g\\|png\\|svg\\|pdf\\|ogv\\|py\\|html\\|js\\|json\\|ya?ml"
           :include (".gitlab-ci.yml")
           :publishing-directory "~/Public/roam-aweber"
           :publishing-function org-publish-attachment
           :display-custom-times t
           :recursive t)
          ("work-roam" :components ("work-roam-html" "work-roam-assets" "work-themes"))

          ("cookbook-html"
           :base-directory "~/org/cookbook"
           :base-extension "org"
           :publishing-directory "~/Public/cookbook"
           :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\"/>")

          ("cookbook-assets"
           :base-directory "~/org/cookbook"
           :base-extension "css\\|js\\|json\\|gif\\|jpe?g\\|png\\|svg\\|pdf"
           :publishing-directory "~/Public/cookbook"
           :publishing-function org-publish-attachment
           :recursive t)
          ("cookbook" :components ("cookbook-html" "cookbook-assets"))

          ("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 "~/roam/personal"
           :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 "~/roam/personal"
           :base-extension "css\\|js\\|json\\|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)

Enhanced Confluence export

Adds my own package that extends the built-in Confluence wiki markup exporter with better formatting and macro support.

  (use-package! ox-confluence-en
    :after ox
    :commands ox-confluence-en-export-as-confluence)

Reload images on source execution

Force images to redisplay after executing a source code block, so I can immediately see the result of regenerating graphs and diagrams.

  (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)

Sticky headers

Keeps the current heading visible at the top of the Emacs window.

  (use-package! org-sticky-header
    :hook (org-mode . org-sticky-header-mode)
    :config (setq org-sticky-header-full-path 'full))

Library of Babel

Load shared code snippets to be used in org documents.

  (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))))

Nicer looking timestamps

  (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))

Tufte HTML

Gorgeous HTML exports.

  ;; (use-package! ox-tufte
  ;;   :after ox)

Journal

  (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"))

Cookbook

  (use-package! org-chef
    :commands (org-chef-get-recipe-from-url))

Ref

Tools for linking and taking notes on books and papers.

  (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/"))

Roam

Powerful cross-linked note-taking.

https://orgroam.com

Capture templates
  (setq org-roam-capture-templates
        '(("d" "default" plain "%?" :target
           (file+head "%<%Y%m%d%H%M%S>-${slug}.org" "#+title: ${title}
  ")
           :unnarrowed t)))
Add backlinks to org-roam exports
  (use-package! org-roam-export-backlinks
    :commands org-roam-export-backlinks-preprocessor
    :init
    (add-to-list 'org-export-before-processing-hook
                 #'org-roam-export-backlinks-preprocessor))
Org Roam Bibtex

Make it easy to take notes on books and papers that I'm reading.

  (use-package! org-roam-bibtex
    :after org-roam
    :hook (org-roam . org-roam-bibtex-mode)
    :bind (:map org-mode-map
           (("C-c n r b" . orb-note-actions))))
Org Roam UI

Provides a fun way to browse through a collection of notes.

  (use-package! org-roam-ui
    :after org-roam
    :commands org-roam-ui-mode
    :config
    (setq org-roam-ui-sync-theme t
          org-roam-ui-follow t
          org-roam-ui-update-on-save t
          org-roam-ui-open-on-start t))
Use writeroom in org-roam buffers

Makes for a much nicer note-taking experience.

  (defun my/org-roam-writeroom ()
    ;; Use a buffer-local local variables hook to ensure the org-roam-directory is
    ;; set properly
    (add-hook 'hack-local-variables-hook
              (lambda ()
                (when (and org-roam-directory
                           (f-ancestor-of?
                            (expand-file-name org-roam-directory)
                            (or (buffer-file-name) default-directory)))
                  (writeroom-mode t)))
              nil t))

  (add-hook! 'org-mode-hook #'my/org-roam-writeroom)
Provide seamless switching between org-roam slipboxes

I keep multiple slipboxes under a common directory, some of which may or may not be available on different machines (e.g. a work slipbox vs a personal one). This gives me an interface for easily switching between them and resyncing their databases. The databases are kept out of the slipbox directories themselves to keep them tidy.

  (defvar my/org-roam-slipbox-directory (expand-file-name "~/roam"))

  (defun my/slipbox-directories ()
    (f-directories my/org-roam-slipbox-directory))

  (defun my/slipbox-names ()
    (-map #'f-filename (my/slipbox-directories)))

  (defun my/slipbox--directory (slipbox)
    (f-join my/org-roam-slipbox-directory slipbox))

  (defun my/slipbox--database (slipbox)
    (f-join my/org-roam-slipbox-directory (s-concat slipbox ".db")))

  (defmacro my/with-slipbox (slipbox &rest body)
    `(let ((org-roam-directory (my/slipbox--directory ,slipbox))
           (org-roam-db-location (my/slipbox--database ,slipbox)))
       ,@body))

  (defun my/org-roam-slipbox-node-find ()
    (interactive)
    (let ((slipbox (completing-read "Slipbox: " (my/slipbox-names))))
      (setq  org-roam-directory (my/slipbox--directory slipbox)
             org-roam-db-location (my/slipbox--database slipbox))
      (unless (f-exists? org-roam-db-location)
        (org-roam-db-sync))
      (org-roam-node-find)))

  (defun my/org-roam-slipbox-db-sync ()
    (interactive)
    (--each (my/slipbox-names)
      (my/with-slipbox it (org-roam-db-sync))))

  (defun my/slipbox-select (slipbox)
    (interactive
     (list (completing-read "Slipbox: " (my/slipbox-names))))
    (unless (member slipbox (my/slipbox-names))
      (error "Unknown slipbox %s" slipbox))
    (setq  org-roam-directory (my/slipbox--directory slipbox)
           org-roam-db-location (my/slipbox--database slipbox))
    (unless (f-exists? org-roam-db-location)
      (org-roam-db-sync))
    slipbox)

  (map! :leader
        (:prefix-map ("n" . "notes")
                     (:prefix ("r" . "roam")
                      :desc "Select slipbox" "c" #'my/slipbox-select)))

  (map! :leader
        (:prefix-map ("n" . "notes")
                     (:prefix ("r" . "roam")
                      :desc "Find file in slipbox" "F" #'my/org-roam-slipbox-node-find)))

  (map! :leader
        (:prefix-map ("n" . "notes")
                     (:prefix ("r" . "roam")
                      :desc "Synchronize all slipbox databases" "S" #'my/org-roam-slipbox-db-sync)))

  ;; Set a default org-roam directory from the available slipboxes
  (if-let (slipbox (-first-item (my/slipbox-names)))
      (my/slipbox-select slipbox))

Sidebar

Display a sidebar with file-local todos and scheduling.

  (use-package! org-sidebar
    :bind (:map org-mode-map
           (("C-c l v s" . org-sidebar-toggle)
            ("C-c l v S" . org-sidebar-tree-toggle)))
    :commands (org-sidebar
               org-sidebar-toggle
               org-sidebar-tree
               org-sidebar-tree-toggle))

Transclusion

Show linked org document sections inline.

  (use-package! org-transclusion
    :after org
    :init
    (map!
     :map global-map "<f12>" #'org-transclusion-add
     :leader
     :prefix "n"
     :desc "Org Transclusion Mode" "t" #'org-transclusion-mode))

Ditaa

Download and use a recent version of ditaa for rendering ASCII diagrams.

  (after! ob-ditaa
    (let ((jar-url "https://github.com/stathissideris/ditaa/releases/download/v0.11.0/ditaa-0.11.0-standalone.jar")
          (jar-path (concat doom-etc-dir "ditaa.jar")))
      (unless (f-exists? jar-path)
        (url-copy-file jar-url jar-path))
      (setq org-ditaa-jar-path jar-path
            org-ditaa-eps-jar-path jar-path)))

DND

  (use-package! org-d20
    :after org)

ReStructuredText

  (use-package! polymode
    :defer t)

  (use-package! poly-rst
    :mode ("\\.rst\\'" . poly-rst-mode))

Unfill

Does the opposite of fill (M-q), removing line breaks from a paragraph or region.

  (use-package! unfill
    :commands (unfill-paragraph
               unfill-region)
    :bind ("M-Q" . unfill-paragraph))

Reading

Epub reader

A major mode for reading and navigating .epub files.

  (use-package! nov
    :mode ("\\.epub\\'" . nov-mode)
    :config
    (setq nov-save-place-file (concat doom-cache-dir "nov-places")))

Kanji Mode

Minor mode for displaying Japanese characters' stroke orders.

  (use-package! kanji-mode
    :commands kanji-mode)

Kanji Glasses Mode

Study kanji by overlaying hiragana readings.

  (use-package! kanji-glasses-mode
    :commands kanji-glasses-mode)

Coding

Arduino

  (use-package! arduino-mode
    :mode "\\.ino\\'")

  (use-package! arduino-cli-mode
    :hook arduino-mode
    :custom
    (arduino-cli-warnings 'all)
    (arduino-cli-verify t))

Erlang

Kerl

Select the active erlang installation managed with kerl.

  (use-package! kerl
    :commands (kerl-use))

Lisp

Paredit

Adds shortcuts to edit the structure of lisp code.

  (use-package! paredit
    :hook ((emacs-lisp-mode . enable-paredit-mode)))

OpenSCAD

Mode for editing OpenSCAD 3D modeling files. Files can be opened externally for live-updated previews within OpenSCAD itself using C-c C-o.

  (use-package! scad-mode
    :mode "\\.scad\\'")

Applications

Email

Configure MU4E to read email synced from my personal and work accounts.

  (use-package! mu4e
    :bind (("<f9>" . mu4e))
    :config

    (require 'f)

    (setq mu4e-maildir "~/Mail")

    (setq user-full-name "Correl Roush")
    (setq mu4e-contexts nil)
    (when (f-exists?
           (f-join mu4e-maildir "Work"))
      (add-to-list
       'mu4e-contexts
       (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
                                             (when (f-exists? "~/.signature-aweber")
                                               (insert-file-contents "~/.signature-aweber"))
                                             (buffer-string)))
                (smtpmail-smtp-user . "correlr@aweber.com")
                (smtpmail-smtp-server . "smtp.gmail.com")
                (smtpmail-smtp-service . 465)
                (smtpmail-stream-type . ssl)))))
    (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
                                             (when (f-exists? "~/.signature")
                                               (insert-file-contents "~/.signature"))
                                             (buffer-string)))
                (smtpmail-smtp-user . "correl@gmail.com")
                (smtpmail-smtp-server . "smtp.gmail.com")
                (smtpmail-smtp-service . 465)
                (smtpmail-stream-type . ssl)))))
    (setq mu4e-context-policy 'pick-first)
    (setq mu4e-compose-context-policy 'ask)
    (setq mu4e-compose-dont-reply-to-self t)
    (setq mu4e-index-lazy-check nil)
    (setq mu4e-headers-include-related nil)
    (setq mu4e-headers-skip-duplicates t)
    (setq mu4e-user-mail-address-list '("correlr@aweber.com"
                                        "correl@gmail.com")))

Prefer sending HTML-formatted messages with plain text as a fallback option (alternative formats should be specified in increasing level of preference per RFC-1341).

  (use-package! org-msg
    :after mu4e
    :config
    (setq org-msg-default-alternatives '(text html)
          org-msg-options "html-postamble:nil toc:nil author:nil email:nil ^:nil"))

Chat

Circe

  (after! circe
    (set-irc-server!
     "liberachat"
     `(:tls nil
       :host "znc.phoenixinquis.is-a-geek.org"
       :port 8667
       :nick "correl"
       :user "correl/liberachat"
       :pass (lambda (&rest _) (+pass-get-secret "Social/znc.phoenixinquis.is-a-geek.org/correl"))))
    (set-irc-server!
     "twitch"
     `(:tls nil
       :host "znc.phoenixinquis.is-a-geek.org"
       :port 8667
       :nick "correl"
       :user "correl/twitch"
       :pass (lambda (&rest _) (+pass-get-secret "Social/znc.phoenixinquis.is-a-geek.org/correl")))))

Matrix

  (use-package! ement
    :commands 'ement-connect
    :init
    (defvar +matrix-workspace-name "*Matrix*")
    (defvar +matrix--old-wconf nil)
    (defun +matrix ()
      (interactive)
      (if (modulep! :ui workspaces)
          (+workspace/new +matrix-workspace-name)
        (setq +matrix--old-wconf (current-window-configuration))
        (delete-other-windows)
        (switch-to-buffer (doom-fallback-buffer)))
      (call-interactively #'ement-connect))
    (defun +matrix/quit ()
      (interactive)
      (ement-disconnect (mapcar #'cdr ement-sessions))
      (when (modulep! :ui workspaces)
        (+workspace/delete +matrix-workspace-name))
      (when +matrix--old-wconf
        (set-window-configuration +matrix--old-wconf)
        (setq +matrix--old-wconf nil)))
    :config
    (when (modulep! :ui popup)
      (set-popup-rule! "\\*Ement " :ignore t))
    :custom
    (ement-room-prism 'both)
    (ement-save-sessions t) ;; Beware, this stores your token to disk in plain text!
    )

Music

Configure EMMS for playing music files on my computer.

  (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))

News Aggregation

Read blogs and articles from the RSS feeds I follow.

  (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"))

    (add-hook! 'elfeed-show-mode-hook #'mixed-pitch-mode))

Kubernetes

Manage a Kubernetes cluster and set up remote shell/file access via TRAMP.

  (use-package! kubernetes
    :commands (kubernetes-overview)
    :config)

  (set-popup-rule! "^\\*kubernetes" :ignore t)

  (use-package! kubernetes-tramp
    :commands (eshell find-file)
    :config
    (setq tramp-remote-shell-executable "sh"))

Project Management

Projectile

Pre-load Projectile with projects in my usual code directories.

  (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))

Jira

Add some commands for interacting with Jira within org documents.

  (after! org
    (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))

Source Control

  (after! forge
    (add-to-list
     'forge-alist
     '("gitlab.aweber.io" "gitlab.aweber.io/api/v4" "gitlab.aweber.io" forge-gitlab-repository)))

Eshell

Change directory in the context of a remote host

Add an lcd command that functions similarly to cd, but is scoped to the remote host being accessed. Basically means I can use lcd / and other absolute paths and not worry about being bounced back to my local filesystem.

  (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)))

Disable company autocompletion on remote hosts

  (defun my/toggle-shell-autocomplete ()
    (when (fboundp 'company-mode)
      (company-mode (if (file-remote-p default-directory) -1 1))))
  
  (add-hook! 'eshell-directory-change-hook #'my/toggle-shell-autocomplete)

Background Processes

Manage background services

  (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))
    (when (f-exists? (expand-file-name "~/Public/roam"))
      (prodigy-define-service
        :name "Org Roam Documents"
        :command "python"
        :args '("-m" "http.server" "3002")
        :cwd (expand-file-name "~/Public/roam")
        :tags '(personal autostart)
        :kill-signal 'sigkill))
    (when (f-exists? (expand-file-name "~/code/correl.github.io"))
      (prodigy-define-service
        :name "Blog"
        :command "hugo"
        :args '("serve" "-D")
        :cwd (expand-file-name "~/code/correl.github.io")
        :rags '(personal)
        :kill-signal 'sigkill))
    (mapcar
     #'prodigy-start-service
     (-concat (prodigy-services-tagged-with 'autostart))))

Screen Sharing

Showing keypresses

(use-package! keypression
  :commands (keypression-mode)
  :bind (("C-c t k" . keypression-mode))
  :config
  (setq keypression-fade-out-delay 2.0
        keypression-cast-command-name t
        keypression-combine-same-keystrokes t
        keypression-combine-format "%s (%d times)"
        keypression-y-offset 100
        keypression-font-face-attribute '(:height 400 :weight bold)))

Swagger API documentation

Make calls to swagger-documented APIs with completion support.

    (use-package! swagg
      :commands (swagg-request swagg-request-with-rest-block)
      :config (setq swagg-rest-block-prelude "#+BEGIN_SRC http
  "
                    swagg-rest-block-postlude "#+END_SRC"))

UUID Generation

  (use-package! uuidgen
    :commands (uuidgen))

Wordle

  (use-package! wordle
    :commands (wordel wordel-marathon))

Operating Systems

Linux

EXWM

Set Emacs + EXWM as the default X window manager
[Desktop]
session=~/.doom.d/start-exwm.sh
emacs -mm -l ~/.doom.d/exwm.el
Configure EXWM
  • Sets the desktop background
  • Starts a bar/system tray and various applets
  • Sets up workspaces
  • Names X window buffers based on which application is running
(defun my/exwm-update-class ()
  (exwm-workspace-rename-buffer exwm-class-name))
(defun my/run-in-background (command)
  (let ((command-parts (split-string command "[ ]+")))
    (apply #'call-process `(,(car command-parts) nil 0 nil ,@(cdr command-parts)))))
(defun my/set-desktop-background ()
  (interactive)
  (start-process-shell-command "feh" nil "feh --bg-scale ~/Pictures/Wallpapers/1520742811045.png"))
(defun my/exwm-init-hook ()
  ;; Start tint2 bar
  (my/run-in-background "tint2")
  ;; Start system tray applets
  (my/run-in-background "nm-applet")
  (my/run-in-background "pasystray")
  (my/run-in-background "blueman-applet")
  (my/run-in-background "nextcloud --background")
  (my/run-in-background "compton"))
(use-package! exwm
  :config
  (setq exwm-input-global-keys
        `(([?\s-r] . exwm-reset)
          ([?\s-w] . exwm-workspace-switch)
          ,@(mapcar (lambda (i)
                      `(,(kbd (format "s-%d" i)) .
                        (lambda ()
                          (interactive)
                          (exwm-workspace-switch-create ,(- i 1)))))
                    (number-sequence 1 9))
          ([?\s-&] . (lambda (command)
                       (interactive (list (read-shell-command "$ ")))
                       (start-process-shell-command command nil command)))))
  (setq exwm-workspace-number 4)
  (exwm-input-set-key (kbd "s-SPC") #'counsel-linux-app)
  (add-hook! 'exwm-update-class-hook #'my/exwm-update-class)
  (add-hook! 'exwm-init-hook #'my/exwm-init-hook)
  (my/set-desktop-background)
  (exwm-enable))
(use-package! exwm-config
  :after exwm)
(use-package! desktop-environment
  :after exwm
  :config
  (desktop-environment-mode))
Application launcher

Use counsel as an application launcher. Scans for .desktop files in all the usual places.

(use-package! counsel
  :custom (counsel-linux-app-format-function #'counsel-linux-app-format-function-name-only)
  :config (counsel-mode 1))

OSX

Editing binary-compressed plist files

From https://www.emacswiki.org/emacs/MacOSXPlist#toc1

  ;; 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)

Miscellaneous Nonsense

BRING ON THE …

A silly interactive method for generating horizontal and vertical text.

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
M-x bring-on-the RET cats RET
  (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)))))

OwO Mode

Make reading an open buffer an exercise in insanity.

  (use-package! owo-mode
    :commands owo-mode)

Elcord

Emits rich presence to Discord.

  (use-package! elcord
    :config
    (setq elcord-display-buffer-details nil
          elcord-quiet t
          elcord-editor-icon "emacs_material_icon"
          elcord-use-major-mode-as-main-icon t)
    (elcord-mode t))

MTG Mode

  (use-package! mtg
    :commands (mtg-mode))