emacs-config/conf.org

2515 lines
85 KiB
Org Mode
Raw Normal View History

2018-08-31 10:23:54 -04:00
* 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/[[https://en.wikipedia.org/wiki/Getting_Things_Done][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)
2018-03-21 21:44:31 -04:00
* ui
2018-07-15 20:51:57 -04:00
** theme
#+BEGIN_SRC emacs-lisp
(use-package spacemacs-theme
:defer t
2018-07-20 00:50:51 -04:00
:config
(setq spacemacs-theme-custom-colors '((lnum . "#64707c"))))
2018-07-15 20:51:57 -04:00
(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))))
2018-03-21 21:44:31 -04:00
#+END_SRC
2018-07-20 02:23:08 -04:00
** modeline
#+BEGIN_SRC emacs-lisp
(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)
#+END_SRC
*** delight
Used to hide minor modes on the modeline
2018-03-21 21:44:31 -04:00
#+BEGIN_SRC emacs-lisp
2018-07-15 00:04:47 -04:00
(use-package delight
:ensure t)
2018-03-21 21:44:31 -04:00
#+END_SRC
2018-07-20 02:23:08 -04:00
** clean the interface
No need for startup screen, tool/menu/scrollbars, or backups
#+BEGIN_SRC emacs-lisp
(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)
#+END_SRC
** other enhancements
*** popup windows
#+BEGIN_SRC emacs-lisp
(setq pop-up-windows nil) ; no popups (eg ediff)
#+END_SRC
*** line wrap
#+BEGIN_SRC emacs-lisp
(set-default 'truncate-lines t)
#+END_SRC
*** smooth scrolling
#+BEGIN_SRC emacs-lisp
(setq scroll-conservatively 100)
#+END_SRC
*** imagemagick
#+BEGIN_SRC emacs-lisp
(when (fboundp 'imagemagick-register-types)
(imagemagick-register-types))
#+END_SRC
*** yes-no prompt
#+BEGIN_SRC emacs-lisp
(defalias 'yes-or-no-p 'y-or-n-p) ; eliminate yes or no prompt on killing procs
#+END_SRC
* packages
2018-03-21 21:44:31 -04:00
** beacon
#+BEGIN_SRC emacs-lisp
2018-07-15 00:04:47 -04:00
(use-package beacon
:ensure t
:delight
:init
(beacon-mode 1))
2018-03-21 21:44:31 -04:00
#+END_SRC
** whichkey
#+BEGIN_SRC emacs-lisp
2018-07-15 00:04:47 -04:00
(use-package which-key
:ensure t
:delight
:init
(which-key-mode))
2018-03-21 21:44:31 -04:00
#+END_SRC
2018-07-13 20:05:50 -04:00
** helm
#+BEGIN_SRC emacs-lisp
(use-package helm
:ensure t
2018-07-14 20:42:20 -04:00
:delight
2018-07-13 20:05:50 -04:00
:init
(helm-mode 1)
:config
2018-07-20 00:50:51 -04:00
(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)
2018-07-14 23:36:42 -04:00
(add-to-list 'display-buffer-alist
`(,(rx bos "*helm" (* not-newline) "*" eos)
(display-buffer-in-side-window)
(inhibit-same-window . t)
2018-07-15 00:04:47 -04:00
(window-height . 0.4)))
(helm-autoresize-mode 1)
(require 'helm-config))
2018-03-21 21:44:31 -04:00
#+END_SRC
2018-09-21 01:03:17 -04:00
** helm-swoop
#+BEGIN_SRC emacs-lisp
(use-package helm-swoop
:ensure t)
#+END_SRC
2018-03-21 21:44:31 -04:00
** rainbow-delimiters
#+BEGIN_SRC emacs-lisp
2018-07-15 00:04:47 -04:00
(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)))
2018-03-21 21:44:31 -04:00
#+END_SRC
** ace-window
#+BEGIN_SRC emacs-lisp
2018-07-03 22:27:12 -04:00
(use-package ace-window
:ensure t
2018-07-20 00:50:51 -04:00
:config
(setq aw-background t)
(custom-set-faces '(aw-leading-char-face
((t (:foreground "#292b2e"
:background "#bc6ec5"
:height 1.0
:box nil))))))
2018-03-21 21:44:31 -04:00
#+END_SRC
** avy
#+BEGIN_SRC emacs-lisp
(use-package avy
:ensure t
2018-07-20 00:50:51 -04:00
:config
(setq avy-background t))
2018-03-21 21:44:31 -04:00
#+END_SRC
** sudo edit
#+BEGIN_SRC emacs-lisp
(use-package sudo-edit
2018-07-14 23:36:42 -04:00
:ensure t)
2018-03-21 21:44:31 -04:00
#+END_SRC
** undo tree
#+BEGIN_SRC emacs-lisp
2018-07-15 00:04:47 -04:00
(use-package undo-tree
:ensure t
:delight
:config
2018-07-20 00:50:51 -04:00
(setq undo-tree-visualizer-diff t)
2018-07-15 00:04:47 -04:00
(global-undo-tree-mode))
2018-03-21 21:44:31 -04:00
#+END_SRC
2018-06-09 18:31:40 -04:00
** fill-column-indicator
#+BEGIN_SRC emacs-lisp
(use-package fill-column-indicator
:ensure t
2018-07-20 00:50:51 -04:00
:config
(setq fci-rule-use-dashes t)
2018-07-15 00:04:47 -04:00
:hook
(prog-mode . fci-mode))
2018-06-09 18:31:40 -04:00
#+END_SRC
** rainbow
#+BEGIN_SRC emacs-lisp
(use-package rainbow-mode
:ensure t)
#+END_SRC
** async
#+BEGIN_SRC emacs-lisp
(use-package async
:ensure t
:delight dired-async-mode
:init
(dired-async-mode 1))
#+END_SRC
** csv-mode
#+BEGIN_SRC emacs-lisp
(use-package csv-mode
:ensure t
:hook (csv-mode . (lambda () (csv-align-fields nil (point-min) (point-max)))))
#+END_SRC
2018-08-10 10:15:09 -04:00
** markdown-mode
#+BEGIN_SRC emacs-lisp
(use-package markdown-mode
:ensure t)
#+END_SRC
** polymode
#+BEGIN_SRC emacs-lisp
(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))
#+END_SRC
2018-05-19 14:45:43 -04:00
* library
A place for duct tape code that I developed (or lovingly stole from others)
** macros
#+BEGIN_SRC emacs-lisp
;; 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))))
#+END_SRC
2018-05-19 15:09:15 -04:00
** functions
#+BEGIN_SRC emacs-lisp
(defun nd/filter-list-prefix (prefix str-list)
2018-09-16 12:05:46 -04:00
"Return a subset of STR-LIST whose first characters are PREFIX."
2018-05-19 15:09:15 -04:00
(seq-filter (lambda (i)
(and (stringp i)
(string-prefix-p prefix i)))
str-list))
2018-09-16 13:19:57 -04:00
(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))
2018-08-22 01:31:00 -04:00
(defun nd/get-apps-from-mime (mimetype)
2018-08-22 23:23:09 -04:00
"Return all applications that can open a given MIMETYPE.
The list is comprised of alists where pairs are of the form (name . command)."
2018-08-22 01:53:36 -04:00
(let* ((case-fold-search nil)
(mime-regex (concat "^MimeType=.*" mimetype ";.*$"))
2018-08-22 01:31:00 -04:00
(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)))
2018-08-22 23:23:09 -04:00
(setq app-list (cons `(,name . ,exec) app-list)))))))))
2018-08-22 01:53:36 -04:00
(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)))
2018-05-19 15:09:15 -04:00
#+END_SRC
2018-07-14 20:42:20 -04:00
** interactive
2018-07-14 20:11:10 -04:00
#+BEGIN_SRC emacs-lisp
2018-07-14 20:42:20 -04:00
(defun nd/split-and-follow-horizontally ()
2018-07-18 10:35:37 -04:00
"Split window horizontally and move focus."
2018-07-14 20:42:20 -04:00
(interactive)
(split-window-below)
(balance-windows)
(other-window 1))
(defun nd/split-and-follow-vertically ()
2018-07-18 10:35:37 -04:00
"Split window vertically and move focus."
2018-07-14 20:42:20 -04:00
(interactive)
(split-window-right)
(balance-windows)
(other-window 1))
2018-07-14 20:11:10 -04:00
(defun nd/switch-to-previous-buffer ()
2018-07-18 10:35:37 -04:00
"Switch the buffer to the last opened buffer."
2018-07-14 20:11:10 -04:00
(interactive)
(switch-to-buffer (other-buffer (current-buffer) 1)))
2018-07-14 20:42:20 -04:00
(defun nd/config-reload ()
2018-07-18 10:35:37 -04:00
"Reloads ~/.emacs.d/conf.org at runtime."
2018-07-14 20:42:20 -04:00
(interactive)
(org-babel-load-file (expand-file-name "~/.emacs.d/conf.org")))
(defun nd/config-visit ()
2018-07-18 10:35:37 -04:00
"Opens the main conf.org file (the one that really matters)."
2018-07-14 20:42:20 -04:00
(interactive)
(find-file "~/.emacs.d/conf.org"))
2018-07-18 10:35:37 -04:00
(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)))
2018-09-16 13:19:57 -04:00
(defun nd/org-close-all-buffers ()
"Kill all org buffers."
(interactive)
(mapc 'kill-buffer (org-buffer-list)))
2018-08-22 00:20:31 -04:00
(defun nd/open-urxvt ()
"Launch urxvt in the current directory."
(interactive)
2018-08-27 17:09:33 -04:00
(let ((cwd (expand-file-name default-directory)))
(call-process "urxvt" nil 0 nil "-cd" cwd)))
2018-07-14 20:11:10 -04:00
#+END_SRC
2018-09-12 16:54:55 -04:00
* editing
** tabs and alignment
First things first, don't use spaces and force tabs to 4 chars by default (because...let's compromise on things that don't matter since I am using spaces anyways)
#+BEGIN_SRC emacs-lisp
(setq-default indent-tabs-mode nil
tab-width 4)
#+END_SRC
2018-09-12 16:54:55 -04:00
** completion
*** company
2018-07-17 23:27:53 -04:00
#+BEGIN_SRC emacs-lisp
(use-package company
:ensure t
2018-07-22 07:37:44 -04:00
:delight " ©"
2018-07-17 23:27:53 -04:00
:config
(setq company-idle-delay 0
company-minimum-prefix-length 3))
#+END_SRC
2018-09-12 16:54:55 -04:00
*** flycheck
2018-07-17 23:27:53 -04:00
#+BEGIN_SRC emacs-lisp
(use-package flycheck
:ensure t
:hook
2018-07-18 13:45:26 -04:00
(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))
2018-07-17 23:27:53 -04:00
#+END_SRC
2018-09-12 16:54:55 -04:00
*** yasnippet
2018-07-17 23:27:53 -04:00
#+BEGIN_SRC emacs-lisp
(use-package yasnippet
:ensure t)
(use-package yasnippet-snippets
:ensure t
:after yasnippet
:hook
((prog-mode . yas-minor-mode))
:config
(yas-reload-all))
#+END_SRC
2018-10-13 11:43:10 -04:00
*** electric pairs
Complete pairs globally. Maybe will add more mode-specific options in addition to defaults (eg =html-mode=)
#+BEGIN_SRC emacs-lisp
(electric-pair-mode t)
#+END_SRC
** flyspell
2018-10-11 17:08:13 -04:00
Obviously I am going to use =helm= when I spellcheck something.
#+BEGIN_SRC emacs-lisp
(use-package flyspell-correct-helm
:ensure t
:after (helm flyspell))
#+END_SRC
2018-10-11 17:08:13 -04:00
Additionally, I want to automatically highlight errors whenever =flyspell-mode= is enabled.
#+BEGIN_SRC emacs-lisp
(add-hook 'flyspell-mode-hook 'flyspell-buffer)
#+END_SRC
** progmode
#+BEGIN_SRC emacs-lisp
(add-hook 'prog-mode-hook #'prettify-symbols-mode)
2018-08-18 18:07:39 -04:00
(add-hook 'prog-mode-hook #'flyspell-prog-mode)
(setq flyspell-issue-message-flag nil)
#+END_SRC
2018-09-12 16:54:55 -04:00
** languages
*** elisp
#+BEGIN_SRC emacs-lisp
(add-hook 'emacs-lisp-mode-hook 'company-mode)
#+END_SRC
2018-09-12 16:54:55 -04:00
*** ess
2018-07-18 13:45:26 -04:00
NOTES:
- ess is not considered part of prog-mode for some reason
2018-07-18 15:39:30 -04:00
- ess-mode requires a running R process for company to work
2018-07-18 13:45:26 -04:00
- flycheck requries r-lintr
2018-03-21 21:44:31 -04:00
#+begin_src emacs-lisp
(defun nd/init-ess-company ()
2018-10-10 19:23:21 -04:00
"Set the company backends for ess modes."
(setq-local company-backends '((company-R-objects company-R-args))))
(use-package ess
:ensure t
:init
(load "ess-site")
:hook
2018-07-18 12:06:51 -04:00
((ess-mode . flycheck-mode)
(ess-mode . company-mode)
(ess-mode . nd/init-ess-company)
(ess-mode . prettify-symbols-mode)
2018-07-18 15:39:30 -04:00
(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"
2018-07-21 21:53:56 -04:00
ess-history-directory (substitute-in-file-name "${XDG_CONFIG_HOME}/r/")))
2018-03-21 21:44:31 -04:00
#+END_SRC
2018-09-12 16:54:55 -04:00
*** python
2018-08-31 10:23:54 -04:00
#+BEGIN_SRC emacs-lisp
2018-03-21 21:44:31 -04:00
(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)))
2018-10-21 01:18:11 -04:00
(setq python-shell-interpreter "ipython"
python-shell-interpreter-args "--colors=Linux --profile=default")
2018-03-21 21:44:31 -04:00
#+END_SRC
2018-09-12 16:54:55 -04:00
*** haskell
#+BEGIN_SRC emacs-lisp
(use-package haskell-mode
:ensure t
2018-07-22 07:37:44 -04:00
:hook
(haskell-mode . company-mode)
2018-07-20 00:50:51 -04:00
:config
(setq haskell-compile-command "ghc -dynamic -Wall -ferror-spans -threaded -fforce-recomp %s"
2018-07-20 00:50:51 -04:00
haskell-interactive-popup-errors nil))
2018-07-22 07:37:44 -04:00
(use-package company-ghc
:ensure t
:after
2018-10-10 19:23:21 -04:00
(company haskell-mode)
2018-07-22 07:37:44 -04:00
:hook
(haskell-mode . (lambda () (setq-local company-backends
'((company-ghc))))))
#+END_SRC
2018-10-10 19:23:21 -04:00
*** latex
2018-10-11 14:19:49 -04:00
**** flycheck
Flycheck should work out of the box.
2018-10-10 19:23:21 -04:00
#+BEGIN_SRC emacs-lisp
(add-hook 'LaTeX-mode-hook #'flycheck-mode)
(add-hook 'Tex-latex-mode-hook #'flycheck-mode)
2018-10-11 14:19:49 -04:00
#+END_SRC
**** company
There are two backends which (kinda) complement each other. The =company-math= package should privide completion for math symbols and the =company-auctex= package should cover pretty much everything else.
#+BEGIN_SRC emacs-lisp
2018-10-10 19:23:21 -04:00
(defun nd/init-company-auctex ()
"Set the company backends for auctex modes."
(setq-local company-backends '((company-auctex-labels
company-auctex-bibs
company-auctex-macros
company-auctex-symbols
company-auctex-environments
;; company-latex-commands
company-math-symbols-latex
company-math-symbols-unicode))))
(use-package company-math
:ensure t
:after company
:config
(setq company-math-allow-unicode-symbols-in-faces '(font-latex-math-face)
company-math-disallow-latex-symbols-in-faces nil))
(use-package company-auctex
:ensure t
:after (company company-math)
:hook
((LaTeX-mode . company-mode)
(LaTeX-mode . nd/init-company-auctex)
(Tex-latex-mode . company-mode)
(Tex-latex-mode . nd/init-company-auctex)))
#+END_SRC
2018-10-11 14:19:49 -04:00
**** auto-fill-mode
I like having my lines short and readable (also easier to git). Turn on autofill here and also make a nice vertical line at 80 chars (=visual-line-mode=).
#+BEGIN_SRC emacs-lisp
(defun nd/turn-on-auto-fill-maybe ()
"Prompts user to turn on `auto-fill-mode'."
(when (y-or-n-p "Activate Auto Fill Mode? ")
(turn-on-auto-fill)))
(add-hook 'LaTeX-mode-hook #'nd/turn-on-auto-fill-maybe)
(add-hook 'LaTeX-mode-hook #'fci-mode)
#+END_SRC
2018-10-11 17:08:13 -04:00
**** flyspell
Spell checking is important for prose
#+BEGIN_SRC emacs-lisp
(add-hook 'LaTeX-mode-hook (lambda () (flyspell-mode 1)))
#+END_SRC
2018-03-21 21:44:31 -04:00
* org-mode
2018-07-21 02:24:45 -04:00
** 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
2018-03-21 21:44:31 -04:00
#+BEGIN_SRC emacs-lisp
2018-07-20 02:30:03 -04:00
(use-package org
:delight
2018-07-21 02:24:45 -04:00
;; source of indent-mode required here
(org-indent-mode nil org-indent)
(visual-line-mode)
:hook
(org-mode . visual-line-mode)
2018-07-20 02:30:03 -04:00
:config
(setq org-startup-indented t
org-directory "~/Org"
2018-07-21 02:24:45 -04:00
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)
2018-07-20 02:30:03 -04:00
(require 'org-protocol)
(run-at-time "00:59" 3600 'org-save-all-org-buffers))
2018-03-21 21:44:31 -04:00
#+END_SRC
*** bullets
2018-07-21 02:24:45 -04:00
These are just so much better to read
#+BEGIN_SRC emacs-lisp
(use-package org-bullets
:ensure t
2018-07-15 00:04:47 -04:00
:hook
(org-mode . org-bullets-mode))
#+END_SRC
*** font height
2018-05-19 14:45:43 -04:00
The fonts in org headings bug me, make them smaller and less invasive
#+BEGIN_SRC emacs-lisp
2018-07-21 02:24:45 -04:00
(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))))
#+END_SRC
*** src blocks
#+BEGIN_SRC emacs-lisp
(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"))
#+END_SRC
*** interactive commands
Some useful additional commands for org buffers
#+BEGIN_SRC emacs-lisp
(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)))))
#+END_SRC
** column view
#+BEGIN_SRC emacs-lisp
(setq org-columns-default-format
2018-09-29 16:40:07 -04:00
"%25ITEM %4TODO %TAGS %5Effort{:} %DELEGATE(DEL)")
2018-07-21 02:24:45 -04:00
(set-face-attribute 'org-column nil :background "#1e2023")
;; org-columns-summary-types
#+END_SRC
** calfw
#+BEGIN_SRC emacs-lisp
(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 ?┓))
2018-07-21 02:44:10 -04:00
(use-package calfw-org
:ensure t
:after calfw
:config
(setq cfw:org-agenda-schedule-args
'(:deadline :timestamp)))
2018-07-21 02:24:45 -04:00
#+END_SRC
** 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
2018-05-19 14:45:43 -04:00
I only need a teeny tiny window below my current window for todo selection
#+BEGIN_SRC emacs-lisp
2018-05-19 15:09:15 -04:00
(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))))
2018-05-19 14:45:43 -04:00
2018-05-19 15:09:15 -04:00
(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))))))
2018-05-19 14:45:43 -04:00
2018-05-19 15:09:15 -04:00
(advice-add #'org-fast-todo-selection :around #'nd/org-todo-window-advice)
2018-05-19 14:45:43 -04:00
#+END_SRC
2018-07-21 02:24:45 -04:00
*** tag selection
2018-05-19 14:45:43 -04:00
By default, the tag selection window obliterates all but the current window...how disorienting :/
#+BEGIN_SRC emacs-lisp
2018-05-19 15:09:15 -04:00
(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))))
2018-05-19 14:45:43 -04:00
2018-05-19 15:09:15 -04:00
(advice-add #'org-fast-tag-selection :around #'nd/org-tag-window-advice)
2018-03-21 21:44:31 -04:00
#+END_SRC
2018-07-21 02:24:45 -04:00
*** capture
2018-05-19 17:39:36 -04:00
Capture should show up in the bottom of any currently active buffer
#+BEGIN_SRC emacs-lisp
(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)
#+END_SRC
** latex
The default is XHTML for some reason (which few use and makes certain barbaric word processors complain). Use the much-superior html5.
#+BEGIN_SRC emacs-lisp
(setq org-html-doctype "html5")
#+END_SRC
Need to export the bibliography when using org mode. Use =latexmk= instead of =pdflatex= because it is better at handling this.
#+BEGIN_SRC emacs-lisp
(setq org-latex-pdf-process (list "latexmk -shell-escape -bibtex -f -pdf %f"))
#+END_SRC
2018-07-21 02:24:45 -04:00
** gtd implementation
*** todo states
2018-03-21 21:44:31 -04:00
#+BEGIN_SRC emacs-lisp
2018-05-19 15:09:15 -04:00
(setq org-todo-keywords
'((sequence "TODO(t)" "NEXT(n)" "|" "DONE(d)")
2018-07-21 01:07:42 -04:00
(sequence "WAIT(w@/!)" "HOLD(h@/!)" "|" "CANC(c@/!)"))
org-todo-keyword-faces
2018-05-19 15:09:15 -04:00
'(("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)))
2018-03-21 21:44:31 -04:00
#+END_SRC
2018-07-21 02:24:45 -04:00
*** tags
2018-07-21 01:24:09 -04:00
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:
2018-07-21 02:24:45 -04:00
- location: a GTD context; these start with "@"
- tools: also a GTD context; these start with "#"
2018-05-15 22:55:11 -04:00
- attribute: useful flags for filtering; these start with "%"
- life areas: key areas of life which define priorities and goals; these start with "_"
2018-07-21 01:24:09 -04:00
NOTE: only these special chars; others make the tag chooser do weird things with the hotkey
2018-04-24 23:37:37 -04:00
#+BEGIN_SRC emacs-lisp
2018-05-19 15:09:15 -04:00
(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)))
2018-05-19 15:09:15 -04:00
(setq org-tag-alist
'((:startgroup)
("@errand" . ?e)
("@home" . ?h)
("@work" . ?w)
("@travel" . ?r)
2018-05-19 15:09:15 -04:00
(:endgroup)
("#laptop" . ?l)
("#tcult" . ?t)
("#phone" . ?p)
2018-05-19 15:09:15 -04:00
("%note" . ?n)
("%inc" . ?i)
("%subdiv" . ?s)
("%flag" . ?f)
2018-05-19 15:09:15 -04:00
(:startgroup)
("_env" . ?E)
("_fin" . ?F)
("_int" . ?I)
("_met" . ?M)
("_phy" . ?H)
("_pro" . ?P)
("_rec" . ?R)
("_soc" . ?S)
(:endgroup)))
2018-05-15 22:55:11 -04:00
2018-05-19 15:09:15 -04:00
(setq org-tag-faces '())
2018-05-15 22:55:11 -04:00
2018-05-19 15:09:15 -04:00
(nd/add-tag-face "PaleGreen" "@")
(nd/add-tag-face "SkyBlue" "#")
(nd/add-tag-face "PaleGoldenrod" "%")
(nd/add-tag-face "violet" "_")
2018-04-24 23:37:37 -04:00
#+END_SRC
2018-07-21 02:24:45 -04:00
*** 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
2018-04-24 23:37:37 -04:00
#+BEGIN_SRC emacs-lisp
2018-07-21 02:24:45 -04:00
(mapc (lambda (i) (add-to-list 'org-default-properties i))
2018-09-29 16:40:07 -04:00
'("PARENT_TYPE" "DELEGATE" "GOAL" "TIME_SHIFT"))
2018-05-19 15:36:04 -04:00
(setq org-global-properties
'(("PARENT_TYPE_ALL" . "periodical iterator")
2018-07-21 02:24:45 -04:00
("Effort_ALL" . "0:05 0:15 0:30 1:00 1:30 2:00 3:00 4:00 5:00 6:00"))
2018-05-19 15:36:04 -04:00
2018-07-21 02:24:45 -04:00
org-use-property-inheritance
'("PARENT_TYPE" "TIME_SHIFT"))
2018-03-21 21:44:31 -04:00
#+END_SRC
2018-07-21 02:24:45 -04:00
*** capture
2018-03-21 21:44:31 -04:00
#+BEGIN_SRC emacs-lisp
(defun nd/org-timestamp-future (days)
"Inserts an active org timestamp DAYS after the current time."
(format-time-string (org-time-stamp-format nil)
(time-add (current-time) (days-to-time 1))))
2018-05-19 15:03:12 -04:00
(let ((capfile "~/Org/capture.org"))
(setq org-capture-templates
2018-07-03 23:30:02 -04:00
`(("t" "todo" entry (file ,capfile)
"* TODO %?\ndeliverable: \n%U\n")
("n" "note" entry (file ,capfile)
"* %? :\\%note:\n%U\n")
2018-07-03 23:30:02 -04:00
("a" "appointment" entry (file ,capfile)
"* %?\n%U\n%^t\n")
("s" "appointment-span" entry (file ,capfile)
2018-07-03 23:30:02 -04:00
"* TODO %?\n%U\n%^t--%^t\n")
("d" "deadline" entry (file ,capfile)
"* TODO %?\nDEADLINE: %^t\ndeliverable:\n%U\n")
2018-09-09 20:50:06 -04:00
("e" "email" entry (file ,capfile)
"* TODO Respond to %:fromname; Re: %:subject\nDEADLINE: %(nd/org-timestamp-future 1)\n%U\n%a\n")
("m" "meeting" entry (file ,capfile)
"* meeting with%? :\\%note:\n%U\n")
2018-09-09 20:50:06 -04:00
2018-07-03 23:30:02 -04:00
("p" "org-protocol" entry (file ,capfile)
2018-07-13 20:05:50 -04:00
"* %^{Title} :\\%note:\n%u\n#+BEGIN_QUOTE\n%i\n#+END_QUOTE"
2018-07-03 23:30:02 -04:00
:immediate-finish t)
("L" "org-protocol link" entry (file ,capfile)
2018-07-13 20:05:50 -04:00
"* %^{Title} :\\%note:\n[[%:link][%:description]]\n%U"
2018-07-03 23:30:02 -04:00
:immediate-finish t))))
2018-03-21 21:44:31 -04:00
#+END_SRC
2018-07-21 02:24:45 -04:00
*** refile
**** general
2018-03-21 21:44:31 -04:00
#+BEGIN_SRC emacs-lisp
2018-07-21 02:24:45 -04:00
(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)
2018-07-22 00:27:40 -04:00
(add-hook 'org-capture-mode-hook (lambda () (evil-append 1)))
2018-03-21 21:44:31 -04:00
#+END_SRC
2018-07-21 02:24:45 -04:00
**** exclude done states
No need to file under DONE or CANC states
2018-03-21 21:44:31 -04:00
#+BEGIN_SRC emacs-lisp
2018-07-21 02:24:45 -04:00
(setq org-refile-target-verify-function
(lambda () (not (member (nth 2 (org-heading-components)) org-done-keywords))))
2018-03-21 21:44:31 -04:00
#+END_SRC
2018-07-21 02:24:45 -04:00
*** clocking
#+BEGIN_SRC emacs-lisp
(setq org-clock-history-length 23
org-clock-out-when-done t
org-clock-persist t
org-clock-report-include-clocking-task t)
2018-07-01 21:16:50 -04:00
#+END_SRC
2018-07-21 02:24:45 -04:00
*** agenda
2018-09-29 16:27:13 -04:00
**** filters
***** custom filtering functions
Some custom filters that are applied to the agenda view. Note that some of these use alternative filter types that are implemented via advising functions (see below).
#+BEGIN_SRC emacs-lisp
(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 that don't have peripheral 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)))
(defun nd/org-agenda-filter-non-effort ()
"Filter agenda by non-effort tasks."
(interactive)
(setq org-agenda-hasprop-filter '("-Effort"))
(org-agenda-filter-apply org-agenda-hasprop-filter 'hasprop))
2018-09-29 16:40:07 -04:00
(defun nd/org-agenda-filter-delegate ()
"Filter agenda by tasks with an external delegate."
(interactive)
(setq org-agenda-hasprop-filter '("+DELEGATE"))
(org-agenda-filter-apply org-agenda-hasprop-filter 'hasprop))
2018-09-29 16:27:13 -04:00
#+END_SRC
***** filter advice
In order to implement the =hasprop= filter the functions, =org-agenda-filter-make-matcher= and =org-agenda-filter-remove-all= need to be advised in order to add the functionality for the =hasprop= filter type.
As it is, this allows any filter using =hasprop= to be applied and removed using the standard =org-agenda-filter-apply= function with the =org-agenda-hasprop-filter= variable (obviously these can all be extended to different filter types). Note this does not give a shiny indicator at the bottom on spaceline like the built-in filter does...oh well.
#+BEGIN_SRC emacs-lisp
;; initialize new filters
(defvar org-agenda-hasprop-filter nil)
2018-09-29 16:27:13 -04:00
(defun nd/org-agenda-filter-make-matcher-prop
(filter type &rest args)
"Return matching matcher form for FILTER and TYPE where TYPE is not
in the regular `org-agenda-filter-make-matcher' function. This is
intended to be uses as :before-until advice and will return nil if
the type is not valid (which is currently 'prop')"
(let (f f1)
;; has property
(cond
((eq type 'hasprop)
(dolist (x filter)
(push (nd/org-agenda-filter-make-matcher-hasprop-exp x) f))))
(if f (cons 'and (nreverse f)))))
(defun nd/org-agenda-filter-make-matcher-hasprop-exp (h)
"Returns form to test the presence or absence of properties H.
H is a string like +prop or -prop"
(let (op)
(let* ((op (string-to-char h))
(h (substring h 1))
(f `(save-excursion
(let ((m (org-get-at-bol 'org-hd-marker)))
(with-current-buffer
(marker-buffer m)
(goto-char m)
(org-entry-get nil ,h))))))
(if (eq op ?-) (list 'not f) f))))
(defun nd/org-agenda-filter-show-all-hasprop nil
(org-agenda-remove-filter 'hasprop))
(advice-add #'org-agenda-filter-make-matcher :before-until
#'nd/org-agenda-filter-make-matcher-prop)
(advice-add #'org-agenda-filter-remove-all :before
(lambda () (when org-agenda-hasprop-filter
(nd/org-agenda-filter-show-all-hasprop))))
#+END_SRC
2018-07-21 02:24:45 -04:00
**** general config
2018-03-21 21:44:31 -04:00
#+BEGIN_SRC emacs-lisp
2018-07-21 02:24:45 -04:00
(setq org-agenda-files '("~/Org"
"~/Org/projects"
2018-07-21 02:24:45 -04:00
"~/Org/reference")
2018-08-20 22:12:37 -04:00
org-agenda-sticky t
2018-07-21 02:24:45 -04:00
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)
"......" "-----------------"))
2018-03-21 21:44:31 -04:00
#+END_SRC
2018-07-21 02:24:45 -04:00
**** right align tags
the agenda does not do this by default...it's annoying
2018-06-21 22:35:03 -04:00
#+BEGIN_SRC emacs-lisp
2018-07-21 02:24:45 -04:00
(add-hook 'org-finalize-agenda-hook
(lambda () (setq org-agenda-tags-column (- 4 (window-width)))
(org-agenda-align-tags)))
#+END_SRC
**** holidays and birthdays
#+BEGIN_SRC emacs-lisp
(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))
2018-06-21 22:35:03 -04:00
#+END_SRC
2018-07-21 02:24:45 -04:00
**** 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
2018-04-02 00:40:42 -04:00
These are the building blocks for skip functions.
2018-07-21 02:24:45 -04:00
****** timestamps
Each of these returns the timestamp if found.
2018-03-21 21:44:31 -04:00
#+BEGIN_SRC emacs-lisp
2018-06-21 22:35:03 -04:00
(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))))
#+END_SRC
2018-07-21 02:24:45 -04:00
****** task level testing
Each of these returns the keyword if true
Doubles as a way to further test the todostate in downstream functions
#+BEGIN_SRC emacs-lisp
(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 ()
2018-05-04 22:13:10 -04:00
(and (nd/heading-has-children 'nd/is-todoitem-p) (nd/is-todoitem-p)))
(defun nd/is-task-p ()
2018-05-04 22:13:10 -04:00
(and (not (nd/heading-has-children 'nd/is-todoitem-p)) (nd/is-todoitem-p)))
2018-05-13 20:15:00 -04:00
(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 ()
2018-05-04 22:13:10 -04:00
(and (not (nd/heading-has-parent 'nd/is-todoitem-p)) (nd/is-task-p)))
#+END_SRC
2018-07-21 02:24:45 -04:00
****** property testing
Returns t is heading matches a certian set of properties
#+BEGIN_SRC emacs-lisp
(defun nd/is-periodical-heading-p ()
(equal "periodical" (org-entry-get nil "PARENT_TYPE" t)))
2018-03-22 00:32:17 -04:00
(defun nd/is-iterator-heading-p ()
(equal "iterator" (org-entry-get nil "PARENT_TYPE" t)))
2018-05-13 20:15:00 -04:00
(defun nd/heading-has-effort-p ()
(org-entry-get nil "Effort"))
(defun nd/heading-has-context-p ()
(let ((tags (org-get-tags-at)))
2018-05-15 22:55:11 -04:00
(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)))
#+END_SRC
2018-07-21 02:24:45 -04:00
****** relational testing
Returns t if heading has certain relationship to other headings
#+BEGIN_SRC emacs-lisp
2018-05-04 22:13:10 -04:00
(defun nd/heading-has-children (heading-test)
"returns t if heading has subheadings that return t when assessed with
heading-test function"
2018-05-04 22:18:04 -04:00
(let ((subtree-end (save-excursion (org-end-of-subtree t)))
has-children previous-point)
2018-04-02 00:40:42 -04:00
(save-excursion
2018-05-04 22:13:10 -04:00
(setq previous-point (point))
2018-04-02 00:40:42 -04:00
(outline-next-heading)
(while (and (not has-children)
2018-05-04 22:13:10 -04:00
(< previous-point (point) subtree-end))
(when (funcall heading-test)
2018-04-02 00:40:42 -04:00
(setq has-children t))
2018-05-04 22:13:10 -04:00
(setq previous-point (point))
(org-forward-heading-same-level 1 t)))
2018-04-02 00:40:42 -04:00
has-children))
2018-05-04 22:13:10 -04:00
(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))))
2018-04-02 00:40:42 -04:00
2018-04-27 23:03:56 -04:00
(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"
2018-04-08 21:54:20 -04:00
(let ((has-todoitem-parent)
(has-non-todoitem-parent))
(save-excursion
(while (and (org-up-heading-safe)
2018-04-13 01:46:47 -04:00
(not has-todoitem-parent))
2018-04-08 21:54:20 -04:00
(if (nd/is-todoitem-p)
(setq has-todoitem-parent t)
(setq has-non-todoitem-parent t))))
(and has-todoitem-parent has-non-todoitem-parent)))
#+END_SRC
2018-07-21 02:24:45 -04:00
****** project level testing
#+BEGIN_SRC emacs-lisp
2018-06-03 21:09:16 -04:00
(defconst nd/project-invalid-todostates
'("WAIT" "NEXT")
"projects cannot have these todostates")
2018-06-26 20:23:45 -04:00
(defmacro nd/compare-statuscodes (op sc1 sc2 sc-list)
2018-06-18 22:19:07 -04:00
`(,op (position ,sc1 ,sc-list) (position ,sc2 ,sc-list)))
2018-06-26 20:23:45 -04:00
(defun nd/decend-into-project (allowed-statuscodes trans-tbl get-task-status)
2018-06-18 22:19:07 -04:00
(let ((project-status (first allowed-statuscodes))
(breaker-status (car (last allowed-statuscodes)))
(previous-point))
;; (message "hi")
2018-06-18 22:19:07 -04:00
(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))
2018-06-26 20:23:45 -04:00
(if (nd/compare-statuscodes > new-status project-status allowed-statuscodes)
2018-06-18 22:19:07 -04:00
(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")
2018-06-26 20:23:45 -04:00
(nd/decend-into-project
2018-06-18 22:19:07 -04:00
'(: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")
2018-06-26 20:23:45 -04:00
(nd/decend-into-project
2018-06-18 22:19:07 -04:00
'(: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")
2018-06-26 20:23:45 -04:00
(nd/decend-into-project
2018-06-18 22:19:07 -04:00
'(:undone-complete :stuck :held :waiting :active)
'((:complete . 0)
(:archivable . 0)
(:scheduled-project . 1)
(:invalid-todostate . 1)
(:done-incomplete . 1))
(lambda (k)
2018-06-26 20:23:45 -04:00
(cond ((equal k "TODO") (if (nd/is-scheduled-heading-p) 4 1))
2018-06-18 22:19:07 -04:00
((equal k "HOLD") 2)
((equal k "WAIT") 3)
((equal k "NEXT") 4)
(t 0)))))
(t (error (concat "invalid keyword detected: " keyword))))))
2018-04-24 23:37:37 -04:00
#+END_SRC
2018-07-21 02:24:45 -04:00
****** iterator testing
2018-06-23 20:28:26 -04:00
#+BEGIN_SRC emacs-lisp
(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)
2018-06-24 12:36:12 -04:00
(subtree-end (save-excursion (org-end-of-subtree t))))
2018-06-23 20:28:26 -04:00
(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))
2018-06-26 20:23:45 -04:00
(if (nd/compare-statuscodes > new-status iter-status nd/iter-statuscodes)
2018-06-23 20:28:26 -04:00
(setq iter-status new-status)))))
(outline-next-heading)))
iter-status))
#+END_SRC
2018-07-21 02:24:45 -04:00
****** periodical testing
2018-06-26 22:59:47 -04:00
#+BEGIN_SRC emacs-lisp
(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))
#+END_SRC
2018-07-21 02:24:45 -04:00
***** skip functions
2018-04-24 23:37:37 -04:00
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.
2018-07-21 02:24:45 -04:00
****** helper skip functions and macros
2018-05-13 20:15:00 -04:00
Subunits for skip functions. Not meant to be used or called from the custom commands api
2018-04-24 23:37:37 -04:00
#+BEGIN_SRC emacs-lisp
(defun nd/skip-heading ()
2018-04-27 23:03:56 -04:00
(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")
2018-04-27 23:03:56 -04:00
"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)))))
2018-05-13 20:15:00 -04:00
#+END_SRC
2018-07-21 02:24:45 -04:00
****** headings
2018-05-15 22:55:11 -04:00
Skip functions for headings which may or may
not be todo-items
2018-05-15 22:55:11 -04:00
Note in the case of stale headings that
2018-05-13 20:15:00 -04:00
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)
#+BEGIN_SRC emacs-lisp
2018-05-15 22:55:11 -04:00
(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))
2018-05-04 22:13:10 -04:00
(not (nd/heading-has-children 'nd/is-todoitem-p))
(not (nd/heading-has-parent 'nd/is-todoitem-p))))
(nd/skip-heading)))))
2018-04-24 23:37:37 -04:00
2018-05-13 20:15:00 -04:00
#+END_SRC
2018-07-21 02:24:45 -04:00
****** atomic tasks
2018-05-13 20:15:00 -04:00
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
#+BEGIN_SRC emacs-lisp
;; 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))))
2018-04-27 23:03:56 -04:00
(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)))))
2018-04-24 23:37:37 -04:00
2018-04-27 23:03:56 -04:00
(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))))
2018-05-13 20:15:00 -04:00
#+END_SRC
2018-07-21 02:24:45 -04:00
****** periodicals
2018-05-13 20:15:00 -04:00
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).
#+BEGIN_SRC emacs-lisp
2018-06-26 22:59:47 -04:00
(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))))
2018-05-13 20:15:00 -04:00
2018-06-26 22:59:47 -04:00
(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))))
2018-05-13 20:15:00 -04:00
#+END_SRC
2018-07-21 02:24:45 -04:00
****** iterators
2018-06-24 12:36:12 -04:00
iterators are like projects but have additional status codes based on
when the iterator will run out
#+BEGIN_SRC emacs-lisp
(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)))))
#+END_SRC
2018-07-21 02:24:45 -04:00
****** project tasks
2018-05-13 20:15:00 -04:00
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)
#+BEGIN_SRC emacs-lisp
(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))
2018-06-26 23:02:21 -04:00
(if (not (nd/heading-has-parent 'nd/is-todoitem-p))
(nd/skip-heading)))
(nd/skip-heading)))))
2018-05-13 20:15:00 -04:00
#+END_SRC
2018-07-21 02:24:45 -04:00
****** header-level errors
2018-05-13 20:15:00 -04:00
Some headers are invalid under certain conditions
which I test here
#+BEGIN_SRC emacs-lisp
2018-04-24 23:37:37 -04:00
(defun nd/skip-non-discontinuous-project-tasks ()
2018-04-27 23:03:56 -04:00
(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)))))
2018-04-24 23:37:37 -04:00
(defun nd/skip-non-undone-closed-todoitems ()
2018-04-27 23:03:56 -04:00
(nd/skip-heading-with
nd/is-todoitem-p
(and (not (member keyword org-done-keywords))
(nd/is-closed-heading-p))))
2018-06-24 12:36:12 -04:00
2018-05-13 20:15:00 -04:00
(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))))
#+END_SRC
2018-07-21 02:24:45 -04:00
****** projects
2018-05-13 20:15:00 -04:00
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
#+BEGIN_SRC emacs-lisp
2018-06-27 21:12:46 -04:00
(defun nd/skip-non-projects (&optional ignore-toplevel)
2018-06-18 22:19:07 -04:00
(save-restriction
(widen)
(let ((keyword (nd/is-project-p)))
(if keyword
(if (and nd/agenda-limit-project-toplevel
2018-06-27 21:12:46 -04:00
(not ignore-toplevel)
2018-06-18 22:19:07 -04:00
(nd/heading-has-parent 'nd/is-todoitem-p))
(nd/skip-subtree))
(nd/skip-heading)))))
2018-04-24 23:37:37 -04:00
#+END_SRC
2018-07-21 02:24:45 -04:00
***** variables
2018-04-24 23:37:37 -04:00
#+BEGIN_SRC emacs-lisp
(defvar nd/agenda-limit-project-toplevel t
2018-07-21 02:24:45 -04:00
"If true, filter projects by all levels or top level only.")
2018-04-24 23:37:37 -04:00
(defvar nd/agenda-hide-incubator-tags t
2018-07-21 02:24:45 -04:00
"If true, don't show incubator headings.")
#+END_SRC
***** interactive view functions
#+BEGIN_SRC emacs-lisp
(defun nd/toggle-project-toplevel-display ()
2018-07-21 02:24:45 -04:00
"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")))
#+END_SRC
2018-07-21 02:24:45 -04:00
**** agenda aesthetics
#+BEGIN_SRC emacs-lisp
2018-06-03 21:09:16 -04:00
(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)))))
#+END_SRC
2018-07-21 02:24:45 -04:00
**** custom commands
#+BEGIN_SRC emacs-lisp
(defun nd/org-agenda-filter-status (filter status-fun a-line
&optional filter-only)
"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
If option FILTER-ONLY is t, the only return the unmodified a-line or
nil to act as a filter (eg does not touch text properties)."
2018-06-18 22:19:07 -04:00
(let* ((m (get-text-property 1 'org-marker a-line))
(s (with-current-buffer (marker-buffer m)
(goto-char m)
2018-06-24 12:36:12 -04:00
(funcall status-fun))))
2018-06-18 22:19:07 -04:00
(if (member s filter)
(if filter-only
a-line
(org-add-props (replace-regexp-in-string
"xxxx" (symbol-name s) a-line)
nil 'project-status s)))))
2018-06-18 22:19:07 -04:00
(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)
2018-06-03 21:09:16 -04:00
`(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)
2018-06-03 21:09:16 -04:00
(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\"")
2018-07-01 21:16:50 -04:00
(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 "/!")))
2018-06-03 21:09:16 -04:00
(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"
2018-06-03 21:09:16 -04:00
"Task View"
(,(nd/agenda-base-task-cmd act-no-rep-match
2018-06-03 21:09:16 -04:00
"Project Tasks"
''nd/skip-non-project-tasks
''(user-defined-up category-keep))
,(nd/agenda-base-task-cmd act-no-rep-match
2018-06-03 21:09:16 -04:00
"Atomic Tasks"
''nd/skip-non-atomic-tasks)))
2018-06-03 21:09:16 -04:00
("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
2018-06-26 20:23:45 -04:00
(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
2018-06-26 20:23:45 -04:00
(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-skip-function '(nd/skip-headings-with-tags nil '("%inc")))
(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))))))
2018-06-03 21:09:16 -04:00
("P"
"Periodical View"
2018-06-26 22:59:47 -04:00
((tags
2018-07-10 18:59:31 -04:00
,(concat actionable "-" iterator "+" periodical "-" habit)
2018-06-26 22:59:47 -04:00
((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"
2018-06-03 21:09:16 -04:00
"Iterator View"
2018-06-24 12:36:12 -04:00
((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)))
2018-06-03 21:09:16 -04:00
("r"
"Refile"
2018-06-03 21:09:16 -04:00
((tags "REFILE"
((org-agenda-overriding-header "Tasks to Refile"))
(org-tags-match-list-sublevels nil))))
2018-09-16 21:42:32 -04:00
("f"
"Flagged"
((tags "%flag" ((org-agenda-overriding-header "Flagged Tasks")))))
("e"
"Critical Errors"
(,(nd/agenda-base-task-cmd task-match
"Discontinous Project"
2018-06-26 20:23:45 -04:00
''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"
2018-06-26 20:23:45 -04:00
''nd/skip-non-done-unclosed-todoitems)))
2018-06-03 21:09:16 -04:00
("A"
"Archivable Tasks and Projects"
2018-07-01 21:16:50 -04:00
(,(nd/agenda-base-header-cmd (concat actionable "-" periodical "-" habit)
2018-06-26 22:59:47 -04:00
"Archivable Atomic Tasks and Iterators"
''nd/skip-non-archivable-atomic-tasks)
2018-07-01 21:16:50 -04:00
,(nd/agenda-base-header-cmd (concat actionable "-" habit)
2018-06-26 22:59:47 -04:00
"Stale Tasks and Periodicals"
''nd/skip-non-stale-headings)
(tags
2018-07-01 21:16:50 -04:00
,(concat actionable "-" periodical "-" iterator "-" habit)
((org-agenda-overriding-header
2018-06-27 21:12:46 -04:00
(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 t))))))))))
2018-04-19 01:22:11 -04:00
#+END_SRC
2018-09-07 11:29:32 -04:00
*** taskjuggler
#+BEGIN_SRC emacs-lisp
(require 'ox-taskjuggler)
2018-09-07 11:29:32 -04:00
#+END_SRC
* tools
2018-10-09 23:22:42 -04:00
** printing
For some reason there is no default way to get a "print prompt." Instead one needs to either install some third-party helper or make a function like this.
#+BEGIN_SRC emacs-lisp
(defun nd/helm-set-printer-name ()
2018-10-09 23:23:18 -04:00
"Set the printer name using helm-completion to select printer."
2018-10-09 23:22:42 -04:00
(interactive)
(let ((pl (or helm-ff-printer-list (helm-ff-find-printers))))
2018-10-10 19:23:47 -04:00
(if pl (setq printer-name (helm-comp-read "Printer: " pl)))))
2018-10-09 23:22:42 -04:00
#+END_SRC
** magit
#+BEGIN_SRC emacs-lisp
(use-package magit
:ensure t
:config
:delight auto-revert-mode
(setq magit-push-always-verify nil
git-commit-summary-max-length 50))
#+END_SRC
** 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.
#+BEGIN_SRC emacs-lisp
(setq dired-no-confirm '(move copy))
#+END_SRC
*** compression
Only supports tar.gz, tar.bz2, tar.xz, and .zip by default. Add support for more fun algos such as lzo and zpaq
#+BEGIN_SRC emacs-lisp
(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 &"))))
#+END_SRC
*** formatting for humans
make sizes human readable
#+BEGIN_SRC emacs-lisp
(setq dired-listing-switches "-Alh")
#+END_SRC
*** 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.
#+BEGIN_SRC emacs-lisp
;; 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)
#+END_SRC
2018-10-03 00:11:05 -04:00
*** directory sized
By default dired uses =ls -whatever= to get its output. This does not have recursive directory contents by default. This nitfy package solves this. This is not on default because navigation is much slower and the du output adds very little in many situations (toggle when needed).
#+BEGIN_SRC emacs-lisp
(use-package dired-du
:ensure t
:config
(setq dired-du-size-format t))
#+END_SRC
** mu4e
*** basic
2018-07-10 18:59:31 -04:00
#+BEGIN_SRC emacs-lisp
(require 'mu4e)
2018-07-10 18:59:31 -04:00
(setq mail-user-agent 'mu4e-user-agent
mu4e-maildir "/mnt/data/Mail"
2018-07-15 11:23:55 -04:00
2018-07-17 22:49:04 -04:00
mu4e-attachment-dir "~/Downloads"
mu4e-view-show-images t
mu4e-headers-show-target nil
message-kill-buffer-on-exit t
2018-07-15 11:23:55 -04:00
mu4e-change-filenames-when-moving t
2018-08-31 10:23:54 -04:00
mu4e-confirm-quit nil
mu4e-view-prefer-html t
mu4e-compose-dont-reply-to-self t
2018-09-15 00:18:47 -04:00
mu4e-get-mail-command "systemctl --user start mbsync"
user-full-name "Dwarshuis, Nathan J")
#+END_SRC
*** headers view
2018-09-12 09:15:03 -04:00
#+BEGIN_SRC emacs-lisp
(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)
#+END_SRC
2018-09-21 01:59:59 -04:00
*** citing
The citation line should enable history filding in outlook. This is enabled by using 32 underscores followed by the addressing info of the previous message(s).
#+BEGIN_SRC emacs-lisp
;; necessary for the header macros below
(require 'nnheader)
(defun nd/message-insert-citation-header ()
"Insert the header of the reply message."
(let* ((h message-reply-headers)
(sep "________________________________")
(from (concat "From: " (mail-header-from h)))
(date (concat "Sent: " (mail-header-date h)))
(to (concat "To: " user-full-name))
(subj (concat "Subject: " (message-strip-subject-re (mail-header-subject h)))))
(insert (string-join `("" ,sep ,from ,date ,to ,subj "") "\n"))))
(setq message-citation-line-function 'nd/message-insert-citation-header)
#+END_SRC
2018-09-21 01:59:59 -04:00
The default "> " things are annoying when citing old messages.
#+BEGIN_SRC emacs-lisp
(setq message-yank-prefix "")
(setq message-yank-cited-prefix "")
(setq message-yank-empty-prefix "")
#+END_SRC
By default the citation is destroyed (as in totally textified) if it is HTML. I want the links to be preserved, so use html2text and set arguments accordingly. Note that =--body-width=0= is necessary to prevent line breaks from being inserted in the middle of links.
#+BEGIN_SRC emacs-lisp
(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)))
(when (and html mu4e-view-prefer-html (member compose-type '(reply forward)))
;; 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)))))
#+END_SRC
2018-09-15 20:33:25 -04:00
*** smtp
#+BEGIN_SRC emacs-lisp
(require 'smtpmail)
;; (require 'smtpmail-async)
2018-09-15 20:33:25 -04:00
;; (require 'secrets)
;; (setq secrets-enabled t)
(setq send-mail-function 'smtpmail-send-it
message-send-mail-function 'smtpmail-send-it)
2018-09-15 20:33:25 -04:00
(add-to-list 'auth-sources (expand-file-name "~/.emacs.d/.authinfo_mu4e.gpg"))
;; (add-to-list 'auth-sources "secrets:default")
#+END_SRC
*** 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.
#+BEGIN_SRC emacs-lisp
2018-09-06 22:01:30 -04:00
(setq mu4e-context-policy 'pick-first
mu4e-compose-context-policy 'ask-if-none
2018-09-15 00:18:37 -04:00
mu4e-user-mail-address-list '("natedwarshuis@gmail.com" "ndwarshuis3@gatech.edu" "ndwarsh@emory.edu")
2018-09-06 22:01:30 -04:00
mu4e-contexts
2018-08-31 10:23:54 -04:00
`( ,(make-mu4e-context
:name "personal"
2018-08-31 10:23:54 -04:00
:match-func
(lambda (msg)
(when msg
(let ((pfx (mu4e-message-field msg :maildir)))
(string-prefix-p "/gmail" pfx))))
2018-08-31 10:23:54 -04:00
:vars '((mu4e-trash-folder . "/gmail/trash")
2018-09-06 20:37:44 -04:00
(mu4e-drafts-folder . "/gmail/drafts")
(mu4e-sent-folder . "/gmail/sent")
(mu4e-refile-folder . "/gmail/archive")
2018-09-06 20:37:44 -04:00
(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 .
2018-09-09 20:50:06 -04:00
(("/gmail/inbox" . ?i)
("/gmail/sent" . ?s)
("/gmail/trash" . ?t)
("/gmail/drafts" . ?d)
("/gmail/archive" . ?a)))))
2018-08-31 10:23:54 -04:00
,(make-mu4e-context
:name "gatech"
2018-08-31 10:23:54 -04:00
:match-func
(lambda (msg)
(when msg
(let ((pfx (mu4e-message-field msg :maildir)))
(string-prefix-p "/gatech" pfx))))
:vars '((mu4e-trash-folder . "/gatech/trash")
2018-09-06 20:37:44 -04:00
(mu4e-drafts-folder . "/gatech/drafts")
(mu4e-sent-folder . "/gatech/sent")
(mu4e-refile-folder . "/gatech/archive")
2018-09-06 20:37:44 -04:00
(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)
2018-09-11 22:11:54 -04:00
("/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")
2018-09-06 20:37:44 -04:00
(mu4e-drafts-folder . "/emory/drafts")
(mu4e-sent-folder . "/emory/sent")
(mu4e-refile-folder . "/emory/archive")
2018-09-06 20:37:44 -04:00
(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)
2018-09-11 22:11:54 -04:00
("/emory/trash" . ?t)
("/emory/drafts" . ?d)
("/emory/archive" . ?a)))))))
2018-07-10 18:59:31 -04:00
#+END_SRC
*** org-mu4e
#+BEGIN_SRC emacs-lisp
(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))
#+END_SRC
2018-09-16 15:17:00 -04:00
*** signature
Signatures take lots of space and make short messages look needlessly clunky, so keep off by default.
#+BEGIN_SRC emacs-lisp
(setq mu4e-compose-signature-auto-include nil
mu4e-compose-signature
2018-09-21 01:14:37 -04:00
(string-join
2018-10-23 09:01:24 -04:00
'("Nathan Dwarshuis"
2018-09-16 15:17:00 -04:00
""
"PhD Student - Biomedical Engineering - Krish Roy Lab"
"Georgia Institute of Technology and Emory University"
"ndwarshuis3@gatech.edu")
"\n"))
#+END_SRC
*** visual-line-mode
By default mu4e adds breaks after 80-ish chars using auto-fill-mode which makes messages look weird when opened. =Visual-line-mode= avoids this problem.
#+BEGIN_SRC emacs-lisp
(add-hook 'mu4e-compose-mode-hook 'turn-off-auto-fill)
(add-hook 'mu4e-compose-mode-hook 'visual-line-mode)
(add-hook 'mu4e-view-mode-hook 'turn-off-auto-fill)
(add-hook 'mu4e-view-mode-hook 'visual-line-mode)
#+END_SRC
** auctex
#+BEGIN_SRC emacs-lisp
(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)
#+END_SRC
** bibtex
2018-09-12 16:46:59 -04:00
#+BEGIN_SRC emacs-lisp
(use-package org-ref
:ensure t
:after org
:config
(setq reftex-default-bibliography (expand-file-name "~/BibTeX/master.bib")
org-ref-bibliography-notes (expand-file-name "~/BibTeX/notes.org")
org-ref-default-bibliography (expand-file-name "~/BibTeX/master.bib")))
2018-09-12 16:46:59 -04:00
(use-package helm-bibtex
:ensure t
:after helm
:config
(setq bibtex-completion-bibliography (expand-file-name "~/BibTeX/master.bib")
bibtex-completion-library-path (expand-file-name "~/BibTeX/pdf")
2018-09-12 16:46:59 -04:00
bibtex-completion-pdf-field "File"))
#+END_SRC
** shell
2018-03-21 21:44:31 -04:00
#+begin_src emacs-lisp
(defadvice ansi-term (before force-bash)
2018-07-21 18:41:42 -04:00
(interactive (list "/bin/zsh")))
2018-03-21 21:44:31 -04:00
(ad-activate 'ansi-term)
2018-09-16 23:46:33 -04:00
(defun nd/term-send-raw-escape ()
"Send a raw escape character to the running terminal."
(interactive)
(term-send-raw-string "\e"))
2018-03-21 21:44:31 -04:00
#+END_SRC
** ediff
2018-03-21 21:44:31 -04:00
#+BEGIN_SRC emacs-lisp
(setq ediff-window-setup-function 'ediff-setup-windows-plain)
#+END_SRC
2018-07-14 20:42:20 -04:00
* keybindings
2018-07-21 17:30:35 -04:00
For the sake of my sanity, all bindings go here. Note this means I don't use =:bind= in use-package forms.
2018-07-14 20:42:20 -04:00
** evil
I like being evil. All package and custom bindings go here.
2018-07-21 17:30:35 -04:00
*** base
2018-07-14 20:42:20 -04:00
#+BEGIN_SRC emacs-lisp
(use-package evil
:ensure t
:init
;; this is required to make evil collection work
(setq evil-want-integration nil)
:config
2018-07-21 17:30:35 -04:00
(evil-mode 1))
#+END_SRC
2018-09-11 08:26:05 -04:00
*** motion
By default, emacs counts a sentence as having at least 2 spaces after punctuation. Make this behave more like vim.
#+BEGIN_SRC emacs-lisp
(setq sentence-end-double-space nil)
#+END_SRC
*** enhancements
delightfully ripped off from vim plugins
**** surround
2018-08-18 18:06:18 -04:00
#+BEGIN_SRC emacs-lisp
(use-package evil-surround
:ensure t
:after evil
:config
(global-evil-surround-mode 1))
#+END_SRC
2018-09-06 20:37:44 -04:00
**** commentary
#+BEGIN_SRC emacs-lisp
(use-package evil-commentary
:ensure t
:after evil
:delight
:config
(evil-commentary-mode))
#+END_SRC
**** replace with register
#+BEGIN_SRC emacs-lisp
(use-package evil-replace-with-register
:ensure t
:after evil
:config
(evil-replace-with-register-install))
#+END_SRC
2018-07-21 17:30:35 -04:00
*** 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.
#+BEGIN_SRC emacs-lisp
(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-<SPC>")
2018-07-21 17:30:35 -04:00
(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")))
2018-07-14 20:42:20 -04:00
#+END_SRC
*** evil-org
#+BEGIN_SRC emacs-lisp
(use-package evil-org
:ensure t
2018-07-14 22:16:32 -04:00
:after (evil org)
2018-07-14 20:42:20 -04:00
:delight
:config
2018-07-15 00:08:00 -04:00
(add-hook 'org-mode-hook 'evil-org-mode)
2018-07-14 22:32:34 -04:00
(add-hook 'evil-org-mode-hook 'evil-org-set-key-theme)
2018-07-14 20:42:20 -04:00
(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
2018-09-29 17:26:58 -04:00
"sE" 'nd/org-agenda-filter-non-effort
"sD" 'nd/org-agenda-filter-delegate
"sP" 'nd/org-agenda-filter-non-peripheral
2018-07-14 20:42:20 -04:00
"e" 'org-agenda-set-effort
"ce" nil))
#+END_SRC
2018-07-20 02:47:34 -04:00
*** evil-magit
#+BEGIN_SRC emacs-lisp
(use-package evil-magit
:ensure t
:after (evil magit))
#+END_SRC
*** 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.
#+BEGIN_SRC emacs-lisp
2018-07-21 17:30:35 -04:00
(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)
#+END_SRC
2018-07-21 21:53:56 -04:00
*** ess
2018-08-01 11:39:47 -04:00
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
2018-07-21 21:53:56 -04:00
#+BEGIN_SRC emacs-lisp
(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
2018-08-01 11:39:47 -04:00
(kbd "C-k") 'comint-previous-input
(kbd "C-j") 'comint-next-input)
2018-07-21 21:53:56 -04:00
(add-hook 'inferior-ess-mode-hook
(lambda ()
(add-hook 'evil-insert-state-entry-hook
'nd/ess-char-mode-insert nil t)))
#+END_SRC
2018-07-14 20:42:20 -04:00
*** collection
2018-07-21 17:30:35 -04:00
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.
2018-07-14 20:42:20 -04:00
#+BEGIN_SRC emacs-lisp
(use-package evil-collection
:ensure t
:after evil
:init
(setq evil-collection-mode-list
2018-09-22 11:05:41 -04:00
'(dired ediff flycheck company which-key helm minibuffer mu4e ediff term))
2018-07-20 00:50:51 -04:00
(setq evil-collection-setup-minibuffer t)
2018-07-14 22:05:02 -04:00
:config
(evil-collection-init))
2018-07-14 20:42:20 -04:00
#+END_SRC
2018-07-20 00:33:40 -04:00
**** dired
2018-07-21 17:30:35 -04:00
Dired makes new buffers by default. Use =find-alternate-file= to avoid this.
2018-07-20 00:33:40 -04:00
#+BEGIN_SRC emacs-lisp
(defun nd/dired-move-to-parent-directory ()
"Move buffer to parent directory (like 'cd ..')."
(interactive)
(find-alternate-file ".."))
(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*")))
2018-07-20 00:54:53 -04:00
(put 'dired-find-alternate-file 'disabled nil)
2018-07-20 00:33:40 -04:00
(evil-define-key 'normal dired-mode-map
"a" 'dired-find-file
2018-09-01 15:49:24 -04:00
"gs" 'nd/dired-sort-by
2018-09-16 23:37:47 -04:00
"^" 'nd/dired-move-to-parent-directory
"q" 'nd/kill-current-buffer
2018-07-20 00:33:40 -04:00
(kbd "<return>") 'dired-find-alternate-file
(kbd "C-<return>") 'nd/dired-xdg-open
2018-09-16 23:37:47 -04:00
(kbd "M-<return>") 'nd/dired-open-with)
2018-07-21 17:30:35 -04:00
#+END_SRC
**** 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.
#+BEGIN_SRC emacs-lisp
(evil-define-key '(normal insert) helm-map
(kbd "<tab>") 'helm-execute-persistent-action
(kbd "C-<tab>") 'helm-select-action)
2018-07-20 00:33:40 -04:00
#+END_SRC
2018-07-21 18:41:42 -04:00
**** term
Since I use vi mode in my terminal emulator, need to preserve the escape key's raw behavior
#+BEGIN_SRC emacs-lisp
(evil-define-key 'insert term-raw-map
2018-09-16 23:46:33 -04:00
(kbd "<escape>") 'nd/term-send-raw-escape
2018-07-21 18:41:42 -04:00
(kbd "C-<escape>") 'evil-normal-state)
#+END_SRC
2018-07-14 20:42:20 -04:00
** local
2018-07-21 17:30:35 -04:00
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)
2018-07-14 20:42:20 -04:00
*** org-mode
#+BEGIN_SRC emacs-lisp
(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)))
#+END_SRC
*** dired
#+BEGIN_SRC emacs-lisp
(define-key dired-mode-map (kbd "C-x g") 'magit)
#+END_SRC
*** haskell-mode
#+BEGIN_SRC emacs-lisp
(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))
#+END_SRC
*** helm-prefix
2018-10-11 17:08:13 -04:00
Some of these are useful enough that I make give them a direct binding without requiring a prefix. For now this is fine.
#+BEGIN_SRC emacs-lisp
(define-key helm-command-prefix (kbd "b") 'helm-bibtex)
2018-09-21 01:04:04 -04:00
(define-key helm-command-prefix (kbd "S") 'helm-swoop)
(define-key helm-command-prefix (kbd "<f8>") 'helm-resume)
#+END_SRC
2018-10-11 17:08:13 -04:00
Give =f= to =nd/helm-flyspell-correct= instead of =helm-multi-files= and give the latter =F= (used much less).
#+BEGIN_SRC emacs-lisp
(define-key helm-command-prefix (kbd "f") 'helm-flyspell-correct)
(define-key helm-command-prefix (kbd "F") 'helm-multi-files)
#+END_SRC
*** outline-magic
#+BEGIN_SRC emacs-lisp
(define-key outline-minor-mode-map (kbd "<tab>") 'outline-cycle)
#+END_SRC
2018-07-14 20:42:20 -04:00
** global
2018-10-10 19:28:58 -04:00
*** function
The function keys are nice because they are almost (not always) free in every mode. Therefore I use these for functions that I need to access anywhere, but not necessary extremely often (because they are out of the way and harder to reach).
2018-07-14 20:42:20 -04:00
#+BEGIN_SRC emacs-lisp
(global-set-key (kbd "<f1>") 'org-agenda)
(global-set-key (kbd "<f2>") 'org-capture)
2018-07-21 02:44:10 -04:00
(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)
2018-08-22 00:20:31 -04:00
(global-set-key (kbd "C-<f5>") 'nd/open-urxvt)
2018-07-18 10:35:37 -04:00
(global-set-key (kbd "<f12>") 'mu4e)
(global-set-key (kbd "C-<f12>") 'global-hl-line-mode)
2018-07-14 20:42:20 -04:00
(global-set-key (kbd "S-<f12>") 'display-line-numbers-mode)
2018-10-10 19:28:58 -04:00
#+END_SRC
*** control/meta
#+BEGIN_SRC emacs-lisp
(global-set-key (kbd "C-<SPC>") 'company-complete)
2018-07-14 20:42:20 -04:00
(global-set-key (kbd "C-c e") 'nd/config-visit)
2018-07-14 23:36:42 -04:00
(global-set-key (kbd "C-c r") 'nd/config-reload)
(global-set-key (kbd "C-c s") 'sudo-edit)
2018-07-14 20:42:20 -04:00
(global-set-key (kbd "C-x 2") 'nd/split-and-follow-horizontally)
(global-set-key (kbd "C-x 3") 'nd/split-and-follow-vertically)
2018-07-18 12:06:51 -04:00
(global-unset-key (kbd "C-x c"))
2018-07-18 10:35:37 -04:00
(global-set-key (kbd "C-x k") 'nd/kill-current-buffer)
(global-set-key (kbd "C-x C-d") 'helm-bookmarks)
2018-07-14 23:36:42 -04:00
(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)
2018-07-18 10:35:37 -04:00
2018-07-14 23:36:42 -04:00
(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)
2018-07-14 20:42:20 -04:00
#+END_SRC