A GNU Emacs Configuration for Academic Research in the Humanities

Table of Contents

Front matter

How this document was generated

This page displays the contents of an org-mode file called settings.org, which I have written in a literate style using the functionality of org-babel. When my Emacs starts up, a minimal init.el bootstraps the straight.el package manager and then interprets settings.org line-by-line, ignoring the prose and looking for Lisp code that I have marked for execution (the parts formatted here with a dark background). I have exported the contents of the configuration file in HTML format using built-in org-mode functionality, a couple of minor customizations, and the “readtheorg” theme.


My configuration assumes that the following are installed:

  • Recursive search through file contents in a directory: silversearcher-ag (ripgrep would also work)
  • General document exporting and importing: LaTeX + XeTeX; Pandoc
  • Version control: Git
  • Fonts: Linux Libertine O; Consolas
  • Backups: backup-each-save.el
  • Extra functionality:
    • For recording Emacs in action: gif-screencast.el (has its own dependencies) and keycast.el
    • scribe-mode.el (my own minor mode for transcribing medieval manuscripts written in Old French)

Contents of init.el

For package management within Emacs, I use straight.el instead of the built-in package.el. For me, the key feature of straight.el is that it allows one to reproduce one’s configuration exactly, right down to the specific installed versions of any given package. Anecdotally, it also seems to install packages better than package.el (I used to have packages fail to install from MELPA all the time).

Best of all, because straight.el is backward-compatible with the syntax of use-package, no major changes need to be made if you want to go back to package.el for whatever reason (and all the helpful use-package code you’ll find in online examples can be incorporated without difficulty).

(defvar bootstrap-version)
(let ((bootstrap-file
       (expand-file-name "straight/repos/straight.el/bootstrap.el" user-emacs-directory))
      (bootstrap-version 5))
  (unless (file-exists-p bootstrap-file)
         'silent 'inhibit-cookies)
      (goto-char (point-max))
  (load bootstrap-file nil 'nomessage))

(straight-use-package 'use-package)
(setq straight-use-package-by-default t)

(straight-use-package 'org)

 (expand-file-name "settings.org"
(put 'downcase-region 'disabled nil)

Contents of settings.el


My Emacs configuration is released under the terms of GNU General Public License:

;; Copyright (C) 2020
;; Joseph R. Johnson

;; This program is free software: you can redistribute it and/or modify it under
;; the terms of the GNU General Public License as published by the Free Software
;; Foundation, either version 3 of the License, or (at your option) any later
;; version.

;; This program is distributed in the hope that it will be useful, but WITHOUT
;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
;; FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
;; details.

;; You should have received a copy of the GNU General Public License along with
;; this program. If not, see <https://www.gnu.org/licenses/>.

General quality of life improvements

Declaring commonly used file paths

Declaring the path to my root working directory:

(setq dropbox-path "/home/joe/Dropbox/")

Next, some common org-mode files for personal organization. My approach is to keep four different files to manage all of my life data that is not tied to a specific reference (like a book or article that I read) through four org-mode files: planner.org, which has all of my tasks and calendar data; notebook.org, where I write down things I’ve learned and don’t want to forget; meetings.org, where I take notes on any in-person meetings; and scratch.org, a place to jot down ideas that don’t yet have a definite place. I store all of these in a folder called core-org-files within my Dropbox.

(setq planner-path (concat dropbox-path "core-org-files/planner.org")
      notebook-path (concat dropbox-path "core-org-files/notebook.org")
      meetings-path (concat dropbox-path "core-org-files/meetings.org")
      scratch-path (concat dropbox-path "core-org-files/scratch.org"))

Finally, the path to my BibTeX library for academic references:

(setq bib-path (concat dropbox-path "references/bibtex/MyLibrary.bib"))

Disabling Customize

Next, let’s make sure nothing gets in the way of our manually programmed changes to Emacs. Emacs includes built-in functionality called Customize which was created in the goal of offering users a graphical user interface modifying their settings (thus avoiding the need to deal with Emacs Lisp code directly). The problem is that while Customize can be greatly useful for tweaking the values of certain variables or changing the program’s theming settings, it cannot help you with the intensive kinds of changes that you will want to make in order to create a workflow for academic research. I have learned through hard experience that doing some tweaking in the Customize interface and some tweaking in direct Emacs Lisp can produce conflicts and inconsistencies.

Somewhat frustratingly, the simple act of never using Customize is not enough to prevent it from doing things: sometimes Emacs automatically invokes it in the background. This next setting relegates any such activity to a separate file that will not be loaded unless you explicitly wish to do so, for example with the command (load custom-file):

(setq custom-file "~/.emacs.d/custom.el")

General improvements from better-defaults.el:

Save bookmarks on exit:

(add-hook 'kill-emacs-hook 'bookmark-save) ;; or '(lambda () (bookmark-save))

Add externally copied text to kill ring no matter what:

(setq save-interprogram-paste-before-kill t)

Tell apropos to give us more info:

(setq apropos-do-all t)

Do not allow middle-click pasting to move the point:

(setq mouse-yank-at-point t)

Always use spaces instead of tab characters:

(setq-default indent-tabs-mode nil)

Add a newline to the end of files on save:

(setq require-final-newline t)

Always load newer bytecode:

(setq load-prefer-newer t)

Do not let ediff make a new Emacs frame:

(setq ediff-window-setup-function 'ediff-setup-windows-plain)


(use-package helm
  :diminish (helm-mode . "")
  :bind (("<f1>" . helm-find-files)
         ("<f4>" . helm-buffers-list)
         ("<f5>" . helm-show-kill-ring)
         ("<f6>" . helm-bookmarks)
         ("M-x" . helm-M-x))
  (helm-mode 1)
  (require 'helm-config)
  (setq-default helm-M-x-fuzzy-match t))


Found on EmacsWiki.

(add-hook 'isearch-mode-end-hook 'goto-match-beginning)
(defun goto-match-beginning ()
  (when isearch-forward (goto-char isearch-other-end)))

Loading Emacs Lisp

It is useful to have a convenient folder in one’s Emacs directory for loading custom Lisp files (after doing this, you can just call require to load the code):

(add-to-list 'load-path "~/.emacs.d/lisp/")

I found it annoying to open up init.el and run load-file every time I wanted to see the results of a configuration change. A quicker alternative:

(defun refresh-emacs ()
  (load "~/.emacs.d/init.el"))


diminish is a package that removes the distracting clutter of minor modes from the Emacs modeline. Since use-package has syntax for this, I tend to prefer using diminish that way whenever possible. This section is for modes that have no corresponding use-package declaration.

(use-package diminish)
(diminish 'auto-revert-mode)
(eval-after-load 'org-indent '(diminish 'org-indent-mode))
(diminish 'visual-line-mode)


(use-package which-key)

Output and message settings

By default, Emacs will truncate messages that exceed a certain length. This can be a big problem, especially when debugging a program. This setting changes this behavior:

(setq eval-expression-print-length nil)

This next line tells Emacs not to warn me when killing a process like a shell on quit (tip found on EmacsRedux):

(setq confirm-kill-processes nil)

Editing improvements

Manuscript transcription minor mode

(require 'scribe-mode)

Inserting guillemets

(defun insert-guillemets ()
  (insert "«»")
(global-set-key (kbd "C-c g") 'insert-guillemets)

Inserting cedillas

I’ve had persistent issues getting Emacs to type “รง” characters correctly using the US-International keyboard layout. This is the best solution I’ve been able to come up with so far.

(defun insert-c-cedilla ()
  (insert "ç"))
(global-set-key (kbd "C-c c") 'insert-c-cedilla)

Inserting org-beamer column properties

(defun insert-beamer-column ()
  (insert ":PROPERTIES:\n:BEAMER_COL: 0.\n:BEAMER_env: block\n:END:")
  (backward-char 25))

Inserting org-beamer LaTeX image limits

(defun limit-latex-image-size ()
  (insert "#+ATTR_LATEX: :width cm")
  (backward-char 2))

move-line up, move-line-down

I love how org-mode lets you move elements up or down with M-up / M-down, and it’d be nice to have that functionality in other contexts, for example when programming. This is the best solution I’ve found so far.

(defun move-line-up ()
  "Move up the current line."
  (transpose-lines 1)
  (forward-line -2)
(defun move-line-down ()
  "Move down the current line."
  (forward-line 1)
  (transpose-lines 1)
  (forward-line -1)
(global-set-key [(meta up)]  'move-line-up)
(global-set-key [(meta down)]  'move-line-down)

Custom function: my/tei-add-line-tags

I do a lot of transcribing of medieval manuscripts in TEI (or pseudo-TEI) format, so the <l> tag is a constant must. This function encloses <l></l> around a highlighted region.

I wrote this at a time when I probably couldn’t have come up with the entire thing off the top of my head; I wish I remembered to note where I adapted the model from. It was probably something like Xah’s Ergo Emacs (his whole guide to Emacs is amazing, and highly recommended).

My implementation is a little buggy/inconsistent - I need to fix it.

(defun my/tei-add-line-tags ()
  (forward-char (current-indentation))
  (while (< (point) (region-end))
    (insert "<l>")
    (move-end-of-line 1)
    (insert "</l>")
(global-set-key (kbd "C-c t") 'my/tei-add-line-tags)

Unfill-paragraph, unfill-region

Solution found on StackOverflow.

(defun unfill-paragraph ()
  (let ((fill-column (point-max)))
    (fill-paragraph nil)))
(defun unfill-region ()
  (let ((fill-column (point-max)))
    (fill-region (region-beginning) (region-end) nil)))
(global-set-key (kbd "C-M-q") 'unfill-paragraph)


(use-package undo-tree
  :diminish (undo-tree-mode . "")

Spell check

(setq ispell-dictionary "francais")
(defun spellcheck-start ()

Writing and research


(use-package helm-bibtex
  :after helm
  :bind (("C-c r" . helm-bibtex))
  (autoload 'helm-bibtex "helm-bibtex" "" t)
  (setq bibtex-completion-bibliography `(,bib-path))
  (setq reftex-default-bibliography `(,bib-path))
  (setq bibtex-completion-notes-path (concat dropbox-path "references/org-note-files"))
  (setq bibtex-completion-find-additional-pdfs t)
  (setq bibtex-completion-pdf-extension '(".pdf" ".djvu"))
  (setq bibtex-completion-pdf-field "File") ;; For Zotero's BibTeX format
  (setq bibtex-completion-format-citation-functions
    '((org-mode      . bibtex-completion-format-citation-pandoc-citeproc)
      (default       . bibtex-completion-format-citation-pandoc-citeproc))))


(use-package helm-ag
  :after helm
  :bind (("C-c s" . helm-do-ag)))


(use-package pdf-tools
  (setq-default pdf-view-display-size 'fit-page)
  (define-key pdf-view-mode-map (kbd "C-s") 'isearch-forward)
  (setq pdf-view-resize-factor 1.1)
  (setq pdf-annot-activate-created-annotations t)
  (setq pdf-view-midnight-colors '("#3F3F3F" . "#DCDCCC")) ;; for Zenburn theme
  (add-hook 'pdf-tools-enabled-hook 'pdf-view-midnight-minor-mode)
  (define-key pdf-view-mode-map (kbd "h") 'pdf-annot-add-highlight-markup-annotation)
  (define-key pdf-view-mode-map (kbd "t") 'pdf-annot-add-text-annotation)
  (define-key pdf-view-mode-map (kbd "D") 'pdf-annot-delete))

Solution for auto-starting midnight mode found here; PDF annotating keys from Pragmatic Emacs.


No frills, just needed for syntax highlighting:

(use-package markdown-mode)


I keep thinking I’ll use this, but haven’t needed it much yet.

(use-package writeroom-mode
  :bind (("<f9>" . writeroom-mode))
  (setq writeroom-width 96))

Word wrap and spacing settings

When first beginning to use Emacs, I disliked the default lack of word-wrap (for writers, this means that one paragraph of prose is interpreted by the program as one giant line, and if you maximize or full-screen the editor, the text appears as one long unreadable thread). One of my first moves was to enable word-wrap globally like so:

(global-visual-line-mode 1)

However, I now believe that there is a better way for writers using Emacs to handle this situation: leave this setting out and use the default fill-paragraph functionality (M-q), with a sensible fill-column setting (I understand that 80 is traditional).

(setq-default fill-column 80)

This last setting lets Emacs know that a sentence ends after a single (rather than double) space. I learned to double-space after full stops, but am gradually adjusting.

(setq sentence-end-double-space nil)


Essential org-mode settings

In my opinion, new users should be particularly careful to modify org-catch-invisible-edits from its default. While getting to know org-mode with the default setting, I lost some data by deleting a big chunk of text that was folded out of view.

(require 'org)
(setq org-agenda-files `(,planner-path))
(setq org-archive-location (concat dropbox-path "archive/org-archive-files/%s_archive::datetree/* Archived items"))
(setq org-todo-keywords '((sequence "TODO" "IN-PROGRESS" "|" "DONE" "CANCELED")
                          (sequence "WATCH" "|" "DONE")
                          (sequence "PROJECT" "|" "DONE")
                          (sequence "WAITING" "TODO" "IN-PROGRESS" "|" "DONE" "CANCELED")
                          (sequence "RAINYDAY" "TODO" "IN-PROGRESS" "|" "DONE" "CANCELED")
                          (sequence "HOLIDAY" "|" "DONE")))
(setq org-catch-invisible-edits 'smart)

Useful org-mode customizations

;; (setq org-list-demote-modify-bullet t)
;; (setq org-list-allow-alphabetical t)

(setq org-use-property-inheritance t)
(setq org-startup-indented t)
(setq org-hide-leading-stars t)
(setq org-src-preserve-indentation t)
(setq org-agenda-skip-deadline-if-done t)
(setq org-src-tab-acts-natively t)
(setq org-agenda-skip-deadline-prewarning-if-scheduled nil)
(setq org-lowest-priority ?D)
(setq org-startup-with-inline-images nil)
(setq org-agenda-use-time-grid nil)
(setq org-tags-column 80)
(setq org-confirm-elisp-link-function nil)
(setq org-cycle-separator-lines 1)
(setq org-agenda-window-setup 'current-window)
(setq org-todo-window-setup 'current-window)
(setq org-log-into-drawer t)
(setq org-agenda-skip-scheduled-if-done t)
(setq org-agenda-prefix-format "%t %s")
;; (setq org-agenda-hide-tags-regexp "research\\|teaching\\|classes\\|life")
(setq org-agenda-hide-tags-regexp nil)
(setq org-blank-before-new-entry
      '((heading . nil)
       (plain-list-item . nil)))

Clock table settings

(setq org-clock-persist 'history) ;; on these two, see https://orgmode.org/manual/Clocking-Work-Time.html

Capture template model

Details like active projects have been anonymized for online publication. When writing, I like to use two org files: one ’scaffolding’ file that contains all ideas/tasks/related info, and another file that contains the essay itself. I then use org-refile to pull tasks out of the scaffolding file and into my planner, where they will be able to appear in the agenda views.

(global-set-key (kbd "<f10>") 'org-capture)
(setq org-capture-templates
      '(("r" "Research")
        ("r1" "Project 1")
        ("r1t" "Project 1 task" entry (file+headline planner-path "Project 1")
         "* TODO %?" :prepend t :kill-buffer t)
        ("r1s" "Project 1 scaffolding" entry (file+headline (lambda () (concat dropbox-path "path/to/project_1")) "Inbox")
         "* %?" :prepend t :kill-buffer t)
        ("r2" "General/other research task" entry (file+headline planner-path "Other research")
         "* TODO %?" :prepend t :kill-buffer t)
        ("t" "Teaching task")
        ("t1" "Class task" entry (file+headline planner-path "Classes")
         "* TODO %?" :prepend t :kill-buffer t)
        ("t2" "Advising task" entry (file+headline planner-path "Advising")
         "* TODO %?" :prepend t :kill-buffer t)
        ("t3" "Grading task" entry (file+headline planner-path "Grading")
         "* TODO %?" :prepend t :kill-buffer t)
        ("t4" "Other teaching" entry (file+headline planner-path "Other teaching")
         "* TODO %?" :prepend t :kill-buffer t)
        ("a" "Administrative task" entry (file+headline planner-path "Administrative/other work")
         "* TODO %?" :prepend t :kill-buffer t)
        ("l" "Life task")
        ("c" "Calendar data")
        ("c1" "General calendar data" entry (file+headline planner-path "General calendar")
         "* %?" :prepend t :kill-buffer t)
        ("c2" "University calendar data" entry (file+headline planner-path "University calendar")
         "* %?" :prepend t :kill-buffer t)
        ("c3" "Birthdays and anniversaries" entry (file+headline planner-path "Birthdays and anniversaries")
         "* %?" :prepend t :kill-buffer t)
        ("m" "Meeting" entry (file+datetree meetings-path)
         "* %?" :prepend t :kill-buffer t)))

Agenda settings

(global-set-key (kbd "C-c a") 'org-agenda)
(setq org-agenda-custom-commands
      '(("w" "Waiting"
         ((todo "WAITING")))
        ("r" "Rainy day ideas"
         ((todo "RAINYDAY")))
        ("p" "Pending archive"
         ((todo "DONE"
                ((org-agenda-overriding-header "Completed and pending archive:")))
          (todo "CANCELED"
                ((org-agenda-overriding-header "Canceled and pending archive:")))))
        ("c" "Week at a glance"
         ((agenda ""
                  ((org-agenda-start-day "1d")
                   (org-agenda-span 'week)
                   (org-agenda-start-on-weekday 'nil)
                   (org-deadline-warning-days 12)
                   (org-agenda-overriding-header "Week at a glance:")))))
        ("f" "Full daily view"
         ((todo "PROJECT"
                ((org-agenda-overriding-header "Projects currently open:")))
          (agenda ""
                    '(org-agenda-skip-entry-if 'todo 'done))
                   (org-deadline-warning-days 12)
                   (org-agenda-start-day "1d")
                   (org-agenda-span 1)))
          (tags-todo "research"
                       '(or (org-agenda-skip-entry-if 'scheduled)
                            (org-agenda-skip-entry-if 'todo
                      (org-agenda-overriding-header "Open research tasks:")))
          (tags-todo "teaching"
                       '(or (org-agenda-skip-entry-if 'scheduled)
                            (org-agenda-skip-entry-if 'todo
                      (org-agenda-overriding-header "Open teaching tasks:")))
          (tags-todo "-research-teaching"
                       '(or (org-agenda-skip-entry-if 'scheduled)
                            (org-agenda-skip-entry-if 'todo
                      (org-agenda-overriding-header "Other open tasks:")))
          (todo "WATCH"
                ((org-agenda-overriding-header "Things to keep an eye on:")))

Refile model

Contents anonymized for publication.

(setq org-refile-targets
      '(("/path/to/project-1.org" :maxlevel . 9)
        ("/path/to/project-2.org" :maxlevel . 9)
        ("/path/to/project-3.org" :maxlevel . 9)
        ("/path/to/project-4.org" :maxlevel . 9)
        (nil :maxlevel . 9)))


This allows helm to read a list of tags in an org file. The standard configuration I found online did not allow tags to be assigned with C-c C-c. I found a fix here.

(use-package helm-org
    :after helm
    (add-to-list 'helm-completing-read-handlers-alist '(org-capture . helm-org-completing-read-tags))
    (add-to-list 'helm-completing-read-handlers-alist '(org-set-tags-command . helm-org-completing-read-tags)))

Beamer export settings

I’m not entirely sure this is necessary, but I haven’t gotten around to experimenting with it.

(require 'ox-latex)
(add-to-list 'org-latex-classes
               ("\\section\{%s\}" . "\\section*\{%s\}")
               ("\\subsection\{%s\}" . "\\subsection*\{%s\}")
               ("\\subsubsection\{%s\}" . "\\subsubsection*\{%s\}")))

LaTeX export: fix hyperlink style

Solutions found here:

(customize-set-value 'org-latex-with-hyperref nil)
(add-to-list 'org-latex-default-packages-alist "\\PassOptionsToPackage{hyphens}{url}")

LaTeX export: using XeLaTeX

Enable this if you want XeLaTeX to be used for exporting via ox (not usually necessary, since Pandoc covers this use case):

(setq org-latex-compiler "xelatex")


(use-package htmlize)

Fixing the theming of code blocks in htmlize export

Solution found here.

(defun eos/org-inline-css-hook (exporter)
  "Insert custom inline css to automatically set the
 background of code to whatever theme I'm using's background"
  (when (eq exporter 'html)
    (let* ((my-pre-bg (face-background 'default))
           (my-pre-fg (face-foreground 'default)))
         "<style type=\"text/css\">\n pre.src {background-color: %s; color: %s;}</style>\n"
         my-pre-bg my-pre-fg))))))
(add-hook 'org-export-before-processing-hook #'eos/org-inline-css-hook)

Exporting from org or Markdown to different formats using Pandoc

I’ve adapted the following function from the one posted by the John Kitchin, adding add two new features: (1) the ability to export timestamped files rather than overwriting existing exports; (2) the ability to automatically export to both docx and pdf in an orderly fashion, each kind in separate directories.

Note: For this to work as expected, you will need to have your desired .csl file and the path to your .bib file specified in the header of your org file.

(defun ox-export-via-pandoc ()
  "Export the current org file as a docx via markdown."
  (let* ((current-file (file-name-nondirectory buffer-file-name))
         (basename (file-name-sans-extension current-file))
         (docx-file (concat basename "_" (format-time-string "%Y-%m-%d-%Hh%Mm%Ss") ".docx"))
         (pdf-file (concat basename "_" (format-time-string "%Y-%m-%d-%Hh%Mm%Ss") ".pdf")))
    (unless (file-directory-p "docx-exports")
      (shell-command "mkdir docx-exports"))
    (shell-command (format
                    "pandoc -s --filter pandoc-citeproc %s -o docx-exports/%s"
                    current-file docx-file))
    (unless (file-directory-p "pdf-exports")
      (shell-command "mkdir pdf-exports"))
    (shell-command (format
                    "pandoc -s --variable mainfont=\"Linux Libertine O\" --pdf-engine=xelatex --filter pandoc-citeproc %s -o pdf-exports/%s"
                    current-file pdf-file))
(global-set-key (kbd "C-c p") 'ox-export-via-pandoc)

And this, for ODT:

(defun ox-export-odt-via-pandoc ()
  "Export the current org file as a odt via markdown."
  (let* ((current-file (file-name-nondirectory buffer-file-name))
         (basename (file-name-sans-extension current-file))
         (odt-file (concat basename "_" (format-time-string "%Y-%m-%d-%Hh%Mm%Ss") ".odt"))
         (pdf-file (concat basename "_" (format-time-string "%Y-%m-%d-%Hh%Mm%Ss") ".pdf")))
    (unless (file-directory-p "odt-exports")
      (shell-command "mkdir odt-exports"))
    (shell-command (format
                    "pandoc -s --filter pandoc-citeproc %s -o odt-exports/%s"
                    current-file odt-file))
    (unless (file-directory-p "pdf-exports")
      (shell-command "mkdir pdf-exports"))
    (shell-command (format
                    "pandoc -s --variable mainfont=\"Linux Libertine O\" --pdf-engine=xelatex --filter pandoc-citeproc %s -o pdf-exports/%s"
                    current-file pdf-file))
(global-set-key (kbd "C-c o") 'ox-export-odt-via-pandoc)


;; (add-hook 'org-agenda-mode-hook (lambda () (hl-line-mode 1))) ;; good for dracula theme
(add-hook 'org-capture-mode-hook 'delete-other-windows)


(require 'org-habit)
(setq org-habit-graph-column 50) ;; 80 for dracula theme
(setq org-habit-following-days 6)
(setq org-habit-preceding-days 30)


Code found in a discussion on r/Emacs. Say you have a section of your agenda view for a certain TODO keyword (like WATCH or PENDING), but you currently have no such tasks in any of your tracked files – this will prevent the section from showing up empty and cluttering the agenda view.

(defun org-agenda-delete-empty-blocks ()
  "Remove empty agenda blocks.
  A block is identified as empty if there are fewer than 2
  non-empty lines in the block (excluding the line with
  `org-agenda-block-separator' characters)."
  (when org-agenda-compact-blocks
    (user-error "Cannot delete empty compact blocks"))
  (setq buffer-read-only nil)
    (goto-char (point-min))
    (let* ((blank-line-re "^\\s-*$")
           (content-line-count (if (looking-at-p blank-line-re) 0 1))
           (start-pos (point))
           (block-re (format "%c\\{10,\\}" org-agenda-block-separator)))
      (while (and (not (eobp)) (forward-line))
         ((looking-at-p block-re)
          (when (< content-line-count 2)
            (delete-region start-pos (1+ (point-at-bol))))
          (setq start-pos (point))
          (setq content-line-count (if (looking-at-p blank-line-re) 0 1)))
         ((not (looking-at-p blank-line-re))
          (setq content-line-count (1+ content-line-count)))))
      (when (< content-line-count 2)
        (delete-region start-pos (point-max)))
      (goto-char (point-min))
      ;; The above strategy can leave a separator line at the beginning
      ;; of the buffer.
      (when (looking-at-p block-re)
        (delete-region (point) (1+ (point-at-eol))))))
  (setq buffer-read-only t))
(add-hook 'org-agenda-finalize-hook #'org-agenda-delete-empty-blocks)



This package keeps your Lisp code properly spaced at all times, without needing to press a special key or enter a command.

(use-package aggressive-indent
  (add-hook 'emacs-lisp-mode-hook #'aggressive-indent-mode))


The following settings mandate the use of UTF-8 encoding in all contexts. In my experience, failing to do this can create confusion when working across multiple operating systems (diacritics getting messed up, etc.).

(prefer-coding-system 'utf-8)
(setq-default buffer-file-coding-system 'utf-8)
(setq-default default-buffer-file-coding-system 'utf-8)
(setq-default coding-system-for-read 'utf-8)
(setq-default coding-system-for-write 'utf-8)
(setq-default locale-coding-system 'utf-8)
(set-terminal-coding-system 'utf-8)
(set-language-environment 'utf-8)
(set-keyboard-coding-system 'utf-8)
(set-default-coding-systems 'utf-8)

Appearance and display


(show-paren-mode 1)
(setq show-paren-delay 0)
(blink-cursor-mode 0)
(tool-bar-mode 0)
(menu-bar-mode 0)
(scroll-bar-mode 0)
(column-number-mode t)
(horizontal-scroll-bar-mode 0)
(setq inhibit-splash-screen t)
(setq split-width-threshold 80)
(setq initial-scratch-message "")
(setq inhibit-startup-message t)
(setq visible-bell t)
(setq scroll-conservatively 1000)
(tooltip-mode -1)

Frame size

Solution found here; hook added to make it work with Emacsclient when it opens a new frame.

(add-hook 'before-make-frame-hook
          #'(lambda ()
              (add-to-list 'default-frame-alist '(left   . 0))
              (add-to-list 'default-frame-alist '(top    . 0))
              (add-to-list 'default-frame-alist '(height . 40))
              ;; (add-to-list 'default-frame-alist '(width  . 122)) ;; good for dracula theme
              (add-to-list 'default-frame-alist '(width  . 112)))) ;; good for Zenburn theme

Zenburn theme

(use-package zenburn-theme
  (load-theme 'zenburn t)
  (setq org-todo-keyword-faces
        '(("WAITING" . "gray")
          ("WATCH" . "#88C087")
          ("PROJECT" . "#88C087")))
  (set-face-attribute 'org-todo nil
                      :bold nil
                      :foreground "#CC9393")
  (set-face-attribute 'org-agenda-date-today nil
                      :bold nil
                      :foreground "#FFFFFF")
  (set-face-attribute 'org-warning nil
                      :bold nil
                      :foreground "#CC9393")
  (set-face-attribute 'font-lock-keyword-face nil
                      :bold nil
                      :foreground "#F0DFAF")
  (set-face-attribute 'org-tag nil
                      :bold nil)
  (font-lock-add-keywords 'org-mode
                          '(("\\[@.*?\\]" . font-lock-keyword-face))) ;;add syntax highlighting for Pandoc citations
  (font-lock-add-keywords 'org-mode
                          '(("\\[-@.*?\\]" . font-lock-keyword-face))))


Requires installed packages: scrot, imagemagick, gifsicle

(require 'gif-screencast)
(global-set-key (kbd "<f11>") 'gif-screencast-start-or-stop)


(require 'keycast)

Miscellaneous key bindings


For improving the ergonomics of common navigation operations within Emacs (making new windows, switching between windows, etc.):

(global-set-key (kbd "M-o") 'other-window)
(global-set-key (kbd "M-5") 'delete-other-windows)
(global-set-key (kbd "M-4") 'split-window-below)
(global-set-key (kbd "M-3") 'split-window-right)
(global-set-key (kbd "M-0") 'delete-window)
(global-set-key (kbd "C-<left>") 'previous-buffer)
(global-set-key (kbd "C-<right>") 'next-buffer)
(setq next-line-add-newlines t)


Likewise, I prefer for these extremely common saving-related functions to depend on a single keystroke:

(global-set-key [f2] 'save-buffer)
(global-set-key [f3] 'save-some-buffers)
(global-set-key [f12] 'save-buffers-kill-terminal)

Quick access

I keep a file called scratch.org for random notes or drafting ideas that don’t really have a conventional place in my workflow and bind it to F8:

(global-set-key [f8] (lambda() (interactive)(find-file scratch-path)))

I found vscode’s key binding for opening a shell within the editor extremely convenient:

(global-set-key (kbd "C-`") 'shell)

Store links for linking purposes:

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



The magit package provides an incredibly fast an intuitive interface for using the Git version control system within Emacs.

(use-package magit
  :bind (("<f7>" . magit-status)))

Here’s an example of how to use magit in a basic case, as configured: press F7 to call it up in a file under version control, then use n and p to navigate to any files whose changes you wish to stage, staging them with s. Press c twice to indicate that you wish to make a commit, enter your commit message, and then press C-c C-c to make the commit.


To install it, download the file backup-each-save.el and place it in your ~/.emacs.d/lisp folder after adding that folder to your load-path). Then, using a solution for filtering from EmacsWiki:

(require 'backup-each-save)
(add-hook 'after-save-hook 'backup-each-save)
(defun backup-each-save-filter (filename)
  (let ((ignored-filenames
         '("^/tmp" "semantic.cache$" "\\.emacs-places$"
           "\\.recentf$" ".newsrc\\(\\.eld\\)?"))
        (matched-ignored-filename nil))
     (lambda (x)
       (when (string-match x filename)
         (setq matched-ignored-filename t)))
    (not matched-ignored-filename)))
(setq backup-each-save-filter-function 'backup-each-save-filter)

Emacs’s built-in backup

Settings for built-in Emacs backup functionality, augmented by snippets of code suggested by Pragmatic Emacs:

(setq auto-save-interval 20
      backup-by-copying t 
      kept-new-versions 10 
      kept-old-versions 0     
      delete-old-versions t  
      version-control t 
      vc-make-backup-files t
      backup-directory-alist '(("." . "~/.emacs.d/backups"))
      auto-save-file-name-transforms '((".*" "~/.emacs.d/auto-save-list/" t)))

Author: Joseph R. Johnson

Created: 2020-10-29 Thu 16:09