emacs-config/conf.org

75 KiB

overview

This is my personal emacs config which currently runs on emacs 26.1 (currently the latest package from the Arch Linux official repos).

I use this for a variety of purposes:

  • maintaining my todo list/GTD workflow (org mode)
  • unified interface for system administration (dired, shell, git, ediff)
  • email (mu4e, work in progress)
  • editing some of my favorite languages (R, Lisp, Haskell, Lua, Python)

ui

theme

(use-package spacemacs-theme
  :defer t
  :config
  (setq spacemacs-theme-custom-colors '((lnum . "#64707c"))))

(defvar nd/theme 'spacemacs-dark)
(defvar nd/theme-window-loaded nil)
(defvar nd/theme-terminal-loaded nil)

;; required for emacsclient/daemon setup
(if (daemonp)
    (add-hook 'after-make-frame-functions
			  (lambda (frame)
                (select-frame frame)
                (if (window-system frame)
                    (unless nd/theme-window-loaded
                      (if nd/theme-terminal-loaded
                          (enable-theme nd/theme)
                        (load-theme nd/theme t))
                      (setq nd/theme-window-loaded t))
                  (unless nd/theme-terminal-loaded
                    (if nd/theme-window-loaded
                        (enable-theme nd/theme)
                      (load-theme nd/theme t))
                    (setq nd/theme-terminal-loaded t)))))
  (progn
    (load-theme nd/theme t)
    (if (display-graphic-p)
        (setq nd/theme-window-loaded t)
      (setq nd/theme-terminal-loaded t))))

modeline

(use-package spaceline
  :ensure t
  :config
  (require 'spaceline-config)
  (setq powerline-default-separator 'arrow
		spaceline-buffer-size-p nil)
  (spaceline-spacemacs-theme))

(line-number-mode 1)
(column-number-mode 1)

delight

Used to hide minor modes on the modeline

(use-package delight
  :ensure t)

clean the interface

No need for startup screen, tool/menu/scrollbars, or backups

(setq inhibit-startup-screen t)

(tool-bar-mode -1)
(menu-bar-mode -1)
(scroll-bar-mode -1)

(setq make-backup-files nil)
(setq auto-save-default nil)

other enhancements

popup windows

(setq pop-up-windows nil) ; no popups (eg ediff)

line wrap

(set-default 'truncate-lines t)

tab width

(setq-default tab-width 4)

smooth scrolling

(setq scroll-conservatively 100)

imagemagick

(when (fboundp 'imagemagick-register-types)
  (imagemagick-register-types))

yes-no prompt

(defalias 'yes-or-no-p 'y-or-n-p) ; eliminate yes or no prompt on killing procs

packages

beacon

(use-package beacon
  :ensure t
  :delight
  :init
  (beacon-mode 1))

whichkey

(use-package which-key
  :ensure t
  :delight
  :init
  (which-key-mode))

helm

(use-package helm
  :ensure t
  :delight
  :init
  (helm-mode 1)
  :config
  (setq helm-autoresize-max-height 0
		helm-autoresize-max-height 40
		helm-M-x-fuzzy-match t
		helm-buffers-fuzzy-matching t
		helm-recentf-fuzzy-match t
		helm-semantic-fuzzy-match t
		helm-imenu-fuzzy-match t
		helm-scroll-amount 8)
  (add-to-list 'display-buffer-alist
               `(,(rx bos "*helm" (* not-newline) "*" eos)
                 (display-buffer-in-side-window)
                 (inhibit-same-window . t)
                 (window-height . 0.4)))
  (helm-autoresize-mode 1)
  (require 'helm-config))

rainbow-delimiters

(use-package rainbow-delimiters
  :ensure t
  :delight
  :hook
  ((prog-mode . rainbow-delimiters-mode)
   (inferior-ess-mode . rainbow-delimiters-mode)
   (ess-mode . rainbow-delimiters-mode)
   (LaTeX-mode . rainbow-delimiters-mode)
   (Tex-latex-mode . rainbow-delimiters-mode)))

ace-window

(use-package ace-window
  :ensure t
  :config
  (setq aw-background t)
  (custom-set-faces '(aw-leading-char-face 
					  ((t (:foreground "#292b2e"
						   :background "#bc6ec5"
						   :height 1.0
						   :box nil))))))

avy

  (use-package avy
    :ensure t
    :config
    (setq avy-background t))

sudo edit

  (use-package sudo-edit
    :ensure t)

undo tree

(use-package undo-tree
  :ensure t
  :delight
  :config
  (setq undo-tree-visualizer-diff t)
  (global-undo-tree-mode))

fill-column-indicator

(use-package fill-column-indicator
  :ensure t
  :config
  (setq fci-rule-use-dashes t)
  :hook
  (prog-mode . fci-mode))

rainbow

(use-package rainbow-mode
  :ensure t)

async

(use-package async
  :ensure t
  :delight dired-async-mode
  :init
  (dired-async-mode 1))

csv-mode

(use-package csv-mode
  :ensure t)
  ;; :hook
  ;; (csv-mode . (lambda () (csv-align-fields nil (point-min) (point-max)))))

markdown-mode

(use-package markdown-mode
  :ensure t)

polymode

(use-package polymode
  :ensure t
  :after markdown-mode
  :mode
  (("\\.Rmd\\'" . poly-markdown+r-mode)
   ("\\.rmd\\'" . poly-markdown+r-mode))
  :config
  (require 'poly-R)
  (require 'poly-markdown))

library

A place for duct tape code that I developed (or lovingly stole from others)

macros

;; lovingly stolen from aaron harris
(defmacro nd/with-advice (adlist &rest body)
  "Execute BODY with temporary advice in ADLIST.

Each element of ADLIST should be a list of the form
  (SYMBOL WHERE FUNCTION [PROPS])
suitable for passing to `advice-add'.  The BODY is wrapped in an
`unwind-protect' form, so the advice will be removed even in the
event of an error or nonlocal exit."
  (declare (debug ((&rest (&rest form)) body))
           (indent 1))
  `(progn
     ,@(mapcar (lambda (adform)
                 (cons 'advice-add adform))
               adlist)
     (unwind-protect (progn ,@body)
       ,@(mapcar (lambda (adform)
                   `(advice-remove ,(car adform) ,(nth 2 adform)))
                 adlist))))

functions

(defun nd/filter-list-prefix (prefix str-list)
  "Return a subset of STR-LIST whose first characters are PREFIX."
  (seq-filter (lambda (i)
                (and (stringp i)
                     (string-prefix-p prefix i)))
              str-list))

(defun nd/move-key (keymap-from keymap-to key)
  "Move KEY from KEYMAP-FROM keymap to KEYMAP-TO keymap."
  (define-key keymap-to key (lookup-key keymap-from key))
  (define-key keymap-from key nil))

(defun nd/get-apps-from-mime (mimetype)
  "Return all applications that can open a given MIMETYPE.
The list is comprised of alists where pairs are of the form (name . command)."
  (let* ((case-fold-search nil)
		 (mime-regex (concat "^MimeType=.*" mimetype ";.*$"))
		 (desktop-dirs '("/usr/share/applications"
						 "/usr/local/share/applications"
						 "~/.local/share/applications"))
		 (desktop-files (mapcan (lambda (d) (directory-files d t ".*\\.desktop" t)) desktop-dirs))
		 (app-list))
	(dolist (file desktop-files app-list)
	  (with-temp-buffer
		(insert-file-contents file)
		(let* ((tb (buffer-string)))
		  (if (string-match mime-regex tb)
			  (let* ((exec (progn (string-match "^Exec=\\(.*\\)$" tb)
								  (match-string 1 tb)))
					 (name (or
							(progn (string-match "^Name=\\(.*\\)$" tb)
								   (match-string 1 tb))
							exec)))
				(setq app-list (cons `(,name . ,exec) app-list)))))))))

(defun nd/get-apps-bulk-from-mime (mimetype)
  "Like `nd/get-apps-from-mime' but only includes apps that can open
multiple files at once for given MIMETYPE."
  (let ((case-fold-search nil))
	(seq-filter (lambda (a) (string-match ".*%[FU].*" (car a))) (nd/get-apps-from-mime mimetype))))
    
(defun nd/execute-desktop-command (cmd file)
  "Opens FILE using CMD in separate process where CMD is from a 
desktop file exec directive."
  (let* ((cmd-arg (replace-regexp-in-string "%[fuFU]" file cmd t t)))
	(call-process-shell-command (concat cmd-arg " &"))))
  
(defun nd/get-mime-type (file)
  "Get the mime type of FILE."
  (let* ((cmd (concat "file --mime-type -b " file))
		 (mt (shell-command-to-string cmd)))
	(replace-regexp-in-string "\n\\'" "" mt)))

interactive

(defun nd/split-and-follow-horizontally ()
  "Split window horizontally and move focus."
  (interactive)
  (split-window-below)
  (balance-windows)
  (other-window 1))

(defun nd/split-and-follow-vertically ()
  "Split window vertically and move focus."
  (interactive)
  (split-window-right)
  (balance-windows)
  (other-window 1))
    
(defun nd/switch-to-previous-buffer ()
  "Switch the buffer to the last opened buffer."
  (interactive)
  (switch-to-buffer (other-buffer (current-buffer) 1)))
  
(defun nd/config-reload ()
  "Reloads ~/.emacs.d/conf.org at runtime."
  (interactive)
  (org-babel-load-file (expand-file-name "~/.emacs.d/conf.org")))

(defun nd/config-visit ()
  "Opens the main conf.org file (the one that really matters)."
  (interactive)
  (find-file "~/.emacs.d/conf.org"))

(defun nd/kill-current-buffer ()
  "Kill the current buffer."
  (interactive)
  (kill-buffer (current-buffer)))

(defun nd/close-all-buffers ()
  "Kill all buffers without regard for their origin."
  (interactive)
  (mapc 'kill-buffer (buffer-list)))

(defun nd/org-close-all-buffers ()
  "Kill all org buffers."
  (interactive)
  (mapc 'kill-buffer (org-buffer-list)))

(defun nd/open-urxvt ()
  "Launch urxvt in the current directory."
  (interactive)
  (let ((cwd (expand-file-name default-directory)))
	(call-process "urxvt" nil 0 nil "-cd" cwd)))

editing

completion

company

(use-package company
  :ensure t
  :delight " ©"
  :config
  (setq company-idle-delay 0
		company-minimum-prefix-length 3))

flycheck

(use-package flycheck
  :ensure t
  :hook
  (prog-mode . flycheck-mode)
  :config
  (setq flycheck-check-syntax-automatically '(save
											  idle-change
											  mode-enabled)
		flycheck-idle-change-delay 2
		flycheck-error-list-minimum-level 'warning
		flycheck-navigation-minimum-level 'warning))

yasnippet

(use-package yasnippet
  :ensure t)

(use-package yasnippet-snippets
  :ensure t
  :after yasnippet
  :hook
  ((prog-mode . yas-minor-mode))
  :config
  (yas-reload-all))

flyspell

(use-package flyspell-correct-helm
  :ensure t
  :after (helm flyspell))

progmode

(add-hook 'prog-mode-hook #'prettify-symbols-mode)
(add-hook 'prog-mode-hook #'flyspell-prog-mode)
(setq flyspell-issue-message-flag nil)

languages

elisp

(add-hook 'emacs-lisp-mode-hook 'company-mode)

ess

NOTES:

  • ess is not considered part of prog-mode for some reason
  • ess-mode requires a running R process for company to work
  • flycheck requries r-lintr
(defun nd/init-ess-company ()
  "Set the company modes for ess modes."
  (setq-local company-backends '((company-R-objects company-R-args))))

(use-package ess
  :ensure t
  :init
  (load "ess-site")
  :hook
  ((ess-mode . flycheck-mode)
   (ess-mode . company-mode)
   (ess-mode . nd/init-ess-company)
   (ess-mode . prettify-symbols-mode)
   (ess-mode . fci-mode)

   (inferior-ess-mode . company-mode)
   (inferior-ess-mode . nd/init-ess-company)
   (inferior-ess-mode . prettify-symbols-mode))
  :config
  (setq inferior-R-args "--quiet --no-save"
		ess-history-file "session.Rhistory"
		ess-history-directory (substitute-in-file-name "${XDG_CONFIG_HOME}/r/")))

python

(elpy-enable)

;; make python tabs 4 chars
(add-hook 'python-mode-hook
      (lambda ()
        (setq indent-tabs-mode t)
        (setq tab-width 4)
        (setq python-indent 4)))

haskell

(use-package haskell-mode
  :ensure t
  :hook
  (haskell-mode . company-mode)
  :config
  (setq haskell-compile-command "ghc -dynamic -Wall -ferror-spans -threaded -fforce-recomp %s"
		haskell-interactive-popup-errors nil))

(use-package company-ghc
  :ensure t
  :after
  (company-mode haskell-mode)
  :hook
  (haskell-mode . (lambda () (setq-local company-backends
									'((company-ghc))))))

org-mode

major mode

general config

Enable some straightforward options:

  • visual-line-mode: wrap text since I like to treat long lines as paragraphs
  • org-indent-mode: indent each level for better visualization
  • enable special behavior for header navigation, killing, and yanking (see these docs for details)
  • logs should go in their own drawer called "LOGBOOK"
  • DONE state should log the time
(use-package org
  :delight
  ;; source of indent-mode required here
  (org-indent-mode nil org-indent)
  (visual-line-mode)
  :hook
  (org-mode . visual-line-mode)
  :config
  (setq org-startup-indented t
		org-directory "~/Org"
		org-modules '(org-habit org-protocol)

		org-special-ctrl-a/e t
		org-special-ctrl-k t
		org-yank-adjusted-subtrees t

		org-log-into-drawer "LOGBOOK"
		org-log-done 'time)

  (require 'org-protocol)
  (run-at-time "00:59" 3600 'org-save-all-org-buffers))

bullets

These are just so much better to read

(use-package org-bullets
  :ensure t
  :hook
  (org-mode . org-bullets-mode))

font height

The fonts in org headings bug me, make them smaller and less invasive

(add-hook 'org-mode-hook
		  (lambda ()
			(let ((heading-height 1.15))
			  (set-face-attribute 'org-level-1 nil :weight 'bold :height heading-height)
			  (set-face-attribute 'org-level-2 nil :weight 'semi-bold :height heading-height)
			  (set-face-attribute 'org-level-3 nil :weight 'normal :height heading-height)
			  (set-face-attribute 'org-level-4 nil :weight 'normal :height heading-height)
			  (set-face-attribute 'org-level-5 nil :weight 'normal :height heading-height))))

src blocks

(setq org-src-window-setup 'current-window
	  org-src-fontify-natively t
	  org-edit-src-content-indentation 0)

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

interactive commands

Some useful additional commands for org buffers

(defun nd/mark-subtree-keyword (new-keyword &optional exclude)
  "marks all tasks in a subtree with keyword unless original keyword
is in the optional argument exclude"
  (let ((subtree-end (save-excursion (org-end-of-subtree t))))
    (if (not (listp exclude))
        (error "exlude must be a list if provided"))
    (save-excursion
      (while (< (point) subtree-end)
        (let ((keyword (nd/is-todoitem-p)))
          (if (and keyword (not (member keyword exclude)))
              (org-todo new-keyword)))
        (outline-next-heading)))))

(defun nd/mark-subtree-done ()
  "marks all tasks in subtree as DONE unless they are already canc"
  (interactive)
  (nd/mark-subtree-keyword "DONE" '("CANC")))

(defun nd/org-clone-subtree-with-time-shift (n &optional shift)
  "Like `org-clone-subtree-with-time-shift' except it resets checkboxes
and reverts all todo keywords to TODO"
  (interactive "nNumber of clones to produce: ")
    
  (let ((shift (or (org-entry-get nil "TIME_SHIFT" 'selective)
                   (read-from-minibuffer
                    "Date shift per clone (e.g. +1w, empty to copy unchanged): "))))
    (condition-case err
        (progn
          (save-excursion
			;; clone once and reset
			(org-clone-subtree-with-time-shift 1 shift)
            (org-forward-heading-same-level 1 t)
            (org-reset-checkbox-state-subtree)
            (nd/mark-subtree-keyword "TODO")
            (call-interactively 'nd/org-log-delete)
            (org-cycle)
			;; clone reset tree again if we need more than one clone
			(if (> n 1)
				(let ((additional-trees (- n 1)))
				  (org-clone-subtree-with-time-shift additional-trees shift)
				  (dotimes (i additional-trees)
					(org-forward-heading-same-level 1 t)
					(org-cycle))))))
      (error (message "%s" (error-message-string err))))))

(defun nd/org-log-delete ()
  "Delete logbook drawer of subtree."
  (interactive)
  (save-excursion
    (goto-char (org-log-beginning))
    (when (save-excursion
            (save-match-data
              (beginning-of-line 0)
              (search-forward-regexp org-drawer-regexp)
              (goto-char (match-beginning 1))
              (looking-at "LOGBOOK")))
      (org-mark-element)
      (delete-region (region-beginning) (region-end))
      (org-remove-empty-drawer-at (point)))))

column view

  (setq org-columns-default-format
        "%25ITEM %4TODO %TAGS %5Effort{:} %OWNER(OWN)")

  (set-face-attribute 'org-column nil :background "#1e2023")
  ;; org-columns-summary-types

calfw

(use-package calfw
  :ensure t
  :config
  (setq cfw:fchar-junction ?╋
		cfw:fchar-vertical-line ?┃
		cfw:fchar-horizontal-line ?━
		cfw:fchar-left-junction ?┣
		cfw:fchar-right-junction ?┫
		cfw:fchar-top-junction ?┯
		cfw:fchar-top-left-corner ?┏
		cfw:fchar-top-right-corner ?┓))

(use-package calfw-org
  :ensure t
  :after calfw
  :config
  (setq cfw:org-agenda-schedule-args
		'(:deadline :timestamp)))

window splitting

Org mode is great and all, but the windows never show up in the right place. The solutions here are simple, but have the downside that the window sizing must be changed when tags/capture templates/todo items are changed. This is because the buffer size is not known at window creation time and I didn't feel like making a function to predict it

todo selection

I only need a teeny tiny window below my current window for todo selection

(defun nd/org-todo-position (buffer alist)
  (let ((win (car (cl-delete-if-not
                   (lambda (window)
                     (with-current-buffer (window-buffer window)
                       (memq major-mode
                             '(org-mode org-agenda-mode))))
                   (window-list)))))
    (when win
      (let ((new (split-window win -4 'below)))
        (set-window-buffer new buffer)
        new))))

(defun nd/org-todo-window-advice (orig-fn)
  "Advice to fix window placement in `org-fast-todo-selection'."
  (let  ((override '("\\*Org todo\\*" nd/org-todo-position)))
    (add-to-list 'display-buffer-alist override)
    (nd/with-advice
        ((#'org-switch-to-buffer-other-window :override #'pop-to-buffer))
      (unwind-protect (funcall orig-fn)
        (setq display-buffer-alist
              (delete override display-buffer-alist))))))

(advice-add #'org-fast-todo-selection :around #'nd/org-todo-window-advice)

tag selection

By default, the tag selection window obliterates all but the current window…how disorienting :/

(defun nd/org-tag-window-advice (orig-fn current inherited table &optional todo-table)
  "Advice to fix window placement in `org-fast-tags-selection'."
  (nd/with-advice
      ((#'delete-other-windows :override #'ignore)
       ;; pretty sure I just got lucky here...
       (#'split-window-vertically :override #'(lambda (&optional size)
                                                (split-window-below (or size -9)))))
    (unwind-protect (funcall orig-fn current inherited table todo-table))))

(advice-add #'org-fast-tag-selection :around #'nd/org-tag-window-advice)

capture

Capture should show up in the bottom of any currently active buffer

(defun nd/org-capture-position (buffer alist)
  (let ((new (split-window (get-buffer-window) -14 'below)))
    (set-window-buffer new buffer)
    new))

(defun nd/org-capture-window-advice (orig-fn table title &optional prompt specials)
  "Advice to fix window placement in `org-capture-select-template'."
  (let  ((override '("\\*Org Select\\*" nd/org-capture-position)))
    (add-to-list 'display-buffer-alist override)
    (nd/with-advice
        ((#'org-switch-to-buffer-other-window :override #'pop-to-buffer))
      (unwind-protect (funcall orig-fn table title prompt specials)
        (setq display-buffer-alist
              (delete override display-buffer-alist))))))

(advice-add #'org-mks :around #'nd/org-capture-window-advice)

latex

(setq org-html-doctype "html5")

gtd implementation

todo states

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

	  org-todo-keyword-faces
      '(("TODO" :foreground "light coral" :weight bold)
        ("NEXT" :foreground "khaki" :weight bold)
        ("DONE" :foreground "light green" :weight bold)
        ("WAIT" :foreground "orange" :weight bold)
        ("HOLD" :foreground "violet" :weight bold)
        ("CANC" :foreground "deep sky blue" :weight bold)))

tags

I use tags for agenda filtering. Very fast and simple. Each tag here starts with a symbol to define its group. Some groups are mutually exclusive, and each group has a different color. Any tag that is not part of these groups (eg some filetags in the few cases I use those) is easy to distinguish as it has the default tag color and is all caps.

There are several types of tags I use:

  • location: a GTD context; these start with "@"
  • tools: also a GTD context; these start with "#"
  • attribute: useful flags for filtering; these start with "%"
  • life areas: key areas of life which define priorities and goals; these start with "_"

NOTE: only these special chars; others make the tag chooser do weird things with the hotkey

(defun nd/add-tag-face (fg-name prefix)
  "Adds list of cons cells to org-tag-faces with foreground set to fg-name.
  Start and end specify the positions in org-tag-alist which define the tags
  to which the faces are applied"
  (dolist (tag (nd/filter-list-prefix prefix (mapcar #'car org-tag-alist)))
    (push `(,tag . (:foreground ,fg-name)) org-tag-faces)))

(setq org-tag-alist
      '((:startgroup)
        ("@errand" . ?e)
        ("@home" . ?h)
        ("@work" . ?w)
        ("@travel" . ?r)
        (:endgroup)
        
        ("#laptop" . ?l)
        ("#tcult" . ?t)
        ("#phone" . ?p)
        
        ("%note" . ?n)
        ("%inc" . ?i)
        ("%subdiv" . ?s)
        ("%flag" . ?f)
        
        (:startgroup)
        ("_env" . ?E)
        ("_fin" . ?F)
        ("_int" . ?I)
        ("_met" . ?M)
        ("_phy" . ?H)
        ("_pro" . ?P)
        ("_rec" . ?R)
        ("_soc" . ?S)
        (:endgroup)))

(setq org-tag-faces '())

(nd/add-tag-face "PaleGreen" "@")
(nd/add-tag-face "SkyBlue" "#")
(nd/add-tag-face "PaleGoldenrod" "%")
(nd/add-tag-face "violet" "_")

properties

Add some useful properties:

  • PARENT_TYPE: used for special groups of timestamped entries (iterators and periodicals).
  • TIME_SHIFT: usually in conjunction with PARENT_TYPE, used as a shorthand when cloning subtrees to shift the time by a specified amount
  • OWNER and GOAL: not currently used
(mapc (lambda (i) (add-to-list 'org-default-properties i))
	  '("PARENT_TYPE" "OWNER" "GOAL" "TIME_SHIFT"))

(setq org-global-properties
      '(("PARENT_TYPE_ALL" . "periodical iterator")
        ("Effort_ALL" . "0:05 0:15 0:30 1:00 1:30 2:00 3:00 4:00 5:00 6:00"))

	  org-use-property-inheritance
	  '("PARENT_TYPE" "TIME_SHIFT"))

capture

(let ((capfile "~/Org/capture.org"))
  (setq org-capture-templates
        `(("t" "todo" entry (file ,capfile)
		   "* TODO %?\ndeliverable: \n%U\n")

          ("n" "note" entry (file ,capfile)
		   "* %?  :\\%note:\n%U\n")

          ("a" "appointment" entry (file ,capfile)
		   "* %?\n%U\n%^t\n")

          ("s" "appointment-span" entry (file ,capfile)
		   "* TODO %?\n%U\n%^t--%^t\n")

          ("d" "deadline" entry (file ,capfile)
		   "* TODO %?\nDEADLINE: %^t\ndeliverable:\n%U\n")

		  ("e" "email" entry (file ,capfile)
		   "* TODO Respond to %:fromname; Re: %:subject\n%U\n%a\n")

		  ("m" "meeting" entry (file ,capfile)
		   "* meeting with%?  :\\%note:\n%U\n")
		  
		  ("p" "org-protocol" entry (file ,capfile)
           "* %^{Title} :\\%note:\n%u\n#+BEGIN_QUOTE\n%i\n#+END_QUOTE"
		   :immediate-finish t)

		  ("L" "org-protocol link" entry (file ,capfile)
           "* %^{Title} :\\%note:\n[[%:link][%:description]]\n%U"
		   :immediate-finish t))))

refile

general
(setq org-refile-targets '((nil :maxlevel . 9)
                           ("~/Org/reference/idea.org" :maxlevel . 9)
                           (org-agenda-files :maxlevel . 9))
	  org-refile-use-outline-path t
	  org-outline-path-complete-in-steps nil
	  org-refile-allow-creating-parent-nodes 'confirm
	  org-indirect-buffer-display 'current-window)

(add-hook 'org-capture-mode-hook (lambda () (evil-append 1)))
exclude done states

No need to file under DONE or CANC states

(setq org-refile-target-verify-function
	  (lambda () (not (member (nth 2 (org-heading-components)) org-done-keywords))))

clocking

(setq org-clock-history-length 23
	  org-clock-out-when-done t
	  org-clock-persist t
	  org-clock-report-include-clocking-task t)

agenda

general config
(setq org-agenda-files '("~/Org"
                        "~/Org/projects"
                        "~/Org/reference")
	  org-agenda-sticky t

	  org-agenda-dim-blocked-tasks nil
	  org-agenda-compact-blocks t
	  org-agenda-window-setup 'current-window
      
	  org-habit-graph-column 50
      
	  org-agenda-start-on-weekday 0
	  org-agenda-span 'day
	  org-agenda-current-time-string "### -- NOW -- ###"
	  org-agenda-time-grid '((daily today remove-match)
							 (0800 1000 1200 1200 1400 1600)
                             "......" "-----------------"))
right align tags

the agenda does not do this by default…it's annoying

(add-hook 'org-finalize-agenda-hook
		  (lambda () (setq org-agenda-tags-column (- 4 (window-width)))
			(org-agenda-align-tags)))
holidays and birthdays
(setq holiday-bahai-holidays nil
	  holiday-hebrew-holidays nil
	  holiday-oriental-holidays nil
	  holiday-islamic-holidays nil)

(setq calendar-holidays (append holiday-general-holidays
								holiday-christian-holidays))
library

Since I am never totally satisfied with how the agenda works, I have a massive library of functions to filter and manipulate tasks (mostly for skip functions). I also have a few variables I set in order to toggle certain views

task helper functions

These are the building blocks for skip functions.

timestamps

Each of these returns the timestamp if found.

(defun nd/get-date-property (date-property)
  "Helper function to get the date property and convert to a number.
If it does not have a date, it will return nil."
  (let ((timestamp (org-entry-get nil date-property)))
    (if timestamp (float-time (date-to-time timestamp)))))

(defun nd/heading-compare-timestamp (timestamp-fun
									 &optional ref-time future)
  "helper function that returns the timestamp (returned by 
timestamp-fun on the current header) if timestamp is futher back in 
time compared to a ref-time (default to 0 which is now, where negative
is past an positive is future). If the future flag is set, returns 
timestamp if it is in the future compared to ref-time. Returns nil if 
no timestamp is found."
  (let* ((timestamp (funcall timestamp-fun))
        (ref-time (or ref-time 0)))
    (if (and timestamp
             (if future
                 (> (- timestamp (float-time)) ref-time)
               (<= (- timestamp (float-time)) ref-time)))
        timestamp)))

(defun nd/is-timestamped-heading-p ()
  (nd/get-date-property "TIMESTAMP"))

(defun nd/is-scheduled-heading-p ()
  (nd/get-date-property "SCHEDULED"))

(defun nd/is-deadlined-heading-p ()
  (nd/get-date-property "DEADLINE"))

(defun nd/is-closed-heading-p ()
  (nd/get-date-property "CLOSED"))

(defun nd/is-stale-heading-p ()
  (nd/heading-compare-timestamp
   (lambda () (let ((ts (org-entry-get nil "TIMESTAMP")))
		   (if (and ts (not (find ?+ ts)))
			   (float-time (date-to-time ts)))))))

(defun nd/is-fresh-heading-p ()
  (nd/heading-compare-timestamp 'nd/is-timestamped-heading-p nil t))

(defvar nd/archive-delay-days 30
  "the number of days to wait before tasks show up in the archive view")

(defun nd/is-archivable-heading-p ()
  (nd/heading-compare-timestamp
   'nd/is-closed-heading-p
    (- (* 60 60 24 nd/archive-delay-days))))
task level testing

Each of these returns the keyword if true Doubles as a way to further test the todostate in downstream functions

  (defun nd/is-todoitem-p ()
    (let ((keyword (nth 2 (org-heading-components))))
      (if (member keyword org-todo-keywords-1)
          keyword)))

  (defun nd/is-project-p ()
    (and (nd/heading-has-children 'nd/is-todoitem-p) (nd/is-todoitem-p)))

  (defun nd/is-task-p ()
    (and (not (nd/heading-has-children 'nd/is-todoitem-p)) (nd/is-todoitem-p)))

  (defun nd/is-project-task-p ()
    (and (nd/heading-has-parent 'nd/is-todoitem-p) (nd/is-task-p)))

  (defun nd/is-atomic-task-p ()
    (and (not (nd/heading-has-parent 'nd/is-todoitem-p)) (nd/is-task-p)))
property testing

Returns t is heading matches a certian set of properties

  (defun nd/is-periodical-heading-p ()
    (equal "periodical" (org-entry-get nil "PARENT_TYPE" t)))

  (defun nd/is-iterator-heading-p ()
    (equal "iterator" (org-entry-get nil "PARENT_TYPE" t)))

  (defun nd/heading-has-effort-p ()
    (org-entry-get nil "Effort"))

  (defun nd/heading-has-context-p ()
    (let ((tags (org-get-tags-at)))
      (or (> (length (nd/filter-list-prefix "#" tags)) 0)
          (> (length (nd/filter-list-prefix "@" tags)) 0))))

  (defun nd/heading-has-tag-p (tag)
    (member tag (org-get-tags-at)))
relational testing

Returns t if heading has certain relationship to other headings

  (defun nd/heading-has-children (heading-test)
    "returns t if heading has subheadings that return t when assessed with 
  heading-test function"
    (let ((subtree-end (save-excursion (org-end-of-subtree t)))
          has-children previous-point)
      (save-excursion
        (setq previous-point (point))
        (outline-next-heading)
        (while (and (not has-children)
                    (< previous-point (point) subtree-end))
          (when (funcall heading-test)
            (setq has-children t))
          (setq previous-point (point))
          (org-forward-heading-same-level 1 t)))
      has-children))

  (defun nd/heading-has-parent (heading-test)
    "returns parent keyword if heading is in the immediate subtree of a heading 
  that evaluated to t with heading-test function"
    (save-excursion (and (org-up-heading-safe) (funcall heading-test))))

  (defun nd/has-discontinuous-parent ()
    "returns t if heading has a parent which is not a
  todoitem which in turn has a parent which is a todoitem"
    (let ((has-todoitem-parent)
          (has-non-todoitem-parent))
      (save-excursion
        (while (and (org-up-heading-safe)
                    (not has-todoitem-parent))
          (if (nd/is-todoitem-p)
              (setq has-todoitem-parent t)
            (setq has-non-todoitem-parent t))))
      (and has-todoitem-parent has-non-todoitem-parent)))
project level testing
(defconst nd/project-invalid-todostates
  '("WAIT" "NEXT")
  "projects cannot have these todostates") 

(defmacro nd/compare-statuscodes (op sc1 sc2 sc-list)
  `(,op (position ,sc1 ,sc-list) (position ,sc2 ,sc-list)))

(defun nd/decend-into-project (allowed-statuscodes trans-tbl get-task-status)
  (let ((project-status (first allowed-statuscodes))
		(breaker-status (car (last allowed-statuscodes)))
		(previous-point))
	;; (message "hi")
	(save-excursion
	  (setq previous-point (point))
	  (outline-next-heading)
	  ;; loop through subproject tasks until breaker-status found
	  (while (and (not (eq project-status breaker-status))
				  (> (point) previous-point))
		(let ((keyword (nd/is-todoitem-p)))
		  (if keyword
			  (let ((new-status
					 ;; if project then descend recursively
					 (if (nd/heading-has-children 'nd/is-todoitem-p)
						 (let ((n (nd/get-project-status)))
						   ;; if project returns an allowed status
						   ;; then use that
						   (or (and (member n allowed-statuscodes) n)
							   ;; otherwise look up the value in the
							   ;; translation table and return error
							   ;; if not found
							   (nth (or (alist-get n trans-tbl)
										(error (concat "status not found: " n)))
									allowed-statuscodes)))
					   ;; if not project then use user-defined function
					   ;; to obtain status of task
					   (nth (funcall get-task-status keyword)
							allowed-statuscodes))))
				;; (message (format "%s" (concat "new status: " (symbol-name new-status))))
				;; (message (format "%s" (concat "project status: " (symbol-name project-status))))
				;; (message (format "%s" keyword))
				(if (nd/compare-statuscodes > new-status project-status allowed-statuscodes)
					(setq project-status new-status)))))
		(setq previous-point (point))
		(org-forward-heading-same-level 1 t)))
	project-status))

(defun nd/get-project-status ()
  (let ((keyword (nd/is-todoitem-p)))
	;; these first three are easy because they only require
	;; testing the project headline and nothing underneath
	(cond
	 ((nd/is-scheduled-heading-p) :scheduled-project)
	 ((equal keyword "HOLD") :held)
	 ((member keyword nd/project-invalid-todostates)
	  :invalid-todostate)
	 
	 ;; these require descending into the project subtasks
	 ((equal keyword "CANC")
	  (nd/decend-into-project
	   '(:archivable :complete)
	   '((:stuck . 1)
		 (:held . 1)
		 (:waiting . 1)
		 (:active . 1)
		 (:scheduled-project . 1)
		 (:invalid-todostate . 1)
		 (:undone-complete . 1)
		 (:done-incomplete . 1))
	   (lambda (k)
		 (if (and (member k org-done-keywords)
				  (nd/is-archivable-heading-p)) 0 1))))
	 
	 ((equal keyword "DONE")
	  (nd/decend-into-project
	   '(:archivable :complete :done-incomplete)
	   '((:stuck . 2)
		 (:held . 2)
		 (:waiting . 2)
		 (:active . 2)
		 (:scheduled-project . 2)
		 (:invalid-todostate . 2)
		 (:undone-complete . 2))
	   (lambda (k)
		 (if (member k org-done-keywords)
			 (if (nd/is-archivable-heading-p) 0 1)
		   2))))
	 
	 ((equal keyword "TODO")
	  (nd/decend-into-project
	   '(:undone-complete :stuck :held :waiting :active)
	   '((:complete . 0)
		 (:archivable . 0)
		 (:scheduled-project . 1)
		 (:invalid-todostate . 1)
		 (:done-incomplete . 1))
	   (lambda (k)
		 (cond ((equal k "TODO") (if (nd/is-scheduled-heading-p) 4 1))
			   ((equal k "HOLD") 2)
			   ((equal k "WAIT") 3)
			   ((equal k "NEXT") 4)
			   (t 0)))))
	 
	 (t (error (concat "invalid keyword detected: " keyword))))))
iterator testing
(defconst nd/iter-future-time (* 7 24 60 60))

(defconst nd/iter-statuscodes '(:uninit :empty :active))

(defun nd/get-iterator-status ()
  (let ((iter-status :uninit)
		(subtree-end (save-excursion (org-end-of-subtree t))))
	(save-excursion
	  (setq previous-point (point))
	  (outline-next-heading)
	  (while (and (not (eq iter-status :active))
				  (< (point) subtree-end))
		(let ((keyword (nd/is-atomic-task-p))
			  (new-status))
		  (if keyword
			  (progn
				(setq new-status (if (nd/heading-compare-timestamp
									  (lambda ()
										(or (nd/is-scheduled-heading-p)
											(nd/is-deadlined-heading-p)))
										nd/iter-future-time t)
									 :active
								   :empty))
				(if (nd/compare-statuscodes > new-status iter-status nd/iter-statuscodes)
					(setq iter-status new-status)))))
		(outline-next-heading)))
	iter-status))
periodical testing
(defconst nd/peri-future-time nd/iter-future-time)

(defconst nd/peri-statuscodes '(:uninit :stale :fresh))

(defun nd/get-periodical-status ()
  (let ((peri-status :uninit)
		(subtree-end (save-excursion (org-end-of-subtree t))))
	(save-excursion
	  (setq previous-point (point))
	  (outline-next-heading)
	  (while (and (not (eq peri-status :fresh))
				  (< (point) subtree-end))
		(if (and (nd/is-periodical-heading-p)
				 (not (nd/heading-has-children 'nd/is-periodical-heading-p)))
			(let ((new-status
				   (if (nd/heading-compare-timestamp
						'nd/is-timestamped-heading-p
						nd/iter-future-time t)
					   :fresh
					 :stale)))
			  (if (nd/compare-statuscodes > new-status peri-status nd/peri-statuscodes)
				  (setq peri-status new-status))))
		(outline-next-heading)))
	peri-status))
skip functions

These are the primary means we use to sort through tasks. Note that we could do this with tags in the custom commands section but I find this easier to maintain and possibly faster.

helper skip functions and macros

Subunits for skip functions. Not meant to be used or called from the custom commands api

  (defun nd/skip-heading ()
    (save-excursion (or (outline-next-heading) (point-max))))

  (defun nd/skip-subtree ()
    (save-excursion (or (org-end-of-subtree t) (point-max))))

  (defconst nd/project-skip-todostates
    '("HOLD" "CANC")
    "These keywords override all contents within their subtrees.
  Currently used to tell skip functions when they can hop over
  entire subtrees to save time and ignore tasks")

  (defmacro nd/skip-heading-with (heading-fun test-fun)
    "Skips headings accoring to certain characteristics. heading-fun
  is a function that tests the heading and returns the todoitem keyword
  on success. Test-fun is a function that further tests the identity of
  the heading and may or may not use the keyword output supplied by
  the heading-fun. This function will not skip if heading-fun and 
  test-fun return true"
    `(save-restriction
       (widen)
       (let ((keyword (,heading-fun)))
         (message keyword)
         (if (not (and keyword ,test-fun))
             (nd/skip-heading)))))
headings

Skip functions for headings which may or may not be todo-items

Note in the case of stale headings that I only care about those that are not part of projects (projects will get taken care of when the entire project is finished) and those that are not DONE/CANC (as those appear in the regular archive section)

  (defun nd/skip-headings-with-tags (pos-tags-list &optional neg-tags-list)
    "Skips headings that have tags in pos-tags-list and also skips
  tags that do not have tags in neg-tags-list"
    (save-restriction
      (widen)
      (let ((header-tags (org-get-tags-at)))
        (if (and (or (not pos-tags-list)
                     (intersection pos-tags-list header-tags :test 'equal))
                 (not (intersection neg-tags-list header-tags :test 'equal)))
            (nd/skip-heading)))))

  (defun nd/skip-non-stale-headings ()
    (save-restriction
      (widen)
      (let ((keyword (nd/is-todoitem-p)))
        (if (not
             (and (nd/is-stale-heading-p)
                  (not (member keyword org-done-keywords))
                  (not (nd/heading-has-children 'nd/is-todoitem-p))
                  (not (nd/heading-has-parent 'nd/is-todoitem-p))))
            (nd/skip-heading)))))
atomic tasks

By definition these have no parents, so I don't need to worry about skipping over projects any todo state is valid and we only sort by done/canc

  ;; NOTE: this assumes that tags-todo will
  ;; filter out all done state tasks
  (defun nd/skip-non-atomic-tasks ()
    (save-excursion
      (widen)
      (if (not (nd/is-atomic-task-p))
          (nd/skip-heading))))

  (defun nd/skip-non-closed-atomic-tasks ()
    (nd/skip-heading-with
     nd/is-atomic-task-p
     (and (member keyword org-done-keywords)
          (not (nd/is-archivable-heading-p)))))

  (defun nd/skip-non-archivable-atomic-tasks ()
    (nd/skip-heading-with
     nd/is-atomic-task-p
     (and (member keyword org-done-keywords)
          (nd/is-archivable-heading-p))))
periodicals

These are headers marked with PARENT_TYPE=periodical property that have timestamped headers as children which in turn may or may not have todo keywords. They are to be refilled when all children are stale Note that I only care about the parent headers as the children should always show up in the agenda simply because they have timestamps. Parents can be either fresh (at least one child in the future) or stale (all children in the past).

(defun nd/skip-non-periodical-parent-headers ()
  (save-restriction
    (widen)
    (if (not (and (nd/is-periodical-heading-p)
                  (not (nd/heading-has-parent 'nd/is-periodical-heading-p))))
        (nd/skip-heading))))

(defun nd/skip-non-periodical-untimestamped ()
  (save-restriction
    (widen)
    (if (not (and (nd/is-periodical-heading-p)
				  (not (nd/is-timestamped-heading-p))
                  (not (nd/heading-has-children 'nd/is-periodical-heading-p))))
        (nd/skip-heading))))
iterators

iterators are like projects but have additional status codes based on when the iterator will run out

(defun nd/skip-non-iterator-parent-headers ()
  (save-restriction
    (widen)
    (if (not (and (nd/is-iterator-heading-p)
                  (not (nd/heading-has-parent 'nd/is-iterator-heading-p))))
        (nd/skip-heading))))

(defun nd/skip-non-iterator-unscheduled ()
  (nd/skip-heading-with
   nd/is-atomic-task-p
   (not (or (nd/is-scheduled-heading-p)
			(nd/is-deadlined-heading-p)))))
project tasks

Since these are part of projects I need to assess if the parent project is skippable, in which case I jump to the next subtree Note that I only care about the keyword in these cases because I don't archive these, I archive their parent projects. The keywords I care about are NEXT, WAIT, and HOLD because these are definitive project tasks that require/inhibit futher action. (TODO = stuck which I take care of at the project level, and DONE/CANC = archivable which is dealt with similarly)

(defun nd/skip-non-project-tasks ()
  (save-restriction
    (widen)
    (let ((keyword (nd/is-todoitem-p)))
      (if keyword
          (if (nd/heading-has-children 'nd/is-todoitem-p)
              (if (member keyword nd/project-skip-todostates)
                  (nd/skip-subtree)
                (nd/skip-heading))
            (if (not (nd/heading-has-parent 'nd/is-todoitem-p))
                (nd/skip-heading)))
        (nd/skip-heading)))))
header-level errors

Some headers are invalid under certain conditions which I test here

  (defun nd/skip-non-discontinuous-project-tasks ()
    (nd/skip-heading-with
     nd/is-todoitem-p
     (nd/has-discontinuous-parent)))

  (defun nd/skip-non-done-unclosed-todoitems ()
    (nd/skip-heading-with
     nd/is-todoitem-p
     (and (member keyword org-done-keywords)
          (not (nd/is-closed-heading-p)))))

  (defun nd/skip-non-undone-closed-todoitems ()
    (nd/skip-heading-with
     nd/is-todoitem-p
     (and (not (member keyword org-done-keywords))
          (nd/is-closed-heading-p))))
          
  (defun nd/skip-atomic-tasks-with-context ()
    (nd/skip-heading-with
     nd/is-atomic-task-p
     (not (nd/heading-has-context-p))))

  (defun nd/skip-project-tasks-with-context ()
    (nd/skip-heading-with
     nd/is-project-task-p
     (not (nd/heading-has-context-p))))

  (defun nd/skip-projects-with-context ()
    (nd/skip-heading-with
     nd/is-project-p
     (not (nd/heading-has-context-p))))

  (defun nd/skip-tasks-with-effort ()
    (nd/skip-heading-with
     nd/is-task-p
     (not (nd/heading-has-effort-p))))
projects

Projects are handled quite simply. They have statuscodes for which I test, and this can all be handled by one function. Note that this is used for "normal" projects as well as iterators

(defun nd/skip-non-projects (&optional ignore-toplevel)
  (save-restriction
    (widen)
    (let ((keyword (nd/is-project-p)))
      (if keyword
          (if (and nd/agenda-limit-project-toplevel
				   (not ignore-toplevel)
                   (nd/heading-has-parent 'nd/is-todoitem-p))
              (nd/skip-subtree))
        (nd/skip-heading)))))
variables
(defvar nd/agenda-limit-project-toplevel t
  "If true, filter projects by all levels or top level only.")

(defvar nd/agenda-hide-incubator-tags t
  "If true, don't show incubator headings.")
interactive view functions
(defun nd/toggle-project-toplevel-display ()
  "Toggle all project headings and toplevel only headings in project blocks."
  (interactive)
  (setq nd/agenda-limit-project-toplevel (not nd/agenda-limit-project-toplevel))
  (when (equal major-mode 'org-agenda-mode)
    (org-agenda-redo))
  (message "Showing %s project view in agenda"
           (if nd/agenda-limit-project-toplevel "toplevel" "complete")))
           
(defun nd/org-agenda-filter-non-context ()
  "Filter all tasks with context tags."
  (interactive)
  (let* ((tags-list (mapcar #'car org-tag-alist))
         (context-tags (append
                        (nd/filter-list-prefix "@" tags-list)
                        (nd/filter-list-prefix "#" tags-list))))
    (setq org-agenda-tag-filter
          (mapcar (lambda (tag) (concat "-" tag)) context-tags))
    (org-agenda-filter-apply org-agenda-tag-filter 'tag)))

(defun nd/org-agenda-filter-non-peripheral ()
  "Filter all tasks with context tags."
  (interactive)
  (let* ((peripheral-tags '("PERIPHERAL")))
    (setq org-agenda-tag-filter
          (mapcar (lambda (tag) (concat "-" tag)) peripheral-tags))
    (org-agenda-filter-apply org-agenda-tag-filter 'tag)))
agenda aesthetics
(setq org-agenda-tags-todo-honor-ignore-options t)

(setq org-agenda-prefix-format
      '((agenda . "  %-12:c %-5:e %?-12t% s")
        (timeline . "  % s")
        (todo . "  %-12:c")
        (tags . "  %-12:c %-5:e ")
        (search . "  %-12:c")))

(defconst nd/org-agenda-todo-sort-order '("NEXT" "WAIT" "HOLD" "TODO"))

(setq org-agenda-cmp-user-defined
      '(lambda (a b)
         (let ((pa (- (length (member
                               (get-text-property 1 'todo-state a)
                               nd/org-agenda-todo-sort-order))))
               (pb (- (length (member
                               (get-text-property 1 'todo-state b)
                               nd/org-agenda-todo-sort-order)))))
           (cond ((or (null pa) (null pb)) nil)
                 ((> pa pb) +1)
                 ((< pa pb) -1)))))
custom commands
(defun nd/org-agenda-filter-status (filter status-fun a-line)
  "Filter for org-agenda-before-sorting-filter-function intended for
agenda project views (eg makes the assumption that all entries are
from projects in the original org buffer)

Will go to the original org buffer and determine the project status
after which it will check if status is in filter. If true, the flag
string in the prefix is replaced with the status and the status is
set as a text property for further sorting"
  (let* ((m (get-text-property 1 'org-marker a-line))
		 (s (with-current-buffer (marker-buffer m)
			  (goto-char m)
			  (funcall status-fun))))
	(if (member s filter)
		(org-add-props (replace-regexp-in-string
					   "xxxx" (symbol-name s) a-line)
					  nil 'project-status s))))

(defun nd/org-agenda-sort-prop (prop order a b)
  (let* ((ta (get-text-property 1 prop a))
		 (tb (get-text-property 1 prop b))
		 (pa (position ta order :test (if (stringp ta) #'equal)))
         (pb (position tb order :test (if (stringp tb) #'equal))))
    (cond ((or (null pa) (null pb)) nil)
          ((< pa pb) +1)
          ((> pa pb) -1))))

(defun nd/agenda-base-header-cmd (match header skip-fun)
  `(tags
    ,match
    ((org-agenda-overriding-header ,header)
     (org-agenda-skip-function ,skip-fun)
     (org-agenda-sorting-strategy '(category-keep)))))

(defun nd/agenda-base-task-cmd (match header skip-fun &optional sort)
  (or sort (setq sort ''(category-keep)))
  `(tags-todo
    ,match
    ((org-agenda-overriding-header ,header)
     (org-agenda-skip-function ,skip-fun)
     (org-agenda-todo-ignore-with-date t)
     (org-agenda-sorting-strategy ,sort))))

(let* ((actionable "-NA-REFILE-%inc")
	   (periodical "PARENT_TYPE=\"periodical\"")
	   (iterator "PARENT_TYPE=\"iterator\"")
	   (habit "STYLE=\"habit\"")
	   (task-match (concat actionable "-" periodical "-" habit "/!"))
       (act-no-rep-match (concat actionable "-" periodical "-" iterator "-" habit "/!"))
       (peri-match (concat actionable "+" periodical "-" iterator "-" habit))
       (iter-match (concat actionable "-" periodical "+" iterator "-" habit "/!")))

  (setq org-agenda-custom-commands
        `(("a"
		   "Calendar View"
		   ((agenda "" ((org-agenda-skip-function '(nd/skip-headings-with-tags '("%inc" "REFILE")))
						(org-agenda-include-diary t)))))

		  ("t"
           "Task View"
           (,(nd/agenda-base-task-cmd act-no-rep-match
                                          "Project Tasks"
                                          ''nd/skip-non-project-tasks
                                          ''(user-defined-up category-keep))
            ,(nd/agenda-base-task-cmd act-no-rep-match
                                          "Atomic Tasks"
                                          ''nd/skip-non-atomic-tasks)))

          ("p"
           "Project View"
		   ((tags-todo
		  	 ,act-no-rep-match
		  	 ((org-agenda-overriding-header
		  	   (concat (and
		  				nd/agenda-limit-project-toplevel "Toplevel ")
		  			   "Projects"))
		  	  (org-agenda-skip-function '(nd/skip-non-projects))
		  	  (org-agenda-before-sorting-filter-function
			   (lambda (l) (nd/org-agenda-filter-status
					   '(:scheduled-project :invalid-todostate :undone-complete
											:done-incomplete :stuck :waiting
											:held :active)
					   'nd/get-project-status l)))
			  (org-agenda-cmp-user-defined
			   (lambda (a b) (nd/org-agenda-sort-prop
						 'project-status
						 '(:scheduled-project :invalid-todostate :undone-complete
											  :done-incomplete :stuck :waiting
											  :held :active)
						 a b)))
		  	  (org-agenda-prefix-format '((tags . "  %-12:c %(format \"xxxx: \")")))
		  	  (org-agenda-sorting-strategy '(user-defined-down category-keep))))))
		  
          ("i"
           "Incubator View"
           ((agenda "" ((org-agenda-span 7)
                        (org-agenda-time-grid nil)
                        (org-agenda-entry-types '(:deadline :timestamp))))
            ,(nd/agenda-base-task-cmd "-NA-REFILE+%inc/!"
									  "Incubated Tasks"
									  ''nd/skip-non-atomic-tasks)
		   (tags-todo
			"-NA-REFILE+%inc/!"
		  	 ((org-agenda-overriding-header
		  	   (concat (and
		  				nd/agenda-limit-project-toplevel "Toplevel ")
		  			   "Incubated Projects"))
		  	  (org-agenda-skip-function '(nd/skip-non-projects))
		  	  (org-agenda-before-sorting-filter-function
			   (lambda (l) (nd/org-agenda-filter-status
					   '(:scheduled-project :invalid-todostate :undone-complete
											:done-incomplete :stuck :waiting
											:held :active)
					   'nd/get-project-status l)))
			  (org-agenda-cmp-user-defined
			   (lambda (a b) (nd/org-agenda-sort-prop
						 'project-status
						 '(:scheduled-project :invalid-todostate :undone-complete
											  :done-incomplete :stuck :waiting
											  :active :held)
						 a b)))
		  	  (org-agenda-prefix-format '((tags . "  %-12:c %(format \"xxxx: \")")))
		  	  (org-agenda-sorting-strategy '(user-defined-down category-keep))))))
		  
          ("P"
           "Periodical View"
		   ((tags
			 ,(concat actionable "-" iterator "+" periodical "-" habit)
		  	 ((org-agenda-overriding-header "Periodical Status")
		  	  (org-agenda-skip-function '(nd/skip-non-periodical-parent-headers))
		  	  (org-agenda-before-sorting-filter-function
			   (lambda (l) (nd/org-agenda-filter-status
					   nd/peri-statuscodes 'nd/get-periodical-status l)))
			  (org-agenda-cmp-user-defined
			   (lambda (a b) (nd/org-agenda-sort-prop
						 'project-status nd/peri-statuscodes a b)))
		  	  (org-agenda-prefix-format '((tags . "  %-12:c %(format \"xxxx: \")")))
		  	  (org-agenda-sorting-strategy '(user-defined-down category-keep))))
            ,(nd/agenda-base-header-cmd "-NA-REFILE+PARENT_TYPE=\"periodical\""
										"Untimestamped"
										''nd/skip-non-periodical-untimestamped)))

          ("I"
           "Iterator View"
           ((tags
			 "-NA-REFILE+PARENT_TYPE=\"iterator\""
		  	 ((org-agenda-overriding-header "Iterator Status")
		  	  (org-agenda-skip-function '(nd/skip-non-iterator-parent-headers))
		  	  (org-agenda-before-sorting-filter-function
			   (lambda (l) (nd/org-agenda-filter-status nd/iter-statuscodes 'nd/get-iterator-status l)))
			  (org-agenda-cmp-user-defined
			   (lambda (a b) (nd/org-agenda-sort-prop 'project-status nd/iter-statuscodes a b)))
		  	  (org-agenda-prefix-format '((tags . "  %-12:c %(format \"xxxx: \")")))
		  	  (org-agenda-sorting-strategy '(user-defined-down category-keep))))
            ,(nd/agenda-base-task-cmd "-NA-REFILE+PARENT_TYPE=\"iterator\"/!"
									  "Unscheduled or Undeaded"
									  ''nd/skip-non-iterator-unscheduled)))

          ("r"
           "Refile"
           ((tags "REFILE"
                  ((org-agenda-overriding-header "Tasks to Refile"))
                  (org-tags-match-list-sublevels nil))))

		  ("e"
		   "Critical Errors"
           (,(nd/agenda-base-task-cmd task-match
									  "Discontinous Project"
									  ''nd/skip-non-discontinuous-project-tasks)
			,(nd/agenda-base-header-cmd task-match
										"Undone Closed"
										''nd/skip-non-undone-closed-todoitems)
            ,(nd/agenda-base-header-cmd (concat actionable "-" periodical)
										"Done Unclosed"
										''nd/skip-non-done-unclosed-todoitems)))

          ("A"
           "Archivable Tasks and Projects"
           (,(nd/agenda-base-header-cmd (concat actionable "-" periodical "-" habit)
										"Archivable Atomic Tasks and Iterators"
										''nd/skip-non-archivable-atomic-tasks)
            ,(nd/agenda-base-header-cmd (concat actionable "-" habit)
										"Stale Tasks and Periodicals"
										''nd/skip-non-stale-headings)
			(tags-todo
			 ,(concat actionable "-" periodical "-" iterator "-" habit)
		  	 ((org-agenda-overriding-header
		  	   (concat (and	nd/agenda-limit-project-toplevel "Toplevel ")
		  			   "Archivable Projects"))
		  	  (org-agenda-skip-function '(nd/skip-non-projects))
		  	  (org-agenda-before-sorting-filter-function
			   (lambda (l) (nd/org-agenda-filter-status '(:archivable) 'nd/get-project-status l)))
			  (org-agenda-cmp-user-defined
			   (lambda (a b) (nd/org-agenda-sort-prop 'project-status '(:archivable) a b)))
		  	  (org-agenda-prefix-format '((tags . "  %-12:c %(format \"xxxx: \")")))
		  	  (org-agenda-sorting-strategy '(user-defined-down category-keep)))))))))

taskjuggler

;;(require 'ox-taskjuggler)

tools

magit

(use-package magit
  :ensure t
  :config
  :delight auto-revert-mode
  (setq magit-push-always-verify nil
		git-commit-summary-max-length 50))

dired

no confirm

Keeping confirmation enabled does weird stuff with helm. Not ideal at the moment but we shall see if I find something better.

(setq dired-no-confirm '(move copy))

interactive functions

(defun nd/dired-xdg-open ()
  "Open all non-text files in external app using xdg-open. Only regular files are considered"
  (interactive)
  (let* ((file-list (seq-filter #'file-regular-p (dired-get-marked-files)))
		 (do-it (if (<= (length file-list) 5)
					t
				  (y-or-n-p "Open more then 5 files? "))))
	(when do-it
	  (mapc
	   (lambda (f) (let ((process-connection-type nil))
				(start-process "" nil "xdg-open" f)))
	   file-list))))

(defun nd/dired-open-with ()
  "Open marked non-text files in external app via open-with dialog
according to mime types as listed in all available desktop files."
  (interactive)
  (let* ((mf (seq-filter #'file-regular-p (dired-get-marked-files)))
		 (qmf (mapcar #'shell-quote-argument mf))
		 (file-mime-list (mapcar (lambda (f) (list f (nd/get-mime-type f))) qmf)))

	(if (= (length file-mime-list) 0)
		(message "No files selected")
	  
	  (let* ((first-pair (car file-mime-list))
			 (last-pairs (cdr file-mime-list))
			 mime-alist file-list)
		(setq file-list (nth 0 first-pair)
			  mime-alist (nd/get-apps-from-mime (nth 1 first-pair)))
		;; if multiple files selected, add to the selection list
		(if last-pairs
			(progn
			  (setq file-list (string-join (mapcar #'car file-mime-list) " "))
			  (dolist (mime (mapcar (lambda (f) (nth 1 f)) last-pairs))
				(setq mime-alist (intersection mime-alist
											   (nd/get-apps-from-mime mime)
											   :test #'equal)))))
		(if (= (length mime-alist) 0)
			(let* ((ml (delete-dups (mapcan #'cdr file-mime-list)))
				   (mls (string-join ml ", ")))
			  (if (= (length ml) 1)
				  (message (concat "No apps found for mime type: "	mls))
				(message (concat "No common apps found for mime types: " mls))))
		  (helm
		   :sources (helm-build-sync-source "Apps"
		  			  :candidates mime-alist
					  :action '(("Open" . (lambda (f) (nd/execute-desktop-command f file-list)))))
		   :buffer "*helm open with*"))))))

(defun nd/dired-sort-by ()
  "Sort current dired buffer by a list of choices presented in helm menu.
Note this assumes there are no sorting switches on `dired-ls'"
  (interactive)
  (let ((sort-alist '(("Name" . "")
					  ("Date" . "-t")
					  ("Size" . "-S")
					  ("Extension" . "-X")
					  ("Dirs First" . "--group-directories-first"))))
	(helm
	 :sources
	 (helm-build-sync-source "Switches"
	   :candidates sort-alist
	   :action
	   '(("Sort" . (lambda (s) (dired-sort-other (concat dired-listing-switches " " s))))))
	 :buffer "*helm sort buffer*")))

compression

Only supports tar.gz, tar.bz2, tar.xz, and .zip by default. Add support for more fun algos such as lzo and zpaq

(if (file-exists-p "/usr/bin/7z")
	(add-to-list 'dired-compress-files-alist
					'("\\.7z\\'" . "7z a %o %i")))

(if (file-exists-p "/usr/bin/lrzip")
	(progn
	  (add-to-list 'dired-compress-files-alist
				   '("\\.lrz\\'" . "lrzip -L 9 -o %o %i &"))
	  (add-to-list 'dired-compress-files-alist
				   '("\\.lzo\\'" . "lrzip -l -L 9 -o %o %i &"))
	  (add-to-list 'dired-compress-files-alist
				   '("\\.zpaq\\'" . "lrzip -z -L 9 -o %o %i &"))))

;; NOTE: this must be after the shorter lrz algos otherwise it will
;; always default to .lrz and not .tar.lrz
(if (file-exists-p "/usr/bin/lrztar")
	(progn
	  (add-to-list 'dired-compress-files-alist
				   '("\\.tar\\.lrz\\'" . "lrztar -L 9 -o %o %i &"))
	  (add-to-list 'dired-compress-files-alist
				   '("\\.tar\\.lzo\\'" . "lrztar -l -L 9 -o %o %i &"))
	  (add-to-list 'dired-compress-files-alist
				   '("\\.tar\\.zpaq\\'" . "lrztar -z -L 9 -o %o %i &"))))

formatting for humans

make sizes human readable

(setq dired-listing-switches "-Alh")

mu4e attachments

By default the included gnus-dired package does not understan mu4e, so override the existing gnus-dired-mail-buffers function to fix. This allows going to a dired buffer, marking files, and attaching them interactively to mu4e draft buffers.

;; from here:
;; https://www.djcbsoftware.nl/code/mu/mu4e/Dired.html#Dired
(require 'gnus-dired)

(eval-after-load 'gnus-dired
  '(defun gnus-dired-mail-buffers ()
	 "Return a list of active mu4e message buffers."
	 (let (buffers)
       (save-current-buffer
		 (dolist (buffer (buffer-list t))
           (set-buffer buffer)
           (when (and (derived-mode-p 'message-mode)
					  (null message-sent-message-via))
			 (push (buffer-name buffer) buffers))))
       (nreverse buffers))))

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

mu4e

basic

(require 'mu4e)

(setq mail-user-agent 'mu4e-user-agent
	  mu4e-maildir "/mnt/data/Mail"

	  mu4e-attachment-dir "~/Downloads"

	  mu4e-view-show-images t
      mu4e-headers-show-target nil
      
      message-kill-buffer-on-exit t
      
	  mu4e-change-filenames-when-moving t

	  mu4e-confirm-quit nil

	  mu4e-view-prefer-html t

	  mu4e-compose-dont-reply-to-self t
      
	  mu4e-get-mail-command "systemctl --user start mbsync"

	  user-full-name "Nate Dwarshuis")

headers view

(setq mu4e-headers-fields '((:human-date . 11)
							(:flags . 5)
							(:from . 22)
							(:thread-subject))
	  mu4e-headers-date-format "%F"
	  mu4e-headers-time-format "%R"
	  mu4e-use-fancy-chars nil)

quoting

(setq
 mu4e-compose-pre-hook
 (lambda ()
   (let* ((msg mu4e-compose-parent-message)
		  (html (and msg (plist-get msg :body-html)))
		  ;; oops, mu4e screwed up
		  (mu4e-html2text-command
		   (if (file-exists-p "/usr/bin/html2text")
			   "html2text --ignore-emphasis --images-to-alt --body-width=0"
			 'mu4e-shr2text)))
	 (if (and (member compose-type '(reply forward)) html)
		 (progn
		   ;; hackity hack, since the normal mu4e-message-body-text function
		   ;; does not render the desired html, do it here and force the
		   ;; aforementioned function to only look at text by removing
		   ;; the html
		   (plist-put msg :body-txt (mu4e~html2text-shell msg mu4e-html2text-command))
		   (plist-put msg :body-html nil))
	   (message "nada")))))

smtp

(require 'smtpmail)
(require 'smtpmail-async)
;; (require 'secrets)
;; (setq secrets-enabled t)
(setq send-mail-function 'async-smtpmail-send-it
	  message-send-mail-function 'async-smtpmail-send-it)
(add-to-list 'auth-sources (expand-file-name "~/.emacs.d/.authinfo_mu4e.gpg"))
;; (add-to-list 'auth-sources "secrets:default")

contexts

I have current have three contexts, personal and two work accounts. The first is a gmail account and the second/third are office365 accounts.

(setq mu4e-context-policy 'pick-first
	  mu4e-compose-context-policy 'ask-if-none
	  mu4e-user-mail-address-list '("natedwarshuis@gmail.com" "ndwarshuis3@gatech.edu" "ndwarsh@emory.edu")
      
      mu4e-contexts
	  `( ,(make-mu4e-context
		   :name "personal"
		   :match-func
		   (lambda (msg)
			 (when msg
			   (let ((pfx (mu4e-message-field msg :maildir)))
				 (string-prefix-p "/gmail" pfx))))
		   :vars '((mu4e-trash-folder . "/gmail/trash")
				   (mu4e-drafts-folder . "/gmail/drafts")
				   (mu4e-sent-folder . "/gmail/sent")
				   (mu4e-refile-folder . "/gmail/archive")
				   (mu4e-sent-messages-behavior . delete)
				   (smtpmail-stream-type . starttls)
				   (smtpmail-smtp-server . "smtp.gmail.com")
				   (smtpmail-smtp-service . 587)
				   (smtpmail-smtp-user . "natedwarshuis@gmail.com")
				   (user-mail-address . "natedwarshuis@gmail.com")
				   (mu4e-maildir-shortcuts .
				   						   (("/gmail/inbox" . ?i)
				   							("/gmail/sent" . ?s)
				   							("/gmail/trash" . ?t)
				   							("/gmail/drafts" . ?d)
											("/gmail/archive" . ?a)))))
		 ,(make-mu4e-context
		   :name "gatech"
		   :match-func
		   (lambda (msg)
			 (when msg
			   (let ((pfx (mu4e-message-field msg :maildir)))
			     (string-prefix-p "/gatech" pfx))))
		   :vars '((mu4e-trash-folder . "/gatech/trash")
				   (mu4e-drafts-folder . "/gatech/drafts")
				   (mu4e-sent-folder . "/gatech/sent")
				   (mu4e-refile-folder . "/gatech/archive")
				   (mu4e-sent-messages-behavior . sent)
				   (smtpmail-stream-type . starttls)
				   (smtpmail-smtp-server . "smtp.office365.com")
				   (smtpmail-smtp-service . 587)
				   (smtpmail-smtp-user . "ndwarshuis3@gatech.edu")
				   (user-mail-address . "ndwarshuis3@gatech.edu")
				   (mu4e-maildir-shortcuts .
										   (("/gatech/inbox" . ?i)
											("/gatech/sent" . ?s)
											("/gatech/trash" . ?t)
											("/gatech/drafts" . ?d)
											("/gatech/archive" . ?a)))))
		 ,(make-mu4e-context
		   :name "emory"
		   :match-func
		   (lambda (msg)
			 (when msg
			   (let ((pfx (mu4e-message-field msg :maildir)))
			     (string-prefix-p "/emory" pfx))))
		   :vars '((mu4e-trash-folder . "/emory/trash")
				   (mu4e-drafts-folder . "/emory/drafts")
				   (mu4e-sent-folder . "/emory/sent")
				   (mu4e-refile-folder . "/emory/archive")
				   (mu4e-sent-messages-behavior . sent)
				   (smtpmail-stream-type . starttls)
				   (smtpmail-smtp-server . "smtp.office365.com")
				   (smtpmail-smtp-service . 587)
				   (smtpmail-smtp-user . "ndwarsh@emory.edu")
				   (user-mail-address . "ndwarsh@emory.edu")
				   (mu4e-maildir-shortcuts .
										   (("/emory/inbox" . ?i)
											("/emory/sent" . ?s)
											("/emory/trash" . ?t)
                                            ("/emory/drafts" . ?d)
											("/emory/archive" . ?a)))))))

org-mu4e

(use-package org-mu4e
  :after (org mu4e)
  :config
  (setq
   ;; for using mu4e in org-capture templates
   org-mu4e-link-query-in-headers-mode nil
   ;; for composing rich-text emails using org mode
   org-mu4e-convert-to-html t))

auctex

(load "auctex.el" nil t t)
(require 'tex-mik)

(setq TeX-view-program-selection '(((output-dvi has-no-display-manager)
									"dvi2tty")
								   ((output-dvi style-pstricks)
									"dvips and gv")
								   (output-dvi "xdvi")
								   (output-pdf "Okular")
								   (output-html "xdg-open")))

;; remove ugly section size
(setq font-latex-fontify-sectioning 'color)

(add-hook 'LaTeX-mode-hook (lambda () (outline-minor-mode 1)))
(add-hook 'Tex-latex-mode-hook (lambda () (outline-minor-mode 1)))

(use-package outline-magic
  :ensure t
  :after outline)

bibtex

(use-package org-ref
  :ensure t
  :after org
  :config
  (setq reftex-default-bibliography "~/BibTeX/master.bib"
		org-ref-bibliography-notes "~/BibTeX/notes.org"
		org-ref-default-bibliography "~/BibTeX/master.bib"))

(use-package helm-bibtex
  :ensure t
  :after helm
  :config
  (setq bibtex-completion-bibliography "~/BibTeX/master.bib"
		bibtex-completion-library-path "~/BibTeX/pdf"
		bibtex-completion-pdf-field "File"))

shell

(defadvice ansi-term (before force-bash)
  (interactive (list "/bin/zsh")))
(ad-activate 'ansi-term)

ediff

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

keybindings

For the sake of my sanity, all bindings go here. Note this means I don't use :bind in use-package forms.

evil

I like being evil. All package and custom bindings go here.

base

(use-package evil
  :ensure t
  :init
  ;; this is required to make evil collection work
  (setq evil-want-integration nil)
  :config
  (evil-mode 1))

motion

By default, emacs counts a sentence as having at least 2 spaces after punctuation. Make this behave more like vim.

(setq sentence-end-double-space nil)

enhancements

delightfully ripped off from vim plugins

surround
(use-package evil-surround
  :ensure t
  :after evil
  :config
  (global-evil-surround-mode 1))
commentary
(use-package evil-commentary
  :ensure t
  :after evil
  :delight
  :config
  (evil-commentary-mode))
replace with register
(use-package evil-replace-with-register
  :ensure t
  :after evil
  :config
  (evil-replace-with-register-install))

unbind emacs keys

Some of these commands just get in the way of being evil (which really means that I keep pressing them on accident). Rather than nullifying them completely, tuck them away in the emacs state map in case I actually want them.

(mapc (lambda (k) (nd/move-key global-map evil-emacs-state-map (eval k)))
	  '((kbd "C-s")
		(kbd "C-p")
		(kbd "C-n")
		(kbd "C-f")
		(kbd "C-b")
		(kbd "C-a")
		(kbd "C-e")
		
		(kbd "C-x C-;")
		(kbd "C-x C-l")
		(kbd "C-x C-u")
		(kbd "C-x C-z")
		(kbd "C-x C-c")

		(kbd "M-c")
		(kbd "M-d")
		(kbd "M-e")
		(kbd "M-r")
		(kbd "M-f")
		(kbd "M-h")
		(kbd "M-j")
		(kbd "C-M-j")
		(kbd "M-k")
		(kbd "M-l")
		(kbd "M-m")
		(kbd "M-q")
		(kbd "M-w")
		(kbd "M-t")
		(kbd "M-u")
		(kbd "M-i")
		(kbd "M-z")
		(kbd "M-v")
		(kbd "M-/")
		(kbd "M-;")
		(kbd "M-DEL")))

evil-org

(use-package evil-org
  :ensure t
  :after (evil org)
  :delight
  :config
  (add-hook 'org-mode-hook 'evil-org-mode)
  (add-hook 'evil-org-mode-hook 'evil-org-set-key-theme)

  (require 'evil-org-agenda)
  (evil-org-agenda-set-keys)
  ;; some of the defaults bug me...
  (evil-define-key 'motion org-agenda-mode-map
	"t" 'nd/toggle-project-toplevel-display
	"D" 'org-agenda-day-view
	"W" 'org-agenda-week-view
	"M" 'org-agenda-month-view
	"Y" 'org-agenda-year-view
	"ct" nil
	"sC" 'nd/org-agenda-filter-non-context
	"sP" 'nd/org-agenda-filter-non-peripheral
	"e" 'org-agenda-set-effort
	"ce" nil))

evil-magit

(use-package evil-magit
  :ensure t
  :after (evil magit))

visual line mode

This is somewhat strange because all I really care about is moving between lines and to the beginning and end as normal. However, I like the idea of thinking of paragraphs as one line (eg df. deletes a sentence even if on multiple lines). Opinion subject to change.

(evil-define-key '(normal visual) 'visual-line-mode
  "j" 'evil-next-visual-line
  "k" 'evil-previous-visual-line
  "0" 'beginning-of-visual-line
  "$" 'end-of-visual-line)

ess

ESS has not joined the dark side. Configure similarly to term (see below) where insert mode goes into "char-mode" where I can type like a normal terminal

(add-to-list 'evil-motion-state-modes 'ess-help-mode)
(add-to-list 'evil-insert-state-modes 'inferior-ess-mode)

(defun nd/ess-char-mode-insert ()
  "If not at the last line, go to the end of the buffer and enter insert mode.  Else just enter insert mode."
  (interactive)
  (if (/= (line-number-at-pos (point)) (line-number-at-pos (point-max)))
		(goto-char (point-max))))

(defun nd/inferior-ess-send-input ()
  "Go into insert mode after `inferior-ess-send-input'."
  (interactive)
  (inferior-ess-send-input)
  (evil-insert 1))

(evil-define-key 'normal inferior-ess-mode-map
  (kbd "RET") 'nd/inferior-ess-send-input)

(evil-define-key '(normal insert) inferior-ess-mode-map
  (kbd "C-k") 'comint-previous-input
  (kbd "C-j") 'comint-next-input)

(add-hook 'inferior-ess-mode-hook
		  (lambda ()
			(add-hook 'evil-insert-state-entry-hook
					  'nd/ess-char-mode-insert nil t)))

flyspell

(evil-define-key 'nornal 'flyspell-mode-map
  (kbd "C-;") 'flyspell-correct-previous-word-generic)

collection

Most packages that don't have an evil version are in this one. I don't like surprises so I set evil-collection-modes-list with the modes I actually want. Some of these are further configured below.

(use-package evil-collection
  :ensure t
  :after evil
  :init
  (setq evil-collection-mode-list
		'(dired flycheck company which-key helm minibuffer mu4e ediff
				term))
  (setq evil-collection-setup-minibuffer t)
  :config
  (evil-collection-init))
dired

Dired makes new buffers by default. Use find-alternate-file to avoid this.

(put 'dired-find-alternate-file 'disabled nil)

(evil-define-key 'normal dired-mode-map
  "a" 'dired-find-file
  "gs" 'nd/dired-sort-by
  (kbd "<return>") 'dired-find-alternate-file
  "^" (lambda () (interactive) (find-alternate-file ".."))
  (kbd "C-<return>") 'nd/dired-xdg-open
  (kbd "M-<return>") 'nd/dired-open-with
  "q" 'nd/kill-current-buffer)
helm

I like tab completion…regardless of what the helm zealots say. This is actually easier and faster because I can just scroll through the source list with j/k and mash TAB when I find the right directory.

(evil-define-key '(normal insert) helm-map
  (kbd "<tab>") 'helm-execute-persistent-action
  (kbd "C-<tab>") 'helm-select-action)
term

Since I use vi mode in my terminal emulator, need to preserve the escape key's raw behavior

(evil-define-key 'insert term-raw-map
  (kbd "<escape>") (lambda () (interactive) (term-send-raw-string "\e"))
  (kbd "C-<escape>") 'evil-normal-state)

local

These are for mode-specific bindings that can/should be outside of the evil maps above (there are not many, and these may be merged with their evil bretheren in the future)

org-mode

(add-hook 'org-mode-hook
          (lambda ()
            (local-set-key (kbd "C-c C-x x") 'nd/mark-subtree-done)
            (local-set-key (kbd "C-c C-x c") 'nd/org-clone-subtree-with-time-shift)))
            
(add-hook 'org-agenda-mode-hook
          (lambda ()
            (local-set-key (kbd "C-c C-c") 'org-agenda-set-tags)))

dired

(define-key dired-mode-map (kbd "C-x g") 'magit)

haskell-mode

(eval-after-load 'haskell-mode
  '(define-key haskell-mode-map (kbd "C-c C-c") 'haskell-compile))
				 
(eval-after-load 'haskell-cabal
  '(define-key haskell-cabal-mode-map (kbd "C-c C-c") 'haskell-compile))

helm-prefix

(define-key helm-command-prefix (kbd "b") 'helm-bibtex)
(define-key helm-command-prefix (kbd "<f8>") 'helm-resume)

outline-magic

(define-key outline-minor-mode-map (kbd "<tab>") 'outline-cycle)

global

(global-set-key (kbd "<f1>") 'org-agenda)
(global-set-key (kbd "<f2>") 'org-capture)
(global-set-key (kbd "<f3>") 'cfw:open-org-calendar)
(global-set-key (kbd "<f4>") 'org-clock-goto)
(global-set-key (kbd "<f5>") 'ansi-term)
(global-set-key (kbd "<f8>") 'helm-command-prefix)
(global-set-key (kbd "C-<f5>") 'nd/open-urxvt)
(global-set-key (kbd "<f12>") 'mu4e)
(global-set-key (kbd "C-<f12>") 'global-hl-line-mode)
(global-set-key (kbd "S-<f12>") 'display-line-numbers-mode)

(global-set-key (kbd "C-c e") 'nd/config-visit)
(global-set-key (kbd "C-c r") 'nd/config-reload)
(global-set-key (kbd "C-c s") 'sudo-edit)

(global-set-key (kbd "C-x 2") 'nd/split-and-follow-horizontally)
(global-set-key (kbd "C-x 3") 'nd/split-and-follow-vertically)
(global-unset-key (kbd "C-x c"))
(global-set-key (kbd "C-x k") 'nd/kill-current-buffer)
(global-set-key (kbd "C-x C-d") 'helm-bookmarks)
(global-set-key (kbd "C-x C-f") 'helm-find-files)
(global-set-key (kbd "C-x C-b") 'helm-buffers-list)

(global-set-key (kbd "C-M-S-k") 'nd/close-all-buffers)
(global-set-key (kbd "C-M-S-o") 'nd/org-close-all-buffers)
(global-set-key (kbd "C-M-S-a") 'org-agenda-kill-all-agenda-buffers)

(global-set-key (kbd "M-b") 'nd/switch-to-previous-buffer)
(global-set-key (kbd "M-o") 'ace-window)
(global-set-key (kbd "M-s") 'avy-goto-char)
(global-set-key (kbd "M-x") 'helm-M-x)