Switch branches/tags
Nothing to show
Find file Copy path
2651d09 Nov 11, 2018
1 contributor

Users who have contributed to this file

9619 lines (7753 sloc) 357 KB

My Emacs Config


;;; init.el --- personal emacs config file -*- lexical-binding: t ; eval: (read-only-mode 1)-*-

Assumptions & Init

We assume that the following variables are defined:

  • malb/common-file-targets - for helm
  • malb/documents-dir - documents
  • malb/inbox-org - this is where I store tasks
  • malb/literature-dirs - PDFs of papers
  • malb/literature-notes-dir - notes on papers
  • malb/mu4e-name-replacements - e-mail name replacements
  • malb/org-files-dir - org files go here
  • malb/org-files - org files
  • malb/org-mode-ics - icalendar file
  • malb/paradox-github-token - github login
  • malb/projectile-ignored-projects - ignored projects
  • malb/projects-dir - a super-repository of which all of my projects are subprojects
  • malb/sage-executable - full path of Sage executable
  • malb/sync-dir - documents that are synchronised
  • malb/deft-directory - where deft files live

We collect our own customisations in the malb group.

(defgroup malb nil
  "malb's personal config"
  :group 'convenience)


We have RAM, lots of it.

(setq global-mark-ring-max 256
      mark-ring-max 256
      kill-ring-max 256)

Define some functions to disable/enable GC (source)

(defun malb/disable-garbage-collection ()
  "Disable garbage collection."
  (setq gc-cons-threshold most-positive-fixnum
        gc-cons-percentage 0.6))

(defun malb/enable-garbage-collection ()
  "Reset garbage collection to small-ish limit."
  (setq gc-cons-threshold 16777216
        gc-cons-percentage 0.1))

Helm runs with GC disabled:

(add-hook 'minibuffer-setup-hook #'malb/disable-garbage-collection)
(add-hook 'minibuffer-exit-hook  #'malb/enable-garbage-collection)

We disable GC during startup, we re-enable it afterwards.

(add-hook 'emacs-startup-hook #'malb/enable-garbage-collection)

Note: Don’t set gc-cons-threshold to something much bigger as it will cause to hang occasionally for a long-ish time.


Enable TLS cert checking (source)

(let ((trustfile (replace-regexp-in-string
                  "\\\\" "/" (replace-regexp-in-string
                              "\n" "" (shell-command-to-string "python -m certifi")))))
  (setq tls-program (list
                     (format "gnutls-cli%s --x509cafile %s -p %%p %%h"
                             (if (eq window-system 'w32) ".exe" "") trustfile))
        gnutls-verify-error t
        gnutls-trustfiles (list trustfile)
        tls-checktrust t))

;; (let ((bad-hosts
;;        (loop for bad
;;              in `(""
;;                   "")
;;              if (condition-case e
;;                     (url-retrieve
;;                      bad (lambda (retrieved) t))
;;                   (error nil))
;;              collect bad)))
;;   (if bad-hosts
;;       (error (format "tls misconfigured; retrieved %s ok"
;;                      bad-hosts))
;;     (url-retrieve ""
;;                   (lambda (retrieved) t))))


Check if any regexp matches string, because OR is hard …

(defun malb/regexp-match-p (regexps string)
  (and string
       (catch 'matched
         (let ((inhibit-changing-match-data t))
           (dolist (regexp regexps)
             (when (string-match regexp string)
               (throw 'matched t)))))))

Package Management

Configure package repositories

(setq package-archives '(("gnu" . "")
                         ("melpa" . "")
                         ("melpa-stable" . "")
                         ("org" . ""))
      package-archive-priorities '(("gnu" . 10)
                                   ("melpa" . 20)
                                   ("melpa-stable" . 5)
                                   ("org" . 20))
      package-menu-hide-low-priority nil)

Get the package manager going, but do not autoload packages.


Use use-package to keep our configuration readable.

(require 'use-package)

(setq  use-package-compute-statistics t
       use-package-verbose t
       use-package-always-ensure t)

Paradox is a better package list

  • Visit the package’s homepage with v
  • View a list of recent commits with l
  • Shortcuts for package filtering:
    • f r filters by regexp (occur)
    • f u display only packages with upgrades
    • f k filters by keyword
    • f c clear filter
  • Hit h to see all keys
(use-package paradox
  :ensure paradox
  :commands (paradox-upgrade-packages paradox-list-packages)
  :config (setq paradox-execute-asynchronously t
                paradox-github-token malb/paradox-github-token
                paradox-automatically-star t))


Allow pixel-wise scaling

(setq frame-resize-pixelwise t)

Disable Clutter

(setq inhibit-startup-screen t)

(if (fboundp 'menu-bar-mode) (menu-bar-mode -1))
(if (fboundp 'tool-bar-mode) (tool-bar-mode -1))
(if (fboundp 'scroll-bar-mode) (scroll-bar-mode -1))


(use-package exec-path-from-shell
  :config (progn (exec-path-from-shell-initialize)
                 (exec-path-from-shell-copy-env "GOPATH")))

Keyboard Shortcuts

We use Super (in our case: CapsLock) as a menu of sorts:

(define-prefix-command 'f2-global-map)
(bind-key "<f2>" #'f2-global-map)

(defun malb/set-menu-key (char func)
  (bind-key (concat "s-" char) func)
  (bind-key char func f2-global-map))

Key Chords

Key chords allow to execute actions by pressing one or two keys really fast. Sometimes we might want to use those. However, mostly, we don’t as the slight delay they introduce (to check if a key chord should be executed or the text inserted) is annoying.

(use-package key-chord
  :config (progn
            (setq key-chord-one-key-delay 0.2
                  key-chord-two-keys-delay 0.1)
            (key-chord-mode 1)))

Window Management


When splitting windows open the previous buffer in it.

(defun malb/vsplit-last-buffer ()
  "Split the window vertically and display the previous buffer."
  (other-window 1 nil)

(defun malb/hsplit-last-buffer ()
  "Split the window horizontally and display the previous buffer."
  (other-window 1 nil)

(bind-key "C-x 2" 'malb/vsplit-last-buffer)
(bind-key "C-x 3" 'malb/hsplit-last-buffer)

Don’t split horizontally.

(setq split-width-threshold 70
      split-height-threshold nil)

(defun malb/split-window-sensibly (&optional window)
  (let ((window (or window (selected-window))))
    ;; DIFF: prefer horizontal split
    (or (and (window-splittable-p window t)
             ;; DIFF: restrict to two windows side-by-side
             (= (length (window-list)) 1)
             ;; Split window horizontally.
             (with-selected-window window
        (and (window-splittable-p window)
             ;; Split window vertically.
             (with-selected-window window
        (and (eq window (frame-root-window (window-frame window)))
             (not (window-minibuffer-p window))
             ;; If WINDOW is the only window on its frame and is not the
             ;; minibuffer window, try to split it vertically disregarding
             ;; the value of `split-height-threshold'.
             (let ((split-height-threshold 0))
               (when (window-splittable-p window)
                 (with-selected-window window

(setq split-window-preferred-function #'malb/split-window-sensibly)

Resize windows to accommodate new ones.

(setq window-combination-resize t)

Restoring Configurations

Winner mode is a global minor mode. When activated, it allows to “undo” (and “redo”) changes in the window configuration.

(winner-mode 1)

zygospore lets you revert C-x 1 (delete-other-window) by pressing C-x 1 again.

(use-package zygospore
  :bind (("C-x 1" . zygospore-toggle-delete-other-windows)))

Multiple Window Configurations (Eyebrowse)

eyebrowse is a simple-minded way of managing window configuration.

(use-package eyebrowse
  :diminish eyebrowse-mode
  :init (setq eyebrowse-keymap-prefix (kbd "C-c E"))
  :config (progn
            (setq eyebrowse-wrap-around t)
            (eyebrowse-mode t)

            (defun malb/eyebrowse-new-window-config ()
              "make new eyebrowse config, re-using indices 1 - (1)0"
              (let ((done nil))
                (dotimes (i 10)
                  ;; start at 1 run till 0
                  (let ((j (mod (+ i 1) 10)))
                    (when (and (not done)
                               (not (eyebrowse--window-config-present-p j)))
                      (eyebrowse-switch-to-window-config j)
                      (call-interactively 'eyebrowse-rename-window-config j)
                      (setq done t))))))))

Switching (Ace-Window)

ace-window for switching windows. (source)

(use-package ace-window
  :commands ace-window
  :bind ("C-x o" . ace-window)
  :config (progn
            (setq aw-keys   '(?a ?s ?d ?f ?j ?k ?l)
                  aw-dispatch-always nil
                  '((?x aw-delete-window     "Ace - Delete Window")
                    (?c aw-swap-window       "Ace - Swap Window")
                    (?n aw-flip-window)
                    (?v aw-split-window-vert "Ace - Split Vert Window")
                    (?h aw-split-window-horz "Ace - Split Horz Window")
                    (?g delete-other-windows "Ace - Maximize Window")
                    (?b balance-windows)
                    (?u winner-undo)
                    (?r winner-redo)))))

Quickly jump back and forth between buffers (source)

(defun malb/switch-to-previous-buffer ()
  "Switch to previously open buffer.

Repeated invocations toggle between the two most recently open buffers."
  (switch-to-buffer (other-buffer (current-buffer) 1)))

Special Windows

Some buffers should behave like pop ups, i.e. display at the bottom with 0.3 height.

(defvar malb/popup-windows '("\\`\\*compilation\\*\\'"
                             "\\`\\*helm flycheck\\*\\'"
                             "\\`\\*Flycheck errors\\*\\'"
                             "\\`\\*helm projectile\\*\\'"
                             "\\`\\*Helm all the things\\*\\'"
                             "\\`\\*Helm Find Files\\*\\'"
                             "\\`\\*Synonyms List\\*\\'"
                             "\\`\\*Google Translate\\*\\'"
                             "\\` \\*LanguageTool Errors\\* \\'"
                             "\\`\\*Edit footnote .*\\*\\'"
                             "\\`\\*TeX errors*\\*\\'"
                             "\\`\\*Org Export Dispatcher\\*\\'"
                             "\\`\\*Helm Swoop\\*\\'"
; (setq display-buffer-alist nil)
(dolist (name malb/popup-windows)
  (add-to-list 'display-buffer-alist
                 (reusable-frames . visible)
                 (side            . bottom)
                 (window-height   . 0.3))) t)

(add-to-list 'display-buffer-alist '(".*" (malb/frame-dispatch)) t)

Closing/Promoting Pop-up-style Windows

In case we just want to kill the bottom window, set a shortcut do to this.

(defun malb/quit-bottom-disposable-windows ()
  "Quit disposable windows of the current frame."
  (dolist (window (window-at-side-list))
    (if (<= (window-height window) (/ (frame-height) 3))
        (delete-window window))))

(defun malb/promote-disposable-window ()
  "Promote disposable window to real window."
  (dolist (window (window-at-side-list))
    (let ((buffer (window-buffer window))
          (display-buffer-alist nil))
      (delete-window window)
      (display-buffer buffer))))

(key-chord-define-global "qq" #'malb/quit-bottom-disposable-windows)
(key-chord-define-global "qw" #'malb/promote-disposable-window)

Compilation Window

If there was no error the window closes automatically.

(defun malb/compilation-exit-autoclose (status code msg)
  ;; If M-x compile exists with a 0
  (when (and (eq status 'exit) (zerop code))
    ;; and delete the *compilation* window
    (let ((compilation-window (get-buffer-window (get-buffer "*compilation*"))))

      (when (and (not (window-at-side-p compilation-window 'top))
                 (window-at-side-p compilation-window 'left)
                 (window-at-side-p compilation-window 'right))
        (delete-window compilation-window))))
  ;; Always return the anticipated result of compilation-exit-message-function
  (cons msg code))

(setq compilation-exit-message-function #'malb/compilation-exit-autoclose)

If you change the variable compilation-scroll-output to a non-nil value, the compilation buffer scrolls automatically to follow the output. If the value is first-error, scrolling stops when the first error appears, leaving point at that error. For any other non-nil value, scrolling continues until there is no more output.

(setq compilation-scroll-output 'first-error)

Dispatch Buffers to Frames

Separate mu4e windows from others (source)

Mu4e buffers start with *mu4e-:

(defvar malb/mu4e-buffer-regexps '("mu4e-.*")
  "Buffer names of mu4e buffers.")

But we allow helm buffers in the mu4e frame, e.g. to pick attachments.

(defvar malb/mu4e-buffer-allowed-regexps (append malb/popup-windows '("\\`\\*helm.*\\*\\'"))
  "Buffer names allowed in mu4e frame.")

Get frame by name or return passed frame.

(defun malb/get-frame (frame)
  "Return a frame, if any, named FRAME (a frame or a string).
If none, return nil.
If FRAME is a frame, it is returned."
  (let ((malb/get-frame-name
         (lambda (&optional frame)
           (unless frame (setq frame (selected-frame)))
           (if (framep frame)
               (cdr (assq 'name (frame-parameters frame)))
             (error "Argument not a frame: `%s'" frame)))))
    (cond ((framep frame) frame)
          ((stringp frame)
           (catch 'get-a-frame-found
             (dolist (fr (frame-list))
               (when (string= frame (funcall malb/get-frame-name fr))
                 (throw 'get-a-frame-found fr)))
           (error "Arg neither a string nor a frame: `%s'" frame)))))

Find a frame which isn’t the mu4e frame or create a fresh one.

(defun malb/switch-to-mu4e ()
  (let ((other-frame (catch 'other-frame
                       (dolist (frame (frame-list))
                         (if (string-match "mu4e" (frame-parameter frame 'name))
                             (throw 'other-frame frame)
    (if other-frame
        (select-frame-set-input-focus other-frame)

Find a frame not named mu4e if there is any, make one otherwise.

(defun malb/switch-away-from-mu4e ()
  (let ((other-frame (catch 'other-frame
                       (dolist (frame (frame-list))
                         (if (and (not (string-match "mu4e" (frame-parameter frame 'name)))
                                  (not (string-match "F1" (frame-parameter frame 'name)))) ;; emacsclient
                             (throw 'other-frame frame)
    (if other-frame
        (select-frame-set-input-focus other-frame)
      (select-frame-set-input-focus  (make-frame)))))

Main dispatch function:

(defun malb/frame-dispatch (buffer alist)
  "Assigning buffers to frames."
  ;; (message (format "dispatching %s" (buffer-name buffer)))
   ;; this is a mu4e buffer
     (malb/regexp-match-p malb/mu4e-buffer-regexps (buffer-name buffer))
     (memq (buffer-local-value 'major-mode buffer) '(mu4e-view-mode mu4e-headers-mode)))
    (if (malb/get-frame "mu4e") (select-frame-set-input-focus (malb/get-frame "mu4e"))
      (make-frame (list '(name . "mu4e"))))
    (unless (get-buffer-window buffer)
      (set-window-buffer (get-largest-window) buffer))
    (select-window (get-buffer-window buffer))
    t ;; we are done

   ;; this is not a mu4e buffer but we’re in the mu4e frame
   ((and (string-match "mu4e" (frame-parameter nil 'name))
         (not (malb/regexp-match-p malb/mu4e-buffer-regexps (buffer-name buffer)))
         (not (malb/regexp-match-p malb/mu4e-buffer-allowed-regexps (buffer-name buffer))))
    nil ;; pass control back to display-buffer-alist
   ;; just hand back control to diplay-buffer-alist
   (t nil))

Overwrite find-file to avoid mu4e frame.

(defun find-file (filename &optional wildcards)
  "Edit file FILENAME.
Switch to a buffer visiting file FILENAME,
creating one if none already exists.
Interactively, the default if you just type RET is the current directory,
but the visited file name is available through the minibuffer history:
type M-n to pull it into the minibuffer.

You can visit files on remote machines by specifying something
like /ssh:SOME_REMOTE_MACHINE:FILE for the file name.  You can
also visit local files as a different user by specifying
/sudo::FILE for the file name.
See the Info node `(tramp)File name Syntax' in the Tramp Info
manual, for more about this.

Interactively, or if WILDCARDS is non-nil in a call from Lisp,
expand wildcards (if any) and visit multiple files.  You can
suppress wildcard expansion by setting `find-file-wildcards' to nil.

To visit a file without any kind of conversion and without
automatically choosing a major mode, use `find-file-literally’."
   (find-file-read-args "Find file: "
  (let ((value (find-file-noselect filename nil nil wildcards)))
    (if (string-match "mu4e" (frame-parameter nil 'name))
          (if (listp value)
              (mapcar display-buffer (nreverse value))
            (display-buffer value)
            (switch-to-buffer value)))
        (if (listp value)
            (mapcar switch-to-buffer (nreverse value))
          (switch-to-buffer value))))))
don’t replace find-file but advice find-file

Run old-function with an empty display-buffer-alist

(defun malb/clean-display-buffer-alist (old-function &rest arguments)
  "Call old-function with an empty display-buffers-alist"
  (let ((display-buffer-alist nil))
    (apply old-function arguments)))

Dedicated Mode


(defvar dedicated-mode nil
  "Mode variable for dedicated minor mode.")

(make-variable-buffer-local 'dedicated-mode)

(defun dedicated-mode (&optional arg)
  "Dedicated minor mode."
  (interactive "P")
  (setq dedicated-mode (not dedicated-mode))
  (set-window-dedicated-p (selected-window) dedicated-mode)
  (if (not (assq 'dedicated-mode minor-mode-alist))
      (setq minor-mode-alist
	    (cons '(dedicated-mode " D")

Window Management Hydra

(defhydra malb/hydra-window ()
^Misc^        ^Split^         ^Buffer^         ^Resize^         ^Eyebrowse^^     ^Center^   ^Jumping^   ^Other^
_←_ ←         _v_ertical      _H_elm-omni      _q_ X←           _[_ \\/ new      _C_enter   _j_ump      _W_ store cfg
_↓_ ↓         _h_orizontal    _p_revious buf   _w_ X↓           _;_ \\/ ←        _,_ C←     _l_ine      _J_ load cfg
_↑_ ↑         _z_ undo        _n_ext buf       _e_ X↑           _'_ \\/ →        _._ C→     ^^          _F_ treemacs
_→_ →         _Z_ reset       _t_oggle buf     _r_ X→           _]_ \\/ close
^^            _d_lt this      _a_ce 1          _g_olden-ratio   _!_ \\/ rename
_SPC_ cancel  _D_lt other     _s_wap
^^            _o_nly this
  ("<left>" windmove-left  :color blue)
  ("<down>" windmove-down  :color blue)
  ("<up>" windmove-up  :color blue)
  ("<right>" windmove-right  :color blue)
  ("j" avy-goto-word-1 :color blue)
  ("l" avy-goto-line :color blue)
  ("q" shrink-window-horizontally)
  ("w" shrink-window)
  ("e" enlarge-window)
  ("r" enlarge-window-horizontally)
  ("g" golden-ratio-mode)
  ("H" malb/helm-omni :color blue)
  ("1" previous-buffer)
  ("2" next-buffer)
  ("p" previous-buffer)
  ("n" next-buffer)
  ("t" malb/switch-to-previous-buffer :color blue)
  ("a" (lambda () (interactive) (ace-window 1)) :color blue)
  ("v" malb/vsplit-last-buffer)
  ("h" malb/hsplit-last-buffer)
  ("s" (lambda () (interactive) (ace-window 4)) :color blue)
  ("d" delete-window)
  ("D" (lambda () (interactive) (ace-window 16)) :color blue)
  ("o" delete-other-windows :color blue)
  ("z" (progn (winner-undo) (setq this-command 'winner-undo)))
  ("Z" winner-redo)
  ("F" treemacs-projectile :color blue)
  ("[" malb/eyebrowse-new-window-config :color blue)
  (";" (lambda () (interactive) (eyebrowse-prev-window-config nil)) :color red)
  ("'" (lambda () (interactive) (eyebrowse-next-window-config nil)) :color red)
  ("]" eyebrowse-close-window-config :color blue)
  ("!" eyebrowse-rename-window-config :color blue)
  ("C" visual-fill-column-mode)
  ("," (lambda () (interactive) (set-fill-column (+ fill-column 2)) (visual-fill-column-adjust)) :color red)
  ("." (lambda () (interactive) (set-fill-column (- fill-column 2)) (visual-fill-column-adjust)) :color red)
  ("W" window-configuration-to-register)
  ("J" jump-to-register  :color blue)
  ("1" eyebrowse-switch-to-window-config-1 :color blue)
  ("2" eyebrowse-switch-to-window-config-2 :color blue)
  ("3" eyebrowse-switch-to-window-config-3 :color blue)
  ("4" eyebrowse-switch-to-window-config-4 :color blue)
  ("5" eyebrowse-switch-to-window-config-5 :color blue)
  ("6" eyebrowse-switch-to-window-config-6 :color blue)
  ("7" eyebrowse-switch-to-window-config-7 :color blue)
  ("8" eyebrowse-switch-to-window-config-8 :color blue)
  ("9" eyebrowse-switch-to-window-config-9 :color blue)
  ("SPC" nil)
  ("`" other-window :color blue))

(key-chord-define-global "\\x" #'malb/hydra-window/body)
(bind-key "¬" #'other-window)

Clean Mode Line

Use diminish.el to remove mentions of minor modes from the mode-line as we’re using a quite few of them and don’t want to waste the real estate. Most diminishing is done by the :diminish parameter to use-package.

(use-package diminish)

delight.el allows us to rewrite mode-line statuses of minor modes. Which we use when the diminish keyword is not enough.

(use-package delight)

Jumping Around (source)

See Emacs Rocks #10 which is on ace-jump-mode which inspired avy.

(use-package avy
  :bind (("C-c C-<SPC>" . avy-goto-word-or-subword-1) ;; avy-goto-char-timer
         ("C-c j j" . avy-goto-word-or-subword-1)
         ("M-g g" . avy-goto-line))
  :config (progn
            (setq avy-background t)))

Jumping to Links

Currently, to jump to a link in an Info-mode or help-mode or woman-mode or org-mode or eww-mode or compilation-mode buffer, you can tab through the links to select the one you want. This is an O(N) operation, where the N is the amount of links. This package turns this into an O(1) operation, or at least O(log(N)) if you manage to squeeze thousands of links in one screen. It does so by assigning a letter to each link using avy. (source)

(use-package ace-link
  :config (ace-link-setup-default))

Jumping through Edit Points

Use goto-chg to jump through edit points (source)

(use-package goto-chg
  :bind (("C-c j ," . goto-last-change)
         ("C-c j ." . goto-last-change-reverse)))

Tip C-u 0 C-c j ,​ description of the change at a particular stop on your tour

Visual Bookmarks


(use-package bm
  :bind (("C-c j b ." . bm-next)
         ("C-c j b ," . bm-previous)
         ("C-c j b SPC" . bm-toggle)))



YASnippet is a template system for Emacs. It allows you to type an abbreviation and automatically expand it into function templates. (source) (source)

Also, see Emacs Rocks #06 which is on yasnippet.

You can call yas-decribe-tables to see currently defined snippets, I usually just use Helm YaSnippet.

We disable yasnippet if there are no snippets. (source)

(use-package yasnippet
  :diminish yas-minor-mode
  :config (progn
            (setq yas-verbosity 1)

            (defun malb/disable-yas-if-no-snippets ()
              (when (and yas-minor-mode (null (yas--get-snippet-tables)))
                (yas-minor-mode -1)))
            (add-hook 'yas-minor-mode-hook #'malb/disable-yas-if-no-snippets)))

The official yasnippet snippet collection

(use-package yasnippet-snippets)

Auto YASnippet

auto-yasnippet is a hybrid of keyboard macros and yasnippet. You create the snippet on the go, usually to be used just in the one place. It’s fast, because you’re not leaving the current buffer, and all you do is enter the code you’d enter anyway, just placing ~ where you’d like yasnippet fields and mirrors to be.

(use-package auto-yasnippet
  :bind (("C-c y c" . aya-create)
         ("C-c y e" . aya-expand)))

Auto Insert

We populate empty files with yasnippet (source)

(defun malb/auto-insert-snippet (key &optional mode)
  "Auto insert a snippet of yasnippet into new file."
  (let ((is-yasnippet-on (not (cond ((functionp yas-dont-activate-functions)
                                     (funcall yas-dont-activate-functions))
                                    ((consp yas-dont-activate-functions)
                                     (some #'funcall yas-dont-activate-functions))
        (snippet (let ((template (cdar (mapcan #'(lambda (table) (yas--fetch table key))
                                               (yas--get-snippet-tables mode)))))
                   (if template (yas--template-content template) nil))))
    (when (and is-yasnippet-on snippet)
      (yas-expand-snippet snippet))))
(use-package autoinsert
  :after yasnippet
  :config (progn
            (setq auto-insert-query nil ; Don't prompt before insertion
                  auto-insert-alist '()) ; Tabula rasa
            (auto-insert-mode 1)
             (lambda (rule) (define-auto-insert
                              (nth 0 rule)
                              (vector `(lambda () (malb/auto-insert-snippet ,(nth 1 rule) ',(nth 2 rule))))))
             `(("/announcements/20.+\\.md$"  "isg-seminar-announce"  markdown-mode)))))
split data from implementation

Helm YaSnippet

helm-c-yasnippet for selecting snippets with helm. However, long-form snippets are mostly handled by yankpad.

(use-package helm-c-yasnippet
  :after (helm yasnippet)
  :commands (helm-yas-complete)
  :bind (:map yas-minor-mode-map
              ("C-c h y" .  helm-yas-complete))
  :config (progn
            (setq helm-yas-space-match-any-greedy t)))


Let’s say that you have text snippets that you want to paste, but that yasnippet or skeleton is a bit too much when you do not need a shortcut/abbrev for your snippet. You like org-mode, so why not write your snippets there? Introducing the yankpad: — (source)

(use-package yankpad
  :after (helm yasnippet projectile)
  :init (setq yankpad-file (expand-file-name "" malb/org-files-dir))
  :config (bind-key "C-c h Y" #'yankpad-insert yas-minor-mode-map))

Auto Completion (Company)

Use company-mode for auto-completion. (source)

(use-package company
  :bind (("M-/" . company-complete))
  :defer nil
  :config (progn
            (setq company-tooltip-limit 20 ; bigger popup window
                  company-idle-delay 0.6   ; delay for popup
                  company-echo-delay 0     ; remove blinking
                  company-show-numbers t   ; show numbers for easy selection
                  company-selection-wrap-around t
                  company-require-match nil
                  company-dabbrev-ignore-case t
                  company-dabbrev-ignore-invisible t
                  company-dabbrev-other-buffers t
                  company-dabbrev-downcase nil
                  company-dabbrev-code-everywhere t
                  company-tooltip-align-annotations t
                  company-minimum-prefix-length 1
                  company-global-modes '(not) ; company is "always on", except for a few … exceptions
                  company-lighter-base "")

            (global-company-mode 1)

            (bind-key "C-n"   #'company-select-next company-active-map)
            (bind-key "C-p"   #'company-select-previous company-active-map)
            (bind-key "<tab>" #'company-complete company-active-map)
            (bind-key "M-?"   #'company-show-doc-buffer company-active-map)
            (bind-key "M-."   #'company-show-location company-active-map)
            (bind-key "M-/"   #'company-complete-common org-mode-map)))

Use company-quickhelp to display quick help.

(use-package company-quickhelp
  :config (company-quickhelp-mode 1))

Use company-pcomplete not always because of this bug.

(use-package company-pcomplete
  :ensure nil
  :config (defun malb/enable-company-pcomplete ()
            (set (make-local-variable 'company-backends)
                 (append (list #'company-pcomplete) company-backends))))
check if conditional enabling of company-pcomplete is still needed
(use-package company-lsp
  :after (company lsp-mode)
  :config (push 'company-lsp company-backends))


For Python use company-anaconda.

(use-package company-anaconda
  :config (add-to-list 'company-backends #'company-anaconda))


For \LaTeX use company-auctex. We also allow unicode symbols via company-math, hence we manage what to add when carefully below.

(use-package company-math)

(use-package company-auctex
  :config (progn
            (add-to-list 'company-backends

company-refex is used for \LaTeX labels.

(use-package company-reftex
   :config (add-to-list 'company-backends #'company-reftex-labels))

BibTeX is handled by Helm BibTeX below.


Use fish-completion for pcomplete which is then used by company-pcomplete

(use-package fish-completion
  :config (progn
            (setq fish-completion-fallback-on-bash-p t)

Fish completion will fall back to bash-completion if there is no fish shell.

(use-package bash-completion)

Company and YaSnippet Integration

Add YasSippet support for all company backends. (source)

Note: Do this at the very end.

(defvar malb/company-mode/enable-yas t
  "Enable yasnippet for all backends.")

(defun malb/company-mode/backend-with-yas (backend)
  (if (or (not malb/company-mode/enable-yas)
          (and (listp backend)
               (member 'company-yasnippet backend)))
    (append (if (consp backend) backend (list backend))
            '(:with company-yasnippet))))

(defun malb/activate-yasnippet-completion ()
  (setq company-backends
        (mapcar #'malb/company-mode/backend-with-yas company-backends)))

(add-hook 'emacs-startup-hook #'malb/activate-yasnippet-completion)


  1. yas-expand is run first and does what it has to, then it calls malb/indent-fold-or-complete.
  2. This function then hopefully does what I want:
    1. if a region is active, just indent
    2. if we’re looking at a space after a non-whitespace character, we try some company-expansion
    3. If hs-minor-mode or outline-minor-mode is active, try those next
    4. otherwise call whatever would have been called otherwise.

(source, source)

(defun malb/indent-fold-or-complete (&optional arg)
  (interactive "P")
   ;; if a region is active, indent
    (indent-region (region-beginning)
   ;; if the next char is space or eol, but prev char not whitespace
   ((and (not (active-minibuffer-window))
         (or (looking-at " ")
             (looking-at "$"))
         (looking-back "[^[:space:]]" nil)
         (not (looking-back "^" nil)))

    (cond (company-mode (company-complete-common))
          (auto-complete-mode (auto-complete))))

   ;; no whitespace anywhere
   ((and (not (active-minibuffer-window))
         (looking-at "[^[:space:]]")
         (looking-back "[^[:space:]]" nil)
         (not (looking-back "^" nil)))
     ((bound-and-true-p hs-minor-mode)
      (save-excursion (end-of-line) (hs-toggle-hiding)))
     ((bound-and-true-p outline-minor-mode)
      (save-excursion (outline-cycle)))))

   ;; by default just call whatever was bound
    (let ((fn (or (lookup-key (current-local-map) (kbd "TAB"))
      (if (not (called-interactively-p 'any))
          (fn arg)
        (setq this-command fn)
        (call-interactively fn))))))

(bind-key "<tab>" #'malb/indent-fold-or-complete)

Sometimes, you just want to fold.

(defun malb/toggle-fold ()
  (cond ((eq major-mode 'org-mode)
        ((bound-and-true-p hs-minor-mode)

        ((bound-and-true-p outline-minor-mode)

(bind-key "C-<tab>" #'malb/toggle-fold)


Helm is incremental completion and selection narrowing framework for Emacs. (source)

See A Package in a league of its own: Helm for a nice introduction.

C-wyanks word at point
M-nyanks symbol at point

General, Buffers, Files

Don’t use the vanilla helm-buffers command for C-x C-b but combine many sources to create malb/helm-omni. (source)

Tip: Use @foo to search for content foo in buffers when in helm-omni. Use *lisp to search for buffers in lisp-mode. Use *!list to search for buffers not in lisp-mode.

(defun malb/helm-omni (&rest arg)
  ;; just in case someone decides to pass an argument, helm-omni won't fail.
  (unless helm-source-buffers-list
    (setq helm-source-buffers-list
          (helm-make-source "Buffers" 'helm-source-buffers)))

    (if (projectile-project-p)
      '(helm-source-buffers-list)) ;; list of all open buffers

    `(((name . "Virtual Workspace")
       (candidates . ,(--map (cons (eyebrowse-format-slot it) (car it))
                             (eyebrowse--get 'window-configs)))
       (action . (lambda (candidate)
                   (eyebrowse-switch-to-window-config candidate)))))

    (if (projectile-project-p)
      '(helm-source-recentf)) ;; all recent files

    ;; always make some common files easily accessible
    '(((name . "Common Files")
       (candidates . malb/common-file-targets)
       (action . (("Open" . (lambda (x) (find-file (eval x))))))))

      helm-source-buffer-not-found ;; ask to create a buffer otherwise
   "*Helm all the things*"))

Use helm for switching buffers, opening files, calling interactive functions.

The default C-x c is quite close to C-x C-c, which quits Emacs. Changed to C-c h. We must set C-c h globally, because we cannot change helm-command-prefix-key once helm-config is loaded. (source)

We also use (helm-all-mark-rings) to jump around marks (set with C-SPC C-SPC et al.).

(use-package helm
  :diminish helm-mode
  :bind (("M-x"       . helm-M-x)
         ("C-x C-b"   . malb/helm-omni)
         ("C-x b"     . malb/helm-omni)
         ("C-x C-f"   . helm-find-files)
         ("C-c <SPC>" . helm-all-mark-rings)
         ("C-c h"     . helm-command-prefix)

         :map helm-map
         ("<tab>" . helm-execute-persistent-action) ;; rebind tab to do persistent action
         ("C-i"   . helm-execute-persistent-action) ;; make TAB works in terminal
         ("C-z"   . helm-select-action)             ;; list actions using C-z

  :config (progn
            (require 'helm-config)
            (require 'helm-for-files)
            (require 'helm-bookmark)
            (unbind-key "C-x c")

            (setq helm-adaptive-mode t
                  helm-bookmark-show-location t
                  helm-buffer-max-length 48
                  helm-display-header-line t
                  helm-ff-skip-boring-files t
                  helm-find-files-ignore-thing-at-point t
                  helm-input-idle-delay 0.01
                  helm-bookmark-show-location t
                  helm-window-prefer-horizontal-split t
                  helm-quick-update           t
                  helm-org-headings-fontify t
                  helm-quick-update t
                  helm-split-window-inside-p t
                  helm-truncate-lines nil
                  helm-ff-auto-update-initial-value nil
                  helm-grep-default-command "ag --vimgrep -z %p %f"
                  helm-grep-default-recurse-command "ag --vimgrep -z %p %f"
                  helm-use-frame-when-more-than-two-windows nil

                  helm-display-function #'helm-default-display-buffer
                  helm-display-buffer-reuse-frame t
                  helm-display-buffer-width 180
                  helm-display-buffer-height 60
                  helm-use-undecorated-frame-option t

                  helm-M-x-fuzzy-match                  t
                  helm-eshell-fuzzy-match               t
                  helm-buffers-fuzzy-matchiqng          t
                  helm-completion-in-region-fuzzy-match t
                  helm-mode-fuzzy-match                 t
                  helm-file-cache-fuzzy-match           t
                  helm-imenu-fuzzy-match                t
                  helm-locate-fuzzy-match               nil
                  helm-lisp-fuzzy-completion            t
                  helm-recentf-fuzzy-match              nil
                  helm-semantic-fuzzy-match             t)

            (when (executable-find "curl")
              (setq helm-net-prefer-curl t))

            (helm-mode t)

            ;; manipulating these lists must happen after helm-mode was called
            (add-to-list 'helm-boring-buffer-regexp-list "\\*CEDET Global\\*")

            (delete "\\.bbl$" helm-boring-file-regexp-list)
            (add-to-list 'helm-boring-file-regexp-list "\\.nav" t)
            (add-to-list 'helm-boring-file-regexp-list "\\.out" t)
            (add-to-list 'helm-boring-file-regexp-list "\\.snm" t)
            (add-to-list 'helm-boring-file-regexp-list "\\.synctex.gz" t)
            (add-to-list 'helm-boring-file-regexp-list "\\.fdb_latexmk" t)
            (add-to-list 'helm-boring-file-regexp-list "\\.fls" t)
            (add-to-list 'helm-boring-file-regexp-list "-blx\\.bib" t)
            (add-to-list 'helm-boring-file-regexp-list "texput\\.log" t)

            (defun malb/helm-in-buffer ()
              "The right kind™ of buffer menu."
              (if (eq major-mode 'org-mode)
                  (call-interactively #'helm-org-in-buffer-headings)
                (call-interactively #'helm-semantic-or-imenu)))

            (add-to-list 'helm-commands-using-frame 'helm-org-in-buffer-headings)
            (add-to-list 'helm-commands-using-frame 'helm-semantic-or-imenu)

            ;; see
            (assq-delete-all 'find-file helm-completing-read-handlers-alist)))

Actions for attaching files to e-mails and for sending them with

(use-package helm-utils
  :ensure nil
  :after transfer-sh
  :config (progn
            (defun malb/helm-mml-attach-files (_candidate)
              "Attach all selected files"
              (let* ((files (helm-marked-candidates)))
                (mapcar 'mml-attach-file files)))

            (defun malb/helmified-mml-attach-files ()
                (helm-exit-and-execute-action 'malb/helm-mml-attach-files)))

            (defun malb/helm-transfer-sh-files (_candidate)
              " all selected files"
              (let* ((files (helm-marked-candidates)))
                (mapcar (lambda (file)
                          (transfer-sh-upload-file-async file (file-name-nondirectory file)))

            (defun malb/helmified-transfer-sh-files ()
                (helm-exit-and-execute-action 'malb/helm-transfer-sh-files)))))

Helm Ring

helm-ring makes the kill ring actually useful, let’s use it.

(use-package helm-ring
  :ensure nil
  :bind (("M-y" . helm-show-kill-ring)))

Helm Swoop

helm-swoop for buffer searching. (source)

Tip: You can edit helm-swoop buffers by pressing C-c C-e.

(use-package helm-swoop
  :bind (("C-c o" . helm-multi-swoop-org)
         ("C-s"   . malb/swoop-or-search)
         ("C-M-s" . helm-multi-swoop-all))
  :config (progn

            (setq malb/helm-swoop-ignore-major-mode

            (setq helm-swoop-pre-input-function  #'malb/helm-swoop-pre-fill ;; I’m going back and forth what I prefer
                  helm-swoop-split-with-multiple-windows nil
                  helm-swoop-split-direction #'split-window-horizontally
                  helm-swoop-split-window-function 'helm-default-display-buffer
                  helm-swoop-speed-or-color t)

            (defun malb/helm-swoop-pre-fill ()
              (thing-at-point 'symbol))

            (defun malb/swoop-or-search ()
              (if (or (> (buffer-size) 1048576) ;; helm-swoop can be slow on big buffers
                      (memq major-mode malb/helm-swoop-ignore-major-mode))

            (defun malb/helm-swoop-C-s ()
              (if (boundp 'helm-swoop-pattern)
                  (if (equal helm-swoop-pattern "")
                      (previous-history-element 1)

            (bind-key "C-S-s" #'helm-swoop-from-isearch isearch-mode-map)
            (bind-key "C-S-s" #'helm-multi-swoop-all-from-helm-swoop helm-swoop-map)
            (bind-key "C-r"   #'helm-previous-line helm-swoop-map)
            (bind-key "C-s"   #'malb/helm-swoop-C-s helm-swoop-map)
            (bind-key "C-r"   #'helm-previous-line helm-multi-swoop-map)
            (bind-key "C-s"   #'malb/helm-swoop-C-s helm-multi-swoop-map)))

Helm Ag

Ack is “a tool like grep, optimized for programmers“. Ag is like ack, but faster. Helm-ag is a helm interface to ag. We use helm-ag mainly via helm-projectile-ag, which allows us to grep through all project files quickly. (source)

Note: You can switch to edit mode with C-c C-e.

(use-package helm-ag
  :commands (helm-ag helm-do-ag malb/helm-ag-projects malb/helm-ag-literature malb/helm-ag)
  :config (progn
            (setq helm-ag-base-command "ag --nocolor --nogroup"
                  helm-ag-command-option nil
                  helm-ag-insert-at-point 'symbol
                  helm-ag-fuzzy-match t
                  helm-ag-use-temp-buffer t
                  helm-ag-use-grep-ignore-list t
                  helm-ag-use-agignore t)

            (defun malb/helm-ag (dir)
              "run helm-ag in DIR."
              (let* ((ignored (mapconcat (lambda (i)
                                           (concat "--ignore " i))
                                         (append grep-find-ignored-files grep-find-ignored-directories)
                                         " "))
                     (helm-ag-base-command (concat helm-ag-base-command " " ignored)))
                (helm-do-ag (file-name-as-directory dir))))

            (defun malb/helm-ag-projects ()
              "run helm-ag in projects directory."
              (malb/helm-ag malb/projects-dir))

            (defun malb/helm-ag-literature ()
              "run helm-ag in projects directory"
              (malb/helm-ag (file-name-as-directory (car malb/literature-dirs))))))

Helm Themes

Switch themes with helm.

(use-package helm-themes
  :commands helm-themes)

Helm Descbinds

(use-package helm-descbinds
  :bind ("C-h b" . helm-descbinds)
  :init (fset 'describe-bindings 'helm-descbinds))

Helm Locate

(use-package helm-locate
  :ensure nil
  :after (helm helm-utils)
  :commands helm-locate
  :bind (:map helm-generic-files-map
              ("C-c C-a" . malb/helmified-mml-attach-files)
              ("C-c C-t" . malb/helmified-transfer-sh-files))
  :config (progn
            (setq helm-locate-command
                  (let ((databases (concat
                    (concat "locate -d " databases  " %s -e -A --regex %s")))

            (add-to-list 'helm-commands-using-frame 'helm-locate)

            (helm-add-action-to-source "Attach to E-mail" #'malb/helm-mml-attach-files helm-source-locate)
            (helm-add-action-to-source ""      #'malb/helm-transfer-sh-files helm-source-locate)))

Helm Org Rifle

Helm + Grep for Org-files

(use-package helm-org-rifle
  :commands (helm-org-rifle-agenda-files helm-org-rifle-occur-agenda-files malb/helm-org-rifle-agenda-files)
  :config (progn
            (defun malb/helm-org-rifle-agenda-files (arg)
              (interactive "p")
              (let ((current-prefix-arg nil))
                 ((equal arg 4) (call-interactively #'helm-org-rifle-agenda-files nil))
                 ((equal arg 16) (helm-org-rifle-occur-agenda-files))
                 (t (helm-org-agenda-files-headings)))))

            (add-to-list 'helm-commands-using-frame 'helm-org-agenda-files-headings)
            (add-to-list 'helm-commands-using-frame 'helm-org-rifle-files)))

Helm Google

helm-google is a simple interface for Google which comes in handy when we want to add a quick link for a term. (source)

(use-package helm-google
  :after helm
  :bind ("C-c h g" . helm-google)
    (add-to-list 'helm-google-actions
                 '("Copy URL" . (lambda (candidate)
                                  (let ((url
                                          "\\1" candidate)))
                                    (kill-new url)))) t)

    (add-to-list 'helm-google-actions
                 '("Org Store Link" . (lambda (candidate)
                                        (let ((title (car (split-string candidate "[\n]+")))
                                                "\\1" candidate)))
                                          (push (list url title) org-stored-links)))) t)))

Helm XRef

Helm interface for xref results

(use-package helm-xref
  :config (setq xref-show-xrefs-function 'helm-xref-show-xrefs))

KDE Desktop Search with Baloo

Baloo is KDE’s desktop search. Below, we implement a tiny helm interface for it.

(require 'helm-source)
(require 'helm-locate)

(defcustom helm-baloo-file-limit 100
  "Limit number of entries returned by baloo to this number."
  :group 'helm-baloo
  :type '(integer :tag "Limit"))

(defun baloo-search (pattern &optional directory)
  (if directory
      (start-process "baloosearch" nil "baloosearch" "-d" directory "-l" (format "%d" helm-baloo-file-limit) pattern)
    (start-process "baloosearch" nil "baloosearch" "-l" (format "%d" helm-baloo-file-limit) pattern)))

(defun helm-baloo-search (&optional directory)
  (baloo-search helm-pattern directory))

(defun helm-baloo-transform (cs)
  (let '(helm-baloo-clean-up-regexp (rx (or
                                         (seq "[0;31m" (+ (not (any "["))) "[0;0m")
    (mapcar (function
             (lambda (c)
                (rx (seq bol (+ space))) ""
                (replace-regexp-in-string helm-baloo-clean-up-regexp "" c))))

(defvar helm-baloo-actions
  '(("Open"                   . (lambda (x) (helm-find-many-files x)))
    ("Attach to E-mail"        . (lambda (x) (malb/helm-mml-attach-files x)))
    (""            . (lambda (x) (malb/helm-transfer-sh-files x)))
    ("Find file as root"      . (lambda (x) (helm-find-file-as-root x)))
    ("Find file other window" . (lambda (x) (helm-find-files-other-window x)))
    ("Find file other frame"  . (lambda (x) (find-file-other-frame x)))
    ("Open Dired in file's directory" . (lambda (x) (helm-open-dired x)))
    ("Grep File(s) `C-u recurse'"     . (lambda (x) (helm-find-files-grep x)))
    ("Zgrep File(s) `C-u Recurse'"    . (lambda (x) (helm-ff-zgrep x)))
    ("Pdfgrep File(s)"                . (lambda (x) (helm-ff-pdfgrep x)))
    ("Insert as org link"             . (lambda (x) (helm-files-insert-as-org-link x)))
    ("Checksum File"                  . (lambda (x) (helm-ff-checksum x)))
    ("Ediff File"                     . (lambda (x) (helm-find-files-ediff-files x)))
    ("Ediff Merge File" . (lambda (x) (helm-find-files-ediff-merge-files x)))
    ("Etags `M-., C-u reload tag file'" . (lambda (x) (helm-ff-etags-select x)))
    ("View file" . (lambda (x) (view-file x)))
    ("Insert file" . (lambda (x) (insert-file x)))
    ("Add marked files to file-cache" . (lambda (x) (helm-ff-cache-add-file x)))
    ("Delete file(s)" . (lambda (x) (helm-delete-marked-files x)))
    ("Copy file(s) `M-C, C-u to follow'" . (lambda (x) (helm-find-files-copy x)))
    ("Rename file(s) `M-R, C-u to follow'"  . (lambda (x) (helm-find-files-rename x)))
    ("Symlink files(s) `M-S, C-u to follow'" . (lambda (x) (helm-find-files-symlink x)))
    ("Relsymlink file(s) `C-u to follow'"   . (lambda (x) (helm-find-files-relsymlink x)))
    ("Hardlink file(s) `M-H, C-u to follow'" . (lambda (x) (helm-find-files-hardlink x)))
    ("Open file externally (C-u to choose)"  . (lambda (x) (helm-open-file-externally x)))
    ("Open file with default tool" . (lambda (x) (helm-open-file-with-default-tool x)))
    ("Find file in hex dump" . (lambda (x) (hexl-find-file) x))))

(defun helm-baloo-no-directory ()
  (helm :sources (helm-build-async-source "Baloo"
                   :candidates-process #'helm-baloo-search
                   :candidate-transformer '(helm-baloo-transform helm-skip-boring-files)
                   :action helm-baloo-actions
                   :keymap helm-generic-files-map
                   :help-message #'helm-generic-file-help-message)
        :buffer "*helm baloo*"))

(defun helm-baloo-in-directory (directory)
  (interactive "D")
  (helm :sources (helm-build-async-source "Baloo"
                   :candidates-process (lambda () (helm-baloo-search directory))
                   :candidate-transformer '(helm-baloo-transform helm-skip-boring-files)
                   :action helm-baloo-actions
                   :keymap helm-generic-files-map
                   :help-message #'helm-generic-file-help-message)
        :buffer "*helm baloo*"))

(defun helm-baloo (&optional arg)
  (interactive "P")
  (if arg
        (call-interactively #'helm-baloo-in-directory))
    (call-interactively #'helm-baloo-no-directory)))

(add-to-list 'helm-commands-using-frame 'helm-baloo-no-directory)
(add-to-list 'helm-commands-using-frame 'helm-baloo-in-directory)
turn helm-baloo into package so it can be lazy loaded


Recent Files

Don’t include boring or remote stuff in list of recently visited files.

(use-package recentf
  :after helm
  :config  (progn
             (setq recentf-max-saved-items 64
                   recentf-exclude (list "COMMIT_EDITMSG"
                                         (expand-file-name malb/mu4e-maildir)))
             (loop for ext in helm-boring-file-regexp-list
                   do (add-to-list 'recentf-exclude ext t))


(use-package saveplace
  :config (setq-default save-place t
                        save-place-file (locate-user-emacs-file "places" ".emacs-places")))


Make sure to auto automatically rescan for imenu change.

(set-default 'imenu-auto-rescan t)

IMenu items for all buffers with the same major mode as the current one.

(use-package imenu-anywhere
  :config (progn
            (defun malb/imenu-anywhere (arg)
              "Call `helm-imenu-anywhere'

- With no prefix, call with default configuration,
- with one prefix argument, call `helm-imenu-anywhere' on all programming mode buffers regardless of project,
- with two prefix arguments, call `helm-imenu-anywhere' on all buffers."
              (interactive "p")
               ((equal arg 4)
                (let ((imenu-anywhere-buffer-filter-functions
                       `((lambda (current other)
                           (let ((parent (buffer-local-value 'major-mode other)))
                             (while (and (not (memq parent '(prog-mode c-mode c++-mode)))
                                         (setq parent (get parent 'derived-mode-parent))))
                  (call-interactively #'helm-imenu-anywhere)))

               ((equal arg 16)
                (let ((imenu-anywhere-buffer-filter-functions '((lambda (current other) t))))
                  (call-interactively #'helm-imenu-anywhere)))

               (t (call-interactively #'helm-imenu-anywhere))))))

imenu-list can be useful, C-x t i

(use-package imenu-list
  :config (setq imenu-list-position 'left
                imenu-list-size 0.15
                imenu-list-auto-resize nil
                imenu-list-after-jump-hook nil))


See here for an introduction to smartparens.

Some of the config below is stolen from hlissner’s emacs.d.

(use-package smartparens
  :diminish smartparens-mode
  :config (progn
            (require 'smartparens-config)
            (require 'smartparens-latex)
            (require 'smartparens-python)

            (smartparens-global-mode t)
            (setq sp-autodelete-wrap t)
            (setq sp-cancel-autoskip-on-backward-movement nil)

            (setq-default sp-autoskip-closing-pair t)

            (bind-key "C-M-f" #'sp-forward-sexp smartparens-mode-map)
            (bind-key "C-M-b" #'sp-backward-sexp smartparens-mode-map)

            (bind-key "C-M-n" #'sp-next-sexp smartparens-mode-map)
            (bind-key "C-M-p" #'sp-previous-sexp smartparens-mode-map)

            (bind-key "C-M-d" 'sp-down-sexp smartparens-mode-map)
            (bind-key "C-M-u" 'sp-backward-up-sexp smartparens-mode-map)

            (bind-key "C-M-a" 'sp-beginning-of-sexp smartparens-mode-map)
            (bind-key "C-M-e" 'sp-end-of-sexp smartparens-mode-map)

            (bind-key "C-M-k" #'sp-kill-sexp smartparens-mode-map)
            (bind-key "C-M-w" #'sp-copy-sexp smartparens-mode-map)

            (bind-key "C-M-t" 'sp-transpose-sexp smartparens-mode-map)

            (bind-key "C-<right>" 'sp-forward-slurp-sexp smartparens-mode-map)
            (bind-key "C-<left>" 'sp-forward-barf-sexp smartparens-mode-map)

            (bind-key "M-S-<backspace>" 'sp-backward-unwrap-sexp smartparens-mode-map)
            (bind-key "C-M-<backspace>" 'sp-splice-sexp-killing-backward smartparens-mode-map)
            (bind-key "C-S-<backspace>" 'sp-splice-sexp-killing-around smartparens-mode-map)

            (defun malb/sp-point-is-template-p (id action context)
              (and (sp-in-code-p id action context)
                   (sp-point-after-word-p id action context)))

            (defun malb/sp-point-after-include-p (id action context)
              (and (sp-in-code-p id action context)
                     (goto-char (line-beginning-position))
                     (looking-at-p "[ 	]*#include[^<]+"))))

            (sp-with-modes '(c-mode c++-mode)
              (sp-local-pair "<" ">"    :when '(malb/sp-point-is-template-p malb/sp-point-after-include-p))
              (sp-local-pair "/*" "*/"  :post-handlers '(("||\n[i]" "RET") ("| " "SPC")))
              (sp-local-pair "/**" "*/" :post-handlers '(("||\n[i]" "RET") ("||\n[i]" "SPC")))
              (sp-local-pair "/*!" "*/" :post-handlers '(("||\n[i]" "RET") ("[d-1]< | " "SPC"))))

            ;; Auto-close more conservatively
            (sp-pair "\"" nil :unless '(sp-point-before-word-p sp-point-after-word-p sp-point-before-same-p))
            (sp-pair "{"  nil :post-handlers '(("||\n[i]" "RET") ("| " " "))
                     :unless '(sp-point-before-word-p sp-point-before-same-p) :wrap "C-{")
            (sp-pair "("  nil :post-handlers '(("||\n[i]" "RET") ("| " " "))
                     :unless '(sp-point-before-word-p sp-point-before-same-p) :wrap "C-(")
            (sp-pair "["  nil :post-handlers '(("| " " "))
                     :unless '(sp-point-before-word-p sp-point-before-same-p))

            (sp-pair "'"  nil :unless '(sp-point-before-word-p sp-point-after-word-p sp-point-before-same-p))
            (sp-local-pair '(sh-mode
                           "`" nil :unless '(sp-point-before-word-p

            (defun malb/latex-replace-dollar (_id action _context)
              (when (eq action 'wrap)
                (sp-get sp-last-wrapped-region
                  (let ((at-beg (= (point) :beg-in)))
                      (goto-char :beg)
                      (delete-char :op-l)
                      (insert "\\("))
                      (goto-char :end-in)
                      (delete-char :cl-l)
                      (insert "\\)"))
                    (setq sp-last-wrapped-region
                           :beg :end "\\(" "\\)"))
                    (goto-char (if at-beg (1+ :beg-in) :end))))))

                '(tex-mode plain-tex-mode latex-mode)
              ;; (sp-local-pair "\\(" "\\)"
              ;;                :unless '(sp-point-before-word-p
              ;;                          sp-point-before-same-p
              ;;                          sp-latex-point-after-backslash)
              ;;                :trigger-wrap "\$"
              ;;                :trigger "\$")

              ;; (sp-local-pair  "$" nil
              ;;                 :unless '(sp-point-before-word-p
              ;;                           sp-point-before-same-p
              ;;                           sp-latex-point-after-backslash)
              ;;                 :post-handlers '(:add malb/latex-replace-dollar))

              ;; (sp-local-pair "\\[" "\\]"
              ;;                :unless '(sp-point-before-word-p
              ;;                          sp-point-before-same-p
              ;;                          sp-latex-point-after-backslash))
              (sp-local-pair "$" nil :actions :rem))

            (add-to-list 'sp-ignore-modes-list 'ein:notebook-multilang-mode)

              (sp-local-pair "=" "=" :unless '(sp-point-before-word-p sp-point-after-word-p))
              (sp-local-pair "_" "_" :unless '(sp-point-before-word-p sp-point-after-word-p))
              (sp-local-pair "/" "/" :unless '(sp-point-before-word-p sp-point-after-word-p))
              (sp-local-pair "~" "~" :unless '(sp-point-before-word-p sp-point-after-word-p)
                             :post-handlers '(("[d1]" "SPC"))))))

Automatically insert closing delimiter

(use-package syntactic-close
  :bind ("C-)" . syntactic-close))


Dragging lines around


(use-package drag-stuff
  :diminish drag-stuff-mode
  :config (progn
            (defhydra malb/hydra-drag-stuff (:color red)
              "drag stuff"
              ("<up>" drag-stuff-up "")
              ("<down>" drag-stuff-down "")
              ("SPC" nil)
              ("q" nil))
            (bind-key "C-c d" #'malb/hydra-drag-stuff/body)))

Visualise the undo tree


Tip: Did you know that Emacs has undo in a region?

(use-package undo-tree
  :diminish undo-tree-mode
  :config (progn
            (setq undo-tree-visualizer-timestamps t)
            (setq undo-tree-visualizer-diff t)))

Reverting Buffers

Automatically revert buffers.

(setq global-auto-revert-non-file-buffers t
      global-auto-revert-ignore-modes '(pdf-view-mode)
      auto-revert-verbose nil)

(global-auto-revert-mode 1)

Save buffer when loosing focus

This can be dangerous, so only enable on per project basis, e.g.

((markdown-mode . ((eval . (focus-autosave-local-mode 1)))))
(use-package focus-autosave-mode
  :delight (focus-autosave-local-mode ""))


Use visual-regexp for visual regular expressions and use visual-regexp-steroids for modern regexps. This makes Emacs regexp actually usable for me.

(use-package visual-regexp
  :bind (("C-c m" . vr/mc-mark)
         ("M-%" . vr/query-replace)
         ("C-S-s" . vr/isearch-forward)
         ("C-S-r" . vr/isearch-backward)))

(use-package visual-regexp-steroids)

Sometimes visual-regexp bombs out, so we have a function to reset it:

(defun malb/reset-visual-regexp ()
  (unload-feature 'visual-regexp t)
  (unload-feature 'visual-regexp-steroids t)
  (require 'visual-regexp)
  (require 'visual-regexp-steroids))

Multiple cursors

Multiple cursors are awesome. (source)

Also see Emacs Rocks #13, which is on multiple-cursors.

(keybinding source)

Commands are bound to C-x m …

(use-package multiple-cursors
  :config (progn
            (defun malb/mc-typo-mode ()
              (add-to-list 'mc/unsupported-minor-modes 'typo-mode))
            (add-hook 'multiple-cursors-mode-hook #'malb/mc-typo-mode)

            (bind-key "M-3" #'mc/mark-previous-like-this)
            (bind-key "M-4" #'mc/mark-next-like-this)
            (bind-key "M-£" #'mc/unmark-previous-like-this)
            (bind-key "M-$" #'mc/unmark-next-like-this)

            (bind-key "C-;" #'mc/mark-all-dwim)

            (define-prefix-command 'malb/mc-map)
            (bind-key "m" 'malb/mc-map ctl-x-map)

            (bind-key "a" #'mc/mark-all-like-this malb/mc-map)
            (bind-key "d" #'mc/mark-all-dwim malb/mc-map)
            (bind-key "s" #'mc/mark-all-symbols-like-this-in-defun malb/mc-map)

            (bind-key "i" #'mc/insert-numbers malb/mc-map)
            (bind-key "l" #'mc/insert-letters malb/mc-map)

            (bind-key "h" #'mc-hide-unmatched-lines-mode malb/mc-map)

            (bind-key "R" #'mc/reverse-regions malb/mc-map)
            (bind-key "S" #'mc/sort-regions malb/mc-map)
            (bind-key "L" #'mc/edit-lines malb/mc-map)

            (bind-key "C-a" #'mc/edit-beginnings-of-lines malb/mc-map)
            (bind-key "C-e" #'mc/edit-ends-of-lines malb/mc-map)))

Recursively narrow

(use-package recursive-narrow
  :config (progn
            (defun malb/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))))
            (add-hook 'recursive-narrow-dwim-functions 'malb/recursive-narrow-dwim-org))
  :bind (("C-x n w" . recursive-widen)
         ("C-x n n" . recursive-narrow-or-widen-dwim)))

Expand region

See Emacs Rocks #9 for an intro to expand-region

(use-package expand-region
  :bind ("C-\\" . er/expand-region)
  :init (setq expand-region-fast-keys-enabled nil)
  :config (progn

            (defun er/add-text-mode-expansions ()
              (make-variable-buffer-local 'er/try-expand-list)
              (setq er/try-expand-list (append
            (add-hook 'markdown-mode-hook 'er/add-text-mode-expansions)
            (add-hook 'LaTeX-mode-hook 'er/add-text-mode-expansions)))


(use-package embrace
  :config (progn

            (defun malb/embrace-latex-mode-hook ()
              (embrace-add-pair ?\( "\\(" "\\)")
              (embrace-add-pair ?\[ "\\[" "\\]")
              (embrace-add-pair ?\) "(" ")")
              (embrace-add-pair ?\] "[" "]")
              (embrace-add-pair ?$  "$" "$")
              (embrace-add-pair   "$$" "$$"))

            (bind-key "M-\\" #'embrace-commander)
            (add-hook 'org-mode-hook #'embrace-org-mode-hook)
            (add-hook 'LaTeX-mode-hook #'malb/embrace-latex-mode-hook)))

Wrap Region

Wrap Region is a minor mode for Emacs that wraps a region with punctuations. For “tagged” markup modes, such as HTML and XML, it wraps with tags.


(use-package wrap-region
  :diminish wrap-region-mode
  :config (wrap-region-add-wrappers
           '(("*" "*" nil org-mode)
             ("~" "~" nil org-mode)
             ("/" "/" nil org-mode)
             ("=" "=" nil org-mode)
             ("_" "_" nil org-mode)
             ("$" "$" nil org-mode)
             ("#+BEGIN_QUOTE\n" "#+END_QUOTE\n" "q" org-mode)
             ("#+BEGIN_SRC \n" "\n#+END_SRC" "s" org-mode)
             ("#+BEGIN_CENTER \n" "\n#+END_CENTER" "c" org-mode)))
  (add-hook 'org-mode-hook 'wrap-region-mode))


(use-package bicycle
  :after outline
  :bind (:map outline-minor-mode-map
              ([C-tab] . bicycle-cycle)
              ([S-tab] . bicycle-cycle-global)))
(use-package hideshow
  :diminish hs-minor-mode
  :config (setq hs-special-modes-alist '((c-mode "{" "}" "/[*/]" nil nil)
                                         (c++-mode "{" "}" "/[*/]" nil nil)
                                         (java-mode "{" "}" "/[*/]" nil nil)
                                         (js-mode "{" "}" "/[*/]" nil)
                                         (json-mode "{" "}" "/[*/]" nil)
                                         (javascript-mode  "{" "}" "/[*/]" nil))))

Hungry Delete

(use-package nv-delete-back
  :bind (("C-<backspace>" . nv-delete-back-all)
         ("M-<backspace>" . nv-delete-back)))

Beginning of …

A better C-a. (source)

(defun malb/beginning-of-line-dwim ()
  "Toggles between moving point to the first non-whitespace character, and
  the start of the line."
  (let ((start-position (point)))
    ;; Move to the first non-whitespace character.

    ;; If we haven't moved position, go to start of the line.
    (when (= (point) start-position)
      (move-beginning-of-line nil))))

(bind-key "C-a" #'malb/beginning-of-line-dwim)
(bind-key "<home>"  #'malb/beginning-of-line-dwim lisp-mode-map)

Redefine M-< and M->. The real beginning and end of buffers (i.e., point-min and point-max) are still accessible by pressing the same key again. (source)

(use-package beginend
  :diminish beginend-global-mode
  :config (progn
            (dolist (mode beginend-modes)
              (diminish (cdr mode)))))

Emacs UX


Helpful is an alternative to the built-in Emacs help that provides much more contextual information. — helpful

(use-package helpful
  :config (progn
            (bind-key "C-h f" #'helpful-callable)
            (bind-key "C-h v" #'helpful-variable)
            (bind-key "C-h k" #'helpful-key)
            (bind-key "C-h ," #'helpful-at-point)))


Refine provides a convenient UI for editing variables. Refine is not for editing files, but for changing elisp variables, particularly lists. — refine

(use-package refine)

Projects (Projectile)

Projectile is a project interaction library for Emacs. […]

This library provides easy project management and navigation. The concept of a project is pretty basic – just a folder containing special file. Currently git, mercurial, darcs and bazaar repos are considered projects by default. So are lein, maven, sbt, scons, rebar and bundler projects. If you want to mark a folder manually as a project just create an empty .projectile file in it. Some of Projectile’s features:

  • jump to a file in project
  • jump to files at point in project
  • jump to a directory in project
  • jump to a file in a directory
  • jump to a project buffer
  • jump to a test in project
  • toggle between files with same names but different extensions (e.g. .h <-> .c/.cpp, Gemfile <-> Gemfile.lock)
  • toggle between code and its test (e.g. main.service.js <-> main.service.spec.js)
  • jump to recently visited files in the project
  • switch between projects you have worked on
  • kill all project buffers
  • replace in project
  • multi-occur in project buffers
  • grep in project
  • regenerate project etags or gtags (requires ggtags).
  • visit project in dired
  • run make in a project with a single key chord (source)


  • C-c p D projectile-dired
  • C-c p F helm-projectile-find-file-in-known-projects
  • C-c p P projectile-test-project
  • C-c p S projectile-save-project-buffers
  • C-c p b helm-projectile-switch-to-buffer
  • C-c p f helm-projectile-find-file
  • C-c p g helm-projectile-find-file-dwim
  • C-c p h helm-projectile
  • C-c p p helm-projectile-switch-project
  • C-c p r projectile-replace
  • C-c p s s helm-projectile-ag
  • C-c p x projectile-run-term etc.

When switching projects:

  • C-d open Dired in project’s directory
  • M-g open project root in vc-dir or magit
  • M-e switch to Eshell: Open a project in Eshell.
  • C-s grep in projects (add prefix C-u to recursive grep)
  • C-c Compile project: Run a compile command at the project root.
  • M-D Remove project(s): Delete marked projects from the list of known projects.
  • C-c @ insert the current file that highlight bar is on as an org link.

Note: next-error has nothing to do with projectile, but <f5> and <f6> kind of go together. previous-error is bound to M-g p.

(use-package projectile
  :bind (("<f5>" . projectile-compile-project)
         ("<f6>" . next-error)
         :map projectile-command-map
         ("I" . #'malb/projectile-ipython))
  :init  (setq projectile-keymap-prefix (kbd "C-c p"))
  :config (progn
            (use-package magit :ensure t)
            (require 'helm-projectile)

            (setq projectile-indexing-method 'alien)
            (defun malb/projectile-ignore-projects (project-root)
                (or (file-remote-p project-root)
                    ;; don't litter project list with cryptobib subprojects
                    (and  (string-match (rx-to-string `(: "cryptobib/" eos) t)
                                        project-root) t)
                    (and  (string-match (rx-to-string `(: bos "/tmp/" ) t)
                                        project-root) t))))

            (setq projectile-make-test-cmd "make check"
                  projectile-ignored-projects malb/projectile-ignored-projects
                  projectile-ignored-project-function #'malb/projectile-ignore-projects
                  projectile-mode-line  '(:eval (format "{%s}" (projectile-project-name))))


Helm integration


  • C-c p h for helm-projectile which combines buffer, file and project switching
  • C-c p F for helm-projectile-find-file-in-known-projects

We add a “Create file“ action (source) and replace grep by ag.

(use-package helm-projectile
  :config (progn
            (defvar malb/helm-source-file-not-found
                  "Create file"
                :action 'find-file))

            (add-to-list 'helm-projectile-sources-list malb/helm-source-file-not-found t)

            (setq projectile-switch-project-action 'helm-projectile)

            (helm-delete-action-from-source "Grep in projects `C-s'" helm-source-projectile-projects)
            (helm-delete-action-from-source "Grep in projects `C-s'" helm-source-projectile-dirty-projects)
            (helm-add-action-to-source "Grep in projects `C-s'" 'helm-do-ag helm-source-projectile-projects 4)
            (helm-add-action-to-source "Grep in projects `C-s'" 'helm-do-ag helm-source-projectile-dirty-projects 4)))



Magit is a very nice Git interface. (source)

(use-package magit
  :commands (magit-status
  :bind ("<f7>" . magit-status)
  :config (progn
            (setq magit-push-always-verify nil
                  magit-last-seen-setup-instructions "2.1.0")

            (setq-default magit-diff-refine-hunk t)
            (global-magit-file-mode t)

            ;; we use magit, not vc for git
            (delete 'Git vc-handled-backends)))


  • try git config status.showUntrackedFiles all (source)

Magit SVN

We enable magit-svn whenever necessary.

(use-package magit-svn
  :after magit
  :config (progn
            (defun malb/magit-svn ()
              (if (file-exists-p (magit-git-dir "svn"))
            (add-hook 'magit-mode-hook #'malb/magit-svn)))

Magit Todo Notes


(use-package magit-todos
  :after magit
  :config (magit-todos-mode))


(use-package magithub
  :after magit
  :config (magithub-feature-autoinject t))

Org Links for Magit Buffers


(use-package orgit)

Git Timemachine

I don’t often use git-timemachine but when I do …

(use-package git-timemachine)

Git Link


functions that create URLs for files and commits in GitHub/Bitbucket/GitLab/… repositories. git-link returns the URL for the current buffer’s file location at the current line number or active region. git-link-commit returns the URL for a commit. URLs are added to the kill ring.

(use-package git-link)


Generate gitignore files

(use-package helm-gitignore
  :defer t)

Org-mode all the things!

See here for an introduction to org-mode.

Note: Ideally, we’d want to put all org-mode stuff into one big :config blog, but that makes it harder to read/explain.

(use-package org
  :bind (("C-c l" . org-store-link)
         ("C-c a" . org-agenda))
  :mode ("\\.org$" . org-mode)
  :diminish (orgstruct-mode orgstruct++-mode orgtbl-mode))


(setq org-directory malb/org-files-dir
      org-agenda-files malb/org-files
      org-default-notes-file malb/inbox-org)


(setq org-enforce-todo-dependencies t   ; enforce todo dependencies
      org-habit-graph-column 117
      org-use-speed-commands t
      org-catch-invisible-edits 'show
      org-adapt-indentation nil         ; Non-nil means adapt indentation to outline node level.
      org-tags-column -120
      org-startup-folded 'fold
      org-log-into-drawer t
      org-log-done t
      org-return-follows-link nil       ; don’t follow links by pressing ENTER
      org-clock-persist 'history
      org-special-ctrl-a/e t               ; begin/end of line to skip starts …
      org-special-ctrl-k t                 ; special keys for killing a headline
      org-edit-src-content-indentation 0   ; don't indent source code
      org-src-preserve-indentation t       ; preserve indentation in code
      org-src-window-setup 'current-window ; when hitting C-c '
      org-reverse-note-order t
      org-footnote-auto-adjust t          ; renumber footnotes automagically
      org-confirm-elisp-link-function nil ;
      org-hidden-keywords nil
      org-list-demote-modify-bullet  '(("+" . "-") ("-" . "+") ("*" . "+"))
      org-list-allow-alphabetical t
      org-goto-interface 'org-outline-path-complete
      org-outline-path-complete-in-steps nil
      org-M-RET-may-split-line '((default . t) (headline . nil))
      org-blank-before-new-entry (quote ((heading . t)
                                         (plain-list-item . nil)))
      org-yank-adjusted-subtrees nil
      org-treat-S-cursor-todo-selection-as-state-change nil
      org-show-context-detail '((agenda . lineage) ;; instead of "local"
                                (bookmark-jump . lineage)
                                (isearch . lineage)
                                (default . ancestors))
      org-format-latex-options (plist-put org-format-latex-options :scale 2.0))

(add-hook 'org-babel-after-execute-hook 'org-display-inline-images)

(bind-key "<home>" #'org-beginning-of-line org-mode-map)
(bind-key "<end>" #'org-end-of-line org-mode-map)
(bind-key "C-c C-." #'org-time-stamp org-mode-map) ; ede binds C-c . too
(bind-key "C-c C-," #'org-time-stamp-inactive org-mode-map) ; fylcheck binds C-c !


(setq org-agenda-tags-column -127
      org-agenda-include-diary nil
      org-agenda-dim-blocked-tasks t
      org-agenda-default-appointment-duration 60
      org-agenda-skip-additional-timestamps-same-entry nil
      org-agenda-skip-timestamp-if-deadline-is-shown t
      org-agenda-skip-deadline-prewarning-if-scheduled t
      org-agenda-ignore-properties '(effort appt category)
      org-agenda-window-setup 'current-window  ; Current window gets agenda
      org-agenda-sticky t                      ; Use sticky agenda's so they persist
      org-agenda-compact-blocks t              ; Compact the block agenda view
      org-agenda-span 14                       ; span 14 days of agenda
      org-agenda-start-on-weekday 1            ; start on Monday

(add-hook 'org-agenda-finalize-hook 'hl-line-mode)


(setq org-hide-mphasis-markers nil ; don’t hide markers for like *foo*
      org-ellipsis " ••• "
      org-highlight-latex-and-related '(latex)
      org-src-fontify-natively t        ; fontify code blocks
      org-startup-with-inline-images t    ; show images when opening a file.
      org-startup-with-latex-preview nil
      org-preview-latex-default-process 'imagemagick
      org-pretty-entities nil
      org-image-actual-width '(1024))

ToDo Keywords & Scheduling

(setq org-todo-keywords
      '((sequence "TODO(t)"
                  "CANCELLED(c@/!)") ;;generic

        (sequence "EXPLORE(x)"
                  "STALLED(s)") ;; papers

        (sequence "REVIEW(v)"
                  "REVIEWED(V!)") ;; reviews

        (type "BLOG(b)"
              "|" "DONE")))


delete SCHEDULED if new state is WAITING

(defun malb/org-after-todo-state-change ()
  (when (or
         (string-equal org-state "WAITING")
         (string-equal org-state "COAUTHOR")
         (string-equal org-state "DELEGATED")
         (string-equal org-state "SUBMITTED"))
      (org-remove-timestamp-with-keyword org-scheduled-string)
      (when (not (org-get-deadline-time (point)))
        (org-deadline nil (org-read-date nil t "+7d"))))))

(add-hook 'org-after-todo-state-change-hook 'malb/org-after-todo-state-change)


(setq org-tag-persistent-alist '((:startgroup . nil)
                                 ("@office" . ?o)
                                 ("@train" . ?t)
                                 ("@home" . ?h)
                                 (:endgroup . nil)
                                 ("quick" . ?q)
                                 ("noexport" . ?n)
                                 ("ignore" . ?i)))


Bind org-table-* command when the point is in an org table (source).

 :map org-mode-map
 :filter (org-at-table-p)
 ("C-c ?" . org-table-field-info)
 ("C-c SPC" . org-table-blank-field)
 ("C-c +" . org-table-sum)
 ("C-c =" . org-table-eval-formula)
 ("C-c `" . org-table-edit-field)
 ("C-#" . org-table-rotate-recalc-marks)
 ("C-c }" . org-table-toggle-coordinate-overlays)
 ("C-c {" . org-table-toggle-formula-debugger))


(add-to-list 'org-structure-template-alist '("m" "#+BEGIN_SRC markdown\n?\n#+END_SRC"))
(add-to-list 'org-structure-template-alist '("more" "#+HTML:<!--more-->\n?"))
(add-to-list 'org-structure-template-alist '("lc" "#+LATEX_CLASS: " ""))
(add-to-list 'org-structure-template-alist '("ld" "#+BEGIN_definition\n<<def:?>>\n#+END_definition"))
(add-to-list 'org-structure-template-alist '("ll" "#+BEGIN_lemma\n<<lem:?>>\n#+END_lemma"))
(add-to-list 'org-structure-template-alist '("lt" "#+BEGIN_theorem\n<<thm:?>>\n#+END_theorem"))
(add-to-list 'org-structure-template-alist '("lp" "#+BEGIN_proof\n?\n#+END_proof"))
(add-to-list 'org-structure-template-alist '("fb" "#+BEAMER: \\framebreak" ""))
(add-to-list 'org-structure-template-alist '("lh" "#+LATEX_HEADER: " ""))
(add-to-list 'org-structure-template-alist '("ao" "#+ATTR_ORG: " ""))
(add-to-list 'org-structure-template-alist '("al" "#+ATTR_LATEX: " ""))
(add-to-list 'org-structure-template-alist '("ca" "#+CAPTION: " ""))
(add-to-list 'org-structure-template-alist '("tn" "#+TBLNAME: " ""))
(add-to-list 'org-structure-template-alist '("b"  "#+BEAMER: " ""))
(add-to-list 'org-structure-template-alist '("n"  "#+NAME: " ""))
(add-to-list 'org-structure-template-alist '("o"  "#+ATTR_BEAMER: :overlay +-"))

Speed Commands


(add-to-list 'org-speed-commands-user (cons "m" 'org-mark-subtree)) ;; Mark a subtree
(add-to-list 'org-speed-commands-user (cons "S" 'widen)) ;; Widen
(add-to-list 'org-speed-commands-user (cons "k" (lambda () ;; kill a subtree
(add-to-list 'org-speed-commands-user (cons "J" (lambda () ;; Jump to headline
                                                  (avy-with avy-goto-line
                                                    (avy--generic-jump "^\\*+" nil avy-style)))))


Targets include this file and any file contributing to the agenda - up to 9 levels deep

(setq org-refile-targets (quote ((org-agenda-files :maxlevel . 9))))

Stop using paths for refile targets - we file directly with helm

(setq org-refile-use-outline-path 'file)

Allow refile to create parent tasks with confirmation

(setq org-refile-allow-creating-parent-nodes '(confirm))
(defun malb/verify-refile-target () ; Exclude DONE state tasks from refile targets
  (not (member (nth 2 (org-heading-components)) org-done-keywords)))

(setq org-refile-target-verify-function 'malb/verify-refile-target)

Spell Checking

;; source
(defun malb/org-ispell ()
  "Configure `ispell-skip-region-alist' for `org-mode'."
  (make-local-variable 'ispell-skip-region-alist)
  (add-to-list 'ispell-skip-region-alist '(org-property-drawer-re))
  (add-to-list 'ispell-skip-region-alist '("~" "~"))
  (add-to-list 'ispell-skip-region-alist '("=" "="))
  (add-to-list 'ispell-skip-region-alist '("#\\+BEGIN_SRC" . "^#\\+END_SRC"))
  (add-to-list 'ispell-skip-region-alist '("#\\+BEGIN_EXAMPLE" . "#\\+END_EXAMPLE"))
  (add-to-list 'ispell-skip-region-alist '("#\\+BEGIN_QUOTE" . "#\\+END_QUOTE")))

(add-hook 'org-mode-hook #'malb/org-ispell)
(add-hook 'org-mode-hook #'flyspell-mode)

Diminish Minor Modes

(use-package org-indent
  :ensure nil


Archive with tags intact and some order in target file preserved. (source)

(defun malb/org-inherited-no-file-tags ()
  (let ((tags (org-entry-get nil "ALLTAGS" 'selective))
        (ltags (org-entry-get nil "TAGS")))
    (mapc (lambda (tag)
            (setq tags
                  (replace-regexp-in-string (concat tag ":") "" tags)))
          (append org-file-tags (when ltags (split-string ltags ":" t))))
    (if (string= ":" tags) nil tags)))

(defadvice org-archive-subtree (around malb/org-archive-subtree-low-level activate)
  (let ((tags (malb/org-inherited-no-file-tags))
         (if (save-excursion (org-back-to-heading)
                             (> (org-outline-level) 1))
             (concat (car (split-string org-archive-location "::"))
                     "::* "
                     (car (org-get-outline-path)))
    (with-current-buffer (find-file-noselect (org-extract-archive-file))
        (while (org-up-heading-safe))
        (org-set-tags-to tags)))))
(setq org-archive-location "~/Documents/Archive/org-files/%s_archive::")


(use-package org-habit
  :ensure nil
  :config (add-to-list 'org-modules 'org-habit))


(use-package org-protocol
  :ensure nil)


Prettier bullets in org-mode. (source)

(use-package org-bullets
  :commands org-bullets-mode
  :init  (progn

           (defun malb/enable-org-bullets ()
             (org-bullets-mode 1))

           (add-hook 'org-mode-hook #'malb/enable-org-bullets)
           (setq org-bullets-bullet-list '("" "" "" "" "" "" ""))))


UTF-8 everywhere.

(setq org-export-coding-system 'utf-8
      org-export-in-background nil
      org-export-use-babel t
      org-export-with-toc nil
      org-export-with-timestamps 'active
      org-export-async-init-file (expand-file-name "org-export-init.el" user-emacs-directory))


(add-to-list 'org-babel-default-header-args '(:eval . "never-export"))
(add-to-list 'org-babel-default-inline-header-args '(:eval . "never-export"))
(setq org-latex-to-mathml-convert-command "java -jar %j -unicode -force -df %o %I"
      org-latex-to-mathml-jar-file (expand-file-name "mathtoweb.jar" user-emacs-directory))


iCal export is triggered after 600 seconds of inactivity.

Note: this code is definitely from somewhere else. I don’t remember where, though.

(use-package ox-icalendar
  :ensure nil
  :config (progn
            (setq org-icalendar-include-todo t
                  org-icalendar-combined-agenda-file malb/org-mode-ics
                  org-icalendar-categories '(category)
                  org-icalendar-use-scheduled '(todo-start event-if-not-todo)
                  org-icalendar-use-deadline '(todo-due)
                  org-icalendar-with-timestamps 'active)))
(defvar malb/org-icalendar-export-timer nil
  "Timer that `malb/org-icalendar-export-timer' uses to reschedule itself, or nil.")
(defun malb/org-icalendar-export-with-delay (secs)
  "Export after `secs' seconds unless the file changed in the meantime."
  (when malb/org-icalendar-export-timer
    (cancel-timer malb/org-icalendar-export-timer))
  (setq malb/org-icalendar-export-timer
         (* 1 secs) nil (lambda ()
                          ;; async, check org-export-init.el
                          (org-icalendar-combine-agenda-files t)
(defun malb/ox-export-after-save-hook ()
  "Save after 600 seconds of no changes."
  (if (eq major-mode 'org-mode)
      (malb/org-icalendar-export-with-delay 600)))

(add-hook 'after-save-hook 'malb/ox-export-after-save-hook)


  • Use XeLaTeX because UTF-8 and fonts.
  • Drop \usepackage[T1]{fontenc} because XeLaTeX doesn’t need and like it.
  • Add some standard (to us) packages
  • Handouts are done via tufte-handout, letters via ox-koma-letter.
(use-package ox-latex
  :ensure nil
  :config (progn
            (setq org-latex-listings 't)
            (add-to-list 'org-latex-packages-alist '("" "listings"))
            (add-to-list 'org-latex-packages-alist '("" "xcolor"))
            (add-to-list 'org-latex-packages-alist '("" "amssymb"))
            (add-to-list 'org-latex-packages-alist '("" "amsmath"))
            (add-to-list 'org-latex-packages-alist '("" "amsthm"))
            (add-to-list 'org-latex-packages-alist '("" "gensymb"))
            (add-to-list 'org-latex-packages-alist '("" "nicefrac"))
            (add-to-list 'org-latex-packages-alist '("" "units"))
            (add-to-list 'org-latex-packages-alist '("" "xspace"))
            (add-to-list 'org-latex-packages-alist '("notions,operators,sets,keys,ff,adversary,primitives,complexity,asymptotics,lambda,landau,advantage" "cryptocode"))
            (add-to-list 'org-latex-packages-alist '("" "newunicodechar"))
            (add-to-list 'org-latex-packages-alist '("" "microtype"))
            (add-to-list 'org-latex-packages-alist '("color=yellow!40" "todonotes") t)

            (setq malb/org-latex-default (with-temp-buffer
                                            (expand-file-name "article-header.tex" user-emacs-directory))

            (setq org-latex-pdf-process '("latexmk -pdflatex=\"xelatex -src-specials -synctex=1 --shell-escape -halt-on-error -file-line-error %%O %%S\" -output-directory=%o -pdf %f")
                  org-latex-prefer-user-labels t
                  org-latex-caption-above nil
                  org-latex-default-packages-alist (remove* '("T1" "fontenc" t ("pdflatex"))
                                                            org-latex-default-packages-alist :test 'equal)
                  org-latex-default-packages-alist (remove '("AUTO" "inputenc" t ("pdflatex"))
                  org-latex-hyperref-template  (concat "\\hypersetup{\n"

            (add-to-list 'org-latex-classes
                         (list "handout"
                               (concat "\\documentclass{tufte-handout}\n"
                               '("\\section{%s}" . "\\section*{%s}")
                               '("\\subsection{%s}" . "\\subsection*{%s}")
                               '("\\paragraph{%s}" . "\\paragraph*{%s}")
                               '("\\subparagraph{%s}" . "\\subparagraph*{%s}")))

            (add-to-list 'org-latex-classes
                         (list "org-report"
                               (concat "\\documentclass{report}\n"
                                       "\\parskip 1em\n"
                                       "\\parindent 0pt\n"
                               '("\\chapter{%s}" . "\\chapter*{%s}")
                               '("\\section{%s}" . "\\section*{%s}")
                               '("\\subsection{%s}" . "\\subsection*{%s}")
                               '("\\paragraph{%s}" . "\\paragraph*{%s}")
                               '("\\subparagraph{%s}" . "\\subparagraph*{%s}")))

            (add-to-list 'org-latex-classes
                         (list "org-article"
                               (concat "\\documentclass{article}\n"
                                       "\\parskip 1em\n"
                                       "\\parindent 0pt\n"
                               '("\\section{%s}" . "\\section*{%s}")
                               '("\\subsection{%s}" . "\\subsection*{%s}")
                               '("\\paragraph{%s}" . "\\paragraph*{%s}")
                               '("\\subparagraph{%s}" . "\\subparagraph*{%s}")))

            (use-package ox-koma-letter
              :ensure org-plus-contrib
              :config (progn
                        (add-to-list 'org-latex-classes
                                     (list "letter"
                                           (concat "\\documentclass{scrlttr2}\n"


  • underline becomes bold in Beamer. (source)
  • strikethrough becomes grey in Beamer.
(use-package ox-beamer
  :ensure nil
  :config (progn
            (add-to-list 'org-beamer-environments-extra
                         '("lemma" "l" "\\begin{lemma}%a" "\\end{lemma}"))
            (defun malb/beamer-underline (contents backend info)
              (when (eq backend 'beamer)
                (replace-regexp-in-string "\\`\\\\[A-Za-z0-9]+" "\\\\textbf" contents)))

            (defun malb/beamer-strikethrough (contents backend info)
              (when (eq backend 'beamer)
                (concat "{"
                        (replace-regexp-in-string "\\`\\\\[A-Za-z0-9]+" "\\\\color{lightgray}" contents)

            (defun malb/beamer-code (contents backend info)
              (when (eq backend 'beamer)
                (concat (replace-regexp-in-string "\\`\\\\[A-Za-z0-9]+" "\\\\lstinline" contents))))

            (add-to-list 'org-export-filter-underline-functions 'malb/beamer-underline)
            (add-to-list 'org-export-filter-strike-through-functions 'malb/beamer-strikethrough)
            (add-to-list 'org-export-filter-code-functions 'malb/beamer-code)

            (add-to-list 'org-latex-classes
                         (list "mbeamer"
                               (concat  "\\documentclass[presentation,smaller]{beamer}\n"
                                           (expand-file-name "talk-header.tex" user-emacs-directory))
                               '("\\section{%s}" . "\\section*{%s}")
                               '("\\subsection{%s}" . "\\subsection*{%s}")
                               '("\\subsubsection{%s}" . "\\subsubsection*{%s}")))))


(use-package ox-odt
  :ensure nil
  :init (progn
          (setq org-odt-styles-file
                (expand-file-name "org-export-template.odt" user-emacs-directory))))

Enable inline comments in org-export (source)

(load-file (expand-file-name "local/ox-inline-comments.el" user-emacs-directory))
org-odt-style-file doesn’t seem to take


ox-reveal — Presentations using reveal.js.

(use-package ox-reveal
    (setq org-reveal-root (expand-file-name "reveal.js/" user-emacs-directory)
          org-reveal-hlevel 2
          org-reveal-head-preamble "<style type=\"text/css\">.reveal p {text-align: left;}</style>"
          org-reveal-theme "solarized" ; beige blood moon night serif simple sky solarized
          org-reveal-mathjax t)))


ox-twbs - Twitter Bootstrap.

(use-package ox-twbs)


(use-package ox-pandoc
  :init (progn
          (setq org-pandoc-menu-entry  '((?m "as md." org-pandoc-export-as-markdown)
                                         (?M "to md and open." org-pandoc-export-to-markdown-and-open)
                                         (?x "to docx." org-pandoc-export-to-docx)
                                         (?X "to docx and open." org-pandoc-export-to-docx-and-open)
                                         (?e "to epub." org-pandoc-export-to-epub)
                                         (?E "to epub and open." org-pandoc-export-to-epub-and-open)
                                         (?3 "to epub3." org-pandoc-export-to-epub3)
                                         ( "to epub3 and open." org-pandoc-export-to-epub3-and-open)
                                         (?j "as json." org-pandoc-export-as-json)
                                         (?J "to json and open." org-pandoc-export-to-json-and-open)
                                         (?r "as rst." org-pandoc-export-as-rst)
                                         (?R "to rst and open." org-pandoc-export-to-rst-and-open)))))

Rich Text Clipboard

Place rich text version of selection in clipboard (source)

(use-package ox-clip)

Ignore Some Headlines

The tag :ignore: ignores a headline when exporting, section content is exported as usual.

(use-package ox-extra
  :ensure nil
  :config (ox-extras-activate '(ignore-headlines)))


If we are in a project we might add a TODO entry to the appropriate entry in

(defun malb/org-capture-projectile ()
  (if (projectile-project-p)
        (let ((malb/projectile-name
          (find-file (expand-file-name "" malb/org-files-dir))
          (goto-char (point-min))
          (if (re-search-forward (concat "^\* " malb/projectile-name ".*\n") nil t)
              (newline 1)
              (goto-char (point-max))
              (insert (concat "* " malb/projectile-name))
              (newline 1)))))
      (find-file malb/inbox-org)
      (goto-char (point-min))
      (re-search-forward "^\* Tasks" nil t)
      (newline 1))))

Template Expansions

  • %[file] Insert the contents of the file given by file.
  • %(sexp) Evaluate Elisp sexp and replace with the result. For convenience, %:keyword (see below) placeholders within the expression will be expanded prior to this. The sexp must return a string.
  • %<...> The result of format-time-string on the … format specification.
  • %t Timestamp, date only.
  • %T Timestamp, with date and time.
  • %u, %U Like the above, but inactive timestamps.
  • %i Initial content, the region when capture is called while the region is active. The entire text will be indented like %i itself.
  • %a Annotation, normally the link created with org-store-link.
  • %A Like %a, but prompt for the description part.
  • %l Like %a, but only insert the literal link.
  • %c Current kill ring head.
  • %x Content of the X clipboard.
  • %K Link to the currently clocked task.
  • %k Title of the currently clocked task.
  • %n User name (taken from user-full-name).
  • %f File visited by current buffer when org-capture was called.
  • %F Full path of the file or directory visited by current buffer.
  • %:keyword Specific information for certain link types, see below.
  • %^g Prompt for tags, with completion on tags in target file.
  • %^G Prompt for tags, with completion all tags in all agenda files.
  • %^t Like %t, but prompt for date. Similarly %^T, %^u, %^U. You may define a prompt like %^{Birthday}t.
  • %^L Like %^C, but insert as link.
  • %^C Interactive selection of which kill or clip to use.
  • %^{prop}p Prompt the user for a value for property prop.
  • %^{prompt} prompt the user for a string and replace this sequence with it. You may specify a default value and a completion table with %^{prompt|default|completion2|completion3...}. The arrow keys access a prompt-specific history.
  • %\n Insert the text entered at the nth %^{prompt}, where n is a number, starting from 1.
  • %? After completing the template, position cursor here.
(use-package org-capture
  :ensure nil
  :bind ("<f9>" . org-capture)
  :config (progn
            (setq org-capture-templates
                  '(("t" "[t]asks")
                    ("tt" "capture a standard [t]ask"
                     entry (file malb/inbox-org)
                     "* TODO %?

%i" :prepend t :empty-lines 1)

                    ("tc" "capture a standard task and the current [c]ontext"
                     entry (file malb/inbox-org)
                     "* TODO %?

%a" :prepend t :empty-lines 1)

                    ("tp" "capture a task for current projectile [p]roject" plain (function malb/org-capture-projectile)
                     "** TODO %?
%a" :prepend t :empty-lines 1)

                    ("e" "[e]-mail")

                    ("er" "quick create a task for [r]esponding to an [e]-mail"
                     entry (file+headline malb/inbox-org "E-Mail")
                     "* REPLY to %:fromname on %a
DEADLINE: %(org-insert-time-stamp (org-read-date nil t \"+1d\"))

%i" :prepend t :empty-lines 1 :immediate-finish t)

                    ("ef" "[f]ile an [e]-mail"
                     entry (file+headline malb/inbox-org "E-Mail")
                     "* %a by %:fromname
:END:" :immediate-finish nil :prepend nil :empty-lines 1)

                    ("es" "keep an [e]-mail [s]earch"
                     entry (file+headline malb/inbox-org "E-Mail")
                     "* %a"
                     :immediate-finish nil :prepend nil :empty-lines 0)

                    ("et" "create a [t]ask linked to an [e]-mail"
                     entry (file+headline malb/work-org "Admin")
                     "* TODO %?
SCHEDULED: %(org-time-stamp nil)
:EFFORT: %^{effort|1:00|0:05|0:15|0:30|2:00|4:00}


See %a by %:fromname
" :immediate-finish nil :prepend t :empty-lines 1)

                    ("eo" "quick create an [o]ffice task linked to an [e]-mail"
                     entry (file+headline malb/work-org "Admin")
                     "* TODO %a (%:fromname) %^{tag|:@office:|:@train:}
SCHEDULED: %(org-insert-time-stamp (let ((tue (org-read-date nil t \"Tue\"))
                             (thu (org-read-date nil t \"Thu\")))
                         (if (time-less-p tue thu)
" :immediate-finish t :prepend t :empty-lines 1)

                    ("eq" "quick create a  task linked to an [e]-mail"
                     entry (file+headline malb/work-org "Admin")
                     "* TODO %a (%:fromname)
SCHEDULED: %(let ((hour (third (decode-time (current-time)))))
                  (if (< hour 16)
                    (insert (org-time-stamp nil))
                   (org-insert-time-stamp (org-read-date nil t \"+1d\"))))
" :immediate-finish t :prepend t :empty-lines 1)

                    ("j" "add entry to [j]ournal"
                     entry (file+olp+datetree (lambda () (expand-file-name ""
                     "** work
** critique

                    ("m" "record notes on a [m]eeting" entry (file+headline malb/inbox-org "Meetings")
                     "* Meeting with %^{who}

" :clock-in t :clock-resume t :empty-lines 1)

                    ("n" "create a [n]ote"
                     entry (file malb/inbox-org)
                     "* %?

%i" :empty-lines 1)

                    ("s" "create note/tip on a piece of [s]oftware" entry
                     (file+headline (lambda () (expand-file-name "" deft-directory)) "Incoming")
                     "* %?

%a" :prepend t :empty-lines 1)))

            (setq org-capture-templates-contexts
                  '(("er" ((in-mode . "mu4e-view-mode")))
                    ("et" ((in-mode . "mu4e-view-mode")))
                    ("eo" ((in-mode . "mu4e-view-mode")))
                    ("en" ((in-mode . "mu4e-view-mode")))
                    ("ef" ((in-mode . "mu4e-view-mode")))
                    ("es" ((in-mode . "mu4e-headers-mode")))))))
(use-package org-protocol-capture-html
  :ensure nil
  :after org-capture
  :config (add-to-list 'org-capture-templates
                       '("w" "capture [w]ebsite" entry
                         (file+headline (lambda () (expand-file-name "" malb/org-files-dir)) "Links")
                         "* %?%a\nCaptured On: %U\n\n%:initial\n\n"
                         :immediate-finish nil
                         :prepend t) t))
(defun malb/mu4e-view-org-capture (arg)
  "Call `org-capture' followed by removing flag from e-mail."
  (interactive "P")
  (org-capture arg)

(bind-key "<f9>" #'malb/mu4e-view-org-capture mu4e-view-mode-map)

Run org-capture in a frame. We patch org-capture-pop-frame to handle “q” appropriately:

(condition-case nil
    (funcall orig-fun goto keys)
  ((debug error) (ocpf--delete-frame)))
(use-package org-capture-pop-frame
  :after (org-capture)
  :config (setq ocpf-frame-parameters '((name . "*Org Capture*")
                                        (width . 120)
                                        (height . 40)
                                        (fullscreen . nil)
                                        (tool-bar-lines . 0)
                                        (menu-bar-lines . 0))))
(add-to-list 'helm-commands-using-frame 'org-refile)

Web Tools

(use-package org-web-tools
  :after org)

Auto Completion

(add-hook 'org-mode-hook #'malb/enable-company-pcomplete)


Activate eldoc and show footnotes in minibuffer.

(use-package org-eldoc
  :ensure nil
  :config (progn
            (add-hook 'org-mode-hook #'org-eldoc-load)
            (add-hook 'org-mode-hook #'eldoc-mode)))

(defun malb/org-eldoc-get-footnote ()
    (let ((fn (org-between-regexps-p "\\[fn:" "\\]")))
      (when fn
          (nth 3 (org-footnote-get-definition (buffer-substring (+ 1 (car fn)) (- (cdr fn) 1)))))))))

(advice-add 'org-eldoc-documentation-function
            :before-until #'malb/org-eldoc-get-footnote)

Inline Tasks

(use-package org-inlinetask
  :ensure nil
  :config (progn
            (defun malb/org-latex-format-inlinetask-function
                (todo todo-type priority title tags contents info)
              "format function for a inlinetasks.
See `org-latex-format-inlinetask-function' for details."
              (concat "\\todo[inline]{\\textbf{" todo "}: " title "}\n"))
            (setq org-latex-format-inlinetask-function #'malb/org-latex-format-inlinetask-function)
            (setq org-inlinetask-min-level 6)))



(defun malb/org-delete-link ()
  "Replace an org link of the format [[LINK][DESCRIPTION]] with DESCRIPTION.
If the link is of the format [[LINK]], delete the whole org link.
In both the cases, save the LINK to the kill-ring.
Execute this command while the point is on or after the hyper-linked org link."
  (when (derived-mode-p 'org-mode)
    (let ((search-invisible t) start end)
        (when (re-search-backward "\\[\\[" nil :noerror)
          (when (re-search-forward "\\[\\[\\(.*?\\)\\(\\]\\[.*?\\)*\\]\\]"
                                   nil :noerror)
            (setq start (match-beginning 0))
            (setq end   (match-end 0))
            (kill-new (match-string-no-properties 1)) ; Save link to kill-ring
            (goto-char start)
            (re-search-forward "\\[\\[.*?\\(\\]\\[\\(.*?\\)\\)*\\]\\]" end)
            (replace-match "\\2")))))))
(defun malb/copy-available-dates ()
  "copy scheduled dates from headings 'TBD'"
    (let ((r))
          (let ((heading   (org-get-heading))
                (scheduled (org-entry-get (point) "SCHEDULED" nil)))
            (if (equal "TBD" heading)
                  (push scheduled r)))
      (kill-new (mapconcat (lambda (x)
                             (concat "- "
                                     (replace-regexp-in-string "<\\|>" "" x)))
                           (nreverse r) "\n")))))

Org Babel

Working with source code in org-mode.

(use-package ob
  :ensure nil
  :config (progn
            ;; load more languages for org-babel
             '((python . t)
               (shell . t)
               (latex . t)
               (ditaa . t)
               (C . t)
               (dot . t)
               (plantuml . t)
               (makefile . t)))

            (setq org-confirm-babel-evaluate nil
                  org-plantuml-jar-path" /usr/share/plantuml/plantuml.jar"
                  org-ditaa-jar-path "/usr/share/ditaa/ditaa.jar")
            (add-to-list 'org-src-lang-modes (quote ("plantuml" . plantuml)))))

Org iPython


(use-package ob-ipython
  :config (progn
            (add-to-list 'org-structure-template-alist
                         '("ip" "#+BEGIN_SRC ipython\n?\n#+END_SRC"
                           "<src lang=\"python\">\n?\n</src>"))

            ;; we use jupyter as the python shell so we work around that here
            (defun ob-ipython--get-python () (locate-file "python" exec-path))

            (advice-add 'ob-ipython-auto-configure-kernels :around
                        (lambda (orig-fun &rest args)
                          "Configure the kernels when found jupyter."
                          (when (executable-find ob-ipython-command)
                            (apply orig-fun args))))))


Add :async keyword to header-args of any org-babel src block and invoke ob-async-org-babel-execute-src-block.

(use-package ob-async)

Org Download


This extension facilitates moving images from point A (e.g. an image inside your browser that you can drag to Emacs) to point B (an Emacs org-mode buffer where the inline link will be inserted).

(use-package org-download)


Agenda commands

F(org-agenda-follow-mode)Toggle Follow mode
L(org-agenda-recenter)Display original location and recenter that window.
oDelete other windows.
f(org-agenda-later)Go forward in time to display
b(org-agenda-earlier)Go backward in time to display earlier dates
r and g(org-agenda-redo)Recreate the agenda buffer.
C-c C-s(org-agenda-schedule)Schedule this item.
C-c C-d(org-agenda-deadline)Set a deadline for this item.
S-<right>(org-agenda-do-date-later)Change the timestamp by one day into the future.
S-<left>(org-agenda-do-date-earlier)Change the timestamp by one day into the past.
>(org-agenda-date-prompt)Change the timestamp associated with the current line.
m(org-agenda-bulk-mark)Mark the entry at point for bulk action.
*(org-agenda-bulk-mark-all)Mark all visible agenda entries for bulk action.
u(org-agenda-bulk-unmark)Unmark entry at point for bulk action.
U(org-agenda-bulk-remove-all-marks)Unmark all marked entries for bulk action.
B(org-agenda-bulk-action)Bulk action: act on all marked entries in the agenda.

Commands I easily forget

  • C-c C-v C-d splits blocks
  • C-c C-j calls (org-goto) which jumps to headlines in a file
  • C-c / calls (org-sparse-tree) which reduces the tree to the nodes with some attribute


In org-mode we can style inline elements with bold, italic, underlined, verbatim, and code. But this breaks if the character just inside the styling code is a non-smart single or double quote. C-c ; is styled; C-c '= is not. We can fix that by inserting a zero-width space between the apostrophe and the = . The first time, we can put the cursor between the apostrophe and the = and enter ~C-x 8 RET ZERO WIDTH SPACE RET~, at which point =C-c '​ will display correctly.

Batch Exporting

Note the order matters, i.e. open the file first then call org-latex-export-to-pdf

emacs --batch -l ~/.emacs.d/org-export-init.el -f org-latex-export-to-pdf


We don’t need a $PAGER here (source)

(setenv "PAGER" "cat")

Toggle Shells

Open various shells at point, close with x Config partly stolen from spacemacs and from (source).

(defun malb/make-toggle-shell (shell-name shell-cmd &optional post-exec per-project enter-exec)
  "Create function to toggle a shell-like thing in current project/directory.

The function behaves as follows:

- The shell-like buffer is renamed to match the current projectile
  project or directory to make multiple windows easier.
- If a buffer of the same name already exists, it is reused.
- If a buffer with the same name is already shown, its window is closed.
- `post-exec` is run after the shell is created.
- `enter-exec` is called before anything else (to set virtualenvs etc)
  `(lambda (arg)
     (interactive "P")
     (let* ((parent (if (buffer-file-name)
                        (file-name-directory (buffer-file-name))
            (height (/ (window-total-height) 3))
            (name   (if (and (not arg) ,per-project)
                        (concat "*" ,shell-name
                                "[" (if (projectile-project-p)
                                      (car (last (split-string parent "/" t)))) "]" "*")
            (golden-ratio-mode nil)
            (window (get-buffer-window name)))
       (if (and window (<= (window-height window) (/ (frame-height) 3)))
             (select-window window)
           (split-window-vertically (- height))
           (other-window 1)
           (if (get-buffer name)
                 (switch-to-buffer name)
                 (set-window-dedicated-p (get-buffer-window (current-buffer)) t))
               (rename-buffer name)
               (set-window-dedicated-p (get-buffer-window (current-buffer)) t)


MultiTerm is a mode based on term.el, for managing multiple terminal buffers in Emacs.

(use-package multi-term
  :config (progn
            (setq term-buffer-maximum-size 16384)

            (add-to-list 'term-bind-key-alist '("S-<left>"  . multi-term-prev))
            (add-to-list 'term-bind-key-alist '("S-<right>" . multi-term-next))
            (add-to-list 'term-bind-key-alist '("<tab>" . malb/term-send-tab))
            (add-to-list 'term-bind-key-alist '("C-r" . term-send-reverse-search-history))

            (bind-key "C-y" #'term-paste term-raw-map)

            ;; rebind C-r to the terminal's native one
            (setq term-bind-key-alist (remove* '"C-r" term-bind-key-alist :test 'equal :key 'car))

            (setq multi-term-switch-after-close nil)

            (defun malb/term-send-tab ()
              "Send tab in term mode."
              (term-send-raw-string "\t"))

            (fset 'malb/toggle-multi-term
                  (malb/make-toggle-shell "multi-term"
                                          '(let ((current-prefix-arg nil)) (multi-term))
                                             (when (and (boundp 'venv-current-name) venv-current-name)
                                               (insert (format "workon %s" venv-current-name))
                                             (insert "ls")


(use-package shell
  :config (progn
            (push (cons "\\*shell\\*" display-buffer--same-window-action) display-buffer-alist)

            (defun malb/comint-close-buffer-when-finished ()
              "This function for close current shell buffer.
When `exit' from shell buffer."
              (when (ignore-errors (get-buffer-process (current-buffer)))
                (set-process-sentinel (get-buffer-process (current-buffer))
                                      (lambda (proc change)
                                        (when (string-match "\\(finished\\|exited\\)" change)
                                          (kill-buffer (process-buffer proc)))))))

            (add-hook 'shell-mode-hook #'toggle-truncate-lines)
            (add-hook 'shell-mode-hook #'malb/enable-company-pcomplete)
            (add-hook 'shell-mode-hook #'malb/comint-close-buffer-when-finished)
            (fset 'malb/toggle-shell
                  (malb/make-toggle-shell "shell" '(shell)  nil t))))



(use-package eshell
  :config (progn

            ;; -------------
            ;; TOGGLE ESHELL
            ;; -------------

            (fset 'malb/toggle-eshell
                  (malb/make-toggle-shell "eshell"
                                          '(eshell "new")
                                          '(progn (insert "ls") (eshell-send-input)) t))

            (bind-key "C-`" #'malb/toggle-eshell)

            ;; ------------
            ;; KEY BINDINGS
            ;; ------------

            (defun malb/eshell-keys ()
              (bind-key "<home>" #'eshell-bol eshell-mode-map)
              (bind-key "M-r" #'helm-eshell-history eshell-mode-map)
              (bind-key "M-/" #'helm-esh-pcomplete eshell-mode-map)
              (bind-key "M-w" #'malb/eshell-kill-ring-save eshell-mode-map)
              (bind-key "ESC ." #'malb/eshell-insert-last-word eshell-mode-map))

            ;; for some reason this needs to be a hook
            (add-hook 'eshell-mode-hook #'malb/eshell-keys)

            ;; ---------------
            ;; VISUAL COMMANDS
            ;; ---------------

            (setq eshell-visual-subcommands '(("git" "diff" "show")
                                              ("tmux" "attach" "new")))

            (defun malb/eshell-add-visual-commands ()
              (add-to-list 'eshell-visual-commands "htop")
              (add-to-list 'eshell-visual-commands "ssh")
              (add-to-list 'eshell-visual-commands "ipython")
              (add-to-list 'eshell-visual-commands "")
              (add-to-list 'eshell-visual-commands "vim"))

            (add-hook 'eshell-mode-hook #'malb/eshell-add-visual-commands)

            (defun malb/visual-command-frame (old-function &rest arguments)
              (let  ((frame (new-frame '((width . 160) (height . 40)))))
                (select-frame frame)
                (apply old-function arguments)
                ;; (set-process-sentinel (get-buffer-process (current-buffer))
                ;;                       (lambda (proc change)
                ;;                         (when (string-match "\\(finished\\|exited\\)" change)
                ;;                           (delete-frame (window-frame (get-buffer-window (process-buffer proc)))))))
                (set-window-dedicated-p (get-buffer-window) t)))

            (advice-add #'eshell-exec-visual :around #'malb/visual-command-frame)

            ;; -------
            ;; ALIASES
            ;; -------

            (defun malb/git-log (&rest args)
              (let* ((branch-or-file (car args))
                     (file-list (if (and branch-or-file (f-file-p branch-or-file))
                                  (cdr args)))
                     (branch (if (and branch-or-file (f-file-p branch-or-file))
                (message branch-or-file)
                (if branch-or-file
                    (magit-log (list branch) '()
                               (mapcar (lambda (f) (concat (eshell/pwd) "/" f))

            (defun eshell/git (command &rest args)
              (pcase command
                ("log" (apply #'malb/git-log args))
                ("status" (progn
                (_ (let ((command (s-join " " (append (list "git" command) args))))
                     (message command)
                     (shell-command-to-string command)))))

            ;; mapped to 'x' in eshell
            (defun eshell/x ()

            (defalias 'eshell/q     'malb/quit-bottom-disposable-windows)
            (defalias 'eshell/en    'find-file)
            (defalias 'eshell/e     'find-file)
            (defalias 'eshell/vim   'find-file) ;; muscle memory
            (defalias 'eshell/d     'dired)
            (defalias 'eshell/emacs 'find-file)
            (defalias 'eshell/less  'view-file)

            (defun eshell/clear ()
              "Clear the eshell buffer."
              (let ((inhibit-read-only t))

            ;; TODO This is broken
            (defun eshell/ag (&optional dir)
              (malb/helm-ag (file-name-as-directory
                             (expand-file-name (or dir ".")))))

            ;; -----------
            ;; MINOR MODES
            ;; -----------

            (defun malb/eshell-minor-modes ()
              (smartparens-mode t))

            (add-hook 'eshell-mode-hook #'malb/eshell-minor-modes)

            ;; ------------------------------------------------
            ;; ------------------------------------------------

            (setq eshell-input-filter
                  (lambda (str)
                    (not (or (string= "" str)
                             (string-prefix-p " " str)))))

            ;; --------------------
            ;; --------------------

            (defun eshell/kpo (&optional nth)
              "Copies the output of the previous command to the kill ring.
When nth is set, it will copy the nth previous command."
                ;; Move to the end of the eshell buffer.
                (goto-char (point-max))
                ;; Move to the start of the last prompt.
                (search-backward-regexp eshell-prompt-regexp nil nil nth)
                ;; Move to the start of the line, before the prompt.
                ;; Remember this position as the end of the region.
                (let ((end (point)))
                  ;; Move to the start of the last prompt.
                  (search-backward-regexp eshell-prompt-regexp)
                  ;; Move one line below the prompt, where the output begins.
                  ;; Find first line that's not blank.
                  (while (looking-at "^[[:space:]]*$")
                  ;; Copy region to kill ring.
                  (copy-region-as-kill (point) end)
                  ;; Output stats on what was copied as a sanity check.
                  (format "Copied %s words to kill ring." (count-words-region (point) end)))))

            (defun malb/eshell-kill-ring-save (arg)
              "Copy selection or previous command's output to kill ring."
              (interactive "P")
              (if (or arg (region-active-p))
                  (call-interactively #'kill-ring-save)

            ;; --------------
            ;; TAB COMPLETION
            ;; --------------

            (defun malb/config-eshell-completion ()
              (setq pcomplete-cycle-completions t
                    pcomplete-ignore-case t)
              (setq-local company-idle-delay nil)
              (define-key eshell-mode-map [remap eshell-pcomplete] #'company-pcomplete))

            (add-hook 'eshell-mode-hook #'malb/config-eshell-completion)

            ;; -----
            ;; ESC-.
            ;; -----

            (defun malb/eshell-last-argument (n)
              (let* ((input (substring-no-properties
                             (eshell-previous-input-string (1- n))))
                     (parse (with-temp-buffer
                              (insert input)
                              (car (reverse (eshell-parse-arguments
                                             (point-min) (point-max)))))))
                (eval parse)))

            (defun malb/eshell-insert-last-word (n)
              (interactive "p")
              (unless (eq last-command this-command)
                (put 'malb/eshell-insert-last-word 'pre-n n)
                (put 'malb/eshell-insert-last-word 'pre-p nil))
              (let ((n (get 'malb/eshell-insert-last-word 'pre-n))
                    (pre-p (get 'malb/eshell-insert-last-word 'pre-p)))
                (when pre-p
                  (delete-region pre-p (point)))
                (put 'malb/eshell-insert-last-word 'pre-n (1+ n))
                (put 'malb/eshell-insert-last-word 'pre-p (point))
                (insert (malb/eshell-last-argument n))))

            ;; ------
            ;; CONFIG
            ;; ------

            (setq eshell-scroll-to-bottom-on-input t
                  eshell-destroy-buffer-when-process-dies t
                  eshell-history-size 8192
                  eshell-buffer-maximum-lines 16384
                  eshell-hist-ignoredups t
                  eshell-list-files-after-cd t
                  eshell-ls-initial-args "-hk"
                  eshell-buffer-shorthand t
                  eshell-plain-echo-behavior t)))

Plan 9 Smart Shell


(use-package em-smart
  :ensure nil
  :config (setq eshell-where-to-jump 'begin
                eshell-review-quick-commands nil
                eshell-smart-space-goes-to-end t))

Git prompt

(use-package eshell-git-prompt
  :config (progn (eshell-git-prompt-use-theme "powerline")))

Git completion


(defun pcmpl-git-commands ()
  "Return the most common git commands by parsing the git output."
    (call-process-shell-command "git" nil (current-buffer) nil "help" "--all")
    (goto-char 0)
    (search-forward "available git commands in")
    (let (commands)
      (while (re-search-forward
	      nil t)
	(push (match-string 1) commands)
	(when (match-string 2)
	  (push (match-string 2) commands)))
      (sort commands #'string<))))

(defconst pcmpl-git-commands (pcmpl-git-commands)
  "List of `git' commands.")

(defvar pcmpl-git-ref-list-cmd "git for-each-ref refs/ --format='%(refname)'"
  "The `git' command to run to get a list of refs.")

(defun pcmpl-git-get-refs (type)
  "Return a list of `git' refs filtered by TYPE."
    (insert (shell-command-to-string pcmpl-git-ref-list-cmd))
    (goto-char (point-min))
    (let (refs)
      (while (re-search-forward (concat "^refs/" type "/\\(.+\\)$") nil t)
	(push (match-string 1) refs))
      (nreverse refs))))

(defun pcmpl-git-remotes ()
  "Return a list of remote repositories."
  (split-string (shell-command-to-string "git remote")))

(defun pcomplete/git ()
  "Completion for `git'."
  ;; Completion for the command argument.
  (pcomplete-here* pcmpl-git-commands)
   ((pcomplete-match "help" 1)
    (pcomplete-here* pcmpl-git-commands))
   ((pcomplete-match (regexp-opt '("pull" "push")) 1)
    (pcomplete-here (pcmpl-git-remotes)))
   ;; provide branch completion for the command `checkout'.
   ((pcomplete-match "checkout" 1)
    (pcomplete-here* (append (pcmpl-git-get-refs "heads")
			     (pcmpl-git-get-refs "tags"))))
    (while (pcomplete-here (pcomplete-entries))))))


Cat directly into a buffer (select with C-c M-b)

cat mylog.log >> #<buffer *scratch*>

Zsh History

(defvar malb/helm-c-source-zsh-history
  '((name . "Zsh History")
    (candidates . malb/helm-c-zsh-history-set-candidates)
    (action . (("Insert" . malb/helm-c-zsh-history-insert)
               ("Execute" . malb/helm-c-zsh-history-run)))
    (requires-pattern . 3)

(defun malb/helm-c-zsh-history-set-candidates (&optional request-prefix)
  (let ((pattern (replace-regexp-in-string
                  " " ".*"
                  (or (and request-prefix
                           (concat request-prefix
                                   " " helm-pattern))
    (with-current-buffer (find-file-noselect "~/.zsh_history" t t)
      (auto-revert-mode -1)
      (goto-char (point-max))
      (loop for pos = (re-search-backward pattern nil t)
            while pos
            collect (replace-regexp-in-string
                     "\\`:.+?;" ""
                     (buffer-substring (line-beginning-position)

(defun malb/helm-c-zsh-history-insert (candidate)
  (insert candidate))

(defun malb/helm-c-zsh-history-run (candidate)
  (malb/named-compile candidate))

(defun malb/helm-command-from-zsh ()
  (require 'helm)
  (helm-other-buffer 'malb/helm-c-source-zsh-history "*helm zsh history*"))


… still my main shell, let’s make sure we can get there quickly.

#!/usr/bin/env bash
if [ -z $(wmctrl -xl | grep -iF 'yakuake.yakuake' | head -n1 | cut -d' ' -f1) ]; then
    qdbus org.kde.yakuake /yakuake/window toggleWindowState;
qdbus org.kde.yakuake /yakuake/sessions org.kde.yakuake.addSession;
qdbus org.kde.yakuake /yakuake/sessions org.kde.yakuake.runCommand "cd $1" >/dev/null 2>&1 &
(defun malb/ykcd ()
  (let ((fn (or (buffer-file-name) "~")))
    (shell-command (concat "ykcd " (file-name-directory fn)))))

Programming (Languages)




We want to pick previous inputs based on prefix (source)

(use-package comint
  :ensure nil
  :config (progn
            (dolist (key '("C-<up>" "M-<up>" "M-p"))
              (bind-key key #'comint-previous-matching-input-from-input comint-mode-map))
            (dolist (key '("C-<down>" "M-<down>" "M-n"))
              (bind-key key #'comint-next-matching-input-from-input comint-mode-map))

            (bind-key "M-r" #'helm-comint-input-ring comint-mode-map)

            (setq comint-scroll-to-bottom-on-input t ; always insert at the bottom
                  comint-scroll-to-bottom-on-output nil
                  comint-input-ignoredups t ; no duplicates in command history
                  comint-prompt-read-only t ; don’t overwrite prompt
                  comint-move-point-for-output t)

            (defun malb/kill-buffer-delete-window ()
              (let ((kill-buffer-query-functions nil))

            (defun malb/kill-buffer (old-function &rest arguments)
              (apply old-function arguments)
              (let ((kill-buffer-query-functions nil))

            (advice-add 'comint-send-eof :around  #'malb/kill-buffer)

            (defun malb/comint-kill-ring-save (arg)
              "Copy selection or previous command's output to kill ring."
              (interactive "P")
              (if (or arg (region-active-p))
                  (call-interactively #'kill-ring-save)
                (let ((proc (get-buffer-process (current-buffer)))
                      (inhibit-read-only t))
                  (let ((end (save-excursion
                               (goto-char (process-mark proc))
                               (forward-line 0)
                        (start (save-excursion
                                 (goto-char comint-last-input-start)
                                 (forward-line 0)
                    (copy-region-as-kill start end)))))

            (bind-key "M-w" #'malb/comint-kill-ring-save comint-mode-map)
            (bind-key "C-S-w" #'malb/kill-buffer-delete-window comint-mode-map)))
Comint History Across Sessions


Directory where comint history will be stored

(defvar comint-history-dir (locate-user-emacs-file "comint-history"))

(unless (file-exists-p comint-history-dir)
  (make-directory comint-history-dir))

Function to write comint history on exit

(defun comint-write-history-on-exit (process event)
  (let ((buf (process-buffer process)))
    (when (buffer-live-p buf)
      (with-current-buffer buf
        (insert (format "\nProcess %s %s" process event))))))

Function to setup comint history

(defun turn-on-comint-history ()
  (let ((process (get-buffer-process (current-buffer))))
    (when process
      (setq comint-input-ring-file-name
            (expand-file-name (format "inferior-%s-history"
                                      (process-name process))
      (comint-read-input-ring t)
      (add-hook 'kill-buffer-hook 'comint-write-input-ring t t)
      (set-process-sentinel process

Setup comint history on comint start

(add-hook 'comint-mode-hook 'turn-on-comint-history)

Save comint history when emacs is killed

(defun comint-write-input-ring-all-buffers ()
  (mapc (lambda (buffer) (with-current-buffer buffer (comint-write-input-ring)))

(add-hook 'kill-emacs-hook 'comint-write-input-ring-all-buffers)

shx or “shell-extras” extends comint-mode in Emacs —

(use-package shx
  :after comint
  :config (progn
            (with-eval-after-load 'python
              (add-hook 'inferior-python-mode-hook #'shx-mode))
            ;; (with-eval-after-load 'sage-shell-mode
            ;;   (add-hook 'sage-shell-mode-hook #'shx-mode))
            (with-eval-after-load 'shell
              (add-hook 'shell-mode-hook #'shx-mode))))

isend (poor person’s REPL)

  1. Open, say, Sage.
  2. M-x isend-associate RET Sage RET
  3. Hitting C-RET will send the current line to the interpreter. If a region is active, all lines spanned by the region will be sent (i.e. no line will be only partially sent).
(use-package isend-mode
  :config (progn
            ;; If you work with python scripts using iPython
            (add-hook 'isend-mode-hook #'isend-default-ipython-setup)))

Spell checking

Enable spell checking in comments and documentation.

(add-hook 'prog-mode-hook 'flyspell-prog-mode)

Flycheck (source)

Use Flycheck to run static checkers on code. We use clang’s checker for flycheck for which we can load per directory configuration using .dir-locals.el, e.g.

((c-mode . ((flycheck-clang-include-path . ("/FULL/PATH/TO/DIR1" "/FULL/PATH/TO/DIR2" ) ))))

Make flycheck prettier based on what spacemacs does.

(use-package flycheck
  :diminish flycheck-mode
  :config (progn
            (add-hook 'org-mode-hook (lambda () (flycheck-mode -1)))

            (bind-key "C-c f n" #'flycheck-next-error flycheck-mode-map)
            (bind-key "C-c f p" #'flycheck-previous-error flycheck-mode-map)
            (bind-key "C-c f l" #'flycheck-list-errors flycheck-mode-map)

            (setq flycheck-check-syntax-automatically '(save mode-enabled))
            (setq flycheck-standard-error-navigation nil)

            (when (fboundp 'define-fringe-bitmap)
              (define-fringe-bitmap 'my-flycheck-fringe-indicator
                (vector #b00000000

            (flycheck-define-error-level 'error
              :overlay-category 'flycheck-error-overlay
              :fringe-bitmap 'my-flycheck-fringe-indicator
              :fringe-face 'flycheck-fringe-error)

            (flycheck-define-error-level 'warning
              :overlay-category 'flycheck-warning-overlay
              :fringe-bitmap 'my-flycheck-fringe-indicator
              :fringe-face 'flycheck-fringe-warning)

            (flycheck-define-error-level 'info
              :overlay-category 'flycheck-info-overlay
              :fringe-bitmap 'my-flycheck-fringe-indicator
              :fringe-face 'flycheck-fringe-info)))

Use helm-flycheck because reasons.

(use-package helm-flycheck
  :after flycheck
  :config (progn
            (bind-key "C-c f h" #'helm-flycheck flycheck-mode-map)))

Use flycheck-pos-tip to display hints about potential issues.

(use-package flycheck-pos-tip
  :after flycheck
  :init (progn
          ;; flycheck errors on a tooltip (doesnt work on console)
          (with-eval-after-load 'flycheck


Comments, as I mean, using comment-dwim-2.

(use-package comment-dwim-2
  :bind ("M-;" . comment-dwim-2))

Make bug references in comments and string clickable

(add-hook 'prog-mode-hook #'bug-reference-prog-mode)


Obey Project Rules

drt-indent guesses the indentation offset originally used for creating source code files and transparently adjusts the corresponding settings in Emacs, making it more convenient to edit foreign files.

(use-package dtrt-indent
  :diminish dtrt-indent-mode
  :config (progn (dtrt-indent-global-mode t)))

Agressive Indenting

aggressive-indent-mode for … aggressive indenting. (source)

Enable it on a per-project basis in order to keep RC check ins clean: use it in own projects but not necessarily in projects where not the main contributor. Use .dir-locals.el to enable it, e.g.:

((c-mode . ((aggressive-indent-mode t))))
(use-package aggressive-indent
  :config (progn
            (add-hook 'emacs-lisp-mode-hook #'aggressive-indent-mode)
            (unbind-key "C-c C-q" aggressive-indent-mode-map))
  :diminish aggressive-indent-mode)

Trailing whitespace

ws-buttler for not leaving trailing white spaces without being that guy™.

(use-package ws-butler
  :diminish ws-butler-mode
  :config (progn
            ;; adding it to prog-mode-hook causes problems for emacsclient
            (add-hook 'c-mode-common-hook   #'ws-butler-mode)
            (add-hook 'python-mode-hook     #'ws-butler-mode)
            (add-hook 'cython-mode-hook     #'ws-butler-mode)
            (add-hook 'latex-mode-hook      #'ws-butler-mode)
            (add-hook 'emacs-lisp-mode-hook #'ws-butler-mode)

Highlight FIXME and friends

(defun malb/fixme-highlight ()
  (font-lock-add-keywords nil
                          '(("\\<\\(FIXME\\|BUG\\|TODO\\|HACK\\|NOTE\\)" 1
                             font-lock-warning-face t))))

(add-hook 'prog-mode-hook #'malb/fixme-highlight)
(add-hook 'python-mode-hook #'malb/fixme-highlight)

Which Function

Show function in mode-line (source)

(use-package which-func
  :config (progn
            (which-function-mode 1)
            (setq which-func-unknown ""
                  which-func-maxout 1024
                  which-func-modes '(latex-mode

            (setq which-func-format
                  `(" "
                    (:propertize which-func-current local-map
                                  (mode-line keymap
                                             (mouse-3 . end-of-defun)
                                             (mouse-2 . narrow-to-defun)
                                             (mouse-1 . beginning-of-defun)))
                                 face which-func
                                 mouse-face mode-line-highlight
                                 help-echo "mouse-1: go to beginning\n\
mouse-2: toggle rest visibility\n\
mouse-3: go to end")
                    " "))

Highlight Symbols

(use-package highlight-symbol
  :diminish highlight-symbol-mode
  :after cc-mode
  :config (progn
            (bind-key "M-n" #'highlight-symbol-next prog-mode-map)
            (bind-key "M-p" #'highlight-symbol-prev prog-mode-map)
            (bind-key "M-n" #'highlight-symbol-next c-mode-base-map)
            (bind-key "M-p" #'highlight-symbol-prev c-mode-base-map)))

Line Numbers

(use-package nlinum
  :config (progn
            (add-hook 'c-mode-common-hook 'nlinum-mode)))

Looking Stuff Up

helm-dash package uses Dash docsets inside emacs to browse documentation.

(use-package helm-dash
  :bind ("C-c h ." . helm-dash-at-point)
  :config (progn
            (setq helm-dash-common-docsets '("Bash"
                                             "Bootstrap 4"
                                             "Emacs Lisp"
                                             "Python 2"
                                             "Python 3"
                  helm-dash-browser-func 'eww)))

Tip: C-c C-f aka helm-follow-mode is your friend.

GNU Global

Use GNU Global for Java (source) and optionally for C++

(use-package ggtags
  :config (progn  (add-hook 'c-mode-common-hook
                            (lambda ()
                              (ggtags-mode t)))

                  (unbind-key "M-." ggtags-mode-map)
                  (unbind-key "M-," ggtags-mode-map)
                  (unbind-key "C-M-." ggtags-mode-map)
                  (bind-key "C-c j g s" #'ggtags-find-other-symbol ggtags-mode-map)
                  (bind-key "C-c j g h" #'ggtags-view-tag-history ggtags-mode-map)
                  (bind-key "C-c j g r" #'ggtags-find-reference ggtags-mode-map)
                  (bind-key "C-c j g f" #'ggtags-find-file ggtags-mode-map)
                  (bind-key "C-c j g c" #'ggtags-create-tags ggtags-mode-map)
                  (bind-key "C-c j g u" #'ggtags-update-tags ggtags-mode-map)))
(use-package helm-gtags
  :init (setq helm-gtags-prefix-key "\C-cjg")
  :diminish helm-gtags-mode
  :config (progn  (setq helm-gtags-ignore-case t
                        helm-gtags-auto-update t
                        helm-gtags-use-input-at-cursor t
                        helm-gtags-pulse-at-cursor t)

                  (add-hook 'c-mode-common-hook #'helm-gtags-mode)

                  (bind-key "C-c j g a" #'helm-gtags-tags-in-this-function helm-gtags-mode-map)
                  (bind-key "C-c j g j" #'helm-gtags-select helm-gtags-mode-map)
                  (bind-key "C-c j g ." #'helm-gtags-dwim helm-gtags-mode-map)
                  (bind-key "C-c j g ," #'helm-gtags-pop-stack helm-gtags-mode-map)
                  (bind-key "C-c j g <"   #'helm-gtags-previous-history helm-gtags-mode-map)
                  (bind-key "C-c j g >"   #'helm-gtags-next-history helm-gtags-mode-map)))

Dump Jump

zero-config jump to definition for JavaScript, Emacs Lisp, Python, Go, Clojure, …

  • dumb-jump-go C-c j d . core functionality. Attempts to jump to the definition for the thing under point
  • dumb-jump-back =C-c j d ,= jumps back to where you were when you jumped. These are chained so if you go down a rabbit hole you can get back out or where you want to be.
  • dumb-jump-quick-look C-c j d q like dumb-jump-go but shows tooltip with file, line, and context
(use-package dumb-jump
  :config (progn
            (setq dumb-jump-selector 'helm)
            (unbind-key "C-M-g" dumb-jump-mode-map)
            (unbind-key "C-M-p" dumb-jump-mode-map)
            (unbind-key "C-M-q" dumb-jump-mode-map)
            (bind-key "C-c j d ." #'dumb-jump-go dumb-jump-mode-map)
            (bind-key "C-c j d ," #'dumb-jump-back dumb-jump-mode-map)
            (bind-key "C-c j d q" #'dumb-jump-quick-look dumb-jump-mode-map)

Smart Jump

This packages tries to smartly go to definition leveraging several methods to do so. If one method fails, this package will go on to the next one, eventually falling back to dumb-jump. — smart-jump

(use-package smart-jump
  :config (smart-jump-setup-default-registers))


All the debuggers.

(use-package realgud
  :commands (realgud:pdb realgud:ipdb realgud:trepan2 realgud:gdb)
  :config (progn
            (setq realgud:pdb-command-name "python -m pdb")))


(setq gdb-find-source-frame t
      gdb-many-windows t)


“Now, when invoking rr replay from Emacs, you simply take the suggested command (gdb --fullname or gdb -i=mi), replace gdb with rr replay, add the log we’re replaying, and things then work as expected.” —


(use-package string-inflection)

Code Formatting

Auto-format source code in many languages with one command – format-all

(use-package format-all)

Language Server Protocol

(use-package lsp-mode
  :after (projectile)
  :config (setq lsp-eldoc-render-all nil))

(use-package lsp-imenu
  :ensure lsp-mode
  :after (lsp-mode)
  :config (add-hook 'lsp-after-open-hook 'lsp-enable-imenu))
(use-package lsp-ui
  :after (lsp-mode))

(use-package lsp-ui-flycheck
  :after (lsp-mode)
  :ensure lsp-ui
  :config (add-hook 'lsp-after-open-hook (lambda () (lsp-ui-flycheck-enable 1))))

(use-package lsp-ui-peek
  :after (lsp-mode)
  :ensure lsp-ui
  ;; register the defaults which will call out to ccls
  :config (smart-jump-register :modes '(c-mode c++-mode)
                               :async 2000
                               :order 1))

(use-package lsp-ui-sideline
  :after (lsp-mode)
  :ensure lsp-ui
  :config (setq lsp-ui-sideline-ignore-duplicate t))

C/C++ development

(use-package cc-mode
  :config (progn
            (add-hook 'c-mode-common-hook #'malb/c-mode-common-hook)
            (add-hook 'c-mode-common-hook #'hs-minor-mode)
            (bind-key "<home>"  #'malb/beginning-of-line-dwim c-mode-base-map)
            (add-to-list 'auto-mode-alist '("\\.inl\\'" . c++-mode))))


C/C++/ObjC language server supporting cross references, hierarchies, completion and semantic highlighting –

(use-package ccls
  :commands lsp-ccls-enable
  :after (lsp-mode)
  :init (progn
          (defun malb/ccls-enable ()
            (condition-case nil
              (user-error nil)))

          (add-hook 'c-mode-hook #'malb/ccls-enable)
          (add-hook 'c++-mode-hook #'malb/ccls-enable)))

Font Lock

Grey out #if 0 blocks.

(defun malb/c-mode-font-lock-if0 (limit)
      (goto-char (point-min))
      (let ((depth 0) str start start-depth)
        (while (re-search-forward "^\\s-*#\\s-*\\(if\\|else\\|endif\\)" limit 'move)
          (setq str (match-string 1))
          (if (string= str "if")
                (setq depth (1+ depth))
                (when (and (null start) (looking-at "\\s-+0"))
                  (setq start (match-end 0)
                        start-depth depth)))
            (when (and start (= depth start-depth))
              (c-put-font-lock-face start (match-beginning 0) 'font-lock-comment-face)
              (setq start nil))
            (when (string= str "endif")
              (setq depth (1- depth)))))
        (when (and start (> depth 0))
          (c-put-font-lock-face start (point) 'font-lock-comment-face)))))

(defun malb/c-mode-common-hook ()
  (font-lock-add-keywords  nil
                           '((malb/c-mode-font-lock-if0 (0 font-lock-comment-face prepend))) 'add-to-end))

Highlight member functions in C/C++ (source)

(dolist (major-mode '(c-mode c++-mode))
  (font-lock-add-keywords major-mode
                               "\\<[_a-zA-Z][_a-zA-Z0-9]*\\>"       ; Object identifier
                               "\\s *"                              ; Optional white space
                               "\\(?:\\.\\|->\\)"                   ; Member access
                               "\\s *"                              ; Optional white space
                               "\\<\\([_a-zA-Z][_a-zA-Z0-9]*\\)\\>" ; Member identifier
                               "\\s *"                              ; Optional white space
                               "(")                                 ; Paren for method invocation
                             1 'font-lock-function-name-face t))))


(use-package doxymacs
  :disabled t
  :diminish doxymacs-mode
  :config (progn
            (defun malb/doxymacs ()
              (doxymacs-mode t)

            (add-hook 'c-mode-common-hook #'malb/doxymacs)))


This code allows to run valgrind and step through warnings/errors. We set --error-errorcode=1 because we bury compilation buffers that finish with exit code zero automatically. By default, valgrind returns the exit code of the program it runs. (source)

(require 'compile "compile")

(defgroup valgrind nil
  "Run valgrind as inferior of Emacs, parse error messages."
  :group 'tools
  :group 'processes)

(defcustom valgrind-command "valgrind --error-exitcode=1 --leak-check=full"
  "*Last shell command used to run valgrind; default for next valgrind run.

Sometimes it is useful for files to supply local values for this variable.
You might also use mode hooks to specify it in certain modes, like this:

    (add-hook 'c-mode-hook
       (lambda ()
         (unless (or (file-exists-p \"makefile\")
                     (file-exists-p \"Makefile\"))
           (set (make-local-variable 'valgrind-command)
                (concat \"make -k \"
                        (file-name-sans-extension buffer-file-name))))))"
  :type 'string
  :group 'valgrind)

;; History of compile commands.
(defvar valgrind-history nil)

(defun valgrind (command)
  "Run valgrind.
Runs COMMAND, a shell command, in a separate process asynchronously
with output going to the buffer `*valgrind*'.

You can then use the command \\[next-error] to find the next error message
and move to the source code that caused it."
   (if (or compilation-read-command current-prefix-arg)
       (list (read-from-minibuffer "Valgrind command: "
                                   (eval valgrind-command) nil nil
                                   '(valgrind-history . 1)))
     (list (eval valgrind-command))))
  (unless (equal command (eval valgrind-command))
    (setq valgrind-command command))
  (compilation-start command t))

Modern C++

(use-package modern-cpp-font-lock
  :diminish modern-c++-font-lock-mode
  :config (add-hook 'c++-mode-hook #'modern-c++-font-lock-mode))

Clang Format

Use ClangFormat to … format code.

(use-package clang-format
  :config (progn
            (setq clang-format-executable "clang-format-3.8")))


(use-package disaster)


(use-package go-mode
  :mode "\\.go$"
  :interpreter "go"
  :config (add-hook 'go-mode-hook #'flycheck-mode))
(use-package go-eldoc
  :hook (go-mode . go-eldoc-setup))
(use-package go-guru
  :commands (go-guru-describe go-guru-freevars go-guru-implements go-guru-peers
                              go-guru-referrers go-guru-definition go-guru-pointsto
                              go-guru-callstack go-guru-whicherrs go-guru-callers go-guru-callees
  :config (unless (executable-find "guru")
            (warn "go-mode: couldn't find guru, refactoring commands won't work")))
(use-package gorepl-mode
  :commands (gorepl-run gorepl-run-load-current-file)
  :config (unless (executable-find "gore")
            (warn "go-mode: couldn't find gore, REPL support disabled")))
(use-package company-go
  :init (setq command-go-gocode-command "gocode"
              company-go-show-annotation t)
  :after go-mode
  :config (if (executable-find command-go-gocode-command)
              (add-to-list 'company-backends #'company-go)
            (warn "go-mode: couldn't find gocode, code completion won't work")))
(use-package gotest
  :bind (:map go-mode-map
              ("C-c a" . go-test-current-project)
              ("C-c m" . go-test-current-file)
              ("C-c ." . go-test-current-test)
              ("C-c b" . go-run)
              ("C-h f" . godoc-at-point)))

Go Projectile is a set of Go language related add-ons for the Emacs Projectile mode. —

(use-package go-projectile
  :after projectile)


We use the “onetwo” style to fill docstrings in Python, i.e.:

"""Process foo, return bar."""

Process foo, return bar.

If processing fails throw ProcessingError.


I often restart Python processes (Cython development), so let’s bind a key for that.

(use-package python
  :mode (("\\.py\\'" . python-mode))
  :bind (:map python-mode-map
              ("C-c C-c" . elpy-shell-send-region-or-buffer)
              ("C-c C-r" . pyvenv-restart-python)
              ("<home>"  . malb/beginning-of-line-dwim)
              ("C-<tab>" . bicycle-cycle )
              ("<backtab>" . bicycle-cycle-global)
              :map inferior-python-mode-map
              ("C-c C-r" . pyvenv-restart-python))
  :hook (python-mode . outline-minor-mode)
  :config (progn
            (setq-default python-indent 4
                          python-fill-docstring-style 'django)

            (setq python-shell-interpreter "jupyter"
                  python-shell-interpreter-args "console --simple-prompt"
                  python-shell-prompt-detect-failure-warning nil)

            (defun malb/inferior-python-setup ()
              (make-local-variable 'company-backends)
              (setq company-backends '(company-files company-capf)))

            (add-hook 'inferior-python-mode-hook #'malb/inferior-python-setup)

            (add-to-list 'python-indent-trigger-commands 'malb/indent-fold-or-complete)
            (add-to-list 'python-shell-completion-native-disabled-interpreters "jupyter")))
(defun malb/make-ipython-shell (shell-name virtual-env &optional venv-vars post-exec per-project)
  `(lambda (arg)
     (interactive "P")
     (let ((project-venv-vars ,venv-vars))
       (venv-workon ,virtual-env))
     (let* ((name   (if (and (not arg) ,per-project)
                        (concat "*" ,shell-name
                                "[" (if (projectile-project-p)
                                      (car (last (split-string parent "/" t)))) "]" "*")
                      (concat "*" ,shell-name "*")))
            (height (/ (window-total-height) 3))
            (window (get-buffer-window name)))
       (if (and window (<= (window-height window) (/ (frame-height) 3)))
             (select-window window)
           (if (get-buffer name)
                 (split-window-vertically (- height))
                 (other-window 1)
                 (switch-to-buffer name)
                 (set-window-dedicated-p (get-buffer-window (current-buffer)) t))
               (let* ((buffer (python-shell-make-comint (python-shell-calculate-command)
                                                        (replace-regexp-in-string "\*" "" name))))
                 (display-buffer buffer '((display-buffer-reuse-window display-buffer-in-side-window) .
                                          ((reusable-frames . visible)
                                           (side            . bottom)
                                           (window-height   . 0.3))))
                 (select-window (get-buffer-window buffer))
                 (set-window-dedicated-p (get-buffer-window (current-buffer)) t)
                 (dolist (cmd ,post-exec)
                   (insert (concat cmd "\n")))

Highlight indentation

It makes sense to highlight indentation in Python.

(use-package highlight-indentation
  :diminish highlight-indentation-mode
  :config (progn (add-hook 'python-mode-hook #'highlight-indentation-mode)))


Use anaconda-mode for auto-completion and stuff, it runs jedi for us. In particular it offers:


Fall back to dumb jump if anaconda fails.

(use-package anaconda-mode
  :after smart-jump
  :diminish anaconda-mode
  :bind (:map anaconda-mode-map ("M-," . anaconda-mode-go-back))
  :config (progn

            (defun malb/local-anaconda-mode ()
              (when (and (buffer-file-name)
                         (not (file-remote-p (buffer-file-name))))

            (add-hook 'python-mode-hook #'malb/local-anaconda-mode)

            (defun malb/local-anaconda-eldoc-mode ()
              (when (and (buffer-file-name)
                         (not (file-remote-p (buffer-file-name))))

            (add-hook 'python-mode-hook #'malb/local-anaconda-eldoc-mode)

            (defun malb/vanilla-python-shell-interpreter (orig-fun &rest args)
              (let ((python-shell-interpreter "python")
                    (python-shell-interpreter-args "-i"))
                (apply orig-fun args)))

            (advice-add 'anaconda-mode-start :around #'malb/vanilla-python-shell-interpreter)))


(use-package helm-pydoc
  :bind (:map python-mode-map ("C-c C-d" . helm-pydoc)))


Python docstring mode

Python docstring mode provides syntax highlighting for docstrings in both ReStructuredText and Epydoc formats, as well as an override for the fill-paragraph function when editing such a docstring that will wrap things according to Python community convention.

Manually fixed bugs:

(use-package python-docstring
  :diminish python-docstring-mode
  :hook (python-mode . python-docstring-mode))


An emacs minor mode for inserting docstring skeleton for Python functions and methods (C-c M-d). The structure of the docstring is as per the requirement of the Sphinx documentation generator.

(use-package sphinx-doc
  :diminish sphinx-doc-mode
  :hook (python-mode . sphinx-doc-mode))


(use-package cython-mode
  :mode (("\\.pyx\\'"  . cython-mode)
         ("\\.spyx\\'" . cython-mode)
         ("\\.pxd\\'"  . cython-mode)
         ("\\.pxi\\'"  . cython-mode)))

Flycheck for Cython

(use-package flycheck-cython
  :after (cython-mode flycheck))


(use-package py-autopep8
  :config (setq py-autopep8-options '("--max-line-length=100")))



You need to install importmagic and epc

$ pip install importmagic epc
(use-package importmagic
  :diminish importmagic-mode
  :hook (python-mode-hook . importmagic-mode))


Elpy is an Emacs package to bring powerful Python editing to Emacs. It combines a number of other packages, both written in Emacs Lisp as well as Python.

Elpy is quite opinionated and we don’t necessarily share all those opinions. Hence, we only enable a small subset.

(use-package elpy
  :bind (:map python-mode-map ("C-c C-c" . elpy-shell-send-region-or-buffer))
  :commands (elpy-shell-send-region-or-buffer elpy-module-yasnippet elpy-module-sane-defaults)
  :config (progn
            (elpy-module-yasnippet 'global-init)
            (defun malb/elpy-sane-defaults ()
              (elpy-module-sane-defaults 'buffer-init))
            (add-hook 'python-mode-hook #'malb/elpy-sane-defaults)))


(use-package pip-requirements)

iPython Notebook (EIN)

EIN is a interface to iPython. (source)

On our system port 8888 is already taken.

(use-package ein
  :defer 30
  :after ob
  :commands (ein:notebooklist-open ein:notebooklist-login ein:query-ipython-version)
  :config (progn
            (setq ein:use-auto-complete t
                  ein:complete-on-dot nil
                  ein:notebook-create-checkpoint-on-save t
                  ein:completion-backend 'ein:use-company-backend
                  ein:url-or-port '(8890 8889)
                  ein:default-url-or-port 8890
                  ein:console-args '("--simple-prompt")
                  ein:console-security-dir "")

            (defun malb/ein-hook ()
              (toggle-truncate-lines t))

            (add-hook 'ein:notebook-multilang-mode-hook #' malb/ein-hook)

            (add-to-list 'org-babel-load-languages '(ein . t))

            (require 'ein-notebook)

            (defun malb/ein-open-here ()
              "We have a link to our projects top directory, this function goes to anything below that."
              (let ((url (replace-regexp-in-string malb/projects-dir "projects" default-directory)))
                (ein:notebooklist-open 8890 url)))

            (bind-key "S-<return>" #'ein:worksheet-execute-cell-and-goto-next ein:notebook-mode-map)
            (bind-key "C-<return>" #'ein:worksheet-execute-cell ein:notebook-mode-map)))

Note: To use EIN from org-mode, specify a notebook via the :session argument. The format for the session argument is {url-or-port}/{path-to-notbook}, e.g. 8889/Untitled.ipynb

(defun malb/fpylll-notebook ()
  (let ((fpylll (prodigy-find-service "fpylll")))
    (if (not (prodigy-service-started-p fpylll))
          (venv-deactivate) ;; avoid weird interactions
          (prodigy-start-service fpylll)
          (sleep-for 1.5))))
  (venv-workon "fpylll2")
  (ein:query-ipython-version 8889 t)
  (ein:notebooklist-login 8889 malb/sage-notebook-password)
  (ein:notebooklist-open 8889))

Virtual Environments

virtualenvwrapper emulates much of the functionality of It strikes me as the most feature complete of the many choices. I also added some features from pyvenv. Okay, so maybe it isn’t that feature complete.

(use-package virtualenvwrapper
  :config (progn
            (setq eshell-modify-global-environment nil)

            (setq malb/venv-preactivate-hook--previous-vars nil)

            (defun malb/venv-preactivate-hook ()
              (dolist (pair malb/venv-preactivate-hook--previous-vars)
                (setenv (car pair) nil))
              (setq malb/venv-preactivate-hook--previous-vars nil)
              (when (boundp 'project-venv-vars)
                (dolist (pair project-venv-vars)
                  (setenv (car pair) (cdr pair)))
                (setq malb/venv-preactivate-hook--previous-vars project-venv-vars)))

            (add-hook 'venv-preactivate-hook #'malb/venv-preactivate-hook)

            ;; some modes don't need to have a virtualenv
            (setq malb/venv-tracking-mode-excluded-modes
                  '(mu4e-headers-mode mu4e-view-mode mu4e-main-mode))

            ;; handling virtualenvs costs and we use post-command-hook, we use this variable to
            ;; check for buffer changes
            (setq malb/venv-tracking-mode--previous-buffer nil)

            (defun malb/track-venv ()
              "Enable virtualenv if local variable `project-venv-name' is set.

Otherwise disable virtualenv."
              ;; only trigger on buffer change
              (when (not (eq (current-buffer)
                ;; check for excluded modes
                (if (not (memq major-mode malb/venv-tracking-mode-excluded-modes))
                    ;; trigger
                    (if (boundp 'project-venv-name)
                        (venv-workon project-venv-name))
                (setq malb/venv-tracking-mode--previous-buffer (current-buffer))))

            (define-minor-mode malb/venv-tracking-mode
              "Global minor mode to track the current virtualenv.

When this mode is active, venv will activate a buffer-specific
virtualenv whenever the user switches to a buffer with a
buffer-local `project-venv-name' variable."
              :global t
              (if malb/venv-tracking-mode
                  (add-hook 'post-command-hook #'malb/track-venv)
                (remove-hook 'post-command-hook #'malb/track-venv)))

            (malb/venv-tracking-mode t) ; enable

            (defun malb/venv-workon-buffer (arg)
              "Enable virtualenv for current buffer.

 When `venv-tracking-mode' is on, `venv-workon' has no effect as
 it is immediately overruled. This function sets
 `project-venv-name' and thus circumvents that issue."
              (interactive "P")
              (cond (arg (progn
                           (set (make-local-variable 'project-venv-name) nil)
                       (set (make-local-variable 'project-venv-name)
                            (venv-read-name (if venv-current-name
                                                (format "Choose a virtualenv (currently %s): "
                                              "Choose a virtualenv: ")))
                       (venv-workon project-venv-name)))))

            ;; virtualenv in spaceline, enabled below
            (eval-after-load "spaceline"
                 (spaceline-define-segment python-venv
                   "The current python venv.  Works with `venv'."
                   (when (and active
                              (bound-and-true-p venv-current-name))
                     (propertize venv-current-name
                                 'face 'spaceline-python-venv
                                 'help-echo (format "Virtual environment (via venv): %s"


Enable sage-shell-mode for running Sage from within Emacs. It’s available on MELPA and hence easier to keep around when we switch Sage installs all the time. To edit a file in sage-shell-mode put # -*- mode: sage-shell:sage -*- on top.

(use-package auto-complete
  :config (progn
            (setq ac-delay 0.3
                  ac-auto-start 2)
            (bind-key "C-/" #'auto-complete ac-mode-map)
            (bind-key "M-/" #'auto-complete ac-mode-map)))

(use-package auto-complete-sage)

(use-package sage-shell-mode
  :config (progn
            (eval-after-load "auto-complete"
              '(setq ac-modes (append '(sage-shell-mode sage-shell:sage-mode) ac-modes)))
            (add-hook 'sage-shell:sage-mode-hook #'ac-sage-setup)
            (add-hook 'sage-shell:sage-mode-hook #'auto-complete-mode)
            (add-hook 'sage-shell:sage-mode-hook #'eldoc-mode)
            (add-hook 'sage-shell-mode-hook #'ac-sage-setup)
            (add-hook 'sage-shell-mode-hook #'auto-complete-mode)
            (add-hook 'sage-shell-mode-hook #'eldoc-mode)
            (setq sage-shell:use-prompt-toolkit t)

            ;; at this point the list starts with not, i.e. is negated
            (when (boundp 'company-global-modes)
              (add-to-list 'company-global-modes 'sage-shell:sage-mode t)
              (add-to-list 'company-global-modes 'sage-shell-mode t))

            (add-hook 'sage-shell-after-prompt-hook #'sage-shell-view-mode)
            (setq sage-shell-view-default-commands 'plots
                  sage-shell-view-scale 1.5)

            (setq sage-shell:input-history-cache-file (concat user-emacs-directory "sage_shell_input_history")
                  sage-shell:sage-executable malb/sage-executable
                  ac-sage-show-quick-help t)

            (fset 'malb/toggle-sage
                  (malb/make-toggle-shell "Sage"
                                          '(progn (venv-deactivate) (sage-shell:run-sage "sage"))

            (advice-add 'sage-shell:send-eof :around  #'malb/kill-buffer)

            (bind-key "C-<up>" #'comint-previous-matching-input-from-input sage-shell-mode-map)
            (bind-key "C-<down>" #'comint-next-matching-input-from-input sage-shell-mode-map)
            (bind-key "M-p" #'comint-previous-matching-input-from-input sage-shell-mode-map)
            (bind-key "M-n" #'comint-next-matching-input-from-input sage-shell-mode-map)
            (bind-key "C-<return>" #'auto-complete sage-shell:sage-mode-map)))

However, I usually don’t do that but use python-mode directly by hacking together an activate script for a sage-devel virtualenv

# This file must be used with "source bin/activate" *from bash*
# you cannot run it directly

deactivate () {
    unset -f pydoc >/dev/null 2>&1

    # reset old environment variables
    # ! [ -z ${VAR+_} ] returns true if VAR is declared at all
    if ! [ -z "${_OLD_VIRTUAL_PATH+_}" ] ; then
        export PATH
        unset _OLD_VIRTUAL_PATH
    if ! [ -z "${_OLD_VIRTUAL_PYTHONHOME+_}" ] ; then
        export PYTHONHOME

    # This should detect bash and zsh, which have a hash command that must
    # be called to get it to forget past commands.  Without forgetting
    # past commands the $PATH changes we made may not be respected
    if [ -n "${BASH-}" ] || [ -n "${ZSH_VERSION-}" ] ; then
        hash -r 2>/dev/null

    if ! [ -z "${_OLD_VIRTUAL_PS1+_}" ] ; then
        export PS1
        unset _OLD_VIRTUAL_PS1

    if ! [ -z ${_OLD_LD_LIBRARY_PATH+x} ] ; then
        export LD_LIBRARY_PATH
        unset _OLD_LD_LIBRARY_PATH

    if ! [ -z ${_OLD_PKG_CONFIG_PATH+x} ] ; then
        export PKG_CONFIG_PATH
        unset _OLD_PKG_CONFIG_PATH

    unset VIRTUAL_ENV
    if [ ! "${1-}" = "nondestructive" ] ; then
    # Self destruct!
        unset -f deactivate

# unset irrelevant variables
deactivate nondestructive


export PATH

export SAGE_ROOT

# unset PYTHONHOME if set
if ! [ -z "${PYTHONHOME+_}" ] ; then
    unset PYTHONHOME

if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT-}" ] ; then
    if [ "x" != x ] ; then
        PS1="(sage-devel) $PS1"
    export PS1

# Make sure to unalias pydoc if it's already there
alias pydoc 2>/dev/null >/dev/null && unalias pydoc



pydoc () {
    python -m pydoc "$@"

# This should detect bash and zsh, which have a hash command that must
# be called to get it to forget past commands.  Without forgetting
# past commands the $PATH changes we made may not be respected
if [ -n "${BASH-}" ] || [ -n "${ZSH_VERSION-}" ] ; then
    hash -r 2>/dev/null

Shortcut for Sage Jupyter notebook.

(defun malb/sage-notebook ()
  (let ((sage (prodigy-find-service "sage-devel")))
    (if (not (prodigy-service-started-p sage))
          (venv-deactivate) ;; avoid weird interactions
          (prodigy-start-service sage)
          (sleep-for 1.5))))
  (venv-workon "sage-devel")
  (ein:query-ipython-version 8890 t)
  (ein:notebooklist-login 8890 malb/sage-notebook-password)
  (ein:notebooklist-open 8890))

Note: If you get [warn] Null value passed to ein:get-ipython-major-version try (ein:query-ipython-version 8890 t)

org babel for Sage can be quite handy for writing talks etc. (source)

(use-package ob-sagemath
  :config (progn
            (setq org-babel-default-header-args:sage '((:session . t)
                                                       (:results . "output replace")
                                                       (:exports . "both")
                                                       (:eval . "never-export")
                                                       (:cache .   "no")
                                                       (:noweb . "no")
                                                       (:hlines . "no")
                                                       (:tangle . "no")))
            (add-to-list 'org-structure-template-alist
                         '("sg" "#+BEGIN_SRC sage\n?\n#+END_SRC"))))



short and sweet LISP editing (source)

Basic navigation by-list and by-region:

hmoves left
jmoves down
kmoves up
lmoves right
fsteps inside the list
bmoves back in history for all above commands
dmoves to different side of sexp

Paredit transformations, callable by plain letters:

smoves down
wmoves up

IDE-like features for Elisp, Clojure, Scheme, Common Lisp, Hy, Python and Julia:

Eevals and inserts
gjumps to any tag in the current directory with semantic
Gjumps to any tag in the current file
M-.jumps to symbol, M-, jumps back
Fjumps to symbol, D jumps back
C-1shows documentation in an overlay
C-2shows arguments in an overlay
Zbreaks out of edebug, while storing current function’s arguments

Code manipulation:

iprettifies code (remove extra space, hanging parens …)
xitransforms cond expression to equivalent if expressions
xctransforms if expressions to an equivalent cond expression
xfflattens function or macro call (extract body and substitute arguments)
xrevals and replaces
xlturns current defun into a lambda
xdturns current lambda into a defun
Oformats the code into one line
Mformats the code into multiple lines
(use-package lispy)

(use-package easy-escape
  :hook ((emacs-lisp-mode lisp-mode) . easy-escape-minor-mode))

Emacs Lisp

(use-package elisp-mode
  :ensure nil
  :bind (:map emacs-lisp-mode-map
              ("C-c C-z" . ielm)
              ("C-c C-c" . eval-defun)
              ("C-c C-b" . eval-buffer)))
(use-package elisp-slime-nav
  :diminish elisp-slime-nav-mode
  :config (progn

            (defun malb/elisp-hook ()
              (lispy-mode 1))

            (setq eldoc-idle-delay 0.3)

            (add-hook 'emacs-lisp-mode-hook #'malb/elisp-hook)
            (add-hook 'lisp-interaction-mode-hook #'malb/elisp-hook)
            (add-hook 'ielm-mode-hook #'elisp-slime-nav-mode)
            (add-hook 'ielm-mode-hook #'smartparens-strict-mode)
            (add-hook 'ielm-mode-hook #'eldoc-mode)

            (bind-key "M-?" #'elisp-slime-nav-describe-elisp-thing-at-point

Eldoc in minibuffer eval.

(add-hook 'eval-expression-minibuffer-setup-hook #'eldoc-mode)

suggest.el is an Emacs package for discovering elisp functions based on examples.

(use-package suggest)
(add-hook 'emacs-lisp-mode-hook #'hs-minor-mode)


(use-package hy-mode
  :mode "\\.hy\\'"
  :hook (hy-mode . lispy-mode))


(use-package web-mode
  :mode "\\\.html?\\\'"
  :after pandoc
  :config (progn
            (setq web-mode-enable-engine-detection t)
            (add-to-list 'pandoc-major-modes '(web-mode . "html"))))


(use-package json-mode)



Tab Completion

Use less tab completion in prose. (souce)

(defun malb/config-prose-completion ()
  "Make auto-complete less agressive in this buffer."
  (setq-local company-minimum-prefix-length 3)
  (setq-local company-idle-delay 0.5))

(add-hook 'text-mode-hook #'malb/config-prose-completion)

Line Wrapping

Put everything back on one line if M-q is pressed twice and use visual-line-mode to do the work of wrapping text for us. (source)

(add-hook 'text-mode-hook #'turn-on-visual-line-mode)
(diminish 'visual-line-mode)

(defun malb/fill-or-unfill ()
  "Like `fill-paragraph', but unfill if used twice."
  (let ((fill-column
         (if (eq last-command 'malb/fill-or-unfill)
             (progn (setq this-command nil)
    (call-interactively #'fill-paragraph)))

(defun malb/org-fill-or-unfill ()
  "Like `org-fill-paragraph', but unfill if used twice."
  (let ((fill-column
         (if (eq last-command 'malb/org-fill-or-unfill)
             (progn (setq this-command nil)
    (call-interactively #'org-fill-paragraph)))

(bind-key [remap fill-paragraph] #'malb/fill-or-unfill)
(bind-key "M-q" #'malb/org-fill-or-unfill org-mode-map)

Indent correctly in visual-line-mode (org-mode has its own thing).

(use-package adaptive-wrap
  :config (progn
            (add-hook 'markdown-mode-hook #'adaptive-wrap-prefix-mode)
            (add-hook 'LaTeX-mode-hook #'adaptive-wrap-prefix-mode)))

Allow for a bit more characters per line by default.

(setq-default fill-column 100)

For some modes we want resp. less wrapping width.

(defun malb/fill-column-32768 ()
  (set-fill-column 32768))

(defun malb/fill-column-72 ()
  (set-fill-column 72))

Indicate soft wrapped lines.

(setq visual-line-fringe-indicators '(nil right-curly-arrow))


typo is a minor mode that will change a number of normal keys to make them insert typographically useful unicode characters. Some of those keys can be used repeatedly to cycle through variations. This includes in particular quotation marks and dashes.

(use-package typo
  :diminish typo-mode
  :config (progn
            (setq-default typo-language "English")
            (add-hook 'org-mode-hook #'typo-mode)))

Visual Fill Column Mode

soft wrap at fill-column


(use-package visual-fill-column
  :defer t
  :config (progn
            (setq-default visual-fill-column-center-text t
                          visual-fill-column-fringes-outside-margins nil)

            (dolist (fn '(text-scale-increase text-scale-decrease text-scale-adjust))
              (advice-add fn :after #'visual-fill-column-adjust))))


(setq sentence-end-double-space nil)
(bind-key "C-x C-t" #'transpose-sentences)
(advice-add 'kill-sentence :after #'fixup-whitespace)

Highlighting Sentences & Paragraphs

Use hl-sentence-mode in markdown-mode.

(use-package hl-sentence
  :config (add-hook 'markdown-mode-hook #'hl-sentence-mode))

Also use focus-mode occationally. (source)

(use-package focus)

Spell Checking

  • Replace with ​'​ before sending it to ispell (source) Note: Below, we adapt “en_GB” (our default) instead of nil (the global default) because otherwise we can’t change dictionaries.
  • Enable/disable company completion from ispell dictionaries (source)
(use-package ispell
  :hook ((message-mode) . malb/company-ispell-setup)
  :config (progn
            (setq ispell-dictionary "en_GB"
                  ispell-silently-savep t)

            ;; Tell ispell.el that ’ can be part of a word. ;
            (setq ispell-local-dictionary-alist
                  '(("en_GB" "[[:alpha:]]" "[^[:alpha:]]"
                     "['\x2019]" nil ("-d" "en_GB") nil utf-8)))

            ;; Don't send ’ to the subprocess.
            (defun malb/replace-apostrophe (args)
              (cons (replace-regexp-in-string
                     "" "'" (car args))
                    (cdr args)))

            (advice-add #'ispell-send-string :filter-args #'malb/replace-apostrophe)

            ;; Convert ' back to ’ from the subprocess.
            (defun malb/replace-quote (args)
              (if (not (or (derived-mode-p 'org-mode)
                           (derived-mode-p 'markdown-mode)
                           (derived-mode-p 'rst-mode)
                           (derived-mode-p 'message-mode)))
                (cons (replace-regexp-in-string
                       "'" "" (car args))
                      (cdr args))))

            (defun malb/company-ispell-setup ()
              (when (boundp 'company-backends)
                (make-local-variable 'company-backends)
                (setq company-backends (delete 'company-dabbrev company-backends))
                (add-to-list 'company-backends '(company-ispell :with company-yasnippet) t)
                (if (and (boundp 'ispell-alternate-dictionary)
                    (setq company-ispell-dictionary ispell-alternate-dictionary))))

            (advice-add #'ispell-parse-output :filter-args #'malb/replace-quote)))

Diminish flyspell-mode as we always use it.

(use-package flyspell

Use ace-flyspell for fixing typos. I find myself pressing C-. in other programs these days just to be frustrated that it doesn’t just work™

(use-package ace-flyspell
  :bind (("C-." . ace-flyspell-correct-word)
         :map flyspell-mode-map
         ("C-." . malb/ace-flyspell-dwim))
  :after flyspell
  :config (progn
            (setq ace-flyspell-new-word-no-query t)

            (defun malb/ace-flyspell-dwim (arg)
              "Run `ace-flyspell-dim` unless prefix argument is given. In that case offer a word to insert."
              (interactive "P")
              (if arg

            (unbind-key "C-;" flyspell-mode-map)))

Automatically pick dictionary.

(use-package auto-dictionary
  :config (progn
            (setf (cdr (rassoc "en" adict-dictionary-list)) "en_GB")))
(use-package guess-language
  :commands (guess-language-buffer)
  :hook (message-mode . guess-language-mode)
  :config (setq guess-language-langcodes '((de . ("de_DE" "German"))
                                           (en . ("en_GB" "English")))
                guess-language-languages '(de en)
                guess-language-min-paragraph-length 35))

Grammar Checking

Langtool (source)

(use-package langtool
  :config (progn
            (setq langtool-language-tool-jar (expand-file-name "languagetool-commandline.jar"
                                                               (concat user-emacs-directory "langtool"))
                  langtool-default-language "en"
                  langtool-disabled-rules '("WHITESPACE_RULE"

            (bind-key "C-x 4 w" #'langtool-check)
            (bind-key "C-x 4 W" #'langtool-check-done)
            (bind-key "C-x 4 l" #'langtool-switch-default-language)
            (bind-key "C-x 4 4" #'langtool-show-message-at-point)
            (bind-key "C-x 4 c" #'langtool-correct-buffer)))

After the deadline (source)

(use-package scimax-email
  :ensure nil)

(defun malb/words-atd ()
  "Send paragraph at point to After the deadline for spell and grammar checking."

  (let* ((url-request-method "POST")
         (url-request-data (format
                             (thing-at-point 'paragraph))))
         (xml  (with-current-buffer
                 (xml-parse-region url-http-end-of-headers (point-max))))
         (results (car xml))
         (errors (xml-get-children results 'error)))

    (switch-to-buffer-other-window "*ATD*")
    (dolist (err errors)
      (let* ((children (xml-node-children err))
             ;; for some reason I could not get the string out, and had to do this.
             (s (car (last (nth 1 children))))
             ;; the last/car stuff doesn't seem right. there is probably
             ;; a more idiomatic way to get this
             (desc (last (car (xml-get-children children 'description))))
             (type (last (car (xml-get-children children 'type))))
             (suggestions (xml-get-children children 'suggestions))
             (options (xml-get-children (xml-node-name suggestions) 'option))
             (opt-string  (mapconcat
                           (lambda (el)
                             (when (listp el)
                               (car (last el))))
                           " ")))

        (insert (format "- %s :: %s %s %s
" s opt-string desc type))))))

Style Checking

Prose linting using … proselint.


(flycheck-define-checker proselint
  "A linter for prose."
  :command ("proselint" source-inplace)
  ((warning line-start (file-name) ":" line ":" column ": "
            (id (one-or-more (not (any " "))))
            (message) line-end))
  :modes (text-mode markdown-mode gfm-mode org-mode message-mode))

(add-to-list 'flycheck-checkers 'proselint)



(use-package synosaurus
  :bind (("C-c w s" . synosaurus-lookup)
         ("C-c w r" . synosaurus-choose-and-replace))
  :config (progn
            (setq synosaurus-choose-method 'popup)
            (unbind-key "C-c s l" synosaurus-mode-map)
            (unbind-key "C-c s r" synosaurus-mode-map)
            (add-hook 'org-mode-hook #'synosaurus-mode)
            (add-hook 'latex-mode-hook #'synosaurus-mode)))


(use-package powerthesaurus
  :bind ("C-c w p" . powerthesaurus-lookup-word))


Use define-word to get a quick reference on a word.

(use-package define-word
  :bind (("C-c w d" . define-word-at-point)))



(use-package google-translate
  :bind ("C-c w t" . google-translate-smooth-translate)
  :init (progn (setq google-translate-translation-directions-alist
                     '(("de" . "en") ("en" . "de")))))

Taking Notes

Use deft for random notes and for a listing of blog entries. (source)

(use-package deft
  :bind ("<f8>" . malb/deft)
  :bind ("S-<f8>" . malb/blog)
  :init (progn
          (setq deft-extensions '("org" "md")
                deft-default-extension "org"
                deft-directory malb/deft-directory
                deft-text-mode 'org-mode
                deft-use-filename-as-title nil
                deft-auto-save-interval 300.0
                deft-use-filter-string-for-filename t
                deft-current-sort-method 'title
                deft-file-naming-rules '((noslash . "-")
                                         (nospace . "-")
                                         (case-fn . downcase)))

          (defun malb/deft-in-dir (dir)
            "Run deft in directory DIR"
            (setq deft-directory dir)
            (switch-to-buffer "*Deft*")

          (defun malb/blog ()
            (malb/deft-in-dir (expand-file-name "web/blog" malb/projects-dir)))

          (defun malb/deft ()
            (malb/deft-in-dir malb/deft-directory))

          (add-hook 'deft-mode-hook #'hl-line-mode)))


Standard setup and quick preview (source)

(use-package markdown-mode
  :mode (("\\.md\\'" . markdown-mode)
         ("\\.markdown\\'" . markdown-mode)
         ("\\.text\\'" . markdown-mode)
         ("README\\.md\\'" . gfm-mode))
  :config (progn
            (defvar malb/markdown.css
              (expand-file-name "themes/foghorn.css" user-emacs-directory))

            (setq markdown-command (concat
                                    "pandoc --highlight-style pygments --smart -s -f markdown -t html -c"
                  markdown-css-paths (list malb/markdown.css)
                  markdown-enable-math t
                  markdown-fontify-code-blocks-natively t)

            (defun malb/markdown-preview-buffer ()
              (require 'shr)
              (let* ((buf-this (buffer-name (current-buffer)))
                     (buf-html (get-buffer-create
                                (format "*md-html (%s)*" buf-this))))
                (markdown (buffer-name buf-html))
                (shr-render-buffer buf-html)
                (kill-buffer buf-html)))

            (add-hook 'markdown-mode-hook #'flyspell-mode)
            (add-hook 'markdown-mode-hook #'outline-minor-mode)

            (bind-key "M-." #'markdown-do markdown-mode-map)
            (bind-key "C-c C-e o" #'malb/markdown-preview-buffer markdown-mode-map) ;; inspired by org-mode-export
            (bind-key "C-<tab>" #'outline-cycle markdown-mode-map)))


Use pandoc-mode to call pandoc for converting markdown to everything else.

(use-package pandoc-mode
  :delight '(:eval (concat " [" (pandoc--get 'write) "]"))
  :config (progn
            (add-hook 'markdown-mode-hook #'conditionally-turn-on-pandoc)
            (add-hook 'org-mode-hook #'conditionally-turn-on-pandoc)
            (add-hook 'pandoc-mode-hook #'pandoc-load-default-settings)

            (defun malb/pandoc-convert (writer &optional buffer beginning end reader)
              (let* ((buffer (or buffer (current-buffer)))
                     (pandoc-buffer (get-buffer-create pandoc--output-buffer-name))
                     (begginning (or beginning (point-min)))
                     (end (or end (point-max)))
                     (reader (or reader (cdr (assq major-mode pandoc-major-modes))))
                (switch-to-buffer pandoc-buffer)
                (switch-to-buffer buffer)
                (call-process-region beginning end "pandoc" nil pandoc-buffer t
                (switch-to-buffer pandoc-buffer)
                (setq text (buffer-string))

            (defun malb/copy-as (arg)
              (interactive "P")
                 (completing-read "Output format to use: " (mapcar 'car pandoc-output-format-extensions) nil t)))
               ((derived-mode-p 'org-mode)
                (malb/-copy-as "org"))))

            (defun malb/copy-as-latex-from-org ()
              (if (org-region-active-p)
                  (kill-new (org-export-string-as (buffer-substring (region-beginning) (region-end)) 'latex t))
                (kill-new (org-export-string-as (buffer-substring (point-min) (point-max)) 'latex t))))

            (defun malb/copy-as-latex ()
              (malb/-copy-as "latex"))

            (defun malb/copy-as-org ()
              (malb/-copy-as "org"))

            (defun malb/-copy-as (what)
              (kill-new (malb/pandoc-convert what
                                             (if (use-region-p) (region-beginning) (point-min))
                                             (if (use-region-p) (region-end) (point-max))))

            (bind-key "M-W" #'malb/copy-as)))


Python’s distutils mandate README.txt or README in ReST. Hence, we add README.txt as the kind of file which wants ReST and use rst-mode to edit it.

(use-package rst
  :commands rst-mode-hook
  :mode "README\\.txt")


C-c C-gforward search
C-c @outline minor mode
C-c ?symbol documentation
(use-package tex
  :commands LaTeX-mode-hook
  :ensure auctex
  :defer t
  :bind (:map LaTeX-mode-map
              ("C-<tab>" . outline-cycle)
              ("M-." . malb/latex-jump))
  :config (progn
            (add-hook 'LaTeX-mode-hook #'visual-line-mode)
            (add-hook 'LaTeX-mode-hook #'flyspell-mode)
            (add-hook 'LaTeX-mode-hook #'LaTeX-math-mode)
            (add-hook 'LaTeX-mode-hook #'turn-on-reftex)
            (add-hook 'LaTeX-mode-hook #'visual-fill-column-mode)
            (add-hook 'LaTeX-mode-hook #'LaTeX-math-mode)

            (setq TeX-view-program-list '(("Okular" "okular --unique %o#src:%n%b")
                                          ("Emacs" "emacsclient -n -e '(find-file-other-window \"%o\")'")))

            (setq TeX-view-program-selection '(((output-dvi style-pstricks) "dvips and gv")
                                               (output-dvi "Okular")
                                               (output-pdf "Emacs")
                                               (output-html "xdg-open")))

            (add-hook 'TeX-after-compilation-finished-functions #'TeX-revert-document-buffer)

            (defun malb/latex-add-environments ()
               '("lemma" LaTeX-env-label)
               '("theorem" LaTeX-env-label)
               '("algorithm" LaTeX-env-label)
               '("corollary" LaTeX-env-label)
               '("definition" LaTeX-env-label)
               '("example" LaTeX-env-label)
               '("proposition" LaTeX-env-label)))

            (setq reftex-section-levels '(("part" . 0)
                                          ("chapter" . 1)
                                          ("section" . 2)
                                          ("subsection" . 3)
                                          ("subsubsection" . 4)
                                          ("frametitle" . 5)
                                          ("paragraph" . 6)
                                          ("subparagraph" . 7)
                                          ("addchap" . -1)
                                          ("addsec" . -2)))

            (add-hook 'LaTeX-mode-hook #'malb/latex-add-environments)

            (add-hook 'LaTeX-mode-hook #'outline-minor-mode)

            (defun malb/latex-jump ()
              "If point is on a citation, jump to the bibtex file, otherwise open refex menu."
              (let ((current (point)))
                (ignore-errors (org-ref-latex-jump-to-bibtex))
                (if (eq current (point))

            (defface malb/unimportant-latex-face
              '((t :height 0.8
                   :inherit font-lock-comment-face))
              "Face used on less relevant math commands.")

             `((,(rx "\\" (or (any ",.!;")
                              (and (or "left" "right" "qquad" "quad")
                0 'malb/unimportant-latex-face prepend)) 'end)

            (setq TeX-auto-save t
                  TeX-parse-self t
                  reftex-plug-into-AUCTeX t
                  LaTeX-math-menu-unicode t
                  TeX-PDF-mode t
                  TeX-source-correlate-mode t
                  TeX-save-query nil
                  TeX-error-overview-open-after-TeX-run t
                  TeX-electric-sub-and-superscript t
                  TeX-electric-math '("\\(" . "\\)")
                  ;; LaTeX-electric-left-right-brace t
                  TeX-quote-after-quote nil)

            (defun malb/latex-replace-in-math ()
              "Call `query-replace-regexp' with `isearch-filter-predicate' set to filter out matches outside LaTeX math environments."
              (let ((isearch-filter-predicate
                     (lambda (BEG END)
                       (save-excursion (save-match-data (goto-char BEG) (texmathp)))))
                    (case-fold-search nil))
                (call-interactively 'query-replace-regexp)))

            (setq-default TeX-master 'dwim)
            (setq-default TeX-auto-local (expand-file-name "auctex-auto" user-emacs-directory))

            (setq font-latex-fontify-sectioning 1.0)

            (add-to-list 'LaTeX-verbatim-environments "lstlisting")))

Better C-c C-e


(defun first-nsp-after (p)
  "Go up to first non-whitespace character *after* the given position."
  (goto-char p)
  (skip-chars-forward " \n\r"))
(defun first-nsp-before (p)
  "Go to just after the first non-whitespace char *before* the given position."
  (goto-char p)
  (skip-chars-backward " \n\r"))
(defun latex/remove-math-delimiters (beg end)
  "Remove delimiters $ ... $, \[ ... \] and/or \( \) in an active
region (if called interactively, otherwise provide a region."
  (interactive "r")
      (narrow-to-region beg end)
      (first-nsp-after (point-min))
      (cond ((equal (char-after) (string-to-char "$")) (delete-char 1))
            ((looking-at-p "\\\\\\[") (delete-char 2))
            ((looking-at-p "\\\\(") (delete-char 2)))

      (first-nsp-before (point-max))
      ;; if the last char is a comma or full stop try skipping it and clearing $
      ;; and \] before it
      ;; This is useful when the formula that was inlined and needs to be displayed
      ;; was at the end of the sentence or clause.
      (when (member (char-to-string (char-before)) '("." ","))
      (cond ((equal (char-before) (string-to-char "$")) (delete-char -1))
            ((looking-back "\\\\\\]" nil) (delete-char -2))
            ((looking-back "\\\\)" nil) (delete-char -2))))))
(advice-add 'LaTeX-environment-menu :before
            (lambda (env)
              (when (and (use-region-p)
                         (or (member env (list "align"
                (let ((deactivate-mark nil)) ;; in order to prevent deactivation
                                             ;; of transient-mark-mode. Relies
                                             ;; essentially on the fact that
                                             ;; `deactivate-mark' is dynamically
                                             ;; bound.
                  (latex/remove-math-delimiters (region-beginning) (region-end))))))

Yanking in Math Environments


(require 'dash)

(defconst math-display-delims
  '(("\\begin{align}" "\\end{align}")
    ("\\begin{align\*}" "\\end{align\*}")
    ("\\begin{displaymath}" "\\end{displaymath}")
    ("\\begin{mathpar}" "\\end{mathpar}")
    ("\\begin{equation}" "\\end{equation}")
    ("\\begin{equation*}" "\\end{equation*}")
    ("\\[" "\\]"))
  "Delimiters of math mode, except $.")
(defun latex-in-math-mode ()
  "Check if we are currently in math mode. Uses
`math-display-delims' variable and relies on auctex to fontify
the math mode correctly."
  (let ((cur (point)))
    (if (equal (char-after cur) 92) ;; if the character we are looking at is \ then we
        ;; might be looking at \end{...}. In this case we
        ;; must check if the previous character is in
        ;; font-latex-math-face.
        (latex/check-if-math-face (get-text-property (max (- cur 1) (point-min)) 'face))
      ;; Otherwise we check as follows. The character we are looking at must be
      ;; in font-latex-math-face. But if we are looking at the opening $ then we must also
      ;; check the previous character, since $ is already in
      ;; font-latex-math-face.
      (and (latex/check-if-math-face (get-text-property cur 'face))
           (if (equal (char-after cur) 36) ;; if the char we are looking at is $
               (latex/check-if-math-face (get-text-property (max (- cur 1) (point-min)) 'face))
(defun latex/check-if-math-face (fp)
  "Check if `font-latex-math-face' is a face in `fp'."
   ((symbolp fp) (equal fp 'font-latex-math-face))
   ((listp fp) (member 'font-latex-math-face fp))))
(defun latex/start-of-math-environment ()
  "Returns a `nil' if we are not looking at the start of a math
environment. Otherwise it returns the length of the start delimiter,
e.g., 1 if $."
  (if (equal (char-after) 36) (list 1) ;; check if we are looking at $ first
    (-keep #'(lambda (pair)
               (when (looking-at-p (regexp-quote (first pair))) (length (first pair))))
(defun latex/end-of-math-environment ()
  "Returns a `nil' if we are not looking at the end of a math
environment. Otherwise it returns the length of the end delimiter,
e.g., 1 if $."
  (if (equal (char-before) 36) (list 1) ;; check if we are just after $ first
    (-keep #'(lambda (pair)
               (save-excursion (ignore-errors ;; backward-char will signal an error if we try to go back too far
                                 (backward-char (length (second pair)))
                                 (when (looking-at-p (regexp-quote (second pair)))
                                   (length (second pair))))))
(defun latex/remove-math-delims (str)
  "Remove math delimiters at the beginning and end of the given string.
There can be whitespace at the beginning and at the end of the
string. If it is, it is left there."
    (insert str)
    (first-nsp-after (point-min))
    (let ((x (latex/start-of-math-environment)))
      (when x
        (delete-char (first x))
        ;; remove the newlines as well (in case there is a newline). This
        ;; works better when removing \begin{...}, since otherwise there is
        ;; redundant space left.
    (first-nsp-before (point-max))
    (let ((x (latex/end-of-math-environment)))
      (when x
        (delete-char (- (first x)))
(defun remove-newline-forward ()
  "This is technically incorrect, but correct in practice."
  (while (member (char-after) (list 10 13)) ;; 10 is \n, 13 is \r
    (delete-char 1)))
(defun remove-newline-backward ()
  "This is technically incorrect, but correct in practice."
  (while (member (char-before) (list 10 13)) ;; 10 is \n, 13 is \r
    (delete-char -1)))
(defun insert-for-yank/remove-math-delims (phi str)
  (funcall phi (if (and (equal major-mode 'latex-mode)
                   (latex/remove-math-delims str)
(advice-add 'insert-for-yank :around 'insert-for-yank/remove-math-delims)

;; to disable the above advice
;; (advice-remove 'insert-for-yank 'insert-for-yank/remove-math-delims)


(use-package auctex-latexmk
  :config (progn
            (setq auctex-latexmk-inherit-TeX-PDF-mode t)

Run latexmk -pvc in async shell

use aux-dir & output-dir in combination with custom rc file to cp output to base dir
(defun malb/run-latexmk (arg)
  (interactive "p")
  (let*  ((engine (if arg "-xelatex" ""))
          (filename (file-name-sans-extension (buffer-file-name)))
          (output-buffer (format "*LaTeXMK %s*" (if (projectile-project-p)
                                                  (file-name-nondirectory (buffer-file-name))))))
    (async-shell-command (format "latexmk -pvc -pdf \"%s\" -interaction=nonstopmode \"%s.tex\""
                                 engine filename)

Utility Functions


(defun malb/align-& (start end)
  "Align columns by ampersand"
  (interactive "r")
  (align-regexp start end
                "\\(\\s-*\\)&" 1 1 t))

(defun malb/align-whitespace (start end)
  "Align columns by whitespace"
  (interactive "r")
  (align-regexp start end
                "\\(\\s-*\\)\\s-" 1 0 t))

(defun malb/align-= (start end)
  "Align columns by equals sign"
  (interactive "r")
  (align-regexp start end
                "\\(\\s-*\\)=" 1 0 t))

(defun malb/align-: (start end)
  "Align columns by equals sign"
  (interactive "r")
  (align-regexp start end
                "\\(\\s-*\\):" 1 0 t))


My standard BibTeX sources are

  • crypto_crossref.bib and abbrev3.bib are from crypto.bib which has most references relevant to crypto,
  • jacm.bib is for the Journal of the ACM provided by the University of Utah,
  • dcc.bib is for Designs, Codes, and Cryptography provided by the University of Utah,
  • rfc.bib is for RFCs and provided by Roland Bless.

These are stored in some common-latex folder which has my paper-template as a subfolder.

(defvar malb/common-latex (concat (file-name-as-directory malb/projects-dir) "common-latex"))

(defvar malb/crypto-bib (concat (file-name-as-directory
                                  (file-name-as-directory malb/common-latex) "paper-template")) "cryptobib"))

(defvar malb/bibtex-files (list (expand-file-name "crypto_crossref.bib" malb/crypto-bib)
                                (expand-file-name "abbrev3.bib" malb/crypto-bib)
                                (expand-file-name "rfc.bib" malb/common-latex)
                                (expand-file-name "jacm.bib" malb/common-latex)
                                (expand-file-name "dcc.bib" malb/common-latex)
                                (expand-file-name "local.bib" malb/common-latex)))

(setq reftex-default-bibliography malb/bibtex-files)

We cite as “Alice15” or “AliBobCha15” or “ABCD15” (source) and clean up BibTeX entries as we like them on C-c C-c.

(use-package bibtex
  :config (progn
            (setq bibtex-align-at-equal-sign t
                  bibtex-comma-after-last-field t
                  bibtex-entry-format '(opts-or-alts

            ;; “no” wrap in BibTeX
            (add-hook 'bibtex-mode-hook #'malb/fill-column-32768)

            (defun bibtex-generate-autokey ()
              (let* ((bibtex-autokey-names nil)
                     (bibtex-autokey-year-length 2)
                     (bibtex-autokey-name-separator "\0")
                     (names (split-string (bibtex-autokey-get-names) "\0"))
                     (year (bibtex-autokey-get-year))
                     (name-char (cond ((= (length names) 1)
                                       (length (car names)))
                                      ((<= (length names) 3)
                                      (t 1)))
                     (existing-keys (bibtex-parse-keys))
                (setq names (mapcar 'capitalize names))
                (setq names (mapconcat (lambda (x)
                                         (substring x 0 (min (length x) name-char)))
                (setq key (format "%s%s" names year))
                (let ((ret key))
                  (loop for c from ?a to ?z
                        while (assoc ret existing-keys)
                        do (setq ret (format "%s%c" key c)))

Force parsing of the BibTeX bibliography (source)

(defun malb/get-bibtex-keys (file)
  (with-current-buffer (find-file-noselect file)
    (mapcar #'car (bibtex-parse-keys))))

(defun malb/latex-parse-bibtex ()
  (mapc 'LaTeX-add-bibitems
        (apply 'append
               (mapcar #'malb/get-bibtex-keys (reftex-get-bibfile-list)))))

Helm BibTeX (BibTeX Completion)

Helm-bibtex is a nice interface for BibTeX. (source)

We patch helm-bibtex-find-pdf-in-library to replace : with _. We also add an option to google the paper’s title

(use-package helm-bibtex
  :commands (helm-bibtex helm-bibtex-with-local-bibliography)
  :config (progn
            (setq bibtex-completion-bibliography malb/bibtex-files
                  bibtex-completion-library-path malb/literature-dirs
                  bibtex-completion-notes-path malb/literature-notes-file
                  helm-bibtex-full-frame t
                  "** ${title} cite:${=key=}\n:PROPERTIES:\n:CUSTOM_ID: ${=key=}\n:END:\n\nfullcite:${=key=}\n\n")

            (add-to-list 'helm-commands-using-frame 'helm-bibtex)

            (defun malb/bibtex-completion-google-this (keys-or-entries)
              (dolist (key-or-entry keys-or-entries)
                (let* ((key (if (stringp key-or-entry)
                              (bibtex-completion-get-value "=key=" key-or-entry))))
                  (helm-google (replace-regexp-in-string
                                "{\\|}" ""  (bibtex-completion-get-value "title"
                                                                         (bibtex-completion-get-entry key)))))))

            (helm-bibtex-helmify-action malb/bibtex-completion-google-this

            (defun bibtex-completion-find-pdf-in-library (key-or-entry &optional find-additional)
              "Searches the directories in `bibtex-completion-library-path'
for a PDF whose name is composed of the BibTeX key plus
`bibtex-completion-pdf-extension'.  The path of the first matching
PDF is returned.

If FIND-ADDITIONAL is non-nil, the paths of all PDFs whose name
starts with the BibTeX key and ends with
`bibtex-completion-pdf-extension' are returned instead."
              (let* ((key (if (stringp key-or-entry)
                            (bibtex-completion-get-value "=key=" key-or-entry)))
                     (main-pdf (cl-loop
                                for dir in (-flatten bibtex-completion-library-path)
                                append (cl-loop
                                        for ext in (-flatten bibtex-completion-pdf-extension)
                                        collect (f-join dir (s-concat (s-replace ":" "_" key) ext))))))
                (if find-additional
                    (sort               ; move main pdf on top of the list if needed
                      for dir in (-flatten bibtex-completion-library-path)
                      append (directory-files dir t
                                              (s-concat "^" (regexp-quote (s-replace ":" "_" key))
                                                        (mapconcat 'regexp-quote
                                                                   (-flatten bibtex-completion-pdf-extension)
                     (lambda (x y)
                       (and (member x main-pdf)
                            (not (member y main-pdf)))))
                  (-flatten (-first 'f-file? main-pdf)))))

            (defun bibtex-completion-add-pdf-to-library (keys)
              "Add a PDF to the library for the first selected entry. The PDF
can be added either from an open buffer or a file."
              (let* ((key (car keys))
                     (source (char-to-string
                              (read-char-choice "Add pdf from [b]uffer or [f]ile? " '(?b ?f))))
                     (buffer (when (string= source "b")
                               (read-buffer-to-switch "Add pdf buffer: ")))
                     (file (when (string= source "f")
                             (expand-file-name (read-file-name "Add pdf file: " nil nil t))))
                     (path (-flatten (list bibtex-completion-library-path)))
                     (path (if (cdr path)
                               (completing-read "Add pdf to: " path nil t)
                             (car path)))
                     (pdf (expand-file-name (concat  (s-replace ":" "_" key) ".pdf") path)))
                  (with-current-buffer buffer
                    (write-file pdf)))
                  (copy-file file pdf)))))

            (eval-after-load "org-ref-helm-bibtex"
              '(progn (helm-delete-action-from-source "Insert BibTeX key" helm-source-bibtex)
                      (helm-add-action-to-source "Insert BibTeX key" 'helm-bibtex-insert-key helm-source-bibtex 0)))))

Company interface to helm-bibtex.

(defvar company-bibtex-completion--hashes nil)

(defun company-bibtex-completion--hashes ()
  (mapcar 'cadr bibtex-completion-cache))

(defvar company-bibtex-completion--candidates nil)

(defun company-bibtex-completion--candidates ()
  (setq company-bibtex-completion--hashes (company-bibtex-completion--hashes))
  (setq company-bibtex-completion--candidates
        (mapcar (lambda (x) (propertize (cdr (assoc "=key=" (cdr x)))
                                        (cdr (assoc "title" (cdr x)))))

(defun company-bibtex-completion-candidates ()
  (when (not (equal (company-bibtex-completion--hashes)

(defun company-bibtex-completion (command &optional arg &rest ignored)
  "bibtex-completion backend"
  (interactive (list 'interactive))
  (cl-case command
    (interactive (company-begin-backend 'company-bibtex-completion))
    (prefix (company-auctex-prefix "\\\\cite[^[{]*\\(?:\\[[^]]*\\]\\)?{\\([^},]*\\)\\="))
    (candidates (all-completions arg (company-bibtex-completion-candidates)))
    (annotation (get-text-property 0 'bibtex-completion-annotation arg))))

(add-to-list 'company-backends #'company-bibtex-completion)
turn company-bibtex-completion into library so it can be lazy loaded

Adding References

I like the interface of biblio better, gscholar-bibtex covers more bases.

- biblio browses and gathers bibliographic references and publications from various sources, by keywords or by DOI.

(use-package biblio)
  • gscholar-bibtex grabs BibTeX entries from from Google Scholar, ACM Digital Library, IEEE Xplore and DBLP.
    (use-package gscholar-bibtex
      :init (progn
                (setq gscholar-bibtex-database-file (expand-file-name "common-latex/local.bib" malb/projects-dir))))

Org Ref

Org-ref is a library for org-mode that provides rich support for citations, labels and cross-references in org-mode. org-ref is especially suitable for org-mode documents destined for LaTeX export and scientific publication. org-ref is also extremely useful for research documents and notes. org-ref bundles several other libraries that provide functions to create and modify bibtex entries from a variety of sources, but most notably from a DOI.

(use-package org-ref
  :after org
  :defer 2
  :config (progn
            (setq org-ref-default-bibliography malb/bibtex-files
                  org-ref-bibliography-notes malb/literature-notes-file
                  org-ref-pdf-directory malb/literature-dirs
                  org-ref-bibtex-hydra-key-binding "\C-cj"
                  org-ref-show-broken-links nil)

            (helm-add-action-to-source "Find online" 'helm-malb/bibtex-completion-google-this helm-source-bibtex)))
(use-package org-ref-latex
  :ensure nil
  :commands org-ref-latex-jump-to-bibtex)

Speed Reading

Spray is a speed reading mode.


(use-package spray
  :config (progn
            (setq spray-wpm 512
                  spray-margin-left 4
                  spray-margin-top 12)
            (bind-key "+" 'spray-faster spray-mode-map)
            (bind-key "-" 'spray-slower spray-mode-map)
            (add-to-list 'spray-unsupported-minor-modes 'beacon-mode)))

AWS Polly

A simple interface for AWS’ text-to-speech API. All text is sent to Amazon’s severs so keep privacy implications in mind before using this.

(defgroup aws-polly nil
  "Run AWS Polly for Text to Speech"
  :group 'tools)

AWS offers various voices to choose from

(defcustom aws-polly-voices '(("Emma"     "en-GB") ("Brian"     "en-GB") ("Joanna"    "en-US") ("Mizuki"   "ja-JP")
                              ("Filiz"    "tr-TR") ("Astrid"    "sv-SE") ("Maxim"     "ru-RU") ("Tatyana"  "ru-RU")
                              ("Carmen"   "ro-RO") ("Ines"      "pt-PT") ("Cristiano" "pt-PT") ("Vitoria"  "pt-BR")
                              ("Ricardo"  "pt-BR") ("Maja"      "pl-PL") ("Jan"       "pl-PL") ("Ewa"      "pl-PL")
                              ("Ruben"    "nl-NL") ("Lotte"     "nl-NL") ("Liv"       "nb-NO") ("Giorgio"  "it-IT")
                              ("Carla"    "it-IT") ("Karl"      "is-IS") ("Dora"      "is-IS") ("Mathieu"  "fr-FR")
                              ("Celine"   "fr-FR") ("Chantal"   "fr-CA") ("Penelope"  "es-US") ("Miguel"   "es-US")
                              ("Enrique"  "es-ES") ("Conchita"  "es-ES") ("Geraint"   "en-GB-WLS") ("Salli" "en-US")
                              ("Kimberly" "en-US") ("Kendra"    "en-US") ("Justin"    "en-US") ("Joey"     "en-US")
                              ("Ivy"      "en-US") ("Raveena"   "en-IN")  ("Amy"      "en-GB") ("Russell"  "en-AU")
                              ("Nicole"   "en-AU") ("Marlene"   "de-DE") ("Hans"      "de-DE") ("Naja"     "da-DK")
                              ("Mads"     "da-DK") ("Gwyneth"  " cy-GB") ("Jacek"     "pl-PL"))
  "Voices to use in AWS polly in order of preference"
  :group 'aws-polly
  :type ''(alist :value-type (string string)))

We call the command line client which can be obtained by pip install awscli

(defcustom aws-polly-command "aws polly synthesize-speech --output-format mp3 --voice-id %s --text \"%s\" %s"
  "Command to run AWS polly"
  :group 'aws-polly
  :type 'string)

Decide if text is a quote

(defun aws-polly-is-quote (text)
  (equal (and (>= (length text) 2) (substring text 0 2)) "  "))

Return the first voice matching detected language.

(defun aws-polly-select-voice (text)
  "Select voice by picking first voice from aws-polly-voices matching detected language in TEXT."
  (let ((lang (with-temp-buffer
                (insert text)
                (replace-regexp-in-string "_" "-" (cadr (assq (guess-language-buffer) guess-language-langcodes)))))
        (voices nil))
    (dolist (entry aws-polly-voices)
      (if (string-prefix-p lang (cadr entry))
          (push (car entry) voices)))
    (setq voices (nreverse voices))
     ((eq (length voices) 0) (caar aws-polly-voices))
     ((eq (length voices) 1) (car voices))
     (t (if (aws-polly-is-quote text)
            (cadr voices)
          (car voices))))))

Offer choice for user to pick voice.

(defun aws-polly-voices-completing-read ()
  "Offer list of AWS Polly voices to choose from and return choice."
   " .*$" "" (completing-read "Voice: "
                              (mapcar (lambda (x) (format "%s (%s)" (car x) (cadr x)))
                                      aws-polly-voices) nil t)))

AWS Polly will not read any text longer than 1500 characters as of writing.

(defvar aws-polly-character-limit 1500
  "Number of characters accepted by AWS polly.")

We call pandoc to convert our buffer to plain text. This kills links etc. which we typically do not want read out.

(defun aws-polly-plaintextify (beginning end)
  (let ((pandoc-use-async nil)
        (reader (cdr (assq major-mode pandoc-major-modes)))
    (if reader
        (replace-regexp-in-string "_" ""
                                  (malb/pandoc-convert "plain" (current-buffer) beginning end reader))
      (buffer-substring beginning end))))

We may want to add some silence between paragraphs.

(defcustom aws-polly-make-silence-command
  "ffmpeg -f lavfi -y -i anullsrc=r=22050:cl=mono -t %f -q:a 9 -acodec libmp3lame %s"
  "Command to make moments of slience"
  :group 'aws-polly
  :type 'string)

(defun aws-polly-make-silence (length output-filename)
  "Write LENGTH seconds of silence to OUTPUT-FILENAME"
  (call-process-shell-command (format aws-polly-make-silence-command length output-filename) nil nil nil))

Binding it all together to read one paragraph (or region) at a time.

(defun aws-polly-region (arg)
  "Speak text with AWS polly.

When no region is active the current paragraph is used. When
prefix argument is given ask for voice first."
  (interactive "P")
  (let* ((beginning (if (not (use-region-p))
                        (save-excursion (backward-paragraph) (point))
         (end (if (not (use-region-p))
                  (save-excursion (forward-paragraph) (point))
         (texts (split-string-and-unquote (aws-polly-plaintextify beginning end) "\n\n"))
         (silence (make-temp-file "emacs-aws-polly-silence" nil ".mp3"))
         (files nil))
    (aws-polly-make-silence 1.0 silence)
    (dolist (text texts)
      (if (> (length text) aws-polly-character-limit)
          (error "AWS polly will only accept up 1500 characters but got %d \"%s\"" (length text) (substring text 0 32))))
    (dolist (text texts)
      (let ((voice (if arg (aws-polly-voices-completing-read)
                     (aws-polly-select-voice text)))
            (output-filename (make-temp-file "emacs-aws-polly" nil ".mp3")))
        (call-process-shell-command (format aws-polly-command voice text output-filename) nil nil nil)
        (setq files (append files (list output-filename silence)))))
    (dolist (file files)
      (emms-add-file file))

E-mail (Mu4e)

E-mail is fetched by mbsync and parsed by mu. Then, we use mu4e. See this blog post for details.

(use-package mu4e
  :ensure nil
  :config  (setq mu4e-headers-skip-duplicates t
                 mu4e-use-fancy-chars t
                 mu4e-view-show-images t
                 message-kill-buffer-on-exit t
                 mu4e-hide-index-messages t
                 mu4e-auto-retrieve-keys t
                 mu4e-compose-dont-reply-to-self t
                 mu4e-completing-read-function 'completing-read
                 mu4e-compose-in-new-frame t
                 mu4e-split-view 'vertical
                 mu4e-headers-visible-columns 134
                 mu4e-headers-visible-lines 16
                 mu4e-context-policy 'pick-first
                 mu4e-compose-context-policy 'ask
                 mu4e-change-filenames-when-moving t
                 mu4e-confirm-quit nil
                 mu4e-index-cleanup t
                 mu4e-view-show-addresses t
                 mu4e-index-lazy-check nil))

Prettier (source)

(setq mu4e-use-fancy-chars t
      mu4e-headers-draft-mark     '("D" . "") ; draft
      mu4e-headers-new-mark       '("N" . "")
      mu4e-headers-seen-mark      '("S" . "")    ; seen
      mu4e-headers-unread-mark    '("u" . "")    ; unseen
      mu4e-headers-flagged-mark   '("F" . "")  ; flagged
      mu4e-headers-new-mark       '("N" . "")  ; new
      mu4e-headers-replied-mark   '("R" . "")  ; replied
      mu4e-headers-passed-mark    '("P" . "")  ; passed
      mu4e-headers-encrypted-mark '("x" . "🔒 ") ; encrypted
      mu4e-headers-signed-mark    '("s" . "")  ; signed
      mu4e-headers-attach-mark    '("a" . "🗎")  ; attachments
      mu4e-headers-empty-parent-prefix '("-" . "")
      mu4e-headers-first-child-prefix '("\\" . "")
      mu4e-headers-has-child-prefix '("+" . ""))


(setq mu4e-maildir malb/mu4e-maildir
      mu4e-drafts-folder "/[Google Mail]/Drafts"
      mu4e-sent-folder   "/[Google Mail]/Sent Mail"
      mu4e-trash-folder  "/[Google Mail]/Bin")

(setq mu4e-maildir-shortcuts
      '(("/Inbox"                       . ?i)
        ("/[Google Mail]/Drafts"        . ?d)
        ("/[Google Mail]/Sent Mail"     . ?s)
        ("/[Google Mail]/Bin"           . ?t)
        ("/royal holloway/msc projects" . ?m)))



(use-package mu4e-query-fragments
  :config (setq mu4e-query-fragments-list
                '(("%hidden" . "flag:trashed")
                  ("%unread" . "flag:unread AND NOT %hidden")
                  ("%today"  . "")
                  ("%week"   . "")
                  ("%month"  . "")
                  ("%inbox"  . "(maildir:\"/royal holloway\" OR maildir:/INBOX)")
                  ("%sent"   . "(maildir:\"/[Google Mail]/Sent Mail\" OR maildir:\"/royal holloway/sent\")")
                  ("%direct" . "%inbox AND NOT flag:list AND NOT %hidden AND (to:martinralbrecht@* OR to:martin.albrecht@*)"))))
(use-package mu4e-jump-to-list)

Canned queries.

(setq mu4e-bookmarks nil)               ; clear out

(add-to-list 'mu4e-bookmarks '("%unread" "Unread" ?u) t)

(add-to-list 'mu4e-bookmarks '("%unread AND %direct" "Unread (direct)" ?i) t)

(add-to-list 'mu4e-bookmarks
             '("%unread AND (flag:list OR OR maildir:/bulk OR maildir:/research/.lists)"
               "Unread (bulk)" ?l) t)

(add-to-list 'mu4e-bookmarks '("flag:flagged" "Flagged" ?f) t)

(add-to-list 'mu4e-bookmarks '("%today" "Today's" ?t) t)
(add-to-list 'mu4e-bookmarks '("%week"  "Last 7 days" ?w) t)

(add-to-list 'mu4e-bookmarks '("%direct AND %week" "Last 7 days (direct)" ?W) t)

 'mu4e-bookmarks '("%direct AND %week AND NOT (flag:replied OR flag:passed OR flag:trashed OR flag:list)"
                   "Last 7 days (direct, unanswered)" ?R) t)

(add-to-list 'mu4e-bookmarks '("%sent AND %week" "Last 7 days (sent)" ?s) t)

 '("mime:application/* AND NOT (mime:application/pgp* or mime:application/ics) AND size:5k..1024M AND %inbox AND %month"
   "Documents (31 days)" ?d) t)


(setq mu4e-get-mail-command "timelimit -t 180 -T 180 mbsync googlemail-default"
      mu4e-update-interval nil)


Handling contacts semi-automagically is one of mu4e’s big selling points to me. The functions below make it more automagic.

My shitty regexp for detecting e-mail addresses

(defconst malb/email-regexp "<?\\([^ ]+@[^ ]+\.[^ >]+\\)>?")
(defun malb/extract-email (str)
  ;; return last sub-string looking like an email address
  (let ((tokens (reverse (split-string-and-unquote str)))
    (dolist (token tokens)
      (string-match malb/email-regexp token)
      (setq match (or match (match-string 1 token))))


A table of canonical names for people who cannot seem to fix their headers …

(defcustom malb/mu4e-name-replacements nil
  "replacement names from e-mail addresses"
  :type '(list :type string)
  :group 'malb)
  • My uni likes “Lastname, Firstname (Year)” which is weird, so we fix it.
  • Some people like to YELL their LASTNAME and then write the first name, we also canonicalise that
  • Some people like to send incomplete data, so we maintain a local replacement list
(defun malb/canonicalise-contact-name (email  &optional name nick)
  (let* ((name (or name ""))
         (case-fold-search nil)
         ;; look up email address and use entry if found
         (candidate (if nick (caddr (assoc (downcase email) malb/mu4e-name-replacements))
                      (cadr (assoc (downcase email) malb/mu4e-name-replacements)))))
    (if candidate
        ;; deal with YELL’d last names
        (setq name (replace-regexp-in-string "^\\(\\<[[:upper:]]+\\>\\) \\(.*\\)" "\\2 \\1" name))
        ;; Foo, Bar becomes Bar Foo
        (setq name (replace-regexp-in-string "^\\(.*\\), \\([^ ]+\\).*" "\\2 \\1" name))
        ;; foo bar becomes Foo Bar
        (setq name (capitalize name))))))

A function to add new replacements.

(defun malb/add-mu4e-name-replacement ()
  (let* ((email (helm-read-string "E-mail: " (downcase (get-text-property (point) 'email))))
         (name  (helm-read-string "Name: "
                                  (malb/canonicalise-contact-name email
                                                                  (get-text-property (point) 'long))))
         (nick  (helm-read-string "Nick: " (replace-regexp-in-string "(.*) " "\\1" name))))
    (add-to-list 'malb/mu4e-name-replacements (list email name nick) t)
    (customize-save-variable 'malb/mu4e-name-replacements malb/mu4e-name-replacements)))
(bind-key "N" #'malb/add-mu4e-name-replacement mu4e-view-mode-map)

Canonicalise contacts as they appear from mu4e.

(defun malb/mu4e-contact-rewrite-function (contact)
  (let* ((email (downcase (plist-get contact :mail)))
         (name (if (equal (plist-get contact :name) email)
                 (plist-get contact :name)))
         (case-fold-search t))
    (plist-put contact :name (malb/canonicalise-contact-name email name))

(setq mu4e-contact-rewrite-function #'malb/mu4e-contact-rewrite-function)

Ignore some e-mail addresses when auto completing:

(setq mu4e-compose-complete-ignore-address-regexp (rx  (or (seq "no" (zero-or-one "-") "reply")
                                                           (seq "replyto-" (one-or-more char) "")
                                                           (seq "@" (one-or-more char) "")
                                                           (seq "do-not-reply" (zero-or-more char) "@")
                                                           (seq "bounce-" (one-or-more char) "@"))))

Extract a pretty-ish list of contacts from an e-mail.

(defun malb/extract-contacts (fields)
  "Return a list of 'name <email>' entries."
  (let (addresses
        (case-fold-search t)
        (search-regexp (mapconcat (lambda (arg) (concat "^" arg ": *"))
                                  fields "\\|")))

    ;; extract addresses
      (goto-char (point-min))
      (while (re-search-forward search-regexp nil t)
          (setq point-end-of-line (re-search-forward "$")))
        (setq addresses (append addresses
                                 (buffer-substring-no-properties (point) point-end-of-line))))))
    (setq addresses (mapcar (lambda (address)
                              (format "\"%s\" <%s>"
                                      (malb/canonicalise-contact-name (car address) (cdr address))
                                      (car address)))

The following is useful in e-mail templates

(defun malb/get-names-from-fields (fields)
  (let (contacts
        (search-regexp (mapconcat (lambda (arg)
                                    (concat "^" arg ": "))
                                  fields "\\|"))
        (case-fold-search t))
      (goto-char (point-min))
      (while (re-search-forward search-regexp nil t)
          (setq point-end-of-line (re-search-forward "$")))
        (setq contacts (append contacts
                                (buffer-substring-no-properties (point)
      (dolist (contact contacts)
        (let ((name (malb/canonicalise-contact-name (car contact) (cdr contact) t)))
          ;; extract first name
          (when (string-match "\\([^ ,]+\\)" name)
            (push (match-string 1 name) ret))))
      (if ret (string-join (nreverse ret) ", ") ret "there"))))

A shortcut:

(defun malb/get-names-from-to-field ()
  (malb/get-names-from-fields '("To")))

Rearrange To and CC

Use Helm to re-arrange recipients of an e-mail. I often get e-mail from Bob with Charley in CC which prompts me to send an e-mail to Charley with Bob in CC.

(defun malb/helm-reorder-recipients ()
  "Re-distribute addresses to To: and CC: fields."
  (let* ((search-regexp "^to: *\\|^cc: *")
         (addresses (malb/extract-contacts '("to" "cc")))
         (case-fold-search t)
         to cc)

    ;; ask user to split into To and CC
    (setq to (helm :sources (helm-build-sync-source "To:"
                              :candidates addresses
                              :action  '(("Select" . (lambda (x) (helm-marked-candidates)))
                                         ("Ignore" . (lambda (x)))))))

    (dolist (address to)
      (setq addresses (delete address addresses)))

    (if addresses
        (setq cc (helm :sources (helm-build-sync-source "CC:"
                                  :candidates addresses
                                  :action  '(("Select" . (lambda (x) (helm-marked-candidates)))
                                             ("Ignore" . (lambda (x))))))))

    ;; Replace To: and CC: fields
      (goto-char (point-min))

      (while (re-search-forward search-regexp nil t)


      (progn (newline)
             (insert "To: ")
             (insert (mapconcat #'identity to ", ")))
      (if cc (progn (newline)
                    (insert "Cc: ")
                    (insert (mapconcat #'identity cc ", ")))))))
(bind-key "C-c ]" #'malb/helm-reorder-recipients mu4e-compose-mode-map)

Contact Look up

(defun malb/fullcontact ()
  (let* ((email (replace-regexp-in-string "<\\(.*?\\)>" "\\1" (thing-at-point 'email)))
         (path (expand-file-name "" user-emacs-directory)))
    (if email
        (let ((msg (shell-command-to-string (format "PYTHONIOENCODING=utf8 python %s -e %s" path email))))
          (message "%s" msg)))))

(bind-key "@" #'malb/fullcontact mu4e-view-mode-map)


(unbind-key "s" mu4e-main-mode-map)

A helm menu for contacts, based on (source)

(defun malb/helm-mu-contacts (arg)
  "Helm interface to e-mail addresses.

When ARG equals then the persistent action is search, otherwise
it is to insert the contact.
  (interactive "p")

  (if (not mu4e~contacts)
        ;; request contacts
        (let ((i 0))
          ;; sleep for at most 2 seconds to wait for contacts to be filed
          (while (and (not mu4e~contacts) (> 2 i))
            (sleep-for 1)
            (+ i 1)))))

  (let ((malb/helm-mu-actions
         '(("Insert" . (lambda (x)
                         (insert (mapconcat 'identity
                                            (helm-marked-candidates) ","))))
           ("Find messages (combine multiple with OR)" .
            (lambda (x)
               (format "contact:%s"
                       (mapconcat 'malb/extract-email (helm-marked-candidates) " OR ")))))
           ("Copy to clipboard" . (lambda (x)
                                    (kill-new (mapconcat 'identity
                                                         (helm-marked-candidates) ","))))
           ("Google search" . (lambda (x) (helm-google (replace-regexp-in-string
                                                        (format "^\\(.*\\) %s" malb/email-regexp) "\\1" x))))

           ("Find messages (combine multiple with AND)" .
            (lambda (x)
               (format "contact:%s"
                       (mapconcat 'malb/extract-email (helm-marked-candidates) " AND ")))))

    (helm :sources (helm-build-sync-source "E-mail address candidates"
                     :candidates  (append
                                   ;; mu contacts
                                   (loop for contact being the hash-key of mu4e~contacts
                                         collect (cons contact contact)))
                     :action  (cond ((equal arg 4) (cons (cadr malb/helm-mu-actions)
                                                         (cons (car malb/helm-mu-actions)
                                                               (cddr  malb/helm-mu-actions))))
                                    (t malb/helm-mu-actions))))))
(key-chord-define mu4e-compose-mode-map ",," #'malb/helm-mu-contacts)
(bind-key "C-c [" #'malb/helm-mu-contacts mu4e-compose-mode-map)

Search mu with helm-mu.

(use-package helm-mu
  :config (progn

            (defun malb/helm-mu (arg)
              (interactive "p")
              (let ((current-prefix-arg nil))
                 ((equal arg 4) (malb/helm-mu-contacts 4))
                 (t (helm-mu)))))

            (dolist (mode-map '(mu4e-main-mode-map mu4e-headers-mode-map mu4e-view-mode-map))
              (unbind-key "s" mode-map)
              (bind-key "ss" #'mu4e-query-fragments-search mode-map)
              (bind-key "sm" #'mu4e-query-fragments-search mode-map)
              (bind-key "sh" #'malb/helm-mu mode-map)
              (bind-key "sc" (defun malb/helm-mu-contacts-messages ()
                               (malb/helm-mu-contacts 4)) mode-map))))

Tip: C-c C-f aka helm-follow-mode is your friend.


Use <TAB> to preview messages and q to close previews.

(require 'mu4e-view)
(require 'mu4e-headers)

(defun malb/preview-message ()
  (sleep-for 0.1) ;; this is a HACK
  (select-window (previous-window)))

;; based on (mu4e-select-other-view)
(defun malb/close-message-view ()
  (let* ((other-buf
	     ((eq major-mode 'mu4e-headers-mode)
	     ((eq major-mode 'mu4e-view-mode)
	  (other-win (and other-buf (get-buffer-window other-buf))))
    (if (window-live-p other-win)
          (select-window other-win)
          (sleep-for 0.1)

(bind-key "<tab>" #'malb/preview-message mu4e-headers-mode-map)
(bind-key "q" #'malb/close-message-view mu4e-headers-mode-map)
(bind-key "z" #'malb/close-message-view mu4e-headers-mode-map)

Visual-line mode all the way.

(bind-key "<home>" #'beginning-of-visual-line mu4e-view-mode-map)
(bind-key "<end>" #'end-of-visual-line mu4e-view-mode-map)

Headers to show in header view.

(setq mu4e-headers-fields '((:human-date . 10)
                            (:flags . 4)
                            (:mailing-list . 16)
                            (:from . 22)
                            (:to . 22)

Use imagemagick, if available

(when (fboundp 'imagemagick-register-types)

View e-mails with width restriction, but wider for HTML

(defun malb/mu4e-view-mode-hook ()
  "View e-mails with width restriction, but wider for HTML."
  (if (boundp 'msg)
      (let* ((txt (mu4e-message-field msg :body-txt))
             (html (mu4e-message-field msg :body-html)))
         ((and (> (* mu4e-view-html-plaintext-ratio-heuristic
                     (length txt)) (length html))
               (or (not mu4e-view-prefer-html) (not html)))
          (set-fill-column 80))
          (set-fill-column 120))))
    (set-fill-column 80))
  (visual-line-mode 1)
  (visual-fill-column-mode 1))

(add-hook 'mu4e-view-mode-hook #'malb/mu4e-view-mode-hook)

Convert HTML mail to plain text using pandoc.

(setq mu4e-html2text-command "iconv -c -t utf-8 | pandoc -f html -t plain")

Action to view e-mails in external browser.

(add-to-list 'mu4e-view-actions
             '("browser" . mu4e-action-view-in-browser) t)

Action to view e-mails in EWW.

(defun malb/view-in-eww (msg)
  (eww-browse-url (concat "file://" (mu4e~write-body-to-html msg))))
(add-to-list 'mu4e-view-actions '("Eww view" . malb/view-in-eww) t)

Action to search messages by/to sender.

(defun malb/search-for-sender (msg)
  "Search for messages sent by the sender of the message at point."
   (concat "from:" (cdar (mu4e-message-field msg :from)))))

(defun malb/search-for-sender-extended (msg)
  "Search for messages sent by the sender of the message at point."
   (concat "from:"  (mu4e-headers-search (cdar (mu4e-message-field msg :from)))
           "OR to:" (mu4e-headers-search (cdar (mu4e-message-field msg :from)))
           "OR cc:" (mu4e-headers-search (cdar (mu4e-message-field msg :from))))))

;; define 'x' as the shortcut
(add-to-list 'mu4e-view-actions
             '("xsender search (from)" . malb/search-for-sender) t)

(add-to-list 'mu4e-view-actions
             '("Xsender search" . malb/search-for-sender-extended) t)

Action to search messages involving all correspondents.

(defun malb/find-correspondence (msg)
  "Find messages involving all correspondents."
  (let ((addresses (append (mapcar (lambda (x) (cdr x))
                                   (mu4e-message-field msg :to))
                           (mapcar (lambda (x) (cdr x))
                                   (mu4e-message-field msg :cc))
                           (mapcar (lambda (x) (cdr x))
                                   (mu4e-message-field msg :from)))))
    (helm :sources `(((name . "Filter candidates:")
                      (candidates . addresses)
                      (after-init-hook . (lambda () (with-helm-buffer
                                                      (run-at-time 0.25 nil #'helm-mark-all))))
                      (action . (("Filter" . (lambda (x) (setq addresses (helm-marked-candidates))))
                                 ("Use all" . (lambda (x))))))))
     (format "contact:%s"
             (mapconcat 'identity addresses " AND ")))))

(add-to-list 'mu4e-view-actions
             '("Find messages" . malb/find-correspondence) t)

(add-to-list 'mu4e-headers-actions
             '("Find messages" . malb/find-correspondence) t)


Don’t break long lines manually.

(setq mu4e-compose-format-flowed t)

(defun malb/no-hard-newlines ()
  (setq use-hard-newlines nil))

(add-hook 'mu4e-compose-mode-hook #'malb/no-hard-newlines)

To use a bit of org-mode’s magic as well by pulling in orgtbl-mode and orgstruct++-mode. We also enable footnotes, to add a footnote try C-c ! a. Also set e-mail width to 72 characters.

(add-hook 'mu4e-compose-mode-hook #'malb/fill-column-72)
(add-hook 'mu4e-compose-mode-hook #'malb/mu4e-compose-frame)
(add-hook 'message-mode-hook #'flyspell-mode)
(add-hook 'message-mode-hook #'turn-on-orgstruct)
(add-hook 'message-mode-hook #'turn-on-orgstruct++)
(add-hook 'message-mode-hook #'turn-on-orgtbl)
(add-hook 'message-mode-hook #'typo-mode)

;; TODO this isn’t as nice as it could be
(bind-key "C-c C-x f" #'org-footnote-action message-mode-map)

Don’t add an empty line when quoting e-mail (source)

(require 'nnheader)

(defun malb/message-insert-citation-line ()
  "Insert a simple citation line."
  (when message-reply-headers
    (insert (mail-header-from message-reply-headers) " writes:")

(setq message-citation-line-function #'malb/message-insert-citation-line)

Various shortcuts to jump to/create headers.

(bind-key "C-c g t" #'message-goto-to mu4e-compose-mode-map)
(bind-key "C-c g c" #'message-goto-cc mu4e-compose-mode-map)
(bind-key "C-c g b" #'message-goto-bcc mu4e-compose-mode-map)
(bind-key "C-c g f" #'message-goto-from mu4e-compose-mode-map)
(bind-key "C-c g s" #'message-goto-subject mu4e-compose-mode-map)

Don’t let display-buffers-alist interfere with mu4e-compose:

(advice-add #'mu4e-compose :around #'malb/clean-display-buffer-alist)
(advice-add #'mu4e~compose-handler :around #'malb/clean-display-buffer-alist)
(advice-add #'mu4e-view :around  #'malb/switch-to-mu4e-advice)


(setq mu4e-attachment-dir (expand-file-name "incoming" malb/sync-dir))
(setq-default mu4e-save-multiple-attachments-without-asking t)

Attachments are mostly handled using the helm baloo interface, but sometimes we want to send files from a directory: C-c C-m C-a (source)

(add-hook 'dired-mode-hook 'turn-on-gnus-dired-mode)

(use-package gnus-dired
  :ensure nil
  :after mu4e
    ;; make the `gnus-dired-mail-buffers' function also work on
    ;; message-mode derived modes, such as mu4e-compose-mode
    (defun gnus-dired-mail-buffers ()
      "Return a list of active message buffers."
      (let (buffers)
          (dolist (buffer (buffer-list t))
            (set-buffer buffer)
            (when (and (derived-mode-p 'message-mode)
                       (null message-sent-message-via))
              (push (buffer-name buffer) buffers))))
        (nreverse buffers)))

    (setq gnus-dired-mail-mode 'mu4e-user-agent)))

Put attachments at end of my writing (source)

(defun malb/mml-attach-file--go-to-eob (orig-fun &rest args)
  "Go to the end of my message before attaching files."
      (goto-char (point-min))
      (let ((point (re-search-forward "^.*writes:$" nil t)))
        (if point
              (goto-char point)
          (goto-char (point-max))))
      (apply orig-fun args))))

(advice-add 'mml-attach-file :around #'malb/mml-attach-file--go-to-eob)

Window Management

Create a new mu4e session in a new frame.

(defun malb/mail ()
  (select-frame (make-frame-command))
  (set-frame-name "mu4e") ;; we use this in our window management

Kill mu4e frame.

(defun malb/mu4e-quit-session ()

(bind-key "q" #'malb/mu4e-quit-session mu4e-main-mode-map)

I patched mu4e to be way less clever about window management after sending an e-mail, my mu4e-message-kill-buffer looks like this:

(defun mu4e-sent-handler (docid path)
  "Handler function, called with DOCID and PATH for the just-sent
message. For Forwarded ('Passed') and Replied messages, try to set
the appropriate flag at the message forwarded or replied-to."
  (mu4e~compose-set-parent-flag path)
  (when (file-exists-p path) ;; maybe the draft was not saved at all
    (mu4e~proc-remove docid))
  ;; kill any remaining buffers for the draft file, or they will hang around...
  ;; this seems a bit hamfisted...
  (dolist (buf (buffer-list))
    (when (and (buffer-file-name buf)
	       (string= (buffer-file-name buf) path))
      (if message-kill-buffer-on-exit
	  (kill-buffer buf))))
  ;; (mu4e~switch-back-to-mu4e-buffer)
  (mu4e-message "Message sent"))

I set my compose frame to be a dedicated window, which then takes care of all window management for me.

(defun malb/mu4e-compose-frame ()
  (sleep-for 0.25) ;; this is a HACK
  (set-frame-size (selected-frame) 80 60)
  (sleep-for 0.25) ;; this is a HACK
  (set-window-dedicated-p (get-buffer-window (current-buffer)) t))


(setq mml2015-encrypt-to-self t)
(define-key mu4e-compose-mode-map (kbd "C-c s") 'mml-secure-message-sign-pgpmime)
(define-key mu4e-compose-mode-map (kbd "C-c e") 'mml-secure-message-encrypt-pgpmime)
(setq epg-gpg-program "gpg2")


Link to mu4e messages and threads.

(use-package org-mu4e
  :ensure nil
  :config (progn
            (setq org-mu4e-link-query-in-headers-mode t)
            (defun malb/switch-to-mu4e-advice (old-function &rest arguments)
                   (apply old-function arguments))
            (advice-add 'org-mu4e-open :around  #'malb/switch-to-mu4e-advice)))
check if advise can be removed since we’re now checking for major-mode


We change the default template to not include the title which is filled in by deft. We also query the user for a date/time for the blog post.

(use-package org2blog
  :config (progn
            (let (credentials)
              (setq credentials (auth-source-user-and-password ""))
              (setq org2blog/wp-blog-alist
                       :url ""
                       :username ,(car credentials)
                       :password ,(cadr credentials)))))

            (defun malb/org2blog/wp-format-buffer (buffer-template)
              "Buffer formatting function without title."
              (format buffer-template
                      (format-time-string "[%Y-%m-%d %a %H:%M]" (org-read-date t t))
                       (lambda (cat) cat)
                       (or (plist-get (cdr org2blog/wp-blog) :default-categories)
                       ", ")))

            (setq org2blog/wp-use-sourcecode-shortcode nil
                  org2blog/wp-show-post-in-browser nil
                  org2blog/wp-default-categories '("cryptography")
                  org2blog/wp-buffer-format-function #'malb/org2blog/wp-format-buffer
                  org2blog/wp-buffer-template "#+DATE: %s
#+OPTIONS: toc:nil num:nil todo:nil pri:nil tags:nil ^:nil

            ;; workaround for UTF-8 bug
            (advice-add 'url-http-create-request :override

            (defun url-http-create-request-debug (&optional ref-url)
              "Create an HTTP request for <code>url-http-target-url', referred to by REF-URL."
              (let* ((extra-headers)
                     (request nil)
                     (no-cache (cdr-safe (assoc "Pragma" url-http-extra-headers)))
                     (using-proxy url-http-proxy)
                     (proxy-auth (if (or (cdr-safe (assoc "Proxy-Authorization"
                                         (not using-proxy))
                                   (let ((url-basic-auth-storage
                                     (url-get-authentication url-http-proxy nil 'any nil))))
                     (real-fname (url-filename url-http-target-url))
                     (host (url-http--encode-string (url-host url-http-target-url)))
                     (auth (if (cdr-safe (assoc "Authorization" url-http-extra-headers))
                             (url-get-authentication (or
                                                      (and (boundp 'proxy-info)
                                                      url-http-target-url) nil 'any nil))))
                (if (equal "" real-fname)
                    (setq real-fname "/"))
                (setq no-cache (and no-cache (string-match "no-cache" no-cache)))
                (if auth
                    (setq auth (concat "Authorization: " auth "\r\n")))
                (if proxy-auth
                    (setq proxy-auth (concat "Proxy-Authorization: " proxy-auth "\r\n")))

                ;; Protection against stupid values in the referrer
                (if (and ref-url (stringp ref-url) (or (string= ref-url "file:nil")
                                                       (string= ref-url "")))
                    (setq ref-url nil))

                ;; We do not want to expose the referrer if the user is paranoid.
                (if (or (memq url-privacy-level '(low high paranoid))
                        (and (listp url-privacy-level)
                             (memq 'lastloc url-privacy-level)))
                    (setq ref-url nil))

                ;; url-http-extra-headers contains an assoc-list of
                ;; header/value pairs that we need to put into the request.
                (setq extra-headers (mapconcat
                                     (lambda (x)
                                       (concat (car x) ": " (cdr x)))
                                     url-http-extra-headers "\r\n"))
                (if (not (equal extra-headers ""))
                    (setq extra-headers (concat extra-headers "\r\n")))

                ;; This was done with a call to </code>format'.  Concatenating parts has
                ;; the advantage of keeping the parts of each header together and
                ;; allows us to elide null lines directly, at the cost of making
                ;; the layout less clear.
                (setq request
                       ;; The request
                       (or url-http-method "GET") " "
                        (if using-proxy (url-recreate-url url-http-target-url) real-fname))
                       " HTTP/" url-http-version "\r\n"
                       ;; Version of MIME we speak
                       "MIME-Version: 1.0\r\n"
                       ;; (maybe) Try to keep the connection open
                       "Connection: " (if (or using-proxy
                                              (not url-http-attempt-keepalives))
                                          "close" "keep-alive") "\r\n"
                                          ;; HTTP extensions we support
                                          (if url-extensions-header
                                               "Extension: %s\r\n" url-extensions-header))
                                          ;; Who we want to talk to
                                          (if (/= (url-port url-http-target-url)
                                                   (url-type url-http-target-url) 'default-port))
                                               "Host: %s:%d\r\n" host (url-port url-http-target-url))
                                            (format "Host: %s\r\n" host))
                                          ;; Who its from
                                          (if url-personal-mail-address
                                               "From: " url-personal-mail-address "\r\n"))
                                          ;; Encodings we understand
                                          (if (or url-mime-encoding-string
                                                  ;; MS-Windows loads zlib dynamically, so recheck
                                                  ;; in case they made it available since
                                                  ;; initialization in url-vars.el.
                                                  (and (eq 'system-type 'windows-nt)
                                                       (fboundp 'zlib-available-p)
                                                       (setq url-mime-encoding-string "gzip")))
                                               "Accept-encoding: " url-mime-encoding-string "\r\n"))
                                          (if url-mime-charset-string
                                               "Accept-charset: "
                                               (url-http--encode-string url-mime-charset-string)
                                          ;; Languages we understand
                                          (if url-mime-language-string
                                               "Accept-language: " url-mime-language-string "\r\n"))
                                          ;; Types we understand
                                          "Accept: " (or url-mime-accept-string "*/*") "\r\n"
                                          ;; User agent
                                          ;; Proxy Authorization
                                          ;; Authorization
                                          ;; Cookies
                                          (when (url-use-cookies url-http-target-url)
                                              host real-fname
                                              (equal "https" (url-type url-http-target-url)))))
                                          ;; If-modified-since
                                          (if (and (not no-cache)
                                                   (member url-http-method '("GET" nil)))
                                              (let ((tm (url-is-cached url-http-target-url)))
                                                (if tm
                                                    (concat "If-modified-since: "
                                                            (url-get-normalized-date tm) "\r\n"))))
                                          ;; Whence we came
                                          (if ref-url (concat
                                                       "Referer: " ref-url "\r\n"))
                                          ;; Length of data
                                          (if url-http-data
                                               "Content-length: " (number-to-string
                                                                   (length url-http-data))
                                          ;; End request
                                          ;; Any data
                ;; Bug#23750
                ;;(unless (= (string-bytes request)
                ;;           (length request))
                ;;  (message "   text byte %d vs %d length" (string-bytes request) (length request)))
                ;;(message "===============================")
                ;;(error "Multibyte text in HTTP request: %s" request))
                (url-http-debug "Request is: \n%s" request)

PDF Viewer

PDF tools is a reasonable PDF viewer for Emacs.

We also add support to extract PDF annotations to a temporary org-mode/markdown buffer, based on pdf-tools-org, and calling tabular or pdftotext.

(use-package pdf-tools
  :magic ("%PDF" . pdf-view-mode)
  :after org
  :config (progn
            (setq-default pdf-view-display-size 'fit-page)

            (require 'pdf-annot)

            (setq pdf-view-resize-factor 1.1
                  pdf-annot-activate-created-annotations t)

            (add-hook 'pdf-annot-list-mode-hook #'pdf-annot-list-follow-minor-mode)

            (defun malb/pdf-extract-table (&optional format)
              (let* ((format- (upcase (or format "CSV")))
                     (pdf-filename (buffer-file-name))
                     (txt-filename (make-temp-name "/tmp/tabula-"))
                     (buffer (generate-new-buffer
                              (generate-new-buffer-name (format "*tabular<%s>*" (file-name-base pdf-filename))))))
                (shell-command (format "java -jar %s -f %s -p %d -o \"%s\" \"%s\""
                                       pdf-filename) nil)
                (switch-to-buffer buffer)
                (insert-file-contents txt-filename)
                 ((eq format nil) (progn
                                    (call-interactively 'mark-whole-buffer)
                                    (call-interactively 'org-table-convert-region)))
                 ((string-equal format "JSON") (progn
                (delete-file txt-filename)))

            (defun malb/pdf-extract-text ()
              (let* ((pdf-filename (buffer-file-name))
                     (txt-filename (make-temp-name "/tmp/tabula-"))
                     (buffer (generate-new-buffer
                              (generate-new-buffer-name (format "*pdftotext<%s>*" (file-name-base pdf-filename))))))
                (shell-command (format "pdftotext -layout -nopgbrk \"%s\" \"%s\"" pdf-filename txt-filename) nil)
                (switch-to-buffer buffer)
                (insert-file-contents txt-filename)
                (delete-file txt-filename)))

            (defun malb/pdf-view-llncs-from-bounding-box (&optional window)
              "Set the height from the page's bounding-box."
              (let* ((bb (pdf-cache-boundingbox (pdf-view-current-page window)))
                     (h-margin (max 0.28 (or pdf-view-bounding-box-margin 0.0)))
                     (w-margin (max 0.05 (or pdf-view-bounding-box-margin 0.0)))
                     (slice (list (- (nth 0 bb)
                                     (/ h-margin 2.0))
                                  (- (nth 1 bb)
                                     (/ w-margin 2.0))
                                  (+ (- (nth 2 bb) (nth 0 bb))
                                  (+ (- (nth 3 bb) (nth 1 bb))
                (apply 'pdf-view-set-slice
                       (append slice (and window (list window))))))

            (defun pdf-tools-org-edges-to-region (edges)
              "Attempt to get 4-entry region \(LEFT TOP RIGHT BOTTOM\) from several EDGES.
We need this to import annotations and to get marked-up text, because
annotations are referenced by its edges, but functions for these tasks
need region."
              (let ((left0 (nth 0 (car edges)))
                    (top0 (nth 1 (car edges)))
                    (bottom0 (nth 3 (car edges)))
                    (top1 (nth 1 (car (last edges))))
                    (right1 (nth 2 (car (last edges))))
                    (bottom1 (nth 3 (car (last edges))))
                    (n (safe-length edges)))
                ;; we try to guess the line height to move
                ;; the region away from the boundary and
                ;; avoid double lines
                (list left0
                      (+ top0 (/ (- bottom0 top0) 3))
                      (- bottom1 (/ (- bottom1 top1) 3)))))

            (defun malb/pdf-annot-export-as-org (compact)
              "Export annotations to Org Buffer."
              (interactive "P")
              (let* ((annots (sort (pdf-annot-getannots) 'pdf-annot-compare-annotations))
                     (source-buffer (current-buffer))
                     (source-buffer-name (file-name-sans-extension (buffer-name)))
                     (source-file-name (buffer-file-name source-buffer))
                     (target-buffer-name (format "*Notes for %s*" source-buffer-name))
                     (target-buffer (get-buffer-create target-buffer-name)))
                (switch-to-buffer target-buffer)
                (insert (format "#+TITLE: Notes for %s\n" source-buffer-name))
                (insert (format "#+STARTUP: indent\n\n"))
                (insert (format "source: [[%s][%s]]\n\n" source-file-name source-buffer))
                 (lambda (annot) ;; traverse all annotations
                     (let ((page (cdr (assoc 'page annot)))
                            (if (pdf-annot-get annot 'markup-edges)
                                (let ((highlighted-text
                                       (with-current-buffer source-buffer
                                         (pdf-info-gettext (pdf-annot-get annot 'page)
                                                            (pdf-annot-get annot 'markup-edges))))))
                                  (replace-regexp-in-string "\n" " " highlighted-text))
                           (note (pdf-annot-get annot 'contents)))

                       (when (or highlighted-text (> (length note) 0))
                         (insert (if compact "- " "* "))
                         (insert (format "page %s" page))

                         (when highlighted-text
                           (insert (if compact (format ": “%s" highlighted-text)
                                     (concat "\n\n#+BEGIN_QUOTE\n"
                         (if (> (length note) 0)
                             (insert (if compact (format " %s\n" note)
                                       (format "\n\n%s\n\n" note)))
                           (insert (if compact "\n" "\n\n")))))))
                  (lambda (annot) (member (pdf-annot-get-type annot) (list 'link)))

            (defun malb/pdf-annot-export-as-md (compact)
              "Export annotations to Makrdown buffer."
              (interactive "P")
              (let* ((annots (sort (pdf-annot-getannots) 'pdf-annot-compare-annotations))
                     (source-buffer (current-buffer))
                     (source-buffer-name (file-name-sans-extension (buffer-name)))
                     (source-file-name (buffer-file-name source-buffer))
                     (target-buffer-name (format "*Notes for %s*" source-buffer-name))
                     (target-buffer (get-buffer-create target-buffer-name)))
                (switch-to-buffer target-buffer)
                (insert (format "---\ntitle: Notes for %s\n---\n\n" source-buffer-name))
                (insert (format "source: [%s](%s)\n\n" source-buffer source-file-name))
                 (lambda (annot) ;; traverse all annotations
                     (let ((page (cdr (assoc 'page annot)))
                            (if (pdf-annot-get annot 'markup-edges)
                                (let ((highlighted-text
                                       (with-current-buffer source-buffer
                                         (pdf-info-gettext (pdf-annot-get annot 'page)
                                                            (pdf-annot-get annot 'markup-edges))))))
                                  (replace-regexp-in-string "\n" " " highlighted-text))
                           (note (pdf-annot-get annot 'contents)))

                       (when (or highlighted-text (> (length note) 0))
                         (insert (if compact "- " "On "))
                         (insert (format "page %s" page))

                         (when highlighted-text
                           (insert (if compact (format ": “%s" highlighted-text)
                                     (concat ":  \n> "
                                             (replace-regexp-in-string "\n" "\n> " highlighted-text)
                         (if (> (length note) 0)
                             (insert (if compact (format " %s\n" note)
                                       (format "\n\n%s\n\n" note)))
                           (insert (if compact "\n" "\n\n")))))))
                  (lambda (annot) (member (pdf-annot-get-type annot) (list 'link)))

            (defhydra malb/hydra-pdf-extract (:color blue)
Org:       _o_ compact  _O_ normal     _t_ table
Markdown:  _m_ compact  _M_ normal
Other:     _p_ plain    _c_ csv table  _j_ json table _x_ ocr
              ("o" (lambda () (interactive) (malb/pdf-annot-export-as-org 1)))
              ("O" malb/pdf-annot-export-as-org)
              ("m" (lambda () (interactive) (malb/pdf-annot-export-as-md  1)))
              ("M" malb/pdf-annot-export-as-md)
              ("c" (lambda () (interactive) (malb/pdf-extract-table "CSV")))
              ("j" (lambda () (interactive) (malb/pdf-extract-table "JSON")))
              ("t" (lambda () (interactive) (malb/pdf-extract-table)))
              ("p" (lambda () (interactive) (malb/pdf-extract-text)))
              ("x" (lambda () (interactive) (start-process (format "ocr %s" buffer-file-name)
                                                           nil "ocrmypdf" buffer-file-name buffer-file-name)))
              ("q" nil "cancel"))

            (bind-key "s h" #'malb/pdf-view-llncs-from-bounding-box pdf-view-mode-map)
            (bind-key "D" #'dedicated-mode pdf-view-mode-map)
            (bind-key "x" #'malb/hydra-pdf-extract/body pdf-view-mode-map)

            (defun malb/pdf-annot-move (forward)
              (let ((annot-list (with-current-buffer
                                    (pdf-annot-get-buffer pdf-annot-edit-contents--annotation)
                (if annot-list
                      (if forward
                          (call-interactively 'tablist-next-line)
                        (call-interactively 'tablist-previous-line))
                      (call-interactively 'tablist-find-entry))
                  (let ((this nil)
                        (next nil)
                        (annotations (sort (pdf-annot-getannots nil nil (cdar pdf-annot-edit-contents--annotation))
                    (dolist (annot (if forward annotations (reverse annotations)))
                      (when (equal this t)
                        (setq next annot)
                        (setq this nil))
                      (when (equal (pdf-annot-get-id annot) (pdf-annot-get-id pdf-annot-edit-contents--annotation))
                        (setq this t)))
                    (pdf-annot-edit-contents-finalize t)
                    (when next
                      (pdf-view-goto-page (pdf-annot-get next 'page))
                      (pdf-annot-edit-contents next))))))

            (defun malb/pdf-annot-next ()
              (malb/pdf-annot-move t))

            (defun malb/pdf-annot-prev ()
              (malb/pdf-annot-move nil))

            (defun malb/pdf-annot-yank-highlight ()
              (let* ((a pdf-annot-edit-contents--annotation)
                     (text (replace-regexp-in-string "\n" " "
                                                      (pdf-annot-get a 'page)
                                                      (pdf-tools-org-edges-to-region (pdf-annot-get a 'markup-edges))
                                                      (pdf-annot-get a 'buffer)))))
                (insert (format "\"%s\"" text))))

            (bind-key "C-c C-n" 'malb/pdf-annot-next pdf-annot-edit-contents-minor-mode-map)
            (bind-key "C-c C-p" 'malb/pdf-annot-prev pdf-annot-edit-contents-minor-mode-map)
            (bind-key "C-c C-y" 'malb/pdf-annot-yank-highlight pdf-annot-edit-contents-minor-mode-map)

            (defun malb/do-to-pdf-view-buffer (fn)
              (let ((cur-window (get-buffer-window)))
                (dolist (window (window-list))
                  (let ((buffer (window-buffer window)))
                    (with-current-buffer buffer
                      (when (eq major-mode 'pdf-view-mode)
                        (select-window window)
                        (call-interactively fn)
                        (select-window cur-window)

            (defun malb/other-pdf-view-next-page ()
              (malb/do-to-pdf-view-buffer #'pdf-view-next-page))

            (defun malb/other-pdf-view-prev-page ()
              (malb/do-to-pdf-view-buffer #'pdf-view-previous-page))

            (bind-key "C->" #'malb/other-pdf-view-next-page org-mode-map)
            (bind-key "C-<" #'malb/other-pdf-view-prev-page org-mode-map)))



dired-listing-switches explained:

  • l: Is the only mandatory one.
  • a: Means to list invisible files.
  • G: Don’t show group information.
  • h: Human readable sizes, such as M for mebibytes.
  • 1v: Affects the sorting of digits, hopefully in a positive way.
  • --group-directories-first: self-explanatory

Note, you can use dired-toggle-read-only (C-x C-q) to make a Dired buffer editable to batch-rename.

(use-package dired
  :ensure nil
  :commands dired
  :config (progn
            (require 'dired-x)
            (setq dired-listing-switches "-laGh1v --group-directories-first")

            (defvar malb/unimportant-files
              (mapconcat 'identity '("\\.idx" "\\.run\\.xml$" "\\.bcf$" ".blg$"
                                     "-blx.bib$" "\\.snm$"
                                     "\\.synctex\\.gz$" "\\.tex\\.backup$" "\\.bib\\.backup$"
                                     "\\.fdb_latexmk$" "\\.fls$"

            (push ".brf" dired-latex-unclean-extensions)
            (push ".bmt" dired-latex-unclean-extensions)
            (push ".out" dired-latex-unclean-extensions)
            (push ".nav" dired-latex-unclean-extensions)
            (push ".snm" dired-latex-unclean-extensions)
            (push ".vrb" dired-latex-unclean-extensions)

            (setq dired-garbage-files-regexp malb/unimportant-files
                  dired-omit-files malb/unimportant-files
                  dired-recursive-copies 'always
                  dired-recursive-deletes 'always
                  dired-dwim-target t
                  dired-auto-revert-buffer t
                  wdired-use-dired-vertical-movement 'sometimes)

            (bind-key "C-s" #'dired-isearch-filenames dired-mode-map)

            (add-hook 'dired-mode-hook (defun malb/enable-dired-omit-mode () (dired-omit-mode)))

            ;; For the few times I’m using Dired, I prefer it not spawning an endless amount of
            ;; buffers. In fact, I’d prefer it using one buffer unless another one is explicitly
            ;; created, but you can’t have everything.

            (bind-key "RET" #'dired-find-alternate-file dired-mode-map)
            (bind-key "^"  (lambda () (interactive) (find-alternate-file "..")) dired-mode-map)


            (put 'dired-find-alternate-file 'disabled nil)))

Show dired histories you have visited.

(use-package helm-dired-history
  :after (dired helm))

dired-collapse renders directories with just one file like GitHub does.

(use-package dired-collapse
  :after dired)

dired-narrow to … narrow down dired buffers

(use-package dired-narrow
  :after dired
  :bind (:map dired-mode-map ("/" . dired-narrow)))

dired-subtree to insert subtrees.

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

dired-ranger for easy copy’n’pasting.

(use-package dired-ranger
  :after dired
  :bind (:map dired-mode-map
              ("C-w" . (lambda (arg)
                         (interactive "P")
                         (dired-ranger-copy arg)
                         (bind-key "C-y" #'dired-ranger-move dired-mode-map)))
              ("M-w" . (lambda (arg)
                         (interactive "P")
                         (dired-ranger-copy arg)
                         (bind-key "C-y" #'dired-ranger-paste dired-mode-map)))))

fd-dired run fd to produce dired buffers.

(use-package fd-dired)

Directory Trees

I’m not really sold on either



(use-package neotree
  :commands neotree
  :config (progn
            (setq neo-theme (if (display-graphic-p) 'icons 'arrow)
                  neo-smart-open t)))


(use-package treemacs
  :defer t
  :config (progn
            (setq treemacs-follow-after-init          t
                  treemacs-width                      35
                  treemacs-indentation                2
                  treemacs-collapse-dirs              3
                  treemacs-silent-refresh             t
                  treemacs-sorting                    'alphabetic-desc
                  treemacs-show-hidden-files          t
                  treemacs-is-never-other-window      nil
                  treemacs-goto-tag-strategy          'refetch-index)

            (add-to-list 'treemacs-pre-file-insert-predicates #'treemacs-is-file-git-ignored?)
            (treemacs-follow-mode t)
            (treemacs-filewatch-mode t)))

(use-package treemacs-projectile
  :defer t)

Dired Sidebar


(use-package dired-sidebar
  :commands (dired-sidebar-toggle-sidebar)
  :init (progn (add-hook 'dired-sidebar-mode-hook
                         (defun malb/auto-revert-local ()
                           (unless (file-remote-p default-directory)
  :config (progn (push 'toggle-window-split dired-sidebar-toggle-hidden-commands)
                 (push 'rotate-windows dired-sidebar-toggle-hidden-commands)
                 (setq dired-sidebar-theme 'ascii
                       dired-sidebar-should-follow-file t
                       dired-sidebar-use-term-integration t)))


.ssh/config already contains the appropriate config for persistent sessions so we ask tramp to respect it.

(setq tramp-use-ssh-controlmaster-options nil
      tramp-verbose 1)
(setq vc-ignore-dir-regexp
      (format "%s\\|%s"

(use-package helm-tramp
  :after helm
  :config (progn
            (add-to-list 'helm-commands-using-frame 'helm-tramp)))
(use-package docker-tramp)


(use-package ediff
  :config (progn
            (setq ediff-window-setup-function 'ediff-setup-windows-plain
                  ediff-split-window-function 'split-window-horizontally
                  ediff-diff-options "-w")
            (add-hook 'ediff-after-quit-hook-internal 'winner-undo)
(setq diff-switches "-u")

PDF Diff

(defun malb/diff-pdf (arg)
  "Run pdftotext on two PDFs and open ediff buffer on texts.

Uses baloo to find files."
  (interactive "P")
  (let*  ((baloofn (helm-build-async-source "Baloo"
                     :candidates-process #'helm-baloo-search
                     :candidate-transformer '(helm-baloo-transform helm-skip-boring-files)
                     :keymap helm-generic-files-map
                     :help-message #'helm-generic-file-help-message))

          (left (if arg
                    (read-file-name "Original: ")
                  (helm :sources baloofn :prompt "Original: ")))
          (right (if arg
                     (read-file-name "New: ")
                   (helm :sources baloofn :prompt "New: ")))
          (left-tmp (make-temp-name "/tmp/left"))
          (right-tmp (make-temp-name "/tmp/right")))

    (shell-command (format "pdftotext -layout -nopgbrk \"%s\" \"%s\"" left left-tmp) nil)
    (shell-command (format "pdftotext -layout -nopgbrk \"%s\" \"%s\"" right right-tmp) nil)
    (ediff-files left-tmp right-tmp)))



(use-package plantuml-mode
  :commands (plantuml-mode)
  :init (progn
            (setq plantuml-jar-path "/usr/share/plantuml/plantuml.jar")))


skinparam monochrome true skinparam dpi 150 skinparam backgroundColor transparent skinparam classBackgroundColor transparent skinparam style strictuml skinparam handwritten true

title Example Sequence Diagram activate Client Client -> Server: Session Initiation note right: Client requests new session activate Server Client <– Server: Authorization Request note left: Server requires authentication Client -> Server: Authorization Response note right: Client provides authentication Server –> Client: Session Token note left: Session established deactivate Server Client -> Client: Saves token deactivate Client


calfw for displaying calendars, because why not.

(use-package calfw
  :commands (malb/calendar cfw:open-calendar-buffer)
  :bind (:map cfw:calendar-mode-map ("q" . kill-this-buffer))
  :config (progn
            (setq calendar-week-start-day 1)
            (use-package calfw-cal :ensure t)
            (use-package calfw-ical :ensure t)
            (use-package calfw-org :ensure t)

            ;; Those "
" make everything kaputt
            (defun malb/cfw:ical-url-to-buffer (orig-fun &rest args)
              (let ((buf (apply orig-fun args)))
                (with-current-buffer buf
                  (while (search-forward "
" nil t)
                    (replace-match "" nil t)))
            (advice-add 'cfw:ical-url-to-buffer :around #'malb/cfw:ical-url-to-buffer)))

File Sharing for interfacing with

(use-package transfer-sh)


Interfacing with

(use-package ix)


To list gists, run gist-list:

  • g - reload the gist list from server
  • e - edit current gist description
  • k - delete current gist
  • + - add a file to the current gist
  • - remove a file from the current gist
  • C-x C-s - save a new version of the gist
  • C-x C-w - rename some file

From a dired buffer, you can: @ - make a gist out of marked files (with a prefix, make it private)

gist-region-or-buffer - Post either the current region, or if mark is not set, the current buffer as a new paste at . Copies the URL into the kill ring. With a prefix argument, makes a private paste.

(use-package gist
  :commands (gist-list gist-buffer)
  :config (progn
            (setq gist-ask-for-description t)))

Copy as

(use-package copy-as-format
  :commands (copy-as-format-github

Manage Local Servers

Prodigy is a way of managing services from Emacs. Use it for IMAP idle via imapnotify. For completeness here’s a matching imapnotify config.

var child_process = require('child_process');

function getStdout(cmd) {
  var stdout = child_process.execSync(cmd);
  return stdout.toString().trim();
} = "";
exports.port = 993;
exports.tls = true;
exports.username = "";
exports.password = // whatever needs doing
exports.onNewMail = "mbsync googlemail-minimal";
exports.onNewMailPost = "emacsclient  -e '(mu4e-update-index)'";
exports.boxes = [ "INBOX"];
(use-package prodigy
  :init (progn
            :name 'email
            :ready-message "Checking E-mail using IMAP IDLE. Ctrl-C to shutdown.")

            :name "imapnotify"
            :command "imapnotify"
            :args (list "-c" (expand-file-name ".config/" (getenv "HOME")))
            :tags '(email)
            :kill-signal 'sigkill)

            :name ""
            :cwd (expand-file-name "web/" malb/projects-dir)
            :command "jekyll"
            :args '("serve" "-P" "4001")
            :port 4001
            :tags '(web))

            :name "discrete-subgroup"
            :cwd (expand-file-name "web/lattice-meetings" malb/projects-dir)
            :command "jekyll"
            :args '("serve" "-P" "4002")
            :url ""
            :tags '(web))

            :name "fpylll"
            :cwd malb/projects-dir
            :command ""
            :port 8889
            :stop-signal 'sigkill
            :kill-process-buffer-on-stop t
            :tags '(development))

            :name "sage-devel"
            :cwd (expand-file-name "sage/notebooks" malb/projects-dir)
            :command ""
            :port 8890
            :stop-signal 'sigkill
            :kill-process-buffer-on-stop t
            :tags '(development))

          ;; start imapnotify
          (prodigy-start-service (prodigy-find-service "imapnotify"))))

Page Breaks

Inspired by this post, make use of page break characters by using page-break-lines and helm-pages. (source)

  • Insert a new page break with C-q C-l.
  • Use C-x [ and C-x ] to move backward and forward through pages, respectively.

(hydra source)

(use-package page-break-lines
  :config (progn
            (add-to-list 'page-break-lines-modes 'c-mode)
            (add-to-list 'page-break-lines-modes 'python-mode)
            (diminish 'page-break-lines-mode)))

(use-package helm-pages
  :config (progn
            (defhydra hydra-page-breaks (global-map "C-x")
              ("[" backward-page "backward")
              ("]" forward-page "forward")
              ("M-p" helm-pages "helm" :color blue)
              ("RET" nil "quit"))))


(use-package emms-setup
  :ensure emms)

(use-package emms-player-mplayer
  :ensure nil
  :after emms-setup
  :config (progn (emms-standard)
                 (define-emms-simple-player mplayer '(file url)
                   (regexp-opt '(".ogg" ".mp3" ".wav" ".mpg" ".mpeg" ".wmv" ".wma"
                                 ".mov" ".avi" ".divx" ".ogm" ".asf" ".mkv" "http://" "mms://"
                                 ".rm" ".rmvb" ".mp4" ".flac" ".vob" ".m4a" ".flv" ".ogv" ".pls"))
                   "mplayer" "-slave" "-quiet" "-really-quiet" "-fullscreen")))



(defun malb/switch-default-browser ()
  "switch between default browser and eww"
  (if (string-equal "browse-url-default-browser" browse-url-browser-function)
      (setq browse-url-browser-function 'eww-browse-url)
    (setq browse-url-browser-function 'browse-url-default-browser))
  (message "%s" browse-url-browser-function))


Paste websites to org-mode with org-eww-copy-for-org-mode.

(use-package org-eww
  :ensure nil
  :after org)



(use-package tracking
  :bind (:map tracking-mode-map
              ("<f2> <right>" . tracking-next-buffer)
              ("<f2> <left>" . tracking-previous-buffer))
  :config (progn
            (unbind-key "C-c C-SPC" tracking-mode-map)
            (unbind-key "C-c C-@" tracking-mode-map)))


(use-package weechat
  :commands (weechat-connect malb/weechat)
  :after prodigy
  :config (progn
            (require 'weechat-notifications)
            (require 'weechat-tracking)
            (require 'weechat-latex)
            (require 'weechat-spelling)
            (require 'weechat-image)

            (setq weechat-tracking-types '(:highlight ("general" . :message)))
            (setq weechat-completing-read-function #'completing-read)

            (add-hook 'weechat-mode-hook #'visual-line-mode)
            (add-hook 'weechat-mode-hook #'visual-fill-column-mode)

            (add-hook 'weechat-mode-hook #'malb/enable-company-pcomplete)

            (defun malb/weechat ()
              (weechat-connect "localhost" 9000 nil 'plain)
              (sleep-for 1.0)


(use-package slack
  :defer 30
  :commands (slack-start)
  :init (progn
          (setq slack-buffer-emojify t
                slack-prefer-current-team t
                lui-fill-column 80
                lui-fill-type ""))
  :config (progn
            (add-hook 'slack-mode-hook #'visual-line-mode)
            (add-hook 'slack-mode-hook #'visual-fill-column-mode)

X11 Integration

(setq select-enable-clipboard t                  ; use clipboard for copy and paste
      save-interprogram-paste-before-kill t      ; keep a copy of clipboard stuff around
      mouse-yank-at-point t
      mouse-drag-copy-region t
      select-enable-primary t
      x-select-request-type '(UTF8_STRING COMPOUND_TEXT TEXT STRING))

Use GTK printing interface.

(setq lpr-command "gtklp")

Autosave & Backups

Put autosave files (ie #foo#) in one place, not scattered across the file system.

(defvar malb/autosave-dir
  (expand-file-name "autosaves" user-emacs-directory))

(make-directory malb/autosave-dir t)

(defun auto-save-file-name-p (filename)
  (string-match "^#.*#$" (file-name-nondirectory filename)))

(defun make-auto-save-file-name ()
  (concat malb/autosave-dir
          (if buffer-file-name
              (concat "#" (file-name-nondirectory buffer-file-name) "#")
             (concat "#%" (buffer-name) "#")))))

Put backup files (ie foo~) in one place too. The backup-directory-alist list contains regexp → directory mappings. Filenames matching a regexp are backed up in the corresponding directory. Emacs will mkdir it if necessary.

(defvar backup-dir (expand-file-name "autosaves" user-emacs-directory))
(setq backup-directory-alist (list (cons "." backup-dir)))

UTF-8 Everywhere

(setq locale-coding-system 'utf-8)
(set-terminal-coding-system 'utf-8)
(set-keyboard-coding-system 'utf-8)
(set-selection-coding-system 'utf-8)

(setq current-language-environment "UTF-8")
(setq default-input-method "rfc1345")

(prefer-coding-system 'utf-8)

Large Files

A file is large if it is 32MB in my world.

(setq large-file-warning-threshold 33554432)

vlf for large files.

(use-package vlf-setup
  :ensure vlf)



Use the built-in show-paren-mode to highlight matching parentheses.

(setq show-paren-delay 0.2)
(show-paren-mode 1)

Characterise files with the same name by their path.

(use-package uniquify
  :ensure nil
  :config (setq uniquify-buffer-name-style 'forward))

I hate tabs …

(setq-default indent-tabs-mode nil)

but if they happen to be there (I’m looking at you Go)

(setq-default tab-width 4)

Pressing y or n is sufficent.

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

Kill whole line not just content on C-k.

(setq kill-whole-line t)

In emacs minibuffer prompt, when you press the left arrow key, the cursor will move back all the way over the prompt text. This is annoying because user often will hold down Alt+b to move back by word to edit, and when user starts to type something, emacs will say ’This is read-only’. Then you have to manually move cursor out of the prompt. You can try it now by calling query-replace or shell-command. Here’s how to set the cursor not going into prompt. (source)

(setq minibuffer-prompt-properties (quote (read-only t point-entered minibuffer-avoid-prompt face minibuffer-prompt)))

Make command history persistent (source)

(setq history-length 128
      history-delete-duplicates t
      savehist-save-minibuffer-history t
      savehist-additional-variables '(kill-ring search-ring regexp-search-ring))
(savehist-mode t)

Always prefer to load newer files, instead of giving precedence to the .elc files.

(setq load-prefer-newer t)

Smoother scrolling.

(setq scroll-conservatively 10
      scroll-preserve-screen-position t)

Smoother mouse scrolling.

(setq mouse-wheel-scroll-amount '(1 ((shift) . 1)) ; one line at a time
      mouse-wheel-progressive-speed nil            ; don't accelerate scrolling
      mouse-wheel-follow-mouse 't)                 ; scroll window under mouse

Use kill -pUSR1 to kill Emacs. (source)

(defun malb/quit-emacs-unconditionally ()
  (save-some-buffers t t)

(define-key special-event-map (kbd "<sigusr1>") #'malb/quit-emacs-unconditionally)

Show key combos

(setq echo-keystrokes 0.1)

Diminish eldoc-mode

(diminish 'eldoc-mode)

When saving a file that starts with #!, make it executable.

(add-hook 'after-save-hook

C-u C-SPC C-SCP pops the mark twice

(setq set-mark-command-repeat-pop t)

Skip over duplicates (source):

(defun malb/multi-pop-to-mark (orig-fun &rest args)
  "Call ORIG-FUN until the cursor moves.
Try the repeated popping up to 10 times. ARGS is passed through."
  (let ((p (point)))
    (dotimes (i 10)
      (when (= p (point))
        (apply orig-fun args)))))

(advice-add 'pop-to-mark-command :around #'malb/multi-pop-to-mark)

Diminish abbrev-mode

(diminish 'abbrev-mode)

In Emacs 25, Isearch can find a wide range of Unicode characters (like á, ⓐ, or 𝒶) when you search for ASCII characters (a in this example). To enable this feature, set the variable search-default-mode to char-fold-to-regexp. (source)

(setq search-default-mode 'char-fold-to-regexp)

Don’t error when killing read-only text.

(setq kill-read-only-ok t)


Add a function for renaming the file being edited (source)

(defun malb/rename-current-buffer-file ()
  "Renames current buffer and file it is visiting."
  (let ((name (buffer-name))
        (filename (buffer-file-name)))
    (if (not (and filename (file-exists-p filename)))
        (error "Buffer '%s' is not visiting a file!" name)
      (let ((new-name (read-file-name "New name: " filename)))
        (if (get-buffer new-name)
            (error "A buffer named '%s' already exists!" new-name)
          (rename-file filename new-name 1)
          (rename-buffer new-name)
          (set-visited-file-name new-name)
          (set-buffer-modified-p nil)
          (message "File '%s' successfully renamed to '%s'"
                   name (file-name-nondirectory new-name)))))))

Copy filename to clipboard

(defun malb/kill-buffer-file-name ()
  "Show current buffer's filename in the echo area and add it to the kill ring."
  (let ((buffer-file-name (buffer-file-name)))
    (if (null buffer-file-name)
        (message "Buffer %s is not associated with a file." (buffer-name))
      (message "%s" (kill-new buffer-file-name)))))

(defun malb/kill-buffer-file-basename ()
  "Show the buffers base name in the echo area and add it to the kill ring."
  (let ((bufer-file-name (buffer-file-name)))
    (if (not (null buffer-file-name))
        (message "%s" (kill-new (file-name-nondirectory buffer-file-name)))
      (error "Buffer %s is not associated with a file" (buffer-name)))))


(defun malb/sudo-edit (&optional arg)
  "Edit currently visited file as root.

With a prefix ARG prompt for a file to visit.
Will also prompt for a file to visit if current
buffer is not visiting a file."
  (interactive "P")
  (if (or arg (not buffer-file-name))
      (find-file (concat "/sudo:root@localhost:"
                         (ido-read-file-name "Find file(as root): ")))
    (find-alternate-file (concat "/sudo:root@localhost:" buffer-file-name))))

Run a command in a named compile buffer (source)

(defun malb/named-compile (command)
    (let ((command (eval compile-command)))
      (if (or compilation-read-command current-prefix-arg)
          (compilation-read-command command)
  (compilation-start command nil (lambda (&rest args)
                                   (format "*compilation %s*" command))))


We don’t want the scratch buffer to be killed ever.

(use-package unkillable-scratch
  :config (progn
            (unkillable-scratch 1)))

Get a scratch for every mode quickly

(use-package scratch)

Make scratch buffer an org-mode buffer.

(setq initial-major-mode 'org-mode
      initial-scratch-message "\
This buffer is for notes you don't want to save, and for Lisp evaluation.
If you want to create a file, visit that file with C-x C-f,
then enter the text in that file's own buffer.

#+BEGIN_SRC emacs-lisp



Clickable URLs

Make links in comments and string clickable

(use-package goto-addr
  :hook ((compilation-mode . goto-address-mode)
         (prog-mode . goto-address-prog-mode)
         (markdown-mode . goto-address-mode)
         (eshell-mode . goto-address-mode)
         (shell-mode . goto-address-mode))

  :bind (:map goto-address-highlight-keymap
              ("C-c C-o" . goto-address-at-point)
              ("<RET>" . newline))

  :commands (goto-address-prog-mode goto-address-mode))

Emacs from Browsers

Emacs from Chrome

(use-package edit-server
  :config (progn
            (setq edit-server-default-major-mode 'markdown-mode)))

Grab the link in the current browser tab

(use-package grab-x-link)


Transparent encryption and decryption.

(use-package epa-file
  :ensure nil
  :config (epa-file-enable))

Memory Usage

(use-package memory-usage)


(use-package alert
  :config (setq alert-default-style 'libnotify))

Upcase, Downcase, Capitalise

(bind-key "M-l" #'downcase-dwim)
(bind-key "M-c" #'capitalize-dwim)
(bind-key "M-u" #'upcase-dwim)



(use-package abgaben
  :after mu4e
  :config (progn
            (setq abgaben-org-file malb/abgaben-org-file
                  abgaben-root-folder malb/abgaben-folder
                  abgaben-heading "Assignments"
                  abgaben-points-re "marks: ?\\([0-9.]*\\)/\\([0-9.]*\\)")

            (add-to-list 'mu4e-view-attachment-actions
                         '("gsave assignment" . abgaben-capture-submission) t)))

File Types

Log Files

Using logview mode.

(use-package logview
  :commands (logview-mode)
  :config (setq logview-auto-revert-mode 'auto-revert-tail-mode))

PCAP Files

Wireshark et al. outputs (source)

(use-package pcap-mode)

ELF Files

List symbols in .so and .a files (source)

(use-package elf-mode
  :mode (("\\.so\\'"  . elf-mode)
         ("\\.a\\'"   . elf-mode)))

Sobj Files


(define-derived-mode sobj-mode python-mode "sobj"
  "Major mode for viewing sobj files."
  (delete-region (point-min) (point-max))
  (call-process "python" nil t t "-c" (format (concat "# -*- coding: utf-8 -*-\n"
                                                      "import pickle\n"
                                                      "import pprint\n"
                                                      "pprint.PrettyPrinter(indent=4).pprint(pickle.load(open('%s', 'rb')))")
  (set-buffer-modified-p nil)

(add-to-list 'auto-mode-alist '("\\.sobj\\'" . sobj-mode))

YaML Files

(use-package yaml-mode)

Docker Filers

(use-package dockerfile-mode
  :mode ("Dockerfile\\." . dockerfile-mode))


Nov.el is surprisingly useful.

(use-package nov
  :mode ("\\.epub$" . nov-mode)
  :config (progn
            (add-hook 'nov-mode-hook #'visual-fill-column-mode)))

Theme & Look

Frame Title

Display buffer name in frame titles (source)

(setq frame-title-format
      '("" (:eval (replace-regexp-in-string "^ +" "" (buffer-name)))
        " - " invocation-name))

Stripe Buffer (source)

Stripe Buffer makes it vastly easier to read tables and dired buffers. We apply this patch for performance.

(use-package stripe-buffer
  :config (progn
            (add-hook 'dired-mode-hook #'turn-on-stripe-buffer-mode)
            (add-hook 'org-mode-hook   #'turn-on-stripe-table-mode)))

Dim other Buffers

(use-package dimmer
  :commands dimmer-mode
  :config (setq dimmer-percent 0.3))


We use Spaceline which customises Powerline. (source)

(use-package spaceline-config
  :ensure spaceline
  :after (eyebrowse helm)
  :config (progn

            ;; patch spaceline to always show eyebrowse window
            ;; otherwise it screws with our colours: mode-line-buffer-id
            (spaceline-define-segment workspace-number
              "The current workspace name or number. Requires `eyebrowse-mode' to be
              (when (bound-and-true-p eyebrowse-mode)
                (let* ((num (eyebrowse--get 'current-slot))
                       (tag (when num (nth 2 (assoc num (eyebrowse--get 'window-configs)))))
                       (str (if (and tag (< 0 (length tag)))
                              (when num (int-to-string num)))))
                  (or (when spaceline-workspace-numbers-unicode
                        (spaceline--unicode-number str))
                      (propertize str 'face 'bold)))))

            (setq spaceline-workspace-numbers-unicode nil)
            (spaceline-spacemacs-theme 'python-venv)
            (setq powerline-default-separator 'arrow)


Use both solarized and zenburn.

Sometimes use a solarized patched to be less yellow by changing


The result looks like this:


Default theme

(use-package solarized
  :ensure solarized-theme
  :config (progn
            (setq solarized-use-variable-pitch nil
                  solarized-high-contrast-mode-line nil
                  solarized-height-minus-1 1.0
                  solarized-height-plus-1  1.0
                  solarized-height-plus-2  1.0
                  solarized-height-plus-3  1.0
                  solarized-height-plus-4  1.0
                  solarized-use-less-bold nil
                  solarized-emphasize-indicators t
                  solarized-scale-org-headlines nil
                  x-underline-at-descent-line t)

            (defun malb/solarized-light-theme (&optional arg)
              "Load solarized-light and my additions."
              (interactive "P")
              (if arg
                    (disable-theme 'solarized-light-malb)
                    (disable-theme 'solarized-light))
                  (load-theme 'solarized-light t)
                  (load-theme 'solarized-light-malb t))))))

For those Long Nights

(use-package zenburn-theme

(defun malb/zenburn-theme (&optional arg)
  "Load zenburn and my additions."
  (interactive "P")
  (if arg
        (disable-theme 'zenburn-malb)
        (disable-theme 'zenburn))
      (load-theme 'zenburn t)
      (load-theme 'zenburn-malb t))))

For those Higher Contrast Needs

(use-package paper-theme
(use-package one-themes

Switch between Light and Dark Theme

(defun malb/toggle-light-dark ()
  "Switch between light and dark theme"
  (if (custom-theme-enabled-p 'solarized-light)
        (malb/solarized-light-theme t)
      (malb/zenburn-theme t)

Load Theme

(defvar malb/theme-loaded nil)

(defun malb/load-theme (frame)
  (when (not malb/theme-loaded)
    (select-frame frame)
    (setq malb/theme-loaded t)))

(if (daemonp)
    (add-hook 'after-make-frame-functions #'malb/load-theme)
  (malb/load-theme (selected-frame)))


Better unicode font support.

(use-package unicode-fonts)


(use-package all-the-icons)

Cousine is wide font which means glyphs rendered in different fonts (when Cousine doesn’t have the matching glyph) are too high. Thus, we scale those other fonts down when Cousine is the default face. Note that face-font-rescale-alist is frame specific, thus the hook.

(defun malb/fonts (frame)
  (with-selected-frame frame
    (set-default-font "Cousine-18")
    (add-to-list 'face-font-rescale-alist (cons (font-spec :family "DejaVu Sans Mono") 0.9) t)
    (add-to-list 'face-font-rescale-alist (cons (font-spec :family "FontAwesome") 0.85) t)
    (add-to-list 'face-font-rescale-alist (cons (font-spec :family "Material Icons") 0.7) t)
    (add-to-list 'face-font-rescale-alist (cons (font-spec :family "Free-Symbola") 0.9) t)
    (add-to-list 'face-font-rescale-alist (cons (font-spec :family "file-icons") 0.75) t)
    (add-to-list 'face-font-rescale-alist (cons (font-spec :family "Noto Sans Symbols") 0.75) t)))

(add-hook 'after-make-frame-functions #'malb/fonts)
(malb/fonts (selected-frame)) ;; initial frame


Set transparency because reasons (source)

(defun malb/transparency (value)
  "Sets the transparency of the frame window. 0=transparent/100=opaque"
  (interactive "nTransparency Value 0 - 100 opaque:")
  (set-frame-parameter (selected-frame) 'alpha value))

Rainbow Mode

Colourise colours or names in buffers (source)

(use-package rainbow-mode
  :config (progn
            (add-hook 'emacs-lisp-mode-hook #'rainbow-mode))
  :diminish rainbow-mode)


Send a little beacon signal after jumps to make it easier to find the cursor. (source)

(use-package beacon
  :diminish beacon-mode
  :config (progn

            (defun malb/beacon-disable-hl-line-mode ()
              (bound-and-true-p hl-line-mode))

            (add-to-list 'beacon-dont-blink-major-modes 'eshell-mode)
            (add-to-list 'beacon-dont-blink-commands 'mwheel-scroll)

            (add-hook 'beacon-dont-blink-predicates

            ;; (beacon-mode t)

Vim-like Empty Line Indicator


(define-fringe-bitmap 'tilde [0 0 0 113 219 142 0 0] nil nil 'center)
(setcdr (assq 'empty-line fringe-indicator-alist) 'tilde)
(set-fringe-bitmap-face 'tilde 'font-lock-comment-face)
(setq-default indicate-empty-lines t)

Text Size

Easily change the text size:

(defun malb/global-text-scale-increase ()
  "Globally increase font size."
  (set-face-attribute 'default nil :height (+ (face-attribute 'default :height) 5)))

(defun malb/global-text-scale-decrease ()
  "Globally decrease font size."
  (set-face-attribute 'default nil :height (- (face-attribute 'default :height) 5)))

(bind-key "C-=" #'text-scale-increase)
(bind-key "C--" #'text-scale-decrease)
(bind-key "C-M-=" #'malb/global-text-scale-increase)
(bind-key "C-M--" #'malb/global-text-scale-decrease)