Ronaldo's Emacs literate configuration
Table of Contents
- 7.1. Update copyright notice on file save
- 7.2. Save the session
- 7.3. Auto close and colorise parentheses, brackets, et cet. (
electric-pair-mode
) - 7.4. Ligatures
- 7.5. TODO, NOTE, FIXME, BUG, … (
hl-todo
) - 7.6. Better
M-x
completion (vertico
& co.) - 7.7. Search everything (
consult
) - 7.8. Icons with
dired
integration (nerd-icons
) - 7.9. Custom modeline (
doom-modeline
) - 7.10. Show possible key bindings (
which-key
) - 7.11. Fold code (
yafolding
) - 7.12. Transpose windows (
transpose-frame
) - 7.13. Switch case of identifiers (
string-inflection
) - 7.14. Transpose lines
Peruse the table of contents to find whatever you may find of interest instead of reading the whole page sequentially. There is also a reference table containing all of custom key bindings.
This text assumes that you are using a Red Hat GNU/Linux distribution with certain tools installed. When problems or requirements arise, be smart and search efficiently to solve your problems if your setup isn't like mine.
Finally, if you have any questions or suggestions, please contact me through email.
0.1. Reference table
Custom key binding | Defined | Function |
---|---|---|
M-g t t |
here | Toggle between themes gligan/dark-theme and gligan/light-theme |
M-g e |
here | Open a terminal emulator. Order: vterm , eshell , term |
M-g d |
here | Input the current date |
M-g j j |
here | Insert a new journal entry |
M-g j s |
here | Search through my journal entries |
M-g o |
here | Open buffer, recent file, bookmark, … |
M-g s |
here | Search for a string in open buffers |
M-g t w |
here | Transpose windows |
M-s M-s |
here | Change casing of an indentifier |
0.2. GC trick
This trick is pretty old. With the aim of optimising startup time, I increased the garbage collection threshold –i.e. the amount of bytes allocated between two collections– to 64 megabytes (Emacs' default is 8 megabytes). I arrived at this number by trial and error –finding the sweet spot is just a matter of trying a number, increasing or decreasing it and seeing what works, repeating this process a few times and you get your number (this is basically Newton's method).
(setq gc-cons-threshold (* 64 1024 1024) gc-cons-percentage 0.5) (setq read-process-output-max (* 3 1024 1024)) ;; 3mb
You should find the number to put in gc-cons-threshold
through the
method described above by yourself. I haven't touched
gc-cons-percentage
, but you could; it could give you better start-up
times.
Having an astronimically large gc-cons-threshold
wouldn't be of any
help. Worse, it would probably slow down Emacs as garbage collection
would take longer. Be advised!
1. Personal
1.1. Elisp interactive functions
1.1.1. Insert today's date
I find that I often need to write today's date, and although I prefer
the European date format (DD-MM-YYYY
), the YYYY-MM-DD
ISO 8601 format
is much more convenient because it sorts itself and is easier to
manipulate. (Please don't ever mention to me the sick MM-DD-YYYY
format, why?)
(defun gligan/today () "Insert today's date as YYYY-MM-DD" (interactive) (insert (format-time-string "%Y-%m-%d"))) (global-set-key (kbd "M-g d") 'gligan/today)
1.1.2. Open a terminal emulator
(defun gligan/open-terminal-emulator () "Tries to open the first one available: vterm, eshell, term" (interactive) (cond ((package-installed-p 'vterm) (vterm)) ((package-installed-p 'eshell) (eshell)) (t (term)))) (global-set-key (kbd "M-g e") 'gligan/open-terminal-emulator)
2. Packages
For managing installed packages, auto-download and configure them I make use of use-package
. For Emacs < 29
, use-package
has to be installed separately as it does not come by default.
(require 'package)
Emacs comes with GNU ELPA by default: a curated package repository containing, well, not many packages. What I'm doing below is pretty standard: I'm adding the larger MELPA repository so that I can install more packages I might want or need. Just be aware of what you're installing and you should be fine.
(add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/"))
Do not forget to initialise:
(package-initialize)
3. General Emacs changes
3.1. Early
Emacs has the ability to run Elisp code before the Emacs window –or
TUI– is initialised. Not all code can run this way, however. This
portion of code should be placed in ~/.emacs.d/early-init.el
in most
cases.
;; Title (setq frame-title-format "%b - Emacs") ;; Minimal window without buttons or scrollbars (menu-bar-mode -1) (tool-bar-mode -1) (scroll-bar-mode -1) ;; Maximised at start up ;; (add-hook 'window-setup-hook 'toggle-frame-maximized t) ;; Fullscreen at start up ;; (add-hook 'window-setup-hook 'toggle-frame-fullscreen t) ;; No window decorations -- good for tiling window managers or minimal ;; configs ;; (add-to-list 'default-frame-alist '(undecorated . t))
Running code early can reduce the startup time of Emacs. For example, in the snippet above, we're disabling the menu, tool and scrollbar modes before they're even loaded. I'm pretty sure this won't affect our init times negatively.
3.2. TODO Main window properties
Like many pop songs, here's my verse for this section:
- I want a semi-transparent window to see my beautiful backgrounds
- I want tabs as spaces, as 2 spaces
- I want the cursor to be a little bar, not a block
- I want automatic saving of files and backup files just in case.
- I want a message telling me how long Emacs took to load
- I don't want line numbers
- I don't want startup or scratch buffer messages
This code basically does the all of the above:
;; Window transparency (set-frame-parameter (selected-frame) 'alpha-background 95) ;; Use two spaces (setq-default tab-width 2) (setq-default indent-tabs-mode nil) (delete-selection-mode 1) (global-subword-mode) ;; Use a small bar as a cursor (setq-default cursor-type 'bar) ;; I don't like line numbers (setq display-line-numbers-type nil) ;; No welcome message; no scratch buffer message (setq inhibit-startup-message t) (setq initial-scratch-message nil) ;; C is more suitable as a default mode to me (setq initial-major-mode 'c-mode) ;; Auto-save & back up files (setq auto-save-default t make-backup-files t) (setq backup-directory-alist '(("." . "~/.emacs.d/backup"))) (defun display-startup-echo-area-message () (message "%s with %d collections" (format "%.2f seconds" (float-time (time-subtract after-init-time before-init-time))) gcs-done))
3.3. Fonts
There are as many programming fonts as there are minutes in a day, really. This is a nice site for choosing one. I use Iosevka Comfy, a modified version of Iosevka by Protesilaos Stavrou, which I really like.
The following variables define the fonts I use through all of this configuration.
(setq gligan/fixed-pitch-font "Iosevka Comfy Motion" gligan/mixed-pitch-font "Iosevka Comfy Motion Duo" gligan/default-font-size 130)
I don't want Emacs panicking if the fonts I'm setting are not
available. For that reason, I define gligan/set-font-when-available
as
a sort of middleman.
(defun gligan/set-font-when-available (face font height) (if (member font (font-family-list)) (set-face-attribute face nil :family font :height height) (message (concat font " not available")))) (gligan/set-font-when-available 'default gligan/fixed-pitch-font gligan/default-font-size) (gligan/set-font-when-available 'fixed-pitch gligan/fixed-pitch-font gligan/default-font-size) (gligan/set-font-when-available 'variable-pitch gligan/mixed-pitch-font gligan/default-font-size)
3.3.1. Font set-up for Org-mode
I want titles in Org-mode to be bigger than their surrounding
paragraph text. Through the gligan/org-mode-font-setup
function I make
that possible.
(defun gligan/org-mode-font-setup () ;; These numbers below define the proportions of the org mode ;; headers in contrast to the paragraph text. (dolist (face '((org-document-title . 1.8) (org-level-1 . 1.3) (org-level-2 . 1.2) (org-level-3 . 1.1))) (set-face-attribute (car face) nil :font gligan/mixed-pitch-font :weight 'regular :height (cdr face))))
Note that here I set up this function as a Org-mode hook.
3.4. Themes
First, if I have a theme installed, I'll treat it as safe; I'm willing to risk my files! Nah, that's just a joke. In practice, most themes are respectable in the sense that people, perhaphs you too, can look at their source, so there is a public judgement in between – this is, in fact, one of the blessings of libre software.
;; Declare all themes as safe. That way, Emacs won't ask the user if ;; it's okay to load a theme (setq custom-safe-themes t)
My main Emacs theme is Monokate; a port that I made of the homonymous theme from Sublime Text. Monokate is a dark, Monokai-like theme that presents just the right amount of contrast; it is neither too mushy nor eye-straining white text on a black background – it is what I desired for my Emacs, so I ported it.
The same sort of features Monokate has I desire for a white theme for when I'm outside or sun rays hit my monitor. For that reason I've developed a white theme of my own: Ellas. Ellas is a warm theme inspired by a less-than-one-week trip I've made to Athens and Santorini. It is nice to look at, but that is my opinion. See my Emacs themes page for screenshots and download instructions.
Other honourable mentions for themes are the ef-themes
collection by
Protesilaos Stavrou, an Emacs personality, especially the greenish
ef-cyprus
theme and perhaps the collection of themes from Doom Emacs.
I've put all the rest of the code in this section into my early-init file and it works.
Everything nowadays has theme toggling; why shouldn't Emacs too? These two variables designate my dark and light theme inside all of this configuration.
(setq gligan/dark-theme 'somnus) ; 'monokate, 'somnus (setq gligan/light-theme 'ellas)
Here I have defined the gligan/toggle-theme
function, that I can run
with M-g t t
or with M-x gligan/toggle-theme RET
.
(defun gligan/toggle-theme () "Switch between Gligan's dark and light themes" (interactive) (disable-theme gligan/current-theme) (setq next-theme (if (equal gligan/current-theme gligan/light-theme) gligan/dark-theme gligan/light-theme)) (setq gligan/current-theme next-theme) (load-theme next-theme t)) (global-set-key (kbd "M-g t t") 'gligan/toggle-theme)
I'm not doing anything fancy: I have two variables that dictate when the light theme should be and if the time when Emacs was open lies between those two hours, then the light theme is set.
;; We start with the dark theme (setq gligan/current-theme gligan/dark-theme) ;; Between these two hours, when opening Emacs, the light theme should be the default (defvar gligan/light-theme-hour-start (string-to-number (getenv "GLIGAN_DAY_START"))) (defvar gligan/light-theme-hour-end (string-to-number (getenv "GLIGAN_DAY_END"))) ;; Switch to light theme if we're between the bounds defined above (let ((hour (string-to-number (substring (current-time-string) 11 13)))) (if (and (<= gligan/light-theme-hour-start hour) (> gligan/light-theme-hour-end hour)) (gligan/toggle-theme) (load-theme gligan/current-theme t)))
4. Packages for languages
4.1. TODO LSP server (lsp-mode
)
(use-package lsp-mode :ensure t :hook ((c-mode ; Alphabetically ordered haskell-mode nim-mode python-mode rust-mode tuareg-mode zig-mode) . lsp-deferred) :config (setq lsp-headerline-breadcrumb-enable nil)) (use-package lsp-ui :ensure t)
4.2. TODO Completion (corfu
)
(use-package corfu :ensure t :custom (corfu-cycle t) (corfu-auto nil) :init (global-corfu-mode))
5. Languages
5.1. C
(use-package irony-eldoc :ensure t :defer t)
5.2. Haskell
(use-package haskell-mode :ensure t :defer t :config)
5.3. OCaml
(use-package tuareg :ensure t :defer t) (use-package dune :ensure t :defer t) (use-package merlin :ensure t :defer t :config (add-hook 'tuareg-mode-hook #'merlin-mode) (setq merlin-error-after-save nil)) (use-package merlin-eldoc :ensure t :defer t :hook (tuareg . merlin-eldoc-setup)) (use-package utop :ensure t :defer t :config (add-hook 'tuareg-mode-hook #'utop-minor-mode))
5.4. Zig
(use-package zig-mode :ensure t :defer t)
5.5. Nim
(use-package nim-mode :ensure t :defer t)
5.6. Fish
(use-package fish-mode :config (setq fish-indent-offset 2))
5.7. CSS
(use-package css-mode :config (setq css-indent-offset 2))
6. Org mode
(defun gligan/tangle-only-org () (when (equal major-mode 'org-mode) (org-babel-tangle))) (use-package org :hook ((org-mode . gligan/org-mode-font-setup) (after-save . gligan/tangle-only-org)) ; Auto tangle on save ; only in Org buffers :config (setq org-directory "~/Documents/Org/") (setq org-hide-emphasis-markers t) (setq org-startup-folded 'fold) (setq org-startup-indented t) (setq org-confirm-babel-evaluate nil) (setq org-preview-latex-default-process 'dvipng))
6.1. org-journal
org-journal
is a nice way to have a journal. I have set up two
keybindings; one for creating a new journal entry and another for
searching for substrings in my journals.
One of the nice things about it, compared to other apps like Notion, Apple Notes, Google Keep or most other note/journal apps, is that my entries are kept on my machine and they're plain text; nothing proprietary. I can export them to other formats if I need to and I can easily read them as plain text if I don't have Emacs. It is also very nicely implemented in Emacs.
(use-package org-journal :ensure t :bind (("M-g j j" . 'org-journal-new-entry) ("M-g j s" . 'org-journal-search-forever)) :config (setq org-journal-dir "~/Documents/Org/Journal/" org-journal-date-format "%A, %d %B %Y")) ; e.g. "Friday, 8 March 2024")
6.2. Skeletons for org-mode
A skeleton is a template for documents. For my blog, I wrote a very
simple skeleton called org-header-skeleton
. Each skeleton can be
invoked by its name from the M-x
menu, or it can even be bound to a
keybinding when in org-mode
. It is up to you.
(define-skeleton org-header-skeleton "Header for my org files, mainly for blog posts" nil "#+title: " (skeleton-read "Document title: ") "\n" "#+author: " user-full-name "\n" ;; "#+EMAIL: " user-mail-address "\n" "#+date: " (format-time-string "%Y-%m-%d") "\n" "#+setupfile: blog.config")
Note that skeletons are not restricted to Org mode. For example,
org-header-skeleton
could be used in c-mode
, for example, even though
it makes no sense. This means that skeletons are mode independent.
6.3. Org modern (org-modern
)
The org-modern
package gives Org documents a really nice look: it
replaces the header asterisks with nice Unicode characters, blocks get
a UI rather than a textual look, et cet. You should have a look at the
org-modern
repository.
(use-package org-modern :ensure t :defer t :hook (org-mode . org-modern-mode))
6.4. Zen mode (olivetti
)
(use-package olivetti :ensure t :defer t ;; :hook (org-mode . olivetti-mode) )
6.5. Proportional fonts (mixed-pitch
)
(use-package mixed-pitch :ensure t :hook (text-mode . mixed-pitch-mode))
6.6. Exporting
6.6.1. to LaTeX
- Syntax highlighting with
minted
Minted is a LaTeX package that adds syntax highlighting to code blocks.
(setq org-latex-src-block-backend 'minted org-latex-packages-alist '(("" "minted")) org-latex-pdf-process '("pdflatex -shell-escape -interaction nonstopmode -output-directory %o %f" "pdflatex -shell-escape -interaction nonstopmode -output-directory %o %f"))
7. Nicities
The following sections configure packages that, whilst not necessary, make the Emacs experience much better.
7.1. Update copyright notice on file save
When working on some projects, there is a copyright notice in every
file. When you edit the file, the copyright notice should also be
updated if the year has changed. To do so, we can hook
copyright-update
to before saving the file
(setq copyright-query nil) ; Don't ask (add-hook 'before-save-hook #'copyright-update)
7.2. Save the session
When using consult
(see here), there is no need to restore the
session, as I can search the recently opened files and bookmarks. It
also doubles the startup time of Emacs.
;; (desktop-save-mode 1)
7.3. Auto close and colorise parentheses, brackets, et cet. (electric-pair-mode
)
Having auto-closing parentheses and coloured pairs of them is a must for some. I have put this section under "Nicities" because I can program without it, although the experience is not pleasant.
As far as I know, electric-pair-mode
comes with Emacs. What is does is
it autocompletes parentheses: you insert (
and it adds )
so that (|
===> (|)
, where the bar is the cursor. It does the same with other
pairs of characters like brackets or quotes.
(electric-pair-mode 1)
Some languages, especially Lisps, make a heavy use of parentheses to denote scope, and so it can be hard to keep track of parenthesis mismatch. With colours, you don't have to count them, just see the rainbow pattern.
(use-package rainbow-delimiters :ensure t :defer t :hook (prog-mode . rainbow-delimiters-mode))
7.4. Ligatures
With the font I'm using, not all but most of the ligatures below work. As far as I know, all Iosevka and Cascadia Code ligatures are included in this list.
(use-package ligature :ensure t :hook (after-init . global-ligature-mode) :config (ligature-set-ligatures 't '("!!" "!!." "!=" "!==" "#!" "##" "###" "####" "#(" "#:" "#=" "#?" "#[" "#_" "#_(" "#{" "$>" "%%" "&&" "(*" "*)" "**" "***" "*/" "*>" "++" "+++" "+:" "+>" "--" "---" "--->" "-->" "-:" "-<" "-<<" "->" "->>" "-|" "-~" ".-" ".." "..." "..<" ".=" ".?" "/*" "//" "///" "/=" "/==" "/>" ":+" ":-" "://" "::" ":::" "::=" ":<" ":=" ":>" ";;" "<!--" "<!---" "<$" "<$>" "<*" "<******>" "<*>" "<+" "<+>" "<-" "<--" "<---" "<---->" "<--->" "<-->" "<-<" "<->" "</" "</>" "<:" "<<" "<<-" "<<<" "<<=" "<=" "<=<" "<==" "<===" "<====>" "<===>" "<==>" "<=>" "<>" "<|" "<|>" "<||" "<|||" "<~" "<~>" "<~~" "=!=" "=/=" "=:" "=:=" "=<<" "==" "===" "===>" "==>" "=>" "=>>" ">-" ">->" ">=" ">=>" ">>" ">>-" ">>=" ">>>" "?." "?:" "?=" "??" "[|" "\\\\" "]#" "^=" "__" "_|_" "www" "{|" "|-" "|=" "|>" "|]" "||" "||=" "||>" "|||>" "|}" "~-" "~=" "~>" "~@" "~~" "~~>")))
7.5. TODO, NOTE, FIXME, BUG, … (hl-todo
)
(use-package hl-todo :defer t :ensure t :hook (prog-mode . hl-todo-mode))
7.6. Better M-x
completion (vertico
& co.)
(use-package vertico :ensure t :config (setq vertico-cycle t) (vertico-mode)) (use-package savehist :ensure t :config (savehist-mode)) (use-package orderless :ensure t :config (setq completion-styles '(orderless flex) completion-category-overrides '((eglot (styles . (orderless flex)))))) (use-package marginalia :ensure t :config (marginalia-mode))
7.7. Search everything (consult
)
Preview files, and search open buffers, recently opened files, bookmarks, … all in one place
(use-package consult :ensure t :bind (("M-g o" . 'consult-recent-file) ("M-g s" . 'consult-line-multi) ("M-g m" . 'consult-man)) :config (recentf-mode))
7.8. Icons with dired
integration (nerd-icons
)
(use-package nerd-icons :ensure t) (use-package nerd-icons-dired :ensure t :hook (dired-mode . nerd-icons-dired-mode))
7.9. Custom modeline (doom-modeline
)
The default mode line is not bad; it does the job of showing the
status of the buffer. If you want something more, doom-modeline
might
be good. It is more full-featured than simple-modeline
(link), which
is more minimalistic and is what I was using before.
The only thing I change from Doom's modeline is the right bar that it has, I find it annoying, so I remove it.
(use-package doom-modeline :ensure t :hook (after-init . doom-modeline-mode) :config (setq doom-modeline-bar-width 0) (column-number-mode 1))
Another interesting alternative is mini-echo-mode
(link), which
collapses the modeline and minibuffer into a single line.
7.10. Show possible key bindings (which-key
)
which-key
implements a menu that appears after a certain amount of
time when sa half-pressed keybinding is entered, showing all the
possible routes or functions you can perform from there.
(use-package which-key :ensure t :hook (after-init . which-key-mode))
7.11. Fold code (yafolding
)
Being able to fold regions of code is a feature that is quintessential
for any modern source code editor. yafolding
figures out the fold
regions through indentation, so it is language agnostic. I do not
customise the key bindings or any other setting – they are perfect
right out of the box for me. Below I leave the default key bindings:
C-RET |
Folds the current scope |
C-S-RET |
Folds the parent |
C-M-RET |
Folds the entire buffer |
(use-package yafolding :ensure t :hook (prog-mode . yafolding-mode))
7.12. Transpose windows (transpose-frame
)
I don't like the name of this package, but it works.
(use-package transpose-frame :bind ("M-g t w" . transpose-frame))
7.13. Switch case of identifiers (string-inflection
)
(use-package string-inflection :bind ("M-s M-s" . string-inflection-all-cycle))
7.14. Transpose lines
(use-package move-text :config (move-text-default-bindings))