Skip to content
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
Cannot retrieve contributors at this time

GNU Emacs Configuration File

Table of Contents


This document contains my entire GNU Emacs configuration file in Literate Programming style. Literate Programming strives to invert the normal programming paradigm; Instead of source code scattered with comments, Literate Programming encourages code to be written as a human readable document, in prose, with source code blocks embedded inside of it. The source code is later interpreted, while the human readable prose is ignored by the interpreter or compiler.

The magic here is provided by org-babel, which provides a method for extracting and evaluating Emacs Lisp expressions inside an org-mode file. The only thing needed in the main Emacs init file is this line:

(org-babel-load-file "~/.emacs.d/")

Basic Setup

Native Compilation

If (and only if) Emacs has native compilation available, set it up to do its thing.

(when (and (fboundp #'native-comp-available-p)
           (functionp #'json-serialize))
  (setq comp-deferred-compilation t))

Where to store Customization

First things first. Because I use org-babel and literate programming style for my config, I want my init.el file to be as small as possible. For that reason, I want Emacs to put all of its generated fluff into a separate file that is actually outside the Emacs directory.

(let ((local-custom-file "~/.emacs-custom.el"))
  (when (not (file-exists-p local-custom-file))
    (write-region "" nil local-custom-file))
  (setq custom-file local-custom-file)
  (load custom-file))

Default Directory

Some builds of emacs on exotic operating systems don’t automatically know that I want my home directory to be the default find-file directory.

(setq default-directory (expand-file-name "~/"))

Reducing Clutter

Next, I like to immediately reduce what I consider to be visual clutter. Your milage may vary, but for me this means turning off startup messages, the splash screen, tool bar, and tooltips.

(setq warning-minimum-level :emergency
      inhibit-startup-message t
      inhibit-splash-screen t)
(tool-bar-mode -1)
(tooltip-mode -1)
(scroll-bar-mode -1)
(menu-bar-mode -1)

Local lisp directory

Some packages are kept as submodules under ~/.emacs.d/lisp/, so I add this to my load path.

(let ((default-directory  "~/.emacs.d/lisp/"))


Better Defaults

A few minor tweaks that I enjoy. First, I like to take space from all windows whenever I split a window

(setq-default window-combination-resize t)

Next, stretch the cursor to fill a full glyph cell

(setq-default x-stretch-cursor t)

I prefer to use ibuffer when listing buffers

(global-set-key [remap list-buffers] 'ibuffer)

Long line improvements

Here are a few settings that help improve Emacs performance when editing very long lines. These tips are taken from

First, we tell Emacs that we’re really only using left-to-right text.

(setq-default bidi-paragraph-direction 'left-to-right)

(if (version<= "27.1" emacs-version)
    (setq bidi-inhibit-bpa t))

Next we set global “so-long-mode”, which tries to tell Emacs to be smarter about opening files with long lines.

(if (version<= "27.1" emacs-version)
    (global-so-long-mode 1))

Tidying Up the Working Directory

Emacs, by default, keeps backup files in the current working directory. I much prefer to keep all backup files together in one place. This will put them all into the directory ~/.emacs.d/backups/, creating the directory if it does not exist.

(if (not (file-exists-p "~/.emacs.d/backups/"))
    (make-directory "~/.emacs.d/backups/" t))
(setq backup-directory-alist
      '(("." . "~/.emacs.d/backups/")))
(setq auto-save-file-name-transforms
      '((".*" "~/.emacs.d/backups/" t)))
(setq backup-by-copying t)
(setq auto-save-default t)

Next, these settings control how many backup versions to keep, and specify that older versions should be silently deleted (don’t warn me).

(setq kept-old-versions 2)
(setq kept-new-versions 5)
(setq delete-old-versions t)

Spelling is important (I’m terrible at spelling).

 ((executable-find "aspell")
  (setq ispell-program-name "aspell"))
 ((executable-find "hunspell")
  (setq ispell-program-name "hunspell")
  (setq ispell-local-dictionary "en_US")
  (setq ispell-local-dictionary-alist
        '(("en_US" "[[:alpha]]" "[^[:alpha:]]" "[']"
           nil ("-d" "en_US") nil utf-8))))
 (t (setq ispell-program-name nil)))

On macOS, I turn off --dired (because ls does not support it).

(when (string= system-type "darwin")
  (setq dired-use-ls-dired nil))

I completely disable lockfiles, which I don’t need, and which only cause trouble.

(setq create-lockfiles nil)

Lastly, I disable the default “Control-Z” behavior of suspending emacs, because I find that I accidentally hit this key combo way too often when my clumsy fingers are trying to hit “Control-X”

(global-unset-key [(control z)])
(global-unset-key [(control x)(control z)])


scroll-step controls the number of lines that the window will scroll automatically when the cursor moves off the screen. By default, it will jump you so that the cursor is centered (vertically) after scrolling. I really don’t like this behavior, so I set it to 1 so the window will only move by a single line.

(setq scroll-step 1)

Next, setting scroll-conservatively to a very large number will further prevent automatic centering. The value 10,000 comes from a suggestion on the Emacs Wiki.

(setq scroll-conservatively 10000)


I always prefer 4 spaces for indents.

(setq-default c-basic-offset 4)
(setq-default sh-basic-offset 4)
(setq-default tab-width 4)
(setq-default indent-tabs-mode nil)

And next, I want to fix how multi-line initialization in C-like languages is handled (for example, when initializing an array or a struct). By default, elements after the brace-list-intro character get lined up directly below it, like this:

int array[3] = {

By setting the correct value for c-set-offset 'brace-list-intro, I can get what I consider to be a much better offset that looks like this:

int array[3] = {

Here’s the setting:

(c-set-offset 'brace-list-intro '+)


Tramp is a useful mode that allows editing files remotely.

The first thing I like to do is set the default connection method.

(setq tramp-default-method "ssh")

Then, I up some default values to make editing large directories happy.

(setq max-lisp-eval-depth 10000)   ; default is 400
(setq max-specpdl-size 10000)      ; default is 1000

Recent Files

Keep a list of recently opened files

(recentf-mode 1)
(setq-default recent-save-file "~/.emacs.d/recentf")

Exec Path

If certain directories exist, they should be added to exec-path, and the PATH environment variable.

(setq loomcom-append-to-path

(mapc #'(lambda (dir)
          (when (file-exists-p (expand-file-name dir))
            ;; Add the directory to exec-path
            (add-to-list 'exec-path (expand-file-name dir))
            ;; Add the directory to the PATH environment variable, but
            ;; replace `~' with `$HOME'
            (setenv "PATH"
                    (concat (getenv "PATH")
                            (concat ":" (replace-regexp-in-string "^~" "$HOME" dir))))))


Enable integration between Emacs and GPG.

(setenv "GPG_AGENT_INFO" nil)
(require 'epa-file)
(require 'password-cache)
(setq epg-pgp-program "gpg")
(setq password-cache-expiry (* 15 60))
(setq epa-file-cache-passphrase-for-symmetric-encryption t)
(setq epa-pinentry-mode 'loopback)

Window Navigation

I frequently split my Emacs windows both horizontally and vertically. Navigation between windows with C-x o is tedious, so I use C-<arrow> to navigate between windows. (N.B.: This overrides the default behavior of moving forward or backward by word using C-<right> nad C-<left>, so keep that in mind)

The typical way of doing this would be just to set the following in your config:

(windmove-default-keybindings 'ctrl)

However, there’s one downside here: If you accidentally try to navigate to a window that doesn’t exist, it raises an error and/or traps into the debugger (if debug-on-error is enabled). No good! So instead, I wrap in a lambda that ignores errors (Inspired by: EmacsWiki WindMove)

(global-set-key (kbd "C-<left>")
                #'(lambda ()
                    (ignore-errors (windmove-left))))
(global-set-key (kbd "C-<right>")
                #'(lambda ()
                    (ignore-errors (windmove-right))))
(global-set-key (kbd "C-<up>")
                #'(lambda ()
                    (ignore-errors (windmove-up))))
(global-set-key (kbd "C-<down>")
                #'(lambda ()
                    (ignore-errors (windmove-down))))

A Resize Helper

I like a standard editor size of 88 by 66 characters (If you know why, you win a cookie!) This helper will set that size automatically.

(defun set-frame-standard-size () (interactive)
       (set-frame-size (selected-frame) 88 66))

(defun set-frame-double-size () (interactive)
       (set-frame-size (selected-frame) 176 66))

Other Key Bindings

Shortcut for “Goto Line”

(global-set-key (kbd "C-x l") #'goto-line)

Shortcut for “Delete Trailing Whitespace”

(global-set-key (kbd "C-c C-x w") #'delete-trailing-whitespace)

Miscellaneous Settings

Turn off the infernal bell, both visual and audible.

(setq ring-bell-function 'ignore)

Enable the upcase-region function. I still have no idea why this is disabled by default.

(put 'upcase-region 'disabled nil)

Whenever we visit a buffer that has no active edits, but the file has changed on disk, automatically reload it.

(global-auto-revert-mode t)

I’m really not smart sometimes, so I need emacs to warn me when I try to quit it.

(setq confirm-kill-emacs 'yes-or-no-p)

Remote X11 seems to have problems with delete for me (mostly XQuartz, I believe), so I force erase to be backspace.

(when (eq window-system 'x)
  (normal-erase-is-backspace-mode 1))

When functions are redefined with defadvice, a warning is emitted. This is annoying, so I disable these warnings.

(setq ad-redefinition-action 'accept)

Tell Python mode to use Python 3

(setq python-shell-interpreter "python3")


Default Face

Not all fonts are installed on all systems where I use Emacs. This code will iterate over a list of fonts, in order of my personal preference, and set the default face to the first one available. Of course, if Emacs is not running in a windowing system, this is ignored.

(when window-system
  (let* ((families '("Hack"
                     "Roboto Mono"
                     "Input Mono"
                     "Courier New"
         (selected-family (cl-dolist (fam families)
                            (when (member fam (font-family-list))
                              (cl-return fam)))))
    (set-face-attribute 'default nil
                        :family selected-family
                        :height 120)
    (set-face-attribute 'fixed-pitch nil
                        :family selected-family
                        :height 120)))

Window Frame


By default, the Emacs frame (what you or I would call a window) title is user@host. I much prefer the frame title to show the actual name of the currently selected buffer.

(setq-default frame-title-format "%b")
(setq frame-title-format "%b")

Changing Font Size on the Fly

By default, you can increase or decrease the font face size in a single window with C-x C-+ or C-x C--, respectively. This is fine, but it applies to the current window only (note: In Emacs, a window is what you or I would probably call a frame or a pane… yes, I know, just work with it). I like to map C-+ and C-- to functions that will change the height of the default face in ALL windows.

First, I create a base function to do the change by a certain amount in a certain direction.

(defun change-face-size (dir-func &optional delta)
  "Increase or decrease font size in all frames and windows.

* DIR-FUNC is a direction function (embiggen-default-face) or
* DELTA is an amount to increase.  By default, the value is 10."
     'default nil :height
     (funcall dir-func (face-attribute 'default :height) delta))))

Then, I create two little helper functions to bump the size up or down.

(defun embiggen-default-face (&optional delta)
  "Increase the default font.

* DELTA is the amount (in point units) to increase the font size.
  If not specified, the dfault is 10."
  (let ((incr (or delta 10)))
    (change-face-size '+ incr)))

(defun ensmallen-default-face (&optional delta)
  "Decrease the default font.

* DELTA is the amount (in point units) to decrease the font size.
  If not specified, the default is 10."
  (let ((incr (or delta 10)))
    (change-face-size '- incr)))

And, finally, bind those functions to the right keys.

(global-set-key (kbd "C-+")  'embiggen-default-face)
(global-set-key (kbd "C--")  'ensmallen-default-face)

Shell Colors

Turn on ANSI colors in the shell.

(autoload 'ansi-color-for-comint-mode-on "ansi-color" nil t)
(add-hook 'shell-mode-hook 'ansi-color-for-comint-mode-on)

Assembly Mode hack

Tabs are all wrong in assembly mode, so here’s a fix.

(add-hook 'asm-mode-hook (lambda ()
                           (setq indent-tabs mode nil)
                           (setq tab-stop-list (number-sequence 8 60 8))))

Line Numbers

I like to see (Line,Column) displayed in the modeline.

(setq line-number-mode t)
(setq column-number-mode t)

I also like seeing line numbers in the gutter, but I want them to be relative, such that the lines are numbered 1,2,3,4… both decreasing and increasing from the current line.

(global-display-line-numbers-mode t)
(setq display-line-numbers 'relative)

Show the Time

I like having the day, date, and time displayed in my modeline. (Note that it’s pointless to display seconds here, since the modeline does not automatically update every second, for efficiency purposes)

(setq display-time-day-and-date t)
(display-time-mode 1)

Line Wrapping

By default, if a frame has been split horizontally, partial windows will not wrap.

(setq truncate-partial-width-windows nil)

I also prefer my fill-column to be at 74, not the default of 70

(setq-default fill-column 74)


Whenever the cursor is on a paren, highlight the matching paren.

(show-paren-mode t)

I like automatic pair matching, but you might want to turn this off if you find it annoying.


Mac OS X Specific Tweaks

GNU Emacs running on recent versions of MacOS in particular exhibit some pretty ugly UI elements. Further, I don’t like having to use the Option key for Meta, so I switch things around on the keyboard. Note, though, that this block is only evaluated when the windowing system is =’ns=, so this won’t do anything at all on Linux.

(when (eq window-system 'ns)
  (add-to-list 'frameset-filter-alist
               '(ns-transparent-titlebar . :never))
  (add-to-list 'frameset-filter-alist
               '(ns-appearance . :never))
  (setq mac-option-modifier 'super
        mac-command-modifier 'meta
        mac-function-modifier 'hyper
        mac-right-option-modifier 'super))

Package Management

Basic Setup

We’ll begin by requiring package mode and setting up URLs to the package archives.

(require 'package)
(setq package-enable-at-startup t)
(setq package-archives '(("gnu" . "")
                         ("melpa" . "")))

Then, actually initialize things.


And then, if the use-package package is not installed, install it immediately.

(unless (package-installed-p 'use-package)
  (package-install 'use-package))
(require 'use-package)


I never tire of experimenting with themes. This section changes pretty often.

(use-package modus-themes
  :ensure t
  (setq modus-themes-org-blocks 'gray-background
        modus-themes-mixed-fonts nil
        modus-themes-subtle-line-numbers t
        modus-themes-region '(bg-only)
        modus-themes-bold-constructs t
        modus-themes-italic-constructs t
        modus-themes-completions '((matches . (extrabold))
                                   (selection . (semibold accented))
                                   (popup . (accented intense)))
        modus-themes-mode-line '(accented borderless padded))
  (load-theme 'modus-vivendi t))

(use-package olivetti
  :ensure t
  (setq olivetti-body-width 90))

Org Mode

Next is org-mode, which I use constantly, day in and day out.

(defun my-org-agenda-format-date-aligned (date)
  "Format a DATE string for display in the daily/weekly agenda, or timeline.
This function makes sure that dates are aligned for easy reading."
  (require 'cal-iso)
  (let* ((dayname (calendar-day-name date 1 nil))
         (day (cadr date))
         (day-of-week (calendar-day-of-week date))
         (month (car date))
         (monthname (calendar-month-name month 1))
         (year (nth 2 date))
         (iso-week (org-days-to-iso-week
                    (calendar-absolute-from-gregorian date)))
         (weekyear (cond ((and (= month 1) (>= iso-week 52))
                          (1- year))
                         ((and (= month 12) (<= iso-week 1))
                          (1+ year))
                         (t year)))
         (weekstring (if (= day-of-week 1)
                         (format " W%02d" iso-week)
    (format "%-2s. %2d %s"
            dayname day monthname)))

(use-package org
  :ensure t
  ;; I like to have visual-line-mode enabled in org buffers
  :init (add-hook 'org-mode-hook #'visual-line-mode)
  (setq org-hide-emphasis-markers t
        org-pretty-entities t
        org-tags-column -65
        org-latex-listings 't
        org-export-default-language "en"
        org-export-with-smart-quotes t
        org-agenda-tags-column -65
        org-deadline-warning-days 14
        org-table-shrunk-column-indicator ""
        org-agenda-block-separator (string-to-char " ")
        org-adapt-indentation nil
        org-confirm-babel-evaluate nil
        org-fontify-whole-heading-line t
        org-agenda-format-date 'my-org-agenda-format-date-aligned
        ;; Use CSS for htmlizing HTML output
        org-html-htmlize-output-type 'css
        ;; Open up org-mode links in the same buffer
        org-link-frame-setup '((file . find-file))))

I have a lot of custom configuration for org-mode.

Timestamp Helpers

When I keep a long-running notes file, I like each top level entry to have a DATE: property set. This function automatically inserts the current timestamp as a property.

(defun timestamp-notes-entry ()
  "Insert a DATE property in the current heading with the current
   (format-time-string "<%F %a %H:%M>" (current-time))))

(define-key org-mode-map (kbd "C-c C-x t") #'timestamp-notes-entry)

Org Agenda

Org Agenda is a great way of tracking time and progress on various projects and repeatable tasks. It’s built into org-mode.

I add a quick and easy way to get into org-agenda from any org-mode buffer by pressing C-c a.

(global-set-key (kbd "C-c a") 'org-agenda)

Next, I add a custom org-agenda command to show the next three weeks.

(setq org-agenda-custom-commands
      '(("n" "Agenda / INTR / PROG / NEXT"
         ((agenda "" nil)
          (todo "INTR" nil)
          (todo "PROG" nil)
          (todo "NEXT" nil)))
        ("W" "Next Week" agenda ""
         ((org-agenda-span 7)
          (org-agenda-start-on-weekday 0)))
        ("N" "Next Three Weeks" agenda ""
         ((org-agenda-span 21)
          (org-agenda-start-on-weekday 0)))))

Then, I define some faces and use them for deadlines in org-agenda.

(defface deadline-soon-face
  '((t (:foreground "#ff0000"
                    :weight bold
                    :slant italic
                    :underline t)))
  "Soon deadlines")

(defface deadline-near-face
  '((t (:foreground "#ffa500"
                    :weight bold
                    :slant italic)))
  "Near deadlines")

(defface deadline-distant-face
  '((t (:foreground "#ffff00"
                    :weight bold
                    :slant italic)))
  "Distant deadlines")

(setq org-agenda-deadline-faces
      '((0.75 . deadline-soon-face)
        (0.5  . deadline-near-face)
        (0.25 . deadline-distant-face)
        (0.0  . deadline-distant-face)))

Then I set my org-todo-keywords so that I can manage my workflow states the way I like to. Although my own list is very linear and simple, they can become quite complex if need be!

(setq org-todo-keywords

And finally, I set some file locations. This is a bit convoluted because I use Agenda both for work and for home. At work, I keep a file called ~/.org-agenda-setup.el that contains my agenda files and archive location information. At home, I just use what’s baked into this file.

Also note that I like to keep archived Agenda items in a separate directory, rather than the default behavior of renaming them to <original-file-name>.org_archive.

(if (file-exists-p "~/.org-agenda-setup.el")
    (load "~/.org-agenda-setup.el")
    (global-set-key (kbd "C-c o")
                    (lambda ()
                      (find-file "~/Nextcloud/agenda/")))
    (setq org-habit-show-habits-only-for-today nil
          org-agenda-files (file-expand-wildcards "~/Nextcloud/agenda/*.org")
          org-default-notes-file "~/Nextcloud/agenda/")))

Org Super Agenda

(use-package org-super-agenda
  :ensure t
  :after org-agenda
  (setq org-super-agenda-groups
        '((:name "Next"
                 :time-grid t
                 :todo "NEXT"
                 :order 1)
          (:name "Language"
                 :time-grid t
                 :tag "language"
                 :order 2)
          (:name "Study"
                 :time-grid t
                 :tag "study"
                 :order 3)
          (:discard (:not (:todo "TODO")))))
  (setq org-agenda-compact-blocks nil
        org-agenda-span 'day
        org-agenda-todo-ignore-scheduled 'future
        org-agenda-skip-deadline-prewarning-if-scheduled 'pre-scheduled
        org-super-agenda-header-separator ""
        org-columns-default-format "%35ITEM %TODO %3PRIORITY %TAGS")
  (set-face-attribute 'org-super-agenda-header nil
                      :weight 'bold))

Org Capture

To capture new notes, I configure Org Capture with a quick key binding of C-c c.

(global-set-key (kbd "C-c c") 'org-capture)

I also set up a couple of notes files.

(setq org-directory (expand-file-name "~/Nextcloud/Notes"))
(setq org-default-notes-file (concat org-directory "/")
      '(("t" "Task" entry (file+olp+datetree "~/Nextcloud/Notes/")
         "* TODO %?\n%i" :empty-lines 1)
        ("j" "Journal" entry (file+datetree "~/Nextcloud/Notes/")
         "* %?\nAdded: %U\n%i" :empty-lines 1)
        ("n" "Note" entry (file "~/Nextcloud/Notes/")
         "* %^{Headline}\nAdded: %U\n\n%?" :empty-lines 1)))

Org-Babel Language Integration

I want to be able to support C, Emacs Lisp, shell, python, and GraphViz blocks in org-babel.

 'org-babel-load-languages '((python . t)
                             (C . t)
                             (shell . t)
                             (emacs-lisp . t)
                             (dot . t)))
(setq org-babel-python-command "python3")

Next I want output header-args to be :results output :wrap EXAMPLE by default. This horrid mess accomplishes that by removing the old :results and :wrap keys from the association list org-babel-default-header-args and then replacing them.

(cons '(:results . "output")
      (cons '(:wrap . "EXAMPLE")

Display Options

I turn on Pretty Entities, which allows Emacs, in graphics mode, to render unicode symbols, math symbols, and so on. I also set a custom ellipsis character that will be shown when sections or blocks are collapsed.

(setq org-pretty-entities t
      org-ellipsis "")

Org Roam

(when (file-directory-p (expand-file-name "~/Nextcloud/org-roam/"))
  (use-package org-roam
    :ensure t
    (setq org-roam-v2-ack t
          org-roam-dailies-directory "journal/")
    (org-roam-directory (expand-file-name "~/Nextcloud/org-roam/"))
    (org-roam-completion-everywhere t)
     '(("d" "default" plain
        :if-new (file+head "%<%Y%m%d>-${slug}.org"
                           "#+TITLE: ${title}\n")
        :unnarrowed t)))
     '(("d" "default" entry "\n* %?"
        :target (file+head "%<%Y-%m-%d>.org" "#+TITLE: %u\n#+STARTUP: showall\n\n")
        :unnarrowed nil ; Show only the current note on entry
        :empty-lines 1)))
    :bind (("C-c n l" . org-roam-buffer-toggle)
           ("C-c n f" . org-roam-node-find)
           ("C-c n i" . org-roam-node-insert)
           :map org-mode-map
           ("C-M-i" . completion-at-point)
           :map org-roam-dailies-map
           ("Y" . org-roam-dailies-capture-yesterday)
           ("T" . org-roam-dailies-capture-tomorrow))
    :bind-keymap ("C-c n d" . org-roam-dailies-map)
    (require 'org-roam-dailies)

Org Superstar

Org Superstar replaces the default asterisk style Org-Mode headers with nicer looking defaults using Unicode.

(use-package org-superstar
  :ensure t
  :hook (org-mode . org-superstar-mode)
  (setq org-superstar-leading-bullet " "))


perspective.el is a tool that allows grouping of buffers into separate “perspectives”, like workgroups in other editors.

(use-package perspective
  :ensure t
  :bind (("C-x k" . persp-kill-buffer*))
  (persp-mode-prefix-key (kbd "C-x M-p"))
  :init (persp-mode))

Support for Encrypted Authinfo

(use-package auth-source
  :ensure t
  (setq auth-sources '("~/.authinfo.gpg")))

Tera Mode

Tera is a templating language used by Zola.

(use-package tera-mode
  :load-path "lisp/tera-mode")


(use-package mastodon
  :ensure t
  :config (setq mastodon-instance-url ""
                mastodon-active-user "twylo"))


Sly is a Common Lisp IDE that is a fork of SLIME, with some additional features.

(use-package sly
  :ensure t
  (setq inferior-lisp-program "sbcl"))

(use-package sly-quicklisp
  :ensure t)

GraphViz (dot) Mode

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

Git Integration

(use-package magit
  :ensure t
  (global-set-key (kbd "C-x g") 'magit-status))
(use-package git-gutter
  :ensure t)


YAML mode is useful for editing Docker files.

(use-package yaml-mode
  :ensure t)


This is just a bit of fun. See: “Let It Snow” on GitHub.

(use-package snow
  :ensure t)


Snippets build in support for typing a few keys, pressing tab, and getting a complete template inserted into your buffer. I use these heavily. In addition to the built-in snippets that come from the yasnippet-snippets package, I have some custom snippets defined in the snippets directory.

(use-package yasnippet
  :ensure t
  :diminish yas-minor-mode
  (setq yas-snippet-dirs
        (append yas-snippet-dirs '("~/.emacs.d/snippets")))

(use-package yasnippet-snippets
  :ensure t
  :after yasnippet
  :config (yasnippet-snippets-initialize))


(use-package markdown-mode
  :ensure t
  (markdown-mode gfm-mode)
  (("README\\.md\\'" . gfm-mode)
   ("\\.md\\'" . markdown-mode)
   ("\\.markdown\\'" . markdown-mode))
  (setq markdown-command "markdown")
  (use-package edit-indirect
    :ensure t))


Gemini is a new project I’m kind of interested in. These packages will help support my interest in it.

(use-package elpher
  :ensure t)

(use-package gemini-mode
  :ensure t)

(use-package ox-gemini
  :ensure t)

Development and Languages

Much of this section, especially with regards to Rust development, is stolen verbatim from Robert Krahn. Thank you!

Python Development

(use-package elpy
  :ensure t
  :init (elpy-enable))

Web Mode

(use-package web-mode
  :ensure t
  (setq web-mode-markup-indent-offset 2
        web-mode-css-indent-offset 2)
  (add-to-list 'auto-mode-alist '("\\.html\\'" . web-mode))
  (add-to-list 'auto-mode-alist '("\\.html?\\'" . web-mode))
  (add-to-list 'auto-mode-alist '("\\.phtml\\'" . web-mode))
  (add-to-list 'auto-mode-alist '("\\.php\\'" . web-mode)))


Let’s use Ivy for completion. See:

(use-package ivy
  :ensure t
  (ivy-mode 1))

SQL Indent Mode

(use-package sql-indent
  :ensure t
  (add-hook 'sql-mode-hook #'sqlind-minor-mode))

Lisp Editing

I really like paredit, especially for Lisp, but I don’t like the default key bindings, so I tweak them heavily. Primarily, the problem is that I use C-<left> and C-<right> to navigate between windows in Emacs, so I don’t want to use them for Paredit. Instead, I remap these to C-S-<left> and C-S-<right>, respectively.

(use-package paredit
  :ensure t
  (autoload 'enable-paredit-mode "paredit" "Structural editing of Lisp")
  (add-hook 'emacs-lisp-mode-hook #'enable-paredit-mode)
  (add-hook 'eval-expression-minibuffer-setup-hook #'enable-paredit-mode)
  (add-hook 'lisp-mode-hook #'enable-paredit-mode)
  (add-hook 'lisp-interaction-mode-hook #'enable-paredit-mode)
  (add-hook 'scheme-mode-hook #'enable-paredit-mode)
  ;; Unmap defaults
  (define-key paredit-mode-map (kbd "C-<left>") nil)
  (define-key paredit-mode-map (kbd "C-<right>") nil)
  ;; Map new keys
  (define-key paredit-mode-map (kbd "C-S-<left>")
  (define-key paredit-mode-map (kbd "C-S-<right>")


I’ve recently been playing more with Haskell.

(use-package haskell-mode
  :ensure t
    (add-hook 'haskell-mode-hook 'turn-on-haskell-doc-mode)
    (add-hook 'haskell-mode-hook 'turn-on-haskell-indent)
    (add-hook 'haskell-mode-hook 'interactive-haskell-mode)
    (setq haskell-process-args-cabal-new-repl
          '("--ghc-options=-ferror-spans -fshow-loaded-modules"))
    (setq haskell-process-type 'cabal-new-repl)
    (setq haskell-stylish-on-save 't)
    (setq haskell-tags-on-save 't)))

(use-package flycheck-haskell
  :ensure t
  (add-hook 'flycheck-mode-hook #'flycheck-haskell-setup)
  (eval-after-load 'haskell-mode-hook 'flycheck-mode))

(use-package flymake-hlint
  :ensure t
  (add-hook 'haskell-mode-hook 'flymake-hlint-load))


Support for the Rust Programming Language.

(use-package rustic
  :ensure t
  :bind (:map rustic-mode-map
              ("M-j" . lsp-ui-imenu)
              ("M-?" . lsp-find-references)
              ("C-c C-c l" . flycheck-list-errors)
              ("C-c C-c a" . lsp-execute-code-action)
              ("C-c C-c r" . lsp-rename)
              ("C-c C-c q" . lsp-workspace-restart)
              ("C-c C-c Q" . lsp-workspace-shutdown)
              ("C-c C-c s" . lsp-rust-analyzer-status))
  ;; comment to disable rustfmt on save
  (setq rustic-format-on-save t)
  (add-hook 'rustic-mode-hook 'loomcom/rustic-mode-hook))

(defun loomcom/rustic-mode-hook ()
  ;; so that run C-c C-c C-r works without having to confirm
  (setq-local buffer-save-without-query t))

LSP Mode

LSP is a language server protocol mode to allow working with various LSP daemons.

Note that I’ve disabled lsp-ui-mode because I’ve discovered I’m finding it to be very distracting. If you want to turn it back on, just add :config (add-hook 'lsp-mode-hook 'lsp-ui-mode) to lsp-mode.

(use-package lsp-mode
  :ensure t
  :commands lsp
  ;; Improve performance and enable features
    (setq read-process-output-max (* 1024 1024)
          gc-cons-threshold 100000000
          vc-handled-backends '(Git) ;; Only support git
          lsp-rust-analyzer-proc-macro-enable t
          lsp-rust-analyzer-cargo-watch-command "clippy"
          lsp-rust-analyzer-cargo-load-out-dirs-from-check t
          ;; These three make things significantly less flashy...
          lsp-eldoc-render-all nil
          lsp-eldoc-hook nil
          lsp-idle-delay 0.5
          lsp-eldoc-hook nil
          lsp-enable-symbol-highlighting nil
          lsp-signature-auto-activate nil
          ;; Do not automatically include headers for me!
          lsp-clients-clangd-args '("--header-insertion=never")
          ;; Do not auto-format for me!
          lsp-enable-indentation nil
          lsp-enable-on-type-formatting nil)

     (make-lsp-client :new-connection (lsp-tramp-connection "/usr/bin/clangd")
                      :major-modes '(c-mode)
                      :remote? t
                      :server-id 'clangd-remote))))

(use-package lsp-ui
  :ensure t
  :commands lsp-ui-mode
  (lsp-ui-peek-always-show t)
  (lsp-ui-sideline-show-hover t)
  (lsp-ui-doc-enable t)
  (lsp-ui-doc-delay 2))

(add-hook 'c-mode-hook 'lsp)
(add-hook 'c++-mode-hook 'lsp)


(use-package corfu
  :ensure t
  (corfu-cycle t)
  (corfu-auto t)
  (corfu-auto-prefix 2)
  (corfu-auto-delay 2.0)
  (corfu-quit-at-boundary 'separator)
  (corfu-echo-documentation 0.25)
  (corfu-preview-current 'separator)
  (corfu-preselect-first t)
  :bind (:map corfu-map
              ("M-SPC" . corfu-insert-separator)
              ("TAB" . corfu-next)
              ([tab] . corfu-next)
              ("S-TAB" . corfu-previous)
              ([backtab] . corfu-previous)
              ("S-<return>" . corfu-insert))
  :init (global-corfu-mode))


(use-package flycheck
  :ensure t)

Loom Communications Blog

I keep my website in org-mode, and I have a long-running blog hosted there. Rather than clutter up this file with a lot of blogging stuff, I put it in a git submodule and load it here if it’s been checked out.

(when (file-exists-p "~/.emacs.d/lisp/loomcom-blog")
  (use-package loomcom-blog
    :ensure nil
    :load-path "~/.emacs.d/lisp/loomcom-blog"))


Email configuration is all in an external, optional file. It’s not checked in here for privacy reasons, which I’m sure you’ll understand!

(let ((mail-conf (expand-file-name "~/.emacs-mail.el")))
  (when (file-exists-p mail-conf)
    (load-file mail-conf)))