Emacs configuration

Table of Contents

Currently tailored for GNU Emacs 29.1.

1. todo-list

1.1. TODO get smooth scrolling working

1.2. SOMEDAY explore additional key themes for lispyville (see 9.2.3)

1.3. TODO get drag-and-drop working with dired

2. Useful resources

3. DONE Early init file

Since we're using elpaca as our package manager, we add the following to early-init.el to prevent package.el from loading packages, prior to our init file loading.

;;; early-init.el --- Early Init File -*- lexical-binding: t; no-byte-compile: t -*-

(setq package-enable-at-startup nil)

Disable GUI elements before we have a chance to peek.

;; minimal UI
(menu-bar-mode -1) ;; disables menubar
(tool-bar-mode -1) ;; disables toolbar
(scroll-bar-mode -1) ;; disables scrollbar

(setq inhibit-splash-screen t ;; no thanks
        use-file-dialog nil ;; don't use system file dialog
        tab-bar-new-button-show nil ;; don't show new tab button
        tab-bar-close-button-show nil ;; don't show tab close button
        tab-line-close-button-show nil) ;; don't show tab close button
;;; early-init.el ends here

4. Package management

4.1. init.el header

;;; init.el --- Personal configuration file -*- lexical-binding: t; no-byte-compile: t; -*-
;; NOTE: init.el is now generated from readme.org.  Please edit that file instead

4.2. DONE elpaca

4.2.1. DONE Bootstrap elpaca

(defvar elpaca-installer-version 0.7)
(defvar elpaca-directory (expand-file-name "elpaca/" user-emacs-directory))
(defvar elpaca-builds-directory (expand-file-name "builds/" elpaca-directory))
(defvar elpaca-repos-directory (expand-file-name "repos/" elpaca-directory))
(defvar elpaca-order '(elpaca :repo "https://github.com/progfolio/elpaca.git"
                              :ref nil :depth 1
                              :files (:defaults "elpaca-test.el" (:exclude "extensions"))
                              :build (:not elpaca--activate-package)))
(let* ((repo  (expand-file-name "elpaca/" elpaca-repos-directory))
       (build (expand-file-name "elpaca/" elpaca-builds-directory))
       (order (cdr elpaca-order))
       (default-directory repo))
  (add-to-list 'load-path (if (file-exists-p build) build repo))
  (unless (file-exists-p repo)
    (make-directory repo t)
    (when (< emacs-major-version 28) (require 'subr-x))
    (condition-case-unless-debug err
        (if-let ((buffer (pop-to-buffer-same-window "*elpaca-bootstrap*"))
                 ((zerop (apply #'call-process `("git" nil ,buffer t "clone"
                                                 ,@(when-let ((depth (plist-get order :depth)))
                                                     (list (format "--depth=%d" depth) "--no-single-branch"))
                                                 ,(plist-get order :repo) ,repo))))
                 ((zerop (call-process "git" nil buffer t "checkout"
                                       (or (plist-get order :ref) "--"))))
                 (emacs (concat invocation-directory invocation-name))
                 ((zerop (call-process emacs nil buffer nil "-Q" "-L" "." "--batch"
                                       "--eval" "(byte-recompile-directory \".\" 0 'force)")))
                 ((require 'elpaca))
                 ((elpaca-generate-autoloads "elpaca" repo)))
            (progn (message "%s" (buffer-string)) (kill-buffer buffer))
          (error "%s" (with-current-buffer buffer (buffer-string))))
      ((error) (warn "%s" err) (delete-directory repo 'recursive))))
  (unless (require 'elpaca-autoloads nil t)
    (require 'elpaca)
    (elpaca-generate-autoloads "elpaca" repo)
    (load "./elpaca-autoloads")))
(add-hook 'after-init-hook #'elpaca-process-queues)
(elpaca `(,@elpaca-order))


;; Install use-package support
(elpaca elpaca-use-package
  ;; Enable use-package :ensure support for Elpaca.
  (elpaca-use-package-mode))
(setq use-package-always-ensure t)
(elpaca-wait)

5. Emacs

5.1. Variables

(defvar patrl/library-path "~/MEGA/library/"
  "Directory .pdf collection lives.")

(defvar patrl/notes-path "~/notes/"
  "Notes.")

(defvar patrl/journal-path (concat patrl/notes-path "daily/")
  "Journal entries.")

(defvar patrl/global-bib-file "~/texmf/bibtex/bib/master.bib"
  "Bibliography.")

(defvar patrl/org-path "~/MEGA/org/"
  "Org path.")

5.2. Defaults

(use-package emacs
  :ensure nil
  :init

  (setq enable-recursive-minibuffers t)

  (setq backup-by-copying t)

  (setq sentence-end-double-space nil)

  (setq frame-inhibit-implied-resize t) ;; useless for a tiling window manager

  (setq show-trailing-whitespace t)

  (setq user-full-name "Patrick D. Elliott") ;; my details
  (setq user-mail-address "[email protected]")

  (defalias 'yes-or-no-p 'y-or-n-p) ;; life is too short

  (setq indent-tabs-mode nil) ;; no tabs

  ;; keep backup and save files in a dedicated directory
  (setq backup-directory-alist
          `((".*" . ,(concat user-emacs-directory "backups")))
          auto-save-file-name-transforms
          `((".*" ,(concat user-emacs-directory "backups") t)))

  (setq create-lockfiles nil) ;; no need to create lockfiles

  (set-charset-priority 'unicode) ;; utf8 in every nook and cranny
  (setq locale-coding-system 'utf-8
          coding-system-for-read 'utf-8
          coding-system-for-write 'utf-8)
  (set-terminal-coding-system 'utf-8)
  (set-keyboard-coding-system 'utf-8)
  (set-selection-coding-system 'utf-8)
  (prefer-coding-system 'utf-8)
  (setq default-process-coding-system '(utf-8-unix . utf-8-unix))

  (global-set-key (kbd "<escape>") 'keyboard-escape-quit) ;; escape quits everything


  ;; Don't persist a custom file
  (setq custom-file (make-temp-file "")) ; use a temp file as a placeholder
  (setq custom-safe-themes t)            ; mark all themes as safe, since we can't persist now
  (setq enable-local-variables :all)     ; fix =defvar= warnings

  (setq delete-by-moving-to-trash t) ;; use trash-cli rather than rm when deleting files.

  ;; less noise when compiling elisp
  (setq byte-compile-warnings '(not free-vars unresolved noruntime lexical make-local))
  (setq native-comp-async-report-warnings-errors nil)
  (setq load-prefer-newer t)

  (show-paren-mode t)

  ;; Hide commands in M-x which don't work in the current mode
  (setq read-extended-command-predicate #'command-completion-default-include-p))
(elpaca-wait)

5.3. Electric

(use-package electric
  :demand t
  :ensure nil
  :init
  (electric-pair-mode +1) ;; automatically insert closing parens
  (setq electric-pair-preserve-balance nil)) ;; more annoying than useful
(elpaca-wait)

6. Keybindings

Mnemonic keybindings with SPC as the leader key, inspired by spacemacs and doom emacs.

The local leader , is used for mode-specific bindings.

6.1. Evil

(use-package evil
  :demand t
  :init
  (setq evil-search-module 'isearch)

  (setq evil-want-C-u-scroll t) ;; allow scroll up with 'C-u'
  (setq evil-want-C-d-scroll t) ;; allow scroll down with 'C-d'

  (setq evil-want-integration t) ;; necessary for evil collection
  (setq evil-want-keybinding nil)

  (setq evil-split-window-below t)
  (setq evil-vsplit-window-right t)

  (setq evil-want-C-i-jump nil) ;; hopefully this will fix weird tab behaviour

  (setq evil-undo-system 'undo-redo) ;; undo via 'u', and redo the undone change via 'C-r'; only available in emacs 28+.
  :config
  (evil-mode t) ;; globally enable evil mode
  ;; set the initial state for some kinds of buffers.
  (evil-set-initial-state 'messages-buffer-mode 'normal)
  (evil-set-initial-state 'dashboard-mode 'normal)
  ;; buffers in which I want to immediately start typing should be in 'insert' state by default.
  (evil-set-initial-state 'eshell-mode 'insert)
  (evil-set-initial-state 'magit-diff-mode 'insert))
(elpaca-wait)

6.1.1. Evil collection

(use-package evil-collection ;; evilifies a bunch of things
  :after evil
  :init
  (setq evil-collection-outline-bind-tab-p t) ;; '<TAB>' cycles visibility in 'outline-minor-mode'
  ;; If I want to incrementally enable evil-collection mode-by-mode, I can do something like the following:
  ;; (setq evil-collection-mode-list nil) ;; I don't like surprises
  ;; (add-to-list 'evil-collection-mode-list 'magit) ;; evilify magit
  ;; (add-to-list 'evil-collection-mode-list '(pdf pdf-view)) ;; evilify pdf-view
  :config
  (evil-collection-init))

6.1.2. Evil commentary

Port of Tim Pope's commentary package

(use-package evil-commentary
  :after evil
  :config
  (evil-commentary-mode)) ;; globally enable evil-commentary

6.1.3. Evil surround

Port of Tim Pope's surround package

(use-package evil-surround
  :after evil
  :hook ((org-mode . (lambda () (push '(?~ . ("~" . "~")) evil-surround-pairs-alist)))
         (org-mode . (lambda () (push '(?$ . ("\\(" . "\\)")) evil-surround-pairs-alist))))
  :config
  (global-evil-surround-mode 1)) ;; globally enable evil-surround

6.1.4. Evil goggles

Show visual hints for evil motions.

(use-package evil-goggles
  :config
  (evil-goggles-mode)

  ;; optionally use diff-mode's faces; as a result, deleted text
  ;; will be highlighed with `diff-removed` face which is typically
  ;; some red color (as defined by the color theme)
  ;; other faces such as `diff-added` will be used for other actions
  (evil-goggles-use-diff-faces))

6.2. General

  • GitHub - noctuid/general.el: More convenient key definitions in emacs

        (use-package general
          :demand t
          :config
          (general-evil-setup)
          ;; integrate general with evil
    
          ;; set up 'SPC' as the global leader key
          (general-create-definer patrl/leader-keys
            :states '(normal insert visual emacs)
            :keymaps 'override
            :prefix "SPC" ;; set leader
            :global-prefix "M-SPC") ;; access leader in insert mode
    
          ;; set up ',' as the local leader key
          (general-create-definer patrl/local-leader-keys
            :states '(normal insert visual emacs)
            :keymaps 'override
            :prefix "," ;; set local leader
            :global-prefix "M-,") ;; access local leader in insert mode
    
          (general-define-key
           :states 'insert
           "C-g" 'evil-normal-state) ;; don't stretch for ESC
    
          ;; unbind some annoying default bindings
          (general-unbind
            "C-x C-r"       ;; unbind find file read only
            "C-x C-z"       ;; unbind suspend frame
            "C-x C-d"       ;; unbind list directory
            "<mouse-2>") ;; pasting with mouse wheel click
    
    
          (patrl/leader-keys
            "SPC" '(execute-extended-command :wk "execute command") ;; an alternative to 'M-x'
            "TAB" '(:keymap tab-prefix-map :wk "tab")) ;; remap tab bindings
    
          (patrl/leader-keys
          "w" '(:keymap evil-window-map :wk "window")) ;; window bindings
    
          (patrl/leader-keys
            "c" '(:ignore t :wk "code"))
    
          ;; help
          ;; namespace mostly used by 'helpful'
          (patrl/leader-keys
            "h" '(:ignore t :wk "help"))
    
          ;; file
          (patrl/leader-keys
            "f" '(:ignore t :wk "file")
            "ff" '(find-file :wk "find file") ;; gets overridden by consult
            "fs" '(save-buffer :wk "save file"))
    
          ;; buffer
          ;; see 'bufler' and 'popper'
          (patrl/leader-keys
            "b" '(:ignore t :wk "buffer")
            "bb" '(switch-to-buffer :wk "switch buffer") ;; gets overridden by consult
            "bk" '(kill-this-buffer :wk "kill this buffer")
            "br" '(revert-buffer :wk "reload buffer"))
    
          ;; bookmark
          (patrl/leader-keys
            "B" '(:ignore t :wk "bookmark")
            "Bs" '(bookmark-set :wk "set bookmark")
            "Bj" '(bookmark-jump :wk "jump to bookmark"))
    
          ;; universal argument
          (patrl/leader-keys
            "u" '(universal-argument :wk "universal prefix"))
    
          ;; notes
          ;; see 'citar' and 'org-roam'
          (patrl/leader-keys
            "n" '(:ignore t :wk "notes")
            ;; see org-roam and citar sections
            "na" '(org-todo-list :wk "agenda todos")) ;; agenda
    
          ;; code
          ;; see 'flymake'
          (patrl/leader-keys
            "c" '(:ignore t :wk "code"))
    
          ;; open
          (patrl/leader-keys
            "o" '(:ignore t :wk "open")
            "os" '(speedbar t :wk "speedbar")) ;; TODO this needs some love
    
          ;; search
          ;; see 'consult'
          (patrl/leader-keys
            "s" '(:ignore t :wk "search"))
    
          ;; templating
          ;; see 'tempel'
          (patrl/leader-keys
            "t" '(:ignore t :wk "template")))
    
    (elpaca-wait)
        ;; "c" '(org-capture :wk "capture")))
    

6.3. Org mode

Resources:

  • zzamboni.org | Beautifying Org Mode in Emacs
  • Ricing up Org Mode
  • GitHub - minad/org-modern: Modern Org Style

    (use-package org
      :demand t
      :init
      ;; edit settings
      (setq org-auto-align-tags nil
                org-tags-column 0
                org-catch-invisible-edits 'show-and-error
                org-special-ctrl-a/e t ;; special navigation behaviour in headlines
                org-insert-heading-respect-content t)
    
      ;; styling, hide markup, etc.
      (setq org-hide-emphasis-markers t
                org-src-fontify-natively t ;; fontify source blocks natively
                org-highlight-latex-and-related '(native) ;; fontify latex blocks natively
                org-pretty-entities t
                org-ellipsis "…")
    
      ;; agenda styling
      (setq org-agenda-tags-column 0
                org-agenda-block-separator ?─
                org-agenda-time-grid
                '((daily today require-timed)
                  (800 1000 1200 1400 1600 1800 2000)
                  " ┄┄┄┄┄ " "┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄")
                org-agenda-current-time-string
                "⭠ now ─────────────────────────────────────────────────")
    
      ;; todo setup
      (setq org-todo-keywords
                ;; it's extremely useful to distinguish between short-term goals and long-term projects
                '((sequence "TODO(t)" "SOMEDAY(s)" "|" "DONE(d)")
                  (sequence "TO-READ(r)" "READING(R)" "|" "HAVE-READ(d)")
                  (sequence "PROJ(p)" "|" "COMPLETED(c)")))
    
    
      (setq org-adapt-indentation nil) ;; interacts poorly with 'evil-open-below'
    
      :custom
      (org-agenda-files '("~/notes/todo.org" "~/notes/teaching.org" "~/notes/projects.org"))
      (org-cite-global-bibliography (list patrl/global-bib-file))
      :general
      (patrl/local-leader-keys
            :keymaps 'org-mode-map
            "a" '(org-archive-subtree :wk "archive")
            "l" '(:ignore t :wk "link")
            "ll" '(org-insert-link t :wk "link")
            "lp" '(org-latex-preview t :wk "prev latex")
            "h" '(consult-org-heading :wk "consult heading")
            "d" '(org-cut-special :wk "org cut special")
            "y" '(org-copy-special :wk "org copy special")
            "p" '(org-paste-special :wk "org paste special")
            "b" '(:keymap org-babel-map :wk "babel")
            "t" '(org-todo :wk "todo")
            "s" '(org-insert-structure-template :wk "template")
            "e" '(org-edit-special :wk "edit")
            "i" '(:ignore t :wk "insert")
            "ih" '(org-insert-heading :wk "insert heading")
            "is" '(org-insert-subheading :wk "insert heading")
            "f" '(org-footnote-action :wk "footnote action")
            ">" '(org-demote-subtree :wk "demote subtree")
            "<" '(org-promote-subtree :wk "demote subtree"))
      (:keymaps 'org-agenda-mode-map
                    "j" '(org-agenda-next-line)
                    "h" '(org-agenda-previous-line))
    
      :hook
      (org-mode . olivetti-mode)
      (org-mode . variable-pitch-mode)
      (org-mode . (lambda () (electric-indent-local-mode -1))) ;; disable electric indentation
    
      :config
      (add-to-list 'org-latex-packages-alist '("" "braket" t))
      (org-babel-do-load-languages
       'org-babel-load-languages
       '((js . t)
             (emacs-lisp . t)
             (awk . t)))
      ;; set up org paths
      (setq org-directory "~/MEGA/org/agenda")
      (setq org-default-notes-file (concat org-directory "/notes.org")))
    (elpaca-wait)
    

6.4. Avy

Super-charged navigation.

Use . after an avy command to trigger embark-act on the selected candidate.

  (use-package avy
    :demand t
    :init
(defun patrl/avy-action-insert-newline (pt)
      (save-excursion
        (goto-char pt)
        (newline))
      (select-window
       (cdr
        (ring-ref avy-ring 0))))
    (defun patrl/avy-action-kill-whole-line (pt)
      (save-excursion
        (goto-char pt)
        (kill-whole-line))
      (select-window
       (cdr
        (ring-ref avy-ring 0))))
    (defun patrl/avy-action-embark (pt)
      (unwind-protect
          (save-excursion
            (goto-char pt)
            (embark-act))
        (select-window
         (cdr (ring-ref avy-ring 0))))
      t) ;; adds an avy action for embark
    :general
    (general-def '(normal motion)
      "s" 'evil-avy-goto-char-timer
      "f" 'evil-avy-goto-char-in-line
      "gl" 'evil-avy-goto-line ;; this rules
      ";" 'avy-resume)
    :config
    (setf (alist-get ?. avy-dispatch-alist) 'patrl/avy-action-embark ;; embark integration
          (alist-get ?i avy-dispatch-alist) 'patrl/avy-action-insert-newline
          (alist-get ?K avy-dispatch-alist) 'patrl/avy-action-kill-whole-line)) ;; kill lines with avy

6.4.1. Link hint

(use-package link-hint
  :general
  (patrl/leader-keys
    "l" '(link-hint-open-link :wk "open link"))
  :config
  (setq browse-url-browser-function 'browse-url-firefox)
  (setq link-hint-avy-style 'pre))

6.5. Which key

Display key bindings.

(use-package which-key
  :after evil
  :demand t
  :init (which-key-mode)
  :config
  (which-key-setup-minibuffer))

7. Appearance

7.1. Mode line

Minimal mode line.

(use-package mood-line
  :demand t
  :config (mood-line-mode))

7.2. Icons

(use-package all-the-icons)


;; prettify dired with icons
(use-package all-the-icons-dired
  :hook
  (dired-mode . all-the-icons-dired-mode))

(use-package all-the-icons-completion
  :after (marginalia all-the-icons)
  :hook (marginalia-mode . all-the-icons-completion-marginalia-setup)
  :init (all-the-icons-completion-mode))

7.3. Olivetti

Add some margins (useful for writing prose).

(use-package olivetti
  :init
  (setq olivetti-body-width 80)
  (setq olivetti-style 'fancy)
  (setq olivetti-minimum-body-width 50))

7.4. Fonts

(defun patrl/setup-font-wolfe ()
    (set-face-attribute 'default nil :font (font-spec :family "Iosevka Comfy Motion" :size 10.0 :weight 'regular))
    (set-face-attribute 'fixed-pitch nil :font (font-spec :family "Iosevka Comfy Motion" :size 10.0 :weight 'regular))
    (set-face-attribute 'variable-pitch nil :font (font-spec :family "Iosevka Etoile" :size 10.0 :weight 'medium))
    (set-fontset-font t 'unicode "JuliaMono"))

(defun patrl/setup-font-vivacia ()
  (set-face-attribute 'default nil :font (font-spec :family "Iosevka Comfy Motion" :size 10.0 :weight 'regular))
  (set-face-attribute 'fixed-pitch nil :font (font-spec :family "Iosevka Comfy Motion" :size 10.0 :weight 'regular))
  (set-face-attribute 'variable-pitch nil :font (font-spec :family "Iosevka Etoile" :size 10.0 :weight 'medium))
  (set-fontset-font t 'unicode "JuliaMono"))

(when (string= (system-name) "wolfe")
  (add-hook 'after-init-hook 'patrl/setup-font-wolfe)
  (add-hook 'server-after-make-frame-hook 'patrl/setup-font-wolfe))

(when (string= (system-name) "vivacia")
  (add-hook 'after-init-hook 'patrl/setup-font-vivacia)
  (add-hook 'server-after-make-frame-hook 'patrl/setup-font-vivacia))

7.5. Themes

Visually distinguish between 'real' buffers and everything else.

(use-package solaire-mode
  :config
  (solaire-global-mode +1))

Some nice themes:

(use-package catppuccin-theme
  :demand t
  :config
  (setq catppuccin-height-title1 1.5)
  (load-theme 'catppuccin t))
(use-package doom-themes
  :config
  ;; Global settings (defaults)
  (setq doom-themes-enable-bold t ; if nil, bold is universally disabled
        doom-themes-enable-italic t)
                                        ; if nil, italics is universally disabled
  ;; (load-theme 'doom-nord t)

  ;; Enable flashing mode-line on errors
  (doom-themes-visual-bell-config)
  ;; Corrects (and improves) org-mode's native fontification.
  (doom-themes-org-config))

Visually highlight todo.

(use-package hl-todo
  :init
  (global-hl-todo-mode))

8. Organization

8.1. Buffer management

8.1.1. Popper

Classify certain buffer types as popups.

Not sure if I want to keep this around.

(use-package popper
  :general
  (patrl/leader-keys
        "bp" '(:ignore t :wk "popper")
        "bpc" '(popper-cycle t :wk "cycle")
        "bpt" '(popper-toggle-latest t :wk "toggle latest")
        "bpb" '(popper-toggle-type t :wk "toggle type")
        "bpk" '(popper-kill-latest-popup t :wk "kill latest"))
  :init
  (setq popper-reference-buffers
        '("\\*Messages\\*"
          "Output\\*$"
          "\\*helpful"
          "\\*Async Shell Command\\*"
          help-mode
          compilation-mode
          magit-process-mode
          "^\\*eshell.*\\*" eshell-mode
          "\\*direnv\\*"
          "\\*elfeed-log\\*"
          "\\*Async-native-compile-log\\*"
          "\\*TeX Help\\*"
          "\\*Embark Collect Live\\*"))
  (popper-mode +1)
  (popper-echo-mode +1))

8.1.2. bufler

This has been quite nice so far.

(use-package bufler
  :custom
  (bufler-workspace-mode t)
  ;; (bufler-workspace-tabs-mode t)
  :general
  (patrl/leader-keys
    "bB" '(bufler :wk "bufler") ;; overrides consult
    "bf" '(bufler-workspace-frame-set :wk "bufler workspace frame set")
    "bl" '(bufler-list :wk "bufler list"))
  (:keymaps 'bufler-list-mode-map
            :states 'normal
            "?" 'hydra:bufler/body
            "RET" 'bufler-list-buffer-switch
            "SPC" 'bufler-list-buffer-peek
            "d" 'bufler-list-buffer-kill))

8.1.3. Projects

Use built in project.el.

(use-package project
  :ensure nil
  :general
  ;; assign built-in project.el bindings a new prefix
  (patrl/leader-keys "p" '(:keymap project-prefix-map :wk "project")))
  1. File management

    Use built-in dired.

    (use-package dired
      :ensure nil
      :general
      (patrl/leader-keys
        "fd" '(dired :wk "dired") ;; open dired (in a directory)
        "fj" '(dired-jump :wk "dired jump")) ;; open direct in the current directory
      ;; ranger like navigation
      (:keymaps 'dired-mode-map
                :states 'normal
                "h" 'dired-up-directory
                "q" 'kill-current-buffer
                "l" 'dired-find-file)
      :hook
      (dired-mode . dired-hide-details-mode))
    
    ;; toggle subtree visibility with 'TAB'
    ;; makes dired a much more pleasant file manager
    (use-package dired-subtree)
    

9. Languages

9.1. Lua

(use-package lua-mode
  :mode "\\.lua\\'")

9.2. Lisp

9.2.1. Fennel

(use-package fennel-mode
 :mode "\\.fnl\\'")

9.2.2. Sly

;; FIXME - compatibility with corfu
(use-package sly)

9.2.3. lispy

(use-package lispy
  :general
  (:keymaps 'lispy-mode-map
              "TAB" 'indent-for-tab-command) ;; necessary for 'corfu'
  :hook
  (reb-lisp-mode . lispy-mode)
  (emacs-lisp-mode . lispy-mode)
  (racket-mode . lispy-mode)
  (fennel-mode . lispy-mode))

(use-package lispyville
  :hook (lispy-mode . lispyville-mode)
  :general
  (:keymaps 'lispyville-mode-map
              "TAB" 'indent-for-tab-command) ;; necessary for 'corfu'
  ;; the following is necessary to retain tab completion in lispy mode
  :config
  ;; TODO play around with keythemes
  (lispyville-set-key-theme '(operators c-w additional)))

9.3. js

(use-package js2-mode)

9.4. Rust

We'll use rust-analyzer by using rustic with eglot.

(use-package rustic
  :mode ("\\.rs\\'" . rustic-mode)
  :config (setq rustic-lsp-client 'eglot))

9.5. TODO haskell

  • Setup LSP
  • Haskell's indentation mode is interacting poorly with tab completion.

    (use-package haskell-mode)
    

dante (which leads on ghcid) can be useful when haskell-language-server isn't available in the local environment for whatever reason.

Note that the following uses 12.5.1 to convert dante's company backend to to a capf.

(use-package dante
  :after haskell-mode cape
  :init
  (defun dante-setup-capf ()
    (add-to-list 'completion-at-point-functions (cape-company-to-capf #'dante-company)))
  :commands 'dante-mode
  :config (dante-setup-capf))

9.6. racket

(use-package racket-mode
  :hook (racket-mode . racket-xp-mode) ;; n.b. this requires Dr. Racket to be installed as a backend
  :general
  (patrl/local-leader-keys
    :keymaps 'racket-mode-map
    "r" '(racket-run-and-switch-to-repl :wk "run")
    "e" '(racket-eval-last-sexp :wk "eval last sexp")
    :keymaps 'racket-xp-mode-map
    "xr" '(racket-xp-rename :wk "rename")))

9.7. nix

(use-package nix-mode
  ;; There's no `nix-mode-map`, so not currently possible to set local bindings.
  :mode "\\.nix\\'")

9.8. markdown

(use-package markdown-mode
  :hook ((markdown-mode . visual-line-mode)
         (markdown-mode . olivetti-mode))
         (markdown-mode . variable-pitch-mode)
  :commands (markdown-mode gfm-mode)
  :mode (("README\\.md\\'" . gfm-mode)
         ("\\.md\\'" . markdown-mode)
         ("\\.markdown\\'" . markdown-mode))
  :init
  (setq markdown-command "pandoc")
  (setq markdown-header-scaling t))
(use-package pandoc-mode
  :after markdown-mode
  :hook (markdown-mode . pandoc-mode))

10. SOMEDAY latex

Useful resources:

Configuring auctex is a little like dealing with a teetering stack of plates.

Bindings:

  • n b: insert citation via citar (normal mode).
  • , p: preview at point.
  • , c: compile.

Auto-activating snippets:

  • mx: insert in-line equation.
  • mq: insert equation.
  • ii: insert itemize environment.
  • forfor: insert forest environment.
  • *i: insert italicized environment.
  • *b: insert bold environment.
(use-package auctex
  :elpaca (auctex :pre-build (("./autogen.sh")
                    ("./configure"
                     "--without-texmf-dir"
                     "--with-packagelispdir=./"
                     "--with-packagedatadir=./")
                    ("make"))
        :build (:not elpaca--compile-info) ;; Make will take care of this step
        :files ("*.el" "doc/*.info*" "etc" "images" "latex" "style")
        :version (lambda (_) (require 'tex-site) AUCTeX-version))
  :mode ("\\.tex\\'" . LaTeX-mode)
  :init
  (setq TeX-parse-self t ; parse on load
        reftex-plug-into-AUCTeX t
        TeX-auto-save t  ; parse on save
        TeX-source-correlate-mode t
        TeX-source-correlate-method 'synctex
        TeX-source-correlate-start-server nil
        TeX-electric-sub-and-superscript t
        TeX-engine 'luatex ;; use lualatex by default
        TeX-save-query nil
        TeX-electric-math (cons "\\(" "\\)")) ;; '$' inserts an in-line equation '\(...\)'
  :general 
  (patrl/local-leader-keys
    :keymaps 'LaTeX-mode-map
    ;; "TAB" 'TeX-complete-symbol ;; FIXME let's 'TAB' do autocompletion (but it's kind of useless to be honest)
    "=" '(reftex-toc :wk "reftex toc")
    "(" '(reftex-latex :wk "reftex label")
    ")" '(reftex-reference :wk "reftex ref")
    "m" '(LaTeX-macro :wk "insert macro")
    "s" '(LaTeX-section :wk "insert section header")
    "e" '(LaTeX-environment :wk "insert environment")
    "p" '(preview-at-point :wk "preview at point")
    "f" '(TeX-font :wk "font")
    "c" '(TeX-command-run-all :wk "compile"))
  :config
;; (add-hook 'TeX-mode-hook #'visual-line-mode)
  (add-hook 'TeX-mode-hook #'reftex-mode)
  (add-hook 'TeX-mode-hook #'olivetti-mode)
  (add-hook 'TeX-mode-hook #'turn-on-auto-fill)
  (add-hook 'TeX-mode-hook #'prettify-symbols-mode)
  (add-hook 'TeX-after-compilation-finished-functions
                #'TeX-revert-document-buffer)
  (add-to-list 'TeX-view-program-selection '(output-pdf "PDF Tools"))
  (add-hook 'TeX-mode-hook #'outline-minor-mode))

evil-tex is a crucial component in my workflow. evil-surround integration makes things a breeze. Some examples:

  • Italicize word from point in normal mode: ysw;i.
(use-package evil-tex
  :hook (LaTeX-mode . evil-tex-mode))

Set up icon support using the following instructions:

(use-package citar
  :after all-the-icons
  :init
  (defun citar-setup-capf ()
    (add-to-list 'completion-at-point-functions 'citar-capf))

  (defvar citar-indicator-files-icons
    (citar-indicator-create
     :symbol (all-the-icons-faicon
              "file-o"
              :face 'all-the-icons-green
              :v-adjust -0.1)
     :function #'citar-has-files
     :padding "  " ; need this because the default padding is too low for these icons
     :tag "has:files"))

  (defvar citar-indicator-links-icons
    (citar-indicator-create
     :symbol (all-the-icons-octicon
              "link"
              :face 'all-the-icons-orange
              :v-adjust 0.01)
     :function #'citar-has-links
     :padding "  "
     :tag "has:links"))

  (defvar citar-indicator-notes-icons
    (citar-indicator-create
     :symbol (all-the-icons-material
              "speaker_notes"
              :face 'all-the-icons-blue
              :v-adjust -0.3)
     :function #'citar-has-notes
     :padding "  "
     :tag "has:notes"))

  (defvar citar-indicator-cited-icons
    (citar-indicator-create
     :symbol (all-the-icons-faicon
              "circle-o"
              :face 'all-the-icon-green)
     :function #'citar-is-cited
     :padding "  "
     :tag "is:cited"))
  :hook
  ;; set up citation completion for latex, org-mode, and markdown
  (LaTeX-mode . citar-setup-capf)
  (org-mode . citar-setup-capf)
  (markdown-mode . citar-setup-capf)
  :config
  (setq citar-indicators
        (list citar-indicator-files-icons
              citar-indicator-links-icons
              citar-indicator-notes-icons
              citar-indicator-cited-icons))
  :general
  (patrl/leader-keys
    "nb" '(citar-open :wk "citar"))
  :init
  (setq citar-notes-paths (list patrl/notes-path))
  (setq citar-library-paths (list patrl/library-path))
  (setq citar-bibliography (list patrl/global-bib-file)))
(use-package citar-embark
  :after citar embark
  :config (citar-embark-mode))

10.0.1. TODO add "citep" and "citealt" to the supported list of latex citation commands.

10.0.2. Engrave

Uses emacs' font lock to highlight source code blocks in latex.

(use-package engrave-faces)

10.0.3. TODO latexmk

;; ;; FIXME
;; (use-package auctex-latexmk
;;   :disabled
;;   :after latex
;;   :init
;;   (setq auctex-latexmk-inherit-TeX-PDF-mode t)
;;   :config
;;   (auctex-latexmk-setup))

10.1. Issues

10.1.1. TODO pdf buffer isn't reverted in preview continuously mode.

I think probably I just need to use zathura as the pdf viewer, which has its own smart revert functionality.

11. Org

11.1. Evil integration

(use-package evil-org
  :after org
  :hook (org-mode . (lambda () evil-org-mode))
  :config
  (require 'evil-org-agenda)
  (evil-org-agenda-set-keys))

11.2. TODO Org-auctex

Provides faster latex previews for org-mode.

;; (use-package org-auctex
;;   :straight (:type git :host github :repo
;;                    "karthink/org-auctex")
;;   :hook (org-mode . org-auctex-mode))

11.3. Transclusion

(use-package org-transclusion
  :after org
  :general
  (patrl/leader-keys
    "nt" '(org-transclusion-mode :wk "transclusion mode")))

11.4. TODO Appear

Reveals hidden elements interactively.

;; (use-package org-appear
;;   :straight (:type git :host github :repo "awth13/org-appear")
;;   :after org
;;   :hook (org-mode . org-appear-mode))

11.5. Cliplink

(use-package org-cliplink
  :after org
  :general
  (patrl/local-leader-keys
    :keymaps 'org-mode-map
    "lc" '(org-cliplink :wk "cliplink")))

11.6. Org modern

Enable org-modern mode globally.

(use-package org-modern
  :after org
  :config (global-org-modern-mode))

11.7. Org roam

(use-package org-roam
  :general
  (patrl/leader-keys
    "nr" '(:ignore t :wk "roam")
    "nri" '(org-roam-node-insert t :wk "insert node")
    "nrt" '(org-roam-buffer-toggle t :wk "roam buffer toggle")
    "nrc" '(org-roam-capture t :wk "roam capture")
    "nrf" '(org-roam-node-find :wk "find node")
    "nrd" '(:ignore t :wk "dailies")
    "nrdt" '(org-roam-dailies-goto-today :wk "today")
    "nrdt" '(org-roam-dailies-goto-yesterday :wk "today")
    "nrdT" '(org-roam-dailies-goto-tomorrow :wk "today")
    "nrdd" '(org-roam-dailies-goto-date :wk "goto date"))
  :config
  ;; org-roam-buffer
  (add-to-list 'display-buffer-alist
               '("\\*org-roam\\*"
                 (display-buffer-in-direction)
                 (direction . right)
                 (window-width . 0.33)
                 (window-height . fit-window-to-buffer)))
  ;; get tags to show up in 'org-roam-node-find':
  (setq org-roam-node-display-template
        (concat "${title:*} "
                (propertize "${tags:10}" 'face 'org-tag)))
  (setq org-roam-completion-everywhere t) ;; roam completion anywhere
  (setq org-roam-directory patrl/notes-path)
  (setq org-roam-db-location (concat org-roam-directory "/.database/org-roam.db"))
  (unless (< emacs-major-version 29)
    (setq org-roam-database-connector 'sqlite-builtin))
  (org-roam-db-autosync-mode) ;; ensures that org-roam is available on startup


  ;; dailies config
  (setq org-roam-dailies-directory "daily/")
  (setq org-roam-dailies-capture-templates
        '(("d" "default" entry
           "* %?"
           :target (file+head "%<%Y-%m-%d>.org"
                              "#+title: %<%Y-%m-%d>\n#+filetags: daily\n")))))

11.8. Export

(use-package citeproc
  :after org)

(with-eval-after-load 'ox-latex
   (add-to-list 'org-latex-classes
                '("scrartcl"
                  "\\documentclass[letterpaper]{scrartcl}"
                  ("\\section{%s}" . "\\section*{%s}")
                  ("\\subsection{%s}" . "\\subsection*{%s}")
                  ("\\subsubsection{%s}" . "\\subsubsection*{%s}")
                  ("\\paragraph{%s}" . "\\paragraph*{%s}")
                  ("\\subparagraph{%s}" . "\\subparagraph*{%s}"))))

11.9. Highlighting

(use-package htmlize)

11.10. Org-noter

(use-package org-noter
  :commands
  org-noter
  :general
  (patrl/local-leader-keys
    :keyamps 'org-noter-doc-mode-map
    "i" 'org-noter-insert-note)
  :config
  (setq org-noter-notes-search-path (list patrl/notes-path))
  (setq org-noter-default-notes-file-names '("literature-notes.org"))
  (setq org-noter-hide-other nil)
  (setq org-noter-always-create-frame nil))

12. Completion

The completion framework I adopt exploits packages primarily developed by Daniel Mendler.

12.1. Vertico with orderless and marginalia

(use-package vertico
  :demand t
  :init
  (vertico-mode)
  (vertico-multiform-mode)
  (setq vertico-multiform-categories
        '((file grid)
          (jinx grid (vertico-grid-annotate . 20))
          (citar buffer)))
  (setq vertico-cycle t) ;; enable cycling for 'vertico-next' and 'vertico-prev'
  :general
  (:keymaps 'vertico-map
            ;; keybindings to cycle through vertico results.
            "C-j" 'vertico-next
            "C-k" 'vertico-previous
            "C-f" 'vertico-exit
            "<backspace>" 'vertico-directory-delete-char
            "C-<backspace>" 'vertico-directory-delete-word
            "C-w" 'vertico-directory-delete-word
            "RET" 'vertico-directory-enter)
  (:keymaps 'minibuffer-local-map
            "M-h" 'backward-kill-word))

(use-package orderless
  :init
  (setq completion-styles '(orderless)
        completion-category-defaults nil
        completion-category-overrides '((file (styles partial-completion)))))

(use-package savehist
  :ensure nil
  :init
  (savehist-mode))

(use-package marginalia
  :after vertico
  :custom
  (marginalia-annotators '(marginalia-annotators-heavy marginalia-annotators-light nil))
  :init
  (marginalia-mode))

12.2. Consult

(use-package consult
  :demand t
  :general
  (patrl/leader-keys
    "bb" '(consult-buffer :wk "consult buffer")
    "Bb" '(consult-bookmark :wk "consult bookmark")
    "ht" '(consult-theme :wk "consult theme")
    "sr" '(consult-ripgrep :wk "consult rg")
    "sg" '(consult-grep :wk "consult grep")
    "sG" '(consult-git-grep :wk "consult git grep")
    "sf" '(consult-find :wk "consult find")
    "sF" '(consult-locate :wk "consult locate")
    "sl" '(consult-line :wk "consult line")
    "sy" '(consult-yank-from-kill-ring :wk "consult yank from kill ring")
    "i" '(consult-imenu :wk "consult imenu"))
  :config
  ;; use project.el to retrieve the project root
  (setq consult-project-root-function
        (lambda ()
          (when-let (project (project-current))
            (car (project-roots project))))))

12.3. Affe

(use-package affe
  :after orderless
  :general
  (patrl/leader-keys
    "sa" '(affe-grep :wk "affe grep")
    "sw" '(affe-find :wk "affe find"))
  :init
  (defun affe-orderless-regexp-compiler (input _type _ignorecase)
    (setq input (orderless-pattern-compiler input))
    (cons input (lambda (str) (orderless--highlight input str))))
  :config
  (setq affe-regexp-compiler #'affe-orderless-regexp-compiler))

12.4. Embark

(use-package embark
  :demand t
  :general
  (patrl/leader-keys
     "." 'embark-act) ;; easily accessible 'embark-act' binding.
  ("C-;" 'embark-dwim)
  (:keymaps 'vertico-map
            "C-." 'embark-act)
  (:keymaps 'embark-heading-map
            "l" 'org-id-store-link)
  :init
  (setq prefix-help-command #'embark-prefix-help-command))

(use-package embark-consult
  :after (embark consult)
  :demand t ; only necessary if you have the hook below
  ;; if you want to have consult previews as you move around an
  ;; auto-updating embark collect buffer
  :hook
  (embark-collect-mode . consult-preview-at-point-mode))

12.5. Corfu

A minimal ui for completion-in-region.

(use-package corfu
  :demand t
  :hook
  (eval-expression-minibuffer-setup . corfu-mode)
  :init
  (global-corfu-mode)
  :custom
  (corfu-cycle t) ;; allows cycling through candidates
  (corfu-auto nil) ;; disables auto-completion
  :bind
  :general
  (:keymaps 'corfu-map
            "SPC" 'corfu-insert-separator)) ;; for compatibility with orderless

(general-unbind
  :states '(insert)
  "C-k") ;; this was interfering with corfu completion

(use-package emacs
  :ensure nil
  :init
  (setq tab-always-indent 'complete)) ;; enable tab completion

12.5.1. Cape

(use-package cape
  ;; bindings for dedicated completion commands
  :general
  ("M-p p" 'completion-at-point ;; capf
   "M-p t" 'complete-tag ;; etags
   "M-p d" 'cape-dabbrev ;; dabbrev
   "M-p h" 'cape-history
   "M-p f" 'cape-file
   "M-p k" 'cape-keyword
   "M-p s" 'cape-symbol
   "M-p a" 'cape-abbrev
   "M-p i" 'cape-ispell
   "M-p l" 'cape-line
   "M-p w" 'cape-dict
   "M-p \\" 'cape-tex
   "M-p &" 'cape-sgml
   "M-p r" 'cape-rfc1345)
  :init
  (add-to-list 'completion-at-point-functions #'cape-file)
  (add-to-list 'completion-at-point-functions #'cape-dabbrev))

Convert the company-reftex-labels backend to a capf using cape and activate in in LaTeX-mode:

(use-package company-reftex
  :after cape
  :init
  (defun reftex-setup-capf ()
    (add-to-list 'completion-at-point-functions (cape-company-to-capf #'company-reftex-labels)))
  :hook
  (LaTeX-mode . reftex-setup-capf))

13. Checkers

  • Use flymake over flycheck.

13.1. Flymake

(use-package flymake
  :ensure nil
  :general
  (patrl/leader-keys
    :keymaps 'flymake-mode-map
    "cf" '(consult-flymake :wk "consult flymake") ;; depends on consult
    "cc" '(flymake-mode :wk "toggle flymake")) ;; depends on consult
  :hook
  (TeX-mode . flymake-mode) ;; this is now working
  (emacs-lisp-mode . flymake-mode)
  :custom
  (flymake-no-changes-timeout nil)
  :general
  (general-nmap "] !" 'flymake-goto-next-error)
  (general-nmap "[ !" 'flymake-goto-prev-error))

14. Tools

14.1. Recursion indicator

(use-package recursion-indicator
  :config
  (recursion-indicator-mode))

14.2. Regular expressions

Use rx notation (S-expressions) as the default for constructing regular expressions in regex builder.

(use-package re-builder
  :ensure nil
  :general (patrl/leader-keys
             "se" '(regexp-builder :wk "regex builder"))
  :config (setq reb-re-syntax 'rx))

14.3. PDF tools

Because this uses natively-compiled code, I install this alongside emacs via nix.

(use-package pdf-tools
  :demand t
  :ensure nil
  :hook (TeX-after-compilation-finished . TeX-revert-document-buffer)
  :mode ("\\.pdf\\'" . pdf-view-mode)
  :config
  (require 'pdf-tools)
  (require 'pdf-view)
  (require 'pdf-misc)
  (require 'pdf-occur)
  (require 'pdf-util)
  (require 'pdf-annot)
  (require 'pdf-info)
  (require 'pdf-isearch)
  (require 'pdf-history)
  (require 'pdf-links)
  (require 'pdf-outline)
  (pdf-tools-install :no-query))

14.4. Jinx

Because this uses natively-compiled code, I install this alongside emacs via nix, rather than in the usual way (hence the build-in straight keyword).

(use-package jinx
  :ensure nil
  :hook (emacs-startup . global-jinx-mode))

14.5. Helpful

(use-package helpful
  :general
  (patrl/leader-keys
    "hc" '(helpful-command :wk "helpful command")
    "hf" '(helpful-callable :wk "helpful callable")
    "hh" '(helpful-at-point :wk "helpful at point")
    "hF" '(helpful-function :wk "helpful function")
    "hv" '(helpful-variable :wk "helpful variable")
    "hk" '(helpful-key :wk "helpful key")))

14.6. Deadgrep

The best search package.

(use-package deadgrep
  :general
  (patrl/leader-keys
    "sd" '(deadgrep :wk "deadgrep")))

14.7. Snippets

14.7.1. Auto-activating snippets

(use-package aas
  :hook (LaTeX-mode . aas-activate-for-major-mode)
  :hook (org-mode . aas-activate-for-major-mode)
  :config
  ;; easy emoji entry in text mode.
  (aas-set-snippets 'text-mode
    ":-)" "🙂"
    "8-)" "😎"
    ":rofl" "🤣"
    ":lol" "😂"
    "<3" "❤️"
    ":eyes" "👀"
    ":dragon" "🐉"
    ":fire" "🔥"
    ":hole" "🕳️"
    ":flush" "😳"
    ":wow" "😮"))

Not sure if laas mode is properly activating in latex mode.

(use-package laas
  ;; disables accent snippets - things like 'l (which expands to \textsl{}) end up being very disruptive in practice.
  :init (setq laas-accent-snippets nil)
  :hook ((LaTeX-mode . laas-mode)
           (org-mode . laas-mode))
  :config
  (aas-set-snippets 'laas-mode
    ;; I need to make sure not to accidentally trigger the following, so I should only use impossible (or extremely rare) bigrams/trigrams.
    ;; "*b" (lambda () (interactive)
    ;;        (yas-expand-snippet "\\textbf{$1}$0"))
    ;; "*i" (lambda () (interactive)
    ;;     (yas-expand-snippet "\\textit{$1}$0"))
    "mx" (lambda () (interactive)
              (yas-expand-snippet "\\\\($1\\\\)$0"))
    "mq" (lambda () (interactive)
              (yas-expand-snippet "\\[$1\\]$0"))
    ;; "*I" (lambda () (interactive)
    ;;      (yas-expand-snippet "\\begin{enumerate}\n$>\\item $0\n\\end{enumerate}"))
    ;; "*e" (lambda () (interactive)
    ;;      (yas-expand-snippet "\\begin{exe}\n$>\\ex $0\n\\end{exe}"))
    ;; "*f" (lambda () (interactive)
    ;;      (yas-expand-snippet "\\begin{forest}\n[{$1}\n[{$2}]\n[{$0}]\n]\n\\end{forest}"))
    "*\"" (lambda () (interactive)
              (yas-expand-snippet "\\enquote{$1}$0"))
    :cond #'texmathp ; expand only while in math
    "Olon" "O(n \\log n)"
    ";:" "\\coloneq"
    ";;N" "\\mathbb{N}"
    ";T" "\\top"
    ";B" "\\bot"
    ";;x" "\\times"
    ";;v" "\\veebar"
    ";;u" "\\cup"
    ";;{" "\\subseteq"
    ";D" "\\Diamond"
    ";;b" "\\Box"
    ;; bind to functions!
    "sum" (lambda () (interactive)
              (yas-expand-snippet "\\sum_{$1}^{$2} $0"))
    "grandu" (lambda () (interactive)
              (yas-expand-snippet "\\bigcup\limits_{$1} $0"))
    "Span" (lambda () (interactive)
               (yas-expand-snippet "\\Span($1)$0"))
    "lam" (lambda () (interactive)
              (yas-expand-snippet "\\lambda $1_{$2}\\,.\\,$0"))
    ;; "set" (lambda () (interactive)
    ;;           (yas-expand-snippet "\\set{ $1 | $2} $0"))
    "txt" (lambda () (interactive)
                (yas-expand-snippet "\\text{$1} $0"))
    ";;o" (lambda () (interactive)
                (yas-expand-snippet "\\oplus"))
    ;; "ev" (lambda () (interactive)
    ;;             (yas-expand-snippet "\\left\\llbracket$3\\right\\rrbracket^$1_$2 $3"))
    ;; clash with event type sigs
    ;; add accent snippets
    :cond #'laas-object-on-left-condition
    "qq" (lambda () (interactive) (laas-wrap-previous-object "sqrt"))))

14.7.2. TODO yasnippet

Deprecate in favour of tempel.

(use-package yasnippet
  :config
  (yas-reload-all)
  (add-to-list 'yas-snippet-dirs "~/.config/emacs-vanilla/snippets")
  (yas-global-mode 1))

14.7.3. Tempel

Templates can be found in templates.

(use-package tempel
  :general
  ("M-p +" 'tempel-complete) ;; M-p completion prefix; see `cape'
  (patrl/leader-keys
    "ti" '(tempel-insert :wk "tempel insert"))
  (:keymaps 'tempel-map
            "TAB" 'tempel-next) ;; progress through fields via `TAB'
  :init
  (defun tempel-setup-capf ()
    (add-hook 'completion-at-point-functions #'tempel-expand))
  (add-hook 'prog-mode-hook 'tempel-setup-capf)
  (add-hook 'text-mode-hook 'tempel-setup-capf))

14.8. Web preview

(use-package simple-httpd
  :commands httpd-serve-directory)

14.9. Magit

(use-package magit
  :general
  (patrl/leader-keys
    "g" '(:ignore t :wk "git")
    "gg" '(magit-status :wk "status")))

14.10. Eshell

(use-package eshell
  :ensure nil
  :general
  (patrl/leader-keys
    "oe" '(eshell :wk "eshell")))

14.11. Language servers

14.11.1. LSP mode

Note I still need to set keybindings

(use-package lsp-mode
  :custom
  (lsp-completion-provider :none) ;; we use corfu!
  :init
  (defun patrl/lsp-mode-setup-completion ()
    (setf (alist-get 'styles (alist-get 'lsp-capf completion-category-defaults))
          '(orderless))) ;; Configure orderless
  :hook
  (lsp-mode . lsp-enable-which-key-integration)
  (lsp-completion-mode . patrl/lsp-mode-setup-completion) ;; setup orderless completion style.
  :commands
  lsp)

(use-package lsp-ui
  :after lsp-mode
  :commands lsp-ui-mode)

(use-package lsp-haskell
  :after lsp-mode
  :config
  (setq lsp-haskell-server-path "haskell-language-server") ;; for some reason this doesn't get found automatically
  ;; (setq lsp-haskell-formatting-provider "brittany")
  )

14.11.2. Eglot

(use-package eglot
  :ensure nil
  :init (setq completion-category-overrides '((eglot (styles orderless))))
  :commands eglot
  :config
  (add-to-list 'eglot-server-programs '(haskell-mode . ("haskell-language-server" "--lsp"))))

14.12. direnv

Essential, since I'm on NixOS.

(use-package direnv
  :config
  (direnv-mode))

14.13. TODO Kmonad

;; (use-package kbd-mode
;;   :straight (:type git :host github :repo
;;                    "kmonad/kbd-mode"))

14.13.1. TODO disable flymake mode in kbd-mode

14.14. TODO Elfeed

Resources:

;; We write a function to determine how we want elfeed to display the buffer with the current entry.
(defun patrl/elfeed-display-buffer (buf &optional act)
  (pop-to-buffer buf)
  (set-window-text-height (get-buffer-window) (round (* 0.7 (frame-height)))))

(defun patrl/elfeed-search-show-entry-pre (&optional lines)
  "Returns a function to scroll forward or back in the Elfeed search results, displaying entries without switching to them."
  (lambda (times)
    (interactive "p")
    (forward-line (* times (or lines 0)))
    (recenter)
    (call-interactively #'elfeed-search-show-entry)
    (select-window (previous-window))
    (unless elfeed-search-remain-on-entry (forward-line -1))))

(use-package elfeed
  :commands elfeed
  :general
  (patrl/leader-keys
    "oE" '(elfeed :wk "elfeed"))
  ;; :config
  (setq elfeed-show-entry-switch #'patrl/elfeed-display-buffer))

(use-package elfeed-org
  :init
 (setq rmh-elfeed-org-files (list "~/MEGA/org/elfeed.org"))
  :config
  (elfeed-org))

14.15. TODO Notmuch

A gmail-focused notmuch setup, with lieer as the sync backend.

(use-package notmuch
  :init
  ;; in gmail, messages are trashed by removing the 'inbox' tag, and adding the 'trash' tag. This will move messages to the gmail trash folder, but won't permnanently delete them.
  (defvar +notmuch-delete-tags '("+trash" "-inbox" "-unread" "-new"))
  ;; in gmail, messages are archived simply by removing the 'inbox' tag.
  (setq notmuch-archive-tags '("-inbox" "-new"))
  ;; show new mail first
  :config
  ;; the 'new' tag isn't synced to the gmail server; messages new to notmuch aquire this tag.
  (add-to-list 'notmuch-saved-searches '(:name "new" :query "tag:new" :key "n"))
  (defun +notmuch/search-delete ()
    (interactive)
    (notmuch-search-add-tag +notmuch-delete-tags)
    (notmuch-tree-next-message))
  (defun +notmuch/tree-delete ()
    (interactive)
    (notmuch-tree-add-tag +notmuch-delete-tags)
    (notmuch-tree-next-message))
  (setq-default notmuch-search-oldest-first nil)
  ;; sending mail using lieer
  (setq message-kill-buffer-on-exit t)
  (setq sendmail-program "gmi")
  (setq send-mail-function 'sendmail-send-it)
  (setq message-sendmail-extra-arguments '("send" "--quiet" "-t" "-C" "~/.mail/personal"))
  (setq notmuch-fcc-dirs nil) ;; let gmail take care of sent mail
  :general
  (patrl/leader-keys
    "on" '(notmuch :wk "notmuch"))
  ;; single key deletion.
  (:keymaps 'notmuch-search-mode-map
            :states 'normal
            "D" '+notmuch/search-delete)
  (:keymaps 'notmuch-tree-mode-map
            :states 'normal
            "D" '+notmuch/tree-delete))

14.15.1. TODO figure out why sent mail is saved to the home folder

14.15.2. TODO consult-notmuch

Search emails interactively using consult.

  • FIXME Currently has a version mismatch.
(use-package consult-notmuch
  :disabled
  :general
  (patrl/leader-keys
    "nmm" '(consult-notmuch t :wk "consult notmuch")
    "nmt" '(consult-notmuch-tree t :wk "consult notmuch tree")
    "nma" '(consult-notmuch-address t :wk "consult notmuch address"))
  :after notmuch)

15. Staging grounds

15.1. Rainbow mode

(use-package rainbow-mode)

15.2. Burly

(use-package burly
  :init
  (burly-tabs-mode +1)
  :general
  (patrl/leader-keys
    "Bf" '(burly-bookmark-frames t :wk "bookmark frames")
    "Bw" '(burly-bookmark-windows t :wk "bookmark windows")
    "Bo" '(burly-open-bookmark t :wk "open bookmark")
    "Bl" '(burly-open-last-bookmark t :wk "open last bookmark")))

15.3. Ebib

(use-package ebib
  :config
  (setq ebib-bibtex-dialect 'biblatex)
  (setq ebib-preload-bib-files (list patrl/global-bib-file))
  :general
  (patrl/leader-keys
    "ob" '(ebib :wk "ebib")))

15.3.1. TODO add biblio support

15.4. Tree-sitter

(use-package tree-sitter)
(use-package tree-sitter-langs)

16. Graveyard

Packages I've tried out which, for whatever reason, haven't suited me.

16.1. Mastodon

(use-package mastodon
  :config
  (setq mastodon-instance-url "https://types.pl"
          mastodon-active-user "patrl"))

16.2. 0x0

(use-package 0x0
  :general
  (patrl/leader-keys
    "x" '(:ignore t :wk "web")
    "x;" '(0x0-dwim t :wk "0x0 dwim")
    "xt" '(0x0-upload-text :wk "0x0 upload text")
    "xf" '(0x0-upload-file :wk "0x0 upload file")
    "xk" '(0x0-upload-kill-ring :wk "0x0 upload kill ring")
    "xp" '(0x0-popup :wk "0x0 popup")
    "xs" '(0x0-shorten-uri :wk "0x0 shorten url")))

16.3. Org-re-reveal

(use-package org-re-reveal)

16.4. Org-present

(use-package org-present)

16.5. TODO Sidebar

;; (use-package org-sidebar
;;   :after org
;;   :straight (:type git :host github :repo "alphapapa/org-sidebar"))

16.6. company

(use-package company
  :custom
  (company-idle-delay nil) ;; turn off auto-completion
  :general
  (:keymap 'company-mode-map
           "C-SPC" 'company-complete) ;; keybinding to trigger company completion
  :hook
  (prog-mode . company-mode)
  (LaTeX-mode . company-mode)
  :config
  ;; the following stops company from using the orderless completion style
  ;; makes company much more useful
  (define-advice company-capf
      (:around (orig-fun &rest args) set-completion-styles)
    (let ((completion-styles '(basic partial-completion)))
      (apply orig-fun args))))

Author: Patrick D. Elliott

Created: 2024-04-25 Thu 18:54

Validate