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

52 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 "Iosevka Nerd Font")
  (defvar my/variable-width-font "Iosevka Aile")
  (defvar my/default-font-height 110)
  (defvar my/default-font-weight 'normal)
  (defvar my/default-font-width 'normal)
  (defvar my/variable-width-font-height 1.2)
  (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.

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

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)

Delete trailing whitespace on save.

  (add-hook 'before-save-hook 'delete-trailing-whitespace)

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

  (setq split-height-threshold nil)

Set fill column to 80

  (setq-default fill-column 80)

Interface

Easy edit config file

  (defun find-config ()
    "Edit config.org"
    (interactive)
    (find-file my/config-file-path))

  (general-define-key
       :prefix my/leader
       "C" 'find-config)

Appearance

I prefer 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 t))

Use Iosevka as font.

  (set-face-attribute 'default nil
                       :family my/default-font
                       :weight my/default-font-weight
                       :width my/default-font-width
                       :height my/default-font-height)
  (set-face-attribute 'variable-pitch nil
                      :family my/variable-width-font
                      :weight my/default-font-weight
                      :width my/default-font-width
                      :height my/variable-width-font-height)

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 '("<---" "<--"  "<<-" "<-" "->" "-->" "--->" "<->" "<-->" "<--->" "<---->" "<!--"
                                         "<==" "<===" "<=" "=>" "=>>" "==>" "===>" ">=" "<=>" "<==>" "<===>" "<====>" "<!---"
                                         "<~~" "<~" "~>" "~~>" "::" ":::" "==" "!=" "===" "!=="
                                         ":=" ":-" ":+" "<*" "<*>" "*>" "<|" "<|>" "|>" "+:" "-:" "=:" "<******>" "++" "+++"))
    ;; 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.

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

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)

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
      (add-to-list 'eglot-server-programs
                   '(conf-toml-mode . ("taplo" "lsp" "stdio"))
                   '(elixir-mode elixir-ts-mode heex-ts-mode . ("elixir-ls")))
      (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-js
    :config
    (setq prettier-js-args '("--single-quote" "true"))
    :hook
    (js2-mode . typescript-mode))

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\\'")))
    :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)
    (before-save . eglot-code-action-organize-imports)
    ;; :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 . eglot-ensure)
    (c++-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
                               `(:nil (: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))))

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)

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

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