Category Archives: emacs

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))

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)))