58 KiB
Emacs config
This is my emacs configuration. It is structured as an org-mode file, to easily provide context and documentation.
Last export:
- Setup
- Early init
- Enable lexical binding
- Personal variables
- Garbage collector
- Increase process output buffer
- Package sources
- Bootstrap use-package
- Bootstrap straight.el and use-package [DISABLED]
- general.el
- Setup asdf [DISABLED]
- Set custom settings to load in temp file [DISABLED]
- Disable the customize interface
- Custom packages location
- Record key frequency
- Save minibuffer history
- Preferences
- Interface
- Programming
- Org
- Misc
Setup
Early init
This ends up in early-init.el
.
;; -*- lexical-binding: t; -*-
;; Prevent package.el from loading packages until we're ready for it
(setq package-enable-at-startup nil)
;; Don't resize the frame to preserve the number of columns or lines
;; being displayed when setting font, menu bar, tool bar, tab bar,
;; internal borders, fringes, or scroll bars. Since I use a tiling
;; window manager, this option is 1. useless anyways and 2. _terribly_ expensive.
(setq frame-inhibit-implied-resize t)
;; Suppress warnings and errors during asynchronous native compilation
(setq native-comp-async-report-warnings-errors nil)
;; Prevent unwanted runtime builds; packages are compiled ahead-of-time when
;; they are installed and site files are compiled when gccemacs is installed.
;; (setq comp-deferred-compilation nil)
(setq native-comp-deferred-compilation nil)
;; Prevent unwanted runtime builds in gccemacs (native-comp); packages are
;; compiled ahead-of-time when they are installed and site files are compiled
;; when gccemacs is installed.
(setq comp-deferred-compilation nil)
;; This should fix input lag by ~80% when using PGTK
(setq-default pgtk-wait-for-event-timeout 0)
Enable lexical binding
Setting lexical-binding
to t
can improve startup time. This has to be first!
;; -*- lexical-binding: t; -*-
Personal variables
This sets some variables with my personal preferences for easy customization
(defvar my/default-font "Monaspace Neon")
(defvar my/variable-width-font "Iosevka Aile")
(defvar my/comment-font "Monaspace Radon")
(defvar my/default-font-height 120)
(defvar my/default-font-weight 'light)
(defvar my/default-font-width 'normal)
(defvar my/variable-width-font-height 1.1)
(defvar my/config-file-path (expand-file-name "config.org" user-emacs-directory))
(defvar my/snippets-dir (expand-file-name "snippets" user-emacs-directory))
(defvar my/local-init-file (expand-file-name "local-init.el" user-emacs-directory))
(defvar my/leader "C-c")
And my personal info.
(setq user-full-name "Erwin Boskma"
user-mail-address "erwin@datarift.nl")
Garbage collector
Increasing the garbage collector threshold should also improve startup time. This increases it from 800 kB to 128MB
(setq gc-cons-threshold (* 128 1024 1024))
This resets the threshold back to it's default. This should not be done if lsp-mode
is enabled, it needs the higher threshold.
(add-hook 'after-init-hook
(lambda ()
(setq gc-cons-threshold 800000)
(message "gc-cons-threshold restored to %s"
gc-cons-threshold)))
Increase process output buffer
LSP responses can be rather large, in the 800KiB - 3MiB range. 2MiB is a decent value
(setq read-process-output-max (* 2 1024 1024))
Package sources
Add repositories where packages are installed from.
(setq package-archives '(("gnu" . "https://elpa.gnu.org/packages/")
("nongnu" . "https://elpa.nongnu.org/nongnu/")
("melpa" . "https://melpa.org/packages/")
("org" . "https://orgmode.org/elpa/")
("onpa" . "https://olanilsson.bitbucket.io/packages/")))
Bootstrap use-package
If use-package
is not installed, install it.
NOTE: Disabled because it is builtin since emacs 29
(require 'package)
(package-initialize)
(unless (package-installed-p 'use-package)
(package-refresh-contents)
(package-install 'use-package)
(cl-eval-when 'compile (require 'use-package)))
By default packages should always be installed from the package manager. This is equivalent to setting :ensure t
on each call to use-package
. To override this for a package (e.g. because it is builtin, or a subpackage), use :ensure nil
. This is done automatically for packages that use :load-path
.
(setq use-package-always-ensure t)
Bootstrap straight.el and use-package [DISABLED]
straight.el is a pure functional package manager and installs packages from git instead of downloading tars
(defvar bootstrap-version)
;; Workaround for flycheck. See https://github.com/radian-software/straight.el/issues/508 for more info
(setq straight-fix-flycheck t)
(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))
Install use-package
and make it use straight.el
by default.
(straight-use-package 'use-package)
(setq straight-use-package-by-default t)
general.el
general.el provides a more convenient way for binding keys in emacs. It also integrates with use-package
with the :general
keyword.
(use-package general
:demand t
:config
(progn
(general-define-key
:prefix my/leader
"a" 'org-agenda
"k" 'general-describe-keybindings)
(general-define-key
"C-M-z" 'zap-to-char
"M-z" 'zap-up-to-char)))
Setup asdf [DISABLED]
asdf is a tool to install and use multiple versions of development tools and programming languages.
(when (executable-find "asdf")
(use-package asdf-vm
:straight (:host github :repo "delonnewman/asdf-vm.el")
;; :load-path "~/.config/emacs/elisp/asdf-vm.el/"
:config
(asdf-vm-init)))
Set custom settings to load in temp file [DISABLED]
Setting custom-file
stops emacs from adding customised settings to init.el
. I prefer to specify everything in this file, so this creates a temporary file where the customisations are stored. This effectively localises customisations to a session
(setq custom-file (make-temp-file "emacs-custom"))
Disable the customize interface
The customize
functionality is annoying and messes up regularly. Stuff it has done so far:
- Clobber the path to
mix
in Elixir projects
This has been borrowed from Doom Emacs
(dolist (sym '(customize-option customize-browse customize-group customize-face
customize-rogue customize-saved customize-apropos
customize-changed customize-unsaved customize-variable
customize-set-value customize-customized customize-set-variable
customize-apropos-faces customize-save-variable
customize-apropos-groups customize-apropos-options
customize-changed-options customize-save-customized))
(put sym 'disabled (concat "This config doesn't support `customize', configure Emacs from " user-emacs-directory "/config.org instead")))
(put 'customize-themes 'disabled (concat "Use `load-theme' in " user-emacs-directory "/config.org instead"))
Custom packages location
Emacs only includes files directly under user-emacs-directory
. I like to keep custom packages in a separate elisp/
subdirectory.
(add-to-list 'load-path (expand-file-name "elisp/" user-emacs-directory))
Record key frequency
Records what keys are used the most, so I can see if I can optimise shortcuts
(use-package keyfreq
:config
(keyfreq-mode 1)
(keyfreq-autosave-mode 1))
Save minibuffer history
(use-package savehist
:ensure nil
:init
(savehist-mode))
Preferences
Don't display the help screen at startup
(setq inhibit-startup-screen t)
Enable line wrapping
(global-visual-line-mode 1)
Disable toolbar and scroll bar
(tool-bar-mode -1)
(menu-bar-mode -1)
(scroll-bar-mode -1)
Don't show the warnings buffer for anything lesser than an error.
(setq warning-minimum-level :error)
Keep buffers up-to-date automatically
(global-auto-revert-mode t)
Automatically scroll the compile buffer
(setq compilation-scroll-output 'first-error)
Always display line numbers, and the relative line number
(setq display-line-numbers-type 'relative)
(global-display-line-numbers-mode)
Show matching parenthesese
(show-paren-mode 1)
(setq show-paren-style 'expression)
Surround marked text with (), [] or {}
(electric-pair-mode 1)
Centralise backup files (the filename~
files), so they don't pollute the project.
(setq backup-directory-alist '((".*" . "~/.local/share/emacs/backup"))
backup-by-copying t ; Don't delink hardlinks
version-control t ; Use version numbers on backup files
delete-old-versions t ; Automatically delete obsolete backups
kept-new-versions 20 ; How many of the newest versions to keep
kept-old-versions 5 ; And how many of the oldest
)
Same for auto save files (the ones with the #
)
(setq auto-save-file-name-transforms '((".*" "~/.local/share/emacs/autosave/" t)))
Default indenting to spaces. If tabs are necessary, this can be set buffer-local. A single tab character can be added using C-q TAB
(setq-default indent-tabs-mode nil)
Show trailing whitespace
(setq show-trailing-whitespace t)
Delete trailing whitespace on save.
(add-hook 'before-save-hook 'delete-trailing-whitespace)
Sentences end in one space, not two. We're not using typewriters.
(setq sentence-end-double-space nil)
Don't move files to trash when deleting.
(setq delete-by-moving-to-trash nil)
Restore cursor position when re-opening a file
(save-place-mode t)
Prefer to open frames in a horizontal split and make sure they're of a decent width
(setq split-height-threshold nil
window-min-width 100)
Set fill column to 80
(setq-default fill-column 80)
Kill whole lines instead of clearing them
(setq kill-whole-line t)
Interface
Easy edit config file
Disabled because the configuration is handled by Nix using emacs-overlay
(defun find-config ()
"Edit config.org"
(interactive)
(find-file my/config-file-path))
(general-define-key
:prefix my/leader
"C" 'find-config)
COMMENT Appearance
Enable pixel scrolling.
(setq pixel-scroll-precision-mode t)
I like the dracula theme
(use-package dracula-theme
:init
(load-theme 'dracula t))
Monokai Pro is also pretty.
(use-package monokai-pro-theme
:init
(load-theme 'monokai-pro-spectrum t))
Set fonts.
(defun my/set-font-size (&optional frame)
(let* ((frame (or frame (selected-frame)))
(geometry (frame-monitor-attribute 'geometry frame))
(mm-size (frame-monitor-attribute 'mm-size frame))
(width-px (caddr geometry))
(width-mm (car mm-size))
(width-in (/ width-mm 25.4))
(display-dpi (/ width-px width-in))
(font-height (cond
((< display-dpi 110) 120)
((< display-dpi 130) 140)
((< display-dpi 160) 160)
(t 160))))
(set-face-attribute 'default frame :height font-height)))
(add-hook 'server-after-make-frame-hook 'my/set-font-size)
(add-hook 'after-make-frame-functions 'my/set-font-size)
(defun my/setup-fonts ()
(set-face-attribute 'default nil :family my/default-font :weight 'light)
(set-face-attribute 'font-lock-comment-face nil :font my/comment-font)
(set-face-attribute 'variable-pitch nil
:font my/variable-width-font
:height my/variable-width-font-height))
(add-hook 'after-init-hook 'my/setup-fonts)
Emoji support
(defun set-emoji-font ()
(when (member "Twitter Color Emoji" (font-family-list))
(set-fontset-font t 'emoji (font-spec :family "Twitter Color Emoji") nil 'prepend)))
(use-package emojify
:defer t
:init
(setq emojify-display-style 'unicode)
:config
(set-emoji-font)
:hook (server-after-make-frame . set-emoji-font))
Enable ligatures
(use-package ligature
:config
;; Enable all Iosevka ligatures in programming modes
;; (ligature-set-ligatures 'prog-mode '("<---" "<--" "<<-" "<-" "->" "-->" "--->" "<->" "<-->" "<--->" "<---->" "<!--"
;; "<==" "<===" "<=" "=>" "=>>" "==>" "===>" ">=" "<=>" "<==>" "<===>" "<====>" "<!---"
;; "<~~" "<~" "~>" "~~>" "::" ":::" "==" "!=" "===" "!=="
;; ":=" ":-" ":+" "<*" "<*>" "*>" "<|" "<|>" "|>" "+:" "-:" "=:" "<******>" "++" "+++"))
;; Ligatures for Monaspace
(ligature-set-ligatures 'prog-mode '(
; ss01
"==" "===" "=/=" "!=" "!==" "/=" "/==" "~~" "=~" "!~"
; ss02
">=" "<="
; ss03
"->" "<-" "=>" "<!--" "-->" "<~" "<~~" "~>" "~~>" "<~>"
; ss04
"</" "/>" "</>" "/\\" "\\/"
; ss05
"|>" "<|"
; ss06
"##" "###"
; ss07
"***" "/*" "*/" "/*/" "(*" "*)" "(*)"
; ss08
".=" ".-" "..<"
; dlig & calt
"<!" "**" "::" "=:" "=!" "=/" "--" ".." "//" "&&" "||" ":=" ":>" ":<" "!!" ">:" "<:" "#=" "?:" "?." "??" ";;" "///" ":::" "..." "=!=" "=:=" "..=" "..-"))
;; Enables ligature checks globally in all buffers. You can also do it
;; per mode with `ligature-mode'.
(global-ligature-mode t))
Add a dashboard on startup
(use-package dashboard
:after all-the-icons
:hook (dashboard-mode . (lambda ()
(setq show-trailing-whitespace nil)))
:config
(setq dashboard-set-navigator t
dashboard-center-content t
dashboard-set-file-icons t
dashboard-set-heading-icons t
dashboard-set-init-info t
dashboard-image-banner-max-height 250
dashboard-banner-logo-title "Bûter, brea en griene tsiis, wa dat net sizze kin is gjin oprjochte Fries."
dashboard-startup-banner (concat user-emacs-directory "images/ue-colorful.png")
dashboard-footer-icon (all-the-icons-octicon "dashboard"
:height 1.1
:v-adjust -0.05
:face 'font-lock-keyword-face))
(setq dashboard-navigator-buttons
`(((,(all-the-icons-octicon "search" :height 0.9 :v-adjust -0.1)
" Find file" nil
(lambda (&rest _) (find-file)) nil "" " C-x C-f"))
((,(all-the-icons-octicon "file-directory" :height 1.0 :v-adjust -0.1)
" Open project" nil
(lambda (&rest _) (projectile-switch-project)) nil "" " C-c p p"))
((,(all-the-icons-octicon "three-bars" :height 1.1 :v-adjust -0.1)
" File explorer" nil
(lambda (&rest _) (projectile-dired)) nil "" " C-c p D"))
((,(all-the-icons-octicon "settings" :height 0.9 :v-adjust -0.1)
" Open settings" nil
(lambda (&rest _) (find-config)) nil "" " C-c C "))))
(setq
dashboard-projects-backend 'projectile
dashboard-items '((recents . 5)
(projects . 5)
(registers . 5)))
(dashboard-setup-startup-hook)
;; Also show dashboard on new emacsclient window
(setq initial-buffer-choice (lambda ()
(get-buffer-create "*dashboard*")))
:custom-face
(dashboard-heading ((t (:weight bold)))))
Modeline
diminish hides modes from the modeline
(use-package diminish)
all-the-icons provides ALL the icons. Run `all-the-icons-install-fonts` after installing to download the actual font.
(use-package all-the-icons
:if (display-graphic-p))
minions adds a menu for minor modes to the modeline
(use-package minions
:config
(minions-mode 1))
Use doom-modeline for a nice and fancy modeline 2023-05-12 Disabled because it causes emacs to hang
(use-package doom-modeline
:init
(doom-modeline-mode 1)
:config
(setq doom-modeline-height 25
doom-modeline-bar-width 6
doom-modeline-lsp t
doom-modeline-github nil
doom-modeline-mu4e nil
doom-modeline-irc nil
doom-modeline-minor-modes t
doom-modeline-persp-name nil
doom-modeline-buffer-file-name-style 'truncate-except-project
doom-modeline-icon t)
:custom-face
(mode-line ((t (:height 0.85))))
(mode-line-inactive ((t (:height 0.85)))))
moody cleans up the modeline a bit so it is nicer to look at
(use-package moody
:config
(setq x-underline-at-descent-line t)
(moody-replace-mode-line-buffer-identification)
(moody-replace-vc-mode)
(moody-replace-eldoc-minibuffer-message-function))
Command completion
Ivy / Counsel
Disabled in favor of vertico
Ivy is a generic completion framework which uses the minibuffer. ivy GitHub page
(use-package ivy
:diminish t
:bind
<<ivy-binds>>
:config
<<ivy-config>>
)
Ivy config
(ivy-mode t)
(setq ivy-initial-inputs-alist nil
ivy-use-virtual-buffers nil)
(define-key read-expression-map (kbd "C-r") 'counsel-expression-history)
Ivy keybindings
("C-x s" . swiper)
("C-x C-r" . ivy-resume)
Counsel enhances emacs commands with ivy. counsel GitHub page
(use-package counsel
:bind
("M-x" . counsel-M-x)
("C-x C-m" . counsel-M-x)
("C-x C-f" . counsel-find-file)
("C-x c k" . counsel-yank-pop))
Company provides completion in the main buffer. company website
(use-package company
:diminish t
:hook
(after-init . global-company-mode))
Hydra allows you to group commands behind a custom prefix. hydra GitHub page
(use-package ivy-hydra)
major-mode-hydra
binds a single key to open a context sensitive hydra based on the current major mode. Hydras can be defined in use-package
definitions via the :mode-hydra
integration.
(use-package major-mode-hydra
:bind
("C-M-SPC" . major-mode-hydra)
:config
(major-mode-hydra-define org-mode
()
("Tools"
(("l" org-lint "lint")))))
Vertico / Corfu
vertico is a minimalistic completion UI based on the default completion system integrated in emacs.
(use-package vertico
:custom
(vertico-cycle t)
:init
(vertico-mode))
marginalia adds extra information to the minibuffer completions.
(use-package marginalia
:after vertico
:custom
(marginalia-annotaters '(marginalia-annotaters-heavy marginalia-annotaters-light nil))
:init
(marginalia-mode))
consult is an alternative to counsel for ivy, but for vertico.
(use-package consult
:general
("C-s" 'consult-line)
("C-x c k" 'consult-yank-pop)
:config
(setq consult-project-function (lambda ()
(when (fboundp 'projectile-project-root)
(projectile-project-root)))))
corfu enhances the completion at point function popups
(use-package corfu
;; :bind
;; ("TAB" . corfu-insert)
:custom
(corfu-cycle t)
(corfu-auto t)
:init
(global-corfu-mode))
orderless is a flexible completion style that allows flexible matching using literal strings, regex and more.
(use-package orderless
:init
(setq completion-styles '(orderless partial-completion basic)
;; completion-category-defaults nil
completion-category-overrides '((file (styles basic partial-completion)))))
Use corfu
with dabbrev
(included with emacs)
(use-package dabbrev
:ensure nil
:general
("M-/" 'dabbrev-completion)
("C-M-/" 'dabbrev-expand)
:custom
(dabbrev-ignored-buffer-regexps '("\\.\\(?:pdf\\|jpe?g\\|png\\)\\'")))
Misc
which-key shows suggestions when you type an incomplete command and wait for a bit (1 second by default). which-key GitHub page
(use-package which-key
:defer 0
:diminish which-key-mode
:config
(which-key-mode)
(setq which-key-idle-delay 1))
Possible candidates
The following packages look interesting, and are worth investigating further:
- avy: quick text navigation
- ace-window: window navigation
- expand-region: context aware text selection
- cape: A set of completion at point extensions
File tree
treemacs is Emacs' equivalent of neovim's NeoTree or vim's NERDTree.
(use-package treemacs
:defer t
:init
(with-eval-after-load 'winum
(define-key winum-keymap (kbd "M-0") #'treemacs-select-window))
:general
("M-0" 'treemacs-select-window)
(:prefix "C-c"
"t 1" 'treemacs-delete-other-windows
"t t" 'treemacs
"t d" 'treemacs-select-directory
"t B" 'treemacs-bookmark
"t C-t" 'treemacs-find-file
"t M-t" 'treemacs-find-tag)
:config
(progn
(treemacs-project-follow-mode)))
Enable integration with projectile
(use-package treemacs-projectile
:after (treemacs projectile))
Add icons
(use-package treemacs-icons-dired
:hook (dired-mode . treemacs-icons-dired-enable-once))
Enable magit
integration
(use-package treemacs-magit
:after (treemacs magit))
Programming
General settings
Auto balance parenthesese. Note: Disabled in favour of electric-pair-mode
(use-package smartparens
:ghook 'prog-mode-hook)
Rainbow delimiters FTW!
(use-package rainbow-delimiters
:ghook 'prog-mode-hook)
Paredit is a minor mode for editing parentheses
(use-package paredit
:hook (emacs-lisp-mode . paredit-mode)
(racket-mode . paredit-mode)
(racket-repl-mode . paredit-mode)
:general
(:keymaps 'paredit-mode-map
"{" 'paredit-open-curly
"}" 'paredit-close-curly
"M-[" 'paredit-wrap-square
"M-{" 'paredit-wrap-curly))
Project management
Projectile works great to manage projects. It includes fuzzy search and supports jumping between files (e.g. .h <-> .cpp). projectile GitHub page
(use-package projectile
:diminish projectile-mode
:config
;; (setq projectile-completion-system 'ivy)
(projectile-mode)
:general
(:prefix "C-c"
"p" '(:keymap projectile-command-map))
:init
(setq projectile-switch-project-action #'projectile-dired
projectile-project-search-path '("~/workspace" "~/workspace/horus" "~/workspace/horus/web" "~/workspace/horus/horus-vr")))
There is also an integration with counsel.
(use-package counsel-projectile
:after projectile
:general
("C-SPC" 'counsel-projectile-switch-project)
:config
(counsel-projectile-mode))
Enable ripgrep support
(use-package rg
:after projectile
:config
(rg-enable-default-bindings))
Git
Magit. Obviously. magit website
(use-package magit
:config
;; (setq magit-completing-read-function 'ivy-completing-read)
:general
(:prefix my/leader
"g s" 'magit-status
"g x" 'magit-checkout
"g c" 'magit-commit
"g p" 'magit-push
"g u" 'magit-pull
"g e" 'magit-ediff-resolve
"g r" 'magit-rebase-interactive
"g i" 'magit-init))
transient can be used to create command dispatchers. Magit uses it to easily add options to git commands, and it displays a nice popup with the possible flags and commands. transient manual
(use-package transient)
Visualise git changes in the gutter, next to the line numbers git-gutter GitHub page
(use-package git-gutter
:diminish t
:ghook
'prog-mode-hook
'org-mode-hook
:config
(setq git-gutter:update-interval 0.02))
(use-package git-gutter-fringe
:config
(define-fringe-bitmap 'git-gutter-fr:added [224] nil nil '(center repeated))
(define-fringe-bitmap 'git-gutter-fr:modified [224] nil nil '(center repeated))
(define-fringe-bitmap 'git-gutter-fr:deleted [128 192 224 240] nil nil 'bottom))
Show inline git-blame with blamer.el. Inspired by VS Code's Git Lens
(use-package blamer
:after bind-key
:bind (("C-c C-i" . blamer-show-commit-info)
("C-c i" . blamer-show-posframe-commit-info))
:config
(global-blamer-mode 1))
Syntax checking and highlighting
Flycheck
Flycheck
is a general purpose syntax highlighting framework that provides hooks for other packages and an improvement of the builtin flymake
.
website
(use-package flycheck
:diminish t
:init
(global-flycheck-mode))
Tree-sitter
tree-sitter is a new development in parsing and syntax highlighting. It has been merged into Emacs 29, but until that's released we're using the emacs-tree-sitter package while on Emacs 28.
(when (< emacs-major-version 29)
(use-package tree-sitter
:config
(global-tree-sitter-mode)
:ghook
('tree-sitter-after-on-hook #'tree-sitter-hl-mode)))
tree-sitter-langs provides tree-sitter
support for a bunch of languages.
(use-package tree-sitter-langs
:after tree-sitter)
Automatically use the <lang>-ts-mode
when it is available
(use-package treesit-auto
:config
(setq treesit-auto-install 'prompt)
(global-treesit-auto-mode))
lsp-mode
lsp-mode adds Language Server Protocol support to emacs.
(use-package lsp-mode
:init
(setq lsp-keymap-prefix "C-c l"
lsp-use-plists nil)
(defun eb/lsp-mode-setup-completion ()
(setf (alist-get 'styles (alist-get 'lsp-capf completion-category-defaults))
'(orderless)))
:hook ((conf-toml-mode
python-mode
sh-mode) . lsp-deferred)
(lsp-mode . lsp-enable-which-key-integration)
(lsp-completion-mode . eb/lsp-mode-setup-completion)
:commands
(lsp lsp-deferred)
:custom
(lsp-completion-provider :none) ;; I'm using corfu
:config
(setq lsp-typescript-surveys-enabled nil
lsp-completion-enable t
lsp-enable-suggest-server-download nil))
lsp-ui provides higher level UI elements for lsp-mode
, like code lenses and flycheck support.
(use-package lsp-ui
:ghook 'lsp-mode-hook
:config
(setq lsp-ui-doc-enable t
lsp-ui-peek-enable t
lsp-ui-sideline-enable t
lsp-ui-imenu-enable t)
:custom
(lsp-ui-doc-position 'bottom))
lsp-ivy integrates ivy
into lsp-mode
(use-package lsp-ivy
:commands lsp-ivy-workspace-symbol)
consult-lsp integrates consult
into lsp-mode
(use-package consult-lsp)
lsp-treemacs provides an integration between lsp-mode
and treemacs.
(use-package lsp-treemacs
:commands lsp-treemacs-errors-list)
dap-mode
dap-mode provides debugging facilities using the Debug Adapter Protocol
(use-package dap-mode
:defer t
:custom
(dap-auto-configure-mode t)
(dap-auto-configure-features '(sessions locals breakpoints expressions tooltip))
:config
(require 'dap-lldb)
(require 'dap-cpptools)
(setq dap-lldb-debugged-program-function (lambda () (read-file-name "Select program executable to debug")))
(dap-register-debug-template "C++ LLDB"
(list :type "lldb-vscode"
:dap-server-path (executable-find "lldb-vscode")
:cwd nil
:args nil
:request "launch"
:program nil))
(defun dap-debug-create-or-edit-json-template ()
"Edit C++ debugging configuration or create and edit if none exists"
(interactive)
(let ((filename (concat (lsp-workspace-root) "/launch.json"))
(default (concat user-emacs-directory "/default-launch.json")))
(unless (file-exists-p filename)
(copy-file default filename))
(find-file-existing filename))))
eglot
eglot is an alternative to lsp-mode
that is builtin with emacs >= 29
(use-package eglot
:config
(fset #'json--log-event #'ignore) ;; Performance boost by not logging every event
(add-to-list 'eglot-server-programs
'(conf-toml-mode . ("taplo" "lsp" "stdio")))
(add-to-list 'eglot-server-programs
'((elixir-mode elixir-ts-mode heex-ts-mode) . ("elixir-ls")))
(add-to-list 'eglot-stay-out-of 'flymake)
(setq eglot-autoshutdown t
eldoc-echo-area-use-multiline-p 0.1)
:hook
(eglot-managed-mode . (lambda ()
(eglot-inlay-hints-mode 1)
(define-key eglot-mode-map (kbd "C-c l a") 'eglot-code-actions)
(define-key eglot-mode-map (kbd "C-c l f") 'eglot-format)
(define-key eglot-mode-map (kbd "C-c l h") 'eldoc)
(define-key eglot-mode-map (kbd "C-c l i") 'eglot-find-implementation)
(define-key eglot-mode-map (kbd "C-c l r") 'eglot-rename)
(define-key eglot-mode-map (kbd "C-c l t") 'eglot-find-typeDefinition)
(define-key eglot-mode-map (kbd "C-c l w d") 'eglot-list-connections)
(define-key eglot-mode-map (kbd "C-c l w r") 'eglot-reconnect)
(define-key eglot-mode-map (kbd "C-c l w q") 'eglot-shutdown)
(define-key eglot-mode-map (kbd "C-c l y") 'eglot-inlay-hints-mode))))
eglot-x adds support for some LSP extensions to eglot
(use-package eglot-x
:vc (:fetcher github :repo nemethf/eglot-x)
:after eglot
:config
(eglot-x-setup))
consult-eglot adds an integration between consult
and eglot
(use-package consult-eglot)
Snippets
Snippets are predefined pieces of code that can be inserted and filled in. YASnippet uses syntax inspired by TextMate is the most popular, for good reason.
(use-package yasnippet
:init
(load "yasnippet.el")
:diminish t
:general
(:keymaps 'yas-minor-mode-map
"<tab>" nil
"TAB" nil
"<C-tab>" 'yas-expand)
:config
(add-to-list 'yas-snippet-dirs my/snippets-dir)
(yas-global-mode))
(use-package yasnippet-snippets)
Languages
JavaScript / TypeScript
Indent 2 spaces
(setq-default js-indent-level 2
typescript-indent-level 2)
js2-mode improves a lot on the builtin js-mode
(use-package js2-mode
:after eglot
:mode
("\\.mjs\\'" . js2-mode)
("\\.jsx?\\'" . js2-jsx-mode)
:hook
(js2-mode . eglot-ensure)
(js2-jsx-mode . eglot-ensure))
Prettier has my preference for formatting JavaScript and TypeScript
(use-package prettier
:config
(setq prettier-enabled-parsers '
(css html json markdown scss svelte toml typescript vue)))
TypeScript stuff
(use-package typescript-mode
:after eglot
:mode
("\\.tsx?\\'" . typescript-mode)
:hook (typescript-mode . eglot-ensure))
Prefer local packages from node_modules
to global ones
(use-package add-node-modules-path)
Web mode
web-mode handles HTML/CSS and JavaScript
(use-package web-mode
:after eglot
:config
(setq web-mode-markup-indent-offset 2
web-mode-css-indent-offset 2
web-mode-code-indent-offset 2
web-mode-enable-auto-pairing t
web-mode-enable-css-colorization t
web-mode-enable-current-element-highlight t
web-mode-enable-current-column-highlight t)
(add-to-list 'web-mode-engines-alist '(("elixir" . "\\.html.heex\\'")
("jinja2" . "\\.jinja2\\'")
("python" . "\\.pt\\'")))
:hook
((html-mode css-mode web-mode) . eglot-ensure))
Markdown
markdown-mode adds support for Markdown editing. gfm-mode
supports GitHub Flavoured Markdown.
(use-package markdown-mode
:after eglot
:mode
(("README\\.md\\'" . gfm-mode)
("\\.md\\'" . markdown-mode)
("\\.markdown\\'" . markdown-mode))
:init
(setq markdown-command "multimarkdown")
:hook
(markdown-mode . display-fill-column-indicator-mode)
(markdown-mode . eglot-ensure))
impatient-mode live renders HTML, but it can be made to work with Markdown with a custom filter
(use-package impatient-mode
:after markdown-mode
:config
(imp-set-user-filter 'markdown-filter))
(defun markdown-filter (buffer)
(princ
(with-temp-buffer
(let ((tmpname (buffer-name)))
(set-buffer buffer)
(set-buffer (markdown tmpname)) ; the function markdown is in `markdown-mode.el'
(buffer-string)))
(current-buffer)))
Elixir
Add support for Elixir with elixir-mode. The elixir-format
hook sets up the correct formatter configuration when in a projectile
project.
(use-package elixir-mode
:after eglot
:hook ((elixir-format . (lambda ()
(if (projectile-project-p)
(setq elixir-format-arguments
(list "--dot-formatter"
(concat (locate-dominating-file buffer-file-name ".formatter.exs") ".formatter.exs")))
(setq elixir-format-arguments nil))))
(elixir-mode . (lambda () (add-hook 'before-save-hook 'elixir-format nil t)))
(elixir-mode . eglot-ensure))
:config
;; (setq lsp-elixir-server-command '("elixir-ls"))
(add-to-list 'auto-mode-alist '("\\.[hl]eex\\'" . elixir-mode)))
Add a mix minor mode to call mix
tasks from emacs.
(use-package mix
:hook
(elixir-mode . mix-minor-mode))
Erlang
(use-package erlang
:mode
("\\.P\\'" . erlang-mode)
("\\.E\\'" . erlang-mode)
("\\.S\\'" . erlang-mode)
:config
(require 'erlang-start))
Rust
Rust support with rust-mode.
(use-package rust-mode
:after eglot
:hook
(rust-mode . eglot-ensure)
(rust-ts-mode . eglot-ensure)
(before-save . eglot-format-buffer)
;; :init
;; (setq lsp-rust-analyzer-cargo-watch-command "clippy"
;; lsp-rust-analyzer-server-display-inlay-hints t
;; lsp-rust-analyzer-binding-mode-hints t
;; lsp-rust-analyzer-display-lifetime-elision-hints-enable "always")
)
Configure rust-analyzer
(defun eb/ra-eglot-config (server)
"initializationOptions for rust-analyzer"
`(:diagnostics (:enable t)
:imports (:granularity (:enforce :json-false :group "crate")
:group t :merge
(:glob t)
:prefix "plain")
:lruCapacity nil
:checkOnSave (:enable t
:command "clippy"
:allTargets t)
:inlayHints (:bindingModeHints t
:chainingHints t
:lifetimeElisionHints (:enable "always" :useParameterNames :json-false)
:maxLength nil
:parameterHints :json-false
:renderColons t
:typeHints (:enable t
:hideClosureInitialization :json-false
:hideNamedConstructor :json-false))
:procMacro (:enable t)))
(with-eval-after-load 'eglot
(add-to-list 'eglot-server-programs
'(rust-mode . ("rust-analyzer" :initializationOptions eb/ra-eglot-config))))
Add cargo support
(use-package cargo
:hook
(rust-mode . cargo-minor-mode))
Flycheck support for Rust with flycheck-rust
(use-package flycheck-rust
:hook
(flycheck-mode . flycheck-rust-setup))
TOML
Support for TOML files with toml-mode
(use-package toml-mode
:mode ("\\.toml\\'" . conf-toml-mode))
Docker
Add docker support with dockerfile-mode
(use-package dockerfile-mode
:after eglot
:hook (dockerfile-mode . eglot-ensure))
Bitbake / Yocto
(use-package bitbake-modes
:straight (:type git :repo "https://bitbucket.org/olanilsson/bitbake-modes.git"))
INI
ini-mode
provides highlighting for ini files
(use-package ini-mode)
JSON
json-mode extends the builtin js-mode
with better syntax highlighting for JSON and adds some editing keybindings
(use-package json-mode
:after eglot
:hook (json-mode . eglot-ensure))
CMake
Add cmake-mode
(use-package cmake-mode
:after eglot
:hook (cmake-mode . eglot-ensure))
YAML
Use yaml-mode to handle YAML files
(use-package yaml-mode
:after eglot
:hook (yaml-mode . eglot-ensure))
C/C++
Enable clangd LSP for C and C++
(use-package cc-mode
:ensure nil
:after eglot
:hook
((c-mode c-ts-mode) . eglot-ensure)
((c++-mode c++-ts-mode) . eglot-ensure))
Enable and configure auto-insert-mode
for Horus projects
(defconst my/generate-cpp-file-executable
"~/workspace/horus/development/code-generation/generate-cpp-file.py"
"Python program to generate a C++ boilerplate")
(when (file-executable-p my/generate-cpp-file-executable)
(define-auto-insert
"\\.[ch]pp\\'"
(lambda nil (call-process my/generate-cpp-file-executable nil t nil buffer-file-name))))
(auto-insert-mode)
Meson
meson is a build system designed to be as fast and as user-friendly as possible.
(use-package meson-mode)
nix
Add nix-mode
(use-package nix-mode
:after eglot
:mode "\\.nix\\'"
:hook (nix-mode . eglot-ensure))
Tell nil
to use nixpkgs-fmt
for formatting nix files.
(with-eval-after-load 'eglot
(add-to-list 'eglot-server-programs
`(nix-mode . ("nil" :initializationOptions
(:formatting (:command ["nixpkgs-fmt"]))))))
Common Lisp
Common Lisp does not use lsp-mode
, but has it's own environment: SLIME or Superior Lisp Interaction Mode for Emacs.
(use-package slime
:init
(setq slime-lisp-implementations
'((sbcl ("sbcl" "--load ~/.quicklisp/setup.lisp") :coding-system utf-8-unix))
slime-default-lisp 'sbcl)
:config
(slime-setup '(slime-fancy slime-quicklisp slime-asdf))
:mode
(("\\.cl\\'" . lisp-mode))
:hook
(lisp-mode . (lambda () (slime-mode t)))
(inferior-lisp-mode . (lambda () (inferior-slime-mode t))))
SLY is a fork of SLIME, by the same author as eglot
, with improved UX
(use-package sly)
Clojure
Similar to lisp
, there is CIDER (Clojure Interactive Development Environment that Rocks) for Clojure(Script)
(use-package cider)
Terraform
terraform-mode is a major mode for Terraform files
(use-package terraform-mode
:after eglot
:hook (terraform-mode . eglot-ensure))
Register terraform-ls
with eglot
(with-eval-after-load 'eglot
(add-to-list 'eglot-server-programs
'(terraform-mode . ("terraform-ls" "serve"))))
Fish
fish-mode provides a major mode for fish shell scripts
(use-package fish-mode
:init
(setq fish-enable-auto-indent t))
Zig
zig-mode provides a major mode for the zig programming language
(use-package zig-mode
:after eglot
:hook (zig-mode . eglot-ensure))
Yuck
(use-package yuck-mode)
Racket
(use-package racket-mode
:after eglot
:hook (racket-mode . eglot-ensure))
Ruby
Let ruby-mode
handle Ruby files
(use-package ruby-mode
:ensure nil
:after eglot
:hook (ruby-mode . eglot-ensure))
MapServer
Add highlighting for MapServer .map files
(use-package mapserver-mode
:straight (:type git :host github :repo "AxxL/mapserver-emacs-mode")
:mode ("\\.map\\'" . mapserver-mode))
Go
It's better than nothing.
(use-package go-mode
:after eglot
:hook (go-mode . eglot-ensure))
Cucumber
feature-mode provides support for user stories written in Cucumber/Gherkin format.
(use-package feature-mode
:config
(setq feature-use-docker-compose nil)
:mode "\\.feature\\'")
Protobuf
(use-package protobuf-mode)
Just
Python
Python
(use-package python-mode
:hook
((python-mode python-ts-mode) . eglot-ensure))
(use-package python
:ensure nil
:hook (python-base-mode . eglot-ensure)
:init (setq python-indent-guess-indent-offset nil))
jinja2
(use-package jinja2-mode)
Haskell
haskell-mode for Haskell files
(use-package haskell-mode
:hook
(((haskell-mode haskell-ts-mode) . eglot-ensure)
turn-on-haskell-unicode-input-method))
Dhall
Dhall is a programmable configuration language that you can think of as: JSON + functions + types + imports
(use-package dhall-mode
:mode "\\.dhall\\'")
nushell
nushell is a new type of shell that operates on typed data
(use-package nushell-ts-mode)
Register nushell
LSP with eglot
(with-eval-after-load 'eglot
(add-to-list 'eglot-server-programs
'(nushell-ts-mode . ("nu" "--lsp"))))
Org
Main org setup
org-mode configuration
(use-package org
:general
<<org-binds>>
:custom
<<org-customisations>>
:hook
<<org-hooks>>
:config
<<org-config>>)
Some keybindings for often used functions
(:prefix my/leader
"r" 'org-capture
"a" 'org-agenda
"s" 'org-store-link
"L" 'org-store-link-global
"O" 'org-open-at-point-global)
Customisations
(org-directory "~/org")
(org-log-done t)
(org-indent-indentation-per-level 2)
(org-startup-indented t)
(org-log-into-drawer t)
(org-default-notes-file (expand-file-name "notes.org" org-directory))
(org-return-follows-link t)
(org-pretty-entities t)
(org-hide-emphasis-markers t)
(org-startup-with-inline-images t)
(org-startup-with-latex-previews t)
(org-image-actual-width '(300))
Hooks
((org-mode . org-indent-mode)
(org-mode . turn-on-visual-line-mode)
(org-mode . variable-pitch-mode)
(org-mode-indent . (lambda () (diminish 'org-indent-mode)))
(org-agenda-mode . (lambda () (hl-line-mode 1))))
Configuration
(add-to-list 'auto-mode-alist '("\\.org\\'" . org-mode))
(dolist (face '((org-level-1 . 1.2)
(org-level-2 . 1.1)
(org-level-3 . 1.05)
(org-level-4 . 1.0)
(org-level-5 . 1.0)
(org-level-6 . 1.0)
(org-level-7 . 1.0)
(org-level-8 . 1.0)))
(set-face-attribute (car face) nil :family my/variable-width-font :weight 'medium :height (cdr face)))
(set-face-attribute 'org-document-title nil :family my/variable-width-font :weight 'bold :height 1.3)
(set-face-attribute 'org-block nil :foreground nil :inherit 'fixed-pitch)
(set-face-attribute 'org-table nil :inherit 'fixed-pitch)
(set-face-attribute 'org-formula nil :inherit 'fixed-pitch)
(set-face-attribute 'org-code nil :inherit '(shadow fixed-pitch))
(set-face-attribute 'org-verbatim nil :inherit '(shadow fixed-pitch))
(set-face-attribute 'org-special-keyword nil :inherit '(font-lock-comment-face fixed-pitch))
(set-face-attribute 'org-checkbox nil :inherit 'fixed-pitch)
org-capture
allows creating and saving quick notes, links and TODOs
(use-package org-capture
;; :straight org
:ensure org
:after org
:config
(setq org-capture-templates
'(("t" "Todo" entry (file+headline "gtd.org")
"* TODO %?\n %i\n %a")
("n" "Note" entry (file+olp+datetree "notes.org")
"* %?\nEntered on %U\n %i\n %a")
("N" "Note (work)" entry (file+olp+datetree "work/notes.org")
"* %?\nEntered on %U\n %i\n %a")
("c" "org-protocol-capture" entry (file+headline "inbox.org" "Links")
"* %^{Title}\n\n[[%:link][%:description]]\n\n %i"
:immediate-finish t))))
org-datetree
allows you to organise captures by date. It is basically a set of headings representing the date, with the first level for the year, the second level for the month and the third for the day.
(use-package org-datetree
;; :straight org
:ensure org
:after org)
org-protocol
can be used to send data to emacs using org-protocol://
URLs.
(use-package org-protocol
;; :straight org
:ensure org
:after org
:config
(setq org-protocol-default-template-key "c"))
org-roam
org-roam helps with non-hierarchical note-taking. It uses the zettelkasten method to capture and organise notes and ideas.
(use-package org-roam
;; :after org
:custom
(org-roam-directory "~/org-roam")
(org-roam-completion-everywhere t)
<<org-roam-templates>>
:config
(require 'org-roam-dailies)
(org-roam-db-autosync-mode)
:general
(:prefix my/leader
:keymaps 'global
"n f" 'org-roam-node-find
"n r" 'org-roam-node-random
"n i" 'org-roam-node-insert
"n l" 'org-roam-buffer-toggle
:keymaps 'org-mode-map
"n o" 'org-id-get-create
"n t" 'org-roam-tag-add
"n a" 'org-roam-alias-add
"n l" 'org-roam-buffer-toggle)
("C-M-i" 'completion-at-point
:keymaps 'org-roam-dailies-map
"Y" 'org-roam-dailies-capture-yesterday
"T" 'org-roam-dailies-capture-tomorrow)
(:prefix my/leader "n d" '(:keymap org-roam-dailies-map)))
Org roam capture templates
(org-roam-capture-templates '(("d" "default" plain
"%?"
:if-new (file+head "%<%Y%m%d%H%M%S>-${slug}.org" "#+title: ${title}\n#+date: %U\n")
:unnarrowed t)
("w" "work note" plain
"%?"
:if-new (file+head "%<%Y%m%d%H%M%S>-${slug}.org" "#+title: ${title}\n#+filetags: :work:\n")
:unnarrowed t)
("p" "project" plain
"* Goals\n\n%?\n\n* Tasks\n\n** TODO Add initial tasks\n\n* Dates\n\n"
:if-new (file+head "%<%Y%m%d%H%M%S>-${slug}.org" "#+title: ${title}\n#+filetags: :project:\n")
:unnarrowed t)))
org-roam-ui provides a frontend to explore and interact with org-roam
notes. It can be started with M-x org-roam-ui-mode RET
, and is then available on http://127.0.0.1:35901/. Updates are sent real-time through a WebSocket.
The settings provided here are also the defaults, set any to nil
to disable.
(use-package org-roam-ui
:after org-roam
:config
(setq org-roam-ui-sync-theme t
org-roam-ui-follow t
org-roam-ui-update-on-save t
org-roam-ui-open-on-start t))
org-chef
org-chef is used to manage recipes and import them from supported websites
(use-package org-chef
:after org-capture
:config
(add-to-list 'org-capture-templates '("r" "Recipes"))
(add-to-list 'org-capture-templates '("rr" "Recipe" entry (file "~/org/cookbook.org")
"%(org-chef-get-recipe-from-url)"
:empty-lines 1))
(add-to-list 'org-capture-templates '("rm" "Manual recipe" entry (file "~/org/cookbook.org")
"* %^{Recipe title: }\n :PROPERTIES:\n :source-url:\n :servings:\n :prep-time:\n :cook-time:\n :ready-in:\n :END:\n** Ingredients\n %?\n** Directions\n\n")))
Misc
direnv
Writing
Enable flyspell
(use-package flyspell
:ensure nil
:init
(setq ispell-list-command "--list")
:hook
(text-mode . flyspell-mode))
(use-package flyspell-correct
:after flyspell
:general
(:keymaps 'flyspell-mode-map
"C-;" 'flyspell-correct-wrapper))
Use ivy for flyspell suggestions. Use M-o
to switch to the actions (e.g. saving a word to your dictionary).
(use-package flyspell-correct-ivy
:after flyspell-correct)
Load local init file
If local-init.el
exists, load it.
(when (file-readable-p my/local-init-file)
(load my/local-init-file))
DO NOT ADD ANYTHING BELOW THIS