My Emacs configuration
Find a file
2023-12-14 09:25:46 +00:00
.gitignore Initial commit: Add config and ignore tangled .el file 2023-12-11 12:07:35 +00:00
README.org Add treemacs with some sensible default config 2023-12-14 09:25:46 +00:00

Emacs Config

Setup

Loading this file is handled automatically on my NixOS config, otherwise add src_emacs-lisp{(org-babel-load-file "~/Projects/emacs/README.org")} to init file.

Common defaults

  (setq custom-file (locate-user-emacs-file "custom.el"))
  (when (and custom-file (file-exists-p custom-file))
    (load custom-file nil 'nomessage))

  (setq user-full-name "Evie Litherland-Smith"
        user-mail-address "evie@xenia.me.uk"
        use-short-answers t
        load-prefer-newer t
        indent-tabs-mode nil
        even-window-sizes nil
        global-auto-revert-non-file-buffers t
        dired-auto-revert-buffer t
        dired-dwim-target t
        tab-always-indent 'complete
        completion-cycle-threshold 3
        completions-detailed t
        xref-show-definitions-function #'xref-show-definitions-completing-read
        kill-do-not-save-duplicates t
        auto-window-vscroll nil
        fast-but-imprecise-scrolling t
        scroll-conservatively 101
        scroll-margin 0
        scroll-preserve-screen-position 1)

  (set-default-coding-systems 'utf-8)
  (set-terminal-coding-system 'utf-8)
  (set-keyboard-coding-system 'utf-8)

  (global-auto-revert-mode +1)
  (delete-selection-mode)
  (recentf-mode -1)


  (keymap-global-set "M-#" #'dictionary-lookup-definition)

  ;; turn on spell checking, if available.
  (when (and (require 'ispell nil :noerror) (executable-find ispell-program-name))
    (add-hook 'text-mode-hook #'flyspell-mode)
    (add-hook 'prog-mode-hook #'flyspell-prog-mode))

  ;; Make shebang (#!) file executable when saved
  (add-hook 'after-save-hook #'executable-make-buffer-file-executable-if-script-p)

Auto-save file settings

  (setq backup-directory-alist '(("." . "~/.local/state/emacs/backups"))
        tramp-backup-directory-alist backup-directory-alist
        tramp-auto-save-directory (cdr (assoc "." tramp-backup-directory-alist)))

package-archive with priorities

  (when (require 'package nil :noerror)
    (add-to-list 'package-archives '("stable" . "https://stable.melpa.org/packages/"))
    (add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/"))

    (setq package-archive-priorities '(("gnu" . 99)
                                       ("nongnu" . 80)
                                       ("stable" . 70)
                                       ("melpa" . 0))))

Authentication

  (when (require 'auth-source nil :noerror)
    (setq auth-sources '("secrets:Login"))
    (when (require 'auth-source-pass nil :noerror)
      (auth-source-pass-enable)))

Helpful

  ;; Make `describe-*' screens more helpful
  (use-package helpful
    :ensure t
    :bind (("<remap> <describe-command>" . helpful-command)
           ("<remap> <describe-function>" . helpful-callable)
           ("<remap> <describe-key>" . helpful-key)
           ("<remap> <describe-symbol>" . helpful-symbol)
           ("<remap> <describe-variable>" . helpful-variable)
           ("C-h F" . helpful-function)
           :map helpful-mode-map
           ("<remap> <revert-buffer>" . helpful-update)))

  ;; Bind extra `describe-*' commands
  (keymap-global-set "C-h K" #'describe-keymap)

ibuffer

  (use-package ibuffer
    :ensure t
    :bind (("C-c b" . ibuffer)))

  (use-package ibuffer-project
    :ensure t
    :after ibuffer
    :hook ((ibuffer . (lambda ()
                        (setq ibuffer-filter-groups (ibuffer-project-generate-filter-groups))
                        (unless (eq ibuffer-sorting-mode 'project-file-relative)
                          (ibuffer-do-sort-by-project-file-relative))))))

Link hint keymaps

  (use-package link-hint
    :ensure t
    :bind (("C-c l o" . link-hint-open-link)
           ("C-c l c" . link-hint-copy-link)
           ("C-c l C-o" . link-hint-open-all-link)
           ("C-c l C-c" . link-hint-copy-all-link)))

Avy keymaps

  (use-package avy
    :ensure t
    :bind (("C-c j w" . avy-goto-word-0)
           ("C-c j c" . avy-goto-char)
           ("C-c j l" . avy-goto-line)
           ("C-c j C-c" . avy-goto-char-2)))

which-func config

  (use-package which-func
    :ensure t
    :init (which-function-mode))

UI

  (setq use-dialog-box nil
        truncate-lines nil
        truncate-partial-width-windows nil)

  (menu-bar-mode -1)
  (tool-bar-mode -1)
  (scroll-bar-mode -1)
  (tab-bar-mode -1)
  (global-display-line-numbers-mode -1)
  (global-prettify-symbols-mode +1)
  (global-visual-line-mode +1)

  (use-package which-key
    :ensure t
    :config (which-key-mode +1))

  (use-package page-break-lines
    :ensure t
    :config (global-page-break-lines-mode +1))

  ;; add visual pulse when changing focus, like beacon but built-in
  ;; from from https://karthinks.com/software/batteries-included-with-emacs/
  (defun pulse-line (&rest _)
    "Pulse the current line."
    (pulse-momentary-highlight-one-line (point)))

  (dolist (command '(scroll-up-command
                     scroll-down-command
                     recenter-top-bottom
                     other-window))
    (advice-add command :after #'pulse-line))

Theme, font and nerd-icons

  (set-frame-parameter nil 'alpha-background 80)
  (set-frame-font "Fira Code-12")

  ;; Theme
  (use-package modus-themes
    :ensure t
    :custom (modus-themes-disable-other-themes t)
    :config (modus-themes-load-theme 'modus-vivendi-tinted))

  ;; Nerd-Icons modes
  (use-package nerd-icons
    :ensure t
    :config (nerd-icons-set-font "Symbols Nerd Font Mono"))

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

  (use-package nerd-icons-ibuffer
    :ensure t
    :after nerd-icons
    :hook (ibuffer-mode))

  (use-package nerd-icons-completion
    :ensure t
    :after nerd-icons
    :config (nerd-icons-completion-mode +1))

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

  (keymap-global-set "C-c i n" #'nerd-icons-insert)

Window management

winner

  (use-package winner
    :ensure t
    :demand
    :bind (("C-c w u" . winner-undo)
           ("C-c w r" . winner-redo))
    :config
    (winner-mode))

ediff

  (use-package ediff
    :bind (("C-c d f" . ediff-files)
           ("C-c d b" . ediff-buffers)
           ("C-c d 3 f" . ediff-files3)
           ("C-c d 3 b" . ediff-buffers3))
    :custom
    (ediff-window-setup-function #'ediff-setup-windows-plain))

Font ligatures

  (use-package ligature
    :ensure t
    :config
    ;; Enable the "www" ligature in every possible major mode
    (ligature-set-ligatures 't '("www"))
    ;; Enable traditional ligature support in eww-mode, if the
    ;; `variable-pitch' face supports it
    (ligature-set-ligatures 'eww-mode '("ff" "fi" "ffi"))
    ;; Enable all Cascadia and Fira Code ligatures in programming modes
    (ligature-set-ligatures '(prog-mode org-mode)
                            '(;; == === ==== => =| =>>=>=|=>==>> ==< =/=//=// =~
                              ;; =:= =!=
                              ("=" (rx (+ (or ">" "<" "|" "/" "~" ":" "!" "="))))
                              ;; ;; ;;;
                              (";" (rx (+ ";")))
                              ;; && &&&
                              ("&" (rx (+ "&")))
                              ;; !! !!! !. !: !!. != !== !~
                              ("!" (rx (+ (or "=" "!" "\." ":" "~"))))
                              ;; ?? ??? ?:  ?=  ?.
                              ("?" (rx (or ":" "=" "\." (+ "?"))))
                              ;; %% %%%
                              ("%" (rx (+ "%")))
                              ;; |> ||> |||> ||||> |] |} || ||| |-> ||-||
                              ;; |->>-||-<<-| |- |== ||=||
                              ;; |==>>==<<==<=>==//==/=!==:===>
                              ("|" (rx (+ (or ">" "<" "|" "/" ":" "!" "}" "\]"
                                              "-" "=" ))))
                              ;; \\ \\\ \/
                              ("\\" (rx (or "/" (+ "\\"))))
                              ;; ++ +++ ++++ +>
                              ("+" (rx (or ">" (+ "+"))))
                              ;; :: ::: :::: :> :< := :// ::=
                              (":" (rx (or ">" "<" "=" "//" ":=" (+ ":"))))
                              ;; // /// //// /\ /* /> /===:===!=//===>>==>==/
                              ("/" (rx (+ (or ">"  "<" "|" "/" "\\" "\*" ":" "!"
                                              "="))))
                              ;; .. ... .... .= .- .? ..= ..<
                              ;; ("\." (rx (or "=" "-" "\?" "\.=" "\.<" (+ "\."))))
                              ;; -- --- ---- -~ -> ->> -| -|->-->>->--<<-|
                              ("-" (rx (+ (or ">" "<" "|" "~" "-"))))
                              ;; *> */ *)  ** *** ****
                              ("*" (rx (or ">" "/" ")" (+ "*"))))
                              ;; www wwww
                              ("w" (rx (+ "w")))
                              ;; <> <!-- <|> <: <~ <~> <~~ <+ <* <$ </  <+> <*>
                              ;; <$> </> <|  <||  <||| <|||| <- <-| <-<<-|-> <->>
                              ;; <<-> <= <=> <<==<<==>=|=>==/==//=!==:=>
                              ;; << <<< <<<<
                              ("<" (rx (+ (or "\+" "\*" "\$" "<" ">" ":" "~"  "!"
                                              "-"  "/" "|" "="))))
                              ;; >: >- >>- >--|-> >>-|-> >= >== >>== >=|=:=>>
                              ;; >> >>> >>>>
                              (">" (rx (+ (or ">" "<" "|" "/" ":" "=" "-"))))
                              ;; #: #= #! #( #? #[ #{ #_ #_( ## ### #####
                              ("#" (rx (or ":" "=" "!" "(" "\?" "\[" "{" "_(" "_"
                                           (+ "#"))))
                              ;; ~~ ~~~ ~=  ~-  ~@ ~> ~~>
                              ("~" (rx (or ">" "=" "-" "@" "~>" (+ "~"))))
                              ;; __ ___ ____ _|_ __|____|_
                              ("_" (rx (+ (or "_" "|"))))
                              ;; Fira code: 0xFF 0x12
                              ("0" (rx (and "x" (+ (in "A-F" "a-f" "0-9")))))
                              ;; The few not covered by the regexps.
                              "{|"  "[|"  "]#"  "(*"  "}#"  "$>"  "^="))
    ;; Enables ligature checks globally in all buffers. You can also do it
    ;; per mode with `ligature-mode'.
    (global-ligature-mode t))

Modeline

  (line-number-mode +1)
  (column-number-mode +1)
  (size-indication-mode +1)
  (display-battery-mode +1)
  (display-time-mode -1)

Doom Modeline

  (use-package doom-modeline
    :ensure t
    :custom
    (doom-modeline-icon t)
    (doom-modeline-env-version nil)
    (doom-modeline-buffer-file-name-style 'file-name)
    (doom-modeline-buffer-encoding 'nondefault)
    (doom-modeline-enable-word-count t)
    (doom-modeline-continuous-word-count-modes
     '(org-mode markdown-mode gfm-mode))
    :config
    (doom-modeline-mode +1))

Darkroom

  (use-package darkroom
    :ensure t
    :defer t)

Zone

  (use-package zone
    :ensure t
    :config (zone-when-idle (* 60 60 2))) ; 2 hours

Git status in fringe

  (use-package diff-hl
    :ensure t
    :init
    (add-hook 'magit-pre-refresh-hook 'diff-hl-magit-pre-refresh)
    (add-hook 'magit-post-refresh-hook 'diff-hl-magit-post-refresh)
    :config
    (global-diff-hl-mode)
    :custom
    (diff-hl-disable-on-remote t)
    (diff-hl-draw-borders nil))

Treemacs

  (use-package treemacs
    :ensure t
    :defer t
    :config
    ;; The default width and height of the icons is 22 pixels. If you are
    ;; using a Hi-DPI display, uncomment this to double the icon size.
    ;;(treemacs-resize-icons 44)

    (treemacs-follow-mode t)
    (treemacs-filewatch-mode t)
    (treemacs-fringe-indicator-mode 'always)
    (when treemacs-python-executable
      (treemacs-git-commit-diff-mode t))

    (pcase (cons (not (null (executable-find "git")))
                 (not (null treemacs-python-executable)))
      (`(t . t)
       (treemacs-git-mode 'deferred))
      (`(t . _)
       (treemacs-git-mode 'simple)))

    (treemacs-hide-gitignored-files-mode nil)
    :bind
    ("M-0"       . treemacs-select-window)
    ("C-x t 1"   . treemacs-delete-other-windows)
    ("C-x t t"   . treemacs)
    ("C-x t d"   . treemacs-select-directory)
    ("C-x t B"   . treemacs-bookmark)
    ("C-x t C-t" . treemacs-find-file)
    ("C-x t M-t" . treemacs-find-tag))

  ;; (use-package treemacs-icons-dired
  ;;   :hook (dired-mode . treemacs-icons-dired-enable-once)
  ;;   :ensure t)

  (use-package treemacs-magit
    :after (treemacs magit)
    :ensure t)

  (use-package treemacs-nerd-icons
    :after (treemacs nerd-icons)
    :ensure t
    :config
    (treemacs-load-theme "nerd-icons"))

Org-mode

For reference information, see Org-mode website

  (setq org-directory "~/Org"
        org-default-notes-file (expand-file-name "notes.org" org-directory)
        org-pretty-entities-include-sub-superscripts t
        org-pretty-entities-include-sub-superscripts t
        org-tags-column 0
        org-outline-path-complete-in-steps nil
        org-return-follows-link t
        org-mouse-1-follows-link t
        org-link-descriptive t
        org-refile-use-outline-path t
        org-refile-allow-creating-parent-nodes t
        org-refile-use-outline-path 'file
        org-refile-targets '((nil :maxlevel . 3)
                             (org-agenda-files :maxlevel . 3)))

  ;; Visually indent org-mode files to a given header level
  (add-hook 'org-mode-hook #'org-indent-mode)

  (use-package org
    :ensure t
    :custom
    (org-babel-load-languages '((emacs-lisp . t)
                                (python . t))))

Keymaps

  (keymap-global-set "C-c o ." #'calendar)
  (keymap-global-set "C-c o e" #'org-edit-src-code)
  (keymap-global-set "C-c o a" #'org-agenda)
  (keymap-global-set "C-c o n" #'org-capture)
  (keymap-global-set "C-c o l" #'org-capture-goto-last-stored)

  (use-package org-modern
    :ensure t
    :hook ((org-mode . org-modern-mode)
           (org-agenda-finalize . org-modern-agenda))
    :custom
    (org-modern-tag nil))

org-agenda

  (setq org-agenda-span 'week
        org-agenda-start-on-weekday 1
        org-agenda-files (list (expand-file-name org-directory)
                               (expand-file-name "journal" org-directory)
                               (expand-file-name "projects" org-directory))
        org-agenda-sticky t
        org-agenda-window-setup 'current-window
        org-agenda-prefix-format '((agenda . " %-12:c%?-12t% s")
                                   (todo . " %-12:c")
                                   (tags . " %-12:c")
                                   (search . " %-12:c")))

org-journal

  (use-package org-journal
    :bind (("C-c o j" . org-journal-new-entry))
    :custom
    (org-journal-dir (expand-file-name "journal" org-directory))
    (org-journal-file-type 'monthly)
    (org-journal-file-format "%Y-%m.org"))

Capture templates

  (setq org-capture-templates
        '(("n" "Note" entry
           (file+headline "inbox.org" "Note")
           "* %?"
           :prepend t
           :empty-lines 1)
          ("t" "Task" entry
           (file+headline "inbox.org" "Task")
           "* TODO %?"
           :prepend t
           :empty-lines 1)
          ("m" "Email Workflow")
          ("mf" "Follow Up" entry (file+olp "mail.org" "Follow Up")
           "* TODO Follow up with %:fromname on %a\nSCHEDULED:%t\nDEADLINE: %(org-insert-time-stamp (org-read-date nil t \"+2d\"))\n\n%i")
          ("mr" "Read Later" entry (file+olp "mail.org" "Read Later")
           "* TODO Read %:subject\nSCHEDULED:%t\nDEADLINE: %(org-insert-time-stamp (org-read-date nil t \"+2d\"))\n\n%a\n\n%i")
          ))

TODO Citar

  (let ((citar-base-directory "~/References"))
    (setq citar-bibliography (expand-file-name "main.bib" citar-base-directory)
          citar-library-paths (list (expand-file-name "library" citar-base-directory))
          citar-notes-paths (list (expand-file-name "notes" citar-base-directory))))
  (use-package citar
    :ensure t
    :bind (("C-c o c o" . citar-open))
    :config
    (defvar citar-indicator-files-icons
      (citar-indicator-create
       :symbol (nerd-icons-octicon
                "nf-oct-file"
                :face 'nerd-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 (nerd-icons-octicon
                "nf-oct-link"
                :face 'nerd-icons-orange
                :v-adjust 0.01)
       :function #'citar-has-links
       :padding "  "
       :tag "has:links"))
    (defvar citar-indicator-notes-icons
      (citar-indicator-create
       :symbol (nerd-icons-octicon
                "nf-oct-note"
                :face 'nerd-icons-blue
                :v-adjust -0.3)
       :function #'citar-has-notes
       :padding "    "
       :tag "has:notes"))
    (defvar citar-indicator-cited-icons
      (citar-indicator-create
       :symbol (nerd-icons-octicon
                "nf-oct-circle"
                :face 'nerd-icon-green)
       :function #'citar-is-cited
       :padding "  "
       :tag "is:cited"))
    (setq citar-indicators (list citar-indicator-files-icons
                                 citar-indicator-links-icons
                                 citar-indicator-notes-icons
                                 citar-indicator-cited-icons)))

Khalel

  (use-package khalel
    :demand
    :hook (org-agenda-mode . khalel-import-events)
    :config
    (khalel-add-capture-template "e")
    :custom
    (khalel-import-org-file (expand-file-name "calendar.org" org-directory))
    (khalel-import-org-file-read-only nil)
    (khalel-import-org-file-confirm-overwrite nil)
    (khalel-import-start-date "-30d")
    (khalel-import-end-date "+30d"))

TODO Crafted config

  

  ;; Disable auto-pairing of "<" in org-mode with electric-pair-mode
  (defun crafted-org-enhance-electric-pair-inhibit-predicate ()
    "Disable auto-pairing of \"<\" in `org-mode' when using `electric-pair-mode'."
    (when (and electric-pair-mode (eql major-mode #'org-mode))
      (setq-local electric-pair-inhibit-predicate
                  `(lambda (c)
                     (if (char-equal c ?<)
                         t
                       (,electric-pair-inhibit-predicate c))))))

  ;; Add hook to both electric-pair-mode-hook and org-mode-hook
  ;; This ensures org-mode buffers don't behave weirdly,
  ;; no matter when electric-pair-mode is activated.
  (add-hook 'electric-pair-mode-hook #'crafted-org-enhance-electric-pair-inhibit-predicate)
  (add-hook 'org-mode-hook #'crafted-org-enhance-electric-pair-inhibit-predicate)

Remote Editing

TRAMP

  (use-package tramp
    :defer t
    :config
    (add-to-list 'tramp-remote-path 'tramp-own-remote-path))

Development Environment

  (use-package vterm
    :custom
    (vterm-copy-mode-remove-fake-newlines t))

  (use-package rainbow-delimiters
    :ensure t
    :hook (prog-mode))

  (use-package apheleia
    :ensure t
    :defer nil
    :bind (("C-c c f" . apheleia-format-buffer))
    :custom (apheleia-remote-algorithm 'local)
    :config (apheleia-global-mode +1))

  (use-package treesit-auto
    :config (global-treesit-auto-mode +1))

  (use-package direnv
    :ensure t
    :custom (direnv-always-show-summary nil)
    :config (direnv-mode +1))

Eldoc

  (use-package eldoc
    :ensure t
    :custom
    (eldoc-echo-area-display-truncation-message nil)
    (eldoc-echo-area-prefer-doc-buffer t)
    (eldoc-echo-area-use-multiline-p nil))

Eglot LSP

  (use-package eglot
    :ensure t
    :bind (("C-c c e" . eglot)
           ("C-c c C-e" . eglot-reconnect)
           ("C-c c a" . eglot-code-actions)
           ("C-c c r" . eglot-rename))
    :hook ((nix-mode
            fortran-mode
            shell-script-mode
            lua-mode
            python-mode
            python-ts-mode
            rust-mode
            rust-ts-mode) . eglot-ensure)
    :custom
    (eglot-extend-to-xref t)
    (eglot-autoshutdown t)
    (eglot-autoreconnect nil)
    :config
    (add-to-list 'eglot-server-programs
                 `((rust-ts-mode rust-mode) . ("rust-analyzer"
                                               :initializationOptions
                                               ( :check (:command "clippy")
                                                 :procMacro (:enable t)
                                                 :cargo ( :buildScripts (:enable t)
                                                          :features "all"))))))

Flymake

  (use-package flymake
    :ensure t
    :bind (("C-c C-." . flymake-goto-next-error)
           ("C-c C-," . flymake-goto-prev-error)))

Diagnostics in popup

  (use-package flymake-popon
    :ensure t
    :after flymake
    :config
    (global-flymake-popon-mode +1))

Project

  (setq project-switch-use-entire-map t
        project-switch-commands '((project-find-file "Find file")
                                  (project-find-regexp "Find regexp")
                                  (project-find-dir "Find directory")
                                  (project-vc-dir "VC-Dir")
                                  (project-eshell "Eshell")))

Version control

Magit

  (use-package magit
    :ensure t
    :bind (("C-c g g" . magit-status)
           ("C-c g d" . magit-dispatch)
           ("C-c g f" . magit-file-dispatch)
           ("C-c g p" . magit-pull)
           ("C-c g P" . magit-push)
           ("<remap> <project-vc-dir" . magit-project-status)
           :map project-prefix-map
           ("m" . magit-project-status))
    :custom
    (magit-define-global-key-bindings nil)
    (magit-clone-default-directory "~/Projects/")
    (magit-commit-show-diff nil)
    (magit-commit-diff-inhibit-same-window t))

Completion

Vertico

  (use-package vertico
    :ensure t
    :custom
    (vertico-cycle t)
    :init
    (vertico-mode)
    :config
    (require 'vertico-directory))

Marginalia

  (use-package marginalia
    :ensure t
    :custom
    (marginalia-annotators '(marginalia-annotators-heavy
                             marginalia-annotators-light
                             nil))
    :config (marginalia-mode +1))

Orderless

  (use-package orderless
    :ensure t
    :custom
    (completion-styles '(orderless basic))
    (completion-category-defaults nil)
    (completion-category-overrides '((file (styles . (partial-completion))))))

Corfu and Cape

  (use-package corfu
    :ensure t
    :after vertico
    :custom
    (corfu-cycle t)
    (corfu-auto t)
    (corfu-auto-prefix 3)
    (corfu-quit-no-match 'separator)
    (corfu-quit-at-boundary 'separator)
    (corfu-preselect 'directory)
    :bind (:map corfu-map
                ("RET" . nil))
    :init
    (global-corfu-mode +1)
    :config
    (when (require 'corfu-popupinfo nil :noerror)
      (corfu-popupinfo-mode +1)
      (keymap-set corfu-map "M-p" #'corfu-popupinfo-scroll-down)
      (keymap-set corfu-map "M-n" #'corfu-popupinfo-scroll-up)
      (keymap-set corfu-map "M-d" #'corfu-popupinfo-toggle))

    (defun corfu-enable-always-in-minibuffer ()
      "Enable Corfu in the minibuffer if Vertico is not active."
      (unless (or (bound-and-true-p vertico--input)
                  (eq (current-local-map) read-passwd-map))
        (setq-local corfu-echo-delay nil ;; Disable automatic echo and popup
                    corfu-auto nil ;; Enable/disable auto completion
                    corfu-popupinfo-delay nil)
        (corfu-mode +1)))
    (add-hook 'minibuffer-setup-hook #'corfu-enable-always-in-minibuffer 1)

    (add-hook 'eshell-mode-hook
              (lambda ()
                (setq-local corfu-auto nil)
                (corfu-mode)))

    (defun corfu-send-shell (&rest _)
      "Send completion candidate when inside comint/eshell."
      (cond
       ((and (derived-mode-p 'eshell-mode) (fboundp 'eshell-send-input))
        (eshell-send-input))
       ((and (derived-mode-p 'comint-mode)  (fboundp 'comint-send-input))
        (comint-send-input))))

    (advice-add #'corfu-insert :after #'corfu-send-shell))

  (use-package cape
    :ensure t
    :after corfu
    :config
    (add-to-list 'completion-at-point-functions #'cape-file)
    (add-to-list 'completion-at-point-functions #'cape-dabbrev)
    (advice-add 'pcomplete-completions-at-point :around #'cape-wrap-silent)
    (advice-add 'pcomplete-completions-at-point :around #'cape-wrap-purify))

Consult

  (use-package consult
    :ensure t
    :bind (("C-s" . consult-line)
           ("<remap> <imenu>" . consult-imenu )
           ("<remap> <switch-to-buffer>" . consult-buffer)
           ("<remap> <project-switch-to-buffer>" . consult-project-buffer)
           ("<remap> <org-goto>" . consult-org-heading)
           ("C-c s f" . consult-fd)
           ("C-c s g" . consult-ripgrep)
           ("C-c s e" . consult-flymake)
           ("C-c s i" . consult-info)
           ("C-c s t" . consult-theme)
           :map minibuffer-local-map
           ("C-r" . consult-history))
    :config (setq completion-in-region-function #'consult-completion-in-region))

  (use-package consult-eglot
    :ensure t
    :after consult
    :commands (consult-eglot-symbols)
    :bind (("C-c s s" . consult-eglot-symbols)))

Embark

  (use-package embark
    :ensure t
    :bind (("<remap> <describe-bindings>" . embark-bindings)
           ("C-." . embark-act))
    :config (setq prefix-help-command #'embark-prefix-help-command))

  (use-package embark-consult
    :ensure t
    :after (embark consult)
    :hook (embark-collect-mode . consult-preview-at-point-mode))

Snippets

  ;; Configure Tempel
  (use-package tempel
    :ensure t
    ;; Require trigger prefix before template name when completing.
    :custom
    (tempel-trigger-prefix "<")

    :bind (("M-+" . tempel-complete) ;; Alternative tempel-expand
           ("M-*" . tempel-insert))

    :init

    ;; Setup completion at point
    (defun tempel-setup-capf ()
      ;; Add the Tempel Capf to `completion-at-point-functions'.
      ;; `tempel-expand' only triggers on exact matches. Alternatively use
      ;; `tempel-complete' if you want to see all matches, but then you
      ;; should also configure `tempel-trigger-prefix', such that Tempel
      ;; does not trigger too often when you don't expect it. NOTE: We add
      ;; `tempel-expand' *before* the main programming mode Capf, such
      ;; that it will be tried first.
      (setq-local completion-at-point-functions
                  (cons #'tempel-expand
                        completion-at-point-functions)))

    (add-hook 'conf-mode-hook 'tempel-setup-capf)
    (add-hook 'prog-mode-hook 'tempel-setup-capf)
    (add-hook 'text-mode-hook 'tempel-setup-capf)

    ;; Optionally make the Tempel templates available to Abbrev,
    ;; either locally or globally. `expand-abbrev' is bound to C-x '.
    (add-hook 'prog-mode-hook #'tempel-abbrev-mode))

  ;; Optional: Add tempel-collection.
  ;; The package is young and doesn't have comprehensive coverage.
  (use-package tempel-collection
    :ensure t)

Aggressive Indent

  (use-package aggressive-indent
    :ensure t
    :hook (elisp-mode
           lisp-mode
           lisp-data-mode
           rust-mode))

Language-specific settings

Python

Set fill column to 88 and enable display in python buffers

  (defun my/enable-fill-column (col)
    "Set and enable fill column"
    (set-fill-column col)
    (display-fill-column-indicator-mode +1))

  (add-hook 'python-mode-hook (lambda () (my/enable-fill-column 88)))

Internet

TODO Gnus

  (setq gnus-select-method '(nnrss "http://www.fsf.org/static/fsforg/rss/news.xml"))

Email

  (setq sendmail-program (executable-find "msmtp")
        send-mail-function #'smtpmail-send-it
        message-sendmail-f-is-evil t
        message-sendmail-extra-arguments '("--read-envelope-from")
        message-send-mail-function #'message-send-mail-with-sendmail
        message-auto-save-directory nil
        message-kill-buffer-on-exit t
        mail-user-agent 'mu4e-user-agent
        read-mail-command 'mu4e)

  (use-package mu4e
    :bind (("C-c o m" . mu4e))
    :custom
    (mu4e-completing-read-function #'completing-read-default)
    (mu4e-split-view 'horizontal)
    (mu4e-attachment-dir "~/Downloads")
    (mu4e-get-mail-command "mbsync -a")
    (mu4e-update-interval (* 5 60)) ; Every 5 minutes
    (mu4e-headers-auto-update nil)
    (mu4e-sent-messages-behavior 'sent)
    (mu4e-change-filenames-when-moving t)
    (mu4e-context-policy 'ask-if-none)
    (mu4e-compose-context-policy 'ask-if-none)
    (mu4e-search-full nil)
    (mu4e-search-include-related t)
    (mu4e-search-threads t)
    (mu4e-search-skip-duplicates t)
    (mu4e-maildir-shortcuts '((:maildir "/Proton/Inbox/" :key ?p)
                              (:maildir "/iCloud/Inbox/" :key ?i)
                              (:maildir "/Outlook/Inbox/" :key ?w)))
    (mu4e-bookmarks '((:name "Unread messages" :query "flag:unread AND NOT flag:trashed" :key ?u :favorite t)
                      (:name "Today's messages" :query "date:today..now" :key ?t)
                      (:name "Flagged messages" :query "flag:flagged" :key ?f)
                      (:name "Last 7 days" :query "date:7d..now" :hide-unread t :key ?w)))
    (mu4e-headers-fields
     '((:human-date . 8)
       (:from . 30)
       (:subject . 72)
       (:flags)))
    (mu4e-headers-visible-flags
     '(draft
       flagged
       unread
       passed
       replied
       trashed
       attach
       calendar
       encrypted
       signed
       list
       personal))
    :config
    (setq
     mu4e-use-fancy-chars t
     mu4e-modeline-all-read '("R:" . "󰑇 ")
     mu4e-modeline-all-clear '("C:" . "󰚭 ")
     mu4e-modeline-new-items '("N:" . "󰎔 ")
     mu4e-modeline-unread-items '("U:" . "󰶊 ")
     mu4e-search-full-label '("F" . "󱊖 ")
     mu4e-search-hide-label '("H" . "󰘓 ")
     mu4e-search-related-label '("R" . "󰻧 ")
     mu4e-search-skip-duplicates-label '("D" . "󰆑 ")
     mu4e-search-threaded-label'("T" . "󱇫 ")
     mu4e-headers-draft-mark '("D" . "󰻣 ")
     mu4e-headers-flagged-mark '("F" . "󰈻 ")
     mu4e-headers-unread-mark '("u" . "󰶊 ")
     mu4e-headers-passed-mark '("P" . "󱃜 ")
     mu4e-headers-replied-mark '("R" . "󱃚 ")
     mu4e-headers-trashed-mark '("T" . "󰩹 ")
     mu4e-headers-attach-mark '("a" . "󰁦 ")
     mu4e-headers-calendar-mark '("c" . "󰃭 ")
     mu4e-headers-encrypted-mark '("x" . "󰇱 ")
     mu4e-headers-signed-mark '("s" . "󰷻 ")
     mu4e-headers-list-mark '("l" . "󰻧 ")
     mu4e-headers-personal-mark '("p" . "󰍡 ")
     mu4e-headers-seen-mark '("S" . "󰑇 ")
     mu4e-headers-new-mark '("N" . "󰎔 ")
     mu4e-headers-from-or-to-prefix '("   " . "To ")
     mu4e-headers-thread-root-prefix          '("* " . "* ")
     mu4e-headers-thread-duplicate-prefix     '("= " . "= ")
     mu4e-headers-thread-blank-prefix         '("  " . "  ")
     mu4e-headers-thread-single-orphan-prefix '("─>" . "─▶")
     mu4e-headers-thread-orphan-prefix        '("┬>" . "┬▶")
     mu4e-headers-thread-connection-prefix    '("│ " . "│ ")
     mu4e-headers-thread-first-child-prefix   '("├>" . "├▶")
     mu4e-headers-thread-child-prefix         '("├>" . "├▶")
     mu4e-headers-thread-last-child-prefix    '("└>" . "╰▶")
     mu4e-marks '((refile :char
                          ("r" . "󰀼 ")
                          :prompt "refile" :dyn-target
                          (lambda
                            (target msg)
                            (mu4e-get-refile-folder msg))
                          :action
                          (lambda
                            (docid msg target)
                            (mu4e--server-move docid
                                               (mu4e--mark-check-target target)
                                               "-N")))
                  (delete :char
                          ("D" . "󰗨 ")
                          :prompt "Delete" :show-target
                          (lambda
                            (target)
                            "delete")
                          :action
                          (lambda
                            (docid msg target)
                            (mu4e--server-remove docid)))
                  (flag :char
                        ("+" . "󰈻 ")
                        :prompt "+flag" :show-target
                        (lambda
                          (target)
                          "flag")
                        :action
                        (lambda
                          (docid msg target)
                          (mu4e--server-move docid nil "+F-u-N")))
                  (move :char
                        ("m" . "󰪹 ")
                        :prompt "move" :ask-target mu4e--mark-get-move-target :action
                        (lambda
                          (docid msg target)
                          (mu4e--server-move docid
                                             (mu4e--mark-check-target target)
                                             "-N")))
                  (read :char
                        ("!" . "󰑇 ")
                        :prompt "!read" :show-target
                        (lambda
                          (target)
                          "read")
                        :action
                        (lambda
                          (docid msg target)
                          (mu4e--server-move docid nil "+S-u-N")))
                  (trash :char
                         ("d" . "󰆴 ")
                         :prompt "dtrash" :dyn-target
                         (lambda
                           (target msg)
                           (mu4e-get-trash-folder msg))
                         :action
                         (lambda
                           (docid msg target)
                           (mu4e--server-move docid
                                              (mu4e--mark-check-target target)
                                              "+T-N")))
                  (unflag :char
                          ("-" . "󱣮 ")
                          :prompt "-unflag" :show-target
                          (lambda
                            (target)
                            "unflag")
                          :action
                          (lambda
                            (docid msg target)
                            (mu4e--server-move docid nil "-F-N")))
                  (untrash :char
                           ("=" . "󱂧 ")
                           :prompt "=untrash" :show-target
                           (lambda
                             (target)
                             "untrash")
                           :action
                           (lambda
                             (docid msg target)
                             (mu4e--server-move docid nil "-T")))
                  (unread :char
                          ("?" . "󰮒 ")
                          :prompt "?unread" :show-target
                          (lambda
                            (target)
                            "unread")
                          :action
                          (lambda
                            (docid msg target)
                            (mu4e--server-move docid nil "-S+u-N")))
                  (unmark :char " " :prompt "unmark" :action
                          (mu4e-error "No action for unmarking"))
                  (action :char
                          ("a" . "󰋘 ")
                          :prompt "action" :ask-target
                          (lambda nil
                            (mu4e-read-option "Action: " mu4e-headers-actions))
                          :action
                          (lambda
                            (docid msg actionfunc)
                            (save-excursion
                              (when
                                  (mu4e~headers-goto-docid docid)
                                (mu4e-headers-action actionfunc)))))
                  (something :char
                             ("*" . "󱗿 ")
                             :prompt "*something" :action
                             (mu4e-error "No action for deferred mark"))))
    (require 'mu4e-org)
    (mu4e-modeline-mode)
    (setq mu4e-contexts
          (list
           (make-mu4e-context
            :name "Xenia"
            :vars '((user-mail-address . "evie@xenia.me.uk")
                    (mu4e-sent-folder . "/Proton/Sent")
                    (mu4e-drafts-folder . "/Proton/Drafts")
                    (mu4e-trash-folder . "/Proton/Trash")
                    (mu4e-refile-folder . "/Proton/Archive")))
           (make-mu4e-context
            :name "Proton"
            :match-func (lambda (msg) (when msg (string-prefix-p "/Proton" (mu4e-message-field msg :maildir))))
            :vars '((user-mail-address . "e.litherlandsmith@proton.me")
                    (mu4e-sent-folder . "/Proton/Sent")
                    (mu4e-drafts-folder . "/Proton/Drafts")
                    (mu4e-trash-folder . "/Proton/Trash")
                    (mu4e-refile-folder . "/Proton/Archive")))
           (make-mu4e-context
            :name "iCloud"
            :match-func (lambda (msg) (when msg (string-prefix-p "/iCloud" (mu4e-message-field msg :maildir))))
            :vars '((user-mail-address . "e.litherlandsmith@icloud.com")
                    (mu4e-sent-folder . "/iCloud/Sent Messages")
                    (mu4e-drafts-folder . "/iCloud/Drafts")
                    (mu4e-trash-folder . "/iCloud/Deleted Messages")
                    (mu4e-refile-folder . "/iCloud/Archive")))
           (make-mu4e-context
            :name "Work"
            :match-func (lambda (msg) (when msg (string-prefix-p "/Outlook" (mu4e-message-field msg :maildir))))
            :vars '((user-mail-address . "evie.litherland-smith@ukaea.uk")
                    (mu4e-sent-folder . "/Outlook/Sent")
                    (mu4e-drafts-folder . "/Outlook/Drafts")
                    (mu4e-trash-folder . "/Outlook/Trash")
                    (mu4e-refile-folder . "/Outlook/Archive"))))))

  (use-package mu4e-alert
    :after mu4e
    :config
    (mu4e-alert-set-default-style 'libnotify)
    (mu4e-alert-enable-notifications)
    (mu4e-alert-disable-mode-line-display))

TODO Feeds

  (let ((elfeed-base-directory "~/.elfeed"))
    (setq elfeed-db-directory (expand-file-name "db" elfeed-base-directory)
          elfeed-enclosure-default-dir (expand-file-name "enclosures" elfeed-base-directory)
          rmh-elfeed-org-files (list (expand-file-name "feeds.org" elfeed-base-directory))))
  (use-package elfeed
    :bind (("C-c f f" . elfeed)
           ("C-c f u" . elfeed-update))
    :hook (elfeed-search-mode . elfeed-update)
    :config
    (use-package elfeed-org
      :ensure t
      :config (elfeed-org))
    (use-package elfeed-tube
      :ensure t
      :config (elfeed-tube-setup)))

Social

Mastodon

  (use-package mastodon
    :commands mastodon
    :custom
    (mastodon-instance-url "https://tech.lgbt")
    (mastodon-active-user "Tux922"))

Matrix / Ement.el

  (defun my/ement-connect-pantalaimon ()
    "Connect to ement.el by calling `ement-connect' with pantalaimon service as uri-prefix"
    (interactive)
    (ement-connect
     :user-id "@evie:xenia.me.uk"
     :uri-prefix "http://localhost:8008"))

  (use-package ement
    :ensure t
    :commands ement-connect
    :bind (("C-c m m" . ement-list-rooms)
           ("C-c m n" . ement-notifications)
           ("C-c m j" . ement-room-join)
           ("C-c m l" . ement-room-leave)
           ("C-c m v" . ement-room-view)
           ("C-c m s" . ement-view-space)
           ("C-c m k" . ement-kill-buffers)
           ("C-c m c" . my/ement-connect-pantalaimon))
    :custom
    (ement-save-sessions t))

Media

EMMS

  (setq emms-mode-line-icon-enabled-p nil)
  (use-package emms
    :bind (("C-c e e" . emms-pause)
           ("C-c e s" . emms-stop)
           ("C-c e b" . emms-browser)
           ("C-c e p" . emms-playlist-mode-go)
           ("<XF86AudioPlay>" . emms-pause)
           ("<XF86AudioPrev>" . emms-previous)
           ("<XF86AudioNext>" . emms-next))
    :custom
    (emms-source-file-default-directory "~/Music")
    (emms-lyrics-dir "~/Music/lyrics")
    (emms-browser-covers #'emms-browser-cache-thumbnail-async)
    (emms-repeat-playlist t)
    :config
    (emms-all)
    (emms-default-players)
    (emms-mpris-enable)
    (add-hook 'emms-player-started-hook #'emms-show))

Writing

Doc-view

  (use-package doc-view
    :defer t
    :ensure t
    :custom
    (doc-view-resolution 200)
    (doc-view-continuous t)
    (doc-view-imenu-enabled t)
    (doc-view-scale-internally t)
    (doc-view-image-width 850))

TODO Crafted Config

  ;;; Whitespace
  (defun crafted-writing-configure-whitespace (use-tabs &optional use-globally &rest enabled-modes)
    "Helper function to configure `whitespace' mode.

  Enable using TAB characters if USE-TABS is non-nil.  If
  USE-GLOBALLY is non-nil, turn on `global-whitespace-mode'.  If
  ENABLED-MODES is non-nil, it will be a list of modes to activate
  whitespace mode using hooks.  The hooks will be the name of the
  mode in the list with `-hook' appended.  If USE-GLOBALLY is
  non-nil, ENABLED-MODES is ignored.

  Configuring whitespace mode is not buffer local.  So calling this
  function twice with different settings will not do what you
  think.  For example, if you wanted to use spaces instead of tabs
  globally except for in Makefiles, doing the following won't work:

  ;; turns on global-whitespace-mode to use spaces instead of tabs
  (crafted-writing-configure-whitespace nil t)

  ;; overwrites the above to turn to use tabs instead of spaces,
  ;; does not turn off global-whitespace-mode, adds a hook to
  ;; makefile-mode-hook
  (crafted-writing-configure-whitespace t nil 'makefile-mode)

  Instead, use a configuration like this:
  ;; turns on global-whitespace-mode to use spaces instead of tabs
  (crafted-writing-configure-whitespace nil t)

  ;; turn on the buffer-local mode for using tabs instead of spaces.
  (add-hook 'makefile-mode-hook #'indent-tabs-mode)

  For more information on `indent-tabs-mode', See the info
  node `(emacs)Just Spaces'

  Example usage:

  ;; Configuring whitespace mode does not turn on whitespace mode
  ;; since we don't know which modes to turn it on for.
  ;; You will need to do that in your configuration by adding
  ;; whitespace mode to the appropriate mode hooks.
  (crafted-writing-configure-whitespace nil)

  ;; Configure whitespace mode, but turn it on globally.
  (crafted-writing-configure-whitespace nil t)

  ;; Configure whitespace mode and turn it on only for prog-mode
  ;; and derived modes.
  (crafted-writing-configure-whitespace nil nil 'prog-mode)"
    (if use-tabs
        (customize-set-variable 'whitespace-style
                                '(face empty trailing indentation::tab
                                       space-after-tab::tab
                                       space-before-tab::tab))
      ;; use spaces instead of tabs
      (customize-set-variable 'whitespace-style
                              '(face empty trailing tab-mark
                                     indentation::space)))

    (if use-globally
        (global-whitespace-mode 1)
      (when enabled-modes
        (dolist (mode enabled-modes)
          (add-hook (intern (format "%s-hook" mode)) #'whitespace-mode))))

    ;; cleanup whitespace
    (customize-set-variable 'whitespace-action '(cleanup auto-cleanup)))

  ;;; parentheses
  (electric-pair-mode 1) ; auto-insert matching bracket
  (show-paren-mode 1)    ; turn on paren match highlighting

  ;;; LaTeX configuration
  (with-eval-after-load 'latex
    (customize-set-variable 'TeX-auto-save t)
    (customize-set-variable 'TeX-parse-self t)
    (setq-default TeX-master nil)

    ;; compile to pdf
    (tex-pdf-mode)

    ;; correlate the source and the output
    (TeX-source-correlate-mode)

    ;; set a correct indentation in a few additional environments
    (add-to-list 'LaTeX-indent-environment-list '("lstlisting" current-indentation))
    (add-to-list 'LaTeX-indent-environment-list '("tikzcd" LaTeX-indent-tabular))
    (add-to-list 'LaTeX-indent-environment-list '("tikzpicture" current-indentation))

    ;; add a few macros and environment as verbatim
    (add-to-list 'LaTeX-verbatim-environments "lstlisting")
    (add-to-list 'LaTeX-verbatim-environments "Verbatim")
    (add-to-list 'LaTeX-verbatim-macros-with-braces "lstinline")
    (add-to-list 'LaTeX-verbatim-macros-with-delims "lstinline")

    ;; electric pairs in auctex
    (customize-set-variable 'TeX-electric-sub-and-superscript t)
    (customize-set-variable 'LaTeX-electric-left-right-brace t)
    (customize-set-variable 'TeX-electric-math (cons "$" "$"))

    ;; open all buffers with the math mode and auto-fill mode
    (add-hook 'LaTeX-mode-hook #'auto-fill-mode)
    (add-hook 'LaTeX-mode-hook #'LaTeX-math-mode)

    ;; add support for references
    (add-hook 'LaTeX-mode-hook #'turn-on-reftex)
    (customize-set-variable 'reftex-plug-into-AUCTeX t)

    ;; to have the buffer refresh after compilation
    (add-hook 'TeX-after-compilation-finished-functions #'TeX-revert-document-buffer))

  (defun crafted-latex-use-pdf-tools ()
    "Use PDF Tools instead of docview, requires a build environment
  to compile PDF Tools.

  Depends on having `pdf-tools'."

    (with-eval-after-load 'latex
      (customize-set-variable 'TeX-view-program-selection '((output-pdf "PDF Tools")))
      (customize-set-variable 'TeX-view-program-list '(("PDF Tools" TeX-pdf-tools-sync-view)))
      (customize-set-variable 'TeX-source-correlate-start-server t)))

  ;; message the user if the latex executable is not found
  (defun crafted-writing-tex-warning-if-no-latex-executable ()
    "Print a message to the minibuffer if the \"latex\" executable cannot be found."
    (unless (executable-find "latex")
      (message "latex executable not found")))
  (add-hook 'tex-mode-hook #'crafted-writing-tex-warning-if-no-latex-executable)

  (when (and (executable-find "latex")
             (executable-find "latexmk"))
    (with-eval-after-load 'latex
      (when (require 'auctex-latexmk nil 'noerror)
        (with-eval-after-load 'auctex-latexmk
          (auctex-latexmk-setup)
          (customize-set-variable 'auctex-latexmk-inherit-TeX-PDF-mode t))

        (defun crafted-writing-tex-make-latexmk-default-command ()
          "Set `TeX-command-default' to \"LatexMk\"."
          (setq TeX-command-default "LatexMk"))
        (add-hook 'TeX-mode-hook #'crafted-writing-tex-make-latexmk-default-command))))

  ;;; Markdown
  (when (fboundp 'markdown-mode)
    ;; because the markdown-command variable may not be loaded (yet),
    ;; check manually for the other markdown processors.  If it is
    ;; loaded, the others are superfluous but `or' fails fast, so they
    ;; are not checked if `markdown-command' is set and the command is
    ;; indeed found.
    (unless (or (and (boundp 'markdown-command)
                     (executable-find markdown-command))
                (executable-find "markdown")
                (executable-find "pandoc"))
      (message "No markdown processor found, preview may not possible."))

    (with-eval-after-load 'markdown-mode
      (customize-set-variable 'markdown-enable-math t)
      (customize-set-variable 'markdown-enable-html t)
      (add-hook 'markdown-mode-hook #'conditionally-turn-on-pandoc)))

  ;;; PDF Support when using pdf-tools
  (when (locate-library "pdf-tools")
    ;; load pdf-tools when going into doc-view-mode
    (defun crafted-writing-load-pdf-tools ()
      "Attempts to require pdf-tools, but for attaching to hooks."
      (require 'pdf-tools nil :noerror))
    (add-hook 'doc-view-mode-hook #'crafted-writing-load-pdf-tools)

    ;; when pdf-tools is loaded, apply settings.
    (with-eval-after-load 'pdf-tools
      (setq-default pdf-view-display-size 'fit-width)))