dotfiles/emacs/init.org
2019-02-07 16:53:52 -05:00

77 KiB
Executable File

This init file is based on this blog post.

It's meant to be loaded from init.el like so:

  (require 'org)
  (org-babel-load-file (expand-file-name "path/to/init.org"))

Default directory

  (cd "~")

Packages

Set up package.el to load from ELPA and MELPA

  (require 'package)
  (setq package-archives
        '(("gnu" . "https://elpa.gnu.org/packages/")
          ("melpa" . "https://melpa.org/packages/")))
  (package-initialize)

`use-package` is a macro that simplifies installing and loading packages. `use-package-always-ensure` makes sure that all packages will be installed before loading is attempted.

  (unless (package-installed-p 'use-package)
    (package-refresh-contents)
    (package-install 'use-package))
  (require 'use-package)
  (setq use-package-always-ensure t)

Quelpa extends package.el to build packages from source from a bunch of targets (git, hg, etc.).

  (use-package quelpa)
  (use-package quelpa-use-package)

Benchmarking

`benchmark-init` does what it says on the box. This sets it up to benchmark my init time and then disable benchmarking after init completes.

  (use-package benchmark-init
    :config
    (add-hook 'after-init-hook 'benchmark-init/deactivate))

Path

`exec-path-from-shell` uses Bash to set MANPATH, PATH, and exec-path from those defined in the user's shell config. This won't work on Windows.

  (use-package exec-path-from-shell
    :if (memq window-system '(mac ns x))
    :config
    (setq exec-path-from-shell-variables '("PATH" "MANPATH" "LEDGER_FILE" "LOLA_HOME"
					   "MODELS_HOME" "LOLA_TRAVEL_SERVICE_HOME"))
    (exec-path-from-shell-initialize))

Autocompletion

There seems to be some contention about whether autocomplete or company are better autocomplete packages. I'm going with company for now because the maintainer seems nicer…

  (use-package company
    :config
    (setq company-idle-delay 0.3)
    (add-hook 'after-init-hook #'global-company-mode))

General

Better keybinding.

  (use-package general)

Which-key

`which-key` makes keybindings discoverable.

  (use-package which-key
    :config
    (which-key-mode))

This function defines a prefix group for `which-key` so that it doesn't display `prefix`.

  (defun jdormit/define-prefix (binding name)
    (which-key-add-key-based-replacements
      (concat leader " " binding)
      name)
    (which-key-add-key-based-replacements
      (concat "," " " binding)
      name))

Evil Mode

Because I like modal editing and dislike RSI.

  (use-package evil
    :config
    (evil-mode 1))

Use the spacebar as a leader key in evil-mode's normal state and in various other modes:

  (defconst leader "SPC")
  (general-define-key
   :states 'motion
   "SPC" nil)
  (general-create-definer leader-def-key
    :states 'motion
    :prefix leader
    :prefix-map 'leader-map)
  (jdormit/define-prefix "?" "help")
  (leader-def-key "?" help-map)

Make undo not undo paragraphs at a time:

  (setq evil-want-fine-undo t)

Password Store

Interfacing with Pass, the "standard Unix password manager". This should also be loaded before `exec-path-from-shell`.

  (defun password-store-synchronize ()
    (interactive)
    (async-shell-command "pass git pull && pass git push"))

  (use-package password-store
    :if (executable-find "pass")
    :commands (password-store-list
	       password-store-get
	       password-store-copy)
    :config
    (setq password-store-password-length 20)
    (leader-def-key "p" 'password-store-copy))

  (use-package pass
    :if (executable-find "pass")
    :commands pass
    :general
    (pass-mode-map "SPC" leader-map)
    (pass-mode-map "S" #'password-store-synchronize)
    :config
    (leader-def-key "ap" 'pass))

Emacs Lisp

Requires:

  (eval-when-compile (require 'subr-x))

Default to lexical binding:

  (setq lexical-binding t)

Load path

For machine or user specific libraries:

  (add-to-list 'load-path (expand-file-name "~/site-lisp"))

And for global ones:

  (add-to-list 'load-path "/usr/local/share/emacs/site-lisp")

Utilities

Reading a file as a string:

  (defun read-file (path)
    "Returns the contents of the file as a string"
    (with-temp-buffer
      (insert-file-contents path)
      (buffer-string)))

Opening a file as sudo:

  (defun sudo-find-file (file-name)
    "Like find file, but opens the file as root."
    (interactive "F")
    (let ((tramp-file-name (concat "/sudo::" (expand-file-name file-name))))
      (find-file tramp-file-name)))

Recursive assoc for nested alists:

  (defun assoc-recursive (alist &rest keys)
    "Recursively find KEYs in ALIST."
    (while keys
      (setq alist (cdr (assoc (pop keys) alist))))
    alist)

Format a millis timestamp into human-readable form:

  (defun format-epoch-millis (millis)
     (interactive "nTimestamp: ")
     (message (format-time-string "%x %X" (/ millis 1000))))

The same but for seconds:

  (defun format-epoch-seconds (seconds)
     (interactive "nTimestamp: ")
     (message (format-time-string "%x %X" seconds)))

Persisting variables between session

The idea behind this is pretty simple - variables get persisted in ~/.emacs.d/<persisted-vars-file> as a plist of (variable-name variable-value).

  (defvar persisted-vars-file "~/.emacs.d/persisted-vars")

This function retrieves the plist of persisted variables or nil if it doesn't exist:

  (defun get-persisted-plist ()
      (let ((file (expand-file-name persisted-vars-file)))
        (when (file-exists-p file)
          (let ((vars-plist-str (read-file file)))
            (unless (string= vars-plist-str "")
              (car (read-from-string vars-plist-str)))))))

This function retrieves a persisted variable:

  (defun get-persisted-var (var-name)
    "Retrieves the value of persisted variable `var-name`, or nil if not found"
    (let ((vars-plist (get-persisted-plist)))
      (when vars-plist
        (plist-get vars-plist var-name))))

And this function persists a variable:

    (defun persist-variable (var-name value)
      (let ((file (expand-file-name persisted-vars-file))
            (vars-plist (get-persisted-plist)))
        (if vars-plist
            (progn
              (plist-put vars-plist var-name value)
              (write-region
               (prin1-to-string vars-plist) nil file))
          (let ((vars-plist `(,var-name ,value)))
            (write-region
             (prin1-to-string vars-plist) nil file)))))

Customization File

I don't want anything to write to my init.el, so save customizations in a separate file:

  (setq custom-file (expand-file-name "custom.el" user-emacs-directory))
  (load custom-file t)

Dropbox

I put lots of stuff in Dropbox, but the actual folder location differs on my different computers. This function resolves to the Dropbox directory:

  (defun get-dropbox-directory ()
    (cond
     ((file-exists-p (expand-file-name "~/Dropbox"))
      (expand-file-name "~/Dropbox"))
     ((file-exists-p (expand-file-name "~/Dropbox (Personal)"))
      (expand-file-name "~/Dropbox (Personal)"))))

Load up libraries from Dropbox, if there are any:

  (add-to-list 'load-path
	 (concat
	  (file-name-as-directory (get-dropbox-directory))
	  "site-lisp"))

Init File

A function to reload my init file. It reloads the major mode after the init file is loaded to rebind keymappings.

  (defun reload-init-file ()
    (interactive)
    (load-file "~/.emacs.d/init.el")
    (funcall major-mode))

And another one to edit it:

  (defun find-init-file ()
    (interactive)
    (find-file "~/init.org"))

Keybindings

These are general keybindings; those specific to certain packages are in the sections for those packages.

In some modes I want vanilla Emacs bindings:

  (add-to-list 'evil-emacs-state-modes 'dired-mode)
  (add-to-list 'evil-emacs-state-modes 'benchmark-init/tabulated-mode)
  (add-to-list 'evil-emacs-state-modes 'benchmark-init/tree-mode)
  (add-to-list 'evil-emacs-state-modes 'neotree-mode)
  (add-to-list 'evil-emacs-state-modes 'cider-stacktrace-mode)
  (add-to-list 'evil-emacs-state-modes 'pass-mode)
  (add-to-list 'evil-emacs-state-modes 'picture-mode)
  (add-to-list 'evil-emacs-state-modes 'geiser-debug-mode)
  (add-to-list 'evil-emacs-state-modes 'undo-tree-visualizer-mode)
  (add-to-list 'evil-emacs-state-modes 'makey-key-mode)
  (add-to-list 'evil-emacs-state-modes 'term-mode)

And in some modes I want to preserve the spacebar as a leader key:

  (general-def picture-mode-map "SPC" leader-map)
  (general-def 'motion Info-mode-map "SPC" leader-map)

Visual line navigation

  (general-def 'motion "j" #'evil-next-visual-line)
  (general-def 'motion "k" #'evil-previous-visual-line)
  (general-def 'motion "j" #'evil-next-visual-line)
  (general-def 'motion "k" #'evil-previous-visual-line)

M-x

  (leader-def-key "SPC" 'execute-extended-command)

Eval-ing

  (leader-def-key "e" #'eval-expression)

Init file commands

  (jdormit/define-prefix "." "dotfile")
  (leader-def-key ".r" 'reload-init-file)
  (leader-def-key ".f" 'find-init-file)

Commands about files

  (jdormit/define-prefix "f" "files")
  (leader-def-key "ff" 'find-file)
  (leader-def-key "fs" 'sudo-find-file)
  (leader-def-key "ft" 'auto-revert-tail-mode)

Window commands

  (jdormit/define-prefix "w" "window")
  (leader-def-key "w/" 'split-window-right)
  (leader-def-key "w-" 'split-window-below)
  (leader-def-key "wm" 'delete-other-windows)
  (leader-def-key "wd" 'delete-window)

Buffer commands

A function to switch to previous buffer from this blog post:

  (defun switch-to-previous-buffer ()
    "Switch to previously open buffer.
  Repeated invocations toggle between the two most recently open buffers."
    (interactive)
    (switch-to-buffer (other-buffer (current-buffer) 1)))

  (leader-def-key "TAB" 'switch-to-previous-buffer)

A function to kill all buffers except the current one from EmacsWiki:

  (defun kill-other-buffers ()
    "Kill all other buffers."
    (interactive)
    (mapc 'kill-buffer (delq (current-buffer) (buffer-list))))
  (jdormit/define-prefix "b" "buffer")
  (leader-def-key "bb" 'switch-to-buffer)
  (leader-def-key "bd" 'kill-buffer)
  (leader-def-key "bm" 'kill-other-buffers)
  (leader-def-key "br" 'rename-buffer)

Frame commands

  (jdormit/define-prefix "F" "frame")
  (leader-def-key "Fn" 'make-frame-command)
  (leader-def-key "Fm" 'delete-other-frames)
  (leader-def-key "Fd" 'delete-frame)

Running shell commands

  (leader-def-key "!" 'shell-command)
  (leader-def-key "|" 'shell-command-on-region)

Toggles

Like in Spacemacs, put all toggle commands behind a prefix:

  (jdormit/define-prefix "t" "toggle")

Toggles about line truncation:

  (leader-def-key "tt" 'toggle-truncate-lines)
  (leader-def-key "tT" 'visual-line-mode)

Toggle lisp debugging:

  (leader-def-key "td" 'toggle-debug-on-error)

Shells/REPLs

Emacs has a shell for every mood!

  (jdormit/define-prefix "s" "shells/REPLs")
  (leader-def-key "ss" 'shell)
  (leader-def-key "si" 'ielm)
  (leader-def-key "se" 'eshell)
  (leader-def-key "sa" 'ansi-term)

Applications

  (jdormit/define-prefix "a" "applications")

Help Buffers

  (general-def 'motion help-mode-map "TAB" #'forward-button)

Whitespace Visualation

  (setq whitespace-line-column 80
	whitespace-style '(face lines-tail))
  (leader-def-key "tw" #'whitespace-mode)

Line Numbers

Toggle line numbers:

  (setq display-line-numbers-type 'visual)
  (leader-def-key "tn" 'display-line-numbers-mode)

Toggle line numbering mode (normal or relative):

  (defun toggle-line-number-mode ()
    (interactive)
    (when display-line-numbers
      (if (eq display-line-numbers 'visual)
	  (progn
	    (setq display-line-numbers t)
	    (setq display-line-numbers-type t))
	(progn
	  (setq display-line-numbers 'visual)
	  (setq display-line-numbers-type 'visual)))))

  (leader-def-key "tr" #'toggle-line-number-mode)

Display line numbers by default in code and org-mode buffers:

  (add-hook 'prog-mode-hook #'display-line-numbers-mode)
  (add-hook 'org-mode-hook #'display-line-numbers-mode)

Amx

A better M-x.

  (use-package amx
    :config
    (amx-mode))

Olivetti Mode

Olivetti is a minor mode for a nice writing environment.

  (use-package olivetti
    :config
    (setq-default olivetti-body-width 100)
    (setq olivetti-body-width 100)
    :commands olivetti-mode)

  (leader-def-key "to" 'olivetti-mode)

Winum

This package includes functions to switch windows by number.

  (use-package winum
    :config
    (winum-mode)
    (leader-def-key "0" 'winum-select-window-0-or-10)
    (leader-def-key "1" 'winum-select-window-1)
    (leader-def-key "2" 'winum-select-window-2)
    (leader-def-key "3" 'winum-select-window-3)
    (leader-def-key "4" 'winum-select-window-4)
    (leader-def-key "5" 'winum-select-window-5)
    (leader-def-key "6" 'winum-select-window-6)
    (leader-def-key "7" 'winum-select-window-7)
    (leader-def-key "8" 'winum-select-window-8)
    (leader-def-key "9" 'winum-select-window-9))

I don't want which-key display "lambda" for the descriptions of these, so set a custom display function. This is stolen from Spacemacs.

  (push '(("\\(.*\\) 0" . "select-window-0") . ("\\1 0..9" . "window 0..9"))
        which-key-replacement-alist)
  (push '((nil . "select-window-[1-9]") . t) which-key-replacement-alist)

NeoTree

A package to browse files in a tree view

  (use-package neotree
    :commands neotree-toggle
    :init
    (leader-def-key "d" 'neotree-toggle)
    :general
    (neotree-mode-map "SPC" leader-map))

Backups and Autosaves

Store backups and autosaves in a centralized place. This should really be the default…

  (make-directory (expand-file-name "~/.emacs.d/autosaves") t)
  (setq auto-save-file-name-transforms '((".*" "~/.emacs.d/autosaves" t)))
  (setq backup-directory-alist '(("." . "~/.emacs.d/backups")))

Paredit/Parinfer

Paredit enables structured editing of s-expressions

  (use-package paredit
    :hook ((emacs-lisp-mode . enable-paredit-mode)
	   (lisp-mode . enable-paredit-mode)
	   (clojure-mode . enable-paredit-mode)
	   (cider-repl-mode . enable-paredit-mode)
	   (ielm-mode . enable-paredit-mode)
	   (scheme-mode . enable-paredit-mode)
	   (geiser-repl-mode . enable-paredit-mode)
	   (slime-repl-mode . enable-paredit-mode)))


  (jdormit/define-prefix "l" "lisp")
  (jdormit/define-prefix "lw" "wrap")
  (leader-def-key "lwr" 'paredit-wrap-round)
  (leader-def-key "lws" 'paredit-wrap-square)
  (leader-def-key "lwc" 'paredit-wrap-curly)
  (leader-def-key "ls" 'paredit-forward-slurp-sexp)
  (leader-def-key "lb" 'paredit-forward-barf-sexp)

Parinfer infers parens from indentation and vice-versa:

  (use-package parinfer
    :init
    (leader-def-key "lt" 'parinfer-toggle-mode)
    (setq parinfer-extensions '(defaults
				 pretty-parens
				 evil
				 paredit
				 smart-tab
				 smart-yank))
    :hook ((clojure-mode . parinfer-mode)
	   (emacs-lisp-mode . parinfer-mode)
	   (common-lisp-mode . parinfer-mode)
	   (scheme-mode . parinfer-mode)
	   (lisp-mode . parinfer-mode)))

Org Mode

Notes, agenda, calendar, blogging, journaling, etc.

  (use-package org
    :mode ("\\.org\\'" . org-mode)
    :commands (org-agenda org-capture))

  (jdormit/define-prefix "o" "org")
  (leader-def-key "oa" 'org-agenda)
  (leader-def-key "oc" 'org-capture)
  (setq org-src-fontify-natively t)

Use RET to follow links:

  (setq org-return-follows-link t)

Always show inline images:

  (add-hook 'org-mode-hook
    (lambda ()
      (org-display-inline-images nil t)
      (org-redisplay-inline-images)))

Agenda files

  (defun agenda-files (&optional file)
    (let ((agenda-dir (concat (file-name-as-directory (get-dropbox-directory)) "org")))
      (if file
	 (concat (file-name-as-directory agenda-dir) file)
	 agenda-dir)))

  (setq org-agenda-files `(,(agenda-files)))

Capture templates

  (setq org-capture-templates
	`(("t" "Task" entry
	   (file ,(agenda-files "todo.org"))
	   "* TODO %i%?")
	  ("b" "Backlog task" entry
	   (file ,(agenda-files "backlog.org"))
	   "* TODO %i%?")
	  ("n" "Note" entry
	   (file ,(agenda-files "notes.org"))
	   "* %^{Description}\n%i%?")
	  ("j" "Journal entry" entry
	   (file ,(agenda-files "journal.org"))
	   "* %<%Y-%m-%d %H:%M:%S>%?")
	  ("p" "Project" entry
	   (file ,(agenda-files "notes.org"))
	   "* %^{Project name}\n\n** What's it supposed to do?\n%?\n** How can I test that it works?\n\n** How can I test that it didn't break anything?\n\n** What alternative approaches are there - what trade-offs can be made?\n\n** What assumptions have I made?\n\n** What deployables will I need to deploy?\n")
	  ("b" "Brain" plain (function org-brain-goto-end)
	   "* %i%?" :empty-lines 1)))

Refile targets

  (setq org-refile-use-outline-path 'file
	org-refile-targets `((org-agenda-files :level . 0)
			     (,(agenda-files "notes.org") :level . 1)))

Todo keywords

    (setq org-todo-keywords 
          '((sequence "TODO(t)" "WAITING(w)" "|" "DONE(d)" "CANCELLED(c)")))

Agenda views

  (setq org-agenda-custom-commands
	'(("T" "Today's list"
	   ((agenda)
	    (todo "TODO" ((org-agenda-files `(,(agenda-files "today.org")))))))))

Keybindings

  (general-def 'normal org-mode-map "T" #'org-insert-todo-heading)
  (general-def 'normal org-mode-map "K" #'org-move-subtree-up)
  (general-def 'normal org-mode-map "J" #'org-move-subtree-down)
  (general-def 'normal org-mode-map "<return>" #'org-return)
  (general-def 'normal org-mode-map "TAB" #'org-cycle)
  (general-def org-mode-map "C-c e" #'org-preview-latex-fragment)
  (general-def "C-c l" #'org-store-link)

Enable using the leader key in the agenda view:

  (general-def org-agenda-mode-map "SPC" leader-map)

Exporting

HTML

Export to HTML:

  (use-package htmlize
    :commands htmlize-buffer)

Don't put section numbers in front of headers:

  (setq org-export-with-section-numbers nil)

Disable the preamble and postamble:

  (setq org-html-preamble nil
	org-html-postamble nil)

Redefine org-html-src-block to wrap code blocks in <pre><code> and language class for use by highlight.js:

  (defun org-html-src-block (src-block _contents info)
    "Transcode a SRC-BLOCK element from Org to HTML.
  CONTENTS holds the contents of the item.  INFO is a plist holding
  contextual information."
    (if (org-export-read-attribute :attr_html src-block :textarea)
	(org-html--textarea-block src-block)
      (let* ((lang (org-element-property :language src-block))
	     (code (org-html-format-code src-block info))
	     (label (let ((lbl (and (org-element-property :name src-block)
				    (org-export-get-reference src-block info))))
		      (if lbl (format " id=\"%s\"" lbl) "")))
	     (klipsify  (and  (plist-get info :html-klipsify-src)
			      (member lang '("javascript" "js"
					     "ruby" "scheme" "clojure" "php" "html")))))
	(if (not lang) (format "<pre><code class=\"example\"%s>\n%s</code></pre>" label code)
	  (format "<div class=\"org-src-container\">\n%s%s\n</div>"
		  ;; Build caption.
		  (let ((caption (org-export-get-caption src-block)))
		    (if (not caption) ""
		      (let ((listing-number
			     (format
			      "<span class=\"listing-number\">%s </span>"
			      (format
			       (org-html--translate "Listing %d:" info)
			       (org-export-get-ordinal
				src-block info nil #'org-html--has-caption-p)))))
			(format "<label class=\"org-src-name\">%s%s</label>"
				listing-number
				(org-trim (org-export-data caption info))))))
		  ;; Contents.
		  (if klipsify
		      (format "<pre><code class=\"src src-%s\"%s%s>%s</code></pre>"
			      lang
			      label
			      (if (string= lang "html")
				  " data-editor-type=\"html\""
				"")
			      code)
		    (format "<pre><code class=\"src %s src-%s\"%s>%s</code></pre>"
			    lang lang label code)))))))

Markdown

  (eval-after-load "org"
    '(require 'ox-md nil t))

org-babel

Literate programming!

  (add-hook 'after-init-hook
    (lambda ()
      (org-babel-do-load-languages
       'org-babel-load-languages
       '((emacs-lisp . t)
	 (python . t)
	 (shell . t)
	 (clojure . t)
	 (lisp . t)
	 (scheme . t)
	 (java . t)
	 (js . t)
	 (dot . t)
	 (ditaa . t)))))

Get rid of the confirmation prompt:

  (setq org-confirm-babel-evaluate nil)

Display inline images after executing a source block:

  (add-hook 'org-babel-after-execute-hook
      (lambda ()
	(org-display-inline-images nil t)
	(org-redisplay-inline-images)))

Images

  (setq org-image-actual-width nil)

Projectile

  (use-package projectile
    :config
    (projectile-mode)
    (jdormit/define-prefix "fp" "projectile")
    (leader-def-key "fpf" 'projectile-find-file)
    (leader-def-key "fpg" 'projectile-grep))

Mode line

A sexy mode line for maximum geek cred:

  (use-package smart-mode-line
    :init
    (sml/setup))

UI

Get rid of the janky buttons:

  (tool-bar-mode -1)

And the ugly scroll bars:

  (set-scroll-bar-mode nil)

Use variable-pitch-mode in text modes:

  (add-hook 'text-mode-hook (lambda () (variable-pitch-mode)))
  (add-hook 'w3m-mode-hook (lambda () (variable-pitch-mode)))

Always use buffer-face-mode in code and text buffers:

  (add-hook 'prog-mode-hook #'buffer-face-mode)
  (add-hook 'text-mode-hook #'buffer-face-mode)

Display the column number in programming modes:

  (add-hook 'prog-mode-hook #'column-number-mode)

Load up some tasty themes:

  (use-package solarized-theme)

UI-related keybindings:

  (jdormit/define-prefix "u" "UI")
  (leader-def-key "ut" #'customize-themes)
  (leader-def-key "uf" #'customize-face)

Frame parameters

Remember the previous frame size if previously set:

  (when-let ((frame-width (get-persisted-var 'frame-width)))
      (set-frame-width (selected-frame) frame-width))

  (when-let ((frame-height (get-persisted-var 'frame-height)))
      (set-frame-height (selected-frame) frame-height))

Default to reasonable value if not:

  (let ((frame-width (get-persisted-var 'frame-width))
        (frame-height (get-persisted-var 'frame-height)))
    (unless frame-width (set-frame-width (selected-frame) 150))
    (unless frame-height (set-frame-height (selected-frame) 60)))

Functions to change the frame size:

  (defun jdormit/set-frame-size (width height)
    (interactive "nWidth: \nnHeight: ")
    (if (display-graphic-p)
        (progn
          (persist-variable 'frame-width width)
          (persist-variable 'frame-height height)
          (set-frame-size (selected-frame) width height))
      (message "Not running graphically")))

  (defun jdormit/set-frame-width (width)
    (interactive "nWidth: ")
    (jdormit/set-frame-size width (frame-height)))

  (defun jdormit/set-frame-height (height)
    (interactive "nHeight: ")
    (jdormit/set-frame-size (frame-width) height))

Keybindings:

  (leader-def-key "Fw" 'jdormit/set-frame-width)
  (leader-def-key "Fh" 'jdormit/set-frame-height)
  (leader-def-key "Fs" 'jdormit/set-frame-size)

EShell

Easy keybinding to open EShell:

  (leader-def-key "'" 'eshell)

Make EShell's tab completion work like Bash's:

  (setq eshell-cmpl-cycle-completions nil)

Destroy shell buffers created by eshell when the process dies::

  (setq eshell-destroy-buffer-when-process-dies t)

Visual programs:

  (defun eshell-setup ()
    (add-to-list 'eshell-visual-commands "crawl")
    (add-to-list 'eshell-visual-commands "ssh"))

  (add-hook 'eshell-mode-hook #'eshell-setup)

Load .dir-locals.el when switching directories:

  (add-hook 'eshell-directory-change-hook #'hack-dir-local-variables-non-file-buffer)

Some aliases:

  (add-hook 'eshell-mode-hook
      (lambda ()
	(eshell/alias "php-debug"
	  "php -d xdebug.remote_enable=on -d xdebug.remote_host=127.0.0.1 -d xdebug.remote_port=9000 -d xdebug.remote_handler=dbgp -d xdebug.idekey=geben -d xdebug.remote_autostart=On $*")
	(eshell/alias "sortpom"
	  "mvn com.github.ekryd.sortpom:sortpom-maven-plugin:sort -Dsort.keepBlankLines -Dsort.sortDependencies=scope,groupId,artifactId -Dsort.createBackupFile=false $*")))

JavaScript

  (use-package json-mode
    :mode (("\\.json\\'" . json-mode)))

Java

LSP Java uses the Eclipse JDT Language Server as a backend to enable Java IDE features.

  (use-package lsp-mode)

  (use-package company-lsp
    :after (company)
    :config
    (setq company-lsp-cache-candidates t))

  (use-package lsp-ui
    :config (setq lsp-ui-sideline-enable t
		  lsp-ui-sideline-show-symbol t
		  lsp-ui-sideline-show-hover t
		  lsp-ui-sideline-show-code-actions t
		  lsp-ui-sideline-update-mode 'point))

  (defun jdormit/set-up-java ()
    (lsp-java-enable)
    (flycheck-mode t)
    (push 'company-lsp company-backends)
    (company-mode t)
    (lsp-ui-flycheck-enable t)
    (set-variable 'c-basic-offset 2)
    (lsp-ui-sideline-mode))

  (use-package lsp-java
    :requires (lsp-ui-flycheck lsp-ui-sideline)
    :config
    (add-hook 'java-mode-hook 'jdormit/set-up-java)
    (setq lsp-inhibit-message t
	  lsp-java-import-maven-enabled t))

Configure Java project sources:

  (setq lsp-java--workspace-folders
	(list (expand-file-name "~/src/Automation")
	      (expand-file-name "~/src/AutomationSharedExecution")
	      (expand-file-name "~/src/AutomationPlatform")))

Python

Elpy is a python IDE package:

  (use-package elpy
    :config
    (elpy-enable)
    (pyvenv-mode))

  (defun eshell/workon (name)
    (pyvenv-workon name))

  (defun eshell/activate (dir)
    (pyvenv-activate dir))

  (defun eshell/deactivate ()
    (pyvenv-deactivate))

Pipenv is the Python standard dependency management/virtual environment tool. pipenv.el teaches Emacs its ways:

  (use-package pipenv
    :hook (python-mode . pipenv-mode)
    :commands (pipenv-mode
	       pipenv-activate
	       pipenv-run))

A function to run a pipenv-aware python repl:

  (defun run-pipenv ()
    "Runs a pipenv-aware Python shell"
    (interactive)
    (pipenv-activate)
    (run-python nil nil t))

Go

Basic support:

  (use-package go-mode
    :mode (("\\.go\\'" . go-mode)))

Add in autocompletion. This requires gocode to be installed:

  (use-package company-go
    :after go-mode
    :config
    (add-hook 'go-mode-hook
	(lambda ()
	  (set (make-local-variable 'company-backends) '(company-go))
	  (company-mode-on))))

Clojure

Start with clojure-mode:

  (use-package clojure-mode
    :mode (("\\.clj\\'" . clojure-mode)
	   ("\\.cljs\\'" . clojurescript-mode)
	   ("\\.cljc\\'" . clojurec-mode)
	   ("\\.edn\\'" . clojure-mode))
    :config
    (define-clojure-indent
      (defroutes 'defun)
      (GET 2)
      (POST 2)
      (PUT 2)
      (DELETE 2)
      (HEAD 2)
      (ANY 2)
      (OPTIONS 2)
      (PATCH 2)
      (rfn 2)
      (let-routes 1)
      (context 2)
      (:= 3)
      (:+ 3)))

Sprinkle in some CIDER:

  (use-package cider
    :commands (cider-mode cider-jack-in cider-jack-in-clojurescript)
    :config (setq cider-known-endpoints
		  '(("local" "localhost" "4005")))
    :hook ((clojure-mode . cider-mode)
	   (clojurescript-mode . cider-mode)
	   (clojurec-mode . cider-mode))
    :general
    (cider-stacktrace-mode-map "SPC" leader-map))

  (defun jdormit/cider-setup ()
    (local-set-key (kbd "C-c M-b") 'cider-debug-defun-at-point))

  (add-hook 'cider-mode-hook 'jdormit/cider-setup)

Scheme

Tell emacs about file extensions which should activate scheme-mode:

  (add-to-list 'auto-mode-alist '("\\.guile\\'" . scheme-mode))
  (add-to-list 'auto-mode-alist '("\\.rkt\\'" . scheme-mode))

Geiser is a Scheme IDE for Emacs that supports a bunch of common Scheme implementations.

  (use-package geiser
    :general
    (geiser-debug-mode-map "SPC" leader-map))

And a handy shortcut to hop into a Geiser REPL:

  (leader-def-key "sg" 'run-geiser)

Common Lisp

SLIME is a set of modes and utilities for writing Common Lisp in Emacs. Quicklisp is the de-facto Common Lisp package manager. It comes with some Emacs bindings.

  (use-package slime-company)

  (use-package slime
    :commands slime
    :config
    (setq inferior-lisp-program
          (executable-find "sbcl")
          slime-contribs '(slime-repl
                           slime-fancy
                           slime-company))
    (when (file-exists-p
           (expand-file-name "~/quicklisp/slime-helper.el"))
      (load (expand-file-name "~/quicklisp/slime-helper.el"))))

    (add-to-list 'auto-mode-alist '("\\.cl\\'" . lisp-mode))

Keyboard shortcut to start a SLIME REPL:

  (leader-def-key "sc" 'slime)

Haskell

  (defun jdormit/haskell-setup ()
    (local-set-key (kbd "C-c M-j") 'interactive-haskell-mode))
  (use-package haskell-mode
    :mode (("\\.hs\\'" . haskell-mode)))
  (add-hook 'haskell-mode-hook 'jdormit/haskell-setup)

PHP

  (use-package php-mode
    :mode "\\.php\\'")

  (use-package mmm-mode)

Geben is an interface to XDebug allowing debugging PHP in Emacs:

  (use-package geben
    :commands (geben))

Some keybindings to start and end Geben:

  (general-def php-mode-map "C-c C-d" #'geben)
  (general-def php-mode-map "C-c C-q" #'geben-end)

An Eshell alias to start PHP using XDebug:

  (add-hook 'eshell-mode-hook
	    (lambda ()
	      (eshell/alias "php-debug"
			    "php -d xdebug.remote_enable=on -d xdebug.remote_host=127.0.0.1 -d xdebug.remote_port=9000 -d xdebug.remote_handler=dbgp -d xdebug.idekey=geben -d xdebug.remote_autostart=On $*")))

Company-mode autocompletion for PHP:

  (use-package company-php
    :config
    (add-hook 'php-mode-hook
	      (lambda ()
		(ac-php-core-eldoc-setup)
		(make-local-variable 'company-backends)
		(add-to-list 'company-backends 'company-ac-php-backend))))

Pharen

Pharen is a Lisp that compiles to PHP. It looks a lot like Clojure.

  (add-to-list 'auto-mode-alist '("\\.phn\\'" . clojure-mode))

CSVs

  (use-package csv-mode
    :mode "\\.csv\\'")

Markdown

  (use-package markdown-mode
    :commands (markdown-mode gfm-mode)
    :mode (("README\\.md\\'" . gfm-mode)
           ("\\.md\\'" . markdown-mode)
           ("\\.markdown\\'" . markdown-mode))
    :init (setq markdown-command "pandoc"))

IELM

Enable lexical scoping in IELM:

  (add-hook 'ielm-mode-hook
            #'(lambda ()
                (interactive)
                (setq lexical-binding t)))

Magit

Magit is objectively the best Git interface.

  (use-package magit
    :commands magit-status
    :general
    (magit-mode-map "SPC" leader-map))

Enable evil keybindings:

  (use-package evil-magit
    :hook ((magit-status-mode . evil-magit-init))
    :config
    (require 'evil-magit))
  (jdormit/define-prefix "g" "git")
  (leader-def-key "gs" 'magit-status)

Use ido-mode for completion within Magit:

  (setq magit-completing-read-function 'magit-ido-completing-read)

Ledger Mode

This mode requires that ledger be installed on the system.

  (use-package ledger-mode
    :mode "\\.ledger\\'"
    :config
    (add-to-list 'evil-emacs-state-modes 'ledger-report-mode)
    (general-def ledger-report-mode-map "SPC" leader-map)
    (add-hook 'ledger-mode-hook
	      (lambda ()
		(variable-pitch-mode 0)))
    (add-hook 'ledger-report-mode-hook
	      (lambda ()
		(variable-pitch-mode 0))))

PDFs

  (use-package pdf-tools
    :mode ("\\.pdf\\'" . pdf-view-mode)
    :config
    (pdf-tools-install)
    :general
    (pdf-view-mode-map "SPC" leader-map))

EPubs

  (defun jdormit/nov-config ()
    (when (member "Input Serif" (font-family-list))
      (face-remap-add-relative 'variable-pitch :family "Input Serif"))
    (olivetti-mode)
    (nov-render-document))

  (use-package nov
    :mode ("\\.epub\\'" . nov-mode)
    :config
    (setq nov-text-width 80)
    (add-hook 'nov-mode-hook 'jdormit/nov-config)
    :general
    ('normal nov-mode-map "r" 'nov-render-document)
    ('normal nov-mode-map "=" 'nov-view-source)
    ('normal nov-mode-map "+" 'nov-view-content-source)
    ('normal nov-mode-map "m" 'nov-display-metadata)
    ('normal nov-mode-map "n" 'nov-next-document)
    ('normal nov-mode-map "]" 'nov-next-document)
    ('normal nov-mode-map "p" 'nov-previous-document)
    ('normal nov-mode-map "[" 'nov-previous-document)
    ('normal nov-mode-map "t" 'nov-goto-toc)
    ('normal nov-mode-map "RET" 'nov-browse-url)
    ('normal nov-mode-map "<follow-link>" 'mouse-face)
    ('normal nov-mode-map "<mouse-2>" 'nov-browse-url)
    ('normal nov-mode-map "TAB" 'shr-next-link)
    ('normal nov-mode-map "M-TAB" 'shr-previous-link)
    ('normal nov-mode-map "<backtab>" 'shr-previous-link)
    ('normal nov-mode-map "SPC" 'nov-scroll-up)
    ('normal nov-mode-map "S-SPC" 'nov-scroll-down)
    ('normal nov-mode-map "DEL" 'nov-scroll-down)
    ('normal nov-mode-map "<home>" 'beginning-of-buffer)
    ('normal nov-mode-map "<end>" 'end-of-buffer)
    ('normal nov-mode-map "SPC" leader-map))

Org mode links

First, keep a reference to filename of the .epub before nov.el blows it away:

  (defvar nov-epub-file)
  (add-hook 'nov-mode-hook
            #'(lambda ()
                (message "epub file: " buffer-file-name)
                (setq nov-epub-file buffer-file-name)))

That reference lets us construct a link back to the .epub:

  (defun org-epub-store-link ()
    (when (eq major-mode 'nov-mode)
      (let ((epub-name (alist-get 'title nov-metadata)))
        (org-store-link-props
         :type "file"
         :link (concat "file://" nov-epub-file)))))

  (add-hook 'org-store-link-functions 'org-epub-store-link)

Dashboard

Instead of the GNU Emacs buffer on startup, display a cool dashboard:

  (use-package dashboard
    :config
    (setq dashboard-items '((recents . 5)
                            (projects . 5))
          dashboard-startup-banner 'official)
    (dashboard-setup-startup-hook))

Mu4e

Because email in Emacs is badass. My mail set up is based on this mutt setup and this mu4e setup.

  (defvar jdormit/mu4e-load-path
    (if (file-exists-p "/usr/local/share/emacs/site-lisp/mu/mu4e")
	"/usr/local/share/emacs/site-lisp/mu/mu4e"
      (if (file-exists-p "/usr/share/emacs/site-lisp/mu4e")
	  "/usr/share/emacs/site-lisp/mu4e")))

  (add-to-list 'load-path jdormit/mu4e-load-path)
  (autoload 'mu4e (concat jdormit/mu4e-load-path "/mu4e.el"))
  (autoload 'mu4e-update-index (concat jdormit/mu4e-load-path "/mu4e.el"))
  (with-eval-after-load 'mu4e
    (require 'org-mu4e)
    (setq
     mu4e-maildir (expand-file-name "~/.mail")
     message-send-mail-function 'message-send-mail-with-sendmail
     mu4e-get-mail-command "mbsync -a"
     sendmail-program (executable-find "msmtp")
     mu4e-attachment-dir "~/Downloads"
     mu4e-compose-format-flowed t
     mu4e-html2text-command "w3m -dump -T text/html -o display_link_number=1"
     mu4e-change-filenames-when-moving t
     org-mu4e-link-query-in-headers-mode nil
     mu4e-maildirs-extension-custom-list
     '("/jeremy-dormitzer-gmail-com/Inbox"
       "/jeremy-dormitzer-net/Inbox"
       "/jeremy-getpterotype-com/Inbox")
     mu4e-contexts
     (let ((per-dir "/jeremy-dormitzer-gmail-com")
	   (dormit-dir "/jeremy-dormitzer-net")
	   (pterotype-dir "/jeremy-getpterotype-com")
	   (lola-dir "/jeremydormitzer-lola-com"))
       `(,(make-mu4e-context
	   :name "Pterotype"
	   :match-func (lambda (msg)
			 (when msg ())
			 (when msg (string-match-p
				    "jeremy-getpterotype-com"
				    (mu4e-message-field msg :path))))
	   :vars `((user-mail-address . "jeremy@getpterotype.com")
		   (user-full-name . "Jeremy Dormitzer")
		   (mu4e-sent-folder . ,(concat pterotype-dir "/Sent"))
		   (mu4e-drafts-folder . ,(concat pterotype-dir "/Drafts"))
		   (mu4e-refile-folder . ,(concat pterotype-dir "/Archive"))
		   (mu4e-trash-folder . ,(concat pterotype-dir "/Trash"))
		   (mu4e-sent-messages-behavior . delete)
		   (mu4e-get-mail-command . "mbsync jeremy-getpterotype-com")
		   (message-sendmail-extra-arguments
		    . ("-a" "jeremy-getpterotype.com"))))
	 ,(make-mu4e-context
	   :name "GMail"
	   :match-func (lambda (msg)
			 (when msg (string-match-p
				    "jeremy-dormitzer-gmail-com"
				    (mu4e-message-field msg :path))))
	   :vars `((user-mail-address . "jeremy.dormitzer@gmail.com")
		   (user-full-name . "Jeremy Dormitzer")
		   (mu4e-sent-folder . ,(concat per-dir "/Sent"))
		   (mu4e-drafts-folder . ,(concat per-dir "/Drafts"))
		   (mu4e-refile-folder . ,(concat per-dir "/Archive"))
		   (mu4e-trash-folder . ,(concat per-dir "/Trash"))
		   (mu4e-sent-messages-behavior . delete)
		   (mu4e-get-mail-command . "mbsync jeremy-dormitzer-gmail-com")
		   (message-sendmail-extra-arguments
		    . ("-a" "jeremy.dormitzer-gmail.com"))))
	 ,(make-mu4e-context
	   :name "Dormitzer"
	   :match-func (lambda (msg)
			 (when msg (string-match-p
				    "jeremy-dormitzer-net"
				    (mu4e-message-field msg :path))))
	   :vars `((user-mail-address . "jeremy@dormitzer.net")
		   (user-full-name . "Jeremy Dormitzer")
		   (mu4e-sent-folder . ,(concat dormit-dir "/Sent"))
		   (mu4e-drafts-folder . ,(concat dormit-dir "/Drafts"))
		   (mu4e-refile-folder . ,(concat dormit-dir "/Archive"))
		   (mu4e-trash-folder . ,(concat dormit-dir "/Trash"))
		   (mu4e-get-mail-command . "mbsync jeremy-dormitzer-net")
		   (message-sendmail-extra-arguments
		    . ("-a" "jeremy-dormitzer.net"))))
	 ,(make-mu4e-context
	   :name "Lola"
	   :match-func (lambda (msg)
			 (when msg ())
			 (when msg (string-match-p
				    "jeremydormitzer-lola-com"
				    (mu4e-message-field msg :path))))
	   :vars `((user-mail-address . "jeremydormitzer@lola.com")
		   (user-full-name . "Jeremy Dormitzer")
		   (mu4e-sent-folder . ,(concat lola-dir "/Sent"))
		   (mu4e-drafts-folder . ,(concat lola-dir "/Drafts"))
		   (mu4e-refile-folder . ,(concat lola-dir "/Archive"))
		   (mu4e-trash-folder . ,(concat lola-dir "/Trash"))
		   (mu4e-sent-messages-behavior . delete)
		   (mu4e-get-mail-command . "mbsync jeremydormitzer-lola-com")
		   (message-sendmail-extra-arguments
		    . ("-a" "jeremydormitzer-lola.com"))))))
     mu4e-context-policy 'ask
     mu4e-compose-context-policy 'ask-if-none))

  (jdormit/define-prefix "am" "mu4e")
  (leader-def-key "amm" 'mu4e)

Custom actions:

  (defun mu4e-view-go-to-url-w3m (&optional MULTI)
    (let ((browse-url-browser-function 'w3m-browse-url))
      (mu4e-view-go-to-url MULTI)))

  (defun mu4e-action-view-in-browser-w3m (msg)
    (let ((browse-url-browser-function 'w3m-browse-url))
      (mu4e-action-view-in-browser msg)))

  (with-eval-after-load 'mu4e
    (add-to-list 'mu4e-view-actions
                 '("View in browser" . mu4e-action-view-in-browser) t)
    (add-to-list 'mu4e-view-actions
                 '("WView in w3m" . mu4e-action-view-in-browser-w3m) t)
    (add-to-list 'mu4e-view-actions
                 '("wGo to URL with w3m" . mu4e-view-go-to-url-w3m) t))

Make mu4e the default sendmail program:

  (setq mail-user-agent 'mu4e-user-agent)

Use the spacebar as a leader key in mu4e modes:

  (general-def mu4e-main-mode-map "SPC" leader-map)
  (general-def mu4e-headers-mode-map "SPC" leader-map)
  (general-def mu4e-view-mode-map "SPC" leader-map)

HTML email

Redefinitions

Redefine org-mime-insert-html-content to export the plain part of HTML emails as ascii instead of org:

  (with-eval-after-load 'org-mime
    (defun org-mime-insert-html-content (body file s opts)
      (let* ((files (org-mime-extract-non-image-files))
	     ;; dvipng for inline latex because MathJax doesn't work in mail
	     ;; Also @see https://github.com/org-mime/org-mime/issues/16
	     ;; (setq org-html-with-latex nil) sometimes useful
	     (org-html-with-latex org-mime-org-html-with-latex-default)
	     ;; we don't want to convert org file links to html
	     (org-html-link-org-files-as-html nil)
	     (org-link-file-path-type 'absolute)
	     ;; makes the replies with ">"s look nicer
	     (org-export-preserve-breaks org-mime-preserve-breaks)
	     (plain (org-mime--export-string body 'ascii))
	     ;; org 9
	     (org-html-htmlize-output-type 'inline-css)
	     ;; org 8
	     (org-export-htmlize-output-type 'inline-css)
	     (html-and-images (org-mime-replace-images (org-mime--export-string s 'html opts)
						       file))
	     (images (cdr html-and-images))
	     (html (org-mime-apply-html-hook (car html-and-images))))

	;; If there are files that were attached, we should remove the links,
	;; and mark them as attachments. The links don't work in the html file.
	(when files
	  (mapc (lambda (f)
		  (setq html (replace-regexp-in-string
			      (format "<a href=\"%s\">%s</a>"
				      (regexp-quote f) (regexp-quote f))
			      (format "%s (attached)" (file-name-nondirectory f))
			      html)))
		files))

	(insert (org-mime-multipart plain
				    html
				    (mapconcat 'identity images "\n")))

	;; Attach any residual files
	(when files
	  (mapc (lambda (f)
		  (when org-mime-debug (message "attaching: %s" f))
		  (mml-attach-file f))
		files)))))

And redefine mu4e~compose-handler to add a new hook:

  (defcustom jdormit-mu4e-compose-hook nil
    "Hook run after the message composition buffer is set up"
    :type 'hook
    :group 'mu4e-compose)

  (with-eval-after-load 'mu4e
    (defun* mu4e~compose-handler (compose-type &optional original-msg includes)
      "Create a new draft message, or open an existing one.

  COMPOSE-TYPE determines the kind of message to compose and is a
  symbol, either `reply', `forward', `edit', `resend' `new'. `edit'
  is for editing existing (draft) messages. When COMPOSE-TYPE is
  `reply' or `forward', MSG should be a message plist.  If
  COMPOSE-TYPE is `new', ORIGINAL-MSG should be nil.

  Optionally (when forwarding, replying) ORIGINAL-MSG is the original
  message we will forward / reply to.

  Optionally (when forwarding) INCLUDES contains a list of
     (:file-name <filename> :mime-type <mime-type> :disposition <disposition>)
  for the attachements to include; file-name refers to
  a file which our backend has conveniently saved for us (as a
  tempfile)."

      ;; Run the hooks defined for `mu4e-compose-pre-hook'. If compose-type is
      ;; `reply', `forward' or `edit', `mu4e-compose-parent-message' points to the
      ;; message being forwarded or replied to, otherwise it is nil.
      (set (make-local-variable 'mu4e-compose-parent-message) original-msg)
      (put 'mu4e-compose-parent-message 'permanent-local t)
      ;; remember the compose-type
      (set (make-local-variable 'mu4e-compose-type) compose-type)
      (put 'mu4e-compose-type 'permanent-local t)
      ;; maybe switch the context
      (mu4e~context-autoswitch mu4e-compose-parent-message
			       mu4e-compose-context-policy)
      (run-hooks 'mu4e-compose-pre-hook)

      ;; this opens (or re-opens) a messages with all the basic headers set.
      (let ((winconf (current-window-configuration)))
	(condition-case nil
	    (mu4e-draft-open compose-type original-msg)
	  (quit (set-window-configuration winconf)
		(mu4e-message "Operation aborted")
		(return-from mu4e~compose-handler))))
      ;; insert mail-header-separator, which is needed by message mode to separate
      ;; headers and body. will be removed before saving to disk
      (mu4e~draft-insert-mail-header-separator)
      ;; maybe encrypt/sign replies
      (mu4e~compose-crypto-reply original-msg compose-type)
      ;; include files -- e.g. when forwarding a message with attachments,
      ;; we take those from the original.
      (save-excursion
	(goto-char (point-max)) ;; put attachments at the end
	(dolist (att includes)
	  (mml-attach-file
	   (plist-get att :file-name) (plist-get att :mime-type))))
      ;; buffer is not user-modified yet
      (mu4e~compose-set-friendly-buffer-name compose-type)
      (set-buffer-modified-p nil)
      ;; now jump to some useful positions, and start writing that mail!

      (if (member compose-type '(new forward))
	  (message-goto-to)
	(message-goto-body))
      ;; bind to `mu4e-compose-parent-message' of compose buffer
      (set (make-local-variable 'mu4e-compose-parent-message) original-msg)
      (put 'mu4e-compose-parent-message 'permanent-local t)

      ;; hide some headers
      (mu4e~compose-hide-headers)
      ;; switch on the mode
      (mu4e-compose-mode)

      (run-hooks 'jdormit-mu4e-compose-hook)

      ;; set mu4e-compose-type once more for this buffer,
      ;; we loose it after the mode-change, it seems
      (set (make-local-variable 'mu4e-compose-type) compose-type)
      (put 'mu4e-compose-type 'permanent-local t)

      (when mu4e-compose-in-new-frame
	;; make sure to close the frame when we're done with the message these are
	;; all buffer-local;
	(push 'delete-frame message-exit-actions)
	(push 'delete-frame message-postpone-actions))))

Actual HTML mail logic

  (with-eval-after-load 'mu4e (require 'org-mu4e))
  (use-package org-mime
    :config
    (setq org-mime-export-options '(:section-numbers nil
                                    :with-author nil
                                    :with-toc nil)))

  (defun htmlize-and-send ()
    (interactive)
    (when (member 'org~mu4e-mime-switch-headers-or-body post-command-hook)
      (org-mime-htmlize)
      (message-send-and-exit)))

  (add-hook 'org-ctrl-c-ctrl-c-hook #'htmlize-and-send t)

  (defun setup-compose-buffer ()
    (org-mu4e-compose-org-mode))

  (add-hook 'jdormit-mu4e-compose-hook #'setup-compose-buffer)

When citing (quoting) messages in a reply, wrap them in org quote blocks instead of prefixing each line with '> ':

  (defun jdormit-citation-line-function ()
    (message-insert-citation-line)
    (insert "#+BEGIN_QUOTE\n"))

  (defun jdormit-cite-function ()
    (let ((message-yank-prefix "")
	  (message-yank-cited-prefix "")
	  (message-yank-empty-prefix "")) 
      (save-excursion
	(message-cite-original)
	(goto-char (point-max))
	(insert "\n#+END_QUOTE"))))

  (with-eval-after-load 'mu4e
    (setq message-citation-line-function #'jdormit-citation-line-function
	  mu4e-compose-cite-function #'jdormit-cite-function))

Some keybindings to send the current org buffer or subtree as an email:

  (general-def org-mode-map "C-c m" #'org-mime-org-buffer-htmlize)
  (general-def org-mode-map "C-c s" #'org-mime-org-subtree-htmlize)

Mu4e-alert

Desktop notifications for mu4e emails.

  (defun jdormit-get-mu4e-alert-style ()
    (if (memq window-system '(mac ns))
	'notifier
      'libnotify))

  (use-package mu4e-alert
    :config
    (setq mu4e-alert-interesting-mail-query
      (concat
       "flag:unread maildir:/jeremy-dormitzer-gmail-com/Inbox"
       " OR flag:unread maildir:/jeremy-dormitzer-net/Inbox"
       " OR flag:unread maildir:/jeremydormitzer-lola-com/Inbox"
       " OR flag:unread maildir:/jeremy-getpterotype-com/Inbox"))
    (mu4e-alert-set-default-style (jdormit-get-mu4e-alert-style))
    (mu4e-alert-enable-notifications)
    (mu4e-alert-enable-mode-line-display)
    (leader-def-key "amu" #'mu4e-alert-view-unread-mails))

w3m

Browsing the web from Emacs. Relies on having w3m installed.

  (use-package w3m
    :commands (w3m
	       w3m-browse-url
	       w3m-search-new-session)
    :config
    (setq w3m-home-page "https://start.duckduckgo.com"
	  w3m-search-default-engine "duckduckgo"
	  w3m-cookie-reject-domains '("www.wsj.com"
				      "www.bbc.com"
				      "www.nytimes.com"
				      "www.washingtonpost.com"))
    (add-to-list 'evil-normal-state-modes 'w3m-mode)
    :general
    ('normal w3m-mode-map "R" 'w3m-reload-this-page)
    ('normal w3m-mode-map "r" 'w3m-redisplay-this-page)
    ('normal w3m-mode-map "TAB" 'w3m-next-anchor)
    ('normal w3m-mode-map "<backtab>" 'w3m-previous-anchor)
    ('normal w3m-mode-map "]" 'w3m-next-form)
    ('normal w3m-mode-map "[" 'w3m-previous-form)
    ('normal w3m-mode-map "}" 'w3m-next-image)
    ('normal w3m-mode-map "{" 'w3m-previous-image)
    ('normal w3m-mode-map "RET" 'w3m-view-this-url)
    ('normal w3m-mode-map "B" 'w3m-view-previous-page)
    ('normal w3m-mode-map "N" 'w3m-view-next-page)
    ('normal w3m-mode-map "^" 'w3m-view-parent-page)
    ('normal w3m-mode-map "C-f" 'w3m-scroll-up-or-next-url)
    ('normal w3m-mode-map "C-b" 'w3m-scroll-down-or-previous-url)
    ('normal w3m-mode-map "u" 'w3m-goto-url)
    ('normal w3m-mode-map "U" 'w3m-goto-url-new-session)
    ('normal w3m-mode-map "H" 'w3m-gohome)
    ('normal w3m-mode-map "M" 'w3m-view-url-with-browse-url)
    ('normal w3m-mode-map "M-d" 'w3m-download)
    ('normal w3m-mode-map "d" 'w3m-download-this-url)
    ('normal w3m-mode-map "I" 'w3m-view-image)
    ('normal w3m-mode-map "M-i" 'w3m-save-image)
    ('normal w3m-mode-map "t" 'w3m-toggle-inline-image)
    ('normal w3m-mode-map "T" 'w3m-toggle-inline-images)
    ('normal w3m-mode-map "C" 'w3m-print-this-url)
    ('normal w3m-mode-map "c" 'w3m-print-current-url)

    ('normal w3m-mode-map "\\" 'w3m-view-source)
    ('normal w3m-mode-map "=" 'w3m-view-header)
    ('normal w3m-mode-map "M-k" 'w3m-cookie)
    ('normal w3m-mode-map "s" 'w3m-search)
    ('normal w3m-mode-map "S" 'w3m-search-new-session)
    ('normal w3m-mode-map "|" 'w3m-pipe-source)
    ('normal w3m-mode-map "M-h" 'w3m-history)
    ('normal w3m-mode-map "q" 'w3m-close-window)
    ('normal w3m-mode-map "Q" 'w3m-quit))

    (jdormit/define-prefix "aw" "w3m")
    (leader-def-key "aww" 'w3m)
    (leader-def-key "aws" 'w3m-search-new-session)
    (leader-def-key "awb" 'w3m-browse-url)

I mostly want `browse-url-at-point` to open stuff in Firefox, but in some cases I want it within Emacs:

  (defun browse-url-at-point-w3m ()
    "Opens the URL at point in w3m"
    (interactive)
    (let ((browse-url-browser-function 'w3m-browse-url))
      (if (eq major-mode 'org-mode)
          (org-open-at-point)
          (browse-url-at-point))))

  (leader-def-key "awB" 'browse-url-at-point-w3m)

I want to be able to set a custom "Referer" header by setting the variable `jdormit/w3m-referers`. I also want to be able to set websites that cookies will never get sent to:

  (defvar jdormit/w3m-referers nil)
  (defvar jdormit/w3m-no-cookie-sites nil)

  (defun get-referer (url referer-list)
    "Retrieve the referer specified by url in referer-list"
    (when (not (eq nil referer-list))
      (let ((first (car referer-list))
            (rest (cdr referer-list)))
        (if (string-match-p (car first) url)
            (cdr first)
          (get-referer url rest)))))

  (defun should-not-set-cookie-p (url no-cookie-sites)
    "Non-nil if cookies should not be sent to url"
    (when (not (eq nil no-cookie-sites))
      (if (string-match-p (car no-cookie-sites) url)
          t
        (should-not-set-cookie-p url (cdr no-cookie-sites)))))

  (advice-add 'w3m-header-arguments :around
              (lambda (w3m-header-arguments &rest r)
                (cl-destructuring-bind
                    (method url temp-file body referer content-type) r
                  (let ((w3m-use-cookies
                         (if (should-not-set-cookie-p url jdormit/w3m-no-cookie-sites)
                             nil 
                           w3m-use-cookies)))
                    (if-let ((referer (get-referer url jdormit/w3m-referers))) 
                              (funcall w3m-header-arguments
                                       method
                                       url
                                       temp-file
                                       body
                                       referer
                                       content-type)
                            (apply w3m-header-arguments r))))))

And here are the websites where I want custom referers and/or no cookies:

  (setq jdormit/w3m-referers '(("www.wsj.com" . "https://google.com")
			       ("www.nytimes.com" . "https://google.com")
			       ("www.newyorker.com" . "https://google.com")
			       ("www.economist.com" . "https://google.com")))
  (setq jdormit/w3m-no-cookie-sites '("www.wsj.com"
				      "www.nytimes.com"
				      "www.economist.com"
				      "www.newyorker.com"))

Render <code> and <pre> blocks in a different face. Which face can be set by setting or customizing the variable w3m-code-block-face. It defaults to 'fixed-pitch:

  (setq w3m-code-open-delimiter "{{{W3M_CODE_BLOCK_BEGIN}}}")
  (setq w3m-code-close-delimiter "{{{W3M_CODE_BLOCK_END}}}")

  (defun w3m-filter-code-blocks (url)
    (w3m-tag-code-block-filter "code")
    (w3m-tag-code-block-filter "pre"))

  (defun w3m-tag-code-block-filter (tag)
    (goto-char (point-min))
    (let ((open-tag-re (format "<%s[ \t\r\f\n]*[^>]*>" tag))
	  (close-tag-re (format "</%s[ \t\r\f\n]*>" tag)))
      (while (re-search-forward open-tag-re nil t)
	(let ((start (match-beginning 0)))
	  (when (re-search-forward close-tag-re nil t)
	    (goto-char start)
	    (insert w3m-code-open-delimiter)
	    (goto-char (+ (string-width w3m-code-open-delimiter) (match-end 0)))
	    (insert w3m-code-close-delimiter))))))

  (defcustom w3m-code-block-face 'fixed-pitch
    "Face for <code> and <pre> blocks in w3m")

  (defun w3m-fontify-code-block ()
    (goto-char (point-min))
    (while (search-forward w3m-code-open-delimiter nil t)
      (let ((begin-block-start (match-beginning 0))
	    (begin-block-end (match-end 0))
	    (code-start (match-end 0)))
	(when (search-forward w3m-code-close-delimiter nil t)
	  (w3m-add-face-property code-start (match-beginning 0) w3m-code-block-face)
	  (delete-region (match-beginning 0) (match-end 0)))
	(delete-region begin-block-start begin-block-end)
	(goto-char (point-min)))))


  ;(add-hook 'w3m-fontify-before-hook 'w3m-fontify-code-block)

Add a w3m filter to handle the code block delimiters:

  ;; (with-eval-after-load 'w3m-filter
  ;;   (add-to-list 'w3m-filter-configuration
  ;;     '(t "Render code blocks with a different face" ".*" w3m-filter-code-blocks)))

Wakatime

Wakatime is a tool that tracks how much time you spend coding and various metrics about your development activity.

It needs a helper script to work properly.

  (setq wakatime-path
    (if (file-exists-p "/usr/local/bin/wakatime")
	"/usr/local/bin/wakatime"
      (when (file-exists-p "/usr/bin/wakatime")
	"/usr/bin/wakatime")))

  (use-package wakatime-mode
    :if wakatime-path
    :init
    (setq wakatime-api-key (password-store-get "wakatime-api-key")
	  wakatime-cli-path wakatime-path)
    :config
    (global-wakatime-mode))

Elfeed

Elfeed is a feed reader for Emacs.

  (defun elfeed-setup-hook ()
    (set (make-local-variable 'browse-url-browser-function)
	 'w3m-browse-url)
    (set (make-local-variable 'jdormit/w3m-referer) "https://www.google.com")
    (general-def 'normal elfeed-search-mode-map "q" 'elfeed-search-quit-window)
    (general-def 'normal elfeed-search-mode-map "C-r" 'elfeed-search-update--force)
    (general-def 'normal elfeed-search-mode-map "R" 'elfeed-search-fetch)
    (general-def 'normal elfeed-search-mode-map "RET" 'elfeed-search-show-entry)
    (general-def 'normal elfeed-search-mode-map "s" 'elfeed-search-live-filter)
    (general-def 'normal elfeed-search-mode-map "S" 'elfeed-search-set-filter)
    (general-def 'normal elfeed-search-mode-map "B" 'elfeed-search-browse-url)
    (general-def 'normal elfeed-search-mode-map "y" 'elfeed-search-yank)
    (general-def 'normal elfeed-search-mode-map "u" 'elfeed-search-tag-all-unread)
    (general-def 'normal elfeed-search-mode-map "r" 'elfeed-search-untag-all-unread)
    (general-def 'normal elfeed-search-mode-map "n" 'next-line)
    (general-def 'normal elfeed-search-mode-map "p" 'previous-line)
    (general-def 'normal elfeed-search-mode-map "+" 'elfeed-search-tag-all)
    (general-def 'normal elfeed-search-mode-map "-" 'elfeed-search-untag-all)
    (general-def 'normal elfeed-show-mode-map "d" 'elfeed-show-save-enclosure)
    (general-def 'normal elfeed-show-mode-map "q" 'elfeed-kill-buffer)
    (general-def 'normal elfeed-show-mode-map "r" 'elfeed-show-refresh)
    (general-def 'normal elfeed-show-mode-map "n" 'elfeed-show-next)
    (general-def 'normal elfeed-show-mode-map "p" 'elfeed-show-prev)
    (general-def 'normal elfeed-show-mode-map "s" 'elfeed-show-new-live-search)
    (general-def 'normal elfeed-show-mode-map "b" 'elfeed-show-visit)
    (general-def 'normal elfeed-show-mode-map "y" 'elfeed-show-yank)
    (general-def 'normal elfeed-show-mode-map "u" (elfeed-expose #'elfeed-show-tag 'unread))
    (general-def 'normal elfeed-show-mode-map "+" 'elfeed-show-tag)
    (general-def 'normal elfeed-show-mode-map "-" 'elfeed-show-untag)
    (general-def 'normal elfeed-show-mode-map "SPC" 'scroll-up-command)
    (general-def 'normal elfeed-show-mode-map "DEL" 'scroll-down-command)
    (general-def 'normal elfeed-show-mode-map "\t" 'shr-next-link)
    (general-def 'normal elfeed-show-mode-map [tab] 'shr-next-link)
    (general-def 'normal elfeed-show-mode-map "\e\t" 'shr-previous-link)
    (general-def 'normal elfeed-show-mode-map [backtab] 'shr-previous-link)
    (general-def 'normal elfeed-show-mode-map [mouse-2] 'shr-browse-url)
    (general-def 'normal elfeed-show-mode-map "A" 'elfeed-show-add-enclosure-to-playlist)
    (general-def 'normal elfeed-show-mode-map "P" 'elfeed-show-play-enclosure))

  (use-package elfeed
    :commands elfeed
    :config
    (add-hook 'elfeed-show-mode-hook 'elfeed-setup-hook)
    (setq-default elfeed-search-filter "@1-week-ago +unread +news ")
    (add-to-list 'evil-normal-state-modes 'elfeed-search-mode)
    (add-to-list 'evil-normal-state-modes 'elfeed-show-mode)
    (setq elfeed-feeds
	  '(("http://www.wsj.com/xml/rss/3_7085.xml" news)
	    ("https://www.wsj.com/xml/rss/3_7014.xml" news)
	    ("http://rss.nytimes.com/services/xml/rss/nyt/HomePage.xml" news)
	    ("https://www.newyorker.com/feed/everything" news)
	    ("https://www.economist.com/sections/business-finance/rss.xml" news)
	    ("https://www.economist.com/sections/economics/rss.xml" news))))

Keybinding for opening Elfeed:

  (leader-def-key "al" 'elfeed)

Undo Tree

  (use-package undo-tree
    :init
    (global-undo-tree-mode)
    (leader-def-key "bu" 'undo-tree-visualize)
    :general
    ('normal "u" #'undo-tree-undo)
    ('normal "C-r" #'undo-tree-redo)
    (undo-tree-visualizer-mode-map "SPC" leader-map))

Emojify

Because emojis make everything better.

  (use-package emojify)
  (leader-def-key "te" 'emojify-mode)

Mastodon

Mastodon is a federated FOSS social network similar to Twitter. Let's put it in Emacs!

First, install a dependency:

  (use-package discover)
  (defun jdormit/mastodon-setup ()
    (general-def 'normal mastodon-mode-map "j" #'mastodon-tl--goto-next-toot)
    (general-def 'normal mastodon-mode-map "k" #'mastodon-tl--goto-prev-toot)
    (general-def 'normal mastodon-mode-map "h" #'mastodon-tl--next-tab-item)
    (general-def 'normal mastodon-mode-map "l" #'mastodon-tl--previous-tab-item)
    (general-def 'normal mastodon-mode-map [?\t] #'mastodon-tl--next-tab-item)
    (general-def 'normal mastodon-mode-map [backtab] #'mastodon-tl--previous-tab-item)
    (general-def 'normal mastodon-mode-map [?\S-\t] #'mastodon-tl--previous-tab-item)
    (general-def 'normal mastodon-mode-map [?\M-\t] #'mastodon-tl--previous-tab-item)
    ;; Navigating to other buffers:
    (general-def 'normal mastodon-mode-map "N" #'mastodon-notifications--get)
    (general-def 'normal mastodon-mode-map "A" #'mastodon-profile--get-toot-author)
    (general-def 'normal mastodon-mode-map "U" #'mastodon-profile--show-user)
    (general-def 'normal mastodon-mode-map "F" #'mastodon-tl--get-federated-timeline)
    (general-def 'normal mastodon-mode-map "H" #'mastodon-tl--get-home-timeline)
    (general-def 'normal mastodon-mode-map "L" #'mastodon-tl--get-local-timeline)
    (general-def 'normal mastodon-mode-map "t" #'mastodon-tl--thread)
    (general-def 'normal mastodon-mode-map "T" #'mastodon-tl--get-tag-timeline)
    (general-def 'normal mastodon-mode-map "q" #'kill-this-buffer)
    (general-def 'normal mastodon-mode-map "Q" #'kill-buffer-and-window)
    ;; Actions
    (general-def 'normal mastodon-mode-map "c" #'mastodon-tl--toggle-spoiler-text-in-toot)
    (general-def 'normal mastodon-mode-map "g" #'undefined) ;; override special mode binding
    (general-def 'normal mastodon-mode-map "n" #'mastodon-toot)
    (general-def 'normal mastodon-mode-map "r" #'mastodon-toot--reply)
    (general-def 'normal mastodon-mode-map "u" #'mastodon-tl--update)
    (general-def 'normal mastodon-mode-map "b" #'mastodon-toot--toggle-boost)
    (general-def 'normal mastodon-mode-map "f" #'mastodon-toot--toggle-favourite)
    (general-def 'normal mastodon-mode-map "?" #'makey-key-mode-popup-mastodon)
    (general-def 'normal mastodon-profile-mode-map "F" #'mastodon-profile--open-followers)
    (general-def 'normal mastodon-profile-mode-map "f" #'mastodon-profile--open-following))

  (eval-and-compile
    (defun mastodon-load-path ()
      (expand-file-name "~/mastodon.el/lisp")))

  (if (file-exists-p (mastodon-load-path))
      (use-package mastodon
	:load-path (lambda () (mastodon-load-path))
	:init (setq mastodon-instance-url "https://mastodon.technology")
	:config
	(jdormit/mastodon-setup)
	:commands
	(mastodon
	 mastodon-toot))
    (use-package mastodon
      :init (setq mastodon-instance-url "https://mastodon.technology")
      :config
      (jdormit/mastodon-setup)
      :commands
      (mastodon
       mastodon-toot)))

  (jdormit/define-prefix "aM" "mastodon")
  (leader-def-key "aMm" 'mastodon)
  (leader-def-key "aMt" 'mastodon-toot)

Calc

  (leader-def-key "ac" 'calc)

Deadgrep

A nice Emacs UI over ripgrep.

  (use-package deadgrep
    :commands deadgrep)

  (leader-def-key "fg" 'deadgrep)

  (add-to-list 'evil-emacs-state-modes 'deadgrep-mode)

RCIRC

IRC in Emacs, just in case anyone actually still uses it…

Channels:

  (with-eval-after-load 'rcirc
    (setq rcirc-server-alist
	  `(("znc.jeremydormitzer.com"
	     :port 3000
	     :nick "jdormit"
	     :user-name "jdormit"
	     :password ,(password-store-get "znc.jeremydormitzer.com")))))

Key bindings:

  (leader-def-key "ai" 'irc)

Use evil keybindings by default:

  (add-to-list 'evil-normal-state-modes 'rcirc-mode)

dumb-jump

Dumb-jump uses ripgrep and some algorithms based on the major-mode to implement jump-to-definition.

  (use-package dumb-jump
    :config (dumb-jump-mode))

  (jdormit/define-prefix "c" "code")
  (leader-def-key "cj" 'dumb-jump-go)
  (leader-def-key "cp" 'dumb-jump-go-prompt)

Dictionary

This package looks up word definitions online.

  (use-package define-word
    :commands (define-word define-word-at-point))

  (jdormit/define-prefix "r" "research")
  (leader-def-key "rd" 'define-word-at-point)
  (leader-def-key "rD" 'define-word)

Gnus

An ancient newsreader.

  (leader-def-key "ag" 'gnus)
  (general-def gnus-mode-map "SPC" leader-map)

It can read email:

  (setq gnus-select-method '(nnnil "")
	gnus-secondary-select-methods
	'((nnmaildir "jeremy@dormitzer.net"
		     (directory "~/.mail/jeremy-dormitzer-net"))    
	  (nnmaildir "jeremy@getpterotype.com"
		     (directory "~/.mail/jeremy-getpterotype-com"))
	  (nnmaildir "jeremy.dormitzer@gmail.com"
		     (directory "~/.mail/jeremy-dormitzer-gmail-com"))  
	  (nnmaildir "jdormitzer@hubspot.com"
		     (directory "~/.mail/jdormitzer-hubspot-com")))
	mm-text-html-renderer 'gnus-w3m)

Or Gnus can read RSS feeds directly:

  ;; (add-to-list 'gnus-secondary-select-methods
  ;;        '(nnrss "http://rss.nytimes.com/services/xml/rss/nyt/HomePage.xml"))

Dired

Enable Dired-X:

  (require 'dired-x)

Preserve the leader key:

  (general-def dired-mode-map "SPC" leader-map)

Crontab

Magit ships with a cool utility called with-editor that lets you run a shell command using the current Emacs instance as $EDITOR. This means we can define a command to edit the crontab with the current Emacs instance:

  (defun edit-crontab ()
    (interactive)
    (with-editor-async-shell-command "crontab -e"))

Emacs Server

In case I need an emacsclient for some reason.

  (add-hook 'after-init-hook #'server-start)

YASnippet

YASnippet is Yet Another Snippet template system.

  (use-package yasnippet
    :config
    (unless (file-exists-p (expand-file-name "~/.emacs.d/snippets"))
      (mkdir (expand-file-name "~/.emacs.d/snippets") t))
    (setq yas-snippet-dirs
     `(,(concat (file-name-as-directory (get-dropbox-directory)) "yasnippet")
       ,(expand-file-name "~/.emacs.d/snippets")))
    (yas-global-mode))

  (use-package yasnippet-snippets
    :after (yasnippet)
    :config (yasnippet-snippets-initialize))

mpc

An Emacs interface to MPD, the Music Player Daemon

  (require 'mpc)
  (leader-def-key "ad" #'mpc)
  (add-to-list 'evil-emacs-state-modes 'mpc-mode)
  (general-def mpc-mode-map "SPC" leader-map)
  (general-def mpc-mode-map "a" #'mpc-playlist-add)

Ido

Interactively do things! Some more info in this blog post.

  (setq ido-enable-flex-matching t)
  (setq ido-everywhere t)
  (ido-mode 1)

Enable it everywhere:

  (use-package ido-completing-read+
    :config
    (ido-ubiquitous-mode 1))

  (use-package crm-custom
    :config
    (crm-custom-mode 1))

The horizontal completion is ugly. Make it vertical:

  (use-package ido-vertical-mode
    :config
    (ido-vertical-mode 1)
    (setq ido-vertical-define-keys 'C-n-and-C-p-only))

The default auto-merge time is too short.

  (setq ido-auto-merge-delay-time 1.5)

graphviz

  (use-package graphviz-dot-mode
    :mode (("\\.dot\\'" . graphviz-dot))
    :init
    (add-to-list 'org-src-lang-modes '("dot" . graphviz-dot)))

Functions

A function that converts a lisp form into Graphviz format, e.g.:

  '(a ((label . "Node A"))
      (b ((label . "Node B"))
	 (d ((label . "Node D"))))
      (c ((label . "Node C"))
	 (e ((label . "Node E")))))

becomes:

  digraph {
	  a[label="Node A"];
	  b[label="Node B"];
	  c[label="Node C"];
	  d[label="Node D"];
	  e[label="Node E"];
	  a -> {b, c};
	  b -> d;
	  c -> e;
  }
  (defun graphviz-make-node-string (id attrs)
    "Makes a Graphviz Dot string representing a node with attributes"
    (if attrs
	(format
	 "%s[%s];"
	 id
	 (mapconcat 'identity
		    (mapcar
		     (lambda (attr)
		       (format "%s=\"%s\"" (car attr) (cdr attr)))
		     attrs)
		    ", "))
      (format "%s;" id)))

  (defun graphviz-make-edge-string (id children)
    "Makes a Graphviz Dot string representing the edges between id and children"
    (when children
      (format "%s -> {%s}"
	      id
	      (mapconcat
	       'identity
	       (mapcar
		(lambda (child)
		  (format "%s" (car child)))
		children)
	       "; ")))) 


  (defun graphviz-parse-graph (graph)
    "Parses a graph into nodes and edges represented in the dot language. 
       Returns an alist ((nodes (<node strings>)) (edges (<edge strings>)))"
    (when graph
      (let* ((id (car graph))
	     (attrs (cadr graph))
	     (children (cddr graph))
	     (child-graphs (mapcar #'graphviz-parse-graph children)))
	`((nodes ,(cons (graphviz-make-node-string id attrs)
			(apply #'append
			       (mapcar (lambda (child)
					 (cadr (assoc 'nodes child)))
				       child-graphs))))
	  (edges ,(let ((edge-string (graphviz-make-edge-string id children))
			(child-edges (apply #'append
					    (mapcar (lambda (child)
						      (cadr (assoc 'edges child)))
						    child-graphs))))
		    (if edge-string
			(cons edge-string child-edges)
		      child-edges)))))))

  (defun graphviz-compile-graph (graph)
    "Transpiles a graph defined as a lisp form to the Graphviz Dot language"
    (let* ((graph (graphviz-parse-graph graph))
	   (nodes (cadr (assoc 'nodes graph)))
	   (edges (cadr (assoc 'edges graph))))
      (message "%s" graph)
      (format "digraph {\n        %s\n        %s\n}"
	      (mapconcat 'identity nodes "\n        ")
	      (mapconcat 'identity edges "\n        "))))

HideShow

hs-minor-mode enables comment and code-folding. It's useful almost everywhere, so just enable it:

  (add-hook 'prog-mode-hook (lambda () (hs-minor-mode 1)))

Slack

  (use-package slack
    :commands (slack-start)
    :init
    (setq slack-buffer-emojify nil)
    (setq slack-prefer-current-team t)
    :config
    (slack-register-team
     :name "hubspot"
     :default t
     :client-id "2152023175.242841693617"
     :client-secret "5a753800968d3a9727ccb83b470db696"
     :token "xoxp-2152023175-199371301809-242880891761-652436eeff14e6e9d083a131ed0eedb4"
     :subscribed-channels '(automation-platform
			    support-workflow
			    workflow-extensions
			    workflows
			    workflows-alerts
			    workflows-backend
			    workflows-yt)               
     :full-and-display-names t)
    :general
    ('normal slack-info-mode-map ",u" #'slack-room-update-messages)
    ('normal slack-mode-map
	     ",c" 'slack-buffer-kill
	     ",ra" 'slack-message-add-reaction
	     ",rr" 'slack-message-remove-reaction
	     ",rs" 'slack-message-show-reaction-users
	     ",pl" 'slack-room-pins-list
	     ",pa" 'slack-message-pins-add
	     ",pr" 'slack-message-pins-remove
	     ",mm" 'slack-message-write-another-buffer
	     ",me" 'slack-message-edit
	     ",md" 'slack-message-delete
	     ",u" 'slack-room-update-messages
	     ",2" 'slack-message-embed-mention
	     ",3" 'slack-message-embed-channel
	     "\C-n" 'slack-buffer-goto-next-message
	     "\C-p" 'slack-buffer-goto-prev-message) 
    ('normal slack-edit-message-mode-map
	     ",k" #'slack-message-cancel-edit
	     ",s" #'slack-message-send-from-buffer
	     ",2" #'slack-message-embed-mention
	     ",3" #'slack-message-embed-channel))
  (use-package alert
    :commands (alert)
    :init
    (setq alert-default-style (jdormit-get-mu4e-alert-style)))

Matrix

  (use-package matrix-client
    :quelpa ((matrix-client :fetcher github :repo "alphapapa/matrix-client.el"
			     :files (:defaults "logo.png" "matrix-client-standalone.el.sh"))))

EMMS

The Emacs Multi-Media System. For libtag to work, libtag must be installed on the system via the system package manager. Then Emms should be installed from source via:

  git clone git://git.sv.gnu.org/emms.git
  cd emms
  make emms-print-metadata
  make
  make install
  (add-to-list 'load-path "/usr/local/share/emacs/site-lisp/emms")
  (when (require 'emms-setup nil t)
    (emms-all)
    (emms-default-players)
    (require 'emms-info-libtag)
    (setq emms-info-functions
	  '(emms-info-libtag)
	  emms-source-file-default-directory
	  (concat (get-dropbox-directory) "/music"))
    (jdormit/define-prefix "ae" "emms")
    (add-to-list 'evil-emacs-state-modes 'emms-browser-mode)
    (general-def emms-browser-mode-map "," leader-map)
    (general-def emms-playlist-mode-map "SPC" leader-map)
    (general-def emms-playlist-mode-map "," leader-map)
    (leader-def-key "aeb" 'emms-smart-browse)
    (leader-def-key "aes" 'emms-start)
    (leader-def-key "aeS" 'emms-stop)
    (leader-def-key "aeP" 'emms-pause)
    (leader-def-key "aen" 'emms-next)
    (leader-def-key "aep" 'emms-previous)
    (leader-def-key "aee" 'emms)
    (leader-def-key "aed" 'emms-play-directory-tree)
    (when (eq system-type 'darwin)
      (define-emms-simple-player afplay '(file)
	(regexp-opt '(".mp3" ".m4a" ".aac"))
	"afplay")
      (setq emms-player-list `(,emms-player-afplay))))

Direnv

Direnv automatically runs bash scripts when you enter certain directories. This sets it up to work with Emacs:

  (use-package direnv
    :if (executable-find "direnv")
    :config (direnv-mode)
    (add-hook 'eshell-directory-change-hook #'direnv-update-directory-environment))

SQL

Emacs has excellent built-in SQL support.

  (leader-def-key "sp" #'sql-postgres)