My personal emacs configuration
Find a file
2026-03-31 19:04:31 +02:00
external feat: Add gptel-cody git-submodule 2026-03-31 18:05:05 +02:00
snippets add(snippets): Add kaboom snippet for c-mode 2024-12-21 19:54:33 +01:00
.gitignore chore: update gitignore with compile elpa files, org-roam-db, ... 2025-08-14 11:44:38 +02:00
.gitmodules feat: Add gptel-cody git-submodule 2026-03-31 18:05:05 +02:00
config.org feat: add emulate-a-terminal 2026-03-31 19:04:31 +02:00
early-init.el early-init: inhibit startup message + fullscreen + avoid flashbang 2026-03-31 18:59:11 +02:00
init.el fix: liniting errors on init.el 2025-05-27 12:55:07 +02:00
README.org Add readme 2018-06-12 21:13:07 +02:00

My Emacs

General config

  (use-package emacs
    :ensure nil
    :custom
    (inhibit-startup-message t)
    :init
    (with-current-buffer (get-buffer-create "*scratch*")
      (insert (format ";;
  ;; ██╗  ██╗███████╗██╗     ██╗      ██████╗
  ;; ██║  ██║██╔════╝██║     ██║     ██╔═══██╗
  ;; ███████║█████╗  ██║     ██║     ██║   ██║
  ;; ██╔══██║██╔══╝  ██║     ██║     ██║   ██║
  ;; ██║  ██║███████╗███████╗███████╗╚██████╔╝
  ;; ╚═╝  ╚═╝╚══════╝╚══════╝╚══════╝ ╚═════╝
  ;;
  ;; ███████╗███╗   ███╗ █████╗  ██████╗███████╗
  ;; ██╔════╝████╗ ████║██╔══██╗██╔════╝██╔════╝
  ;; █████╗  ██╔████╔██║███████║██║     ███████╗
  ;; ██╔══╝  ██║╚██╔╝██║██╔══██║██║     ╚════██║
  ;; ███████╗██║ ╚═╝ ██║██║  ██║╚██████╗███████║
  ;; ╚══════╝╚═╝     ╚═╝╚═╝  ╚═╝ ╚═════╝╚══════╝
  ;;
  ;;   Loading time : %s
  ;;   Packages     : %s
  ;;
  "
		      (emacs-init-time)
		      (number-to-string (length package-activated-list)))))

    (message (emacs-init-time))
    )

Scratch buffer

(setopt initial-major-mode 'fundamental-mode)  ; default mode for the *scratch* buffer

Mode line

Declutter mode line by removing load-average:

(setopt display-time-default-load-average nil) ; this information is useless for most

Right-click behaviour

Make right-click do something sensible and open the context-menu.

(when (display-graphic-p)
  (context-menu-mode))

Package repos

(with-eval-after-load 'package
  (add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t))

Use-package

Always ensure

(require 'use-package-ensure)
(setq use-package-always-ensure t)

Bell

The audible bell is annoying AF.

(setq visible-bell 1)

Enable column numbers

(setq column-number-mode 1)

Whitespace cleanup

(use-package whitespace
  :ensure nil
  :hook (before-save-hook . delete-trailing-whitespace)
  ;; if we wanna remove this hook at any time, eval:
  ;; (remove-hook 'before-save-hook #'whitespace-cleanup)
  )

Save history and recent files

;; The built-in `savehist-mode' saves minibuffer histories.  Vertico
;; can then use that information to put recently selected options at
;; the top.
;;
;; Further reading: https://protesilaos.com/emacs/dotemacs#h:25765797-27a5-431e-8aa4-cc890a6a913a
(savehist-mode 1)

;; The built-in `recentf-mode' keeps track of recently visited files.
;; You can then access those through the `consult-buffer' interface or
;; with `recentf-open'/`recentf-open-files'.
;;
;; I do not use this facility, because the files I care about are
;; either in projects or are bookmarked.
(recentf-mode 1)

Backups

(defvar myrmi-backup-directory (concat user-emacs-directory "backups"))
(if (not (file-exists-p myrmi-backup-directory))
    (make-directory myrmi-backup-directory t)
  )
(setq backup-directory-alist `(("." . ,myrmi-backup-directory)))
(setq make-backup-files t
      backup-by-copying t
      version-control t
      delete-old-versions t
      delete-by-moving-to-trash t
      kept-old-versions 6
      kept-new-versions 9
      auto-save-default t
      auto-save-timeout 20
      auto-save-interval 200
  )

Yes-or-no

Because I'm lazy, important yes-or-no questions can be answered with y-or-n:

(defalias 'yes-or-no-p 'y-or-n-p)

Switch windows

(global-set-key (kbd "M-o") 'other-window)

ibuffer

Use list-buffers bigger brother.

(global-set-key [remap list-buffers] 'ibuffer)

Eldoc

(use-package eldoc
  :ensure nil
  :init
  (global-eldoc-mode))

Isearch

Inspired by emacs-solo:

(use-package isearch
  :ensure nil
  :config
  (setq isearch-lazy-count t)                ; Display number of matches
  (setq lazy-count-prefix-format "(%s/%s) ") ; eye-candy to add braces
  (defun isearch-copy-selected-word ()
    "Copy the current `isearch` selection to the kill ring."
    (interactive)
    (when isearch-other-end
      (let ((selection (buffer-substring-no-properties isearch-other-end (point))))
	(kill-new selection)
	(isearch-exit))))

  ;; Bind `M-w` in isearch to copy the selected word, so M-s M-. M-w
  ;; does a great job of 'copying the current word under cursor'.
  (define-key isearch-mode-map (kbd "M-w") 'isearch-copy-selected-word))

Reference that might be interesting for later: https://endlessparentheses.com/leave-the-cursor-at-start-of-match-after-isearch.html

Flymake

(use-package flymake
  :ensure nil
  :hook
  (prog-mode-hook . flymake-mode)
  :custom
  (flymake-show-diagnostics-at-end-of-line 'short)
  (flymake-indicator-type 'margins)
  (flymake-margin-indicators-string
   `((error "!" compilation-error)
     (warning "?" compilation-warning)
     (note "i" compilation-info))
   )
  )

Which-func

Show function we are currently in in the mode-line.

(use-package which-func
  :ensure nil
  :custom
  (which-func-display 'mode-and-header)
  :hook
  (prog-mode-hook . which-function-mode)
  )

Line-numbers

Show line numbers.

(global-display-line-numbers-mode t)

Abbrev

(global-set-key [remap dabbrev-expand] 'hippie-expand)

Zap

(global-set-key (kbd "M-S-z") 'zap-up-to-char)

Spell checking

Look into customizing the 'ispell' group.

(add-hook 'prog-mode-hook 'flyspell-prog-mode)

Delete selection mode

(delete-selection-mode t)

Enable disabled commands

Some commands are disabled to protect the user. Narrow-region/page is a really handy feature, enable it:

(put 'narrow-to-page 'disabled nil)
(put 'narrow-to-region 'disabled nil)

Adaptive cursor width

Make cursor the width of the character it is under f.e. full width of a tab.

(setq x-stretch-cursor t)

Auto-revert

;; Automatically reread from disk if the underlying file changes
(setopt auto-revert-avoid-polling t)
;; Some systems don't do file notifications well; see
;; https://todo.sr.ht/~ashton314/emacs-bedrock/11
(setopt auto-revert-interval 5)
(setopt auto-revert-check-vc-info t)

(global-auto-revert-mode t)

Enable mouse right-click

;; Make right-click do something sensible
(when (display-graphic-p)
  (context-menu-mode))

Keybindings

;; Move through windows with Ctrl-<arrow keys>
(windmove-default-keybindings 'control)

Which-key

Shows a popup of available keybindings when typing a long key sequence (e.g. C-x …).

(use-package which-key
  :ensure nil
  :config
  (which-key-mode))

Minibuffer

(setopt enable-recursive-minibuffers t) ; Use the minibuffer whilst in the minibuffer

Highlight line mode

Specify list of modes to highlight the current line with.

(let ((hl-line-hooks '(text-mode-hook prog-mode-hook)))
  (mapc (lambda (hook) (add-hook hook 'hl-line-mode)) hl-line-hooks))

Resize-mode

Minor-mode to easily resize frames (works with EXWM (firefox, …)). Courtesy goes to kuanyui (https://gist.github.com/kuanyui/65a408d393871048771c):

;;; resize-frame.el --- A minor mode to resize frames easily.  -*- lexical-binding: t; -*-

;; Copyright (C) 2014  kuanyui

;; Author: kuanyui <azazabc123@gmail.com>
;; Keywords: frames, tools, convenience
;; License: WTFPL 1.0

;;; Commentary:

;; Press "ESC `" and use arrow-keys or i/j/k/l to adjust frames. press any key to done.

;;; Code:

(defvar resize-frame-map
  (let ((map (make-keymap)))
    (define-key map (kbd "<up>") 'enlarge-window)
    (define-key map (kbd "<down>") 'shrink-window)
    (define-key map (kbd "<right>") 'enlarge-window-horizontally)
    (define-key map (kbd "<left>") 'shrink-window-horizontally)
    (set-char-table-range (nth 1 map) t 'resize-frame-done)
    (define-key map (kbd "C-p") 'enlarge-window)
    (define-key map (kbd "C-n") 'shrink-window)
    (define-key map (kbd "C-f") 'enlarge-window-horizontally)
    (define-key map (kbd "C-b") 'shrink-window-horizontally)
    map))

(define-minor-mode resize-frame
  "A simple minor mode to resize-frame.
C-c C-c to apply."
  ;; The initial value.
  :init-value nil
  ;; The indicator for the mode line.
  :lighter " ResizeFrame"
  ;; The minor mode bindings.
  :keymap resize-frame-map
  :global t
  (if (<= (length (window-list)) 1)
      (progn (setq resize-frame nil)
	     (message "Only root frame exists, abort."))
      (message "Use arrow-keys or C-p/n/f/b to adjust frames.")))

(defun resize-frame-done ()
  (interactive)
  (setq resize-frame nil)
  (message "Done."))

(global-set-key (kbd "C-x C-r") 'resize-frame)

Completion

Minibuffer

(use-package vertico
  :custom
  ;; (vertico-scroll-margin 0) ;; Different scroll margin
  ;; (vertico-count 20) ;; Show more candidates
  ;; (vertico-resize t) ;; Grow and shrink the Vertico minibuffer
  (vertico-cycle t) ;; Enable cycling for `vertico-next/previous'
  :hook (after-init . vertico-mode)
  )

Consult

(use-package consult
  ;; Replace bindings. Lazily loaded by `use-package'.
  :bind (;; C-c bindings in `mode-specific-map'
	 ;; ("C-c M-x" . consult-mode-command)
	 ;; ("C-c h" . consult-history)
	 ;; ("C-c k" . consult-kmacro)
	 ;; ("C-c m" . consult-man)
	 ;; ("C-c i" . consult-info)
	 ([remap Info-search] . consult-info)
	 ;; C-x bindings in `ctl-x-map'
	 ("C-x M-:" . consult-complex-command)     ;; orig. repeat-complex-command
	 ("C-x b" . consult-buffer)                ;; orig. switch-to-buffer
	 ("C-x 4 b" . consult-buffer-other-window) ;; orig. switch-to-buffer-other-window
	 ("C-x 5 b" . consult-buffer-other-frame)  ;; orig. switch-to-buffer-other-frame
	 ("C-x t b" . consult-buffer-other-tab)    ;; orig. switch-to-buffer-other-tab
	 ("C-x r b" . consult-bookmark)            ;; orig. bookmark-jump
	 ("C-x p b" . consult-project-buffer)      ;; orig. project-switch-to-buffer
	 ;; Custom M-# bindings for fast register access
	 ;; ("M-#" . consult-register-load)
	 ("M-'" . consult-register-store)          ;; orig. abbrev-prefix-mark (unrelated)
	 ;; ("C-M-#" . consult-register)
	 ;; Other custom bindings
	 ("M-y" . consult-yank-pop)                ;; orig. yank-pop
	 ;; M-g bindings in `goto-map'
	 ("M-g e" . consult-compile-error)
	 ("M-g f" . consult-flymake)               ;; Alternative: consult-flycheck
	 ("M-g g" . consult-goto-line)             ;; orig. goto-line
	 ("M-g M-g" . consult-goto-line)           ;; orig. goto-line
	 ;; ("M-g o" . consult-outline)               ;; Alternative: consult-org-heading
	 ;; ("M-g m" . consult-mark)
	 ;; ("M-g k" . consult-global-mark)
	 ("M-i" . consult-imenu)
	 ("M-I" . consult-imenu-multi)
	 ;; M-s bindings in `search-map'
	 ("M-s d" . consult-find)                  ;; Alternative: consult-fd
	 ;; ("M-s c" . consult-locate)
	 ("M-s g" . consult-grep)
	 ;; ("M-s G" . consult-git-grep)
	 ;; ("M-s r" . consult-ripgrep)
	 ("M-s l" . consult-line)
	 ;; ("M-s L" . consult-line-multi)
	 ;; ("M-s k" . consult-keep-lines)
	 ;; ("M-s u" . consult-focus-lines)
	 ;; Isearch integration
	 ("M-s e" . consult-isearch-history)
	 :map isearch-mode-map
	 ("M-e" . consult-isearch-history)         ;; orig. isearch-edit-string
	 ("M-s e" . consult-isearch-history)       ;; orig. isearch-edit-string
	 ("M-s l" . consult-line)                  ;; needed by consult-line to detect isearch
	 ("M-s L" . consult-line-multi)            ;; needed by consult-line to detect isearch
	 ;; Minibuffer history
	 :map minibuffer-local-map
	 ("M-s" . consult-history)                 ;; orig. next-matching-history-element
	 ("M-r" . consult-history)                 ;; orig. previous-matching-history-element
	 )

  ;; Enable automatic preview at point in the *Completions* buffer. This is
  ;; relevant when you use the default completion UI.
  :hook (completion-list-mode . consult-preview-at-point-mode)

  ;; The :init configuration is always executed (Not lazy)
  :init

  ;; Optionally configure the register formatting. This improves the register
  ;; preview for `consult-register', `consult-register-load',
  ;; `consult-register-store' and the Emacs built-ins.
  ;; (setq register-preview-delay 0.5
	;; register-preview-function #'consult-register-format)

  ;; Optionally tweak the register preview window.
  ;; This adds thin lines, sorting and hides the mode line of the window.
  ;; (advice-add #'register-preview :override #'consult-register-window)

  ;; Use Consult to select xref locations with preview
  (setq xref-show-xrefs-function #'consult-xref
	xref-show-definitions-function #'consult-xref)

  ;; Configure other variables and modes in the :config section,
  ;; after lazily loading the package.
  ;; :config

  ;; Optionally configure preview. The default value
  ;; is 'any, such that any key triggers the preview.
  ;; (setq consult-preview-key 'any)
  ;; (setq consult-preview-key "M-.")
  ;; (setq consult-preview-key '("S-<down>" "S-<up>"))
  ;; For some commands and buffer sources it is useful to configure the
  ;; :preview-key on a per-command basis using the `consult-customize' macro.
  ;; (consult-customize
   ;; consult-theme :preview-key '(:debounce 0.2 any)
   ;; consult-ripgrep consult-git-grep consult-grep
   ;; consult-bookmark consult-recent-file consult-xref
   ;; consult--source-bookmark consult--source-file-register
   ;; consult--source-recent-file consult--source-project-recent-file
   ;; :preview-key "M-."
   ;; :preview-key '(:debounce 0.4 any))

  ;; Optionally configure the narrowing key.
  ;; Both < and C-+ work reasonably well.
  ;; (setq consult-narrow-key "<") ;; "C-+"

  ;; Optionally make narrowing help available in the minibuffer.
  ;; You may want to use `embark-prefix-help-command' or which-key instead.
  ;; (keymap-set consult-narrow-map (concat consult-narrow-key " ?") #'consult-narrow-help)
)

In-buffer completion

Consult

(setq completion-in-region-function
      (lambda (&rest args)
	(apply (if vertico-mode
		   #'consult-completion-in-region
		 #'completion--in-region)
	       args)))

Corfu

(use-package corfu
  ;; Optional customizations
  :bind (:map corfu-map ("<tab>" . corfu-complete))
  :config
  (setq tab-always-indent 'complete)
  :custom
  (corfu-cycle t)                ;; Enable cycling for `corfu-next/previous'
  (corfu-auto t)                 ;; Enable auto completion
  ;; (corfu-separator ?\s)          ;; Orderless field separator
  ;; (corfu-quit-at-boundary nil)   ;; Never quit at completion boundary
  ;; (corfu-quit-no-match nil)      ;; Never quit, even if there is no match
  ;; (corfu-preview-current nil)    ;; Disable current candidate preview
  ;; (corfu-preselect 'prompt)      ;; Preselect the prompt
  ;; (corfu-on-exact-match nil)     ;; Configure handling of exact matches
  ;; (corfu-scroll-margin 5)        ;; Use scroll margin

  ;; Enable Corfu only for certain modes. See also `global-corfu-modes'.
  ;; :hook ((prog-mode . corfu-mode)
  ;;        (shell-mode . corfu-mode)
  ;;        (eshell-mode . corfu-mode))

  ;; Recommended: Enable Corfu globally.  This is recommended since Dabbrev can
  ;; be used globally (M-/).  See also the customization variable
  ;; `global-corfu-modes' to exclude certain modes.
  :init
  (global-corfu-mode))

Orderless

(use-package orderless
  :demand t
  :custom
  (completion-styles '(orderless basic))
  ;;    (gnus-completion-styles '(orderless substring basic))
  ;;    (completion-category-overrides '((file (styles basic partial-completion))))

;; Below not necessary if using vertico
;;  (completion-category-overrides '(
;;                                   (command (styles orderless basic partial-completion))
;;                                   (file (styles orderless basic partial-completion))
;;;;                                   (buffer (styles orderless basic))
;;                                   (variable (styles orderless basic))
;;                                   (symbol (styles orderless basic))
;;                                   (consult-location (styles orderless))
;;                                   (consult-multi (styles orderless))
;;                                   )
;;                                 )
)

Marginalia

;; Enable rich annotations using the Marginalia package
(use-package marginalia
  ;; Bind `marginalia-cycle' locally in the minibuffer.  To make the binding
  ;; available in the *Completions* buffer, add it to the
  ;; `completion-list-mode-map'.
  :bind (:map minibuffer-local-map
	   ("M-A" . marginalia-cycle))

  ;; The :init section is always executed.
  :init
  ;; Marginalia must be activated in the :init section of use-package such that
  ;; the mode gets enabled right away. Note that this forces loading the
  ;; package.
  (marginalia-mode))

Dired

Dired-x

(with-eval-after-load 'dired
  (require 'dired-x)
  ;; Set dired-x global variables here.  For example:
  ;; (setq dired-x-hands-off-my-keys nil)
  )

(add-hook 'dired-mode-hook
	  (lambda ()
	    ;; Set dired-x buffer-local variables here.  For example:
	    ;; (dired-omit-mode 1)
	    ))

Guess target directory

I currently prefer to have two dired windows open in the same frame. Instruct dired to 'Prefer next windows on the same frame' when renaming/copying files.

(setq dired-dwim-target 'dired-dwim-target-next)

Whole-line-or-region

Source: https://github.com/purcell/whole-line-or-region

Operate on the current line if no region is active.

(use-package whole-line-or-region
  :config
  (whole-line-or-region-global-mode 1)
)

Terminal

Eat: Emulate-A-Terminal

For more info: https://codeberg.org/akib/emacs-eat

(use-package eat
  :custom
  (eat-term-name "xterm")
  :config
  (eat-eshell-mode)                     ; use Eat to handle term codes in program output
  (eat-eshell-visual-command-mode))     ; commands like less will be handled by Eat

Eshell

Smart mode

Plan 9 smart terminal features, for more info: https://www.masteringemacs.org/article/complete-guide-mastering-eshell

(require 'eshell)
(require 'em-smart)
(setq eshell-where-to-jump 'begin)
(setq eshell-review-quick-commands nil)
(setq eshell-smart-space-goes-to-end t)

(add-hook 'eshell-mode-hook 'eshell-smart-initialize)

Toggle between char- and line-mode

Courtesy goes to https://joelmccracken.github.io/entries/switching-between-term-mode-and-line-mode-in-emacs-term/

(require 'term)

(defun jnm/term-toggle-mode ()
  "Toggles term between line mode and char mode"
  (interactive)
  (if (term-in-line-mode)
      (term-char-mode)
    (term-line-mode)))

(define-key term-mode-map (kbd "C-c C-j") 'jnm/term-toggle-mode)
(define-key term-mode-map (kbd "C-c C-k") 'jnm/term-toggle-mode)

(define-key term-raw-map (kbd "C-c C-j") 'jnm/term-toggle-mode)
(define-key term-raw-map (kbd "C-c C-k") 'jnm/term-toggle-mode)

For the keybindings, we have to defien them in both raw and line mode. From the help page of term mode: If you define custom keybindings, make sure to assign them to the correct keymap (or to both): use term-raw-map in raw mode and term-mode-map in line mode.

Eat: Emulate A Terminal

;; Eat: Emulate A Terminal
(use-package eat
  :ensure t
  :custom
  (eat-term-name "xterm")
  :config
  (eat-eshell-mode)                     ; use Eat to handle term codes in program output
  (eat-eshell-visual-command-mode))     ; commands like less will be handled by Eat

Theme

(use-package monokai-theme
  :config
    (load-theme 'monokai t)
)

Dashboard

(use-package dashboard
  :custom
  (dashboard-center-content t)      ;; Center content
  (dashboard-icon-type 'nerd-icons) ;; Nerd icons used
  (dashboard-set-heading-icons t)   ;; Heading icons enabled
  (dashboard-set-file-icons t)      ;; File icons enabled
  (dashboard-startup-banner 'logo)  ;; Use alternative logo
  :config
  (dashboard-setup-startup-hook))

Hydra

https://github.com/abo-abo/hydra

(use-package hydra
  :config
  ;; Zoom hydra
  (defhydra hydra-zoom (global-map "<f1>")
    "zoom"
    ("g" text-scale-increase "in")
    ("l" text-scale-decrease "out")
    )

  )

Zygospore

Revert C-x 1 by pressing C-x 1 again: https://github.com/louiskottmann/zygospore.el

FYI: At one point, used this together with sr-speedbar. They did not play well together…

(use-package zygospore
  :config
    (global-set-key (kbd "C-x 1") 'zygospore-toggle-delete-other-windows)
)

Iedit

Highlight occurences of symbol and replace them simultanously. Shortkey: C-;

https://github.com/victorhge/iedit

(use-package iedit)

Programming

Angry faces

(defface highlight-angry-faces
  '(
    (default :background "Yellow" :foreground "Red")
    )
  "Angry faces highlighting."
  :group 'basic-faces
)

(mapc (lambda (mode)
	(font-lock-add-keywords
	 mode
	 '(
	   ("\\<\\(FIXME\\)" 1 'highlight-angry-faces t)
	   ("\\<\\(TODO\\)" 1 'highlight-angry-faces t)
	   )))
      '(text-mode emacs-lisp-mode rust-mode zig-mode c-ts-mode c-mode prog-mode)
)

Electric pair

(use-package elec-pair
  :ensure nil
  :hook (prog-mode-hook . electric-pair-mode))

Paren

(use-package paren
  :ensure nil
  :hook (after-init-hook . show-paren-mode)
  :custom
  (show-paren-delay 0)
  (show-paren-style 'mixed)
  (show-paren-context-when-offscreen t)) ;; show matches within window splits

Eglot

(use-package eglot
  :ensure nil
  :custom
  (eglot-autoshutdown t)
  :init
  (setq eglot-stay-out-of '(xref))
  (add-hook 'prog-mode-hook 'eglot-ensure)
  (add-hook 'eglot-managed-mode-hook (lambda ()
				       (if (eglot-managed-p)
					   (add-hook 'xref-backend-functions 'eglot-xref-backend)
					 (remove-hook 'xref-backend-functions 'eglot-xref-backend)
					 )))

  )

Markdown-mode

(use-package markdown-mode
)

Yasnippet

(use-package yasnippet
  :hook
  (prog-mode . yas-minor-mode)
  (org-mode . yas-minor-mode)
  (text-mode . yas-minor-mode)
  :config
    (yas-reload-all)
)

Magit

Core

(use-package magit
  )

Dumb-jump

(use-package dumb-jump
  :init
  (add-hook 'xref-backend-functions #'dumb-jump-xref-activate)
)

C-programming

Tree-sitter

(add-to-list 'major-mode-remap-alist '(c-mode . c-ts-mode))

Compilation

Goto end of buffer on completion

Compilation output is almost always bigger than a normal buffer. Move to the end if the compilation finishes.

(defun goto-end-compilation-buffer (comp-buffer msg)
  (goto-char (point-max))
  )

(add-hook 'compilation-finish-functions #'goto-end-compilation-buffer)

Rust

(use-package rust-mode
  :init
  (setq rust-mode-treesitter-derive t))

Zig

(use-package zig-mode
  )

Python

(use-package python-mode
  )

Multiple cursors

(use-package multiple-cursors
  :bind
    ("C-x r a" . mc/edit-beginnings-of-lines)
    ("C-x r e" . mc/edit-ends-of-lines)
    ("C->" . mc/mark-next-like-this)
    ("C-<" . mc/mark-previous-like-this)
    ("C-c C->" . mc/mark-all-like-this)
)

Volatile highlights

Show/highlight changes when doing undo/yanks/kills/…

https://github.com/k-talo/volatile-highlights.el

(use-package volatile-highlights
  :config
    (volatile-highlights-mode t)
)

Comment-dwim-2

Replacement for built-in comment-dwim, more comment features.

https://github.com/remyferre/comment-dwim-2

(use-package comment-dwim-2
  :bind
  ("M-;" . comment-dwim-2)
)

Org

General config

(use-package org
  :ensure nil
  :mode ("\\.org\\'" . org-mode)
  :config
  (setq
   ;; Start collapsed for speed
   org-startup-folded t
   ;; Use ={}= for subscripting: https://orgmode.org/manual/Subscripts-and-superscripts.html
   org-use-sub-superscripts '{}
   ;; Preserve indentation in SRC blocks
   org-src-preserve-indentation t
   )
  ;; Ellipsis styling
  (setq org-ellipsis " ▼ ")
  (set-face-attribute 'org-ellipsis nil :inherit 'default :box nil))

Org-todo

Mark parent entry as DONE when children are DONE

(defun org-summary-todo (n-done n-not-done)
  "Switch entry to DONE when all subentries are done, to TODO otherwise."
  (let (org-log-done org-todo-log-states)   ; turn off logging
    (org-todo (if (= n-not-done 0) "DONE" "TODO"))))

(add-hook 'org-after-todo-statistics-hook #'org-summary-todo)

Org bullets

(use-package org-bullets
  :after org
  :config
    (add-hook 'org-mode-hook (lambda () (org-bullets-mode))))

Org Roam

(use-package org-roam
  :demand
  :custom
  (org-roam-directory "~/projects/notes")
  (org-roam-completion-everywhere t)
  :config
  (org-roam-setup)
  (setq org-roam-node-display-template (concat "${title:*} " (propertize "${tags}" 'face 'org-tag)))
  (org-roam-db-autosync-mode)
  ;; Add todo lists to org-agenda
  (custom-set-variables '(org-agenda-files (directory-files-recursively org-roam-directory "todo\\.org$")))
  (load (expand-file-name "init.el" org-roam-directory) :no-error-if-file-is-missing)
)

Consult

(use-package consult-org-roam
   :ensure t
   :after org-roam
   :init
   (require 'consult-org-roam)
   ;; Activate the minor mode
   (consult-org-roam-mode 1)
   ;; :custom
   ;; Use `ripgrep' for searching with `consult-org-roam-search'
   ;; (consult-org-roam-grep-func #'consult-ripgrep)

   ;; Configure a custom narrow key for `consult-buffer', default is 'n', this sets it to 'r'
   ;; (consult-org-roam-buffer-narrow-key ?r)

   ;; Display org-roam buffers right after non-org-roam buffers
   ;; in consult-buffer (and not down at the bottom)
   ;; (consult-org-roam-buffer-after-buffers t)
   :config
   ;; Eventually suppress previewing for certain functions
   (consult-customize
    consult-org-roam-forward-links
    :preview-key "M-.")
   :bind
   ;; Define some convenient keybindings as an addition
   ("C-c n f" . consult-org-roam-file-find)
   ("C-c n b" . consult-org-roam-backlinks)
   ("C-c n B" . consult-org-roam-backlinks-recursive)
   ("C-c n l" . consult-org-roam-forward-links)
   ("C-c n s" . consult-org-roam-search)
   ("C-c n d" . org-roam-dailies-goto-today)
   )

Org Download

(use-package org-download
  :after org
  :config
  (add-hook 'dired-mode-hook 'org-download-enable)
  )

Version control

We only care about git (and forced to use svn sometimes). This should speed up TRAMP as well.

(setq vc-handled-backends '(SVN Git))

TRAMP

These are settings recommended to make tramp go faster. Inspired by: https://www.gnu.org/software/tramp/#Frequently-Asked-Questions-1

;; Make sure tramp cache is kept
(setopt tramp-persistency-file-name (expand-file-name "cache/tramp" user-emacs-directory))

;; Assume files are updated only in TRAMP
(setq remote-file-name-inhibit-cache t)

;; Normally, Emacs keeps auto-save files locally, but assume we don't want to auto-save files in TRAMP
(setq remote-file-name-inhibit-auto-save t
      remote-file-name-inhibit-auto-save-visited t)

;; Disable directory-local variables for remote files
(setq enable-remote-dir-locals nil)

;; Assume only one emacs session is editing a file
(setq remote-file-name-inhibit-locks t)

;; Use an external method, such as scp, which are faster than internal methods for large file.
(setq tramp-use-scp-direct-remote-copying t)

Make it go brrrr

Inspired by: https://coredumped.dev/2025/06/18/making-tramp-go-brrrr./

Limit inline copy to 2MB

(setq tramp-copy-size-limit (* 1024 1024) ;; 1MB
      tramp-verbose 2)

Use async processes

This fixes 'magit' on TRAMP: https://coredumped.dev/2025/06/18/making-tramp-go-brrrr./#use-direct-async

(connection-local-set-profile-variables
 'remote-direct-async-process
 '((tramp-direct-async-process . t)))

(connection-local-set-profiles
 '(:application tramp :protocol "scp")
 'remote-direct-async-process)

(setq magit-tramp-pipe-stty-settings 'pty)

Fix remote compile

(with-eval-after-load 'tramp
  (with-eval-after-load 'compile
    (remove-hook 'compilation-mode-hook #'tramp-compile-disable-ssh-controlmaster-options)))

Disable functionality while using TRAMP

Several modes/functionality depend on external programs being present to be fast enough for practical use. We disable these when using TRAMP to make TRAMP faster and usable. F.e. When connecting to a remote server, it might not have a language server installed. So we disable 'eglot' to not have to wait until it errors out. We disable:

  • eglot
(defun myrmi/eglot-ensure-advice (orig-fun &rest args)
  "Only run eglot-ensure if not on a remote file."
  (unless (file-remote-p default-directory)
    (apply orig-fun args)))

(advice-add 'eglot-ensure :around #'myrmi/eglot-ensure-advice)
  • project
(defun myrmi/project-current-advice (orig-fun &rest args)
  "Don't detect projects for remote files."
  (unless (file-remote-p default-directory)
    (apply orig-fun args)))

(advice-add 'project-current :around #'myrmi/project-current-advice)
  • projectile
(defun myrmi/disable-projectile-for-tramp ()
  "Disable projectile-mode for remote files."
  (when (and (fboundp 'projectile-mode)
             (file-remote-p default-directory))
    (projectile-mode -1)))

(add-hook 'find-file-hook #'myrmi/disable-projectile-for-tramp)

Elisp

Add demos to describe-function

(use-package elisp-demos
  :config
  (advice-add 'describe-function-1 :after #'elisp-demos-advice-describe-function-1)
  )

LLM

gptel

(use-package gptel
  :ensure t
  )

Cody

Cody provides a gptel integration: https://github.com/sourcegraph/gptel-cody

However, it's not on melpa or anything yet so we have to load it manually:

(add-to-list 'load-path (expand-file-name "external/gptel-cody" user-emacs-directory))
(use-package gptel-cody
  :ensure nil ;; Load from local path
  :after gptel
  :config
  (require 'auth-source)
  (when-let* ((auth (car (auth-source-search :host "cody-api" :max 1)))
              (host (plist-get auth :user))
              (secret (plist-get auth :secret)))
    (setq gptel-backend (gptel-make-cody "Cody"
                          :host host
                          :key (if (functionp secret)
                                   (funcall secret)
                                 secret)))))

Custom

Modeline

(setq-default mode-line-format
	      '("%e" "  "
		;; (:propertize " " display (raise +0.1)) ;; Top padding
		;; (:propertize " " display (raise -0.1)) ;; Bottom padding
		(:propertize "𝝮  " face font-lock-keyword-face)

		(:propertize
		 ("" mode-line-mule-info mode-line-client mode-line-modified mode-line-remote mode-line-window-dedicated))

		mode-line-frame-identification
		mode-line-buffer-identification
		"   "
		mode-line-position
		mode-line-format-right-align
		"  "
		(project-mode-line project-mode-line-format)
		"  "
		(vc-mode vc-mode)
		"  "
		mode-line-modes
		mode-line-misc-info
		mode-line-end-spaces)
	      project-mode-line t
	      mode-line-buffer-identification '(" %b")
	      mode-line-position-column-line-format '(" %l:%c"))

Org-roam

Inspired by https://github.com/org-roam/org-roam/wiki/User-contributed-Tricks#filter-by-a-tag .

(defun myrmi/org-roam-node-find-tag-filter ()
  "Select a single tag from list and filter `org-roam-node' by it."
  (interactive)
  (let ((tag (car (completing-read-multiple "Tag: "
					    (org-roam-tag-completions)))))
    (org-roam-node-find nil nil
			(lambda (node)
			  (member tag
				  (org-roam-node-tags node))))))

Font

'Inspired' by https://protesilaos.com/codelog/2024-11-28-basic-emacs-configuration/#h:1e4fde73-a2a2-4dc5-82ad-02cf3884ece6 .

(let ((mono-spaced-font "Monospace")
      (proportionately-spaced-font "Sans"))
  (set-face-attribute 'default nil :family mono-spaced-font :height 100)
  (set-face-attribute 'fixed-pitch nil :family mono-spaced-font :height 1.0)
  (set-face-attribute 'variable-pitch nil :family proportionately-spaced-font :height 1.0))

Icon fonts

To make this setup work, the user must type M-x and then call the command 'nerd-icons-install-fonts'. This will store the icon font files in a local directory (on Linux this is ~/.local/share/fonts).

(use-package nerd-icons
  :ensure t)

(use-package nerd-icons-completion
  :ensure t
  :after marginalia
  :config
  (add-hook 'marginalia-mode-hook #'nerd-icons-completion-marginalia-setup))

(use-package nerd-icons-corfu
  :ensure t
  :after corfu
  :config
  (add-to-list 'corfu-margin-formatters #'nerd-icons-corfu-formatter))

(use-package nerd-icons-dired
  :ensure t
  :hook
  (dired-mode . nerd-icons-dired-mode))

Sudo current buffer

(defun myrmi/sudo-current-buffer ()
  "Use TRAMP to `sudo' the current buffer."
  (interactive)
  (when buffer-file-name
    (find-alternate-file
      (concat "/sudo:root@localhost:"
	buffer-file-name)
    )
  )
)

Save symbol at point

(defun myrmi/save-symbol-at-point ()
  "Make symbol at point the latest kill in the kill ring."
  (interactive)
  (let ((symbol (thing-at-point 'symbol)))
    (when symbol (kill-new symbol))))

(global-set-key (kbd "C-M-w") 'myrmi/save-symbol-at-point)

Ceedling

(defvar ceedling-project-file-name "project.yml")
(defvar ceedling-cmd "ceedling")
(defvar ceedling-project-root ".")

(defun myrmi/run-ceedling-tests (&optional file-name)
  (interactive)
  (let* (
	  (file-path (or file-name buffer-file-name))
	  (root-path (or (locate-dominating-file file-path ceedling-project-file-name) ceedling-project-root))
	)
    (compile
     (concat "cd " root-path " && " ceedling-cmd)
     )
    )
  )

Set path to shell path

(defun set-exec-path-from-shell-PATH ()
  (let ((path-from-shell
	   (replace-regexp-in-string "[[:space:]\n]*$" ""
	  (shell-command-to-string "$SHELL -l -c 'echo $PATH'"))))
    (setenv "PATH" path-from-shell)
    (setq exec-path (split-string path-from-shell path-separator))))

(set-exec-path-from-shell-PATH)

Reload dir-locals.el

(defun myrmi/reload-dir-locals-for-current-buffer ()
  "Reload dir locals for the current buffer"
  (interactive)
  (let ((enable-local-variables :all))
    (hack-dir-local-variables-non-file-buffer)))

(defun myrmi/reload-dir-locals-for-all-buffers-in-this-directory ()
  "For every buffer with the same `default-directory` as the
   current buffer, reload dir-locals."
  (interactive)
  (let ((dir default-directory))
    (dolist (buffer (buffer-list))
      (with-current-buffer buffer
	(when (equal default-directory dir)
	  (myrmi/reload-dir-locals-for-current-buffer))))))

Visit/reload config

These snippets assume my-config-file variable is set outside this configuration. This should normally be done by the init.el to load this configuration.

(defun myrmi/visit-config ()
  "Visit emacs config"
  (interactive)
  (find-file my-config-file))

(defun myrmi/reload-config ()
  "Reload emacs config at runtime"
  (interactive)
  (org-babel-load-file my-config-file))

Minibuffer

Close minibuffer when pressing C-g

'Inspired' by https://protesilaos.com/codelog/2024-11-28-basic-emacs-configuration/#h:1e4fde73-a2a2-4dc5-82ad-02cf3884ece6 .

(defun myrmi/keyboard-quit-dwim ()
  "Do-What-I-Mean behaviour for a general `keyboard-quit'.

The generic `keyboard-quit' does not do the expected thing when
the minibuffer is open.  Whereas we want it to close the
minibuffer, even without explicitly focusing it.

The DWIM behaviour of this command is as follows:

- When the region is active, disable it.
- When a minibuffer is open, but not focused, close the minibuffer.
- When the Completions buffer is selected, close it.
- In every other case use the regular `keyboard-quit'."
  (interactive)
  (cond
   ((region-active-p)
    (keyboard-quit))
   ((derived-mode-p 'completion-list-mode)
    (delete-completion-window))
   ((> (minibuffer-depth) 0)
    (abort-recursive-edit))
   (t
    (keyboard-quit))))

(define-key global-map (kbd "C-g") #'myrmi/keyboard-quit-dwim)