Permalink
Switch branches/tags
Nothing to show
Find file Copy path
a2436e2 Oct 12, 2018
1 contributor

Users who have contributed to this file

2673 lines (2468 sloc) 90.6 KB

Jethro’s Emacs Configuration

Introduction

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

If you have want to contact me for any reason, you can reach me at my email: jethrokuan95@gmail.com.

Notes:

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

About the Author

I’m currently a Computer Science undergraduate at the National University of Singapore. My configuration centers around my daily Emacs usage, which involves:

  1. Backend development (primarily in Python and JS)
  2. Frontend development (HTML/CSS/JS/React)
  3. ML/DL stuff (Python)
  4. Email (notmuch)
  5. RSS (elfeed + elfeed-org)
  6. Getting Things Done (org-mode)
(setq user-full-name "Jethro Kuan"
      user-mail-address "jethrokuan95@gmail.com")

Preinit: use-package

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

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

(eval-when-compile
  (require 'use-package)
  (require 'diminish)
  (require 'bind-key))

(setq use-package-always-ensure t)

;; Always demand if daemon-mode
(setq use-package-always-demand (daemonp))

Better Defaults

Function Redefinition

Some warnings are generated when functions are redefined via defadvice. For example:

ad-handle-definition: `tramp-read-passwd’ got redefined

To suppress these warnings, we accept these redefinitions:

(setq ad-redefinition-action 'accept)

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.

(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)
  :config
  (setq auto-save-file-name-transforms
        `((".*" ,(no-littering-expand-var-file-name "auto-save/") t))))

Reloading Emacs Config

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

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

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

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.

(use-package autorevert
  :ensure f
  :diminish t
  :hook
  (dired-mode . auto-revert-mode)
  :config
  (global-auto-revert-mode +1)
  :custom
  (auto-revert-verbose nil))

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.

(delete-selection-mode 1)

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)

No lockfiles

Emacs creates lock files to make sure that only one instance of emacs is editing a particular file. However, this often interferes with some of the language server stuff that facilitates auto-completion, among other things. Since I use the emacs daemon, I won’t face these issues, and hence I turn it off.

(setq create-lockfiles nil)

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)

compile with <f9>

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

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

Auto-saving on focus out

Auto save all open buffers, when Emacs loses focus.

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

goto-addr

(use-package goto-addr
  :hook ((compilation-mode . goto-address-mode)
         (prog-mode . goto-address-prog-mode)
         (eshell-mode . goto-address-mode)
         (shell-mode . goto-address-mode))
  :bind (:map goto-address-highlight-keymap
              ("<RET>" . goto-address-at-point)
              ("M-<RET>" . newline))
  :commands (goto-address-prog-mode
             goto-address-mode))

Email

Managing email in emacs is not so simple. Thankfully, I use NixOS, which provides a reproducible environment for my email setup. You can see it here.

The setup involves running mbsync every 5 minutes for a bidirectional sync using the IMAP protocol. I use Gmail as my mail store, and pass to provide my account credentials.

mbsync configuration

(use-package notmuch
  :preface (setq-default notmuch-command (executable-find "notmuch"))
  :if (executable-find "notmuch")
  :bind (("<f2>" . notmuch)
         :map notmuch-search-mode-map
         ("t" . jethro/notmuch-toggle-read)
         ("r" . notmuch-search-reply-to-thread)
         ("R" . notmuch-search-reply-to-thread-sender)
         :map notmuch-show-mode-map
         ("<tab>" . org-next-link)
         ("<backtab>". org-previous-link)
         ("C-<return>" . browse-url-at-point))
  :config
  (defun jethro/notmuch-toggle-read ()
    "toggle read status of message"
    (interactive)
    (if (member "unread" (notmuch-search-get-tags))
        (notmuch-search-tag (list "-unread"))
      (notmuch-search-tag (list "+unread"))))
  :custom
  (message-auto-save-directory "~/.mail/drafts/")
  (message-send-mail-function 'message-send-mail-with-sendmail)
  (sendmail-program (executable-find "msmtp"))

  ;; We need this to ensure msmtp picks up the correct email account
  (message-sendmail-envelope-from 'header)
  (mail-envelope-from 'header)
  (mail-specify-envelope-from t)
  (message-sendmail-f-is-evil nil)
  (message-kill-buffer-on-exit t)
  (notmuch-always-prompt-for-sender t)
  (notmuch-archive-tags '("-inbox" "-unread"))
  (notmuch-crypto-process-mime t)
  (notmuch-hello-sections '(notmuch-hello-insert-saved-searches))
  (notmuch-labeler-hide-known-labels t)
  (notmuch-search-oldest-first nil)
  (notmuch-archive-tags '("-inbox" "-unread"))
  (notmuch-message-headers '("To" "Cc" "Subject" "Bcc"))
  (notmuch-saved-searches '((:name "unread" :query "tag:unread")
                            (:name "to-me" :query "tag:to-me")
                            (:name "sent" :query "tag:sent")
                            (:name "personal" :query "tag:personal")
                            (:name "nushackers" :query "tag:nushackers")
                            (:name "nus" :query "tag:nus"))))

Org-mode Integration

I use org-mode to manage everything. org-notmuch provides the facility to capture email into a task.

(use-package org-notmuch
  :ensure f
  :after org notmuch
  :bind
  (:map notmuch-show-mode-map
        ("C" . jethro/org-capture-email))
  :config
  (defun jethro/org-capture-email ()
    (interactive)
    (org-capture nil "e")))

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

Zenburn

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

Tao-theme

(use-package tao-theme
  :init
  (load-theme 'tao-yang t)
  :config
  (use-package color-identifiers-mode
    :defer 3))

Rainbow-delimiters-mode

We use rainbow delimiters to show imbalanced parenthesis.

(use-package rainbow-delimiters
  :defer 5
  :config
  (rainbow-delimiters-mode +1)
  (set-face-attribute 'rainbow-delimiters-unmatched-face nil
                      :foreground 'unspecified
                      :inherit 'error))

Color-identifiers mode

(use-package color-identifiers-mode
  :config
  (color-identifiers-mode +1))

Remove blinking cursor

(blink-cursor-mode 0)

Add PATH to shell

Only if on Mac OSX.

(use-package exec-path-from-shell
  :ensure f
  :if (memq window-system '(mac ns))
  :config
  (exec-path-from-shell-initialize))

Eshell

(use-package eshell
  :commands eshell
  :bind
  (("C-x m" . jethro/eshell-here))
  :config
  (require 'em-smart)
  (let ((bash (executable-find "bash")))
    (setq-default explicit-shell-file-name bash)
    (setq-default shell-file-name bash))
  (use-package esh-autosuggest
    :hook (eshell-mode . esh-autosuggest-mode))
  (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 "*"))))))
  (defun eshell/x ()
    (unless (one-window-p)
      (delete-window))
    (eshell/exit))
  :custom
  (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))

Web Browsing with eww

(use-package eww
  :bind
  (:map eww-mode-map
        ("o" . eww)
        ("O" . eww-browse-with-external-browser)
        ("j" . next-line)
        ("k" . previous-line))
  :init
  (add-hook 'eww-mode-hook #'toggle-word-wrap)
  (add-hook 'eww-mode-hook #'visual-line-mode)
  :custom
  (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)))
  (shr-external-browser 'browse-url-generic)
  (browse-url-browser-function 'browse-url-firefox)
  (browse-url-new-window-flag  t)
  (browse-url-firefox-new-window-is-tab t))

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

Reading feeds with elfeed

(use-package elfeed
  :bind
  (("<f6>" . elfeed))
  :custom
  (shr-width 80))

elfeed-org

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

syncing elfeed database

(defun jethro/elfeed-load-db-and-open ()
  "Wrapper to load the elfeed db from disk before opening"
  (interactive)
  (elfeed-db-load)
  (elfeed)
  (elfeed-search-update--force))

(defun jethro/elfeed-save-db-and-bury ()
  "Wrapper to save the elfeed db to disk before burying buffer"
  (interactive)
  (elfeed-db-save)
  (quit-window))

(bind-key "<f6>" 'jethro/elfeed-load-db-and-open)

(eval-after-load 'elfeed-search
  '(define-key elfeed-search-mode-map (kbd "q") 'jethro/elfeed-save-db-and-bury))

Core Utilities

Hydra

(use-package hydra)

Ivy

Ivy is generic completion frontend for Emacs. Ivy is more efficient, simpler and more customizable.

flx

Flx is required for fuzzy-matching.

(use-package flx)

Counsel

Counsel contains ivy enhancements for commonly-used functions.

(use-package counsel
  :diminish ivy-mode
  :bind
  (("C-c C-r" . ivy-resume)
   ("M-x" . counsel-M-x)
   ("C-c i" . counsel-imenu)
   ("C-x b" . ivy-switch-buffer)
   ("C-x B" . ivy-switch-buffer-other-window)
   ("C-x k" . kill-buffer)
   ("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 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))
  :custom
  (counsel-find-file-at-point t)
  (ivy-use-virtual-buffers t)
  (ivy-display-style 'fancy)
  (ivy-initial-inputs-alist nil)
  (ivy-use-selectable-prompt t)
  (ivy-re-builders-alist
   '((ivy-switch-buffer . ivy--regex-plus)
     (swiper . ivy--regex-plus)
     (t . ivy--regex-fuzzy)))
  :config
  (ivy-mode +1)
  (ivy-set-actions
   t
   '(("I" insert "insert")))
  (ivy-set-occur 'ivy-switch-buffer 'ivy-switch-buffer-occur))

Swiper

(use-package swiper
  :bind
  (("C-s" . swiper)
   ("C-r" . swiper)
   ("C-c C-s" . counsel-grep-or-swiper)
   :map swiper-map
   ("M-q" . swiper-query-replace)
   ("C-l". swiper-recenter-top-bottom)
   ("C-." . swiper-mc)
   ("C-'" . swiper-avy))
  :custom
  (counsel-grep-swiper-limit 20000)
  (counsel-rg-base-command
   "rg -i -M 120 --no-heading --line-number --color never %s .")
  (counsel-grep-base-command
   "rg -i -M 120 --no-heading --line-number --color never '%s' %s"))

wgrep

wgrep allows you to edit a grep buffer and apply those changes to the file buffer.

(use-package wgrep
  :commands
  wgrep-change-to-wgrep-mode
  ivy-wgrep-change-to-wgrep-mode)

rg

(use-package rg
  :bind* (("M-s" . rg)))

Visual Enhancements

Whitespace-mode

(use-package whitespace
  :ensure f
  :diminish whitespace-mode
  :hook (prog-mode . whitespace-mode)
  :custom
  (whitespace-line-column 80)
  (whitespace-style '(face lines-tail)))

Moody

(use-package moody
  :config
  (setq x-underline-at-descent-line t)
  (moody-replace-mode-line-buffer-identification)
  (moody-replace-vc-mode))

Minions

(use-package minions
  :config
  (minions-mode +1))

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

beacon

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

(use-package beacon
  :defer 10
  :diminish beacon-mode
  :custom
  (beacon-push-mark 10)
  :config
  (beacon-mode +1))

Show Matching parenthesis

Always show matching parenthesis.

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

volatile-highlights

Highlights recently copied/pasted text.

(use-package volatile-highlights
  :defer 5
  :diminish volatile-highlights-mode
  :config
  (volatile-highlights-mode +1))

diff-hl

(use-package diff-hl
  :defer 3
  :bind (("C-c h v" . jethro/hydra-diff-hl/body))
  :hook
  (dired-mode . diff-hl-dired-mode)
  :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")))

Moving Around

Scroll Other Window

This minor mode changes the binding of scroll-other-window based on the active major mode.

(defvar-local sow-scroll-up-command nil)

(defvar-local sow-scroll-down-command nil)

(defvar sow-mode-map
  (let ((km (make-sparse-keymap)))
    (define-key km (kbd "C-M-v") 'sow-scroll-other-window-down)
    (define-key km (kbd "C-M-V") ' scroll-other-window)
    (define-key km [remap scroll-other-window] 'sow-scroll-other-window)
    (define-key km [remap scroll-other-window-down] 'sow-scroll-other-window-down)
    km)
  "Keymap used for `sow-mode'")

(define-minor-mode sow-mode
  "FIXME: Not documented."
  nil nil nil
  :global t)

(defun sow-scroll-other-window (&optional arg)
  (interactive "P")
  (sow--scroll-other-window-1 arg))

(defun sow-scroll-other-window-down (&optional arg)
  (interactive "P")
  (sow--scroll-other-window-1 arg t))

(defun sow--scroll-other-window-1 (n &optional down-p)
  (let* ((win (other-window-for-scrolling))
         (cmd (with-current-buffer (window-buffer win)
                (if down-p
                    (or sow-scroll-down-command #'scroll-up-command)
                  (or sow-scroll-up-command #'scroll-down-command)))))
    (with-current-buffer (window-buffer win)
      (save-excursion
        (goto-char (window-point win))
        (with-selected-window win
          (funcall cmd n))
        (set-window-point win (point))))))

(add-hook 'Info-mode-hook
          (lambda nil
            (setq sow-scroll-up-command
                  (lambda (_) (Info-scroll-up))
                  sow-scroll-down-command
                  (lambda (_) (Info-scroll-down)))))

(add-hook 'doc-view-mode-hook
          (lambda nil
            (setq sow-scroll-up-command
                  'doc-view-scroll-up-or-next-page
                  sow-scroll-down-command
                  'doc-view-scroll-down-or-previous-page)))

(add-hook 'pdf-view-mode-hook
          (lambda nil
            (setq sow-scroll-up-command
                  'pdf-view-scroll-up-or-next-page
                  sow-scroll-down-command
                  'pdf-view-scroll-down-or-previous-page)))
(sow-mode +1)

Eyebrowse

(use-package eyebrowse
  :bind* (("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))
  :config
  (eyebrowse-mode +1))

Crux

(use-package crux
  :bind (("C-c o" . crux-open-with)
         ("C-c C" . 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-and-comment-current-line-or-region)
         ("s-o" . crux-smart-open-line-above)))

avy

Use avy to move between visible text.

(use-package avy
  :bind*
  (("C-'" . avy-goto-char-timer))
  :custom
  (avy-keys '(?h ?t ?n ?s ?m ?w ?v ?z)))

smart-jump

This packages tries to smartly go to definition leveraging several methods to do so.

If one method fails, this package will go on to the next one, eventually falling back to dumb-jump.

(use-package smart-jump
  :defer 5
  :config
  (smart-jump-setup-default-registers))

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

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
  :commands wdired-mode wdired-change-to-wdired-mode
  :custom
  (wdired-allow-to-change-permissions t))

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
  :bind
  (:map dired-mode-map
        ("i" . dired-subtree-insert)
        (";" . dired-subtree-remove)))

ibuffer

(use-package ibuffer
  :bind (([remap list-buffers] . ibuffer))
  :custom
  (ibuffer-expert t))

shackle

(use-package shackle
  :diminish shackle-mode
  :if (not (bound-and-true-p disable-pkg-shackle))
  :custom
  (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)))
  :config
  (shackle-mode +1))

Editing Text

easy-kill

(use-package easy-kill
  :bind*
  (([remap kill-ring-save] . easy-kill)))

visual-regexp

(use-package visual-regexp
  :bind (("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)

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
  (global-aggressive-indent-mode +1)
  :custom
  (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 (("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 (("C-=" . er/expand-region)))

smartparens

(use-package smartparens
  :bind (: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))
  :config
  (require 'smartparens-config)
  (smartparens-global-strict-mode +1)
  ;; 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)

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
  :hook
  (prog-mode . ws-butler-mode))

Linting with Flycheck

(use-package flycheck
  :bind (("C-c h f" . jethro/hydra-flycheck/body))
  :hook
  (prog-mode . 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
  :after flycheck
  :hook
  (flycheck-mode . flycheck-pos-tip-mode))

Templating with Yasnippet

(use-package yasnippet
  :diminish yas-global-mode yas-minor-mode
  :config
  (yas-global-mode +1)
  :custom
  (yas-snippet-dirs '("~/.emacs.d/snippets/snippets/")))

Autocompletions with Company

(use-package company
  :defer 3
  :diminish company-mode
  :bind (:map company-active-map
              ("M-n" . nil)
              ("M-p" . nil)
              ("C-n" . company-select-next)
              ("C-p" . company-select-previous))
  :custom
  (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))
  :config
  (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))
  (global-company-mode +1))

(use-package company-quickhelp
  :after company
  :bind (:map company-active-map
              ("M-h" . company-quickhelp-manual-begin))
  :hook
  (company-mode . company-quickhelp-mode))

Spellcheck with Flyspell

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

;; FIXME: use flyspell-correct-wrapper once Nixpkgs is up-to-date
(use-package flyspell-correct
  :bind
  (:map flyspell-mode-map
        (("C-;" . flyspell-correct-next))))

(use-package flyspell-correct-ivy
  :after flyspell-correct)

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

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)

dtrt-indent

dtrt-indent guesses the indentation settings of files, and sets the buffer local variables accordingly. This makes it pleasant to edit corresponding text files.

(use-package dtrt-indent
  :diminish t
  :config
  (dtrt-indent-mode +1))

Direnv

(use-package direnv
  :if (executable-find "direnv")
  :custom
  (direnv-always-show-summary t)
  :config
  (direnv-mode +1))

Languages

Language Servers

(use-package lsp-mode
  :commands lsp-mode
  :config
  (require 'lsp-imenu)
  (add-hook 'lsp-after-open-hook 'lsp-enable-imenu)
  :custom
  (lsp-message-project-root-warning t))

(use-package lsp-ui
  :after lsp-mode
  :init
  (add-hook 'lsp-mode-hook #'lsp-ui-mode)
  :config
  (define-key lsp-ui-mode-map [remap xref-find-definitions] #'lsp-ui-peek-find-definitions)
  (define-key lsp-ui-mode-map [remap xref-find-references] #'lsp-ui-peek-find-references))

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

Common Lisp

(use-package slime
  :commands slime
  :custom
  (inferior-lisp-program "sbcl")
  (slime-contribs '(slime-fancy)))

(use-package slime-company
  :after slime
  :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
  :mode "\\.ex[s]?\\'")

Alchemist

(use-package alchemist
  :after elixir-mode)

Docker

(use-package docker
  :commands docker-mode)

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

Nix

(use-package nix-mode
  :mode "\\.nix\\'"
  :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
  :hook
  (haskell-mode . intero-mode))

Go

(use-package go-mode
  :mode ("\\.go\\'" . go-mode)
  :hook
  (go-mode . compilation-auto-quit-window)
  (go-mode . (lambda ()
               (set (make-local-variable 'company-backends) '(company-go))
               (company-mode)))
  (go-mode . (lambda ()
               (add-hook 'before-save-hook 'gofmt-before-save)
               (local-set-key (kbd "M-.") 'godef-jump)))
  (go-mode . (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
  :after go-mode)

(use-package golint
  :after go-mode
  :config
  (add-to-list 'load-path (concat (getenv "GOPATH")  "/src/github.com/golang/lint/misc/emacs"))
  (require 'golint))

(use-package gorepl-mode
  :after go-mode
  :hook
  (go-mode . gorepl-mode))

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

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

lsp-python

(use-package lsp-python
  :after lsp-mode company-lsp
  :hook
  (python-mode . lsp-python-enable))

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

Highlight Indent Guides

(use-package highlight-indent-guides
  :hook
  (python-mode . highlight-indent-guides-mode)
  :custom
  (highlight-indent-guides-method 'character))

Isend-mode

(use-package isend-mode
  :bind
  (:map isend-mode-map
        ("C-M-e" . isend-send-defun))
  :hook
  (isend-mode. 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)
         ("\\.js[x]?\\'" . web-mode))
  :custom
  (web-mode-enable-css-colorization t)
  (web-mode-content-types-alist
   '(("jsx" . "\\.js[x]?\\'")))
  :config
  (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
  :hook
  (web-mode . emmet-mode)
  (vue-mode . emmet-mode))

CSS

Rainbow-mode

(use-package rainbow-mode
  :diminish rainbow-mode
  :hook
  (css-mode . rainbow-mode)
  (scss-mode . rainbow-mode))

SCSS-mode

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

Javascript

JS2-mode

(use-package js2-mode
  :hook
  (web-mode-hook . js2-minor-mode)
  :config
  (setq-default flycheck-disabled-checkers
                (append flycheck-disabled-checkers
                        '(javascript-jshint)))
  :custom
  (js-switch-indent-offset 2))

Indium

(use-package indium
  :after js2-mode
  :bind (:map js2-mode-map
              ("C-c C-l" . indium-eval-buffer))
  :hook
  ((js2-mode . indium-interaction-mode)))

js-doc

(use-package js-doc
  :bind (:map js2-mode-map
              ("C-c i" . js-doc-insert-function-doc)
              ("@" . js-doc-insert-tag))
  :custom
  (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"))

lsp-javascript-typescript

(use-package lsp-javascript-typescript
  :hook
  (js2-mode-hook . lsp-javascript-typescript-enable))

prettier

(use-package prettier-js
  :hook
  (js2-minor-mode . prettier-js-mode))

Java

Google C Style

(use-package google-c-style
  :commands
  (google-set-c-style))

Java LSP Setup

(use-package lsp-java
  :after lsp-mode
  :hook
  (java-mode . lsp-java-enable))

Groovy mode

(use-package groovy-mode
  :mode ("\\.gradle\\'" . groovy-mode))

Typescript

typescript-mode

(use-package typescript-mode
  :mode "\\.ts\\'")

Tide

(use-package tide
  :after typescript-mode
  :hook
  (before-save . tide-format-before-save)
  (typescript-mode . (lambda ()
                       (tide-setup)
                       (flycheck-mode +1)
                       (eldoc-mode +1)
                       (tide-hl-identifier-mode +1)
                       (company-mode +1))))

JSON

(use-package json-mode
  :mode "\\.json\\'"
  :hook
  (json-mode . (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)
  :custom
  (markdown-fontify-code-blocks-natively t)
  (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))

AsciiDoc

(use-package adoc-mode
  :mode ("\\.adoc\\'" . adoc-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))
  :hook
  (clojure-mode . eldoc-mode)
  (clojure-mode . subword-mode)
  (clojure-mode . cider-mode))

Cider

(use-package cider
  :after clojure-mode
  :hook
  (cider-repl-mode-hook . company-mode)
  (cider-mode . company-mode)
  :diminish subword-mode
  :custom
  (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 ";; => ")
  (cider-cljs-lein-repl "(do (use 'figwheel-sidecar.repl-api) (start-figwheel!) (cljs-repl))")
  :config
  (cider-repl-toggle-pretty-printing))

clj-refactor

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

Squiggly-clojure

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

Latex

AucTeX

(use-package auctex
  :defer t
  :mode ("\\.tex\\'" . latex-mode)
  :custom
  (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)
  (TeX-view-program-list '(("pdf-tools" "TeX-pdf-tools-sync-view")
                           ("zathura" "zathura --page=%(outpage) %o")))
  (TeX-view-program-selection '((output-pdf "pdf-tools")
                                (output-pdf "zathura")))
  :config
  (setq-default TeX-engine 'luatex)
  (add-hook 'LaTeX-mode-hook
            (lambda ()
              (company-mode)
              (setq TeX-PDF-mode t)
              (setq TeX-source-correlate-method 'synctex)
              (setq TeX-source-correlate-start-server t)))
  (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
  :after auctex company-mode)

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
  :mode (("\\.pdf\\'" . pdf-view-mode))
  :bind
  (:map pdf-view-mode-map
        (("h" . pdf-annot-add-highlight-markup-annotation)
         ("t" . pdf-annot-add-text-annotation)
         ("D" . pdf-annot-delete)
         ("C-s" . isearch-forward)))
  :custom
  ;; More fine-grained resizing (10%)
  (pdf-view-resize-factor 1.1)
  :config
  ;; Install pdf tools
  (pdf-tools-install))

Scala

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

Org-Mode

Setup

I use org-plus-contrib, which contains several contrib plugins, including org-drill and some org-babel language support.

To install org-plus-contrib, add the package archive to Emacs.

(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
  :ensure org-plus-contrib
  :mode ("\\.org\\'" . org-mode)
  :bind
  (("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))
  :custom
  (org-return-follows-link t)
  (org-agenda-diary-file "~/.org/diary.org")
  (org-babel-load-languages
   '((emacs-lisp . t)
     (python . t)))
  :custom-face
  (variable-pitch ((t (:family "iA Writer Duospace"))))
  (org-document-title ((t (:foreground "#171717" :weight bold :height 1.5))))
  (org-done ((t (:background "#E8E8E8" :foreground "#0E0E0E" :strike-through t :weight bold))))
  (org-headline-done ((t (:foreground "#171717" :strike-through t))))
  (org-level-1 ((t (:foreground "#090909" :weight bold :height 1.3))))
  (org-level-2 ((t (:foreground "#090909" :weight normal :height 1.2))))
  (org-level-3 ((t (:foreground "#090909" :weight normal :height 1.1))))
  (org-image-actual-width '(600))
  :config
  (add-to-list 'org-structure-template-alist '("el" "#+BEGIN_SRC emacs-lisp :tangle yes?\n\n#+END_SRC")))

Variable Pitch Mode

We use a font that’s easier on the eyes for long blocks of text. (ET Bembo)

(add-hook 'org-mode-hook
          '(lambda ()
             (setq line-spacing 0.2) ;; Add more line padding for readability
             (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-verbatim
                    'org-block-begin-line
                    'org-block-end-line
                    'org-meta-line
                    'org-document-info-keyword))))

Other org-mode ricing configuration:

(setq org-startup-indented t
      org-hide-emphasis-markers t
      org-pretty-entities t)

Org Gcal

(use-package password-store
  :defer 10
  :init
  (require 'auth-source-pass)
  :load-path "./elisp"
  :custom
  (auth-source-backend '(password-store)))

(use-package org-gcal
  :after (auth-source-pass password-store)
  :custom
  (org-gcal-client-id "1025518578318-g5llmkeftf20ct2s7j0b4pmu7tr6am1r.apps.googleusercontent.com")
  (org-gcal-client-secret `,(auth-source-pass-get 'secret "gmail/org-gcal"))
  (jethro/org-gcal-directory "~/.org/gtd/calendars/")
  :config
  (defun jethro/get-gcal-file-location (loc)
    (concat (file-name-as-directory jethro/org-gcal-directory) loc))
  (setq org-gcal-file-alist `(("jethrokuan95@gmail.com" . ,(jethro/get-gcal-file-location "personal.org"))
                              ("62ad47vpojb2uqb53hpnqsuv5o@group.calendar.google.com" . ,(jethro/get-gcal-file-location "school.org"))
                              ("wing.nus@gmail.com" . ,(jethro/get-gcal-file-location "wing.org"))
                              ("linuxnus.org_f1e8c6kcuuj0k1elmhh9vboo5c@group.calendar.google.com" . ,(jethro/get-gcal-file-location "nushackers_public.org"))
                              ("linuxnus.org_r7v0mr9m4h4u9rjpf2chimo61o@group.calendar.google.com" . ,(jethro/get-gcal-file-location "nushackers_private.org")))))

Run on Timer

Run org-gcal-fetch every 5 minutes to update the calendars.

(run-at-time (* 5 60) nil
             (lambda ()
               (let ((inhibit-message t))
                 (org-gcal-refresh-token)
                 (org-gcal-fetch))))

Org Mode for GTD

This subsection 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.

Why my own implementation of GTD?

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 more similar 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
nextThis contains one-off tasks that don’t belong to projects.
projectsThis contains the list of projects, and their corresponding todo items
(require 'find-lisp)
(setq jethro/org-agenda-directory "~/.org/gtd/")
(setq org-agenda-files
      (find-lisp-find-files jethro/org-agenda-directory "\.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 %?")
        ("p" "paper" entry (file "~/.org/papers/papers.org")
         "* TODO %(jethro/trim-citation-title \"%:title\")\n%a" :immediate-finish t)
        ("e" "email" entry (file+headline "~/.org/gtd/emails.org" "Emails")
         "* TODO [#A] Reply: %a :@home:@school:" :immediate-finish t)
        ("w" "Weekly Review" entry (file+olp+datetree "~/.org/gtd/reviews.org")
         (file "~/.org/gtd/templates/weekly_review.org"))
        ("s" "Snippet" entry (file "~/.org/deft/capture.org")
         "* Snippet %<%Y-%m-%d %H:%M>\n%?")))

Stage 2: Processing

During predetermined times of each day, process the inbox, each item in inbox sorted into their respective folders.

org-agenda provides a brilliant interface for processing the inbox. At the end of the “processing” stage, inbox.org should be empty.

A few factors are key:

  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?

Each item in inbox.org would be placed in either a non-actionable file, or an actionable file (projects, or next) with a physical actionable.

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 Aenda Someday View

This view is where I review the thingns I would like to do someday:

(setq jethro/org-agenda-someday-view
      `("s" "Someday" todo ""
        ((org-agenda-files '("~/.org/gtd/someday.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 '(("next.org" :level . 0)
                           ("someday.org" :level . 0)
                           ("projects.org" :maxlevel . 1)))
(defvar jethro/org-agenda-bulk-process-key ?f
  "Default key for bulk processing inbox items.")

(defun jethro/org-process-inbox ()
  "Called in org-agenda-mode, processes all inbox items."
  (interactive)
  (org-agenda-bulk-mark-regexp "inbox:")
  (jethro/bulk-process-entries))

(defun jethro/org-agenda-process-inbox-item ()
  "Process a single item in the org-agenda."
  (org-with-wide-buffer
   (org-agenda-set-tags)
   (org-agenda-priority)
   (org-agenda-set-effort)
   (org-agenda-refile nil nil t)))

(defun jethro/bulk-process-entries ()
  (if (not (null org-agenda-bulk-marked-entries))
      (let ((entries (reverse org-agenda-bulk-marked-entries))
            (processed 0)
            (skipped 0))
        (dolist (e entries)
          (let ((pos (text-property-any (point-min) (point-max) 'org-hd-marker e)))
            (if (not pos)
                (progn (message "Skipping removed entry at %s" e)
                       (cl-incf skipped))
              (goto-char pos)
              (let (org-loop-over-headlines-in-active-region) (funcall 'jethro/org-agenda-process-inbox-item))
              ;; `post-command-hook' is not run yet.  We make sure any
              ;; pending log note is processed.
              (when (or (memq 'org-add-log-note (default-value 'post-command-hook))
                        (memq 'org-add-log-note post-command-hook))
                (org-add-log-note))
              (cl-incf processed))))
        (org-agenda-redo)
        (unless org-agenda-persistent-marks (org-agenda-bulk-unmark-all))
        (message "Acted on %d entries%s%s"
                 processed
                 (if (= skipped 0)
                     ""
                   (format ", skipped %d (disappeared before their turn)"
                           skipped))
                 (if (not org-agenda-persistent-marks) "" " (kept marked)")))
    ))



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

(setq org-agenda-bulk-custom-functions `((,jethro/org-agenda-bulk-process-key jethro/org-agenda-process-inbox-item)))

(define-key org-agenda-mode-map "i" 'org-agenda-clock-in)
(define-key org-agenda-mode-map "r" 'jethro/org-process-inbox)
(define-key org-agenda-mode-map "R" 'org-agenda-refile)
(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 org-agenda-start-with-log-mode t)
(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 "TODO"
               ((org-agenda-overriding-header "Emails")
                (org-agenda-files '("~/.org/gtd/emails.org"))))
         (todo "NEXT"
               ((org-agenda-overriding-header "In Progress")
                (org-agenda-files '("~/.org/gtd/someday.org"
                                    "~/.org/gtd/projects.org"
                                    "~/.org/gtd/next.org"))
                ;; (org-agenda-skip-function '(org-agenda-skip-entry-if 'deadline 'scheduled))
                ))
         (todo "TODO"
               ((org-agenda-overriding-header "Projects")
                (org-agenda-files '("~/.org/gtd/projects.org"))
                ;; (org-agenda-skip-function #'jethro/org-agenda-skip-all-siblings-but-first)
                ))
         (todo "TODO"
               ((org-agenda-overriding-header "One-off Tasks")
                (org-agenda-files '("~/.org/gtd/next.org"))
                (org-agenda-skip-function '(org-agenda-skip-entry-if 'deadline 'scheduled))))
         nil)))

(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 "<f1>" 'jethro/switch-to-agenda)

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
  :after org
  :bind
  (:map org-agenda-mode-map
        (("I" . org-pomodoro)))
  :custom
  (org-pomodoro-format "%s"))

Org Mode for Note taking

Deft

(use-package deft
  :after org
  :bind
  (("C-c n" . deft))
  :custom
  (deft-default-extension "org")
  (deft-directory "~/.org/deft/")
  (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 Download

This extension facilitates moving images from point A to point B. Use this to capture screenshots into deft.

(use-package org-download
  :after org
  :bind
  (:map org-mode-map
        (("s-Y" . org-download-screenshot)
         ("s-y" . org-download-yank)))
  :config
  (if (memq window-system '(mac ns))
      (setq org-download-screenshot-method "screencapture -i %s")
    (setq org-download-screenshot-method "maim -s %s"))
  (add-hook 'org-mode-hook
            (lambda ()
              (when (buffer-file-name)
                (setq-local org-download-image-dir (format "./images/%s" (file-name-base buffer-file-name)))))))

Publishing

(use-package ox-publish
  :ensure f
  :no-require t
  :commands org-publish-all org-publish
  :after org
  :custom
  (org-html-htmlize-output-type nil)
  (org-html-head-include-default-style nil)
  (org-publish-project-alist
   '(("org-notes-assets"
      :base-directory "~/.org/deft/css/"
      :base-extension "css\\|js\\|png\\|jpg\\|gif\\|pdf\\|mp3\\|ogg\\|swf"
      :publishing-directory "~/.org/deft/docs/css/"
      :recursive t
      :publishing-function org-publish-attachment
      )
     ("org-notes-images"
      :base-directory "~/.org/deft/images/"
      :base-extension "css\\|js\\|png\\|jpg\\|gif\\|pdf\\|mp3\\|ogg\\|swf"
      :publishing-directory "~/.org/deft/docs/images/"
      :recursive t
      :publishing-function org-publish-attachment
      )
     ("org-notes"
      :base-directory "~/.org/deft/"
      :base-extension "org"
      :publishing-directory "~/.org/deft/docs/"
      :recursive t
      :publishing-function org-html-publish-to-html
      :headline-levels 4
      :auto-sitemap t
      :author "Jethro Kuan"
      :email "jethrokuan95@gmail.com"
      :sitemap-filename "index.org"
      :sitemap-title "Jethro's Braindump"
      :style "<link rel=\"stylesheet\" href=\"https://unpkg.com/sakura.css/css/sakura.css\" type=\"text/css\">")
     ("org" :components ("org-notes-assets" "org-notes-images")))))

Org mode for Journalling

(use-package org-journal
  :custom
  (org-journal-dir "~/.org/journal/"))

Exporting PDFs

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

(use-package ox-latex
  :defer 3
  :after org
  :ensure f
  :config
  (require 'ox-latex)
  :custom
  (org-latex-pdf-process
   '("pdflatex -shell-escape -interaction nonstopmode %f"
     "bibtex %b"
     "pdflatex -shell-escape -interaction nonstopmode %f"))
  (org-latex-default-table-environment "tabular")
  (org-latex-tables-booktabs t)
  (org-latex-listings 'minted)
  (org-format-latex-options (plist-put org-format-latex-options :scale 2.0))
  (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.1in,0.1in}, a4paper,landscape]{geometry}
  \\usepackage{hyperref}
  \\usepackage{amsmath}
  \\usepackage{multicol}
  \\usepackage{booktabs}
  \\usepackage{enumitem}
  \\usepackage[compact]{titlesec}
  \\renewcommand\\maketitle{}
  \\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}
  \\usepackage{setspace}
  \\singlespacing
  \\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}"))))
  :config
  (defvar-local jethro/org-multicol-latex-column-count
    3
    "Column count for multicolumn export.")

  (defmacro jethro/-org-multicol (action)
    `(lambda (async subtreep visible-only body-only)
       (let ((contents (buffer-string))
             (buffer-name (file-name-sans-extension buffer-file-name))
             (col-count jethro/org-multicol-latex-column-count))
         (with-temp-buffer
           (insert "#+LATEX_CLASS: latex-notes\n")
           (insert contents)
           (goto-char (point-min))
           (org-next-visible-heading 1)
           (insert
            (format "#+BEGIN_EXPORT latex\n\\begin{multicols*}{%s}\n#+END_EXPORT\n" col-count))
           (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 ,action)))))

  (setq jethro/org-multicol-to-latex (jethro/-org-multicol nil)
        jethro/org-multicol-to-pdf (jethro/-org-multicol (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)))))






Putting it all together

(setq org-agenda-custom-commands 
      `(,jethro/org-agenda-inbox-view
        ,jethro/org-agenda-someday-view
        ,jethro/org-agenda-todo-view
        ;; ,jethro/org-agenda-papers-view ;; archived
        ))

Project Management

Version Control

vc

(use-package vc
  :bind (("C-x v =" . jethro/vc-diff)
         ("C-x v H" . vc-region-history)) ; New command in emacs 25.x
  :config
  (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 (("C-c h s" . jethro/hydra-smerge/body))
  :init
  (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
  (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-mine)
    ("l" smerge-keep-other)
    ("a" smerge-keep-all)
    ("RET" smerge-keep-current)
    ("\C-m" smerge-keep-current)
    ("<" smerge-diff-base-mine)
    ("=" smerge-diff-mine-other)
    (">" smerge-diff-base-other)
    ("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 (("s-g" . magit-status)
         ("C-c g" . magit-status)
         ("s-G" . magit-blame)
         ("C-c G" . magit-blame))
  :hook
  (magit-mode . hl-line-mode)
  :custom
  (magit-auto-revert-mode nil))

Gists

(use-package gist
  :defer 10)

Projectile

(use-package projectile
  :init
  (setq projectile-keymap-prefix (kbd "C-x p"))
  :custom
  (projectile-use-git-grep t)
  (projectile-create-missing-test-files t)
  (projectile-completion-system 'ivy)
  (projectile-switch-project-action  #'projectile-commander)
  :config
  (projectile-global-mode +1)
  (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))))

(use-package counsel-projectile
  :after ivy projectile
  :bind (("s-f" . counsel-projectile-find-file)
         ("s-b" . counsel-projectile-switch-to-buffer)
         ("C-c s" . counsel-projectile-rg)))

Miscellaneous

Olivetti

(use-package olivetti
  :hook
  (text-mode . olivetti-mode)
  :bind (("C-c M o" . olivetti-mode))
  :custom
  (olivetti-body-width 100))

bury-successful-compilation

Closes compile buffer if there are no errors.

(use-package bury-successful-compilation
  :hook
  (prog-mode . bury-successful-compilation))

Stack Exchange

(use-package sx
  :commands sx)