My Emacs configuration
Table of Contents
Below are fragments from my Emacs configuration file
(~/.emacs.d/init.el
) using
straight.el instead of
Emacs builtin package.el
. (For old package.el
based version
see here).
The newest version of this config is available from a github repo.
Bootstrap straight.el
(defvar bootstrap-version) (let ((bootstrap-file (expand-file-name "straight/repos/straight.el/bootstrap.el" user-emacs-directory)) (bootstrap-version 6)) (unless (file-exists-p bootstrap-file) (with-current-buffer (url-retrieve-synchronously "https://raw.githubusercontent.com/radian-software/straight.el/develop/install.el" 'silent 'inhibit-cookies) (goto-char (point-max)) (eval-print-last-sexp))) (load bootstrap-file nil 'nomessage)) (setq package-enable-at-startup nil straight-use-package-by-default t) (straight-use-package 'use-package)
Productivity
Key discoverability
(use-package which-key :init (setq which-key-idle-delay 2.0 which-key-idle-secondary-delay 1.0) :config (which-key-mode))
More efficient buffer/file selection
Requires: fzf and ripgrep (rg), and git.
(setq recentf-max-saved-items 100) (global-set-key "\C-cq" #'bury-buffer) (use-package prescient :config (prescient-persist-mode)) (use-package ivy-prescient :after counsel :config (ivy-prescient-mode)) (use-package counsel :demand :bind (("C-c a" . swiper-all) ("C-c e" . counsel-flycheck) ("C-c f" . counsel-fzf) ("C-c g" . counsel-git) ("C-c i" . counsel-imenu) ("C-c j" . counsel-git-grep) ("C-c L" . counsel-locate) ("C-c o" . counsel-outline) ("C-c r" . counsel-rg) ("C-c R" . counsel-register) ("C-c T" . counsel-load-theme) ("C-S-s" . swiper) ([remap dired] . counsel-dired)) :init (setq ivy-use-virtual-buffers t) :config (ivy-mode 1) (counsel-mode 1)) (use-package ivy-rich :after ivy :config (ivy-rich-mode 1)) (use-package helpful :init (setq counsel-describe-function-function #'helpful-callable) (setq counsel-describe-variable-function #'helpful-variable))
File explorer sidebar
Install fonts with M-x all-the-icons-install-fonts
.
(use-package dired-sidebar :bind ("C-c s" . dired-sidebar-toggle-sidebar)) (use-package all-the-icons :defer) (use-package all-the-icons-dired :hook (dired-mode . all-the-icons-dired-mode)) (use-package all-the-icons-ivy-rich :after ivy-rich :config (all-the-icons-ivy-rich-mode 1)) (use-package treemacs :bind ("C-c S" . treemacs))
Window selection enhancements
(use-package windmove :bind (("H-w j" . windmove-down) ("H-w k" . windmove-up) ("H-w h" . windmove-left) ("H-w l" . windmove-right))) (use-package windswap :bind (("H-w J" . windswap-down) ("H-w K" . windswap-up) ("H-w H" . windswap-left) ("H-w L" . windswap-right)))
In buffer movement enhancements
Search
(use-package ctrlf :config (ctrlf-mode))
Improved in buffer search
(use-package ace-jump-mode :bind ("C-." . ace-jump-mode))
Go to last change in the current buffer
(use-package goto-chg :bind ("C-c G" . goto-last-change))
Editing enhancements
Context aware insertion of pairs of parenthesis
(use-package smartparens :hook ((prog-mode . smartparens-mode) (emacs-lisp-mode . smartparens-strict-mode)) :init (setq sp-base-key-bindings 'sp) :config (define-key smartparens-mode-map [M-backspace] #'backward-kill-word) (define-key smartparens-mode-map [M-S-backspace] #'sp-backward-unwrap-sexp) (require 'smartparens-config))
Edit with multiple cursors
(use-package multiple-cursors :bind (("C-c n" . mc/mark-next-like-this) ("C-c p" . mc/mark-previous-like-this)))
Fix trailing spaces but only in modified lines
(use-package ws-butler :hook (prog-mode . ws-butler-mode))
Switching buffers
Bind keys from H-r a
to H-r z
to switch to buffers from a
register from a
to z
.
(defalias 'pr #'point-to-register) (defun my-switch-to-register () "Switch to buffer given by key binding last character." (interactive) (let* ((v (this-command-keys-vector)) (c (aref v (1- (length v)))) (r (get-register c))) (if (and (markerp r) (marker-buffer r)) (switch-to-buffer (marker-buffer r)) (jump-to-register c)))) (setq my-switch-to-register-map (make-sparse-keymap)) (dolist (character (number-sequence ?a ?z)) (define-key my-switch-to-register-map (char-to-string character) #'my-switch-to-register)) (global-set-key (kbd "H-r") my-switch-to-register-map)
Switching tabs
(dolist (i (number-sequence 1 9)) (global-set-key (format "\C-c%d" i) `(lambda () (interactive) (tab-select ,i)))) (tab-bar-mode 1)
Yasnippet and abbrev mode
(setq-default abbrev-mode 1) (use-package yasnippet :defer 2 :config (yas-global-mode 1)) (use-package yasnippet-snippets :defer) (use-package ivy-yasnippet :bind ("C-c y" . ivy-yasnippet))
Programming modes
company and lsp-mode
For modes using company for tab completion add
(use-package company :bind (:map prog-mode-map ("C-i" . company-indent-or-complete-common) ("C-M-i" . counsel-company)) :hook (emacs-lisp-mode . company-mode)) (use-package company-prescient :after company :config (company-prescient-mode))
For modes that also use Language Server Protocol from lsp-mode add
(use-package lsp-mode :hook ((c-mode c++-mode d-mode elm-mode go-mode js-mode kotlin-mode python-mode typescript-mode vala-mode web-mode) . lsp) :init (setq lsp-keymap-prefix "H-l" lsp-rust-analyzer-proc-macro-enable t) :config (lsp-enable-which-key-integration t)) (use-package lsp-ui :init (setq lsp-ui-doc-position 'at-point lsp-ui-doc-show-with-mouse nil lsp-ui-sideline-show-code-actions t) :bind (("C-c A" . lsp-execute-code-action) ("C-c d" . lsp-ui-doc-show) ("C-c I" . lsp-ui-imenu))) (use-package flycheck :defer)
Compilation
(global-set-key "\C-ck" #'compile)
C and C++
Requires: company and lsp-mode, clangd.
(use-package cc-mode :straight nil :bind (:map c-mode-map ("C-i" . company-indent-or-complete-common) :map c++-mode-map ("C-i" . company-indent-or-complete-common)) :init (setq-default c-basic-offset 8))
D
Requires: company and lsp-mode, dmd
, dub
,
dcd, and
dtools (for rdmd
) to
build serve-d (but
first check for newer version) with
$ dub fetch serve-d@0.7.4
$ dub run serve-d --build=release
$ ln -s ~/.dub/packages/serve-d-0.7.4/serve-d/serve-d ~/.local/bin/
(use-package d-mode :bind (:map d-mode-map ("C-i" . company-indent-or-complete-common)))
Dart
Requires: company and lsp-mode, flutter
in PATH
,
flutter doctor
should be OK, and M-x lsp-dart-dap-setup
.
(use-package dart-mode) (use-package lsp-dart :hook (dart-mode . lsp) :bind (:map lsp-mode ("C-M-x" . lsp-dart-dap-flutter-hot-reload)))
Elm
Requires: company and lsp-mode.
(use-package elm-mode :hook (elm-mode . elm-indent-mode))
Emacs lisp
Requires: company and lsp-mode (actually just company
) and
smartparens.
Go
Requires: company and lsp-mode, gopls.
(defun my-go-before-save () "Format buffer and organize imports in Go mode." (when (eq major-mode 'go-mode) (lsp-organize-imports) (lsp-format-buffer))) (use-package go-mode :hook (before-save . my-go-before-save))
JavaScript
Requires: company and lsp-mode ts-ls
(proposed by lsp-mode
when missing).
(use-package js :straight nil :bind (:map js-mode-map ([remap js-find-symbol] . xref-find-definitions)) :init (setq js-indent-level 4))
Kotlin
Requires: kotlin-language-server.
(use-package kotlin-mode)
Lisp
(use-package sly :defer :config (setq inferior-lisp-program "sbcl" sly-mrepl-pop-sylvester nil) :custom-face (sly-mrepl-output-face ((t (:foreground "sienna"))))) (use-package paren-face :defer) (use-package paredit :defer) (use-package highlight-parentheses :defer) (defun my-lisp-mode-hook-fn () (smartparens-mode 0) (paredit-mode 1) (paren-face-mode 1) (highlight-parentheses-mode 1) (company-mode 1)) (add-hook 'lisp-mode-hook 'my-lisp-mode-hook-fn) (add-hook 'emacs-lisp-mode-hook 'my-lisp-mode-hook-fn)
Meson build system
(use-package meson-mode :defer :init (setq meson-indent-basic 4))
Python
Requires: company and lsp-mode, pylsp.
Rust
Requires: company and lsp-mode, rust-src
from Rustup
and rust-analyzer.
(use-package rustic :defer)
TypeScript
Requires: company and lsp-mode, web-mode settings (for tsx),
ts-ls
(proposed by lsp-mode when missing),
tsconfig.json.
(use-package typescript-mode :defer)
Vala
Requires: company and lsp-mode, vala-language-server.
(use-package vala-mode :defer)
Web mode
Requires: company and lsp-mode.
(use-package web-mode :mode "\\.\\([jt]sx\\)\\'")
Other modes
(use-package ansible :straight (:nonrecursive t)) (use-package devdocs :bind ("C-c D" . devdocs-lookup)) (use-package diff-hl :defer) (use-package htmlize :defer) (use-package rainbow-mode :hook css-mode) (use-package restclient :defer) (use-package vterm :defer :init (dolist (i (number-sequence 0 9)) (global-set-key (kbd (format "H-%d" i)) `(lambda () (interactive) (vterm ,i))))) (use-package yaml-mode :defer)
Git
(use-package magit :bind (("C-x g" . magit-status) ("C-x M-g" . magit-dispatch)) :init (setq project-switch-commands nil)) ; avoid magit error on C-n/C-p (use-package git-messenger :bind ("C-x G" . git-messenger:popup-message) :config (setq git-messenger:show-detail t git-messenger:use-magit-popup t)) (use-package git-timemachine :bind ("C-c t" . git-timemachine))
Appearance
Minimalistic look
(setq inhibit-startup-screen t) (set-scroll-bar-mode 'right) (menu-bar-mode 0) (tool-bar-mode 0) (scroll-bar-mode 0)
Mode line
(use-package smart-mode-line :config (setq sml/no-confirm-load-theme t sml/shorten-directory t sml/shorten-modes t sml/name-width 50 sml/mode-width 'full sml/theme 'respectful) (sml/setup))
Switching themes
(use-package base16-theme :defer) (use-package faff-theme :defer) (setq my-dark-theme 'base16-espresso my-light-theme 'base16-mexico-light) (defun my-select-theme (theme) (mapc #'disable-theme custom-enabled-themes) (load-theme (cond ((eq theme 'dark) my-dark-theme) ((eq theme 'light) my-light-theme) (t theme)) t) (sml/setup)) (defun my-select-theme-if-none-selected (frame) (if (and (eq 'x (window-system frame)) (null (seq-filter (lambda (theme) (not (string-prefix-p "smart-mode-line-" (symbol-name theme)))) custom-enabled-themes))) (my-select-theme 'dark))) (my-select-theme-if-none-selected nil) (add-to-list 'after-make-frame-functions #'my-select-theme-if-none-selected) (defun my-toggle-theme () "Toggle between dark and light themes." (interactive) (my-select-theme (if (custom-theme-enabled-p my-dark-theme) my-light-theme my-dark-theme))) (global-set-key (kbd "C-S-<f6>") #'my-toggle-theme)