commit cc18ffe2863c97ce1b4e7612102a268d7e5be5f9 Author: Jeremy Dormitzer Date: Sun Jan 27 21:46:29 2019 -0500 Initial commit diff --git a/bash/.bash_logout b/bash/.bash_logout new file mode 100644 index 0000000..0e4e4f1 --- /dev/null +++ b/bash/.bash_logout @@ -0,0 +1,3 @@ +# +# ~/.bash_logout +# diff --git a/bash/.bash_profile b/bash/.bash_profile new file mode 100644 index 0000000..c5e5ab3 --- /dev/null +++ b/bash/.bash_profile @@ -0,0 +1,8 @@ +# +# ~/.bash_profile +# + +[[ -f ~/.bashrc ]] && . ~/.bashrc + +export LEDGER_FILE="~/journal.ledger" +PATH="$PATH":"/home/jdormit/.local/bin":"/home/jdormit/bin/":"/home/jdormit/.gem/ruby/2.5.0/bin":"/home/jdormit/go/bin" diff --git a/bash/.bashrc b/bash/.bashrc new file mode 100644 index 0000000..95df7f7 --- /dev/null +++ b/bash/.bashrc @@ -0,0 +1,11 @@ +# +# ~/.bashrc +# + +# If not running interactively, don't do anything +[[ $- != *i* ]] && return + +alias ls='ls --color=auto' +PS1='[\u@\h \W]\$ ' + +source /usr/share/nvm/init-nvm.sh diff --git a/configure-stow.sh b/configure-stow.sh new file mode 100755 index 0000000..d180dd7 --- /dev/null +++ b/configure-stow.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +echo "--target=$HOME +--dir=$(dirname $(readlink -f $0))" > ~/.stowrc diff --git a/emacs/init.org b/emacs/init.org new file mode 100755 index 0000000..cdcd39b --- /dev/null +++ b/emacs/init.org @@ -0,0 +1,2381 @@ +#+PROPERTY: header-args :results silent + +This init file is based on [[https://medium.com/@CBowdon/pinching-the-best-bits-from-spacemacs-869b8c793ad3][this blog post]]. + +It's meant to be loaded from init.el like so: +#+BEGIN_SRC emacs-lisp :tangle no + (require 'org) + (org-babel-load-file (expand-file-name "path/to/init.org")) +#+END_SRC + +* Packages +Set up package.el to load from ELPA and MELPA +#+BEGIN_SRC emacs-lisp + (require 'package) + (setq package-archives + '(("gnu" . "https://elpa.gnu.org/packages/") + ("melpa" . "https://melpa.org/packages/"))) + (package-initialize) +#+END_SRC + +`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. +#+BEGIN_SRC emacs-lisp + (unless (package-installed-p 'use-package) + (package-refresh-contents) + (package-install 'use-package)) + (require 'use-package) + (setq use-package-always-ensure t) +#+END_SRC + +[[https://framagit.org/steckerhalter/quelpa][Quelpa]] extends package.el to build packages from source from a bunch of targets (git, hg, etc.). +#+BEGIN_SRC emacs-lisp + (use-package quelpa) + (use-package quelpa-use-package) +#+END_SRC + +* 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. +#+BEGIN_SRC emacs-lisp + (use-package benchmark-init + :config + (add-hook 'after-init-hook 'benchmark-init/deactivate)) +#+END_SRC + +* 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. +#+BEGIN_SRC emacs-lisp + (use-package exec-path-from-shell + :if (memq window-system '(mac ns x)) + :config + (setq exec-path-from-shell-variables '("PATH" "MANPATH" "LEDGER_FILE")) + (exec-path-from-shell-initialize)) +#+END_SRC + +* Autocompletion +There seems to be [[https://github.com/company-mode/company-mode/issues/68][some contention]] about whether autocomplete or company are better autocomplete packages. I'm going with company for now because the maintainer seems nicer... +#+BEGIN_SRC emacs-lisp + (use-package company + :config + (setq company-idle-delay 0.3) + (add-hook 'after-init-hook #'global-company-mode)) +#+END_SRC + +* General +Better keybinding. +#+BEGIN_SRC emacs-lisp + (use-package general) +#+END_SRC + +* Which-key +`which-key` makes keybindings discoverable. +#+BEGIN_SRC emacs-lisp + (use-package which-key + :config + (which-key-mode)) +#+END_SRC + +This function defines a prefix group for `which-key` so that it doesn't display `prefix`. +#+BEGIN_SRC emacs-lisp + (defun jdormit/define-prefix (binding name) + (which-key-add-key-based-replacements + (concat leader " " binding) + name)) +#+END_SRC + +* Evil Mode +Because I like modal editing and dislike RSI. +#+BEGIN_SRC emacs-lisp + (use-package evil + :config + (evil-mode 1)) +#+END_SRC + +Use the spacebar as a leader key in evil-mode's normal state and in various other modes: +#+BEGIN_SRC emacs-lisp + (defconst leader "SPC") + (general-define-key + :states 'motion + "SPC" nil) + (general-create-definer leader-def-key + :states 'motion + :prefix leader + :prefix-map 'leader-map) +#+END_SRC + +#+BEGIN_SRC emacs-lisp + (jdormit/define-prefix "?" "help") + (leader-def-key "?" help-map) +#+END_SRC + +Make undo not undo paragraphs at a time: +#+BEGIN_SRC emacs-lisp + (setq evil-want-fine-undo t) +#+END_SRC + +* Password Store +Interfacing with Pass, the "standard Unix password manager". This should also be loaded before `exec-path-from-shell`. +#+BEGIN_SRC emacs-lisp + (use-package password-store + :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 + :commands pass + :general + (pass-mode-map "SPC" leader-map)) + + (leader-def-key "ap" 'pass) + + (defun password-store-synchronize () + (interactive) + (async-shell-command "pass git pull && pass git push")) +#+END_SRC + +* Emacs Lisp +Requires: +#+BEGIN_SRC emacs-lisp + (eval-when-compile (require 'subr-x)) +#+END_SRC + +Default to lexical binding: +#+BEGIN_SRC emacs-lisp + (setq lexical-binding t) +#+END_SRC + +** Load path +For machine or user specific libraries: +#+BEGIN_SRC emacs-lisp + (add-to-list 'load-path (expand-file-name "~/site-lisp")) +#+END_SRC + +And for global ones: +#+BEGIN_SRC emacs-lisp + (add-to-list 'load-path "/usr/local/share/emacs/site-lisp") +#+END_SRC + +** Utilities +Reading a file as a string: +#+BEGIN_SRC emacs-lisp + (defun read-file (path) + "Returns the contents of the file as a string" + (with-temp-buffer + (insert-file-contents path) + (buffer-string))) +#+END_SRC + +Opening a file as sudo: +#+BEGIN_SRC emacs-lisp + (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))) + +#+END_SRC + +Recursive =assoc= for nested alists: +#+BEGIN_SRC emacs-lisp + (defun assoc-recursive (alist &rest keys) + "Recursively find KEYs in ALIST." + (while keys + (setq alist (cdr (assoc (pop keys) alist)))) + alist) +#+END_SRC + +Format a millis timestamp into human-readable form: +#+BEGIN_SRC emacs-lisp + (defun format-epoch-millis (millis) + (interactive "nTimestamp: ") + (message (format-time-string "%x %X" (/ millis 1000)))) +#+END_SRC + +The same but for seconds: +#+BEGIN_SRC emacs-lisp + (defun format-epoch-seconds (seconds) + (interactive "nTimestamp: ") + (message (format-time-string "%x %X" seconds))) +#+END_SRC + +** Persisting variables between session +The idea behind this is pretty simple - variables get persisted in ~/.emacs.d/ as a plist of (variable-name variable-value). + +#+BEGIN_SRC emacs-lisp + (defvar persisted-vars-file "~/.emacs.d/persisted-vars") +#+END_SRC + +This function retrieves the plist of persisted variables or nil if it doesn't exist: +#+BEGIN_SRC emacs-lisp + (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))))))) +#+END_SRC + +This function retrieves a persisted variable: +#+BEGIN_SRC emacs-lisp + (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)))) +#+END_SRC + +And this function persists a variable: +#+BEGIN_SRC emacs-lisp + (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))))) +#+END_SRC + +* 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: +#+BEGIN_SRC emacs-lisp + (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)")))) + +#+END_SRC + +Load up libraries from Dropbox, if there are any: +#+BEGIN_SRC emacs-lisp + (add-to-list 'load-path + (concat + (file-name-as-directory (get-dropbox-directory)) + "site-lisp")) +#+END_SRC + +* Init File +A function to reload my init file. It reloads the major mode after the init file is loaded to rebind keymappings. +#+BEGIN_SRC emacs-lisp + (defun reload-init-file () + (interactive) + (load-file "~/.emacs.d/init.el") + (funcall major-mode)) +#+END_SRC + +And another one to edit it: +#+BEGIN_SRC emacs-lisp + (defun find-init-file () + (interactive) + (find-file "~/init.org")) +#+END_SRC + +* 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: +#+BEGIN_SRC emacs-lisp + (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) +#+END_SRC + +And in some modes I want to preserve the spacebar as a leader key: +#+BEGIN_SRC emacs-lisp + (general-def picture-mode-map "SPC" leader-map) + (general-def 'motion Info-mode-map "SPC" leader-map) +#+END_SRC + +** Visual line navigation +#+BEGIN_SRC emacs-lisp + (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) +#+END_SRC + +** M-x +#+BEGIN_SRC emacs-lisp + (leader-def-key "SPC" 'execute-extended-command) +#+END_SRC + +** Eval-ing +#+BEGIN_SRC emacs-lisp + (leader-def-key "e" #'eval-expression) +#+END_SRC + +** Init file commands +#+BEGIN_SRC emacs-lisp + (jdormit/define-prefix "." "dotfile") + (leader-def-key ".r" 'reload-init-file) + (leader-def-key ".f" 'find-init-file) +#+END_SRC + +** Commands about files +#+BEGIN_SRC emacs-lisp + (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) +#+END_SRC + +** Window commands +#+BEGIN_SRC emacs-lisp + (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) +#+END_SRC + +** Buffer commands +A function to switch to previous buffer from [[http://emacsredux.com/blog/2013/04/28/switch-to-previous-buffer/][this blog post]]: +#+BEGIN_SRC emacs-lisp + (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) +#+END_SRC + +A function to kill all buffers except the current one from [[https://www.emacswiki.org/emacs/KillingBuffers#toc2][EmacsWiki]]: +#+BEGIN_SRC emacs-lisp + (defun kill-other-buffers () + "Kill all other buffers." + (interactive) + (mapc 'kill-buffer (delq (current-buffer) (buffer-list)))) +#+END_SRC + +#+BEGIN_SRC emacs-lisp + (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) +#+END_SRC +** Frame commands +#+BEGIN_SRC emacs-lisp + (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) +#+END_SRC + +** Running shell commands +#+BEGIN_SRC emacs-lisp + (leader-def-key "!" 'shell-command) + (leader-def-key "|" 'shell-command-on-region) +#+END_SRC + +** Toggles +Like in Spacemacs, put all toggle commands behind a prefix: +#+BEGIN_SRC emacs-lisp + (jdormit/define-prefix "t" "toggle") +#+END_SRC + +Toggles about line truncation: +#+BEGIN_SRC emacs-lisp + (leader-def-key "tt" 'toggle-truncate-lines) + (leader-def-key "tT" 'visual-line-mode) +#+END_SRC + +Toggle lisp debugging: +#+BEGIN_SRC emacs-lisp + (leader-def-key "td" 'toggle-debug-on-error) +#+END_SRC + +** Shells/REPLs +Emacs has a shell for every mood! +#+BEGIN_SRC emacs-lisp + (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) +#+END_SRC + +** Applications +#+BEGIN_SRC emacs-lisp + (jdormit/define-prefix "a" "applications") +#+END_SRC + +* Line Numbers +Toggle line numbers: +#+BEGIN_SRC emacs-lisp + (setq display-line-numbers-type 'visual) + (leader-def-key "tn" 'display-line-numbers-mode) +#+END_SRC + +Toggle line numbering mode (normal or relative): +#+BEGIN_SRC emacs-lisp + (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) +#+END_SRC + +Display line numbers by default in code and org-mode buffers: +#+BEGIN_SRC emacs-lisp + (add-hook 'prog-mode-hook #'display-line-numbers-mode) + (add-hook 'org-mode-hook #'display-line-numbers-mode) +#+END_SRC + +* Amx +A better M-x. +#+BEGIN_SRC emacs-lisp + (use-package amx + :config + (amx-mode)) +#+END_SRC + +* Olivetti Mode +Olivetti is a minor mode for a nice writing environment. +#+BEGIN_SRC emacs-lisp + (use-package olivetti + :config + (setq-default olivetti-body-width 100) + (setq olivetti-body-width 100) + :commands olivetti-mode) + + (leader-def-key "to" 'olivetti-mode) +#+END_SRC + +* Winum +This package includes functions to switch windows by number. +#+BEGIN_SRC emacs-lisp + (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)) +#+END_SRC + +I don't want which-key display "lambda" for the descriptions of these, so set a custom display function. This is [[https://github.com/syl20bnr/spacemacs/blob/master/layers/+distributions/spacemacs-bootstrap/packages.el#L312][stolen from Spacemacs]]. +#+BEGIN_SRC emacs-lisp + (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) +#+END_SRC + +* NeoTree +A package to browse files in a tree view +#+BEGIN_SRC emacs-lisp + (use-package neotree + :commands neotree-toggle + :init + (leader-def-key "d" 'neotree-toggle) + :general + (neotree-mode-map "SPC" leader-map)) +#+END_SRC + +* Backups and Autosaves +Store backups and autosaves in a centralized place. This should really be the default... +#+BEGIN_SRC emacs-lisp + (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"))) +#+END_SRC + +* Paredit/Parinfer +Paredit enables structured editing of s-expressions +#+BEGIN_SRC emacs-lisp + (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) +#+END_SRC + +Parinfer infers parens from indentation and vice-versa: +#+BEGIN_SRC emacs-lisp + (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))) +#+END_SRC + +* Org Mode +Notes, agenda, calendar, blogging, journaling, etc. + +#+BEGIN_SRC emacs-lisp + (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) +#+END_SRC + +Use RET to follow links: +#+BEGIN_SRC emacs-lisp + (setq org-return-follows-link t) +#+END_SRC + +Always show inline images: +#+BEGIN_SRC emacs-lisp + (add-hook 'org-mode-hook + (lambda () + (org-display-inline-images nil t) + (org-redisplay-inline-images))) +#+END_SRC + +** Agenda files +#+BEGIN_SRC emacs-lisp + (setq org-agenda-files '("~/org")) +#+END_SRC + +** Capture templates +#+BEGIN_SRC emacs-lisp + (setq org-capture-templates + '(("t" "Task" entry + (file "~/org/todo.org") + "* TODO %i%?") + ("b" "Backlog task" entry + (file "~/org/backlog.org") + "* TODO %i%?") + ("n" "Note" entry + (file "~/org/notes.org") + "* %^{Description}\n%i%?") + ("j" "Journal entry" entry + (file "~/org/journal.org") + "* %<%Y-%m-%d %H:%M:%S>%?") + ("p" "Project" entry + (file "~/org/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))) +#+END_SRC + +** Refile targets +#+BEGIN_SRC emacs-lisp + (setq org-refile-use-outline-path 'file + org-refile-targets '(("~/org/todo.org" :level . 0) + ("~/org/backlog.org" :level . 0))) +#+END_SRC + +** Todo keywords +#+BEGIN_SRC emacs-lisp + (setq org-todo-keywords + '((sequence "TODO(t)" "WAITING(w)" "|" "DONE(d)" "CANCELLED(c)"))) +#+END_SRC + +** Agenda views +#+BEGIN_SRC emacs-lisp + (setq org-agenda-custom-commands + '(("t" "Todo list" + ((agenda) + (todo "TODO" ((org-agenda-files '("~/org/todo.org" + "~/org/scheduled.org")))))) + ("b" "backlog" + ((agenda) + (todo "TODO" ((org-agenda-files '("~/org/todo.org" + "~/org/backlog.org" + "~/org/scheduled.org")))))) + ("a" "all" + ((agenda) + (todo "TODO" ((org-agenda-files '("~/org")))))))) +#+END_SRC + +** Keybindings +#+BEGIN_SRC emacs-lisp + (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 "" #'org-return) + (general-def org-mode-map "C-c e" #'org-preview-latex-fragment) + (general-def "C-c l" #'org-store-link) +#+END_SRC + +Enable using the leader key in the agenda view: +#+BEGIN_SRC emacs-lisp + (general-def org-agenda-mode-map "SPC" leader-map) +#+END_SRC + +** Exporting +*** HTML +Export to HTML: +#+BEGIN_SRC emacs-lisp + (use-package htmlize + :commands htmlize-buffer) +#+END_SRC + +Don't put section numbers in front of headers: +#+BEGIN_SRC emacs-lisp + (setq org-export-with-section-numbers nil) +#+END_SRC + +Disable the preamble and postamble: +#+BEGIN_SRC emacs-lisp + (setq org-html-preamble nil + org-html-postamble nil) +#+END_SRC + +Redefine org-html-src-block to wrap code blocks in
 and language class for use by highlight.js:
+#+BEGIN_SRC emacs-lisp
+  (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 "
\n%s
" label code) + (format "
\n%s%s\n
" + ;; Build caption. + (let ((caption (org-export-get-caption src-block))) + (if (not caption) "" + (let ((listing-number + (format + "%s " + (format + (org-html--translate "Listing %d:" info) + (org-export-get-ordinal + src-block info nil #'org-html--has-caption-p))))) + (format "" + listing-number + (org-trim (org-export-data caption info)))))) + ;; Contents. + (if klipsify + (format "
%s
" + lang + label + (if (string= lang "html") + " data-editor-type=\"html\"" + "") + code) + (format "
%s
" + lang lang label code))))))) +#+END_SRC + +*** Markdown +#+BEGIN_SRC emacs-lisp + (eval-after-load "org" + '(require 'ox-md nil t)) +#+END_SRC + +** org-babel +Literate programming! +#+BEGIN_SRC emacs-lisp + (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))))) +#+END_SRC + +Get rid of the confirmation prompt: +#+BEGIN_SRC emacs-lisp + (setq org-confirm-babel-evaluate nil) +#+END_SRC + +Display inline images after executing a source block: +#+BEGIN_SRC emacs-lisp + (add-hook 'org-babel-after-execute-hook + (lambda () + (org-display-inline-images nil t) + (org-redisplay-inline-images))) +#+END_SRC + +* Projectile +#+BEGIN_SRC emacs-lisp + (use-package projectile + :config + (projectile-mode) + (jdormit/define-prefix "fp" "projectile") + (leader-def-key "fpf" 'projectile-find-file) + (leader-def-key "fpg" 'projectile-grep)) +#+END_SRC + +* Mode line +A sexy mode line for maximum geek cred: +#+BEGIN_SRC emacs-lisp + (use-package smart-mode-line + :init + (sml/setup)) +#+END_SRC + +* UI +Get rid of the janky buttons: +#+BEGIN_SRC emacs-lisp + (tool-bar-mode -1) +#+END_SRC + +And the ugly scroll bars: +#+BEGIN_SRC emacs-lisp + (set-scroll-bar-mode nil) +#+END_SRC + +Use =variable-pitch-mode= in text modes: +#+BEGIN_SRC emacs-lisp + (add-hook 'text-mode-hook (lambda () (variable-pitch-mode))) + (add-hook 'w3m-mode-hook (lambda () (variable-pitch-mode))) +#+END_SRC + +Always use =buffer-face-mode= in code and text buffers: +#+BEGIN_SRC emacs-lisp + (add-hook 'prog-mode-hook #'buffer-face-mode) + (add-hook 'text-mode-hook #'buffer-face-mode) +#+END_SRC + +Load up some tasty solarized themes: +#+BEGIN_SRC emacs-lisp + (use-package solarized-theme) +#+END_SRC + +And also [[https://github.com/kunalb/poet][poet-theme]]: +#+BEGIN_SRC emacs-lisp + (use-package poet-theme) +#+END_SRC + +A function to load a font, including overriding any theme settings for org-mode headers: +#+BEGIN_SRC emacs-lisp + (defun jdormit/get-font-alist () + (let ((font-alist (get-persisted-var 'jdormit/font))) + (if font-alist + font-alist + (list)))) + + (defun jdormit/load-font (face font height) + (interactive + (list + (intern (completing-read "Change face: " (face-list))) + (completing-read "Load font: " (font-family-list)) + (read-minibuffer "Font height: " "120"))) + (if (not (number-or-marker-p height)) + (error "Height must be a number.") + (if (member font (font-family-list)) + (if (member face (face-list)) + (let ((font-alist + (assq-delete-all face (jdormit/get-font-alist)))) + (progn + (set-face-attribute face nil :family font :height height) + (persist-variable + 'jdormit/font + (cons `(,face (,font ,height)) font-alist)))) + (error "Face %s not found." (symbol-name face))) + (error "Font %s not found." font)))) + + (defun jdormit/load-persisted-fonts () + (let ((fonts (jdormit/get-font-alist))) + (dolist (font fonts) + (jdormit/load-font (car font) (car (cadr font)) (cadr (cadr font)))))) +#+END_SRC + +A function to load a theme then override font settings: +#+BEGIN_SRC emacs-lisp + (defun jdormit/load-theme (theme) + (interactive + (list (intern (completing-read "Load custom theme: " + (mapcar 'symbol-name + (custom-available-themes)))))) + (load-theme theme t) + (jdormit/load-persisted-fonts) + (persist-variable 'jdormit/theme theme)) +#+END_SRC + +UI-related keybindings: +#+BEGIN_SRC emacs-lisp + (jdormit/define-prefix "u" "UI") + (leader-def-key "ut" 'jdormit/load-theme) + (leader-def-key "uf" 'jdormit/load-font) +#+END_SRC + +Some defaults: +#+BEGIN_SRC emacs-lisp + (let ((default (assoc 'default (jdormit/get-font-alist))) + (fixed-pitch (assoc 'fixed-pitch (jdormit/get-font-alist))) + (variable-pitch (assoc 'variable-pitch (jdormit/get-font-alist))) + (theme (get-persisted-var 'jdormit/theme))) + (unless default (jdormit/load-font 'default "Courier" 120)) + (unless fixed-pitch (jdormit/load-font 'fixed-pitch "Courier" 115)) + (unless variable-pitch (jdormit/load-font 'variable-pitch "Palatino" 120)) + (unless theme (jdormit/load-theme 'poet))) +#+END_SRC + +Load up previously saved theme and font: +#+BEGIN_SRC emacs-lisp + (when-let ((theme (get-persisted-var 'jdormit/theme))) + (jdormit/load-theme theme)) + (jdormit/load-persisted-fonts) +#+END_SRC + +* Frame parameters +Remember the previous frame size if previously set: +#+BEGIN_SRC emacs-lisp + (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)) +#+END_SRC + +Default to reasonable value if not: +#+BEGIN_SRC emacs-lisp + (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))) +#+END_SRC + +Functions to change the frame size: +#+BEGIN_SRC emacs-lisp + (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)) +#+END_SRC + +Keybindings: +#+BEGIN_SRC emacs-lisp + (leader-def-key "Fw" 'jdormit/set-frame-width) + (leader-def-key "Fh" 'jdormit/set-frame-height) + (leader-def-key "Fs" 'jdormit/set-frame-size) +#+END_SRC + +* EShell +Easy keybinding to open EShell: +#+BEGIN_SRC emacs-lisp + (leader-def-key "'" 'eshell) +#+END_SRC + +Make EShell's tab completion work like Bash's: +#+BEGIN_SRC emacs-lisp + (setq eshell-cmpl-cycle-completions nil) +#+END_SRC + +Destroy shell buffers created by eshell when the process dies:: +#+BEGIN_SRC emacs-lisp + (setq eshell-destroy-buffer-when-process-dies t) +#+END_SRC + +Visual programs: +#+BEGIN_SRC emacs-lisp + (defun eshell-setup () + (add-to-list 'eshell-visual-commands "crawl") + (add-to-list 'eshell-visual-commands "ssh")) + + (add-hook 'eshell-mode-hook #'eshell-setup) +#+END_SRC + +Some aliases: +#+BEGIN_SRC emacs-lisp + (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 $*"))) +#+END_SRC + +* JavaScript +#+BEGIN_SRC emacs-lisp + (use-package json-mode + :mode (("\\.json\\'" . json-mode))) +#+END_SRC + +* Java +LSP Java uses the Eclipse JDT Language Server as a backend to enable Java IDE features. +#+BEGIN_SRC emacs-lisp + (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)) +#+END_SRC + +Configure Java project sources: +#+BEGIN_SRC emacs-lisp + (setq lsp-java--workspace-folders + (list (expand-file-name "~/src/Automation") + (expand-file-name "~/src/AutomationSharedExecution") + (expand-file-name "~/src/AutomationPlatform"))) +#+END_SRC + +* Python +Elpy is a python IDE package: +#+BEGIN_SRC emacs-lisp + (use-package elpy + :config (elpy-enable)) +#+END_SRC + +Pipenv is the Python standard dependency management/virtual environment tool. pipenv.el teaches Emacs its ways: +#+BEGIN_SRC emacs-lisp + (use-package pipenv + :hook (python-mode . pipenv-mode) + :commands (pipenv-mode + pipenv-activate + pipenv-run)) +#+END_SRC + +A function to run a pipenv-aware python repl: +#+BEGIN_SRC emacs-lisp + (defun run-pipenv () + "Runs a pipenv-aware Python shell" + (interactive) + (pipenv-activate) + (run-python nil nil t)) +#+END_SRC + +* Go +Basic support: +#+BEGIN_SRC emacs-lisp + (use-package go-mode + :mode (("\\.go\\'" . go-mode))) +#+END_SRC + +Add in autocompletion. This requires [[https://github.com/mdempsky/gocode][gocode]] to be installed: +#+BEGIN_SRC emacs-lisp + (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)))) + +#+END_SRC + +* Clojure +Start with clojure-mode: +#+BEGIN_SRC emacs-lisp + (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))) +#+END_SRC + +Sprinkle in some CIDER: +#+BEGIN_SRC emacs-lisp + (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) +#+END_SRC + +* Scheme +Tell emacs about file extensions which should activate scheme-mode: +#+BEGIN_SRC emacs-lisp + (add-to-list 'auto-mode-alist '("\\.guile\\'" . scheme-mode)) + (add-to-list 'auto-mode-alist '("\\.rkt\\'" . scheme-mode)) +#+END_SRC +[[http://www.nongnu.org/geiser/geiser_1.html][Geiser]] is a Scheme IDE for Emacs that supports a bunch of common Scheme implementations. +#+BEGIN_SRC emacs-lisp + (use-package geiser + :general + (geiser-debug-mode-map "SPC" leader-map)) +#+END_SRC + +And a handy shortcut to hop into a Geiser REPL: +#+BEGIN_SRC emacs-lisp + (leader-def-key "sg" 'run-geiser) +#+END_SRC + +* Common Lisp +[[https://common-lisp.net/project/slime/][SLIME]] is a set of modes and utilities for writing Common Lisp in Emacs. [[https://www.quicklisp.org/beta/][Quicklisp]] is the de-facto Common Lisp package manager. It comes with some Emacs bindings. +#+BEGIN_SRC emacs-lisp + (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)) +#+END_SRC + +Keyboard shortcut to start a SLIME REPL: +#+BEGIN_SRC emacs-lisp + (leader-def-key "sc" 'slime) +#+END_SRC + +* Haskell +#+BEGIN_SRC emacs-lisp + (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) +#+END_SRC + +* PHP +#+BEGIN_SRC emacs-lisp + (use-package php-mode + :mode "\\.php\\'") + + (use-package mmm-mode) +#+END_SRC + +Geben is an interface to XDebug allowing debugging PHP in Emacs: +#+BEGIN_SRC emacs-lisp + (use-package geben + :commands (geben)) +#+END_SRC + +Some keybindings to start and end Geben: +#+BEGIN_SRC emacs-lisp + (general-def php-mode-map "C-c C-d" #'geben) + (general-def php-mode-map "C-c C-q" #'geben-end) +#+END_SRC + +An Eshell alias to start PHP using XDebug: +#+BEGIN_SRC emacs-lisp + (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 $*"))) +#+END_SRC + +Company-mode autocompletion for PHP: +#+BEGIN_SRC emacs-lisp + (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)))) + +#+END_SRC + +* Pharen +[[https://pharen.org][Pharen]] is a Lisp that compiles to PHP. It looks a lot like Clojure. +#+BEGIN_SRC emacs-lisp + (add-to-list 'auto-mode-alist '("\\.phn\\'" . clojure-mode)) +#+END_SRC + +* CSVs +#+BEGIN_SRC emacs-lisp + (use-package csv-mode + :mode "\\.csv\\'") +#+END_SRC + +* Markdown +#+BEGIN_SRC emacs-lisp + (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")) +#+END_SRC + +* IELM +Enable lexical scoping in IELM: +#+BEGIN_SRC emacs-lisp + (add-hook 'ielm-mode-hook + #'(lambda () + (interactive) + (setq lexical-binding t))) +#+END_SRC + +* Magit +Magit is objectively the best Git interface. +#+BEGIN_SRC emacs-lisp + (use-package magit + :commands magit-status + :general + (magit-mode-map "SPC" leader-map)) +#+END_SRC + +Enable evil keybindings: +#+BEGIN_SRC emacs-lisp + (use-package evil-magit + :hook ((magit-status-mode . evil-magit-init)) + :config + (require 'evil-magit)) +#+END_SRC + +#+BEGIN_SRC emacs-lisp + (jdormit/define-prefix "g" "git") + (leader-def-key "gs" 'magit-status) +#+END_SRC + +Use ido-mode for completion within Magit: +#+BEGIN_SRC emacs-lisp + (setq magit-completing-read-function 'magit-ido-completing-read) +#+END_SRC + +* Ledger Mode +This mode requires that [[https://github.com/ledger/ledger][ledger]] be installed on the system. +#+BEGIN_SRC emacs-lisp + (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)))) +#+END_SRC + +* PDFs +#+BEGIN_SRC emacs-lisp + (use-package pdf-tools + :mode ("\\.pdf\\'" . pdf-view-mode) + :config + (pdf-tools-install) + :general + (pdf-view-mode-map "SPC" leader-map)) +#+END_SRC + +* EPubs +#+BEGIN_SRC emacs-lisp + (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 "" 'mouse-face) + ('normal nov-mode-map "" 'nov-browse-url) + ('normal nov-mode-map "TAB" 'shr-next-link) + ('normal nov-mode-map "M-TAB" 'shr-previous-link) + ('normal nov-mode-map "" '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 "" 'beginning-of-buffer) + ('normal nov-mode-map "" 'end-of-buffer) + ('normal nov-mode-map "SPC" leader-map)) +#+END_SRC + +** Org mode links +First, keep a reference to filename of the .epub before nov.el blows it away: +#+BEGIN_SRC emacs-lisp + (defvar nov-epub-file) + (add-hook 'nov-mode-hook + #'(lambda () + (message "epub file: " buffer-file-name) + (setq nov-epub-file buffer-file-name))) +#+END_SRC + +That reference lets us construct a link back to the .epub: +#+BEGIN_SRC emacs-lisp + (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) +#+END_SRC + +* Dashboard +Instead of the *GNU Emacs* buffer on startup, display a cool dashboard: +#+BEGIN_SRC emacs-lisp + (use-package dashboard + :config + (setq dashboard-items '((recents . 5) + (projects . 5)) + dashboard-startup-banner 'official) + (dashboard-setup-startup-hook)) +#+END_SRC + +* Mu4e +Because email in Emacs is badass. My mail set up is based on [[http://stevelosh.com/blog/2012/10/the-homely-mutt/][this mutt setup]] and [[https://notanumber.io/2016-10-03/better-email-with-mu4e/][this mu4e setup]]. +#+BEGIN_SRC emacs-lisp + (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")) + `(,(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")))))) + mu4e-context-policy 'ask + mu4e-compose-context-policy 'ask-if-none)) + + (leader-def-key "am" 'mu4e) +#+END_SRC + +Custom actions: +#+BEGIN_SRC emacs-lisp + (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)) +#+END_SRC + +Make mu4e the default sendmail program: +#+BEGIN_SRC emacs-lisp + (setq mail-user-agent 'mu4e-user-agent) +#+END_SRC + +Use the spacebar as a leader key in mu4e modes: +#+BEGIN_SRC emacs-lisp + (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) +#+END_SRC + +** HTML email +*** Redefinitions +Redefine =org-mime-insert-html-content= to export the plain part of HTML emails as ascii instead of org: +#+BEGIN_SRC emacs-lisp + (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 "%s" + (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))))) +#+END_SRC + +And redefine =mu4e~compose-handler= to add a new hook: +#+BEGIN_SRC emacs-lisp + (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 :mime-type :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)))) +#+END_SRC + +*** Actual HTML mail logic +#+BEGIN_SRC emacs-lisp + (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) +#+END_SRC + +When citing (quoting) messages in a reply, wrap them in org quote blocks instead of prefixing each line with '> ': +#+BEGIN_SRC emacs-lisp + (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)) +#+END_SRC + +Some keybindings to send the current org buffer or subtree as an email: +#+BEGIN_SRC emacs-lisp + (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) +#+END_SRC + +* Mu4e-alert +Desktop notifications for mu4e emails. + +#+BEGIN_SRC emacs-lisp + (defun jdormit-get-mu4e-alert-style () + (if (memq window-system '(mac ns)) + 'notifier + 'libnotify)) + + (use-package mu4e-alert + :after (mu4e) + :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:/jeremy-getpterotype-com/Inbox")) + (mu4e-alert-set-default-style (jdormit-get-mu4e-alert-style)) + (mu4e-alert-enable-notifications) + (mu4e-alert-enable-mode-line-display)) +#+END_SRC + +* w3m +Browsing the web from Emacs. Relies on having [[http://w3m.sourceforge.net/][w3m]] installed. +#+BEGIN_SRC emacs-lisp + (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 "" 'w3m-next-anchor) + ('normal w3m-mode-map "" '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) +#+END_SRC + +I mostly want `browse-url-at-point` to open stuff in Firefox, but in some cases I want it within Emacs: + +#+BEGIN_SRC emacs-lisp + (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) +#+END_SRC + +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: +#+BEGIN_SRC emacs-lisp + (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)))))) +#+END_SRC + +And here are the websites where I want custom referers and/or no cookies: +#+BEGIN_SRC emacs-lisp + (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")) +#+END_SRC + +Render == and =
= 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=:
+#+BEGIN_SRC emacs-lisp
+  (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 "" 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  and 
 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)
+
+#+END_SRC
+
+Add a w3m filter to handle the code block delimiters:
+#+BEGIN_SRC emacs-lisp
+  ;; (with-eval-after-load 'w3m-filter
+  ;;   (add-to-list 'w3m-filter-configuration
+  ;;     '(t "Render code blocks with a different face" ".*" w3m-filter-code-blocks)))
+#+END_SRC
+
+* Wakatime
+[[https://wakatime.com/emacs][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.
+#+BEGIN_SRC emacs-lisp
+  (defvar wakatime-path
+    (if (file-exists-p "/usr/local/bin/wakatime")
+        "/usr/local/bin/wakatime"
+      (when (file-exists-p "/usr/bin/wakatime")
+        "/usr/bin/wakatime")))
+
+  (when wakatime-path
+    (let ((wakatime-key (password-store-get "wakatime-api-key")))
+      (use-package wakatime-mode
+        :init
+        (setq wakatime-api-key wakatime-key
+              wakatime-cli-path wakatime-path)
+        :config
+        (global-wakatime-mode))))
+#+END_SRC
+
+* Elfeed
+Elfeed is a feed reader for Emacs.
+#+BEGIN_SRC emacs-lisp
+  (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))))
+#+END_SRC
+
+Keybinding for opening Elfeed:
+#+BEGIN_SRC emacs-lisp
+  (leader-def-key "al" 'elfeed)
+#+END_SRC
+
+* Undo Tree
+#+BEGIN_SRC emacs-lisp
+  (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))
+#+END_SRC
+
+* Emojify
+Because emojis make everything better.
+
+#+BEGIN_SRC emacs-lisp
+  (use-package emojify)
+  (leader-def-key "te" 'emojify-mode)
+#+END_SRC
+
+* Mastodon
+[[https://joinmastodon.org/][Mastodon]] is a federated FOSS social network similar to Twitter. Let's put it in Emacs!
+
+First, install a dependency:
+#+BEGIN_SRC emacs-lisp
+  (use-package discover)
+#+END_SRC
+
+#+BEGIN_SRC emacs-lisp
+  (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)
+#+END_SRC
+
+* Calc
+#+BEGIN_SRC emacs-lisp
+  (leader-def-key "ac" 'calc)
+#+END_SRC
+
+* Deadgrep
+A nice Emacs UI over [[https://github.com/BurntSushi/ripgrep#installation][ripgrep]].
+#+BEGIN_SRC emacs-lisp
+  (use-package deadgrep
+    :commands deadgrep)
+
+  (leader-def-key "fg" 'deadgrep)
+
+  (add-to-list 'evil-emacs-state-modes 'deadgrep-mode)
+#+END_SRC
+
+* RCIRC
+IRC in Emacs, just in case anyone actually still uses it...
+
+Channels:
+#+BEGIN_SRC emacs-lisp
+  (with-eval-after-load 'rcirc
+    (add-to-list 'rcirc-server-alist
+		 `("znc.jeremydormitzer.com"
+		   :port 3000
+		   :nick "jdormit"
+		   :user-name "jdormit/freenode"
+		   :password ,(password-store-get "znc.jeremydormitzer.com")))   
+    (add-to-list 'rcirc-server-alist
+		 `("znc.jeremydormitzer.com"
+		   :channels ("#social")
+		   :nick "jdormit"
+		   :user-name "jdormit/w3"
+		   :port 3000
+		   :password ,(password-store-get "znc.jeremydormitzer.com"))))
+#+END_SRC
+
+Key bindings:
+#+BEGIN_SRC emacs-lisp
+  (leader-def-key "ai" 'irc)
+#+END_SRC
+
+Use evil keybindings by default:
+#+BEGIN_SRC emacs-lisp
+  (add-to-list 'evil-normal-state-modes 'rcirc-mode) 
+#+END_SRC
+
+* dumb-jump
+[[https://github.com/jacktasia/dumb-jump][Dumb-jump]] uses ripgrep and some algorithms based on the major-mode to implement jump-to-definition.
+
+#+BEGIN_SRC emacs-lisp
+  (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)
+#+END_SRC
+
+* Dictionary
+This package looks up word definitions online.
+#+BEGIN_SRC emacs-lisp
+  (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)
+#+END_SRC
+
+* Gnus
+An ancient newsreader.
+
+#+BEGIN_SRC emacs-lisp
+  (leader-def-key "ag" 'gnus)
+  (general-def gnus-mode-map "SPC" leader-map)
+#+END_SRC
+
+It can read email:
+#+BEGIN_SRC emacs-lisp
+  (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)  
+#+END_SRC
+
+Or Gnus can read RSS feeds directly:
+#+BEGIN_SRC emacs-lisp
+  ;; (add-to-list 'gnus-secondary-select-methods
+  ;;        '(nnrss "http://rss.nytimes.com/services/xml/rss/nyt/HomePage.xml"))
+#+END_SRC
+
+* Dired
+Enable [[info:dired-x#Top][Dired-X]]:
+
+#+BEGIN_SRC emacs-lisp
+  (require 'dired-x)
+#+END_SRC
+
+Preserve the leader key:
+#+BEGIN_SRC emacs-lisp
+  (general-def dired-mode-map "SPC" leader-map)
+#+END_SRC
+
+* 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:
+
+#+BEGIN_SRC emacs-lisp
+  (defun crontab ()
+    (interactive)
+    (with-editor-async-shell-command "crontab -e"))
+#+END_SRC
+
+* Emacs Server
+In case I need an =emacsclient= for some reason.
+#+BEGIN_SRC emacs-lisp
+  (add-hook 'after-init-hook #'server-start)
+#+END_SRC
+
+* YASnippet
+YASnippet is Yet Another Snippet template system.
+#+BEGIN_SRC emacs-lisp
+  (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))
+#+END_SRC
+
+* mpc
+An Emacs interface to MPD, the Music Player Daemon
+#+BEGIN_SRC emacs-lisp
+  (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)
+#+END_SRC
+
+* Ido
+Interactively do things! Some more info in [[https://masteringemacs.org/article/introduction-to-ido-mode][this blog post]].
+#+BEGIN_SRC emacs-lisp
+  (setq ido-enable-flex-matching t)
+  (setq ido-everywhere t)
+  (ido-mode 1)
+#+END_SRC
+
+Enable it everywhere:
+#+BEGIN_SRC emacs-lisp
+  (use-package ido-completing-read+
+    :config
+    (ido-ubiquitous-mode 1))
+
+  (use-package crm-custom
+    :config
+    (crm-custom-mode 1))
+#+END_SRC
+
+The horizontal completion is ugly. Make it vertical:
+#+BEGIN_SRC emacs-lisp
+  (use-package ido-vertical-mode
+    :config
+    (ido-vertical-mode 1)
+    (setq ido-vertical-define-keys 'C-n-and-C-p-only))
+#+END_SRC
+
+The default auto-merge time is too short.
+#+BEGIN_SRC emacs-lisp
+  (setq ido-auto-merge-delay-time 1.5)
+#+END_SRC
+
+* graphviz
+#+BEGIN_SRC emacs-lisp
+  (use-package graphviz-dot-mode
+    :mode (("\\.dot\\'" . graphviz-dot))
+    :init
+    (add-to-list 'org-src-lang-modes '("dot" . graphviz-dot))) 
+#+END_SRC
+
+** Functions
+A function that converts a lisp form into Graphviz format, e.g.:
+#+BEGIN_EXAMPLE
+  '(a ((label . "Node A"))
+      (b ((label . "Node B"))
+	 (d ((label . "Node D"))))
+      (c ((label . "Node C"))
+	 (e ((label . "Node E")))))
+#+END_EXAMPLE
+
+becomes:
+
+#+BEGIN_EXAMPLE
+  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;
+  }
+#+END_EXAMPLE
+
+#+BEGIN_SRC emacs-lisp
+  (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 ()) (edges ()))"
+    (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        "))))
+
+#+END_SRC
+
+* HideShow
+[[help:hs-minor-mode][hs-minor-mode]] enables comment and code-folding. It's useful almost everywhere, so just enable it:
+#+BEGIN_SRC emacs-lisp
+  (add-hook 'prog-mode-hook (lambda () (hs-minor-mode 1)))
+#+END_SRC
+
+* Slack
+#+BEGIN_SRC emacs-lisp
+  (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))
+#+END_SRC
+
+#+BEGIN_SRC emacs-lisp
+  (use-package alert
+    :commands (alert)
+    :init
+    (setq alert-default-style (jdormit-get-mu4e-alert-style)))
+#+END_SRC
+
+* Matrix
+#+BEGIN_SRC emacs-lisp
+  (use-package matrix-client
+    :quelpa ((matrix-client :fetcher github :repo "alphapapa/matrix-client.el"
+			     :files (:defaults "logo.png" "matrix-client-standalone.el.sh"))))   
+#+END_SRC
+
+* EMMS
+The Emacs Multi-Media System
+#+BEGIN_SRC emacs-lisp
+  (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)
+    (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))
+#+END_SRC
+
diff --git a/mbsync/.mbsyncrc b/mbsync/.mbsyncrc
new file mode 100644
index 0000000..1e0bd8c
--- /dev/null
+++ b/mbsync/.mbsyncrc
@@ -0,0 +1,116 @@
+IMAPAccount jeremy-dormitzer-net
+Host mail.dormitzer.net
+user jeremy@dormitzer.net
+PassCmd "pass show jeremy@dormitzer.net | head -n 1"
+PipelineDepth 50
+
+IMAPStore jeremy-dormitzer-net-remote
+Account jeremy-dormitzer-net
+
+MaildirStore jeremy-dormitzer-net-local
+Path ~/.mail/jeremy-dormitzer-net/
+Inbox ~/.mail/jeremy-dormitzer-net/Inbox
+SubFolders Verbatim
+
+Channel jeremy-dormitzer-net
+Master :jeremy-dormitzer-net-remote:
+Slave :jeremy-dormitzer-net-local:
+Patterns *
+Create Both
+SyncState *
+
+IMAPAccount jeremy-getpterotype-com
+Host imap.gmail.com
+user jeremy@getpterotype.com
+PassCmd "pass show jeremy@getpterotype.com | head -n 1"
+SSLType IMAPS
+CertificateFile ~/cert.pem
+AuthMechs LOGIN
+PipelineDepth 50
+
+IMAPStore jeremy-getpterotype-com-remote
+Account jeremy-getpterotype-com
+
+MaildirStore jeremy-getpterotype-com-local
+Path ~/.mail/jeremy-getpterotype-com/
+Inbox ~/.mail/jeremy-getpterotype-com/Inbox
+SubFolders Verbatim
+
+Channel jeremy-getpterotype-com-inbox
+Master :jeremy-getpterotype-com-remote:
+Slave :jeremy-getpterotype-com-local:
+Patterns "INBOX"
+Create Both
+SyncState *
+
+Channel jeremy-getpterotype-com-trash
+Master :jeremy-getpterotype-com-remote:"[Gmail]/Trash"
+Slave :jeremy-getpterotype-com-local:"Trash"
+Create Both
+SyncState *
+
+Channel jeremy-getpterotype-com-sent
+Master :jeremy-getpterotype-com-remote:"[Gmail]/Sent Mail"
+Slave :jeremy-getpterotype-com-local:"Sent"
+Create Both
+SyncState *
+
+Channel jeremy-getpterotype-com-all
+Master :jeremy-getpterotype-com-remote:"[Gmail]/All Mail"
+Slave :jeremy-getpterotype-com-local:"Archive"
+Create Both
+SyncState *
+
+Group jeremy-getpterotype-com
+Channel jeremy-getpterotype-com-inbox
+Channel jeremy-getpterotype-com-trash
+Channel jeremy-getpterotype-com-sent
+Channel jeremy-getpterotype-com-all
+
+IMAPAccount jeremy-dormitzer-gmail-com
+Host imap.gmail.com
+user jeremy.dormitzer@gmail.com
+PassCmd "pass show imap.gmail.com | head -n 1"
+SSLType IMAPS
+CertificateFile ~/cert.pem
+AuthMechs LOGIN
+PipelineDepth 50
+
+IMAPStore jeremy-dormitzer-gmail-com-remote
+Account jeremy-dormitzer-gmail-com
+
+MaildirStore jeremy-dormitzer-gmail-com-local
+Path ~/.mail/jeremy-dormitzer-gmail-com/
+Inbox ~/.mail/jeremy-dormitzer-gmail-com/Inbox
+SubFolders Verbatim
+
+Channel jeremy-dormitzer-gmail-com-inbox
+Master :jeremy-dormitzer-gmail-com-remote:
+Slave :jeremy-dormitzer-gmail-com-local:
+Patterns "INBOX"
+Create Both
+SyncState *
+
+Channel jeremy-dormitzer-gmail-com-trash
+Master :jeremy-dormitzer-gmail-com-remote:"[Gmail]/Trash"
+Slave :jeremy-dormitzer-gmail-com-local:"Trash"
+Create Both
+SyncState *
+
+Channel jeremy-dormitzer-gmail-com-sent
+Master :jeremy-dormitzer-gmail-com-remote:"[Gmail]/Sent Mail"
+Slave :jeremy-dormitzer-gmail-com-local:"Sent"
+Create Both
+SyncState *
+
+Channel jeremy-dormitzer-gmail-com-all
+Master :jeremy-dormitzer-gmail-com-remote:"[Gmail]/All Mail"
+Slave :jeremy-dormitzer-gmail-com-local:"Archive"
+Create Both
+SyncState *
+
+Group jeremy-dormitzer-gmail-com
+Channel jeremy-dormitzer-gmail-com-inbox
+Channel jeremy-dormitzer-gmail-com-trash
+Channel jeremy-dormitzer-gmail-com-sent
+Channel jeremy-dormitzer-gmail-com-all
\ No newline at end of file
diff --git a/msmtp/.msmtprc b/msmtp/.msmtprc
new file mode 100644
index 0000000..cc45b9d
--- /dev/null
+++ b/msmtp/.msmtprc
@@ -0,0 +1,26 @@
+defaults
+host smtp.gmail.com
+port 587
+protocol smtp
+auth on
+tls on
+tls_starttls on
+tls_trust_file /etc/ssl/certs/ca-certificates.crt
+
+account jeremy.dormitzer-gmail.com
+from jeremy.dormitzer@gmail.com
+user jeremy.dormitzer@gmail.com
+passwordeval pass show imap.gmail.com | head -n 1
+
+account jeremy-dormitzer.net
+host mail.dormitzer.net
+from jeremy@dormitzer.net
+user jeremy@dormitzer.net
+passwordeval pass show jeremy@dormitzer.net | head -n 1
+
+account jeremy-getpterotype.com
+from jeremy@getpterotype.com
+user jeremy@getpterotype.com
+passwordeval pass show jeremy@getpterotype.com | head -n 1
+
+account default : jeremy.dormitzer-gmail.com
\ No newline at end of file