emacs-files/configuration.org

40 KiB

GNU Emacs Configuration File

Introduction

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/configuration.org")

Basic Setup

Hack: Turn Off cl Deprecation Warnings

Some of the packages I use still do (require 'cl), which is now deprecated in favor of (require 'cl-lib). I don't use this deprecated library myself, so this hack simply silences those deprecation warnings.

  (setq byte-compile-warnings '(cl-functions))

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)
             (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
        initial-scratch-message nil)
  (tool-bar-mode -1)
  (tooltip-mode -1)
  (scroll-bar-mode -1)
  (if (display-graphic-p)
      (menu-bar-mode t)
    (menu-bar-mode -1))

Behavior

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)

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).

  (cond
   ((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)])

Scrolling

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)

Indentation

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] = {
                0,
                1,
                2,
};

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] = {
    0,
    1,
    2,
};

Here's the setting:

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

Tramp

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 4000)   ; default is 400
  (setq max-specpdl-size 5000)      ; 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 the exec-path.

  (when (file-exists-p "/usr/local/bin")
    (setq exec-path (append exec-path '("/usr/local/bin")))
    (setenv "PATH" (concat (getenv "PATH") ":/usr/local/bin")))

  (when (file-exists-p "/opt/homebrew/bin")
    (setq exec-path (append exec-path '("/opt/homebrew/bin")))
    (setenv "PATH" (concat (getenv "PATH") ":/opt/homebrew/bin")))

  (when (file-exists-p "/opt/homebrew/opt/llvm/bin")
    (setq exec-path (append exec-path '("/opt/homebrew/opt/llvm/bin")))
    (setenv "PATH" (concat (getenv "PATH") ":/opt/homebrew/opt/llvm/bin")))

  (when (file-exists-p (expand-file-name "~/bin"))
    (setq exec-path (append exec-path '("~/bin")))
    (setenv "PATH" (concat (getenv "PATH") ":$HOME/bin")))

  (when (file-exists-p "/Library/TeX/texbin")
    (setq exec-path (append exec-path '("/Library/TeX/texbin")))
    (setenv "PATH" (concat (getenv "PATH") ":/Library/TeX/texbin")))

  (when (file-exists-p "~/.cargo/bin")
    ;; Add to front of the list
    (add-to-list 'exec-path "~/.cargo/bin")
    (setenv "PATH" (concat (getenv "PATH") ":~/.cargo/bin")))

Encryption

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 ()
                      (interactive)
                      (ignore-errors (windmove-left))))
  (global-set-key (kbd "C-<right>")
                  #'(lambda ()
                      (interactive)
                      (ignore-errors (windmove-right))))
  (global-set-key (kbd "C-<up>")
                  #'(lambda ()
                      (interactive)
                      (ignore-errors (windmove-up))))
  (global-set-key (kbd "C-<down>")
                  #'(lambda ()
                      (interactive)
                      (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")

Appearance

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"
                       "Inconsolata"
                       "Dejavu"
                       "Menlo"
                       "Monaco"
                       "Courier New"
                       "Courier"
                       "fixed"))
           (selected-family (cl-dolist (fam families)
                              (when (member fam (font-family-list))
                                (cl-return fam)))))
      (set-face-attribute 'default nil
                          :family selected-family
                          :weight 'medium
                          :height 140)))

Window Frame

Title

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
    (ensmallen-default-face)
  ,* DELTA is an amount to increase.  By default, the value is 10."
    (progn
      (set-face-attribute
       '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."
    (interactive)
    (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."
    (interactive)
    (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)

Line Numbers

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

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

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)

Parentheses

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.

  (electric-pair-mode)

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 '(("org" . "https://orgmode.org/elpa/")
                           ("gnu" . "https://elpa.gnu.org/packages/")
                           ("melpa" . "https://melpa.org/packages/")))

Then, actually initialize things.

  (package-initialize)

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

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

Theme

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

  (use-package doom-themes
    :ensure t
    :config
    (setq doom-themes-enable-bold t
          doom-themes-enable-italic t)
    (when window-system
      (load-theme 'doom-one t))
    (doom-themes-visual-bell-config)
    (doom-themes-org-config))

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)))

  (eval-and-compile
    (setq org-load-paths '("~/.emacs.d/org-mode/lisp"
                           "~/.emacs.d/org-contrib/lisp")))

  (use-package org
    :load-path org-load-paths
    :ensure t
    :defer t
    :config
    (use-package org-drill
      :ensure t
      :defer t)
    (use-package htmlize
      :ensure t)
    (require 'ox-latex)
    (setq 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-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))))


  (with-eval-after-load 'ox-latex
    (add-to-list 'org-latex-classes
                 '("org-plain-latex"
                   "\\documentclass{article}
  [NO-DEFAULT-PACKAGES]
  [PACKAGES]
  [EXTRA]"
                   ("\\section{%s}" . "\\section*{%s}")
                   ("\\subsection{%s}" . "\\subsection*{%s}")
                   ("\\subsubsection{%s}" . "\\subsubsection*{%s}")
                   ("\\paragraph{%s}" . "\\paragraph*{%s}")
                   ("\\subparagraph{%s}" . "\\subparagraph*{%s}"))))

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
  timestamp."
    (interactive)
    (org-set-property
     "DATE"
     (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))) t)
  (defface deadline-near-face
    '((t (:foreground "#ffa500"
                      :weight bold
                      :slant italic))) t)
  (defface deadline-distant-face
    '((t (:foreground "#ffff00"
                      :weight bold
                      :slant italic))) t)

  (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
        '((sequence
           "TODO(t)"
           "NEXT(n)"
           "PROG(p)"
           "INTR(i)"
           "DONE(d)")))

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")
    (progn
      (global-set-key (kbd "C-c o")
                      (lambda ()
                        (interactive)
                        (find-file "~/Nextcloud/agenda/agenda.org")))
      (setq org-habit-show-habits-only-for-today nil
            org-agenda-files (file-expand-wildcards "~/Nextcloud/agenda/*.org")
            org-default-notes-file "~/Nextcloud/agenda/agenda.org")))

Org Super Agenda

  (use-package org-super-agenda
    :ensure t
    :defer t
    :after org-agenda
    :init
    (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")))))
    :config
    (org-super-agenda-mode)
    (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)

Org-Babel Language Integration

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

  (org-babel-do-load-languages
   'org-babel-load-languages '((C . t)
                               (emacs-lisp . t)
                               (dot . t)))

HTML Export Tweaks

I prefer to insert periods after section numbers when exporting org-mode files to HTML. This tweak enables that.

  (defun my-html-filter-headline-yesdot (text backend info)
    "Ensure dots in headlines.
  ,* TEXT is the text being exported.
  ,* BACKEND is the backend (e.g. 'html).
  ,* INFO is ignored."
    (when (org-export-derived-backend-p backend 'html)
      (save-match-data
        (when (let ((case-fold-search t))
                (string-match
                 (rx (group "<span class=\"section-number-" (+ (char digit)) "\">"
                            (+ (char digit ".")))
                     (group "</span>"))
                 text))
          (replace-match "\\1.\\2"
                         t nil text)))))

  (eval-after-load 'ox
    '(progn
       (add-to-list 'org-export-filter-headline-functions
                    'my-html-filter-headline-yesdot)))

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 "...")

Export Settings

This adds support the LaTeX class koma-article on LaTeX export.

  (add-to-list 'org-latex-classes
               '("koma-article"
                 "\\documentclass{scrartcl}"
                 ("\\section{%s}" . "\\section*{%s}")
                 ("\\subsection{%s}" . "\\subsection*{%s}")
                 ("\\subsubsection{%s}" . "\\subsubsection*{%s}")
                 ("\\paragraph{%s}" . "\\paragraph*{%s}")
                 ("\\subparagraph{%s}" . "\\subparagraph*{%s}")))

Org Roam

  (when (file-directory-p (expand-file-name "~/Nextcloud/org-roam/"))
    (use-package org-roam
      :ensure t
      :custom
      (org-roam-directory (expand-file-name "~/Nextcloud/org-roam/"))
      :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))
      :config
      (setq org-roam-v2-ack t)
      (org-roam-setup)))

Org Superstar

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

  (use-package org-superstar
    :ensure t
    :defer t
    :init
    (add-hook 'org-mode-hook (lambda () (org-superstar-mode 1)))
    :config
    (setq org-superstar-leading-bullet " "))

Support for Encrypted Authinfo

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

Twittering

  (use-package twittering-mode
    :defer t
    :ensure t)

Sly

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

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

  (use-package sly-quicklisp
    :defer t
    :ensure t)

GraphViz (dot) Mode

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

Git Integration

  (use-package magit
    :ensure t
    :defer t
    :init
    (global-set-key (kbd "C-x g") 'magit-status)
    (add-hook 'prog-mode-hook #'git-gutter-mode))
  (use-package git-gutter
    :defer t
    :ensure t)

YAML

YAML mode is useful for editing Docker files.

  (use-package yaml-mode
    :defer t
    :ensure t)

Snow

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

  (use-package snow
    :defer t
    :ensure t)

Snippets

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
    :config
    (setq yas-snippet-dirs
          (append yas-snippet-dirs '("~/.emacs.d/snippets")))
    (yas-global-mode))

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

Markdown

  (use-package markdown-mode
    :ensure t
    :defer t
    :commands (markdown-mode gfm-mode)
    :mode (("README\\.md\\'" . gfm-mode)
           ("\\.md\\'" . markdown-mode)
           ("\\.markdown\\'" . markdown-mode))
    :init (setq markdown-command "multimarkdown"))

Gemini

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
    :defer t)

  (use-package gemini-mode
    :ensure t
    :defer t)

  (use-package ox-gemini
    :ensure t
    :defer t)

Development and Languages

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

Web Mode

  (use-package web-mode
    :ensure t
    :defer t
    :config
    (setq web-mode-markup-indent-offset 2
          web-mode-css-indent-offset 2)
    :init
    (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)))

Ivy

Let's use Ivy for completion. See: https://github.com/abo-abo/swiper">https://github.com/abo-abo/swiper

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

SQL Indent Mode

  (use-package sql-indent
    :ensure t
    :defer t
    :config
    (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
    :defer t
    :init
    (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)
    :config
    ;; 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>")
      'paredit-forward-barf-sexp)
    (define-key paredit-mode-map (kbd "C-S-<right>")
      'paredit-forward-slurp-sexp))

Haskell

I've recently been playing more with Haskell.

  (use-package haskell-mode
    :ensure t
    :init
    (progn
      (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
    :config
    (add-hook 'flycheck-mode-hook #'flycheck-haskell-setup)
    (eval-after-load 'haskell-mode-hook 'flycheck-mode))

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

Rustic

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))
    :config
    ;; 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))

CCLS

  (use-package ccls
    :ensure t
    :defer t
    :config
    (setq ccls-executable "ccls")
    (setq lsp-prefer-flymake nil)
    (setq-default flycheck-disabled-checkers '(c/c++-clang c/c++-cppcheck c/c++-gcc))
    :hook ((c-mode c++-mode objc-mode) .
           (lambda () (require 'ccls) (lsp))))

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
    :defer t
    :commands lsp
    :config
    ;; Improve performance and enable features
    (setq read-process-output-max (* 1024 1024)
          gc-cons-threshold 100000000
          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 1.0
          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))

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

Company

  (use-package company
    :ensure t
    :defer t
    :custom
    (company-idle-delay 1.0) ;; how long to wait until popup
    ;; (company-begin-commands nil) ;; uncomment to disable popup
    :bind
    (:map company-mode-map
          ("<tab>" . tab-indent-or-complete)
          ("TAB" . tab-indent-or-complete))
    (:map company-active-map
          ("C-n". company-select-next)
          ("C-p". company-select-previous)
          ("M-<". company-select-first)
          ("M->". company-select-last)))

Helpers!

  (defun company-yasnippet-or-completion ()
    (interactive)
    (or (do-yas-expand)
        (company-complete-common)))

  (defun check-expansion ()
    (save-excursion
      (if (looking-at "\\_>") t
        (backward-char 1)
        (if (looking-at "\\.") t
          (backward-char 1)
          (if (looking-at "::") t nil)))))

  (defun do-yas-expand ()
    (let ((yas/fallback-behavior 'return-nil))
      (yas/expand)))

  (defun tab-indent-or-complete ()
    (interactive)
    (if (minibufferp)
        (minibuffer-complete)
      (if (or (not yas/minor-mode)
              (null (do-yas-expand)))
          (if (check-expansion)
              (company-complete-common)
            (indent-for-tab-command)))))

Flycheck

  (use-package flycheck
    :ensure t)

Email

Email configuration is all in an external, optional file.

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

Miscellaneous Hacks

Emacs 28

Turn on native compilation of packages

  (when (>= emacs-major-version 28)
    (setq package-native-compile 1))

Emacs 27

Beginning in Emacs 27, a new attribute, :extend, was added to faces. It determines whether the background of a face will extend to the right margin or not. It defaults to nil, but I prefer it to be set for some things.

  (when (>= emacs-major-version 27)
    (set-face-attribute 'org-block nil :extend t))

An Emacs 27 oddity or bug?

Some time in Emacs 27's development lifetime, the default value of the variable truncate-string-ellipsis became unbound. It's supposed to be a string that's used when truncating a string to width with the truncate-string-to-width function. I think this is a bug, but to work around it, we just define it here.

It might also be a bug with mu4e. Maybe mu4e is unbinding the variable?

Hopefully we can remove this hack after the bug is fixed, whoever's fault it is.

  (setq truncate-string-ellipsis "...")