Sacha Chua's Emacs configuration

Table of Contents

Configuration

About this file

Inspired by the Emacs Starter Kit, I set up my configuration file using Org-babel. Because my username is Sacha, I can save this as Sacha.org in my ~/.emacs.d directory to have it automatically load. You could load it with (org-babel-load-file "/path/to/file"), changing the path appropriately, but you'll probably want to tweak it extensively first.

This page: HTML - Org - .el - Github - also, http://sach.ac/dotemacs

To be precise, this is what's in the first part of my ~/.emacs.d/init.el (what used to be the ~/.emacs file):

;; This sets up the load path so that we can override it
(package-initialize nil)
;; Override the packages with the git version of Org and other packages
(add-to-list 'load-path "~/elisp/org-mode/lisp")
(add-to-list 'load-path "~/elisp/org-mode/contrib/lisp")
(add-to-list 'load-path "~/code/org2blog")
(add-to-list 'load-path "~/Dropbox/2014/presentations/org-reveal")
(add-to-list 'load-path "/usr/local/share/emacs/site-lisp")
;; Load the rest of the packages
(package-initialize t)
(setq package-enable-at-startup nil)
(require 'org)
(require 'ob-tangle)
(org-babel-load-file (expand-file-name "~/.emacs.d/Sacha.org"))

If you're new to Emacs Lisp, you probably don't want to copy and paste large chunks of this code. Instead, copy small parts of it (always making sure to copy a complete set of parentheses) into your *scratch* buffer or some other buffer in emacs-lisp-mode. Use M-x eval-buffer to evaluate the code and see if you like the way that Emacs behaves. See An Introduction to Programming in Emacs Lisp for more details on Emacs Lisp. You can also find the manual by using C-h i (info) and choosing "Emacs Lisp Intro".

I've installed a lot of packages. See the package sources section to add the repositories to your configuration. When you see use-package and a package name you might like, you can use M-x package-install to install the package of that name. Note that use-package is itself provided by a package, so you'll probably want to install that and bind-key.

If you're viewing the Org file, you can open source code blocks (those are the ones in beginsrc) in a separate buffer by moving your point inside them and typing C-c ' (org-edit-special). This opens another buffer in emacs-lisp-mode, so you can use M-x eval-buffer to load the changes. If you want to explore how functions work, use M-x edebug-defun to set up debugging for that function, and then call it. You can learn more about edebug in the Emacs Lisp manual.

I like using (setq ...) more than Customize because I can neatly organize my configuration that way.

Personal information

(setq user-full-name "Sacha Chua"
      user-mail-address "sacha@sachachua.com")

Emacs initialization

Install packages if necessary

ELPA makes it easy to install packages without tracking down all the different websites. Let's define a function that makes it easy to install packages. I don't actually use this a lot any more, but it can be handy.

(defun sacha/package-install (package &optional repository)
  "Install PACKAGE if it has not yet been installed.
If REPOSITORY is specified, use that."
  (unless (package-installed-p package)
    (let ((package-archives (if repository
                                (list (assoc repository package-archives))
                              package-archives)))
    (package-install package))))

Add my elisp directory and other files

Sometimes I load files outside the package system. As long as they're in a directory in my load-path, Emacs can find them.

(add-to-list 'load-path "~/.emacs.d")
(add-to-list 'load-path "~/elisp")
(add-to-list 'load-path "~/elisp/artbollocks-mode")
(sacha/package-install 'use-package)
(require 'use-package)

Load secrets

I keep slightly more sensitive information in a separate file so that I can easily publish my main configuration.

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

Add package sources

(add-to-list 'package-archives '("org" . "http://orgmode.org/elpa/") t)
(add-to-list 'package-archives '("melpa" . "http://melpa.org/packages/") t)

Use M-x package-refresh-contents to reload the list of packages after adding these for the first time.

Byte-compile my init files to speed things up next time

I hardly ever use this because I don't care about my Emacs startup speed. The trick to using Emacs is to not close it. I start Emacs when my computer boots up. I suspend my computer when I go to sleep. Pretty much the only time I shut down is when I need to update the system.

(defun sacha/byte-recompile ()
  (interactive)
  (byte-recompile-directory "~/.emacs.d" 0)
  (byte-recompile-directory "~/elisp" 0))

General configuration

Backups

This is one of the things people usually want to change right away. By default, Emacs saves backup files in the current directory. These are the files ending in ~ that are cluttering up your directory lists. The following code stashes them all in ~/.emacs.d/backups, where I can find them with C-x C-f (find-file) if I really need to.

(setq backup-directory-alist '(("." . "~/.emacs.d/backups")))

Disk space is cheap. Save lots.

(setq delete-old-versions -1)
(setq version-control t)
(setq vc-make-backup-files t)
(setq auto-save-file-name-transforms '((".*" "~/.emacs.d/auto-save-list/" t)))

History

From http://www.wisdomandwonder.com/wordpress/wp-content/uploads/2014/03/C3F.html

(setq savehist-file "~/.emacs.d/savehist")
(savehist-mode 1)
(setq history-length t)
(setq history-delete-duplicates t)
(setq savehist-save-minibuffer-history 1)
(setq savehist-additional-variables
      '(kill-ring
        search-ring
        regexp-search-ring))

Windows configuration   drill

When you're starting out, tooltips, menus, and the tool bar can be very helpful. (Emacs Basics: Using the Mouse). Eventually, you may want to reclaim that extra little bit of screenspace. The following code turns those things off when using a graphical Emacs.

(when window-system
  (tooltip-mode -1)
  (tool-bar-mode -1)
  (menu-bar-mode -1)
  (scroll-bar-mode -1))

Winner mode - undo and redo window configuration

winner-mode lets you use C-c <left> and C-c <right> to switch between window configurations. This is handy when something has popped up a buffer that you want to look at briefly before returning to whatever you were working on. When you're done, press C-c <left>.

(sacha/package-install 'winner)
(use-package winner
  :config (winner-mode 1))

Sentences end with a single space

In my world, sentences end with a single space. This makes sentence navigation commands work for me.

(setq sentence-end-double-space nil)

Helm - interactive completion

Helm makes it easy to complete various things. I find it to be easier to configure than ido in order to get completion in as many places as possible, although I prefer ido's way of switching buffers.

(use-package helm
  :init
  (progn 
    (require 'helm-config) 
    (setq helm-candidate-number-limit 100)
    ;; From https://gist.github.com/antifuchs/9238468
    (setq helm-idle-delay 0.0 ; update fast sources immediately (doesn't).
          helm-input-idle-delay 0.01  ; this actually updates things
                                        ; reeeelatively quickly.
          helm-quick-update t
          helm-M-x-requires-pattern nil
          helm-ff-skip-boring-files t)
    (helm-mode))
  :config
  (progn
    ;; I don't like the way switch-to-buffer uses history, since
    ;; that confuses me when it comes to buffers I've already
    ;; killed. Let's use ido instead.
    (add-to-list 'helm-completing-read-handlers-alist 
                 '(switch-to-buffer . ido))
    ;; Unicode
    (add-to-list 'helm-completing-read-handlers-alist 
                 '(insert-char . ido)))
  :bind (("C-c h" . helm-mini) 
         ("M-x" . helm-M-x)))
(ido-mode -1) ;; Turn off ido mode in case I enabled it accidentally

Mode line format

Display a more compact mode line

(use-package smart-mode-line
  :init
  (progn
  (setq-default
   mode-line-format 
   '("%e"
     mode-line-front-space
     mode-line-mule-info
     mode-line-client
     mode-line-modified
     mode-line-remote
     mode-line-frame-identification
     mode-line-buffer-identification
     "   "
     mode-line-position
     (vc-mode vc-mode)
     "  "
     mode-line-modes
     mode-line-misc-info
     mode-line-end-spaces))))

Hide minor modes I care less about:

(require 'diminish)
(eval-after-load "yasnippet" '(diminish 'yas-minor-mode))
(eval-after-load "undo-tree" '(diminish 'undo-tree-mode))
(eval-after-load "guide-key" '(diminish 'guide-key-mode))
(eval-after-load "smartparens" '(diminish 'smartparens-mode))
(eval-after-load "guide-key" '(diminish 'guide-key-mode))
(eval-after-load "eldoc" '(diminish 'eldoc-mode))
(diminish 'visual-line-mode)

Change "yes or no" to "y or n"

Lazy people like me never want to type "yes" when "y" will suffice.

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

Minibuffer editing - more space!

Sometimes you want to be able to do fancy things with the text that you're entering into the minibuffer. Sometimes you just want to be able to read it, especially when it comes to lots of text. This binds C-M-e in a minibuffer) so that you can edit the contents of the minibuffer before submitting it.

(sacha/package-install 'miniedit)
(use-package miniedit
  :commands minibuffer-edit
  :init (miniedit-install))

Set up a light-on-dark color scheme

I like light on dark because I find it to be more restful. The color-theme in ELPA was a little odd, though, so we define some advice to make it work. Some things still aren't quite right.

(defadvice color-theme-alist (around sacha activate)
  (if (ad-get-arg 0)
      ad-do-it
    nil))
(sacha/package-install 'color-theme)
(sacha/package-install 'color-theme-solarized)
(defun sacha/setup-color-theme ()
  (interactive)
  (color-theme-solarized 'dark)
  (set-face-foreground 'secondary-selection "darkblue")
  (set-face-background 'secondary-selection "lightblue")
  (set-face-background 'font-lock-doc-face "black")
  (set-face-foreground 'font-lock-doc-face "wheat")
  (set-face-background 'font-lock-string-face "black")
  (set-face-foreground 'org-todo "green")
  (set-face-background 'org-todo "black"))

(use-package color-theme
  :init
  (when window-system
    (sacha/setup-color-theme)))

I sometimes need to switch to a lighter background for screenshots. For that, I use color-theme-vim.

Some more tweaks to solarized:

(when window-system
  (custom-set-faces
   '(erc-input-face ((t (:foreground "antique white"))))
   '(helm-selection ((t (:background "ForestGreen" :foreground "black"))))
   '(org-agenda-clocking ((t (:inherit secondary-selection :foreground "black"))) t)
   '(org-agenda-done ((t (:foreground "dim gray" :strike-through nil))))
   '(org-done ((t (:foreground "PaleGreen" :weight normal :strike-through t))))
   '(org-clock-overlay ((t (:background "SkyBlue4" :foreground "black"))))
   '(org-headline-done ((((class color) (min-colors 16) (background dark)) (:foreground "LightSalmon" :strike-through t))))
   '(outline-1 ((t (:inherit font-lock-function-name-face :foreground "cornflower blue"))))))

Undo tree mode - visualize your undos and branches

People often struggle with the Emacs undo model, where there's really no concept of "redo" - you simply undo the undo.

This lets you use C-x u (undo-tree-visualize) to visually walk through the changes you've made, undo back to a certain point (or redo), and go down different branches.

(use-package undo-tree
  :init
  (progn
    (global-undo-tree-mode)
    (setq undo-tree-visualizer-timestamps t)
    (setq undo-tree-visualizer-diff t)))

Help - guide-key

It's hard to remember keyboard shortcuts. The guide-key package pops up help after a short delay.

(use-package guide-key
  :init
  (setq guide-key/guide-key-sequence '("C-x r" "C-x 4" "C-c"))
  (guide-key-mode 1))  ; Enable guide-key-mode

UTF-8

From http://www.wisdomandwonder.com/wordpress/wp-content/uploads/2014/03/C3F.html

(prefer-coding-system 'utf-8)
(when (display-graphic-p)
  (setq x-select-request-type '(UTF8_STRING COMPOUND_TEXT TEXT STRING)))

Killing text

From https://github.com/itsjeyd/emacs-config/blob/emacs24/init.el

(defadvice kill-region (before slick-cut activate compile)
  "When called interactively with no active region, kill a single line instead."
  (interactive
    (if mark-active (list (region-beginning) (region-end))
      (list (line-beginning-position)
        (line-beginning-position 2)))))

Navigation

Pop to mark

Handy way of getting back to previous places.

(bind-key "C-x p" 'pop-to-mark-command)
(setq set-mark-command-repeat-pop t)

Text size

(bind-key "C-+" 'text-scale-increase)
(bind-key "C--" 'text-scale-decrease)

Helm-swoop - quickly finding lines

This promises to be a fast way to find things. Let's bind it to Ctrl-Shift-S to see if I can get used to that…

(use-package helm-swoop
 :bind (("C-S-s" . helm-swoop)))

Windmove - switching between windows

Windmove lets you move between windows with something more natural than cycling through C-x o (other-window). Windmove doesn't behave well with Org, so we need to use different keybindings.

(use-package windmove
  :bind
  (("<f2> <right>" . windmove-right)
   ("<f2> <left>" . windmove-left)
   ("<f2> <up>" . windmove-up)
   ("<f2> <down>" . windmove-down)))

Make window splitting more useful

Copied from http://www.reddit.com/r/emacs/comments/25v0eo/you_emacs_tips_and_tricks/chldury

(defun sacha/vsplit-last-buffer (prefix)
  "Split the window vertically and display the previous buffer."
  (interactive "p")
  (split-window-vertically)
  (other-window 1 nil)
  (unless prefix
    (switch-to-next-buffer)))
(defun sacha/hsplit-last-buffer (prefix)
  "Split the window horizontally and display the previous buffer."
  (interactive "p")
  (split-window-horizontally)
  (other-window 1 nil)
  (unless prefix (switch-to-next-buffer)))
(bind-key "C-x 2" 'sacha/vsplit-last-buffer)
(bind-key "C-x 3" 'sacha/hsplit-last-buffer)

Searching based on the current word

This lets me search up and down. I don't use this often, though.

(defun sacha/search-word-backward ()
  "Find the previous occurrence of the current word."
  (interactive)
  (let ((cur (point)))
    (skip-syntax-backward "w_")
    (goto-char
     (if (re-search-backward (concat "\\_<" (current-word) "\\_>") nil t)
         (match-beginning 0)
       cur))))

(defun sacha/search-word-forward ()
  "Find the next occurrence of the current word."
  (interactive)
  (let ((cur (point)))
    (skip-syntax-forward "w_")
    (goto-char
     (if (re-search-forward (concat "\\_<" (current-word) "\\_>") nil t)
         (match-beginning 0)
       cur))))
(defadvice search-for-keyword (around sacha activate)
  "Match in a case-insensitive way."
  (let ((case-fold-search t))
    ad-do-it))
(global-set-key '[M-up] 'sacha/search-word-backward)
(global-set-key '[M-down] 'sacha/search-word-forward)

Frequently-accessed files

Registers allow you to jump to a file or other location quickly. To jump to a register, use C-x r j followed by the letter of the register. Using registers for all these file shortcuts is probably a bit of a waste since I can easily define my own keymap, but since I rarely go beyond register A anyway…

(mapcar
 (lambda (r)
   (set-register (car r) (cons 'file (cdr r))))
 '((?i . "~/.emacs.d/Sacha.org")
   (?o . "~/personal/organizer.org")
   (?b . "~/personal/business.org")
   (?B . "~/Dropbox/books")
   (?e . "~/code/dev/emacs-notes/tasks.org")
   (?w . "~/Dropbox/public/sharing/index.org")
   (?W . "~/Dropbox/public/sharing/blog.org")
   (?j . "~/personal/journal.org")
   (?I . "~/Dropbox/Inbox/To Blog")
   (?g . "~/sachac.github.io/evil-plans/index.org")
   (?c . "~/code/dev/elisp-course.org")
   (?l . "~/dropbox/public/sharing/learning.org")))

Browse-kill-ring - see what you've cut so that you can paste it   drill

Make sense of the kill ring! This lets you list the contents of the kill ring and paste a specific item. You can also manipulate your kill ring contents.

If you're new to Emacs, you might not yet know about what the kill ring is. It stores the items that you cut (or kill, in Emacs terms). You're not limited to pasting just the most recent item - you can paste the second-to-the-last item you cut, and so on. I remember to use C-y and M-y when going backwards in the kill ring, but I never quite remember how to go forward, so browse-kill-ring makes it easier.

(sacha/package-install 'browse-kill-ring)
(use-package browse-kill-ring
  :init 
  (progn 
    (browse-kill-ring-default-keybindings) ;; M-y
    (setq browse-kill-ring-quit-action 'save-and-restore)))

Key chords

I'm on a Dvorak keyboard, so these might not work for you. Experimenting with this. key-chord lets you define keyboard shortcuts that use ordinary keys.

Some code from http://emacsredux.com/blog/2013/04/28/switch-to-previous-buffer/

(defun sacha/key-chord-define (keymap keys command)
  "Define in KEYMAP, a key-chord of two keys in KEYS starting a COMMAND.
\nKEYS can be a string or a vector of two elements. Currently only elements
that corresponds to ascii codes in the range 32 to 126 can be used.
\nCOMMAND can be an interactive function, a string, or nil.
If COMMAND is nil, the key-chord is removed.

MODIFICATION: Do not define the transposed key chord.
"
  (if (/= 2 (length keys))
      (error "Key-chord keys must have two elements"))
  ;; Exotic chars in a string are >255 but define-key wants 128..255 for those
  (let ((key1 (logand 255 (aref keys 0)))
        (key2 (logand 255 (aref keys 1))))
    (define-key keymap (vector 'key-chord key1 key2) command)))
(fset 'key-chord-define 'sacha/key-chord-define)

(defun sacha/switch-to-previous-buffer ()
  "Switch to previously open buffer.
Repeated invocations toggle between the two most recently open buffers."
  (interactive)
  (switch-to-buffer (other-buffer (current-buffer) 1)))

(defun sacha/org-check-agenda ()
  "Peek at agenda."
  (interactive)
  (cond
   ((derived-mode-p 'org-agenda-mode)
    (if (window-parent) (delete-window) (bury-buffer)))
   ((get-buffer "*Org Agenda*")
    (switch-to-buffer-other-window "*Org Agenda*"))
   (t (org-agenda nil "a"))))

(defvar sacha/key-chord-command-map (make-sparse-keymap))
(bind-key "h" 'sacha/org-check-agenda sacha/key-chord-command-map)
(bind-key "u" 'emms-pause sacha/key-chord-command-map)
(bind-key "t" 'emms-seek-forward sacha/key-chord-command-map)
(bind-key "n" 'emms-seek-to sacha/key-chord-command-map)

(use-package key-chord
  :init
  (progn 
    (fset 'key-chord-define 'sacha/key-chord-define)
    (key-chord-mode 1)
    ;; k can be bound too
    (key-chord-define-global "uu"     'undo)
    (key-chord-define-global "yy"     'browse-kill-ring)
    (key-chord-define-global "jj"     'ace-jump-word-mode)
    (key-chord-define-global "jw"     'ace-window)
    (key-chord-define-global "jl"     'ace-jump-line-mode)
    (key-chord-define-global "jz"     'ace-jump-zap-up-to-char)
    (key-chord-define-global "jZ"     'ace-jump-zap-to-char)
    (key-chord-define-global "FF"     'find-file)
    (key-chord-define-global "qq"     'sacha/org-quick-clock-in-task)
    (key-chord-define-global "hh"     sacha/key-chord-command-map)
    (key-chord-define-global "hc"     'emms-seek-forward)
    (key-chord-define-global "h-"     'emms-seek-to)
    (key-chord-define-global "xx"     'er/expand-region)
    (key-chord-define-global "JJ"     'sacha/switch-to-previous-buffer)))

Smartscan

From https://github.com/itsjeyd/emacs-config/blob/emacs24/init.el

(use-package smartscan
  :init (global-smartscan-mode t))

Dired

From http://www.masteringemacs.org/articles/2011/03/25/working-multiple-files-dired/

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

Move to beginning of line

Copied from http://emacsredux.com/blog/2013/05/22/smarter-navigation-to-the-beginning-of-a-line/

(defun sacha/smarter-move-beginning-of-line (arg)
  "Move point back to indentation of beginning of line.

Move point to the first non-whitespace character on this line.
If point is already there, move to the beginning of the line.
Effectively toggle between the first non-whitespace character and
the beginning of the line.

If ARG is not nil or 1, move forward ARG - 1 lines first.  If
point reaches the beginning or end of the buffer, stop there."
  (interactive "^p")
  (setq arg (or arg 1))

  ;; Move lines first
  (when (/= arg 1)
    (let ((line-move-visual nil))
      (forward-line (1- arg))))

  (let ((orig-point (point)))
    (back-to-indentation)
    (when (= orig-point (point))
      (move-beginning-of-line 1))))

;; remap C-a to `smarter-move-beginning-of-line'
(global-set-key [remap move-beginning-of-line]
                'sacha/smarter-move-beginning-of-line)

Recent files

(require 'recentf)
(setq recentf-max-saved-items 200
      recentf-max-menu-items 15)
(recentf-mode)

Copy filename to clipboard

http://emacsredux.com/blog/2013/03/27/copy-filename-to-the-clipboard/ https://github.com/bbatsov/prelude

(defun prelude-copy-file-name-to-clipboard ()
  "Copy the current buffer file name to the clipboard."
  (interactive)
  (let ((filename (if (equal major-mode 'dired-mode)
                      default-directory
                    (buffer-file-name))))
    (when filename
      (kill-new filename)
      (message "Copied buffer file name '%s' to the clipboard." filename))))

Narrowing

From http://endlessparentheses.com/emacs-narrow-or-widen-dwim.html

(add-to-list 'load-path "~/elisp/recursive-narrow")
(defun sacha/recursive-narrow-dwim-org ()
    (if (derived-mode-p 'org-mode) 
         (cond ((or (org-at-block-p) (org-in-src-block-p)) (org-narrow-to-block))
               (t (org-narrow-to-subtree))))
)
(use-package recursive-narrow
  :config
  (add-hook 'recursive-narrow-dwim-functions 'sacha/recursive-narrow-dwim-org)
  :bind
  (("C-x n w" . recursive-widen)
   ("C-x n n" . recursive-narrow-or-widen-dwim)))

Reading

https://github.com/xahlee/xah_emacs_init/blob/master/xah_emacs_font.el From Xah Lee:

(defun xah-toggle-margin-right ()
  "Toggle the right margin between `fill-column' or window width.
This command is convenient when reading novel, documentation."
  (interactive)
  (if (eq (cdr (window-margins)) nil)
      (set-window-margins nil 0 (- (window-body-width) fill-column))
    (set-window-margins nil 0 0)))

Writing

Avoiding weasel words

(use-package artbollocks-mode
  :init
  (progn
    (setq artbollocks-weasel-words-regex
          (concat "\\b" (regexp-opt
                         '("one of the"
                           "should"
                           "just"
                           "sort of"
                           "a lot"
                           "probably"
                           "maybe"
                           "perhaps"
                           "I think"
                           "really"
                           "pretty"
                           "nice"
                           "action"
                           "utilize"
                           "leverage") t) "\\b"))
    ;; Don't show the art critic words, or at least until I figure
    ;; out my own jargon
    (setq artbollocks-jargon nil)))

Unfill paragraph

I unfill paragraphs a lot because Wordpress likes adding extra <br> tags if I don't. (I should probably just tweak my Wordpress installation.)

  (defun sacha/unfill-paragraph (&optional region)
    "Takes a multi-line paragraph and makes it into a single line of text."
    (interactive (progn
                   (barf-if-buffer-read-only)
                   (list t)))
    (let ((fill-column (point-max)))
      (fill-paragraph nil region)))
(bind-key "M-Q" 'sacha/unfill-paragraph)

I never actually justify text, so I might as well change the way fill-paragraph works. With the code below, M-q will fill the paragraph normally, and C-u M-q will unfill it.

  (defun sacha/fill-or-unfill-paragraph (&optional unfill region)
    "Fill paragraph (or REGION).
  With the prefix argument UNFILL, unfill it instead."
    (interactive (progn
                   (barf-if-buffer-read-only)
                   (list (if current-prefix-arg 'unfill) t)))
    (let ((fill-column (if unfill (point-max) fill-column)))
      (fill-paragraph nil region)))
(bind-key "M-q" 'sacha/fill-or-unfill-paragraph)

Also, visual-line-mode is so much better than auto-fill-mode. It doesn't actually break the text into multiple lines - it only looks that way.

(remove-hook 'text-mode-hook #'turn-on-auto-fill)
(add-hook 'text-mode-hook 'turn-on-visual-line-mode)

Transpose

;; Transpose stuff with M-t
(bind-key "M-t" nil) ;; which used to be transpose-words
(bind-key "M-t l" 'transpose-lines)
(bind-key "M-t w" 'transpose-words)
(bind-key "M-t t" 'transpose-words)
(bind-key "M-t M-t" 'transpose-words)
(bind-key "M-t s" 'transpose-sexps)

Unicode

(defmacro sacha/insert-unicode (unicode-name)
  `(lambda () (interactive)
     (insert-char (cdr (assoc-string ,unicode-name (ucs-names))))))
(bind-key "C-x 8 s" (sacha/insert-unicode "ZERO WIDTH SPACE"))
(bind-key "C-x 8 S" (sacha/insert-unicode "SNOWMAN"))

Org

I use Org Mode to take notes, publish my blog, and do all sorts of stuff.

My files

#

Here are the Org files I use. I should probably organize them better. =)

organizer.org My main Org file. Inbox for M-x org-capture, tasks, weekly reviews, etc.
business.org Business-related notes and TODOs
people.org People-related tasks
evil-plans/index.org High-level goals
sharing/index.org Things to write about
decisions.org Pending, current, and reviewed decisions
blog.org Topic index for my blog
learning.org Learning plan
outline.org Huge outline of notes by category
tracking.org Temporary Org file for tracking various things
delegation.org Templates for assigning tasks - now using Google Docs instead
books.org Huge file with book notes
calendar.org Used to use this with ical2org, but have been having problems with ical2org lately; no longer use
ideal.org Planning ideal days
archive.org Archived subtrees
latin.org Latin notes
101things.org Old goals for 101 things in 1001 days
life.org Questions, processes, tools

Modules

Org has a whole bunch of optional modules. These are the ones I'm currently experimenting with.

  (setq org-modules '(org-bbdb 
                      org-gnus
                      org-drill
                      org-info
                      org-jsinfo
                      org-habit
                      org-irc
                      org-mouse
                      org-annotate-file
                      org-eval
                      org-expiry
                      org-interactive-query
                      org-man
                      org-panel
                      org-screen
                      org-toc))
(org-load-modules-maybe t)
(setq org-expiry-inactive-timestamps t)

Keyboard shortcuts

(bind-key "C-c r" 'org-capture)
(bind-key "C-c a" 'org-agenda)
(bind-key "C-c l" 'org-store-link)
(bind-key "C-c L" 'org-insert-link-global)
(bind-key "C-c O" 'org-open-at-point-global)
(bind-key "<f9> <f9>" 'org-agenda-list)
(bind-key "<f9> <f8>" (lambda () (interactive) (org-capture nil "r")))
(bind-key "C-TAB" 'org-cycle org-mode-map)
(bind-key "C-c v" 'org-show-todo-tree org-mode-map)
(bind-key "C-c C-r" 'org-refile org-mode-map)
(bind-key "C-c R" 'org-reveal org-mode-map)

append-next-kill is more useful to me than org-table-copy-region.

(eval-after-load 'org
  '(progn
     (bind-key "C-M-w" 'append-next-kill org-mode-map)))

I don't use the diary, but I do use the clock a lot.

(use-package org-agenda
  :init (bind-key "i" 'org-agenda-clock-in org-agenda-mode-map))

Navigation

From http://stackoverflow.com/questions/15011703/is-there-an-emacs-org-mode-command-to-jump-to-an-org-heading

(setq org-goto-interface 'outline
      org-goto-max-level 10)
(require 'imenu)
(bind-key "M-o" 'imenu)
(bind-key "C-c j" 'org-clock-goto) ;; jump to current task from anywhere
(bind-key "C-c C-w" 'org-refile)
(setq org-cycle-include-plain-lists 'integrate)

Link Org subtrees and navigate between them

The following code makes it easier for me to link trees with entries, as in http://sachachua.com/evil-plans

(defun sacha/org-follow-entry-link ()
  "Follow the defined link for this entry."
  (interactive)
  (if (org-entry-get (point) "LINK")
      (org-open-link-from-string (org-entry-get (point) "LINK"))
    (org-open-at-point)))

(bind-key "C-c o" 'sacha/org-follow-entry-link org-mode-map)

(defun sacha/org-link-projects (location)
  "Add link properties between the current subtree and the one specified by LOCATION."
  (interactive
   (list (let ((org-refile-use-cache nil))
     (org-refile-get-location "Location"))))
  (let ((link1 (org-store-link nil)) link2)
    (save-window-excursion
      (org-refile 4 nil location)
      (setq link2 (org-store-link nil))
      (org-set-property "LINK" link1))
    (org-set-property "LINK" link2)))

Taking notes

My org files are in my personal directory, which is actually a symlink to a directory in my Dropbox. That way, I can update my Org files from multiple computers.

(setq org-directory "~/personal")
(setq org-default-notes-file "~/personal/organizer.org")

This makes it easier to add links from outside.

(defun sacha/yank-more ()
  (interactive)
  (insert "[[")
  (yank)
  (insert "][more]]"))
(global-set-key (kbd "<f6>") 'sacha/yank-more)
Date trees

This quickly adds a same-level heading for the succeeding day.

(defun sacha/org-insert-heading-for-next-day ()
  "Insert a same-level heading for the following day."
  (interactive)
  (let ((new-date
         (seconds-to-time
          (+ 86400.0
             (float-time
              (org-read-date nil 'to-time (elt (org-heading-components) 4)))))))
    (org-insert-heading-after-current)
    (insert (format-time-string "%Y-%m-%d\n\n" new-date))))
Templates

I use org-capture templates to quickly jot down tasks, ledger entries, notes, and other semi-structured pieces of information.

(defvar sacha/org-basic-task-template "* TODO %^{Task}    
SCHEDULED: %^t
%?
:PROPERTIES:
:Effort: %^{effort|1:00|0:05|0:15|0:30|2:00|4:00}
:END:" "Basic task data")
(setq org-capture-templates
      `(("t" "Tasks" entry 
         (file+headline "~/personal/organizer.org" "Tasks")
         ,sacha/org-basic-task-template)
        ("e" "Emacs idea" entry
         (file+headline "~/code/dev/emacs-notes/tasks.org" "Emacs")
         "* TODO %^{Task}"
         :immediate-finish)
        ("b" "Business task" entry
         (file+headline "~/personal/business.org" "Tasks")
         ,sacha/org-basic-task-template)
        ("p" "People task" entry
         (file+headline "~/personal/people.org" "Tasks")
         ,sacha/org-basic-task-template)
        ("j" "Journal entry" plain
         (file+datetree+prompt "~/personal/journal.org")
         "%K - %a\n%i\n%?\n")
        ("db" "Done - Business" entry
         (file+headline "~/personal/business.org" "Tasks")
         "* DONE %^{Task}\nSCHEDULED: %^t\n%?")
        ("dp" "Done - People" entry
         (file+headline "~/personal/people.org" "Tasks")
         "* DONE %^{Task}\nSCHEDULED: %^t\n%?")
        ("dt" "Done - Task" entry
         (file+headline "~/personal/organizer.org" "Tasks")
         "* DONE %^{Task}\nSCHEDULED: %^t\n%?")
        ("q" "Quick note" item
         (file+headline "~/personal/organizer.org" "Quick notes"))
        ("l" "Ledger entries")
        ("lm" "MBNA" plain
         (file "~/personal/ledger")
         "%(org-read-date) %^{Payee}
  Liabilities:MBNA  
  Expenses:%^{Account}  $%^{Amount}
" :immediate-finish)
        ("ln" "No Frills" plain
         (file "~/personal/ledger")
         "%(let ((org-read-date-prefer-future nil)) (org-read-date)) * No Frills
  Liabilities:MBNA  
  Assets:Wayne:Groceries  $%^{Amount}
" :immediate-finish)    
        ("lc" "Cash" plain
         (file "~/personal/ledger")
         "%(org-read-date) * %^{Payee}
  Expenses:Cash 
  Expenses:%^{Account}  %^{Amount}
")             
        ("b" "Book" entry
         (file+datetree "~/personal/books.org" "Inbox")
         "* %^{Title}  %^g
%i
*Author(s):* %^{Author} \\\\
*ISBN:* %^{ISBN}

%?

*Review on:* %^t \\
%a
%U"
         :clock-in :clock-resume)
         ("c" "Contact" entry (file "~/personal/contacts.org")
          "* %(org-contacts-template-name)
:PROPERTIES:
:EMAIL: %(org-contacts-template-email)
:END:")
         ("n" "Daily note" table-line (file+olp "~/personal/organizer.org" "Daily notes")
          "| %u | %^{Note} |"
          :immediate-finish)
         ("r" "Notes" entry
          (file+datetree "~/personal/organizer.org")
          "* %?\n\n%i\n"
          )))
(bind-key "C-M-r" 'org-capture)
  • Allow refiling in the middle(ish) of a capture

    This lets me use C-c C-r to refile a capture and then jump to the new location. I wanted to be able to file tasks under projects so that they could inherit the QUANTIFIED property that I use to track time (and any Beeminder-related properties too), but I also wanted to be able to clock in on them.

    (defun sacha/org-capture-refile-and-jump ()
      (interactive)
      (org-capture-refile)
      (org-refile-goto-last-stored))
    (require 'org-capture)
    (bind-key "C-c C-r" 'sacha/org-capture-refile-and-jump org-capture-mode-map)
    
Refiling

org-refile lets you organize notes by typing in the headline to file them under.

(setq org-reverse-note-order t)
(setq org-refile-use-outline-path nil)
(setq org-refile-allow-creating-parent-nodes 'confirm)
(setq org-refile-use-cache nil)
(setq org-refile-targets '((org-agenda-files . (:maxlevel . 6))))
(setq org-blank-before-new-entry nil)
Estimating WPM

I'm curious about how fast I type some things.

(require 'org-clock)
(defun sacha/org-entry-wpm ()
  (interactive)
  (save-restriction
    (save-excursion
      (org-narrow-to-subtree)
      (goto-char (point-min))
      (let* ((words (count-words-region (point-min) (point-max)))
       (minutes (org-clock-sum-current-item))
       (wpm (/ words minutes)))
  (message "WPM: %d (words: %d, minutes: %d)" wpm words minutes)
  (kill-new (number-to-string wpm))))))

Managing tasks

Track TODO state

The parentheses indicate keyboard shortcuts that I can use to set the task state. @ and ! toggle logging.

(setq org-todo-keywords
 '((sequence
    "TODO(t)"  ; next action
    "TOBLOG(b)"  ; next action
    "STARTED(s)"
    "WAITING(w@/!)"
    "SOMEDAY(.)" "|" "DONE(x!)" "CANCELLED(c@)")
   (sequence "TODELEGATE(-)" "DELEGATED(d)" "COMPLETE(x)")))
(setq org-todo-keyword-faces
      '(("TODO" . (:foreground "green" :weight bold))
        ("DONE" . (:foreground "cyan" :weight bold))
        ("WAITING" . (:foreground "red" :weight bold))
        ("SOMEDAY" . (:foreground "gray" :weight bold))))
Projects

Projects are headings with the :project: tag, so we generally don't want that tag inherited, except when we display unscheduled tasks that don't belong to any projects.

(setq org-tags-exclude-from-inheritance '("project"))

This code makes it easy for me to focus on one project and its tasks.

(add-to-list 'org-speed-commands-user '("N" org-narrow-to-subtree))
(add-to-list 'org-speed-commands-user '("W" widen))
(defun sacha/org-agenda-for-subtree ()
  (interactive)
  (if (derived-mode-p 'org-agenda-mode)
    (let* ((marker (or (org-get-at-bol 'org-marker)
                       (org-agenda-error)))
           (hdmarker (or (org-get-at-bol 'org-hd-marker) marker))
           (pos (marker-position marker))
           (col (current-column))
           newhead)
      (org-with-remote-undo (marker-buffer marker)
        (with-current-buffer (marker-buffer marker)
          (widen)
          (let ((org-agenda-view-columns-initially t))
            (org-agenda nil "t" 'subtree)))))
    (let ((org-agenda-view-columns-initially t))
      (org-agenda nil "t" 'subtree))))
(add-to-list 'org-speed-commands-user '("T" sacha/org-agenda-for-subtree))

There's probably a proper way to do this, maybe with <. Oh, that would work nicely. < C-c a t too.

Tag tasks with GTD-ish contexts

This defines keyboard shortcuts for those, too.

(setq org-tag-alist '(("@work" . ?b) 
                      ("@home" . ?h) 
                      ("@writing" . ?w)
                      ("@errands" . ?e) 
                      ("@drawing" . ?d)
                      ("@coding" . ?c)
                      ("@phone" . ?p)
                      ("@reading" . ?r)
                      ("@computer" . ?l)
                      ("quantified" . ?q)
                      ("lowenergy" . ?0)
                      ("highenergy" . ?1)))
Enable filtering by effort estimates

That way, it's easy to see short tasks that I can finish.

(add-to-list 'org-global-properties
      '("Effort_ALL". "0:05 0:15 0:30 1:00 2:00 3:00 4:00"))
Track time
(setq org-clock-idle-time nil)
(setq org-log-done 'time)
(setq org-clock-persist t)
(org-clock-persistence-insinuate)
(setq org-clock-report-include-clocking-task t)
(defadvice org-clock-in (after sacha activate)
  "Mark STARTED when clocked in."
  (save-excursion
    (catch 'exit
      (cond
       ((derived-mode-p 'org-agenda-mode)
        (let* ((marker (or (org-get-at-bol 'org-marker)
                           (org-agenda-error)))
               (hdmarker (or (org-get-at-bol 'org-hd-marker) marker))
               (pos (marker-position marker))
               (col (current-column))
               newhead)
          (org-with-remote-undo (marker-buffer marker)
            (with-current-buffer (marker-buffer marker)
              (widen)
              (goto-char pos)
              (org-back-to-heading t)
              (if (org-get-todo-state)
                  (org-todo "STARTED"))))))
       (t (if (org-get-todo-state)
                  (org-todo "STARTED")))))))

Too many clock entries clutter up a heading.

(setq org-log-into-drawer "LOGBOOK")
(setq org-clock-into-drawer 1)
Habits

I like using org-habits to track consistency. My task names tend to be a bit long, though, so I've configured the graph column to show a little bit more to the right.

(setq org-habit-graph-column 80)
(setq org-habit-show-habits-only-for-today nil)

If you want to use habits, be sure to schedule your tasks and add a STYLE property with the value of habit to the tasks you want displayed.

Estimating tasks

From "Add an effort estimate on the fly when clocking in" on the Org Hacks page:

(add-hook 'org-clock-in-prepare-hook
          'sacha/org-mode-ask-effort)

(defun sacha/org-mode-ask-effort ()
  "Ask for an effort estimate when clocking in."
  (unless (org-entry-get (point) "Effort")
    (let ((effort
           (completing-read
            "Effort: "
            (org-entry-get-multivalued-property (point) "Effort"))))
      (unless (equal effort "")
        (org-set-property "Effort" effort)))))

Modifying org agenda so that I can display a subset of tasks

I want to create an agenda command that displays a list of tasks by context. That way, I can quickly preview a bunch of contexts and decide what I feel like doing the most.

(defvar sacha/org-agenda-limit-items nil "Number of items to show in agenda to-do views; nil if unlimited.")
(defadvice org-agenda-finalize-entries (around sacha activate)
  (if sacha/org-agenda-limit-items
      (progn
        (setq list (mapcar 'org-agenda-highlight-todo list))
        (setq ad-return-value
              (subseq list 0 sacha/org-agenda-limit-items))
        (when org-agenda-before-sorting-filter-function
          (setq list (delq nil (mapcar org-agenda-before-sorting-filter-function list))))
        (setq ad-return-value
              (mapconcat 'identity
                         (delq nil 
                               (subseq
                                (sort list 'org-entries-lessp)
                                0
                                sacha/org-agenda-limit-items))
                         "\n")))
    ad-do-it))

Flexible scheduling of tasks

I (theoretically) want to be able to schedule tasks for dates like the first Saturday of every month. Fortunately, someone else has figured that out!

;; Get this from https://raw.github.com/chenfengyuan/elisp/master/next-spec-day.el
(load "~/elisp/next-spec-day.el" t)

Org agenda

Basic configuration

I have quite a few Org files, but I keep my agenda items and TODOs in only a few of them them for faster scanning.

(setq org-agenda-files
      (delq nil
            (mapcar (lambda (x) (and (file-exists-p x) x))
                    '("~/personal/organizer.org"
                      "~/personal/people.org"
                      "~/personal/business.org"
                      "~/Dropbox/public/sharing/index.org"
                      "~/dropbox/public/learning.org"
                      "~/code/dev/emacs-notes/tasks.org"
                      "~/sachac.github.io/evil-plans/index.org"
                      "~/personal/cooking.org"
                      "~/personal/routines.org"))))

I like looking at two days at a time when I plan using the Org agenda. I want to see my log entries, but I don't want to see scheduled items that I've finished. I like seeing a time grid so that I can get a sense of how appointments are spread out.

(setq org-agenda-span 2)
(setq org-agenda-sticky nil)
(setq org-agenda-show-log t)
(setq org-agenda-skip-scheduled-if-done t)
(setq org-agenda-skip-deadline-if-done t)
(setq org-agenda-skip-deadline-prewarning-if-scheduled 'pre-scheduled)
(setq org-agenda-time-grid
      '((daily today require-timed)
       "----------------"
       (800 1000 1200 1400 1600 1800)))
(setq org-columns-default-format "%50ITEM %12SCHEDULED %TODO %3PRIORITY %Effort{:} %TAGS")

Some other keyboard shortcuts:

(bind-key "Y" 'org-agenda-todo-yesterday org-agenda-mode-map)
Starting my weeks on Saturday

I like looking at weekends as week beginnings instead, so I want the Org agenda to start on Saturdays.

(setq org-agenda-start-on-weekday 6)
Display projects with associated subtasks

I wanted a view that showed projects with a few subtasks underneath them. Here's a sample of the output:

Headlines with TAGS match: +PROJECT
Press `C-u r' to search again with new search string
  organizer:  Set up communication processes for Awesome Foundation Toronto
  organizer:  TODO Announce the next pitch night
  organizer:  TODO Follow up with the winner of the previous pitch night for any news to include in the updates

  organizer:  Tidy up the house so that I can find things quickly
  organizer:  TODO Inventory all the things in closets and boxes         :@home:
  organizer:  TODO Drop things off for donation                       :@errands:

  organizer:  Learn how to develop for Android devices
(defun sacha/org-agenda-project-agenda ()
  "Return the project headline and up to `sacha/org-agenda-limit-items' tasks."
  (save-excursion
    (let* ((marker (org-agenda-new-marker))
           (heading
            (org-agenda-format-item "" (org-get-heading) (org-get-category) nil))
           (org-agenda-restrict t)
           (org-agenda-restrict-begin (point))
           (org-agenda-restrict-end (org-end-of-subtree 'invisible))
           ;; Find the TODO items in this subtree
           (list (org-agenda-get-day-entries (buffer-file-name) (calendar-current-date) :todo)))
      (org-add-props heading
          (list 'face 'defaults
                'done-face 'org-agenda-done
                'undone-face 'default
                'mouse-face 'highlight
                'org-not-done-regexp org-not-done-regexp
                'org-todo-regexp org-todo-regexp
                'org-complex-heading-regexp org-complex-heading-regexp
                'help-echo
                (format "mouse-2 or RET jump to org file %s"
                        (abbreviate-file-name
                         (or (buffer-file-name (buffer-base-buffer))
                             (buffer-name (buffer-base-buffer))))))
        'org-marker marker
        'org-hd-marker marker
        'org-category (org-get-category)
        'type "tagsmatch")
      (concat heading "\n"
              (org-agenda-finalize-entries list)))))

  (defun sacha/org-agenda-projects-and-tasks (match)
    "Show TODOs for all `org-agenda-files' headlines matching MATCH."
    (interactive "MString: ")
    (let ((todo-only nil))
      (if org-agenda-overriding-arguments
          (setq todo-only (car org-agenda-overriding-arguments)
                match (nth 1 org-agenda-overriding-arguments)))
      (let* ((org-tags-match-list-sublevels
              org-tags-match-list-sublevels)
             (completion-ignore-case t)
             rtn rtnall files file pos matcher
             buffer)
        (when (and (stringp match) (not (string-match "\\S-" match)))
          (setq match nil))
        (setq matcher (org-make-tags-matcher match)
              match (car matcher) matcher (cdr matcher))
        (catch 'exit
          (if org-agenda-sticky
              (setq org-agenda-buffer-name
                    (if (stringp match)
                        (format "*Org Agenda(%s:%s)*"
                                (or org-keys (or (and todo-only "M") "m")) match)
                      (format "*Org Agenda(%s)*" (or (and todo-only "M") "m")))))
          (org-agenda-prepare (concat "TAGS " match))
          (org-compile-prefix-format 'tags)
          (org-set-sorting-strategy 'tags)
          (setq org-agenda-query-string match)
          (setq org-agenda-redo-command
                (list 'org-tags-view `(quote ,todo-only)
                      (list 'if 'current-prefix-arg nil `(quote ,org-agenda-query-string))))
          (setq files (org-agenda-files nil 'ifmode)
                rtnall nil)
          (while (setq file (pop files))
            (catch 'nextfile
              (org-check-agenda-file file)
              (setq buffer (if (file-exists-p file)
                               (org-get-agenda-file-buffer file)
                             (error "No such file %s" file)))
              (if (not buffer)
                  ;; If file does not exist, error message to agenda
                  (setq rtn (list
                             (format "ORG-AGENDA-ERROR: No such org-file %s" file))
                        rtnall (append rtnall rtn))
                (with-current-buffer buffer
                  (unless (derived-mode-p 'org-mode)
                    (error "Agenda file %s is not in `org-mode'" file))
                  (save-excursion
                    (save-restriction
                      (if org-agenda-restrict
                          (narrow-to-region org-agenda-restrict-begin
                                            org-agenda-restrict-end)
                        (widen))
                      (setq rtn (org-scan-tags 'sacha/org-agenda-project-agenda matcher todo-only))
                      (setq rtnall (append rtnall rtn))))))))
          (if org-agenda-overriding-header
              (insert (org-add-props (copy-sequence org-agenda-overriding-header)
                          nil 'face 'org-agenda-structure) "\n")
            (insert "Headlines with TAGS match: ")
            (add-text-properties (point-min) (1- (point))
                                 (list 'face 'org-agenda-structure
                                       'short-heading
                                       (concat "Match: " match)))
            (setq pos (point))
            (insert match "\n")
            (add-text-properties pos (1- (point)) (list 'face 'org-warning))
            (setq pos (point))
            (unless org-agenda-multi
              (insert "Press `C-u r' to search again with new search string\n"))
            (add-text-properties pos (1- (point)) (list 'face 'org-agenda-structure)))
          (org-agenda-mark-header-line (point-min))
          (when rtnall
            (insert (mapconcat 'identity rtnall "\n") ""))
          (goto-char (point-min))
          (or org-agenda-multi (org-agenda-fit-window-to-buffer))
          (add-text-properties (point-min) (point-max)
                               `(org-agenda-type tags
                                                 org-last-args (,todo-only ,match)
                                                 org-redo-cmd ,org-agenda-redo-command
                                                 org-series-cmd ,org-cmd))
          (org-agenda-finalize)
          (setq buffer-read-only t)))))
Org agenda custom commands

There are quite a few custom commands here, and I often forget to use them. =) But it's good to define them, and over time, I'll get the hang of using these more!

Key Description
. What am I waiting for?
T Not really an agenda command - shows the to-do tree in the current file
b Shows business-related tasks
o Shows personal tasks and miscellaneous tasks (o: organizer)
w Show all tasks for the upcoming week
W Show all tasks for the upcoming week, aside from the routine ones
g … Show tasks by context: b - business; c - coding; w - writing; p - phone; d - drawing, h - home
0 Show common contexts with up to 3 tasks each, so that I can choose what I feel like working on
) (shift-0) Show common contexts with all the tasks associated with them
9 Show common contexts with up to 3 unscheduled tasks each
( (shift-9) Show common contexts with all the unscheduled tasks associated with them
d Timeline for today (agenda, clock summary)
u Unscheduled tasks to do if I have free time
U Unscheduled tasks that are not part of projects
P Tasks by priority
p My projects
2 Projects with tasks
  (defvar sacha/org-agenda-contexts
    '((tags-todo "+@phone")
      (tags-todo "+@work")
      (tags-todo "+@drawing")
      (tags-todo "+@coding")
      (tags-todo "+@writing")
      (tags-todo "+@computer")
      (tags-todo "+@home")
      (tags-todo "+@errands"))
    "Usual list of contexts.")
  (defun sacha/org-agenda-skip-scheduled ()
    (org-agenda-skip-entry-if 'scheduled 'deadline 'regexp "\n]+>"))
  (setq org-agenda-custom-commands
        `(("T" tags-todo "TODO=\"TODO\"-goal-routine-SCHEDULED={.+}")
          ("b" todo ""
           ((org-agenda-files '("~/personal/business.org"))))
          ("B" todo ""
           ((org-agenda-files '("~/Dropbox/books"))))
          ("o" todo ""
           ((org-agenda-files '("~/personal/organizer.org"))))
          ("c" todo ""
           ((org-agenda-prefix-format "")
            (org-agenda-cmp-user-defined 'sacha/org-sort-agenda-items-todo)
            (org-agenda-view-columns-initially t)
            ))
          ;; Weekly review
          ("w" "Weekly review" agenda ""
           ((org-agenda-span 7)
            (org-agenda-log-mode 1)))
          ("W" "Weekly review sans routines" agenda "" 
           ((org-agenda-span 7) 
            (org-agenda-log-mode 1)
            (org-agenda-tag-filter-preset '("-routine"))))
          ("2" "Bi-weekly review" agenda "" ((org-agenda-span 14) (org-agenda-log-mode 1)))
          ("gb" "Business" todo ""  
           ((org-agenda-files '("~/personal/business.org"))
            (org-agenda-view-columns-initially t)))
          ("gc" "Coding" tags-todo "@coding" 
           ((org-agenda-view-columns-initially t)))
          ("gw" "Writing" tags-todo "@writing"
           ((org-agenda-view-columns-initially t)))
          ("gp" "Phone" tags-todo "@phone"
           ((org-agenda-view-columns-initially t)))
          ("gd" "Drawing" tags-todo "@drawing"
           ((org-agenda-view-columns-initially t)))
          ("gh" "Home" tags-todo "@home"
           ((org-agenda-view-columns-initially t)))
          ("ge" "Errands" tags-todo "@errands"
           ((org-agenda-view-columns-initially t)))
          ("0" "Top 3 by context"
           ,sacha/org-agenda-contexts
           ((org-agenda-sorting-strategy '(priority-up effort-down))
            (sacha/org-agenda-limit-items 3)))
          (")" "All by context"
           ,sacha/org-agenda-contexts
           ((org-agenda-sorting-strategy '(priority-down effort-down))
            (sacha/org-agenda-limit-items nil)))
          ("9" "Unscheduled top 3 by context"
           ,sacha/org-agenda-contexts
           ((org-agenda-skip-function 'sacha/org-agenda-skip-scheduled)
            (org-agenda-sorting-strategy '(priority-down effort-down))
            (sacha/org-agenda-limit-items 3)))
          ("(" "All unscheduled by context"
           ,sacha/org-agenda-contexts
           ((org-agenda-skip-function 'sacha/org-agenda-skip-scheduled)
            (org-agenda-sorting-strategy '(priority-down effort-down))
            ))
          ("d" "Timeline for today" ((agenda "" ))
           ((org-agenda-ndays 1)
            (org-agenda-show-log t)
            (org-agenda-log-mode-items '(clock closed))
            (org-agenda-clockreport-mode t)
            (org-agenda-entry-types '())))
          ("." "Waiting for" todo "WAITING")
          ("u" "Unscheduled tasks" tags-todo "-someday-TODO=\"SOMEDAY\"-TODO=\"DELEGATED\"-TODO=\"WAITING\"-project"
           ((org-agenda-skip-function 'sacha/org-agenda-skip-scheduled)
            (org-agenda-view-columns-initially t)
            (org-tags-exclude-from-inheritance '("project"))
            (org-agenda-overriding-header "Unscheduled TODO entries: ")
            (org-columns-default-format "%50ITEM %TODO %3PRIORITY %Effort{:} %TAGS")
            (org-agenda-sorting-strategy '(todo-state-up priority-down effort-up tag-up category-keep))))
          ("U" "Unscheduled tasks outside projects" tags-todo "-project"
           ((org-agenda-skip-function 'sacha/org-agenda-skip-scheduled)
            (org-tags-exclude-from-inheritance nil)
            (org-agenda-view-columns-initially t)
            (org-agenda-overriding-header "Unscheduled TODO entries outside projects: ")
            (org-agenda-sorting-strategy '(todo-state-up priority-down tag-up category-keep effort-down))))
          ("P" "By priority"
           ((tags-todo "+PRIORITY=\"A\"")
            (tags-todo "+PRIORITY=\"B\"")
            (tags-todo "+PRIORITY=\"\"")
            (tags-todo "+PRIORITY=\"C\""))
           ((org-agenda-prefix-format "%-10c %-10T %e ")
            (org-agenda-sorting-strategy '(priority-down tag-up category-keep effort-down))))
          ("pp" tags "+project-someday-TODO=\"DONE\""
           ((org-tags-exclude-from-inheritance '("project"))
            (org-agenda-sorting-strategy '(priority-down tag-up category-keep effort-down))))
          ("p." tags "+project-TODO=\"DONE\""
           ((org-tags-exclude-from-inheritance '("project"))
            (org-agenda-sorting-strategy '(priority-down tag-up category-keep effort-down))))
          ("S" tags-todo "TODO=\"STARTED\"")
          ("2" "List projects with tasks" sacha/org-agenda-projects-and-tasks
           "+PROJECT"
             ((sacha/org-agenda-limit-items 3)))))
(bind-key "<apps> a" 'org-agenda)
Make it easy to mark a task as done

Great for quickly going through the to-do list. Gets rid of one extra keystroke. ;)

(defun sacha/org-agenda-done (&optional arg)
  "Mark current TODO as done.
This changes the line at point, all other lines in the agenda referring to
the same tree node, and the headline of the tree node in the Org-mode file."
  (interactive "P")
  (org-agenda-todo "DONE"))
;; Override the key definition for org-exit
(define-key org-agenda-mode-map "x" 'sacha/org-agenda-done)
Make it easy to mark a task as done and create a follow-up task
  (defun sacha/org-agenda-mark-done-and-add-followup ()
    "Mark the current TODO as done and add another task after it.
Creates it at the same level as the previous task, so it's better to use
this with to-do items than with projects or headings."
    (interactive)
    (org-agenda-todo "DONE")
    (org-agenda-switch-to)
    (org-capture 0 "t"))
;; Override the key definition
(define-key org-agenda-mode-map "X" 'sacha/org-agenda-mark-done-and-add-followup)
Capture something based on the agenda
(defun sacha/org-agenda-new ()
  "Create a new note or task at the current agenda item.
Creates it at the same level as the previous task, so it's better to use
this with to-do items than with projects or headings."
  (interactive)
  (org-agenda-switch-to)
  (org-capture 0))
;; New key assignment
(define-key org-agenda-mode-map "N" 'sacha/org-agenda-new)
Sorting by date and priority
(setq org-agenda-sorting-strategy
      '((agenda time-up priority-down tag-up effort-up category-keep)
        (todo user-defined-up todo-state-up priority-down effort-up)
        (tags user-defined-up)
        (search category-keep)))
(setq org-agenda-cmp-user-defined 'sacha/org-sort-agenda-items-user-defined)    
(require 'cl)
(defun sacha/org-get-context (txt)
  "Find the context."
  (car (member-if
        (lambda (item) (string-match "@" item))
        (get-text-property 1 'tags txt))))

(defun sacha/org-compare-dates (a b)
  "Return 1 if A should go after B, -1 if B should go after A, or 0 if a = b."
  (cond
   ((and (= a 0) (= b 0)) nil)
   ((= a 0) 1)
   ((= b 0) -1)
   ((> a b) 1)
   ((< a b) -1)
   (t nil)))

(defun sacha/org-complete-cmp (a b)
  (let* ((state-a (or (get-text-property 1 'todo-state a) ""))
         (state-b (or (get-text-property 1 'todo-state b) "")))
    (or
     (if (member state-a org-done-keywords-for-agenda) 1)
     (if (member state-b org-done-keywords-for-agenda) -1))))

(defun sacha/org-date-cmp (a b)
  (let* ((sched-a (or (get-text-property 1 'org-scheduled a) 0))
         (sched-b (or (get-text-property 1 'org-scheduled b) 0))
         (deadline-a (or (get-text-property 1 'org-deadline a) 0))
         (deadline-b (or (get-text-property 1 'org-deadline b) 0)))
    (or
     (sacha/org-compare-dates
      (sacha/org-min-date sched-a deadline-a)
      (sacha/org-min-date sched-b deadline-b)))))

(defun sacha/org-min-date (a b)
  "Return the smaller of A or B, except for 0."
  (funcall (if (and (> a 0) (> b 0)) 'min 'max) a b))

(defun sacha/org-sort-agenda-items-user-defined (a b)
  ;; compare by deadline, then scheduled date; done tasks are listed at the very bottom
  (or
   (sacha/org-complete-cmp a b)
   (sacha/org-date-cmp a b)))

(defun sacha/org-context-cmp (a b)
  "Compare CONTEXT-A and CONTEXT-B."
  (let ((context-a (sacha/org-get-context a))
        (context-b (sacha/org-get-context b)))
    (cond
     ((null context-a) +1)
     ((null context-b) -1)
     ((string< context-a context-b) -1)
     ((string< context-b context-a) +1)
     (t nil))))

(defun sacha/org-sort-agenda-items-todo (a b)
  (or
   (org-cmp-time a b)
   (sacha/org-complete-cmp a b)
   (sacha/org-context-cmp a b)
   (sacha/org-date-cmp a b)
   (org-cmp-todo-state a b)
   (org-cmp-priority a b)
   (org-cmp-effort a b)))
Preventing things from falling through the cracks

This helps me keep track of unscheduled tasks, because I sometimes forget to assign tasks a date. I also want to keep track of stuck projects.

(defun sacha/org-agenda-list-unscheduled (&rest ignore)
  "Create agenda view for tasks that are unscheduled and not done."
  (let* ((org-agenda-todo-ignore-with-date t)
   (org-agenda-overriding-header "List of unscheduled tasks: "))
    (org-agenda-get-todos)))
(setq org-stuck-projects
      '("+PROJECT-MAYBE-DONE"
        ("TODO")
        nil
        "\\<IGNORE\\>"))

Weekly review

I regularly post weekly reviews to keep track of what I'm done, remind me to plan for the upcoming week, and list blog posts, sketches, and links. I

I want to try out grouping tasks by topic first, then breaking it down into previous/next week.

(defvar sacha/weekly-review-line-regexp 
  "^  \\([^:]+\\): +\\(Sched[^:]+: +\\)?TODO \\(.*?\\)\\(?:[      ]+\\(:[[:alnum:]_@#%:]+:\\)\\)?[        ]*$"
  "Regular expression matching lines to include.")
(defvar sacha/weekly-done-line-regexp 
  "^  \\([^:]+\\): +.*?\\(?:Clocked\\|Closed\\):.*?\\(?:TODO\\|DONE\\) \\(.*?\\)\\(?:[       ]+\\(:[[:alnum:]_@#%:]+:\\)\\)?[        ]*$"
  "Regular expression matching lines to include as completed tasks.")

(defun sacha/quantified-get-hours (category time-summary)
  "Return the number of hours based on the time summary."
  (if (stringp category)
      (if (assoc category time-summary) (/ (cdr (assoc category time-summary)) 3600.0) 0)
    (apply '+ (mapcar (lambda (x) (sacha/quantified-get-hours x time-summary)) category))))

(defun sacha/org-summarize-focus-areas ()
  "Summarize previous and upcoming tasks as a list."
  (interactive)
  (let ((base-date (apply 'encode-time (org-read-date-analyze "-fri" nil '(0 0 0))))
        (line-re sacha/weekly-review-line-regexp)
        (done-re sacha/weekly-done-line-regexp)
        business relationships life business-next relationships-next life-next string 
        start end time-summary biz-time)
    (setq start (format-time-string "%Y-%m-%d" (days-to-time (- (time-to-number-of-days base-date) 6))))
    (setq end (format-time-string "%Y-%m-%d" (days-to-time (1+ (time-to-number-of-days base-date)))))
    (setq time-summary (quantified-summarize-time start end))
    (setq biz-time (sacha/quantified-get-hours "Business" time-summary))
    (save-window-excursion
      (org-agenda nil "W")
      (setq string (buffer-string))
      (with-temp-buffer
        (insert string)
        (goto-char (point-min))
        (while (re-search-forward line-re nil t)
          (cond
           ((string= (match-string 1) "routines") nil) ; skip routine tasks
           ((or (string= (match-string 1) "business") (string= (match-string 1) "tasks"))
            (add-to-list 'business-next (concat "  - [ ] " (match-string 3))))
           ((string= (match-string 1) "people")
            (add-to-list 'relationships-next (concat "  - [ ] " (match-string 3))))
           (t (add-to-list 'life-next (concat "  - [ ] " (match-string 3))))))))
    (save-window-excursion
      (org-agenda nil "W")
      (org-agenda-later -1)
      (org-agenda-log-mode 16)
      (setq string (buffer-string))
      ;; Get any completed tasks from the current week as well
      (org-agenda-later 1)
      (org-agenda-log-mode 16)
      (setq string (concat string "\n" (buffer-string)))
      (with-temp-buffer
        (insert string)
        (goto-char (point-min))
        (while (re-search-forward done-re nil t)
          (cond
           ((string= (match-string 1) "routines") nil) ; skip routine tasks
           ((or (string= (match-string 1) "business") (string= (match-string 1) "tasks"))
            (add-to-list 'business (concat "  - [X] " (match-string 2))))
           ((string= (match-string 1) "people")
            (add-to-list 'relationships (concat "  - [X] " (match-string 2))))
           (t (add-to-list 'life (concat "  - [X] " (match-string 2))))))))
    (setq string
          (concat
           (format "- *Business* (%.1fh - %d%%)\n" biz-time (/ biz-time 1.68))
           (mapconcat 'identity (sort business 'string<) "\n") "\n"
           (mapconcat 'identity (sort business-next 'string<) "\n")
           "\n"
           (format "  - *Earn* (%.1fh - %d%% of Business)\n"
                   (sacha/quantified-get-hours "Business - Earn" time-summary)
                   (/ (sacha/quantified-get-hours "Business - Earn" time-summary) (* 0.01 biz-time)))
           (format "  - *Build* (%.1fh - %d%% of Business)\n"
                   (sacha/quantified-get-hours "Business - Build" time-summary)
                   (/ (sacha/quantified-get-hours "Business - Build" time-summary) (* 0.01 biz-time)))
           (format "    - *Drawing* (%.1fh)\n"
                   (sacha/quantified-get-hours '("Business - Build - Drawing"
                                                 "Business - Build - Book review")  time-summary))
           (format "    - *Delegation* (%.1fh)\n"
                   (sacha/quantified-get-hours "Business - Build - Delegation" time-summary))
           (format "    - *Packaging* (%.1fh)\n"
                   (sacha/quantified-get-hours "Business - Build - Packaging" time-summary))
           (format "    - *Paperwork* (%.1fh)\n"
                   (sacha/quantified-get-hours "Business - Build - Paperwork"  time-summary))
           (format "  - *Connect* (%.1fh - %d%% of Business)\n"
                   (sacha/quantified-get-hours "Business - Connect" time-summary)
                   (/ (sacha/quantified-get-hours "Business - Connect" time-summary) (* 0.01 biz-time)))
           (format "- *Relationships* (%.1fh - %d%%)\n"
                   (sacha/quantified-get-hours '("Discretionary - Social"
                                                 "Discretionary - Family") time-summary)
                   (/ (sacha/quantified-get-hours '("Discretionary - Social"
                                                    "Discretionary - Family") time-summary) 1.68))
           (mapconcat 'identity (sort relationships 'string<) "\n") "\n"
           (mapconcat 'identity (sort relationships-next 'string<) "\n")
           "\n"
           (format "- *Discretionary - Productive* (%.1fh - %d%%)\n"
                   (sacha/quantified-get-hours "Discretionary - Productive" time-summary)
                   (/ (sacha/quantified-get-hours "Discretionary - Productive" time-summary) 1.68))
           (format "  - *Emacs* (%.1fh - %d%% of all)\n"
                   (sacha/quantified-get-hours "Discretionary - Productive - Emacs" time-summary)
                   (/ (sacha/quantified-get-hours "Discretionary - Productive - Emacs" time-summary) 1.68))
           (mapconcat 'identity (sort life 'string<) "\n") "\n"
           (mapconcat 'identity (sort life-next 'string<) "\n") "\n"
           (format "  - *Writing* (%.1fh)\n"
                   (sacha/quantified-get-hours "Discretionary - Productive - Writing" time-summary))
           (format "- *Discretionary - Play* (%.1fh - %d%%)\n"
                   (sacha/quantified-get-hours "Discretionary - Play" time-summary)
                   (/ (sacha/quantified-get-hours "Discretionary - Play" time-summary) 1.68))
                                        ;                 (format "- *Discretionary - Travel* (%.1fh - %d%%)\n"
                                        ;                         (sacha/quantified-get-hours "Discretionary - Travel" time-summary)
                                        ;                         (/ (sacha/quantified-get-hours "Discretionary - Travel" time-summary) 1.68))
           (format "- *Personal routines* (%.1fh - %d%%)\n"
                   (sacha/quantified-get-hours "Personal" time-summary)
                   (/ (sacha/quantified-get-hours "Personal" time-summary) 1.68))
           (format "- *Unpaid work* (%.1fh - %d%%)\n"
                   (sacha/quantified-get-hours "Unpaid work" time-summary)
                   (/ (sacha/quantified-get-hours "Unpaid work" time-summary) 1.68))
           (format "- *Sleep* (%.1fh - %d%% - average of %.1f per day)\n"
                   (sacha/quantified-get-hours "Sleep" time-summary)
                   (/ (sacha/quantified-get-hours "Sleep" time-summary) 1.68)
                   (/ (sacha/quantified-get-hours "Sleep" time-summary) 7)
                   )))
    (if (called-interactively-p 'any)
        (insert string)
      string)))

I use this to put together a quick summary of how I spent my time.

The following code makes it easy to add a line:

(defun sacha/org-add-line-item-task (task)
  (interactive "MTask: ")
  (org-insert-heading)
  (insert "[ ] " task)
  (let ((org-capture-entry '("t" "Tasks" entry
                             (file+headline "~/personal/organizer.org" "Tasks")
                             "")))
    (org-capture nil "t")
    (insert "TODO " task "\nSCHEDULED: <" (org-read-date) ">")))
;(define-key org-mode-map (kbd "C-c t") 'sacha/org-add-line-item-task)

Now we put it all together…

(defun sacha/org-prepare-weekly-review ()
  "Prepare weekly review template."
  (interactive)
  (let ((base-date (apply 'encode-time (org-read-date-analyze "-fri" nil '(0 0 0))))
        start end)
    (setq start (format-time-string "%Y-%m-%d" (days-to-time (- (time-to-number-of-days base-date) 6))))
    (setq end (format-time-string "%Y-%m-%d" (days-to-time (1+ (time-to-number-of-days base-date)))))
      (insert
       (concat
        "*** Weekly review: Week ending " (format-time-string "%B %e, %Y" base-date) "  :weekly:\n"
        "*Blog posts*\n\n"
        "*Sketches*\n\n" 
        (sacha/flickr-export-and-extract start end) "\n"
        "*Link round-up*\n\n"
        (sacha/evernote-export-and-extract start end)
        "\n\n*Focus areas and time review*\n\n"
        (sacha/org-summarize-focus-areas)
        "\n"))))
Flickr extract
(defun sacha/flickr-extract-links-for-review (filename start end)
  "Extract Flickr titles and URLs from FILENAME from START to END.
     The file should be a CSV downloaded by the Flickr metadata downloader.
     Start date and end date should be strings in the form yyyy-mm-dd."
  (interactive (list (read-file-name "File: ")
                          (org-read-date)
                          (org-read-date)))
       (require 'csv)
       (let (list)
         (with-temp-buffer
           (insert-file-contents filename)
           (goto-char (point-min))
           (setq list
                 (mapconcat
                  (lambda (x) (concat "- [[" (car x) "][" (cdr x) "]]"))
                  (sort
                   (delq nil
                         (mapcar (lambda (x)
                                   (let ((title (cdr (assoc "FileName" x))))
                                     (if (and (not (string< title start))
                                              (string< title end))
                                         (cons (cdr (assoc "URL" x)) title))))
                                 (csv-parse-buffer t)))
                   (lambda (a b) (string<  (cdr a) (cdr b))))
                  "\n"))
           (setq list
                 (replace-regexp-in-string "\\([0-9]+\\)-\\([0-9]+\\)-\\([0-9]+\\)"
                                           "\\1.\\2.\\3" list))
           (setq list
                 (replace-regexp-in-string "\\[\"" "[" list))
           (setq list
                 (replace-regexp-in-string "\"
" "" (replace-regexp-in-string "\"\\]" "]" list))))
         (if (called-interactively-p 'any)
             (insert list)
           list)))

     (defun sacha/flickr-export-and-extract (start end &optional do-insert)
       "Download Flickr metadata and extract the relevant part."
       (interactive (list (org-read-date) (org-read-date) t))
       (shell-command "c:/sacha/dropbox/bin/flickr.bat")
       (if do-insert
           (insert (sacha/flickr-extract-links-for-review "c:/sacha/dropbox/bin/flickr_metadata.csv" start end))
         (sacha/flickr-extract-links-for-review "c:/sacha/dropbox/bin/flickr_metadata.csv" start end)))
Link-related convenience functions
(defun kensanata/resolve-redirect (url)
  "Resolve shortened URL by launching `curl --head' and parsing the result."
  (let* ((curl (shell-command-to-string
                (format "curl --silent --head %s" url)))
         (location (when (and (string-match "^HTTP/1\.1 301" curl)
                              (string-match "^Location: \\(.*\\)" curl))
                     (match-string 1 curl))))
    (or location url)))

(defun sacha/resolve-urls-in-region (beg end)
  "Expand URLs between BEG and END."
  (interactive "r")
  (save-excursion
    (save-restriction
      (narrow-to-region beg end)
      (goto-char (point-min))
      (while (re-search-forward org-bracket-link-regexp nil t)
        (replace-match (save-match-data (kensanata/resolve-redirect
                                         (match-string 1))) t t nil 1))
      (goto-char (point-min))
      (while (re-search-forward org-link-re-with-space nil t)
        (replace-match (save-match-data (kensanata/resolve-redirect
                                         (match-string 0))) t t nil)))))

(defun sacha/open-urls-in-region (beg end)
  "Open URLs between BEG and END."
  (interactive "r")
  (save-excursion
    (save-restriction
      (narrow-to-region beg end)
      (goto-char (point-min))
      (while (re-search-forward org-plain-link-re nil t)
        (org-open-at-point)))))
Evernote-related extract
(defun sacha/evernote-export-and-extract (start-date end-date)
  "Extract notes created on or after START-DATE and before END-DATE."
  (let ((filename "c:/sacha/tmp/Evernote.enex"))
    (call-process 
     "c:/Program Files (x86)/Evernote/Evernote/enscript.exe"
     nil t t
     "exportNotes"
     "/q" (concat
           " tag:roundup"
           " created:" (replace-regexp-in-string "-" "" start-date)
           " -created:" (replace-regexp-in-string "-" "" end-date))
     "/f" filename)
    (sacha/evernote-extract-links-for-review filename)))

(defun sacha/evernote-extract-links-for-review (filename)
  "Extract note names and URLs from FILENAME.
     The file should be an ENEX export."
  (interactive (list (read-file-name "File: ")
                     (org-read-date)
                     (org-read-date)))
  (let (list)
    (with-temp-buffer
      (insert-file-contents filename)
      (goto-char (point-min))
      (while (re-search-forward "<title>\\(.+?\\)</title>\\(.*?\n\\)*?.*?href=\"\\(.*?\\)\"" nil t)
        (setq list
              (cons
               (cons
                (match-string-no-properties 1)
                (match-string-no-properties 3)) list))))
    (setq list
          (mapconcat (lambda (x)
                       (concat "- [["
                               (kensanata/resolve-redirect (cdr x))
                               "][" (car x) "]]: ")) list "\n"))
          (if (called-interactively-p 'any)
              (insert list)
            list)))

Moving lines around

This makes it easier to reorganize lines in my weekly review.

(defun sacha/org-move-line-to-destination ()
  "Moves the current list item to <<destination>> in the current buffer."
  (interactive)
  (save-window-excursion
    (save-excursion
      (let ((string
             (buffer-substring-no-properties
              (line-beginning-position) (line-end-position))))
        (delete-region (line-beginning-position) (1+ (line-end-position)))
        (goto-char (point-min))
        (re-search-forward "<<destination>>" nil t)
        (insert "\n" (make-string (- (match-beginning 0) (line-beginning-position)) ?\ ) (s-trim string))))))
(bind-key "C-c d" 'sacha/org-move-line-to-destination org-mode-map)

Monthly reviews

I want to be able to see what I worked on in a month so that I can write my monthly reviews. This code makes it easy to display a month's clocked tasks and time. I haven't been particularly thorough in tracking time before, but now that I have a shortcut that logs in Quantified Awesome as well as in Org, I should end up clocking more.

(defun sacha/org-review-month (start-date)
  "Review the month's clocked tasks and time."
  (interactive (list (org-read-date)))
  ;; Set to the beginning of the month
  (setq start-date (concat (substring start-date 0 8) "01"))
  (let ((org-agenda-show-log t)
        (org-agenda-start-with-log-mode t)
        (org-agenda-start-with-clockreport-mode t)
        (org-agenda-clockreport-parameter-plist '(:link t :maxlevel 3)))
    (org-agenda-list nil start-date 'month)))

Viewing, navigating, and editing the Org tree

I often cut and paste subtrees. This makes it easier to cut something and paste it elsewhere in the hierarchy.

(eval-after-load 'org
  '(progn
     (bind-key "C-c k" 'org-cut-subtree org-mode-map)
     (setq org-yank-adjusted-subtrees t)))

Fix timestamps in Flickr links

Photosync (for syncing with Flickr) can't deal with periods in filenames. Org gets confused with dashes in timestamps that are in link text. Solution? Save files with dashes, then use a little code to replace dashes in the region.

(defun sacha/fix-flickr-list (beg end)
  (interactive "r")
  (save-excursion (save-restriction
    (narrow-to-region beg end)
    (goto-char (point-min))
    (while (re-search-forward "\\([0-9]+\\)-\\([0-9]+\\)-\\([0-9]+\\)" nil t)
      (replace-match (concat (match-string 1) "." (match-string 2) "." (match-string 3)) nil t)))))

Organize my blog index

(defun sacha/org-file-blog-index-entries (beg end location)
  "Copy entries into blog.org."
  (interactive
   (list
    (if (region-active-p) (point) (line-beginning-position))
    (if (region-active-p) (mark) (1+ (line-end-position)))
    (let ((org-refile-targets
           '(("~/Dropbox/Public/sharing/blog.org" . (:maxlevel . 3)))))
      (save-excursion (org-refile-get-location "Location")))))
  (let ((s
         (replace-regexp-in-string
          "^[ \t]*- "
          "- [X] "
          (buffer-substring-no-properties beg end))))
    (save-window-excursion
      (save-excursion
        (find-file (nth 1 location))
        (save-excursion
          (save-restriction
            (widen)
            (goto-char (nth 3 location))
            (looking-at org-outline-regexp)
            (forward-line 1)
            (insert s)
            (org-update-statistics-cookies nil)))))))
(bind-key "C-c f" 'sacha/org-file-blog-index-entries org-mode-map)

Publishing

Timestamps and section numbers make my published files look more complicated than they are. Let's turn them off by default.

(setq org-export-with-section-numbers nil)
(setq org-html-include-timestamps nil)

This makes it easier to publish my files:

    (if (string= system-name "webdev")
       (setq sacha/emacs-notes-directory "~/code/dev/emacs-notes")
     (setq sacha/emacs-notes-directory "c:/sacha/code/dev/emacs-notes"))
    (setq org-publish-project-alist
          '(("public"
             :base-directory "c:/sacha/Dropbox/public"
             :publishing-directory "c:/sacha/Dropbox/public"
             :publishing-function sacha/org-html-publish-to-html-trustingly
             )
            ("sharing"
             :base-directory "c:/sacha/Dropbox/public/sharing"
             :publishing-directory "c:/sacha/Dropbox/public/sharing"
             :publishing-function sacha/org-html-publish-to-html-trustingly
             )
            ("emacs-config"
             :base-directory "~/.emacs.d"
             :publishing-directory "~/.emacs.d"
             :publishing-function sacha/org-html-publish-to-html-trustingly
             )
            ("book-notes"
             :base-directory "c:/sacha/Dropbox/books"
             :publishing-directory "c:/sacha/Dropbox/books/html"
             :publishing-function sacha/org-html-publish-to-html-trustingly
             :makeindex t)))
(load "~/code/dev/emacs-chats/build-site.el" t)
(load "~/code/dev/emacs-notes/build-site.el" t)

If a file is in a publishing project, publish it.

(defun sacha/org-publish-maybe ()
  (interactive)
  (save-excursion
    (when (org-publish-get-project-from-filename
           (buffer-file-name (buffer-base-buffer)) 'up)
      (org-publish-current-file))))
(bind-key "C-c C-p C-p" 'sacha/org-publish-maybe org-mode-map)

Make it easy to publish and browse a file.

(defun sacha/org-publish-and-browse ()
  (interactive)
  (save-buffer)
  (sacha/org-publish-maybe)
  (browse-url (org-export-output-file-name ".html" nil default-directory)))
(bind-key "<apps> b" 'sacha/org-publish-and-browse)
Org2blog

I use org2blog to post to my blog, which is Wordpress-based. I used to use punchagan's org2blog, but there's a completely different one in ELPA, so I figured I'd give that a try. UPDATE 2014-10-29: Overriding it with the Git version (see the first section of this config) so that I can use thumbnail support for now…

(sacha/package-install 'org2blog)
(sacha/package-install 'htmlize)
(require 'org2blog-autoloads)
(setq org-export-with-toc nil)
(setq org-export-htmlize-output-type 'css)
(defadvice org2blog/wp-post-buffer (around sacha activate)
  (let ((org-confirm-babel-evaluate nil))
    ad-do-it))
Publish without prompting

I want to be able to export without having to say yes to code blocks all the time.

(defun sacha/org-html-export-trustingly ()
  (interactive)
  (let ((org-confirm-babel-evaluate nil))
    (org-html-export-to-html)))

(defun sacha/org-html-publish-to-html-trustingly (plist filename pub-dir)
  (let ((org-confirm-babel-evaluate nil))
    (org-html-publish-to-html plist filename pub-dir)))
Stylesheet / header

Might as well take advantage of my stylesheet:

(setq org-html-head "<link rel=\"stylesheet\" type=\"text/css\"
href=\"http://sachachua.com/blog/wp-content/themes/sacha-v3/foundation/css/foundation.min.css\"></link>
<link rel=\"stylesheet\" type=\"text/css\" href=\"http://sachachua.com/org-export.css\"></link>
<link rel=\"stylesheet\" type=\"text/css\" href=\"http://sachachua.com/blog/wp-content/themes/sacha-v3/style.css\"></link>
<script src=\"http://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js\"></script>")
(setq org-html-htmlize-output-type 'css)
(setq org-src-fontify-natively t)
Footer

Make it easy to scroll to the top:

(setq org-html-preamble "<a name=\"top\" id=\"top\"></a>")
(setq org-html-postamble "
<style type=\"text/css\">
.back-to-top {
    position: fixed;
    bottom: 2em;
    right: 0px;
    text-decoration: none;
    color: #000000;
    background-color: rgba(235, 235, 235, 0.80);
    font-size: 12px;
    padding: 1em;
    display: none;
}

.back-to-top:hover {    
    background-color: rgba(135, 135, 135, 0.50);
}
</style>

<div class=\"back-to-top\">
<a href=\"#top\">Back to top</a> | <a href=\"mailto:sacha@sachachua.com\">E-mail me</a>
</div>

<script type=\"text/javascript\">
    var offset = 220;
    var duration = 500;
    jQuery(window).scroll(function() {
        if (jQuery(this).scrollTop() > offset) {
            jQuery('.back-to-top').fadeIn(duration);
        } else {
            jQuery('.back-to-top').fadeOut(duration);
        }
    });
</script>")
Copy region

Sometimes I want a region's HTML in my kill-ring/clipboard without any of the extra fluff:

(defun sacha/org-copy-region-as-html (beg end &optional level)
  "Make it easier to copy code for Wordpress posts and other things."
  (interactive "r\np")
  (let ((org-export-html-preamble nil)
        (org-html-toplevel-hlevel (or level 3)))
    (kill-new
     (org-export-string-as (buffer-substring beg end) 'html t))))

Sometimes I want a subtree:

(defun sacha/org-copy-subtree-as-html ()
  (interactive)
  (sacha/org-copy-region-as-html
   (org-back-to-heading)
   (org-end-of-subtree)))
UTF-8 checkboxes

This snippet turns - [X] into ☑ and - [ ] into ☐, but leaves [-] alone.

(setq org-html-checkbox-type 'unicode)
(setq org-html-checkbox-types
 '((unicode (on . "<span class=\"task-done\">&#x2611;</span>") 
            (off . "<span class=\"task-todo\">&#x2610;</span>") 
            (trans . "<span class=\"task-in-progress\">[-]</span>"))))

Structure templates

Org makes it easy to insert blocks by typing <s[TAB], etc. I hardly ever use LaTeX, but I insert a lot of Emacs Lisp blocks, so I redefine <l to insert a Lisp block instead.

(setq org-structure-template-alist 
      '(("s" "#+begin_src ?\n\n#+end_src" "<src lang=\"?\">\n\n</src>")
        ("e" "#+begin_example\n?\n#+end_example" "<example>\n?\n</example>")
        ("q" "#+begin_quote\n?\n#+end_quote" "<quote>\n?\n</quote>")
        ("v" "#+BEGIN_VERSE\n?\n#+END_VERSE" "<verse>\n?\n</verse>")
        ("c" "#+BEGIN_COMMENT\n?\n#+END_COMMENT")
        ("p" "#+BEGIN_PRACTICE\n?\n#+END_PRACTICE")
        ("l" "#+begin_src emacs-lisp\n?\n#+end_src" "<src lang=\"emacs-lisp\">\n?\n</src>")
        ("L" "#+latex: " "<literal style=\"latex\">?</literal>")
        ("h" "#+begin_html\n?\n#+end_html" "<literal style=\"html\">\n?\n</literal>")
        ("H" "#+html: " "<literal style=\"html\">?</literal>")
        ("a" "#+begin_ascii\n?\n#+end_ascii")
        ("A" "#+ascii: ")
        ("i" "#+index: ?" "#+index: ?")
        ("I" "#+include %file ?" "<include file=%file markup=\"?\">")))

Quick links

  (setq org-link-abbrev-alist
    '(("google" . "http://www.google.com/search?q=")
("gmap" . "http://maps.google.com/maps?q=%s")
("blog" . "http://sachachua.com/blog/p/")))

Speed commands

These are great for quickly acting on tasks.

(setq org-use-effective-time t)

(defun sacha/org-use-speed-commands-for-headings-and-lists ()
  "Activate speed commands on list items too."
  (or (and (looking-at org-outline-regexp) (looking-back "^\**"))
      (save-excursion (and (looking-at (org-item-re)) (looking-back "^[ \t]*")))))
(setq org-use-speed-commands 'sacha/org-use-speed-commands-for-headings-and-lists)

(add-to-list 'org-speed-commands-user '("x" org-todo "DONE"))
(add-to-list 'org-speed-commands-user '("y" org-todo-yesterday "DONE"))
(add-to-list 'org-speed-commands-user '("!" sacha/org-clock-in-and-track))
(add-to-list 'org-speed-commands-user '("s" call-interactively 'org-schedule))
(add-to-list 'org-speed-commands-user '("d" sacha/org-move-line-to-destination))
(add-to-list 'org-speed-commands-user '("i" call-interactively 'org-clock-in))
(add-to-list 'org-speed-commands-user '("o" call-interactively 'org-clock-out))
(bind-key "!" 'sacha/org-clock-in-and-track org-agenda-mode-map)

Attachments

Org lets you attach files to an Org file. Haven't gotten the hang of this yet, but looks interesting.

(setq org-attach-store-link-p 'attached)
(setq org-attach-auto-tag nil)

Counting

Good way to remind myself that I have lots of STARTED tasks.

(defun sacha/org-summarize-task-status ()
  "Count number of tasks by status. 
Probably should make this a dblock someday."
  (interactive)
  (let (result)
    (org-map-entries
     (lambda ()
       (let ((todo (elt (org-heading-components) 2)))
         (if todo
             (if (assoc todo result)
                 (setcdr (assoc todo result)
                         (1+ (cdr (assoc todo result))))
               (setq result (cons (cons todo 1) result)))))))
    (message "%s" (mapconcat (lambda (x) (format "%s: %d" (car x) (cdr x)))
                             result "\n"))))

Diagrams and graphics

Ooooh. Graphviz and Ditaa make it easier to create diagrams from Emacs. See http://sachachua.com/evil-plans for examples and source.

(setq org-ditaa-jar-path "C:/Sacha/Dropbox/bin/ditaa.jar")
(setq org-startup-with-inline-images t)
(add-hook 'org-babel-after-execute-hook 'org-display-inline-images)
(org-babel-do-load-languages
 'org-babel-load-languages
 '((dot . t)
   (ditaa . t) 
   (R . t)))
(add-to-list 'org-src-lang-modes '("dot" . graphviz-dot))

Presentations

(use-package ox-reveal)

Share my Emacs configuration

This code gets around the fact that my config is called Sacha.org, but I want it to export as sacha-emacs.org in my Dropbox's public directory. Although now that I'm shifting to Github Pages, maybe I don't need this any more…

(defun sacha/org-share-emacs ()
  "Share my Emacs configuration."
  (interactive)
  (let* ((destination-dir "~/Dropbox/Public/")
         (destination-filename "sacha-emacs.org"))
    (save-restriction
      (save-excursion
        (widen)
        (write-region (point-min) (point-max) 
          (expand-file-name destination-filename destination-dir))
        (with-current-buffer (find-file-noselect (expand-file-name
                                                  destination-filename destination-dir))
          (org-babel-tangle-file buffer-file-name 
                                 (expand-file-name
                                  "sacha-emacs.el" destination-dir) "emacs-lisp")
          (org-html-export-to-html))))))

Task dependencies

(setq org-enforce-todo-dependencies t)
(setq org-track-ordered-property-with-tag t)

Custom links

From http://endlessparentheses.com/use-org-mode-links-for-absolutely-anything.html?source=rss

(org-add-link-type "tag" 'endless/follow-tag-link)

(defun endless/follow-tag-link (tag) "Display a list of TODO headlines with tag TAG. With prefix argument, also display headlines without a TODO keyword." (org-tags-view (null current-prefix-arg) tag))

Coding

Web development

;; from FAQ at http://web-mode.org/ for smartparens
(defun sacha/web-mode-hook ()
  (setq web-mode-enable-auto-pairing nil))

(defun sacha/sp-web-mode-is-code-context (id action context)
  (when (and (eq action 'insert)
             (not (or (get-text-property (point) 'part-side)
                      (get-text-property (point) 'block-side))))
    t))

(use-package web-mode
  :init
  (setq web-mode-enable-current-element-highlight t)
  :config
  (progn
    (add-to-list 'auto-mode-alist '("\\.html?\\'" . web-mode))
    (setq web-mode-ac-sources-alist
          '(("css" . (ac-source-css-property))
            ("html" . (ac-source-words-in-buffer ac-source-abbrev)))
          )))

Tab width of 2 is compact and readable

(setq-default tab-width 2)

New lines are always indented

I almost always want to go to the right indentation on the next line.

(global-set-key (kbd "RET") 'newline-and-indent)

Adapt to being on Windows

I'm on Windows, so I use Cygwin to add Unix-y tools to make my life easier. These config snippets seem to help too.

(setenv "CYGWIN" "nodosfilewarning")
(add-hook 'comint-output-filter-functions
    'shell-strip-ctrl-m nil t)
(add-hook 'comint-output-filter-functions
    'comint-watch-for-password-prompt nil t)

Expand region

This is something I have to get the hang of too. It gradually expands the selection. Handy for Emacs Lisp.

(sacha/package-install 'expand-region)
(use-package expand-region
  :bind ("C-=" . er/expand-region))

Emacs Lisp

Edebug

Did you know edebug has a trace function? I didn't. Thanks, agumonkey!

(setq edebug-trace t)

While edebugging, use T to view a trace buffer (*edebug-trace*). Emacs will quickly execute the rest of your code, printing out the arguments and return values for each expression it evaluates.

Eldoc

Eldoc provides minibuffer hints when working with Emacs Lisp.

(autoload 'turn-on-eldoc-mode "eldoc" nil t)
(add-hook 'emacs-lisp-mode-hook 'turn-on-eldoc-mode)
(add-hook 'lisp-interaction-mode-hook 'turn-on-eldoc-mode)
(add-hook 'ielm-mode-hook 'turn-on-eldoc-mode)
Refactoring   drill

More things that I need to get used to…

;; C-c C-v l : elint current buffer in clean environment.
;; C-c C-v L : elint current buffer by multiple emacs binaries.
;;             See `erefactor-lint-emacsen'
;; C-c C-v r : Rename symbol in current buffer.
;;             Resolve `let' binding as long as i can.
;; C-c C-v R : Rename symbol in requiring modules and current buffer.
;; C-c C-v h : Highlight current symbol in this buffer
;;             and suppress `erefacthr-highlight-mode'.
;; C-c C-v d : Dehighlight all by above command.
;; C-c C-v c : Switch prefix bunch of symbols.
;;             ex: '(hoge-var hoge-func) -> '(foo-var foo-func)
;; C-c C-v ? : Display flymake elint warnings/errors

  (use-package erefactor
    :config
    (define-key emacs-lisp-mode-map "\C-c\C-v" erefactor-map))
Jumping to code
(define-key emacs-lisp-mode-map (kbd "C-c .") 'find-function-at-point)
(bind-key "C-c f" 'find-function)

Snippets

(use-package yasnippet-bundle
  :init
  (progn
    (yas/initialize)
    (yas/load-directory "~/elisp/snippets")
    (setq yas/key-syntaxes '("w_" "w_." "^ "))))
;;        (global-set-key (kbd "C-c y") (lambda () (interactive)
;;                                         (yas/load-directory "~/elisp/snippets")))

Show column number

I sometimes need to know where I am in a line.

(column-number-mode 1)

Don't show whitespace in diff, but show context

(setq vc-diff-switches '("-b" "-B" "-u"))

Javascript

This makes script blocks easier to copy:

(defvar sacha/javascript-test-regexp (concat (regexp-quote "/** Testing **/") "\\(.*\n\\)*")
  "Regular expression matching testing-related code to remove.
See `sacha/copy-javascript-region-or-buffer'.")

(defun sacha/copy-javascript-region-or-buffer (beg end)
  "Copy the active region or the buffer, wrapping it in script tags.
Add a comment with the current filename and skip test-related
code. See `sacha/javascript-test-regexp' to change the way
test-related code is detected."
  (interactive "r")
  (unless (region-active-p)
    (setq beg (point-min) end (point-max)))
  (kill-new
   (concat
    "<script type=\"text/javascript\">\n"
    (if (buffer-file-name) (concat "// " (file-name-nondirectory (buffer-file-name)) "\n") "")
    (replace-regexp-in-string
     sacha/javascript-test-regexp
     ""
     (buffer-substring (point-min) (point-max))
     nil)
    "\n</script>")))

And the rest of the js2 config:

(use-package js2-mode
  :commands js2-mode
  :init
    (add-to-list 'auto-mode-alist '("\\.js$" . js2-mode))
  :config
  (progn 
    (bind-key "C-x C-e" 'js-send-last-sexp js2-mode-map)
    (bind-key "C-M-x" 'js-send-last-sexp-and-go js2-mode-map)
    (bind-key "C-c b" 'js-send-buffer js2-mode-map)
    (bind-key "C-c C-b" 'js-send-buffer-and-go js2-mode-map)
    (bind-key "C-c w" 'sacha/copy-javascript-region-or-buffer js2-mode-map)
    (bind-key "C-c l" 'js-load-file-and-go js2-mode-map)))

Magit - nice git interface

Thanks to sheijk for hints on tweaking magit to limit it to the current directory!

  (sacha/package-install 'magit)
  (defun sacha/magit-commit-all ()
    "Publish the current file and commit all the current changes."
    (interactive)
    (sacha/org-publish-maybe)
    (magit-status default-directory)
    (magit-stage-all)
    (call-interactively 'magit-log-edit))

  (use-package magit
    :init 
    (setq magit-git-executable "c:/program files (x86)/git/bin/git.exe"
          magit-diff-options '("-b")) ; ignore whitespace
    :config
    (progn
      (defvar sacha/magit-limit-to-directory nil "Limit magit status to a specific directory.")
      (defun sacha/magit-status-in-directory (directory)
        "Displays magit status limited to DIRECTORY.
Uses the current `default-directory', or prompts for a directory
if called with a prefix argument. Sets `sacha/magit-limit-to-directory'
so that it's still active even after you stage a change. Very experimental."
        (interactive (list (expand-file-name
                            (if current-prefix-arg
                                (read-directory-name "Directory: ")
                              default-directory))))
        (setq sacha/magit-limit-to-directory directory)
        (magit-status directory))

      (defadvice magit-insert-untracked-files (around sacha activate)
        (if sacha/magit-limit-to-directory
            (magit-with-section (section untracked 'untracked "Untracked files:" t)
              (let ((files (cl-mapcan
                            (lambda (f)
                              (when (eq (aref f 0) ??) (list f)))
                            (magit-git-lines
                             "status" "--porcelain" "--" sacha/magit-limit-to-directory))))
                (if (not files)
                    (setq section nil)
                  (dolist (file files)
                    (setq file (magit-decode-git-path (substring file 3)))
                    (magit-with-section (section file file)
                      (insert "\t" file "\n")))
                  (insert "\n"))))
          ad-do-it))

      (defadvice magit-insert-unstaged-changes (around sacha activate)
        (if sacha/magit-limit-to-directory
            (let ((magit-current-diff-range (cons 'index 'working))
                  (magit-diff-options (copy-sequence magit-diff-options)))
              (magit-git-insert-section (unstaged "Unstaged changes:")
                  #'magit-wash-raw-diffs
                "diff-files"
                "--" sacha/magit-limit-to-directory
                ))
          ad-do-it))

      (defadvice magit-insert-staged-changes (around sacha activate)
        "Limit to `sacha/magit-limit-to-directory' if specified."
        (if sacha/magit-limit-to-directory
            (let ((no-commit (not (magit-git-success "log" "-1" "HEAD"))))
              (when (or no-commit (magit-anything-staged-p))
                (let ((magit-current-diff-range (cons "HEAD" 'index))
                      (base (if no-commit
                                (magit-git-string "mktree")
                              "HEAD"))
                      (magit-diff-options (append '("--cached") magit-diff-options)))
                  (magit-git-insert-section (staged "Staged changes:")
                      (apply-partially #'magit-wash-raw-diffs t)
                    "diff-index" "--cached" base "--" sacha/magit-limit-to-directory))))
          ad-do-it)))
    :bind (("C-x v d" . magit-status)
           ("C-x v C-d" . sacha/magit-status-in-directory)
           ("C-x v p" . magit-push) 
           ("C-x v c" . sacha/magit-commit-all)))

The proper way to implement this is probably to patch or override the definition of magit-git-insert-section so that it takes a list of options to add at the end of the command, but that can wait for another time (or braver souls).

TODO Make this better by adding a post command options variable

Tag files

I don't often use a TAGS file, but when I do, I don't want to have to set my tags file per project. I search for it in the directory tree instead.

(defun sacha/recursive-find-file (file &optional directory)
  "Find the first FILE in DIRECTORY or its parents."
  (setq directory (or directory (file-name-directory (buffer-file-name)) (pwd)))
  (if (file-exists-p (expand-file-name file directory))
      (expand-file-name file directory)
    (unless (string= directory "/")
      (sacha/recursive-find-file file (expand-file-name ".." directory)))))

(defun sacha/find-tags ()
  "Set the TAGS file."
  (set (make-variable-buffer-local 'tags-table-list) nil)
  (set (make-variable-buffer-local 'tags-file-name) 
       (sacha/recursive-find-file "TAGS")))

(eval-after-load 'drupal-mode
  '(progn
     (add-hook 'drupal-mode-hook 'sacha/find-tags)))

Projects

(sacha/package-install 'projectile)
(setq projectile-keymap-prefix (kbd "C-c p")) 
(use-package projectile
  :init 
  (progn
    (projectile-global-mode)
    (setq projectile-completion-system 'default)
    (setq projectile-enable-caching t)))
(use-package helm-projectile)

Exploring MELPA recipes


Ruby

(sacha/package-install 'robe)
(use-package robe
  :init
  (progn (add-hook 'ruby-mode-hook 'robe-mode)
         (add-hook 'robe-mode-hook 'ac-robe-setup)
         (add-hook 'ruby-mode-hook 'auto-complete-mode)))
(defun sacha/rspec-verify-single ()
  "Runs the specified example at the point of the current buffer."
  (interactive)
  (rspec-run-single-file
   (concat 
     (rspec-spec-file-for (buffer-file-name))
     ":" 
     (save-restriction
               (widen)
               (number-to-string (line-number-at-pos))))
   (rspec-core-options)))

(sacha/package-install 'rspec-mode)
(use-package rspec-mode
  :config
  (progn 
    (setq rspec-command-options "--fail-fast --format documentation")
    (bind-key "C-c , ," 'rspec-rerun rspec-mode-map)
    (fset 'rspec-verify-single 'sacha/rspec-verify-single)))

SASS

(add-hook 'sass-mode-hook
          (lambda () (setq indent-tabs-mode nil)))
(setq-default indent-tabs-mode nil)

Skewer

This lets you send HTML, CSS, and Javascript fragments to Google Chrome. You may need to start Chrome with chrome --allow-running-insecure-content, if you're using the user script with HTTPS sites.

(sacha/package-install 'skewer-mode)
(use-package skewer-mode
  :config (skewer-setup))

Autocomplete

(use-package company
  :config
  (global-company-mode))

Ledger (personal finance): Make it easier to review my credit card transactions

(defun sacha/ledger-go-to-beginning-of-entry ()
  "Move to the beginning of the current entry."
  (while (and (not (bobp))
              (eq (ledger-context-line-type (ledger-context-at-point))
                  'acct-transaction))
    (forward-line -1)))

(defun sacha/ledger-entry-date ()
  "Returns the date of the entry containing point or nil."
  (save-excursion
    (sacha/ledger-go-to-beginning-of-entry)
    (let ((context-info (ledger-context-other-line 0)))
      (when (eq (ledger-context-line-type context-info) 'entry)
        (goto-char (line-beginning-position))
        (if (looking-at "\\([-0-9\\./]+\\)")
            (match-string-no-properties 1))))))

(defun sacha/ledger-guess-mbna ()
  "Adds a sub-account for the dates for my credit card transactions."
  (interactive)
  (save-excursion
    (sacha/ledger-go-to-beginning-of-entry)
    (forward-line 1)
    (let ((amount 0) (date (sacha/ledger-entry-date)) month)
      (if (string-match "[0-9]+[-\\.]\\([0-9]+\\)[-\\.]\\([0-9]+\\)" date)
          (setq month (string-to-number (match-string 1 date))))
      ;; Is this a payment or a charge?
      (save-excursion
        (while (and (eq (ledger-context-line-type (ledger-context-at-point))
                        'acct-transaction)
                    (not (eobp)))
          (let ((context (ledger-context-at-point)))
            (if (ledger-context-field-value context 'amount)
                (if (string-match "MBNA" (ledger-context-field-value context 'account))
                    (setq amount (string-to-number (ledger-context-field-value context 'amount)))
                  (setq amount (- (string-to-number (ledger-context-field-value context 'amount)))))))
          (forward-line 1)))
      (save-excursion
        (while (and (eq (ledger-context-line-type (ledger-context-at-point))
                        'acct-transaction)
                    (not (eobp)))
          (let ((context (ledger-context-at-point)))
            (if (string-match "MBNA" (ledger-context-field-value context 'account))
                (if (re-search-forward "\\(MBNA\\)[ \t]*[-$\.0-9]*[ \t]*$" (line-end-position) t)
                    (replace-match
                     (concat "MBNA:"
                             (elt
                              '("January" "February" "March" "April" "May" "June" "July" "August" "September" "October" "November" "December")
                              (% (+ (if (> amount 0) 10 11) month) 12)))
                             t t nil 1))))
          (forward-line 1))))))

Internet Relay Chat

IRC is a great way to hang out with other Emacs geeks.

(use-package erc
  :config
  (setq erc-autojoin-channels-alist '(("freenode.net"
         "#org-mode"
         "#hacklabto"
         "#emacs"))
  erc-server "irc.freenode.net"
  erc-nick "sachac"))

Self-tracking, statistics, and other data transformations

Quantified Awesome

(defun sacha/org-clock-in-and-track ()
  "Start the clock running. Clock into Quantified Awesome."
  (interactive)
  (if (derived-mode-p 'org-agenda-mode) (org-agenda-clock-in) (org-clock-in))
  (call-interactively 'sacha/org-quantified-track))
(bind-key "!" 'sacha/org-clock-in-and-track org-agenda-mode-map)

(defmacro sacha/with-org-task (&rest body)
  "Run BODY within the current agenda task, clocked task, or cursor task."
  `(cond
    ((derived-mode-p 'org-agenda-mode)
     (let* ((marker (org-get-at-bol 'org-marker))
            (buffer (marker-buffer marker))
            (pos (marker-position marker)))
       (with-current-buffer buffer
         (save-excursion
           (save-restriction
             (widen)
             (goto-char pos)
             ,@body)))))
    ((and (derived-mode-p 'org-mode) (org-at-heading-p)) (save-excursion ,@body))
    ((org-clocking-p) (save-excursion (org-clock-goto) ,@body))
    ((derived-mode-p 'org-mode) ,@body)))

(defun sacha/org-quantified-track (&optional category note)
  "Create a tracking record using CATEGORY and NOTE.
Default to the current task in the agenda, the currently-clocked
entry, or the current subtree in Org."
  (interactive (list nil nil))
  (unless (and category note)
    (sacha/with-org-task
     (setq category (or category
                        (org-entry-get-with-inheritance "QUANTIFIED")))
     (unless category
       (setq category (read-string "Category: "))
       (org-set-property "QUANTIFIED" category))
     (setq note (or note (elt (org-heading-components) 4) (read-string "Note: ")))))
  (quantified-track (concat category " | " note)))

(defun sacha/org-quick-clock-in-task (location jump)
  "Track and clock in on the specified task.
If JUMP is non-nil or the function is called with the prefix argument, jump to that location afterwards."
  (interactive (list (save-excursion (org-refile-get-location "Location")) current-prefix-arg))
  (when location
    (if jump
        (progn (org-refile 4 nil location) (sacha/org-clock-in-and-track))
      (save-window-excursion
        (org-refile 4 nil location)
        (sacha/org-clock-in-and-track)))))
(bind-key "C-c q" 'sacha/org-quick-clock-in-task)

(use-package quantified)

Compare times and effort estimates

This is for comparing times in column view and in tables.

(defun sacha/compare-times (clocked estimated)
  (if (and (> (length clocked) 0) estimated)
      (format "%.2f"
            (/ (* 1.0 (org-hh:mm-string-to-minutes clocked))
               (org-hh:mm-string-to-minutes estimated)))
    ""))

Use with #+COLUMNS: %40ITEM %17Effort(Estimated){:} %CLOCKSUM, #+BEGIN: columnview :hlines 1#+END:, and

#+TBLFM: $4='(sacha/compare-times $3 $2)

R

(sacha/package-install 'ess)                
(use-package ess-site
  :commands R)

Workrave

    (defvar sacha/workrave-file (expand-file-name ".\\Workrave\\historystats" (getenv "AppData")))

(defun sacha/workrave-transform-statistics (&optional file)
  (interactive (list sacha/workrave-file))
  (with-current-buffer (find-file-noselect file)
  ;; D day month-1 year hour min day month-1 year hour min
    (let ((result "Date\tStart\tEnd\tClicks\tKeystrokes\n"))
      (goto-char (point-min))
      (while (re-search-forward "^D \\(.*\\)" nil t)
  (let ((dates (split-string (match-string 1))))
    (if (re-search-forward "^m \\(.*\\)" nil t)
        (let ((info (split-string (match-string 1))))
    (setq result
          (concat result
            (format "%d-%d-%s\t%s:%02d\t%s:%02d\t%s\t%s\n"
              (+ 1900 (string-to-number (elt dates 2))) ; year
              (1+ (string-to-number (elt dates 1))) ; month
              (elt dates 0) ; day
              (elt dates 3) ; start hour
              (string-to-number (elt dates 4)) ; start min
              (elt dates 8) ; end hour
              (string-to-number (elt dates 9)) ; end min
              (elt info 5) ; clicks
              (elt info 6) ; keystrokes
              )))))))
      (if (interactive-p)
    (kill-new result)
  result))))

Blog

(defun sacha/strip-blog-share ()
  (interactive)
  (let (base)
    (save-excursion
      (goto-char (point-min))
      (while (re-search-forward 
              "<div class=\"sharedaddy sd-sharing-enabled\">.*?<div class=\"sharing-clear\"></div></div></div></div>" nil t)
        (replace-match "")))))

Artrage

        (defun sacha/artrage-export-png (directory &optional prefix)
          "Change an Artrage script file (arscript) to export images to DIRECTORY. 
    If PREFIX is specified, use that instead of image-."
          (interactive "MPath: ")
          (unless (file-directory-p directory)
            (make-directory directory t))
          (while (re-search-forward "[0-9\\.]+s" nil t)
            (replace-match "0.000s"))
          (goto-char (point-min))
          (while (search-forward "<StrokeEvent>" nil t)
            (replace-match (concat 
                            "EvType: Command    CommandID: ExportLayer    Idx: -1    Channels: NO    Path: \""
                            directory
                            "/" (or prefix "image-")
                            ".png\"
<StrokeEvent>") t t)))

Workarounds

color-theme sometimes comes across lists. Odd!

(defadvice face-attribute (around sacha activate)
  (if (symbolp (ad-get-arg 0))
      ad-do-it))

ido-sort-mtime stopped working when I upgraded to Windows 8

(defadvice ido-sort-mtime (around sacha activate)
  (setq ido-temp-list
        (sort ido-temp-list 
              (lambda (a b)
                (let ((ta (or (nth 5 (file-attributes (concat ido-current-directory a))) '(0 0)))
                      (tb (or (nth 5 (file-attributes (concat ido-current-directory b))) '(0 0))))
                  (if (= (nth 0 ta) (nth 0 tb))
                      (> (nth 1 ta) (nth 1 tb))
                    (> (nth 0 ta) (nth 0 tb)))))))
  (setq ad-return-value
        (ido-to-end  ;; move . files to end (again)
         (delq nil (mapcar
                    (lambda (x) (if (string-equal (substring x 0 1) ".") x))
                    ido-temp-list)))))

Cygwin mogrify doesn't work for me, but ImageMagick does

;(setq eimp-mogrify-program "c:/Program Files/ImageMagick-6.8.3-Q16/mogrify.exe")

Advanced stuff / things I tend to forget about

Editing multiple things

Multiple cursors mode   drill

I often define keyboard macros to process multiple lines in a region. Maybe multiple-cursors will be an even better way. Looks promising! See Emacs Rocks episode 13 (multiple-cursors) for a great demo.

(sacha/package-install 'multiple-cursors)
(sacha/package-install 'phi-search)
(sacha/package-install 'phi-search-mc)
(use-package multiple-cursors
  :bind 
   (("C-c m a" . mc/mark-all-like-this)
    ("C-c m m" . mc/mark-all-like-this-dwim)
    ("C-c m l" . mc/edit-lines)
    ("C-c m n" . mc/mark-next-like-this)
    ("C-c m p" . mc/mark-previous-like-this)
    ("C-c m s" . mc/mark-sgml-tag-pair)
    ("C-c m d" . mc/mark-all-like-this-in-defun)))
(use-package phi-search-mc
  :config
  (phi-search-mc/setup-keys))
(use-package mc-extras
  :config
    (define-key mc/keymap (kbd "C-. =") 'mc/compare-chars))

Thanks to Irreal and Planet Emacsen for the link!

All   drill

M-x all lets you edit all lines matching a given regexp.

(use-package all)

Edit list   drill

M-x edit-list makes it easier to edit an Emacs Lisp list.

(use-package edit-list
  :commands edit-list)

Ace Jump mode   drill

Quickly jump to a position in the current view.

(use-package ace-jump-mode
  :bind ("M-SPC" . ace-jump-mode))
(bind-key "M-S-SPC" 'just-one-space)

Ace Window looks useful too.

(use-package ace-window
  :config 
  (setq aw-keys '(?a ?o ?e ?u ?i ?d ?h ?t ?n ?s))
  :bind ("C-x o" . ace-window))

Network: TRAMP and editing files over SSH

Emacs lets you edit files on remote servers, which is pretty darn cool. On Windows, these things help a little.

(setq tramp-default-method "plink")
(setq tramp-auto-save-directory "c:\\sacha\\tmp")

Other nifty Emacs things I want to learn

Finding the closest Makefile   drill

Look up the directory hierarchy from FILE for a file named NAME. Stop at the first parent directory containing a file NAME, and return the directory. Return nil if not found.

Answer

find-dominating-file

iedit

(use-package iedit)

Smartparens mode   drill

(sacha/package-install 'smartparens)
(use-package smartparens
  :init 
  (progn
    (require 'smartparens-config)
    (add-hook 'emacs-lisp-mode-hook 'smartparens-mode)
    (add-hook 'emacs-lisp-mode-hook 'show-smartparens-mode)

;;;;;;;;;;;;;;;;;;;;;;;;
    ;; keybinding management

    (define-key sp-keymap (kbd "C-c s r n") 'sp-narrow-to-sexp)
    (define-key sp-keymap (kbd "C-M-f") 'sp-forward-sexp)
    (define-key sp-keymap (kbd "C-M-b") 'sp-backward-sexp)
    (define-key sp-keymap (kbd "C-M-d") 'sp-down-sexp)
    (define-key sp-keymap (kbd "C-M-a") 'sp-backward-down-sexp)
    (define-key sp-keymap (kbd "C-S-a") 'sp-beginning-of-sexp)
    (define-key sp-keymap (kbd "C-S-d") 'sp-end-of-sexp)

    (define-key sp-keymap (kbd "C-M-e") 'sp-up-sexp)
    (define-key emacs-lisp-mode-map (kbd ")") 'sp-up-sexp)
    (define-key sp-keymap (kbd "C-M-u") 'sp-backward-up-sexp)
    (define-key sp-keymap (kbd "C-M-t") 'sp-transpose-sexp)

    (define-key sp-keymap (kbd "C-M-n") 'sp-next-sexp)
    (define-key sp-keymap (kbd "C-M-p") 'sp-previous-sexp)

    (define-key sp-keymap (kbd "C-M-k") 'sp-kill-sexp)
    (define-key sp-keymap (kbd "C-M-w") 'sp-copy-sexp)

    (define-key sp-keymap (kbd "M-<delete>") 'sp-unwrap-sexp)
    (define-key sp-keymap (kbd "M-<backspace>") 'sp-backward-unwrap-sexp)

    (define-key sp-keymap (kbd "C-<right>") 'sp-forward-slurp-sexp)
    (define-key sp-keymap (kbd "C-<left>") 'sp-forward-barf-sexp)
    (define-key sp-keymap (kbd "C-M-<left>") 'sp-backward-slurp-sexp)
    (define-key sp-keymap (kbd "C-M-<right>") 'sp-backward-barf-sexp)

    (define-key sp-keymap (kbd "M-D") 'sp-splice-sexp)
    (define-key sp-keymap (kbd "C-M-<delete>") 'sp-splice-sexp-killing-forward)
    (define-key sp-keymap (kbd "C-M-<backspace>") 'sp-splice-sexp-killing-backward)
    (define-key sp-keymap (kbd "C-S-<backspace>") 'sp-splice-sexp-killing-around)

    (define-key sp-keymap (kbd "C-]") 'sp-select-next-thing-exchange)
    (define-key sp-keymap (kbd "C-<left_bracket>") 'sp-select-previous-thing)
    (define-key sp-keymap (kbd "C-M-]") 'sp-select-next-thing)

    (define-key sp-keymap (kbd "M-F") 'sp-forward-symbol)
    (define-key sp-keymap (kbd "M-B") 'sp-backward-symbol)

    (define-key sp-keymap (kbd "C-c s t") 'sp-prefix-tag-object)
    (define-key sp-keymap (kbd "C-c s p") 'sp-prefix-pair-object)
    (define-key sp-keymap (kbd "C-c s c") 'sp-convolute-sexp)
    (define-key sp-keymap (kbd "C-c s a") 'sp-absorb-sexp)
    (define-key sp-keymap (kbd "C-c s e") 'sp-emit-sexp)
    (define-key sp-keymap (kbd "C-c s p") 'sp-add-to-previous-sexp)
    (define-key sp-keymap (kbd "C-c s n") 'sp-add-to-next-sexp)
    (define-key sp-keymap (kbd "C-c s j") 'sp-join-sexp)
    (define-key sp-keymap (kbd "C-c s s") 'sp-split-sexp)

;;;;;;;;;;;;;;;;;;
    ;; pair management

    (sp-local-pair 'minibuffer-inactive-mode "'" nil :actions nil)
    (sp-local-pair 'web-mode "<" nil :when '(sacha/sp-web-mode-is-code-context))

;;; markdown-mode
    (sp-with-modes '(markdown-mode gfm-mode rst-mode)
      (sp-local-pair "*" "*" :bind "C-*")
      (sp-local-tag "2" "**" "**")
      (sp-local-tag "s" "```scheme" "```")
      (sp-local-tag "<"  "<_>" "</_>" :transform 'sp-match-sgml-tags))

;;; tex-mode latex-mode
    (sp-with-modes '(tex-mode plain-tex-mode latex-mode)
      (sp-local-tag "i" "1d5f8e69396c521f645375107197ea4dfbc7b792quot;<" "1d5f8e69396c521f645375107197ea4dfbc7b792quot;>"))

;;; html-mode
    (sp-with-modes '(html-mode sgml-mode web-mode)
      (sp-local-pair "<" ">"))

;;; lisp modes
    (sp-with-modes sp--lisp-modes
      (sp-local-pair "(" nil :bind "C-("))))

Web browsing

Minor tweak for Firefox on Windows. Otherwise I get "Searching for program" "permission denied" "firefox".

(setq browse-url-firefox-program
      "C:/Program Files (x86)/Mozilla Firefox/firefox.exe")

Transcript editing

(require 'emms)
(require 'emms-player-simple)
(require 'emms-source-file)
(require 'emms-source-playlist)
(require 'emms-player-mplayer)
(setq emms-player-list '(emms-player-mplayer))
(defun sacha/split-sentence-and-capitalize ()
  (interactive)
  (delete-char 1)
  (insert ".")
  (capitalize-word 1))
(defun sacha/split-sentence-delete-word-and-capitalize ()
  (interactive)
  (delete-char 1)
  (insert ".")
  (kill-word 1)
  (capitalize-word 1))
(defun sacha/delete-word-and-capitalize ()
  (interactive)
  (skip-syntax-backward "w")
  (kill-word 1)
  (capitalize-word 1))
(bind-key "C-c t s"  'sacha/split-sentence-and-capitalize org-mode-map)
(bind-key "C-c t -"  'sacha/split-sentence-delete-word-and-capitalize org-mode-map)
(bind-key "C-c t d"  'sacha/delete-word-and-capitalize org-mode-map)
(bind-key "C-c e SPC" 'emms-pause)
(bind-key "C-c e e" 'emms-pause)
(bind-key "C-c e +" 'emms-seek-forward)
(bind-key "C-c e -" 'emms-seek-backward)
(bind-key "C-c e s" 'emms-seek)

(defun sacha/emms-player-mplayer-set-speed (speed)
  "Depends on mplayer's -slave mode"
  (interactive "MSpeed: ")
  (process-send-string emms-player-simple-process-name 
     (format "speed_set %s\n" speed)))

(defvar sacha/emms-player-mplayer-speed-increment 0.1)

(defun sacha/emms-player-mplayer-speed-up ()
  "Depends on mplayer's -slave mode"
  (interactive)
  (process-send-string emms-player-simple-process-name 
     (format "speed_incr %f\n" sacha/emms-player-mplayer-speed-increment)))
(defun sacha/emms-player-mplayer-slow-down ()
  "Depends on mplayer's -slave mode"
  (interactive)
  (process-send-string emms-player-simple-process-name 
     (format "speed_incr %f\n" (- 0 sacha/emms-player-mplayer-speed-increment))))

(bind-key "C-c e [" 'sacha/emms-player-mplayer-slow-down)
(bind-key "C-c e ]" 'sacha/emms-player-mplayer-speed-up)

Startup

(find-file "~/personal/organizer.org")
(require 'org-compat)
(org-agenda nil "a")

Other cool configs you may want to check out

Inactive/infrequent things

Beeminder

https://github.com/sachac/beeminder.el

This bit of code lets me track sent messages in Gnus:

(defun sacha/beeminder-track-message ()
  (save-excursion
    (goto-char (point-min))
    (when (re-search-forward "Newsgroups: .*emacs")
      (goto-char (point-min))
      (when (re-search-forward "Subject: \\(.*\\)" nil t)
        (beeminder-add-data "orgml" "1" (match-string 1))))))

And this loads the beeminder code:

(use-package beeminder
  :config (add-hook 'message-send-news-hook 'sacha/beeminder-track-message))

Strike through DONE headlines

I wanted a quick way to visually distinguish DONE tasks from tasks I still need to do. This handy snippet from the Emacs Org-mode mailing list does the trick by striking through the headlines for DONE tasks.

(setq org-fontify-done-headline t)
(custom-set-faces
 '(org-done ((t (:foreground "PaleGreen"   
                 :weight normal
                 :strike-through t))))
 '(org-headline-done 
            ((((class color) (min-colors 16) (background dark)) 
               (:foreground "LightSalmon" :strike-through t)))))

Rainbow delimiters

(use-package rainbow-delimiters
  :init (global-rainbow-delimiters-mode))

Drupal

(define-derived-mode drupal-mode php-mode "Drupal"
  "Major mode for Drupal source code.
\\{drupal-mode-map}"
  (setq case-fold-search t) 
  (setq indent-tabs-mode nil)
  (setq c-basic-offset 2)
  (setq indent-tabs-mode nil)
  (setq tab-width 2)
  (setq fill-column 78)
  (c-set-offset 'arglist-cont 0)
  (c-set-offset 'arglist-intro '+)
  (c-set-offset 'case-label 2)
  (c-set-offset 'arglist-close 0)
  (setq yas/buffer-local-condition 
  '(cond
   ((looking-at "\\w") nil)
   ((and
     (not (bobp))
     (or (equal "font-lock-comment-face"
                (get-char-property (1- (point)) 'face))
         (equal "font-lock-string-face"
                (get-char-property (1- (point)) 'face))))
    '(require-snippet-condition . force-in-comment))
   (t t))))
(define-key drupal-mode-map (kbd "TAB") 'indent-according-to-mode)
(add-hook 'drupal-mode-hook (lambda () (flymake-mode 1)))
(add-hook 'drupal-mode-hook (lambda () (yas/minor-mode 1)))
(add-to-list 'auto-mode-alist '("\\.\\(php\\|test\\|module\\|inc\\|install\\|engine\\|profile\\|.theme\\)$" . drupal-mode))
(add-to-list 'auto-mode-alist '("\\.tpl.php$" . html-helper-mode))
(define-key drupal-mode-map '[M-S-up] 'flymake-goto-prev-error)
(define-key drupal-mode-map '[M-S-down] 'flymake-goto-next-error)
(define-key drupal-mode-map (kbd "C-c C-c") 'comment-dwim)

(defun sacha/drupal-module-name ()
  "Return the Drupal module name for .module and .install files."    (file-name-sans-extension (file-name-nondirectory
                             (buffer-file-name))))
(add-to-list 'hs-special-modes-alist '(drupal-mode "{" "}" "/[*/]" nil hs-c-like-adjust-block-beginning))

Autoconnect to IRC so that I don't forget

(erc :server "irc.freenode.net" :port 6667 :nick "sachac")

Org - send things to the bottom of the list

Handy for collecting items together.

(defun sacha/org-send-to-bottom-of-list ()
  "Send the current line to the bottom of the list."
  (interactive)
  (beginning-of-line)
  (let ((kill-whole-line t))
    (save-excursion
      (kill-line 1)
      (org-end-of-item-list)
      (yank))))

Previous weekly review

  (defvar sacha/org-quantified-categories 
    '(("Business" 
       ("Earn" . "Business - Earn") 
       ("E1" . "Business - Earn - Consulting - E1") 
       ("Connect" . "Business - Connect") 
       ("Build" . "Business - Build"))
      ("Discretionary"
       ("Social" . "Discretionary - Social")
       ("Productive" . "Discretionary - Productive")
       ("Writing" . "Discretionary - Productive - Writing")
       ("Emacs" . "Discretionary - Productive - Emacs")
       ("Play" . "Discretionary - Play"))
      ("Personal" ;("Biking" . "Personal - Bike")
       ("Routines" . "Personal - Routines"))
      ("Sleep" nil)
      ("Unpaid work" 
       ("Commuting" . "Unpaid work - Subway")
       ("Cook" . "Unpaid work - Cook")
       ("Tidy" . "Unpaid work - Tidy up")))
    "Categories for time summary.")

  (defun sacha/org-summarize-time-use (&optional start end)
    (require 'quantified)
    (interactive)
    (unless start (setq start (format-time-string "%Y-%m-%d" (days-to-time (- (time-to-number-of-days base-date) 6)))))
    (unless end (setq end (format-time-string "%Y-%m-%d" (days-to-time (1+ (time-to-number-of-days base-date))))))
    (let ((time-summary (quantified-summarize-time start end))
          (categories sacha/org-quantified-categories)
          result)
      (setq result
            (mapconcat
             (lambda (a)
               (if (assoc (car a) time-summary)
                   (concat
                    (format "- %s: %.1f hours" (car a) (/ (cdr (assoc (car a) time-summary)) 3600.0))
                    (if (cdr a)
                        (let ((detail
                               (delq nil
                                     (mapcar (lambda (b)
                                               (if (assoc (cdr b) time-summary)
                                                   (format "%s: %.1f"
                                                           (car b)
                                                           (/ (cdr (assoc (cdr b) time-summary)) 3600.0))
                                                 nil))
                                             (cdr a)))))
                          (if detail
                              (concat " (" (mapconcat 'identity detail ", ") ")")
                            ""))
                      "")
                    (if (string-equal (car a) "Sleep")
                        (format " - average of %.1f hours per day" (/ (cdr (assoc (car a) time-summary)) 3600.0 7.0))
                      "")
                    "\n")))
       categories ""))
(if (called-interactively-p)
    (insert result)
  result)))

List upcoming tasks so that I can see if I'm overloaded.

(defun sacha/org-summarize-upcoming-week ()
  "Summarize upcoming tasks as a list."
  (interactive)
  (org-agenda nil "w")
  (let ((string (buffer-string))
        business relationships life)
    (with-temp-buffer
      (insert string)
      (goto-char (point-min))
      (while (re-search-forward sacha/weekly-review-line-regexp nil t)
        (cond
         ((string= (match-string 1) "routines") nil) ; skip routine tasks
         ((string= (match-string 1) "business")
          (add-to-list 'business (concat "  - [ ] " (match-string 3))))
         ((string= (match-string 1) "people")
          (add-to-list 'relationships (concat "  - [ ] " (match-string 3))))
         (t (add-to-list 'life (concat "  - [ ] " (match-string 3)))))))
    (setq string
          (concat
      "*Plans for next week*\n"
      "- Business\n"
      (mapconcat 'identity business "\n")
      "\n- Relationships\n"
      (mapconcat 'identity relationships "\n")
      "\n- Life\n"
      (mapconcat 'identity life "\n")))
    (if (called-interactively-p)
        (kill-new string)
      string)))

This uses Org Agenda's log mode to summarize the tasks that I checked off. I still need to match it up with the plans for the previous week to see which items I'd planned ahead, and which ones were new tasks. (Hmm, is it important to track those separately? I might just skip it.)

(defun sacha/org-summarize-previous-week ()
  "Summarize previously-completed tasks as a list."
  (interactive)
  (save-window-excursion
    (org-agenda nil "w")
    (org-agenda-later -1)
    (org-agenda-log-mode 16)
    (let ((string (buffer-string))
          business relationships life)
      (with-temp-buffer
        (insert string)
      (goto-char (point-min))
      (while (re-search-forward sacha/weekly-review-line-regexp nil t)
        (cond
         ((string= (match-string 1) "routines") nil) ; skip routine tasks
         ((string= (match-string 1) "business")
          (add-to-list 'business (concat "  - " (match-string 2))))
         ((string= (match-string 1) "people")
          (add-to-list 'relationships (concat "  - " (match-string 2))))
         (t (add-to-list 'life (concat "  - " (match-string 2)))))))
    (setq string
          (concat
           "*Accomplished this week*\n\n"
           "- Business\n"
           (mapconcat 'identity business "\n")
           "\n- Relationships\n"
           (mapconcat 'identity relationships "\n")
           "\n- Life\n"
           (mapconcat 'identity life "\n")))
    (if (called-interactively-p)
        (kill-new string)
      string))))

Animation for Emacs chats

(defun sacha/animate-emacs-chat ()
  (interactive)
  (text-scale-set 6)
  (erase-buffer)
  (sit-for 3)
  (let ((list '("Emacs Chat: Sacha Chua"
                "interviewed by Bastien Guerry"
                ""
                "July 24, 2013"
                "sachachua.com/emacs-chat"))
        (approx-width 41)
        (approx-height 16)
        row)
    (setq row (/ (- approx-height (length list)) 2))
    (mapcar
     (lambda (x)
       (animate-string x
                       row
                       (/ (- approx-width (length x)) 2))
       (setq row (1+ row)))
     list)))

Idle timer

This snippet is from John Wiegley - http://lists.gnu.org/archive/html/emacs-orgmode/2010-03/msg00367.html. It shows the org agenda when Emacs is idle.

Thanks to winner-mode, I can get back to my previous buffers with C-c left.

(defun jump-to-org-agenda ()
  (interactive)
  (let ((buf (get-buffer "*Org Agenda*"))
        wind)
    (if buf
        (if (setq wind (get-buffer-window buf))
            (select-window wind)
          (if (called-interactively-p)
              (progn
                (select-window (display-buffer buf t t))
                (org-fit-window-to-buffer)
                ;; (org-agenda-redo)
                )
            (with-selected-window (display-buffer buf)
              (org-fit-window-to-buffer)
              ;; (org-agenda-redo)
              )))
      (call-interactively 'org-agenda-list)))
  ;;(let ((buf (get-buffer "*Calendar*")))
  ;;  (unless (get-buffer-window buf)
  ;;    (org-agenda-goto-calendar)))
  )

(run-with-idle-timer 300 t 'jump-to-org-agenda)

Old Flickr/Evernote export

;; I don't use these as much now that I have the functions above.
(defun sacha/evernote-extract-links (filename)
  "Extract note names and URLs from an ENEX file."
  (interactive)

  (goto-char (point-min))
  (let (list)
    (while (re-search-forward "<title>\\(.+?\\)</title>\\(.*?\n\\)*?.*?href=\"\\(.*?\\)\"" nil t)
      (setq list (cons (cons (match-string-no-properties 1) (match-string-no-properties 3)) list)))
    (delete-region (point-min) (point-max))
    (insert (mapconcat (lambda (x) (concat "- [[" (cdr x) "][" (car x) "]]")) list "\n"))))      

(defun sacha/flickr-extract-this-week ()
  "Extract this week's sketch titles and URLs from the flickr_metadata CSV."
  (interactive)
  (let ((base-date (apply 'encode-time (org-read-date-analyze "-fri" nil '(0 0 0))))
        start end list)
    (setq start (format-time-string "%Y-%m-%d" (days-to-time (- (time-to-number-of-days base-date) 6))))
    (setq end (format-time-string "%Y-%m-%d" (days-to-time (1+ (time-to-number-of-days base-date)))))
    (setq list (csv-parse-buffer t))
    (erase-buffer)
    (insert
     (mapconcat (lambda (x) (concat "- [[" (car x) "][" (cdr x) "]]"))
                (sort
                 (delq nil
                       (mapcar (lambda (x)
                                 (let ((title (cdr (assoc "FileName" x))))
                                   (if (and (not (string< title start))
                                            (string< title end))
                                       (cons (cdr (assoc "URL" x)) title))))
                               list))
                 (lambda (a b) (string<  (cdr a) (cdr b)))
                 )
                "\n"))))

Presentation code for Emacs Conference

(defvar sacha/org-show-presentation-file "~/Dropbox/Emacs Conference/public.org" "File containing the presentation.")
(defvar sacha/org-show-slide-tag "slide" "Tag that marks slides.")
(defvar sacha/org-show-slide-tag-regexp (concat ":" (regexp-quote sacha/org-show-slide-tag) ":"))
(require 'eimp)

;; From org-pres--eimp-fit
(defun sacha/org-show-eimp-fit ()
  "Function used as a hook, fits the image found to the window."
  (when (eq major-mode 'image-mode)
    (eimp-fit-image-to-window nil)))
(add-hook 'find-file-hook 'sacha/org-show-eimp-fit)

(defun sacha/org-show-execute-slide ()
  "Process slide at point.
  If it contains an Emacs Lisp source block, evaluate it.
  If it contains an image, view it and switch to that buffer.
  Else, focus on that buffer.
  Hide all drawers."
  (interactive)
  (find-file sacha/org-show-presentation-file)
  (org-narrow-to-subtree)
  (visual-line-mode)
  (let ((heading-text (nth 4 (org-heading-components))))
    (cond
     ;; view images
     ((and (goto-char (point-min))
           (re-search-forward "\\[\\[.*\\.\\(jpg\\|gif\\|png\\)" nil t))
      (delete-other-windows)
      (let ((org-link-frame-setup '((file . find-file))))
        (org-open-at-point))
      (delete-other-windows)
      (goto-char (point-min)))
     ;; find and execute source code blocks
     ((and (goto-char (point-min))
           (re-search-forward "#\\+begin_src" nil t))
      (let ((info (org-babel-get-src-block-info)))
        (unwind-protect
            (eval (read (concat "(progn " (nth 1 info) ")"))))))
     (t
      (switch-to-buffer (current-buffer))
      (text-scale-set 4)
      (org-show-subtree)
      (org-cycle-hide-drawers t)
      (org-display-inline-images)
      (delete-other-windows)))
    (set-frame-name heading-text)))

(defun sacha/org-show-next-slide ()
  "Show the next slide."
  (interactive)
  (find-file sacha/org-show-presentation-file)
  (widen)
  (goto-char (line-end-position))
  (when (re-search-forward sacha/org-show-slide-tag-regexp nil t)
    (sacha/org-show-execute-slide)))

(defun sacha/org-show-previous-slide ()
  "Show the next slide."
  (interactive)
  (find-file sacha/org-show-presentation-file)
  (widen)
  (goto-char (line-beginning-position))
  (when (re-search-backward sacha/org-show-slide-tag-regexp nil t)
    (sacha/org-show-execute-slide)))

(global-set-key '[f5] 'sacha/org-show-previous-slide)
(global-set-key '[f6] 'sacha/org-show-execute-slide)
(global-set-key '[f7] 'sacha/org-show-next-slide)

Enable minibuffer completion

[2013-03-31] Superseded by ido-hacks?

It can be difficult to remember the full names of Emacs commands, so I use icomplete-mode for minibuffer completion. This also makes it easier to discover commands.

(icomplete-mode 1)

Because I'm trying to use helm instead of ido…

Ido-mode: Much better navigationy things

[2013-03-31]: Let's try using Helm instead.

Ido-mode is awesome. Let's make it awesomer. I usually want to go to recently-opened files first.

(use-package ido
  :init
  (progn
  (ido-mode 1)
  (setq ido-default-buffer-method 'selected-window)
  (add-hook 'ido-make-file-list-hook 'ido-sort-mtime)
  (add-hook 'ido-make-dir-list-hook 'ido-sort-mtime)
  (defun ido-sort-mtime ()
    (setq ido-temp-list
          (sort ido-temp-list 
                (lambda (a b)
                  (let ((ta (nth 5 (file-attributes (concat ido-current-directory a))))
                        (tb (nth 5 (file-attributes (concat ido-current-directory b)))))
                    (if (= (nth 0 ta) (nth 0 tb))
                        (> (nth 1 ta) (nth 1 tb))
                      (> (nth 0 ta) (nth 0 tb)))))))
    (ido-to-end  ;; move . files to end (again)
     (delq nil (mapcar
                (lambda (x) (if (string-equal (substring x 0 1) ".") x))
                ido-temp-list))))))

Ido and Org

When I use org-refile to organize my notes, I like seeing the latest entries on top. Ido-related and verify-related snippets are from "Using ido-mode for org-refile (and archiving via refile)" in Org Hacks.

(setq ido-everywhere t)
(setq ido-enable-flex-matching t)
(setq ido-max-directory-size 100000)
(ido-mode (quote both))
(setq org-completion-us-ido t)

Finding files

I don't want to think about directory structures, I just want to open files.

(require 'filecache)
(require 'ido)
(defun file-cache-ido-find-file (file)
  "Using ido, interactively open file from file cache'.
First select a file, matched using ido-switch-buffer against the contents
in `file-cache-alist'. If the file exist in more than one
directory, select directory. Lastly the file is opened."
  (interactive (list (file-cache-ido-read "File: "
                                          (mapcar
                                           (lambda (x)
                                             (car x))
                                           file-cache-alist))))
  (let* ((record (assoc file file-cache-alist)))
    (find-file
     (expand-file-name
      file
      (if (= (length record) 2)
          (car (cdr record))
        (file-cache-ido-read
         (format "Find %s in dir: " file) (cdr record)))))))

(defun file-cache-ido-read (prompt choices)
  (let ((ido-make-buffer-list-hook
         (lambda ()
           (setq ido-temp-list choices))))
    (ido-read-buffer prompt)))
(add-to-list 'file-cache-filter-regexps "docs/html")
(add-to-list 'file-cache-filter-regexps "\\.svn-base$")
(add-to-list 'file-cache-filter-regexps "\\.dump$")

To use this code, I add something like

(sacha/file-cache-setup-tree "sacha/proj1" "C-c d"
                             '("/dir1"
                               "/dir2"))

to my config. Then C-c d (or whatever keyboard shortcut I use) searches for files within the specified directories.

Keywiz - keyboard quizzes

(use-package keywiz)
(defun sacha/load-keybindings ()
  "Since we don't want to have to pass through a keywiz game each time..."
  (setq keywiz-cached-commands nil)
  (do-all-symbols (sym)
    (when (and (commandp sym)
               (not (memq sym '(self-insert-command
                                digit-argument undefined))))
      (let ((keys (apply 'nconc (mapcar
                                 (lambda (key)
                                   (when (keywiz-key-press-event-p key)
                                     (list key)))
                                 (where-is-internal sym)))))
        ;;  Politically incorrect, but clearer version of the above:
        ;;    (let ((keys (delete-if-not 'keywiz-key-press-event-p
        ;;                               (where-is-internal sym))))
        (and keys
             (push (list sym keys) keywiz-cached-commands))))))
(sacha/load-keybindings)
;; Might be good to use this in org-agenda...
(defun sacha/random-keybinding ()
  "Describe a random keybinding."
  (let* ((command (keywiz-random keywiz-cached-commands))
         (doc (and command (documentation (car command)))))
    (if command
        (concat (symbol-name (car command)) " "
                "(" (mapconcat 'key-description (cadr command) ", ") ")"
                (if doc
                    (concat ": " (substring doc 0 (string-match "\n" doc)))
                  ""))
      "")))

Work-in-progress: monthly review

… and for good measure, let's do some kind of monthly review too.

(defun sacha/org-prepare-monthly-review ()
  "Prepare monthly review template."
  (interactive)
  (let* ((base-date (org-read-date-analyze "-1m" nil '(0 0 0)))
         (start (encode-time 0 0 0 1 (elt base-date 4) (elt base-date 5)))
         (end (encode-time 0 0 0 1 (1+ (elt base-date 4)) (elt base-date 5))))
    (save-window-excursion
      (kill-new
       (concat
        "*** Monthly review: " (format-time-string "%B %Y" base-date) "  :monthly:\n"
        "*Blog posts*\n\n"
        "*Time review*\n"
        (sacha/org-summarize-time-use
         (format-time-string "%Y-%m-%d" start)
         (format-time-string "%Y-%m-%d" end)
         (float-time (time-subtract end start))
        "\n")))
    (yank))))

MobileOrg for Android

I've been playing around with MobileOrg so that I can review my agenda and capture notes on my smartphone. My main Org file is too big to open easily there, though.

(use-package org-mobile
  :init
  (progn
    (autoload 'org-mobile-pull "org-mobile" nil t)
    (autoload 'org-mobile-push "org-mobile" nil t))
  :config
  (progn
    (setq org-mobile-directory "~/Dropbox/mobile")
    (setq org-mobile-inbox-for-pull "~/personal/mobileorg.org")
    (setq default-buffer-file-coding-system 'utf-8)
    (setq org-mobile-files '("/cygdrive/c/sacha/personal/organizer.org"
                             "/cygdrive/c/sacha/personal/business.org"
                             "/cygdrive/c/sacha/personal/books.org"))
    (setq org-mobile-agendas '("a"))))

Encryption

(require 'org-crypt)
(org-crypt-use-before-save-magic)
(setq org-tags-exclude-from-inheritance (quote ("crypt")))

(setq org-crypt-key nil)
  ;; GPG key to use for encryption
  ;; Either the Key ID or set to nil to use symmetric encryption.

  ;;     (setq auto-save-default nil)
  ;; Auto-saving does not cooperate with org-crypt.el: so you need
  ;; to turn it off if you plan to use org-crypt.el quite often.
  ;; Otherwise, you'll get an (annoying) message each time you
  ;; start Org.

  ;; To turn it off only locally, you can insert this:
  ;;
  ;; # -*- buffer-auto-save-file-name: nil; -*-

Games

Typing of Emacs

(use-package typing
  :init
  (autoload 'typing-of-emacs "typing" nil t)
  :config
  (progn
    (setq toe-starting-length 6)
    (setq toe-starting-time-per-word 2)
    (setq toe-max-length 20)))

2048 in Emacs, and colours too   emacs

While browsing through M-x list-packages, I noticed that there was a new MELPA package that implemented the 2048 game in Emacs. I wrote the following code to colorize it. Haven't tested the higher numbers yet, but they're easy enough to tweak if the colours disagree with your theme. =)

(defface 2048-face-2    '((t . (:background "khaki" :foreground "black"))) "Face for the tile 2" :group '2048-faces)
(defface 2048-face-4    '((t . (:background "burlywood" :foreground "black"))) "Face for the tile 4" :group '2048-faces)
(defface 2048-face-8    '((t . (:background "orange3" :foreground "black"))) "Face for the tile 8" :group '2048-faces)
(defface 2048-face-16   '((t . (:background "orange" :foreground "black"))) "Face for the tile 16" :group '2048-faces)
(defface 2048-face-32   '((t . (:background "orange red" :foreground "black"))) "Face for the tile 32" :group '2048-faces)
(defface 2048-face-64   '((t . (:background "firebrick" :foreground "white"))) "Face for the tile 64" :group '2048-faces)
(defface 2048-face-128  '((t . (:background "dark red" :foreground "white"))) "Face for the tile 128" :group '2048-faces)
(defface 2048-face-256  '((t . (:background "dark magenta" :foreground "white"))) "Face for the tile 256" :group '2048-faces)
(defface 2048-face-512  '((t . (:background "magenta" :foreground "black"))) "Face for the tile 512" :group '2048-faces)
(defface 2048-face-1024 '((t . (:background "gold" :foreground "black"))) "Face for the tile 1024" :group '2048-faces)
(defface 2048-face-2048 '((t . (:background "yellow" :foreground "black"))) "Face for the tile 2048" :group '2048-faces)


  (defun sacha/2048-set-font-size ()
    (text-scale-set 5)
    (goto-char (point-min)))

  (use-package 2048-game
    :config
    (progn
     (add-hook '2048-mode-hook 'sacha/2048-set-font-size)))

Path

(setenv "PATH" (concat "\"c:/program files/postgresql/9.3/bin;\"" (getenv "PATH")))
Back to top | E-mail me