I have been using Emacs since I was a late teen (I am now in my forties), but I have mostly used Emacs as a plain text editor for much of this time. My customisation of Emacs was thus limited to a handful of snippets cribbed from the internet. I didn’t use many packages or see the need for a package repository to pull them from.
Only very recently (end 2023) did I start to see the true power of Emacs Lisp, and the draw of environments like Org Mode and Magit. And as I’ve explored these things, my Emacs configuration has correspondingly started to grow. This file collects all these ideas in one place. The structure of this file — which is an Org Mode file that exports to actual configuration files — was very inspired by a much richer variant by Prot.
Please write to me if you need any help with how it’s used or if you’d like to suggest any improvements.
There are some things (mostly pertinent to the base UI) that need to be set really early in Emacs’ startup. This is so that the UI doesn’t first show up uncustomised, and then “flash” as it redraws based on any later UI customisation (such as a change of theme).
This early initialisation configuration goes into a handily-named file
called early-init.el.
Depending on your system, there might be some default configuration
shared by other Emacs users in a file called default.el. To ensure
our Emacs behaves consistently everywhere, we ignore this and start
from a blank slate.
(setq inhibit-default-init t)There are a few elements like a graphical menu and a scroll-bar that are useful for beginners when first getting acquainted with Emacs. But as you get more experienced navigating the app with a keyboard, they get less useful. As a more advanced user, I remove them from the UI to let me focus more on the content.
(menu-bar-mode -1)
(tool-bar-mode -1)
(scroll-bar-mode -1)In the snippet above, -1 is convention for “don’t load” the
corresponding mode.
Emacs tends to start with a default (white) colour when first loaded, which is then refreshed later as a custom theme is loaded. We avoid this by hard-coding the background colour to the same value as a known custom theme. (I do not know how to make it dynamic for now, but this is not a problem as I rarely change my base theme.)
(setq default-frame-alist
;; bg-main from the modus-vivendi-tinted theme is hard-coded below
(append default-frame-alist '((background-color . "#0d0e1c"))))Here, default-frame-alist is a list of key-value pairs that help set
attributes for newly created frames (what you’d call a window). We’re
extending the default by this background colour.
I generally use Emacs on macOS, and this needs me to tweak it a tiny bit to my liking. For now, I’ve hard-coded my configuration to assume I’m running on macOS, but I also use Linux from time to time and in that context will need to add a conditional.
Emacs has two primary modifier keys, the Control key (C) and the
Meta key (M). M is traditionally mapped to Alt on most
keyboards, but on a Mac, Command is so much more comfortable.
(setq-default mac-command-modifier 'meta);; put emacs-derived customisations into a separate file
(setq custom-file (expand-file-name "custom.el" user-emacs-directory))
(when (file-exists-p custom-file)
(load custom-file))
;; set environment variables
(setenv "LC_ALL" "C");; default to better frame titles
(setq-default frame-title-format
(concat "%b - emacs@" (system-name)))
;; remove splash screen on start-up
(setq inhibit-startup-screen t)
;; hide scratch message on start-up
(setq-default initial-scratch-message "")
;; default to text-mode
(setq-default initial-major-mode 'text-mode)
(setq-default default-major-mode 'text-mode)What follows is simply the remainder of my existing configuration. I will break it out into sections and document it better in time.
;; copy selected text
(setq-default mouse-drag-copy-region t)
;; enable column number mode
(setq-default column-number-mode t)
;; enable visual feedback on selections
(setq-default transient-mark-mode t)
;; show the boundaries of the file
(setq-default indicate-buffer-boundaries 'right)
;; split buffers horizontally when opening multiple files
;; (setq-default split-width-threshold 0)
;; don't require two spaces after full stops to define sentences
(setq-default sentence-end-double-space nil)
;; show trailing spaces and empty lines
(setq-default show-trailing-whitespace t)
(setq-default indicate-empty-lines t)
;; enable up- and down-casing
(put 'downcase-region 'disabled nil)
(put 'upcase-region 'disabled nil)
;; prevent extraneous tabs and use 2 spaces
(setq-default indent-tabs-mode nil)
(setq-default tab-width 2)
;; highlight matching pairs of parentheses
(setq-default show-paren-delay 0)
(show-paren-mode)
;; set default indentation for different languages
(setq c-default-style "bsd"
c-basic-offset 2)
(setq sgml-basic-offset 2)
;; turn on interactive do
(ido-mode t)
(setq-default ido-enable-flex-matching t)
(setq-default ido-everywhere t)
;; enable flyspell-mode with an appropriate dictionary
(add-hook 'text-mode-hook 'flyspell-mode)
(setq ispell-dictionary "british")
;; setup ediff to have a neater layout
(setq ediff-split-window-function 'split-window-horizontally)
(setq ediff-window-setup-function 'ediff-setup-windows-plain);; load emacs' package system and add melpa repository
(require 'package)
(add-to-list 'package-archives
'("melpa" . "https://melpa.org/packages/") t)
(package-initialize)
(unless package-archive-contents
(package-refresh-contents))What follows is simply the remainder of my existing configuration. I will break it out into sections and document it better in time.
;; configure useful packages with use-package
(use-package magit :ensure t)
(use-package unfill :ensure t)
(use-package smex :ensure t)
(use-package go-mode :ensure t)
(use-package julia-mode :ensure t)
(use-package php-mode :ensure t)
(use-package markdown-mode :ensure t)
(use-package yaml-mode :ensure t)
(use-package graphviz-dot-mode :ensure t)
(defun theme-custom-faces ()
(modus-themes-with-colors
(custom-set-faces
;; Add "padding" to the mode lines
`(mode-line ((,c :box (:line-width 3 :color ,bg-mode-line-active))))
`(mode-line-inactive ((,c :box (:line-width 3 :color ,bg-mode-line-inactive)))))))
(use-package modus-themes
:ensure t
:config
(setq modus-themes-to-toggle '(modus-operandi-tinted modus-vivendi-tinted)
modus-themes-bold-constructs t
modus-themes-italic-constructs t
modus-themes-org-blocks 'gray-background)
(setq modus-themes-common-palette-overrides
'((bg-mode-line-active bg-blue-subtle)
(fg-mode-line-active fg-main)
(border-mode-line-active bg-blue-subtle)))
(modus-themes-load-theme 'modus-vivendi-tinted)
(define-key global-map (kbd "<f5>") #'modus-themes-toggle))
(add-hook 'modus-themes-after-load-theme-hook #'theme-custom-faces)
(setq org-edit-src-content-indentation 0)
(global-set-key (kbd "C-c a") 'org-agenda)
;; consider https://github.com/minad/org-modern
(use-package org-bullets
:ensure t
:config
(add-hook 'org-mode-hook (lambda () (org-bullets-mode 1))))
(setq org-agenda-files '("~/Notes/todo.org"))
;; setup corfu
(use-package corfu
:ensure t
:custom
(corfu-cycle t)
(corfu-separator ?\s)
(corfu-scroll-margin 5)
:init
(global-corfu-mode))
(use-package emacs
:init
(setq completion-cycle-threshold 3)
(setq tab-always-indent 'complete))
;; setup tree-sitter
(use-package tree-sitter
:ensure t
:config
(global-tree-sitter-mode)
(add-hook 'tree-sitter-after-on-hook #'tree-sitter-hl-mode))
(use-package tree-sitter-langs
:ensure t
:after tree-sitter)
;; configure a development environment for python
(use-package python
:ensure t
:hook ((python-mode . eglot-ensure)
(python-mode . tree-sitter-hl-mode)))
(use-package mastodon
:ensure t
:config
(setq mastodon-instance-url "https://hachyderm.io/"
mastodon-active-user "harish")
)
(use-package gptel
:ensure t
;; :config
;; (setq mastodon-instance-url "https://hachyderm.io/"
;; mastodon-active-user "harish")
)
;; (add-hook 'after-init-hook 'global-company-mode)
;; enable smex
(global-set-key (kbd "M-x") 'smex)
(global-set-key (kbd "M-X") 'smex-major-mode-commands)
(global-set-key (kbd "C-c C-c M-x") 'execute-extended-command)
;; turn on auto-fill mode for LaTeX files
(add-hook 'tex-mode-hook 'turn-on-auto-fill t)
;; turn on YAML mode for YAML files
(add-to-list 'auto-mode-alist '("\\.yml\\'" . yaml-mode))
(add-to-list 'auto-mode-alist '("\\.yaml\\'" . yaml-mode))
;; turn on octave mode for M files
(add-to-list 'auto-mode-alist '("\\.m\\'" . octave-mode))The minibuffer is the small interface at the bottom of the Emacs window where you can enter commands, input parameters, see results of these commands and so on. The internet suggests that with the following packages, it will be much more functional.
- vertico
- marginalia
- orderless
- consult
- embark
- embark-consult
- wgrep
- savehist
- recentf
At the moment I only use interactive-do, which is awesome but also like 90 years old.
Core settings and early initialisation Fetch necessary packages Broad UI customisation
These are specific to my needs, and are likely not useful for other
people. They are prefixed with my initials, hn-.
(defun hn-journal-todo (start-date end-date &optional prefix)
"Generate a todo list for journal entries from START-DATE to END-DATE with an optional PREFIX."
(interactive
(list
(read-string "Enter start date (YYYY-MM-DD): ")
(read-string "Enter end date (YYYY-MM-DD): ")
(read-string "Enter prefix: " "** Write entry for ")))
(let* ((start-time (date-to-time start-date))
(end-time (date-to-time end-date))
(one-day (seconds-to-time 86400)) ; 24 hours * 60 minutes * 60 seconds
(current-time start-time))
(while (time-less-p current-time (time-add end-time one-day))
(let ((entry-date (format-time-string "%A %d-%m-%Y" current-time)))
(insert (format "%s%s\n" (or prefix "** Write entry for ") entry-date)))
(setq current-time (time-add current-time one-day)))))