/emacs.d-2

A "modern" emacs config

MIT LicenseMIT

The Bestest Emacs

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:

Goals

Multi-platform

Be multi-platform, as I jump between Linux, Windows, and WSL

Consistency and Modernity

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, Maximal Reuse

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.

Startup

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.

Early Init

:header-args+: :tangle “./early-init.el”

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; -*-

Defer Compliations

(defvar comp-deferred-compliation)
(setq comp-deferred-compilation t)

Low-haniging Speedup Fruits

(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)

Hide bars

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)

Reduce GC

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)

Temporarily avoid special handling of files

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)))

Disable site-run-file

(setq site-run-file nil)

Don’t compact font caches

(setq inhibit-compacting-font-caches t)

Improve I/O

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

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)

Benchmarking

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 the garbage collector magic hack

(use-package gcmh
  :demand t
  :config
  (gcmh-mode 1))

End early-init.el

(provide 'early-init)

General Emacs settings

Lexical Binding

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.

Constants

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"))

Make Emacs Sensible

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)

UTF-8 by Default

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

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

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

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

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")))

Interface

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.

Aesthetics

(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)))

Themes

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)

Fonts

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

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))

Dashboard

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))

Mode Line

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))

Tabs

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))))))))

Fast Scroll

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))

Wrap Long Lines

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 Mode

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))

Ligatures

(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

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))

Packages

Helpful

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

info-colors adds pretty Info colors.

(use-package info-colors
  :defer 1
  :config
  (add-hook 'Info-selection-hook 'info-colors-fontify-node))

Restart-emacs

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

prescient.el teaches ivy and company better sorting and filtering.

(use-package prescient
  :defer 1
  :config
  (prescient-persist-mode 1))

Counsel, Ivy

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

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))

Format All

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

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

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

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))

Git Gutter

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

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)

Eshell

(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))

Vterm

(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)

Text Editing

Make Text Editing Sensible

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

editorconfig looks for an .editorconfig file, and sets indents and other coding conventions as instructed.

(use-package editorconfig
  :defer 1
  :config
  (editorconfig-mode 1))

Cleanup Trailing Whitespace

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

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

=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

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)))

Org Mode

org

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

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))

org-clock

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

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))

Languages

Emacs Lisp

(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))

Git

(use-package gitconfig-mode)
(use-package gitignore-mode)

Nix

(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))

Markdown

(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")))

Clojure

(use-package clojure-mode)

(use-package cider
  :hook (clojure-mode . cider-mode))

(use-package clj-refactor
  :after cider
  :hook (clojure-mode . clj-refactor-mode))

Pug

(use-package pug-mode)

PHP

(use-package php-mode)

Web Mode

(use-package web-mode)
(use-package emmet-mode)

JS

(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))

Appendix

Tangle Emacs config on save

Source

(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))))

Remove flycheck elisp warnings in init.el

;; Local Variables:
;; flycheck-disabled-checkers: (emacs-lisp-checkdoc)
;; byte-compile-warnings: (not free-vars)
;; End: