My Emacs Configuration

Put all the noise generated by the custom system into a separate file.

init.el
emacs r1
(setq custom-file (locate-user-emacs-file "custom.el"))
(when (file-exists-p custom-file)
 (load custom-file))

Setup package archives

init.el
emacs r1
(package-initialize)
(add-to-list 'package-archives '("org" . "https://orgmode.org/elpa/"))
(add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/"))

And install some utility libraries

init.el
emacs r1
(dolist (pkg '(s))
  (unless (package-installed-p pkg)
    (unless package-archive-contents
      (package-refresh-contents))
    (package-install pkg)))

Work Around

I am currently using Aurora which encourages the use of homebrew to install cli programs. This means executables are normally placed in /home/linuxbrew/.linuxbrew/bin/ which is usually added to PATH for my user account. However, this does not happen when launching Emacs via krunner and so it fails to find any tools installed there.

To work around this, let’s manually add it to exec-path

init.el
emacs r1
(if (file-exists-p "/home/linuxbrew/.linuxbrew/bin")
    (add-to-list 'exec-path "/home/linuxbrew/.linuxbrew/bin"))

Now that I’ve started running the Emacs server via systemctl --user, $HOME/.local/bin does not appear to be on the PATH either. (No idea why, so let’s just add it here and move on.)

init.el
emacs r1
(if (file-exists-p "/home/alex/.local/bin")
    (add-to-list 'exec-path "/home/alex/.local/bin"))

However, this isn’t enough to let functions like shell-command find the command, we also need to update Emacs’ version of PATH

init.el
emacs r1
(setenv "PATH" (string-join exec-path ":"))

Basic Editing

init.el
emacs r1
(recentf-mode t)
(savehist-mode t)
(save-place-mode t)

Backup Files

By default Emacs will litter folders with backup files (filename.txt~), rather than disable them entirely just put them out of sight.

init.el
emacs r1
(setq backup-directory-alist `(("." . ,(expand-file-name "backups" user-emacs-directory))))

Kill Ring

a.k.a. the clipboard.

init.el
emacs r1
(setq kill-do-not-save-duplicates t           ; when would you want duplicates?...
      select-active-regions nil               ; for some reason needed to make kills appear in the system clipboard...
                                              ; see: https://www.reddit.com/r/emacs/comments/1mbf0pd/killringsave_not_in_clipboard/
      save-interprogram-paste-before-kill t)  ; save system clipboard to kill-ring before killing new text.

Repeat Mode

repeat-mode is awesome and while I should really look at trying some of the things shown in Karthink’s guide, simply turning the mode on gives you a lot.

init.el
emacs r1
(repeat-mode 1)

Whitespace

Visualise certain whitespace characters

face

Enable visualisations that use faces

empty (requires face)

Highlight empty lines at the start/end of the buffer.

trailing (requires face)

Highlight trailing whitespace

lines-char (requires face)

If a line is too long (according to whitespace-line-column), highlight the first character that crosses the threshold

tab-mark

Render tab characters in the buffer (like set list in vim). Uses whitespace-display-mappings to determine the character(s) to use.

init.el
emacs r1
(setq whitespace-style '(face empty trailing tab-mark))
(add-hook 'prog-mode-hook 'whitespace-mode)
(add-hook 'prog-mode-hook 'display-fill-column-indicator-mode)

Enable automatic whitespace cleanup on each save

init.el
emacs r1
(add-hook 'before-save-hook #'whitespace-cleanup)

Indent using spaces by default, and ensure that the TAB key is only used for indentation.

init.el
emacs r1
(setq tab-always-indent t)
(setq-default indent-tabs-mode nil)

Compilation

Basic settings for Emacs’ compilation framework

init.el
emacs r1
(setq compilation-always-kill t
      compilation-scroll-output t)

Enable coloured output in compilation buffers

init.el
emacs r1
(add-hook 'compilation-filter-hook 'ansi-color-compilation-filter)

Attempt at a sane display-buffer-alist rule for compilation windows

init.el
emacs r1
(add-to-list 'display-buffer-alist
             '("\\*compilation\\*"
               (display-buffer-reuse-window
                display-buffer-in-previous-window
                display-buffer-reuse-mode-window
                display-buffer-at-bottom)
               (window-height . 0.25)))

Make F5 call recompile

init.el
emacs r1
(keymap-global-set "<f5>" 'recompile)

The following adds a simple minor mode which, when enabled, will call recompile each time the buffer is saved

init.el
emacs r1
(define-minor-mode recompile-on-save-mode
  "When enabled, run `recompile' after the current buffer is saved"
  :lighter " ⭮"
  (if recompile-on-save-mode
      (add-hook 'after-save-hook 'recompile nil t)
    (remove-hook 'after-save-hook 'recompile t)))

Dired

Settings for dired

init.el
emacs r1
(use-package dired
 :hook ((dired-mode . dired-hide-details-mode)
        (dired-mode . hl-line-mode))
 :config
 (setq dired-dwim-target t
       dired-maybe-use-globstar t
       dired-isearch-filenames 'dwim
       ;; -A, all files - except '.' & '..'
       ;; -G, omit owning group
       ;; -h, human readable file sizes
       ;; -l, long listing, required for dired.
       dired-listing-switches "-AGhl --group-directories-first --time-style=long-iso"))

project.el

The built-in project management, project.el is good enough for my needs. However, so that Emacs works nicely with monorepo style repositories defining some additional root marker files will get project.el to consider a sub-directory of a repo a valid project.

init.el
emacs r1
(setq project-vc-extra-root-markers '("Cargo.toml"
                                      "package.json"
                                      "pyproject.toml"))

This however, introduces a few problems the most obvious of which is that project-find-file doesn’t respect .gitignore when run from within a subproject. The following advice ensures that the right vc backend is selected for subprojects

init.el
emacs r1
(defun alc-project-try-vc-subproject (orig-fun &rest args)
  "Advice for `project-try-vc'.

When using `project-vc-extra-root-markers' to teach project.el
about subprojects within a monorepo, `project-try-vc'
successfully finds the subject's root but fails to detect the
backend. But by calling `vc-responsible-backend' on the found
root, we can fill in the blanks.

As a result, commands like `project-find-file' now respect the
parent repo's .gitignore file even when being run from within a
subproject."
  (let* ((res (apply orig-fun args))
         (dir (nth 2 res))
         (backend (or (nth 1 res)
                      (ignore-errors (vc-responsible-backend dir)))))
    (if dir
        `(vc ,backend ,dir))))

(advice-add 'project-try-vc :around #'alc-project-try-vc-subproject)

Languages

And radian-software/apheleia for automatic formatting of buffers

init.el
emacs r1
(use-package apheleia
  :ensure t
  :config
  (apheleia-global-mode))

JavaScript

init.el
emacs r1
(use-package js
  :mode ("\\.js\\'" . js-mode)
  :custom
  (js-indent-level 2)
  :hook ((js-ts-mode . eglot-ensure)))

TypeScript

init.el
emacs r1
(use-package typescript-ts-mode
  :mode "\\.ts\\'"
  :hook ((typescript-ts-mode . eglot-ensure)))

YAML

init.el
emacs r1
(use-package yaml-mode
  :ensure t)

Awdur Templates

Making use of awdur’s templates removes the need to ensure code like (provide '<feature>) finds its way into an explicit code block.

emacs r1
{%- extends "default" %}
{% block header %};;; {{ output.path.name }} --- TODO add description  -*- lexical-binding: t -*-

;;; Code:
{% endblock %}

{% block footer %}

(provide '{{ output.path.stem }})

;;; {{ output.path.name }} ends here
{% endblock %}
emacs r1
{%- extends "default" -%}
{% block header %}
/// <reference types="tree-sitter-cli/dsl" />
// @ts-check

export default grammar({
{% endblock %}

{% block footer %}
});
{% endblock %}