Permalink
Switch branches/tags
Nothing to show
Find file
1cc7faa Nov 5, 2017
3143 lines (2965 sloc) 116 KB

Jethro’s Emacs.d Configuration

Introduction

This document is a constant work-in-progress, and will contain the latest updates to my Emacs configuration.

Notes:

  1. I use a Dvorak 60% keyboard, so some of the keybindings may not be suitable for you.
  2. I use StumpWM, and many of my keybindings are designed around that

About the Author

Typically this section is unimportant, but knowing who I am and what I do may help you understand why my configuration is the way it is.

I’m currently a Computer Science undergraduate, who does primarily back-end development (python), and some web development (HTML/CSS/JS, React). I also dabble in Lisps, and run most of my life in Org-mode.

Preinit: use-package

Use-package allows for isolation of package configuration, while maintaining tidiness and performance.

(unless (package-installed-p 'use-package)
  (package-refresh-contents)
  (package-install 'use-package))

(eval-and-compile
  (defvar use-package-verbose t) 
  (require 'cl)
  (require 'use-package)
  (require 'bind-key)
  (require 'diminish)
  (setq use-package-always-ensure t))

EXWM

;; Shrink fringes to 1 pixel
(fringe-mode 1)

;; You may want Emacs to show you the time
(setq display-time-default-load-average nil)
(display-time-mode t)

;; Load EXWM
(require 'exwm)

;; Set the initial number of workspaces.
(setq exwm-workspace-number 4)

;; All buffers created in EXWM mode are named "*EXWM*". You may want to change
;; it in `exwm-update-class-hook' and `exwm-update-title-hook', which are run
;; when a new window class name or title is available. Here's some advice on
;; this subject:
;; + Always use `exwm-workspace-rename-buffer` to avoid naming conflict.
;; + Only renaming buffer in one hook and avoid it in the other. There's no
;;   guarantee on the order in which they are run.
;; + For applications with multiple windows (e.g. GIMP), the class names of all
;;   windows are probably the same. Using window titles for them makes more
;;   sense.
;; + Some application change its title frequently (e.g. browser, terminal).
;;   Its class name may be more suitable for such case.
;; In the following example, we use class names for all windows expect for
;; Java applications and GIMP.
(add-hook 'exwm-update-class-hook
          (lambda ()
            (unless (or (string-prefix-p "sun-awt-X11-" exwm-instance-name)
                        (string= "gimp" exwm-instance-name))
              (exwm-workspace-rename-buffer exwm-class-name))))
(add-hook 'exwm-update-title-hook
          (lambda ()
            (when (or (not exwm-instance-name)
                      (string-prefix-p "sun-awt-X11-" exwm-instance-name)
                      (string= "gimp" exwm-instance-name))
              (exwm-workspace-rename-buffer exwm-title))))

;; `exwm-input-set-key' allows you to set a global key binding (available in
;; any case). Following are a few examples.
;; + We always need a way to go back to line-mode from char-mode
(exwm-input-set-key (kbd "s-r") #'exwm-reset)
;; + Bind a key to switch workspace interactively
(exwm-input-set-key (kbd "s-w") #'exwm-workspace-switch)
;; + Bind "s-0" to "s-9" to switch to the corresponding workspace.
(dotimes (i 10)
  (exwm-input-set-key (kbd (format "s-%d" i))
                      `(lambda ()
                         (interactive)
                         (exwm-workspace-switch-create ,i))))
;; + Application launcher ('M-&' also works if the output buffer does not
;;   bother you). Note that there is no need for processes to be created by
;;   Emacs.
(exwm-input-set-key (kbd "s-SPC")
                    (lambda ()
                      (interactive)
                      (shell-command "rofi -show run")))
(exwm-input-set-key (kbd "s-p")
                    (lambda ()
                      (interactive)
                      (shell-command "rofi-pass")))

;; The following example demonstrates how to set a key binding only available
;; in line mode. It's simply done by first push the prefix key to
;; `exwm-input-prefix-keys' and then add the key sequence to `exwm-mode-map'.
;; The example shorten 'C-c q' to 'C-q'.
(push ?\C-q exwm-input-prefix-keys)
(define-key exwm-mode-map [?\C-q] #'exwm-input-send-next-key)

;; The following example demonstrates how to use simulation keys to mimic the
;; behavior of Emacs. The argument to `exwm-input-set-simulation-keys' is a
;; list of cons cells (SRC . DEST), where SRC is the key sequence you press and
;; DEST is what EXWM actually sends to application. Note that SRC must be a key
;; sequence (of type vector or string), while DEST can also be a single key.
(exwm-input-set-simulation-keys
 '(([?\C-b] . left)
   ([?\C-f] . right)
   ([?\C-p] . up)
   ([?\C-n] . down)
   ([?\C-a] . home)
   ([?\C-e] . end)
   ([?\M-v] . prior)
   ([?\C-v] . next)
   ([?\C-d] . delete)
   ([?\C-k] . (S-end delete))))

;; You can hide the mode-line of floating X windows by uncommenting the
;; following lines
(add-hook 'exwm-floating-setup-hook #'exwm-layout-hide-mode-line)
(add-hook 'exwm-floating-exit-hook #'exwm-layout-show-mode-line)

;; You can hide the minibuffer and echo area when they're not used, by
;; uncommenting the following line
;; (setq exwm-workspace-minibuffer-position 'bottom)

;; Do not forget to enable EXWM. It will start by itself when things are ready.
(exwm-enable)

Core Layer

User Configuration

First, some information about me:

(setq user-full-name "Jethro Kuan"
      user-mail-address "jethrokuan95@gmail.com")

Emacs Server

Load the emacs server, if it is not running. This allows for almost-instant emacs “startup”.

(require 'server)
(unless (server-running-p)
  (server-start))

No littering

This package sets out to fix this by changing the values of path variables to put files in either no-littering-etc-directory (defaulting to ~/.emacs.d/etc/) or no-littering-var-directory (defaulting to ~/.emacs.d/var/), and by using descriptive file names and subdirectories when appropriate. This is similar to a color-theme; a “path-theme” if you will.

(use-package no-littering
  :config
  (require 'recentf)
  (add-to-list 'recentf-exclude no-littering-var-directory)
  (add-to-list 'recentf-exclude no-littering-etc-directory)
  (setq auto-save-file-name-transforms
        `((".*" ,(no-littering-expand-var-file-name "auto-save/") t))))

jethro-mode-map

I defined a minor mode for my own keybindings. This allows me to:

  1. Inspect what keybindings I have customized to my own liking
  2. Swap back to vanilla emacs keybindings if I disable jethro-mode
(defvar jethro-mode-map (make-sparse-keymap)
  "Keymap for `jethro-mode'.")

(define-minor-mode jethro-mode
  "A minor mode so that my key settings override annoying major modes."
  ;; If init-value is not set to t, this mode does not get enabled in
  ;; `fundamental-mode' buffers even after doing \"(global-jethro-mode 1)\".
  ;; More info: http://emacs.stackexchange.com/q/16693/115
  :init-value t
  :lighter    " j"
  :keymap     jethro-mode-map)

(define-globalized-minor-mode global-jethro-mode jethro-mode jethro-mode)

(add-to-list 'emulation-mode-map-alists `((jethro-mode . ,jethro-mode-map)))

;; Turn off the minor mode in the minibuffer
(defun turn-off-jethro-mode ()
  "Turn off jethro-mode."
  (jethro-mode -1))

(add-hook 'minibuffer-setup-hook #'turn-off-jethro-mode)

Reloading Emacs Config

I want an easy way to reload my configuration when I change it. I bind it to <f11>.

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

(bind-key "<f11>" 'reload-init jethro-mode-map)

Emacs GC settings

Reduce the frequency of garbage collection by making it happen on each 50MB of allocated data (the default is on every 0.76MB). Also, warn when opening large files.

(setq gc-cons-threshold 50000000)
(setq large-file-warning-threshold 100000000)

Auto Revert

Often when switching git branches, files tend to change. By default, Emacs does not revert the buffers affected, which can lead to some confusion. Turn on auto-revert-mode globally, so that when the files change, the buffers reflect the latest editions as well.

NOTE: This can be quite slow, when the changes are massive across branches.

(diminish 'auto-revert-mode)
(global-auto-revert-mode 1)

Custom file

Using the customize interface can be nice, but it tends to pollute init.el. Move all customizations to a separate file.

(setq custom-file "~/.emacs.d/custom.el")

Use y/n over yes/no

y/n is easier to type than yes/no

(defalias 'yes-or-no-p 'y-or-n-p)

Replace region when typing

Type over a selected region, instead of deleting before typing.

(add-hook 'after-init-hook 'delete-selection-mode)

Recentf

When I’m using Emacs via emacsclient, my recent files don’t get saved because I never ever quit Emacs. Instead, now I run the function every 5 minutes. Inhibit recentf from printing messages into the minibuffer.

(require 'recentf)
(run-at-time (* 5 60) nil
	       (lambda ()
		 (let ((inhibit-message t))
		   (recentf-save-list))))

Editing Preferences

Emacs uses double-spaces by default. Use single spaces instead:

(setq sentence-end-double-space nil)

Also, use 2 spaces for tabs. Death to tabs!

(setq-default tab-width 2)
(setq-default js-indent-level 2)
(setq-default indent-tabs-mode nil)

Line wrapping for text modes

Don’t wrap lines for coding. Create a hook that enables wrapping, for text-modes like org-mode and markdown-mode.

(setq-default truncate-lines t)

(defun jethro/truncate-lines-hook ()
  (setq truncate-lines nil))

(add-hook 'text-mode-hook 'jethro/truncate-lines-hook)

Backup directory

(setq backup-directory-alist
      `((".*" . ,temporary-file-directory)))
(setq auto-save-file-name-transforms
      `((".*" ,temporary-file-directory t)))

Load secrets

Store secrets in a different file, not committed into the git repository.

(load "~/.emacs.d/secrets.el" t)

Custom Commands

Nuke all buffers with C-c !

(defun jethro/nuke-all-buffers ()
  (interactive)
  (mapcar 'kill-buffer (buffer-list))
  (delete-other-windows))

(bind-key "C-c !" 'jethro/nuke-all-buffers jethro-mode-map)

compile with <f9>

(defun jethro/compile ()
  (interactive)
  (setq-local compilation-read-command nil)
  (call-interactively 'compile))

(bind-key "<f9>" 'jethro/compile jethro-mode-map)

Autosaving

Auto save all open buffers, when Emacs loses focus.

(add-hook 'focus-out-hook
          (lambda () (save-some-buffers t)))

Appearance

Font

I use Iosevka. Other good free alternatives include Source Code Pro, Office Code Pro and the Powerline font families.

(setq default-frame-alist '((font . "Iosevka-16")))

Removing UI Cruft

Remove the useless toolbars and splash screens.

(tooltip-mode -1)
(tool-bar-mode -1)
(menu-bar-mode -1)
(scroll-bar-mode -1)
(setq inhibit-splash-screen t)
(setq inhibit-startup-message t)

Theme

Challenger Deep

(use-package challenger-deep
  :init
  (load-theme 'challenger-deep t))

Zenburn

(use-package zenburn-theme
    :init
    (load-theme 'zenburn t))

Tomorrow (Eighties)

(use-package color-theme-sanityinc-tomorrow
  :init
  (load-theme 'sanityinc-tomorrow-eighties t))

tao

(use-package tao-theme
  :init
  (load-theme 'tao-yang t))

color-identifiers-mode

(use-package color-identifiers-mode
  :diminish color-identifiers-mode
  :init
  (add-hook 'after-init-hook 'global-color-identifiers-mode)
  :config
  (let ((faces '(font-lock-comment-face font-lock-comment-delimiter-face font-lock-constant-face font-lock-type-face font-lock-function-name-face font-lock-variable-name-face font-lock-keyword-face font-lock-string-face font-lock-builtin-face font-lock-preprocessor-face font-lock-warning-face font-lock-doc-face)))
    (dolist (face faces)
      (set-face-attribute face nil :foreground nil :weight 'normal :slant 'normal)))

  (set-face-attribute 'font-lock-comment-delimiter-face nil :slant 'italic)
  (set-face-attribute 'font-lock-comment-face nil :slant 'italic)
  (set-face-attribute 'font-lock-doc-face nil :slant 'italic)
  (set-face-attribute 'font-lock-keyword-face nil :weight 'bold)
  (set-face-attribute 'font-lock-builtin-face nil :weight 'bold)
  (set-face-attribute 'font-lock-preprocessor-face nil :weight 'bold))

Rainbow-delimiters-mode

We use rainbow delimiters to show imbalanced parenthesis.

(use-package rainbow-delimiters
  :ensure t 
  :init
  (add-hook 'after-init-hook 'rainbow-delimiters-mode)
  :config
  (set-face-attribute 'rainbow-delimiters-unmatched-face nil
                      :foreground 'unspecified
                      :inherit 'error))

Remove blinking cursor

(blink-cursor-mode 0)

Additional Code Highlighting

(use-package highlight-numbers
  :init
  (add-hook 'prog-mode-hook #'highlight-numbers-mode))

(use-package highlight-quoted
  :init
  (add-hook 'prog-mode-hook #'highlight-quoted-mode))

(use-package highlight-defined
  :init
  (add-hook 'prog-mode-hook #'highlight-defined-mode))

(use-package highlight-operators
  :init
  (add-hook 'prog-mode-hook #'highlight-operators-mode))

(use-package highlight-escape-sequences
  :init
  (add-hook 'prog-mode-hook #'hes-mode))

Shell

(require 'eshell)

Set default shell to bash

Because fish doesn’t play well with Emacs.

(setq-default explicit-shell-file-name "/run/current-system/sw/bin/bash")
(setq-default shell-file-name "/run/current-system/sw/bin/bash")

Add PATH to shell

(use-package exec-path-from-shell 
  :config
  (exec-path-from-shell-initialize))

Eshell configuration

(require 'em-smart)

(setq eshell-scroll-to-bottom-on-input 'all
      eshell-hist-ignoredups t
      eshell-save-history-on-exit t
      eshell-prefer-lisp-functions nil
      eshell-destroy-buffer-when-process-dies t
      eshell-glob-case-insensitive nil
      eshell-error-if-no-glob nil
      eshell-where-to-jump 'begin
      eshell-review-quick-commands nil
      eshell-smart-space-goes-to-end t)

Fish-like auto-completion

This was obtained from http://whyarethingsthewaytheyare.com/fishlike-autosuggestions-in-eshell/.

(with-eval-after-load 'company
  (defun company-eshell-autosuggest-candidates (prefix)
    (let* ((history
            (cl-remove-duplicates
             (mapcar (lambda (str)
                       (string-trim (substring-no-properties str)))
                     (ring-elements eshell-history-ring))
             :from-end t
             :test #'string=))
           (most-similar (cl-find-if
                          (lambda (str)
                            (string-prefix-p prefix str))
                          history)))
      (when most-similar
        `(,most-similar))))

  (defun company-eshell-autosuggest--prefix ()
    (let ((prefix
           (string-trim-left
            (buffer-substring-no-properties
             (save-excursion
               (eshell-bol))
             (save-excursion (end-of-line) (point))))))
      (if (not (string-empty-p prefix))
          prefix
        'stop)))

  (defun company-eshell-autosuggest (command &optional arg &rest ignored)
    (interactive (list 'interactive))
    (cl-case command
      (interactive (company-begin-backend 'company-eshell))
      (prefix (and (eq major-mode 'eshell-mode)
                   (company-eshell-autosuggest--prefix)))
      (candidates (company-eshell-autosuggest-candidates arg)))))

(defun setup-eshell-autosuggest ()
  (require 'company)
  (setq-local company-backends '(company-eshell-autosuggest))
  (setq-local company-frontends '(company-preview-frontend)))

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

Eshell theme

(use-package eshell-git-prompt
  :config
  (eshell-git-prompt-use-theme 'powerline))

Open eshell in current/project directory

(defun jethro/eshell-here ()
  "Opens up a new shell in projectile root. If a prefix argument is
passed, use the buffer's directory."
  (interactive) 
  (let* ((projectile-name (projectile-project-name))
         (current-directory (car
                             (last
                              (split-string
                               (if (buffer-file-name)
                                   (file-name-directory (buffer-file-name))
                                 default-directory) "/" t)))))
    (split-window-vertically)
    (other-window 1)
    (if (equal projectile-name "-")
        (progn
          (eshell "new")
          (rename-buffer (concat "*eshell: " current-directory "*")))
      (projectile-with-default-dir (projectile-project-root)
        (eshell "new")
        (rename-buffer (concat "*eshell: " projectile-name "*"))))))

(bind-key "C-x m" 'jethro/eshell-here jethro-mode-map)

Exiting eshell

(defun eshell/x ()
  (unless (one-window-p)
    (delete-window))
  (eshell/exit))

Isearch

(bind-key "C-s" 'eshell-isearch-forward eshell-mode-map)
(bind-key "C-r" 'eshell-isearch-backward eshell-mode-map)

Quitting Eshell

(defun eshell/x ()
  (delete-window)
  (eshell/exit))

with-editor

Use with-editor to use current Emacs to open everything that invokes $EDITOR.

(use-package with-editor
  :ensure t
  :init
  (progn
    (add-hook 'shell-mode-hook  'with-editor-export-editor)
    (add-hook 'eshell-mode-hook 'with-editor-export-editor)))

Web Browsing

(use-package eww
  :defer t
  :init
  (setq browse-url-browser-function
        '((".*google.*maps.*" . browse-url-generic)
          ;; Github goes to firefox, but not gist
          ("http.*\/\/github.com" . browse-url-generic)
          ("groups.google.com" . browse-url-generic)
          ("docs.google.com" . browse-url-generic)
          ("melpa.org" . browse-url-generic)
          ("build.*\.elastic.co" . browse-url-generic)
          (".*-ci\.elastic.co" . browse-url-generic)
          ("internal-ci\.elastic\.co" . browse-url-generic)
          ("zendesk\.com" . browse-url-generic)
          ("salesforce\.com" . browse-url-generic)
          ("stackoverflow\.com" . browse-url-generic)
          ("apache\.org\/jira" . browse-url-generic)
          ("thepoachedegg\.net" . browse-url-generic)
          ("zoom.us" . browse-url-generic)
          ("t.co" . browse-url-generic)
          ("twitter.com" . browse-url-generic)
          ("\/\/a.co" . browse-url-generic)
          ("youtube.com" . browse-url-generic)
          ("." . eww-browse-url)))
  (setq shr-external-browser 'browse-url-generic)
  (setq browse-url-browser-function 'browse-url-firefox
        browse-url-new-window-flag  t
        browse-url-firefox-new-window-is-tab t) 
  (add-hook 'eww-mode-hook #'toggle-word-wrap)
  (add-hook 'eww-mode-hook #'visual-line-mode)
  :config
  (use-package s :ensure t)
  (define-key eww-mode-map "o" 'eww)
  (define-key eww-mode-map "O" 'eww-browse-with-external-browser)
  (define-key eww-mode-map "j" 'next-line)
  (define-key eww-mode-map "k" 'previous-line)

  (use-package eww-lnum 
    :bind (:map eww-mode-map
                ("f" . eww-lnum-follow)
                ("U" . eww-lnum-universal))))

elfeed

(use-package elfeed
  :bind ("<f6>" . elfeed))

elfeed-org

(use-package elfeed-org
  :config
  (elfeed-org)
  (setq rmh-elfeed-org-files '("~/.org/deft/feeds.org")))

Core Utilities

Dash

Dash is a library used to simplify Emacs-lisp development. Some custom elisp code use Dash, so I load it first here anyway.

(use-package dash)

Hydra

(use-package hydra)

Ivy

I’ve recently switched over from helm to ivy. Ivy is simpler, and easier to extend.

flx

Flx is required for fuzzy-matching.

(use-package flx)

Fuzzy Isearch

(use-package flx-isearch
  :bind (:map jethro-mode-map
              ("C-M-s" . flx-isearch-forward)
              ("C-M-r" . flx-isearch-backward)))

Counsel

Counsel contains ivy enhancements for commonly-used functions.

(use-package counsel
  :diminish ivy-mode
  :bind
  (:map jethro-mode-map
        ("C-c C-r" . ivy-resume)
        ("M-a" . counsel-M-x)
        ("C-s" . counsel-grep-or-swiper)
        ("C-r" . counsel-grep-or-swiper)
        ("C-c i" . counsel-imenu)
        ("C-x C-f" . counsel-find-file)
        ("C-x j" . counsel-dired-jump)
        ("C-x l" . counsel-locate)
        ("C-c j" . counsel-git)
        ("C-c f" . counsel-recentf)
        ("M-y" . counsel-yank-pop)
        :map swiper-map
        ("C-r" . ivy-previous-line)
        :map help-map
        ("f" . counsel-describe-function)
        ("v" . counsel-describe-variable)
        ("l" . counsel-info-lookup-symbol)
        :map ivy-minibuffer-map
        ("C-d" . ivy-dired)
        ("C-o" . ivy-occur)
        ("<return>" . ivy-alt-done)
        ("M-<return>" . ivy-immediate-done)
        :map read-expression-map
        ("C-r" . counsel-expression-history))
  :init
  (add-hook 'after-init-hook 'ivy-mode)
  :config
  (setq counsel-grep-swiper-limit 20000)
  (defun ivy-dired ()
    (interactive)
    (if ivy--directory
        (ivy-quit-and-run
         (dired ivy--directory)
         (when (re-search-forward
                (regexp-quote
                 (substring ivy--current 0 -1)) nil t)
           (goto-char (match-beginning 0))))
      (user-error
       "Not completing files currently")))
  (setq counsel-grep-base-command
        "rg -i -M 120 --no-heading --line-number --color never '%s' %s")
  (setq counsel-find-file-at-point t)
  (setq ivy-use-virtual-buffers t)
  (setq ivy-display-style 'fancy)
  (setq ivy-initial-inputs-alist nil)
  (setq ivy-re-builders-alist
        '((ivy-switch-buffer . ivy--regex-plus)
          (swiper . ivy--regex-plus)
          (t . ivy--regex-fuzzy))) 
  (ivy-set-actions
   t
   '(("I" insert "insert"))))

wgrep

(use-package wgrep)

rg

(use-package rg
  :bind (:map jethro-mode-map
              ("M-s" . rg)))

Visual Enhancements

Whitespace-mode

(require 'whitespace)
(setq whitespace-line-column 80) ;; limit line length
(setq whitespace-style '(face lines-tail))
(diminish 'whitespace-mode)
(add-hook 'prog-mode-hook 'whitespace-mode)

Modeline

Flycheck

Show flycheck status in modeline.

(defface jethro/ok-face
  '((t :foreground "#bfebbf"))
  "Face for ok status in the mode-line.")

(defface jethro/warning-face
  '((t :foreground "#dfaf8f"))
  "Face for warning status in the mode-line.")

(defface jethro/error-face
  '((t :foreground "#cc9393"))
  "Face for error status in the mode-line.")

(defun jethro/modeline-flycheck-status ()
  "Return the status of flycheck to be displayed in the mode-line."
  (when flycheck-mode
    (let* ((text (pcase flycheck-last-status-change
                   (`finished (if flycheck-current-errors
                                  (let ((count (let-alist (flycheck-count-errors flycheck-current-errors)
                                                 (+ (or .warning 0) (or .error 0)))))
                                    (propertize (format "%s Issue%s" count (if (eq 1 count) "" "s"))
                                                'face 'jethro/warning-face))
                                (propertize "✔ No Issues"
                                            'face 'jethro/ok-face)))
                   (`running     (propertize "⟲ Running"
                                             'face 'jethro/warning-face))
                   (`no-checker  (propertize "⚠ No Checker"
                                             'face 'jethro/warning-face))
                   (`not-checked "✖ Disabled")
                   (`errored     (propertize "⚠ Error"
                                             'face 'jethro/error-face))
                   (`interrupted (propertize "⛔ Interrupted"
                                             'face 'jethro/error-face))
                   (`suspicious  ""))))
      (propertize text
                  'help-echo "Show Flycheck Errors"
                  'local-map (make-mode-line-mouse-map
                              'mouse-1 #'flycheck-list-errors)))))

Eyebrowse

(defun jethro/modeline-eyebrowse ()
  (when eyebrowse-mode
    (eyebrowse-mode-line-indicator)))

Modeline Format

(defun jethro/org-mode-line-string ()
  "Returns org-mode-line-string if bound, empty
string if not"
  (if (boundp 'org-mode-line-string)
      org-mode-line-string
    ""))

(defun jethro/org-pomodoro-mode-line ()
  "Returns org-pomodoro-mode-line string if bound, empty
string if not"
  (if (boundp 'org-pomodoro-mode-line)
      org-pomodoro-mode-line
    ""))

(setq-default mode-line-format
              `("%e"
                mode-line-front-space mode-line-mule-info mode-line-client mode-line-modified mode-line-remote mode-line-frame-identification mode-line-buffer-identification
                " " 
                (:eval (jethro/modeline-flycheck-status))
                " "
                (:eval (jethro/org-mode-line-string))
                " "
                (:eval (jethro/org-pomodoro-mode-line))
                " "
                (vc-mode vc-mode)
                " "
                (:eval (jethro/modeline-eyebrowse))))

Thick modeline bar

(custom-set-faces
 '(mode-line ((t (:background "#2B2B2B" :foreground "#DCDCCC" :box (:line-width 4 :color "#2B2B2B"))))))

smart-mode-line

(use-package smart-mode-line
  :init
  (add-hook 'after-init-hook 'sml/setup)
  :config 
  (setq sml/theme 'respectful)
  (setq sml/name-width 44)
  (setq sml/shorten-directory t)
  (setq sml/shorten-modes nil)
  (setq sml/mode-width 'full)
  (setq sml/replacer-regexp-list
        '(("^~/.org/" ":O:")
          ("^~/\\.emacs\\.d/" ":ED:")))
  (setq rm-blacklist
        (format "^ \\(%s\\)$"
                (mapconcat #'identity
                           '("j"
                             "FlyC.*"
                             "Fill"
                             "Projectile.*"
                             "GitGutter"
                             "ivy"
                             "company"
                             ""
                             "OrgSrc"
                             ","
                             "ElDoc")
                           "\\|"))))

Zooming

(with-eval-after-load 'hydra
  (defhydra jethro/hydra-zoom ()
    "zoom"
    ("i" text-scale-increase "in")
    ("o" text-scale-decrease "out"))

  (bind-key "C-c h z" 'jethro/hydra-zoom/body jethro-mode-map))

beacon

Beacon makes sure you don’t lose track of your cursor when jumping around a buffer.

(use-package beacon
  :diminish beacon-mode
  :init
  (add-hook 'after-init-hook 'beacon-mode)
  :config 
  (setq beacon-push-mark 10))

Show Matching parenthesis

Always show matching parenthesis.

(show-paren-mode 1)
(setq show-paren-delay 0)

golden-ratio

Give the working window more screen estate.

(use-package golden-ratio
  :diminish golden-ratio-mode
  :init
  (add-hook 'after-init-hook 'golden-ratio-mode))

volatile-highlights

Highlights recently copied/pasted text.

(use-package volatile-highlights
  :diminish volatile-highlights-mode
  :init
  (add-hook 'after-init-hook 'volatile-highlights-mode))

diff-hl

  (use-package diff-hl
    :bind (:map jethro-mode-map 
                ("C-c h v" . jethro/hydra-diff-hl/body))
    :init 
    (defconst jethro/diff-hl-mode-hooks '(emacs-lisp-mode-hook
                                          conf-space-mode-hook ;.tmux.conf
                                          markdown-mode-hook
                                          css-mode-hook
                                          web-mode-hook
                                          sh-mode-hook
                                          python-mode-hook
                                          yaml-mode-hook ;tmuxp yaml configs
                                          c-mode-hook)
      "List of hooks of major modes in which diff-hl-mode should be enabled.")

    (dolist (hook jethro/diff-hl-mode-hooks)
      (add-hook hook #'diff-hl-mode))

    (defhydra jethro/hydra-diff-hl (:color red)
      "diff-hl"
      ("=" diff-hl-diff-goto-hunk "goto hunk")
      ("<RET>" diff-hl-diff-goto-hunk "goto hunk")
      ("u" diff-hl-revert-hunk "revert hunk")
      ("[" diff-hl-previous-hunk "prev hunk")
      ("p" diff-hl-previous-hunk "prev hunk")
      ("]" diff-hl-next-hunk "next hunk")
      ("n" diff-hl-next-hunk "next hunk") 
      ("q" nil "cancel"))

    (add-hook 'dired-mode-hook #'diff-hl-dired-mode))
*** Simple Custom mode-line
Copied from https://github.com/bastibe/.emacs.d/blob/master/init.el
  (setq-default mode-line-format
                '(((:eval (let* ((buffer-name (concat
                                               (propertize (buffer-name) 'face '(:weight bold))
                                               ":" (propertize (format-mode-line "%l,%c") 'face '(:weight light))))
                                 (left (concat (format-mode-line mode-line-front-space)
                                               "(" (if (buffer-modified-p) "" "") ")"
                                               " "
                                               (format "%-30s" buffer-name)
                                               "    "
                                               (if vc-mode (concat "" vc-mode " (" (symbol-name (vc-state (buffer-file-name))) ")") "")
                                               "  "
                                               (format-mode-line mode-line-misc-info)))
                                 (right (concat "("
                                                (propertize (format-mode-line mode-name) 'face '(:weight bold))
                                                (format-mode-line minor-mode-alist)
                                                ")"
                                                (format-mode-line mode-line-end-spaces)))
                                 (padding (make-string (- (window-width) 4 (length left) (length right)) ? )))
                            (format "%s %s %s" left padding right))))))

Moving Around

Keychord

(use-package key-chord
  :config
  (key-chord-mode 1))

Layouts

Eyebrowse

(use-package eyebrowse
  :bind (:map jethro-mode-map
              ("M-0" . eyebrowse-switch-to-window-config-0)
              ("M-1" . eyebrowse-switch-to-window-config-1)
              ("M-2" . eyebrowse-switch-to-window-config-2)
              ("M-3" . eyebrowse-switch-to-window-config-3)
              ("M-4" . eyebrowse-switch-to-window-config-4)
              ("M-5" . eyebrowse-switch-to-window-config-5)
              ("M-6" . eyebrowse-switch-to-window-config-6)
              ("M-7" . eyebrowse-switch-to-window-config-7)
              ("M-8" . eyebrowse-switch-to-window-config-8)
              ("M-9" . eyebrowse-switch-to-window-config-9))
  :init
  (add-hook 'after-init-hook 'eyebrowse-mode))

Persp-mode

(use-package persp-mode
  :bind
  (:map jethro-mode-map
        ("C-x b" . persp-switch-to-buffer)
        ("C-x k" . persp-kill-buffer))
  :init
  (setq persp-keymap-prefix (kbd "C-c p"))
  (setq persp-lighter
        '(:eval
          (format
           (propertize
            " [p] %.10s"
            'face (let ((persp (get-current-persp)))
                    (if persp
                        (if (persp-contain-buffer-p (current-buffer) persp)
                            'persp-face-lighter-default
                          'persp-face-lighter-buffer-not-in-persp)
                      'persp-face-lighter-nil-persp)))
           (file-name-nondirectory (directory-file-name (safe-persp-name (get-current-persp)))))))
  (persp-mode 1))

Eyebrowse and Persp-Mode Integration

(use-package dash)
(require 'dash)
(defun jethro//get-persp-workspace (&optional persp frame)
  "Get the correct workspace parameters for perspective.
PERSP is the perspective, and defaults to the current perspective.
FRAME is the frame where the parameters are expected to be used, and
defaults to the current frame."
  (let ((param-names (if (display-graphic-p frame)
                         '(gui-eyebrowse-window-configs
                           gui-eyebrowse-current-slot
                           gui-eyebrowse-last-slot)
                       '(term-eyebrowse-window-configs
                         term-eyebrowse-current-slot
                         term-eyebrowse-last-slot))))
    (--map (persp-parameter it persp) param-names)))

(defun jethro//set-persp-workspace (workspace-params &optional persp frame)
  "Set workspace parameters for perspective.
WORKSPACE-PARAMS should be a list containing 3 elements in this order:
- window-configs, as returned by (eyebrowse--get 'window-configs)
- current-slot, as returned by (eyebrowse--get 'current-slot)
- last-slot, as returned by (eyebrowse--get 'last-slot)
PERSP is the perspective, and defaults to the current perspective.
FRAME is the frame where the parameters came from, and defaults to the
current frame.
Each perspective has two sets of workspace parameters: one set for
graphical frames, and one set for terminal frames."
  (let ((param-names (if (display-graphic-p frame)
                         '(gui-eyebrowse-window-configs
                           gui-eyebrowse-current-slot
                           gui-eyebrowse-last-slot)
                       '(term-eyebrowse-window-configs
                         term-eyebrowse-current-slot
                         term-eyebrowse-last-slot))))
    (--zip-with (set-persp-parameter it other persp)
                param-names workspace-params)))

(defun jethro/load-eyebrowse-for-perspective (type &optional frame)
  "Load an eyebrowse workspace according to a perspective's parameters.
 FRAME's perspective is the perspective that is considered, defaulting to
 the current frame's perspective.
 If the perspective doesn't have a workspace, create one."
  (when (eq type 'frame)
    (let* ((workspace-params (jethro//get-persp-workspace (get-frame-persp frame) frame))
           (window-configs (nth 0 workspace-params))
           (current-slot (nth 1 workspace-params))
           (last-slot (nth 2 workspace-params)))
      (if window-configs
          (progn
            (eyebrowse--set 'window-configs window-configs frame)
            (eyebrowse--set 'current-slot current-slot frame)
            (eyebrowse--set 'last-slot last-slot frame)
            (eyebrowse--load-window-config current-slot))
        (eyebrowse--set 'window-configs nil frame)
        (eyebrowse-init frame)
        (jethro/save-eyebrowse-for-perspective frame)))))

(defun jethro/load-eyebrowse-after-loading-layout (_state-file _phash persp-names)
  "Bridge between `persp-after-load-state-functions' and
`jethro/load-eyebrowse-for-perspective'.
_PHASH is the hash were the loaded perspectives were placed, and
PERSP-NAMES are the names of these perspectives."
  (let ((cur-persp (get-current-persp)))
    ;; load eyebrowse for current perspective only if it was one of the loaded
    ;; perspectives
    (when (member (or (and cur-persp (persp-name cur-persp))
                      persp-nil-name)
                  persp-names)
      (jethro/load-eyebrowse-for-perspective 'frame))))

(defun jethro/update-eyebrowse-for-perspective (&rest _args)
  "Update and save current frame's eyebrowse workspace to its perspective."
  (let* ((current-slot (eyebrowse--get 'current-slot))
         (current-tag (nth 2 (assoc current-slot (eyebrowse--get 'window-configs)))))
    (eyebrowse--update-window-config-element
     (eyebrowse--current-window-config current-slot current-tag)))
  (jethro/save-eyebrowse-for-perspective))

(defun jethro/save-eyebrowse-for-perspective (&optional frame)
  "Save FRAME's eyebrowse workspace to FRAME's perspective.
FRAME defaults to the current frame."
  (jethro//set-persp-workspace (list (eyebrowse--get 'window-configs frame)
                                     (eyebrowse--get 'current-slot frame)
                                     (eyebrowse--get 'last-slot frame))
                               (get-frame-persp frame)
                               frame))

(add-hook 'persp-before-switch-functions
          #'jethro/update-eyebrowse-for-perspective)
(add-hook 'eyebrowse-post-window-switch-hook
          #'jethro/save-eyebrowse-for-perspective)
(add-hook 'persp-activated-functions
          #'jethro/load-eyebrowse-for-perspective)
(add-hook 'persp-before-save-state-to-file-functions #'jethro/update-eyebrowse-for-perspective)
(add-hook 'persp-after-load-state-functions #'jethro/load-eyebrowse-after-loading-layout)

ibuffer

(with-eval-after-load "ibuffer"

  (require 'ibuf-ext)

  (define-ibuffer-filter persp
      "Toggle current view to buffers of current perspective."
    (:description "persp-mode"
                  :reader (persp-prompt (safe-persp-name (get-frame-persp)) t))
    (find buf (safe-persp-buffers (persp-get-by-name qualifier))))

  (defun persp-update-or-add-ibuffer-group ()
    (let ((perspslist (list (safe-persp-name (get-frame-persp))
                            (cons 'persp (safe-persp-name (get-frame-persp))))))
      (setq ibuffer-saved-filter-groups
            (delete* "persp-mode" ibuffer-saved-filter-groups 
                     :test 'string= :key 'car))
      (add-to-list 'ibuffer-saved-filter-groups (list "persp-mode" perspslist))))

  (add-hook 'ibuffer-mode-hook
            #'(lambda ()
                (persp-update-or-add-ibuffer-group)
                (ibuffer-switch-to-saved-filter-groups "persp-mode"))))

guru-mode

(use-package guru-mode
  :diminish guru-mode
  :init
  (add-hook 'after-init-hook 'guru-global-mode))

Crux

(use-package crux 
  :bind (:map jethro-mode-map
              ("C-c o" . crux-open-with)
              ("C-c n" . crux-cleanup-buffer-or-region)
              ("C-c D" . crux-delete-file-and-buffer)
              ("C-a" . crux-move-beginning-of-line)
              ("M-o" . crux-smart-open-line)
              ("C-c r" . crux-rename-file-and-buffer)
              ("M-d" . crux-duplicate-current-line-or-region)
              ("M-D" . crux-duplicate-and-comment-current-line-or-region)
              ("s-o" . crux-smart-open-line-above)))

Anzu

(use-package anzu
  :diminish anzu-mode
  :init
  (add-hook 'after-init-hook 'global-anzu-mode)
  :config
  (define-key isearch-mode-map [remap isearch-query-replace]  #'anzu-isearch-query-replace)
  (define-key isearch-mode-map [remap isearch-query-replace-regexp] #'anzu-isearch-query-replace-regexp))

avy

Use avy to move between visible text.

(use-package avy
  :bind
  (:map jethro-mode-map
        ("C-'" . avy-goto-char)
        ("C-," . avy-goto-char-2))
  :config
  (setq avy-keys '(?h ?t ?n ?s ?m ?w ?v ?z)))

dumb-jump

Use it to jump to function definitions. Requires no external depedencies.

(use-package dumb-jump
  :bind (("M-g o" . dumb-jump-go-other-window)
         ("M-g j" . dumb-jump-go)
         ("M-g i" . dumb-jump-go-prompt)
         ("M-g x" . dumb-jump-go-prefer-external)
         ("M-g z" . dumb-jump-go-prefer-external-other-window))
  :config (setq dumb-jump-selector 'ivy))

Window switching

(use-package windmove 
  :config
  ;; use command key on Mac
  (windmove-default-keybindings 'super)
  ;; wrap around at edges
  (setq windmove-wrap-around t))

Dired

Requiring dired

(require 'dired)

Dired for Mac OSX

(let ((gls "/usr/local/bin/gls"))
  (if (file-exists-p gls)
      (setq insert-directory-program gls)))

trash files instead of deleting them

(setq delete-by-moving-to-trash t)

find-dired

(require 'find-dired)
(setq find-ls-option '("-print0 | xargs -0 ls -ld" . "-ld"))

Hide details

Hide details and only show file and folder names.

(defun jethro/dired-mode-setup-hook ()
  "hook for dired-mode"
  (dired-hide-details-mode 1))

(add-hook 'dired-mode-hook 'jethro/dired-mode-setup-hook)

Peep Dired

(use-package peep-dired
  :bind (:map peep-dired-mode-map
              ("SPC" . nil)
              ("<backspace>" . nil))
  (setq peep-dired-cleanup-eagerly t))

Sort directories first

(setq dired-listing-switches "-aBhl  --group-directories-first")

Recursive Copying and Deleting

(setq dired-recursive-copies (quote always))
(setq dired-recursive-deletes (quote top))

dired-jump from file

(require 'dired-x)

allow editing of permissions

(use-package wdired
  :config
  (setq wdired-allow-to-change-permissions t))

dired-k

(use-package dired-k
  :config
  (define-key dired-mode-map (kbd "K") 'dired-k)
  (setq dired-k-style 'git))

dired-narrow

(use-package dired-narrow
  :bind (:map dired-mode-map
              ("N" . dired-narrow-fuzzy)))

dired-ranger

(use-package dired-ranger
  :bind (:map dired-mode-map
              ("C" . dired-ranger-copy)
              ("P" . dired-ranger-paste)
              ("M" . dired-ranger-move)))

dired-subtree

The dired-subtree package (part of the magnificent dired hacks) allows you to expand subdirectories in place, like a tree structure.

(use-package dired-subtree
  :config
  (bind-keys :map dired-mode-map
             ("i" . dired-subtree-insert)
             (";" . dired-subtree-remove)))

all-the-icons-dired

(use-package all-the-icons-dired
  :init
  (add-hook 'dired-mode-hook 'all-the-icons-dired-mode))

hydra window movements

(defhydra jethro/window-movement ()
  ("h" windmove-left)
  ("s" windmove-right)
  ("t" windmove-down)
  ("n" windmove-up)
  ("y" other-window "other") 
  ("f" find-file "file")
  ("F" find-file-other-window "other file")
  ("v" (progn (split-window-right) (windmove-right)))
  ("o" delete-other-windows :color blue)
  ("d" delete-window "delete")
  ("q" nil))

(bind-key "M-'" 'jethro/window-movement/body jethro-mode-map)

ibuffer

(use-package ibuffer
  :bind (:map jethro-mode-map
              ([remap list-buffers] . ibuffer))
  :config 
  (setq ibuffer-expert t))

shackle

(use-package shackle
  :diminish shackle-mode
  :if (not (bound-and-true-p disable-pkg-shackle))
  :config
  (shackle-mode 1) 
  (setq shackle-rules 
        '((compilation-mode :select nil)
          ("*undo-tree*" :size 0.25 :align right)
          ("*eshell*" :select t :size 0.3 :align t)
          ("*Shell Command Output*" :select nil)
          ("\\*Async Shell.*\\*" :regexp t :ignore t)
          (occur-mode :select nil :align t)
          ("*Help*" :select t :inhibit-window-quit t :other t)
          ("*Completions*" :size 0.3 :align t)
          ("*Messages*" :select nil :inhibit-window-quit t :other t)
          ("\\*[Wo]*Man.*\\*" :regexp t :select t :inhibit-window-quit t :other t) 
          ("*Calendar*" :select t :size 0.3 :align below)
          ("*info*" :select t :inhibit-window-quit t :same t)
          (magit-status-mode :select t :inhibit-window-quit t :same t)
          (magit-log-mode :select t :inhibit-window-quit t :same t))))

go to matching parentheses

(defun goto-match-paren (arg)
  "Go to the matching parenthesis if on parenthesis, otherwise insert %.
vi style of % jumping to matching brace."
  (interactive "p")
  (cond ((looking-at "\\s\(") (forward-list 1) (backward-char 1))
        ((looking-at "\\s\)") (forward-char 1) (backward-list 1))
        (t (self-insert-command (or arg 1)))))

(bind-key "C-%" 'goto-match-paren jethro-mode-map)

occur

(bind-key "C-c C-o" 'occur jethro-mode-map)

Editing Text

easy-kill

(use-package easy-kill
  :config
  (global-set-key [remap kill-ring-save] 'easy-kill))

visual-regexp

(use-package visual-regexp
  :bind (:map jethro-mode-map
              ("C-M-%" . vr/query-replace)
              ("C-c m" . vr/mc-mark)))

Align Regexp

(defun jethro/align-repeat (start end regexp &optional justify-right after)
  "Repeat alignment with respect to the given regular expression.
If JUSTIFY-RIGHT is non nil justify to the right instead of the
left. If AFTER is non-nil, add whitespace to the left instead of
the right."
  (interactive "r\nsAlign regexp: ")
  (let* ((ws-regexp (if (string-empty-p regexp)
                        "\\(\\s-+\\)"
                      "\\(\\s-*\\)"))
         (complete-regexp (if after
                              (concat regexp ws-regexp)
                            (concat ws-regexp regexp)))
         (group (if justify-right -1 1)))
    (message "%S" complete-regexp)
    (align-regexp start end complete-regexp group 1 t)))

;; Modified answer from http://emacs.stackexchange.com/questions/47/align-vertical-columns-of-numbers-on-the-decimal-point
(defun jethro/align-repeat-decimal (start end)
  "Align a table of numbers on decimal points and dollar signs (both optional)"
  (interactive "r")
  (require 'align)
  (align-region start end nil
                '((nil (regexp . "\\([\t ]*\\)\\$?\\([\t ]+[0-9]+\\)\\.?")
                       (repeat . t)
                       (group 1 2)
                       (spacing 1 1)
                       (justify nil t)))
                nil))

(defmacro jethro/create-align-repeat-x (name regexp &optional justify-right default-after)
  (let ((new-func (intern (concat "jethro/align-repeat-" name))))
    `(defun ,new-func (start end switch)
       (interactive "r\nP")
       (let ((after (not (eq (if switch t nil) (if ,default-after t nil)))))
         (jethro/align-repeat start end ,regexp ,justify-right after)))))

(jethro/create-align-repeat-x "comma" "," nil t)
(jethro/create-align-repeat-x "semicolon" ";" nil t)
(jethro/create-align-repeat-x "colon" ":" nil t)
(jethro/create-align-repeat-x "equal" "=")
(jethro/create-align-repeat-x "math-oper" "[+\\-*/]")
(jethro/create-align-repeat-x "ampersand" "&")
(jethro/create-align-repeat-x "bar" "|")
(jethro/create-align-repeat-x "left-paren" "(")
(jethro/create-align-repeat-x "right-paren" ")" t)
(jethro/create-align-repeat-x "backslash" "\\\\")

(defvar align-regexp-map nil "keymap for `align-regexp'")

(setq align-regexp-map (make-sparse-keymap))
(define-key align-regexp-map (kbd "&") 'jethro/align-repeat-ampersand)
(define-key align-regexp-map (kbd "(") 'jethro/align-repeat-left-paren)
(define-key align-regexp-map (kbd ")") 'jethro/align-repeat-right-paren)
(define-key align-regexp-map (kbd ",") 'jethro/align-repeat-comma)
(define-key align-regexp-map (kbd ".") 'jethro/align-repeat-decimal)
(define-key align-regexp-map (kbd ":") 'jethro/align-repeat-colon)
(define-key align-regexp-map (kbd ";") 'jethro/align-repeat-semicolon)
(define-key align-regexp-map (kbd "=") 'jethro/align-repeat-equal)
(define-key align-regexp-map (kbd "\\") 'jethro/align-repeat-backslash)
(define-key align-regexp-map (kbd "a") 'align)
(define-key align-regexp-map (kbd "c") 'align-current)
(define-key align-regexp-map (kbd "m") 'jethro/align-repeat-math-oper)
(define-key align-regexp-map (kbd "r") 'jethro/align-repeat)
(define-key align-regexp-map (kbd "|") 'jethro/align-repeat-bar)

(bind-key "C-x a" 'align-regexp-map jethro-mode-map)

aggressive-indent

Keep your text indented at all times. Remember to turn this off for indentation-dependent languages like Python and Haml.

(use-package aggressive-indent
  :diminish aggressive-indent-mode
  :config
  (add-hook 'after-init-hook 'global-aggressive-indent-mode)
  (setq aggressive-indent-excluded-modes
        '(bibtex-mode
          cider-repl-mode
          coffee-mode
          comint-mode
          conf-mode
          Custom-mode
          diff-mode
          doc-view-mode
          dos-mode
          erc-mode
          jabber-chat-mode
          haml-mode
          intero-mode
          haskell-mode
          interative-haskell-mode
          haskell-interactive-mode
          image-mode
          makefile-mode
          makefile-gmake-mode
          minibuffer-inactive-mode
          netcmd-mode
          python-mode
          sass-mode
          slim-mode
          special-mode
          shell-mode
          snippet-mode
          eshell-mode
          tabulated-list-mode
          term-mode
          TeX-output-mode
          text-mode
          yaml-mode)))

multiple-cursors

A port of Sublime Text’s multiple-cursors functionality.

(use-package multiple-cursors
  :bind (:map jethro-mode-map
              ("C-M-c" . mc/edit-lines)
              ("C->" . mc/mark-next-like-this)
              ("C-<" . mc/mark-previous-like-this)
              ("C-c C-<" . mc/mark-all-like-this)))

expand-region

Use this often, and in combination with multiple-cursors.

(use-package expand-region
  :bind (:map jethro-mode-map
              ("C-=" . er/expand-region)))

smartparens

(use-package smartparens
  :bind (("C-c h S" . jethro/smartparens/body)
         (:map smartparens-mode-map
               ("C-M-f" . sp-forward-sexp)
               ("C-M-b" . sp-backward-sexp)
               ("C-M-u" . sp-backward-up-sexp)
               ("C-M-d" . sp-down-sexp)
               ("C-M-p" . sp-backward-down-sexp)
               ("C-M-n" . sp-up-sexp)
               ("M-s" . sp-splice-sexp)
               ("C-M-<up>" . sp-splice-sexp-killing-backward)
               ("C-M-<down>" . sp-splice-sexp-killing-forward)
               ("C-M-r" . sp-splice-sexp-killing-around)
               ("C-)" . sp-forward-slurp-sexp)
               ("C-<right>" . sp-forward-slurp-sexp)
               ("C-}" . sp-forward-barf-sexp)
               ("C-<left>" . sp-forward-barf-sexp)
               ("C-(" . sp-backward-slurp-sexp)
               ("C-M-<left>" . sp-backward-slurp-sexp)
               ("C-{" . sp-backward-barf-sexp)
               ("C-M-<right>" . sp-backward-barf-sexp)
               ("M-S" . sp-split-sexp)))  
  :init
  (add-hook 'after-init-hook 'smartparens-global-strict-mode)
  :config
  (require 'smartparens-config)
  (defhydra jethro/smartparens (:hint nil)
    "
Sexps (quit with _q_)
^Nav^            ^Barf/Slurp^                 ^Depth^
^---^------------^----------^-----------------^-----^-----------------
_f_: forward     _<left>_:    slurp forward   _R_:      splice
_b_: backward    _<right>_:   barf forward    _r_:      raise
_u_: backward ↑  _C-<left>_:  slurp backward  _<up>_:   raise backward
_d_: forward ↓   _C-<right>_: barf backward   _<down>_: raise forward
_p_: backward ↓
_n_: forward ↑
^Kill^           ^Misc^                       ^Wrap^
^----^-----------^----^-----------------------^----^------------------
_w_: copy        _j_: join                    _(_: wrap with ( )
_k_: kill        _s_: split                   _{_: wrap with { }
^^               _t_: transpose               _'_: wrap with ' '
^^               _c_: convolute               _\"_: wrap with \" \"
^^               _i_: indent defun"
    ("q" nil)
    ;; Wrapping
    ("(" (lambda (a) (interactive "P") (sp-wrap-with-pair "(")))
    ("{" (lambda (a) (interactive "P") (sp-wrap-with-pair "{")))
    ("'" (lambda (a) (interactive "P") (sp-wrap-with-pair "'")))
    ("\"" (lambda (a) (interactive "P") (sp-wrap-with-pair "\"")))
    ;; Navigation
    ("f" sp-forward-sexp)
    ("b" sp-backward-sexp)
    ("u" sp-backward-up-sexp)
    ("d" sp-down-sexp)
    ("p" sp-backward-down-sexp)
    ("n" sp-up-sexp)
    ;; Kill/copy
    ("w" sp-copy-sexp)
    ("k" sp-kill-sexp)
    ;; Misc
    ("t" sp-transpose-sexp)
    ("j" sp-join-sexp)
    ("s" sp-split-sexp)
    ("c" sp-convolute-sexp)
    ("i" sp-indent-defun)
    ;; Depth changing
    ("R" sp-splice-sexp)
    ("r" sp-splice-sexp-killing-around)
    ("<up>" sp-splice-sexp-killing-backward)
    ("<down>" sp-splice-sexp-killing-forward)
    ;; Barfing/slurping
    ("<right>" sp-forward-slurp-sexp)
    ("<left>" sp-forward-barf-sexp)
    ("C-<left>" sp-backward-barf-sexp)
    ("C-<right>" sp-backward-slurp-sexp))

  ;; Org-mode config

  (sp-with-modes 'org-mode
    (sp-local-pair "'" nil :unless '(sp-point-after-word-p))
    (sp-local-pair "*" "*" :actions '(insert wrap) :unless '(sp-point-after-word-p sp-point-at-bol-p) :wrap "C-*" :skip-match 'sp--org-skip-asterisk)
    (sp-local-pair "_" "_" :unless '(sp-point-after-word-p))
    (sp-local-pair "/" "/" :unless '(sp-point-after-word-p) :post-handlers '(("[d1]" "SPC")))
    (sp-local-pair "~" "~" :unless '(sp-point-after-word-p) :post-handlers '(("[d1]" "SPC")))
    (sp-local-pair "=" "=" :unless '(sp-point-after-word-p) :post-handlers '(("[d1]" "SPC")))
    (sp-local-pair "«" "»"))

  (defun sp--org-skip-asterisk (ms mb me)
    (or (and (= (line-beginning-position) mb)
             (eq 32 (char-after (1+ mb))))
        (and (= (1+ (line-beginning-position)) me)
             (eq 32 (char-after me))))))

zap-up-to-char

(autoload 'zap-up-to-char "misc"
  "Kill up to, but not including ARGth occurrence of CHAR.

  \(fn arg char)"
  'interactive)

(bind-key "M-z" 'zap-up-to-char jethro-mode-map)

move-text

I disabled this, because it interferes with some org-mode keybindings, and I realise I don’t use this as much as I’d like to.

(use-package move-text
  :bind (:map jethro-mode-map
              ("M-<up>" . move-text-up)
              ("M-<down>" . move-text-down)))

ws-butler

Only lines touched get trimmed. If the white space at end of buffer is changed, then blank lines at the end of buffer are truncated respecting require-final-newline. Trimming only happens when saving.

(use-package ws-butler
  :diminish 'ws-butler-mode
  :config
  (add-hook 'prog-mode-hook 'ws-butler-mode))

Linting with Flycheck

(use-package flycheck
  :bind (:map jethro-mode-map
              ("C-c h f" . jethro/hydra-flycheck/body))
  :init
  (add-hook 'prog-mode-hook 'flycheck-mode)
  :config
  (defun jethro/adjust-flycheck-automatic-syntax-eagerness ()
    "Adjust how often we check for errors based on if there are any.
This lets us fix any errors as quickly as possible, but in a
clean buffer we're an order of magnitude laxer about checking."
    (setq flycheck-idle-change-delay
          (if flycheck-current-errors 0.3 3.0)))

  ;; Each buffer gets its own idle-change-delay because of the
  ;; buffer-sensitive adjustment above.
  (make-variable-buffer-local 'flycheck-idle-change-delay)

  ;; Remove newline checks, since they would trigger an immediate check
  ;; when we want the idle-change-delay to be in effect while editing.
  (setq-default flycheck-check-syntax-automatically '(save
                                                      idle-change
                                                      mode-enabled))

  (add-hook 'flycheck-after-syntax-check-hook
            'jethro/adjust-flycheck-automatic-syntax-eagerness)

  (defun flycheck-handle-idle-change ()
    "Handle an expired idle time since the last change.
This is an overwritten version of the original
flycheck-handle-idle-change, which removes the forced deferred.
Timers should only trigger inbetween commands in a single
threaded system and the forced deferred makes errors never show
up before you execute another command."
    (flycheck-clear-idle-change-timer)
    (flycheck-buffer-automatically 'idle-change))

  ;; Temporary workaround: Direnv needs to load PATH before flycheck looks
  ;; for linters
  (setq flycheck-executable-find
        (lambda (cmd)
          (direnv-update-environment default-directory)
          (executable-find cmd)))
  
  (defhydra jethro/hydra-flycheck
    (:pre (progn (setq hydra-lv t) (flycheck-list-errors))
          :post (progn (setq hydra-lv nil) (quit-windows-on "*Flycheck errors*"))
          :hint nil)
    "Errors"
    ("f"  flycheck-error-list-set-filter                            "Filter")
    ("n"  flycheck-next-error                                       "Next")
    ("p"  flycheck-previous-error                                   "Previous")
    ("<" flycheck-first-error                                      "First")
    (">"  (progn (goto-char (point-max)) (flycheck-previous-error)) "Last")
    ("q"  nil))
  (use-package flycheck-pos-tip
    :init
    (add-hook 'flycheck-mode-hook 'flycheck-pos-tip-mode)))

Templating with Yasnippet

(use-package yasnippet
  :diminish yas-global-mode yas-minor-mode
  :init (add-hook 'after-init-hook 'yas-global-mode)
  :config (setq yas-snippet-dirs '("~/.emacs.d/snippets/snippets/")))

Autocompletions with Company

(use-package company
  :diminish company-mode
  :bind (:map company-active-map
              ("M-n" . nil)
              ("M-p" . nil)
              ("C-n" . company-select-next)
              ("C-p" . company-select-previous))
  :init
  (add-hook 'after-init-hook 'global-company-mode)
  :config
  (setq company-dabbrev-ignore-case nil
        company-dabbrev-code-ignore-case nil
        company-dabbrev-downcase nil
        company-idle-delay 0
        company-minimum-prefix-length 2
        company-require-match nil
        company-begin-commands '(self-insert-command)
        company-transformers '(company-sort-by-occurrence))
  (use-package company-quickhelp
    :bind (:map company-active-map
                ("M-h" . company-quickhelp-manual-begin))
    :config (company-quickhelp-mode 1))
  (defun company-mode/backend-with-yas (backend)
    (if (and (listp backend) (member 'company-yasnippet backend))
        backend
      (append (if (consp backend) backend (list backend))
              '(:with company-yasnippet))))

  (setq company-backends (mapcar #'company-mode/backend-with-yas company-backends)))

Spellcheck with Flyspell

(use-package flyspell 
  :ensure f 
  :diminish flyspell-mode
  :init
  (setenv "DICTIONARY" "en_GB")
  :config   
  (add-hook 'text-mode-hook 'flyspell-mode))

Auto-fill-mode

(add-hook 'text-mode-hook 'auto-fill-mode)
(diminish 'auto-fill-mode)

Hippie Expand

(bind-key "M-/" 'hippie-expand)

(setq hippie-expand-try-functions-list
      '(yas-hippie-try-expand
        try-expand-all-abbrevs
        try-complete-file-name-partially
        try-complete-file-name
        try-expand-dabbrev
        try-expand-dabbrev-from-kill
        try-expand-dabbrev-all-buffers
        try-expand-list
        try-expand-line
        try-complete-lisp-symbol-partially
        try-complete-lisp-symbol))

Conveniences

Fill and unfill paragraphs

Stolen from http://endlessparentheses.com/fill-and-unfill-paragraphs-with-a-single-key.html.

(defun endless/fill-or-unfill ()
  "Like `fill-paragraph', but unfill if used twice."
  (interactive)
  (let ((fill-column
         (if (eq last-command 'endless/fill-or-unfill)
             (progn (setq this-command nil)
                    (point-max))
           fill-column)))
    (call-interactively #'fill-paragraph)))

(global-set-key [remap fill-paragraph]
                #'endless/fill-or-unfill)

Keyboard hydra

(defhydra jethro/hydra-draw-box (:color pink)
  "Draw box with IBM single line box characters (ESC to Quit)."
  ("ESC" nil :color blue) ;; Esc to exit.
  ("'" (lambda () (interactive) (insert "")) "top left ┌")
  ("," (lambda () (interactive) (insert "")) "top ┬")
  ("." (lambda () (interactive) (insert "")) "top right ┐")
  ("a" (lambda () (interactive) (insert "")) "left ├")
  ("o" (lambda () (interactive) (insert "")) "center ┼")
  ("e" (lambda () (interactive) (insert "")) "right ┤")
  (";" (lambda () (interactive) (insert "")) "bottom left └")
  ("q" (lambda () (interactive) (insert "")) "bottom ┴")
  ("j" (lambda () (interactive) (insert "")) "bottom right ┘")
  ("k" (lambda () (interactive) (insert "")) "horizontal ─")
  ("x" (lambda () (interactive) (insert "")) "vertical │"))

(bind-key "C-c h d" 'jethro/hydra-draw-box/body jethro-mode-map)

Environment

Direnv

(use-package direnv
  :init
  (add-hook 'after-init-hook 'direnv-mode)
  (setq direnv-always-show-summary t))

Languages

Common Lisp

(use-package slime
  :config
  (setq inferior-lisp-program "sbcl")
  (setq slime-contribs '(slime-fancy))
  (use-package slime-company
    :config
    (slime-setup '(slime-company))))

Emacs Lisp

(bind-key "C-c C-k" 'eval-buffer emacs-lisp-mode-map)

Elixir

elixir-mode

(use-package elixir-mode)

Alchemist

(use-package alchemist)

Docker

(use-package docker
  :commands docker-mode)

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

Nix

(use-package nix-mode
  :config
  (add-hook 'nix-mode-hook (lambda ()
                             (aggressive-indent-mode -1))))

Haskell

(use-package haskell-mode
  :mode ("\\.hs\\'" . haskell-mode)
  :init
  (add-hook 'haskell-mode-hook
            (lambda ()
              (setq compile-command "stack build --fast --test --bench --no-run-tests --no-run-benchmarks"))))

Intero

(use-package intero
  :init
  (add-hook 'haskell-mode-hook 'intero-mode))

Go

(use-package go-mode
  :mode ("\\.go\\'" . go-mode)
  :config
  (add-hook 'go-mode-hook 'compilation-auto-quit-window)
  (add-hook 'go-mode-hook (lambda ()
                            (set (make-local-variable 'company-backends) '(company-go))
                            (company-mode)))
  (add-hook 'go-mode-hook (lambda ()
                            (add-hook 'before-save-hook 'gofmt-before-save)
                            (local-set-key (kbd "M-.") 'godef-jump)))
  (add-hook 'go-mode-hook
            (lambda ()
              (unless (file-exists-p "Makefile")
                (set (make-local-variable 'compile-command)
                     (let ((file (file-name-nondirectory buffer-file-name)))
                       (format "go build %s"
                               file))))))
  (use-package go-dlv
    :config (require 'go-dlv))
  (use-package golint
    :config
    (add-to-list 'load-path (concat (getenv "GOPATH")  "/src/github.com/golang/lint/misc/emacs"))
    (require 'golint))
  (use-package gorepl-mode
    :config (add-hook 'go-mode-hook #'gorepl-mode))
  (use-package company-go
    :config (add-hook 'go-mode-hook (lambda ()
                                      (set (make-local-variable 'company-backends) '(company-go))
                                      (company-mode)))))

C

(defun jethro/compile-c () 
  (unless (file-exists-p "Makefile")
    (set (make-local-variable 'compile-command)
         (let ((file (file-name-nondirectory buffer-file-name)))
           (format "cc -Wall %s -o %s --std=c99"
                   file
                   (file-name-sans-extension file))))))

(add-hook 'c-mode-hook jethro/compile-c)

C++

C++ compile function

(add-hook 'c++-mode-hook
          (lambda ()
            (unless (file-exists-p "Makefile")
              (set (make-local-variable 'compile-command)
                   (let ((file (file-name-nondirectory buffer-file-name)))
                     (format "g++ -Wall -s -pedantic-errors %s -o %s --std=c++14"
                             file
                             (file-name-sans-extension file)))))))

Fish

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

Rust

(use-package rust-mode
  :mode ("\\.rs\\'" . rust-mode))

Python

Python Path

(eval-after-load "python-mode"
  (lambda ()
    (setq python-remove-cwd-from-path t)))

Sphinx Docs

(use-package sphinx-doc
  :init
  (add-hook 'python-mode-hook 'sphinx-doc-mode))

Anaconda

(use-package anaconda-mode
  :init
  (add-hook 'python-mode-hook 'anaconda-mode)
  (add-hook 'python-mode-hook 'anaconda-eldoc-mode))

Company

(use-package company-anaconda
  :config
  (eval-after-load "company"
    '(add-to-list 'company-backends '(company-anaconda))))

isort

(use-package py-isort
  :commands
  (py-isort-buffer py-isort-region))

yapfify

(use-package yapfify)

pytest

(use-package pytest
  :bind (:map python-mode-map
              ("C-c a" . pytest-all)
              ("C-c m" . pytest-module)
              ("C-c ." . pytest-one)
              ("C-c d" . pytest-directory)
              ("C-c p a" . pytest-pdb-all)
              ("C-c p m" . pytest-pdb-module)
              ("C-c p ." . pytest-pdb-one)))

realgud

(use-package realgud)

Highlight Indent Guides

(use-package highlight-indent-guides
  :init
  (add-hook 'python-mode-hook 'highlight-indent-guides-mode)
  :config
  (setq highlight-indent-guides-method 'character))

Isend-mode

(use-package isend-mode
  :bind
  (:map isend-mode-map
        ("C-M-e" . isend-send-defun))
  :init
  (add-hook 'isend-mode-hook 'isend-default-python-setup))

HTML

Web-mode

(use-package web-mode
  :mode (("\\.html\\'" . web-mode)
         ("\\.html\\.erb\\'" . web-mode)
         ("\\.mustache\\'" . web-mode)
         ("\\.jinja\\'" . web-mode)
         ("\\.njk\\'" . web-mode)
         ("\\.php\\'" . web-mode))
  :config
  (setq web-mode-enable-css-colorization t)
  (setq-default css-indent-offset 2
                web-mode-markup-indent-offset 2
                web-mode-css-indent-offset 2
                web-mode-code-indent-offset 2
                web-mode-attr-indent-offset 2))

Emmet-mode

(use-package emmet-mode
  :diminish emmet-mode
  :config
  (add-hook 'web-mode-hook 'emmet-mode)
  (add-hook 'vue-mode-hook 'emmet-mode))

CSS

Rainbow-mode

(use-package rainbow-mode
  :diminish rainbow-mode
  :config
  (add-hook 'css-mode-hook 'rainbow-mode)
  (add-hook 'scss-mode-hook 'rainbow-mode))

SCSS-mode

(use-package scss-mode
  :mode "\\.scss\\'" 
  :config (progn
            (setq scss-compile-at-save nil)))

Javascript

JS2-mode

Here I also added tern-mode. This requires the tern executable:

npm install -g tern
(use-package js2-mode
  :mode ("\\.js\\'" . js2-mode)
  :config
  (setq-default flycheck-disabled-checkers
                (append flycheck-disabled-checkers
                        '(javascript-jshint)))
  (setq js-switch-indent-offset 2)
  (use-package tern
    :diminish tern-mode
    :config 
    (add-hook 'js2-mode-hook 'tern-mode)
    (use-package company-tern
      :config
      (add-to-list 'company-backends 'company-tern))))

Indium

(use-package indium)

Flycheck

(require 'flycheck)
(flycheck-add-mode 'javascript-eslint 'js2-mode)
(flycheck-add-mode 'javascript-eslint 'web-mode)

Skewer

(use-package skewer-mode  
  :bind (:map skewer-mode-map
              ("C-c C-k" . skewer-load-buffer))
  :config
  (add-hook 'js2-mode-hook 'skewer-mode))

js-comint

(use-package js-comint
  :config
  (add-hook 'js2-mode-hook
            (lambda ()
              (local-set-key (kbd "C-x C-e") 'js-send-last-sexp)
              (local-set-key (kbd "C-M-x") 'js-send-last-sexp-and-go)
              (local-set-key (kbd "C-c b") 'js-send-buffer)
              (local-set-key (kbd "C-c C-b") 'js-send-buffer-and-go)
              (local-set-key (kbd "C-c l") 'js-load-file-and-go))))

js-doc

(use-package js-doc
  :bind (:map js2-mode-map
              ("C-c i" . js-doc-insert-function-doc)
              ("@" . js-doc-insert-tag))
  :config
  (setq js-doc-mail-address "jethrokuan95@gmail.com"
        js-doc-author (format "Jethro Kuan <%s>" js-doc-mail-address)
        js-doc-url "http://www.jethrokuan.com/"
        js-doc-license "MIT"))

JS2-refactor

(use-package js2-refactor
  :config
  (add-hook 'js2-mode-hook #'js2-refactor-mode)
  (js2r-add-keybindings-with-prefix "C-c C-j"))

Vue-mode

Additional support for Vue.js projects.

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

React-mode

(defun jethro/setup-rjsx-mode ()  
  (setq-local emmet-expand-jsx-className? t)
  (setq-local web-mode-enable-auto-quoting nil))

(use-package rjsx-mode
  :init
  (add-to-list 'auto-mode-alist '("\\.jsx\\'" . rjsx-mode))
  (add-to-list 'auto-mode-alist '("\\.react.js\\'" . rjsx-mode))
  (add-to-list 'auto-mode-alist '("\\index.android.js\\'" . rjsx-mode))
  (add-to-list 'auto-mode-alist '("\\index.ios.js\\'" . rjsx-mode))
  (add-to-list 'magic-mode-alist '("/\\*\\* @jsx React\\.DOM \\*/" . rjsx-mode))
  (add-to-list 'magic-mode-alist '("^import React" . rjsx-mode))
  (add-hook 'rjsx-mode-hook 'jethro/setup-rjsx-mode)
  (add-hook 'rjsx-mode-hook 'tern-mode)
  (add-hook 'rjsx-mode-hook 'emmet-mode)
  :config
  (with-eval-after-load 'flycheck
    (dolist (checker '(javascript-eslint javascript-standard))
      (flycheck-add-mode checker 'rjsx-mode)))
  (defun jethro/line-align-closing-bracket ()
    "Workaround sgml-mode and align closing bracket with opening bracket"
    (save-excursion
      (beginning-of-line)
      (when (looking-at-p "^ +\/?> *$")
        (delete-char sgml-basic-offset))))
  (advice-add #'js-jsx-indent-line
              :after
              #'jethro/line-align-closing-bracket))

Typescript

typescript-mode

(use-package typescript-mode)

Tide

(defun setup-tide-mode ()
  (interactive)
  (tide-setup)
  (flycheck-mode +1)
  (eldoc-mode +1)
  (tide-hl-identifier-mode +1)
  (company-mode +1))

(use-package tide
  :mode "\\.ts\\'"
  :init
  (add-hook 'before-save-hook 'tide-format-before-save)
  (add-hook 'typescript-mode-hook #'setup-tide-mode)
  :config
  (setq company-tooltip-align-annotations t))

JSON

(use-package json-mode
  :mode "\\.json\\'"
  :config (add-hook 'json-mode-hook (lambda ()
                                      (make-local-variable 'js-indent-level)
                                      (setq js-indent-level 2))))

Markdown

(use-package markdown-mode
  :mode ("\\.md\\'" . markdown-mode)
  :commands (markdown-mode gfm-mode)
  :init
  (setq markdown-fontify-code-blocks-natively t)
  :config 
  (setq markdown-command "multimarkdown --snippet --smart --notes"
        markdown-enable-wiki-links t
        markdown-indent-on-enter 'indent-and-new-item
        markdown-asymmetric-header t
        markdown-live-preview-delete-export 'delete-on-destroy))

(use-package org-table
  :after 'markdown-mode
  :init
  (add-hook 'markdown-mode-hook 'orgtbl-mode))

Clojure

Clojure-mode

(use-package clojure-mode
  :mode (("\\.clj\\'" . clojure-mode)
         ("\\.boot\\'" . clojure-mode)
         ("\\.edn\\'" . clojure-mode)
         ("\\.cljs\\'" . clojurescript-mode)
         ("\\.cljs\\.hl\\'" . clojurescript-mode))
  :init
  (add-hook 'clojure-mode-hook #'eldoc-mode)
  (add-hook 'clojure-mode-hook #'subword-mode)
  (add-hook 'clojure-mode-hook #'cider-mode)
  (add-hook 'clojure-mode-hook #'clj-refactor-mode))

Cider

(use-package cider
  :init
  (add-hook 'cider-mode-hook #'clj-refactor-mode)
  (add-hook 'cider-repl-mode-hook #'company-mode)
  (add-hook 'cider-mode-hook #'company-mode)
  :diminish subword-mode
  :config
  (setq nrepl-log-messages t                  
        cider-repl-display-in-current-window t
        cider-repl-use-clojure-font-lock t    
        cider-prompt-save-file-on-load 'always-save
        cider-font-lock-dynamically '(macro core function var)
        nrepl-hide-special-buffers t
        cider-show-error-buffer nil
        cider-overlays-use-font-lock t
        cider-repl-result-prefix ";; => ")
  (setq cider-cljs-lein-repl "(do (use 'figwheel-sidecar.repl-api) (start-figwheel!) (cljs-repl))")
  (cider-repl-toggle-pretty-printing))

clj-refactor

(use-package clj-refactor
  :defines cljr-add-keybindings-with-prefix
  :diminish clj-refactor-mode
  :config (cljr-add-keybindings-with-prefix "C-c C-j"))

Squiggly-clojure

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

Latex

AucTeX

(use-package auctex
  :defer t
  :config
  (setq TeX-auto-save t
        TeX-parse-self t
        TeX-syntactic-comment t
        ;; Synctex support
        TeX-source-correlate-start-server nil
        ;; Don't insert line-break at inline math
        LaTeX-fill-break-at-separators nil)
  (setq TeX-view-program-list '(("Evince" "evince --page-index=%(outpage) %o")
                                ("qpdfview" "qpdfview %o#%(outpage)")))
  (setq TeX-view-program-selection '((output-pdf "qpdfview")
                                     (output-pdf "Evince")))
  (when latex-enable-auto-fill
    (add-hook 'LaTeX-mode-hook 'latex/auto-fill-mode))
  (when latex-enable-folding
    (add-hook 'LaTeX-mode-hook 'TeX-fold-mode))
  (add-hook 'LaTeX-mode-hook 'LaTeX-math-mode)
  (add-hook 'LaTeX-mode-hook 'TeX-source-correlate-mode)
  (add-hook 'LaTeX-mode-hook 'TeX-PDF-mode))

Autocomplete support

(use-package company-auctex
  :defer t)

Yaml

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

PDFs

We use pdf-tools for PDF viewing, which has first class support for highlighting and annotations.

(use-package pdf-tools
  :defer 10
  :config
  (pdf-tools-install))

Org-Mode

Setup

Basics

We use org-plus-contrib, which contains several contrib plugins that may come in handy later, including org-drill and some org-babel language support.

To install org-plus-contrib, one has to add the package archive to Emacs. This is typically where you also add MELPA.

(when (>= emacs-major-version 24)
  (require 'package)
  (add-to-list 'package-archives '("melpa" . "http://melpa.org/packages/") t)
  (add-to-list 'package-archives '("org" . "http://orgmode.org/elpa/") t)
  (package-initialize))
(use-package org-plus-contrib
  :bind
  (:map jethro-mode-map
        ("C-c l" . org-store-link)
        ("C-c a" . org-agenda)
        ("C-c b" . org-iswitchb)
        ("C-c c" . org-capture))
  :bind
  (:map org-mode-map
        ("M-n" . outline-next-visible-heading)
        ("M-p" . outline-previous-visible-heading)))

Saner Defaults

(setq org-return-follows-link t)

Org Bullets

(use-package org-bullets
  :init
  (add-hook 'org-mode-hook 'org-bullets-mode)
  :config
  (setq org-bullets-bullet-list
        '("" "" "" "" "" "")))

Org Diary file

(setq org-agenda-diary-file "~/.org/diary.org")

Variable Pitch Mode

(add-hook 'org-mode-hook
          '(lambda ()
             (variable-pitch-mode 1) ;; All fonts with variable pitch.
             (mapc
              (lambda (face) ;; Other fonts with fixed-pitch.
                (set-face-attribute face nil :inherit 'fixed-pitch))
              (list 'org-code
                    'org-link
                    'org-block
                    'org-table
                    'org-block-begin-line
                    'org-block-end-line
                    'org-meta-line
                    'org-document-info-keyword))))

Easy Templates

I added an emacs-lisp easy template for my literate Emacs configuration.

(add-to-list 'org-structure-template-alist '("el" "#+BEGIN_SRC emacs-lisp :tangle yes?\n\n#+END_SRC"))

Org Download

This extension facilitates moving images from point A to point B.

(use-package org-download
  :config
  (setq org-download-image-dir "./images"))

Org Mode for GTD

This document aims to extensively document my implementation of Getting Things Done, a methodology by David Allen. This will always be a work-in-progress, and is fully representative of the GTD setup I am currently using.

This document is written primarily for my own reference. However, it is also written with readers who are looking for inspiration when implementing GTD in org-mode.

Reasoning

There is no shortage of existing GTD implementations, in org-mode. Perhaps the best reference document out there is by Bernt Hansen, published here. However, there are some slight deviations from the GTD that David Allen proposes, and some conveniences he takes making the GTD system he implements weaker, that can perhaps be solved by writing some Elisp. This is a major adaptation of his setup, but with additional customizations that make it closer to the ideal system that David Allen speaks about.

Organizing Your Life Into Org-mode Files

Bernt Hansen uses separate files as logical groups, such as a separation between work and life. This may suit your purpose, but this makes it a lot harder to write general Elisp code for. Once a new logical group appears, the code that generates the weekly review would have to change as well, for example.

Instead, I use David Allen’s physical categories as different files, and use org-mode tags to separate the different context. That is, I have the files:

file (.org)Purpose
inboxIncludes everything on your mind: tasks, ideas etc.
somedayIncludes things that will be done later on (with no specific deadline), to be reviewed often
referenceI don’t actually have this file; I use deft-mode as my braindump
waitingThis contains a list of names of people as level one headings, and things I’m waiting for them to complete as subheadings
nextThis contains one-off tasks that don’t belong to projects.
projectsThis contains the list of projects, and their corresponding todo items
(setq org-agenda-files '("~/.org/gtd/inbox.org"
                         "~/.org/gtd/timetable.org"
                         "~/.org/gtd/projects.org"
                         "~/.org/gtd/tickler.org"))

Stage 1: Collecting

Collecting needs to be convenient. This is achieved easily be using org-capture. The capture template is kept simple, to minimize friction in capturing new items as they pop up.

(setq org-capture-templates
      `(("i" "inbox" entry (file "~/.org/gtd/inbox.org")
         "* TODO %?
   Captured %<%Y-%m-%d %H:%M>")
        ("w" "Web site" entry (file "~/.org/deft/websites.org")
         "* %c\n" :immediate-finish t)
        ("p" "paper" entry (file "~/.org/gtd/inbox.org")
         "* TODO Read: %:title\n%a" :immediate-finish t)))

Stage 2: Processing

During predetermined times of each day, the inbox is to be processed, each item in inbox sorted into their respective folders.

org-agenda provides a brilliant interface for viewing and processing the inbox. At the end of the “processing” stage, inbox.org should be empty, unless the processing is done on the whim. This will be facilitated with an iOS or android app later on.

The process is clearly outlined in GTD, but key to the GTD implementation here are a few factors:

  1. Which file: Is this to be done someday when there’s time, or is this a project (old or new), or is this a simple action?
  2. Adding of context: Is this school-related, or work-related? Do I have to be at a specific location to perform this task?

At the end of the process, the item in inbox would have placed in either a non-actionable file, or an actionable file (projects, or next) with a physical actionable. To encourage this, we have a list of verbs.

David Allen recommends processing inbox items top-down or bottom-up, one item at a time. However, I like to have an overview of my inbox, so I can estimate the number of items left to process.

This process is therefore contigent on several factors:

  1. There aren’t too many items in the inbox at the same time. This can prove to be too distracting. Fortunately, I’ve yet to experience this.
  2. Processing of inbox is more regular. Keeping inbox zero at all times should be a goal, but not a priority.

Org Agenda Inbox View

This view is where I see all my inbox items: it is a simple list of captured items in inbox.org.

(require 'org-agenda)
(setq jethro/org-agenda-inbox-view
      `("i" "Inbox" todo ""
        ((org-agenda-files '("~/.org/gtd/inbox.org")))))

Org TODO Keywords

keywordmeaning
TODOAn item that has yet to be processed, or cannot be attempted at this moment.
NEXTAn action that can be completed at this very moment, in the correct context
DONEAn item that is completed, and ready to be archived
WAITINGAn item that awaits input from an external party
HOLDAn item that is delayed due to circumstance
CANCELLEDAn item that was once considered, but no longer to be attempted

WAITING, HOLD, and CANCELLED are all keywords that require supplementary information. For example, who am I waiting for? Or why is this item on hold? As such, it is convenient to trigger a note when an item transitions to these states. Note that the triggers only happen with “slow” state transitions, i.e. C-c C-t.

(setq org-todo-keywords
      '((sequence "TODO(t)" "NEXT(n)" "|" "DONE(d)")
        (sequence "WAITING(w@/!)" "HOLD(h@/!)" "|" "CANCELLED(c@/!)")))

(setq org-log-done 'time)
(setq org-log-into-drawer t)
(setq org-log-state-notes-insert-after-drawers nil)

The Process

Step 1: Clarifying
Tags
(setq org-tag-alist (quote (("@errand" . ?e)
                            ("@office" . ?o)
                            ("@home" . ?h)
                            ("@school" . ?s)
                            (:newline)
                            ("WAITING" . ?w)
                            ("HOLD" . ?H)
                            ("CANCELLED" . ?c))))

(setq org-fast-tag-selection-single-key nil)
Step 2: Organizing

This step involves refiling the item in the appropriate location. We set org-refile-allow-creating-parent-nodes to =’confirm=, because this allows us to create new projects if there are no matches.

When capturing new projects, it helps to pen down a few things about the project:

  1. Project Purpose/Principles
  2. Outcome Vision

This is currently done using org-add-note, but when my elisp-fu gets stronger, I’d create a dedicated buffer with a template each time a project is created.

;; https://github.com/syl20bnr/spacemacs/issues/3094
(setq org-refile-use-outline-path 'file
      org-outline-path-complete-in-steps nil)
(setq org-refile-allow-creating-parent-nodes 'confirm)
(setq org-refile-targets '(("someday.org" :maxlevel . 1)
                           ("projects.org" :maxlevel . 1)))
(defun jethro/org-rename-item ()
  (interactive)
  (save-excursion
    (when (org-at-heading-p)
      (let* ((hl-text (nth 4 (org-heading-components)))
             (new-header (read-string "New Text: " nil nil hl-text)))
        (unless (or (null hl-text)
                    (org-string-match-p "^[ \t]*:[^:]+:$" hl-text))
          (beginning-of-line)
          (search-forward hl-text (point-at-eol))
          (replace-string
           hl-text
           new-header
           nil (- (point) (length hl-text)) (point)))))))

(defun jethro/org-agenda-process-inbox-item (&optional goto rfloc no-update)
  (interactive "P") 
  (org-with-wide-buffer   
   (org-agenda-set-tags) 
   (org-agenda-refile nil nil t)
   ;; (org-mark-ring-push)
   ;; (org-refile-goto-last-stored)
   ;; (jethro/org-rename-item)
   ;; (org-mark-ring-goto)
   (org-agenda-redo)))

(defun jethro/org-inbox-capture ()
  "Capture a task in agenda mode."
  (interactive)
  (org-capture nil "i"))

(define-key org-agenda-mode-map "i" 'org-agenda-clock-in)
(define-key org-agenda-mode-map "r" 'jethro/org-agenda-process-inbox-item)
(define-key org-agenda-mode-map "c" 'jethro/org-inbox-capture)
add advice
(defvar jethro/new-project-template
  "
    *Project Purpose/Principles*:

    *Project Outcome*:
    "
  "Project template, inserted when a new project is created")

(defvar jethro/is-new-project nil
  "Boolean indicating whether it's during the creation of a new project")

(defun jethro/refile-new-child-advice (orig-fun parent-target child)
  (let ((res (funcall orig-fun parent-target child)))
    (save-excursion
      (find-file (nth 1 parent-target))
      (goto-char (org-find-exact-headline-in-buffer child))
      (org-add-note)
      )
    res))

(advice-add 'org-refile-new-child :around #'jethro/refile-new-child-advice)

Clocking in

(defun jethro/set-todo-state-next ()
  "Visit each parent task and change NEXT states to TODO"
  (org-todo "NEXT"))

(add-hook 'org-clock-in-hook 'jethro/set-todo-state-next 'append)

Stage 3: Reviewing

Custom agenda Commands

(setq org-agenda-block-separator nil)
(setq jethro/org-agenda-todo-view
      `(" " "Agenda"
        ((agenda ""
                 ((org-agenda-span 'day)
                  (org-deadline-warning-days 365)))
         (todo "TODO"
               ((org-agenda-overriding-header "To Refile")
                (org-agenda-files '("~/.org/gtd/inbox.org"))))
         (todo "NEXT"
               ((org-agenda-overriding-header "In Progress")
                (org-agenda-files '("~/.org/gtd/someday.org" "~/.org/gtd/projects.org"))
                (org-agenda-skip-function '(org-agenda-skip-entry-if 'deadline 'scheduled))))
         (todo "TODO"
               ((org-agenda-overriding-header "Todo")
                (org-agenda-files '("~/.org/gtd/someday.org" "~/.org/gtd/projects.org"))
                (org-agenda-skip-function '(org-agenda-skip-entry-if 'deadline 'scheduled))))
         nil)))

(setq org-agenda-custom-commands
      `(,jethro/org-agenda-inbox-view
        ,jethro/org-agenda-todo-view))

(defun jethro/org-agenda-skip-all-siblings-but-first ()
  "Skip all but the first non-done entry."
  (let (should-skip-entry)
    (unless (or (org-current-is-todo)
                (not (org-get-scheduled-time (point))))
      (setq should-skip-entry t))
    (save-excursion
      (while (and (not should-skip-entry) (org-goto-sibling t))
        (when (org-current-is-todo)
          (setq should-skip-entry t))))
    (when should-skip-entry
      (or (outline-next-heading)
          (goto-char (point-max))))))

(defun org-current-is-todo ()
  (string= "TODO" (org-get-todo-state)))

(defun jethro/switch-to-agenda ()
  (interactive)
  (org-agenda nil " ")
  (delete-other-windows))

(bind-key "<f10>" 'jethro/switch-to-agenda jethro-mode-map)

Column View

(setq org-columns-default-format "%40ITEM(Task) %Effort(EE){:} %CLOCKSUM(Time Spent) %SCHEDULED(Scheduled) %DEADLINE(Deadline)")

Stage 4: Doing

Org-pomodoro

(use-package org-pomodoro
  :bind
  (:map org-agenda-mode-map
        (("I" . org-pomodoro)))
  :config
  (setq org-pomodoro-format "%s"))

Org Mode for Note taking

Deft

(use-package deft
  :bind
  (:map jethro-mode-map
        ("C-c n" . deft))
  :config
  (setq deft-default-extension "org")
  (setq deft-directory "~/.org/deft/")
  (setq deft-use-filename-as-title t))

Exporting Deft Notes

(defun jethro/org-export-deft-file (file)
  (interactive)
  (org-html-export-to-html t t))

Org Mode for Blogging

Blog Mode

(define-derived-mode blog-mode org-mode "blog")
(add-to-list 'auto-mode-alist '("\\.blog\\'" . blog-mode))

(bind-key "C-c C-c" 'jethro/org-hugo-export blog-mode-map)
(bind-key "C-c TAB" 'jethro/insert-blog-props blog-mode-map)

Helpers

(defun jethro/org-kwds ()
  "parse the buffer and return a cons list of (property . value)
from lines like:
#+PROPERTY: value"
  (org-element-map (org-element-parse-buffer 'element) 'keyword
    (lambda (keyword) (cons (org-element-property :key keyword)
                            (org-element-property :value keyword)))))

(defun jethro/org-kwd (KEYWORD)
  "get the value of a KEYWORD in the form of #+KEYWORD: value"
  (cdr (assoc KEYWORD (jethro/org-kwds))))

(defun now-is ()
  (concat (format-time-string "%Y-%m-%dT%T")
          ((lambda (x) (concat (substring x 0 3) ":" (substring x 3 5)))
           (format-time-string "%z"))))

(defun jethro/promote-everything () 
  "Promote all subtrees in buffer"
  (interactive)
  (save-excursion
    (save-match-data 
      (goto-char (point-min)) 
      (while (search-forward-regexp "^\\*+" nil t)
        (delete-backward-char 1)))))

Exporting

;; http://whyarethingsthewaytheyare.com/setting-up-the-blog/#workflow
(defun jethro/org-hugo-export ()
  "Export current subheading to the hugo blog."
  (interactive)
  ;; Save cursor position
  (save-excursion
    ;; Go to top level heading for subtree (you can change the number 10
    ;; if you /really/ need more than 10 sublevels...)
    ;; (unless (eq (org-current-level) 1)
    ;;   (outline-up-heading 10)) 
    (let* ((hl (org-element-at-point)) 
           (title (org-element-property :title hl)) 
           (slug (org-element-property :SLUG hl))
           (filename (concat (jethro/org-kwd "HUGO_CONTENT_ROOT")
                             (format "%s.org" slug)))
           (date (org-element-property :DATE hl))
           (tags
            (format "%s"
                    (mapconcat 'identity (org-get-tags) " "))))
      ;; Make the export
      (org-copy-subtree)
      (with-temp-buffer (generate-new-buffer filename) 
                        (goto-char (point-min))
                        (org-yank)
                        (goto-char (point-min))
                        (condition-case nil
                            (while (org-promote-subtree)) 
                          (error nil))
                        (goto-char (point-min))
                        (let ((end (search-forward ":END:")))
                          (delete-region (point-min) end))
                        (jethro/promote-everything)
                        (insert "#+TITLE: " title)
                        (insert "\n#+DATE: " date)
                        (insert "\n#+SLUG: " slug)
                        (insert "\n#+TAGS: " tags)
                        (write-file filename)))))

Inserting post properties

(defun jethro/get-post-title (title)
  "Get post title from TITLE"
  (replace-regexp-in-string " " "-" (replace-regexp-in-string "[^a-zA-Z0-9 ]" ""
                                                              (downcase title))))
(defun jethro/insert-blog-props ()
  (interactive)
  (let* ((title (cdr (assoc "ITEM" (org-entry-properties))))
         (slug (jethro/get-post-title title))
         (date (now-is))
         (str (format ":PROPERTIES:
:SLUG:     %s
:DATE:     %s
:END:" slug date)))
    (insert str)))

Exporting PDFs

I use export to LaTeX through ox-latex, using xelatex for a nicer export template.

(setq org-latex-pdf-process
      '("pdflatex -shell-escape -interaction nonstopmode %f"
        "pdflatex -shell-escape -interaction nonstopmode %f"))
(require 'ox-latex)
(setq org-latex-default-table-environment "tabular")
(setq org-latex-tables-booktabs t)
(setq org-latex-listings 'minted)
(setq org-format-latex-options (plist-put org-format-latex-options :scale 2.0))
(setq org-latex-classes
      '(("article"
         "\\documentclass{article}
\\usepackage[margin=1in]{geometry}
\\usepackage{amsmath,amsthm,amssymb}

\\newcommand{\N}{\mathbb{N}}
\\newcommand{\Z}{\mathbb{Z}}

\\usepackage{hyperref}
\\usepackage{minted}
\\usepackage{tabularx}
\\usepackage{parskip}
\\linespread{1.1}
\\renewcommand\\headrulewidth{0.4pt}
\\renewcommand\\footrulewidth{0.4pt}
\\setlength\\columnsep{10pt}
\\setlength{\\columnseprule}{1pt}"
         ("\\section{%s}" . "\\section*{%s}")
         ("\\subsection{%s}" . "\\subsection*{%s}")
         ("\\subsubsection{%s}" . "\\subsubsection*{%s}")
         ("\\paragraph{%s}" . "\\paragraph*{%s}")
         ("\\subparagraph{%s}" . "\\subparagraph*{%s}"))
        ("book"
         "\\documentclass[10pt]{memoir}
                        \\usepackage{charter}
                        \\usepackage[T1]{fontenc}
                        \\usepackage{booktabs}
                        \\usepackage{amsmath}
                        \\usepackage{minted}
                        \\usemintedstyle{borland}
                        \\usepackage{color}
                        \\usepackage{epigraph}
                        \\usepackage{enumitem}
                        \\setlist{nosep}
                        \\setlength\\epigraphwidth{13cm}
                        \\setlength\\epigraphrule{0pt}
                        \\usepackage{fontspec}
                        \\usepackage{graphicx}
                        \\usepackage{hyperref}
                        \\hypersetup {colorlinks = true, allcolors = red}
                        \\title{}
                        [NO-DEFAULT-PACKAGES]
                        [NO-PACKAGES]"
         ("\\chapter{%s}" . "\\chapter*{%s}")
         ("\\section{%s}" . "\\section*{%s}")
         ("\\subsection{%s}" . "\\subsection*{%s}")
         ("\\subsubsection{%s}" . "\\subsubsection*{%s}")
         ("\\paragraph{%s}" . "\\paragraph*{%s}")
         ("\\subparagraph{%s}" . "\\subparagraph*{%s}"))
        ("latex-notes"
         "\\documentclass[8pt]{article}
  \\usepackage[margin={0.2in,0.2in}, a4paper,landscape]{geometry}
  \\usepackage{hyperref}
  \\usepackage{amsmath}
  \\usepackage{multicol}
  \\usepackage{booktabs}
  \\usepackage{enumitem}
  \\usepackage[compact]{titlesec}
  \\titlespacing{\\section}{0pt}{*2}{*0}
  \\titlespacing{\\subsection}{0pt}{*2}{*0}
  \\titlespacing{\\subsubsection}{0pt}{*2}{*0}
  \\titleformat*{\\section}{\\large\\bfseries}
  \\titleformat*{\\subsection}{\\normalsize\\bfseries}
  \\titleformat*{\\subsubsection}{\\normalsize\\bfseries}
  \\setlist[itemize]{leftmargin=*}
  \\setlist[enumerate]{leftmargin=*}
  \\setlength\\columnsep{5pt}
  \\setlength{\\columnseprule}{1pt}
  \\setlength{\\parindent}{0cm}
  \\setlist{nosep}         
  \\usepackage{minted}
  \\usemintedstyle{bw}
  \\usemintedstyle[java]{bw}
  \\setminted[]{frame=none,fontsize=\\footnotesize,linenos=false}
  "
         ("\\section{%s}" . "\\section*{%s}")
         ("\\subsection{%s}" . "\\subsection*{%s}")
         ("\\subsubsection{%s}" . "\\subsubsection*{%s}")
         ("\\paragraph{%s}" . "\\paragraph*{%s}")
         ("\\subparagraph{%s}" . "\\subparagraph*{%s}"))))

(defun jethro/org-multicol-to-latex (async subtreep visible-only body-only)
  (let ((contents (buffer-string))
        (buffer-name (file-name-sans-extension buffer-file-name)))
    (with-temp-buffer
      (insert "#+LATEX_CLASS: latex-notes\n")
      (insert contents)
      (goto-char (point-min))
      (org-next-visible-heading 1)
      (insert "#+BEGIN_EXPORT latex\n\\begin{multicols*}{4}\n#+END_EXPORT\n")
      (goto-char (point-max))
      (insert "#+BEGIN_EXPORT latex\n\\end{multicols*}\n#+END_EXPORT")
      (org-export-to-file 'latex (format "%s.tex" buffer-name)
        async subtreep visible-only body-only nil))))

(defun jethro/org-multicol-to-pdf (async subtreep visible-only body-only)
  (let ((contents (buffer-string))
        (buffer-name (file-name-sans-extension buffer-file-name)))
    (with-temp-buffer
      (insert "#+LATEX_CLASS: latex-notes\n")
      (insert contents)
      (goto-char (point-min))
      (org-next-visible-heading 1)
      (insert "#+BEGIN_EXPORT latex\n\\begin{multicols*}{4}\n#+END_EXPORT\n")
      (goto-char (point-max))
      (insert "#+BEGIN_EXPORT latex\n\\end{multicols*}\n#+END_EXPORT")
      (org-export-to-file 'latex (format "%s.tex" buffer-name)
        async subtreep visible-only body-only nil
        (lambda (file) (org-latex-compile file))))))

(org-export-define-derived-backend 'latex-notes 'latex
                                   :menu-entry
                                   '(?L "Export to LaTeX notes"
                                        ((?l "Export to LaTeX" jethro/org-multicol-to-latex)
                                         (?p "Export to PDF" jethro/org-multicol-to-pdf))))

Org Mode for Reading papers

(use-package org-ref
  :after org
  :config
  (setq org-ref-notes-directory "~/.org/papers/"
        org-ref-bibliography-notes "~/.org/papers/index.org"
        org-ref-default-bibliography '("~/.org/papers/index.bib")
        org-ref-pdf-directory "~/.org/papers/lib/")
  (setq ivy-bibtex-bibliography "~/.org/papers/index.bib" ;; where your references are stored
        ivy-bibtex-library-path "~/.org/papers/lib/" ;; where your pdfs etc are stored
        ivy-bibtex-notes-path "~/.org/papers/index.org" ;; where your notes are stored
        bibtex-completion-bibliography "~/.org/papers/index.bib" ;; writing completion
        bibtex-completion-notes-path "~/.org/papers/index.org"
        bibtex-completion-library-path "~/.org/papers/lib/")
  (setq bibtex-completion-pdf-symbol "")
  (setq bibtex-completion-notes-symbol "")
  (setq bibtex-autokey-year-length 4
        bibtex-autokey-name-year-separator "-"
        bibtex-autokey-year-title-separator "-"
        bibtex-autokey-titleword-separator "-"
        bibtex-autokey-titlewords 2
        bibtex-autokey-titlewords-stretch 1
        bibtex-autokey-titleword-length 5)
  (require 'org-ref-bibtex)
  (key-chord-define-global "jj" 'org-ref-bibtex-hydra/body) 
  (require 'org-ref-url-utils)
  (require 'org-ref-arxiv))

Project Management

Version Control

vc

(use-package vc
  :bind (:map jethro-mode-map
              ("C-x v =" . jethro/vc-diff)
              ("C-x v H" . vc-region-history)) ; New command in emacs 25.x
  :config
  (progn
    (defun jethro/vc-diff (no-whitespace)
      "Call `vc-diff' as usual if buffer is not modified.
If the buffer is modified (yet to be saved), call `diff-buffer-with-file'.
If NO-WHITESPACE is non-nil, ignore all white space when doing diff."
      (interactive "P")
      (let* ((no-ws-switch '("-w"))
             (vc-git-diff-switches (if no-whitespace
                                       no-ws-switch
                                     vc-git-diff-switches))
             (vc-diff-switches (if no-whitespace
                                   no-ws-switch
                                 vc-diff-switches))
             (diff-switches (if no-whitespace
                                no-ws-switch
                              diff-switches))
             ;; Set `current-prefix-arg' to nil so that the HISTORIC arg
             ;; of `vc-diff' stays nil.
             current-prefix-arg)
        (if (buffer-modified-p)
            (diff-buffer-with-file (current-buffer))
          (call-interactively #'vc-diff))))))

Smerge-mode

Useful when handling git merge conflicts.

(use-package smerge-mode
  :bind (:map jethro-mode-map
              ("C-c h s" . jethro/hydra-smerge/body))
  :init
  (progn
    (defun jethro/enable-smerge-maybe ()
      "Auto-enable `smerge-mode' when merge conflict is detected."
      (save-excursion
        (goto-char (point-min))
        (when (re-search-forward "^<<<<<<< " nil :noerror)
          (smerge-mode 1))))
    (add-hook 'find-file-hook #'jethro/enable-smerge-maybe :append))
  :config 
  (defalias 'smerge-keep-upper 'smerge-keep-mine)
  (defalias 'smerge-keep-lower 'smerge-keep-other)
  (defalias 'smerge-diff-base-upper 'smerge-diff-base-mine)
  (defalias 'smerge-diff-upper-lower 'smerge-diff-mine-other)
  (defalias 'smerge-diff-base-lower 'smerge-diff-base-other)

  (defhydra jethro/hydra-smerge (:color pink
                                        :hint nil
                                        :pre (smerge-mode 1)
                                        ;; Disable `smerge-mode' when quitting hydra if
                                        ;; no merge conflicts remain.
                                        :post (smerge-auto-leave))
    "
   ^Move^       ^Keep^               ^Diff^                 ^Other^
   ^^-----------^^-------------------^^---------------------^^-------
   _n_ext       _b_ase               _<_: upper/base        _C_ombine
   _p_rev       _u_pper              _=_: upper/lower       _r_esolve
   ^^           _l_ower              _>_: base/lower        _k_ill current
   ^^           _a_ll                _R_efine
   ^^           _RET_: current       _E_diff
   "
    ("n" smerge-next)
    ("p" smerge-prev)
    ("b" smerge-keep-base)
    ("u" smerge-keep-upper)
    ("l" smerge-keep-lower)
    ("a" smerge-keep-all)
    ("RET" smerge-keep-current)
    ("\C-m" smerge-keep-current)
    ("<" smerge-diff-base-upper)
    ("=" smerge-diff-upper-lower)
    (">" smerge-diff-base-lower)
    ("R" smerge-refine)
    ("E" smerge-ediff)
    ("C" smerge-combine-with-next)
    ("r" smerge-resolve)
    ("k" smerge-kill-current)
    ("q" nil "cancel" :color blue)))

Magit

(use-package magit
  :bind (:map jethro-mode-map
              ("s-g" . magit-status)
              ("C-c g" . magit-status)
              ("s-G" . magit-blame)
              ("C-c G" . magit-blame))
  :init
  (add-hook 'magit-mode-hook 'hl-line-mode)
  :config
  (setq magit-auto-revert-mode nil))

Projectile

(use-package projectile
  :demand t
  :init
  (setq projectile-keymap-prefix (kbd "C-x p"))
  (add-hook 'after-init-hook 'projectile-global-mode)
  :config
  (require 'projectile)
  (use-package counsel-projectile
    :bind (:map jethro-mode-map
                ("s-f" . counsel-projectile-find-file)
                ("s-b" . counsel-projectile-switch-to-buffer)
                ("C-c s" . jethro/counsel-projectile-rg))
    :config
    (defun jethro/counsel-projectile-rg (&optional options)
      "Ivy version of `projectile-rg'."
      (interactive)
      (if (projectile-project-p)
          (let* ((options
                  (if current-prefix-arg
                      (read-string "options: ")
                    options))
                 (ignored
                  (unless (eq (projectile-project-vcs) 'git)
                    ;; rg supports git ignore files
                    (append
                     (cl-union (projectile-ignored-files-rel) grep-find-ignored-files)
                     (cl-union (projectile-ignored-directories-rel) grep-find-ignored-directories))))
                 (options
                  (concat options " "
                          (mapconcat (lambda (i)
                                       (concat "--ignore-file " (shell-quote-argument i)))
                                     ignored
                                     " "))))
            (counsel-rg (ivy-thing-at-point)
                        (projectile-project-root)
                        options
                        (projectile-prepend-project-name "rg")))
        (user-error "You're not in a project")))
    (counsel-projectile-on))
  (setq projectile-use-git-grep t)
  (setq projectile-create-missing-test-files t)
  (setq projectile-completion-system 'ivy)

  (setq projectile-switch-project-action
        #'projectile-commander)
  (def-projectile-commander-method ?S
    "Run a search in the project"
    (counsel-projectile-rg))
  (def-projectile-commander-method ?s
    "Open a *eshell* buffer for the project."
    (projectile-run-eshell))
  (def-projectile-commander-method ?d
    "Open project root in dired."
    (projectile-dired))
  (def-projectile-commander-method ?g
    "Show magit status."
    (magit-status))
  (def-projectile-commander-method ?j
    "Jack-in."
    (let* ((opts (projectile-current-project-files))
           (file (ivy-read
                  "Find file: " 
                  opts)))
      (find-file (expand-file-name
                  file (projectile-project-root)))
      (run-hooks 'projectile-find-file-hook)
      (cider-jack-in))))

ivy switch persp

(defun jethro/ivy-persp-switch-project (arg)
  (interactive "P")
  (ivy-read "Switch to Project Perspective: "
            (if (projectile-project-p)
                (cons (abbreviate-file-name (projectile-project-root))
                      (projectile-relevant-known-projects))
              projectile-known-projects)
            :action (lambda (project)
                      (let ((persp-reset-windows-on-nil-window-conf t))
                        (persp-switch project)
                        (let ((projectile-completion-system 'ivy))
                          (projectile-switch-project-by-name project))))))

(bind-key "C-x p p" 'jethro/ivy-persp-switch-project jethro-mode-map)

Magithub (Disabled)

(use-package magithub
  :after magit
  :config (magithub-feature-autoinject t))

Miscellaneous

SOS

Search Stack Overflow

(use-package sos
  :commands (sos))

which-key

(use-package which-key
  :diminish which-key-mode
  :init
  (add-hook 'after-init-hook 'which-key-mode))

Olivetti

(use-package olivetti
  :bind (:map jethro-mode-map
              ("C-c M o" . olivetti-mode)))

bury-successful-compilation

Closes compile buffer if there are no errors.

(use-package bury-successful-compilation
  :init
  (add-hook 'after-init-hook 'bury-successful-compilation))

Load Custom File

Custom file should take precedence.

(load custom-file)