My Emacs Config
Header
;;; 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 helmmalb/documents-dir- documentsmalb/inbox-org- this is where I store tasksmalb/literature-dirs- PDFs of papersmalb/literature-notes-dir- notes on papersmalb/mu4e-name-replacements- e-mail name replacementsmalb/org-files-dir- org files go heremalb/org-files- org filesmalb/org-mode-ics- icalendar filemalb/paradox-github-token- github loginmalb/private-org- this is where I store tasksmalb/projectile-ignored-projects- ignored projectsmalb/projects-dir- a super-repository of which all of my projects are subprojectsmalb/sage-executable- full path of Sage executablemalb/sync-dir- documents that are synchronisedmalb/deft-directory- where deft files livemalb/dropbox-dir- where Dropbox is mountedmalb/theme- theme choice
We collect our own customisations in the malb group.
(defgroup malb nil
"malb's personal config"
:group 'convenience)TLS
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 `("https://wrong.host.badssl.com/"
;; "https://self-signed.badssl.com/")
;; 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 "https://badssl.com"
;; (lambda (retrieved) t))))Utilities
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" . "https://elpa.gnu.org/packages/")
("melpa" . "https://melpa.org/packages/")))Get the package manager going, but do not autoload packages.
(package-initialize)Use use-package to keep our configuration readable.
(setq use-package-verbose t)
(require 'use-package)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 rfilters by regexp (occur)f udisplay only packages with upgradesf kfilters by keywordf cclear filter
- Hit
hto 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))Fullscreen
Allow pixel-wise scaling
(setq frame-resize-pixelwise t)Maximise the window as soon as possible
(add-to-list 'default-frame-alist '(fullscreen . maximized))Startup
(setq inhibit-startup-screen t)Disable Clutter
(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))Memory
We have RAM, lots of it.
(setq global-mark-ring-max 256
mark-ring-max 256
kill-ring-max 256)(source)
(defun malb/disable-garbage-collection ()
"Disable garbage collection."
(setq gc-cons-threshold most-positive-fixnum))
(defun malb/enable-garbage-collection ()
"Reset garbage collection to small-ish limit."
(setq gc-cons-threshold 1048576))
(add-hook 'minibuffer-setup-hook #'malb/disable-garbage-collection)
(add-hook 'minibuffer-exit-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.
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
:ensure t
:config (progn
(setq key-chord-one-key-delay 0.2
key-chord-two-keys-delay 0.1)
(key-chord-mode 1)))Window Management
Splitting
When splitting windows open the previous buffer in it.
(defun malb/vsplit-last-buffer ()
"Split the window vertically and display the previous buffer."
(interactive)
(split-window-vertically)
(other-window 1 nil)
(switch-to-next-buffer))
(defun malb/hsplit-last-buffer ()
"Split the window horizontally and display the previous buffer."
(interactive)
(split-window-horizontally)
(other-window 1 nil)
(switch-to-next-buffer))
(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
(split-window-right)))
(and (window-splittable-p window)
;; Split window vertically.
(with-selected-window window
(split-window-below)))
(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
(split-window-below))))))))
(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
:ensure t
:bind (("C-x 1" . zygospore-toggle-delete-other-windows)))Multiple window configurations
eyebrowse is a simple-minded way of managing window configuration.

(use-package eyebrowse
:ensure t
:diminish eyebrowse-mode
:after latex-preview-pane
: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"
(interactive)
(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))))))
(defun malb/close-latex-preview-pane-before-eyebrowse-switch ()
;; latex-preview-pane uses window-parameters which are not preserved by eyebrowse, so
;; we close the preview pane before switching, it will be regenerated when we edit the
;; TeX file.
(when (lpp/window-containing-preview)
(delete-window (lpp/window-containing-preview))))
(add-to-list 'eyebrowse-pre-window-switch-hook
#'malb/close-latex-preview-pane-before-eyebrowse-switch)))Switching
ace-window for switching windows, but we only call it as a subroutine from a hydra below.

(use-package ace-window
:ensure t
:config (progn
(setq aw-keys '(?a ?s ?d ?f ?j ?k ?l)
aw-dispatch-always nil
aw-dispatch-alist
'((?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."
(interactive)
(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 '("\\`\\*helm flycheck\\*\\'"
"\\`\\*Flycheck errors\\*\\'"
"\\`\\*helm projectile\\*\\'"
"\\`\\*Helm all the things\\*\\'"
"\\`\\*Helm Find Files\\*\\'"
"\\`\\*Help\\*\\'"
"\\`\\*ielm\\*\\'"
"\\`\\*Synonyms List\\*\\'"
"\\`\\*anaconda-doc\\*\\'"
"\\`\\*Google Translate\\*\\'"
"\\` \\*LanguageTool Errors\\* \\'"
"\\`\\*Edit footnote .*\\*\\'"
"\\`\\*TeX errors*\\*\\'"
"\\`\\*mu4e-update*\\*\\'"
"\\`\\*prodigy-.*\\*\\'"
"\\`\\*Org Export Dispatcher\\*\\'"
"\\`\\*Helm Swoop\\*\\'"
"\\`\\*Backtrace\\*\\'"
"\\`\\*Messages\\*\\'"
"\\`\\*Calendar\\*\\'"))Closing bottom most 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."
(interactive)
(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."
(interactive)
(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 is no compilation window, open one at the bottom, spanning the complete width of the frame. Otherwise, reuse existing window. In the former case, if there was no error the window closes automatically.
(add-to-list 'display-buffer-alist
`(,(rx bos "*compilation*" eos)
(display-buffer-reuse-window
display-buffer-in-side-window)
(reusable-frames . visible)
(side . bottom)
(window-height . 0.3)))(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 specific 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)))
nil))
(t
(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)) ;; emacsclient
(throw 'other-frame frame)
nil)))))
(if other-frame
(select-frame-set-input-focus other-frame)
(malb/mail))))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)
nil)))))
(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)))
(cond
;; this is a mu4e buffer
((or
(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))))
(malb/switch-away-from-mu4e)
nil ;; pass control back to display-buffer-alist
)
;; just hand back control to diplay-buffer-alist
(t nil))
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’."
(interactive
(find-file-read-args "Find file: "
(confirm-nonexistent-file-or-buffer)))
(let ((value (find-file-noselect filename nil nil wildcards)))
(if (string-match "mu4e" (frame-parameter nil 'name))
(progn
(if (listp value)
(mapcar display-buffer (nreverse value))
(display-buffer value)
(switch-to-buffer value)))
(progn
(if (listp value)
(mapcar switch-to-buffer (nreverse value))
(switch-to-buffer value))))))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)))Apply special buffer rules
; (setq display-buffer-alist nil)
(dolist (name malb/popup-windows)
(add-to-list 'display-buffer-alist
`(,name
(malb/frame-dispatch
display-buffer-reuse-window
display-buffer-in-side-window)
(reusable-frames . visible)
(side . bottom)
;; height only applies when golden-ratio-mode is off
(window-height . 0.3))) t)
(add-to-list 'display-buffer-alist '(".*" (malb/frame-dispatch)) t)Golden ratio
Golden Ratio for resizing windows. We also inhibit when modes in golden-ratio-exclude-modes are active any window, not only in the target window.
(use-package golden-ratio
:ensure t
:after ispell
:diminish golden-ratio-mode
:config (progn
(require 'ispell)
(setq golden-ratio-adjust-factor 1.0
golden-ratio-exclude-modes '(imenu-list-major-mode
eshell-mode
pdf-view-mode
mu4e-view-mode
mu4e-main-mode
mu4e-headers-mode
calendar-mode
compilation-mode))
(defun malb/golden-ratio-inhibit-functions ()
(cond
;; which function is exempt
((bound-and-true-p which-key--current-page-n))
;; helm is exempt
((bound-and-true-p helm-alive-p))
;; embrace is exempt
((eq this-command 'embrace-commander))
;; if ispell is running let's not golden ratio
((get-buffer ispell-choices-buffer))
;; any olivetti mode buffer disables gr
;; we also block if any buffer has inhibit major-mode not only target
(t (catch 'inhibit
(dolist (window (window-list))
(with-current-buffer (window-buffer window)
(if (or (memq major-mode golden-ratio-exclude-modes)
(bound-and-true-p olivetti-mode))
(throw 'inhibit t))))
(throw 'inhibit nil)))))
(setq golden-ratio-exclude-buffer-regexp malb/popup-windows)
(setq golden-ratio-inhibit-functions '(malb/golden-ratio-inhibit-functions))))Dedicated mode
(source)
(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")
minor-mode-alist))))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 "z`" #'malb/hydra-window/body)
(bind-key "C-x o" #'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
:ensure t)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
:ensure t)Jumping Around
(source)
See Emacs Rocks #10 which is on ace-jump-mode which inspired avy.
(use-package avy
:ensure t
: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-modeorhelp-modeorwoman-modeororg-modeoreww-modeorcompilation-modebuffer, 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
:ensure t
:config (ace-link-setup-default))Jumping through edit points
Use goto-chg to jump through edit points (source)
(use-package goto-chg
:ensure t
: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
(source)
(use-package bm
:ensure t
:bind (("C-c j b ." . bm-next)
("C-c j b ," . bm-previous)
("C-c j b SPC" . bm-toggle)))Snippets
YASnippet is a template system for Emacs. It allows you to type an abbreviation and automatically expand it into function templates. (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
:ensure t
:diminish yas-minor-mode
:config (progn
(yas-global-mode)
(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)))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
:ensure t
: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."
(interactive)
(let ((is-yasnippet-on (not (cond ((functionp yas-dont-activate)
(funcall yas-dont-activate))
((consp yas-dont-activate)
(some #'funcall yas-dont-activate))
(yas-dont-activate))))
(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)
(mapc
(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
Auto Completion
Use company-mode for auto-completion.

(use-package company
:ensure t
:bind (("M-/" . company-complete))
: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-lighter-base "\xf04f")
(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
:ensure t
:config (company-quickhelp-mode 1))company-flx for flx matching with capf.
(use-package company-flx
:disabled ;; performance
:ensure t
:config (company-flx-mode t))Use company-pcomplete because of this bug.
(use-package company-pcomplete
:config (defun malb/enable-company-pcomplete ()
(set (make-local-variable 'company-backends)
(append (list #'company-pcomplete) company-backends))))C/C++
For C/C++ use company-semantic which can be a bit tricky to set up, but works well once that is done. Also use company-c-headers.
(use-package company-c-headers
:ensure t
:config (progn
(defun malb/ede-object-system-include-path ()
"Return the system include path for the current buffer."
(when ede-object
(ede-system-include-path ede-object)))
(setq company-c-headers-path-system
#'malb/ede-object-system-include-path)
(add-to-list 'company-backends #'company-c-headers)))Python
For Python use company-anaconda.
(use-package company-anaconda
:ensure t
:config (add-to-list 'company-backends #'company-anaconda))LaTeX
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
:ensure t)
(use-package company-auctex
:ensure t
:config (progn
(defun company-auctex-labels (command &optional arg &rest ignored)
"company-auctex-labels backend"
(interactive (list 'interactive))
(case command
(interactive (company-begin-backend 'company-auctex-labels))
(prefix (company-auctex-prefix "\\\\.*ref{\\([^}]*\\)\\="))
(candidates (company-auctex-label-candidates arg))))
(add-to-list 'company-backends
'(company-auctex-macros
company-auctex-environments
company-math-symbols-unicode
company-math-symbols-latex))
(add-to-list 'company-backends #'company-auctex-labels)
(add-to-list 'company-backends #'company-auctex-bibs)))Shell
(use-package company-shell
:ensure t
:config (progn
(setq company-shell-modes '(sh-mode shell-mode))
(add-to-list 'company-backends 'company-shell)))YaSnippet
Add YasSippet support for all company backends. (source)
Note: Do this at the end of company-mode config.
(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)))
backend
(append (if (consp backend) backend (list backend))
'(:with company-yasnippet))))
(setq company-backends
(mapcar #'malb/company-mode/backend-with-yas company-backends))All the words
Enable/disable company completion from ispell dictionaries (source)
(defun malb/toggle-company-ispell ()
(interactive)
(cond
((member '(company-ispell :with company-yasnippet) company-backends)
(setq company-backends (delete '(company-ispell :with company-yasnippet) company-backends))
(add-to-list 'company-backends '(company-dabbrev :with company-yasnippet) t)
(message "company-ispell disabled"))
(t
(setq company-backends (delete '(company-dabbrev :with company-yasnippet) company-backends))
(add-to-list 'company-backends '(company-ispell :with company-yasnippet) t)
(message "company-ispell enabled!"))))
(defun malb/company-ispell-setup ()
;; @see https://github.com/company-mode/company-mode/issues/50
(when (boundp 'company-backends)
(make-local-variable 'company-backends)
(setq company-backends (delete '(company-dabbrev :with company-yasnippet) company-backends))
(add-to-list 'company-backends '(company-ispell :with company-yasnippet) t)
;; https://github.com/redguardtoo/emacs.d/issues/473
(if (and (boundp 'ispell-alternate-dictionary)
ispell-alternate-dictionary)
(setq company-ispell-dictionary ispell-alternate-dictionary))))Tab DWIM
yas-expandis run first and does what it has to, then it callsmalb/indent-fold-or-complete.- This function then hopefully does what I want:
a. if a region is active, just indent b. if we’re looking at a space after a non-whitespace character, we try some company-expansion c. If
hs-minor-modeoroutline-minor-modeis active, try those next d. otherwise call whatever would have been called otherwise.
(defun malb/indent-fold-or-complete (&optional arg)
(interactive "P")
(cond
;; if a region is active, indent
((use-region-p)
(indent-region (region-beginning)
(region-end)))
;; 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:]]")
(not (looking-back "^")))
(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:]]")
(not (looking-back "^")))
(cond
((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
(t
(let ((fn (or (lookup-key (current-local-map) (kbd "TAB"))
'indent-for-tab-command)))
(if (not (called-interactively-p 'any))
(fn arg)
(setq this-command fn)
(call-interactively fn))))))
(defun malb/toggle-fold ()
(interactive)
(cond ((eq major-mode 'org-mode)
(org-force-cycle-archived))
((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)))))
(bind-key "<tab>" #'malb/indent-fold-or-complete)
(bind-key "C-<tab>" #'malb/toggle-fold)Helm
Helm is incremental completion and selection narrowing framework for Emacs.

See A Package in a league of its own: Helm for a nice introduction.
| Combo | Command |
|---|---|
C-w | yanks word at point |
M-n | yanks symbol at point |
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.
(interactive)
(unless helm-source-buffers-list
(setq helm-source-buffers-list
(helm-make-source "Buffers" 'helm-source-buffers)))
(helm-other-buffer
(append
(if (projectile-project-p)
'(helm-source-projectile-buffers-list
helm-source-buffers-list)
'(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-projectile-recentf-list
helm-source-recentf)
'(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-files-in-current-dir
helm-source-locate
helm-source-bookmarks
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
:ensure 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))
:config (progn
(require 'helm-config)
(require 'helm-for-files)
(require 'helm-bookmark)
(bind-key "C-c h" #'helm-command-prefix)
(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-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")
(setq 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-google-suggest-use-curl-p 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)
;; rebind tab to do persistent action
(bind-key "<tab>" #'helm-execute-persistent-action helm-map)
;; make TAB works in terminal
(bind-key "C-i" #'helm-execute-persistent-action helm-map)
;; list actions using C-z
(bind-key "C-z" #'helm-select-action helm-map)
;; see https://github.com/emacs-helm/helm/commit/1de1701c73b15a86e99ab1c5c53bd0e8659d8ede
(assq-delete-all 'find-file helm-completing-read-handlers-alist)))Helm Flx
helm-flx implements intelligent helm fuzzy sorting, provided by flx.
(use-package helm-flx
:ensure t
:disabled
:config (progn
;; these are helm configs, but they kind of fit here nicely
(setq helm-flx-for-helm-find-files t
helm-flx-for-helm-locate t)
(helm-flx-mode +1)))Note: disabled because it slows down helm after a day or so of Emacs uptime.
Helm Ring
helm-ring makes the kill ring actually useful, let’s use it.
(use-package helm-ring
: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.
(defun malb/helm-swoop-pre-fill ()
(thing-at-point 'symbol)) ;; I’m going back and forth what I prefer
(setq malb/helm-swoop-ignore-major-mode
'(dired-mode paradox-menu-mode doc-view-mode pdf-view-mode mu4e-headers-mode org-mode markdown-mode latex-mode ein:notebook-multilang-mode))
(defun malb/swoop-or-search ()
(interactive)
(if (or (> (buffer-size) 1048576) ;; helm-swoop can be slow on big buffers
(memq major-mode malb/helm-swoop-ignore-major-mode))
(isearch-forward)
(helm-swoop)))
(use-package helm-swoop
:ensure t
:bind (("C-c o" . helm-multi-swoop-org)
("C-s" . malb/swoop-or-search)
("C-M-s" . helm-multi-swoop-all))
:config (progn
(setq helm-swoop-pre-input-function #'malb/helm-swoop-pre-fill
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)
;; https://emacs.stackexchange.com/questions/28790/helm-swoop-how-to-make-it-behave-more-like-isearch
(defun malb/helm-swoop-C-s ()
(interactive)
(if (boundp 'helm-swoop-pattern)
(if (equal helm-swoop-pattern "")
(previous-history-element 1)
(helm-next-line))
(helm-next-line)))
(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
:ensure t
:config (setq helm-ag-base-command "ag --nocolor --nogroup"
helm-ag-command-option "--all-text"
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."
(interactive)
(malb/helm-ag malb/projects-dir))
(defun malb/helm-ag-literature ()
"run helm-ag in projects directory"
(interactive)
(malb/helm-ag (file-name-as-directory (car malb/literature-dirs))))Helm Themes
Switch themes with helm.
(use-package helm-themes
:ensure t)Helm Descbinds
(use-package helm-descbinds
:ensure t
:bind ("C-h b" . helm-descbinds)
:init (fset 'describe-bindings 'helm-descbinds))Helm Locate
(use-package helm-locate
:config (progn
(setq helm-locate-command
(let ((databases (concat
"/var/lib/mlocate/mlocate.db:"
(expand-file-name ".locate.db" (getenv "HOME")))))
(concat "locate -d " databases " %s -e --regex %s")))
(helm-add-action-to-source "Attach to E-mail" #'mml-attach-file helm-source-locate)))Helm YaSnippet
(use-package helm-c-yasnippet
:ensure t
:bind ("C-c h y" . helm-yas-complete)
:config (progn
(setq helm-yas-space-match-any-greedy t)))Helm Org Rifle
(use-package helm-org-rifle
:ensure t
:config (progn
(defun malb/helm-org-rifle-agenda-files (arg)
(interactive "p")
(let ((current-prefix-arg nil))
(cond
((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)))))))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
:ensure t
:bind ("C-c h g" . helm-google)
:config
(progn
(add-to-list 'helm-google-actions
'("Copy URL" . (lambda (candidate)
(let ((url
(replace-regexp-in-string
"https://.*q=\\(.*\\)\&sa=.*"
"\\1" candidate)))
(kill-new url)))) t)
(add-to-list 'helm-google-actions
'("Org Store Link" . (lambda (candidate)
(let ((title (car (split-string candidate "[\n]+")))
(url
(replace-regexp-in-string
"https://.*q=\\(.*\\)\&sa=.*"
"\\1" candidate)))
(push (list url title) org-stored-links)))) t)))Helm Semantic, Imenu, Org
(defun malb/helm-in-buffer ()
"the right kind™ of buffer menu"
(interactive)
(if (eq major-mode 'org-mode)
(call-interactively #'helm-org-in-buffer-headings)
(call-interactively #'helm-semantic-or-imenu)))History
Recent Files
Don’t include boring or remote stuff in list of recently visited files.
(use-package recentf
:config (progn
(setq recentf-max-saved-items 64
recentf-exclude (list "COMMIT_EDITMSG"
"~$"
"/tmp/"
"/ssh:"
"/sudo:"
"/scp:"
(expand-file-name malb/mu4e-maildir)))
(loop for ext in helm-boring-file-regexp-list
do (add-to-list 'recentf-exclude ext t))
))Saveplace
(use-package saveplace
:config (setq-default save-place t
save-place-file (locate-user-emacs-file "places" ".emacs-places")))IMenu
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
:ensure t
: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")
(cond
((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))))
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
(use-package imenu-list
:ensure t
:config (setq imenu-list-position 'left
imenu-list-size 0.15
imenu-list-auto-resize nil))Parenthesis
See here for an introduction to smartparens.
Some of the config below is stolen from hlissner’s emacs.d.
(use-package smartparens
:ensure t
:diminish smartparens-mode
:config (progn
(require 'smartparens-config)
(require 'smartparens-latex)
(smartparens-global-mode t)
(setq sp-autodelete-wrap t)
(setq sp-cancel-autoskip-on-backward-movement nil)
(setq-default sp-autoskip-closing-pair t) (setq sp-autoescape-string-quote nil) ;; don't escape quotes in strings
(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 "M-<right>" 'sp-next-sexp smartparens-mode-map)
(bind-key "M-<left>" 'sp-previous-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 "M-d" 'sp-kill-sexp smartparens-mode-map)
(bind-key "M-S-<backspace>" 'sp-backward-unwrap-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 "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)
(save-excursion
(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
markdown-mode
python-mode
cython-mode)
"`" nil :unless '(sp-point-before-word-p
sp-point-after-word-p
sp-point-before-same-p))
;; https://github.com/Fuco1/smartparens/issues/652#issuecomment-250518705
(defun malb/latex-replace-dollar (_id action _context)
(when (eq action 'wrap)
(sp-get sp-last-wrapped-region
(let ((at-beg (= (point) :beg-in)))
(save-excursion
(goto-char :beg)
(delete-char :op-l)
(insert "\\("))
(save-excursion
(goto-char :end-in)
(delete-char :cl-l)
(insert "\\)"))
(setq sp-last-wrapped-region
(sp--get-last-wraped-region
:beg :end "\\(" "\\)"))
(goto-char (if at-beg (1+ :beg-in) :end))))))
(sp-with-modes
'(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)))
;; https://github.com/millejoh/emacs-ipython-notebook/issues/172
(add-to-list 'sp-ignore-modes-list 'ein:notebook-multilang-mode)
(sp-with-modes
'org-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)
:post-handlers '(("[d1]" "SPC"))))))Automatically insert closing delimiter
(use-package syntactic-close
:ensure t
:bind ("C-c x c" . syntactic-close))Editing
Dragging lines around
(source)
(use-package drag-stuff
:ensure t
: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
(source)
Tip: Did you know that Emacs has undo in a region?
(use-package undo-tree
:ensure t
:diminish undo-tree-mode
:config (progn
(global-undo-tree-mode)
(setq undo-tree-visualizer-timestamps t)
(setq undo-tree-visualizer-diff t))
)Highlight last edits
(source)
(use-package volatile-highlights
:ensure t
:commands volatile-highlights-mode
:config (volatile-highlights-mode t)
:diminish volatile-highlights-mode)Zap up to char
Kill everything up to character, e.g. if we have “Lorem| ipsum” typing M-z u would leave us with “Lorem|um”.
(use-package avy-zap
:ensure t
:bind ("M-z" . avy-zap-up-to-char-dwim))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
:ensure t
:config (progn
(diminish 'focus-autosave-local-mode " ♻")))Regexp
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-steroids
:ensure t)
(use-package visual-regexp
:ensure t
:bind (("C-c m" . vr/mc-mark)
("M-%" . vr/query-replace)
("C-S-s" . vr/isearch-forward)
("C-S-r" . vr/isearch-backward)))Multiple cursors
Multiple cursors are awesome.

Also see Emacs Rocks #13, which is on multiple-cursors.
Commands are bound to C-x m …
(use-package multiple-cursors
:ensure t
: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
:ensure t
: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
:ensure t
:bind ("C-`" . er/expand-region))Embrace
(use-package embrace
:ensure t
:config (progn
(bind-key "M-`" #'embrace-commander)
(add-hook 'org-mode-hook #'embrace-org-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.
(source)
(use-package wrap-region
:ensure t
: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))Folding
oushine provides outline-cycle
(use-package outshine
:ensure t
;; breaks LaTeX
;; :config (add-hook 'outline-minor-mode-hook 'outshine-hook-function)
:config (progn
(defun malb/save-excursion (old-function &rest arguments)
"Call old-function with `save-mark-and-excursion'"
(let ((current (point)))
(save-excursion
(call-interactively old-function)
(if (not (= current (point)))
(call-interactively old-function)))))
(advice-add #'outline-cycle :around #'malb/save-excursion)))(use-package hideshow
:diminish hs-minor-mode
:config (setq hs-special-modes-alist
(mapcar 'purecopy
'((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))
:ensure t)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."
(interactive)
(let ((start-position (point)))
;; Move to the first non-whitespace character.
(back-to-indentation)
;; 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
:ensure t
:diminish beginend-global-mode
:config (progn
(beginend-global-mode)
(dolist (mode beginend-modes)
(diminish (cdr mode)))))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
.projectilefile 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-occurin project buffers- grep in project
- regenerate project
etagsorgtags(requiresggtags).- visit project in
dired- run make in a project with a single key chord
(source)
Commands:
C-c p Dprojectile-diredC-c p Fhelm-projectile-find-file-in-known-projectsC-c p Pprojectile-test-projectC-c p Sprojectile-save-project-buffersC-c p bhelm-projectile-switch-to-bufferC-c p fhelm-projectile-find-fileC-c p ghelm-projectile-find-file-dwimC-c p hhelm-projectileC-c p phelm-projectile-switch-projectC-c p rprojectile-replaceC-c p s shelm-projectile-agC-c p xprojectile-run-termetc.
When switching projects:
C-dopen Dired in project’s directoryM-gopen project root in vc-dir or magitM-eswitch to Eshell: Open a project in Eshell.C-sgrep in projects (add prefix C-u to recursive grep)C-cCompile project: Run a compile command at the project root.M-DRemove 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
:ensure t
:bind (("<f5>" . projectile-compile-project)
("<f6>" . next-error))
:config (progn
(use-package magit :ensure t)
(require 'helm-projectile)
(helm-projectile-on)
(defun malb/projectile-ignore-projects (project-root)
(progn
(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-switch-project-action 'helm-projectile
projectile-mode-line '(:eval (format "»{%s}" (projectile-project-name))))
(projectile-global-mode)))Helm integration
Commands:
C-c p hforhelm-projectilewhich combines buffer, file and project switchingC-c p Fforhelm-projectile-find-file-in-known-projects
We add a “Create file“ action (source) and replace grep by ag.
(use-package helm-projectile
:ensure t
:config (progn
(defvar malb/helm-source-file-not-found
(helm-build-dummy-source
"Create file"
:action 'find-file))
(add-to-list
'helm-projectile-sources-list
malb/helm-source-file-not-found t)
(helm-delete-action-from-source
"Grep in projects `C-s'"
helm-source-projectile-projects)
(helm-add-action-to-source
"Grep in projects `C-s'"
'helm-do-ag helm-source-projectile-projects 4)))Git
Magit
Magit is a very nice Git interface.

We enable magit-svn whenever necessary.
(use-package magit
:ensure t
:commands (magit-status
magit-diff
magit-commit
magit-log
magit-push
magit-stage-file
magit-unstage-file)
: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)
;; we use magit, not vc for git
(delete 'Git vc-handled-backends)))
(use-package magit-svn
:ensure t
:after magit
:config (progn
(defun malb/magit-svn ()
(if (file-exists-p (magit-git-dir "svn"))
(magit-svn-mode)))
(add-hook 'magit-mode-hook #'malb/magit-svn)))Notes
- try
git config status.showUntrackedFiles all(source)
GitHub
(use-package magithub
:ensure t
:after magit
:config (magithub-feature-autoinject t))Org Links for Magit Buffers
(use-package orgit
:ensure t)Git Timemachine
I don’t often use git-timemachine but when I do …
(use-package git-timemachine
:ensure t)Git Guttter
(use-package git-gutter-fringe
:ensure t
:config (progn
(define-fringe-bitmap 'git-gutter-fr:added
[224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224]
nil nil 'center)
(define-fringe-bitmap 'git-gutter-fr:modified
[224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224 224]
nil nil 'center)
(define-fringe-bitmap 'git-gutter-fr:deleted
[0 0 0 0 0 0 0 0 0 0 0 0 0 128 192 224 240 248]
nil nil 'center)))
(use-package git-gutter
:ensure t
:diminish git-gutter-mode
:config (progn
(setq git-gutter:disabled-modes '(org-mode))))Git Link
functions that create URLs for files and commits in GitHub/Bitbucket/GitLab/… repositories.
git-linkreturns the URL for the current buffer’s file location at the current line number or active region.git-link-commitreturns the URL for a commit. URLs are added to the kill ring.
(use-package git-link
:ensure t)Gists
To list gists, run gist-list:
g- reload the gist list from servere- edit current gist descriptionk- delete current gist+- add a file to the current gist–- remove a file from the current gistC-x C-s- save a new version of the gistC-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 https://gist.github.com . Copies the URL into the kill ring. With a prefix argument, makes a private paste.
(use-package gist
:ensure t
:config (progn
(setq gist-ask-for-description t)))Gitignore
Generate gitignore files
(use-package helm-gitignore
:ensure t
: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))Files
(setq org-directory malb/org-files-dir
org-agenda-files malb/org-files
org-default-notes-file malb/inbox-org)Options
(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 -110
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-startup-with-inline-images t ; show images when opening a file.
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))
org-blank-before-new-entry (quote ((heading . auto)
(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)))
(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 !Agenda
(setq org-agenda-tags-column -117
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-drawer-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)Look
(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-image-actual-width '(1024))Org Babel
Working with source code in org-mode.
(use-package ob
:config (progn
;; load more languages for org-babel
(org-babel-do-load-languages
'org-babel-load-languages
'((python . t)
(shell . t)
(latex . t)
(ditaa . t)
(dot . t)
(plantuml . t)
(makefile . t)))
(setq org-src-tab-acts-natively nil ;; BUG
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
:ensure t
:config (progn
(add-to-list 'org-structure-template-alist
'("ip" "#+BEGIN_SRC ipython\n?\n#+END_SRC"
"<src lang=\"python\">\n?\n</src>"))
(setq org-babel-default-header-args:ipython
'((:results . "output replace")
(:session . nil)
(:exports . "both")
(:cache . "no")
(:noweb . "no")
(:eval . "never-export")
(:hlines . "no")
(:tangle . "no")))))ToDo Keywords & Scheduling
(setq org-todo-keywords
'((sequence "TODO(t)"
"WAITING(w@)"
"PING(p)"
"DELEGATED(e@/!)"
"|"
"DONE(d)"
"CANCELLED(c@/!)") ;;generic
(sequence "EXPLORE(x)"
"WRITE"
"READ(r)"
"COAUTHOR(@/!)"
"REVISE(!)"
"SUBMITTED(@/!)"
"|"
"PUBLISHED(!)"
"ONHOLD(h@/!)"
"STALLED(s)") ;; papers
(sequence "REVIEW(v)"
"INFO(i@/!)"
"|"
"REVIEWED(V!)") ;; reviews
(type "BLOG(b)"
"REPLY(r)"
"CALL"
"LOAD"
"|" "DONE")))
(org-clock-persistence-insinuate)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"))
(progn
(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)Tags
(setq org-tag-persistent-alist '((:startgroup . nil)
("@office" . ?o)
("@train" . ?t)
("@home" . ?h)
(:endgroup . nil)
("quick" . ?q)
("noexport" . ?n)
("ignore" . ?i)))Tables
Bind org-table-* command when the point is in an org table (source).
(bind-keys
: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))Templates
(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
(source)
(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
(org-mark-subtree)
(kill-region
(region-beginning)
(region-end)))))
(add-to-list 'org-speed-commands-user (cons "J" (lambda () ;; Jump to headline
(avy-with avy-goto-line
(avy--generic-jump "^\\*+" nil avy-style)))))Refiling
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 nil)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 http://endlessparentheses.com/ispell-and-org-mode.html
(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)
(add-hook 'org-mode-hook #'malb/company-ispell-setup) ;; complete from ispell dictDiminish Minor Modes
(defun malb/diminish-org-indent-mode ()
(ignore-errors
(diminish 'org-indent-mode)))
(add-hook 'org-mode-hook #'malb/diminish-org-indent-mode)Archiving
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))
(org-archive-location
(if (save-excursion (org-back-to-heading)
(> (org-outline-level) 1))
(concat (car (split-string org-archive-location "::"))
"::* "
(car (org-get-outline-path)))
org-archive-location)))
ad-do-it
(with-current-buffer (find-file-noselect (org-extract-archive-file))
(save-excursion
(while (org-up-heading-safe))
(org-set-tags-to tags)))))Habit
(use-package org-habit
:config (add-to-list 'org-modules 'org-habit))Protocol
(use-package org-protocol)Bullets
Prettier bullets in org-mode.
(source)
(use-package org-bullets
:ensure t
: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 '("●" "▲" "■" "✶" "◉" "○" "○"))))Export
UTF-8 everywhere.
(setq org-export-coding-system 'utf-8
org-export-in-background nil
org-export-babel-evaluate 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))(source)
(add-to-list 'org-babel-default-header-args '(:eval . "never-export"))
(add-to-list 'org-babel-default-inline-header-args '(:eval . "never-export"))iCalendar
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
: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' used to reschedule itself, or nil.")(defun malb/org-icalendar-export-with-delay (secs)
"Export after `secs' seconds unless the file changed in the mean time."
(when malb/org-icalendar-export-timer
(cancel-timer malb/org-icalendar-export-timer))
(setq malb/org-icalendar-export-timer
(run-with-idle-timer
(* 1 secs) nil (lambda ()
;; async, check org-export-init.el
(org-icalendar-combine-agenda-files t)
(org-agenda-redo)))))(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)LaTeX
- Use XeLaTeX because UTF-8 and fonts.
- Drop
\usepackage[T1]{fontenc}because XeLaTeX doesn’t need and like it it. - Add some standard (to us) packages
- Handouts are done via tufte-handout, letters via
ox-koma-letter.
(use-package ox-latex
: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 org-latex-pdf-process '("latexmk -xelatex -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-default-packages-alist)
org-latex-hyperref-template (concat "\\hypersetup{\n"
"pdfauthor={%a},\n"
"pdftitle={%t},\n"
"pdfkeywords={%k},\n"
"pdfsubject={%d},\n"
"pdfcreator={%c},\n"
"pdflang={%L},\n"
"colorlinks,\n"
"citecolor=gray,\n"
"filecolor=gray,\n"
"linkcolor=gray,\n"
"urlcolor=gray\n"
"}\n"))
(add-to-list 'org-latex-classes
(list "handout"
(concat "\\documentclass{tufte-handout}\n"
"\\usepackage{fontspec}\n"
"[DEFAULT-PACKAGES]\n"
"[PACKAGES]\n"
"\\newunicodechar{ }{~}\n"
"\\newtheorem{lemma}{Lemma}\n"
"\\newtheorem{theorem}{Theorem}\n"
"\\newtheorem{definition}{Definition}\n"
"\\newtheorem{remark}{Remark}\n"
"\\newtheorem{corollary}{Corollary}\n"
"[EXTRA]\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"
"[DEFAULT-PACKAGES]\n"
"[PACKAGES]\n"
"\\usepackage{fontspec}\n"
"\\usepackage[a4paper,includeheadfoot,margin=2.54cm]{geometry}\n"
"\\usepackage[hang,flushmargin]{footmisc}\n"
"\\parskip 1em\n"
"\\parindent 0pt\n"
"\\linespread{1.25}\n"
"\\newcommand{\\malb}[2][inline]{\\todo[#1]{\\textbf{malb:} #2}\\xspace}"
"\\lstdefinelanguage{Sage}[]{Python}{morekeywords={True,False,sage,cdef,cpdef,ctypedef,self},sensitive=true}\n"
"\\lstset{frame=none,showtabs=False, showspaces=False, showstringspaces=False,\n"
" commentstyle={\\color{gray}}, keywordstyle={\\color{black}\\textbf},\n"
" stringstyle={\\color{darkgray}}, frame=single, basicstyle=\\tt\\scriptsize\\relax,\n"
" inputencoding=utf8, literate={…}{{\\ldots}}1, belowskip=0.0em,}\n"
"\\newunicodechar{ }{~}\n"
"\\newtheorem{lemma}{Lemma}\n"
"\\newtheorem{theorem}{Theorem}\n"
"\\newtheorem{definition}{Definition}\n"
"\\newtheorem{remark}{Remark}\n"
"\\newtheorem{corollary}{Corollary}\n"
"[EXTRA]\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"
"[DEFAULT-PACKAGES]\n"
"[PACKAGES]\n"
"\\usepackage{fontspec}\n"
"\\usepackage[a4paper,includeheadfoot,margin=2.54cm]{geometry}\n"
"\\usepackage[hang,flushmargin]{footmisc}\n"
"\\parskip 1em\n"
"\\parindent 0pt\n"
"\\newcommand{\\malb}[2][inline]{\\todo[#1]{\\textbf{malb:} #2}\\xspace}"
"\\lstdefinelanguage{Sage}[]{Python}{morekeywords={True,False,sage,cdef,cpdef,ctypedef,self},sensitive=true}\n"
"\\lstset{frame=none,showtabs=False, showspaces=False, showstringspaces=False,\n"
" commentstyle={\\color{gray}}, keywordstyle={\\color{black}\\textbf},\n"
" stringstyle={\\color{darkgray}}, frame=single, basicstyle=\\tt\\scriptsize\\relax,\n"
" inputencoding=utf8, literate={…}{{\\ldots}}1, belowskip=0.0em,}\n"
"\\newunicodechar{ }{~}\n"
"\\newtheorem{lemma}{Lemma}\n"
"\\newtheorem{theorem}{Theorem}\n"
"\\newtheorem{definition}{Definition}\n"
"\\newtheorem{remark}{Remark}\n"
"\\newtheorem{corollary}{Corollary}\n"
"[EXTRA]\n")
'("\\section{%s}" . "\\section*{%s}")
'("\\subsection{%s}" . "\\subsection*{%s}")
'("\\paragraph{%s}" . "\\paragraph*{%s}")
'("\\subparagraph{%s}" . "\\subparagraph*{%s}")))
(use-package ox-koma-letter
:config (progn
(add-to-list 'org-latex-classes
(list "letter"
(concat "\\documentclass{scrlttr2}\n"
"\\usepackage{fontspec}\n"
"[DEFAULT-PACKAGES]\n"
"[EXTRA]\n")))))))Beamer
- underline becomes bold in Beamer. (source)
strikethroughbecomes grey in Beamer.
(use-package ox-beamer
: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"
(with-temp-buffer
(insert-file-contents
(expand-file-name "talk-header.tex" user-emacs-directory))
(buffer-string)))
'("\\section{%s}" . "\\section*{%s}")
'("\\subsection{%s}" . "\\subsection*{%s}")
'("\\subsubsection{%s}" . "\\subsubsection*{%s}")))))ODT
(use-package ox-odt
: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
Reveal.js
ox-reveal — Presentations using reveal.js.
(use-package ox-reveal
:ensure t
:config
(progn
(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)))Bootstrap
ox-twbs - Twitter Bootstrap.
(use-package ox-twbs
:ensure t)Pandoc
(use-package ox-pandoc
:ensure t
:init (progn
(setq org-pandoc-menu-entry '( (103 "to md_github and open." org-pandoc-export-to-markdown_github-and-open)
(71 "as md_github." org-pandoc-export-as-markdown_github)
(120 "to docx and open." org-pandoc-export-to-docx-and-open)
(88 "to docx." org-pandoc-export-to-docx)
(51 "to epub and open." org-pandoc-export-to-epub-and-open)
(35 "to epub." org-pandoc-export-to-epub)
(101 "to epub3 and open." org-pandoc-export-to-epub3-and-open)
(69 "to epub3." org-pandoc-export-to-epub3)
(106 "to json and open." org-pandoc-export-to-json-and-open)
(74 "as json." org-pandoc-export-as-json)
(58 "to rst and open." org-pandoc-export-to-rst-and-open)
(42 "as rst." org-pandoc-export-as-rst)))))Rich Text Clipboard
Place rich text version of selection in clipboard (source)
(use-package ox-clip
:ensure t)Ignore Some Headlines
The tag :ignore: ignores a headline when exporting, section content is exported as usual.
(use-package ox-extra
:config (ox-extras-activate '(ignore-headlines)))Capture
If we are in a project we might add a TODO entry to the appropriate entry in projects.org.
(defun malb/org-capture-projectile ()
(if (projectile-project-p)
(progn
(let ((malb/projectile-name
(projectile-project-name)))
(find-file (expand-file-name "projects.org" malb/org-files-dir))
(goto-char (point-min))
(if (re-search-forward (concat "^\* " malb/projectile-name ".*\n") nil t)
(newline 1)
(progn
(goto-char (point-max))
(insert (concat "* " malb/projectile-name))
(newline 1)
))))
(progn
(find-file malb/private-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.%tTimestamp, date only.%TTimestamp, with date and time.%u, %ULike the above, but inactive timestamps.%iInitial content, the region when capture is called while the region is active. The entire text will be indented like%iitself.%aAnnotation, normally the link created withorg-store-link.%ALike%a, but prompt for the description part.%lLike%a, but only insert the literal link.%cCurrent kill ring head.%xContent of the X clipboard.%KLink to the currently clocked task.%kTitle of the currently clocked task.%nUser name (taken from user-full-name).%fFile visited by current buffer when org-capture was called.%FFull path of the file or directory visited by current buffer.%:keywordSpecific information for certain link types, see below.%^gPrompt for tags, with completion on tags in target file.%^GPrompt for tags, with completion all tags in all agenda files.%^tLike %t, but prompt for date. Similarly%^T,%^u,%^U. You may define a prompt like%^{Birthday}t.%^LLike %^C, but insert as link.%^CInteractive selection of which kill or clip to use.%^{prop}pPrompt 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.%\nInsert 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
: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 %?
DEADLINE: %t
:PROPERTIES:
:CREATED: %U
:END:
%i" :prepend t :empty-lines 1)
("tc" "capture a standard task and the current [c]ontext"
entry (file malb/inbox-org)
"* TODO %?
:PROPERTIES:
:CREATED: %U
:END:
%i
%a" :prepend t :empty-lines 1)
("tp" "capture a task for current projectile [p]roject" plain (function malb/org-capture-projectile)
"** TODO %?
:PROPERTIES:
:CREATED: %U
:END:
%i
%a" :prepend t :empty-lines 1)
("e" "[e]-mail")
("er" "quick capture of 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\"))
:PROPERTIES:
:CREATED: %U
:END:
%U" :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
:PROPERTIES:
:CREATED: %U
: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/inbox-org "E-Mail")
"* TODO %?
SCHEDULED: %(org-time-stamp nil)
:PROPERTIES:
:EFFORT: %^{effort|1:00|0:05|0:15|0:30|2:00|4:00}
:CREATED: %U
:END:
See %a by %:fromname
" :immediate-finish nil :prepend t :empty-lines 1)
("j" "add entry to [j]ournal"
entry (file+datetree (lambda () (expand-file-name "journal.org"
malb/org-files-dir)))
"** research
%?
** critique
")
("m" "record notes on a [m]eeting" entry (file+headline malb/inbox-org "Meetings")
"* Meeting with %^{who}
%U
%i%?
" :clock-in t :clock-resume t :empty-lines 1)
("n" "create a [n]ote"
entry (file malb/inbox-org)
"* %?
:PROPERTIES:
:CREATED: %U
:END:
%i" :empty-lines 1)
("s" "create note/tip on a piece of [s]oftware" entry
(file+headline (lambda () (expand-file-name "software.org" deft-directory)) "Incoming")
"* %?
:PROPERTIES:
:CREATED: %U
:END:
%i
%a" :prepend t :empty-lines 1)))
(setq org-capture-templates-contexts
'(("er" ((in-mode . "mu4e-view-mode")))
("et" ((in-mode . "mu4e-view-mode")))
("ef" ((in-mode . "mu4e-view-mode")))
("es" ((in-mode . "mu4e-headers-mode")))))))(use-package org-protocol-capture-html
:after org-capture
:config (add-to-list 'org-capture-templates
'("w" "capture [w]ebsite" entry
(file+headline (lambda () (expand-file-name "inbox.org" malb/org-files-dir)) "Links")
"* %?%a\nCaptured On: %U\n\n%:initial\n\n"
:immediate-finish nil
:prepend t) t))Web Tools
(use-package org-web-tools
:ensure t
:after org)Auto Completion
(add-hook 'org-mode-hook #'malb/enable-company-pcomplete)Eldoc
Activate eldoc and show footnotes in minibuffer.
(use-package org-eldoc
:config (progn
(add-hook 'org-mode-hook #'org-eldoc-load)
(add-hook 'org-mode-hook #'eldoc-mode)))
(defun malb/org-eldoc-get-footnote ()
(save-excursion
(let ((fn (org-between-regexps-p "\\[fn:" "\\]")))
(when fn
(save-match-data
(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
: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)))Dropbox
This minor mode syncs DropBox notes from mobile devices into org datetree file that can be part of org agenda. The minor mode starts a daemon that periodically scans the note directory.
(use-package org-dropbox
:ensure t
:config (progn
(setq org-dropbox-note-dir (expand-file-name "notes/" malb/dropbox-dir)
org-dropbox-datetree-file (expand-file-name "dropbox.org" malb/org-files-dir))
(org-dropbox-mode)))Functions
(source)
(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."
(interactive)
(when (derived-mode-p 'org-mode)
(let ((search-invisible t) start end)
(save-excursion
(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
(replace-regexp "\\[\\[.*?\\(\\]\\[\\(.*?\\)\\)*\\]\\]" "\\2"
nil start end)))))))(defun malb/copy-available-dates ()
"copy scheduled dates from headings 'TBD'"
(interactive)
(save-excursion
(let ((r))
(while
(let ((heading (org-get-heading))
(scheduled (org-entry-get (point) "SCHEDULED" nil)))
(if (equal "TBD" heading)
(progn
(add-to-list 'r scheduled t)))
(org-get-next-sibling)))
(kill-new (mapconcat (lambda (x)
(concat "- "
(replace-regexp-in-string "<\\|>" "" x)))
r "\n")))))Tips
Agenda commands
F | (org-agenda-follow-mode) | Toggle Follow mode |
L | (org-agenda-recenter) | Display original location and recenter that window. |
o | Delete 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-dsplits blocksC-c C-jcalls(org-goto)which jumps to headlines in a fileC-c /calls(org-sparse-tree)which reduces the tree to the nodes with some attribute
Style
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 file.org -f org-latex-export-to-pdfProgramming (languages)
General
REPL
Comint
We want to pick previous inputs based on prefix (source)
(use-package comint
: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 t
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 ()
(interactive)
(let ((kill-buffer-query-functions nil))
(kill-buffer)))
(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)
(comint-write-input-ring)
(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-history-dir))
(comint-read-input-ring t)
(add-hook 'kill-buffer-hook 'comint-write-input-ring t t)
(set-process-sentinel process
#'comint-write-history-on-exit))))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)))
(buffer-list)))
(add-hook 'kill-emacs-hook 'comint-write-input-ring-all-buffers)isend (poor person’s REPL)
- Open, say, Sage.
M-xisend-associateRET Sage RET- Hitting
C-RETwill 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
:ensure t
: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
:ensure t
:commands global-flycheck-mode
:diminish flycheck-mode
:config (progn
(global-flycheck-mode)
(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
#b00000000
#b00000000
#b00000000
#b00011000
#b01111110
#b11111111
#b11111111
#b11111111
#b11111111
#b11111111
#b01111110
#b00011000
#b00000000
#b00000000
#b00000000
#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
:ensure t
: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
:ensure t
:config (progn
;; flycheck errors on a tooltip (doesnt work on console)
(when (display-graphic-p (selected-frame))
(eval-after-load 'flycheck
'(custom-set-variables
'(flycheck-display-errors-function #'flycheck-pos-tip-error-messages)))
)
))Comments
Comments, as I mean, using comment-dwim-2.
(use-package comment-dwim-2
:ensure t
:bind ("M-;" . comment-dwim-2))Make links in comments and string clickable
(add-hook 'prog-mode-hook #'goto-address-prog-mode)Make bug references in comments and string clickable
(add-hook 'prog-mode-hook #'bug-reference-prog-mode)Indenting
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
:ensure t
:config (progn (dtrt-indent-mode t)
(setq dtrt-indent-active-mode-line-info "")))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
:ensure t
: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
:ensure t
: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 'emacs-lisp-mode-hook #'ws-butler-mode)
))Highlight FIXME and friends
(defun malb/fixme-highlight ()
(font-lock-add-keywords nil
'(("\\<\\(FIXME\\|BUG\\|TODO\\|HACK\\)" 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
markdown-mode
c-mode
emacs-lisp-mode
org-mode
c++-mode))
(setq which-func-format
`(" "
(:propertize which-func-current local-map
(keymap
(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
:ensure t
: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)))[#C] Line numbers
(use-package nlinum
:ensure t)Looking Stuff Up
helm-dash package uses Dash docsets inside emacs to browse documentation.
(use-package helm-dash
:ensure t
:bind ("C-c h ." . helm-dash-at-point)
:config (progn
(setq helm-dash-common-docsets '("Bash"
"Bootstrap 4"
"C"
"C++"
"Emacs Lisp"
"Flask"
"HTML"
"Java"
"LaTeX"
"Markdown"
"NumPy"
"Python 2"
"Python 3"
"SQLAlchemy"
"SQLite"
"Vagrant"
)
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
:ensure t
:config (progn (add-hook 'c-mode-common-hook
(lambda ()
(ggtags-mode t)))
(unbind-key "M-." ggtags-mode-map)
(unbind-key "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
:ensure t
: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)))RTags
RTags can come in handy.
- create a
compile_commands.jsonfile using bear, e.g.bear make check - launch
rdm - tell rtags about the project
cd /project/dir/ && rc -J /path/to/compile_commands.json
On the other hand, rtags seems to have trouble dealing with inline functions.
(use-package rtags
:ensure t
:config (progn
(rtags-enable-standard-keybindings nil "C-c j r")
(setq rtags-use-helm t
rtags-autostart-diagnostics t)
(add-hook 'c-mode-hook #'rtags-start-process-unless-running)
(add-hook 'c++-mode-hook #'rtags-start-process-unless-running)))Note See this post for a nice intro to rtags.
(use-package flycheck-rtags
:after flycheck
:ensure t)(use-package company-rtags
:after flycheck
:ensure t)CamelCase
(use-package string-inflection
:ensure t)Dump Jump
zero-config jump to definition for JavaScript, Emacs Lisp, Python, Go, Clojure, …
dumb-jump-goC-M-gcore functionality. Attempts to jump to the definition for the thing under pointdumb-jump-backC-M-pjumps 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-lookC-M-qlikedumb-jump-gobut shows tooltip with file, line, and context
(use-package dumb-jump
:ensure t
: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)
(dumb-jump-mode)))Debuggers
(use-package realgud
:ensure t
:commands (realgud:pdb realgud:ipdb realgud:trepan2 realgud:gdb)
:config (progn
(setq realgud:pdb-command-name "python -m pdb")))GDB
(setq gdb-find-source-frame t
gdb-many-windows 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))))Semantic
Enable semantic for C and C++ (cf. malb/inhibit-semantic-p). Also enable some useful minor modes (documentation from C-h v RET semantic-default-submodes):
global-semanticdb-minor-modeMaintain tag database.global-semantic-idle-scheduler-modeReparse buffer when idle.global-semantic-idle-summary-modeShow summary of tag at point.global-semantic-idle-completions-modeShow completions when idle.global-semantic-decoration-modeAdditional tag decorations.global-semantic-highlight-func-modeHighlight the current tag.global-semantic-mru-bookmark-modeProvide `switch-to-buffer’-like keybinding for tag names.global-semantic-idle-local-symbol-highlight-mode- Highlight references of the symbol under point.global-semantic-stickyfunc-mode- show the title of a tag in the header line.
(use-package semantic
:defer t
:init (progn
(use-package semantic/ia)
(use-package semantic/bovine/gcc)
(add-to-list 'semantic-default-submodes 'global-semanticdb-minor-mode)
(add-to-list 'semantic-default-submodes 'global-semantic-idle-scheduler-mode)
(add-to-list 'semantic-default-submodes 'global-semantic-idle-summary-mode)
(add-to-list 'semantic-default-submodes 'global-semantic-decoration-mode)
(add-to-list 'semantic-default-submodes 'global-semantic-highlight-func-mode)
(add-to-list 'semantic-default-submodes 'global-semantic-mru-bookmark-mode)
(add-to-list 'semantic-default-submodes 'global-semantic-idle-local-symbol-highlight-mode)
(semanticdb-enable-gnu-global-databases 'c-mode t)
(semanticdb-enable-gnu-global-databases 'c++-mode t)
(setq semanticdb-default-save-directory (expand-file-name "semantic" user-emacs-directory))
(semantic-mode 1)
(global-ede-mode t)
(ede-enable-generic-projects)
(setq-default semantic-case-fold t)
(defun malb/inhibit-semantic-p ()
(member major-mode '(python-mode cython-mode)))
(add-to-list 'semantic-inhibit-functions #'malb/inhibit-semantic-p)
(setq semantic-new-buffer-setup-functions
(remove-if (lambda (buffer-setup-function)
(member (car buffer-setup-function)
'(python-mode js-mode scheme-mode html-mode)))
semantic-new-buffer-setup-functions))
(remove-hook 'python-mode-hook 'wisent-python-default-setup)
(bind-key "M-?" #'semantic-analyze-proto-impl-toggle c-mode-base-map)
(bind-key "M-." #'semantic-ia-fast-jump c-mode-base-map)
(bind-key "M-r" #'semantic-symref-symbol c-mode-base-map)
(bind-key "C-c j s ?" #'semantic-analyze-proto-impl-toggle c-mode-base-map)
(bind-key "C-c j s ." #'semantic-ia-fast-jump c-mode-base-map)
(bind-key "C-c j s J" #'semantic-complete-jump c-mode-base-map)
(bind-key "C-c j s r" #'semantic-symref-symbol c-mode-base-map)
(bind-key "C-c j s p" #'senator-previous-tag c-mode-base-map)
(bind-key "C-c j s n" #'senator-next-tag c-mode-base-map)
(bind-key "C-c j s C-w" #'senator-kill-tag c-mode-base-map)
(bind-key "C-c j s M-w" #'senator-copy-tag c-mode-base-map)))Force semantic parsing
The following code parses a complete project with semantic. This is useful for exploring a new project. (source)
(defvar malb/c-files-regex ".*\\.\\(c\\|cpp\\|h\\|hpp\\)"
"A regular expression to match any c/c++ related files under a directory")
(defun malb/semantic-parse-dir (root regex)
"This function is an attempt of mine to force semantic to
parse all source files under a root directory. Arguments:
-- root: The full path to the root directory
-- regex: A regular expression against which to match all files in the directory"
(let (
;;make sure that root has a trailing slash and is a dir
(root (file-name-as-directory root))
(files (directory-files root t ))
)
;; remove current dir and parent dir from list
(setq files (delete (format "%s." root) files))
(setq files (delete (format "%s.." root) files))
(while files
(setq file (pop files))
(if (not(file-accessible-directory-p file))
;;if it's a file that matches the regex we seek
(progn (when (string-match-p regex file)
(save-excursion
(semanticdb-file-table-object file))
))
;;else if it's a directory
(malb/semantic-parse-dir file regex)
)
)
)
)
(defun malb/semantic-parse-current-dir (regex)
"Parses all files under the current directory matching regex"
(malb/semantic-parse-dir (file-name-directory(buffer-file-name)) regex))
(defun malb/parse-curdir-c ()
"Parses all the c/c++ related files under the current directory
and inputs their data into semantic"
(interactive)
(malb/semantic-parse-current-dir malb/c-files-regex))
(defun malb/parse-dir-c (dir)
"Prompts the user for a directory and parses all c/c++ related files
under the directory"
(interactive (list (read-directory-name "Provide the directory to search in:")))
(malb/semantic-parse-dir (expand-file-name dir) malb/c-files-regex))SRefactor
(use-package srefactor
:ensure t
:defer 10)Font Lock
Grey out #if 0 blocks.
(defun malb/c-mode-font-lock-if0 (limit)
(save-restriction
(widen)
(save-excursion
(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")
(progn
(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)))))
nil)
(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
`((,(concat
"\\<[_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))))Doxygen
(use-package doxymacs
:diminish doxymacs-mode
:config (progn
(defun malb/doxymacs ()
(doxymacs-mode t)
(doxymacs-font-lock))
(add-hook 'c-mode-common-hook #'malb/doxymacs)))Valgrind
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."
(interactive
(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
:ensure t
: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
:ensure t
:config (progn
(setq clang-format-executable "clang-format-3.8")))Python
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
:config (progn
(setq-default python-indent 4
python-fill-docstring-style 'django)
(setq python-shell-interpreter "ipython"
;; add --simple-prompt or install https://github.com/ipython/rlipython
python-shell-interpreter-args "-i --simple-prompt"
python-shell-prompt-regexp "In \\[[0-9]+\\]: "
python-shell-prompt-output-regexp "Out\\[[0-9]+\\]: "
python-shell-completion-setup-code
"from IPython.core.completerlib import module_completion"
python-shell-completion-module-string-code
"';'.join(module_completion('''%s'''))\n"
python-shell-completion-string-code
"';'.join(get_ipython().Completer.all_completions('''%s'''))\n")
(bind-key "C-c C-c" #'elpy-shell-send-region-or-buffer python-mode-map)
(bind-key "C-c C-r" #'pyvenv-restart-python python-mode-map)
(bind-key "C-c C-r" #'pyvenv-restart-python inferior-python-mode-map)
(bind-key "<home>" #'malb/beginning-of-line-dwim python-mode-map)
;; http://emacs.stackexchange.com/a/21186/8930
(add-to-list 'python-indent-trigger-commands 'malb/indent-fold-or-complete)
(add-to-list 'python-shell-completion-native-disabled-interpreters "jupyter")
;; Fold logical units
;; (add-hook 'python-mode-hook #'origami-mode)
;; (bind-key "C-<tab>" #'origami-toggle-node python-mode-map)
;; (bind-key "C-S-<iso-lefttab>" #'origami-toggle-all-nodes python-mode-map)
(add-hook 'python-mode-hook #'outline-minor-mode)
(bind-key "C-<tab>" #'outline-cycle python-mode-map)))Highlight indentation
It makes sense to highlight indentation in Python.
(use-package highlight-indentation
:ensure t
:diminish highlight-indentation-mode
:config (progn (add-hook 'python-mode-hook #'highlight-indentation-mode)))Autocompletion
Use anaconda-mode for auto-completion and stuff, it runs jedi for us. In particular it offers:
C-M-i | anaconda-mode-complete |
M-. | malb/python-mode-find-definitions |
M-, | anaconda-mode-find-assignments |
M-r | anaconda-mode-find-references |
M-* | anaconda-mode-go-back |
M-? | anaconda-mode-show-doc |
|---|
Fall back to dumb jump if anaconda fails.
(use-package anaconda-mode
:ensure t
:after dumb-jump
:diminish anaconda-mode
:config (progn
(add-hook 'python-mode-hook #'anaconda-mode)
(add-hook 'python-mode-hook #'anaconda-eldoc-mode)
(defun malb/python-mode-find-definitions (&optional arg)
(interactive "P")
(let ((p (point)))
(if (not (called-interactively-p 'any))
(anaconda-mode-find-definitions arg)
(call-interactively 'anaconda-mode-find-definitions))
(if (equal p (point))
(dumb-jump-go))))
(bind-key "M-." #'malb/python-mode-find-definitions anaconda-mode-map)))Pydoc
(use-package helm-pydoc
:ensure t
:config (progn
(bind-key "C-c C-d" #'helm-pydoc python-mode-map)))Docstrings
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
:ensure t
:diminish python-docstring-mode
:config (progn
(add-hook 'python-mode-hook #'python-docstring-mode)))Sphinx-doc
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
:ensure t
:diminish sphinx-doc-mode
:config (progn
(add-hook 'python-mode-hook #'sphinx-doc-mode)))Cython
(use-package cython-mode
:ensure t
:mode (("\\.pyx\\'" . cython-mode)
("\\.spyx\\'" . cython-mode)
("\\.pxd\\'" . cython-mode)
("\\.pxi\\'" . cython-mode)))(use-package flycheck-cython
:ensure t)AutoPEP8
(use-package py-autopep8
:ensure t
:config (progn
(setq py-autopep8-options '("--max-line-length=100"))))ElPy
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
:ensure t
: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)))PIP
(use-package pip-requirements
:ensure t)iPython Notebook
EIN is a interface to iPython.
(source)
On our system port 8888 is already taken.
(use-package ein
:ensure t
:commands (ein:notebook-open ein:query-ipython-version)
:config (progn
(setq ein:use-auto-complete t
ein:complete-on-dot nil
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)
(require 'ein-notebook)
(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)))Virtual Environments
virtualenvwrapper emulates much of the functionality of virtualenvwrapper.sh. It strikes me as the most feature complete of the many choices. I also added some features from pyvenv.
(use-package virtualenvwrapper
:ensure t
:config (progn
(venv-initialize-interactive-shells)
(venv-initialize-eshell)
(defun malb/track-venv ()
"Enable virtualenv if local variable `project-venv-name' is set.
Otherwise disable virtualenv."
;; (if (buffer-file-name) (hack-local-variables))
(if (boundp 'project-venv-name)
(venv-workon project-venv-name)))
(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)
(venv-deactivate)))
(t
(progn
(set (make-local-variable 'project-venv-name)
(venv-read-name (if venv-current-name
(format "Choose a virtualenv (currently %s): "
venv-current-name)
"Choose a virtualenv: ")))
(venv-workon project-venv-name)))))
;; virtualenv in spaceline, enabled below
(eval-after-load "spaceline"
'(progn
(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"
venv-current-name))))))))Sage
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
:ensure t
: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
:ensure t)
(use-package sage-shell-mode
:ensure t
: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)
(defun malb/toggle-sage ()
""
(interactive)
(let* ((parent (if (buffer-file-name)
(file-name-directory (buffer-file-name))
default-directory))
(height (/ (window-total-height) 3))
(name "*Sage*" ;; (concat "*Sage<" (if (projectile-project-p)
;; (projectile-project-name)
;; (car (last (split-string parent "/" t)))) ">*")
)
(golden-ratio-mode nil)
(window (get-buffer-window name)))
(if (and window (<= (window-height window) (/ (frame-height) 3)))
(progn
(select-window window)
(delete-window))
(progn
(split-window-vertically (- height))
(other-window 1)
(if (get-buffer name)
(progn
(switch-to-buffer name)
(set-window-dedicated-p (get-buffer-window (current-buffer)) t))
(progn
(venv-deactivate)
(sage-shell:run-sage "sage")
(rename-buffer name)
(set-window-dedicated-p (get-buffer-window (current-buffer)) t)))))))
(defun malb/kill-buffer (old-function &rest arguments)
(apply old-function arguments)
(let ((kill-buffer-query-functions nil))
(kill-buffer)))
(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
PATH="$_OLD_VIRTUAL_PATH"
export PATH
unset _OLD_VIRTUAL_PATH
fi
if ! [ -z "${_OLD_VIRTUAL_PYTHONHOME+_}" ] ; then
PYTHONHOME="$_OLD_VIRTUAL_PYTHONHOME"
export PYTHONHOME
unset _OLD_VIRTUAL_PYTHONHOME
fi
# 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
fi
if ! [ -z "${_OLD_VIRTUAL_PS1+_}" ] ; then
PS1="$_OLD_VIRTUAL_PS1"
export PS1
unset _OLD_VIRTUAL_PS1
fi
### LD_LIBRARY_HACK
if ! [ -z ${_OLD_LD_LIBRARY_PATH+x} ] ; then
LD_LIBRARY_PATH="$_OLD_LD_LIBRARY_PATH"
export LD_LIBRARY_PATH
unset _OLD_LD_LIBRARY_PATH
fi
### END_LD_LIBRARY_HACK
### PKG_CONFIG_HACK
if ! [ -z ${_OLD_PKG_CONFIG_PATH+x} ] ; then
PKG_CONFIG_PATH="$_OLD_PKG_CONFIG_PATH"
export PKG_CONFIG_PATH
unset _OLD_PKG_CONFIG_PATH
fi
### END_PKG_CONFIG_HACK
unset VIRTUAL_ENV
if [ ! "${1-}" = "nondestructive" ] ; then
# Self destruct!
unset -f deactivate
fi
}
# unset irrelevant variables
deactivate nondestructive
VIRTUAL_ENV="/opt/sage-devel/local"
export VIRTUAL_ENV
_OLD_VIRTUAL_PATH="$PATH"
PATH="$VIRTUAL_ENV/bin:$PATH"
export PATH
SAGE_ROOT="/opt/sage-devel/"
SAGE_LOCAL="/opt/sage-devel/local/"
export SAGE_ROOT
export SAGE_LOCAL
# unset PYTHONHOME if set
if ! [ -z "${PYTHONHOME+_}" ] ; then
_OLD_VIRTUAL_PYTHONHOME="$PYTHONHOME"
unset PYTHONHOME
fi
if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT-}" ] ; then
_OLD_VIRTUAL_PS1="$PS1"
if [ "x" != x ] ; then
PS1="$PS1"
else
PS1="(sage-devel) $PS1"
fi
export PS1
fi
# Make sure to unalias pydoc if it's already there
alias pydoc 2>/dev/null >/dev/null && unalias pydoc
### LD_LIBRARY_HACK
_OLD_LD_LIBRARY_PATH="$LD_LIBRARY_PATH"
LD_LIBRARY_PATH="$VIRTUAL_ENV/lib:$LD_LIBRARY_PATH"
export LD_LIBRARY_PATH
### END_LD_LIBRARY_HACK
### PKG_CONFIG_HACK
_OLD_PKG_CONFIG_PATH="$PKG_CONFIG_PATH"
PKG_CONFIG_PATH="$VIRTUAL_ENV/lib/pkgconfig:$PKG_CONFIG_PATH"
export PKG_CONFIG_PATH
### END_PKG_CONFIG_HACK
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
fiShortcut for Sage Jupyter notebook.
(defun malb/sage-notebook ()
(interactive)
(let ((sage (prodigy-find-service "sage-devel")))
(if (not (prodigy-service-started-p sage))
(progn
(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
:ensure t
: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"))))Lisp
General
short and sweet LISP editing (source)
Basic navigation by-list and by-region:
| binding | command |
|---|---|
h | moves left |
j | moves down |
k | moves up |
l | moves right |
f | steps inside the list |
b | moves back in history for all above commands |
d | moves to different side of sexp |
Paredit transformations, callable by plain letters:
| binding | command |
> | slurps |
|---|---|
< | barfs |
r | raises |
C | convolutes |
s | moves down |
w | moves up |
IDE-like features for Elisp, Clojure, Scheme, Common Lisp, Hy, Python and Julia:
| binding | command |
|---|---|
e | evals |
E | evals and inserts |
g | jumps to any tag in the current directory with semantic |
G | jumps to any tag in the current file |
M-. | jumps to symbol, M-, jumps back |
F | jumps to symbol, D jumps back |
C-1 | shows documentation in an overlay |
C-2 | shows arguments in an overlay |
Z | breaks out of edebug, while storing current function’s arguments |
Code manipulation:
| binding | command |
|---|---|
i | prettifies code (remove extra space, hanging parens …) |
xi | transforms cond expression to equivalent if expressions |
xc | transforms if expressions to an equivalent cond expression |
xf | flattens function or macro call (extract body and substitute arguments) |
xr | evals and replaces |
xl | turns current defun into a lambda |
xd | turns current lambda into a defun |
O | formats the code into one line |
M | formats the code into multiple lines |
(use-package lispy
:ensure t)Emacs Lisp
(use-package elisp-slime-nav
:ensure t
:diminish elisp-slime-nav-mode
:config (progn
(defun malb/elisp-hook ()
(elisp-slime-nav-mode)
(smartparens-strict-mode)
(eldoc-mode)
(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
emacs-lisp-mode-map)))(bind-key "C-c C-z" #'ielm emacs-lisp-mode-map)(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
:ensure t)(add-hook 'emacs-lisp-mode-hook #'hs-minor-mode)Hy
(use-package hy-mode
:ensure t
:config (progn
(add-hook 'hy-mode-hook #'lispy-mode)))Web
(use-package web-mode
:ensure t
:config (setq web-mode-enable-engine-detection t))JSON
(use-package json-mode
:ensure t)Prose
General
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."
(interactive)
(let ((fill-column
(if (eq last-command 'malb/fill-or-unfill)
(progn (setq this-command nil)
(point-max))
fill-column)))
(call-interactively #'fill-paragraph)))
(defun malb/org-fill-or-unfill ()
"Like `org-fill-paragraph', but unfill if used twice."
(interactive)
(let ((fill-column
(if (eq last-command 'malb/org-fill-or-unfill)
(progn (setq this-command nil)
(point-max))
fill-column)))
(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
:ensure t
: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))Typography
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
:ensure t
:diminish typo-mode
:config (progn
(setq-default typo-language "English")
(add-hook 'markdown-mode-hook #'typo-mode)
(add-hook 'org-mode-hook #'typo-mode)
(add-hook 'rst-mode-hook #'typo-mode)))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.
(use-package ispell
:config (progn
(setq ispell-dictionary "en_GB")
;; 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)))
args
(cons (replace-regexp-in-string
"'" "’" (car args))
(cdr args))))
(advice-add #'ispell-parse-output :filter-args #'malb/replace-quote)))Visual Fill Column Mode
(source)
(use-package visual-fill-column
:ensure t
: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))))Sentences
(setq sentence-end-double-space nil)
(bind-key "C-x C-t" #'transpose-sentences)We also delete trailing whitespaces when we delete a sentence.
(defadvice kill-sentence (after delete-horizontal-space activate)
"Delete trailing spaces and tabs as well."
(delete-horizontal-space))Highlighting Sentences & Paragraphs
Use hl-sentence-mode in markdown-mode.
(use-package hl-sentence
:ensure t
:config (add-hook 'markdown-mode-hook #'hl-sentence-mode))Also use focus-mode occationally.
(source)
(use-package focus
:ensure t)Spell Checking
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
:ensure t
:bind ("C-." . ace-flyspell-dwim)
:init (progn
(eval-after-load "flyspell"
'(bind-key "C-." #'ace-flyspell-dwim flyspell-mode-map))
(eval-after-load "flyspell"
'(unbind-key "C-;" flyspell-mode-map))))Diminish flyspell-mode as we always use it.
(eval-after-load "flyspell"
'(diminish 'flyspell-mode))Automatically pick dictionary.
(use-package auto-dictionary
:ensure t
:config (progn
(setf (cdr (rassoc "en" adict-dictionary-list)) "en_GB")
(add-hook 'markdown-mode-hook #'adict-guess-dictionary)))Grammar Checking
(source)
(use-package langtool
:ensure t
: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"
"EN_QUOTES"))
(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)))Style Checking
Prose linting using … proselint.
(source)
(flycheck-define-checker proselint
"A linter for prose."
:command ("proselint" source-inplace)
:error-patterns
((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)Synonyms
(use-package synosaurus
:ensure t
: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)))Dictionary
Use define-word to get a quick reference on a word.
(use-package define-word
:ensure t
:bind (("C-c w d" . define-word-at-point)))Translating
(use-package google-translate
:ensure t
:bind ("C-c w t" . google-translate-smooth-translate)
:init (progn (setq google-translate-translation-directions-alist
'(("en" . "de") ("de" . "en")))))Taking Notes
Use deft for random notes and for a listing of blog entries.

(use-package deft
:ensure t
: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*")
(kill-this-buffer)
(deft))
(defun malb/blog ()
(interactive)
(malb/deft-in-dir (expand-file-name "web/blog" malb/projects-dir)))
(defun malb/deft ()
(interactive)
(malb/deft-in-dir malb/deft-directory))
(add-hook 'deft-mode-hook #'hl-line-mode)))Markdown
Standard setup and quick preview (source)
(use-package markdown-mode
:ensure t
:mode (("\\.md\\'" . markdown-mode)
("\\.markdown\\'" . markdown-mode)
("\\.text\\'" . markdown-mode)
("\\.txt\\'" . 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 --smart -s -f markdown -t html -c" malb/markdown.css)
markdown-css-paths (list malb/markdown.css)
markdown-enable-math t)
(defun malb/markdown-preview-buffer ()
(interactive)
(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)))
(bind-key "M-." #'markdown-jump markdown-mode-map)
(bind-key "C-c C-e o" #'malb/markdown-preview-buffer markdown-mode-map) ;; inspired by org-mode-export
(add-hook 'markdown-mode-hook #'flyspell-mode)
(add-hook 'markdown-mode-hook #'malb/company-ispell-setup)
(add-hook 'markdown-mode-hook #'outline-minor-mode)
(bind-key "C-<tab>" #'outline-cycle markdown-mode-map)))Pandoc
Use pandoc-mode to call pandoc for converting markdown to everything else.
(use-package pandoc-mode
:ensure t
:config (progn
(add-hook 'markdown-mode-hook #'pandoc-mode)
(add-hook 'org-mode-hook #'conditionally-turn-on-pandoc)
(add-hook 'pandoc-mode-hook #'pandoc-load-default-settings)
(delight 'pandoc-mode
'(:eval (concat " p[" (pandoc--get 'write) "]")))
(defun malb/copy-as (arg)
(interactive "P")
(cond
(arg (malb/-copy-as
(completing-read "Output format to use: "
pandoc--output-formats nil t)))
((derived-mode-p 'org-mode) (malb/copy-as-latex-from-org))
(t (malb/-copy-as "org"))))
(defun malb/copy-as-latex-from-org ()
(interactive)
(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 ()
(interactive)
(malb/-copy-as "latex"))
(defun malb/copy-as-org ()
(interactive)
(malb/-copy-as "org"))
(defun malb/-copy-as (what)
(let ((pandoc-use-async nil)
(buffer (get-buffer-create pandoc--output-buffer-name)))
(pandoc--set 'read (cdr (assq major-mode pandoc-major-modes)))
(pandoc--call-external what nil
(if (use-region-p)
(cons (region-beginning) (region-end))))
(switch-to-buffer buffer)
(kill-new (buffer-string))
(bury-buffer)))
(bind-key "M-W" #'malb/copy-as)))ReST
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-mode
:commands rst-mode-hook
:mode "README\\.txt")LaTeX
| Command | Explanation |
|---|---|
C-c C-g | forward search |
C-c @ | outline minor mode |
C-c ? | symbol documentation |
(use-package tex
:commands LaTeX-mode-hook
:ensure auctex
:defer t
: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)
(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 ()
(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)
(bind-key "C-<tab>" #'outline-cycle LaTeX-mode-map)
(bind-key "M-." #'reftex-goto-label LaTeX-mode-map)
;; http://endlessparentheses.com/improving-latex-equations-with-font-lock.html
(defface malb/unimportant-latex-face
'((t :height 0.8
:inherit font-lock-comment-face))
"Face used on less relevant math commands.")
(font-lock-add-keywords
'latex-mode
`((,(rx "\\" (or (any ",.!;")
(and (or "left" "right" "qquad" "quad")
symbol-end)))
0 'malb/unimportant-latex-face prepend)) 'end)
(setq TeX-auto-save t
TeX-parse-self t
reftex-plug-into-AUCTeX 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)
;; https://stackoverflow.com/questions/19845598/emacs-regex-replacing-a-string-inside-a-latex-equation
(fset 'malb/latex-replace-in-math
`(lambda (regexp to-string &optional delimited start end backward)
"Like `query-replace-regexp' but only replaces in LaTeX-math environments."
,(interactive-form 'query-replace-regexp)
(let ((replace-re-search-function (lambda (regexp bound noerror)
(catch :found
(while (let ((ret (re-search-forward regexp bound noerror)))
(when (save-match-data (texmathp)) (throw :found ret))
ret))))))
(query-replace-regexp regexp to-string delimited start end backward))))
(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")))LatexMK
(use-package auctex-latexmk
:ensure t
:config (progn
(setq auctex-latexmk-inherit-TeX-PDF-mode t)
(auctex-latexmk-setup)))Run latexmk -pvc in async shell
(defun malb/run-latexmk (arg)
(interactive "p")
(let* ((engine (if arg "-xelatex" ""))
(filename (file-name-sans-extension (buffer-file-name))))
(async-shell-command (format "latexmk -pvc -pdf \"%s\" -interaction=nonstopmode \"%s.tex\""
engine filename))))LaTeX preview pane
(use-package latex-preview-pane
:diminish latex-preview-pane-mode
:ensure t
:config (progn
(setq latex-preview-pane-multifile-mode (quote auctex))))Setup everything
Setup everything for LaTeX (this can be slow, hence we call it manually)
(defun malb/latex-init ()
(interactive)
(ignore-errors
(malb/latex-parse-bibtex)) ; don't die if there's no bibtex file
;; (latex-preview-pane-mode 1)
)Utility Functions
(source)
(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))Scimax
(source)
(use-package scimax-email)
(defun malb/words-atd ()
"Send paragraph at point to After the deadline for spell and grammar checking."
(interactive)
(let* ((url-request-method "POST")
(url-request-data (format
"key=some-random-text-&data=%s"
(url-hexify-string
(thing-at-point 'paragraph))))
(xml (with-current-buffer
(url-retrieve-synchronously
"http://service.afterthedeadline.com/checkDocument")
(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*")
(erase-buffer)
(org-mode)
(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))))
options
" ")))
(insert (format "- %s :: %s %s %s
" s opt-string desc type))))))BibTeX
My standard BibTeX sources are
crypto_crossref.bibandabbrev3.bibare from crypto.bib which has most references relevant to crypto,jacm.bibis for the Journal of the ACM provided by the University of Utah,dcc.bibis for Designs, Codes, and Cryptography provided by the University of Utah,rfc.bibis 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
(concat
(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
required-fields
sort-fields
page-dashes
whitespace
last-comma
delimiters
numerical-fields
realign))
;; “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)
3)
(t 1)))
(existing-keys (bibtex-parse-keys))
key)
(setq names (mapcar 'capitalize names))
(setq names (mapconcat (lambda (x)
(substring x 0 (min (length x) name-char)))
names
""))
(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)))
ret)))))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 ()
(interactive)
(mapc 'LaTeX-add-bibitems
(apply 'append
(mapcar #'malb/get-bibtex-keys (reftex-get-bibfile-list)))))Helm BibTeX
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
:ensure t
: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
bibtex-completion-notes-template-one-file
"** ${title} cite:${=key=}\n:PROPERTIES:\n:CUSTOM_ID: ${=key=}\n:END:\n\nfullcite:${=key=}\n\n")
(defun malb/bibtex-completion-google-this (keys-or-entries)
(dolist (key-or-entry keys-or-entries)
(let* ((key (if (stringp key-or-entry)
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
helm-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)
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
(cl-loop
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)))
(cond
(buffer
(with-current-buffer buffer
(write-file pdf)))
(file
(copy-file file pdf)))))
(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)))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
:ensure t)- gscholar-bibtex grabs BibTeX entries from from Google Scholar, ACM Digital Library, IEEE Xplore and DBLP.
(use-package gscholar-bibtex :ensure t :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
:ensure t
: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)
;; move Insert BibTeX key to front, TODO org-ref screws with this order, hence this stuff is here
(helm-add-action-to-source "Find online" 'helm-malb/bibtex-completion-google-this helm-source-bibtex)))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
: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 116
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-empty-parent-prefix '("-" . "○")
mu4e-headers-first-child-prefix '("\\" . "▶")
mu4e-headers-has-child-prefix '("+" . "●"))Folders
(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)))Queries
(source)
(use-package mu4e-query-fragments
:ensure t
:config (setq mu4e-query-fragments-list
'(("%hidden" . "flag:trashed")
("%unread" . "flag:unread AND NOT %hidden")
("%today" . "date:today..now")
("%week" . "date:7d..now")
("%month" . "date:31d..now")
("%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"))))(use-package mu4e-jump-to-list
:ensure t)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 from:trac@sagemath.org 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)
(add-to-list
'mu4e-bookmarks '("%inbox 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)
(add-to-list
'mu4e-bookmarks
'("mime:application/* AND NOT (mime:application/pgp* or mime:application/ics) AND size:5k..1024M AND %inbox AND %month"
"Documents (31 days)" ?d) t)Retrieving
(setq mu4e-get-mail-command "timelimit -t 180 -T 180 mbsync googlemail-default"
mu4e-update-interval nil)Contacts
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)))
(match))
(dolist (token tokens)
(string-match malb/email-regexp token)
(setq match (or match (match-string 1 token))))
match))Canonicalisation
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 (cddr (assoc (downcase email) malb/mu4e-name-replacements))
(cadr (assoc (downcase email) malb/mu4e-name-replacements)))))
(if candidate
candidate
(progn
;; 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 ()
(interactive)
(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* ((name (or (plist-get contact :name) ""))
(email (downcase (plist-get contact :mail)))
(case-fold-search t))
(plist-put contact :name (malb/canonicalise-contact-name email name))
contact))
(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) "@plus.google.com")
(seq "@" (one-or-more char) ".twitter.com")
(seq "do-not-reply" (zero-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 (address
addresses
point-end-of-line
(case-fold-search t)
(search-regexp (mapconcat (lambda (arg) (concat "^" arg ": *"))
fields "\\|")))
;; extract addresses
(save-excursion
(goto-char (point-min))
(while (re-search-forward search-regexp nil t)
(save-excursion
(setq point-end-of-line (re-search-forward "$")))
(setq addresses (append addresses
(mail-header-parse-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)))
addresses))))The following is useful in e-mail templates
(defun malb/get-names-from-fields (fields)
(let (contacts
ret
contact
point-end-of-line
(search-regexp (mapconcat (lambda (arg)
(concat "^" arg ": "))
fields "\\|"))
(case-fold-search t))
(save-excursion
(goto-char (point-min))
(while (re-search-forward search-regexp nil t)
(save-excursion
(setq point-end-of-line (re-search-forward "$")))
(setq contacts (append contacts
(mail-header-parse-addresses
(buffer-substring-no-properties (point)
point-end-of-line)))))
(dolist (contact contacts)
(setq name (malb/canonicalise-contact-name (car contact) (cdr contact) t))
;; extract first name
(if (string-match "\\([^ ,]+\\)" name)
(progn
(setq name (match-string 1 name))
(if ret
(setq ret (concat ret ", " name))
(setq ret name)))))
(if ret ret "there"))))A shortcut:
(defun malb/get-names-from-to-field ()
(interactive)
(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."
(interactive)
(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
(save-excursion
(goto-char (point-min))
(while (re-search-forward search-regexp nil t)
(kill-whole-line))
(message-goto-from)
(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)Searching
(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)
(progn
;; request contacts
(mu4e~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 AND)" .
(lambda (x)
(mu4e-headers-search
(format "contact:(%s)"
(mapconcat 'malb/extract-email (helm-marked-candidates) " AND ")))))
("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 OR)" .
(lambda (x)
(mu4e-headers-search
(format "contact:(%s)"
(mapconcat 'malb/extract-email (helm-marked-candidates) " OR "))))))))
(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
:ensure t
:config (progn
(defun malb/helm-mu (arg)
(interactive "p")
(let ((current-prefix-arg nil))
(cond
((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 ()
(interactive)
(malb/helm-mu-contacts 4)) mode-map))))Tip: C-c C-f aka helm-follow-mode is your friend.
Viewing
Use <TAB> to preview messages and q to close previews.
(require 'mu4e-view)
(require 'mu4e-headers)
(defun malb/preview-message ()
(interactive)
(mu4e-headers-view-message)
(sleep-for 0.1) ;; this is a HACK
(select-window (previous-window)))
;; based on (mu4e-select-other-view)
(defun malb/close-message-view ()
(interactive)
(let* ((other-buf
(cond
((eq major-mode 'mu4e-headers-mode)
(mu4e-get-view-buffer))
((eq major-mode 'mu4e-view-mode)
(mu4e-get-headers-buffer))))
(other-win (and other-buf (get-buffer-window other-buf))))
(if (window-live-p other-win)
(progn
(select-window other-win)
(sleep-for 0.1)
(mu4e~view-quit-buffer))
(mu4e~headers-quit-buffer))))
(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 . 12)
(:from . 22)
(:subject)))Render HTML e-mails in eww aka shr
;; use imagemagick, if available
(when (fboundp 'imagemagick-register-types)
(imagemagick-register-types))
(use-package mu4e-contrib
:config (setq mu4e-html2text-command #'mu4e-shr2text))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."
(let* ((txt (mu4e-message-field msg :body-txt))
(html (mu4e-message-field msg :body-html)))
(cond
((and (> (* mu4e-view-html-plaintext-ratio-heuristic
(length txt)) (length html))
(or (not mu4e-view-prefer-html) (not html)))
(set-fill-column 80))
(t
(set-fill-column 120)))
(visual-line-mode)
(visual-fill-column-mode)))
(add-hook 'mu4e-view-mode-hook #'malb/mu4e-view-mode-hook)Actions 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)(defun malb/search-for-sender (msg)
"Search for messages sent by the sender of the message at point."
(mu4e-headers-search
(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."
(mu4e-headers-search
(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)
(defun malb/find-correspondence (msg)
"Find messages involving all recipients."
(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.01 nil #'helm-mark-all))))
(action . (("Filter" . (lambda (x) (setq addresses (helm-marked-candidates))))
("Use all" . (lambda (x))))))))
(mu4e-headers-search
(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)Writing
Set format=flowed (source)
(setq mu4e-compose-format-flowed t)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)
(add-hook 'message-mode-hook #'adict-guess-dictionary)
(add-hook 'message-mode-hook #'malb/company-ispell-setup)
;; 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
(newline)
(insert (mail-header-from message-reply-headers) " writes:")
(newline)))
(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)Attachments
(setq mu4e-attachment-dir "~/Downloads")Attachments are mostly handled using the helm baloo interface below, 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
:config
(progn
;; 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)
(save-current-buffer
(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."
(save-excursion
(save-restriction
(widen)
(goto-char (point-min))
(let ((point (re-search-forward "^.*writes:$" nil t)))
(if point
(progn
(goto-char point)
(previous-line))
(goto-char (point-max))))
(newline)
(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 ()
(interactive)
(select-frame (make-frame-command))
(sleep-for 0.1) ;; this is a HACK
(toggle-frame-maximized)
(sleep-for 0.1) ;; this is a HACK
(set-frame-size (selected-frame) 200 64)
(set-frame-name "mu4e") ;; we use this in our window management
(mu4e))Kill mu4e frame.
(defun malb/mu4e-quit-session ()
(interactive)
(kill-buffer)
(delete-frame))
(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 ()
(toggle-frame-maximized)
(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))PGP
(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")Org-Mu4e
Link to mu4e messages and threads.
(use-package org-mu4e
:config (progn
(setq org-mu4e-link-query-in-headers-mode t)
(defun malb/switch-to-mu4e-advice (old-function &rest arguments)
(malb/switch-to-mu4e)
(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
Blogging
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
:ensure t
:config (progn
(let (credentials)
(setq credentials (auth-source-user-and-password "martinralbrecht.wordpress.com"))
(setq org2blog/wp-blog-alist
`(("martinralbrecht"
:url "https://martinralbrecht.wordpress.com/xmlrpc.php"
: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))
(mapconcat
(lambda (cat) cat)
(or (plist-get (cdr org2blog/wp-blog) :default-categories)
org2blog/wp-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
#+CATEGORY: %s
#+TAGS:
#+DESCRIPTION:
\n")
;; http://blog.somof.net/?p=1310 workaround for UTF-8 bug
(advice-add 'url-http-create-request :override
'url-http-create-request-debug)
(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"
url-http-extra-headers))
(not using-proxy))
nil
(let ((url-basic-auth-storage
'url-http-proxy-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))
nil
(url-get-authentication (or
(and (boundp 'proxy-info)
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
(concat
;; The request
(or url-http-method "GET") " "
(url-http--encode-string
(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
(format
"Extension: %s\r\n" url-extensions-header))
;; Who we want to talk to
(if (/= (url-port url-http-target-url)
(url-scheme-get-property
(url-type url-http-target-url) 'default-port))
(format
"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
(concat
"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)
(zlib-available-p)
(setq url-mime-encoding-string "gzip")))
(concat
"Accept-encoding: " url-mime-encoding-string "\r\n"))
(if url-mime-charset-string
(concat
"Accept-charset: "
(url-http--encode-string url-mime-charset-string)
"\r\n"))
;; Languages we understand
(if url-mime-language-string
(concat
"Accept-language: " url-mime-language-string "\r\n"))
;; Types we understand
"Accept: " (or url-mime-accept-string "*/*") "\r\n"
;; User agent
(url-http-user-agent-string)
;; Proxy Authorization
proxy-auth
;; Authorization
auth
;; Cookies
(when (url-use-cookies url-http-target-url)
(url-http--encode-string
(url-cookie-generate-header-lines
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"))
extra-headers
;; Length of data
(if url-http-data
(concat
"Content-length: " (number-to-string
(length url-http-data))
"\r\n"))
;; End request
"\r\n"
;; Any data
url-http-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)
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
:ensure t
:config (progn
(pdf-tools-install)
(setq-default pdf-view-display-size 'fit-page)
(setq pdf-annot-activate-created-annotations t)
(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\""
malb/tabular-jar
format-
(pdf-view-current-page)
txt-filename
pdf-filename) nil)
(switch-to-buffer buffer)
(insert-file-contents txt-filename)
(cond
((eq format nil) (progn
(org-mode)
(call-interactively 'mark-whole-buffer)
(call-interactively 'org-table-convert-region)))
((string-equal format "JSON") (progn
(json-mode)
(json-pretty-print-buffer))))
(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."
(interactive)
(let* ((bb (pdf-cache-boundingbox (pdf-view-current-page window)))
(h-margin (max 0.25 (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))
h-margin)
(+ (- (nth 3 bb) (nth 1 bb))
w-margin))))
(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))
right1
(- 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)
(org-mode)
(erase-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))
(mapc
(lambda (annot) ;; traverse all annotations
(progn
(let ((page (cdr (assoc 'page annot)))
(highlighted-text
(if (pdf-annot-get annot 'markup-edges)
(let ((highlighted-text
(with-current-buffer source-buffer
(pdf-info-gettext (pdf-annot-get annot 'page)
(pdf-tools-org-edges-to-region
(pdf-annot-get annot 'markup-edges))))))
(replace-regexp-in-string "\n" " " highlighted-text))
nil))
(note (pdf-annot-get annot 'contents)))
(insert (if compact "- " "* "))
(insert (format "page %s" page))
(when highlighted-text
(insert (if compact (format ": “%s” " highlighted-text)
(concat "\n\n#+BEGIN_QUOTE\n"
highlighted-text
"\n#+END_QUOTE"))))
(if (> (length note) 0)
(insert (if compact (format " %s\n" note)
(format "\n\n%s\n\n" note)))
(insert (if compact "\n" "\n\n"))))))
(cl-remove-if
(lambda (annot) (member (pdf-annot-get-type annot) (list 'link)))
annots)
)))
(defun malb/pdf-annot-export-as-md (compact)
"Export annotations to Markdown 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)
(markdown-mode)
(erase-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))
(mapc
(lambda (annot) ;; traverse all annotations
(progn
(insert (if compact
(format "- page %s: " (cdr (assoc 'page annot)))
(format "On page %s: \n" (cdr (assoc 'page annot)))))
;; insert text from marked-up region in an org-mode quote
(when (pdf-annot-get annot 'markup-edges)
(let ((highlighted-text (with-current-buffer source-buffer
(pdf-info-gettext (pdf-annot-get annot 'page)
(pdf-tools-org-edges-to-region
(pdf-annot-get annot 'markup-edges))))))
(if compact
(insert (format "“%s”\n\n" (replace-regexp-in-string "\n" " " highlighted-text)))
(insert (format "> %s\n\n" (replace-regexp-in-string "\n" "\n> " highlighted-text))))))
(let ((note (pdf-annot-get annot 'contents)))
(if (> (length note) 0)
(insert (format "%s\n\n" note))))))
(cl-remove-if
(lambda (annot) (member (pdf-annot-get-type annot) (list 'link)))
annots)
)))
(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
"
("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)))
("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)))Directories
Dired
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
: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$"
"\\.pyc"
"\\(?:\\.\\(?:aux\\|bak\\|dvi\\|log\\|out\\|nav\\|orig\\|rej\\|toc\\|vrb\\|pyg\\)\\)\\'"
"^\\.?#\\|^\\..+$")
"\\|"))
(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.
;; https://github.com/wasamasa/dotemacs/blob/master/init.org
(bind-key "RET" #'dired-find-alternate-file dired-mode-map)
(dired-async-mode)
(put 'dired-find-alternate-file 'disabled nil)))(use-package dired-x)Show dired histories you have visited.
(use-package helm-dired-history
:ensure t)dired-collapse renders directories with just one file like GitHub does.
(use-package dired-collapse
:ensure t)dired-narrow to … narrow down dired buffers
(use-package dired-narrow
:ensure t
:bind (:map dired-mode-map ("/" . dired-narrow)))dired-subtree to insert subtrees.
(use-package dired-subtree
:ensure t
:config
(bind-keys :map dired-mode-map
("i" . dired-subtree-insert)
(";" . dired-subtree-remove)))Neotree
(use-package neotree
:ensure t
:config (progn
(setq neo-theme (if (display-graphic-p) 'icons 'arrow)
neo-smart-open t)))Treemacs
(use-package treemacs
:ensure t
:defer t
:config (progn
(setq treemacs-follow-after-init t
treemacs-width 35
treemacs-indentation 2
treemacs-git-integration t
treemacs-collapse-dirs 3
treemacs-silent-refresh t
treemacs-change-root-without-asking nil
treemacs-sorting 'alphabetic-desc
treemacs-show-hidden-files t
treemacs-never-persist t
treemacs-is-never-other-window nil
treemacs-goto-tag-strategy 'refetch-index)
(treemacs-follow-mode t)
(treemacs-filewatch-mode t)))
(use-package treemacs-projectile
:defer t
:ensure t
:config (setq treemacs-header-function #'treemacs-projectile-create-header))Diff
(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)))Shells
We don’t need a $PAGER here (source)
(setenv "PAGER" "cat")Multi-Term
MultiTerm is a mode based on term.el, for managing multiple terminal buffers in Emacs. malb/multi-term-here opens terminal at point, close with q Config partly stolen from spacemacs.
(use-package multi-term
:ensure t
: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))
;; 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))
(add-to-list 'term-bind-key-alist '("C-r" . term-send-reverse-search-history))
(bind-key "C-y" #'term-paste term-raw-map)
(defun malb/term-send-tab ()
"Send tab in term mode."
(interactive)
(term-send-raw-string "\t"))
(add-to-list 'term-bind-key-alist '("<tab>" . malb/term-send-tab))
(defun malb/toggle-multi-term ()
"Toggles an multi-term in the project/directory associated with the current buffer.
The multi-term is renamed to match the current projectile project
or directory to make multiple multi-term windows easier. If a
multi-term of the same name already exists, it is reused.
If a multi-term with the same name is already shown, its window
is closed."
(interactive)
(let* ((parent (if (buffer-file-name)
(file-name-directory (buffer-file-name))
default-directory))
(height (/ (window-total-height) 3))
(name (concat "*terminal<" (if (projectile-project-p)
(projectile-project-name)
(car (last (split-string parent "/" t)))) ">*"))
(golden-ratio-mode nil)
(window (get-buffer-window name)))
(if (and window (<= (window-height window) (/ (frame-height) 3)))
(progn
(select-window window)
(delete-window))
(progn
(split-window-vertically (- height))
(other-window 1)
(if (get-buffer name)
(progn
(switch-to-buffer name)
(set-window-dedicated-p (get-buffer-window (current-buffer)) t))
(progn (multi-term)
(set-window-dedicated-p (get-buffer-window (current-buffer)) t)
(rename-buffer name)
(insert (concat "ls"))
(term-send-input)))))))))EShell
malb/toggle-eshell (source) and other stuff (source)
(use-package eshell
:config (progn
(defun malb/toggle-eshell ()
"Toggles an eshell in the project/directory associated with the current buffer.
The eshell is renamed to match the current projectile project or
directory to make multiple eshell windows easier. If an eshell of
the same name already exists, it is reused.
If an eshell with the same name is already shown, its window is closed."
(interactive)
(let* ((parent (if (buffer-file-name)
(file-name-directory (buffer-file-name))
default-directory))
(height (/ (window-total-height) 3))
(name (concat "*eshell<" (if (projectile-project-p)
(projectile-project-name)
(car (last (split-string parent "/" t)))) ">*"))
(golden-ratio-mode nil)
(window (get-buffer-window name)))
(if (and window (<= (window-height window) (/ (frame-height) 3)))
(progn
(select-window window)
(delete-window))
(progn
(split-window-vertically (- height))
(other-window 1)
(if (get-buffer name)
(progn
(switch-to-buffer name)
(set-window-dedicated-p (get-buffer-window (current-buffer)) t))
(progn
(eshell "new")
(rename-buffer name)
(set-window-dedicated-p (get-buffer-window (current-buffer)) t)
(insert (concat "ls"))
(eshell-send-input)))))))
(bind-key "C-§" #'malb/toggle-eshell)
(defun malb/eshell-keys ()
(bind-key "<home>" #'eshell-bol eshell-mode-map)
(bind-key "M-l" #'helm-eshell-history eshell-mode-map)
(bind-key "M-/" #'helm-esh-pcomplete eshell-mode-map))
;; https://github.com/bbatsov/emacs-dev-kit/blob/master/eshell-config.el
;; for some reason this needs to be a hook
(add-hook 'eshell-mode-hook #'malb/eshell-keys)
(setenv "PAGER" "cat") ;; https://github.com/howardabrams/dot-files/blob/master/emacs-eshell.org
(setq eshell-visual-subcommands '(("git" ("log" "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-hook 'eshell-mode-hook #'malb/eshell-add-visual-commands)
(defun malb/eshell-minor-modes ()
(smartparens-mode t))
(add-hook 'eshell-mode-hook #'malb/eshell-minor-modes)
;; mapped to 'x' in eshell
(defun eshell/x ()
(delete-window)
(eshell/exit))
(defalias 'eshell/q 'malb/quit-bottom-disposable-windows)
(defalias 'eshell/en 'find-file)
(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))
(erase-buffer)
(eshell-send-input)))
(setq eshell-input-filter
(lambda (str)
(not (or (string= "" str)
(string-prefix-p " " str)))))
(defun eshell/ag (&optional dir)
(malb/helm-ag (file-name-as-directory
(expand-file-name (or dir ".")))))
(defun malb/config-eshell-completion ()
(setq pcomplete-cycle-completions t
pcomplete-ignore-case t)
(setq-local company-idle-delay nil)
;; (malb/enable-company-pcomplete)
(define-key eshell-mode-map [remap eshell-pcomplete] #'company-complete))
(add-hook 'eshell-mode-hook #'malb/config-eshell-completion)
(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)))Git prompt
(use-package eshell-git-prompt
:ensure t
:config (progn (eshell-git-prompt-use-theme "powerline")))Git completion
(source)
(defun pcmpl-git-commands ()
"Return the most common git commands by parsing the git output."
(with-temp-buffer
(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
"^[[:blank:]]+\\([[:word:]-.]+\\)[[:blank:]]*\\([[:word:]-.]+\\)?"
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."
(with-temp-buffer
(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)
(cond
((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"))))
(t
(while (pcomplete-here (pcomplete-entries))))))Tips
Cat directly into a buffer (select with C-c M-b)
cat mylog.log >> #<buffer *scratch*>Tmux
(use-package emamux
:ensure t
:config (setq emamux:completing-read-type 'helm))Diagrams
PlantUML
(use-package plantuml-mode
:ensure t
:commands (plantuml-mode)
:init (progn
(setq plantuml-jar-path "/usr/share/plantuml/plantuml.jar")))Example
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
Calendar
calfw for displaying calendars, because why not.
(use-package calfw
:ensure t
:config (setq calendar-week-start-day 1))
(use-package calfw-cal)
(use-package calfw-ical)
(use-package calfw-org)Transfer.sh
transfer.sh for interfacing with https://transfer.sh.
(use-package transfer-sh
:ensure t)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.host = "imap.gmail.com";
exports.port = 993;
exports.tls = true;
exports.username = "martinralbrecht@gmail.com";
exports.password = // whatever needs doing
exports.onNewMail = "mbsync googlemail-minimal";
exports.onNewMailPost = "emacsclient -e '(mu4e-update-index)'";
exports.boxes = [ "INBOX"];(use-package prodigy
:ensure t
:init (progn
(prodigy-define-tag
:name 'email
:ready-message "Checking E-mail using IMAP IDLE. Ctrl-C to shutdown.")
(prodigy-define-service
:name "imapnotify"
:command "imapnotify"
:args (list "-c" (expand-file-name ".config/imapnotify.gmail.js" (getenv "HOME")))
:tags '(email)
:kill-signal 'sigkill)
(prodigy-define-service
:name "malb.io"
:cwd (expand-file-name "web/malb.io" malb/projects-dir)
:command "jekyll"
:args '("serve" "-P" "4001")
:port 4001
:tags '(web))
(prodigy-define-service
:name "discrete-subgroup"
:cwd (expand-file-name "web/lattice-meetings" malb/projects-dir)
:command "jekyll"
:args '("serve" "-P" "4002")
:url "http://127.0.0.1:4002/discrete-subgroup/"
:tags '(web))
(prodigy-define-service
:name "fpylll"
:cwd (expand-file-name "lattices/fpylll" malb/projects-dir)
:command "/home/malb/Scripts/fpylll-jupyter.sh"
:port 8889
:stop-signal 'sigkill
:kill-process-buffer-on-stop t
:tags '(development))
(prodigy-define-service
:name "sage-devel"
:cwd (expand-file-name "sage/notebooks" malb/projects-dir)
:command "/home/malb/Scripts/sage-jupyter.sh"
: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 [andC-x ]to move backward and forward through pages, respectively.
(use-package page-break-lines
:ensure t
:config (progn
(global-page-break-lines-mode)
(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
:ensure t
:config (progn
(defhydra hydra-page-breaks (global-map "C-x")
"pages"
("[" backward-page "backward")
("]" forward-page "forward")
("M-p" helm-pages "helm" :color blue)
("RET" nil "quit"))))X11 Integration
(setq x-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
x-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) "#")
(expand-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)EWW
(source)
(defun malb/switch-default-browser ()
"switch between default browser and eww"
(interactive)
(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))Org EWW
Paste websites to org-mode with org-eww-copy-for-org-mode.
(use-package org-eww
:after org)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)Misc
Settings
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
:config (setq uniquify-buffer-name-style 'forward))I hate tabs.
(setq-default indent-tabs-mode nil)Pressing y or n is sufficent.
(defalias 'yes-or-no-p 'y-or-n-p)Overwrite a selection by typing over it.
(pending-delete-mode t)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)
(setq history-delete-duplicates t)
(setq savehist-save-minibuffer-history t)
(setq 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 mouseUse kill -pUSR1 to kill Emacs. (source)
(defun malb/quit-emacs-unconditionally ()
(interactive)
(my-quit-emacs '(4)))
(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
'executable-make-buffer-file-executable-if-script-p)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)(setq search-default-mode 'char-fold-to-regexp)Functions
Add a function for renaming the file being edited (source)
(defun rename-current-buffer-file ()
"Renames current buffer and file it is visiting."
(interactive)
(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 (source)
(defun malb/copy-file-name-to-clipboard ()
"Copy the current buffer file name to the clipboard."
(interactive)
(let ((filename (if (equal major-mode 'dired-mode)
default-directory
(buffer-file-name))))
(when filename
(kill-new filename)
(message "Copied buffer file name '%s' to the clipboard." filename))))(source)
(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))))Scratch
We don’t want the scratch buffer to be killed ever.
(use-package unkillable-scratch
:ensure t
:config (progn
(unkillable-scratch 1)))Get a scratch for every mode quickly
(use-package scratch
:ensure t)We know what scratch is for
(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
#+END_SRC
")Emacs from Chrome
(use-package edit-server
:ensure t
:config (progn
(edit-server-start)))Memory Usage
(use-package memory-usage
:ensure t)Speed Reading
Spray is a speed reading mode.
(source)
(use-package spray
:ensure t
:config (progn
(setq spray-wpm 500)
(add-to-list 'spray-unsupported-minor-modes 'beacon-mode)))Alert
(use-package alert
:config (setq alert-default-style 'libnotify))File Types
Log Files
Using logview mode.
(use-package logview
:ensure t
:config (setq logview-auto-revert-mode 'auto-revert-tail-mode))PCAP Files
Wireshark et al. outputs (source)
(use-package pcap-mode
:ensure t)ELF Files
List symbols in .so and .a files (source)
(use-package elf-mode
:ensure t
:mode (("\\.so\\'" . elf-mode)
("\\.a\\'" . elf-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
:ensure t
:config (progn
(add-hook 'dired-mode-hook 'turn-on-stripe-buffer-mode)
(add-hook 'org-mode-hook 'turn-on-stripe-table-mode)
(set-face-attribute 'stripe-highlight nil :background "#eee8d5")))Theme
Use a solarized patched to be less yellow by changing
| color | original | malb |
s-base2 | #EEE8D5 | #EEEDEA |
s-base3 | #FDF6E3 | #FDFCF9 |
The result looks like this:
Note: We don’t set colours directly but instead wait until the first frame is created because we typically run emacs as a daemon.
(when (string-equal malb/theme "solarized")
(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/load-main-theme ()
(load-theme 'solarized-light t)
;; steal org style from Leuven
(set-face-attribute 'org-checkbox nil :weight 'bold
:box '(:line-width 1 :style 'pressed-button)
:foreground "white" :background "light gray")
(set-face-attribute 'org-done nil :weight 'bold :box '(:line-width 1 :color "#BBBBBB")
:foreground "#8BB300" :background "#F0F0F0")
(set-face-attribute 'org-scheduled-previously nil :foreground "#cb4b16")
(set-face-attribute 'org-tag nil :weight 'normal
:box '(:line-width 1 :color "#BBBBBB") :foreground "#9A9FA4")
(set-face-attribute 'org-todo nil :weight 'bold
:box '(:line-width 1 :color "#D8ABA7") :foreground "#cb4b16"
:background "#FFE6E4")
(set-face-attribute 'org-block-begin-line nil :inherit 'org-meta-line
:background (solarized-with-color-variables 'light base02)
:foreground "#657b83" :slant 'normal)
(set-face-attribute 'org-block-end-line nil :inherit 'org-meta-line
:background (solarized-with-color-variables 'light base02)
:foreground "#657b83" :slant 'normal)
(set-face-attribute 'org-block nil
:foreground "#657b83"
:background (solarized-with-color-variables
'light
(solarized-color-blend base02 base03 0.3)))
(unicode-fonts-setup)
(when (boundp 'hl-sentence-mode)
(set-face-attribute 'hl-sentence nil
:background (solarized-with-color-variables
'light
(solarized-color-blend base02 base03 0.6))))
(when (boundp 'which-func-mode)
(set-face-attribute 'which-func nil :foreground "#DEB542"))
;; steal spacemacs flycheck style
(when (boundp 'flycheck-mode)
(progn
(set-face-attribute 'flycheck-error nil :underline "#dc322f")
(set-face-attribute 'flycheck-warning nil :underline "#b58900")
(set-face-attribute 'flycheck-info nil :underline "#268bd2")
(set-face-attribute 'flycheck-fringe-error nil
:background (solarized-with-color-variables 'light base03)
:foreground "#dc322f" :weight 'bold)
(set-face-attribute 'flycheck-fringe-warning nil
:background (solarized-with-color-variables 'light base03)
:foreground "#DEB542" :weight 'bold)
(set-face-attribute 'flycheck-fringe-info nil
:background (solarized-with-color-variables 'light base03)
:foreground "#69B7F0" :weight 'bold)))
(eval-after-load "eshell-git-prompt"
'(progn
(set-face-attribute 'eshell-git-prompt-powerline-dir-face nil
:foreground (solarized-with-color-variables 'light base03))
(set-face-attribute 'eshell-git-prompt-powerline-clean-face nil
:foreground (solarized-with-color-variables 'light base03))
(set-face-attribute 'eshell-git-prompt-powerline-not-clean-face nil
:foreground (solarized-with-color-variables 'light base03))))
(eval-after-load "pdf-tools"
'(progn
(setq pdf-view-midnight-colors (cons "#444444"
(solarized-with-color-variables 'light base03)))
nil))
(eval-after-load "ein-cell"
'(progn
(set-face-attribute 'ein:cell-input-area nil
:background (solarized-with-color-variables
'light
(solarized-color-blend base02 base03 0.5)))
(set-face-attribute 'ein:cell-input-prompt nil
:background nil
:foreground "#657b83" :weight 'bold)))
(eval-after-load "dired-subtree"
(progn
(set-face-attribute 'dired-subtree-depth-1-face nil :background
(solarized-with-color-variables
'light
(solarized-color-blend base02 base03 0.3)))
(set-face-attribute 'dired-subtree-depth-2-face nil :background
(solarized-with-color-variables
'light
(solarized-color-blend base02 base03 0.3)))
(set-face-attribute 'dired-subtree-depth-3-face nil :background
(solarized-with-color-variables
'light
(solarized-color-blend base02 base03 0.3)))
(set-face-attribute 'dired-subtree-depth-4-face nil :background
(solarized-with-color-variables
'light
(solarized-color-blend base02 base03 0.3)))
(set-face-attribute 'dired-subtree-depth-5-face nil :background
(solarized-with-color-variables
'light
(solarized-color-blend base02 base03 0.3)))
(set-face-attribute 'dired-subtree-depth-6-face nil :background
(solarized-with-color-variables
'light
(solarized-color-blend base02 base03 0.3)))))))))(setq org-todo-keyword-faces
'(("CANCELLED" :foreground "gray" :weight bold)
("DISABLED" :foreground "gray" :weight bold)
("ONHOLD" :foreground "gray" :weight bold)
("STALLED" :foreground "gray" :weight bold)
("SUBMITTED" :foreground "gray" :weight bold)
("DELEGATED" :foreground "dark orange"
:weight bold :box (:line-width 1 :color "#D8ABA7") :background "#FFE6E4")
("WAITING" :foreground "dark orange"
:weight bold :box (:line-width 1 :color "#D8ABA7") :background "#FFE6E4")
("COAUTHOR" :foreground "dark orange"
:weight bold :box (:line-width 1 :color "#D8ABA7") :background "#FFE6E4")))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))Icons
(source)
(use-package all-the-icons
:ensure t)Powerline
We use Spaceline which customises Powerline.
(source)
(use-package spaceline-config
:ensure spaceline
: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
enabled."
(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)))
tag
(when num (int-to-string num)))))
(or (when spaceline-workspace-numbers-unicode
(spaceline--unicode-number str))
(propertize str 'face 'bold)))))
(defun malb/load-mode-line-theme ()
(setq spaceline-workspace-numbers-unicode nil)
(spaceline-spacemacs-theme 'python-venv)
(spaceline-helm-mode)
(spaceline-toggle-hud-off)
(spaceline-toggle-buffer-encoding-abbrev-off)
(set-face-attribute 'powerline-active1 nil :background "grey22" :foreground "white smoke")
(set-face-attribute 'powerline-active2 nil :background "grey40" :foreground "gainsboro")
(set-face-attribute 'powerline-inactive1 nil :background "grey55" :foreground "white smoke")
(set-face-attribute 'powerline-inactive2 nil :background "grey65" :foreground "gainsboro")
(set-face-attribute 'mode-line-buffer-id nil :foreground "gray40")
(powerline-reset))))Rainbow mode
Colourise colours or names in buffers (source)
(use-package rainbow-mode
:ensure t
:config (progn
(add-hook 'emacs-lisp-mode-hook #'rainbow-mode))
:diminish rainbow-mode)Beacon
Send a little beacon signal after jumps to make it easier to find the cursor.
(source)
(use-package beacon
:ensure t
:diminish beacon-mode
:config (progn
(defun malb/beacon-disable-hl-line-mode ()
(bound-and-true-p hl-line-mode))
(setq beacon-color "#EEAD0E")
(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
#'malb/beacon-disable-hl-line-mode)
(beacon-mode t)))Minimap
I don’t often use minimap, but it can be useful sometimes.
(use-package minimap
:ensure t
:config (progn
(setq minimap-display-semantic-overlays nil
minimap-major-modes '(prog-mode markdown-mode)
minimap-minimum-width 15
minimap-width-fraction 0.05
minimap-highlight-line nil
minimap-window-location 'right)
(set-face-attribute 'minimap-active-region-background nil :background nil)
(set-face-attribute 'minimap-font-face nil :height 0.2)))Vim-like Empty Line Indicator
(source)
(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."
(interactive)
(set-face-attribute 'default nil :height (+ (face-attribute 'default :height) 5)))
(defun malb/global-text-scale-decrease ()
"Globally decrease font size."
(interactive)
(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)
(bind-key "C-<mouse-4>" #'text-scale-increase)
(bind-key "C-<mouse-5>" #'text-scale-decrease)
(bind-key "C-M-<mouse-4>" #'malb/global-text-scale-increase)
(bind-key "C-M-<mouse-5>" #'malb/global-text-scale-decrease)Bell
(setq visible-bell nil)
(defun malb/invert-mode-line ()
(invert-face 'mode-line)
(invert-face 'powerline-active1)
(invert-face 'powerline-active2)
(powerline-reset))
(setq ring-bell-function (lambda ()
(malb/invert-mode-line)
(run-with-timer 0.05 nil #'malb/invert-mode-line)))Load Theme
(defvar malb/theme-loaded nil)
(defun malb/load-theme (frame)
(when (not malb/theme-loaded)
(select-frame frame)
(malb/load-main-theme)
(malb/load-mode-line-theme)
(setq malb/theme-loaded t)))
(if (daemonp)
(add-hook 'after-make-frame-functions #'malb/load-theme)
(malb/load-theme (selected-frame)))KDE Desktop Search with Baloo
Baloo is KDE’s desktop search. Below, we implement a tiny helm interface to it.
(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)
(start-process "baloosearch" nil "baloosearch" (format "-l %d " helm-baloo-file-limit) pattern))
(defun helm-baloo-search ()
(baloo-search helm-pattern))
(defun helm-baloo-transform (cs)
(let '(helm-baloo-clean-up-regexp (rx (or
control
(seq "[0;31m" (+ (not (any "["))) "[0;0m")
"[0;32m"
"[0;0m")))
(mapcar (function
(lambda (c)
(replace-regexp-in-string
(rx (seq bol (+ space))) ""
(replace-regexp-in-string helm-baloo-clean-up-regexp "" c))))
cs)))
(require 'helm-utils)
(defun malb/helm-mml-attach-files (_candidate)
"Attach all selected files"
(let* ((files (helm-marked-candidates)))
(mapcar 'mml-attach-file files)))
(defun malb/helm-transfer-sh-files (_candidate)
"Transfer.sh all selected files"
(let* ((files (helm-marked-candidates)))
(mapcar 'transfer-sh-upload-file-async files)))
(defvar helm-source-baloo
(helm-build-async-source "Baloo"
:candidates-process #'helm-baloo-search
:candidate-transformer '(helm-baloo-transform helm-skip-boring-files)
:action '(("Open" . (lambda (x) (helm-find-many-files x)))
("Attach to E-mail" . (lambda (x) (malb/helm-mml-attach-files x)))
("Transfer.sh" . (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)))
:keymap helm-generic-files-map
:help-message #'helm-generic-file-help-message))
(bind-key "C-c C-a" (lambda ()
(interactive)
(with-helm-alive-p
(helm-exit-and-execute-action 'malb/helm-mml-attach-files))) helm-generic-files-map)
(bind-key "C-c C-t" (lambda ()
(interactive)
(with-helm-alive-p
(helm-exit-and-execute-action 'malb/helm-transfer-sh-files))) helm-generic-files-map)
(defun helm-baloo ()
(interactive)
(call-process-shell-command "balooctl check" nil 0)
(helm :sources helm-source-baloo
:buffer "*helm baloo*"))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") ("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") ("Brian" "en-GB") ("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)AWS polly only renders to mp3, we use mplayer to play the produced file.
(defcustom aws-polly-play-command "mplayer %s"
"Command to play mp3"
:group 'aws-polly
:type 'string)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)
(adict-guess-buffer-language)))
(voice nil))
(dolist (entry aws-polly-voices voice)
(if (and (not voice) (string-prefix-p lang (cadr entry)))
(setq voice (car entry))))
(or voice (caar aws-polly-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."
(replace-regexp-in-string
" .*$" "" (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)))
(text))
(if reader
(progn
(pandoc--set 'read reader)
(get-buffer-create pandoc--output-buffer-name)
(pandoc--call-external "plain" nil (cons beginning end))
(switch-to-buffer (get-buffer-create pandoc--output-buffer-name))
(setq text (buffer-string))
(bury-buffer)
(replace-regexp-in-string "_" "" text))
(buffer-substring beginning end))))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))
(region-beginning)))
(end (if (not (use-region-p))
(save-excursion (forward-paragraph) (point))
(region-end)))
(text (aws-polly-plaintextify beginning end))
(output-filename (make-temp-file "emacs-aws-polly" nil ".mp3"))
(voice (if arg (aws-polly-voices-completing-read)
(aws-polly-select-voice text))))
(if (> (length text) aws-polly-character-limit)
(error "AWS polly will only accept up 1500 characters but got %s" (length text)))
(shell-command (format aws-polly-command voice text output-filename))
(start-process-shell-command "aws-polly-play" nil (format aws-polly-play-command output-filename))))Key Bindings
(malb/set-menu-key "<f2>" #'malb/hydra-window/body)
(malb/set-menu-key "A" #'malb/imenu-anywhere)
(malb/set-menu-key "a" #'aws-polly-region)
(malb/set-menu-key "b" #'malb/helm-omni)
(malb/set-menu-key "C" #'helm-calcul-expression)
(malb/set-menu-key "c" #'mu4e-compose-new)
(malb/set-menu-key "d" "\C-a\C- \C-n\M-w\C-y\C-p") ; duplicate previous line
(malb/set-menu-key "e" #'malb/toggle-eshell)
(malb/set-menu-key "f" #'helm-find-files)
(malb/set-menu-key "i" #'malb/helm-in-buffer)
(malb/set-menu-key "j" #'avy-goto-word-1)
(malb/set-menu-key "k" #'helm-baloo)
(malb/set-menu-key "l" #'helm-bibtex)
(malb/set-menu-key "m" #'malb/mail)
(malb/set-menu-key "M" #'malb/helm-mu)
(malb/set-menu-key "n" #'malb/sage-notebook)
(malb/set-menu-key "/" #'malb/helm-ag-projects)
(malb/set-menu-key "o" #'malb/helm-org-rifle-agenda-files)
(malb/set-menu-key "P" #'prodigy)
(malb/set-menu-key "p" #'pandoc-main-hydra/body)
(malb/set-menu-key "s" #'malb/toggle-sage)
(malb/set-menu-key "x" #'helm-M-x)
(malb/set-menu-key "z" #'malb/toggle-multi-term)Remap join-line to M-j where it’s easier to get to. join-line will join the line you’re on with the line above it in a reasonable manner for the type of file you’re editing.
(bind-key "M-j" #'join-line)C-z only annoys me, use C-x C-z when you need it
(bind-key "C-z" nil)cycle-spacing, when called multiple times, cycles through
- replacing all spaces with a single space
- removing all spaces
- restoring the original spacing
(source)
(bind-key "M-SPC" #'cycle-spacing)Toggle Map
(source)
(define-prefix-command 'malb/toggle-map)
(define-key ctl-x-map "t" 'malb/toggle-map)
(bind-key "e" #'toggle-debug-on-error malb/toggle-map)
(bind-key "f" #'malb/toggle-fold malb/toggle-map)
(bind-key "l" #'toggle-truncate-lines malb/toggle-map)
(bind-key "q" #'toggle-debug-on-quit malb/toggle-map)
(bind-key "i" #'imenu-list-smart-toggle malb/toggle-map)
(bind-key "d" #'dedicated-mode malb/toggle-map)
(bind-key "c" #'visual-fill-column-mode malb/toggle-map)Learn key bindings
(use-package which-key
:ensure t
:diminish which-key-mode
:config (progn
(which-key-mode 1)))
