My Emacs Configuration¶
Put all the noise generated by the custom system into a separate file
init.el(setq custom-file (locate-user-emacs-file "custom.el"))
(when (file-exists-p custom-file)
(load custom-file))
Setup package archives
init.el(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
magnars/s.el: String manipulation library
init.el(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(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(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(setenv "PATH" (string-join exec-path ":"))
Basic Editing¶
init.el(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(setq backup-directory-alist `(("." . ,(expand-file-name "backups" user-emacs-directory))))
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(repeat-mode 1)
Whitespace¶
Visualise certain whitespace characters
- face
Enable visualisations that use faces
empty(requiresface)Highlight empty lines at the start/end of the buffer.
trailing(requiresface)Highlight trailing whitespace
lines-char(requiresface)If a line is too long (according to
whitespace-line-column), highlight the first character that crosses the thresholdtab-markRender tab characters in the buffer (like
set listin vim). Useswhitespace-display-mappingsto determine the character(s) to use.
init.el(setq whitespace-style '(face empty trailing lines-char tab-mark))
(add-hook 'prog-mode-hook 'whitespace-mode)
Enable automatic whitespace cleanup on each save
init.el(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(setq tab-always-indent t)
(setq-default indent-tabs-mode nil)
Compilation¶
Basic settings for Emacs’ compilation framework
init.el(setq compilation-always-kill t
compilation-scroll-output t)
Enable coloured output in compilation buffers
init.el(add-hook 'compilation-filter-hook 'ansi-color-compilation-filter)
Attempt at a sane display-buffer-alist rule for compilation windows
init.el(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(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(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)))
*Completions*¶
Use C-n and C-p to cycle through completion candidates.
init.el(dolist (kmap (list minibuffer-local-map completion-in-region-mode-map))
(keymap-set kmap "C-p" #'minibuffer-previous-completion)
(keymap-set kmap "C-n" #'minibuffer-next-completion))
Only pop open the *Completions* buffer on the second attempt at completing something.
However, once it is open keep it updated and only focus it on the second completion attempt with no progress made.
init.el(setq completion-auto-help 'visible
completion-auto-select 'second-tab)
If there are only 3 (or fewer) possible candidates, cycle between them
init.el(setq completion-cycle-threshold 3)
Tweak how the *Completions* buffer is rendered
init.el(setq completions-detailed t
completions-format 'one-column
completions-group t
completions-header-format (propertize "%s candidates\n" 'face 'shadow)
completions-max-height 15
completion-show-help nil)
Use the oantolin/orderless completion style
init.el(use-package orderless
:ensure t
:custom
(completion-styles '(orderless basic))
(completion-category-defaults nil)
(completion-category-overrides '((file (styles basic partial-completion)))))
Consult¶
Use minad/consult
init.el(use-package consult
:ensure t
:custom
(consult-preview-key "M-.")
:bind (("C-x b" . consult-buffer)))
And since I use embark, use the recommended emabrk-consult package
init.el(use-package embark-consult
:after (embark consult)
:ensure t)
Embark¶
Use oantolin/embark!
init.el(use-package embark
:ensure t
:bind (("C-." . embark-act)
("M-." . embark-dwim)))
Dired¶
Settings for dired
init.el(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(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(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(use-package apheleia
:ensure t
:config
(apheleia-global-mode))
reStructuredText¶
init.el(use-package esbonio
:vc (:url "https://github.com/swyddfa/esbonio.el" :rev "main")
:hook ((rst-mode . esbonio-eglot-ensure)
(rst-mode . visual-line-mode)))
TypeScript¶
init.el(use-package typescript-ts-mode
:mode "\\.ts\\'"
:ensure t
:hook ((typescript-ts-mode . eglot-ensure)))
YAML¶
init.el(use-package yaml-mode
:ensure t)
LLMs¶
init.el(use-package gptel
:ensure t
:bind (("C-c RET" . gptel-send))
:config
(setq gptel-model 'llama3.2:latest)
(setq gptel-backend (gptel-make-ollama "Ollama"
:host "localhost:11434"
:stream t
:models '(llama3.2:latest))))
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.
elisp-module{%- extends "default" %}
{% block header %};;; {{ path.name }} --- TODO add description -*- lexical-binding: t -*-
;;; Code:
{% endblock %}
{% block footer %}
(provide '{{ path.stem }})
;;; {{ path.name }} ends here
{% endblock %}