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:
- I use a Dvorak 60% keyboard, so some of the keybindings may not be suitable for you.
- 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:
- Backend development (primarily in Python and JS)
- Frontend development (HTML/CSS/JS/React)
- ML/DL stuff (Python)
- Email (notmuch)
- RSS (elfeed + elfeed-org)
- 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))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.
(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 |
|---|---|
| inbox | Includes everything on your mind: tasks, ideas etc. |
| someday | Includes things that will be done later on (with no specific deadline), to be reviewed often |
| reference | I don’t actually have this file; I use deft-mode as my braindump |
| next | This contains one-off tasks that don’t belong to projects. |
| projects | This 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:
- 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?
- 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:
- 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.
- 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
| keyword | meaning |
|---|---|
| TODO | An item that has yet to be processed, or cannot be attempted at this moment. |
| NEXT | An action that can be completed at this very moment, in the correct context |
| DONE | An item that is completed, and ready to be archived |
| WAITING | An item that awaits input from an external party |
| HOLD | An item that is delayed due to circumstance |
| CANCELLED | An 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:
- Project Purpose/Principles
- 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)