Reorder TODO items in your org-mode agenda

I use org-mode to manage my to-do list with priorities and deadlines but inevitably I have multiple items without a specific deadline or scheduled date and that have the same priority. These appear in my agenda in the order in which they were added to my to-do list, but I’ll sometimes want to change that order. This can be done temporarily using M-UP or M-DOWN in the agenda view, but these changes are lost when the agenda is refreshed.

I came up with a two-part solution to this. The main part is a generic function to move the subtree at the current point to be the top item of all subtrees of the same level. Here is the function:

(defun bjm/org-headline-to-top ()
  "Move the current org headline to the top of its section"
  (interactive)
  ;; check if we are at the top level
  (let ((lvl (org-current-level)))
    (cond
     ;; above all headlines so nothing to do
     ((not lvl)
      (message "No headline to move"))
     ((= lvl 1)
      ;; if at top level move current tree to go above first headline
      (org-cut-subtree)
      (beginning-of-buffer)
      ;; test if point is now at the first headline and if not then
      ;; move to the first headline
      (unless (looking-at-p "*")
        (org-next-visible-heading 1))
      (org-paste-subtree))
     ((> lvl 1)
      ;; if not at top level then get position of headline level above
      ;; current section and refile to that position. Inspired by
      ;; https://gist.github.com/alphapapa/2cd1f1fc6accff01fec06946844ef5a5
      (let* ((org-reverse-note-order t)
             (pos (save-excursion
                    (outline-up-heading 1)
                    (point)))
             (filename (buffer-file-name))
             (rfloc (list nil filename nil pos)))
        (org-refile nil nil rfloc))))))

This will move any to-do item to the top of all of the items at the same level as that item. This is equivalent to putting the cursor on the headline you want to move and hitting M-UP until you reach the top of the section.

Now I want to be able to run this from the agenda-view, which is accomplished with the following function, which I then bind to the key 1 in the agenda view.

(defun bjm/org-agenda-item-to-top ()
    "Move the current agenda item to the top of the subtree in its file"
  (interactive)
  ;; save buffers to preserve agenda
  (org-save-all-org-buffers)
  ;; switch to buffer for current agenda item
  (org-agenda-switch-to)
  ;; move item to top
  (bjm/org-headline-to-top)
  ;; go back to agenda view
  (switch-to-buffer (other-buffer (current-buffer) 1))
  ;; refresh agenda
  (org-agenda-redo)
  )

  ;; bind to key 1
  (define-key org-agenda-mode-map (kbd "1") 'bjm/org-agenda-item-to-top)

Now in my agenda view, I just hit 1 on a particular item and it is moved permanently to the top of its level (with deadlines and priorities still taking precedence in the final sorting order).

Pop up a quick shell with shell-pop

There are several ways of running a shell inside Emacs. I don’t find that I need to use it very often as I do so much within Emacs these days, but when I do it’s handy to quickly bring up the shell, run a command the then dismiss it again. The shell-pop package does this very smartly. One key combo (I use C-t) pops up a shell window for the directory containing the file you are currently editing, and then C-t dismisses the shell window when you are done.

The github page has lots of details on how to configure it, and I use a fairly minimal setup and use the ansi-term terminal emulator and zsh as my shell. Here is my configuration:

(use-package shell-pop
  :bind (("C-t" . shell-pop))
  :config
  (setq shell-pop-shell-type (quote ("ansi-term" "*ansi-term*" (lambda nil (ansi-term shell-pop-term-shell)))))
  (setq shell-pop-term-shell "/bin/zsh")
  ;; need to do this manually or not picked up by `shell-pop'
  (shell-pop--set-shell-type 'shell-pop-shell-type shell-pop-shell-type))

The last line is needed but I can’t remember where I got it from!

Even more PDF-tools tweaks

Once I started tweaking pdf-tools I couldn’t resist a few more bits of streamlining my workflow. I wanted to be able to commit an annotation by hitting RET rather than C-c C-c and then use SHIFT and RET to actually enter a newline, a bit like in many messaging apps. The problem was that the necessary commands are in pdf-annot-edit-contents-minor-mode-map which is a key map that is not available until that minor mode is loaded. This causes an error if you try to define a key in that map in the same way as I did for the keys to add annotations. This is remedied by wrapping the definitions in with-eval-after-load which waits to execute the code until the file that defines the pdf-annot mode is loaded.

The other thing I wanted to do was have the pdf file auto-save every time I commit an annotation. I did this by advising the function pdf-annot-edit-contents-commit to run save-buffer afterwards. This also had to be inside with-eval-after-load as the pdf-annot-edit-contents-commit function is not available to be advised until the file defining the pdf-annot mode is loaded. The only other issue I had was that when the advice to pdf-annot-edit-contents-commit called save-buffer, it passed its argument (the text of the annotation) to save-buffer which then failed. I hacked around this by writing a simple wrapper to save buffer that ignores any arguments. There is probably a better way to do this!

Putting it all together with my previous tweaks, my pdf-tools config looks like this:

;; wrapper for save-buffer ignoring arguments
(defun bjm/save-buffer-no-args ()
  "Save buffer ignoring arguments"
  (save-buffer))

(use-package pdf-tools
 :pin manual ;;manually update
 :config
 ;; initialise
 (pdf-tools-install)
 (setq-default pdf-view-display-size 'fit-page)
 ;; automatically annotate highlights
 (setq pdf-annot-activate-created-annotations t)
 ;; use isearch instead of swiper
 (define-key pdf-view-mode-map (kbd "C-s") 'isearch-forward)
 ;; turn off cua so copy works
 (add-hook 'pdf-view-mode-hook (lambda () (cua-mode 0)))
 ;; more fine-grained zooming
 (setq pdf-view-resize-factor 1.1)
 ;; keyboard shortcuts
 (define-key pdf-view-mode-map (kbd "h") 'pdf-annot-add-highlight-markup-annotation)
 (define-key pdf-view-mode-map (kbd "t") 'pdf-annot-add-text-annotation)
 (define-key pdf-view-mode-map (kbd "D") 'pdf-annot-delete)
 ;; wait until map is available
 (with-eval-after-load "pdf-annot"
   (define-key pdf-annot-edit-contents-minor-mode-map (kbd "<return>") 'pdf-annot-edit-contents-commit)
   (define-key pdf-annot-edit-contents-minor-mode-map (kbd "<S-return>") 'newline)
   ;; save after adding comment
   (advice-add 'pdf-annot-edit-contents-commit :after 'bjm/save-buffer-no-args)))

More PDF-tools tweaks

I’ve added a couple more tweaks to my pdf-tools configuration.

First, I’ve found that CUA mode interferes with the ability to copy text from the pdf, so let’s turn it off in pdf-view-mode

;; turn off cua so copy works
(add-hook 'pdf-view-mode-hook (lambda () (cua-mode 0)))

Next, I want more fine grained zooming with + and - than the default 25%, so I’ll set it to 10%

;; more fine-grained zooming
(setq pdf-view-resize-factor 1.1)

Finally, for my most commonly used annotation tools (adding a highlight, adding a text note and deleting an annotation), I want quicker shortcuts. With the following I just hit h, t or D respectively for those tools (instead of e.g. C-c C-a h to highlight).

;; keyboard shortcuts
(define-key pdf-view-mode-map (kbd "h") 'pdf-annot-add-highlight-markup-annotation)
(define-key pdf-view-mode-map (kbd "t") 'pdf-annot-add-text-annotation)
(define-key pdf-view-mode-map (kbd "D") 'pdf-annot-delete)

Putting it all together, my current setup looks like this:

(use-package pdf-tools
 :pin manual ;; manually update
 :config
 ;; initialise
 (pdf-tools-install)
 ;; open pdfs scaled to fit page
 (setq-default pdf-view-display-size 'fit-page)
 ;; automatically annotate highlights
 (setq pdf-annot-activate-created-annotations t)
 ;; use normal isearch
 (define-key pdf-view-mode-map (kbd "C-s") 'isearch-forward)
 ;; turn off cua so copy works
 (add-hook 'pdf-view-mode-hook (lambda () (cua-mode 0)))
 ;; more fine-grained zooming
 (setq pdf-view-resize-factor 1.1)
 ;; keyboard shortcuts
 (define-key pdf-view-mode-map (kbd "h") 'pdf-annot-add-highlight-markup-annotation)
 (define-key pdf-view-mode-map (kbd "t") 'pdf-annot-add-text-annotation)
 (define-key pdf-view-mode-map (kbd "D") 'pdf-annot-delete))

View and annotate PDFs in Emacs with PDF-tools

The pdf-tools packages allows you to read and annotate PDF documents in Emacs. The installation process is described on the github page, but on my Mac, I needed to install poppler (I used macports) and add

export PKG_CONFIG_PATH=/opt/local/lib/pkgconfig

to my zshrc. Then after installing the package in Emacs with the following code

(use-package pdf-tools
 :pin manual ;; manually update
 :config
 ;; initialise
 (pdf-tools-install)
 ;; open pdfs scaled to fit page
 (setq-default pdf-view-display-size 'fit-page)
 ;; automatically annotate highlights
 (setq pdf-annot-activate-created-annotations t)
 ;; use normal isearch
 (define-key pdf-view-mode-map (kbd "C-s") 'isearch-forward))

I ran M-x pdf-tools-install, ignored an error message about epdfinfo and then restarted emacs, and all was well.

I use the :pin manual option in use-package to stop pdf-tools being automatically updated when I update the rest of my packages, since it would need the installation command and restart each time it updated.

There are lots of nice features in pdf-tools but I’ll just mention the ones I use most often. You can search pdf files like normal buffers, but the enhanced search tool swiper doesn’t work with pdf-tools so I set C-s to call the normal isearch when in this mode.

You can also make annotations in pdf-tools and I set my configuration above so that when an annotation (like highlighting some text) is created, a buffer opens prompting for a text note to go with the annotation. So for example, to highlight a comment on some text I

  1. Select the text in the pdf (mouse required for this unfortunately)
  2. Use C-c C-a h to highlight it yellow
  3. Type some notes in the annotation buffer that pops up and use C-c C-c to complete the annotation.

Other useful annotations are C-c C-a t and then mouse click to add a text note somewhere to the pdf page, C-c C-a o to strike-through text, and C-c C-a D and then click to delete an annotation.

Saving the pdf buffer as normal saves all the annotations, and they will be readable in any PDF viewer so this works well when collaborating with the unenlightened!

Tree-style directory views in dired with dired-subtree

By default, Emacs’ file browser/manager dired usually presents you with a flat list of files in a given directory. Entering a subdirectory then opens a new buffer with the listing of the subdirectory. Sometimes you might want to be able to see the contents of the subdirectory and the current directory in the same view. Many GUI file browsers visualise this with a tree structure with nodes that can be expanded or collapsed. In Emacs there is a built-in function dired-insert-subdir that inserts a listing of the subdirectory under the cursor, at the bottom of the current buffer instead of in a new buffer, but I’ve never found that very helpful.

The dired-subtree package (part of the magnificent dired hacks) improves on this by allowing you to expand subdirectories in place, like a tree structure. To install the package, use the following code:

(use-package dired-subtree
  :config
  (bind-keys :map dired-mode-map
             ("i" . dired-subtree-insert)
             (";" . dired-subtree-remove)))

This sets up the keybinds so that in dired, hitting i on a subdirectory expands it in place with an indented listing. You can expand sub-subdirectories in the same way, and so on. Hitting ; inside an expanded subdirectory collapses it.

Happily, some of my other favourite tools from dired hacks like dynamically narrowing the directory listing or copying and pasting files work as you would want in these expanded subdirectories.

Get that spacemacs look without spacemacs

Spacemacs is an active, popular, and very comprehensive modular Emacs configuration. I’ve heard lots of good things about it but am happy with my own personal configuration so don’t want to switch. However, I really like the appearance of spacemacs. Happily, the key components are available stand-alone without needing the full spacemacs configuration.

I used the zenburn theme for a long time but more recently have switched to the spacemacs theme. I set this up using the following code in my emacs config file

(use-package spacemacs-theme
  :ensure t
  :init
  (load-theme 'spacemacs-dark t)
  (setq spacemacs-theme-org-agenda-height nil)
  (setq spacemacs-theme-org-height nil))

This contains a couple of options to stop the theme using variable heights for org-mode agenda items and headings. However, they didn’t have the desired effect for me to I also needed to add the following lines to my org-mode configuration:

;; set sizes here to stop spacemacs theme resizing these
(set-face-attribute 'org-level-1 nil :height 1.0)
(set-face-attribute 'org-level-2 nil :height 1.0)
(set-face-attribute 'org-level-3 nil :height 1.0)
(set-face-attribute 'org-scheduled-today nil :height 1.0)
(set-face-attribute 'org-agenda-date-today nil :height 1.1)
(set-face-attribute 'org-table nil :foreground "#008787")

The other aspect of the spacemacs appearance I like is the modeline. This is replicated using the spaceline package

(use-package spaceline
  :demand t
  :init
  (setq powerline-default-separator 'arrow-fade)
  :config
  (require 'spaceline-config)
  (spaceline-spacemacs-theme))