2023-12-08
This is a short walkthrough for how I configure emacs. Generally speaking, this is a very minimal (but powerful!) config; there are far more involved frameworks out there such as doom if you would like to try those, although I would highly recommend rolling your own configuration first.
The configuration is split into three files in ~/.config/emacs/
: early-init.el
, init.el
, and config.org
.
We add a garbage collection hack, which cuts startup time quite a bit.
(setq gc-cons-threshold 402653184
gc-cons-percentage 0.6)
(add-hook 'emacs-startup-hook
(lambda () (setq gc-cons-threshold 16777216
gc-cons-percentage 0.1)))
Additionally, we disable loading packages for now.
(setq package-enable-at-startup nil)
Here we simply load config.org
if it is available.
(let ((config (concat user-emacs-directory "config.org")))
(when (file-readable-p config)
(org-babel-load-file config)))
This file is an org document. Org is a very powerful tool, and you can use it to document your config in a structured way. Just be sure to wrap all of the code blocks below like this:
#+BEGIN_SRC emacs-lisp
;; code goes here...
#+END_SRC
This way, org-babel-load-file
knows to evaluate your code.
Now we begin the actual configuration.
First, let's disable backup files. These pollute directories and are generally annoying.
(setq make-backup-files nil
auto-save-default nil)
Same goes for lock files.
(setq create-lockfiles nil)
I like to have instant keystroke feedback in the echo area. For some reason, this value cannot be zero, so we will set it to a very low value instead.
(setq echo-keystrokes 0.001)
Disable the bell.
(setq ring-bell-function 'ignore)
Allow yes/no prompts to accept single character input.
(fset 'yes-or-no-p 'y-or-n-p)
Fix some tiling window manager issues.
(setq frame-resize-pixelwise t)
Blink the cursor.
(blink-cursor-mode)
Show lines and columns in the modeline.
(line-number-mode)
(column-number-mode)
Configure line wrapping for code.
For general writing, it may be more comfortable to enable visual-line-mode
.
(set-default 'truncate-lines t)
Instantly indicate matching parentheses.
(setq show-paren-delay 0)
(show-paren-mode)
Enable relative line numbers.
(global-display-line-numbers-mode)
(setq display-line-numbers-type 'relative)
Some sane scrolling settings.
(setq mouse-wheel-scroll-amount '(2 ((shift) . 1))
mouse-wheel-progressive-speed nil
mouse-wheel-follow-mouse t
redisplay-dont-pause t
scroll-margin 1
scroll-step 1
scroll-conservatively 10000
scroll-preserve-screen-position 1
hscroll-margin 3
hscroll-step 1)
Inhibit the emacs startup screen.
(setq inhibit-splash-screen t
inhibit-startup-message t)
Set a custom scratch buffer. I prefer to see the scratch buffer upon startup rather than a dashboard. This message can be anything!
(setq initial-scratch-message
(concat ";; -- GNU EMACS --" "\n"
";;" "\n"
";; Q@Q" "\n"
";; @@@" "\n"
";; Q@@@@QBNMRD" "\n"
";; wB@@#" "\n"
";; gQ@Q" "\n"
";; Q@@@@@Q" "\n"
";; @@@@Q" "\n"
";; @@@@Q" "\n"
";; Q@@@QQ#NNggg" "\n"
";; #Q@@Q" "\n"
";;" "\n"
";; Version " (number-to-string emacs-major-version) "." (number-to-string emacs-minor-version) "\n"
";; Initialized " (format-time-string "%a %b %d %H:%M:%S %Z %Y" after-init-time) "\n"
";;" "\n"
";; This buffer is for text that is not saved, and for Lisp evaluation." "\n"
";; To create a file, visit it with C-x C-f and enter text in its buffer." "\n"
"\n"
"(recentf-open-files)"))
Key chords allow you to play emacs as though it were an instrument. I've found that it is always more efficient to bind common tasks to a chord than to type a command.
Let's first set a binding to kill emacs completely, including the daemon.
(global-set-key (kbd "C-x M-c") 'kill-emacs)
Tab is, in many modes, bound to indent-for-tab-command
by default.
While this is super useful, to insert a literal tab character (\t
), you would normally have to type C-q tab
.
Let's make it possible in one keypress.
(global-set-key (kbd "C-<tab>") (lambda () (interactive) (insert "\t")))
There are numerous default keybindings in emacs; every key on the keyboard either is bound to a function or serves as a prefix for more bindings.
Unfortunately, that doesn't leave many open chords for our own purposes, and I personally do not like to mess with the defaults too much.
Thus, I like to establish one universal prefix, or leader, for most of my bindings, within which I have plenty of new patterns to choose from.
Over time I have settled on the ergonomic C-s
.
C-s
is normally bound to isearch-forward
, but we can break the rules a single time and remap this to C-s C-s
.
Let's create some helper functions.
(defconst config-keybind-prefix
"C-s"
"Keybind prefix to avoid clashes with existing commands.
`isearch-forward' is remapped to C-s C-s.")
(keymap-global-unset config-keybind-prefix)
(defun config-bind (key command)
"globally bind `command' to `config-keybind-prefix' + `key'"
(global-set-key (kbd (concat config-keybind-prefix " " key)) command))
(config-bind config-keybind-prefix 'isearch-forward)
Quickly open the configuration.
(config-bind "1" (lambda () (interactive)
(find-file (concat user-emacs-directory "config.org"))))
Some more bindings for quick navigation.
(config-bind "p" 'beginning-of-buffer)
(config-bind "n" 'end-of-buffer)
Some shortcuts for dealing with windows and buffers.
(config-bind "k" 'kill-buffer-and-window)
(config-bind "C-b" 'ibuffer)
Out of the box emacs looks... charmingly dated. While some prefer this appearance, there are many settings which can modernize your favorite editor in this respect.
Hide all of the window features. You shouldn't be using these anyway.
(menu-bar-mode -1)
(tool-bar-mode -1)
(scroll-bar-mode -1)
Minimize the fringe.
(set-fringe-mode 1)
Recently emacs was given true frame background transparency.
Toggle it with C-s t
.
(defvar frame-background-transparent-value
90
"The frame's alpha value when transparency is toggled in `toggle-background-transparency'")
(defconst frame-background-opaque-value
100
"The frame's alpha value when transparency is not toggled in `toggle-background-transparency'")
(defun toggle-background-transparency ()
"Toggle current frame's `alpha-background' parameter between `frame-background-transparent-value' and `frame-background-opaque-value'."
(interactive)
(set-frame-parameter nil 'alpha-background
(if (eq frame-background-opaque-value
(frame-parameter nil 'alpha-background))
frame-background-transparent-value
frame-background-opaque-value)))
(set-frame-parameter nil 'alpha-background 100)
(add-to-list 'default-frame-alist '(alpha-background . 100))
(config-bind "t" 'toggle-background-transparency)
Themes will dramatically change the look of the editor. There are a few builtin themes, but later on I will show you a package which offers far more options.
Establish a theme directory.
(let ((themes-dir (concat user-emacs-directory "themes/")))
(make-directory themes-dir t)
(add-to-list 'custom-theme-load-path themes-dir))
Some initialization settings.
(setq init-font "Fira Code-25"
init-theme 'doom-gruvbox)
Some helper functions.
(defun font-exists-p (font)
"check if font exists"
(if (null (x-list-fonts font))
nil
t))
(defun custom-theme-exists-p (theme)
"check if theme exists"
(let ((file (locate-file (concat (symbol-name theme) "-theme.el")
(custom-theme--load-path)
'("" "c"))))
file))
Apply the settings at the right time.
(setq first-frame t)
(defun on-new-frame (&rest args)
(interactive)
(when (and first-frame window-system)
(when (font-exists-p init-font)
(set-face-font 'default init-font))
(when (custom-theme-exists-p init-theme)
(load-theme init-theme t))
(setq first-frame nil)))
(advice-add 'server-create-window-system-frame :after 'on-new-frame)
(advice-add 'server-create-tty-frame :after 'on-new-frame)
Make a special function to reset theme settings before loading a new one.
(defun reset-load-theme (theme)
"Wipe theme settings and load new. If `theme' is nil, just reset"
(mapcar #'disable-theme custom-enabled-themes)
(if theme
(progn
(load-theme theme)
(message "loaded theme %s" theme))
(message "reset all themes")))
Create an interactive wrapper.
(defun pick-theme ()
"select theme from `custom-available-themes'"
(interactive)
(reset-load-theme
(intern-soft
(completing-read "Pick Theme " (custom-available-themes)))))
These packages are builtin to emacs.
Recentf is great for keeping track of your recently edited files.
(setq recentf-max-menu-items 25
recentf-max-saved-items 25)
(recentf-mode 1)
(run-at-time 60 300 'recentf-save-list)
Navigate diagnostics using flymake.
(config-bind "C-p" 'flymake-goto-prev-error)
(config-bind "C-n" 'flymake-goto-next-error)
(config-bind "d" 'flymake-show-buffer-diagnostics)
Eglot handles lsp functionality.
(config-bind "e" 'eglot)
(config-bind "C-e" 'eglot-shutdown)
I disable documentation in the echo area and inlay hints.
(setq eldoc-echo-area-use-multiline-p nil
eglot-ignored-server-capabilities '(:inlayHintProvider))
Some basic settings.
(setq org-src-fontify-natively t
org-src-tab-acts-natively t
org-startup-indented t
org-src-window-setup 'other-window)
Easily insert emacs-lisp code using C-c C-, e l
.
(add-to-list 'org-structure-template-alist '("el" . "SRC emacs-lisp"))
These packages are not builtin and are instead fetched by the package manager.
Initialize.
(require 'package)
(package-initialize)
Add the melpa package repository.
(add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t)
Require use-package
.
This macro is now integrated into emacs-29, so I will finally use it.
(require 'use-package)
Vertical selection framework. This adds another dimension to the minibuffer and makes it far easier to search through items.
(use-package vertico
:ensure t
:init
(vertico-mode))
Add annotations in the minibuffer, providing documentation or other relevant information to the items.
(use-package marginalia
:ensure t
:bind (:map minibuffer-local-map
("M-A" . marginalia-cycle))
:init
(marginalia-mode))
Orderless provides better completion algorithms.
(use-package orderless
:ensure t
:custom
(completion-styles '(orderless basic))
(completion-category-overrides '((file (styles basic partial-completion)))))
Consult adds incredibly useful completing read functions.
There are even more than I've bound here; try typing M-x consult-
!
(use-package consult
:ensure t
:custom
(consult-async-min-input 1)
(consult-async-input-throttle 0.001)
(consult-async-refresh-delay 0.001)
:config
(config-bind "b" 'consult-buffer)
(config-bind "r" 'consult-register)
(config-bind "l" 'consult-goto-line)
(config-bind "s" 'consult-line)
(config-bind "i" 'consult-imenu)
(config-bind "f" 'consult-flymake)
(config-bind "m" 'consult-bookmark)
(config-bind "g p" 'consult-grep)
(config-bind "g g" 'consult-git-grep))
Completion popups.
Initialize a completion with M-<tab>
, or completion-at-point
.
We also enable documentation popups.
I personally dislike auto-completion, but you may enable it with (setq corfu-auto t)
.
(use-package corfu
:ensure t
:custom
(corfu-quit-no-match t)
(corfu-popupinfo-delay 0)
:config
(config-bind "c" 'global-corfu-mode)
(global-corfu-mode)
(corfu-popupinfo-mode))
After some time, reveal possible conclusions to key-chords in the minibuffer.
(use-package which-key
:ensure t
:config
(which-key-mode))
Snippets!
(use-package yasnippet
:ensure t
:config
(config-bind "y" 'yas-insert-snippet)
(yas-global-mode))
(use-package yasnippet-snippets
:ensure t)
Magit is simply the best git interface. "v" is for version control.
(use-package magit
:ensure t
:config
(config-bind "v" 'magit-status))
Show git changes to the left of the line numbers.
(use-package git-gutter
:ensure t
:config
(global-git-gutter-mode))
Some nice themes to explore; use pick-theme
.
(use-package doom-themes
:ensure t)
Initialize frame if not running in daemon mode.
(unless (daemonp)
(on-new-frame))
I hope this provides a good starting point to your emacs journey! Happy hacking!