Inspired by Spacemacs and Doom Emacs. Most of the “aesthetic” parts of those distributions are available as plugins, so gluing together your own configuration isn’t too hard.
Featuring:
- straight.el for package management
- maximal use-package for package configuration
- maximal EVIL for vim comfort
Be multi-platform, as I jump between Linux, Windows, and WSL
Be as idiomatic as possible. Parts of this config have been copied and pasted from other places, but are rewritten/updated if needed to stay consistent and easy to read.
Every feature provided by use-package
is used over calling elisp functions. That means :custom
is preferred over setq
, :hook
over add-hook
, etc. where possible.
Minimal customization of packages we use, unless they misbehave. Try to document and rationalize any setting set thats not part of the package’s setup instructions.
Try to avoid “Not Invented Here” syndrome, use an existing package to do something instead of rolling our own.
Emacs startup times can be painful, if left uncontrolled. With a “basic” config like this one still resulting in multi-second startup times, its a frequent topic of debate. Distributions like doom-emacs
use fast startup times as one of the main selling points. Let’s try to pull in all the wisdom on how to keep our config simple yet fast.
In Emacs 27+, package initialization occurs before user-init-file
is loaded, but after early-init-file
. We handle package initialization, so we must prevent Emacs from doing it early!
;;; -*- lexical-binding: t; -*-
(defvar comp-deferred-compliation)
(setq comp-deferred-compilation t)
(setq package-enable-at-startup nil)
;; Resizing the Emacs frame can be a terribly expensive part of changing the
;; font. By inhibiting this, we easily halve startup times with fonts that are
;; larger than the system default.
(setq frame-inhibit-implied-resize t)
Prevent the glimpse of un-styled Emacs by disabling these UI elements early.
(menu-bar-mode -1)
(unless (and (display-graphic-p) (eq system-type 'darwin))
(push '(menu-bar-lines . 0) default-frame-alist))
(push '(tool-bar-lines . 0) default-frame-alist)
(push '(vertical-scroll-bars) default-frame-alist)
Following Doom-Emacs FAQ, we max the garbage collection threshold on startup, and reset it to the original value after.
;; max memory available for gc on startup
(defvar me/gc-cons-threshold 16777216)
(setq gc-cons-threshold most-positive-fixnum
gc-cons-percentage 0.6)
(add-hook 'emacs-startup-hook
(lambda ()
(setq gc-cons-threshold me/gc-cons-threshold
gc-cons-percentage 0.1)))
;; max memory available for gc when opening minibuffer
(defun me/defer-garbage-collection-h ()
(setq gc-cons-threshold most-positive-fixnum))
(defun me/restore-garbage-collection-h ()
;; Defer it so that commands launched immediately after will enjoy the
;; benefits.
(run-at-time
1 nil (lambda () (setq gc-cons-threshold me/gc-cons-threshold))))
(add-hook 'minibuffer-setup-hook #'me/defer-garbage-collection-h)
(add-hook 'minibuffer-exit-hook #'me/restore-garbage-collection-h)
(setq garbage-collection-messages t)
We also set the file-name-handler-alist
to an empty list, and reset it after Emacs has finished initializing.
(defvar me/-file-name-handler-alist file-name-handler-alist)
(setq file-name-handler-alist nil)
(add-hook 'emacs-startup-hook
(lambda ()
(setq file-name-handler-alist me/-file-name-handler-alist)))
(setq site-run-file nil)
(setq inhibit-compacting-font-caches t)
Optimizations for improving I/O performance. Increase max bytes read from a sub-process in a single op (Emacs 27+)
(when (boundp 'read-process-output-max)
;; 1MB in bytes, default 4096 bytes
(setq read-process-output-max 1048576))
straight.el is used to download packages for us from all over the web. It stores them all in their respective git folders in .emacs.d/straight
, which makes debugging, and contributing fixes back upstream as easy as possible.
First, we configure some settings for staight.el
to better integrate with use-package
. use-package is a nice and consistent way to declare packages and their respective configs.
(setq straight-use-package-by-default t
use-package-always-defer t
straight-cache-autoloads t
straight-vc-git-default-clone-depth 1
vc-follow-symlinks t)
Then, we want to enable debugging whenever we encounter an error.
(setq debug-on-error t)
Now, let’s fetch straight.el
.
(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))
(setq vc-follow-symlinks 'ask) ; restore default
Let’s load an optional package which gives us some convenience functions, like straight-x-clean-unused-repo
to remove any packages we don’t have configured anymore.
(require 'straight-x)
Now, let’s install use-package
.
(straight-use-package 'use-package)
We use esup and benchmark-init-el to keep tabs on our startup speed.
(use-package esup
:demand t
:commands esup)
(use-package benchmark-init
:demand t
:hook (after-init . benchmark-init/deactivate))
Also let’s print a message to the *messages*
buffer with the total startup time.
(add-hook
'emacs-startup-hook
(lambda ()
(message "Emacs ready in %s with %d garbage collections."
(format
"%.2f seconds"
(float-time
(time-subtract after-init-time before-init-time)))
gcs-done)))
(use-package gcmh
:demand t
:config
(gcmh-mode 1))
(provide 'early-init)
Make elisp in this file behave like we expect these days. Everyone has this set, but no one explains why.
In non-elisp speak, it adds proper scoping and “closure” behaviour to variables.This Emacswiki article explains it well.
;;; config.el -*- lexical-binding: t ; eval: (view-mode -1) -*-
Enable view-mode
, which both makes the file read-only (as a reminder
that init.el
is an auto-generated file, not supposed to be edited),
and provides some convenient key bindings for browsing through the
file.
Let’s define some constants we use throughout our config.
;; environment
(defconst *is-windows* (eq system-type 'windows-nt))
(defconst *is-unix* (not *is-windows*))
;; fonts
(defconst *mono-font-family*
(if *is-windows* "JetBrainsMono NF" "GoMono Nerd Font"))
(defconst *mono-font-height*
(if *is-windows* 90 90))
(defconst *serif-font-family*
(if *is-windows* "Georgia" "IBM Plex Serif"))
(defconst *serif-font-height*
(if *is-windows* 110 110))
(defconst *project-dir* (expand-file-name "~/git"))
Essentially what vim-sensible does, but we use better-defaults in emacs. But it doesn’t do everything, so we need to help it out.
(use-package better-defaults
:demand t)
(setq default-directory "~/"
;; always follow symlinks when opening files
vc-follow-symlinks t
;; overwrite text when selected, like we expect.
delete-seleciton-mode t
;; quiet startup
inhibit-startup-message t
initial-scratch-message nil
;; hopefully all themes we install are safe
custom-safe-themes t
;; simple lock/backup file management
create-lockfiles nil
backup-by-copying t
delete-old-versions t
;; when quiting emacs, just kill processes
confirm-kill-processes nil
;; ask if local variables are safe once.
enable-local-variables t)
;; use human-readable sizes in dired
(setq-default dired-listing-switches "-alh")
;; life is too short to type yes or no
(defalias 'yes-or-no-p 'y-or-n-p)
;; always highlight code
(global-font-lock-mode 1)
;; refresh a buffer if changed on disk
(global-auto-revert-mode 1)
;; save window layout & buffers
;; (setq desktop-restore-eager 5)
;; (desktop-save-mode 1)
Emacs is very conservative about assuming encoding. Everything is utf-8 these days, lets have that as the default.
(prefer-coding-system 'utf-8)
(set-default-coding-systems 'utf-8)
(set-terminal-coding-system 'utf-8)
(set-keyboard-coding-system 'utf-8)
(set-selection-coding-system 'utf-8)
(set-file-name-coding-system 'utf-8)
(set-clipboard-coding-system 'utf-8)
(if *is-windows*
(set-w32-system-coding-system 'utf-8))
(set-buffer-file-coding-system 'utf-8)
no-littering teaches Emacs to not leave it’s files everywhere, and just keep them neatly in .emacs.d
where they don’t bother anyone.
We also set custom-file
to be within one of these new nice directories, so Emacs doesn’t keep chaging init.el
and messing with our git workflow.
(use-package no-littering
:demand t
:config
(setq
auto-save-file-name-transforms
`((".*" ,(no-littering-expand-var-file-name "auto-save/") t)))
(setq custom-file (no-littering-expand-etc-file-name "custom.el"))
(when (file-exists-p custom-file)
(load custom-file)))
which-key pops up a nice window whenever we hesitate about a keyboard shortcut, and shows all the possible keys we can press. Popularized by Spacemacs and Doom-Emacs, we can now configure absurd key combinations, forget about them, and then be delighted to discover them again!
(use-package which-key
:demand t
:after evil
:custom
(which-key-allow-evil-operators t)
(which-key-show-remaining-keys t)
(which-key-sort-order 'which-key-prefix-then-key-order)
:config
(which-key-mode 1)
(which-key-setup-minibuffer)
(set-face-attribute
'which-key-local-map-description-face nil :weight 'bold))
EVIL is vim emulation in Emacs. There are a number of other evil packages which add vim-like bindings to various modes.
(use-package evil
:demand t
:init
(setq evil-want-integration t
evil-want-keybinding nil
evil-want-C-u-scroll t
evil-want-Y-yank-to-eol t
evil-split-window-below t
evil-vsplit-window-right t
evil-respect-visual-line-mode t)
:config
(evil-mode 1))
(use-package evil-collection
:demand t
:after evil
:config
(evil-collection-init))
(use-package evil-commentary
:demand t
:after evil
:config
(evil-commentary-mode 1))
(use-package evil-surround
:demand t
:after evil
:config
(global-evil-surround-mode 1))
(use-package evil-org
:demand t
:after evil org
:hook (org-mode . evil-org-mode)
:config
(add-hook 'evil-org-mode-hook 'evil-org-set-key-theme)
(require 'evil-org-agenda)
(evil-org-agenda-set-keys))
general.el is a wrapper around Emacs key-binding mechanisms to make them easier to use. It integrates with use-package, evil, and which-key.
We will define two “leader maps”, similar to vim’s <leader>
and <localleader>
that we will use to bind global and major-mode-specific keybindings. This is how we’re kind of like
(use-package general
:demand t
:config
(general-evil-setup t)
(general-create-definer leader-def
:states '(normal motion emacs)
:keymaps 'override
:prefix "SPC"
:non-normal-prefix "C-SPC")
(leader-def "" '(:ignore t :wk "leader"))
(general-create-definer localleader-def
:states '(normal motion emacs)
:keymaps 'override
:prefix "SPC m"
:non-normal-prefix "C-SPC m")
(localleader-def "" '(:ignore t :wk "mode")))
A good-looking tool is a pleasure to work with. Here, we try to tweak all the dials Emacs gives us to make it pretty and A E S T H E T I C
.
(setq ring-bell-function 'ignore ; no bell
;; better scrolling
scroll-step 1
scroll-conservatively 101
scroll-preserve-screen-position 1
mouse-wheel-scroll-amount '(1 ((shift) . 5))
mouse-wheel-follow-mouse t
;; lines between the cursor and the edge of the screen
scroll-margin 3
;; wrap lines that are too long.
truncate-lines nil
;; don't resize frames a character at a time, but use pixels
frame-resize-pixelwise t)
;; add some space between lines for easier reading.
(setq-default line-spacing 1)
;; highlight the current line
(global-hl-line-mode t)
;; Add padding inside buffer windows
(setq-default left-margin-width 2
right-margin-width 2)
(set-window-buffer nil (current-buffer)) ; Use them now.
;; Add padding inside frames (windows)
(add-to-list 'default-frame-alist '(internal-border-width . 8))
(set-frame-parameter nil 'internal-border-width 8) ; Use them now
;; fix color display when loading emacs in terminal
(defun enable-256color-term ()
(interactive)
(load-library "term/xterm")
(terminal-init-xterm))
(unless (display-graphic-p)
(if (string-suffix-p "256color" (getenv "TERM"))
(enable-256color-term)))
We will load all the themes. We need to :defer
them, to prevent each theme getting loaded upon init, and flashing emacs and conflicting with each other.
(use-package leuven-theme
:defer t)
(use-package vivid-theme
:straight (:host github :repo "websymphony/vivid-theme")
:defer t)
(use-package doom-themes
:defer t
:config
(doom-themes-visual-bell-config)
(doom-themes-treemacs-config)
(doom-themes-org-config)
(doom-themes-set-faces nil
;; extending faces breaks orgmode collapsing for now
'(org-block-begin-line :extend nil)
'(org-block-end-line :extend nil)
;; different sized headings are nice.
'(outline-1 :height 1.3)
'(outline-2 :height 1.1)
'(outline-3 :height 1.0)))
(defun me/init-theme ()
(load-theme 'doom-dracula t))
(add-hook 'emacs-startup-hook #'me/init-theme)
The unicode-fonts package helps Emacs use the full range of unicode characters provided by most fonts.
We set a regular font and a variable-pitch
one, the latter is used by mixed-pitch-mode
to render regular text with a proportional font.
(use-package persistent-soft
:demand t)
(use-package unicode-fonts
:demand t
:after persistent-soft
:config
(unicode-fonts-setup)
(custom-set-faces
`(default ((t (:family ,*mono-font-family*
:height ,*mono-font-height*))))
`(variable-pitch ((t (:family ,*serif-font-family*
:height ,*serif-font-height*))))))
all-the-icons allows emacs to show pretty icons anywhere we want.
We pair it with all-the-icons-dired to show them in dired
, treemacs-all-the-icons to show them in treemacs
, all-the-icons-ivy to show them in ivy
, and all-the-icons-ivy-rich to show them in ivy-rich
.
(use-package all-the-icons
:demand t)
(use-package all-the-icons-dired
:defer 1
:after all-the-icons
:hook (dired-mode . all-the-icons-dired-mode))
(use-package treemacs-all-the-icons
:defer 1
:after all-the-icons treemacs
:config
(treemacs-load-theme "all-the-icons"))
(use-package all-the-icons-ivy
:defer 1
:after all-the-icons
:config
(all-the-icons-ivy-setup))
(use-package all-the-icons-ivy-rich
:defer 1
:after all-the-icons
:config
(all-the-icons-ivy-rich-mode 1))
emacs-dashboard adds a nice startup screen, showing recent files, projectes, etc.
(use-package dashboard
:demand t
:after all-the-icons projectile
:custom
;; show in `emacsclient -c`
(initial-buffer-choice #'(lambda () (get-buffer "*dashboard*")))
(dashboard-startup-banner 'logo)
(dashboard-set-heading-icons t)
(dashboard-set-file-icons t)
(dashboard-center-content t)
(dashboard-items '((recents . 10)
(projects . 5)
(bookmarks . 5)))
:config
(dashboard-setup-startup-hook))
doom-modeline provides a clean and simple modeline (bottom bar) for each buffer. We pair it with the minions minor mode to collect all minor modes into a single menu. anzu is used to show the number of matches when we search in a file.
(use-package anzu
:defer 1
:after isearch
:config
(global-anzu-mode 1))
(use-package minions
:defer 1
:config
(minions-mode 1))
(use-package doom-modeline
:demand t
:custom
(inhibit-compacting-font-caches t)
(doom-modeline-height 28)
;; 1 minor mode will be shown thanks to minions
(doom-modeline-minor-modes t)
:config
(doom-modeline-mode 1))
centaur-tabs add tabs to the top of the window for emacs. It might sound crazy, but they are useful to keep an eye on which buffers you have open, especially when you jump between projects.
Out of the box they come configured ok, but not perfect. We configure the tabs to group by project, and hide/show them for more buffers.
(use-package centaur-tabs
:defer 1
:after all-the-icons
:general
(:states 'normal
"gt" 'centaur-tabs-forward
"gT" 'centaur-tabs-backward)
(leader-def
"tg" 'centaur-tabs-toggle-groups)
:hook
(dashboard-mode . centaur-tabs-local-mode)
(term-mode . centaur-tabs-local-mode)
(calendar-mode . centaur-tabs-local-mode)
(org-agenda-mode . centaur-tabs-local-mode)
(helpful-mode . centaur-tabs-local-mode)
:custom
(centaur-tabs-style "bar")
(centaur-tabs-set-icons t)
(centaur-tabs-set-modified-marker t)
(centaur-tabs-height 28)
(x-underline-at-descent-line t)
(uniquify-separator "/")
(uniquify-buffer-name-style 'forward)
(centaur-tabs-gray-out-icons 'buffer)
(centaur-tabs-modified-marker "")
:config
(centaur-tabs-headline-match)
(centaur-tabs-enable-buffer-reordering)
(centaur-tabs-mode t)
(centaur-tabs-change-fonts *mono-font-family* *mono-font-height*)
(defun centaur-tabs-buffer-groups ()
"`centaur-tabs-buffer-groups' control buffers' group rules.
Group centaur-tabs with mode if buffer is derived from `eshell-mode' `emacs-lisp-mode' `dired-mode' `org-mode' `magit-mode'.
All buffer name start with * will group to \"Emacs\".
Other buffer group by `centaur-tabs-get-group-name' with project name."
(list
(cond
;; ((not (eq (file-remote-p (buffer-file-name)) nil))
;; "Remote")
((or (string-equal "*" (substring (buffer-name) 0 1))
(memq major-mode '(magit-process-mode
magit-status-mode
magit-diff-mode
magit-log-mode
magit-file-mode
magit-blob-mode
magit-blame-mode)))
"Emacs")
((derived-mode-p 'dired-mode)
"Dired")
((memq major-mode '(helpful-mode
help-mode))
"Help")
((memq major-mode '(org-agenda-clockreport-mode
org-agenda-mode
org-beamer-mode
org-src-mode
org-indent-mode
org-bullets-mode
org-cdlatex-mode
org-agenda-log-mode
diary-mode))
"OrgMode")
(t
(or (concat "Project: " (projectile-project-name))
(centaur-tabs-get-group-name (current-buffer))))))))
Always redraw immediately when scrolling, more responsive and doesn’t hang! Sourced from http://emacs.stackexchange.com/a/31427/2418
(setq fast-but-imprecise-scrolling t
jit-lock-defer-time 0)
fast-scroll “works by temporarily disabling font-lock and switching to a barebones mode-line, until you stop scrolling (at which point it re-enables)”. It only does this when scrolling super fast, to keep everything responsive.
(use-package fast-scroll
:defer 1
:hook
(fast-scroll-start . (lambda () (flycheck-mode -1)))
(fast-scroll-end . (lambda () (flycheck-mode 1)))
:config
(fast-scroll-config)
(fast-scroll-mode 1))
visual-fill-column wraps lines at fill-column
, and makes it easier to read long lines of code. It is preferred over the built-in visual-line-mode
because it doesn’t break words.
(use-package visual-fill-column
:defer 1
:hook (org-src . visual-fill-column-mode)
:custom
(visual-line-fringe-indicators
'(left-curly-arrow right-curly-arrow))
(split-window-preferred-function
'visual-fill-column-split-window-sensibly)
:config
(advice-add 'text-scale-adjust
:after #'visual-fill-column-adjust)
(global-visual-fill-column-mode 1)
(global-visual-line-mode 1))
mixed-pitch allows us to use proportional fonts to display text that isn’t code, and make files more readable.
(use-package mixed-pitch
:after all-the-icons
:defer 1
:custom
(mixed-pitch-set-height t)
:hook (text-mode . mixed-pitch-mode))
(use-package ligature
:straight (:host github :repo "mickeynp/ligature.el")
:defer 1
:config
(ligature-set-ligatures 't '("www"))
(ligature-set-ligatures
'prog-mode
'("-->" "//" "/**" "/*" "*/" "<!--" ":=" "->>" "<<-" "->" "<-"
"<=>" "==" "!=" "<=" ">=" "=:=" "!==" "&&" "||" "..." ".."
"|||" "///" "&&&" "===" "++" "--" "=>" "|>" "<|" "||>" "<||"
"|||>" "<|||" ">>" "<<" "::=" "|]" "[|" "{|" "|}"
"[<" ">]" ":?>" ":?" "/=" "[||]" "!!" "?:" "?." "::"
"+++" "??" "###" "##" ":::" "####" ".?" "?=" "=!=" "<|>"
"<:" ":<" ":>" ">:" "<>" "***" ";;" "/==" ".=" ".-" "__"
"=/=" "<-<" "<<<" ">>>" "<=<" "<<=" "<==" "<==>" "==>" "=>>"
">=>" ">>=" ">>-" ">-" "<~>" "-<" "-<<" "=<<" "---" "<-|"
"<=|" "/\\" "\\/" "|=>" "|~>" "<~~" "<~" "~~" "~~>" "~>"
"<$>" "<$" "$>" "<+>" "<+" "+>" "<*>" "<*" "*>" "</>" "</" "/>"
"<->" "..<" "~=" "~-" "-~" "~@" "^=" "-|" "_|_" "|-" "||-"
"|=" "||=" "#{" "#[" "]#" "#(" "#?" "#_" "#_(" "#:" "#!" "#="
"&="))
(global-ligature-mode t))
solaire-mode darkens non-important buffers, to help you focus on what matters.
;; A more complex, more lazy-loaded config
(use-package solaire-mode
:defer 1
:hook
;; Ensure solaire-mode is running in all solaire-mode buffers
(change-major-mode . turn-on-solaire-mode)
;; ...if you use auto-revert-mode, this prevents solaire-mode from turning
;; itself off every time Emacs reverts the file
(after-revert . turn-on-solaire-mode)
;; To enable solaire-mode unconditionally for certain modes:
(ediff-prepare-buffer . solaire-mode)
;; Highlight the minibuffer when it is activated:
(minibuffer-setup . solaire-mode-in-minibuffer)
:custom
(solaire-mode-auto-swap-bg t)
:config
(solaire-global-mode +1))
helpful makes a better Emacs *help*
buffer, with colors and contextual information.
(use-package helpful
:defer 1
:general
(leader-def
"h" '(:ignore t :wk "help")
"hf" 'helpful-callable
"hv" 'helpful-variable
"hk" 'helpful-key
"ho" 'helpful-at-point)
:config
(add-to-list 'display-buffer-alist
'("*[Hh]elp"
(display-buffer-reuse-mode-window
display-buffer-pop-up-window))))
info-colors adds pretty Info colors.
(use-package info-colors
:defer 1
:config
(add-hook 'Info-selection-hook 'info-colors-fontify-node))
restart-emacs teaches Emacs to restart itself. I added a me/reload-init
command as well to just reload the init.el
file without a full restart.
(defun me/reload-init ()
"Reload init.el."
(interactive)
(message "Reloading init.el...")
(load user-init-file nil 'nomessage)
(message "Reloading init.el... done."))
(use-package restart-emacs
:commands restart-emacs
:general
(leader-def
"q" '(:ignore t :wk "exit emacs")
"qR" 'restart-emacs
"qr" 'me/reload-init))
prescient.el teaches ivy
and company
better sorting and filtering.
(use-package prescient
:defer 1
:config
(prescient-persist-mode 1))
swiper/ivy/counsel is a great UI to visualize and filter lists. It sets itself up to augment most prompts to filter possible matches as you type. It’s good stuff.
(use-package ivy
:defer 1
:custom
;; add bookmarks and recentf to buffer lists
(ivy-use-virtual-buffers t)
;; better matching method
(ivy-re-builders-alist '((t . ivy--regex-plus)))
:config
(ivy-mode 1))
(use-package counsel
:defer 1
:general
(leader-def
"SPC" '(counsel-M-x :wk "M-x")
"f" '(:ignore t :wk "file")
"ff" 'counsel-find-file
"fr" 'counsel-buffer-or-recentf
"b" '(:ignore t :wk "buffer")
"bb" 'switch-to-buffer
"bd" 'kill-this-buffer
"bn" 'next-buffer
"bp" 'previous-buffer
"bx" 'kill-buffer-and-window
"tc" 'counsel-load-theme)
(:states 'normal
"C-p" 'projectile-find-file
"C-S-p" 'counsel-M-x)
:config
(counsel-mode 1))
;; better fuzzy matching.
(use-package flx
:defer 1
:after ivy counsel)
(use-package ivy-prescient
:defer 1
:after ivy counsel prescient
:config
(ivy-prescient-mode 1))
;; add more information to ivy/counsel
(use-package ivy-rich
:defer 1
:after ivy counsel all-the-icons-ivy-rich
:config
(setcdr (assq t ivy-format-functions-alist) #'ivy-format-function-line)
(ivy-rich-mode 1)
(setq ivy-initial-inputs-alist nil))
(use-package ivy-posframe
:defer 1
:after ivy counsel
:config
(setq ivy-posframe-display-functions-alist
'((t . ivy-posframe-display-at-frame-top-center)))
(ivy-posframe-mode 1))
flycheck gathers syntax errors and warnings on-the-fly. We use flycheck-posframe to show them if the cursor is on a flycheck warning.
(use-package flycheck
:defer 1
:init
(global-flycheck-mode t))
(use-package flycheck-posframe
:defer 1
:after flycheck
:hook (flycheck-mode . flycheck-posframe-mode)
:config
(flycheck-posframe-configure-pretty-defaults)
(add-hook 'flycheck-posframe-inhibit-functions #'company--active-p)
(add-hook 'flycheck-posframe-inhibit-functions #'evil-insert-state-p)
(add-hook 'flycheck-posframe-inhibit-functions #'evil-replace-state-p)
(advice-add 'org-edit-src-exit :after #'flycheck-posframe-hide-posframe))
emacs-format-all-the-code knows about all the different formatters for different languuages, and tries to run them if they are installed. We configure it to format all modes that are in the auto-format-modes
list on save. We well add modes to this later.
(defcustom auto-format-modes '()
"Modes to turn on format-all-mode in")
(defcustom auto-format-dirs '()
"Directories to turn on format-all-mode in")
(defun me/auto-format-buffer-p ()
(and
(member major-mode auto-format-modes)
(buffer-file-name)
(save-match-data
(let ((dir (file-name-directory (buffer-file-name))))
(cl-some (lambda (regexp) (string-match regexp dir))
auto-format-dirs)))))
(defun me/maybe-format-all-mode ()
(format-all-mode (if (me/auto-format-buffer-p) 1 0)))
(use-package format-all
:commands format-all-mode
:hook (after-change-major-mode . me/maybe-format-all-mode))
company-mode gives us the standard dropdown as-you-type of modern IDEs.
(use-package company
:defer 1
:config
(global-company-mode 1))
(use-package company-prescient
:defer 1
:after company prescient
:config
(company-prescient-mode 1))
(use-package company-posframe
:defer 1
:after company
:config
(company-posframe-mode 1))
magit is a magic UI for dealing with git. The keybinds are intuitive, and it pops up suggestion a-la which-key
if you aren’t sure what button to press next.
(use-package magit
:commands magit
:general
(leader-def
"g" '(:ignore t :wk "git")
"gs" '(magit :wk "git status")
"gg" '(magit :wk "git status"))
:custom
(magit-repository-directories `((,*project-dir* . 3))))
We pair it with magit-todos which shows any TODO
, FIXME
, XXX
, BUG
, etc. comments in the codebase.
(use-package magit-todos
:after magit
:commands magit-todos-list magit-todos-mode
:custom
(magit-todos-nice nil)
:hook (magit-mode . magit-todos-mode))
magit-delta improves the coloring of diffs in magit using delta.
(use-package magit-delta
:if *is-unix*
:after magit
:commands magit-delta-mode
:custom
(magit-delta-default-dark-theme "Dracula")
:hook (magit-mode . magit-delta-mode))
projectile teaches Emacs to be aware of different ways a “project” folder can be recognized, and enables easy jumping and using of multiple projects in the same instance of emacs.
(defun me/expand-git-project-dirs (root)
"Return a list of all project directories 2 levels deep in ROOT.
Given my git projects directory ROOT, with a layout like =git/{hub,lab}/<user>/project=, return a list of 'user' directories that are part of the ROOT."
(mapcan #'(lambda (d) (cddr (directory-files d t)))
(cddr (directory-files root t))))
(use-package projectile
:demand t
:general
(leader-def
"p" '(:ignore t :wk "project")
"pd" 'projectile-dired)
:custom
(projectile-completion-system 'default)
(projectile-enable-caching t)
(if (file-directory-p *project-dir*)
(projectile-project-search-path)
(me/expand-git-project-dirs *project-dir*))
(projectile-sort-order 'recently-active)
(projectile-indexing-method (if *is-unix* 'hybrid 'native))
:config
(projectile-save-known-projects)
(projectile-mode +1))
(use-package counsel-projectile
:defer 1
:after counsel projectile
:general
(leader-def
"pp" 'counsel-projectile-switch-project
"pb" 'counsel-projectile-switch-to-buffer
"fp" 'counsel-projectile-find-file-dwim
"pf" 'counsel-projectile-find-file-dwim
"p/" 'counsel-projectile-rg))
diff-hl shows uncommitted git changes on left side of the buffer.
(use-package diff-hl
:defer 1
:hook
(dired-mode . diff-hl-dired-mode-unless-remote)
:config
(global-diff-hl-mode 1))
treemacs is a sidebar tree file explorer of the current directory/project.
evil
, projectile
, and magit
integration is enabled.
(use-package treemacs
:defer 2
:commands treemacs treemacs-find-file
:general
(leader-def
"tt" 'treemacs
"tf" 'treemacs-find-file))
(use-package treemacs-evil
:defer 1
:after treemacs evil)
(use-package treemacs-projectile
:defer 1
:after treemacs projectile)
(use-package treemacs-magit
:defer 1
:after treemacs-magit)
(use-package company-fish
:defer 1
:if (executable-find "fish")
:straight (:host github :repo "CeleritasCelery/company-fish")
:after company
:hook
(shell-mode . company-mode)
(eshell-mode . company-mode)
:config
(add-to-list 'company-backends 'company-fish))
(use-package eshell-syntax-highlighting
:defer 1
:straight (:host github :repo "akreisher/eshell-syntax-highlighting")
:after esh-mode
:config
(eshell-syntax-highlighting-global-mode 1))
(use-package em-smart
:defer 1
:straight (:type built-in)
:custom
(eshell-where-to-jump 'begin)
(eshell-review-quick-commands nil)
(eshell-smart-space-goes-to-end t))
(use-package eterm-256color
:hook (term-mode . eterm-256color-mode))
(defun me/disable-global-hl-line-mode ()
(global-hl-line-mode -1))
(use-package vterm
:straight nil
:commands vterm vterm-other-window
:hook (vterm-mode . #'me/disable-global-hl-line-mode)
:custom
(vterm-term-environment-variable "eterm-color")
:config
(remove-hook 'vterm-mode-hook 'vterm))
(use-package multi-vterm
:commands
multi-vterm
multi-vterm-next
multi-vterm-prev
multi-vterm-dedicated-toggle
(leader-def "pt" '(multi-vterm-projectile :wk "toggle vterm"))
multi-vterm-project)
Emacs has some cool features built-in that make editing text nice. Let’s turn them on.
;; treat camel-cased words as individual words.
(add-hook 'prog-mode-hook 'subword-mode)
;; don't assume sentences end with two spaces after a period.
(setq sentence-end-double-space nil)
;; show matching parens
(show-paren-mode t)
(setq show-paren-delay 0.0)
;; limit files to 80 columns. Controversial, I know.
(setq-default fill-column 80)
;; handle very long lines without hurting emacs
(global-so-long-mode)
editorconfig looks for an .editorconfig
file, and sets indents and other coding conventions as instructed.
(use-package editorconfig
:defer 1
:config
(editorconfig-mode 1))
whitespace-cleanup-mode cleans up messy whitespace in a document only if it was clean when opening.
(defun me/hide-trailing-whitespace ()
(setq show-trailing-whitespace nil))
(use-package whitespace-cleanup-mode
:demand t
:hook
(special-mode . me/hide-trailing-whitespace)
(comint-mode . me/hide-trailing-whitespace)
(compilation-mode . me/hide-trailing-whitespace)
(term-mode . me/hide-trailing-whitespace)
(vterm-mode . me/hide-trailing-whitespace)
(shell-mode . me/hide-trailing-whitespace)
(minibuffer-setup . me/hide-trailing-whitespace)
:custom
(show-trailing-whitespace t)
:config
(global-whitespace-cleanup-mode 1))
rainbow-delimiters color brackets in various colors to easier identify them.
(use-package rainbow-delimiters
:defer 1
:hook (prog-mode . rainbow-delimiters-mode)
:config
(set-face-attribute 'rainbow-delimiters-unmatched-face nil
:foreground "red"
:inherit 'error
:box t))
=evil-lion= is a package to make alignment of text easier.
(use-package evil-lion
:commands evil-lion-left evil-lion-right
:after evil
:general
(:states 'normal
"ga" 'evil-lion-left
"gA" 'evil-lion-right)
(:states 'visual
"ga" 'evil-lion-left
"gA" 'evil-lion-right))
parinfer is a magical way to edit lispy languages, that allows you to just focus on indentation and code layout. The brackets get inserted and adjusted automagically.
We use parinfer-rust-mode most of the time, and fall back to parinfer-mode, an pure elisp variant on Windows.
(use-package parinfer-rust-mode
:defer 1
:if *is-unix*
:hook
emacs-lisp-mode
lisp-mode
clojure-mode
:custom
(parinfer-rust-auto-download t))
(use-package parinfer
:defer 1
:if *is-windows*
:hook
(emacs-lisp-mode . parinfer-mode)
(lisp-mode . parinfer-mode)
(clojure-mode . parinfer-mode)
:init
(setq parinfer-extensions '(defaults pretty-parens evil)))
orgmode is a tool to organize information in plaintext documents. This configuration is using orgmode to interleave text and code.
(use-package org
:general
(leader-def
"o" '(:ignore t :wk "org")
"oa" 'org-agenda)
(localleader-def
:keymaps 'org-mode-map
:major-modes t
"," '(org-insert-structure-template :wk "insert block")
"e" '(:ignore t :wk "execute")
"ee" '(org-babel-execute-maybe :wk "execute (dwim)")
"es" '(org-babel-execute-src-block :wk "execute block")
"eb" '(org-babel-execute-buffer :wk "execute buffer")
"et" '(org-babel-execute-subtree :wk "execute subtree")
"'" '(org-edit-special :wk "edit block")
"tt" 'counsel-org-tag
"tv" 'org-change-tag-in-region
"b" '(:ignore t :wk "babel")
"bt" 'org-babel-tangle)
(:keymaps 'org-src-mode
:definer 'minor-mode
:states 'normal
"RET" '(org-edit-src-exit :wk "save")
"q" '(org-edit-src-abort :wk "abort"))
:custom
(org-directory "~/Sync/org")
;; use syntax-highlighting for src blocks
(org-src-fontify-natively t)
;; open another window when editing src blocks
(org-src-window-setup 'other-window)
;; strip blank lines when closing src block editor
(org-src-strip-leading-and-trailing-blank-lines t)
;; preserve indentation in src blocks, don't re-indent
(org-src-preserve-indentation t)
;; respect the src block syntax for tabs
(org-src-tab-acts-natively t)
;; wrap lines on startup
(org-startup-truncated nil)
;; if editing in an invisible region, complain.
(org-catch-invisible-edits 'show-and-error)
;; don't ask when evaluating every src block
(org-confirm-babel-evaluate nil)
;; don't hide emphasis markers, because there are soo many
(org-hide-emphasis-markers nil)
;; try to draw utf8 characters, don't just show their code
(org-pretty-entities t)
;; add a background to begin_quote and begin_verse blocks.
(org-fontify-quote-and-verse-blocks t)
;; use a pretty character to show a collapsed section
(org-ellipsis " ▿")
;; don't collapse blank lines when collapsing a tree
;; as that messes with the ellipsis.
(org-cycle-separator-lines -1)
;; don't align tags
(org-tag-column 0)
;; allow #+BIND to be used for org-export
(org-export-allow-bind-keywords t)
:hook (org-mode . org-indent-mode)
:config
(add-to-list 'org-structure-template-alist '("se" . "src emacs-lisp"))
(add-to-list 'org-structure-template-alist '("ss" . "src sh"))
(add-to-list 'org-structure-template-alist '("sp" . "src python"))
(org-babel-do-load-languages 'org-babel-load-languages
'((emacs-lisp . t)
(python . t)
(shell . t))))
org-superstar-mode makes prettier the headings in orgmode, with unicode bulletpoints.
(defun me/lightweight-superstar-mode ()
"Start Org Superstar differently depending on the number of lists items."
(let ((list-items
(count-matches "^[ \t]*?\\([+-*]\\|[ \t]\\*\\)"
(point-min) (point-max))))
(unless (< list-items 100))
(org-superstar-toggle-lightweight-lists))
(org-superstar-mode 1))
(use-package org-superstar
:after all-the-icons org
:commands
org-superstar-mode
org-superstar-toggle-lightweight-lists
:hook (org-mode . me/lightweight-superstar-mode)
:custom
;; draw pretty unicode heading bullets
(org-superstar-headline-bullets-list '("⌾" "◈" "⚬" "▷"))
;; don't hide leading stars
(org-hide-leading-stars nil)
;; replace them with spaces!
(org-superstar-leading-bullet ?\s)
;; draw pretty todo items
(org-superstar-special-todo-items t)
;; draw pretty unicode list bullets
(org-superstar-prettify-item-bullets t))
Track time spent on tasks in org-mode. Inspired by raxod502/radian emacs config, we lazy-load org-clock, as org-clock-load
and org-clock-save
tend to cause a second or two delay.
(use-package org-clock
:straight nil
:after org
:custom
;; resume clock when clocking into a task with an open clock
(org-clock-in-resume t)
;; don't keep empty clock-times, usually made in error
(org-clock-out-remove-zero-time-clocks t)
;; include the task in the clock report
(org-clock-report-include-clocking-task t)
;; only auto-resolve clocks when theres no ongoing clock
(org-clock-auto-clock-resolution 'when-no-clock-is-running)
;; save the running clock when emacs closes
(org-clock-persist t)
:general
(localleader-def
:keymap org-mode-map
"c" '(:ignore t :wk "clock")
"ci" 'org-clock-in
"co" 'org-clock-out
"cf" 'org-clock-goto
"cq" 'org-clock-cancel
"cc" 'org-clock-in-last)
:commands
org-clock-in
org-clock-out
org-clock-goto
org-clock-cancel
org-clock-in-last
org-clock-load
org-clock-save
:hook
;; lazy-load org-clock-persistence-insinuate,
;; as it slows down init quite a bit.
;; source:
(org-mode . org-clock-load)
(kill-emacs-hook . (lambda ()
(when (featurep 'org-clock)
(org-clock-save))))
:config
(org-clock-load))
org-projectile creates a per-project org file, and adds some convenience functions to make it easy to jump to.
(use-package org-projectile
:after projectile org
:defer 1
:general
(leader-def
"po" 'org-projectile-project-todo-completing-read
"op" 'org-projectile-project-todo-completing-read)
:custom
(org-projectile-per-project-filepath "todo.org")
;; https://github.com/IvanMalison/org-projectile#project-headings-are-links
(org-confirm-elisp-link-function nil)
:config
(org-projectile-per-project)
(projectile-add-known-project org-directory)
;; avoid adding non-existing files.
(setq org-agenda-files
(append org-agenda-files
(delq nil (mapcar (lambda (file) (if (file-exists-p file) file))
(org-projectile-todo-files)))))
(push (org-projectile-project-todo-entry) org-capture-templates))
(defun me/disable-flycheck-checkers-for-elisp ()
(setq-local flycheck-disabled-checkers '(emacs-lisp-checkdoc)))
(use-package elisp-mode
:straight (:type built-in)
:hook
(org-src-mode . me/disable-flycheck-checkers-for-elisp)
:general
(localleader-def
:keymaps 'emacs-lisp-mode-map
:major-modes t
"e" '(:ignore t :wk "eval")
"ee" 'eval-defun
"es" 'eval-last-sexp
"eb" 'eval-buffer
"er" 'eval-region))
(use-package gitconfig-mode)
(use-package gitignore-mode)
(use-package nix-mode)
(use-package nixpkgs-fmt
:hook (nix-mode . nixpkgs-fmt-on-save-mode))
(use-package pretty-sha-path
:hook
(shell-mode . pretty-sha-path-mode)
(dired-mode . pretty-sha-path-mode))
(use-package direnv
:config
(direnv-mode 1))
(use-package markdown-mode
:commands gfm-mode markdown-mode
:mode
("README\\.md\\'" . gfm-mode)
("\\.md\\'" . markdown-mode)
("\\.markdown\\'" . markdown-mode)
:custom
(markdown-command '("pandoc" "--from=markdown" "--to=html5")))
(use-package clojure-mode)
(use-package cider
:hook (clojure-mode . cider-mode))
(use-package clj-refactor
:after cider
:hook (clojure-mode . clj-refactor-mode))
(use-package pug-mode)
(use-package php-mode)
(use-package web-mode)
(use-package emmet-mode)
(use-package prettier-js
:hook
(web-mode . #'prettier-js-mode)
(js2-mode . #'prettier-js-mode))
(use-package js2-mode
:hook
(js-mode . #'js2-minor-mode))
(use-package async
:demand t)
(defvar *config-file* (expand-file-name "config.org" user-emacs-directory)
"The configuration file.")
(defvar *config-last-change* (nth 5 (file-attributes *config-file*))
"Last modification time of the configuration file.")
(defvar *show-async-tangle-results* nil
"Keeps *emacs* async buffers around for later inspection.")
(defun me/config-updated ()
"Checks if the configuration file has been updated since the last time."
(time-less-p *config-last-change*
(nth 5 (file-attributes *config-file*))))
(defun me/async-babel-tangle (org-file)
"Tangles the org file asynchronously."
(let ((init-tangle-start-time (current-time))
(file (buffer-file-name))
(async-quiet-switch "-q"))
(async-start
`(lambda ()
(require 'ob-tangle)
(org-babel-tangle-file ,org-file))
(unless *show-async-tangle-results*
`(lambda (result)
(if result
(message "SUCCESS: %s successfully tangled (%.2fs)."
,org-file
(float-time
(time-subtract (current-time)
',init-tangle-start-time)))
(message "ERROR: %s as tangle failed." ,org-file)))))))
(defun me/config-tangle ()
"Tangles the org file asynchronously."
(when (me/config-updated)
(setq *config-last-change*
(nth 5 (file-attributes *config-file*)))
(me/async-babel-tangle *config-file*)))
(add-hook 'org-mode-hook
(lambda ()
(when (equal (expand-file-name buffer-file-truename)
*config-file*)
(add-hook 'after-save-hook
'me/config-tangle
nil 'make-it-local))))
;; Local Variables:
;; flycheck-disabled-checkers: (emacs-lisp-checkdoc)
;; byte-compile-warnings: (not free-vars)
;; End: