#+TITLE: Emacs Configuration #+AUTHOR: Thomas Voss #+DATE: <2023-08-09 Wed> #+DESCRIPTION: My Emacs configuration — before I ran ‘rm -rf ~/.config’ that is #+STARTUP: overview * Headers The first thing we need in any configuration is a header setting ~lexical-binding~ to ~t~. This will allow us to have non-autistic behavior when scoping variables. #+BEGIN_SRC elisp :tangle early-init.el ;;; early-init.el --- Emacs early configuration file -*- lexical-binding: t -*- #+END_SRC #+BEGIN_SRC elisp :tangle init.el ;;; init.el --- Emacs configuration file -*- lexical-binding: t -*- #+END_SRC * Early Init File :PROPERTIES: :header-args: :tangle early-init.el :END: ** Numerical Constants These are not really important, but nice to have to make the following configuration more readable. The reason they’re defined in the ~early-init.el~ is simply because I need to use them here. #+BEGIN_SRC elisp (defconst 1-KiB 1024 "The number of bytes in 1 kibibyte.") (defconst 1-MiB (* 1-KiB 1024) "The number of bytes in 1 mebibyte.") (defconst 1-GiB (* 1-MiB 1024) "The number of bytes in 1 gibibyte.") #+END_SRC ** XDG Directories We want to define variables for the main XDG directories for Emacs to use. These are nice for keeping things organized and out of the way. #+BEGIN_SRC elisp (defconst mm-cache-directory (expand-file-name "emacs" (or (getenv "XDG_CACHE_HOME") (expand-file-name ".cache" (getenv "HOME")))) "The XDG-conformant cache directory that Emacs should use.") (defconst mm-config-directory (expand-file-name "emacs" (or (getenv "XDG_CONFIG_HOME") (expand-file-name ".config" (getenv "HOME")))) "The XDG-conformant config directory that Emacs should use.") (defconst mm-data-directory (expand-file-name "emacs" (or (getenv "XDG_DATA_HOME") (expand-file-name ".local/share" (getenv "HOME")))) "The XDG-conformant data directory that Emacs should use.") #+END_SRC We also need to ensure our directories actually exist. #+BEGIN_SRC elisp (mapc (lambda (x) (make-directory x t)) (list mm-cache-directory mm-config-directory mm-data-directory)) #+END_SRC We don’t want to have all sorts of random garbage populating the configuartion directory; let’s throw it all in the cache directory instead. #+BEGIN_SRC elisp (setq user-emacs-directory mm-cache-directory auto-save-list-file-prefix (expand-file-name "auto-save-list/" mm-cache-directory) backup-directory-alist`(("." . ,(expand-file-name "backups" mm-cache-directory)))) #+END_SRC ** Default Limits These are just some limits Emacs abides by as far as garbage-collection and process communication go. They’re pretty low by default though; any modern system is capable of much higher. #+BEGIN_SRC elisp (setq gc-cons-threshold (* 512 1-MiB) read-process-output-max 1-MiB) #+END_SRC ** Package Management Finally, we want to completely disable ~package.el~ since we’re going to be using ~straight.el~ in the main configuration. #+BEGIN_SRC elisp (setq package-enable-at-startup nil) #+END_SRC * Init File :PROPERTIES: :header-args: :tangle init.el :END: ** Helper Functions and -Macros *** Shorter Lambdas It’s annoying to have to write out ~(lambda () BODY)~ every time I want to write a lambda that takes no args — a very common operation. The solution is to just use an actual lambda. If I ever forget how to enter a lambda, it’s =C-x 8 RET=. #+BEGIN_SRC elisp (defmacro λ (&rest body) "Convenience macro to create lambda functions that take no arguments with much short and concise syntax. Calling ‘λ’ with BODY is equivalent to calling ‘lambda’ with an empty argument list and BODY." (declare (pure t) (side-effect-free t)) `(lambda () ,@body)) #+END_SRC *** Hooks from Modes It is very often that I have a mode symbol and I want to extract the corresponding hook from it. Luckily there’s a pretty standard naming convention here. #+BEGIN_SRC elisp (defun mm-mode-to-hook (mode) "Get the hook corresponding to MODE." (declare (pure t) (side-effect-free t)) (intern (concat (symbol-name mode) "-hook"))) #+END_SRC *** Tree-Sitter Modes As I was writing this configuration, Emacs 29 released on the Arch repositories with native support for ~tree-sitter~. As a result many major-modes now have two versions — a regular version and a ~tree-sitter~ version. I should have pretty equal configurations for both versions of a mode so it’s useful to be able to grab one from the other. #+BEGIN_SRC elisp (defun mm-mode-to-ts-mode (mode) "Get the tree-sitter mode corresponding to MODE." (declare (pure t) (side-effect-free t)) (intern (concat (string-remove-suffix "-mode" (symbol-name mode)) "-ts-mode"))) #+END_SRC *** Less-Verbose Mapc One pattern that I need to do a lot is running a ~dolist~ over a list of items, and performing some action for each of them. This could also be done with ~mapcar~. Since this is such a common task, I should probably make a macro to make it just that little bit easier for me: #+BEGIN_SRC elisp (defmacro mm-for-each (sequence &rest body) "Execute BODY for each element of SEQUENCE. The variable ‘x’ is automatically set to the current subject of the iteration." (declare (indent defun)) `(mapc (lambda (x) ,@body) ,sequence)) #+END_SRC ** Package Management For package management I like to use ~straight.el~. Before setting that up though it’s probably best to disable native-compilation warnings; we’ll get a whole lot of those when ~straight.el~ is installing packages. #+BEGIN_SRC elisp (setq comp-async-report-warnings-errors nil) #+END_SRC After doing that, we can bootstrap ~straight.el~. The two options enabled at the end are just configuring ~use-package~ to always use ~straight.el~ by default, and to always ensure packages unless stated otherwise. #+BEGIN_SRC elisp (defvar bootstrap-version) (let ((bootstrap-file (expand-file-name "straight/repos/straight.el/bootstrap.el" user-emacs-directory)) (bootstrap-version 6)) (unless (file-exists-p bootstrap-file) (with-current-buffer (url-retrieve-synchronously "https://raw.githubusercontent.com/radian-software/straight.el/develop/install.el" 'silent 'inhibit-cookies) (goto-char (point-max)) (eval-print-last-sexp))) (load bootstrap-file nil 'nomessage)) (setq straight-use-package-by-default t use-package-always-ensure t) #+END_SRC ** Documentation Documentation is absolutely essential. The ~helpful~ package gives us much better documentation for things, so let’s use it. I’m mostly just overriding the bindings for the standard ~describe-*~ functions. #+BEGIN_SRC elisp (use-package helpful :bind (("C-h f" . #'helpful-callable) ("C-h v" . #'helpful-variable) ("C-h k" . #'helpful-key) ("C-h o" . #'helpful-symbol) ("C-c C-d" . #'helpful-at-point))) #+END_SRC ** Key Bindings *** Editing This Config This configuration is one of the files I visit the most. Not just for making large customizations, but also for simply editing the tab-width for a mode I happen to be using. For that reason there should probably be a binding to get here. #+BEGIN_SRC elisp (global-set-key (kbd "C-c e") (λ (interactive) (find-file (expand-file-name "config.org" mm-config-directory)))) #+END_SRC *** Evil Mode The default Emacs keybindings are horrible and dangerous. Feel free to use them if you want to develop genuine problems with your hands. For this reason, ~evil-mode~ bindings are the way to go. Also as I was writing this, Bram Moolenaar, the creator of Vim died. RIP. Here we setup bindings in the ~:bind~ section; the functions bound to that don’t exist yet will be created shortly. You also need to set all these variables /before/ ~evil-mode~ is loaded — it’s just how ~evil~ works I suppose. Finally I like to have ~visual-line-mode~ enabled as I find it far more intuitive. #+BEGIN_SRC elisp (use-package evil :bind (:map evil-normal-state-map ("€" . #'evil-end-of-visual-line) ("h" . #'evil-window-left) ("j" . #'evil-window-down) ("k" . #'evil-window-up) ("l" . #'evil-window-right) ("a" . #'mm--evil-align-regexp) ("s" . #'mm--evil-sort-lines) (";" . #'mm--evil-comment-or-uncomment-region)) :init ;; We need to set these variables before loading ‘evil-mode’ (setq evil-want-Y-yank-to-eol t evil-search-wrap nil evil-v$-excludes-newline t evil-respect-visual-line-mode t evil-split-window-below t evil-vsplit-window-right t evil-want-fine-undo t evil-undo-system 'undo-redo evil-want-keybinding nil) (evil-mode) (global-visual-line-mode) :config (evil-set-leader nil (kbd "SPC"))) #+END_SRC *** Evil Surround This is probably one of the more useful Vim/Emacs extensions out there. It let’s you easy add-, remove-, and change surrounding pairs such as quotation marks and parenthesis from a /text object/. I like to use unicode single- and double quotation marks — so we also want to add support for those. #+BEGIN_SRC elisp (use-package evil-surround :after evil :config (global-evil-surround-mode)) #+END_SRC Supporting custom pairs is a bit tricky since we need to define an evil /text object/ to make them work properly. Also we add some custom Jinja pairs! #+BEGIN_SRC elisp (defmacro mm--evil-define-and-bind-quoted-text-object (name key start-regex end-regex) (let ((inner-name (make-symbol (concat "evil-inner-" name))) (outer-name (make-symbol (concat "evil-a-" name)))) `(progn (evil-define-text-object ,inner-name (count &optional beg end type) (evil-select-paren ,start-regex ,end-regex beg end type count nil)) (evil-define-text-object ,outer-name (count &optional beg end type) (evil-select-paren ,start-regex ,end-regex beg end type count t)) (define-key evil-inner-text-objects-map ,key #',inner-name) (define-key evil-outer-text-objects-map ,key #',outer-name)))) (mm--evil-define-and-bind-quoted-text-object "single-quote-open" "‘" "‘" "’") (mm--evil-define-and-bind-quoted-text-object "single-quote-close" "’" "‘" "’") (mm--evil-define-and-bind-quoted-text-object "double-quote-open" "“" "“" "”") (mm--evil-define-and-bind-quoted-text-object "double-quote-close" "”" "“" "”") (setq-default evil-surround-pairs-alist (append '((?‘ . ("‘ " . " ’")) (?’ . ("‘" . "’")) (?“ . ("“ " . " ”")) (?“ . ("“" . "”"))) evil-surround-pairs-alist)) (add-hook 'html-mode-hook (λ (setq-local evil-surround-pairs-alist (append '((?% . ("{% " . " %}")) (?# . ("{# " . " #}")) (?{ . ("{{ " . " }}"))) evil-surround-pairs-alist)) (mm--evil-define-and-bind-quoted-text-object "jinja-action" "%" "{%" "%}") (mm--evil-define-and-bind-quoted-text-object "jinja-comment" "#" "{#" "#}") (mm--evil-define-and-bind-quoted-text-object "jinja-code" "{" "{{" "}}"))) #+END_SRC *** Evil Collection This is a super-simple to setup package that adds ~evil~ bindings to many other custom packages like ~magit~ and ~mu4e~. #+BEGIN_SRC elisp (use-package evil-collection :after evil :config (evil-collection-init)) #+END_SRC *** Aligning Text I absolutely love aligning text with ~align-regexp~. It is in my opinion one of the most underrated Emacs functions. Unfortunately, it aligns with tabs or -spaces based on the setting of ~indent-tabs-mode~. Personally I am almost always indenting with tabs, but I prefer to align with spaces. Luckily we can use some advice to force the usage of spaces. #+BEGIN_SRC elisp (defadvice align-regexp (around align-regexp-with-spaces preactivate compile) "Advice to force ‘align-regexp’ to always align with spaces, regardless of the value of ‘indent-tabs-mode’." (let ((indent-tabs-mode nil)) ad-do-it)) #+END_SRC Now that it’s behaving properly, it should also be turned into an ~evil~ operator so it can be used with Vim motions. #+BEGIN_SRC elisp (evil-define-operator mm--evil-align-regexp (beg end regexp repeat) "Wrapper around ‘align-regexp’ to allow for use as an ‘evil-mode’ operator." (interactive (let ((range (evil-operator-range))) (list (car range) (cadr range) (concat "\\(\\s-*\\)" (read-string "Align regexp: ")) (y-or-n-p "Repeat? ")))) (align-regexp beg end regexp 1 1 repeat)) #+END_SRC *** Sorting Text Another very common operation is sorting text. So why not make an operator for that too? #+BEGIN_SRC elisp (evil-define-operator mm--evil-sort-lines (beg end) "Wrapper around ‘sort-lines’ to allow for use as an ‘evil-mode’ operator." (sort-lines nil beg end)) #+END_SRC *** Commenting Code Commenting code is a super common task — so make it an operator! #+BEGIN_SRC elisp (evil-define-operator mm--evil-comment-or-uncomment-region (beg end) "Wrapper around ‘comment-or-uncomment-region’ to allow for use as an ‘evil-mode’ operator." (comment-or-uncomment-region beg end)) #+END_SRC ** Completions *** Savehist-Mode This mode is super handy. It let’s you preserve your history in minibuffer prompts. #+BEGIN_SRC elisp (savehist-mode) #+END_SRC *** Vertico & Marginalia Vertico is a great package for enhanced completions in the minibuffer. It’s minimal and works great. We also want to configure the highlighting of the current line to match up with what is used for ~hl-line-mode~. Vertico also doesn’t offer a builtin function to go up a directory when typing out a path, so that’s what the ~mm-minibuffer-backward-kill~ is for. #+BEGIN_SRC elisp (defun mm-minibuffer-backward-kill (arg) "When minibuffer is completing a file name delete up to parent folder, otherwise delete a word." (interactive "p") (if minibuffer-completing-file-name (if (string-match-p "/." (minibuffer-contents)) (zap-up-to-char (- arg) ?/) (delete-minibuffer-contents)) (backward-kill-word arg))) (use-package vertico :bind (:map vertico-map ("C-j" . vertico-next) ("C-k" . vertico-previous) ("C-l" . vertico-insert) :map minibuffer-local-map ("C-h" . mm-minibuffer-backward-kill)) :custom (vertico-cycle t) :init (vertico-mode) (add-hook 'mm-after-load-theme-hook (λ (face-spec-set 'vertico-current `((t (:background ,(face-attribute 'hl-line :background nil t)))))))) #+END_SRC Marginalia is kind of a suppliment I like to use with Vertico. It adds, well… /marginalia/ to the completions in the minibuffer. This includes things like file sizes and permissions when looking at files, function docstrings when looking at those, etc. #+BEGIN_SRC elisp (use-package marginalia :after vertico :init (marginalia-mode)) #+END_SRC *** Orderless Orderless is another pretty neat package. It allows for better completion candidate filtering. I personally prefer to use the ~orderless-prefixes~ completion style where entering the string “foo bar baz” will match the options that have components beginning with /foo/, /bar/, and /baz/ in that order. #+BEGIN_SRC elisp (use-package orderless :custom (completion-styles '(orderless basic)) (orderless-matching-styles '(orderless-prefixes)) (completion-category-overrides '((file (styles basic partial-completion))))) #+END_SRC *** Company Company is a package to give me actual completion popups; it’s super useful for autocompleting code but has other uses too I guess. #+BEGIN_SRC elisp (use-package company :hook ((conf-mode prog-mode) . company-mode) :custom (company-minimum-prefix-length 1) (company-idle-delay 0.0)) #+END_SRC ** Mathematics The built-in emacs calculator ~calc~ is genuinely the best calculator program I have ever used. Annoyingly though, it has a ‘trail’ that is always around. I don’t like it. #+BEGIN_SRC elisp (setq calc-display-trail nil) #+END_SRC ** Programming *** Indentation Indentation in Emacs is a royal pain in the ass. Not only is there a ~tab-width~ variable and the ~indent-tabs-mode~ mode, but lots of modes just have an extra ~tab-width~-esque variable for some reason? I try to fix this all with a custom function that reads a list of mode-specific indentation settings. #+BEGIN_SRC elisp (setq-default tab-width 8 evil-shift-width 8 indent-tabs-mode t) (defvar mm-indentation-settings '((c-mode :width 8 :extra-vars (c-basic-offset)) (css-mode :extra-vars (css-indent-offset)) (emacs-lisp-mode :spaces t) (graphviz-dot-mode :extra-vars (graphviz-dot-indent-width)) (lisp-mode :spaces t) (org-mode :spaces t) (python-mode :width 4 :spaces nil :extra-vars (python-indent-offset)) (rust-mode :width 4 :spaces nil) (sgml-mode :width 4 :spaces nil :extra-vars (sgml-basic-offset)) (xml-mode :width 4 :spaces nil)) "A list of per-mode indentation settings. Each list contains a major-mode and the 3 optional keyword arguments of :spaces, :width, and :extra-vars. When setting the settings for a given major-mode, the settings will also be applied for that modes tree-sitter variant. If :spaces is non-nil, then indentation will be performed with spaces instead of tabs characters. If :width is non-nil, then it will override the modes given tab-width. If :extra-vars is non-nill, then it shall be a list of additional mode-specific variables that need to be assigned the desired indentation-width.") (defun mm-set-indentation-settings () "Apply the indentation settings specified by ‘mm-indentation-settings’." (interactive) (mm-for-each mm-indentation-settings (let* ((mode (car x)) (args (cdr x)) (width (or (plist-get args :width) (default-value 'tab-width))) (spaces (or (plist-get args :spaces) (not (default-value 'indent-tabs-mode)))) (extra (plist-get args :extra-vars)) (callback (λ (indent-tabs-mode (when spaces -1)) (setq-local tab-width width evil-shift-width width) (mm-for-each extra (set x width))))) (add-hook (mm-mode-to-hook mode) callback 95) (let ((ts-hook (mm-mode-to-hook (mm-mode-to-ts-mode mode)))) (when (boundp ts-hook) (add-hook ts-hook callback 95)))))) (mm-set-indentation-settings) #+END_SRC *** Git Integration I like to use Magit for my ~git~ integration. I do believe it is the best ~git~ client ever made for any editor ever. Anyone who disagrees has simply never used Emacs before. The only command that really needs binding is ~magit-status~. All other commands I will end up executing from there with the transient commands. I also install ~magit-todos~. It’s a super minimal package that simply finds all the TODOs in a repository and displays them in the ~magit-status~ buffer so that I don’t forget them. #+BEGIN_SRC elisp (use-package magit :bind ("C-c g" . magit-status) :custom (magit-display-buffer-function #'magit-display-buffer-same-window-except-diff-v1)) (use-package magit-todos :after magit :init (magit-todos-mode)) #+END_SRC *** Tree-Sitter Emacs 29 brings native support for Tree-Sitter! This doesn’t just mean better- and faster syntax highlighting, but also things such as structured editing. In order to make use of Tree-Sitter the language parsers /do/ need to be installed, so let’s do that. Tree-Sitter doesn’t check to see if the language grammars are already installed unfortunately, but it’s easy enough to do manually. #+BEGIN_SRC elisp (setq treesit-language-source-alist '((bash "https://github.com/tree-sitter/tree-sitter-bash") (c "https://github.com/tree-sitter/tree-sitter-c") (cpp "https://github.com/tree-sitter/tree-sitter-cpp") (css "https://github.com/tree-sitter/tree-sitter-css") (elisp "https://github.com/Wilfred/tree-sitter-elisp") (go "https://github.com/tree-sitter/tree-sitter-go") (html "https://github.com/tree-sitter/tree-sitter-html") (json "https://github.com/tree-sitter/tree-sitter-json") (make "https://github.com/alemuller/tree-sitter-make") (markdown "https://github.com/ikatyang/tree-sitter-markdown") (python "https://github.com/tree-sitter/tree-sitter-python") (toml "https://github.com/tree-sitter/tree-sitter-toml"))) ;; Automatically install missing grammars (thread-last (mapcar #'car treesit-language-source-alist) (seq-remove #'treesit-language-available-p) (mapc #'treesit-install-language-grammar)) #+END_SRC *** Language Server Protocol LSP is an absolute necessity when programming. Luckily ~lsp-mode~ has us covered. There is Eglot which is now built in to Emacs 29, but it was giving me some really weird issues so I won’t be using that for now. ~lsp-bridge~ also looked promising but I didn’t like it too much. Also ~yasnippet~ is dead, so why they won’t just move on from it I have no idea. I’ll just disable snippets. #+BEGIN_SRC elisp (use-package lsp-mode :custom (lsp-enable-snippet nil) :init (setq lsp-keymap-prefix "C-c l") :hook ((rust-ts-mode . lsp-deferred)) :commands (lsp lsp-deferred)) #+END_SRC *** Emmet-Mode This mode is super useful for writing HTML. It lets me expand something like ‘figure>(figcaption>code)+pre’ into an actual HTML structure. #+BEGIN_SRC elisp (use-package emmet-mode :hook (html-mode . emmet-mode)) #+END_SRC *** Compilation Buffer Emacs allows you to compile your project with =C-x p c= which is cool and all, but it annoyingly creates a compilation buffer that I need to manually close every time. I would like to have this buffer, but only when things go wrong. #+BEGIN_SRC elisp (add-hook 'compilation-finish-functions (lambda (buffer _) (with-current-buffer buffer (unless (or (> compilation-num-warnings-found 0) (> compilation-num-errors-found 0)) (kill-buffer buffer))))) #+END_SRC ** User Interface The default Emacs UI is fucking atrocious — we need to make it better. *** Shorter Prompts For some reason emacs has both the ~y-or-n-p~ and ~yes-or-no-p~ functions. I do not like having to spell out full words — /y/ or /n/ is good enough for me — so let’s just redefine ~yes-or-no-p~. #+BEGIN_SRC elisp (fset #'yes-or-no-p #'y-or-n-p) #+END_SRC *** Disable Basic UI Modes Emacs has a lot of UI modes that are enabled by default to make life easier for the novice user. I am not the novice user and would rather these modes never turned on ever again. #+BEGIN_SRC elisp (menu-bar-mode -1) (scroll-bar-mode -1) (tool-bar-mode -1) (tooltip-mode -1) #+END_SRC *** Warnings I am typically not a fan around being warned about things unless something is actually breaking or going wrong. In order, these options disable the following warnings: 1. Opening large files 2. Following symbolic links 3. Adding advice to functions #+BEGIN_SRC elisp (setq large-file-warning-threshold nil vc-follow-symlinks t ad-redefinition-action 'accept) #+END_SRC *** Visible Bell Why not? I might disable this later. #+BEGIN_SRC elisp (setq visible-bell t) #+END_SRC *** Scrolling By default, scrolling is really bad. Luckily we can improve it a lot; there’s even a pixel-precision scrolling mode! #+BEGIN_SRC elisp (setq mouse-wheel-scroll-amount '(1 ((shift) . 1)) mouse-wheel-progressive-speed nil mouse-wheel-follow-mouse t scroll-step 1) (pixel-scroll-precision-mode) #+END_SRC *** Auto Reverting Buffers This is just good to have all of the time; you should never be looking at a file whose state was changed by an external process, and not see those changes *instantly*. #+BEGIN_SRC elisp (setq global-auto-revert-non-file-buffers t) (global-auto-revert-mode) #+END_SRC *** Highlight Matching Parenthesis This is generally a good thing — especially when writing lisp code — but I don’t want this /everywhere/. #+BEGIN_SRC elisp (defvar mm-highlight-matching-parenthesis-modes '(prog-mode conf-mode) "A list of modes for which the parenthesis that pairs up with the parenthesis at point should be highlighted.") (show-paren-mode -1) (mm-for-each (mapcar #'mm-mode-to-hook mm-highlight-matching-parenthesis-modes) (add-hook x #'show-paren-local-mode)) #+END_SRC *** Line- and Column Numbers It’s a bit tricky to remember, but ~line-number-mode~ and ~display-line-numbers-mode~ are very different. The former displays the current line number in the modeline while the latter displays line numbers on the left of the buffer. Personally I like to always display the current line number in the modeline along with the column number — I find that it helps me wrap text properly when I am not using ~auto-fill-mode~ — but I would prefer to only display line numbers in certain modes. #+BEGIN_SRC elisp (line-number-mode) (column-number-mode) ;; Enable and disable line numbers for some modes (defvar mm-enable-line-numbers-modes '(text-mode prog-mode conf-mode) "A list of modes for which line numbers should be displayed.") (defvar mm-disable-line-numbers-modes '(org-mode) "A list of modes for which line numbers shouldn’t be displayed.") (mm-for-each (mapcar #'mm-mode-to-hook mm-enable-line-numbers-modes) (add-hook x #'display-line-numbers-mode)) (mm-for-each (mapcar #'mm-mode-to-hook mm-disable-line-numbers-modes) (add-hook x (λ (display-line-numbers-mode -1)))) #+END_SRC *** Emacs Theme The default theme is a light theme. I am not one of these weak-eyed retards that cannot handle a light theme, but it is really, /really/ bad. Personally I quite enjoy the /Sanity Inc./ themes, and they’re a lot less generic than the Doom One theme that everyone and their grandmother uses. Personally I am not sure if I prefer ~tomorrow-night~ or ~tomorrow-eighties~, so why not make it random? #+BEGIN_SRC elisp (use-package color-theme-sanityinc-tomorrow :config (let ((n (random 2))) (cond ((eq n 0) (load-theme 'sanityinc-tomorrow-night t)) ((eq n 1) (load-theme 'sanityinc-tomorrow-eighties t))))) #+END_SRC There is one issue though. I like to have ~vertico~ use the same color when highlighting my current selection as I use to highlight the current line, and that changes with each theme I use. For this reason we need some advice around the ~load-theme~ function to fire a hook. #+BEGIN_SRC elisp (defvar mm-after-load-theme-hook nil "Hook called after ‘load-theme’ is run.") (defadvice load-theme (after load-theme-with-hooks preactivate compile) "Advice to run ‘mm-after-load-theme-hook’ hooks after loading a custom theme." (run-hooks 'mm-after-load-theme-hook)) #+END_SRC *** TODO Fonts My favorite monospace font has got to be /Iosevka/. It’s good looking, it’s far more compact than the usual american-sized monospace fonts, and you can customize just about every character from loads of variants. I have my own custom compiled variant called /Iosevka Smooth/. On the proportional side of things, I am not really sure what font to use. /Vollkorn/ tends to be my go-to serif-font on the web, but I dunno how well it translates to Emacs. I am also a bit fan of Ysabeau for sans-serif. I need to play around with this. #+BEGIN_SRC elisp (defvar mm-monospace-font '("Iosevka Smooth" :weight light :height 162) "The default monospace font to use. This is a list containing a font name, font weight, and font height in that order.") (defvar mm-proportional-font '("Ysabeau" :weight light :height 180) "The default proportional font to use. This is a list containing a font name, font weight, and font height in that order.") #+END_SRC Actually /setting/ the fonts is a bit tricky. I don’t really fully understand why it works like this, but something with the whole server/client model of Emacs is really fucking with this, so we need to add a hook to set the font for every frame. We also can’t forget the frame that’s actually running this code. #+BEGIN_SRC elisp (defun mm-set-fonts () "Set the fonts specified by ‘mm-monospace-font’ and ‘mm-proportional-font’." (interactive) (let* ((mono-family (car mm-monospace-font)) (mono-props (cdr mm-monospace-font)) (prop-family (car mm-proportional-font)) (prop-props (cdr mm-proportional-font)) (mono-weight (plist-get mono-props :weight)) (mono-height (plist-get mono-props :height)) (prop-weight (plist-get prop-props :weight)) (prop-height (plist-get prop-props :height))) (set-face-attribute 'default nil :font mono-family :weight mono-weight :height mono-height) (set-face-attribute 'fixed-pitch nil :font mono-family :weight mono-weight :height mono-height) (set-face-attribute 'variable-pitch nil :font prop-family :weight prop-weight :height prop-height))) (add-hook 'after-make-frame-functions (lambda (_) (mm-set-fonts))) (mm-set-fonts) #+END_SRC *** Line Highlighting This is just something I personally like having. It makes it very easy for me to figure out where my point is at all times. #+BEGIN_SRC elisp (global-hl-line-mode) #+END_SRC *** Frame Management Prot has made a fantastic package known as ~beframe~. It allows you to have each frame have its own buffer list. This is insanely useful for keeping my buffers nice and organized. It’s also useful for having a way to (for example) kill all buffers you were using in a frame before closing it. #+BEGIN_SRC elisp (use-package beframe :init (beframe-mode)) (defun mm-beframe-kill-frame () (interactive) (mapc #'kill-buffer (beframe-buffer-list)) (save-buffers-kill-terminal)) #+END_SRC *** TODO Transparency Transparency is totally useless, but it looks cool. Luckily transparent background support was added in Emacs 29! TODO: Why doesn’t ~mm--set-frame-alpha-background~ get run automatically? #+BEGIN_SRC elisp (defun mm--set-frame-alpha-background (&optional frame) "Set the background opacity of FRAME to ‘mm-alpha-background’. If FRAME is nil then the selected frame is used." (when (display-graphic-p) (set-frame-parameter frame 'alpha-background mm-alpha-background))) (defcustom mm-alpha-background 90 "The opacity of a graphical Emacs frame, ranging from 0–100. This variable should customized via ‘setopt’ so that the correct setter gets executed. For interactive customization you can use ‘mm-set-alpha-background’." :type '(natnum) :set (lambda (symbol value) (set symbol value) (mm--set-frame-alpha-background))) (defun mm-set-alpha-background (val) "Set the current frames background opacity to VAL." (interactive "NOpacity: ") (setopt mm-alpha-background val)) (add-hook 'after-make-frame-functions #'mm--set-frame-alpha-background) #+END_SRC ** Extra Modes Some modes aren’t installed by default with Emacs, so let’s fetch them #+BEGIN_SRC elisp (use-package git-modes) (use-package graphviz-dot-mode) (use-package markdown-mode) #+END_SRC ** Customization Settings Emacs has a /customization variable/ that contains various configuration-related settings that are set by different commands, as well as the customization UI. I would rather have these saved in a temporary file since any customizations I make that I would like to have be persistent will be explicitly written into this file. #+BEGIN_SRC elisp (setq custom-file (expand-file-name (format "emacs-custom-%s.el" (user-uid)) temporary-file-directory)) (load custom-file t) #+END_SRC ** Backup Files It’s always good to have backups. I would know — I’ve wiped both =~= and =~/.config= before! Despite the name, ~version-control~ actually just adds version numbers to the backup names — it doesn’t start using a VCS. #+BEGIN_SRC elisp (setq delete-old-versions t version-control t kept-new-versions 2 kept-old-versions 6) #+END_SRC ** Mode Specific Settings *** Org-Mode I really enjoy using ~org-indent-mode~. It indents the contents of the org-buffer to keep everything looking nice and structured. #+BEGIN_SRC elisp (add-hook 'org-mode-hook #'org-indent-mode) #+END_SRC *** Auto-Fill-Mode I do like to use this mode, especially when writing. I also want it when coding /sometimes/, but not always. Overall it’s a pretty useful mode — but I am super mixed on when I do and -don’t want it. I always want it in ~org-mode~ though. #+BEGIN_SRC elisp (setq-default fill-column 80) (add-hook 'org-mode-hook #'auto-fill-mode) #+END_SRC ** Email #+BEGIN_SRC elisp (use-package mu4e :ensure nil :custom (user-full-name "Thomas Voss") (mu4e-change-filenames-when-moving t) (mu4e-get-mail-command "mbsync -a -c /home/thomas/.config/isync/mbsyncrc") (mu4e-maildir "~/mail") (sendmail-program "/usr/bin/msmtp") (send-mail-function 'smtpmail-send-it) (message-sendmail-f-is-evil t) (message-sendmail-extra-arguments '("--read-envelope-from")) (message-send-mail-function 'message-send-mail-with-sendmail) :config (setq mm--mu4e-personal-context (make-mu4e-context :name "Personal" :match-func (lambda (msg) (when msg (string-prefix-p "/mail@thomasvoss.com" (mu4e-message-field msg :maildir)))) :vars '((user-mail-address . "mail@thomasvoss.com") (mu4e-drafts-folder . "/mail@thomasvoss.com/Drafts") (mu4e-sent-folder . "/mail@thomasvoss.com/Sent") (mu4e-refile-folder . "/mail@thomasvoss.com/Archive") (mu4e-trash-folder . "/mail@thomasvoss.com/Junk") (mu4e-maildir-shortcuts . '((:name "Inbox" :maildir "/mail@thomasvoss.com/Inbox" :key ?i) (:name "Archive" :maildir "/mail@thomasvoss.com/Archive" :key ?a) (:name "Drafts" :maildir "/mail@thomasvoss.com/Drafts" :key ?d) (:name "Sent" :maildir "/mail@thomasvoss.com/Sent" :key ?s) (:name "Junk" :maildir "/mail@thomasvoss.com/Junk" :key ?j)))))) (setq mm--mu4e-legacy-context (make-mu4e-context :name "Legacy" :match-func (lambda (msg) (when msg (string-prefix-p "/thomasvoss@live.com" (mu4e-message-field msg :maildir)))) :vars '((user-mail-address . "thomasvoss@live.com") (user-full-name . "Thomas Voss") (mu4e-drafts-folder . "/thomasvoss@live.com/Drafts") (mu4e-sent-folder . "/thomasvoss@live.com/Sent") (mu4e-refile-folder . "/thomasvoss@live.com/Archive") (mu4e-trash-folder . "/thomasvoss@live.com/Junk") (mu4e-maildir-shortcuts . '((:name "Inbox" :maildir "/thomasvoss@live.com/Inbox" :key ?i) (:name "POP" :maildir "/thomasvoss@live.com/POP" :key ?p) (:name "Archive" :maildir "/thomasvoss@live.com/Archive" :key ?a) (:name "Drafts" :maildir "/thomasvoss@live.com/Drafts" :key ?d) (:name "Sent" :maildir "/thomasvoss@live.com/Sent" :key ?s) (:name "Junk" :maildir "/thomasvoss@live.com/Junk" :key ?j)))))) (setq mm--mu4e-humanwave-context (make-mu4e-context :name "Humanwave" :match-func (lambda (msg) (when msg (string-prefix-p "/thomas.voss@humanwave.nl" (mu4e-message-field msg :maildir)))) :vars '((user-mail-address . "thomas.voss@humanwave.nl") (user-full-name . "Thomas Voss") (mu4e-drafts-folder . "/thomas.voss@humanwave.nl/[Gmail]/Drafts") (mu4e-sent-folder . "/thomas.voss@humanwave.nl/[Gmail]/Sent Mail") (mu4e-refile-folder . "/thomas.voss@humanwave.nl/[Gmail]/All Mail") (mu4e-trash-folder . "/thomas.voss@humanwave.nl/[Gmail]/Trash") (mu4e-maildir-shortcuts . '((:name "Inbox" :maildir "/thomas.voss@humanwave.nl/Inbox" :key ?i) (:name "Archive" :maildir "/thomas.voss@humanwave.nl/[Gmail]/All Mail" :key ?a) (:name "Drafts" :maildir "/thomas.voss@humanwave.nl/[Gmail]/Drafts" :key ?d) (:name "Sent" :maildir "/thomas.voss@humanwave.nl/[Gmail]/Sent Mail" :key ?s) (:name "Junk" :maildir "/thomas.voss@humanwave.nl/[Gmail]/Junk" :key ?j)))))) (setq mu4e-contexts (list mm--mu4e-personal-context mm--mu4e-legacy-context mm--mu4e-humanwave-context))) #+END_SRC