emacs-files/configuration.org

44 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

Lexical Scoping

By default, Emacs uses dynamic binding. Some aspects of my configuration rely on lexical binding, so that gets turned on first!

  ;; -*- lexical-binding: 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)
  (when window-system
    (progn
      (tool-bar-mode -1)
      (tooltip-mode -1)))

That said, I do often want a menu and a scroll bar, so those can stay on.

  (menu-bar-mode t)
  (when (fboundp #'scroll-bar-mode) (scroll-bar-mode t))

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/"))
    (normal-top-level-add-subdirs-to-load-path))

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)

On macOS, I turn off --dired, because ls does not support it there!

  (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 for me.

  (setq create-lockfiles nil)

Turn Off Suspending Emacs with Control-Z

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

Long line improvements

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

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

Spelling is important (I'm terrible at spelling). This block configures ispell. It relies on an external dictionary program; first it will check to see if aspell is available, and if so, set it as the default spell checking program. If that's not found, it will search for hunspell and set that. If neither is found, oh well, no spell checking program for us.

  (setq ispell-local-dictionary "en_US"
        ispell-local-dictionary-alist
        '(("en_US" "[[:alpha]]" "[^[:alpha:]]" "[']"
           nil ("-d" "en_US") nil utf-8)))

  (cond
   ((executable-find "aspell")
    (setq ispell-program-name "aspell"))
   ((executable-find "hunspell")
    (setq ispell-program-name "hunspell"))
   (t (setq ispell-program-name nil)))

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 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
        '("/usr/local/bin"
          "/opt/homebrew/bin"
          "/opt/homebrew/opt/llvm/bin"
          "~/bin"
          "~/.local/bin"
          "/Library/TeX/texbin"
          "~/.cargo/bin"))

  (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))))))
        loomcom-append-to-path)

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
                          :height 120)
      (set-face-attribute 'fixed-pitch nil
                          :family selected-family
                          :height 120)))

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)

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)
                             (electric-indent-mode)
                             (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)

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 '(("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 modus-themes
    :ensure t
    :config
    (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
    :config
    (setq olivetti-body-width 90))

Completion UI - Vertico

I recently switched to Vertico and Consult for completion and completion, which replaces Helm / Ivy.

  (use-package vertico
    :ensure t
    :init (vertico-mode))

  (use-package savehist
    :ensure t
    :init (savehist-mode))

  (use-package orderless
    :ensure t
    :init
    (setq completion-styles '(orderless basic)
          completion-category-defaults nil
          completion-category-overrides '((file (styles partial-completion)))))

  (use-package consult
    :ensure t
    :hook (completion-list-mode . consult-preview-at-point-mode)
    :bind (("C-c M-x" . consult-mode-command)
           ("C-c h" . consult-history)
           ("C-c k" . consult-kmacro)
           ("C-c m" . consult-man)
           ("C-c i" . consult-info)
           ([remap Info-search] . consult-info)
           ("C-x b" . consult-buffer)
           ("C-x 4 b" . consult-buffer-other-window)
           ("C-x 5 b" . consult-buffer-other-frame)
           ("C-x r b" . consult-bookmark)
           ("C-x p b" . consult-project-buffer)
           ("M-#" . consult-register-load)
           ("M-'" . consult-register-store)
           ("C-M-#" . consult-register)
           ("M-y" . consult-yank-pop)
           ("M-g e" . consult-compile-error)
           ("M-g f" . consult-flymake)
           ("M-g g" . consult-goto-line)
           ("M-g M-g" . consult-goto-line)
           ("M-g o" . consult-outline)
           ("M-g m" . consult-mark)
           ("M-g k" . consult-global-mark)
           ("M-g i" . consult-imenu)
           ("M-g I" . consult-imenu-multi)
           ("M-s d" . consult-find)
           ("M-s D" . consult-locate)
           ("M-s g" . consult-grep)
           ("M-s G" . consult-git-grep)
           ("M-s r" . consult-ripgrep)
           ("M-s l" . consult-line)
           ("M-s L" . consult-line-multi)
           ("M-s k" . consult-keep-lines)
           ("M-s u" . consult-focus-lines)
           ("M-s e" . consult-isearch-history)
           :map isearch-mode-map
           ("M-e" . consult-isearch-history)
           ("M-s e" . consult-isearch-history)
           ("M-s l" . consult-line)
           ("M-s L" . consult-line-multi)
           :map minibuffer-local-map
           ("M-s" . consult-history)
           ("M-r" . consult-history))
    :init
    (setq register-preview-delay 0.5
          register-preview-function #'consult-register-format)

    (advice-add #'register-preview :override #'consult-register-window)

    (setq xref-show-xrefs-function #'consult-xref
          xref-show-definitions-function #'consult-xref)

    :config
    (consult-customize
     consult-theme :preview-key '(:debounce 0.2 any)
     consult-ripgrep consult-git-grep consult-grep
     consult-bookmark consult-recent-file consult-xref
     consult--source-bookmark consult--source-file-register
     consult--source-recent-file consult--source-project-recent-file
     :preview-key '(:debounce 0.4 any))
    (setq consult-narrow-key "<"))

Git Integration

Magit and git-gutter are both essential, I can't live without them.

  (use-package magit
    :ensure t)

  (use-package git-gutter
    :ensure t)

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)
    :config
    (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
  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)))
    "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
        '((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-directory (expand-file-name "~/Nextcloud/Notes"))
      (setq org-default-notes-file (concat org-directory "/notes.org")
            org-capture-templates
            '(("t" "Task" entry (file+olp+datetree "~/Nextcloud/Notes/tasks.org")
               "* TODO %?\n%i" :empty-lines 1)
              ("j" "Journal" entry (file+datetree "~/Nextcloud/Notes/journal.org")
               "* %?\nAdded: %U\n%i" :empty-lines 1)
              ("n" "Note" entry (file "~/Nextcloud/Notes/notes.org")
               "* %^{Headline}\nAdded: %U\n\n%?" :empty-lines 1)))
      (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
    :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, shell, python, and GraphViz blocks in org-babel.

  (org-babel-do-load-languages
   '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")
              (assq-delete-all
               :wrap
               (assq-delete-all
                :results
                org-babel-default-header-args))))

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

Choose the best directory for org-roam: In my Nextcloud directory, if it exists, otherwise in Documents.

  (setq my-org-roam-directory
        (if (file-directory-p (expand-file-name "~/Nextcloud/org-roam/"))
            "~/Nextcloud/org-roam/"
          "~/Documents/org-roam/"))

Next, ensure org-roam is installed and configured.

  (use-package org-roam
    :ensure t
    :init
    (setq org-roam-v2-ack t
          org-roam-dailies-directory "journal/")
    :custom
    (org-roam-directory (expand-file-name my-org-roam-directory))
    (org-roam-completion-everywhere t)
    (org-roam-capture-templates
     '(("d" "default" plain
        "%?"
        :if-new (file+head "%<%Y%m%d>-${slug}.org"
                           "#+TITLE: ${title}\n")
        :unnarrowed t)))
    (org-roam-dailies-capture-templates
     '(("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)
    :config
    (require 'org-roam-dailies)
    (org-roam-db-autosync-mode)
    (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
    :hook (org-mode . org-superstar-mode)
    :config
    (setq org-superstar-leading-bullet " "))

Perspective

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*))
    :custom
    (persp-mode-prefix-key (kbd "C-x M-p"))
    :init (persp-mode))

Support for Encrypted Authinfo

Various workflows require accessing authentication credentials stored in an encrypted file named ~/.authinfo.gpg. The auth-source package lets me get quick access to those credentials.

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

Tera Mode

Tera is a templating language used by Zola, which I use for my website and blog. This mode helps with editing those templates.

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

Sly

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

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

  (use-package sly-quicklisp
    :ensure t)

GraphViz (dot) Mode

If you don't know GraphViz, you should. It's a very useful language for generating visual flowcharts and graphs.

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

YAML

YAML mode is useful for editing Docker files, among many other things.

  (use-package yaml-mode
    :ensure t)

Snow

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

  (use-package snow
    :ensure t)

Fireplace

Sometimes you want a roaring fire.

  (use-package fireplace
    :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
    :commands
    (markdown-mode gfm-mode)
    :mode
    (("README\\.md\\'" . gfm-mode)
     ("\\.md\\'" . markdown-mode)
     ("\\.markdown\\'" . markdown-mode))
    :init
    (setq markdown-command "markdown")
    :config
    (use-package edit-indirect
      :ensure t))

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)

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

  (add-hook 'python-mode-hook
            (lambda ()
              (setq-default indent-tabs-mode nil)
              (setq-default tab-width 4)
              (setq-default py-indent-tabs-mode nil)))

Web Mode

  (use-package web-mode
    :ensure 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)))

SQL Indent Mode

  (use-package sql-indent
    :ensure 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
    :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
    :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
    :commands lsp
    :config
    ;; Improve performance and enable features
    (progn
      (setq vc-ignore-dir-regexp
            (format "\\(%s\\)\\|\\(%s\\)" vc-ignore-dir-regexp tramp-file-name-regexp)
            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)

      (lsp-register-client
       (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
    :custom
    (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)

Corfu

  (use-package corfu
    :ensure t
    :custom
    (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))

Flycheck

  (use-package flycheck
    :ensure t)

Email

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