dotfiles/emacs/init.org

213 KiB
Executable File

-- eval: (add-hook 'after-save-hook 'org-babel-tangle 0 t) --

This is a literate init file holding my Emacs configuration. It is initially loaded by a bootstrap file that lives at ~/.emacs.d/init.el; after the initial bootstrapping it writes itself to ~/.emacs.el. Since ~/.emacs.el takes priority over ~/.emacs.d/init.el, after the initial bootstrapping process the tangled ~/.emacs.el file will get loaded without needing to load the bootstrap file first.

Prelude

Enables lexical binding for everything in init.el:

  ;;;  -*- lexical-binding: t; -*-

Garbage collection

Some GC tweaks "borrowed" from Doom emacs.

Turn off GC during init and restore it afterwards:

  (setq gc-cons-threshold most-positive-fixnum
	gc-cons-percentage 0.6)

  (add-hook 'emacs-startup-hook
      (lambda ()
	(setq gc-cons-threshold 100000000
	      gc-cons-percentage 0.1)))

Also suppress GC for 1 second after the minibuffer is active to avoid stuttering autocompletion and other GC hangups:

  (defun defer-garbage-collection ()
    (setq gc-cons-threshold most-positive-fixnum))

  (defun restore-garbage-collection ()
    (run-at-time
     1 nil (lambda () (setq gc-cons-threshold 100000000))))

  (add-hook 'minibuffer-setup-hook #'defer-garbage-collection)
  (add-hook 'minibuffer-exit-hook #'restore-garbage-collection)

Unset file-handler-alist during initialization

Another optimization from Doom Emacs.

  (defvar file-name-handler-alist-backup file-name-handler-alist)
  (setq file-name-handler-alist nil)
  (add-hook 'emacs-startup-hook
	    (lambda ()
	      (setq file-name-handler-alist file-name-handler-alist-backup)))

Variables

  (setq vc-follow-symlinks t
        frame-resize-pixelwise t
        tab-always-indent 'complete
        enable-recursive-minibuffers t
        read-process-output-max (* 1024 1024))
  (setq-default indent-tabs-mode nil)

Default directory

  (cd "~")

Packages

Load straight.el to manage package installation:

  (defvar bootstrap-version)
  (unless (boundp 'bootstrapping-init)
   (let ((bootstrap-file
	 (expand-file-name "straight/repos/straight.el/bootstrap.el" user-emacs-directory))
	(bootstrap-version 5))
    (unless (file-exists-p bootstrap-file)
      (with-current-buffer
	  (url-retrieve-synchronously
	   "https://raw.githubusercontent.com/raxod502/straight.el/develop/install.el"
	   'silent 'inhibit-cookies)
	(goto-char (point-max))
	(eval-print-last-sexp)))
    (load bootstrap-file nil 'nomessage)))

`use-package` is a macro that simplifies installing and loading packages.

  (straight-use-package 'use-package)
  (setq straight-use-package-by-default t)

Benchmarking

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

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

Load org mode

Load org-mode early to avoid a version clash.

  (use-package org
    :straight org-plus-contrib
    :commands (org-element-map)
    :mode (("\\.org\\'" . org-mode)))

  ;; Annoying that this is necessary...
  (require 'org)
  (require 'org-refile)
  (require 'org-protocol)

Doom themes

  (use-package doom-themes)

  (use-package doom-modeline)
  (doom-modeline-mode 1)

Ewal theme

  (use-package doom-ewal-themes
    :straight (doom-ewal-themes
	       :host github
	       :repo "jdormit/doom-ewal-themes"
	       :files ("themes" :defaults)))

Customization File

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

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

Path

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

  (use-package exec-path-from-shell
    :if (memq window-system '(mac ns x))
    :config
    (setq exec-path-from-shell-variables '("PATH" "MANPATH" "LEDGER_FILE" "LOLA_HOME"
                                           "MODELS_HOME" "LOLA_TRAVEL_SERVICE_HOME" "WORKON_HOME"
                                           "PIPENV_VERBOSITY" "PIPENV_DONT_LOAD_ENV"
                                           "PIPENV_MAX_DEPTH")
          exec-path-from-shell-check-startup-files nil)
    (exec-path-from-shell-initialize))

General

Better keybinding.

  (use-package general
    :init
    (setq general-override-states '(insert
				    emacs
				    hybrid
				    normal
				    visual
				    motion
				    operator
				    replace)))

Delete trailing whitespace on save:

  (defvar *should-delete-trailing-whitespace* t)
  (defun delete-trailing-whitespace-hook-fn ()
    (when *should-delete-trailing-whitespace*
      (delete-trailing-whitespace)))
  (add-hook 'before-save-hook #'delete-trailing-whitespace-hook-fn)

Autocompletion

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

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

  (general-def "C-M-i" #'company-complete)
  (general-def "M-<tab>" #'company-complete)

Which-key

`which-key` makes keybindings discoverable.

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

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

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

Evil Mode

Because I like modal editing and dislike RSI.

  (use-package evil
    :init
    (setq evil-want-keybinding nil)
    :config
    (evil-mode 1))

Make undo not undo paragraphs at a time:

  (setq evil-want-fine-undo t)

Add a convenience function for making buffer-local ex-commands:

  (defun evil-ex-define-local-cmd (cmd function)
    (set (make-local-variable 'evil-ex-commands) (copy-tree evil-ex-commands))
    (evil-ex-define-cmd cmd function))

evil-collection

A collection of evil bindings for various modes

  (use-package evil-collection
    :after (evil)
    :config
    (setq evil-collection-company-use-tng nil)
    (evil-collection-init))

leader key

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

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

evil-snipe

  (use-package evil-snipe
    :after (evil)
    :config
    (evil-snipe-mode 1)
    (evil-snipe-override-mode 1)
    (with-eval-after-load 'magit
      (add-hook 'magit-mode-hook 'turn-off-evil-snipe-override-mode))
    (with-eval-after-load 'prodigy
      (add-hook 'prodigy-mode-hook 'turn-off-evil-snipe-mode))
    (with-eval-after-load 'pass
      (add-hook 'pass-mode-hook 'turn-off-evil-snipe-mode)))

evil-commentary

Adds Evil commands to comment out lines of code:

  (use-package evil-commentary
    :after (evil)
    :hook ((prog-mode . evil-commentary-mode)))

Additional keybindings

  (general-def 'normal "zM" #'hs-hide-level :keymaps 'override)
  (general-def 'normal "z=" #'text-scale-increase :keymaps 'override)
  (general-def 'normal "z-" #'text-scale-decrease :keymaps 'override)
  (general-def 'normal "z0" #'text-scale-adjust :keymaps 'override)
  (general-def 'normal view-mode-map "0" nil :keymaps 'override)
  (general-def 'normal prodigy-view-mode-map "0" nil :keymaps 'override)
  (general-def 'normal messages-buffer-mode-map "SPC" leader-map :keymaps 'override)

Multiple cursors

  (use-package evil-mc
    :defer 0
    :config
    (global-evil-mc-mode)
    (general-def '(normal visual)
      "gs" evil-mc-cursors-map
      "M-n" #'evil-mc-make-and-goto-next-cursor
      "M-p" #'evil-mc-make-and-goto-prev-cursor
      "C-n" #'evil-mc-make-and-goto-next-match
      "C-t" #'evil-mc-skip-and-goto-next-match
      "C-p" #'evil-mc-make-and-goto-prev-match))
  (use-package evil-multiedit
    :defer 0
    :config
    (evil-multiedit-default-keybinds))

Transient

A framework for creating Magit-style popups:

  (use-package transient
    :commands (define-transient-command))

Hydra

Hydras are convenient keybinding menus.

  (use-package hydra
    :defer t)

Magit

Magit is objectively the best Git interface.

  (use-package magit
    :commands (magit-status
               magit-blame
               magit-find-file
               magit-name-local-branch)
    :config
    (require 'evil-magit))
  (jdormit/define-prefix "g" "git")
  (leader-def-key "gs" #'magit-status)
  (leader-def-key "gg" #'magit-file-dispatch)
  (leader-def-key "gf" #'magit-find-file)

Use ido-mode for completion within Magit:

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

Forge

Forge is an extension for Magit that lets it interact with code forges (e.g. GitHub).

  (use-package forge
    :after (magit)
    :config
    (add-to-list 'forge-alist '("git.jeremydormitzer.com"
                                "git.jeremydormitzer.com/api/v1"
                                "git.jeremydormitzer.com"
                                forge-gitea-repository))
    :general
    ((normal motion) magit-mode-map "yu" #'forge-copy-url-at-point-as-kill)
    ((normal motion visual) forge-topic-list-mode-map
     "y" #'forge-copy-url-at-point-as-kill
     "q" #'quit-window))

evil-magit

Evil keybindings for magit!

  (use-package evil-magit
    :after (evil magit forge)
    :general
    ('normal magit-mode-map "SPC" leader-map))

Transient

  (setq transient-default-level 7)

git-link

Open files in Git forges (GitHub, GitLab, etc.):

  (use-package git-link
    :init
    (defun git-link-copy ()
      (interactive)
      (let ((git-link-open-in-browser nil))
        (call-interactively 'git-link)))
    (leader-def-key "gl" #'git-link)
    (leader-def-key "gy" #'git-link-copy)
    :custom
    (git-link-open-in-browser t)
    (git-link-remote-alist '(("git.sr.ht" git-link-sourcehut)
                             ("github" git-link-github)
                             ("bitbucket" git-link-bitbucket)
                             ("gitorious" git-link-gitorious)
                             ("gitlab" git-link-gitlab)
                             ("visualstudio\\|azure" git-link-azure)
                             ("git.jeremydormitzer.com" git-link-bitbucket))))

with-editor

A utility from the author of Magit to run shell commands using the current Emacs instance as $EDITOR.

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

Password Store

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

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

  (use-package password-store
    :defer t
    :if (executable-find "pass")
    :config
    (setq password-store-password-length 20)
    :init
    (leader-def-key "P" 'password-store-copy)
    (epa-file-enable))

  (use-package pass
    :if (executable-find "pass")
    :commands pass
    :general
    ('(normal motion visual) pass-mode-map "S" #'password-store-synchronize))

  (leader-def-key "ap" #'pass)

  (setq auth-sources '("~/.authinfo" password-store))

Emacs Lisp

Packages

Some helpful ELisp packages:

  • deferred is an async API library
  • s.el is a string manipulation library
  • dash.el is a list manipulation library
  • f.el is a file manipulation library
  • request is an HTTP library
  (use-package deferred
    :commands (deferred:next
                deferred:nextc
                deferred:error
                deferred:cancel
                deferred:watch
                deferred:wait
                deferred:$
                deferred:loop
                deferred:parallel
                deferred:earlier
                deferred:call
                deferred:apply
                deferred:process
                deferred:process-buffer
                deferred:wait-idle
                deferred:url-retrieve
                deferred:url-get
                deferred:url-post
                deferred:new
                deferred:succeed
                deferred:fail
                deferred:callback
                deferred:callback-post
                deferred:errorback
                deferred:errorback-post
                deferred:try
                deferred:timeout
                deferred:process))
  (use-package s
    :defer t
    :init
    (add-hook 'emacs-startup-hook (lambda () (require 's)))
    :config
    (defun camel-case (&optional arg)
      (interactive "P")
      (let* ((bounds (bounds-of-thing-at-point 'symbol))
             (word (buffer-substring (car bounds) (cdr bounds)))
             (camel (if arg (s-upper-camel-case word)
                      (s-lower-camel-case word))))
        (delete-region (car bounds) (cdr bounds))
        (insert camel))))
  (use-package dash
    :defer t
    :init
    (add-hook 'emacs-startup-hook (lambda () (require 'dash))))
  (use-package dash-functional
    :defer t
    :init
    (add-hook 'emacs-startup-hook (lambda () (require 'dash-functional))))
  (use-package f
    :defer t
    :init
    (add-hook 'emacs-startup-hook (lambda () (require 'f))))
  (use-package request
    :commands (request request-deferred))

Editing Elisp

  (general-def '(normal motion) emacs-lisp-mode-map "C-c C-c" #'eval-defun :keymaps 'override)
  (general-def '(normal motion insert) lisp-interaction-mode-map "C-c C-c" #'eval-print-last-sexp :keymaps 'override)
  (add-hook 'ielm-mode-hook 'smartparens-strict-mode)

Load path

For machine or user specific libraries:

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

And for global ones:

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

Utilities

Reading a file as a string:

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

Opening a file as sudo:

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

Recursive assoc for nested alists:

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

Format a millis timestamp into human-readable form:

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

The same but for seconds:

  (defun format-epoch-seconds (seconds)
     (interactive "nTimestamp: ")
     (message (format-time-string "%F %r" seconds)))

Checking if a buffer contains a string:

  (defun buffer-contains-substring (string)
    (save-excursion
      (save-match-data
	(goto-char (point-min))
	(search-forward string nil t))))

Pretty-print JSON:

  (defun pprint-json (raw-json)
    (with-temp-buffer
      (insert raw-json)
      (json-pretty-print (point-min) (point-max))
      (buffer-substring (point-min) (point-max))))

Load environment variables into Emacs from a shell script:

  (cl-defun extract-vars-from-env-file (file &key dir)
    "Extracts an alist of variable name to value from
     a bash script that exports environment variables."
    (let ((file (expand-file-name file))
	  (var-re "\\(.+?\\)=\\(.+\\)$")
	  (env '()))
      (with-temp-buffer
	(cd (expand-file-name (or dir (file-name-directory file))))
	(insert (shell-command-to-string (concat "source "
						 (shell-quote-argument file)
						 " > /dev/null && env")))
	(goto-char (point-min))
	(save-match-data
	  (while (re-search-forward var-re nil t)
	    (push (cons (match-string 1) (match-string 2)) env))))
      env))

  (defun source-env-file (file)
    (interactive "fFile: \n")
    (let ((env (extract-vars-from-env-file file)))
      (dolist (binding env)
	(setenv (car binding) (cdr binding)))))

  (cl-defmacro with-env-from-file (file &rest body)
    (declare (indent 1))
    (let ((env-var (make-symbol "the-env"))
	  (path-var (make-symbol "the-path")))
      `(let* ((,env-var (extract-vars-from-env-file ,file))
	      (,path-var (assoc "PATH" ,env-var))
	      (exec-path
	       (if ,path-var
		   (append (split-string (cdr ,path-var) ":") exec-path)
		 exec-path))
	      (process-environment
	       (append
		(mapcar
		 (lambda (elt) (format "%s=%s" (car elt) (cdr elt)))
		 ,env-var)
		process-environment)))
	 ,@body)))

  (cl-defun call-with-env-from-file (file callback &key dir)
    (let* ((env (extract-vars-from-env-file file :dir dir))
	   (path (assoc "PATH" env))
	   (exec-path
	    (if path
		(append (split-string (cdr path) ":") exec-path)
	      exec-path))
	   (process-environment
	    (append (mapcar (lambda (elt) (format "%s=%s" (car elt) (cdr elt))) env)
		    process-environment)))
      (funcall callback)))

  (defmacro with-env (env &rest body)
    (declare (indent 1))
    `(let* ((process-environment
	     (append
	      (mapcar
	       (lambda (elt) (format "%s=%s" (car elt) (cdr elt)))
	       ,env)
	      process-environment)))
       ,@body))

Convenience macro to run some code in a particular default-directory:

  (defmacro with-default-directory (dir &rest body)
    (declare (indent 1))
    `(let ((default-directory ,dir))
       ,@body))
  (defun random-alnum (&optional n)
    (let* ((n-chars (or n 1))
	   (alnum "abcdefghijklmnopqrstuvwxyz0123456789")
	   (result ""))
      (dotimes (_ n-chars result)
	(let ((i (% (abs (random)) (length alnum))))
	  (setq result
		(concat result (substring alnum i (1+ i))))))))

A handy function to colorize a buffer with ANSI escape characters in it:

  (defun ansi-color (&optional begin end)
    (interactive)
    (let ((begin (or begin (point-min)))
	  (end (or end (point-max)))
	  (inhibit-read-only t))
      (ansi-color-apply-on-region begin end)))

Persisting variables between session

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

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

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

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

This function retrieves a persisted variable:

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

And this function persists a variable:

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

Process handling

Some utilities for calling out to other processes.

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

A function to call a process passing some string as stdin and returning the process output:

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

The same function but for commands that need to run in a shell:

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

Running a shell command as sudo:

  (defun sudo-shell-command (command)
    (with-temp-buffer
      (cd "/sudo::/")
      (shell-command command)))

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

  (defvar buffer-mode-hooks '())

To add a new hook, push the code to run onto the correct list:

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

Whenever the buffer changes, look up the major-mode to see if there is any code to run:

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

Aliases

  (defalias 'doc 'describe-symbol)

Miscellaneous

  (setq warning-suppress-types
	'((undo discard-info)))

Dropbox

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

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

Load up libraries from Dropbox, if there are any:

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

Init File

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

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

And another one to edit it:

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

Keybindings

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

In some modes I want vanilla Emacs bindings:

  (with-eval-after-load 'evil
    (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 'cider-stacktrace-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))

Visual line navigation

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

M-x

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

Eval-ing

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

Init file commands

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

Commands about files

  (jdormit/define-prefix "f" "files")
  (leader-def-key "ff" 'find-file)
  (leader-def-key "fd" 'dired)
  (leader-def-key "fs" 'sudo-find-file)
  (leader-def-key "ft" 'auto-revert-tail-mode)
  (leader-def-key "fp" 'find-file-at-point)
  (general-def '(normal motion visual) "gf" 'find-file-at-point)

Window commands

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

Buffer commands

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

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

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

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

  (defun kill-other-buffers ()
    "Kill all other buffers."
    (interactive)
    (mapc 'kill-buffer (delq (current-buffer) (buffer-list))))
  (jdormit/define-prefix "b" "buffer")
  (leader-def-key "bb" #'switch-to-buffer)
  (leader-def-key "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)

Frame commands

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

Running shell commands

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

Toggles

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

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

Toggles about line truncation:

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

Toggle lisp debugging:

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

Shells/REPLs

Emacs has a shell for every mood!

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

Applications

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

Help Buffers

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

Code commands

  (jdormit/define-prefix "c" "code")
  (leader-def-key "cd" #'xref-find-definitions)
  (leader-def-key "cr" #'xref-find-references)
  (leader-def-key "ca" #'xref-find-apropos)
  (general-def 'normal "M-." #'xref-find-definitions)

Process list

  (general-def '(normal motion visual) process-menu-mode-map
    "d" #'process-menu-delete-process)

Evil-numbers

Handy functions to increment/decrement numbers at point:

  (use-package evil-numbers
    :straight (:host github :repo "janpath/evil-numbers")
    :general
    ((normal visual motion) "g+" 'evil-numbers/inc-at-pt)
    ((normal visual motion) "g-" 'evil-numbers/dec-at-pt)
    ((normal visual motion) "g C-+" 'evil-numbers/inc-at-pt-incremental)
    ((normal visual motion) "g C--" 'evil-numbers/dec-at-pt-incremental))

Winner

Winner is a minor mode that keeps an undo/redo history of the window configuration:

  (winner-mode 1)
  (leader-def-key "wn" #'winner-redo)
  (leader-def-key "wp" #'winner-undo)

Buffer move

A handy package to shift buffers between open windows:

  (use-package buffer-move
    :general
    ("C-S-j" #'buf-move-down)
    ("C-S-k" #'buf-move-up)
    ("C-S-h" #'buf-move-left)
    ("C-S-l" #'buf-move-right))

Info

  (use-package info
    :init
    (defhydra hydra-info (:color blue
			  :hint nil)
      "
  Info-mode:

    ^^_]_ forward  (next logical node)       ^^_l_ast (←)        _u_p (↑)                             _f_ollow reference       _T_OC
    ^^_[_ backward (prev logical node)       ^^_r_eturn (→)      _m_enu (↓) (C-u for new window)      _i_ndex                  _d_irectory
    ^^_n_ext (same level only)               ^^_H_istory         _g_oto (C-u for new window)          _,_ next index item      _c_opy node name
    ^^_p_rev (same level only)               _<_/_t_op           _b_eginning of buffer                virtual _I_ndex          _C_lone buffer
    regex _s_earch (_S_ case sensitive)      ^^_>_ final         _e_nd of buffer                      ^^                       _a_propos

    _1_ .. _9_ Pick first .. ninth item in the node's menu.

  "
      ("]"   Info-forward-node)
      ("["   Info-backward-node)
      ("n"   Info-next)
      ("p"   Info-prev)
      ("s"   Info-search)
      ("S"   Info-search-case-sensitively)

      ("l"   Info-history-back)
      ("r"   Info-history-forward)
      ("H"   Info-history)
      ("t"   Info-top-node)
      ("<"   Info-top-node)
      (">"   Info-final-node)

      ("u"   Info-up)
      ("^"   Info-up)
      ("m"   Info-menu)
      ("g"   Info-goto-node)
      ("b"   beginning-of-buffer)
      ("e"   end-of-buffer)

      ("f"   Info-follow-reference)
      ("i"   Info-index)
      (","   Info-index-next)
      ("I"   Info-virtual-index)

      ("T"   Info-toc)
      ("d"   Info-directory)
      ("c"   Info-copy-current-node-name)
      ("C"   clone-buffer)
      ("a"   info-apropos)

      ("1"   Info-nth-menu-item)
      ("2"   Info-nth-menu-item)
      ("3"   Info-nth-menu-item)
      ("4"   Info-nth-menu-item)
      ("5"   Info-nth-menu-item)
      ("6"   Info-nth-menu-item)
      ("7"   Info-nth-menu-item)
      ("8"   Info-nth-menu-item)
      ("9"   Info-nth-menu-item)

      ("?"   Info-summary "Info summary")
      ("h"   Info-help "Info help")
      ("q"   Info-exit "Info exit")
      ("C-g" nil "cancel" :color blue))
    :general
    (Info-mode-map "C-/" 'hydra-info/body))

xref

After I select an xref reference, I want the xref buffer closed:

  (defun xref-goto-xref-and-quit ()
    (interactive)
    (xref-goto-xref t))
  (general-def 'normal xref--xref-buffer-mode-map "RET"
    #'xref-goto-xref-and-quit :keymaps 'override)

Don't prompt for an identifier for these xref commands:

  (setq xref-prompt-for-identifier
	'(not xref-find-definitions
	      xref-find-definitions-other-window
	      xref-find-definitions-other-frame
	      xref-find-references))

Some keybindings:

  (general-def "M-r" #'xref-find-references)

IBuffer

  (use-package ibuffer
    :straight (:type built-in)
    :init
    (defhydra hydra-ibuffer-main (:color pink :hint nil)
      "
   ^Navigation^ | ^Mark^        | ^Actions^        | ^View^
  -^----------^-+-^----^--------+-^-------^--------+-^----^-------
    _k_:    ʌ   | _m_: mark     | _D_: delete      | _g_: refresh
   _RET_: visit | _u_: unmark   | _S_: save        | _s_: sort
    _j_:    v   | _*_: specific | _a_: all actions | _/_: filter
  -^----------^-+-^----^--------+-^-------^--------+-^----^-------
  "
      ("j" ibuffer-forward-line)
      ("RET" ibuffer-visit-buffer :color blue)
      ("k" ibuffer-backward-line)

      ("m" ibuffer-mark-forward)
      ("u" ibuffer-unmark-forward)
      ("*" hydra-ibuffer-mark/body :color blue)

      ("D" ibuffer-do-delete)
      ("S" ibuffer-do-save)
      ("a" hydra-ibuffer-action/body :color blue)

      ("g" ibuffer-update)
      ("s" hydra-ibuffer-sort/body :color blue)
      ("/" hydra-ibuffer-filter/body :color blue)

      ("o" ibuffer-visit-buffer-other-window "other window" :color blue)
      ("q" quit-window "quit ibuffer" :color blue)
      ("." nil "toggle hydra" :color blue))

    (defhydra hydra-ibuffer-mark (:color teal
				  :columns 5
				  :after-exit (hydra-ibuffer-main/body))
      "Mark"
      ("*" ibuffer-unmark-all "unmark all")
      ("M" ibuffer-mark-by-mode "mode")
      ("m" ibuffer-mark-modified-buffers "modified")
      ("u" ibuffer-mark-unsaved-buffers "unsaved")
      ("s" ibuffer-mark-special-buffers "special")
      ("r" ibuffer-mark-read-only-buffers "read-only")
      ("/" ibuffer-mark-dired-buffers "dired")
      ("e" ibuffer-mark-dissociated-buffers "dissociated")
      ("h" ibuffer-mark-help-buffers "help")
      ("z" ibuffer-mark-compressed-file-buffers "compressed")
      ("b" hydra-ibuffer-main/body "back" :color blue))

    (defhydra hydra-ibuffer-action (:color teal
				    :columns 4
				    :after-exit
				    (if (eq major-mode 'ibuffer-mode)
					(hydra-ibuffer-main/body)))
      "Action"
      ("A" ibuffer-do-view "view")
      ("E" ibuffer-do-eval "eval")
      ("F" ibuffer-do-shell-command-file "shell-command-file")
      ("I" ibuffer-do-query-replace-regexp "query-replace-regexp")
      ("H" ibuffer-do-view-other-frame "view-other-frame")
      ("N" ibuffer-do-shell-command-pipe-replace "shell-cmd-pipe-replace")
      ("M" ibuffer-do-toggle-modified "toggle-modified")
      ("O" ibuffer-do-occur "occur")
      ("P" ibuffer-do-print "print")
      ("Q" ibuffer-do-query-replace "query-replace")
      ("R" ibuffer-do-rename-uniquely "rename-uniquely")
      ("T" ibuffer-do-toggle-read-only "toggle-read-only")
      ("U" ibuffer-do-replace-regexp "replace-regexp")
      ("V" ibuffer-do-revert "revert")
      ("W" ibuffer-do-view-and-eval "view-and-eval")
      ("X" ibuffer-do-shell-command-pipe "shell-command-pipe")
      ("b" nil "back"))

    (defhydra hydra-ibuffer-sort (:color amaranth :columns 3)
      "Sort"
      ("i" ibuffer-invert-sorting "invert")
      ("a" ibuffer-do-sort-by-alphabetic "alphabetic")
      ("v" ibuffer-do-sort-by-recency "recently used")
      ("s" ibuffer-do-sort-by-size "size")
      ("f" ibuffer-do-sort-by-filename/process "filename")
      ("m" ibuffer-do-sort-by-major-mode "mode")
      ("b" hydra-ibuffer-main/body "back" :color blue))

    (defhydra hydra-ibuffer-filter (:color amaranth :columns 4)
      "Filter"
      ("m" ibuffer-filter-by-used-mode "mode")
      ("M" ibuffer-filter-by-derived-mode "derived mode")
      ("n" ibuffer-filter-by-name "name")
      ("c" ibuffer-filter-by-content "content")
      ("e" ibuffer-filter-by-predicate "predicate")
      ("f" ibuffer-filter-by-filename "filename")
      (">" ibuffer-filter-by-size-gt "size")
      ("<" ibuffer-filter-by-size-lt "size")
      ("/" ibuffer-filter-disable "disable")
      ("b" hydra-ibuffer-main/body "back" :color blue))
    :general
    ((normal motion visual insert emacs) ibuffer-mode-map "." 'hydra-ibuffer-main/body))

Speedbar

Speedbar is cool but having it open in a separate frame is annoying. This makes it open in a side window in the same frame:

  (use-package sr-speedbar
    :commands (sr-speedbar-toggle
	       sr-speedbar-open
	       sr-speedbar-select-window
	       sr-speedbar-exist-p)
    :general
    (speedbar-mode-map "q" #'sr-speedbar-close))

  (defun switch-to-speedbar ()
    (interactive)
    (unless (sr-speedbar-exist-p)
      (sr-speedbar-open))
    (sr-speedbar-select-window))

  (leader-def-key "S" #'switch-to-speedbar)

Whitespace Visualation

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

Line Numbers

Toggle line numbers:

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

Toggle line numbering mode (normal or relative):

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

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

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

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

Amx

A better M-x.

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

Olivetti Mode

Olivetti is a minor mode for a nice writing environment.

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

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

Winum

This package includes functions to switch windows by number.

  (defun winum-assign-0-to-neotree ()
    (when (string-match-p (buffer-name) ".*\\NeoTree\\*.*") 10))

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

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

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

NeoTree

A package to browse files in a tree view

  (use-package neotree
    :straight (neotree :host github :repo "jaypei/emacs-neotree" :branch "dev")
    :commands (neotree-project-dir neotree-toggle)
    :init
    (leader-def-key "d" 'neotree-project-dir)
    :general
    ('normal neotree-mode-map "SPC" leader-map)
    :config
    (defun neotree-project-dir ()
      "Open NeoTree using the git root."
      (interactive)
      (let ((project-dir (projectile-project-root))
	    (file-name (buffer-file-name))
	    (cw (selected-window)))
	(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.")))
	(select-window cw t)))
    (setq neo-smart-open nil
	  neo-force-change-root t
	  neo-show-hidden-files nil
	  neo-toggle-window-keep-p t
	  neo-theme (if (display-graphic-p) 'icons 'arrow)
	  neo-autorefresh t
	  projectile-switch-project-action 'neotree-project-action))

  (use-package all-the-icons
    :after (neotree))

And while we're here let's enable all-the-icons for dired as well:

  (use-package all-the-icons-dired
    :after (all-the-icons)
    :commands (all-the-icons-dired-mode)
    :config
    (add-hook 'dired-mode-hook #'all-the-icons-dired-mode))

Backups and Autosaves

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

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

Smartparens/Parinfer

Smartparens enables structured editing of s-expressions and other pairs:

  (use-package smartparens
    :hook ((prog-mode . smartparens-strict-mode))
    :init
    (defhydra hydra-smartparens (:hint nil)
      "
   Moving^^^^                       Slurp & Barf^^   Wrapping^^            Sexp juggling^^^^               Destructive
  ------------------------------------------------------------------------------------------------------------------------
   [_a_] beginning  [_n_] down      [_h_] bw slurp   [_R_]   rewrap        [_S_] split   [_t_] transpose   [_c_] change inner  [_w_] copy
   [_e_] end        [_N_] bw down   [_H_] bw barf    [_u_]   unwrap        [_s_] splice  [_A_] absorb      [_C_] change outer
   [_f_] forward    [_p_] up        [_l_] slurp      [_U_]   bw unwrap     [_r_] raise   [_E_] emit        [_k_] kill          [_g_] quit
   [_b_] backward   [_P_] bw up     [_L_] barf       [_(__{__[_] wrap (){}[]   [_j_] join    [_o_] convolute   [_K_] bw kill       [_q_] quit"
      ;; Moving
      ("a" sp-beginning-of-sexp)
      ("e" sp-end-of-sexp)
      ("f" sp-forward-sexp)
      ("b" sp-backward-sexp)
      ("n" sp-down-sexp)
      ("N" sp-backward-down-sexp)
      ("p" sp-up-sexp)
      ("P" sp-backward-up-sexp)

      ;; Slurping & barfing
      ("h" sp-backward-slurp-sexp)
      ("H" sp-backward-barf-sexp)
      ("l" sp-forward-slurp-sexp)
      ("L" sp-forward-barf-sexp)

      ;; Wrapping
      ("R" sp-rewrap-sexp)
      ("u" sp-unwrap-sexp)
      ("U" sp-backward-unwrap-sexp)
      ("(" sp-wrap-round)
      ("{" sp-wrap-curly)
      ("[" sp-wrap-square)

      ;; Sexp juggling
      ("S" sp-split-sexp)
      ("s" sp-splice-sexp)
      ("r" sp-raise-sexp)
      ("j" sp-join-sexp)
      ("t" sp-transpose-sexp)
      ("A" sp-absorb-sexp)
      ("E" sp-emit-sexp)
      ("o" sp-convolute-sexp)

      ;; Destructive editing
      ("c" sp-change-inner :exit t)
      ("C" sp-change-enclosing :exit t)
      ("k" sp-kill-sexp)
      ("K" sp-backward-kill-sexp)
      ("w" sp-copy-sexp)

      ("q" nil)
      ("g" nil))
    :config
    (require 'smartparens-config)
    :general
    (prog-mode-map "C-c p" 'hydra-smartparens/body)
    ((normal motion visual) prog-mode-map "g p" 'hydra-smartparens/body))

  (use-package evil-smartparens
    :after (evil smartparens)
    :hook ((smartparens-enabled . evil-smartparens-mode)))

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

Enable ES6 arrow functions in web-mode ("borrowed" from this GitHub comment):

  (with-eval-after-load 'smartparens
    (defun sp-after-equals-p (_id action _context)
      (when (memq action '(insert navigate))
	(sp--looking-back-p "=>" 2)))

    (defun sp-after-equals-skip (ms mb _me)
      (when (eq ms ">")
	(save-excursion
	  (goto-char mb)
	  (sp--looking-back-p "=" 1))))

    (sp-local-pair '(web-mode) "<" nil
		   :unless '(:add sp-after-equals-p)
		   :skip-match 'sp-after-equals-skip-p))

Parinfer infers parens from indentation and vice-versa. Currently disabled since it turned out to be more annoying than good…

  (use-package parinfer
    :disabled
    :init
    (leader-def-key "lt" 'parinfer-toggle-mode)
    (setq parinfer-extensions '(defaults
				 pretty-parens
				 evil
				 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)))

jq

The JSON multitool.

  (use-package jq-mode
    :commands (jq-mode jq-interactively))

Org Mode

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

  (jdormit/define-prefix "o" "org")
  (leader-def-key "oa" 'org-agenda)
  (leader-def-key "oc" 'org-capture)
  (setq org-src-fontify-natively t
        org-ellipsis " ▼"
        org-directory (concat (get-dropbox-directory) "/org")
        org-link-elisp-confirm-function 'y-or-n-p
        org-startup-with-inline-images t)
  (add-hook 'org-mode-hook #'auto-fill-mode)

Use RET to follow links:

  (setq org-return-follows-link t)

Always show inline images:

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

Include a timestamp when TODO entries are closed:

  (setq org-log-done 'time)

Tell Emacs how to open file links of various types:

  (with-eval-after-load 'org
    (add-to-list 'org-file-apps '(directory . emacs))
    (add-to-list 'org-file-apps '("log" . emacs)))

A function to add ids to every heading in an Org file:

  (defun org-get-create-ids-all ()
    (interactive)
    (save-excursion
      (goto-char (point-max))
      (while (outline-previous-heading)
	(org-id-get-create))))

Agenda files

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

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

Capture templates

  (setq org-capture-templates
	`(("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%?")
	  ("l" "Log" entry
	   (file ,(agenda-files "log.org"))
	   "* %<%Y-%m-%d %H:%M:%S>%?")))

Refile targets

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

Todo keywords

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

Agenda views

  (setq org-agenda-todo-ignore-scheduled 'future
	org-agenda-tags-todo-honor-ignore-options t
	org-agenda-span 'day
	org-agenda-custom-commands
	'(("L" "Lola" ((tags-todo "@lola")))
	  ("t" "TODOs"
	   ((agenda)
	    (alltodo)))))

Keybindings

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

Set up evil keybindings:

  (use-package evil-org
    :after (evil org)
    :hook (org-mode . evil-org-mode)
    :config
    (add-hook 'evil-org-mode-hook
              (lambda ()
                (evil-org-set-key-theme
                 '(textobjects
                   insert
                   navigation
                   additional
                   shift
                   todo))
                (general-def 'insert org-mode-map [backspace] 'org-delete-backward-char)))
    (require 'evil-org-agenda)
    (evil-org-agenda-set-keys)
    (general-def 'motion org-agenda-mode-map "SPC" leader-map)
    (general-def 'motion org-agenda-mode-map "gs" 'org-save-all-org-buffers)
    (general-def '(normal motion) evil-org-mode-map
      "C-S-j" nil
      "C-S-k" nil
      "C-S-h" nil
      "C-S-l" nil))

And a global keybinding to open an org file:

  (defun find-org-file (file)
    (interactive
     (list (completing-read
	    "Find org file: "
	    (directory-files (agenda-files) t))))
    (find-file file))

  (leader-def-key "fo" #'find-org-file)

Finally, add a helpful agenda-mode hydra:

  (with-eval-after-load 'org-agenda
    (defhydra hydra-org-agenda (:pre (setq which-key-inhibit t)
				:post (setq which-key-inhibit nil)
				:hint none)
      "
  Org agenda (_q_uit)

  ^Clock^      ^Visit entry^              ^Date^             ^Other^
  ^-----^----  ^-----------^------------  ^----^-----------  ^-----^---------
  _ci_ in      _SPC_ in other window      _ds_ schedule      _gr_ reload
  _co_ out     _TAB_ & go to location     _dd_ set deadline  _._  go to today
  _cq_ cancel  _RET_ & del other windows  _dt_ timestamp     _gd_ go to date
  _cj_ jump    _o_   link                 _+_  do later      ^^
  ^^           ^^                         _-_  do earlier    ^^
  ^^           ^^                         ^^                 ^^
  ^View^          ^Filter^                 ^Headline^         ^Toggle mode^
  ^----^--------  ^------^---------------  ^--------^-------  ^-----------^----
  _vd_ day        _ft_ by tag              _ht_ set status    _tf_ follow
  _vw_ week       _fr_ refine by tag       _hk_ kill          _tl_ log
  _vt_ fortnight  _fc_ by category         _hr_ refile        _ta_ archive trees
  _vm_ month      _fh_ by top headline     _hA_ archive       _tA_ archive files
  _vy_ year       _fx_ by regexp           _h:_ set tags      _tr_ clock report
  _vn_ next span  _fd_ delete all filters  _hp_ set priority  _td_ diaries
  _vp_ prev span  ^^                       ^^                 ^^
  _vr_ reset      ^^                       ^^                 ^^
  ^^              ^^                       ^^                 ^^
  "
      ;; Entry
      ("hA" org-agenda-archive-default)
      ("hk" org-agenda-kill)
      ("hp" org-agenda-priority)
      ("hr" org-agenda-refile)
      ("h:" org-agenda-set-tags)
      ("ht" org-agenda-todo)
      ;; Visit entry
      ("o"   link-hint-open-link :exit t)
      ("<tab>" org-agenda-goto :exit t)
      ("TAB" org-agenda-goto :exit t)
      ("SPC" org-agenda-show-and-scroll-up)
      ("RET" org-agenda-switch-to :exit t)
      ;; Date
      ("dt" org-agenda-date-prompt)
      ("dd" org-agenda-deadline)
      ("+" org-agenda-do-date-later)
      ("-" org-agenda-do-date-earlier)
      ("ds" org-agenda-schedule)
      ;; View
      ("vd" org-agenda-day-view)
      ("vw" org-agenda-week-view)
      ("vt" org-agenda-fortnight-view)
      ("vm" org-agenda-month-view)
      ("vy" org-agenda-year-view)
      ("vn" org-agenda-later)
      ("vp" org-agenda-earlier)
      ("vr" org-agenda-reset-view)
      ;; Toggle mode
      ("ta" org-agenda-archives-mode)
      ("tA" (org-agenda-archives-mode 'files))
      ("tr" org-agenda-clockreport-mode)
      ("tf" org-agenda-follow-mode)
      ("tl" org-agenda-log-mode)
      ("td" org-agenda-toggle-diary)
      ;; Filter
      ("fc" org-agenda-filter-by-category)
      ("fx" org-agenda-filter-by-regexp)
      ("ft" org-agenda-filter-by-tag)
      ("fr" org-agenda-filter-by-tag-refine)
      ("fh" org-agenda-filter-by-top-headline)
      ("fd" org-agenda-filter-remove-all)
      ;; Clock
      ("cq" org-agenda-clock-cancel)
      ("cj" org-agenda-clock-goto :exit t)
      ("ci" org-agenda-clock-in :exit t)
      ("co" org-agenda-clock-out)
      ;; Other
      ("q" nil :exit t)
      ("gd" org-agenda-goto-date)
      ("." org-agenda-goto-today)
      ("gr" org-agenda-redo))
    (general-def '(normal visual motion insert emacs) org-agenda-mode-map "." 'hydra-org-agenda/body))

Exporting

HTML

Export to HTML:

  (use-package htmlize
    :commands htmlize-buffer)

Don't put section numbers in front of headers:

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

Disable the preamble and postamble:

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

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

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

Github-flavored markdown

  (use-package ox-gfm
    :defer t
    :init
    (with-eval-after-load 'org
      (require 'ox-gfm)))

Jira

  (use-package ox-jira
    :defer t
    :straight (:host github :repo "stig/ox-jira.el")
    :init
    (with-eval-after-load 'org
      (require 'ox-jira)))

org-babel

Literate programming!

  (add-hook 'org-mode-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)
         (restclient . t)))))

Get rid of the confirmation prompt:

  (setq org-confirm-babel-evaluate nil)

Display inline images after executing a source block:

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

Enable async source block execution. Note: this execute the contents of the source block in a separate Emacs process, so blocks that rely on anything defined in init.org or the current Emacs process won't work as expected.

  (use-package ob-async
    :straight (ob-async :host github :repo "astahlman/ob-async")
    :defer t
    :hook (org-mode . (lambda () (require 'ob-async))))

Filter out the "u" from unicode results in org tabels returned from Python source blocks:

  (with-eval-after-load 'ob-python
    (defun org-babel-python-table-or-string (results)
      "Convert RESULTS into an appropriate elisp value.
  If the results look like a list or tuple, then convert them into an
  Emacs-lisp table, otherwise return the results as a string."
      (let ((res (org-babel-script-escape results)))
	(if (listp res)
	    (mapcar (lambda (el)
		      (cond
		       ((eq el 'None) org-babel-python-None-to)
		       ((listp el) (-filter (lambda (m) (not (eq m 'u))) el))
		       (t el)))
		    res)
	  res))))

Images

  (setq org-image-actual-width nil)

org-scratch

It's very useful to open a new org buffer for a quick org-babel exploration.

  (defun org-scratch (&optional make-new)
    "Switches to an org-mode buffer not associated with any file.
  If called with a prefix argument, always creates a new buffer;
  otherwise, switches to the existing *org-scratch* buffer if it exists."
    (interactive "P")
    (let ((org-scratch-buffer-name
	   (generate-new-buffer-name
	    "*org-scratch*"
	    (unless make-new "*org-scratch*"))))
      (switch-to-buffer org-scratch-buffer-name)
      (org-mode)))

  (leader-def-key "os" #'org-scratch)

org-gcal

Integrate Google calendar with org-mode:

  (defun get-calendar-file (name)
    (concat (file-name-as-directory (get-dropbox-directory))
	    "org/" name))

  (use-package org-gcal
    :after (org)
    :commands (org-gcal-sync
	       org-gcal-fetch
	       org-gcal-post-at-point
	       org-gcal-delete-at-point
	       org-gcal-refresh-token)
    :config
    (setq org-gcal-client-id (password-store-get "lola-org-gcal-client-id")
	  org-gcal-client-secret (password-store-get "lola-org-gcal-client-secret")
	  org-gcal-file-alist `(("jeremydormitzer@lola.com" . ,(get-calendar-file "lola-gcal.org"))
				("jeremy.dormitzer@gmail.com" . ,(get-calendar-file "personal-gcal.org"))
				("lut2o2moohg6qkdsto1qfq7th4@group.calendar.google.com" . ,(get-calendar-file "j-n-gcal.org")))
	  org-gcal-notify-p nil))

  (defun org-gcal-fetch-and-save ()
    (interactive)
    (deferred:$
      (org-gcal-fetch)
      (deferred:nextc it
	(lambda ()
	  (dolist (entry org-gcal-file-alist)
	    (with-current-buffer (cdr entry)
	      (save-buffer)))))))

  (add-hook 'emacs-startup-hook #'org-gcal-fetch-and-save)

  (defun org-agenda-redo-and-fetch-gcal (&optional all)
    (interactive "P")
    (let ((cb (if all #'org-agenda-redo-all #'org-agenda-redo)))
      (deferred:nextc (org-gcal-fetch-and-save) cb)))

  (with-eval-after-load 'org-agenda
   (general-def '(normal motion) org-agenda-mode-map "gR" #'org-agenda-redo-and-fetch-gcal))

Utilities

A function to get the TITLE property of the current org buffer:

  (defun org-get-title (&optional contents)
    (let ((raw (or contents
		   (buffer-substring (point-min) (point-max)))))
      (with-temp-buffer
	(insert raw)
	(car
	 (org-element-map
	     (org-element-parse-buffer)
	     'keyword
	   (lambda (el)
	     (when (string-match-p "TITLE"
				   (org-element-property :key el))
	       (org-element-property :value el))))))))

org-present

An ultra-minimalist presentation mode for Org:

  (use-package org-present
    :commands (org-present)
    :config
    (defun org-present-on ()
      (org-present-big)
      (org-display-inline-images)
      (org-present-hide-cursor)
      (display-line-numbers-mode -1)
      (org-present-read-only))
    (defun org-present-off ()
      (org-present-small)
      (org-present-show-cursor)
      (unless org-startup-with-inline-images
       (org-remove-inline-images))
      (display-line-numbers-mode)
      (org-present-read-write))
    (add-hook 'org-present-mode-hook #'org-present-on)
    (add-hook 'org-present-mode-quit-hook #'org-present-off)
    ;; Redefine org-present to call org-present-narrow after running hooks
    (defun org-present ()
      "init."
      (interactive)
      (setq org-present-mode t)
      (org-present-add-overlays)
      (run-hooks 'org-present-mode-hook)
      (org-present-narrow)
      (org-present-run-after-navigate-functions))
    :init
    (general-def '(normal visual motion emacs) org-present-mode-keymap
     "<left>" #'org-present-prev
     "<right>" #'org-present-next
     "C-k" #'org-present-prev
     "C-j" #'org-present-next
     "q" #'org-present-quit))

link-hint

A very helpful package that provides jump-to-link functionality:

  (use-package link-hint
    :commands (link-hint-open-link
	       link-hint-copy-link)
    :init
    (jdormit/define-prefix "ol" "link-hint")
    (leader-def-key "oll" #'link-hint-open-link)
    (leader-def-key "olc" #'link-hint-copy-link))

org-cliplink

Intelligently inserts an org-mode link from the clipboard.

  (use-package org-cliplink
    :commands (org-cliplink
	       org-cliplink-clipboard-content)
    :general
    (org-mode-map "C-c C-S-l" #'org-cliplink))

org-board

Org-board is a bookmarking/archiving system built on Org mode:

  (use-package org-board
    :defer t
    :after org
    :init

    ;; Org-capture setup

    (defvar org-capture-bookmark-last-url nil)
    (defvar org-capture-bookmark-last-title nil)

    (defun org-capture-bookmark-get-url ()
      (let* ((clip (org-cliplink-clipboard-content))
	     (parsed (url-generic-parse-url clip)))
	(if (url-type parsed)
	    clip
	  (read-string "Bookmark URL: "))))

    (defun org-capture-bookmark-get-title (url)
      (or (org-cliplink-retrieve-title-synchronously url)
	  (read-string "Bookmark title: ")))

    (defun bookmark-file (title)
      (concat (get-dropbox-directory)
	      (format "/org/%s.org" (org-roam--get-new-id title))))

    (defun org-capture-bookmark-file ()
      (let* ((url (org-capture-bookmark-get-url))
	     (title (org-capture-bookmark-get-title url)))
	(setq org-capture-bookmark-last-url url)
	(setq org-capture-bookmark-last-title title)
	(bookmark-file title)))

    (defun org-capture-bookmark-link ()
      (format "[[%s][%s]]"
	      org-capture-bookmark-last-url
	      org-capture-bookmark-last-title))

    (defun org-capture-bookmark-title ()
      org-capture-bookmark-last-title)

    (defun save-bookmark (url)
      (save-excursion
	(goto-char (point-min))
	(when (search-forward "* Bookmark" nil t)
	  (org-board-new url)
	  (wallabag-add-entry url
			      (cl-function
			       (lambda (&key data &allow-other-keys)
				 (let ((entry-url (format "%s/view/%s"
							  wallabag-base-url
							  (alist-get 'id data))))
				   (message "Added bookmark to Wallabag: %s" entry-url))))))))

    (defun org-capture-bookmark-after-finalize ()
      "Runs `org-board-new' on the captured entry.
    Also saves to Wallabag."
      (let ((success (not org-note-abort))
	    (key (plist-get org-capture-plist :key))
	    (desc (plist-get org-capture-plist :description)))
	(when (and success
		   (equal key "b")
		   (equal desc "Bookmark")
		   org-capture-bookmark-last-url)
	  (save-bookmark org-capture-bookmark-last-url)
	  (setq org-capture-bookmark-last-url nil)
	  (setq org-capture-bookmark-last-title nil))))

    (add-hook 'org-capture-prepare-finalize-hook
	      #'org-capture-bookmark-after-finalize)

    (add-to-list 'org-capture-templates
		 '("b" "Bookmark" plain
		   (file org-capture-bookmark-file)
		   "#+TITLE: %(org-capture-bookmark-title)\n\n- tags :: [[file:deft/bookmarks.org][Bookmarks]]\n- source :: %(org-capture-bookmark-link)\n%?\n* Bookmark"))

    ;; Org-protocol setup
    (defun make-org-protocol-bookmark (url title)
      (with-temp-buffer
	(let ((filename (bookmark-file title)))
	  (save-excursion
	    (insert (concat (format "#+TITLE: %s\n\n" title)
			    "- tags :: [[file:deft/bookmarks.org][Bookmarks]]\n"
			    (format "- source :: [[%s][%s]]\n\n" url title)
			    "* Bookmark"))
	    (write-file filename)
	    (save-bookmark url)
	    (save-buffer)))))

    (defun bookmark-via-org-protocol (url)
      (org-cliplink-retrieve-title (url-unhex-string url) #'make-org-protocol-bookmark))

    (add-to-list 'org-protocol-protocol-alist
		 '("Bookmark"
		   :protocol "bookmark"
		   :function bookmark-via-org-protocol
		   :kill-client t))

    :config
    (add-to-list 'org-board-wget-switches "--recursive")
    (add-to-list 'org-board-wget-switches "--level=1")
    (add-to-list 'org-board-wget-switches "--span-hosts")
    ;; Use w3m instead of eww to open org-board archived links
    (advice-add 'org-board-open-with :around
		(lambda (oldfn filename-string arg &rest args)
		  (cond
		   ((not (file-exists-p filename-string)) 1)
		   ((and filename-string
			 (or (and arg (eq org-board-default-browser 'system))
			     (and (not arg) (eq org-board-default-browser 'eww))))
		    (let ((filename (concat "file://"
					    (s-chop-prefix "file://"
							   filename-string))))
		      (w3m filename t)
		      0))
		   (:else (apply oldfn filename-string arg args)))))
    :general
    (org-mode-map "C-c b" org-board-keymap))

org-rifle

Quickly find stuff in Org files:

  (use-package helm-org-rifle
    :defer t
    :init
    (defvar helm-org-rifle-commands-map (make-sparse-keymap))
    (general-def helm-org-rifle-commands-map
      "r" #'helm-org-rifle
      "a" #'helm-org-rifle-agenda-files
      "b" #'helm-org-rifle-current-buffer
      "d" #'helm-org-rifle-directories
      "f" #'helm-org-rifle-files
      "o" #'helm-org-rifle-org-directory
      "R" #'helm-org-rifle-occur
      "A" #'helm-org-rifle-occur-agenda-files
      "B" #'helm-org-rifle-occur-current-buffer
      "D" #'helm-org-rifle-occur-directories
      "F" #'helm-org-rifle-occur-files
      "O" #'helm-org-rifle-occur-org-directory)
    (leader-def-key "or" helm-org-rifle-commands-map)
    (jdormit/define-prefix "or" "org-rifle"))

Org Noter

Org Noter lets me take org-mode notes on PDFs, epubs, and DocView files:

  (use-package org-noter
    :commands org-noter
    :general
    ((normal visual motion) org-noter-doc-mode-map "i" #'org-noter-insert-note)
    ((normal visual motion) org-noter-doc-mode-map "q" #'org-noter-kill-session)
    ((normal visual motion) org-noter-doc-mode-map "C-M-n" #'org-noter-sync-next-note)
    ((normal visual motion) org-noter-doc-mode-map "C-M-p" #'org-noter-sync-prev-note)
    ((normal visual motion) org-noter-doc-mode-map "M-." #'org-noter-sync-current-page-or-chapter)
    ((normal visual motion) org-noter-doc-mode-map "M-i" #'org-noter-insert-precise-note)
    ((normal visual motion) org-noter-doc-mode-map "M-n" #'org-noter-sync-next-page-or-chapter)
    ((normal visual motion) org-noter-doc-mode-map "M-p" #'org-noter-sync-prev-page-or-chapter)
    ((normal visual motion) org-noter-doc-mode-map "C-M-." #'org-noter-sync-current-note))

Org Roam

Org-roam is another backlink package for org-mode:

  (use-package org-roam
    :after org
    :straight (:host github :repo "jethrokuan/org-roam")
    :commands
    (org-roam
     org-roam-today
     org-roam-find-file
     org-roam-insert
     org-roam-show-graph
     org-roam--get-new-id)
    :hook
    (after-init . org-roam-mode)
    :custom
    (org-roam-directory (concat (get-dropbox-directory) "/org"))
    :init
    (leader-def-key "fo" #'org-roam-find-file)
    (leader-def-key "of" #'org-roam-find-file)
    (defvar org-roam-map (make-sparse-keymap))
    (leader-def-key "on" org-roam-map)
    (jdormit/define-prefix "on" "org-roam")
    (which-key-add-key-based-replacements "C-c n" "org-roam")
    (which-key-add-major-mode-key-based-replacements
      'org-mode "gn" "org-roam")
    :config
    (add-hook 'org-roam-backlinks-mode-hook #'olivetti-mode)
    :general
    (org-roam-map "l" #'org-roam)
    (org-roam-map "t" #'org-roam-today)
    (org-roam-map "f" #'org-roam-find-file)
    (org-roam-map "i" #'org-roam-insert)
    (org-roam-map "g" #'org-roam-show-graph)
    ((normal motion visual) org-mode-map "gr" org-roam-map)
    ((normal motion visual) org-roam-backlinks-mode-map "<return>" #'org-open-at-point)
    ((normal motion visual emacs) org-roam-backlinks-mode-map "q" #'quit-window)
    ("C-c n" org-roam-map))

org-journal

org-journal is a package that provides some convenience functions around keeping a daily Org-mode journal. I also set it up so it plays nice with Org-roam:

  (use-package org-journal
    :defer t
    :init
    (defun org-journal-file-header-func ()
      (let ()
	(format "#+TITLE: %s"
		(format-time-string "%Y-%m-%d"))))
    (defun org-journal-today ()
      (interactive)
      (org-journal-new-entry t))
    (defun org-journal-capture-func ()
      (org-journal-new-entry t)
      (goto-char (point-min))
      ;; Account for the #+TITLE
      (forward-line))
    (add-to-list 'org-capture-templates
		 '("j" "Journal entry" entry (function org-journal-capture-func)
		   "* %(format-time-string org-journal-time-format)\n%?"))
    (jdormit/define-prefix "oj" "org-journal")
    (leader-def-key "ojn" #'org-journal-new-entry)
    (leader-def-key "ojt" #'org-journal-today)
    :custom
    (org-journal-file-type 'daily)
    (org-journal-dir (concat (get-dropbox-directory) "/org"))
    (org-journal-file-format "%Y-%m-%d.org")
    (org-journal-file-header 'org-journal-file-header-func)
    (org-journal-carryover-items "")
    :general
    (org-roam-map "t" 'org-journal-today))

org-super-agenda

  (use-package org-super-agenda
    :init
    (setq org-super-agenda-groups
	  '((:name "Today"
		   :tag "today")
	    (:name "Lola"
		   :tag "@lola")
	    (:name "unifyDB"
		   :tag "@unifydb")
	    (:name "Personal"
		   :tag "@personal")))
    (org-super-agenda-mode)
    :config
    (setq org-super-agenda-header-map (make-sparse-keymap)))

Projectile

  (use-package projectile
    :commands (projectile-find-file
	       projectile-grep
	       projectile-switch-project
	       projectile-project-root)
    :init
    (defhydra hydra-projectile (:color teal :hint nil) "
    PROJECTILE: %(projectile-project-root)

       Find File Search/Tags Buffers Cache
  ------------------------------------------------------------------------------------------
  _s-f_: file _a_: ag _i_: Ibuffer _c_: cache clear _ff_: file dwim
   _g_: update gtags _b_: switch to buffer _x_: remove known project
   _fd_: file curr dir _o_: multi-occur _s-k_: Kill all buffers _X_:
   cleanup non-existing _r_: recent file ^^^^_z_: cache current _d_:
   dir

  "
      ("a" projectile-ag)
      ("b" projectile-switch-to-buffer)
      ("c" projectile-invalidate-cache)
      ("d" projectile-find-dir)
      ("s-f" projectile-find-file)
      ("ff" projectile-find-file-dwim)
      ("fd" projectile-find-file-in-directory)
      ("g" ggtags-update-tags)
      ("s-g" ggtags-update-tags)
      ("i" projectile-ibuffer)
      ("K" projectile-kill-buffers)
      ("s-k" projectile-kill-buffers)
      ("m" projectile-multi-occur)
      ("o" projectile-multi-occur)
      ("s-p" projectile-switch-project "switch project")
      ("p" projectile-switch-project)
      ("s" projectile-switch-project)
      ("r" projectile-recentf)
      ("x" projectile-remove-known-project)
      ("X" projectile-cleanup-known-projects)
      ("z" projectile-cache-current-file)
      ("`" hydra-projectile-other-window/body "other window")
      ("q" nil "cancel" :color blue))
    :config
    (projectile-mode)
    (jdormit/define-prefix "p" "projectile")
    (leader-def-key "p" projectile-command-map))

  (defmacro with-projectile-root (&rest body)
    `(with-temp-buffer
       (when (projectile-project-root)
	 (cd (projectile-project-root)))
       ,@body))

Mode line

UI

Get rid of the janky buttons:

  (tool-bar-mode -1)

And the menu bar:

  (menu-bar-mode -1)

And the ugly scroll bars:

  (set-scroll-bar-mode nil)

Use variable-pitch-mode in text modes:

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

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

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

Display the column number in programming modes:

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

Disable the bell:

  (setq ring-bell-function 'ignore)

Render stuff differently based on whether or not we are graphical:

  (defun graphical-setup ()
    (when (display-graphic-p (selected-frame))
      (message "Running graphically")))

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

Try to make the background normal colored in the terminal:

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

UI-related keybindings:

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

Centaur tabs

Centaur tabs is a package that gives Emacs buffer tabs similar to those in Atom or VS Code:

  (use-package centaur-tabs
    :commands (centaur-tabs-mode
	       centaur-tabs-local-mode
	       centaur-tabs-mode-on-p)
    :init
    (setq centaur-tabs-set-icons t
	  centaur-tabs-gray-out-icons 'buffer
	  centaur-tabs-height 30
	  centaur-tabs-set-bar 'under
	  x-underline-at-descent-line t
	  centaur-tabs-set-modified-marker t
	  centaur-tabs-show-navigation-buttons t
	  centaur-tabs-down-tab-text " ☰ "
	  centaur-tabs-backward-tab-text " ◀ "
	  centaur-tabs-forward-tab-text " ▶ "
	  centaur-tabs-close-button "✕"
	  centaur-tabs-modified-marker "⬤"
	  centaur-tabs-cycle-scope 'tabs
	  centaur-tabs-label-fixed-length 20)
    (leader-def-key "uT" #'centaur-tabs-mode)
    (centaur-tabs-mode)
    :config
    (centaur-tabs-group-by-projectile-project)

    ;; Custom buffer groups
    (defun centaur-tabs-projectile-buffer-groups ()
    "Return the list of group names BUFFER belongs to."
    (if centaur-tabs-projectile-buffer-group-calc
        (symbol-value 'centaur-tabs-projectile-buffer-group-calc)
      (set (make-local-variable 'centaur-tabs-projectile-buffer-group-calc)

           (cond
            ((or (get-buffer-process (current-buffer))
                 (memq major-mode '(comint-mode compilation-mode))) '("Term"))
            ((string-equal "*" (substring (buffer-name) 0 1)) '("Misc"))
            ((condition-case _err
                 (projectile-project-root)
               (error nil)) (list (projectile-project-name)))
            ((memq major-mode '(emacs-lisp-mode python-mode emacs-lisp-mode c-mode
                                                c++-mode javascript-mode js-mode
                                                js2-mode makefile-mode
                                                lua-mode vala-mode)) '("Coding"))
            ((memq major-mode '(nxhtml-mode html-mode
                                            mhtml-mode css-mode)) '("HTML"))
            ((memq major-mode '(org-journal-mode)) '("Journal"))
            ((memq major-mode '(org-mode calendar-mode diary-mode)) '("Org"))
            ((memq major-mode '(dired-mode)) '("Dir"))
            (t '("Other"))))
      (symbol-value 'centaur-tabs-projectile-buffer-group-calc)))

    ;; Don't show tabs for certain types of buffers
    (advice-add 'centaur-tabs-hide-tab :around
                (lambda (oldfn buf &rest args)
                  (if (with-current-buffer buf
                        (eq major-mode 'vuiet-mode))
                      t
                    (apply oldfn buf args))))

    ;; Only show tabs in buffers visiting files
    (advice-add 'centaur-tabs-line :around
                (lambda (oldfn &rest args)
                  (if (buffer-file-name)
                      (apply oldfn args)
                    (setq header-line-format nil))))

    ;; Enable prefix argument for tab switching keybindings
    (advice-add 'centaur-tabs-forward :around
                (lambda (oldfn &rest args)
                  (if (numberp current-prefix-arg)
                      (dotimes (_ current-prefix-arg)
                        (apply oldfn args))
                    (apply oldfn args))))
    (advice-add 'centaur-tabs-backward :around
                (lambda (oldfn &rest args)
                  (if (numberp current-prefix-arg)
                      (dotimes (_ current-prefix-arg)
                        (apply oldfn args))
                    (apply oldfn args))))

    ;; Use Org-mode titles for tab names when possible
    (advice-add 'centaur-tabs-buffer-tab-label :around
                (lambda (oldfn tab &rest args)
                  (if-let ((title (or (car
                                       (org-roam-db--get-titles
                                        (buffer-file-name (car tab))))
                                   (org-get-title
                                    (with-current-buffer (car tab)
                                      (buffer-substring (point-min)
                                                        (min (point-max) 200)))))))
                      (if (> centaur-tabs-label-fixed-length 0)
                          (centaur-tabs-truncate-string centaur-tabs-label-fixed-length
                                                        (format " %s" title))
                        (format " %s" title))
                    (apply oldfn tab args))))

    ;; Add a cache to speed up icon rendering for huge groups
    (defvar centaur-tabs-icon-cache (make-hash-table :test 'equal)
      "A cache holding icons generated for centaur-tabs mode tabs.")
    (advice-add 'centaur-tabs-icon :around
                (lambda (oldfn tab face selected &rest args)
                  (let ((key (list tab face selected)))
                    (or (gethash key centaur-tabs-icon-cache)
                        (puthash key
                                 (apply oldfn tab face selected args)
                                 centaur-tabs-icon-cache)))))

    :general
    ((normal motion visual) "g t" #'centaur-tabs-forward)
    ((normal motion visual) "g T" #'centaur-tabs-backward)
    (leader-map "pt" #'centaur-tabs-counsel-switch-group)
    :hook
    (git-commit-mode . (lambda ()
                         (when (centaur-tabs-mode-on-p)
                           (centaur-tabs-local-mode)))))

Frame parameters

Functions to change the frame size:

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

Keybindings:

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

Transpose Frame

A handy utility that reverse the current frame window split (vertical to horizontal or vice-versa):

  (use-package transpose-frame
    :defer t
    :init
    (leader-def-key "wt" 'transpose-frame))

EShell

Easy keybinding to open EShell:

  (defun open-eshell (&optional arg)
    (interactive "P")
    (if (and (fboundp 'projectile-project-root)
	     (projectile-project-root))
	(projectile-run-eshell arg)
      (eshell arg)))

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

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

  (setq eshell-cmpl-cycle-completions nil)

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

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

Visual programs:

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

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

And a function to run any program visually:

  (defun eshell/v (&rest args)
    (apply #'eshell-exec-visual args))

Load .dir-locals.el when switching directories:

  (add-hook 'eshell-mode-hook #'hack-dir-local-variables-non-file-buffer)
  (add-hook 'eshell-after-prompt-hook #'hack-dir-local-variables-non-file-buffer)

A function to properly clear the eshell:

  (defun clear-eshell (&optional prefix)
    (interactive)
    (let ((input (eshell-get-old-input)))
      (eshell/clear-scrollback)
      (eshell-emit-prompt)
      (insert input)))

  (add-hook 'eshell-mode-hook
   (lambda () (general-def eshell-mode-map "C-c C-o" #'clear-eshell)))

Some aliases:

  (setq eshell-aliases
    '(("k" . "kubectl $*")
      ("kctx" . "kubectx $*")
      ("root" . "cd (projectile-project-root)")))
  (add-hook
   'eshell-mode-hook
   (lambda ()
     (dolist (alias eshell-aliases)
       (eshell/alias (car alias) (cdr alias)))))

Prompt

  (defun jdormit-eshell-prompt ()
    (let ((branch (magit-name-local-branch "HEAD")))
     (format "%s%s"
       (if branch (format "(%s) " branch) "")
       (concat (abbreviate-file-name (eshell/pwd))
	 (if (= (user-uid) 0) " # " " $ ")))))

  (setq jdormit-eshell-prompt-regex "^[^#$\n]* [#$] ")

  (setq eshell-prompt-function 'jdormit-eshell-prompt)
  (setq eshell-prompt-regexp jdormit-eshell-prompt-regex)

Flycheck

Syntax checking etc.:

  (use-package flycheck
    :init
    (defhydra hydra-flycheck
      (:pre (flycheck-list-errors)
	    :post (quit-windows-on "*Flycheck errors*")
	    :hint nil)
      "Errors"
      ("f" flycheck-error-list-set-filter "Filter")
      ("j" flycheck-next-error "Next")
      ("k" flycheck-previous-error "Previous")
      ("gg" flycheck-first-error "First")
      ("G" (progn (goto-char (point-max)) (flycheck-previous-error)) "Last")
      ("q" nil))
    :config
    (setq-default flycheck-disabled-checkers '(emacs-lisp emacs-lisp-checkdoc))
    (global-flycheck-mode)
    :general
    ((normal motion visual) flycheck-mode-map "ze" 'hydra-flycheck/body))

Tabs

  (defun disable-tab-insert ()
    (interactive)
    (setq indent-tabs-mode nil))

JSON

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

  (defun json-pprint ()
    (interactive)
    (let ((begin (if (region-active-p) (region-beginning) (point-min)))
	  (end (if (region-active-p) (region-end) (point-max))))
      (if (executable-find "jq")
	  (shell-command-on-region begin end "jq ." nil t)
	(json-pretty-print begin end))))

  (general-def json-mode-map "C-M-\\" 'json-pprint)

JavaScript

Some formatting stuff:

  (setq js-indent-level 4)
  (use-package web-mode
    :mode (("\\.html\\'" . web-mode)
	   ("\\.js\\'" . web-mode)
	   ("\\.jsx\\'" . web-mode)
	   ("\\.mako\\'" . web-mode)
	   ("\\.jinja2\\'" . web-mode)
	   ("\\.hbs\\'" . web-mode))
    :config
    (setq web-mode-engines-alist
	  '(("django" . "\\.jinja2\\'")))
    (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-deferred))))
    (add-hook 'web-mode-hook #'disable-tab-insert))

Use nvm to manage node versions:

  (use-package nvm
    :straight ((nvm :host github :repo "rejeep/nvm.el"))
    :commands (nvm-use nvm-use-for nvm-use-for-buffer))

A command to format JS via prettier:

  (defun prettier ()
    (interactive)
    (let ((start (if (use-region-p) (region-beginning) (point-min)))
	  (end (if (use-region-p) (region-end) (point-max)))
	  (parser (cond
		   ((eq major-mode 'graphql-mode) "graphql")
		   (t "babel"))))
      (shell-command-on-region start end (concat "prettier --parser " parser) nil t)))

NVM

Manage node version via NVM within Emacs:

  (use-package nvm
    :commands (nvm-use
	       nvm-use-for
	       nvm-use-for-buffer
	       nvm--installed-versions)
    :init
    (defun nvm (version)
      (interactive (list
		    (completing-read "Node version: "
		     (mapcar #'car
			     (nvm--installed-versions)))))
      (nvm-use version)))

Java

  (use-package lsp-java
    :hook ((java-mode . lsp-deferred)))

Kotlin

  (use-package kotlin-mode
    :mode (("\\.kt\\'" . kotlin-mode))
    :config
    (with-eval-after-load 'lsp
      (when (executable-find "kotlin-language-server")
	(add-hook 'kotlin-mode-hook #'lsp-deferred))))

Groovy

Used for Jenkins configuration scripts and probably other things.

  (use-package groovy-mode
    :commands (groovy-mode)
    :mode (("\\.groovy\\'" . groovy-mode))
    :init
    (add-to-list 'interpreter-mode-alist
		 '("groovy" . groovy-mode))
    :custom
    (groovy-indent-offset 2))

Typescript

  (use-package typescript-mode
    :mode ("\\.ts\\'")
    :config
    (with-eval-after-load 'lsp
      (add-hook 'typescript-mode-hook 'lsp-deferred)))

LSP Mode

Emacs support for the Language Server Protocol

  (use-package lsp-mode
    :defer t
    :init
    (defhydra hydra-lsp (:exit t :hint nil)
      "
   Buffer^^               Server^^                   Symbol
  -------------------------------------------------------------------------------------
   [_f_] format           [_M-r_] restart            [_d_] declaration  [_i_] implementation  [_o_] documentation
   [_m_] imenu            [_S_]   shutdown           [_D_] definition   [_t_] type            [_r_] rename
   [_x_] execute action   [_M-s_] describe session   [_R_] references   [_s_] signature"
      ("d" lsp-find-declaration)
      ("D" lsp-ui-peek-find-definitions)
      ("R" lsp-ui-peek-find-references)
      ("i" lsp-ui-peek-find-implementation)
      ("t" lsp-find-type-definition)
      ("s" lsp-signature-help)
      ("o" lsp-describe-thing-at-point)
      ("r" lsp-rename)

      ("f" lsp-format-buffer)
      ("m" lsp-ui-imenu)
      ("x" lsp-execute-code-action)

      ("M-s" lsp-describe-session)
      ("M-r" lsp-restart-workspace)
      ("S" lsp-shutdown-workspace))
    :general
    (lsp-mode-map "C-c h" 'hydra-lsp/body)
    ((normal visual motion) lsp-mode-map "K" #'lsp-describe-thing-at-point)
    ((normal visual motion) "gl" lsp-command-map)
    :hook
    ((lsp-mode . (lambda ()
                   (let ((lsp-keymap-prefix "gl"))
                     (lsp-enable-which-key-integration)))))
    :config
    (setq lsp-prefer-flymake nil)
    :commands lsp-mode lsp lsp-deferred
    :custom
    (lsp-enable-snippet nil)
    (lsp-eldoc-render-all nil))

  (use-package lsp-ui
    :after (lsp-mode)
    :custom
    (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)
    (lsp-ui-doc-alignment 'window)
    (lsp-ui-doc-header t)
    (lsp-ui-doc-position 'top)
    (lsp-ui-doc-background '((t (:inherit region))))
    (lsp-ui-doc-header '((t (:inherit lsp-face-highlight-write))))
    (lsp-ui-sideline-current-symbol '((t (:inherit font-lock-constant-face
                                                   :weight ultra-bold)))))

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

Python

General

  (leader-def-key "sp" #'run-python)
  (add-hook 'python-mode-hook #'disable-tab-insert)

ISort is a Python utility to sort imports:

  (use-package py-isort
    :commands (py-isort-buffer py-isort-region)
    :config
    (setq py-isort-options '("-m=3"))
    :general
    (python-mode-map "C-c C-i" #'py-isort-buffer))

Run black on the current buffer:

  (general-def 'normal python-mode-map "C-M-\\" #'format-all-buffer)

sphinx-doc.el automatically generates doc strings for Python functions (and updates existing ones!):

  (use-package sphinx-doc
    :hook ((python-mode . sphinx-doc-mode)))

Dev environment/IDE stuff

Support pyvenv within Emacs:

  (use-package pyvenv
    :defer 0
    :commands (pyvenv-mode
               pyvenv-tracking-mode
               pyvenv-workon
               pyvenv-activate
               pyvenv-track-virtualenv)
    :config
    (pyvenv-mode
     pyvenv-tracking-mode))

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

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

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

And support pyenv (NOT pyvenv) to change Python versions:

  (use-package pyenv-mode
    :defer t)

Use the LSP python client:

  (defun python-lsp ()
    (when (derived-mode-p 'python-mode)
      (lsp-deferred)))

  (add-hook 'hack-local-variables-hook #'python-lsp)

  (general-def 'normal python-mode-map "C-c C-d" #'lsp-describe-thing-at-point)

Autoflake

Autoflake is a tool that removes unused imports and variables from Python code:

  (defvar autoflake-args '()
    "Arguments to pass to the autoflake command.

  See URL `https://pypi.org/project/autoflake' for options.")

  (defun autoflake (arg)
    (interactive "P")
    (let ((autoflake-cmd (or (executable-find "autoflake")
			     (error "Autoflake executable not found")))
	  (file (cond
		 ((or arg (not buffer-file-name))
		  (read-file-name "Run autoflake on file: "))
		 (buffer-file-name buffer-file-name)
		 (:else (error "Invalid file for autoflake")))))
      (apply #'call-process autoflake-cmd nil nil nil
	     (append autoflake-args (list "--in-place" file)))))

Testing

python-pytest.el integrates Pytest with Emacs:

  (use-package  python-pytest
    :commands (python-pytest-popup
	       python-pytest--current-defun)
    :general
    (python-mode-map "C-c t p" #'python-pytest-popup)
    ((normal motion visual) python-pytest-mode-map "g r" #'python-pytest-repeat)
    ((normal motion visual) python-pytest-mode-map "q" #'quit-window))

And borrowing some functions from python-pytest, we can get some nosetests support as well:

  (defvar nosetests-args ""
    "Additional args to pass to nosetests")

  (defun run-nose (args &optional debug)
    "Runs nosetests with `args'

  If `debug' is non-nil, run in a GUD PDB session."
    (let* ((nosetests-cmd (executable-find "nosetests"))
	   (cmdline (format "%s %s" nosetests-cmd args)))
      (when (not nosetests-cmd)
	(user-error "Nosetests command not found"))
      (if debug
	  (realgud:pdb cmdline)
	(compile cmdline))))

  (defun run-nose-reading-args (arg nose-args &optional debug)
    "Runs nosetests with default args or prompts for args with prefix

  If `debug' is non-nil, run in a GUD PDB session."
    (let ((args (if arg
		    (read-string "Nosetests arguments: "
				 nil nil nosetests-args)
		  nosetests-args)))
      (run-nose (format "%s %s" args nose-args) debug)))

  (defun run-nose-in-project (arg nose-args &optional debug)
    "Runs nosetests from the project root

  If `debug' is non-nil, run in a GUD PDB session."
    (let ((dir (or (projectile-project-root)
		   default-directory)))
      (with-temp-buffer
	(cd dir)
	(run-nose-reading-args arg nose-args debug))))

  (defun nosetests-all (arg)
    "Runs nosetests on all project tests, prompting for the tests directory"
    (interactive "P")
    (let ((test-dir (read-file-name "Test directory: "
				    (or (projectile-project-root)
					default-directory)
				    nil t "tests" #'directory-name-p)))
      (run-nose-in-project arg (directory-file-name test-dir))))

  (defun nosetests-debug-all (arg)
    "Runs nosetests in a GUD session on all project tests"
    (interactive "P")
    (let ((test-dir (read-file-name "Test directory: "
				    (or (projectile-project-root)
					default-directory)
				    nil t "tests" #'directory-name-p)))
      (run-nose-in-project arg (directory-file-name test-dir) t)))

  (defun nosetests-module (arg module)
    "Runs nosetests in the module of the current file"
    (interactive (list current-prefix-arg
		       (read-file-name "Run nosetests on module: "
				       nil nil t
				       (file-name-directory
					(buffer-file-name))
				       #'directory-name-p)))
    (run-nose-in-project arg (directory-file-name module)))

  (defun nosetests-debug-module (arg module)
    "Runs nosetests in a GUD session in the module of the current file"
    (interactive (list current-prefix-arg
		       (read-file-name "Run nosetests on module: "
				       nil nil t
				       (file-name-directory
					(buffer-file-name))
				       #'directory-name-p)))
    (run-nose-in-project arg (directory-file-name module) t))

  (defun nosetests-file (arg file)
    "Runs nosetests on the current file"
    (interactive (list current-prefix-arg
		       (read-file-name "Run nosetests on file: "
				       nil nil t (buffer-file-name))))
    (run-nose-in-project arg file))

  (defun nosetests-debug-file (arg file)
    "Runs nosetests in a GUD session on the current file"
    (interactive (list current-prefix-arg
		       (read-file-name "Run nosetests on file: "
				       nil nil t (buffer-file-name))))
    (run-nose-in-project arg file t))

  (defun nosetests-def (arg def)
    "Runs nosetests on the enclosing function at point"
    (interactive (list current-prefix-arg
		       (python-pytest--current-defun)))
    (run-nose-in-project arg (format "%s:%s"
				     (buffer-file-name)
				     def)))

  (defun nosetests-debug-def (arg def)
    "Runs nosetests in a GUD session on the enclosing function at point"
    (interactive (list current-prefix-arg
		       (python-pytest--current-defun)))
    (run-nose-in-project arg
			 (format "%s:%s"
				 (buffer-file-name)
				 def)
			 t))

  (defvar nosetests-map (make-sparse-keymap)
    "Keymap for nosetests")

  (defvar nosetests-debug-map (make-sparse-keymap)
    "Keymap for debugging nosetests")

  (general-def python-mode-map "C-c t n" nosetests-map)
  (general-def nosetests-map "a" #'nosetests-all)
  (general-def nosetests-map "m" #'nosetests-module)
  (general-def nosetests-map "f" #'nosetests-file)
  (general-def nosetests-map "d" #'nosetests-def)
  (general-def nosetests-map "b" nosetests-debug-map)

  (general-def nosetests-debug-map "a" #'nosetests-debug-all)
  (general-def nosetests-debug-map "m" #'nosetests-debug-module)
  (general-def nosetests-debug-map "f" #'nosetests-debug-file)
  (general-def nosetests-debug-map "d" #'nosetests-debug-def)

  (which-key-add-major-mode-key-based-replacements
    'python-mode "C-c t n" "nosetests")
  (which-key-add-major-mode-key-based-replacements
    'python-mode "C-c t n b" "debug")

Hy

Python but Lispy!

  (defun run-hy ()
    (interactive)
    (run-lisp (expand-file-name "~/.virtualenvs/hy/bin/hy")))

Go

Basic support:

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

LSP support - requires go-langserver.

  (add-hook 'go-mode-hook #'lsp-deferred)

Clojure

Start with clojure-mode:

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

Add flycheck support:

  (use-package flycheck-clojure
    :commands (flycheck-clojure-setup))

Sprinkle in some CIDER:

  (use-package cider
    :commands (cider-mode cider-jack-in cider-jack-in-clojurescript)
    :config
    (setq cider-known-endpoints
	  '(("local" "localhost" "4005"))
	  cider-prompt-for-symbol nil)
    (general-def cider-mode-map "C-c t" cider-test-commands-map)
    (flycheck-clojure-setup)
    (add-hook 'cider-repl-mode-hook 'smartparens-strict-mode)
    :hook ((clojure-mode . cider-mode)
	   (clojurescript-mode . cider-mode)
	   (clojurec-mode . cider-mode))
    :general
    (cider-stacktrace-mode-map "SPC" leader-map)
    ('normal cider-mode-map "M-." #'cider-find-var))

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

Add some handy hydras:

  (use-package cider-hydra
    :after (cider)
    :hook ((cider-mode . cider-hydra-mode)))

Clj-refactor adds magical refactoring abilities:

  (use-package clj-refactor
    :init
    (defun clj-refactor-setup ()
      (interactive)
      (clj-refactor-mode 1)
      (cljr-add-keybindings-with-prefix "C-c r"))
    :config
    (setq cljr-auto-sort-ns t)
    :hook ((clojure-mode . clj-refactor-setup)))

Integrate with cljfmt, the Clojure code formatter:

  (defun cljfmt ()
    (interactive)
    (let* ((start (if (use-region-p) (region-beginning) (point-min)))
	   (end (if (use-region-p) (region-end) (point-max)))
	   (text (buffer-substring start end))
	   (file (make-temp-file "cljfmt"))
	   (fmted
	    (with-temp-buffer
	      (insert text)
	      (write-file file)
	      (shell-command
	       (concat
		"clojure "
		"-Sdeps "
		"'{:aliases {:fmt {:extra-deps {cljfmt {:mvn/version \"0.6.4\"}} :main-opts [\"-m\" \"cljfmt.main\"]}}}' "
		"-A:fmt "
		"fix "
		file))
	      (revert-buffer nil t)
	      (buffer-substring (point-min) (point-max)))))
      (delete-region start end)
      (goto-char start)
      (insert fmted)))

  (general-def clojure-mode-map "C-M-\\" #'cljfmt)

Add support for running Org-mode Clojure source blocks with Babashka:

  (with-eval-after-load 'ob-clojure
    (defcustom org-babel-clojure-backend nil
      "Backend used to evaluate Clojure code blocks."
      :group 'org-babel
      :type '(choice
              (const :tag "inf-clojure" inf-clojure)
              (const :tag "cider" cider)
              (const :tag "slime" slime)
              (const :tag "bb" bb)
              (const :tag "Not configured yet" nil)))

    (defun elisp->clj (in)
      (cond
       ((listp in) (concat "[" (s-join " " (mapcar #'elisp->clj in)) "]"))
       (t (format "%s" in))))

    (defun ob-clojure-eval-with-bb (expanded params)
      "Evaluate EXPANDED code block with PARAMS using babashka."
      (unless (executable-find "bb")
        (user-error "Babashka not installed"))
      (let* ((stdin (let ((stdin (cdr (assq :stdin params))))
                      (when stdin
                        (elisp->clj
                         (org-babel-ref-resolve stdin)))))
             (input (cdr (assq :input params)))
             (file (make-temp-file "ob-clojure-bb" nil nil expanded))
             (command (concat (when stdin (format "echo %s | " (shell-quote-argument stdin)))
                              (format "bb %s -f %s"
                                      (cond
                                       ((equal input "edn") "")
                                       ((equal input "text") "-i")
                                       (t ""))
                                      (shell-quote-argument file))))
             (result (shell-command-to-string command)))
        (s-trim result)))

    (defun org-babel-execute:clojure (body params)
      "Execute a block of Clojure code with Babel."
      (unless org-babel-clojure-backend
        (user-error "You need to customize org-babel-clojure-backend"))
      (let* ((expanded (org-babel-expand-body:clojure body params))
             (result-params (cdr (assq :result-params params)))
             result)
        (setq result
              (cond
               ((eq org-babel-clojure-backend 'inf-clojure)
                (ob-clojure-eval-with-inf-clojure expanded params))
               ((eq org-babel-clojure-backend 'cider)
                (ob-clojure-eval-with-cider expanded params))
               ((eq org-babel-clojure-backend 'slime)
                (ob-clojure-eval-with-slime expanded params))
               ((eq org-babel-clojure-backend 'bb)
                (ob-clojure-eval-with-bb expanded params))))
        (org-babel-result-cond result-params
          result
          (condition-case nil (org-babel-script-escape result)
            (error result)))))

    (customize-set-variable 'org-babel-clojure-backend 'bb))

  (add-hook 'org-mode-hook (lambda () (require 'ob-clojure)))

Scheme

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

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

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

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

And a handy shortcut to hop into a Geiser REPL:

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

Common Lisp

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

  (use-package slime-company
    :after (slime))

  (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-hook 'slime-repl-mode-hook 'smartparens-strict-mode)
    :general
    ((normal motion visual insert emacs) slime-mode-map "M-." #'slime-edit-definition))

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

Keyboard shortcut to start a SLIME REPL:

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

Haskell

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

PHP

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

  (use-package mmm-mode
    :after php-mode)

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

  (use-package geben
    :commands (geben))

Some keybindings to start and end Geben:

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

An Eshell alias to start PHP using XDebug:

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

LSP for PHP requires php-language-server to be installed in ~/.composer:

  (add-hook 'php-mode-hook #'lsp-deferred)

YAML

  (use-package yaml-mode
    :mode ("//.yml//'"))

Pharen

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

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

Bash

Use LSP if bash-language-server is installed.

  (when (executable-find "bash-language-server")
    (add-hook 'sh-mode-hook #'lsp-deferred))

Ruby

  (add-hook 'ruby-mode-hook #'lsp-deferred)

Rust

  (use-package rust-mode
    :mode "\\.rs\\'"
    :general
    (rust-mode-map "C-c <tab>" #'rust-format-buffer)
    :config
    (add-hook 'rust-mode-hook #'lsp-deferred))


  (use-package cargo
    :after (rust-mode)
    :config
    (add-hook 'rust-mode-hook #'cargo-minor-mode))

XML

Set up hideshow for nXML mode:

  (add-hook 'nxml-mode-hook #'hs-minor-mode)

  (add-to-list 'hs-special-modes-alist
	       '(nxml-mode
		 "<!--\\|<[^/>]*[^/]>" ;; regexp for start block
		 "-->\\|</[^/>]*[^/]>" ;; regexp for end block
		 "<!--"
		 nxml-forward-element
		 nil))

A function to format XML using tidy or xmllint if available, falling back to sgml-pretty-print:

  (defun xml-pretty-print ()
    (interactive)
    (let ((start (if (region-active-p) (region-beginning) (point-min)))
	  (end (if (region-active-p) (region-end) (point-max))))
      (cond
       ((executable-find "tidy")
	(shell-command-on-region start end "tidy -wrap 88 -q -i -xml" nil t))
       ((executable-find "xmllint")
	(shell-command-on-region start end "xmllint --format -" nil t))
       (t (sgml-pretty-print start end)))))

  (general-def nxml-mode-map "C-M-\\" #'xml-pretty-print)

CSVs

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

Markdown

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

Edit-indirect allows markdown-mode to edit source blocks in separate buffers:

  (use-package edit-indirect
    :defer t)

IELM

Enable lexical scoping in IELM:

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

Ledger Mode

This mode requires that ledger be installed on the system.

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

Importing

  (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
      (completing-read "Bank: "
			    (mapcar #'car bank-alist))
      (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")))

PDFs

  (use-package pdf-tools
    :mode ("\\.pdf\\'" . pdf-view-mode)
    :init
    (defhydra hydra-pdftools (:color blue :hint nil)
      "
                                                                        ╭───────────┐
         Move  History   Scale/Fit     Annotations  Search/Link    Do   │ PDF Tools │
     ╭──────────────────────────────────────────────────────────────────┴───────────╯
           ^^_g_^^      _B_    ^↧^    _+_    ^ ^     [_al_] list    [_s_] search    [_u_] revert buffer
           ^^^↑^^^      ^↑^    _H_    ^↑^  ↦ _W_ ↤   [_am_] markup  [_o_] outline   [_i_] info
           ^^_p_^^      ^ ^    ^↥^    _0_    ^ ^     [_at_] text    [_F_] link      [_d_] dark mode
           ^^^↑^^^      ^↓^  ╭─^─^─┐  ^↓^  ╭─^ ^─┐   [_ad_] delete  [_f_] search link
      _h_ ←pag_e_→ _l_  _N_  │ _P_ │  _-_    _b_     [_aa_] dired
           ^^^↓^^^      ^ ^  ╰─^─^─╯  ^ ^  ╰─^ ^─╯   [_y_]  yank
           ^^_n_^^      ^ ^  _r_eset slice box
           ^^^↓^^^
           ^^_G_^^
     --------------------------------------------------------------------------------
          "
      ("\\" hydra-master/body "back")
      ("<ESC>" nil "quit")
      ("al" pdf-annot-list-annotations)
      ("ad" pdf-annot-delete)
      ("aa" pdf-annot-attachment-dired)
      ("am" pdf-annot-add-markup-annotation)
      ("at" pdf-annot-add-text-annotation)
      ("y"  pdf-view-kill-ring-save)
      ("+" pdf-view-enlarge :color red)
      ("-" pdf-view-shrink :color red)
      ("0" pdf-view-scale-reset)
      ("H" pdf-view-fit-height-to-window)
      ("W" pdf-view-fit-width-to-window)
      ("P" pdf-view-fit-page-to-window)
      ("n" pdf-view-next-page-command :color red)
      ("p" pdf-view-previous-page-command :color red)
      ("d" pdf-view-dark-minor-mode)
      ("b" pdf-view-set-slice-from-bounding-box)
      ("r" pdf-view-reset-slice)
      ("g" pdf-view-first-page)
      ("G" pdf-view-last-page)
      ("e" pdf-view-goto-page)
      ("o" pdf-outline)
      ("s" pdf-occur)
      ("i" pdf-misc-display-metadata)
      ("u" pdf-view-revert-buffer)
      ("F" pdf-links-action-perform)
      ("f" pdf-links-isearch-link)
      ("B" pdf-history-backward :color red)
      ("N" pdf-history-forward :color red)
      ("l" image-forward-hscroll :color red)
      ("h" image-backward-hscroll :color red))
    :config
    (pdf-tools-install)
    :general
    (pdf-view-mode-map "SPC" leader-map)
    ((normal motion visual) pdf-view-mode-map "." #'hydra-pdftools/body)
    ((normal motion visual) pdf-view-mode-map "F" #'pdf-links-action-perform))

EPubs

  (defun jdormit/nov-config ()
    (olivetti-mode)
    (nov-render-document))

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

Org mode links

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

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

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

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

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

Dashboard

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

  (use-package dashboard
    :general
    (dashboard-mode-map "SPC" leader-map)
    :config
    (with-eval-after-load 'evil
      (add-to-list 'evil-emacs-state-modes 'dashboard-mode))
    (setq dashboard-items '((recents . 5)
			    (projects . 5))
	  dashboard-startup-banner 'official
	  dashboard-set-heading-icons t
	  dashboard-set-file-icons t
	  dashboard-set-navigator t))

  (dashboard-setup-startup-hook)

For some reason Emacs is starting up with the dashboard and the scratch buffer open in a split configuration. Not sure why, but let's put a stop to that…

  (add-hook
   'after-init-hook
   (lambda ()
     (switch-to-buffer "*dashboard*")
     (delete-other-windows)))

Email

I use mu/mu4e as my email client.

First, add it to the load path:

  (defvar mu4e-load-path)
  (setq mu4e-load-path
        (cond
         ((file-exists-p "/usr/local/share/emacs/site-lisp/mu/mu4e")
          "/usr/local/share/emacs/site-lisp/mu/mu4e")
         ((file-exists-p "/usr/share/emacs/site-lisp/mu4e")
          "/usr/share/emacs/site-lisp/mu4e")))
  (add-to-list 'load-path mu4e-load-path)

Then set up autoloads on the entry functions:

  (autoload 'mu4e (concat mu4e-load-path "/mu4e.el") nil t)
  (autoload 'mu4e-update-index (concat mu4e-load-path "/mu4e.el") nil t)

Then configure it:

  (with-eval-after-load 'mu4e
    (setq
     ;; General
     mu4e-maildir (expand-file-name "~/.mail")
     mu4e-completing-read-function 'completing-read
     mu4e-attachment-dir (expand-file-name "~/Downloads")
     mu4e-change-filenames-when-moving t
     user-mail-address "jeremy.dormitzer@gmail.com"
     mu4e-view-show-images t
     mu4e-headers-skip-duplicates t
     mail-user-agent 'mu4e-user-agent
     ;; Custom actions
     mu4e-view-actions '(("capture message" . mu4e-action-capture-message)
                         ("view as pdf" . mu4e-action-view-as-pdf)
                         ("show this thread" . mu4e-action-show-thread)
                         ("View in browser" . mu4e-action-view-in-browser))
     ;; Bookmarked searches
     mu4e-bookmarks '((:name "Inbox"
                             :query (concat "maildir:/jeremy-dormitzer-gmail-com/Inbox"
                                            " OR maildir:/jeremydormitzer-lola-com/Inbox")
                             :key ?i)
                      (:name "Unread messages"
                             :query "flag:unread AND NOT flag:trashed"
                             :key ?u)
                      (:name "Today's messages"
                             :query "date:today..now"
                             :key ?t)
                      (:name "Last 7 days"
                             :query "date:7d..now"
                             :key ?p))
     ;; Getting mail
     mu4e-get-mail-command "mbsync -a"
     ;; Sending mail
     send-mail-function #'sendmail-send-it
     message-send-mail-function #'sendmail-send-it
     sendmail-program (executable-find "msmtp")
     ;; Let Gmail handle putting sent messages in the sent folder
     mu4e-sent-messages-behavior 'delete
     ;; Move to trash folder instead of adding trash flag for Gmail mailboxes
     mu4e-move-to-trash-patterns '("jeremy-dormitzer-gmail-com" "jeremydormitzer-lola-com")
     ;; HTML email rendering
     shr-use-colors nil
     ;; Make sure mu4e knows about my different accounts
     mu4e-context-policy 'ask
     mu4e-compose-context-policy 'ask
     mu4e-contexts
     `(,(make-mu4e-context
         :name "Personal 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")
                 (mu4e-sent-folder . "/jeremy-dormitzer-gmail-com/Sent")
                 (mu4e-drafts-folder . "/jeremy-dormitzer-gmail-com/Drafts")
                 (mu4e-refile-folder . "/jeremy-dormitzer-gmail-com/Archive")
                 (mu4e-trash-folder . "/jeremy-dormitzer-gmail-com/Trash")
                 (message-sendmail-extra-arguments
                  . ("-a" "jeremy-dormitzer-gmail-com"))))
       ,(make-mu4e-context
         :name "Lola Gmail"
         :match-func (lambda (msg)
                       (when msg
                         (string-match-p
                          "jeremydormitzer-lola-com"
                          (mu4e-message-field msg :path))))
         :vars '((user-mail-address . "jeremydormitzer@lola.com")
                 (mu4e-sent-folder . "/jeremydormitzer-lola-com/Sent")
                 (mu4e-drafts-folder . "/jeremydormitzer-lola-com/Drafts")
                 (mu4e-refile-folder . "/jeremydormitzer-lola-com/Archive")
                 (mu4e-trash-folder . "/jeremydormitzer-lola-com/Trash")
                 (message-sendmail-extra-arguments
                  . ("-a" "jeremydormitzer-lola-com"))))))

    ;; Custom mark function to mark messages matching the current message
    (defun mu4e-mark-matching-pred (msg from)
      (mu4e-message-contact-field-matches msg :from from))

    (defun mu4e-mark-matching-input ()
      (let* ((msg (mu4e-message-at-point t)))
        (if (not msg)
            (error "No message at point")
          (cdr (mu4e-message-field msg :from)))))

    (setq mu4e-headers-custom-markers
          '(("Older than"
             (lambda
               (msg date)
               (time-less-p
                (mu4e-msg-field msg :date)
                date))
             (lambda nil
               (mu4e-get-time-date "Match messages before: ")))
            ("Newer than"
             (lambda
               (msg date)
               (time-less-p date
                            (mu4e-msg-field msg :date)))
             (lambda nil
               (mu4e-get-time-date "Match messages after: ")))
            ("Bigger than"
             (lambda
               (msg bytes)
               (>
                (mu4e-msg-field msg :size)
                (* 1024 bytes)))
             (lambda nil
               (read-number "Match messages bigger than (Kbytes): ")))
            ("Matching current message from: field"
             (lambda (msg from)
               (mu4e-message-contact-field-matches msg :from from))
             (lambda ()
               (let* ((msg (mu4e-message-at-point t)))
                 (if (not msg)
                     (error "No message at point")
                   (cdar (mu4e-message-field msg :from))))))))
    (add-hook 'mu4e-compose-pre-hook
              (lambda ()
                (set
                 (make-local-variable '*should-delete-trailing-whitespace*)
                 nil))))

Support sending attachments from Dired:

  (with-eval-after-load 'dired
    (require 'gnus-dired)
    ;; make the `gnus-dired-mail-buffers' function also work on
    ;; message-mode derived modes, such as mu4e-compose-mode
    (defun gnus-dired-mail-buffers ()
      "Return a list of active message buffers."
      (let (buffers)
        (save-current-buffer
          (dolist (buffer (buffer-list t))
            (set-buffer buffer)
            (when (and (derived-mode-p 'message-mode)
                       (null message-sent-message-via))
              (push (buffer-name buffer) buffers))))
        (nreverse buffers)))

    (setq gnus-dired-mail-mode 'mu4e-user-agent)
    (add-hook 'dired-mode-hook 'turn-on-gnus-dired-mode))

Support sending rich-text emails via Markdown:

  (defvar *message-md-pandoc-html-template*
    "<html>
    <head>
      <meta charset=\"utf-8\" />
    </head>
    <body>
    $body$
    </body>
  </html>"
    "The default template used when converting markdown to HTML via pandoc.")

  (defun gfm->html (gfm &optional template)
    "Converts GitHub-flavored markdown to HTML via pandoc.

  By default, the template `*message-md-pandoc-html-template*' is used,
  but this can be overridden with the TEMPLATE argument."
    (unless (executable-find "pandoc")
      (error "Pandoc not found, unable to convert"))
    (let ((template-file (make-temp-file "gfm->html-template"
                                         nil ".html"
                                         (or template *message-md-pandoc-html-template*))))
      (with-temp-buffer
        (insert gfm)
        (unless
            (= 0
               (call-process-region (point-min) (point-max) "pandoc" t t nil
                                    "--template" template-file "--quiet"
                                    "-f" "gfm" "-t" "html"))
          (error "Markdown to HTML conversion failed: %s"
                 (buffer-substring (point-min) (point-max))))
        (buffer-substring (point-min) (point-max)))))

  (defun mml-node->str (node)
    "Converts a parsed MML node back to an MML string."
    (let ((node-name (car node))
          (node-alist (cdr node)))
      (format "<#%s%s>\n%s"
              node-name
              (cl-reduce (lambda (acc pair)
                           (if (equal (car pair) 'contents)
                               acc
                             (concat acc (format " %s=%S"
                                                 (car pair)
                                                 (cdr pair)))))
                         node-alist
                         :initial-value "")
              (cdr (assoc 'contents node-alist))
              node-name)))

  (defun multipart-message-as-string (plain html inline attachments)
    "Given MML nodes PLAIN, and HTML and MML node lists INLINE and
  ATTACHMENTS, constructs a multipart MML email and returns it as a
  string."
    (let* ((alternative (concat "<#multipart type=alternative>\n"
                                (mml-node->str plain) "\n"
                                (mml-node->str html) "\n"
                                "<#/multipart>"))
           (related (when inline
                      (concat "<#multipart type=related>\n"
                              alternative "\n"
                              (cl-reduce
                               (lambda (acc node)
                                 (concat acc (mml-node->str node) "\n"))
                               inline
                               :initial-value "")
                              "<#/multipart>")))
           (mixed (when attachments
                    (concat "<#multipart type=mixed>\n"
                            (or related alternative) "\n"
                            (cl-reduce
                             (lambda (acc node)
                               (concat acc (mml-node->str node) "\n"))
                             attachments
                             :initial-value "")
                            "<#/multipart>"))))
      (or mixed related alternative)))

  (defun assoc-mml-node (key node)
    (cdr (assoc key (cdr node))))

  (defun multipart-html-message (raw)
    "Creates a multipart HTML email with a text part and an html part."
    (with-temp-buffer
      (insert raw)
      (let* ((parsed (mml-parse))
             (plain (cl-reduce
                     (lambda (acc node)
                       (if (not (equal (assoc-mml-node 'type node) "text/plain"))
                           acc
                         `(part (type . "text/plain")
                                (contents . ,(concat (cdaddr acc)
                                                     (cdaddr node))))))
                     parsed
                     :initial-value '(part (type . "text/plain")
                                           (contents . ""))))
             (html `(part (type . "text/html")
                          (contents . ,(gfm->html (cdaddr plain)))))
             (inline (nreverse
                      (cl-reduce
                       (lambda (acc node)
                         (if (not (equal (assoc-mml-node 'disposition node)
                                         "inline"))
                             acc
                           (cons node acc)))
                       parsed
                       :initial-value nil)))
             (attachments (nreverse
                           (cl-reduce
                            (lambda (acc node)
                              (if (not (equal (assoc-mml-node 'disposition node)
                                              "attachment"))
                                  acc
                                (cons node acc)))
                            parsed
                            :initial-value nil))))
        (multipart-message-as-string plain
                                     html
                                     inline
                                     attachments))))

  (defun convert-message-to-markdown ()
    "Convert the message in the current buffer to a multipart HTML email.

  The HTML is rendered by treating the message content as Markdown."
    (interactive)
    (let* ((begin
            (save-excursion
              (goto-char (point-min))
              (search-forward mail-header-separator)))
           (end (point-max))
           (raw-body (buffer-substring begin end)))
      (undo-boundary)
      (delete-region begin end)
      (save-excursion
        (goto-char begin)
        (newline)
        (insert (multipart-html-message raw-body)))))

  (defun message-md-send (&optional arg)
    "Convert the current buffer and send it.
  If given prefix arg ARG, skips markdown conversion."
    (interactive "P")
    (unless arg
      (convert-message-to-markdown))
    (message-send))

  (defun message-md-send-and-exit (&optional arg)
    "Convert the current buffer and send it, then exit from mail buffer.
  If given prefix arg ARG, skips markdown conversion."
    (interactive "P")
    (unless arg
      (convert-message-to-markdown))
    (message-send-and-exit))

  (with-eval-after-load 'message
    (define-key message-mode-map (kbd "C-c C-s") #'message-md-send)
    (define-key message-mode-map (kbd "C-c C-c") #'message-md-send-and-exit))

  ;; Handle replies to HTML emails as well
  (defun html->gfm (html)
    (unless (executable-find "pandoc")
      (error "Pandoc not found, unable to convert"))
    (with-temp-buffer
      (insert html)
      (unless
          (= 0
             (call-process-region (point-min) (point-max) "pandoc" t t nil
                                  "--quiet"
                                  "-f" "html-native_divs-native_spans"
                                  "-t" "gfm"))
        (error "HTML to mardkwon conversion failed: %s"
               (buffer-substring (point-min) (point-max))))
      (buffer-substring (point-min) (point-max))))

  (with-eval-after-load 'mu4e
    (defvar message-md-rich-text-reply t)
    (defvar message-md--inhibit-rich-text-reply nil)

    (defun html-to-md-command (msg)
      "Returns the text of MSG as Markdown."
      (if (mu4e-msg-field msg :body-html)
          (html->gfm (mu4e-msg-field msg :body-html))
        (mu4e-msg-field msg :body-txt)))

    (defun mu4e-draft-cite-original-advice (oldfn &rest args)
      (let ((res (if (and message-md-rich-text-reply
                          (not message-md--inhibit-rich-text-reply))
                     (let ((mu4e-view-prefer-html t)
                           (mu4e-html2text-command #'html-to-md-command))
                       (apply oldfn args))
                   (apply oldfn args))))
        (setq message-md--inhibit-rich-text-reply nil)
        res))

    (defun mu4e-compose-reply-advice (oldfn &rest args)
      (when current-prefix-arg
        (setq message-md--inhibit-rich-text-reply t))
      (apply oldfn args))

    (advice-add 'mu4e~draft-cite-original :around #'mu4e-draft-cite-original-advice)
    (advice-add 'mu4e-compose-reply :around #'mu4e-compose-reply-advice))

  ;; Add an "X-Attachment-Id" header to MIME stuff as well as a Content-Id:
  (with-eval-after-load 'mml
    (defun mml-insert-mime-headers (cont type charset encoding flowed)
      (let (parameters id disposition description)
        (setq parameters
              (mml-parameter-string
               cont mml-content-type-parameters))
        (when (or charset
                  parameters
                  flowed
                  (not (equal type mml-generate-default-type))
                  mml-insert-mime-headers-always)
          (when (consp charset)
            (error
             "Can't encode a part with several charsets"))
          (insert "Content-Type: " type)
          (when charset
            (mml-insert-parameter
             (mail-header-encode-parameter "charset" (symbol-name charset))))
          (when flowed
            (mml-insert-parameter "format=flowed"))
          (when parameters
            (mml-insert-parameter-string
             cont mml-content-type-parameters))
          (insert "\n"))
        (when (setq id (cdr (assq 'id cont)))
          (insert "Content-ID: " id "\n"))
        (when (setq x-attachment-id (cdr (assq 'x-attachment-id cont)))
          (insert "X-Attachment-Id: " x-attachment-id "\n"))
        (setq parameters
              (mml-parameter-string
               cont mml-content-disposition-parameters))
        (when (or (setq disposition (cdr (assq 'disposition cont)))
                  parameters)
          (insert "Content-Disposition: "
                  (or disposition
                      (mml-content-disposition type (cdr (assq 'filename cont)))))
          (when parameters
            (mml-insert-parameter-string
             cont mml-content-disposition-parameters))
          (insert "\n"))
        (unless (eq encoding '7bit)
          (insert (format "Content-Transfer-Encoding: %s\n" encoding)))
        (when (setq description (cdr (assq 'description cont)))
          (insert "Content-Description: ")
          (setq description (prog1
                                (point)
                              (insert description "\n")))
          (mail-encode-encoded-word-region description (point))))))

  (defun message-md-insert-inline-image (image type description)
    (interactive
     (let* ((file (mml-minibuffer-read-file "Insert image: "))
            (type (if current-prefix-arg
                      (or (mm-default-file-encoding file)
                          "application/octet-stream")
                    (mml-minibuffer-read-type file)))
            (description (if current-prefix-arg
                             nil
                           (mml-minibuffer-read-description))))
       (list file type description)))
    (let ((id (format "%s%s" (s-snake-case description) (random))))
     (insert (format "![%s](cid:%s)" description id))
     (save-excursion
       (goto-char (point-max))
       (newline)
       (insert
        (format (concat "<#part id=\"<%s>\" x-attachment-id=%S "
                        "type=%S filename=%S disposition=inline description=%s>")
                id id type image description)))))

Global keybindings:

  (leader-def-key "am" #'mu4e)

Keybindings within mu4e:

  (with-eval-after-load 'mu4e
    (general-def '(normal motion insert emacs) mu4e-headers-mode-map "t" #'mu4e-headers-mark-thread)
    (general-def '(normal motion insert emacs) mu4e-view-mode-map "t" #'mu4e-view-mark-thread))

Mu4e uses shr to render HTML emails. Unfortunately the shr function that sets faces in the rendered document has a bug: it appends the shr faces to the existing face-list, rather than prepending the shr face. This means that e.g. links don't actually get rendered correctly if there is some non-link face already on the text. The fix:

  (with-eval-after-load 'shr
   (defun shr-add-font (start end type)
     (save-excursion
       (goto-char start)
       (while (< (point) end)
	 (when (bolp)
	   (skip-chars-forward " "))
	 ;; Remove the APPEND argument to add-face-text-property
	 ;; so the face ends up at the head of the face list
	 (add-face-text-property (point) (min (line-end-position) end) type)
	 (if (< (line-end-position) end)
	     (forward-line 1)
	   (goto-char end))))))

w3m

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

  (use-package w3m
    :commands (w3m
               w3m-browse-url
               w3m-search-new-session)
    :init
    (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")
          w3m-use-tab-line nil
          browse-url-browser-function '(("nytimes.com" . w3m-browse-url)
                                        ("wsj.com" . w3m-browse-url)
                                        ("." . browse-url-default-browser)))
    :general
    ('normal w3m-mode-map "SPC" leader-map)
    ('(normal visual motion) w3m-mode-map "C-f" #'w3m-scroll-up-or-next-url)
    ('(normal visual motion) w3m-mode-map "C-b" #'w3m-scroll-down-or-previous-url)
    ('normal w3m-mode-map "J" #'w3m-previous-buffer)
    ('normal w3m-mode-map "K" #'w3m-next-buffer)
    ('normal w3m-mode-map "gs" #'w3m-search)
    ('normal w3m-mode-map "gS" #'w3m-search-new-session)
    ('normal w3m-mode-map "gl" #'link-hint-open-link)
    ('normal w3m-mode-map "gc" #'link-hint-copy-link)
    ('normal w3m-mode-map "zc" #'w3m-print-current-url))

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

Add a w3m filter to handle the code block delimiters:

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

Wakatime

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

It needs a helper script to work properly.

  (use-package wakatime-mode
    :if (executable-find "wakatime")
    :init
    (setq wakatime-api-key (password-store-get "wakatime-api-key")
	  wakatime-cli-path (executable-find "wakatime"))
    :config
    (global-wakatime-mode)
    ;; global-wakatime-mode breaks recovering autosaves for some reason
    (advice-add 'recover-this-file :around
		(lambda (oldfn &rest args)
		  (let ((wakatime-was-enabled global-wakatime-mode))
		   (when wakatime-was-enabled
		     (global-wakatime-mode -1))
		   (apply oldfn args)
		   (when wakatime-was-enabled
		     (global-wakatime-mode))))))

Elfeed

Elfeed is a feed reader for Emacs.

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

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

Keybinding for opening Elfeed:

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

Undo Tree

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

Emojify

Because emojis make everything better.

  (use-package emojify
    :commands (emojify-mode
	       emojify-apropos-emoji
	       emojify-insert-emoji)
    :hook
    ((emacs-startup . emojify-mode))
    :init
    (leader-def-key "te" 'emojify-mode))

Calc

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

Deadgrep

A nice Emacs UI over ripgrep.

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

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

RCIRC

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

Channels:

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

Key bindings:

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

Use evil keybindings by default:

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

dumb-jump

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

  (use-package dumb-jump
    :commands (dumb-jump-go dumb-jump-prompt)
    :config (dumb-jump-mode))

  (leader-def-key "cj" 'dumb-jump-go)
  (leader-def-key "cp" 'dumb-jump-go-prompt)

Dictionary

This package looks up word definitions online.

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

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

Gnus

An ancient newsreader.

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

It can read email:

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

Or Gnus can read RSS feeds directly:

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

Dired

Variables:

  (setq dired-dwim-target t)

Set up a hydra for dired:

  (use-package dired
    :straight (:type built-in)
    :init
    (defhydra hydra-dired (:hint nil :color pink)
      "
  _+_ mkdir          _v_iew           _m_ark             _(_ details        _i_nsert-subdir    wdired
  _C_opy             _O_ view other   _U_nmark all       _)_ omit-mode      _$_ hide-subdir    C-x C-q : edit
  _D_elete           _o_pen other     _u_nmark           _l_ redisplay      _w_ kill-subdir    C-c C-c : commit
  _R_ename           _M_ chmod        _t_oggle           _g_ revert buf     _e_ ediff          C-c ESC : abort
  _Y_ rel symlink    _G_ chgrp        _E_xtension mark   _s_ort             _=_ pdiff
  _S_ymlink          ^ ^              _F_ind marked      _._ toggle hydra   \\ flyspell
  _r_sync            ^ ^              ^ ^                ^ ^                _?_ summary
  _z_ compress-file  _A_ find regexp
  _Z_ compress       _Q_ repl regexp

  T - tag prefix
  "
      ("\\" dired-do-ispell)
      ("(" dired-hide-details-mode)
      (")" dired-omit-mode)
      ("+" dired-create-directory)
      ("=" diredp-ediff)         ;; smart diff
      ("?" dired-summary)
      ("$" diredp-hide-subdir-nomove)
      ("A" dired-do-find-regexp)
      ("C" dired-do-copy)        ;; Copy all marked files
      ("D" dired-do-delete)
      ("E" dired-mark-extension)
      ("e" dired-ediff-files)
      ("F" dired-do-find-marked-files)
      ("G" dired-do-chgrp)
      ("g" revert-buffer)        ;; read all directories again (refresh)
      ("i" dired-maybe-insert-subdir)
      ("l" dired-do-redisplay)   ;; relist the marked or single directory
      ("M" dired-do-chmod)
      ("m" dired-mark)
      ("O" dired-display-file)
      ("o" dired-find-file-other-window)
      ("Q" dired-do-find-regexp-and-replace)
      ("R" dired-do-rename)
      ("r" dired-do-rsynch)
      ("S" dired-do-symlink)
      ("s" dired-sort-toggle-or-edit)
      ("t" dired-toggle-marks)
      ("U" dired-unmark-all-marks)
      ("u" dired-unmark)
      ("v" dired-view-file)      ;; q to exit, s to search, = gets line #
      ("w" dired-kill-subdir)
      ("Y" dired-do-relsymlink)
      ("z" diredp-compress-this-file)
      ("Z" dired-do-compress)
      ("q" nil)
      ("." nil :color blue))
    :general
    ((normal visual motion insert emacs) dired-mode-map "." 'hydra-dired/body)
    (dired-mode-map "SPC" leader-map))

Crontab

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

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

Emacs Server

In case I need an emacsclient for some reason.

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

YASnippet

YASnippet is Yet Another Snippet template system.

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

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

mpc

An Emacs interface to MPD, the Music Player Daemon

  (leader-def-key "ad" #'mpc)
  (with-eval-after-load 'evil
    (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)

wgrep

  (use-package wgrep
    :defer t)

Ivy

An alternative minibuffer completion framework:

  (use-package counsel
    :defer 0
    :config
    (ivy-mode 1)
    (counsel-mode 1)
    (setq ivy-height 20
          ivy-wrap t
          ivy-use-virtual-buffers nil
          ivy-count-format "%d/%d ")
    (with-eval-after-load 'projectile
      (setq projectile-completion-system 'ivy))
    (leader-def-key "SPC" #'counsel-M-x)
    (jdormit/define-prefix "i" "ivy")
    (jdormit/define-prefix "iU" "ui")
    (leader-def-key "ir" #'ivy-resume)
    (leader-def-key "ip" #'swiper-thing-at-point)
    (leader-def-key "iP" #'counsel-yank-pop)
    (leader-def-key "iu" #'counsel-unicode-char)
    (leader-def-key "iUt" #'counsel-load-theme)
    (leader-def-key "is" #'swiper)
    (leader-def-key "ia" #'swiper-all)
    (leader-def-key "ff" #'counsel-find-file)
    (leader-def-key "oc" #'counsel-org-capture)
    (leader-def-key "bb" #'counsel-ibuffer)

    (if (executable-find "rg")
        (leader-def-key "ig" #'counsel-rg)
      (leader-def-key "ig" #'counsel-grep))

    (defvar counsel-set-frame-font-history nil)
    (defun counsel-set-frame-font (font)
      (interactive (list (ivy-read "Font: " (delete-dups (font-family-list))
                                   :require-match t
                                   :history 'counsel-set-frame-font-history
                                   :caller 'counsel-set-frame-font)))
      (set-frame-font font))
    (leader-def-key "iUf" #'counsel-set-frame-font)

    (defun counsel-ibuffer-kill-buffer (x)
      (kill-buffer (cdr x)))
    (ivy-set-actions
     'counsel-ibuffer
     '(("k" counsel-ibuffer-kill-buffer "kill buffer")))

    ;; Function to open files without Ivy to avoid lag in really huge directories
    (defun find-file-default (filename &optional wildcards)
      (interactive
       (let ((completing-read-function 'completing-read-default))
         (find-file-read-args "Find file: "
                              (confirm-nonexistent-file-or-buffer))))
      (funcall-interactively 'find-file filename wildcards))

    :general
    ("C-c C-r" #'ivy-resume)
    ("M-x" #'counsel-M-x)
    ("C-x C-f" #'counsel-find-file)
    ("C-M-u" #'counsel-unicode-char)
    ("C-c P" #'counsel-yank-pop)
    ("C-s" #'swiper-isearch)
    (help-map "f" #'counsel-describe-function)
    (help-map "v" #'counsel-describe-variable)
    ((normal motion visual) "g/" #'swiper-thing-at-point))

  (use-package ivy-hydra
    :after counsel)

  (use-package counsel-projectile
    :after (counsel projectile)
    :commands (counsel-projectile
               counsel-projectile-switch-project
               counsel-projectile-find-file
               counsel-projectile-grep)
    :init
    (counsel-projectile-mode)
    (leader-def-key "pp" #'counsel-projectile-switch-project)
    (leader-def-key "pf" #'counsel-projectile)
    (if (executable-find "rg")
        (leader-def-key "pg" #'counsel-projectile-rg)
      (leader-def-key "pg" #'counsel-projectile-grep)))

  (use-package lsp-ivy
    :after (ivy lsp)
    :commands (lsp-ivy-workspace-symbol
               lsp-ivy-global-workspace-symbol))

  (leader-def-key "cs" #'lsp-ivy-workspace-symbol)

graphviz

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

Functions

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

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

becomes:

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

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


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

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

HideShow

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

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

Slack

  (use-package slack
    :commands (slack-start)
    :init
    (setq slack-buffer-emojify t)
    (setq slack-prefer-current-team t)
    :config
    (slack-register-team
     :name "lolatravel"
     :default t
     ;;  :client-id "2400384563.846406584294"
     ;;   :client-secret "31d7bb557aebc773ef26fb53b0f3caf5"
     :token "xoxs-2400384563-531653313251-753670261511-dd9fd44b4755430caf78045af40361d9a2bf46d8a5392f09ea1b6f3c6db0b545"
     ;;   :token "xoxp-2400384563-531653313251-844227895840-6bd114a9d3d8642116f0a1d50675fa43"
     :subscribed-channels '(1-1-2020
			    backend
			    booking-team-backend
			    critical-bugs
			    dev
			    devops
			    frontend
			    search-and-book
			    smash
			    southwest
			    work
			    not-work)
     :full-and-display-names t)
    :general
    ('normal slack-info-mode-map ",u" #'slack-room-update-messages)
    ('normal slack-mode-map
	     ",c" 'slack-buffer-kill
	     ",ra" 'slack-message-add-reaction
	     ",rr" 'slack-message-remove-reaction
	     ",rs" 'slack-message-show-reaction-users
	     ",pl" 'slack-room-pins-list
	     ",pa" 'slack-message-pins-add
	     ",pr" 'slack-message-pins-remove
	     ",mm" 'slack-message-write-another-buffer
	     ",me" 'slack-message-edit
	     ",md" 'slack-message-delete
	     ",u" 'slack-room-update-messages
	     ",2" 'slack-message-embed-mention
	     ",3" 'slack-message-embed-channel
	     "\C-n" 'slack-buffer-goto-next-message
	     "\C-p" 'slack-buffer-goto-prev-message)
    ('normal slack-edit-message-mode-map
	     ",k" #'slack-message-cancel-edit
	     ",s" #'slack-message-send-from-buffer
	     ",2" #'slack-message-embed-mention
	     ",3" #'slack-message-embed-channel))
  (use-package alert
    :commands (alert)
    :init
    (setq alert-default-style (if (eq system-type 'darwin)
				  'osx-notifier
				'libnotify)))

Matrix

  (use-package matrix-client
    :commands matrix-client-connect
    :straight ((matrix-client :host github :repo "alphapapa/matrix-client.el"
			      :files (:defaults "logo.png" "matrix-client-standalone.el.sh"))))

EMMS

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

  git clone git://git.sv.gnu.org/emms.git
  cd emms
  make emms-print-metadata
  make
  make install
  (add-to-list 'load-path "/usr/local/share/emacs/site-lisp/emms")
  (autoload 'emms-smart-browse "emms")
  (autoload 'emms-start "emms")
  (autoload 'emms-stop "emms")
  (autoload 'emms-pause "emms")
  (autoload 'emms-next "emms")
  (autoload 'emms-previous "emms")
  (autoload 'emms "emms")
  (autoload 'emms-play-directory-tree "emms")
  (autoload 'emms-all "emms-setup")
  (with-eval-after-load 'emms
    (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")
    (with-eval-after-load 'evil
      (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))))

Direnv

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

  (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
	      (lambda ()
		(unless (file-remote-p default-directory)
		  (direnv-update-directory-environment)))))

SQL

Emacs has excellent built-in SQL support.

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

GraphQL

GraphQL mode for editing GraphQL queries:

  (use-package graphql-mode
    :mode "\\.gql\\'"
    :commands (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
	 (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
	 (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))

GraphQL environments:

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

And ob-graphql for evaluating GraphQL source blocks in org-mode:

  (use-package ob-graphql
    :defer t)

Docker

Syntax highlighting for Dockerfiles:

  (use-package dockerfile-mode
    :mode ("\\Dockerfile\\'"))

Kubernetes

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

AWS

S3

  (use-package s3ed
    :commands (s3ed-mode
               s3ed-find-file
               s3ed-save-file)
    :init
    (jdormit/define-prefix "fS" "s3")
    (leader-def-key "fSf" #'s3ed-find-file)
    (leader-def-key "fSs" #'s3ed-save-file)
    :config
    (defun browse-blob-in-emacs (url bufname)
      (let* ((token (password-store-get "blob-logs-token"))
             (buf (generate-new-buffer bufname))
             (blob (shell-command-to-string (concat "curl "
                                                    "-s "
                                                    "--cookie 'TOKEN=" token "' "
                                                    url)))
             (status
              (with-current-buffer buf
                (insert blob)
                (json-mode)
                (format-all-buffer))))
        (if (not (equal status "Formatting error"))
            (switch-to-buffer-other-window buf)
          (with-current-buffer buf
            (set-buffer-modified-p nil)
            (kill-buffer))
          (switch-to-buffer-other-window "*format-all-errors*"))))

    (defun s3ed-open-blob-log (arg)
      "Opens the blob log at point via the cloudfront proxy.

    If given prefix arg ARG, opens in browser, otherwise opens in Emacs."
      (interactive "P")
      (let ((fname-at-point (dired-file-name-at-point)))
        (if (string-match
             "\\(bloblogs.ops.lola.co[m]*\\)\/blobs\/\\(\\(production\\|dev\\|local\\|smoke\\|staging\\)\/[0-9]+/.*\\)"
             fname-at-point)
            (let* ((proxy-url (format "https://%s" (match-string 0 fname-at-point))))
              (if arg
                  (browse-url proxy-url)
                (browse-blob-in-emacs proxy-url fname-at-point)))
          (error "Not in a blob logs directory"))))
    :general
    ((normal) s3ed-mode-map "B" #'s3ed-open-blob-log))

Prodigy

Prodigy gives Emacs a nice way to run services (web servers, etc.).

  (use-package prodigy
    :commands (prodigy)
    :general
    ('normal 'prodigy-mode-map "SPC" leader-map)
    ('normal 'prodigy-view-mode-map "SPC" leader-map)
    :config
    (setq prodigy-completion-system 'default)
    (add-hook 'prodigy-view-mode-hook (lambda () (toggle-truncate-lines 1))))

  (leader-def-key "aP" #'prodigy)

Add the ability to associate a file with a service instead of a buffer:

  (with-eval-after-load 'prodigy
    (defun prodigy-service-file (service)
      "Return SERVICE file.

  If SERVICE file exists, use that. If not, find the first SERVICE
  tag that has a file and return that."
      (let ((file (prodigy-service-or-first-tag-with service :file)))
	(if (functionp file)
	    (prodigy-callback-with-plist file service)
	  file)))
    (defun prodigy-display-process-file-or-buffer ()
      (interactive)
      (when-let (service (prodigy-service-at-pos))
	(if-let (file (prodigy-service-file service))
	    (progn
	      (find-file-literally file)
	      (prodigy-view-mode)
	      (auto-revert-tail-mode)
	      (general-define-key
	       :states 'normal
	       :keymaps 'local
	       "Q" #'kill-this-buffer))
	  (prodigy-switch-to-process-buffer service))))
    (general-def 'normal prodigy-mode-map "`" #'prodigy-display-process-file-or-buffer))

And add the ability to inhibit all service output processing:

  (with-eval-after-load 'prodigy
    (defun prodigy-start-service (service &optional callback)
      "Start process associated with SERVICE unless already started.

  When CALLBACK function is specified, that is called when the
  process has been started.

  When the process is started, a timer starts and checks every
  second for `prodigy-start-tryouts' times if the process is live.
  If the process is not live after `prodigy-start-tryouts' seconds,
  the process is put in failed status."
      (declare (indent 1))
      (unless (prodigy-service-started-p service)
	(let* ((default-directory
		 (-if-let (cwd (prodigy-service-cwd service))
		     (f-full cwd)
		   default-directory))
	       (name (plist-get service :name))
	       (sudo (plist-get service :sudo))
	       (command (prodigy-service-command service))
	       (args (prodigy-service-args service))
	       (exec-path (append (prodigy-service-path service) exec-path))
	       (env (--map (s-join "=" it) (prodigy-service-env service)))
	       (process-environment (append env process-environment))
	       (process nil)
	       (create-process
		(lambda ()
		  (unless process
		    (setq process (apply (if sudo 'prodigy-start-sudo-process 'start-process)
					 (append (list name nil  command) args)))))))
	  (-when-let (init (prodigy-service-init service))
	    (funcall init))
	  (-when-let (init-async (prodigy-service-init-async service))
	    (let (callbacked)
	      (funcall
	       init-async
	       (lambda ()
		 (setq callbacked t)
		 (funcall create-process)))
	      (with-timeout
		  (prodigy-init-async-timeout
		   (error "Did not callback async callback within %s seconds"
			  prodigy-init-async-timeout))
		(while (not callbacked) (accept-process-output nil 0.005)))))
	  (funcall create-process)
	  (let ((tryout 0))
	    (prodigy-every 1
		(lambda (next)
		  (setq tryout (1+ tryout))
		  (if (process-live-p process)
		      (when callback (funcall callback))
		    (if (= tryout prodigy-start-tryouts)
			(prodigy-set-status service 'failed)
		      (funcall next))))))
	  (plist-put service :process process)
	  (when (not (plist-get service :inhibit-process-filter))
	    (set-process-filter
	     process
	     (lambda (_ output)
	       (run-hook-with-args 'prodigy-process-on-output-hook service output))))
	  (set-process-query-on-exit-flag process nil)))))

Lola

Some functions to make my day job easier.

Services (Prodigy)

  (defun call-with-venv (venv callback)
    (let ((venv-dir (cond
                     ((file-exists-p venv) venv)
                     ((file-exists-p
                       (substitute-in-file-name
                        (format "$WORKON_HOME/%s" venv)))
                      (substitute-in-file-name
                       (format "$WORKON_HOME/%s" venv)))
                     ((t (error "virtual environment %s does not exist" venv))))))
      (call-with-env-from-file (format "%s/bin/activate" venv-dir) callback)))

  (defun get-latest-lola-log (prefix)
    (lambda ()
      (concat
       "~/lola/logs/"
       (car (last (directory-files "~/lola/logs"
                                   nil
                                   (concat prefix "[[:digit:]-]+T[[:digit:]:]+\\.log")))))))

  (defun lola-log-file (prefix)
    (format "~/lola/logs/%s%s.log"
            prefix
            (format-time-string "%Y-%m-%dT%H:%M:%S")))

  (defun kill-log-buffers ()
    (interactive)
    (kill-matching-buffers "\\.log$" nil t)
    (message "Killed log buffers"))

  (cl-defun python-service-setup (venv &optional env-file &key env-dir)
    (lambda (done)
      (call-with-venv
       venv
       (if env-file
           (lambda ()
             (call-with-env-from-file env-file done :dir env-dir))
         done))))

  (defun call-with-lola-env (callback)
    (let ((process-environment
           (cons (format "LOLA_ENV=%s"
                         (completing-read
                          "Environment: "
                          '("local" "development" "staging")))
                 process-environment)))
      (funcall callback)))

  (prodigy-define-tag
    :name 'lola)

  (prodigy-define-service
    :name "lola-server (gunicorn)"
    :tags '(lola backend)
    :command "bash"
    :args (lambda ()
            (list
             "-c"
             (format
              "gunicorn -c server/web/gunicorn.conf.py \
  -b 127.0.0.1:7200 bin.start_web:init_and_create_flask_app\\(\\) \
  &> %s"
              (lola-log-file "lola-server-"))))
    :file (get-latest-lola-log "lola-server-")
    :inhibit-process-filter t
    :cwd "~/lola/lola-server"
    :stop-signal 'int
    :truncate-output t
    :init-async (python-service-setup "lola-server"
                                      "~/lola/lola-server/.env"))

  (prodigy-define-service
    :name "lola-server celery worker"
    :tags '(lola backend)
    :command "python"
    :args '("bin/start_celery_worker.py" "-P" "gevent" "-n" "lola-server")
    :cwd "~/lola/lola-server"
    :stop-signal 'int
    :truncate-output t
    :init-async (python-service-setup "lola-server"
                                      "~/lola/lola-server/.env"))

  (prodigy-define-service
    :name "travel-service"
    :tags '(lola backend)
    :command "bash"
    :args (lambda ()
            (list
             "-c"
             (format "python bin/start_web.py &> %s"
                     (lola-log-file "travel-svc-"))))
    :cwd "~/lola/lola-travel-service"
    :file (get-latest-lola-log "travel-svc-")
    :inhibit-process-filter t
    :stop-signal 'int
    :truncate-output t
    :init-async (python-service-setup "travel-service"
                                      "~/lola/lola-travel-service/.env"))

  (prodigy-define-service
    :name "travel-service celery worker"
    :tags '(lola backend)
    :command "bash"
    :args (lambda ()
            (list
             "-c"
             (concat "python "
                     "bin/start_celery_workers.py "
                     "-n " "travel-service "
                     "-Q "
                     "default,io_pool,cpu_pool,priority_io_pool,priority_cpu_pool "
                     (format "&> %s" (lola-log-file "travel-svc-celery-")))))
    :file (get-latest-lola-log "travel-svc-celery-")
    :inhibit-process-filter t
    :cwd "~/lola/lola-travel-service"
    :stop-signal 'int
    :truncate-output t
    :init-async (python-service-setup "travel-service"
                                      "~/lola/lola-travel-service/.env"))

  (prodigy-define-service
    :name "secrets"
    :tags '(lola backend)
    :command "python"
    :args '("bin/cmdline.py" "www")
    :cwd "~/lola/secrets"
    :truncate-output t
    :stop-signal 'int
    :init-async (python-service-setup "secrets"
                                      "~/lola/secrets/.env"))

  (prodigy-define-service
    :name "lola-desktop"
    :tags '(lola frontend)
    :command "npm"
    :args '("start")
    :cwd "~/lola/lola-desktop"
    :port 3001
    :env '(("PORT" "3001"))
    :stop-signal 'int
    :init-async #'call-with-lola-env)

  (prodigy-define-service
    :name "wallet"
    :tags '(lola frontend)
    :command "npm"
    :args '("start")
    :cwd "~/lola/wallet"
    :stop-signal 'int
    :env '(("PORT" "3000"))
    :init-async #'call-with-lola-env)

  (prodigy-define-service
    :name "agent-console"
    :tags '(lola frontend)
    :command "npm"
    :args '("start")
    :cwd "~/lola/agent-console"
    :stop-signal 'int
    :env '(("PORT" "3002"))
    :init-async (lambda (done)
                  (call-with-lola-env
                   (lambda ()
                     (nvm-use "v10.15.1" done)))))

  (prodigy-define-service
    :name "luigid"
    :command "luigid"
    :cwd "~/lola/data-pipeline"
    :port 8082
    :stop-signal 'int
    :init-async (python-service-setup "data-pipeline"
                                      "~/lola/data-pipeline/.env"))

  (prodigy-define-service
    :name "prometheus"
    :command "prometheus"
    :args '("--config.file=prometheus.yml")
    :port 9090
    :stop-signal 'int
    :cwd "~/prometheus")

  (prodigy-define-service
    :name "priceline-service"
    :tags '(lola backend)
    :command "~/lola/python-services/priceline/bin/start.sh"
    :args '("web")
    :cwd "~/lola/python-services"
    :stop-signal 'int
    :init-async (python-service-setup "~/lola/python-services/.venv"
                                      "~/lola/python-services/priceline/.env"
                                      :env-dir "~/lola/python-services"))

  (prodigy-define-service
    :name "threev-service"
    :tags '(lola backend)
    :command "~/lola/python-services/threev/bin/start.sh"
    :args '("web")
    :cwd "~/lola/python-services"
    :stop-signal 'int
    :init-async (python-service-setup "~/lola/python-services/.venv"
                                      "~/lola/python-services/threev/.env"
                                      :env-dir "~/lola/python-services"))

  (prodigy-define-service
    :name "ean-hotels-service"
    :tags '(lola backend)
    :command "~/lola/python-services/ean_hotels/bin/start.sh"
    :args '("web")
    :cwd "~/lola/python-services"
    :stop-signal 'int
    :init-async (python-service-setup "~/lola/python-services/.venv"
                                      "~/lola/python-services/ean_hotels/.env"
                                      :env-dir "~/lola/python-services"))

  (prodigy-define-service
    :name "email-template-service"
    :tags '(lola backend)
    :command "npm"
    :args '("start")
    :cwd "~/lola/email-template-service"
    :env '(("PORT" "7300"))
    :stop-signal 'int
    :init-async (lambda (done)
                  (nvm-use "10.15.1" done)))

  (prodigy-define-service
    :name "mabl-link-agent"
    :command "link-agent"
    :args (lambda ()
            (list "-a" (password-store-get "mabl-link-agent")
                  "-n" "jdormit-macbook")))

  (prodigy-define-service
    :name "xray-daemon"
    :command "xray_mac"
    :args '("-o" "-n" "us-east-1"))

Services (eShell)

  (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 release-manager ()
    (interactive)
    (run-service-in-eshell "*release-manager*"
			   "~/lola/release-manager"
			   "pipenv run python release-manager"))

  (jdormit/define-prefix "L" "lola")
  (leader-def-key "Lr" #'release-manager)

Python stuff

Run pip install in the current project:

  (defun pip-install (reqs)
    (interactive
     (list
      (read-file-name "Requirements file: "
		      (or (projectile-project-root)
			  default-directory)
		      nil t "requirements"
		      (lambda (name)
			(string-match-p "requirements.*"
					name)))))
    (with-temp-buffer
      (cd (or (projectile-project-root)
	      default-directory))
      (compile (format "pip install -r %s" reqs))))

AWS-MFA

The aws-mfa command:

  (defun aws-mfa (mfa-token)
    (interactive "MMFA code: ")
    (let ((proc (start-process "aws-mfa"
			       "*aws-mfa*"
                               "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))

1Password

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

Resetting DNSResponder

  (defun reset-dnsresponsder ()
    (interactive)
    (sudo-shell-command "killall -HUP mDNSResponder"))

Devpi

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

Release notes

  (defvar release-notes-repo (expand-file-name
			      "~/lola/release-manager/release_notes")
    "The directory containing the release notes repository.")

  (defun lola-release-notes (staged &optional repos)
    "Prints release notes for the latest release.
  If called with a prefix argument, prints release notes
  for the upcoming release instead."
    (interactive "P")
    (let* ((all-repos '("lola-server"
			"lola-travel-service"
			"lola-desktop"))
	   (repos
	    (or
	     repos
	     (if (y-or-n-p "Query all repos?")
		 all-repos
	       (completing-read-multiple "Repos: "
					 all-repos)))))
      (with-env-from-file (expand-file-name
			   (concat release-notes-repo "/.env"))
	(async-shell-command
	 (format
	  (concat release-notes-repo "/venv/bin/python "
		  release-notes-repo "/release_notes/query_release_notes.py "
		  (when staged "--staged ")
		  " %s")
	  (combine-and-quote-strings repos))
	 "*release-notes*"))))

StumpWM

A handy keybinding to connect to the StumpWM SBCL process via SLIME:

  (defun connect-stumpwm ()
    (interactive)
    (slime-connect "127.0.0.1" 4004))

Emacs Network Client

Emacs frontend for networkmanager.

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

Deft

A fuzzy-finder for notes.

  (use-package deft
    :commands (deft)
    :init
    (setq deft-extensions '("org" "txt" "md" "markdown" "text")
	  deft-recursive t
	  deft-directory (concat
			  (file-name-as-directory (get-dropbox-directory))
			  "org"))
    ;; Still lots of notes in the old Deft directory
    (add-to-list 'org-agenda-files
		 (concat (file-name-as-directory (get-dropbox-directory))
			 "/org/deft"))
    (leader-def-key "D" #'deft)
    (leader-def-key "od" #'deft)
    :config
    (setq deft-use-filter-string-for-filename t
	  deft-file-naming-rules '((noslash . "-")
				   (nospace . "-")
				   (case-fn . downcase))
	  deft-auto-save-interval 0)
    (add-to-list 'evil-emacs-state-modes 'deft-mode))

Tell Deft to use the TITLE property for entry titles, if it exists:

  (with-eval-after-load 'deft
    (advice-add
     'deft-parse-title :around
     (lambda (old-fn file contents &rest args)
       (let ((title (org-get-title contents)))
	 (if title
	     title
	   (apply old-fn file contents args))))))

Pollen

  (use-package pollen-mode
    :mode ("\\.p\\'" "\\.pp\\'" "\\.pm\\'"))

Ngrok

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

Make

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

Redis

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

Restclient

Explore APIs from within Emacs!

  (use-package restclient
    :commands (restclient-mode))

  (use-package company-restclient
    :after (restclient company)
    :config
    (add-to-list 'company-backends 'company-restclient))

  (use-package ob-restclient
    :defer t
    :hook (org-mode . (lambda () (require 'ob-restclient))))

IMenu

Get a nice IMenu sidebar:

  (use-package imenu-list
    :config
    (setq imenu-list-focus-after-activation t)
    :general
    (imenu-list-major-mode-map "SPC" leader-map)
    (imenu-list-major-mode-map "." #'imenu-list-display-entry))

  (defun open-imenu-list ()
    (interactive)
    (if (and (fboundp 'lsp-ui-imenu)
	     (boundp 'lsp-mode)
	     lsp-mode)
	(lsp-ui-imenu)
      (imenu-list-smart-toggle)))

  (leader-def-key "\\" #'open-imenu-list)
  (leader-def-key "m" #'imenu)

calfw-org

A fancy calendar view:

  (use-package calfw
    :commands (cfw:open-calendar-buffer))

  (use-package calfw-org
    :config (require 'calfw)
    :commands (cfw:open-org-calendar))

  (leader-def-key "oC" #'cfw:open-org-calendar)

hackernews

  (use-package hackernews
    :commands (hackernews)
    :config
    (setq hackernews-internal-browser-function 'w3m-browse-url)
    :general
    ('normal hackernews-mode-map "SPC" leader-map))
  (leader-def-key "ah" 'hackernews)

counsel-spotify

Spotify in Emacs!

  (use-package counsel-spotify
    :commands (counsel-spotify-search-track
	       counsel-spotify-search-album
	       counsel-spotify-search-artist
	       counsel-spotify-seach-tracks-by-album
	       counsel-spotify-search-tracks-by-artist
	       counsel-spotify-play
	       counsel-spotify-toggle-play-pause
	       counsel-spotify-next
	       counsel-spotify-previous)
    :config
    (setq counsel-spotify-client-id "825add4224704126adf3912b847c86df"
	  counsel-spotify-client-secret "f71fb236c06b4af886658667056ef7bd"))

  (jdormit/define-prefix "iS" "counsel-spotify")
  (leader-def-key "iSt" #'counsel-spotify-search-track)
  (leader-def-key "iSa" #'counsel-spotify-search-album)
  (leader-def-key "iSA" #'counsel-spotify-search-artist)
  (leader-def-key "iS SPC" #'counsel-spotify-toggle-play-pause)
  (leader-def-key "iSn" #'counsel-spotify-next)
  (leader-def-key "iSp" #'counsel-spotify-previous)

ISpell

  (when (executable-find "hunspell")
    (setq ispell-program-name "hunspell"
	  ispell-really-hunspell t))

VTerm

A better terminal emulator for Emacs. Replaces ansi-term, not EShell.

  (defun eshell-exec-in-vterm (&rest args)
    (let* ((program (car args))
	   (buf (generate-new-buffer
		 (concat "*" (file-name-nondirectory program) "*"))))
      (with-current-buffer buf
	(vterm-mode)
	(vterm-send-string (concat (s-join " " args) "\n")))
      (switch-to-buffer buf)))

  (use-package vterm
    :if module-file-suffix
    :init
    (with-eval-after-load 'em-term
      (defun eshell-exec-visual (&rest args)
	(apply #'eshell-exec-in-vterm args)))
    :commands (vterm vterm-other-window vterm-mode))

  (defun run-vterm (&optional new-buffer)
    (interactive "P")
    (let ((buffer-name (when (not new-buffer) "vterm")))
      (if (and buffer-name (get-buffer buffer-name))
	  (switch-to-buffer buffer-name)
	(vterm buffer-name))))

  (defun open-vterm (&optional arg)
    (interactive "P")
    (if (and (fboundp 'projectile-project-root)
	     (projectile-project-root))
	(projectile-run-vterm arg)
      (run-vterm arg)))

  (leader-def-key "sv" 'open-vterm)

Hide mode line

Does what it says on the box. I use it to hide the mode line in Neotree buffers.

  (use-package hide-mode-line
    :hook ((neotree-mode imenu-list-major-mode) . hide-mode-line-mode))

show-paren-mode

  (show-paren-mode 1)

Dash

Dash is a code browser app for MacOS. dash-at-point is an Emacs interface to it.

  (use-package dash-at-point
    :if (file-exists-p "/Applications/Dash.app")
    :general
    ((normal visual motion insert emacs) "C-c d" 'dash-at-point)
    ((normal visual motion insert emacs) "C-c e" 'dash-at-point-with-docset))

Alfred

Add a function to power the alfred-org-capture Alfred workflow:

  (defun make-orgcapture-frame ()
    "Create a new frame and run org-capture."
    (interactive)
    (make-frame '((name . "Org Capture") (width . 100) (height . 24)
		  (top . 400) (left . 300)))

    (select-frame-by-name "Org Capture")
    (add-hook 'org-capture-after-finalize-hook #'delete-frame t t)
    (counsel-org-capture))

gist.el

Integrate with GitHub gists from Emacs!

  (use-package gist
    :defer t
    :config
    (add-hook 'gist-mode-hook
	      (lambda ()
		(evil-ex-define-local-cmd "w[rite]" 'gist-mode-save-buffer))))

ffap

Add the ability to jump to a particular line number in find-file-at-point. "Borrowed" from the EmacsWiki.

  (defvar ffap-file-at-point-line-number nil
    "Variable to hold line number from the last `ffap-file-at-point' call.")

  (defadvice ffap-file-at-point (after ffap-store-line-number activate)
    "Search `ffap-string-at-point' for a line number pattern and
  save it in `ffap-file-at-point-line-number' variable."
    (let* ((string (ffap-string-at-point)) ;; string/name definition copied from `ffap-string-at-point'
	   (name
	    (or (condition-case nil
		    (and (not (string-match "//" string)) ; foo.com://bar
			 (substitute-in-file-name string))
		  (error nil))
		string))
	   (line-number-string
	    (and (string-match ":[0-9]+" name)
		 (substring name (1+ (match-beginning 0)) (match-end 0))))
	   (line-number
	    (and line-number-string
		 (string-to-number line-number-string))))
      (if (and line-number (> line-number 0))
	  (setq ffap-file-at-point-line-number line-number)
	(setq ffap-file-at-point-line-number nil))))

  (defadvice find-file-at-point (after ffap-goto-line-number activate)
    "If `ffap-file-at-point-line-number' is non-nil goto this line."
    (when ffap-file-at-point-line-number
      (goto-line ffap-file-at-point-line-number)
      (setq ffap-file-at-point-line-number nil)))

Format-all-the-code

A package that bundles together common code beautifying tools for many languages (the actual formatting tools still need to be installed separately):

  (use-package format-all
    :commands (format-all-buffer)
    :init
    (leader-def-key "cf" 'format-all-buffer))

Compiling

Enable ANSI colors in compile buffers:

  (autoload 'ansi-color-apply-on-region "ansi-color")
  (defun colorize-compilation-buffer ()
    (ansi-color-apply-on-region compilation-filter-start (point-max)))
  (add-hook 'compilation-filter-hook #'colorize-compilation-buffer)

Set up some keybindings for Comint-mode compilation buffers:

  (general-def (normal visual motion) compilation-shell-minor-mode-map "q" #'quit-window)
  (general-def (normal visual motion) compilation-shell-minor-mode-map "gr" #'recompile)

Wallabag

My Wallabag client is still a WIP, but it is useful enough to pull in right now:

  (use-package wallabag
    :defer t
    :straight (:host github :repo "jdormit/emacs-wallabag-client")
    :custom
    (wallabag-base-url "https://wallabag.jeremydormitzer.com")
    (wallabag-client-id (password-store-get "wallabag-client-id"))
    (wallabag-client-secret (password-store-get "wallabag-client-secret"))
    (wallabag-username "jdormit")
    (wallabag-password (password-store-get "wallabag.jeremydormitzer.com")))

Clipmon

Syncs up the X Windows clipboard with the Emacs kill ring. Only necessary on Linux.

  (use-package clipmon
    :if (eq window-system 'x)
    :hook (after-init . clipmon-mode-start))

GUD

Emacs ships with a built-in debugger interface that I primarily use for PDB. The default keybindings are RSI-inducing, so here's a hydra to use instead:

  (defhydra hydra-gud (:hint nil)
    "
  _n_: step over         _b_: set breakpoint     _e_: execute statement  _l_: refresh buffer
  _s_: step into         _d_: remove breakpoint  _p_: print statement    _q_: quit hyra
  _>_: up stack frame    _f_: finish function    _w_: watch expression
  _<_: down stack frame  _c_: continue
  "
    (">" gud-up)
    ("<" gud-down)
    ("b" gud-break)
    ("d" gud-remove)
    ("e" gud-statement)
    ("f" gud-finish)
    ("l" gud-refresh)
    ("n" gud-next)
    ("p" gud-print)
    ("c" gud-cont :color blue)
    ("s" gud-step)
    ("w" gud-watch)
    ("q" nil))

  (leader-def-key "cg" #'hydra-gud/body)

realgud

Like GUD, but better!

  (use-package realgud
    :init
    (defun projectile-pdb ()
      (interactive)
      (with-projectile-root
       (funcall-interactively #'realgud:pdb)))
    (leader-def-key "pdp" #'projectile-pdb))

EDiff

  (setq ediff-window-setup-function #'ediff-setup-windows-plain)

Apache Drill

  (use-package sql-drill
    :straight (:host github :repo "jdormit/sql-drill.el")
    :commands (sql-drill)
    :init
    (when (file-exists-p "~/drill/apache-drill-1.17.0/bin/drill-embedded")
      (setq sql-drill-program
	    (expand-file-name "~/drill/apache-drill-1.17.0/bin/drill-embedded"))))

Helpful

A much-improved help buffer:

  (use-package helpful
    :defer t
    :init
    (setq counsel-describe-function-function #'helpful-callable
	  counsel-describe-variable-function #'helpful-variable)
    (add-hook 'emacs-lisp-mode-hook
	      (lambda ()
		(set (make-local-variable 'evil-lookup-func)
		     #'helpful-at-point)))
    :general
    (help-map "k" #'helpful-key))

Inform

Provides links to the help documentation for ELisp symbols from Info buffers:

  (use-package inform
    :straight (:host github :repo "dieter-wilhelm/inform")
    :defer 0
    :config
    ;; If Helpful is installed, link to Helpful buffers instead of vanilla help
    (with-eval-after-load 'helpful
     (define-button-type 'inform-function
       :supertype 'inform-xref
       'inform-function 'helpful-function
       'inform-echo (purecopy "mouse-2, RET: describe this function"))

     (define-button-type 'inform-variable
       :supertype 'inform-xref
       'inform-function 'helpful-variable
       'inform-echo (purecopy "mouse-2, RET: describe this variable"))

     (define-button-type 'inform-symbol
       :supertype 'inform-xref
       'inform-function #'helpful-symbol
       'inform-echo (purecopy "mouse-2, RET: describe this symbol"))))

Vuiet

A music browser and player:

  (use-package versuri
    :straight (versuri :host github :repo "mihaiolteanu/versuri")
    :defer t)

  (use-package vuiet
    :straight (vuiet :host github :repo "mihaiolteanu/vuiet")
    :commands (vuiet-track-artist
               vuiet-track-name
               vuiet-track-duration
               vuiet--playing-track)
    :defer t
    :config
    (setq vuiet-update-mode-line-automatically t
          vuiet-update-mode-line-interval 1)
    (defun vuiet-mode-setup ()
      (set (make-local-variable 'org-link-elisp-confirm-function)
           nil))
    (add-hook 'vuiet-mode-hook #'vuiet-mode-setup)
    :general
    ((normal motion visual) vuiet-mode-map "q" #'quit-window)
    ((normal motion visual) vuiet-mode-map "C-m" #'org-open-at-point))

And a handy hydra for it:

  (defun now-playing (length)
    (let* ((str (s-truncate length
                            (if-let ((track (vuiet--playing-track)))
                                (format "%s - %s"
                                        (vuiet-track-artist track)
                                        (vuiet-track-name track))
                              "----")))
           (remainder (- length (length str))))
      (if (> remainder 0)
          (let* ((left-pad (/ remainder 2))
                 (right-pad (- remainder left-pad)))
            (s-prepend (s-repeat left-pad " ")
                       (s-append (s-repeat right-pad " ") str)))
        str)))

  (defun track-pos ()
    (if-let ((track (vuiet--playing-track)))
        (format "[%s/%s]"
                (format-time-string "%M:%S" (condition-case nil
                                                (mpv-get-playback-position)
                                              (error 0)))
                (s-pad-left 5 "0" (vuiet-track-duration track)))
      "[00:00/00:00]"))

  (defvar hydra-vuiet-body-timer)
  (defun refresh-hydra-vuiet ()
    (interactive)
    (hydra-show-hint hydra-vuiet/hint 'refresh-hydra-vuiet))
  (defhydra hydra-vuiet (:hint nil
                         :color blue
                         :body-pre (setq hydra-vuiet-body-timer
                                               (run-at-time 1 1 #'refresh-hydra-vuiet))
                         :post (cancel-function-timers 'refresh-hydra-vuiet))
    "
                      ---            ^^                ^^            ^^                          ---
  +------------------/  /            ^^                ^^            ^^      +------------------/  /
  | -    -------    /  /             ^^                ^^            ^^      | -    -------    /  /
  |(-) .d########b. //)|+------------^^----------------^^------------^^-----+|(-) .d########b. //)| Play [_t_]ag(s)
  |  .d############//  ||+-----------^^----------------^^------------^^----+||  .d############//  | Play [_a_]rtist
  | .d######''####//b. |||%s(now-playing 43)^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^||| .d######''####//b. | Play a[_l_]bum
  | 9######(  )#-//##P ||+-----------^^--+-------------^^+-----------^^----+|| 9######(  )#-//##P |
  | 'b######++#/-/##d' ||            ^^  |%s(track-pos)^^|           ^^     || 'b######++#/-/##d' | [_L_]ove current track
  |  '9############P'  ||            ^^  +-------------^^+           ^^     ||  '9############P'  | [_U_]nlove current track
  |  -'9a#######aP'    ||  +---------^^+---------------^^--+---------^^--+  ||  -'9a#######aP'    |
  | |-|  `''''''       ||  |  _s_top   |   _p_lay/pause    |   _n_ext    |  || |-|  `''''''       | [_I_]nfo menu
  |  ---..-----------  ||  +---------^^+---------------^^--+---------^^--+  ||  ---..-----------  | [_S_]imilar menu
  | |---||-----------| |+------------^^----------------^^------------^^-----+| |---||-----------| |
  |                    |             ^^                ^^            ^^      |                    | [_q_]uit
  +--------------------+             ^^                ^^            ^^      +--------------------+
  "
    ("s" vuiet-stop)
    ("p" vuiet-play-pause :color red)
    ("n" vuiet-next :color red)
    ("t" vuiet-play-tag-similar)
    ("a" vuiet-play-artist)
    ("l" vuiet-play-album)
    ("L" vuiet-love-track)
    ("U" vuiet-unlove-track)
    ("I" hydra-vuiet-info/body)
    ("S" hydra-vuiet-similar/body)
    ("q" nil))
  (general-def "C-c v" #'hydra-vuiet/body)

  (defvar hydra-vuiet-info-body-timer)
  (defun refresh-hydra-vuiet-info ()
    (interactive)
    (hydra-show-hint hydra-vuiet-info/hint 'refresh-hydra-vuiet-info))
  (defhydra hydra-vuiet-info (:hint nil
                              :color blue
                              :body-pre (setq hydra-vuiet-info-body-timer
                                              (run-at-time 1 1 #'refresh-hydra-vuiet-info))
                              :post (cancel-function-timers 'refresh-hydra-vuiet-info))
    "
                      ---            ^^                ^^            ^^                          ---
  +------------------/  /            ^^                ^^            ^^      +------------------/  /
  | -    -------    /  /             ^^                ^^            ^^      | -    -------    /  /
  |(-) .d########b. //)|+------------^^----------------^^------------^^-----+|(-) .d########b. //)| Info for [_t_]ag
  |  .d############//  ||+-----------^^----------------^^------------^^----+||  .d############//  | Info for [_a_]rtist
  | .d######''####//b. |||%s(now-playing 43)^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^||| .d######''####//b. | Info for a[_l_]bum
  | 9######(  )#-//##P ||+-----------^^--+-------------^^+-----------^^----+|| 9######(  )#-//##P | Info for [_c_]urrent artist
  | 'b######++#/-/##d' ||            ^^  |%s(track-pos)^^|           ^^     || 'b######++#/-/##d' | L[_y_]rics for current track
  |  '9############P'  ||            ^^  +-------------^^+           ^^     ||  '9############P'  | [_L_]oved tracks
  |  -'9a#######aP'    ||  +---------^^+---------------^^--+---------^^--+  ||  -'9a#######aP'    |
  | |-|  `''''''       ||  |  _s_top   |   _p_lay/pause    |   _n_ext    |  || |-|  `''''''       | [_P_]layer menu
  |  ---..-----------  ||  +---------^^+---------------^^--+---------^^--+  ||  ---..-----------  | [_S_]imilar menu
  | |---||-----------| |+------------^^----------------^^------------^^-----+| |---||-----------| |
  |                    |             ^^                ^^            ^^      |                    | [_q_]uit
  +--------------------+             ^^                ^^            ^^      +--------------------+
  "
    ("s" vuiet-stop)
    ("p" vuiet-play-pause :color red)
    ("n" vuiet-next :color red)
    ("t" vuiet-tag-info)
    ("a" vuiet-artist-info-search)
    ("l" vuiet-album-info-search)
    ("c" vuiet-playing-artist-info)
    ("y" vuiet-playing-track-lyrics)
    ("L" vuiet-loved-tracks-info)
    ("P" hydra-vuiet/body)
    ("S" hydra-vuiet-similar/body)
    ("q" nil))

  (defvar hydra-vuiet-similar-body-timer)
  (defun refresh-hydra-vuiet-similar ()
    (interactive)
    (hydra-show-hint hydra-vuiet-similar/hint 'refresh-hydra-vuiet-similar))
  (defhydra hydra-vuiet-similar (:hint nil
                                 :color blue
                                 :body-pre (setq hydra-vuiet-similar-body-timer
                                                 (run-at-time 1 1 #'refresh-hydra-vuiet-similar))
                                 :post (cancel-function-timers 'refresh-hydra-vuiet-similar))
    "
                      ---            ^^                ^^            ^^                          ---
  +------------------/  /            ^^                ^^            ^^      +------------------/  /
  | -    -------    /  /             ^^                ^^            ^^      | -    -------    /  /
  |(-) .d########b. //)|+------------^^----------------^^------------^^-----+|(-) .d########b. //)| Play [_t_]ag similar
  |  .d############//  ||+-----------^^----------------^^------------^^----+||  .d############//  | Play [_a_]rtist similar
  | .d######''####//b. |||%s(now-playing 43)^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^||| .d######''####//b. | Play [_l_]oved tracks similar
  | 9######(  )#-//##P ||+-----------^^--+-------------^^+-----------^^----+|| 9######(  )#-//##P |
  | 'b######++#/-/##d' ||            ^^  |%s(track-pos)^^|           ^^     || 'b######++#/-/##d' | Play [_c_]urrent artist similar
  |  '9############P'  ||            ^^  +-------------^^+           ^^     ||  '9############P'  | Play current [_T_]ag similar
  |  -'9a#######aP'    ||  +---------^^+---------------^^--+---------^^--+  ||  -'9a#######aP'    |
  | |-|  `''''''       ||  |  _s_top   |   _p_lay/pause    |   _n_ext    |  || |-|  `''''''       | [_P_]layer menu
  |  ---..-----------  ||  +---------^^+---------------^^--+---------^^--+  ||  ---..-----------  | [_I_]nfo menu
  | |---||-----------| |+------------^^----------------^^------------^^-----+| |---||-----------| |
  |                    |             ^^                ^^            ^^      |                    | [_q_]uit
  +--------------------+             ^^                ^^            ^^      +--------------------+
  "
    ("s" vuiet-stop)
    ("p" vuiet-play-pause :color red)
    ("n" vuiet-next :color red)
    ("t" vuiet-play-tag-similar)
    ("a" vuiet-play-artist-similar)
    ("l" vuiet-play-loved-tracks-similar)
    ("c" vuiet-play-playing-artist-similar)
    ("T" vuiet-play-playing-tag-similar)
    ("P" hydra-vuiet/body)
    ("I" hydra-vuiet-info/body)
    ("q" nil))

Jira

Jira in Emacs:

  (use-package org-jira
    :init
    (setq jiralib-url "https://lola.atlassian.net"
          org-jira-working-dir (concat (get-dropbox-directory) "/org/jira")
          org-jira-jira-status-to-org-keyword-alist '(("Won't Fix" . "CANCELLED")))
    (add-to-list 'org-agenda-files org-jira-working-dir)
    (add-to-list 'org-super-agenda-groups
                 `(:name "Jira" :file-path ,org-jira-working-dir)
                 t))

Twitter

Socially networking from Emacs:

  (use-package twittering-mode
    :commands (twit)
    :init
    (leader-def-key "at" #'twit)
    :config
    (setq twittering-use-master-password t)
    (defun twittering-browse-uri (uri)
      (interactive (list (or (get-text-property (point) 'uri)
                             (if (get-text-property (point) 'field)
                                 (let* ((id (get-text-property (point) 'id))
                                        (status (twittering-find-status id)))
                                   (twittering-get-status-url-from-alist status))
                               nil))))
      (when uri (browse-url uri)))
    :general
    (twittering-mode-map "SPC" leader-map)
    (twittering-mode-map "W" #'twittering-browse-uri))