#+PROPERTY: header-args :results silent #+PROPERTY: header-args:emacs-lisp :lexical t 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 * Prelude Enables lexical binding for everything in init.el: #+BEGIN_SRC emacs-lisp ;;; -*- lexical-binding: t; -*- #+END_SRC Requires: #+BEGIN_SRC emacs-lisp (require 'json) #+END_SRC * Default directory #+BEGIN_SRC emacs-lisp (cd "~") #+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" "LOLA_HOME" "MODELS_HOME" "LOLA_TRAVEL_SERVICE_HOME" "WORKON_HOME")) (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 Delete trailing whitespace on save: #+BEGIN_SRC emacs-lisp (add-hook 'before-save-hook #'delete-trailing-whitespace) #+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) (which-key-add-key-based-replacements (concat "," " " 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 * Magit Magit is objectively the best Git interface. #+BEGIN_SRC emacs-lisp (use-package magit :commands magit-status :config (add-to-list 'evil-emacs-state-modes 'magit-mode) (add-to-list 'evil-emacs-state-modes 'magit-status-mode) :general (magit-mode-map "SPC" leader-map)) #+END_SRC #+BEGIN_SRC emacs-lisp (jdormit/define-prefix "g" "git") (leader-def-key "gs" #'magit-status) (leader-def-key "gb" #'magit-blame) (leader-def-key "gf" #'magit-find-file) #+END_SRC Use ido-mode for completion within Magit: #+BEGIN_SRC emacs-lisp (setq magit-completing-read-function 'magit-ido-completing-read) #+END_SRC ** Forge [[https://github.com/magit/forge][Forge]] is an extension for Magit that lets it interact with code forges (e.g. GitHub). #+BEGIN_SRC emacs-lisp (use-package forge :quelpa :config (add-to-list 'forge-alist '("git.jeremydormitzer.com" "git.jeremydormitzer.com/api/v1" "git.jeremydormitzer.com" forge-gitea-repository))) #+END_SRC * with-editor A utility from the author of Magit to run shell commands using the current Emacs instance as $EDITOR. #+BEGIN_SRC emacs-lisp (shell-command-with-editor-mode) (add-hook 'shell-mode-hook #'with-editor-export-editor) (add-hook 'term-exec-hook #'with-editor-export-editor) (add-hook 'eshell-mode-hook #'with-editor-export-editor) #+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 (defun password-store-synchronize () (interactive) (with-editor-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)) (leader-def-key "ap" #'pass) #+END_SRC * Emacs Lisp Requires: #+BEGIN_SRC emacs-lisp (eval-when-compile (require 'subr-x)) #+END_SRC ** Editing Elisp #+BEGIN_SRC emacs-lisp (general-def 'motion emacs-lisp-mode "C-c C-c" #'eval-defun) #+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 "%F %r" (/ millis 1000)))) #+END_SRC The same but for seconds: #+BEGIN_SRC emacs-lisp (defun format-epoch-seconds (seconds) (interactive "nTimestamp: ") (message (format-time-string "%F %r" seconds))) #+END_SRC Checking if a buffer contains a string: #+BEGIN_SRC emacs-lisp (defun buffer-contains-substring (string) (save-excursion (save-match-data (goto-char (point-min)) (search-forward string nil t)))) #+END_SRC Pretty-print JSON: #+BEGIN_SRC emacs-lisp (defun pprint-json (raw-json) (with-temp-buffer (insert raw-json) (json-pretty-print (point-min) (point-max)) (buffer-substring (point-min) (point-max)))) #+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 ** Process handling Some utilities for calling out to other processes. #+BEGIN_SRC emacs-lisp (defun make-process-sentinel (success err) "Makes a process sentinel that calls `success` on success and `err` on error" (lambda (proc event) (cond ((string-match-p "finished" event) (funcall success)) (t (funcall err))))) (defun make-success-err-msg-sentinel (buf success-msg err-msg &optional kill-on-err) (make-process-sentinel (lambda () (message success-msg) (kill-buffer buf)) (lambda () (message err-msg) (when kill-on-err (kill-buffer buf))))) #+END_SRC A function to call a process passing some string as stdin and returning the process output: #+BEGIN_SRC emacs-lisp (defun make-process-fn (program &rest args) "Returns a function that, when called, call `program` with arguments `args`, passing the function argument as stdin" (lambda (&optional input) (with-temp-buffer (if input (progn (insert input) (apply #'call-process-region (point-min) (point-max) program t t nil args)) (apply #'call-process program nil t nil args)) (buffer-substring-no-properties (point-min) (point-max))))) #+END_SRC The same function but for commands that need to run in a shell: #+BEGIN_SRC emacs-lisp (defun make-shell-fn (program &rest args) "Returns a function that, when called, calls `program` in a shell with arguments `args`, passing the function argument as stdin" (lambda (&optional input) (let ((cmd (combine-and-quote-strings `(,program ,@args)))) (with-temp-buffer (if input (progn (insert input) (call-shell-region (point-min) (point-max) cmd t t)) (call-process-shell-command cmd nil t)) (buffer-substring-no-properties (point-min) (point-max)))))) #+END_SRC Running a shell command as sudo: #+BEGIN_SRC emacs-lisp (defun sudo-shell-command (command) (with-temp-buffer (cd "/sudo::/") (shell-command command))) #+END_SRC ** Buffer switch hooks I want to be able to run code whenever I switch to a buffer running certain modes. The code to run is stored in a alist mapping mode names to lists of code to run (stored as a raw data structure to be eval'ed): #+BEGIN_SRC emacs-lisp (defvar buffer-mode-hooks '()) #+END_SRC To add a new hook, push the code to run onto the correct list: #+BEGIN_SRC emacs-lisp (defun add-buffer-mode-hook (mode fn) (if-let ((existing-entry (assoc mode buffer-mode-hooks))) (push fn (cdr existing-entry)) (let ((new-entry `(,mode . (,fn)))) (push new-entry buffer-mode-hooks)))) #+END_SRC Whenever the buffer changes, look up the major-mode to see if there is any code to run: #+BEGIN_SRC emacs-lisp (defun run-buffer-mode-hooks () (when-let ((entry (assoc major-mode buffer-mode-hooks))) (dolist (fn (cdr entry)) (funcall fn)))) (add-hook 'buffer-list-update-hook #'run-buffer-mode-hooks) #+END_SRC * Customization File I don't want anything to write to my init.el, so save customizations in a separate file: #+BEGIN_SRC emacs-lisp (setq custom-file (expand-file-name "custom.el" user-emacs-directory)) (load custom-file t) #+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 '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 '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 ":" #'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 "bn" #'next-buffer) (leader-def-key "bp" #'previous-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 "Fo" #'other-frame) (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 ** Help Buffers #+BEGIN_SRC emacs-lisp (general-def 'motion help-mode-map "TAB" #'forward-button) #+END_SRC * Whitespace Visualation #+BEGIN_SRC emacs-lisp (setq whitespace-line-column 80 whitespace-style '(face lines-tail)) (leader-def-key "tw" #'whitespace-mode) #+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-project-dir :init (leader-def-key "d" #'neotree-toggle) :config (defun neotree-project-dir () "Open NeoTree using the git root." (interactive) (let ((project-dir (projectile-project-root)) (file-name (buffer-file-name)))) (neotree-toggle) (if project-dir (if (neo-global--window-exists-p) (progn (neotree-dir project-dir) (neotree-find file-name)) (message "Could not find git project root.")))) (setq neo-smart-open t) (setq neo-theme (if (display-graphic-p) 'icons 'arrow)) :general (neotree-mode-map "SPC" leader-map)) (use-package all-the-icons :after (neotree)) #+END_SRC And while we're here let's enable all-the-icons for dired as well: #+BEGIN_SRC emacs-lisp (use-package all-the-icons-dired :after (all-the-icons) :config (add-hook 'dired-mode-hook #'all-the-icons-dired-mode)) #+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 * jq The JSON multitool. #+BEGIN_SRC emacs-lisp (use-package jq-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 (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))) #+END_SRC ** Capture templates #+BEGIN_SRC emacs-lisp (setq org-capture-templates `(("l" "Lola task" entry (file+headline ,(agenda-files "todo.org") "Lola") "* TODO %i%?") ("p" "Personal task" entry (file+headline ,(agenda-files "todo.org") "Personal") "* 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) ("l" "Log" entry (file ,(agenda-files "log.org")) "* %<%Y-%m-%d %H:%M:%S>%?"))) #+END_SRC ** Refile targets #+BEGIN_SRC emacs-lisp (setq org-refile-use-outline-path 'file org-refile-targets `((org-agenda-files :level . 0) (,(agenda-files "notes.org") :level . 1) (,(agenda-files "todo.org") :level . 1))) #+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-todo-ignore-scheduled 'future) (setq org-agenda-tags-todo-honor-ignore-options t) (setq org-agenda-custom-commands '(("L" "Lola" ((tags-todo "@lola"))) ("T" "Today's list" ((agenda) (tags-todo "today"))))) #+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 '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) #+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 And a global keybinding to open an org file: #+BEGIN_SRC emacs-lisp (defun find-org-file (file) (interactive (list (ido-completing-read+ "Find org file: " (directory-files (agenda-files) t)))) (find-file file)) (leader-def-key "fo" #'find-org-file) #+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) (ledger . t) (sql . t) (jq . 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 ** Images #+BEGIN_SRC emacs-lisp (setq org-image-actual-width '(1000)) #+END_SRC * Projectile #+BEGIN_SRC emacs-lisp (use-package projectile :config (projectile-mode) (jdormit/define-prefix "p" "projectile") (leader-def-key "pf" 'projectile-find-file) (leader-def-key "pg" '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 menu bar: #+BEGIN_SRC emacs-lisp (menu-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 Display the column number in programming modes: #+BEGIN_SRC emacs-lisp (add-hook 'prog-mode-hook #'column-number-mode) #+END_SRC Render stuff differently based on whether or not we are graphical: #+BEGIN_SRC emacs-lisp (defun graphical-setup () (when (display-graphic-p (selected-frame)) (message "Running graphically") (use-package solarized-theme))) (defun non-graphical-setup () (when (not (display-graphic-p (selected-frame))) (message "Running in terminal") (menu-bar-mode -1))) (defun do-graphical-non-graphical-setup () (graphical-setup) (non-graphical-setup)) (add-hook 'window-setup-hook #'do-graphical-non-graphical-setup) #+END_SRC Try to make the background normal colored in the terminal: #+BEGIN_SRC emacs-lisp (defvar no-background-in-tty-faces '(default line-number magit-section-highlight)) (defun on-frame-open (frame) (unless (display-graphic-p frame) (menu-bar-mode -1) (dolist (face no-background-in-tty-faces) (set-face-background face "unspecified" frame)))) (mapc #'on-frame-open (frame-list)) (add-hook 'after-make-frame-functions #'on-frame-open) (defun on-after-init () (unless (display-graphic-p (selected-frame)) (dolist (face no-background-in-tty-faces) (set-face-background face "unspecified" (selected-frame))))) (add-hook 'window-setup-hook #'on-after-init) #+END_SRC UI-related keybindings: #+BEGIN_SRC emacs-lisp (jdormit/define-prefix "u" "UI") (leader-def-key "ut" #'customize-themes) (leader-def-key "uf" #'customize-face) (leader-def-key "uc" #'display-time-mode) (leader-def-key "ub" #'display-battery-mode) #+END_SRC * Frame parameters Functions to change the frame size: #+BEGIN_SRC emacs-lisp (defun jdormit/set-frame-size (width height) (interactive "nWidth: \nnHeight: ") (if (display-graphic-p) (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 And a function to run any program visually: #+BEGIN_SRC emacs-lisp (defun eshell/v (&rest args) (apply #'eshell-exec-visual args)) #+END_SRC Load .dir-locals.el when switching directories: #+BEGIN_SRC emacs-lisp (add-hook 'eshell-mode-hook #'hack-dir-local-variables-non-file-buffer) (add-hook 'eshell-directory-change-hook #'hack-dir-local-variables-non-file-buffer) #+END_SRC Some aliases: #+BEGIN_SRC emacs-lisp (defvar eshell-aliases '(("k" . "kubectl $*") ("kctx" . "kubectx $*"))) (add-hook 'eshell-mode-hook (lambda () (dolist (alias eshell-aliases) (eshell/alias (car alias) (cdr alias))))) #+END_SRC * JSON #+BEGIN_SRC emacs-lisp (use-package json-mode :mode (("\\.json\\'" . json-mode))) (use-package json-navigator :commands (json-navigator-navigator json-navigator-navigate-after-point json-navigator-navigate-region)) #+END_SRC * JavaScript Some formatting stuff: #+BEGIN_SRC emacs-lisp (setq js-indent-level 4) #+END_SRC #+BEGIN_SRC emacs-lisp (use-package web-mode :mode (("\\.html\\'" . web-mode) ("\\.js\\'" . web-mode) ("\\.jsx\\'" . web-mode)) :config (add-hook 'web-mode-hook (lambda () (when (equal web-mode-content-type "javascript") (web-mode-set-content-type "jsx")) (when (or (equal web-mode-content-type "javascript") (equal web-mode-content-type "jsx")) (lsp))))) #+END_SRC Use nvm to manage node versions: #+BEGIN_SRC emacs-lisp (use-package nvm :quelpa ((nvm :fetcher github :repo "rejeep/nvm.el")) :commands (nvm-use nvm-use-for nvm-use-for-buffer)) #+END_SRC * HTML #+BEGIN_SRC emacs-lisp (use-package web-mode) #+END_SRC * LSP Mode Emacs support for the Language Server Protocol #+BEGIN_SRC emacs-lisp (use-package lsp-mode :config (general-def 'normal lsp-mode-map "SPC cd" #'lsp-find-definition) (general-def 'normal lsp-mode-map "SPC cr" #'lsp-find-references)) (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)) (use-package dap-mode :after (lsp-mode)) (add-to-list 'evil-emacs-state-modes 'xref--xref-buffer-mode) (with-eval-after-load 'lsp-clients (defun lsp-typescript-javascript-tsx-jsx-activate-p (filename major-mode) "Checks if the javascript-typescript language server should be enabled based on FILE-NAME and MAJOR-MODE" (or (member major-mode '(typescript-mode typescript-tsx-mode js-mode js2-mode rjsx-mode)) (and (eq major-mode 'web-mode) (or (string-suffix-p ".tsx" filename t) (string-suffix-p ".jsx" filename t) (string-suffix-p ".js" filename t)))))) #+END_SRC * Java LSP Java uses the Eclipse JDT Language Server as a backend to enable Java IDE features. #+BEGIN_SRC emacs-lisp (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 #+BEGIN_SRC emacs-lisp (leader-def-key "sp" #'run-python) #+END_SRC Elpy is a python IDE package: #+BEGIN_SRC emacs-lisp (use-package elpy :init (elpy-enable)) #+END_SRC Integrate with pyenv: #+BEGIN_SRC emacs-lisp (use-package pyvenv :config (pyvenv-mode)) (defun eshell/workon (name) (pyvenv-workon name)) (defun eshell/activate (dir) (pyvenv-activate dir)) (defun eshell/deactivate () (pyvenv-deactivate)) #+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 * Hy Python but Lispy! #+BEGIN_SRC emacs-lisp (defun run-hy () (interactive) (run-lisp (expand-file-name "~/.virtualenvs/hy/bin/hy"))) #+END_SRC * Go Basic support: #+BEGIN_SRC emacs-lisp (use-package go-mode :mode (("\\.go\\'" . go-mode))) #+END_SRC LSP support - requires [[https://github.com/sourcegraph/go-langserver][go-langserver]]. #+BEGIN_SRC emacs-lisp (add-hook 'go-mode-hook #'lsp) #+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 Enable Org-mode Clojure evaluation: #+BEGIN_SRC emacs-lisp (require 'ob-clojure) (setq org-babel-clojure-backend 'cider) #+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 LSP for PHP requires [[https://github.com/felixfbecker/php-language-server][php-language-server]] to be installed in ~/.composer: #+BEGIN_SRC emacs-lisp (add-hook 'php-mode-hook #'lsp) #+END_SRC * YAML #+BEGIN_SRC emacs-lisp (use-package yaml-mode :mode ("//.yml//'")) #+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 * Bash Use LSP if [[https://github.com/mads-hartmann/bash-language-server][bash-language-server]] is installed. #+BEGIN_SRC emacs-lisp (when (executable-find "bash-language-server") (add-hook 'sh-mode-hook #'lsp)) #+END_SRC * Ruby #+BEGIN_SRC emacs-lisp (add-hook 'ruby-mode-hook #'lsp) #+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 * 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 ** Importing #+BEGIN_SRC emacs-lisp (defvar ledger-file (expand-file-name "~/journal.ledger")) (defvar bank-alist '(("DCU Checking" . ((acct . "Assets:Checking") (fid . "9999"))) ("DCU Savings" . ((acct . "Assets:Savings") (fid . "9999"))) ("DCU Visa" . ((acct . "Liabilities:DCU Visa") (fid . "9999"))) ("Chase Visa" . ((acct . "Liabilities:Chase Visa"))))) (defun ledger-import-ofx (bank file) (interactive (list (ido-completing-read+ "Bank: " (mapcar #'car bank-alist)) (ido-read-file-name "OFX file: "))) (if-let ((ledger-autosync (executable-find "ledger-autosync"))) (let* ((bank-def (alist-get bank bank-alist)) (acct (alist-get 'acct bank-def)) (fid (alist-get 'fid bank-def)) (cmd (concat ledger-autosync (if fid (concat " --fid " fid) "") " --account '" acct "'" " '" file "'")) (output (shell-command-to-string cmd))) (find-file ledger-file) (goto-char (point-max)) (insert "\n") (insert output) (ledger-sort-region (point-min) (point-max)) (ledger-post-align-postings (point-min) (point-max))) (error "Unable to find ledger-autosync"))) #+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") (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 . "jdormit@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) #+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 ;; :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)) #+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 "TAB" '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
  (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))
#+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
    (setq rcirc-server-alist
	  `(("znc.jeremydormitzer.com"
	     :port 3000
	     :nick "jdormit"
	     :user-name "jdormit"
	     :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 edit-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. For libtag to work, libtag must be installed on the system via the system package manager. Then Emms should be installed from source via:

#+BEGIN_SRC shell :tangle no
  git clone git://git.sv.gnu.org/emms.git
  cd emms
  make emms-print-metadata
  make
  make install
#+END_SRC

#+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)
    (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" ".m4p"))
	"afplay")
      (setq emms-player-list `(,emms-player-afplay))))
#+END_SRC

* Direnv
[[https://direnv.net/][Direnv]] automatically runs bash scripts when you enter certain directories. This sets it up to work with Emacs:
#+BEGIN_SRC emacs-lisp
  (defun update-cider-env ()
    (direnv-update-directory-environment nrepl-project-dir))

  (use-package direnv
    :if (executable-find "direnv")
    :config (direnv-mode)
    (add-hook 'eshell-mode-hook #'direnv-update-directory-environment)
    (add-hook 'eshell-directory-change-hook #'direnv-update-directory-environment)
    ;; (add-buffer-mode-hook 'cider-repl-mode #'update-cider-env)
    )
#+END_SRC
* SQL
Emacs has excellent built-in SQL support.
#+BEGIN_SRC emacs-lisp
  (leader-def-key "sP" #'sql-postgres)
#+END_SRC
* GraphQL
#+BEGIN_SRC emacs-lisp
  (use-package graphql-mode
    :quelpa ((graphql-mode :fetcher github
			   :repo "davazp/graphql-mode"))
    :config
    (defvar graphql-env-alist '()
      "An alist defining available GraphQL servers

  The key is any symbol and the value is a cons pair of
  (graphql-url . graphql-auth-token)")
    (setq graphql-variables-file "~/variables.graphql")
    (defun graphql-set-env (env)
      (interactive
       (list
	(intern
	 (ido-completing-read+
	  "GraphQL env: "
	  (mapcar #'car graphql-env-alist)))))
      (let* ((gql-params (alist-get env graphql-env-alist))
	     (url (car gql-params))
	     (auth-token (cdr gql-params)))
	(setq graphql-url url
	      graphql-extra-headers `(("Authorization" . ,auth-token)))))
    (defun graphql (env)
      "Opens a graphql-mode buffer in the specified environment"
      (interactive
       (list
	(intern
	 (ido-completing-read+
	  "GraphQL env: "
	  (mapcar #'car graphql-env-alist)))))
      (graphql-set-env env)
      (let ((graphql-buf (get-buffer-create "*graphql-query*")))
	(set-buffer graphql-buf)
	(when (string= "" (buffer-substring (point-min) (point-max)))
	  (insert "{\n  \n}")
	  (goto-char 5))
	(graphql-mode)
	(switch-to-buffer graphql-buf)))
    (defun find-graphql-variables-file ()
      (interactive)
      (find-file graphql-variables-file))
    (jdormit/define-prefix "q" "graphql")
    (leader-def-key "qe" #'graphql-set-env)
    (leader-def-key "qf" #'find-graphql-variables-file)
    (leader-def-key "aq" #'graphql)
    (general-def graphql-mode-map "C-c C-e" #'graphql-set-env)
    (general-def graphql-mode-map "C-c C-v" #'find-graphql-variables-file))
#+END_SRC

GraphQL environments:
#+BEGIN_SRC emacs-lisp
  (setq graphql-env-alist
    `((dev . ("https://api-dev.lola.co/api/graphql" .
	      ,(password-store-get "lola-graphql-dev-token")))
      (local . ("http://localhost:7200/api/graphql" .
		,(password-store-get "lola-graphql-local-token")))))
#+END_SRC
* Docker
Syntax highlighting for Dockerfiles:
#+BEGIN_SRC emacs-lisp
  (use-package dockerfile-mode
    :mode ("\\Dockerfile\\'"))
#+END_SRC
* Kubernetes
#+BEGIN_SRC emacs-lisp
  (use-package kubernetes
    :ensure t
    :commands (kubernetes-overview)
    :init (leader-def-key "ak" #'kubernetes-overview)
    :config
    (add-to-list 'evil-emacs-state-modes 'kubernetes-overview-mode)
    (general-def kubernetes-overview-mode-map "SPC" leader-map))
#+END_SRC

* AWS
** S3
#+BEGIN_SRC emacs-lisp
  (defun save-to-s3 (bucket public)
    (interactive "MS3 bucket URL (e.g. s3://bucket/filename): \nP")
    (let* ((tmp-file (make-temp-file "s3-upload"))
	   (cmd `("aws" "s3" "cp" ,@(if public '("--acl" "public-read")) ,tmp-file ,bucket)))
      (setq last-s3-url bucket)
      (write-region (point-min) (point-max) tmp-file)
      (let ((proc (apply #'start-process "aws-s3" "*aws-s3*" cmd)))
	(set-process-sentinel
	 proc
	 (make-success-err-msg-sentinel "*aws-s3*"
					(format "Uploaded to %s" last-s3-url)
					"Failed to upload to s3, check *aws-s3* buffer for details")))))
#+END_SRC
* Lola
Some functions to make my day job easier.

** Backend services
#+BEGIN_SRC emacs-lisp :lexical yes
  (defun run-service-in-eshell (name dir cmd &optional setup)
    (if-let ((buf (get-buffer name)))
	(progn (when (eq major-mode 'eshell-mode)
		 (eshell-interrupt-process)
		 (while eshell-process-list))
	       (kill-buffer buf)))
    (let ((buf (get-buffer-create name)))
      (switch-to-buffer buf)
      (cd dir)
      (eshell-mode)
      (when setup (funcall setup))
      (insert cmd)
      (eshell-send-input)))

  (defun get-python-setup (venv)
    (lambda ()
      (eshell/deactivate)
      (direnv-update-directory-environment (eshell/pwd))
      (pyvenv-workon venv)))

  (defun lola-server ()
    (interactive)
    (run-service-in-eshell
     "*lola-server*"
     "~/lola/lola-server"
     "gunicorn -t 120 -c server/web/gunicorn.conf.py bin.start_web:init_and_create_flask_app\\(\\)"
     (get-python-setup "lola-server")))

  (defun lola-celery-worker ()
    (interactive)
    (run-service-in-eshell
     "*lola-celery-worker*"
     "~/lola/lola-server"
     "python bin/start_celery_worker.py -P gevent"
     (get-python-setup "lola-server")))

  (defun lola-travel-service ()
    (interactive)
    (run-service-in-eshell
     "*lola-travel-service*"
     "~/lola/lola-travel-service"
     "python bin/start_web.py"
     (get-python-setup "travel-service")))

  (defun lola-secrets ()
    (interactive)
    (run-service-in-eshell
     "*lola-secrets*"
     "~/lola/secrets"
     "python bin/cmdline.py www"
     (get-python-setup "secrets")))

  (defun lola-desktop (env)
    (interactive
     (list (ido-completing-read+
	    "Environment: "
	    '("local" "development" "staging"))))
    (run-service-in-eshell
     "*lola-desktop*"
     "~/lola/lola-desktop"
     "npm start"
     (lambda () (setenv "LOLA_ENV" env))))

  (defun lola-wallet (env)
    (interactive
     (list (ido-completing-read+
	    "Environment: "
	    '("local" "development" "staging"))))
    (run-service-in-eshell
     "*lola-wallet*"
     "~/lola/wallet"
     "npm start"
     (lambda () (setenv "LOLA_ENV" env))))

  (defun lola-run-all ()
    (interactive)
    (let ((fns (list #'lola-travel-service
		     #'lola-server
		     #'lola-celery-worker
		     #'lola-desktop
		     #'lola-wallet
		     #'lola-secrets)))
      (mapcar #'call-interactively fns)))

  (defun run-luigid ()
    (interactive)
    (run-service-in-eshell "*luigid*"
			   "~/lola/data-pipeline"
			   "luigid"
			   (get-python-setup "data-pipeline")))

  (defun release-manager ()
    (interactive)
    (run-service-in-eshell "*release-manager*"
			   "~/lola/gittools"
			   "./release-manager"
			   (get-python-setup "gittools")))

  (jdormit/define-prefix "L" "lola")
  (leader-def-key "Ls" #'lola-server)
  (leader-def-key "Lc" #'lola-celery-worker)
  (leader-def-key "Lt" #'lola-travel-service)
  (leader-def-key "Ld" #'lola-desktop)
  (leader-def-key "Lw" #'lola-wallet)
  (leader-def-key "LS" #'lola-secrets)
  (leader-def-key "LA" #'lola-run-all)
  (leader-def-key "LL" #'run-luigid)
  (leader-def-key "Lr" #'release-manager)
#+END_SRC

** AWS-MFA
The aws-mfa command:
#+BEGIN_SRC emacs-lisp
  (defun aws-mfa (mfa-token)
    (interactive "MMFA code: ")
    (let ((proc (start-process "aws-mfa"
			       "*aws-mfa*"
			       "/Users/jdormit/.local/share/virtualenvs/k8s-deploy/bin/aws-mfa"
			       "--force")))
      (set-process-sentinel
       proc
       (make-success-err-msg-sentinel "*aws-mfa*"
				      "AWS MFA succeeded"
				      "AWS MFA failed, check *aws-mfa* buffer for details"))
      (process-send-string proc (concat mfa-token "\n"))))

  (with-eval-after-load 'kubernetes
    (general-def kubernetes-overview-mode-map "m" #'aws-mfa))
#+END_SRC

** 1Password
#+BEGIN_SRC emacs-lisp
  (defvar op-token nil
    "The 1Password session token")

  (defun 1pass-signin ()
    (interactive)
    (cl-letf* ((signin-address "team-lolatravel.1password.com")
	       (signin-email "jeremydormitzer@lola.com")
	       (secret-key (password-store-get "1pass-lola-secret-key"))
	       ((symbol-function 'op-signin)
		(make-shell-fn "pass" "team-lolatravel.1password.com" "|" "op" "signin" signin-address signin-email secret-key "--output=raw"))
	       (token (op-signin)))
      (if (string-match-p "ERROR" token)
	  (error "Unable to sign in to 1Password: %s" token)
	(setf op-token token)
	(message (format "Signed in to 1Password with session token %s" op-token))
	op-token)))

  (defun op-fn (&rest args)
    (lambda (&optional input)
      (cl-letf* (((symbol-function 'op-function)
		  (apply #'make-shell-fn "op" `(,@args ,(format "--session=%s" op-token))))
		 (output (op-function input)))
	(if (or (string-match-p "Authentication required" output)
		(string-match-p "You are not currently signed in" output))
	    (cl-letf* ((new-token (1pass-signin))
		       ((symbol-function 'op-function)
			(apply #'make-shell-fn "op" `(,@args ,(format "--session=%s" new-token)))))
	      (op-function input))
	  output))))

  (defun op-list-items ()
    (cl-flet ((op-list-items-fn (op-fn "list" "items")))
      (mapcar #'cdr
	      (mapcar (apply-partially #'assoc 'title)
		      (mapcar (apply-partially #'assoc 'overview)
			      (json-read-from-string (op-list-items-fn)))))))

  (defun op-get-item (item)
    (cl-flet ((op-get-item (op-fn "get" "item" item)))
      (json-read-from-string (op-get-item))))

  (defun op-get-item-field (item-json field-designation)
    (let* ((fields (assoc-recursive item-json 'details 'fields))
	   (pw-field (car (seq-filter
			   (lambda (field)
			     (string= field-designation (cdr (assoc 'designation field))))
			   fields))))
      (when pw-field (cdr (assoc 'value pw-field)))))

  (defun op-copy-password (item)
    (interactive
     (list
      (ido-completing-read+ "1Password item: " (op-list-items))))
    (if-let ((password (op-get-item-field (op-get-item item) "password")))
	(with-temp-buffer
	  (insert password)
	  (copy-region-as-kill (point-min) (point-max))
	  (message "Copied password for \"%s\" to kill ring." item))
      ;; TODO if no password found, prompt for alternate field in record to return
      (error "No password found in 1Password for \"%s\"." item)))

  (leader-def-key "ao" #'op-copy-password)
#+END_SRC
** Resetting DNSResponder
#+BEGIN_SRC emacs-lisp
  (defun reset-dnsresponsder ()
    (interactive)
    (sudo-shell-command "killall -HUP mDNSResponder"))
#+END_SRC
** Devpi
#+BEGIN_SRC emacs-lisp
  (defvar devpi-indices nil
    "Login profiles for devpi. An alist
     where the keys are index names and
     the values are alists with the keys
     devpi-index-url, devpi-index-username,
     and devpi-index-password-fn.")

  (setq devpi-indices
	`((pip .
	       ((devpi-index-url . "https://pip.aws.lolatravel.com/pip/dev")
		(devpi-index-username . "pip")
		(devpi-index-password-fn . ,(lambda ()
					      (op-get-item-field
					       (op-get-item "PIP Devpi Login")
					       "password")))))
	  (jdormit .
		   ((devpi-index-url . "https://pip.aws.lolatravel.com/jdormit/dev")
		    (devpi-index-username . "jdormit")
		    (devpi-index-password-fn . ,(lambda ()
						  (password-store-get "devpi-jdormit")))))))

  (defun devpi-use-index (index-name)
    (interactive
     (list
      (intern
       (ido-completing-read+
	"Devpi index to use: "
	(mapcar #'car devpi-indices)))))
    (cl-letf* ((index-alist (alist-get index-name devpi-indices))
	       (index-url (alist-get 'devpi-index-url index-alist))
	       (index-username (alist-get 'devpi-index-username index-alist))
	       ((symbol-function 'index-password-fn) (alist-get 'devpi-index-password-fn index-alist))
	       (index-password (index-password-fn))
	       ((symbol-function 'devpi-use) (make-process-fn "devpi" "use" index-url))
	       ((symbol-function 'devpi-login)
		(make-process-fn "devpi" "login" index-username "--password" index-password))
	       (login-result (devpi-login))
	       (use-result (devpi-use)))
      (save-match-data
	(if (and (string-match "credentials valid" login-result)
		 (string-match "current devpi index" use-result))
	    (message "Switched to Devpi index %s" index-name)
	  (with-current-buffer (get-buffer-create "*devpi*")
	    (erase-buffer)
	    (goto-char (point-min))
	    (insert login-result)
	    (insert use-result))
	  (message "Failed to switch to Devpi index %s - check the *devpi* buffer for details" index-name)))))

  (defun devpi-upload ()
    (interactive)
    (cl-letf (((symbol-function 'devpi-upload) (make-process-fn "devpi" "upload")))
      (message (devpi-upload))))
#+END_SRC
* StumpWM
A handy keybinding to connect to the StumpWM SBCL process via SLIME:
#+BEGIN_SRC emacs-lisp
  (defun connect-stumpwm ()
    (interactive)
    (slime-connect "127.0.0.1" 4004))
#+END_SRC
* Emacs Network Client
Emacs frontend for networkmanager.
#+BEGIN_SRC emacs-lisp
  (use-package enwc
    :config
    (setq enwc-default-backend 'nm)
    (add-to-list 'evil-emacs-state-modes 'enwc-mode)
    (general-def enwc-mode-map "SPC" leader-map)
    :init
    (leader-def-key "an" #'enwc)
    :commands (enwc))
#+END_SRC
* Deft
A fuzzy-finder for notes.
#+BEGIN_SRC emacs-lisp
  (use-package deft
    :init
    (setq deft-extensions '("org" "txt" "md" "markdown" "text"))
    :config
    (setq deft-directory (concat (file-name-as-directory (get-dropbox-directory))
				 "org/deft")
	  deft-use-filter-string-for-filename t
	  deft-file-naming-rules '((noslash . "-")
				   (nospace . "-")
				   (case-fn . downcase)))
    (add-to-list 'evil-emacs-state-modes 'deft-mode)
    (leader-def-key "D" #'deft)
    (add-to-list 'org-agenda-files deft-directory))
#+END_SRC
* Pollen
#+BEGIN_SRC emacs-lisp
  (use-package pollen-mode)
#+END_SRC
* Ngrok
#+BEGIN_SRC emacs-lisp
  (defun ngrok (port &optional subdomain)
    (interactive "nPort: \nsSubdomain: ")
    (let ((buf (get-buffer-create
		(concat "*ngrok-"
			(number-to-string port)
			"*")))
	  (cmd (if (and subdomain (not (string-empty-p subdomain)))
		   (concat "ngrok http "
			   "--subdomain=" subdomain " "
			   (number-to-string port))
		 (concat "ngrok http " (number-to-string port)))))
      (async-shell-command cmd buf)))
#+END_SRC
* Make
#+BEGIN_SRC emacs-lisp
  (defun make ()
    (interactive)
    (let ((project-root (projectile-project-root)))
      (if project-root
	  (with-temp-buffer
	    (cd project-root)
	    (async-shell-command "make"))
	(error "Not in a project"))))
#+END_SRC
* Redis
#+BEGIN_SRC emacs-lisp
  (defun redis-cli (&optional host port)
    (interactive (list (read-string "host (default localhost): " nil nil "localhost")
		       (read-number "port: " 6379)))
    (let ((cli-path (executable-find "redis-cli"))
	  (host (or host "localhost"))
	  (port (or port 6379)))
      (if cli-path
	  (progn
	    (make-comint-in-buffer
	     "redis-cli"
	     nil
	     "redis-cli"
	     nil
	     "-h" host
	     "-p" (number-to-string port)
	     "--no-raw")
	    (switch-to-buffer "*redis-cli*"))
	(error "Can't find redis-cli"))))
#+END_SRC