#+TITLE: DOOM Emacs Configuration #+STARTUP: indent overview #+PROPERTY: header-args :tangle config.el * Look And Feel ** Theme I'm feeling the gruvbox theme lately. #+begin_src emacs-lisp (setq doom-theme 'doom-gruvbox) #+end_src ** Default font #+begin_src emacs-lisp (setq doom-font (font-spec :family "Inconsolata" :size 16)) #+end_src ** Dashboard Customize the Doom Emacs dashboard with commissioned artwork, chosen randomly. The dashboard can be reloaded with the =g= key. #+begin_src emacs-lisp (defun my/choice (&rest things) (let ((index (random (length things)))) (nth index things))) (defun my/dashboard-randomize () (interactive) (let ((logo (my/choice "~/Pictures/Patreon/Jon Bliss/phoenix-and-catgirl-500.png" "~/Pictures/Patreon/Jon Bliss/Bassist-final-transparent-500.png" "~/Pictures/Patreon/Jon Bliss/catgirl-final-transparent-500.png" "~/Pictures/Patreon/Jon Bliss/lapiz-final-transparent-500.png" "~/Pictures/Patreon/Jon Bliss/Poledancer-final-transparent-500.png" "~/Pictures/Patreon/Jon Bliss/Hacker-final-transparent-500.png" "~/Pictures/Patreon/Bee/IMG_3666_transparent_500.png"))) (setq fancy-splash-image logo) (if (called-interactively-p) (+doom-dashboard-reload)))) (my/dashboard-randomize) (map! :map +doom-dashboard-mode-map "g" #'my/dashboard-randomize) #+end_src ** Frame transparency Set the background transparency to 90% so my wallpaper shows through. #+begin_src emacs-lisp (set-frame-parameter (selected-frame) 'alpha '(90 . 90)) (add-to-list 'default-frame-alist '(alpha . (90 . 90))) #+end_src ** 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/ #+begin_src emacs-lisp (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)) #+end_src ** Mixed Pitch Facilitates mixing monospace and proportional fonts. I'm using an MIT-licensed version of the [[https://github.com/edwardtufte/et-book][Edward Tufte book font]] because it's /gorgeous/. #+begin_src emacs-lisp (use-package! mixed-pitch :hook ((org-mode markdown-mode rst-mode) . mixed-pitch-mode)) (setq doom-variable-pitch-font (font-spec :family "ETBookOT")) #+end_src * UX Improvements ** Disable C-z backgrounding when the GUI is loaded In OSX, this minimizes the window. It's incredibly annoying, and I accidentally hit it too much. I also can't think of any reason I'd want to background Emacs outside of a terminal session. #+begin_src emacs-lisp (if (display-graphic-p) (global-unset-key (kbd "C-z"))) #+end_src ** Custom settings Store options set via =customize-*= in a separate file (Emacs stores them in =init.el= by default). #+BEGIN_SRC emacs-lisp (setq custom-file "~/.emacs.d/custom.el") (if (file-exists-p custom-file) (load custom-file)) (setf custom-safe-themes t) #+END_SRC ** Eval and Replace Replace an s-expression with its value. Taken from [[http://emacsredux.com/blog/2013/06/21/eval-and-replace/][Emacs Redux]]. #+begin_src emacs-lisp (defun eval-and-replace () "Replace the preceding sexp with its value." (interactive) (backward-kill-sexp) (condition-case nil (prin1 (eval (read (current-kill 0))) (current-buffer)) (error (message "Invalid expression") (insert (current-kill 0))))) (global-set-key (kbd "C-)") 'eval-and-replace) #+end_src ** Prompt for unsafe local variables Doom sets this to =:safe=, logging unsafe variables for later addressing. I'd rather continue to be prompted. #+begin_src emacs-lisp (setq enable-local-variables t) #+end_src * 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. #+begin_src emacs-lisp (setq my/bibliographies '("~/Documents/bibliography/references.bib" "~/Documents/bibliography/calibre.bib")) #+end_src *** Helm BibTeX #+begin_src emacs-lisp (setq bibtex-completion-bibliography my/bibliographies bibtex-completion-pdf-field "File" bibtex-completion-notes-path "~/org/roam") #+end_src *** Citar #+begin_src emacs-lisp (setq citar-bibliography my/bibliographies citar-notes-paths '("~/org/roam")) #+end_src ** Org :PROPERTIES: :header-args: :tangle no :noweb-ref org :END: Configure a variety of options and tools for [[https://orgmode.org][Org Mode]], the markup I use for everything from simple notes to task management. #+begin_src emacs-lisp :noweb yes :tangle "config.el" :noweb-ref org-all (after! org <>) #+end_src *** Override DOOM indentation behavior #+begin_src emacs-lisp (defun my/org-init-babel () (setq org-src-preserve-indentation nil)) (add-hook! 'org-mode-hook #'my/org-init-babel) #+end_src *** Disable DOOM's centralized attachment system It's incompatible with all of the org files I already have using the standard setup. #+begin_src emacs-lisp (setq org-attach-directory "data/") (remove-hook! 'org-load-hook #'(+org-init-centralized-attachments-h)) #+end_src *** Agenda Set up my agenda view. I use separate files for my personal TODOs and my work TODOs, synced externally using Nextcloud. #+begin_src emacs-lisp (after! org-agenda (require 'f) (setq my/agenda-files '((personal . ("~/Nextcloud/org/personal.org")) (work . ("~/Nextcloud/org/aweber.org"))) org-agenda-files (-filter #'f-exists? (-concat (if (string-equal (system-name) "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"))) #+end_src *** LaTeX Export **** Document Classes Tell Emacs about all of the LaTeX classes I use to export documents. #+BEGIN_SRC emacs-lisp (use-package! ox-latex :config (seq-map (apply-partially #'add-to-list 'org-latex-classes) '(("koma-letter" "\\documentclass{scrlttr2}" ("\\section{%s}" . "\\section*{%s}") ("\\subsection{%s}" . "\\subsection*{%s}") ("\\subsubsection{%s}" . "\\subsubsection*{%s}") ("\\paragraph{%s}" . "\\paragraph*{%s}") ("\\subparagraph{%s}" . "\\subparagraph*{%s}")) ("koma-article" "\\documentclass{scrartcl}" ("\\section{%s}" . "\\section*{%s}") ("\\subsection{%s}" . "\\subsection*{%s}") ("\\subsubsection{%s}" . "\\subsubsection*{%s}") ("\\paragraph{%s}" . "\\paragraph*{%s}") ("\\subparagraph{%s}" . "\\subparagraph*{%s}")) ("koma-book" "\\documentclass{scrbook}" ("\\section{%s}" . "\\section*{%s}") ("\\subsection{%s}" . "\\subsection*{%s}") ("\\subsubsection{%s}" . "\\subsubsection*{%s}") ("\\paragraph{%s}" . "\\paragraph*{%s}") ("\\subparagraph{%s}" . "\\subparagraph*{%s}")) ("koma-book-chapters" "\\documentclass{scrbook}" ("\\chapter{%s}" . "\\chapter*{%s}") ("\\section{%s}" . "\\section*{%s}") ("\\subsection{%s}" . "\\subsection*{%s}") ("\\subsubsection{%s}" . "\\subsubsection*{%s}") ("\\paragraph{%s}" . "\\paragraph*{%s}") ("\\subparagraph{%s}" . "\\subparagraph*{%s}")) ("koma-report" "\\documentclass{scrreprt}" ("\\chapter{%s}" . "\\chapter*{%s}") ("\\section{%s}" . "\\section*{%s}") ("\\subsection{%s}" . "\\subsection*{%s}") ("\\subsubsection{%s}" . "\\subsubsection*{%s}") ("\\paragraph{%s}" . "\\paragraph*{%s}") ("\\subparagraph{%s}" . "\\subparagraph*{%s}")) ("memoir" "\\documentclass{memoir}" ("\\section{%s}" . "\\section*{%s}") ("\\subsection{%s}" . "\\subsection*{%s}") ("\\subsubsection{%s}" . "\\subsubsection*{%s}") ("\\paragraph{%s}" . "\\paragraph*{%s}") ("\\subparagraph{%s}" . "\\subparagraph*{%s}")) ("hitec" "\\documentclass{hitec}" ("\\section{%s}" . "\\section*{%s}") ("\\subsection{%s}" . "\\subsection*{%s}") ("\\subsubsection{%s}" . "\\subsubsection*{%s}") ("\\paragraph{%s}" . "\\paragraph*{%s}") ("\\subparagraph{%s}" . "\\subparagraph*{%s}")) ("paper" "\\documentclass{paper}" ("\\section{%s}" . "\\section*{%s}") ("\\subsection{%s}" . "\\subsection*{%s}") ("\\subsubsection{%s}" . "\\subsubsection*{%s}") ("\\paragraph{%s}" . "\\paragraph*{%s}") ("\\subparagraph{%s}" . "\\subparagraph*{%s}")) ("letter" "\\documentclass{letter}" ("\\section{%s}" . "\\section*{%s}") ("\\subsection{%s}" . "\\subsection*{%s}") ("\\subsubsection{%s}" . "\\subsubsection*{%s}") ("\\paragraph{%s}" . "\\paragraph*{%s}") ("\\subparagraph{%s}" . "\\subparagraph*{%s}")) ("tufte-handout" "\\documentclass{tufte-handout}" ("\\section{%s}" . "\\section*{%s}") ("\\subsection{%s}" . "\\subsection*{%s}") ("\\subsubsection{%s}" . "\\subsubsection*{%s}") ("\\paragraph{%s}" . "\\paragraph*{%s}") ("\\subparagraph{%s}" . "\\subparagraph*{%s}")) ("tufte-book" "\\documentclass{tufte-book}" ("\\section{%s}" . "\\section*{%s}") ("\\subsection{%s}" . "\\subsection*{%s}") ("\\subsubsection{%s}" . "\\subsubsection*{%s}") ("\\paragraph{%s}" . "\\paragraph*{%s}") ("\\subparagraph{%s}" . "\\subparagraph*{%s}")) ("tufte-book-chapters" "\\documentclass{tufte-book}" ("\\chapter{%s}" . "\\chapter*{%s}") ("\\section{%s}" . "\\section*{%s}") ("\\subsection{%s}" . "\\subsection*{%s}") ("\\subsubsection{%s}" . "\\subsubsection*{%s}") ("\\paragraph{%s}" . "\\paragraph*{%s}") ("\\subparagraph{%s}" . "\\subparagraph*{%s}")) ("labbook" "\\documentclass{labbook}" ("\\chapter{%s}" . "\\chapter*{%s}") ("\\section{%s}" . "\\section*{%s}") ("\\subsection{%s}" . "\\labday{%s}") ("\\subsubsection{%s}" . "\\experiment{%s}") ("\\paragraph{%s}" . "\\paragraph*{%s}") ("\\subparagraph{%s}" . "\\subparagraph*{%s}"))))) #+END_SRC **** DnD This adds an additional LaTeX export option that outputs documents resembling a Dungeons and Dragons manual. #+begin_src emacs-lisp (use-package! ox-dnd :after ox) #+end_src *** Capture templates Set up my capture templates for making new notes and journal entries. #+begin_src emacs-lisp (setq org-capture-templates `( ;; Personal ("j" "Journal Entry" plain (file+datetree "~/org/journal.org") "%U\n\n%?" :empty-lines-before 1) ("t" "TODO" entry (file+headline "~/Nextcloud/org/personal.org" "Unsorted") "* TODO %^{Description}\n%?") ("n" "Note" entry (file+headline "~/Nextcloud/org/personal.org" "Notes") "* %^{Description}\n%U\n\n%?") ;; Org-Protocol ("b" "Bookmark" entry (file+headline "~/org/bookmarks.org" "Unsorted") "* %^{Title}\n\n Source: %u, %c\n\n %i") ("p" "Webpage" entry (file "~/org/articles.org") "* %a\n\n%U %?\n\n%:initial") ;; Email ;; https://martinralbrecht.wordpress.com/2016/05/30/handling-email-with-emacs/ ("r" "respond to email (mu4e)" entry (file+headline "~/org/todo.org" "Email") "* REPLY to [[mailto:%:fromaddress][%:fromname]] on %a\nDEADLINE: %(org-insert-time-stamp (org-read-date nil t \"+1d\"))\n%U\n\n" :immediate-finish t :prepend t) ;; Work ("w" "Work") ("wt" "Work TODO" entry (file+headline "~/Nextcloud/org/aweber.org" "Unsorted") "* TODO %^{Description}\n%?") ("wl" "Log Work Task" entry (file+datetree "~/org-aweber/worklog.org") "* %^{Description} %^g\nAdded: %U\n\n%?" :clock-in t :clock-keep t) ("wL" "Log Work Task (no clock)" entry (file+datetree "~/org-aweber/worklog.org") "* %^{Description} %^g\nAdded: %U\n\n%?") ("wj" "Log work on JIRA issue" entry (file+datetree "~/org-aweber/worklog.org") ,(concat "* %?\n" ":PROPERTIES:\n" ":JIRA_ID: %^{JIRA_ID}\n" ":END:\n" "Added: %U\n\n" "[[jira:%\\1][%\\1]]") :clock-in t :clock-keep t) ("wr" "respond to email (mu4e)" entry (file+headline "~/Nextcloud/org/aweber.org" "Unsorted") "* REPLY to [[mailto:%:fromaddress][%:fromname]] on %a\nDEADLINE: %(org-insert-time-stamp (org-read-date nil t \"+1d\"))\n%U\n\n" :immediate-finish t :prepend t))) #+end_src *** Custom ID generation Because I'm all kinds of crazy, I like the custom IDs of my work log entries to be based on their headings. #+begin_src emacs-lisp (use-package! org-id :after org :config ;; https://writequit.org/articles/emacs-org-mode-generate-ids.html#automating-id-creation (defun eos/org-custom-id-get (&optional pom create prefix) "Get the CUSTOM_ID property of the entry at point-or-marker POM. If POM is nil, refer to the entry at point. If the entry does not have an CUSTOM_ID, the function returns nil. However, when CREATE is non nil, create a CUSTOM_ID if none is present already. PREFIX will be passed through to `org-id-new'. In any case, the CUSTOM_ID of the entry is returned." (interactive) (org-with-point-at pom (let ((id (org-entry-get nil "CUSTOM_ID"))) (cond ((and id (stringp id) (string-match "\\S-" id)) id) (create (setq id (org-id-new (concat prefix "h"))) (org-entry-put pom "CUSTOM_ID" id) (org-id-add-location id (buffer-file-name (buffer-base-buffer))) id))))) (defun eos/org-add-ids-to-headlines-in-file () "Add CUSTOM_ID properties to all headlines in the current file which do not already have one. Only adds ids if the `auto-id' option is set to `t' in the file somewhere. ie, ,#+OPTIONS: auto-id:t" (interactive) (save-excursion (widen) (goto-char (point-min)) (when (re-search-forward "^#\\+OPTIONS:.*auto-id:t" (point-max) t) (org-map-entries (lambda () (eos/org-id-get (point) 'create))))) (save-excursion (widen) (goto-char (point-min)) (when (re-search-forward "^#\\+OPTIONS:.*auto-id:worklog" (point-max) t) (let ((my/org-worklog-id-depth 2)) (org-map-entries (lambda () (my/org-worklog-id-get (point) 'create)))))) (save-excursion (widen) (goto-char (point-min)) (when (re-search-forward "^#\\+OPTIONS:.*auto-id:readable" (point-max) t) (let ((my/org-worklog-id-depth 0)) (org-map-entries (lambda () (my/org-worklog-id-get (point) 'create))))))) ;; automatically add ids to saved org-mode headlines (add-hook 'org-mode-hook (lambda () (add-hook 'before-save-hook (lambda () (when (and (eq major-mode 'org-mode) (eq buffer-read-only nil)) (eos/org-add-ids-to-headlines-in-file)))))) (defun my/org-remove-all-ids () (interactive) (save-excursion (widen) (goto-char (point-min)) (org-map-entries (lambda () (org-entry-delete (point) "CUSTOM_ID"))))) (defvar my/org-worklog-id-depth 2) (defun my/org-worklog-id-new (&optional prefix) (let ((path (or (-drop my/org-worklog-id-depth (org-get-outline-path t)) (last (org-get-outline-path t))))) (mapconcat (lambda (s) (->> s (s-downcase) (s-replace-regexp "[^[:alnum:]]+" "-"))) path "-"))) (defun my/org-worklog-id-get (&optional pom create prefix) (interactive) (org-with-point-at pom (let ((id (org-entry-get nil "CUSTOM_ID"))) (cond ((and id (stringp id) (string-match "\\S-" id)) id) (create (setq id (my/org-worklog-id-new prefix)) (org-entry-put pom "CUSTOM_ID" id) id)))))) #+end_src *** Publish projects Tell Emacs how to build the document collections I export to HTML. #+begin_src emacs-lisp (require 'org-attach) (setq org-html-mathjax-options '((path "https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.2/MathJax.js?config=TeX-AMS-MML_HTMLorMML"))) (setq org-re-reveal-root "https://cdn.jsdelivr.net/reveal.js/3.0.0/") (defun my/org-work-publish-to-html (plist filename pub-dir) (message "Publishing %s" filename) (cond ((string-match-p "slides.org$" filename) (org-re-reveal-publish-to-reveal plist filename pub-dir)) (t (let ((org-html-head (concat ;; Tufte ;; "" ;; "" ;; Org-Spec ;; "" ;; "" ;; "" ;; ReadTheOrg "" "" "" "" "" "" "" ;; Bigblow ;; "" ;; "" ;; "" ;; "" ;; "" ;; "" ;; "" ;; "" ;; "" ;; "" ;; "" ))) (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 "" ;; :html-head-extra "" ;; :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 "") ("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 "" :html-head-extra "" :publishing-directory "~/Public/org" :publishing-function org-html-publish-to-tufte-html) ("work" :components ("work-html" "work-roam-html" "work-todo" "work-assets" "work-themes")) ("dotfiles-common" :base-directory "~/dotfiles" :publishing-directory "~/Public/dotfiles" :base-extension "css\\|gif\\|jpe?g\\|png\\|svg" :recursive t :publishing-function org-publish-attachment) ("dotfiles-html" :base-directory "~/dotfiles" :base-extension "org" :publishing-directory "~/Public/dotfiles" :publishing-function (org-html-publish-to-html org-babel-tangle-publish) :htmlized-source t :html-head "" :html-head-extra "" :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 "" "")) ;; Org-Spec ;; "" ;; "" ("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 "") ("roam-assets" :base-directory "~/org/roam" :base-extension "css\\|gif\\|jpe?g\\|png\\|svg\\|pdf" :publishing-directory "~/Public/roam" :publishing-function org-publish-attachment :recursive t) ("roam" :components ("roam-html" "roam-assets")) ("sicp-html" :base-directory "~/code/sicp" :base-extension "org" :publishing-directory "~/Public/sicp" :publishing-function (org-html-publish-to-html org-org-publish-to-org org-babel-tangle-publish) :htmlized-source t :html-head "" :html-link-home "/" :html-doctype "html5" :html-html5-fancy t :with-sub-superscript nil :auto-sitemap t :sitemap-filename "index.org" :sitemap-title "SICP Exercises and Notes" :sitemap-sort-folders last :recursive t) ("sicp-assets" :base-directory "~/code/sicp" :base-extension "css\\|gif\\|jpe?g\\|png\\|svg\\|scheme\\|pl" :publishing-directory "~/Public/sicp" :publishing-function org-publish-attachment :recursive t) ("sicp" :components ("sicp-html" "sicp-assets")))) ;; Don't prompt for babel evaluation, ever. (setq org-confirm-babel-evaluate nil) (require 'ox-confluence) (defun my/org-publish () (interactive) (org-publish "work") (let ((org-link-abbrev-alist (seq-concatenate 'list org-link-abbrev-alist '(("jira" . "https://jira.aweber.io/browse/") ("gitlab" . "https://gitlab.aweber.io/"))))) (org-store-agenda-views)) (shell-command "org-publish")) (bind-key "C-c o p" #'my/org-publish) #+end_src *** Enhanced Confluence export Adds [[https://github.com/correl/ox-confluence-en][my own package]] that extends the built-in Confluence wiki markup exporter with better formatting and macro support. #+begin_src emacs-lisp (use-package! ox-confluence-en :after ox :commands ox-confluence-en-export-as-confluence) #+end_src *** Reload images on source execution Force images to redisplay after executing a source code block, so I can immediately see the result of regenerating graphs and diagrams. #+begin_src emacs-lisp (defun my/redisplay-org-images () (when org-inline-image-overlays (org-redisplay-inline-images))) (add-hook 'org-babel-after-execute-hook 'my/redisplay-org-images) #+end_src *** Sticky headers Keeps the current heading visible at the top of the Emacs window. #+begin_src emacs-lisp (use-package! org-sticky-header :hook (org-mode . org-sticky-header-mode) :config (setq org-sticky-header-full-path 'full)) #+end_src *** Library of Babel Load shared code snippets to be used in org documents. #+begin_src emacs-lisp (let ((org-dirs '("~/org" "~/org-aweber"))) (seq-map #'org-babel-lob-ingest (seq-filter #'f-exists? (seq-map (lambda (path) (f-join path "library-of-babel.org")) org-dirs)))) #+end_src *** Nicer looking timestamps #+begin_src emacs-lisp (setq org-time-stamp-custom-formats '("<%A, %B %d %Y>" . "<%A, %B %d %Y %H:%M>")) (defun org-export-filter-timestamp-remove-brackets (timestamp backend info) "removes relevant brackets from a timestamp" (cond ((org-export-derived-backend-p backend 'latex) (replace-regexp-in-string "[<>]\\|[][]" "" timestamp)) ((org-export-derived-backend-p backend 'ascii) (replace-regexp-in-string "[<>]\\|[][]" "" timestamp)) ((org-export-derived-backend-p backend 'html) (replace-regexp-in-string "&[lg]t;\\|[][]" "" timestamp)))) (after! ox (add-to-list 'org-export-filter-timestamp-functions 'org-export-filter-timestamp-remove-brackets)) #+end_src *** Tufte HTML Gorgeous HTML exports. #+begin_src emacs-lisp (use-package! ox-tufte :after ox) #+end_src *** Journal #+begin_src emacs-lisp (use-package org-journal :if (f-dir? "~/org-aweber") :custom (org-journal-date-prefix "#+title: ") (org-journal-file-format "%Y-%m-%d.org") (org-journal-dir "~/org-aweber") (org-journal-date-format "%A, %d %B %Y")) #+end_src *** Ref Tools for linking and taking notes on books and papers. #+begin_src emacs-lisp (use-package! org-ref :config (setq reftex-default-bibliography my/bibliographies) ;; see org-ref for use of these variables (setq org-ref-bibliography-notes "~/Documents/bibliography/notes.org" org-ref-default-bibliography my/bibliographies org-ref-pdf-directory "~/Documents/bibliography/bibtex-pdfs/")) #+end_src *** Roam Powerful cross-linked note-taking. https://orgroam.com **** Capture templates #+begin_src emacs-lisp (setq org-roam-capture-templates '(("d" "default" plain "%?" :if-new (file+head "%<%Y%m%d%H%M%S>-${slug}.org" "#+title: ${title} ") :unnarrowed t) ("w" "work" plain "%?" :if-new (file+head "aweber/%<%Y%m%d%H%M%S>-${slug}.org" "#+title: ${title} ") :unnarrowed t))) #+end_src **** Add backlinks to org-roam exports Adapted from https://org-roam.readthedocs.io/en/master/org_export/. #+begin_src emacs-lisp (defun my/org-roam--rewrite-backlink-content-links (path content) "Re-write the links in backlink CONTENT to be relative to PATH." (with-temp-buffer (insert content) (org-mode) (let ((ast (org-element-parse-buffer))) (org-element-map ast 'link (lambda (link) (when (string= (org-element-property :type link) "file") (org-element-put-property link :path (f-relative (org-element-property :path link) path))))) (org-no-properties (org-element-interpret-data ast))))) (defun my/org-roam--backlinks-list-with-content (file) "Generate a list of backlinks for FILE with content." (when (and (stringp file) (f-file? file)) (with-temp-buffer (cd (f-dirname file)) (hack-dir-local-variables-non-file-buffer) (if-let* ((backlinks (org-roam--get-backlinks file)) (grouped-backlinks (--group-by (nth 0 it) backlinks))) (progn (dolist (group grouped-backlinks) (let ((file-from (car group)) (bls (cdr group))) (insert (format "** [[file:%s][%s]]\n" (f-relative file-from (f-dirname file)) (org-roam-db--get-title file-from))) (dolist (backlink bls) (pcase-let* ((`(,file-from _ ,props) backlink) (content (plist-get props :content))) (when content (let ((rewritten (my/org-roam--rewrite-backlink-content-links (f-dirname file) (plist-get props :content)))) (insert (s-trim (s-replace "\n" " " rewritten))))) (insert "\n\n"))))))) (buffer-string)))) (defun my/org-roam--reference-details () (let* ((key (cdr (assoc "ROAM_KEY" (org-roam--extract-global-props '("ROAM_KEY"))))) (ref (org-roam--extract-ref)) (reftype (car ref)) (citekey (cdr ref)) (bibtex (when citekey (bibtex-completion-get-entry citekey)))) (when citekey (cond (bibtex (my/org-roam--reference-details-bibtex bibtex)) ((s-equals? "website" reftype) (my/org-roam--reference-details-url key)) (t (my/org-roam--reference-details-default citekey)))))) (defun my/org-roam--reference-details-default (citekey) (my/org-roam--reference-details-list `(("Key" . ,(concat "=" citekey "="))))) (defun my/org-roam--reference-details-url (url) (my/org-roam--reference-details-list `(("Webpage" . ,(org-link-make-string url))))) (defun my/org-roam--reference-details-bibtex (entry) (let* ((author (bibtex-completion-clean-string (cdr (assoc "author" entry)))) (calibreid (bibtex-completion-clean-string (cdr (assoc "calibreid" entry)))) (identifiers (seq-map (lambda (s) (let ((pair (s-split-up-to ":" s 2))) (cons (car pair) (cadr pair)))) (if-let ((pairs (cdr (assoc "identifiers" entry)))) (s-split "," (bibtex-completion-clean-string pairs))))) (doi (cdr (assoc "doi" identifiers))) (isbn (cdr (assoc "isbn" identifiers))) (goodreads (cdr (assoc "goodreads" identifiers))) (amazon (cdr (or (assoc "amazon" identifiers) (assoc "mobi-asin" identifiers))))) (concat (cdr (assoc "note" entry)) "\n\n" (my/org-roam--reference-details-list (seq-remove #'null (list (cons "Author" author) (when isbn (cons "ISBN" isbn)) (when doi (cons "DOI" (org-link-make-string (format "https://doi.org/%s" doi)))) (when amazon (cons "Amazon" (org-link-make-string (format "https://www.amazon.com/dp/ASIN/%s" amazon)))) (when goodreads (cons "Goodreads" (org-link-make-string (format "https://goodreads.com/book/show/%s" goodreads)))) (cons "Calibre Library" (org-link-make-string (format "https://calibre.phoenixinquis.is-a-geek.org/book/%s" calibreid))))))))) (defun my/org-roam--reference-details-list (details-alist) (org-list-to-org (cons 'descriptive (mapcar (lambda (pair) (let ((field (car pair)) (text (cdr pair))) (list (concat field " :: " text)))) details-alist)))) (defun my/org-export-preprocessor (backend) "Append org-roam backlinks with content when applicable before passing to the org export BACKEND." (let ((links (my/org-roam--backlinks-list-with-content (buffer-file-name))) (details (my/org-roam--reference-details))) (unless (or (not (stringp details)) (string= details "")) (save-excursion (goto-char (point-max)) (insert (concat "\n* Reference Details\n") details))) (unless (or (not (stringp links)) (string= links "")) (save-excursion (goto-char (point-max)) (insert (concat "\n* Backlinks\n") links))))) (add-hook 'org-export-before-processing-hook 'my/org-export-preprocessor) #+end_src **** Org Roam Bibtex Make it easy to take notes on books and papers that I'm reading. #+begin_src emacs-lisp (use-package! org-roam-bibtex :after org-roam :hook (org-roam . org-roam-bibtex-mode) :bind (:map org-mode-map (("C-c n r b" . orb-note-actions)))) #+end_src **** Org Roam Server Provides a fun way to browse through a collection of notes. #+begin_src emacs-lisp (use-package! org-roam-server :commands org-roam-server-mode) #+end_src **** Org Roam UI #+begin_src emacs-lisp (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)) #+end_src **** Use writeroom in org-roam buffers Makes for a much nicer note-taking experience. #+begin_src emacs-lisp (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) #+end_src **** 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. #+begin_src emacs-lisp (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))) #+end_src When setting up additional slipboxes, be sure to set both =org-roam-directory= /and/ =org-roam-db-location=. An example =.dir-locals.el=: #+begin_example ((nil . ((eval . (setq-local org-roam-directory (expand-file-name "./") org-roam-db-location (expand-file-name "./org-roam.db")))))) #+end_example *** Sidebar Display a sidebar with file-local todos and scheduling. #+begin_src emacs-lisp (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)) #+end_src *** Transclusion Show linked org document sections inline. #+begin_src emacs-lisp (use-package! org-transclusion :bind (:map org-mode-map ("C-c l v t" . org-transclusion-mode)) :commands (org-transclusion-mode)) #+end_src *** Ditaa Download and use a recent version of [[https://github.com/stathissideris/ditaa][ditaa]] for rendering ASCII diagrams. #+begin_src emacs-lisp (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))) #+end_src ** ReStructuredText #+begin_src emacs-lisp (use-package! polymode :defer t) (use-package! poly-rst :mode ("\\.rst\\'" . poly-rst-mode)) #+end_src ** Unfill Does the opposite of =fill (M-q)=, removing line breaks from a paragraph or region. #+begin_src emacs-lisp (use-package! unfill :commands (unfill-paragraph unfill-region) :bind ("M-Q" . unfill-paragraph)) #+end_src * Reading ** Epub reader A major mode for reading and navigating =.epub= files. #+begin_src emacs-lisp (use-package! nov :mode ("\\.epub\\'" . nov-mode) :config (setq nov-save-place-file (concat doom-cache-dir "nov-places"))) #+end_src ** Kanji Mode Minor mode for displaying Japanese characters' stroke orders. #+begin_src emacs-lisp (use-package! kanji-mode :commands kanji-mode) #+end_src ** Kanji Glasses Mode Study kanji by overlaying hiragana readings. #+begin_src emacs-lisp (use-package! kanji-glasses-mode :commands kanji-glasses-mode) #+end_src * Coding ** Erlang *** Kerl Select the active erlang installation managed with [[https://github.com/kerl/kerl][kerl]]. #+begin_src emacs-lisp (use-package! kerl :commands (kerl-use)) #+end_src ** Lisp *** Paredit Adds shortcuts to edit the structure of lisp code. #+begin_src emacs-lisp (use-package! paredit :hook ((emacs-lisp-mode . enable-paredit-mode))) #+end_src * Applications ** Email Configure MU4E to read email synced from my personal and work accounts. #+begin_src emacs-lisp (use-package! mu4e :bind (("" . 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"))) #+end_src Prefer sending HTML-formatted messages with plain text as a fallback option (alternative formats should be specified in increasing level of preference per [[https://www.w3.org/Protocols/rfc1341/7_2_Multipart.html][RFC-1341]]). #+begin_src emacs-lisp (use-package! org-msg :config (setq org-msg-default-alternatives '(text html) org-msg-options "html-postamble:nil toc:nil author:nil email:nil ^:nil")) #+end_src ** Chat *** Circe #+begin_src emacs-lisp (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"))))) #+end_src ** Music Configure EMMS for playing music files on my computer. #+begin_src emacs-lisp (use-package! emms :commands (emms emms-play-file emms-play-directory emms-smart-browse) :config (let ((emms-player-base-format-list ;; Add some VGM formats to the list for VLC to play (append emms-player-base-format-list '("nsf" "spc" "gym")))) (require 'emms-player-vlc)) (require 'emms-setup) (emms-all) (setq emms-player-list '(emms-player-vlc)) ;; Use the installed VLC app if we're in OSX (if (f-exists? "/Applications/VLC.app/Contents/MacOS/VLC") (setq emms-player-vlc-command-name "/Applications/VLC.app/Contents/MacOS/VLC"))) (map! :leader (:prefix-map ("x" . "EMMS") :desc "Play file" "f" #'emms-play-file :desc "Play directory" "d" #'emms-play-directory :desc "Smart Browser" "b" #'emms-smart-browse)) #+end_src ** News Aggregation Read blogs and articles from the RSS feeds I follow. #+begin_src emacs-lisp (use-package! elfeed :commands (elfeed my/elfeed my/elfeed-emacs my/elfeed-blogs) :bind (("" . 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)) #+end_src ** Kubernetes Manage a Kubernetes cluster and set up remote shell/file access via TRAMP. #+begin_src emacs-lisp (use-package! kubernetes :commands (kubernetes-overview) :config) (set-popup-rule! "^\\*kubernetes" :ignore t) (use-package! kubernetes-tramp :config (setq tramp-remote-shell-executable "sh")) #+end_src ** Twitter It's Twitter, in Emacs. #+begin_src emacs-lisp (define-key! twittering-mode-map "f" #'twittering-favorite "F" #'twittering-unfavorite) #+end_src ** Project Management *** Projectile Pre-load Projectile with projects in my usual code directories. #+begin_src emacs-lisp (after! projectile (require 'dash) (require 'f) (setq projectile-switch-project-action #'magit-status) (let ((project-directories (-filter #'f-directory? '("~/code" "~/git")))) (-map (lambda (directory) (-map (lambda (project) (-> (concat project "/") ;; Projectile likes trailing slashes (projectile-add-known-project))) (-filter (lambda (f) (and (not (s-ends-with? "." f)) (f-directory? f))) (-map (lambda (f) (concat directory "/" f)) (directory-files directory))))) project-directories)) (projectile-cleanup-known-projects)) #+end_src *** Jira Add some commands for interacting with Jira within org documents. #+begin_src emacs-lisp (use-package jira-api :config (setq jira-api-host "jira.aweber.io" jira-api-user "correlr")) (defun my/org-clock-last-time-in-seconds () (save-excursion (let ((end (save-excursion (org-end-of-subtree)))) (when (re-search-forward (concat org-clock-string ".*\\(\\[[^]]+\\]\\)--\\(\\[[^]]+\\]\\)") end t) (let* ((start (match-string 1)) (end (match-string 2))) (floor (- (org-time-string-to-seconds end) (org-time-string-to-seconds start)))))))) (defun my/org-jira-add-worklog-latest () (interactive) (let ((jira-id (org-entry-get (point) "JIRA_ID")) (seconds (my/org-clock-last-time-in-seconds))) (when (and jira-id seconds) (jira-api-log-work jira-id seconds) (message (format "Logged %d minutes to %s on JIRA" (/ seconds 60) jira-id))))) (defun my/org-jira-add-worklog-total () (interactive) (let ((jira-id (org-entry-get (point) "JIRA_ID")) (seconds (* 60 (org-clock-sum-current-item)))) (when (and jira-id seconds) (jira-api-log-work jira-id seconds) (message (format "Logged %d minutes to %s on JIRA" (/ seconds 60) jira-id))))) (defun my/org-clock-add-jira-worklog-last () "Add a work log entry to a JIRA. To log work to JIRA, set a property named JIRA_ID on the entry to be logged to a JIRA issue ID." (interactive) (save-excursion (save-window-excursion (org-clock-goto) (my/org-jira-add-worklog-latest)))) (defun my/org-jira-browse () (interactive) (-if-let (jira-id (org-entry-get (point) "JIRA_ID")) (let ((protocol (if jira-api-use-ssl "https" "http"))) (browse-url (concat protocol "://" jira-api-host "/browse/" jira-id))))) (defun my/org-jira-list () (interactive) (let ((buffer (generate-new-buffer "*org-jira*"))) (switch-to-buffer buffer) (org-mode) (insert "ohai") (setq-local buffer-read-only t) (display-buffer buffer))) ;; (add-hook 'org-clock-out-hook 'my/org-clock-add-jira-worklog-last) (map! :map org-mode-map "C-c j t" #'my/org-jira-add-worklog-total "C-c j l" #'my/org-jira-add-worklog-latest "C-c j b" #'my/org-jira-browse "C-c j c" #'jira-api-create-issue-from-heading "C-c j u" #'jira-api-update-issue-from-heading) #+end_src ** Source Control #+begin_src emacs-lisp (after! forge (add-to-list 'forge-alist '("gitlab.aweber.io" "gitlab.aweber.io/api/v4" "gitlab.aweber.io" forge-gitlab-repository))) #+end_src ** 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. #+begin_src emacs-lisp (defun eshell/lcd (&optional directory) (interactive) (if (file-remote-p default-directory) (with-parsed-tramp-file-name default-directory nil (eshell/cd (tramp-make-tramp-file-name (tramp-file-name-method v) (tramp-file-name-user v) (tramp-file-name-domain v) (tramp-file-name-host v) (tramp-file-name-port v) (or directory "") (tramp-file-name-hop v)))) (eshell/cd directory))) #+end_src *** Disable company autocompletion on remote hosts #+begin_src emacs-lisp (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) #+end_src ** Background Processes Manage background services #+begin_src emacs-lisp (use-package! prodigy :defer 2 :config (global-set-key (kbd "") '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)))) #+end_src ** Screen Sharing *** Showing keypresses #+begin_src emacs-lisp (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))) #+end_src ** UUID Generation #+begin_src emacs-lisp (use-package! uuidgen :commands (uuidgen)) #+end_src ** Wordle #+begin_src emacs-lisp (use-package! wordle :commands (wordel wordel-marathon)) #+end_src * Operating Systems ** Linux *** EXWM **** Set Emacs + EXWM as the default X window manager #+begin_src sh :tangle ~/.dmrc [Desktop] session=~/.doom.d/start-exwm.sh #+end_src #+begin_src sh :tangle start-exwm.sh :shebang #!/bin/sh emacs -mm -l ~/.doom.d/exwm.el #+end_src **** 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 #+begin_src emacs-lisp :tangle exwm.el (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)) #+end_src ***** Application launcher Use counsel as an application launcher. Scans for =.desktop= files in all the usual places. #+begin_src emacs-lisp (use-package! counsel :custom (counsel-linux-app-format-function #'counsel-linux-app-format-function-name-only) :config (counsel-mode 1)) #+end_src ** OSX *** Editing binary-compressed plist files From https://www.emacswiki.org/emacs/MacOSXPlist#toc1 #+begin_src emacs-lisp ;; Allow editing of binary .plist files. (add-to-list 'jka-compr-compression-info-list ["\\.plist$" "converting text XML to binary plist" "plutil" ("-convert" "binary1" "-o" "-" "-") "converting binary plist to text XML" "plutil" ("-convert" "xml1" "-o" "-" "-") nil nil "bplist"]) ;;It is necessary to perform an update! (jka-compr-update) #+end_src * Miscellaneous Nonsense ** BRING ON THE ... A silly interactive method for generating horizontal and vertical text. #+CAPTION: M-x bring-on-the RET cats RET #+begin_example B R I N G O N T H E C A T S R I N G O N T H E C A T S #+end_example #+begin_src emacs-lisp (defun bring-on-the (thing) (interactive "sBring on the: ") (let ((upthing (seq-into (s-upcase (s-concat "bring on the " thing)) 'list))) (insert (s-concat (seq-into (-interleave upthing (-repeat (length upthing) 32)) 'string) "\n" (seq-into (-interleave (rest upthing) (-repeat (1- (length upthing)) ?\n)) 'string))))) #+end_src ** OwO Mode Make reading an open buffer an exercise in insanity. #+begin_src emacs-lisp (use-package! owo-mode :commands owo-mode) #+end_src ** Elcord Emits rich presence to Discord. #+begin_src emacs-lisp (use-package! elcord :config (setq elcord-display-buffer-details nil elcord-quiet t) (elcord-mode t)) #+end_src