For now, org-babel-tangle
is working alright but auto-tangling on
save would be good
This is my handwritten Emacs configuration file that is stolen from
based on the things that I like from Doom Emacs (and other personal
set-ups from folk such as daviwil, Howard Abrams and Prot), without
using the full lazy loading all singing, all dancing set up that
doom
provides. I really like what doom
has to offer but it
really is a lot of code under the hood.
I’m definitely not saying I think I can make something that is better, but I
do think that I can make something that fits my needs without needing the full
generality of doom
. That should be a lot easier to reason about and learning
more about how all this works is a large part of why I’m doing it.
This file can be tangled out to the corresponding real
files by calling
org-babel-tangle
inside of this buffer.
Make sure that the tangled output files have the correct headers in them
;;; early-init.el -*- lexical-binding: t; -*-
;;; init.el -*- lexical-binding: t; -*-
Emacs 27.1 introduced early-init.el
which runs before init.el
, before
package initialisation takes place and before site files are loaded. I’m
always using a version of Emacs that is at least as new as this so it makes
sense to use it to help with improving start-up where possible.
See the manual page for the init file for more details on how all of this works.
A big contributor to startup times is garbage collection. So up the threshold to
temporarily prevent it from running, then reset it later by enabling gcmh-mode
(not resetting it will cause stuttering / freezes).
(setq gc-cons-threshold most-positive-fixnum)
In Emacs 27+, package initialisation occurs before user-init-file
is loaded,
but after early-init-file
. I’m using straight.el
so prevent Emacs from doing
it early!
(setq package-enable-at-startup nil)
In non-interactive sessions, prioritise non-byte-compiled source files to
prevent the use of stale byte-code. Otherwise, it saves a little IO time to skip
the mtime
checks on every *.elc
file.
(setq load-prefer-newer noninteractive)
file-name-handler-alist
is consulted on each call to require
, load
and
various path/io functions. You get a minor speed up by un-setting this but you
need to restore it later because it is needed for handling encrypted or
compressed files (among other things).
Premature re-displays can substantially affect startup times and produce ugly flashes of un-styled Emacs.
Site files tend to use load-file
, which emits “Loading X…” messages in the
echo area, which in turn triggers a re-display. Re-displays can have a big
effect on startup times and in this case happens so early that Emacs may flash
white while starting up. We only want this on start-up though so we need to
unload the advice before init.el
is loaded.
(unless (or (daemonp)
noninteractive
init-file-debug)
(let ((original-file-name-handler-alist file-name-handler-alist))
(setq-default file-name-handler-alist nil)
(defun sminez/reset-file-handler-alist-h ()
(setq file-name-handler-alist
;; Merge instead of overwrite because there may have been changes to
;; `file-name-handler-alist' since startup we want to preserve.
(delete-dups (append file-name-handler-alist
original-file-name-handler-alist))))
(add-hook 'emacs-startup-hook #'sminez/reset-file-handler-alist-h 101))
(setq-default inhibit-redisplay t
inhibit-message t)
(add-hook 'window-setup-hook
(lambda ()
(setq-default inhibit-redisplay nil
inhibit-message nil)
(redisplay)))
(define-advice load-file (:override (file) silence)
(load file nil 'nomessage))
(define-advice startup--load-user-init-file (:before (&rest _) sminez-init)
(advice-remove #'load-file #'load-file@silence)))
I’m not massively worried about start-up time for now, but getting things set up for tracking it will probably pay off in the long run when I inevitably end up with something causing things to grind to a halt…
(defun sminez/display-startup-time ()
(message
"Emacs loaded in %s."
(format "%.2f seconds"
(float-time (time-subtract after-init-time before-init-time)))))
(add-hook 'emacs-startup-hook #'sminez/display-startup-time)
Contrary to what many Emacs users have in their configuration files, you don’t
need more than this to make UTF-8 the default coding system. That said,
set-language-environment
also sets defualt-input-method
which we don’t
want.
(set-language-environment "UTF-8")
(setq default-input-method nil)
At this point, we’re done with the start-up tweaks and it’s time to actually begin loading in the configuration!
(setq user-emacs-directory (file-name-directory load-file-name))
(add-to-list 'load-path (concat (file-name-directory load-file-name) "lib"))
Remember these variables’ initial values, so we can safely reset them at a later time, or consult them without fear of contamination.
(dolist (var '(exec-path load-path process-environment))
(unless (get var 'initial-value)
(put var 'initial-value (default-value var))))
The core shared library code lives in ~/.emacs.d/lib/sminez-lib.el
. For now,
there isn’t much there but lets load it in anyway so that once it does start
to flesh out, it’s always available.
(require 'sminez-lib)
There are a few things that are helpful to define at this top level so that they can be re-used and referenced everywhere else. For the most part these are things about the specific system that we are running on and (typically) not things that I change all that much.
(defconst NATIVECOMP (if (fboundp 'native-comp-available-p)
(native-comp-available-p)))
(defconst EMACS28+ (> emacs-major-version 27))
(defconst EMACS29+ (> emacs-major-version 28))
(defconst IS-MAC (eq system-type 'darwin))
(defconst IS-LINUX (eq system-type 'gnu/linux))
(defconst sminez-lib-dir (concat user-emacs-directory "lib/")
"Directory containing core library functionality.")
(defconst sminez-local-dir (concat user-emacs-directory ".local/")
"Root directory for local storage.
Use this as a storage location for this system's installation of Emacs.
These files should not be shared across systems.")
(defconst sminez-etc-dir (concat sminez-local-dir "etc/")
"Directory for non-volatile local storage.
Use this for files that don't change much, like server binaries, external
dependencies or long-term shared data.")
(defconst sminez-cache-dir (concat sminez-local-dir "cache/")
"Directory for volatile local storage.
Use this for files that change often, like cache files.")
(defconst sminez-autoloads-file
(concat sminez-local-dir "autoloads." emacs-version ".el")
"Where `sminez-reload-lib-autoloads' stores its core autoloads.
This file is responsible for informing Emacs where to find all of the
autoloaded core functions (in lib/autoload/*.el).")
;; TODO: This needs sorting so that the env file can be generated correctly
(defconst sminez-env-file (concat sminez-local-dir "env")
"The location of your envvar file, generated by `doom env`.
This file contains environment variables scraped from the shell environment,
which is loaded at startup (if it exists). This is helpful if Emacs can't
easily be launched from the correct shell session (looking at you MacOS).")
There is a package called no-littering that helps with keeping your .emacs.d
directory free of auto-generated files and other crud. There is a comment in the
source code of doom
that hints at it being a bit too opinionated about how it
goes about that so I just take the setup that doom
uses as I’m happy with how
that works.
(setq async-byte-compile-log-file (concat sminez-etc-dir "async-bytecomp.log")
custom-file (concat user-emacs-directory "custom.el")
desktop-dirname (concat sminez-etc-dir "desktop")
desktop-base-file-name "autosave"
desktop-base-lock-name "autosave-lock"
pcache-directory (concat sminez-cache-dir "pcache/")
request-storage-directory (concat sminez-cache-dir "request")
shared-game-score-directory (concat sminez-etc-dir "shared-game-score/"))
FIXME: This defadvice!
macro is not ported over yet
(defadvice! sminez--write-to-sane-paths-a (fn &rest args)
"Write 3rd party files to `sminez-etc-dir' to keep `user-emacs-directory' clean.
Also writes `put' calls for saved safe-local-variables to `custom-file' instead
of `user-init-file' (which `en/disable-command' in novice.el.gz is hardcoded to
do)."
:around #'en/disable-command
:around #'locate-user-emacs-file
(let ((user-emacs-directory sminez-etc-dir)
(user-init-file custom-file))
(apply fn args)))
Emacs is essentially one huge security vulnerability, what with all the dependencies it pulls in from all corners of the globe. While it’s probably fine, let’s try to at least be a little more discerning than the default.
(setq gnutls-verify-error (and (fboundp 'gnutls-available-p)
(gnutls-available-p)
(not (getenv-internal "INSECURE")))
gnutls-algorithm-priority
(when (boundp 'libgnutls-version)
(concat "SECURE128:+SECURE192:-VERS-ALL"
(if (>= libgnutls-version 30605)
":+VERS-TLS1.3")
":+VERS-TLS1.2"))
;; `gnutls-min-prime-bits' is set based on recommendations from
;; https://www.keylength.com/en/4/
gnutls-min-prime-bits 3072
tls-checktrust gnutls-verify-error
;; Emacs is built with `gnutls' by default, so `tls-program' would not be
;; used in that case. Otherwise, people have reasons to not go with
;; `gnutls', we use `openssl' instead. For more details, see
;; https://redd.it/8sykl1
tls-program '("openssl s_client -connect %h:%p -CAfile %t -nbio -no_ssl3 -no_tls1 -no_tls1_1 -ign_eof"
"gnutls-cli -p %p --dh-bits=3072 --ocsp --x509cafile=%t \
--strict-tofu --priority='SECURE192:+SECURE128:-VERS-ALL:+VERS-TLS1.2:+VERS-TLS1.3' %h"
;; compatibility fallbacks
"gnutls-cli -p %p %h"))
Oh, and Emacs stores =authinfo in $HOME
and in plain-text. Let’s…not?
This file stores usernames, passwords etc…
(setq auth-sources (list (concat sminez-etc-dir "authinfo.gpg")
"~/.authinfo.gpg"))
There are a tonne of dials and knobs to play around with when it comes to
configuring the behaviour of a lot of the built in functionality in Emacs. If
I’m honest, I probably don’t need all of these but given that I like the
behaviour of doom
so far I’m just lifting what they set and documenting
what each setting is for. That way, if I notice anything funky in the future
I can hunt it down and see what is up.
A second, case-insensitive pass over auto-mode-alist
is a waste of time, and
indicates that things have been configured incorrectly (don’t rely on case
insensitivity for file names).
(setq auto-mode-case-fold nil)
Disable bidirectional text scanning for a modest performance boost. I’ve set
this to `nil’ in the past, but the bidi-display-reordering
’s docs say that
is an undefined state and suggest this to be just as good:
Disabling the BPA makes re-display faster, but might produce incorrect display
reordering of bidirectional text with embedded parentheses and other bracket
characters whose paired-bracket
Unicode property is non-nil.
(setq-default bidi-display-reordering 'left-to-right
bidi-paragraph-direction 'left-to-right)
(setq bidi-inhibit-bpa t)
We can reduce rendering / line scan work for Emacs by not rendering cursors or
regions in non-focused windows. It’s also possible to opt-in to faster scrolling
over unfontified
regions so long as you don’t mind brief moments of wonky
syntax highlighting (which should self correct once scrolling stops).
(setq-default cursor-in-non-selected-windows nil)
(setq highlight-nonselected-windows nil
fast-but-imprecise-scrolling t)
Resizing the Emacs frame can be a pretty expensive part of changing fonts. Preventing this halves startup times, particularly when using fonts that are larger than the system default (which would cause the frame to resize).
(setq frame-inhibit-implied-resize t)
Emacs “updates” its UI more often than it really needs to. Slowing it down slightly actually makes a noticeable difference.
(setq idle-update-delay 1.0) ; default is 0.5
The garbage collector introduces annoying pauses and stuttering into the Emacs
experience, but you can use gcmh to keep it at bay while you’re using Emacs,
and then invoke it when things are idle. That said, if the idle delay is too
long, we run the risk of runaway memory usage in busy sessions. If it’s too low,
then we may as well not be using gcmh
at all.
(setq gcmh-idle-delay 'auto ; default is 15s
gcmh-auto-idle-delay-factor 10
gcmh-high-cons-threshold (* 16 1024 1024)) ; 16mb
(add-hook 'emacs-startup-hook #'gcmh-mode)
Increase how much is read from processes in a single chunk (default is 4kb). This is further increased elsewhere, where needed (like our LSP module).
(setq read-process-output-max (* 64 1024)) ; 64kb
There are quite a few things that spam debug output and message
which
aren’t all that helpful: lets just shut them up.
Setting the initial scratch buffer major mode to fundamental
takes seconds
off startup time, rather than, say, org-mode
or text-mode
, which pull in a
ton of packages.
(setq ad-redefinition-action 'accept
debug-on-error init-file-debug
jka-compr-verbose init-file-debug
inhibit-startup-screen t
inhibit-startup-echo-area-message user-login-name
inhibit-default-init t
initial-major-mode 'fundamental-mode
initial-scratch-message nil)
(unless (daemonp)
(advice-add #'display-startup-echo-area-message :override #'ignore))
y/n
is more than sufficient thank you very much.
(fset 'yes-or-no-p 'y-or-n-p)
At work I have a Mac-Book and for my personal set up I use Linux. Some things
need to be modified a bit depending on which machine I’m on so just check the
system type to determine which one we are on.
(The IS-LINUX
and IS-MAC
constants are defined in early-init.el
)
(cond (IS-LINUX (setq user-full-name "Innes Anderson-Morrison"
user-mail-address "[email protected]"
command-line-ns-option-alist nil))
(IS-MAC (setq user-full-name "Innes Anderson-Morrison"
user-mail-address "[email protected]"
command-line-x-option-alist nil)))
Out of the box, the Emacs UI is pretty messy. Lets sort that.
(push '(menu-bar-lines . 0) default-frame-alist)
(push '(tool-bar-lines . 0) default-frame-alist)
(push '(vertical-scroll-bars) default-frame-alist)
(setq display-line-numbers-mode 1)
(setq-default display-line-numbers-width 3
display-line-numbers-widen t)
(menu-bar-mode -1)
(tool-bar-mode -1)
(scroll-bar-mode -1)
(blink-cursor-mode -1)
(global-display-line-numbers-mode)
(global-hl-line-mode)
(column-number-mode)
(setq uniquify-buffer-name-style 'forward
ring-bell-function #'ignore
visible-bell nil
blink-matching-paren nil
x-stretch-cursor nil
hscroll-margin 2
hscroll-step 1
;; Emacs spends too much effort recentering the screen if you scroll the
;; cursor more than N lines past window edges (where N is the settings of
;; `scroll-conservatively'). This is especially slow in larger files
;; during large-scale scrolling commands. If kept over 100, the window is
;; never automatically recentered.
scroll-conservatively 101
scroll-margin 0
scroll-preserve-screen-position t
;; Reduce cursor lag by a tiny bit by not auto-adjusting `window-vscroll'
;; for tall lines.
auto-window-vscroll nil)
(setq indicate-buffer-boundaries nil
indicate-empty-lines nil)
(setq frame-title-format '("%b – Emacs")
icon-title-format frame-title-format)
;; Don't resize the frames in steps; it looks weird, especially in tiling window
;; managers, where it can leave unseemly gaps.
(setq frame-resize-pixelwise t)
;; But do not resize windows pixelwise, this can cause crashes in some cases
;; when resizing too many windows at once or rapidly.
(setq window-resize-pixelwise nil)
;; The native border "consumes" a pixel of the fringe on righter-most splits,
;; `window-divider' does not. Available since Emacs 25.1.
(setq window-divider-default-places t
window-divider-default-bottom-width 1
window-divider-default-right-width 1)
(add-hook 'emacs-startup-hook #'window-divider-mode)
(setq use-dialog-box nil)
(when (bound-and-true-p tooltip-mode)
(tooltip-mode -1))
(when IS-LINUX
(setq x-gtk-use-system-tooltips nil))
(setq split-width-threshold 160
split-height-threshold nil)
;; Allow for minibuffer-ception. Sometimes we need another minibuffer command
;; while we're in the minibuffer.
(setq enable-recursive-minibuffers t)
;; Show current key-sequence in minibuffer ala 'set showcmd' in vim. Any
;; feedback after typing is better UX than no feedback at all.
(setq echo-keystrokes 0.02)
;; Expand the minibuffer to fit multi-line text displayed in the echo-area. This
;; doesn't look too great with direnv, however...
(setq resize-mini-windows 'grow-only)
;; Typing yes/no is obnoxious when y/n will do
(advice-add #'yes-or-no-p :override #'y-or-n-p)
;; Try to keep the cursor out of the read-only portions of the minibuffer.
(setq minibuffer-prompt-properties '(read-only t intangible t cursor-intangible t face minibuffer-prompt))
(add-hook 'minibuffer-setup-hook #'cursor-intangible-mode)
(setq global-auto-revert-non-file-buffers t)
(global-auto-revert-mode 1)
(setq-default indent-tabs-mode nil
tab-width 4)
(setq confirm-nonexistent-file-or-buffer nil)
I really like the reproducible build nature of straight.el (especially compared
to the built in package.el
workflow). The README
for straight is huge and
probably something I should take a longer look at in the future. For now though,
I’m just following the example for bootstrapping straight.el
itself which
seems to be significantly simpler than what doom
does? (Not sure what doom’s
approach gets you over the one recommended by straight itself yet)
This is taken straight from the README
:
;; make sure that we have straght.el on this system
(defvar bootstrap-version)
(let ((bootstrap-file
(expand-file-name "straight/repos/straight.el/bootstrap.el" user-emacs-directory))
(bootstrap-version 5))
(unless (file-exists-p bootstrap-file)
(with-current-buffer
(url-retrieve-synchronously
"https://raw.githubusercontent.com/raxod502/straight.el/develop/install.el"
'silent 'inhibit-cookies)
(goto-char (point-max))
(eval-print-last-sexp)))
(load bootstrap-file nil 'nomessage))
;; For everything else, we'll be using `use-package` so bring that in now.
(straight-use-package 'use-package)
doom
has a number of helper macros for package management and loading that are
pretty convenient. I don’t want the full set up that doom
provides but I’m
bringing in some of those QOL improvements to keep things neat and as simple as
possible when configuring each package that I use.
Evaluate a body after a package (or combination of packages) have loaded.
This is a wrapper around eval-after-load
that:
- Suppresses warnings for disabled packages at compile-time
- No-ops for package that are disabled by the user (via
package!
) - Supports compound package statements (see below)
- Prevents eager expansion pulling in auto-loaded macros all at once
PACKAGE
is a symbol or list of symbols: these are package names, not modes,
functions or variables. Valid values are:
- An unquoted package symbol (the name of a package)
(after! helm BODY...)
- An unquoted list of package symbols (i.e. BODY is evaluated once both
magit
andgit-gutter
have loaded)(after! (magit git-gutter) BODY...)
- An unquoted, nested list of compound package lists, using any combination of
:or/:any
and:and/:all
:(after! (:or package-a package-b ...) BODY...)
(after! (:and package-a package-b ...) BODY...)
(after! (:and package-a (:or package-b package-c) ...) BODY...)
For list values, omitting :or/:any/:and/:all
implies :and/:all
.
(defmacro after! (package &rest body)
"Evaluate BODY after PACKAGE have loaded."
(declare (indent defun) (debug t))
(if (symbolp package)
;; Simple case: `after' a single package is loaded
;; (after! foo BODY...)
(list (if (or (not (bound-and-true-p byte-compile-current-file))
(require package nil 'noerror))
#'progn
#'with-no-warnings)
;; Avoid `with-eval-after-load' to prevent eager macro expansion
;; from pulling (or failing to pull) in autoloaded macros/packages.
`(eval-after-load ',package ',(macroexp-progn body)))
;; One of the following cases:
;; (after! (package-a package-b ...) BODY...)
;; (after! (:or package-a package-b ...) BODY...)
;; (after! (:and package-a package-b ...) BODY...)
;; (after! (:and package-a (:or package-b package-c) ...) BODY...)
(let ((p (car package)))
(cond ((memq p '(:or :any))
(macroexp-progn
(cl-loop for next in (cdr package)
collect `(after! ,next ,@body))))
((memq p '(:and :all))
(dolist (next (reverse (cdr package)) (car body))
(setq body `((after! ,next ,@body)))))
(`(after! (:and ,@package) ,@body))))))
Add a list of directories to load-path
. Paths are resolved relative to the
file where add-load-path!
is called.
(defmacro add-load-path! (&rest dirs)
"Add DIRS to `load-path', relative to the current file.
The current file is the file from which `add-to-load-path!' is used."
`(let ((default-directory ,(dir!))
file-name-handler-alist)
(dolist (dir (list ,@dirs))
(cl-pushnew (expand-file-name dir) load-path :test #'string=))))
In early-init.el
the GC limit was bumped as high as it can go but
now we need to actually sort it out going forward:
(use-package gcmh
:straight t
:config (gcmh-mode 1))
Before anything else, evil. I love the extensibility of Emacs but good god do I need the editor itself to be as close to Vim as possible.
(use-package undo-tree
:straight t
:init (global-undo-tree-mode 1))
(use-package evil
:straight t
:init
(setq evil-want-C-i-jump nil
evil-want-keybinding nil
evil-want-integration t
evil-undo-system 'undo-tree
evil-want-Y-yank-to-eol t
)
:config
(evil-mode 1)
(setq evil-search-module 'rg
evil-magic 'very-magic
evil-want-fine-undo t
evil-want-change-word-to-end t
evil-split-window-below t
evil-split-window-right t))
(use-package evil-escape
:straight t
:after evil
:init (setq-default evil-escape-key-sequence "fd")
:config (evil-escape-mode))
(use-package evil-collection
:straight t
:after evil
:config (evil-collection-init))
(use-package evil-nerd-commenter
:straight t)
keyboard-quit
is too much of a nuclear option. I want ESC/C-g
to
do-what-I-mean. It serves four purposes (in order):
- Quit active states; e.g. highlights, searches, snippets, iedit, multiple-cursors, recording macros, etc.
- Close popup windows remotely (if it is allowed to)
- Refresh buffer indicators, like git-gutter and flycheck
- Or fall back to
keyboard-quit
It should do these things incrementally (rather than all at once) and it
shouldn’t interfere with recording macros or the mini-buffer. This may require
you press ESC/C-g
a couple of times to reach keyboard-quit
but it’s much
more intuitive.
(defvar sminez-escape-hook nil
"A hook run when C-g is pressed (or ESC in normal mode, for evil users).
More specifically, when `sminez/escape' is pressed. If any hook returns non-nil,
all hooks after it are ignored.")
(defun sminez/escape (&optional interactive)
"Run `sminez-escape-hook'."
(interactive (list 'interactive))
(cond ((minibuffer-window-active-p (minibuffer-window))
;; quit the minibuffer if open.
(when interactive
(setq this-command 'abort-recursive-edit))
(abort-recursive-edit))
;; Run all escape hooks. If any returns non-nil, then stop there.
((run-hook-with-args-until-success 'sminez-escape-hook))
;; don't abort macros
((or defining-kbd-macro executing-kbd-macro) nil)
;; Back to the default
((unwind-protect (keyboard-quit)
(when interactive
(setq this-command 'keyboard-quit))))))
(global-set-key [remap keyboard-quit] #'sminez/escape)
(with-eval-after-load 'eldoc
(eldoc-add-command 'sminez/escape))
I love which-key. There’s probably a whole bunch that it can do that I’ve not even begun to look into but the fact that it can remind me what on earth I bound everything to without me needing to remember to register each binding is just a life saver.
(use-package which-key
:straight t
:config
(which-key-mode))
General is a package for providing nicer keybinding definitions with built in
support for evil-mode
and vim-like key combinations. There is a lot that it
can do out of the box, but the doom
keybinding system is built on top of
this with even more magic. From what I can tell, other than the improved
ergonomics of the map!
macro, it also does a whole heap of things to try to
lazy load bindings and make sure that things can be overwritten and tweaked
in a nicer way.
We’ll see if this ends up biting me in the long run but for now I’m just going
to stick with general
. I tried extracting the map!
macro out of doom
and
after an hour or so it was clear that it’s tied pretty deeply into the guts of
all of the module and package management stuff that I’m aiming to avoid.
(use-package general
:straight t
:init
(defalias 'def-key! #'general-def)
(defalias 'undef-key! #'general-unbind))
(after! general
(general-create-definer sminez/leader-def
:prefix "SPC")
(general-create-definer sminez/local-leader-def
:prefix "SPC m")
(sminez/leader-def
:keymaps 'normal
"SPC" '(execute-extended-command :wk "M-x")
"," '(consult-buffer :wk "switch buffer")
"." '(find-file :wk "find file")
";" '(evilnc-comment-or-uncomment-lines :wk "comment line")
;; SECTION: Buffers
"b" '(nil :wk "buffers")
"bb" '(consult-buffer :wk "switch buffer")
"bd" '(kill-this-buffer :wk "delete buffer")
"bi" '(ibuffer :wk "ibuffer")
;; SECTION: Files
"f" '(nil :wk "file")
"ff" '(find-file :wk "find file")
"fr" '(consult-recent-file :wk "recent files")
"fs" '(save-buffer :wk "save buffer")
;; SECTION: Search
"s" '(nil :wk "search")
"ss" '(consult-line :wk "search current buffer")
;; SECTION: Windows
"w" '(nil :wk "window")
"wc" '(delete-window :wk "close window")
"wh" '(evil-window-left :wk "window left")
"wj" '(evil-window-down :wk "window down")
"wk" '(evil-window-up :wk "window up")
"wl" '(evil-window-right :wk "window right")
"w/" '(evil-window-vsplit :wk "vertical split")
"w-" '(evil-window-split :wk "horizontal split")
)
(general-define-key
"C-SPC" 'company-complete-common)
)
(use-package savehist
:straight t
:custom (savehist-file (concat sminez-cache-dir "savehist"))
:config
(setq savehist-save-minibuffer-history t
savehist-autosave-interval nil ; save on kill only
savehist-additional-variables
'(kill-ring ; persist clipboard
register-alist ; persist macros
mark-ring global-mark-ring ; persist marks
search-ring regexp-search-ring) ; persist searches
history-length 25)
(savehist-mode 1))
(recentf-mode 1)
(setq recentf-max-menu-items 25
recentf-max-saved-items 25)
There are loads of different completion / search frameworks for Emacs (I’ve tried helm and ivy previously) but for now I’m settled on vertico. It’s pretty bare bones, integrates nicely with the built-in functionality of Emacs itself (as opposed to being a completely new framework) and also plays well with a number of other packages that follow the same design philosophy (see below).
As with a lot of these packages: see the README
in GitHub for a lot more
information (as and when I have time to go through it…)
(use-package vertico
:straight t
:init
(vertico-mode)
(setq vertico-cycle t
vertico-count 17))
(use-package emacs
:init
(setq enable-recursive-minibuffers t))
Orderless provides a really nice alternative for incremental filtering of
completion targets based on fragments of search patterns (it’s a little hard to
summarise without an example so see the README
for more details). At the
moment I’m pretty much just using the “out of the box” default settings and not
making that much use of the wider feature set: this is definitely one to read
more into and see what it can do…
(use-package orderless
:straight t
:init
(setq completion-styles '(orderless)
completion-category-defaults nil
completion-category-overrides '((file (styles partial-completion)))))
Marginalia provides nice help summaries in completion mini-buffers which make it
a lot easier to quickly hunt for things that you’ve not used for a while (or at
all) without needing to constantly stop and look up the docs. With that in mind,
remember to document your variables and functions! Trust me, you’ll thank
yourself later when you have to hunt for them.
There is some customisation available for marginalia
but I just use the
defaults for now as they give me all that I need.
(use-package marginalia
:straight t
:init
(marginalia-mode))
Consult provides some greatly enhanced versions of built in Emacs commands along with a whole heap of extra stuff I’ve not even begun to scratch the surface on.
(use-package consult
:straight t
:after company-mode
:init
(general-define-key
[remap apropos] #'consult-apropos
[remap bookmark-jump] #'consult-bookmark
[remap evil-show-marks] #'consult-mark
[remap evil-show-jumps] #'+vertico/jump-list
[remap evil-show-registers] #'consult-register
[remap goto-line] #'consult-goto-line
[remap imenu] #'consult-imenu
[remap locate] #'consult-locate
[remap load-theme] #'consult-theme
[remap man] #'consult-man
[remap recentf-open-files] #'consult-recent-file
[remap switch-to-buffer] #'consult-buffer
[remap switch-to-buffer-other-window] #'consult-buffer-other-window
[remap switch-to-buffer-other-frame] #'consult-buffer-other-frame
[remap yank-pop] #'consult-yank-pop
[remap persp-switch-to-buffer] #'+vertico/switch-workspace-buffer))
Better help documentation.
(use-package helpful
:straight t
:commands helpful--read-symbol
:init
;; Make `apropos' et co search more extensively.
(setq apropos-do-all t)
(global-set-key [remap describe-function] #'helpful-callable)
(global-set-key [remap describe-command] #'helpful-command)
(global-set-key [remap describe-variable] #'helpful-variable)
(global-set-key [remap describe-key] #'helpful-key)
(global-set-key [remap describe-symbol] #'helpful-symbol))
Company is the text completion framework for Emacs: everything integrates with
it. So lets set it up! One thing I change from the default behaviour is to
prevent it auto-completing as I type. I find it really distracting and actually,
needing to hit C-SPC
when I want completion candidates is pretty nice: it
better fits my mental model of “ok what should this thing be?” instead of being
more like having someone sat next to you trying to second guess everything you
are typing…
(use-package company
:straight t
:commands (company-complete-common
company-complete-common-or-cycle
company-manual-begin
company-grab-line)
:init
(setq company-minimum-prefix-length 2
company-tooltip-limit 14
company-tooltip-align-annotations t
company-require-match 'never
company-global-modes
'(not erc-mode
circe-mode
message-mode
help-mode
gud-mode
vterm-mode)
company-frontends
'(company-pseudo-tooltip-frontend ; always show candidates in overlay tooltip
company-echo-metadata-frontend) ; show selected candidate docs in echo area
;; Buffer-local backends will be computed when loading a major mode, so
;; only specify a global default here.
company-backends '(company-capf)
company-auto-commit nil
company-dabbrev-other-buffers nil
company-dabbrev-ignore-case nil
company-dabbrev-downcase nil)
:config
(add-hook 'company-mode-hook #'evil-normalize-keymaps)
(company-mode 1)
(global-company-mode)
(setq company-idle-delay nil))
I first tried out the modus themes from Prot
on a whim when I was thinking
about how “in your face” my (then) theme of choice was. (It was called
laserwave
which tells you pretty much all you need to know). After switching I
wasn’t too sure if I liked it or not but the more I stuck with them the more
they have grown on me, even to the extent that I’m using modus-operandi
for
writing Lisp and Org files as it’s actually a really nice experience.
The themes come with quite a few configuration options which I’ve tinkered
around with until I’ve found what works for me quite well at this point. I would
quite like to try out the mixed-pitch
stuff for Org mode but I can’t for the
life of me get it to stop jiggling the focused line a bit every time I move the
cursor…
(use-package modus-themes
:straight t
:init
(setq modus-themes-italic-constructs t
modus-themes-bold-constructs t
modus-themes-region '(bg-only no-extend accented)
modus-themes-org-blocks 'gray-background
modus-themes-deuteranopia nil
modus-themes-syntax '(alt-syntax green-strings)
modus-themes-subtle-line-numbers t
modus-themes-headings
'((1 . (1.2))
(2 . (1.15))
(3 . (1.1))
(4 . (1.05)))
modus-themes-diffs 'desaturated
modus-themes-hl-line '(intense accented))
(modus-themes-load-themes)
:config
(modus-themes-load-operandi))
(set-frame-font "Fira Code 10")
(set-face-attribute 'default nil :family "Fira Code")
(set-face-attribute 'variable-pitch nil :family "Alegreya")
;; Add frame borders and window dividers
(modify-all-frames-parameters
'((right-divider-width . 10)
(internal-border-width . 10)))
(dolist (face '(window-divider
window-divider-first-pixel
window-divider-last-pixel))
(face-spec-reset-face face)
(set-face-foreground face (face-attribute 'default :background)))
(set-face-background 'fringe (face-attribute 'default :background))
(setq org-auto-align-tags nil
org-tags-column 0
org-pretty-entities t
org-ellipsis " [...]"
org-src-window-setup 'other-window
org-src-preserve-indentation t
org-src-tab-acts-natively t
org-src-fontify-natively t
org-edit-src-content-indentation 0)
(use-package org-modern
:straight t
:init
(add-hook 'org-mode-hook #'org-modern-mode)
(add-hook 'org-agenda-finalize-hook #'org-modern-mode)
:config
(setq org-modern-block t))
(use-package minions
:straight t
:config (minions-mode 1))
(use-package yasnippet
:straight t
:config
(yas-global-mode 1))
(use-package doom-snippets
:after yasnippet
:straight
(doom-snippets
:type git
:host github
:repo "hlissner/doom-snippets"
:files ("*.el" "*")))
hl-todo is pretty simple but easily configurable and really nice for helping draw a little more attention to TODO comments and similar notes in files.
(use-package hl-todo
:straight t
:config
(setq hl-todo-highlight-punctuation ":"
hl-todo-keyword-faces
`(("TODO" warning bold)
("NOTE" success bold)
("FIXME" error bold)
("XXX" font-lock-constant-face bold)
("HACK" font-lock-warning-face bold)
("REVIEW" font-lock-keyword-face bold)
("DEPRECATED" font-lock-doc-face bold)
("SECTION" font-lock-comment-face bold)
("BUG" error bold)))
(hl-todo-mode 1)
(global-hl-todo-mode))