nixos-config/home-manager/modules/emacs/config.org

59 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

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 "MonaspiceNe Nerd Font")
  (defvar my/variable-width-font "Iosevka Aile")
  (defvar my/comment-font "MonaspiceRn Nerd Font")
  (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)

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:

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

TRAMP

Set some connection properties

  (with-eval-after-load "tramp" (add-to-list 'tramp-connection-properties
                                             (list (regexp-quote "/sshx:hass:")
                                                   "remote-shell" "/bin/bash")))

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
                 '((toml-mode toml-ts-mode conf-toml-mode) . ("taplo" "lsp" "stdio")))
    (add-to-list 'eglot-server-programs
                 `((elixir-mode elixir-ts-mode heex-ts-mode) .
                   ,(eglot-alternatives '(("nextls" "--stdio=true"
                                           :initializationOptions (:experimental (:completions (:enable t))))
                                          "elixir-ls"))))
    (add-to-list 'eglot-server-programs
                 '(dhall-mode . ("dhall-lsp-server")))
    (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
    :mode
    (("\\.cl\\'" . lisp-mode))
    :config
    (setq sly-lisp-implementations
          '((sbcl ("sbcl") :coding-system utf-8-unix))
          sly-default-lisp 'sbcl))

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

protobuf-mode

  (use-package protobuf-mode)

Just

just-mode provides syntax highlighting for Justfiles, for the command runner just

  (use-package just-mode)

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

hare

hare-mode for hare support.

  (use-package hare-mode
    :ensure nil ;; It's installed outside emacs
    )

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

envrc adds support for direnv to emacs. envrc works on a per-buffer basis, so when you open multiple projects the environments don't pollute eachother. NOTE: this should be loaded last.

  (use-package envrc
    :init
    (envrc-global-mode)
    :general
    (:prefix my/leader "e" '(:keymap envrc-command-map)))

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