Easily manage Emacs workspaces with eyebrowse

You know how many windows managers have workspaces you can switch between? These are variously called “virtual desktops” (e.g. KDE) or “spaces” on OS X, but the idea is the same; you have one workspace with a collection of windows/apps (say for mail and browsing) and another with the windows/apps for a particular project, and you can quickly switch between them. The eyebrowse packages gives a nice simple interface to the same experience in Emacs.

I install and configure eyebrowse with the following code in my emacs config file:

(use-package eyebrowse
  :diminish eyebrowse-mode
  :config (progn
            (define-key eyebrowse-mode-map (kbd "M-1") 'eyebrowse-switch-to-window-config-1)
            (define-key eyebrowse-mode-map (kbd "M-2") 'eyebrowse-switch-to-window-config-2)
            (define-key eyebrowse-mode-map (kbd "M-3") 'eyebrowse-switch-to-window-config-3)
            (define-key eyebrowse-mode-map (kbd "M-4") 'eyebrowse-switch-to-window-config-4)
            (eyebrowse-mode t)
            (setq eyebrowse-new-workspace t)))

The enables the shortcuts M-1 to M-4 to access 4 virtual desktops (N.B. you will have to disable the M- numeric prefixes first). Of course you can add more than 4 if you need to.

Now you will start by default in workspace 1. If you hit M-2 you will switch to a new empty workspace, numbered 2 in the modeline. It will initially just contain the scratch buffer, since we used (setq eyebrowse-new-workspace t). Open whichever buffers and window arrangements you like then hit M-1 to switch back to the first desktop where you will see the windows and buffers you had set up there.

A useful command is C-c C-w , (N.B. the comma is part of the command!) which runs eyebrowse-rename-window-config allowing you to name a workspace, and that name then appears in the modeline instead of the workspace number.

Prevent comments from breaking paragraphs in org-mode latex export

In an org-mode document, comments like this:

Some text forming a paragraph
# with some lines
# commented out
but I still want this to be a single paragraph.

are exported in latex like this:

Some text forming a paragraph

but I still want this to be a single paragraph.

which leads to a paragraph break between the two lines. This is the intended behaviour of the exporter, but I want it to export like this:

Some text forming a paragraph
but I still want this to be a single paragraph.

This was raised on stackexchange, and the mighty John Kitchin provided a quick solution with the following simple function to strip comments from the org file, which we then add to the org export hook:

;; remove comments from org document for use with export hook
;; https://emacs.stackexchange.com/questions/22574/orgmode-export-how-to-prevent-a-new-line-for-comment-lines
(defun delete-org-comments (backend)
  (loop for comment in (reverse (org-element-map (org-element-parse-buffer)
                    'comment 'identity))
    do
    (setf (buffer-substring (org-element-property :begin comment)
                (org-element-property :end comment))
          "")))

;; add to export hook
(add-hook 'org-export-before-processing-hook 'delete-org-comments)

and then when we export an org-mode file, the comments are stripped out on-the-fly giving the desired result. The original org-mode file is not modified – the comments stay in place.

Case-Insensitive Sorting in Dired on OS X

I like my dired directory listings to be sorted by name regardless of case. This was a bit fiddly to get working in OS X, but I found a solution using the built-in ls-lisp with a few extra options, rather than the system ls to generate the dired listing.

Here are the required settings:

;; using ls-lisp with these settings gives case-insensitve
;; sorting on OS X
(require 'ls-lisp)
(setq dired-listing-switches "-alhG")
(setq ls-lisp-use-insert-directory-program nil)
(setq ls-lisp-ignore-case t)
(setq ls-lisp-use-string-collate nil)
;; customise the appearance of the listing
(setq ls-lisp-verbosity '(links uid))
(setq ls-lisp-format-time-list '("%b %e %H:%M" "%b %e  %Y"))
(setq ls-lisp-use-localized-time-format t)

One downside of this is that it breaks dired-quick-sort, but I can live with that.

Set up a shortcut to insert a symbol

Despite living in the UK I am hard-wired from some years spent in the US to use a US keyboard layout. One problem for me is that these keyboards do not have a £ symbol on them. On a Mac, I can insert a £ using OPTION-3 but not in Emacs since I have OPTION set to META. This is easily addressed with a bit of code

(define-key global-map (kbd "C-c M-3") (lambda () (interactive) (insert "£")))

Now C-c M-3 will insert the £ symbol. N.B. I could have just bound this to M-3 to match the behaviour elsewhere on my Mac, but I already use that for something else!

A workflow to quickly add photos to org-mode notes

I was at a conference this week and a colleague was making notes using Evernote on her laptop and taking photos of key slides on her phone which then appeared in her notes. Of course I was making my notes in org-mode but I was envious of this behaviour so decided to emulate it.

With the function below, I can take a photo on my phone and upload to google drive (I use Photo & Picture Resizer, but you could use anything you like to get the pictures onto your computer). Then with a single command in Emacs, I am prompted with a list of photos in the folder to which they are uploaded, with the most recent first. The selected image is then:

  1. Moved the same directory as my org-mode notes file
  2. Renamed based on the heading of the current section in my notes, with a numeric suffix if there is already a photo with that name
  3. Linked in the notes and then the image is displayed

Here is a demonstration:

insert-slide-image.gif

Here is the code:

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; add image from conference phone upload                                 ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; use case is taking a photo of a slide in a conference and uploading
;; it to google drive or dropbox or whatever to get it on your
;; computer. You then want to embed it in an org-mode document by
;; moving it to the same folder and renaming according to the current
;; section of the org file, avoiding name clashes

;; required libraries
(require 'dash)
(require 'swiper)
(require 's)

;; start directory
(defvar bjm/conference-image-dir (expand-file-name "/path/to/image/upload/dir"))

(defun bjm/insert-conference-image ()
  "Insert image from conference directory, rename and add link in current file.

The file is taken from a start directory set by `bjm/conference-image-dir' and moved to the current directory, renamed and embedded at the point as an org-mode link. The user is presented with a list of files in the start directory, from which to select the file to move, sorted by most recent first."
  (interactive)
  (let (file-list target-dir file-list-sorted start-file start-file-full file-ext end-file end-file-base end-file-full file-number)
    ;; clean directories from list but keep times
    (setq file-list
          (-remove (lambda (x) (nth 1 x))
                   (directory-files-and-attributes bjm/conference-image-dir)))

    ;; get target directory
    (setq target-dir (file-name-directory (buffer-file-name)))

    ;; sort list by most recent
  ;; http://stackoverflow.com/questions/26514437/emacs-sort-list-of-directories-files-by-modification-date
  (setq file-list-sorted
        (mapcar #'car
                (sort file-list
                      #'(lambda (x y) (time-less-p (nth 6 y) (nth 6 x))))))

  ;; use ivy to select start-file
  (setq start-file (ivy-read
                    (concat "Move selected file to " target-dir ":")
                    file-list-sorted
                    :re-builder #'ivy--regex
                    :sort nil
                    :initial-input nil))

  ;; add full path to start file and end-file
  (setq start-file-full
        (expand-file-name start-file bjm/conference-image-dir))
  ;; generate target file name from current org section
  ;; (setq file-ext (file-name-extension start-file t))

  ;; my phone app doesn't add an extension to the image so I do it
  ;; here. If you want to keep the existing extension then use the
  ;; line above
  (setq file-ext ".jpg")
  ;; get section heading and clean it up
  (setq end-file-base (s-downcase (s-dashed-words (nth 4 (org-heading-components)))))
  ;; shorten to first 40 chars to avoid long file names
  (setq end-file-base (s-left 40 end-file-base))
  ;; number to append to ensure unique name
  (setq file-number 1)
  (setq end-file (concat
                  end-file-base
                  (format "-%s" file-number)
                  file-ext))

  ;; increment number at end of name if file exists
  (while (file-exists-p end-file)
    ;; increment
    (setq file-number (+ file-number 1))
    (setq end-file (concat
                    end-file-base
                    (format "-%s" file-number)
                    file-ext))
    )

  ;; final file name including path
  (setq end-file-full
        (expand-file-name end-file target-dir))
  ;; rename file
  (rename-file start-file-full end-file-full)
  (message "moved %s to %s" start-file-full end-file)
  ;; insert link
  (insert (org-make-link-string (format "file:%s" end-file)))
  ;; display image
  (org-display-inline-images t t)))

Multiple choices in yasnippets

I’ve written before about using yasnippet as a powerful text expansion and template tool in Emacs. In its basic form you type a keyword and press TAB to expand that into a template with active fields and then use TAB to move between the fields adding your content. I recently discovered that you can programme a snippet to contain multiple choices for fields.

For example, the following snippet uses the keyword test and inserts a template with the cursor initially in the first field and prompts the user to choose from the options “high”, “average” and “below average”. Selecting your choice and then hitting TAB takes you to the next field with options of “easy” and “hard”.

# -*- mode: snippet -*-
# name: test
# key: test
# --
Importance: ${1:$$(yas-choose-value '("high" "average" "below average"))}
Difficulty: ${2:$$(yas-choose-value '("easy" "hard"))}
$0

Insert internal org-mode links the ivy way

Org-mode is a fantastic way to organise information in simple text files. I often use internal links to other sections in a document for navigation, but I’ve found that there is not a great mechanism to quickly insert an internal link. I wanted an interface that would provide me a list of headlines in the document that I could use to select a link target, so I put together the functions below.

These simple functions leverage the fantastic ivy and worf packages, so install them first. Then put the code below into your emacs config file. Then, invoking M-x bjm/worf-insert-internal-link provides an ivy completion interface for the list of headlines in the document. Once you select the headline you want as the link target, the link is inserted for you.

;; use ivy to insert a link to a heading in the current document
;; based on `worf-goto`
(defun bjm/worf-insert-internal-link ()
  "Use ivy to insert a link to a heading in the current `org-mode' document. Code is based on `worf-goto'."
  (interactive)
  (let ((cands (worf--goto-candidates)))
    (ivy-read "Heading: " cands
              :action 'bjm/worf-insert-internal-link-action)))


(defun bjm/worf-insert-internal-link-action (x)
  "Insert link for `bjm/worf-insert-internal-link'"
  ;; go to heading
  (save-excursion
    (goto-char (cdr x))
    ;; store link
    (call-interactively 'org-store-link)
    )
  ;; return to original point and insert link
  (org-insert-last-stored-link 1)
  ;; org-insert-last-stored-link adds a newline so delete this
  (delete-backward-char 1)
  )

To eww or not to eww

Recent Emacs versions include a decent text based web browser called eww. There is a good example configuration here. I found that I like using eww for some things, but at other times I want to use a full GUI web browser (I like Firefox).

I set eww to be my default browser in Emacs with

(setq browse-url-browser-function 'eww-browse-url)

but then I create some wrapper functions for times when I want to use my system default browser (i.e. Firefox).

In mu4e emails, hitting g will now open a URL in eww, but with the following lines I can use G to open in Firefox:

;; open link in firefox rather than eww
(defun bjm/mu4e-view-go-to-url-gui ()
  "Wrapper for mu4e-view-go-to-url to use gui browser instead of eww"
  (interactive)
  (let ((browse-url-browser-function 'browse-url-default-browser))
    (mu4e-view-go-to-url)))
;; bind it
(define-key mu4e-view-mode-map (kbd "G") 'bjm/mu4e-view-go-to-url-gui)

In elfeed, hitting b opens an article in eww if that is set to be the default Emacs browser. With the following lines I can hit B to open an article in Firefox. Note that I had to resort to calling the Mac open command to open the URL with the system default browser. On linux systems the xdg-open command should do the same thing.

;; browse article in gui browser instead of eww
(defun bjm/elfeed-show-visit-gui ()
  "Wrapper for elfeed-show-visit to use gui browser instead of eww"
  (interactive)
  (let ((browse-url-generic-program "/usr/bin/open"))
    (elfeed-show-visit t)))

(define-key elfeed-show-mode-map (kbd "B") 'bjm/elfeed-show-visit-gui)

A shorter shortcut to capture todo tasks

Org mode is wonderful for managing todo lists. Usually to add a task, I would run org-capture using the default C-c c and then select my todo template by hitting t. I do this so often that I wanted a shorter shortcut, so I defined a function to call org-capture with the specific template I wanted and bound this to a simple shortcut (I used C-9 after removing numeric prefixes):

;; function to capture a todo
(defun bjm/org-capture-todo ()
  (interactive)
  "Capture a TODO item"
  (org-capture nil "t"))

;; bind
(define-key global-map (kbd "C-9") 'bjm/org-capture-todo)

Now C-9 takes me straight to my todo capture template.

An unobtrusive email monitor for mu4e on the Mac

This is not exactly an Emacs post, but some users of the mighty mu4e might find it useful. I don’t like big alerts to new emails that distract me from what I am doing, but I also don’t want to keep switching to my mu4e buffer to see if new emails have arrived. My solution is to have a small notification item in my Mac status bar to tell me how many emails I have.

It looks like this (the bit underlined in red):

email-counter2.png

The text @ 1/3/0/0 tells me that I have, respectively,

  • 1 unread email in my inbox.
  • 3 total emails in my inbox (i.e. 3 too many!).
  • 0 emails in my drafts folder. This is useful to keep an eye on in case I save a draft and then forget about it.
  • 0 emails in my outbox. This is my postfix email outbox and is useful to keep an eye on in case emails sent when I’m offline don’t get automatically sent when I reconnect.

To set this up I use shellwrangler which is a small app that embeds the output of a script in the status bar. Update 2017-11-10 shellwrangler appears dead and bitbar looks like a superior alternative. This then calls the following perl script which generates the text. Shellwrangler Bitbar updates at a specified interval so that is all there is to it.

#!/usr/bin/perl -w

###########################################################################
#
# Count number of emails in inbox, drafts and outbox for mail indexed
# with mu and sent with sendmail or equivalent
#
# by Ben Maughan
# http://www.pragmaticemacs.com
#
# Feel free to distribute, modify, whatever
#
###########################################################################

## total mails in inbox
chomp(my @tot=`/usr/local/bin/mu find maildir:/work/INBOX 2>/dev/null`);
## unread mails in inbox
chomp(my @unread=`/usr/local/bin/mu find maildir:/work/INBOX AND flag:unread 2>/dev/null`);
## drafts
chomp(my @drafts=`/usr/local/bin/mu find maildir:/work/[Gmail].Drafts 2>/dev/null`);

## number in outbox
chomp(my @mailq=`/usr/bin/mailq`);
my @outbox = grep /^[0-9A-F]{12}/, @mailq;

printf "@ %d/%d/%d/%d",$#unread+1,$#tot+1,$#drafts+1,$#outbox+1;

exit;