Emacs Configuration

Table of Contents


This is my literate Emacs configuration, written in org-mode and exported with ox-jekyll-md. You can see the raw, un-exported version on my emacs.d GitHub repo. Take a look at init.el there, which is first read by Emacs and does some basic bootstrapping, setting up package archives and use-package, and then calls org-babel-load-file on, the unexported version of this file, which pulls all the Emacs Lisp source blocks from this file, stitches them into an Emacs Lisp file called emacs.el, and executes it.

This is my third or fourth Emacs configuration. I was a vim user but started using Emacs in grad school, some time around 2015 I think, when I discovered org-mode. I started out using Spacemacs then, but quickly got overwhelmed by how much stuff there was in it and moved to vanilla Emacs. Here is my last vanilla Emacs config, which probably grew out of that move. In the summer of 2019 I switched to doom-emacs and loved it — most of the defaults were sane and I felt like it showed the power and extensibility of Emacs without overwhelming me by forcing me to do things its way, like Spacemacs did.

I decided to roll my own config again because I started having to fight Doom Emacs defaults to make things work the way I wanted, and with each update of Doom there’d be more changes I’d have to make to my config.el to keep my config working. Doom had introduced me to some new Emacs programs that I loved, like Magit and Ivy, but had a lot of features I didn’t use. Doom, as a project, has to target everyone — I just need a config that works for me.

This config is an active work in progress, and this web page is a living document of it. Eventually I’d like to set up a hook to export the web page whenever I push changes to, but right now it is generated by manually running C-c C-e P p to export the project (see Publishing), so it may not always be up to date.

UI and basic configuration

Disable blinking cursors and scroll bars and tool bars and menus, show line numbers:

(blink-cursor-mode 0)
(scroll-bar-mode 0)
(tool-bar-mode 0)
(tooltip-mode 0)
(menu-bar-mode 0)
(global-hl-line-mode 1) ;; highlight current line
(add-to-list 'default-frame-alist
	     '(fullscreen . maximized)) ;; open new frames full screen

Stop cluttering working directories with back up files and save them to /tmp/. Everything is under version control anyway, right?

(setq backup-directory-alist
      `((".*" . ,temporary-file-directory)))
(setq auto-save-file-name-transforms
      `((".*" ,temporary-file-directory t)))


(add-to-list 'default-frame-alist
	     '(font . "Source Code Pro Medium:pixelsize=15:foundry=ADBO:weight=normal:slant=normal:width=normal:spacing=100:scalable=true"))

Doom Themes

(use-package doom-themes
  ;; Global settings (defaults)
  (setq doom-themes-enable-bold t    ; if nil, bold is universally disabled
	doom-themes-enable-italic t) ; if nil, italics is universally disabled
  (load-theme 'doom-tomorrow-night t)

  ;; Enable flashing mode-line on errors

  ;; Corrects (and improves) org-mode's native fontification.


(use-package evil
  (evil-mode 1)
  (define-key evil-normal-state-map (kbd "j") 'evil-next-visual-line)
  (define-key evil-normal-state-map (kbd "k") 'evil-previous-visual-line))

Org mode


Org agenda files are saved $HOME/org.

(setq-default org-agenda-files (quote ("~/org")))
(setq org-directory "~/org")
(global-set-key (kbd "C-c a") 'org-agenda)

Configure TODO keywords:

(setq-default org-todo-keywords
	      '((sequence "TODO(t)" "IN PROGRESS(p)" "WAITING(w)" "|" "DONE(d)" "CLOSED(c)")))
(setq org-todo-keyword-faces
      '(("IN PROGRESS" warning bold)
	("WAITING" error bold)
	("CLOSED" org-done)))

Add a timestamp when you close a task:

(setq-default org-log-done 'time)


Soft-wrap lines, and don’t do it mid-word.

(setq-default org-startup-truncated nil)
(add-hook 'org-mode-hook #'visual-line-mode)

Use indentation, not extra \*s for headings.

(setq-default org-startup-indented t)

Don’t let org edit things under collapsed headings.

(setq-default org-catch-invisible-edits 'smart)


(global-set-key (kbd "C-c l") 'org-store-link)
(global-set-key (kbd "C-c C-l") 'org-insert-link)


By default Babel will only allow you to execute emacs-lisp source code blocks. You can enable Babel to allow execution of code blocks in a bunch of different languages though — a full list is here:

(setq org-src-tab-acts-natively t)
 '((emacs-lisp . t)
   (python . t)
   (ruby . t)
   (dot . t)))


This sets up Jekyll markdown export for my blog. See this post on

(use-package ox-jekyll-md
  :ensure t
  (setq org-jekyll-md-use-todays-date nil)
  (setq org-jekyll-md-include-yaml-front-matter t))
(require 'ox)
(require 'ox-publish)
(setq org-publish-project-alist
	 :base-directory "~/muumuus/org/"
	 :publishing-directory "~/muumuus/_posts"
	 :base-extension "org"
	 :recursive t
	 :publishing-function org-jekyll-md-publish-to-md
	 :headline-levels 4
	 :with-toc nil ; don't export a table of contents
	 :section-numbers nil)
	 :base-directory "~/.emacs.d/"
	 :publishing-directory "~/muumuus/_pages/"
	 :base-extension "org"
	 :recursive nil
	 :publishing-function org-jekyll-md-publish-to-md
	 :headline-levels 4
	 :with-toc t
	 :section-numbers nil)))

Capture templates

(global-set-key (kbd "C-c c") 'org-capture)
(setq org-capture-templates
      '(("b" "Blog" entry (file+headline "~/org/" "Blog ideas")
	 "* TITLE\n#+TITLE:\n#+DATE: %t\n#+JEKYLL_TAGS:\n#+JEKYLL_LAYOUT: post\n\n%?")
	("d" "Divide and Conquer: Algorithms on Coursera"
	 entry (file+headline "~/org/" "Divide and Conquer: Algorithms on Coursera")
	 "* %^{Title}\n#+DATE: %t\n\n%?")))


Make it so if you have split windows, both with dired buffers, and you perform a rename or copy action on an item in one dired buffer, its default target is the other dired buffer.

(setq dired-dwim-target t)

Evil keybindings:

(evil-set-initial-state 'dired-mode 'normal)


(use-package company
  :ensure t
  :init (add-hook 'after-init-hook 'global-company-mode)
  (:map company-active-map
	("<return>" . nil)
	("C-<return>" . company-complete-selection))
  (setq company-idle-delay 0)
  (setq company-minimum-prefix-length 2)
  (setq company-auto-complete 'company-explicit-action-p))



(use-package graphviz-dot-mode
  :ensure t
  (setq graphviz-dot-indent-width 4))

(use-package company-graphviz-dot
  :ensure t)


Recognize .latex files as… LaTeX.

(setq auto-mode-alist (cons '("\\.latex$" . latex-mode) auto-mode-alist))


I had issues with syntax highlighting and identation breaking using enh-ruby-mode, so I’m back to just plain ruby-mode.

flymake-ruby for syntax checking.

(use-package flymake-ruby
  :ensure t
  :hook (ruby-mode . flymake-ruby-load))

inf-ruby opens irb in a buffer.

(use-package inf-ruby
  :ensure t)

rubocop is a linter.

(use-package rubocop
  :ensure t
  :hook (ruby-mode . rubocop-mode))


(use-package salt-mode
  :ensure t
  (add-hook 'salt-mode-hook
	    (lambda ()
	      (flyspell-mode 1))))


(use-package terraform-mode
  :ensure t)


(use-package magit
  :bind ("C-x g" . magit-status)
  :ensure t)
(use-package evil-magit
  :ensure t)
(require 'evil-magit)


(use-package diminish
  :ensure t)


(use-package counsel
  :ensure t
  :diminish ivy-mode
  :bind (("C-s" . swiper-isearch)
	 ("M-x" . counsel-M-x)
	 ("C-c k" . counsel-rg))
  (ivy-mode 1)
  (counsel-mode 1)
  (setq ivy-use-virtual-buffers t))

Start Emacs server



These are sources I’ve used to build my emacs configuration: