dotfiles/.doom.d/config.org
2023-08-30 12:19:29 -04:00

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

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-html"
           :base-directory "~/org-aweber"
           :base-extension "org"
           ;; :exclude "\\(^knowledge-transfer.org$\\|-archive.org$\\)"
           :exclude "\\(^README.org$\\|^worklog\\|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"))

          ("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 "~/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\\|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

  (setq org-roam-directory (file-truename (if (f-dir? "~/org-roam") "~/org-roam"
                                            "~/org/roam")))
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

When we visit a buffer in a different slip box (different org-roam-directory) than we were visiting previously, ensure the cache is updated.

  (defvar my/org-roam-directory-cache (list
                                       (cons (expand-file-name org-roam-directory)
                                             (expand-file-name org-roam-db-location))))

  (after! savehist
  (add-to-list 'savehist-additional-variables
               'my/org-roam-directory-cache))

  (defun my/org-roam-directory--lookup (path)
    (alist-get path my/org-roam-directory-cache nil nil #'s-equals?))

  (defun my/org-roam-directory--update ()
    (setq my/org-roam-directory-cache
          (cons (cons org-roam-directory org-roam-db-location)
                (seq-filter
                 (lambda (x) (not (s-equals? org-roam-directory (car x))))
                 my/org-roam-directory-cache))))

  (add-hook! 'org-roam-buffer-prepare-hook #'my/org-roam-directory--update)

  (defun my/org-roam-find-in-directory ()
    (interactive)
    (let* ((org-roam-directory (completing-read "Roam Directory"
                                                (mapcar #'car  my/org-roam-directory-cache)))
           (org-roam-db-location (my/org-roam-directory--lookup
                                  org-roam-directory)))
      (org-roam-find-file)))

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

When setting up additional slipboxes, be sure to set both org-roam-directory and org-roam-db-location. An example .dir-locals.el:

((nil . ((eval . (setq-local org-roam-directory (expand-file-name "./")
                             org-roam-db-location (expand-file-name "./org-roam.db"))))))

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

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

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

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