Skip to content

kpmgeek/emacs-config

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

52 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Emacs configuration file

Prelude

This file organizes settings from most-to-least general. If you’re interested in basing your own configuration on mine, but don’t want all of my bells and whistles (or you want different bells and whistles), then this is probably the section to keep. It describes a fairly minimal Emacs setup, with as few packages as possible.[fn:packages] In particular, it pulls in package management and sets up some sane defaults, many of which are pulled from the better-defaults package.[fn:better-defaults]

;;; init.el --- main init   -*- lexical-binding: t -*-

;;; Commentary:
;; THIS IS A GENERATED FILE; changes should be made to README.org

;;; Code:

; I guess this is useful for pre-27 Emacs, but it's mostly so that flycheck stops
; yelling at me.
(require 'early-init (expand-file-name "early-init.el" user-emacs-directory))

I use lexical binding (I believe now the default in Emacs 27.1) and a lot of byte-compilation. Now that we have early-init.el, we can byte-compile the main init file, as we can use it to tell Emacs that a newer uncompiled file should be preferred to the byte-compiled file.

;;; early-init.el --- early bird init   -*- lexical-binding: t; no-byte-compile: t -*-

;;; Commentary:
;; THIS IS A GENERATED FILE; changes should be made to README.org

;;; Code:

(setq load-prefer-newer t)

[fn:packages] Specifically: straight.el, use-package, no-littering and on macOS, exec-path-from-shell.

[fn:better-defaults] I originally used better-defaults directly, but got fed up with having to peek inside it to check if a setting had already been configured. Additionally, it sets up some things I don’t care about: ido-mode (I use ivy) and isearch (I use swiper). I include ivy and swiper when I spin off versions of this file for people, so I haven’t bothered to set up ido-mode and isearch. Check better-defaults for how to enable them.

Package Management

Package management and configuration is based on a combination of use-package for declarative package configuration, and straight.el for reproducible package installation. The lock file generated by straight.el is checked in alongside the configration. Between the two of these, I can delay loading packages until they’re needed, and I can revert to older versions of packages if (when) something breaks.

Some configuration options need to be set before the bootstrap code for straight.el is loaded. Specifically, we want use-package to automatically tell straight.el about the packages we’re using so that they can be installed. The setting below prevents us from having to use the :straight option in use-package forms (which is the replacement for :ensure, which should not be used).

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

Because straight.el is installed before we’ve initialized any kind of package management, bootstrap code must be run to set it up. This is just copy-pasted from the README. Note that installing packages requires Git to be installed and in the path. It also requires a network connection.

(defvar bootstrap-version)
(let ((bootstrap-file
       (expand-file-name "straight/repos/straight.el/bootstrap.el" user-emacs-directory))
      (bootstrap-version 5))
  (unless (file-exists-p bootstrap-file)
    (with-current-buffer
        (url-retrieve-synchronously
         "https://raw.githubusercontent.com/raxod502/straight.el/develop/install.el"
         'silent 'inhibit-cookies)
      (goto-char (point-max))
      (eval-print-last-sexp)))
  (load bootstrap-file nil 'nomessage))

Now we can ask straight.el to install use-package for us.

(setq use-package-verbose t)
(straight-use-package 'use-package)
(require 'bind-key)  ; needed for byte-compilation

No Littering

Emacs and its packages tend to scatter files around madly. The no-littering package helps tidy that up, and replaces a number of paths that I previously configured manually.

(use-package no-littering)

I used to have this in my early-init.el, but it turns out that several settings need to wait until we get to the main init phase, so here it is, right at the top.

Init File in Org-Mode

To help keep things organized without creating an explosion of little files, the user configuration all lives in this org-mode file, README.org. On buffer save, it gets tangled into regular elisp files, init.el. If for some reason the auto-tangle doesn’t work, it can be trigged manually with org-babel-tangle (C-c C-v t).

(defun my/tangle-init ()
  "Tangle the buffer if it's README.org in the user dir."
  (when (equal (buffer-file-name)
               (expand-file-name "README.org" user-emacs-directory))
    ;; Avoid running hooks when tangling
    (let ((prog-mode-hook nil)
          (org-confirm-babel-evaluate nil))
      (org-babel-tangle)
      (byte-compile-file (expand-file-name "init.el" user-emacs-directory)))))

(add-hook 'after-save-hook 'my/tangle-init)

Since init.el gets overwritten constantly, leting Emacs add customizations to it isn’t very useful. It should put them in a separate file instead. I personally don’t use the custom interface, or if I do, I eventually incorporate the changes into this file, but I sometimes spin off versions of my config to help other people get started, and it’s good for them to be able to use the customization interface.

(setq custom-file (no-littering-expand-etc-file-name "custom.el"))
(add-hook 'after-init-hook #'(lambda ()
                               (message "loading %s" custom-file)
                               (load custom-file t)))

Platform-Specific Settings

I’ve elected to group platform-specific settings with related platform-independant settings, rather than in one place. To assist with both writing them and locating them, I define some constants to test against and grep for.

GNU-Like Systems (incl. Windows)

Some Emacs features need to be configured differently depending on whether or not the core utils installed support the options provided by the GNU versions of those tools. Personally, I don’t install the GNU versions of tools on macOS and FreeBSD, since it can break other things, but I do install them on Windows. If your systems look different, you will need to change this.

(defconst *my/is-winnt*
  (or (string= "windows-nt" system-type)
      (string= "cygwin" system-type))
  "Non-nil if Emacs is running under Windows.")

(defconst *my/is-gnu-like*
  (or *my/is-winnt*  ; usually means GoW/Cygwin/MSYS2
      (string-prefix-p "gnu" (symbol-name system-type)))
  "Non-nil if we expect GNU-like coreutils.")

macOS

While I’ve deployed versions of this config file to macOS, I don’t test it often, so some changes may be required.

(defconst *my/is-macos*
  (memq window-system '(mac ns))
  "Non-nil if Emacs is running under macOS.")

If you configure environment variables in your shell’s config file, GUI applications will miss out on them. The exec-path-from-shell package fixes this by launching a shell and querying it.

(use-package exec-path-from-shell
  :if *my/is-macos*
  :config
  (exec-path-from-shell-initialize))

Safety & Backups

By default, Emacs keeps the previous version of each file around in a backup file, which is placed alongside the backed-up file. This makes them easy to find, but causes a lot of clutter. A better option is to store them all in one directory; this also makes it practical to keep multiple versions. (See No Littering)

(setq backup-by-copying t    ; don't clobber symlinks
      version-control t      ; numbered backups
      delete-old-versions t  ; manage excess backups
      kept-old-versions 6
      kept-new-versions 9)

Delete files by moving them to the system trash, rather than unlinking them from the filesystem.

(setq delete-by-moving-to-trash t)

(when *my/is-macos*
  (defun system-move-file-to-trash (file)
    "Use \"trash\" to move FILE to the system trash.
     When using Homebrew, install it using \"brew install trash\"."
    (call-process (executable-find "trash")
                  nil 0 nil
                  file)))

History & Recent File Persistence

Emacs can save your minibuffer history and several other histories to a file for you using the built-in savehist-mode. This allows them to persist across sessions.

(use-package savehist
  :custom
  (savehist-save-minibuffer-history t)
  (history-length 10000)  ; set to t for infinite history
  (history-delete-duplicates t)
  (savehist-additional-variables '(kill-ring
                                   search-ring
                                   regexp-search-ring
                                   shell-command-history))
  :config
  (savehist-mode +1))

Emacs can also save a list of your recently-edited files using the built-in recentf-mode There are better tools for jumping between large numbers of files, which I set up later, but this is good to have around, especially for loose files.

(use-package recentf
  :custom
  (recentf-max-saved-items 100)
  :config
  (recentf-mode +1))

Last but not least, the built-in save-place-mode will remember where the cursor was in a file when it was closed, and put the cursor back there when it’s reopened.

(use-package saveplace
  :config
  (save-place-mode +1))

Buffer Management

By default, if two buffers point to different files with the same filename, Emacs numbers them. The built-in uniquify library changes this behaviour to use the folder name instead.

(use-package uniquify
  :straight nil
  :custom
  (uniquify-buffer-name-style 'forward))

The built-in ibuffer-mode provides an improved interface for buffer management. All that’s needed to set it up is to bind it to a key.

(global-set-key (kbd "C-x C-b") 'ibuffer)

I want my buffers to be set up for UTF-8 with UNIX line endings unless otherwise specified, even on Windows.

(set-language-environment "UTF-8")
(setq-default buffer-file-coding-system 'utf-8-unix)

When the file a buffer is monitoring is changed outside Emacs, it’s helpful to have the buffer updated to match the file if the buffer hasn’t been modified. The built-in auto-revert-mode provides this feature.

(global-auto-revert-mode +1)

Sparse GUI

By default, Emacs provides a set of conventional UI elements. I like to turn most of these off, since there are alternatives that I’m more used to and take up less screen space. In spun-off configurations, I turn all of these back on. These also go into the early-init.el file, so that Emacs doesn’t spend time loading those elements and then unloading them again.

I prefer Emacs to go straight to an empty *scratch* when it loads, with no startup message.

(setq inhibit-startup-message t)
(setq initial-scratch-message nil)

The menu bar can be useful for rarely-used commands to which one doesn’t remember the keybindings. I generally prefer to use M-x to search for them by name, rather than hunting through the menu bar. However, on macOS, a menu bar gets drawn anyway, so we might as well leave it enabled.

(unless *my/is-macos*
  (menu-bar-mode -1))

The toolbar is geared towards frequently-used commands, and outlives its usefulness once you learn the keybindings.

(when (fboundp 'tool-bar-mode)
  (tool-bar-mode -1))

Word-wrapping is nicer than horizontal scrollbars, and there are smaller vertical position indicators available than vertical scrollbars.

(when (fboundp 'scroll-bar-mode)
  (scroll-bar-mode +1))
(when (fboundp 'horizontal-scroll-bar-mode)
  (horizontal-scroll-bar-mode -1))

If you like line numbers, change this to +1 instead of -1.

(global-display-line-numbers-mode -1)

I find a blinking cursor distracting, but many people are used to it or need it in order to locate the cursor. Again, change to +1 from -1 to re-enable the cursor.

(blink-cursor-mode +1)

See Alarm Bell on the Emacs Wiki for options related to the bell. I currently prefer a simple flashing bell.

(setq visible-bell t)

Emacs often asks yes-or-no questions, but some of the prompts don’t accept bare y/n answers. Replacing the yes-or-no prompt with the y-or-n prompt fixes that if you want less typing in your life.

(fset 'yes-or-no-p 'y-or-n-p)

Core Text Editing

Conventional text editors allow you to replace text by selecting it and beginning to type. Emacs can do that too with the built-in delete-selection-mode.

(delete-selection-mode +1)

When doing cut/paste (kill/yank) from inside Emacs, save the system clipboard to the kill ring so that we can get it back later. Also, when pasting with the mouse, insert at the point, rather than the actual click location.

(setq save-interprogram-paste-before-kill t
      mouse-yank-at-point t)

Show the parenthesis matching the one the cursor is on.

(show-paren-mode +1)

Use spaces instead of tabs for indentation by default. This can also be toggled per-mode or per-buffer. Also, require a final newline in files; per POSIX, it’s required, though it usually doesn’t matter. However, it does matter for a few files (crontab), and Git doesn’t seem to be a huge fan of files without trailing newlines.

(setq-default indent-tabs-mode nil)
(setq require-final-newline t)

Trailing whitespace tends to be a bit of a liability, since you can’t see it easily. We would like buffers to highlight it by default, with the exception of the minibuffer, where it doesn’t matter and tends to jank up the display from some tools.

(setq-default show-trailing-whitespace t)

(add-hook 'minibuffer-setup-hook #'(lambda ()
                                     (setq show-trailing-whitespace nil)))

Long lines are also a bit of a liability. Many people standardise on 80, but I usually settle on 88. Notably, the Black autoformatter for Python does this as well. This setting can be changed per-mode and per-buffer.

(setq-default fill-column 80)
(setq-default auto-fill-function 'do-auto-fill)

Some files do have long lines in them, and I like to have those lines word-wrapped instead of scrolling off the side of the page.

(setq-default truncate-lines nil)
(global-visual-line-mode 1)

By default, Emacs binds M-z to zap-to-char, which deletes everything up to and including the specified character. An alternative that does not delete the specified character, zap-up-to-char, is included with Emacs. better-defaults rebinds this.

(autoload 'zap-up-to-char "misc"
  "Kill up to, but not including ARGth occurrence of CHAR." t)
(global-set-key (kbd "M-z") 'zap-up-to-char)

Input special characters by typing the TeX code for them.

(setq default-input-method "TeX")

By default, Emacs searches for the ends of sentences by looking for a full stop and two spaces. I’m not in the habit of writing two spaces after a full stop, so I need it to look for one space, instead.

(setq sentence-end-double-space nil)

Emacs provides a feature called hippie-expand which tries to expand the word at the point into something more useful. It just needs to be bound to a keystroke.

(global-set-key (kbd "M-/") 'hippie-expand)

Spell-Checking

Emacs has a built-in spellchecker, Flyspell, which works by shelling out to another program, such as Ispell, Aspell, or Hunspell. If you’re on Windows, you’re currently pretty much stuck with the old version of Hunspell from EZWinPorts. If you find something better, please let me know. If you’re on Linux or macOS, my understanding is that GNU Aspell is faster, but Hunspell has better language support; you should be able to get either through your package manager.

(use-package ispell
  :defer t
  :custom
  (ispell-program-name (if *my/is-winnt* "hunspell" "aspell"))
  (ispell-dictionary "en_US"))

(use-package flyspell
  :delight (flyspell-mode " ~")
  :hook ((text-mode . flyspell-mode)
         (prog-mode . flyspell-prog-mode)))

Emacs Server

By starting an Emacs server, we can use emacsclient to open files in our current Emacs session. I’ve disabled this section, as I use a systemd user service to run Emacs.

(add-hook 'after-init-hook #'server-start)

Apropos

The apropos system is used for finding relevant commands. This option expands the search to look for more stuff.

(setq apropos-do-all t)

Ediff

The built-in ediff-mode provides an interface for diffing files and working with patches. By default it launches into a separate frame, but we can tell it to load in the current frame instead. Additionally, it’s nicer if it displays windows side-by-side instead of stacked vertically.

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

Mixed-DPI

Because I often have a mixed-DPI setup, if I’m under X, I need to be able to rescale an entire Emacs frame at a time on the fly, so I also include keybindings for that. This can be hooked into for e.g. fixing treemacs icons.

(defvar my/toggle-face-height-hook nil
  "Called when toggling the face height for mixed-DPI setups.")

(defun my/current-default-face-height ()
  "Get the height of the default face in the current frame."
  (face-attribute 'default :height (selected-frame)))

(defun my/toggle-face-height ()
  "Toggle the height of the default face in the current frame.
Useful when moving Emacs frames between monitors in mixed-DPI setups."
  (interactive)

  (set-face-attribute 'default (selected-frame) :height
                      (if (> (my/current-default-face-height) 80) 60 100))
  (run-hooks 'my/toggle-face-height-hook))

(global-set-key (kbd "C-x t s") 'my/toggle-face-height)

Visual Flair

No point in setting up your editor so that it can make coffee and do your taxes if you can’t stand looking at it. This section customizes the visual aspects of the Emacs experience, and is fairly modular.

Libraries

The dash.el library provides extended functionality for dealing with lists in elisp. It’s used in enough packages it’ll end up in all but the most conservative configs anyway, so it might as well get included explicitly so that it can be used here.

(use-package dash :config (global-dash-fontify-mode))

Initial Frame Size

Emacs starts with a tiny frame. This is less than useful on modern monitors.

(add-to-list 'default-frame-alist '(width . 150))
(add-to-list 'default-frame-alist '(height . 50))

Fonts

I use Fantasque Sans Mono for fixed-pitch text, and Source Serif Pro for variable-pitch text.

(add-to-list 'default-frame-alist
             '(font . "Fantasque Sans Mono-12"))

(set-face-attribute 'variable-pitch nil
                    :family "Source Serif Pro"
                    :height 1.25)

The height of 1.25 is chosen because otherwise it’s too small on my screen, and the reciprocal is exactly 0.8, which is useful for fixed-width text which, for some reason, inherits its height from the variable-pitch face.

Several modern coding fonts supply coding ligatures, which e.g. display >= as ≥. Emacs can use these, but needs to be told what characters to consider for ligatures. This is a bit of a pain, but one side-benefit is that ligatures that you dislike can be selectively disabled.

The machinery for setting this up involves giving Emacs a set of regular expressions grouped by the first character of the ligature. The following function makes the appropriate arrangements automatically.

(defun my/enable-compositions (ligatures)
  "Set up the `composition-function-table' for a list of LIGATURES."
  (-each (-group-by 'string-to-char ligatures)
    (-lambda ((char . comps))
      (set-char-table-range composition-function-table char
                            `([,(regexp-opt comps) 0 font-shape-gstring])))))

All that remains is to pass in the strings that should become ligatures.

(defvar my/compositions
  '("!=" "!=="
    "==" "===" "=>" "==>" "=>>" "=/=" "=<<"
    "->" "-->" "->>" "-<" "-<<"
    "<-" "<-<" "<<-" "<--" "<->" "<=<" "<<=" "<==" "<=>" "<~~" "<~" "<<<"
    "<<" "<=" "<~>" "<>" "<|||" "<||" "<|" "<|>" "<!--"
    ">->" ">=>" ">>=" ">>-" ">-" ">=" ">>" ">>>"
    "~~" "~>" "~~>"
    "|>" "||>" "|||>" "||"
    "::" "&&"
    ;; "//"  ;; c++-mode hangs when this is enabled???
    "/*" "/**/"
    "*/"))
;(my/enable-compositions my/compositions)

If the ligatures included in your font aren’t enough for you, Emacs has prettify-symbols-mode, which can visually replace strings with other strings, e.g. make the lambda elisp keyword display as \lambda. It can be turned on per-buffer, per-mode, or globally. I find it disruptive when enabled globally, but I may yet revisit it.

(global-prettify-symbols-mode -1)

Color Scheme

I like gruvbox (Emacs version), which provides both dark and light versions in a variety of contrast levels, and theming information for a variety of common extensions.

(use-package gruvbox-theme
  :demand t
  :config
  (setq my/light-theme 'gruvbox-light-medium)
  (setq my/dark-theme 'gruvbox-dark-soft)
  (setq my/initial-theme my/dark-theme)
  (load-theme my/initial-theme t))

For eye-comfort, it’s worth making it easy to toggle between dark and light versions depending on the environment.

(defun my/toggle-theme ()
  "Toggle between dark and light themes."
  (interactive)

  (let ((is-dark (seq-contains-p custom-enabled-themes my/dark-theme)))
    (-each custom-enabled-themes 'disable-theme)
    (load-theme (if is-dark my/light-theme my/dark-theme) t)))

(global-set-key (kbd "C-x t t") 'my/toggle-theme)

Icons

All The Icons provides a set of icon-font based icons which can be used by several other packages. You need to run M-x all-the-icons-install-fonts before it will work.

(use-package all-the-icons)

Mode Line

The default Emacs modeline is pretty plain. I’m now using doom-modeline. I’ve also tried out Spaceline and Powerline; they’re fine, I just found this to be easier to work with.

(use-package doom-modeline
  :custom
  (doom-modeline-major-mode-icon nil)
  (doom-modeline-height 25)
  (doom-modeline-project-detection 'projectile)
  (doom-modeline-minor-modes t)
  (doom-modeline-checker-simple-format nil)
  (doom-modeline-gnus nil)
  (doom-modeline-irc nil)
  :config
  (progn
    (doom-modeline-mode +1)
    (column-number-mode +1)))

Once a few minor modes are enabled, the modeline can get cluttered. The delight package can help with this, by abbreviating or eliding mode names. use-package integrates with it as well. Some of the modes enabled in the Prelude section can benefit from this.

(use-package delight)
(use-package emacs
  :delight
  (auto-fill-function " $")
  (visual-line-mode))

Nicer Form Feeds

Some elisp files use the form-feed character to separate sections (along with some Emacs modes—I’m looking at you, elisp-compile-mode). We can display them as lines using page-break-lines (or form-feed, if you’re using Emacs in text mode).

(use-package page-break-lines
  :delight page-break-lines-mode
  :config (global-page-break-lines-mode +1))

Core Enhancements

This is, effectively, part 2 of the Prelude section. The difference is that while the Prelude only brings in a package if it’s absolutely essential, this section brings in packages which do at least one of two things:

  • Improve or replace built-in Emacs features in a way that’s broadly useful.
  • Add features that are useful in practically every context.

The packages in this section either show up in many Emacs configs, or are part of a class of packages one of which shows up in many Emacs configs.

Libraries

prescient.el is used to order searches by frecency. Make changes to its options here; integration with other packages is located with those packages.

(use-package prescient
  :config (prescient-persist-mode +1))

Keybindings

The Hydra library provides tools for constructing groups of keybindings that require fewer keypresses by allowing a prefix to be implied if the previous keybinding was part of the same group.

(use-package hydra)

May Emacs keybindings require a sequence of keystrokes. Which keys do what in which mode can be hard to remember; this provides a popup that shows which-key you might need next.

(use-package which-key
  :delight which-key-mode
  :config (which-key-mode +1))

Text Completion

Emacs features completion abilities, but company-mode expands those into a framework that other modes can build on.

(use-package company
  :demand t
  :delight company-mode
  :hook (after-init . global-company-mode))

(use-package company-prescient
  :config (company-prescient-mode +1))

Less dramatically, Smartparens helps insert paired characters for you. This config uses the strict mode, which tries to help you out when deleting text would cause delimiters to become unbalanced. It’s enabled automatically for programming modes.

(use-package smartparens
  :delight (smartparens-mode " ()")
  :hook ((prog-mode . smartparens-mode)
         (emacs-lisp-mode . smartparens-strict-mode))
  :config
  (require 'smartparens-config))

To go with that, rainbow-delimiters helps make it easy to see what’s going on with those parentheses.

(use-package rainbow-delimiters
  :hook (prog-mode . rainbow-delimiters-mode))

Minibuffer Completion

Emacs has minibuffer completion built-in in the form of ido-mode. However, there are two more advanced completion frameworks available: Ivy and Helm. Both have wide support, but at time of writing, the Helm maintainer had just halted development. I ended up switching to Ivy, which I rather like so far.

(use-package ivy
  :demand t
  :delight ivy-mode
  :bind (("C-c C-r" . ivy-resume)
         ("C-x B" . ivy-switch-buffer-other-window))
  :custom
  (ivy-count-format "(%d/%d) ")
  (ivy-use-virtual-buffers t)
  (ivy-virtual-abbreviate 'full)
  :config (ivy-mode +1))

Ivy pairs well with Counsel, from the same repository, which integrates Ivy with a variety of built-in Emacs features.

(use-package counsel
  :after ivy
  :delight counsel-mode
  :config (counsel-mode +1))

prescient.el integrates with Ivy, and sort of de-integrates with Counsel.

(use-package ivy-prescient
  :after counsel
  :config (ivy-prescient-mode +1))

In order to display more information in Ivy minibuffers, ivy-rich adds the concept of transformers, and defines some useful default ones, which can display extra information (such a docstrings) inside Ivy menus.

(use-package ivy-rich
  :after (ivy counsel all-the-icons-ivy-rich)
  :custom
  (ivy-rich-path-style 'abbrev)
  :config
  (setcdr (assq t ivy-format-functions-alist) #'ivy-format-function-line)
  (ivy-rich-mode +1))

For example, we can have icons in there.

(use-package all-the-icons-ivy-rich
  :after counsel-projectile
  :config (all-the-icons-ivy-rich-mode +1))

Searching

Expanding on the Ivy configuration above, Swiper, again part of the same project, provides a nifty search interface in the minibuffer.

(use-package swiper
  :after ivy
  :bind (("C-s" . swiper)))

Additionally, I like having TODO/FIXME/etc. comments highlighted with a dedicated search feature. hl-todo provides this, and we can give it a nice Hydra map.

(use-package hl-todo
  :demand t
  :config
  (progn
    (defun my/hl-todo-swiper ()
      "Search for TODOs in Swiper"
      (interactive)
      (swiper (substring (hl-todo--regexp) 2 -2)))

    (defhydra hydra-hl-todo (hl-todo-mode-map "C-c")
      "Search TODOs"
      ("N" hl-todo-previous "previous")
      ("n" hl-todo-next "next")
      ("s" my/hl-todo-swiper "swiper" :exit t)
      ("o" hl-todo-occur "occur" :exit t)
      ("i" hl-todo-insert "insert" :exit t))
    (global-hl-todo-mode +1)))

Syntax Checking

Flycheck is a replacement for the built-in Flymake. Several language-specific modes can be configured to take advantage of it.

(use-package flycheck
  :demand t
  :delight flycheck-mode  ; doom-modeline has a dedicated indicator for this
  :hook (after-init . global-flycheck-mode))

Projects

Emacs doesn’t really have a concept of projects, i.e. groups of related files, as such. It’s useful, though, and Projectile provides a framework and features for working with this.

(use-package projectile
  :delight (projectile-mode
            (:eval (format " p:%s" (projectile-project-type))))
  :bind-keymap ("C-c p" . projectile-command-map)
  :bind (("<f7>" . projectile-compile-project)
         ("<f5>" . projectile-run-project))
  :custom
  (projectile-completion-system 'ivy)
  :config (projectile-discover-projects-in-search-path))

It natively supports Ivy, but counsel-projectile takes the integration further.

(use-package counsel-projectile
  :config (counsel-projectile-mode +1))  ; also enables projectile-mode

Undo/Redo

Tree-style undo/redo via undo-tree (also wiki).

(use-package undo-tree
  :delight undo-tree-mode
  :custom
  (undo-tree-visualizer-timestamps t)
  (undo-tree-visualizer-diff t)
  :config (global-undo-tree-mode +1))

Window Management

I’m currently using ace-window for navigating windows, since Treemacs depends on it anyway (see File Explorer). Another option is winum.

(use-package ace-window
  :demand t
  :custom
  (aw-display-mode-overlay nil)
  (aw-dispatch-always t)
  (aw-background nil)
  :bind ("C-x o" . ace-window)
  :config (ace-window-display-mode +1))

File Explorer

I find it helpful to have a tree-style file explorer in a sidebar. Treemacs provides this feature. I have it bound into the ace-window keymap.

(use-package treemacs
  :hook (after-init . treemacs-select-window)  ; open on start
  :config
  (progn
    (add-to-list 'aw-dispatch-alist '(?t treemacs-select-window))
    (add-to-list 'aw-dispatch-alist '(?T treemacs))
    (treemacs-git-mode 'deferred)
    (treemacs-filewatch-mode 1)
    (define-key treemacs-mode-map [mouse-1]
      #'treemacs-single-click-expand-action)))

It also integrates with projectile-mode (see Projects).

(use-package treemacs-projectile
  :after (treemacs projectile))

It also needs to be hooked into the Mixed-DPI helper, otherwise the icon sizes end up wrong.

(add-hook 'my/toggle-face-height-hook
          #'(lambda ()
              (treemacs-resize-icons
               (if (> (my/current-default-face-height) 80) 22 11))))

If All The Icons was installed, Treemacs can use them.

(use-package treemacs-all-the-icons
  :requires all-the-icons
  :config (treemacs-load-theme 'all-the-icons))

Git

Magit is a Git porcelain, and can be considered one of the killer apps for Emacs. Never leave home without it. It’s good enough that, as a longtime Mercurial fan, I stoppped using it for personal projects because it didn’t work with Magit.

(use-package magit
  :bind ("C-x g" . magit-status))

We do need to tell auto-fill-mode to wrap at 72 instead of whatever it was wrapping at before. (The git-commit package is part of Magit.)

(use-package git-commit
  :hook (git-commit-mode . (lambda () (setq fill-column 72))))

Treemacs provides a Magit integration so that it knows when to redo highlighting for which files are staged/unstaged.

(use-package treemacs-magit
  :after (treemacs magit))

It’s also handy to show which lines have changed in the fringe. git-gutter-fringe provides that.

(use-package git-gutter-fringe
  :delight git-gutter-mode
  :config (global-git-gutter-mode +1))

Git Config Modes for editing common config files. Some of these are useful for similar files from other tools (e.g. ignore files).

(use-package git-modes
  :mode (("/\\.gitattributes\\'" . gitattributes-mode)
         ("/info/attributes\\'" . gitattributes-mode)
         ("/git/attributes\\'" . gitattributes-mode)
         ("/\\.gitconfig\\'" . gitconfig-mode)
         ("/\\.git/config\\'" . gitconfig-mode)
         ("/modules/.*/config\\'" . gitconfig-mode)
         ("/git/config\\'" . gitconfig-mode)
         ("/\\.gitmodules\\'" . gitconfig-mode)
         ("/etc/gitconfig\\'" . gitconfig-mode)
         ("/\\.gitignore\\'" . gitignore-mode)
         ("/info/exclude\\'" . gitignore-mode)
         ("/git/ignore\\'" . gitignore-mode)))

Evil

As a VIM refugee with modal editing burned into my fingers, I really like Evil. It emulates vi inside of Emacs, and it does it really well. If you are not used to VIM, you do not want anything to do with this section.

(defun my/c-c ()
  "Start a key sequence for a major mode command."
  (interactive)
  (setq unread-command-events (listify-key-sequence (kbd "C-c"))))

(defun my/c-x ()
  "Start a key sequence for a general command."
  (interactive)
  (setq unread-command-events (listify-key-sequence (kbd "C-x"))))

(use-package evil
  :demand t
  :bind (("C-x SPC" . counsel-M-x)      ; available as SPC SPC
         :map evil-normal-state-map
         ("SPC" . my/c-x)
         ("," . my/c-c)
         ;; Swiper integration
         ("/" . swiper)
         ("?" . swiper-backward)
         ("*" . swiper-thing-at-point))
  :custom
  (evil-undo-system 'undo-tree)
  (evil-search-module 'evil-search)  ; fixes the thing where nN always go backward
  :config
  (evil-mode +1))

At this time, I don’t make any particular effort to rebind things to vi-style keybindings; I mostly care about basic editing. However, some modes don’t work properly without being told about Evil, so the rest of this section does just that. It’s organized by package, rather than feature.

Magit

Evil provides an Evil-Magit integration.

(use-package evil-magit
  :after (evil magit))

Smartparens

Add the fancy paren handling into Evil with evil-cleverparens. (See Text Completion)

(use-package evil-cleverparens
  :delight evil-cleverparens-mode
  :hook (lisp-mode . evil-cleverparens-mode))

Treemacs

Treemacs provides an integration for Evil.

(use-package treemacs-evil
  :after (treemacs evil))

Extended Enhancements

Additional enhancements that are less-generally useful. May get moved up to Prelude or Core Enhancements as I see fit.

Fast Restart

Botched your Emacs state? Made changes to the init code that require a full restart? Want to spend as little time outside Emacs as possible while restarting? Then just M-x restart-emacs and get on with your work.

(use-package restart-emacs
  :commands restart-emacs)

(Provided that you are working, instead of fussing over your initfiles like me.)

Prettier Autocomplete

company-box makes the autocomplete popup a bit prettier, and also causes it to not be a jumbled mess in buffers using variable-pitch fonts.

(use-package company-box
  :delight
  :hook (company-mode . company-box-mode))

Window Management II

The window-purpose package allows windows to be “dedicated” to buffers with a particular purpose. Handy if you want to avoid your compilation buffer leaping around taking over windows.

(use-package window-purpose
  :bind (:map purpose-mode-map
              ("C-x b" . nil)
              ("C-x C-f" . nil))
  :config
  (purpose-mode +1)
  (require 'window-purpose-x)
  (purpose-x-magit-single-on))

The C-x b and C-x C-f bindings override the ones from Counsel, so those are unmapped as suggested for if you’re using Helm. I’m not sure that the window-purpose versions get you, but it seems to work fine for now.

EditorConfig

Provides support for EditorConfig files.

(use-package editorconfig
  :ensure t
  :config
  (editorconfig-mode +1))

Languages

Common

Many languages now have Language Server Protocol backends for them. For Emacs, lsp-mode allows us to take advantage of these. The language-specific configurations are grouped under the relevant headers, including the hooks.

(use-package lsp-mode
  :hook ((lsp-mode . lsp-enable-which-key-integration))
  :custom
  (lsp-keymap-prefix "C-l")
  :commands lsp)

(use-package lsp-ui
  :commands lsp-ui-mode)

(use-package lsp-ivy  ; ivy integration
  :commands lsp-ivy-workspace-symbol)

(use-package lsp-treemacs  ; treemacs integration
  :commands lsp-treemacs-errors-list)

El Doc gets pulled in a lot (LSP uses it), and it’s generally obvious that it’s present because you start getting lots of little popups, so it doesn’t need a lighter.

(delight 'eldoc-mode nil t)

Meson

Support for the Meson build system using meson-mode.

(use-package meson-mode)

C/C++

Still need to test some of this, get the kinks worked out on Windows, and review https://oremacs.com/2017/03/28/emacs-cpp-ide/ for more stuff. Maybe pull in the packages suggested on the irony-mode page, too.

Emacs already has a respectable C/C++ mode, but irony-mode can take advantage of libclang to improve the experience.

(defun my/indent-setup ()
  (c-set-offset 'arglist-intro '+))

(add-hook 'c-mode-hook #'my/indent-setup)
(add-hook 'c++-mode-hook #'my/indent-setup)

(use-package irony
  :hook (((c++-mode c-mode objc-mode) . irony-mode)
         (irony-mode . irony-cdb-autosetup-compile-options))
  :init
  (when *my/is-winnt*
    (add-to-list 'exec-path (expand-file-name "~/scoop/apps/llvm/10.0.0/bin") t)

    ;; Suggested in the documentation to improve performance.
    (when (boundp 'w32-pipe-read-delay)
      (setq w32-pipe-read-delay 0))
    (when (boundp 'w32-pipe-buffer-size)
      (setq irony-server-w32-pipe-buffer-size (* 64 1024)))))

(use-package flycheck-irony
  :hook ((flycheck-mode . flycheck-irony-setup)))

(use-package company-irony
  :config
  (add-to-list 'company-backends 'company-irony))

(use-package irony-eldoc
  :hook ((irony-mode . irony-eldoc)))

In theory cmake-ide should provide a bunch of bells and whistles, but the documentation is a bit sparse. company-c-headers might be worth a look as well.

CUDA

A simple cuda-mode which provides font-lock features. Could get most of the way there by just adding entries for c++-mode to auto-mode-alist.

(use-package cuda-mode
  :mode (("\\.cu\\'" . cuda-mode)
         ("\\.cuh\\'" . cuda-mode)))

Dhall

(use-package dhall-mode)

elisp

Emacs has pretty much everything you need for working with elisp, but if you’re byte-compiling those files, it’s good to auto-compile so that the bytecode stays up-to-date. (Note that straight.el will handle compiling packages itself.) This wants to be set up as early as possible, so it goes into our early-init.el.

(use-package auto-compile
  :config
  (auto-compile-on-load-mode +1)
  (auto-compile-on-save-mode +1))

fish shell

A fish-mode for working with fish shell, which is what I use on UNIXy systems.

(use-package fish-mode
  :hook (fish-mode . (lambda ()
                       (add-hook 'before-save-hook 'fish_indent-before-save)))
  :mode (("\\.fish\\'" . fish-mode)
         ("/fish_funced\\..*\\'" . fish-mode))
  :interpreter ("fish" . fish-mode))

Go

Golang development with go-mode.el. I tried out company-go but it turned out to be intolerably slow; I understand that using LSP with gopls is now the preferred option. I haven’t done much Go lately so this will sit around as-is until I need it again.

;; (use-package company-go)
(use-package go-mode
  :mode ("\\.go\\'". go-mode)
  :init
  (progn
    (defun my/go-mode-locals ()
      ;; (set (make-local-variable 'company-backends) '(company-go))
      ;; (company-mode 1)
      (setq tab-width 3))
    (add-hook 'go-mode-hook #'my/go-mode-locals)
    (add-hook 'go-mode-hook #'flycheck-mode)
    (add-hook 'before-save-hook #'gofmt-before-save)))

Kubernetes

(use-package kubernetes
  :commands (kubernetes-overview))

(use-package kubernetes-evil
  :after kubernetes)

Lua

lua-mode for writing Lua.

(use-package lua-mode
  :commands (lua-mode)
  :mode ("\\.lua\\'" . lua-mode)
  :interpreter ("lua" . lua-mode))

Markdown

Markdown Mode for writing Markdown.

(use-package markdown-mode
  :mode (("README\\.md\\'" . gfm-mode)
         ("\\.md\\'" . markdown-mode)
         ("\\.markdown\\'" . markdown-mode))
  :custom (markdown-command '("pandoc" "--from=markdown" "--to=html5")))
(define-minor-mode my/writer-mode
  "Minor mode for writing prose."
  :init-value nil :lighter nil :global nil
  (if my/writer-mode
      (my/writer-mode--enable)
    (my/writer-mode--disable)))

(defface my/writer-mode-default-face
  '((t :inherit font-lock-comment-face
       :family "iA Writer Duo S"
       :height 1.20))
  "Default face for body text.")

(defface my/writer-mode-hl-line-face
  '((t :foreground "#ebdbb2"))
  "Default face for current line.")

(defun my/writer-mode--window-max-text-width (&optional window)
  "Return the maximum possible text width of WINDOW."
  (or window (setq window (selected-window)))
  (let* ((margins (window-margins window))
         (buffer (window-buffer window))
         (scale (if (and (boundp 'text-scale-mode-step)
                         (boundp 'text-scale-mode-amount))
                    (with-current-buffer buffer
                      (expt text-scale-mode-step
                            text-scale-mode-amount))
                  1.0)))
    (truncate (/ (+ (window-width window)
                    (or (car margins) 0)
                    (or (cdr margins) 0))
                 (float scale)
                 1.1))))

(defun my/writer-mode--adjust-window (&optional window)
  "Adjust the margins and fringes of WINDOW."
  (or window (setq window (selected-window)))
  (with-selected-window window
    (when my/writer-mode
      (set-window-fringes window nil nil t)
      (set-window-parameter window 'min-margins '(0 . 0))
      (let* ((total-width (my/writer-mode--window-max-text-width window))
             (margins (max 0 (- total-width fill-column))))
        (set-window-margins window (/ margins 2))))))

(defun my/writer-mode--enable ()
  "Set up `my/writer-mode' for the current buffer."
  (add-hook 'window-configuration-change-hook
            #'my/writer-mode--adjust-window 'append 'local)
  (add-hook 'window-state-change-functions
            #'my/writer-mode--adjust-window 'append 'local)
  (set (make-local-variable 'buffer-face-mode-face) 'my/writer-mode-default-face)
  (set (make-local-variable 'hl-line-face) 'my/writer-mode-hl-line-face)
  (buffer-face-mode +1)
  (hl-line-mode +1)
  (setq fill-column 70))

(defun my/writer-mode--disable ()
  "Disable `my/writer-mode' for the current buffer."
  (remove-hook 'window-configuration-change-hook
               #'my/writer-mode--adjust-window 'local)
  (remove-hook 'window-state-change-functions
               #'my/writer-mode--adjust-window 'local)
  (buffer-face-mode -1)
  (hl-line-mode -1)
  (setq fill-column (default-value 'fill-column))
  (let ((window (get-buffer-window (current-buffer))))
    (set-window-margins window 0 0)
    (set-window-parameter window 'min-margins nil)
    (set-window-fringes window nil)))

Org-Mode

Of course we want the full-fat Org mode. Which I really should learn to use more effectively.

(use-package org
  :mode ("\\.org\\'" . org-mode)
  :bind (("C-c l" . org-store-link)
         ("C-c a" . org-agenda)
         ("C-c c" . org-capture))
  :custom
  (org-log-done t)
  (org-hide-emphasis-markers t))

htmlize isn’t strictly part of Org, but it’s needed for the export to HTML feature and I’m not really sure where else to put it.

(use-package htmlize
  :commands (htmlize-buffer
             htmlize-region
             htmlize-file
             htmlize-many-files
             htmlize-many-files-dired))

This uses org-variable-pitch.el to make Org buffers, which tend to be full of running text, more readable. I honestly thought this wouldn’t be very good but so far I quite like it. It does mess up the autocomplete popup unless you’re using something like company-box, though.

(delight 'buffer-face-mode nil t)
(use-package org-variable-pitch
  :delight
  (org-variable-pitch-minor-mode)
  :hook (org-mode . org-variable-pitch--enable)
  :config
  (set-face-attribute 'org-variable-pitch-fixed-face nil
                      :family (org-variable-pitch--get-fixed-font)
                      :height 0.8)
  (set-face-attribute 'org-level-1 nil :height (+ 1.0 (expt 0.5 0)))
  (set-face-attribute 'org-level-2 nil :height (+ 1.0 (expt 0.5 1)))
  (set-face-attribute 'org-level-3 nil :height (+ 1.0 (expt 0.5 2)))
  (set-face-attribute 'org-level-4 nil :height (+ 1.0 (expt 0.5 3)))
  (set-face-attribute 'org-level-5 nil :height (+ 1.0 (expt 0.5 4)))
  (set-face-attribute 'org-level-6 nil :height (+ 1.0 (expt 0.5 5)))
  (set-face-attribute 'org-level-7 nil :height (+ 1.0 (expt 0.5 6)))
  (set-face-attribute 'org-level-8 nil :height (+ 1.0 (expt 0.5 7))))

org-d20 provides RPG tools for Org. I should really learn how to use this, and maybe also move my campaign notes out a crappy notepad file now that I have Emacs starting up faster.

(use-package org-d20
  :commands org-d20-mode)

Ocaml

This is a huge mess of stuff that I found somewhere and didn’t bother to write down the URL where I found it, so now I have to puzzle it back out again. Apparently ggtags was involved somehow but I never actually got it set up. Better annotations when I fix this next time I try to write some Ocaml.

I’m pretty sure this function is supposed to hunt down an elisp file that opam creates for me, but I need to teach it about Windows too I guess. Right now it’s disabled so I don’t get errors.

(defun my/ocaml/init-opam ()
  (if (executable-find "opam")
      (let ((share (string-trim-right
                    (with-output-to-string
                      (with-current-buffer
                          standard-output
                        (process-file
                         shell-file-name nil '(t nil) nil shell-command-switch
                         "opam config var share"))))))
        (cond ((string= "" share)
               (message "warning: `%s' output empty string." "opam config var share"))
              ((not (file-directory-p share))
               (message "%s" "warning: opam share directory does not exist."))
              (t (setq opam-share share
                       opam-load-path (concat share "/emacs/site-lisp"))
                 (add-to-list 'load-path opam-load-path))))
    (unless (executable-find "ocamlmerlin")
      (message "warning: cannot find `%s' or `%s' executable." "opam" "merlin"))))

Tuareg

(use-package tuareg
  :mode (("\\.ml[ily]?$" . tuareg-mode)
         ("\\.topml$" . tuareg-mode))
  :init
  (progn
    ;; (my/ocaml/init-opam)
    (add-hook 'tuareg-mode-hook 'company-mode)
    (add-hook 'tuareg-mode-hook 'flycheck-mode)
    (dolist (ext '(".cmo" ".cmx" ".cma" ".cmxa" ".cmi" ".cmxs" ".cmt"
                   ".cmti" ".annot"))
      (add-to-list 'completion-ignored-extensions ext))))

Merlin

(use-package merlin
  :delight (merlin-mode "")
  :hook (tuareg-mode . merlin-mode)
  :init
  (progn
    (add-to-list 'company-backends 'merlin-company-backend)))

ocp-indent

(use-package ocp-indent
  :hook (tuareg-mode . ocp-indent-caml-mode-setup))

… this should probably be up there in the Tuareg block.

(with-eval-after-load 'smartparens
  (sp-local-pair 'tuareg-mode "'" nil :actions nil)
  (sp-local-pair 'tuareg-mode "`" nil :actions nil))

Setup for an inferior utop.

(use-package utop
  :delight (utop-minor-mode " ū")
  :hook (tuareg-mode . utop-minor-mode)
  :config
  (progn
    (if (executable-find "opam")
        (setq utop-command "opam config exec -- utop -emacs")
      (message "warning: cannot find `opam' executable."))))

flycheck-ocaml

(use-package flycheck-ocaml
  :after (flycheck merlin)
  :config
  (progn
    (setq merlin-error-after-save nil)
    (flycheck-ocaml-setup)))

Syntax highlighting and Projectile project type for dune.

(use-package dune
  :mode ("\\(?:\\`\\|/\\)dune\\(?:\\.inc\\)?\\'" . dune-mode)
  :commands (dune-promote dune-runtest-and-promote)
  :after projectile
  :init
  (projectile-register-project-type
   'dune '("dune-project")
   :compile "dune build"
   :test "dune runtest"))

Python

Emacs includes a solid built-in python-mode, but since we have LSP support, we can extend that a bit. (There are many other Python modes, such as Elpy, but I jump between languages enough that I’m beginning to value consistency a bit.)

This is currently disabled due to struggling with LSP.

(use-package lsp-python-ms
  :defer t
  :custom
  (lsp-python-ms-auto-install-server t)
  (lsp-python-ms-executable (executable-find "Microsoft.Python.LanguageServer"))
  :hook (python-mode . (lambda ()
                         (require 'lsp-python-ms)
                         (lsp))))

Automatically format Python code on save using the Black formatter.

(use-package blacken
  :delight blacken-mode
  :hook (python-mode . blacken-mode))

Rust

There are multiple Rust modes, and I settled on Rustic for undocumented reasons. This is currently disabled because I haven’t been using Rust lately.

(use-package rustic
  :mode ("\\.rs\\'" . rustic-mode))

Scheme

Geiser, for great justice.

(use-package geiser)
(use-package geiser-chicken)
(use-package geiser-gambit)
(use-package geiser-guile
  :custom (geiser-guile-binary "guile3"))
(add-to-list 'load-path (expand-file-name "chicken" no-littering-etc-directory))
(use-package chicken
  :custom (geiser-chicken-binary "chicken-csi")
  :straight nil)

TeX

So there’s a built-in TeX mode, or we can add AUCTeX. Right now this assumes we’re on Windows, using Sumatra PDF as a viewer; I should probably fix it to use Zathura or something.

(use-package tex-site
  :straight auctex
  :mode ("\\.tex\\'" . TeX-latex-mode)
  :custom
  (TeX-parse-self t) ; Enable parse on load.
  (TeX-auto-save t) ; Enable parse on save.
  (TeX-view-program-list
   '(("SumatraPDF"
      ("SumatraPDF.exe -reuse-instance"
       (mode-io-correlate " -forward-search \"%b\" %n")
       " %o")
      "SumatraPDF")))
  (TeX-view-program-selection '((output-pdf "SumatraPDF")))
  (TeX-source-correlate-mode t)
  (TeX-source-correlate-method 'synctex))

Zig

zig-mode for syntax highlighting and auto-indentation.

(use-package zig-mode
  :commands (zig-mode)
  :hook (zig-mode . lsp)
  :mode ("\\.zig\\'" . zig-mode))

Projectile integration.

(with-eval-after-load 'projectile
  (projectile-register-project-type
   'zig '("build.zig")
   :compile "zig build"
   :test "zig build"))

… so I tried to set up an LSP server for Zig, but right now it crashes whenever I open more than one file, which is a bit of of a non-starter. Also, makes Windows-specific assumptions.

(with-eval-after-load "lsp-mode"
  (add-to-list 'lsp-language-id-configuration '(zig-mode . "zig"))
  (lsp-register-client
   (make-lsp-client
    :new-connection (lsp-stdio-connection
                     (expand-file-name "~/Source/zls/zig-cache/bin/zls.exe"))
    :major-modes '(zig-mode)
    :server-id 'zls)))

Machine-Specific Settings

Machine-specific settings are loaded by keying off of an string constructed from the hostname and system type.

(defconst *my/local-id*
  (format "%s.%s" (downcase (system-name)) system-type)
  "Hostname-based identifier for the current installation.")

(defvar my/local-config-count 0
  "The number of local configs that have been loaded.")

(defmacro my/config-for-local-id (id &rest body)
  "Run BODY only on the installation identified by ID."
  (declare (indent defun))
  `(when (string= ,id *my/local-id*)
     (setq my/local-config-count (1+ my/local-config-count))
     ,@body))

Note that the machine-specific settings are loaded as the last thing, after the rest of the init file has been processed. I mostly use these for setting up installation paths for things which can vary between machines, e.g. Python.

Playground

Experimental, ad-hoc, questionable, and poorly-understood configuration sections.

TRAMP

I’m not quite sure what this is trying to accomplish, or whether it accomplished it.

(setq-default explicit-shell-file-name "/bin/bash")

Fortune

Gets a fortune from the web, and displays it in the *scratch* buffer. On UNIX, we could use the actual fortune program, but I can’t find a port of it for Windows.

(defvar my/fortune "https://api.justyy.workers.dev/api/fortune")

(defun my/fortune ()
  "Insert a fortune from the web into the *scratch* buffer."
  (interactive)
  (let ((url-request-method "GET"))
    (url-retrieve
     my/fortune
     (lambda (status)
       (unless (plist-member status :error)
         (goto-char (point-min))
         (re-search-forward "^$")
         (let ((p (point)))
           (insert "[")
           (goto-char (point-max))
           (insert "]")
           (goto-char p))
         (let ((message (car (json-parse-buffer :array-type 'list))))
           (with-current-buffer "*scratch*"
             (goto-char (point-max))
             (let ((p (point)))
               (insert message)
               (comment-region p (point)))))
         (kill-buffer))))))

;; (my/fortune)

Potentially a better option would just be to teach Emacs how to read fortune files itself. Also, the output would probably look nicer in a formatted buffer, rather than tossed into an elisp buffer as a comment.

Splash Screen

A groovy splash screen. Currently included in this repo.

(add-to-list 'load-path (expand-file-name "groovy-splash" no-littering-etc-directory))
(use-package groovy-splash
  :straight nil
  :hook (after-init . groovy-splash-show)
  :custom
  (groovy-splash-segments '(groovy-splash-groovy-fill
                            groovy-splash-blank-line
                            groovy-splash-logo
                            groovy-splash-blank-line
                            groovy-splash-rule
                            groovy-splash-blank-line
                            groovy-splash-recentf
                            groovy-splash-blank-fill
                            groovy-splash-oracle
                            groovy-splash-blank-line)))
(use-package nasm-mode)
(add-to-list 'load-path (expand-file-name "noweb-mode" no-littering-etc-directory))
(use-package noweb-mode
  :straight nil)

(use-package forth-mode
  :straight nil
  :load-path "lib")

Olivetti Mode

Neatens up prose

        (use-package olivetti
  :config
    (setq-default
olivetti-body-width line-width-characters))

Hide Mode Line

(use-package hide-mode-line)

Writeroom mode

Distraction free, need to add disabling treemacs.

(use-package writeroom-mode)

Fountain Mode

Screenwriting

       (use-package fountain-mode
  :config
  (add-to-list 'auto-mode-alist '("\\.fountain$" . fountain-mode))
  (add-hook 'fountain-mode-hook #'olivetti-mode)
  (defun export-to-pdf ()
    (shell-command-to-string (format "afterwriting --config afterwriting-config.json --source %s --pdf --overwrite" buffer-file-name)))
  (add-hook 'after-save-hook #'export-to-pdf))
(custom-set-variables
 ;; custom-set-variables was added by Custom.
 ;; If you edit it by hand, you could mess it up, so be careful.
 ;; Your init file should contain only one such instance.
 ;; If there is more than one, they won't work right.
 '(package-selected-packages (quote (fountain-mode olivetti evil use-package))))
(custom-set-faces
 ;; custom-set-faces was added by Custom.
 ;; If you edit it by hand, you could mess it up, so be careful.
 ;; Your init file should contain only one such instance.
 ;; If there is more than one, they won't work right.
 )

Novel Mode

Read EPUB

(use-package nov)
(add-to-list 'auto-mode-alist'("\\.epub\\'" . nov-mode))

Coda

Write out completion messages after loading each of the main files.

(message "early bird init complete")

(provide 'early-init)
;;; early-init.el ends here

Additionally, for the init.el, we write out which machine-specific section(s) we loaded.

(message "Loaded %d sections matching local id \"%s\""
         my/local-config-count *my/local-id*)
(message "main init complete")

(provide 'init)
;;; init.el ends here

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published