52 KiB
52 KiB
Emacs Config
- Setup
- Common defaults
- UI
- Org-mode
- Password Store
- Remote Editing
- Development Environment
- Internet
- Media
- Writing
Setup
Loading this file is handled automatically on my NixOS config, otherwise add (org-babel-load-file "~/.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 t
global-auto-revert-non-file-buffers t
dired-auto-revert-buffer t
dired-dwim-target t
tab-always-indent t
completion-cycle-threshold nil
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 +1)
;; 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)))
(savehist-mode +1)
Recent files
(use-package recentf
:config
(run-at-time nil (* 5 60) 'recentf-save-list)
(recentf-mode +1)
:custom
(recentf-max-saved-items 2048))
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))))
diminish modes
(use-package diminish :ensure t)
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)
Spell checking
;; turn on spell checking, if available.
(when (and (require 'ispell nil :noerror) (executable-find ispell-program-name))
(use-package flyspell
:ensure t
:diminish
:hook ((text-mode . flyspell-mode)
(prog-mode . flyspell-prog-mode))
:custom
(flyspell-mode-line-string nil)
(flyspell-use-meta-tab nil))
(use-package flyspell-correct
:ensure t
:diminish
:after flyspell
:bind ( :map flyspell-mode-map
("C-;" . flyspell-correct-wrapper)))
(use-package consult-flyspell
:ensure t
:diminish
:after (consult flyspell)
:bind ( :map flyspell-mode-map
("C-c s ;" . consult-flyspell))
:config
(setq consult-flyspell-always-check-buffer t)))
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
:diminish
:bind (("C-c j j" . avy-goto-char-2)
("C-c j w" . avy-goto-word-0)
("C-c j c" . avy-goto-char)
("C-c j l" . avy-goto-line)))
which-func config
(use-package which-func
:ensure t
:init (which-function-mode))
Shells and terminals
(use-package shell
:bind (("C-c t s" . shell)))
(use-package eshell
:bind (("C-c t e" . eshell)))
UI
(setq use-dialog-box nil
truncate-lines nil
truncate-partial-width-windows nil)
(menu-bar-mode +1)
(global-prettify-symbols-mode +1)
(global-display-line-numbers-mode -1)
(tool-bar-mode -1)
(scroll-bar-mode -1)
(tab-bar-mode -1)
(add-hook 'prog-mode-hook #'(lambda () (display-line-numbers-mode +1)))
(with-eval-after-load 'diminish
(diminish 'visual-line-mode))
(global-visual-line-mode +1)
(use-package which-key
:ensure t
:diminish
:config (which-key-mode +1))
(use-package page-break-lines
:ensure t
:diminish
: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
(add-to-list 'initial-frame-alist '(width . 120))
(add-to-list 'initial-frame-alist '(height . 80))
(add-to-list 'initial-frame-alist '(alpha-background . 80))
(add-to-list 'initial-frame-alist '(font . "FiraCode Nerd Font-12"))
(add-to-list 'default-frame-alist '(alpha-background . 80))
(add-to-list 'default-frame-alist '(font . "FiraCode Nerd Font-12"))
;; Theme
(use-package modus-themes
:ensure t
:custom
(modus-themes-disable-other-themes t)
(modus-themes-to-toggle '(modus-operandi-tinted modus-vivendi-tinted))
(modus-themes-bold-constructs nil) ; Doesn't work nicely with FiraCode
(modus-themes-italic-constructs nil) ; Doesn't work nicely with FiraCode
(modus-themes-org-blocks 'gray-background)
(modus-themes-completions '((matches . (extrabold underline))
(selection . (semibold italic))))
(modus-themes-headings '((1 . (1.4))
(2 . (1.3))
(3 . (1.2))
(agenda-date . (1.3))
(agenda-structure . (light 1.8))
(t . (1.1))))
:config
(modus-themes-load-theme 'modus-vivendi-tinted))
;; Nerd-Icons modes
(use-package nerd-icons
:ensure t
:diminish
:config (nerd-icons-set-font "Symbols Nerd Font Mono-12"))
(use-package nerd-icons-dired
:ensure t
:after nerd-icons
:diminish
:hook (dired-mode))
(use-package nerd-icons-ibuffer
:ensure t
:after nerd-icons
:diminish
:hook (ibuffer-mode))
(use-package nerd-icons-completion
:ensure t
:after nerd-icons
:diminish
:config (nerd-icons-completion-mode +1))
(use-package nerd-icons-corfu
:ensure t
:after (corfu nerd-icons)
:diminish
:config (add-to-list 'corfu-margin-formatters #'nerd-icons-corfu-formatter))
(keymap-global-set "C-c i n" #'nerd-icons-insert)
Window management
windmove
(use-package windmove
:ensure t
:demand
:diminish
:config (windmove-mode +1)
:bind (("C-c w k" . windmove-up)
("C-c w C-k" . windmove-display-up)
("C-c w K" . windmove-swap-states-up)
("C-c w j" . windmove-down)
("C-c w C-j" . windmove-display-down)
("C-c w J" . windmove-swap-states-down)
("C-c w h" . windmove-left)
("C-c w C-h" . windmove-display-left)
("C-c w H" . windmove-swap-states-left)
("C-c w l" . windmove-right)
("C-c w C-l" . windmove-display-right)
("C-c w L" . windmove-swap-states-right)))
winner
(use-package winner
:ensure t
:demand
:diminish
:bind (("C-c w u" . winner-undo)
("C-c w r" . winner-redo))
:config
(winner-mode))
ediff
(use-package ediff
:ensure t
:diminish
: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
:diminish
:config
(ligature-set-ligatures
'(text-mode prog-mode org-mode)
'("++" "/=" "&&" "||" "||=" ; Arithmetic
"->" "=>" "::" "__" ; Scope
"==" "===" "!=" "=/=" ; Equality
"<=" ">=" "<=>" ; Comparisons
"/*" "*/" "//" "///" ; Comments
"<<" "<<<" "<<=" ">>" ">>>" ">>=" "|=" "^=" ; Bit operations
; TODO add hexadecimal ex?
"</" "<!--" "</>" "-->" "/>" ; HTML
"##" "###" "####" "#####" "######" ; Markdown
"..=" "::<" ; Rust
"-|" "_|_" "|-" "||-" "|=" "||=" ; Logic
":>" ":<" ">:" "<:" "::>" "<::" ">::" ; Other
"{|" "|}" "#[" "]#" "::=" "#!" "#="
))
(global-ligature-mode +1))
Notifications
(use-package alert
:ensure t
:diminish
:custom
(alert-default-style 'libnotify))
Modeline
(line-number-mode -1)
(column-number-mode -1)
(size-indication-mode -1)
(display-battery-mode -1)
(display-time-mode -1)
(setq mode-line-compact 'long)
Darkroom
(use-package darkroom
:ensure t
:diminish
:defer t)
Zone
(use-package zone
:ensure t
:diminish
:config (zone-when-idle (* 60 60 2))) ; 2 hours
Git status in fringe
(use-package diff-hl
:ensure t
:diminish
: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
:diminish
:custom
(treemacs-follow-mode t)
(treemacs-filewatch-mode t)
(treemacs-hide-gitignored-files-mode nil)
:bind
("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-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)
org-agenda
(setq org-agenda-span 'fortnight
org-agenda-start-on-weekday 1
org-agenda-sticky nil
org-agenda-window-setup 'current-window
org-agenda-tags-column 0
org-agenda-prefix-format '((agenda . " %-12:c%?-12t% s")
(todo . " %-12:c")
(tags . " %-12:c")
(search . " %-12:c")))
(let ((agenda-file (expand-file-name ".agenda" org-directory)))
(if (file-exists-p agenda-file)
(setq org-agenda-files agenda-file)))
org-alert
(use-package org-alert
:ensure t
:after alert
:config
(setq org-alert-notification-title "org-mode agenda"
org-alert-notify-cutoff 30
org-alert-notify-after-event-cutoff 60)
(org-alert-enable))
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"))
org-present
(use-package org-present
:ensure t
:after org
:commands (org-present))
Capture templates
(setq org-capture-templates
'(("n" "Note" entry
(file "inbox.org")
"* %?")
("t" "Task" entry
(file+headline "tasks.org" "Inbox")
"* TODO %?"
:prepend t)
("r" "Reading List" entry
(file+headline "reading.org" "Inbox")
"* %?")
("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")
))
org-noter
(use-package org-noter
:ensure t
:diminish
:after (org doc-view)
:commands (org-noter)
:custom
(org-noter-always-create-frame nil)
(org-noter-kill-frame-at-session-end nil)
(org-noter-auto-save-last-location t)
(org-noter-default-notes-file-names '("notes.org"))
(org-noter-doc-property-in-notes t)
(org-noter-notes-search-path (list
(expand-file-name org-directory)
(expand-file-name "~/References/library/")
(expand-file-name "~/Documents/")))
(org-noter-prefer-root-as-file-level nil))
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
:diminish
:bind (("C-c r r" . citar-open)
("C-c r e" . citar-open-entry)
("C-c r f" . citar-open-files)
("C-c r l" . citar-open-links)
("C-c r n" . citar-open-notes)
("C-c r a" . citar-attach-files)
("C-c r i" . citar-insert-citation))
: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 " " ; need this because the default padding is too low for these icons
: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 " " ; need this because the default padding is too low for these icons
: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 " " ; need this because the default padding is too low for these icons
:tag "is:cited"))
(setq citar-indicators (list citar-indicator-files-icons
citar-indicator-links-icons
citar-indicator-notes-icons
citar-indicator-cited-icons)))
Citar Embark integration
(use-package citar-embark
:ensure t
:diminish
:after (citar embark)
:config
(citar-embark-mode +1))
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)
Password Store
(use-package password-store
:ensure t
:defer t
:diminish)
(use-package password-store-otp
:ensure t
:defer t
:diminish)
(use-package pass
:ensure t
:defer t
:diminish
:bind (("C-c P" . pass))
:custom
(pass-show-keybindings nil)
(pass-username-field "login"))
Remote Editing
TRAMP
(use-package tramp
:defer t
:diminish
:config
(add-to-list 'tramp-remote-path 'tramp-own-remote-path))
Connection variables
(connection-local-set-profile-variables
'remote-disable-apheleia
'((apheleia-mode . nil)
(apheleia-inhibit . t)))
(connection-local-set-profile-variables
'remote-no-corfu-auto
'((corfu-auto . nil)))
(connection-local-set-profiles
'(:application tramp :machine "heimdall")
'remote-disable-apheleia
'remote-no-corfu-auto)
Development Environment
(use-package rainbow-delimiters
:ensure t
:diminish
:hook (prog-mode))
(use-package direnv
:ensure t
:diminish
:custom (direnv-always-show-summary nil)
:config (direnv-mode +1))
Tree-sitter
Set treesit to fontify all elements, default was 3 (out of 4)
(use-package treesit
:diminish
:custom
(treesit-font-lock-level 4))
(use-package treesit-auto
:diminish
:after (treesit)
:config
(treesit-auto-add-to-auto-mode-alist)
(global-treesit-auto-mode +1))
(setq python-ts-mode-hook python-mode-hook)
(with-eval-after-load 'rust-mode
(setq rust-ts-mode-hook rust-mode-hook))
Eldoc
(use-package eldoc
:ensure t
:diminish
: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
:diminish
:demand
: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
f90-mode
shell-script-mode
lua-mode
python-base-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"))))))
Apheleia formatting
(use-package apheleia
:ensure t
:diminish
:bind (("C-c c f" . apheleia-format-buffer))
:hook (prog-mode)
:custom (apheleia-remote-algorithm 'local)
:config
(add-to-list 'apheleia-mode-alist '(python-ts-mode . ruff))
(add-to-list 'apheleia-mode-alist '(python-mode . ruff)))
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
:diminish
: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
:diminish
: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-clone-set-remote.pushDefault t)
(magit-commit-show-diff nil)
(magit-commit-diff-inhibit-same-window t)
(magit-diff-adjust-tab-width t)
(magit-diff-refine-hunk 'all)
(magit-diff-refine-ignore-whitespace t))
Completion
Vertico
(use-package vertico
:ensure t
:diminish
:custom
(vertico-cycle t)
:init
(vertico-mode)
:config
(require 'vertico-directory))
Marginalia
(use-package marginalia
:ensure t
:diminish
:custom
(marginalia-annotators '(marginalia-annotators-heavy
marginalia-annotators-light
nil))
:config (marginalia-mode +1))
Orderless
(use-package orderless
:ensure t
:diminish
:custom
(completion-styles '(orderless basic))
(completion-category-defaults nil)
(completion-category-overrides '((file (styles . (partial-completion))))))
Corfu and Cape
(use-package corfu
:ensure t
:diminish
:demand
:custom
(corfu-cycle t)
(corfu-auto t)
(corfu-auto-delay 0.2)
(corfu-auto-prefix 3)
(corfu-quit-no-match 'separator)
(corfu-quit-at-boundary 'separator)
(corfu-preview-current nil)
(corfu-preselect 'directory)
:bind ( :map corfu-map
("M-SPC" . corfu-insert-separator)
("RET" . nil)
("TAB" . corfu-insert)
([tab] . corfu-insert))
:init
(global-corfu-mode +1)
(corfu-history-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-quit-at-boundary t
corfu-quit-no-match t)
(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
:diminish
:demand
:init
(add-to-list 'completion-at-point-functions #'cape-emoji)
(add-to-list 'completion-at-point-functions #'cape-file)
(add-to-list 'completion-at-point-functions #'cape-dabbrev))
Consult
(use-package consult
:ensure t
:diminish
:bind (("<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 l" . consult-line)
("C-c s r" . consult-recent-file)
("C-c s f" . consult-fd)
("C-c s g" . consult-ripgrep)
("C-c s e" . consult-flymake)
("C-c s j" . consult-imenu)
("C-c s i" . consult-info)
:map minibuffer-local-map
("C-r" . consult-history))
:config (setq completion-in-region-function #'consult-completion-in-region))
(use-package consult-eglot
:ensure t
:diminish
:after (consult eglot)
:bind (("C-c s s" . consult-eglot-symbols)))
(use-package consult-yasnippet
:ensure t
:diminish
:after (consult yasnippet)
:bind (("C-c s y" . consult-yasnippet)))
Embark
(use-package embark
:ensure t
:diminish
:bind (("<remap> <describe-bindings>" . embark-bindings)
("C-." . embark-act))
:config (setq prefix-help-command #'embark-prefix-help-command))
(use-package embark-consult
:ensure t
:diminish
:after (embark consult)
:hook (embark-collect-mode . consult-preview-at-point-mode))
Snippets
(use-package yasnippet-snippets :ensure t)
(use-package yasnippet
:ensure t
:diminish
:hook ((prog-mode org-mode) . yas-minor-mode)
:config
(require 'yasnippet-snippets)
(yas-reload-all))
(use-package yasnippet-capf
:ensure t
:diminish
:after yasnippet
:init (add-to-list 'completion-at-point-functions #'yasnippet-capf))
Aggressive Indent
(use-package aggressive-indent
:ensure t
:diminish
:hook (elisp-mode
lisp-mode
lisp-data-mode
rust-mode))
Language-specific settings
Nix
(use-package nix-mode
:mode "\\.nix\\'"
:diminish
:config
(require 'nix)
(require 'nix-flake))
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-base-mode-hook (lambda () (my/enable-fill-column 88)))
Rust
(use-package cargo
:ensure t
:diminish
:hook (rust-ts-mode . cargo-minor-mode))
Internet
TODO Gnus
(setq gnus-select-method '(nnrss "http://www.fsf.org/static/fsforg/rss/news.xml"))
(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)
(set-variable read-mail-command 'mu4e)
(use-package mu4e
:bind
(("C-c m" . mu4e)
:map mu4e-view-mode-map
("o n" . mu4e-org-store-and-capture))
:custom
(add-hook 'mu4e-compose-mode-hook (lambda () (auto-save-mode -1))) ; Don't auto-save to drafts
(add-hook 'mu4e-headers-mode-hook (lambda () (visual-line-mode -1))) ; Disable visual-line / word wrapping in mu4e headers
(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 . 24)
(:subject . 64)
(:flags)))
(mu4e-headers-visible-flags
'(draft
flagged
unread
passed
replied
trashed
attach
calendar
encrypted
signed
list
personal))
:config
(setq mu4e-use-fancy-chars t)
)
Account contexts
(with-eval-after-load 'mu4e
(require 'mu4e-context)
(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"))))))
Modeline configuration
(with-eval-after-load 'mu4e
(require 'mu4e-modeline)
(setq mu4e-modeline-all-read '("R:" . " ")
mu4e-modeline-all-clear '("C:" . " ")
mu4e-modeline-new-items '("N:" . " ")
mu4e-modeline-unread-items '("U:" . " "))
(mu4e-modeline-mode +1))
Headers mode glyphs
(with-eval-after-load 'mu4e
(setq 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 '("└>" . "╰▶")))
Custom marks
(with-eval-after-load 'mu4e
(setq 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")))))
Desktop notifications
(with-eval-after-load 'mu4e
(require 'mu4e-notification)
(setq mu4e-notification-support t))
Org-mode integration
(with-eval-after-load 'mu4e
(require 'mu4e-org))
iCalendar integration
(with-eval-after-load 'mu4e
(require 'mu4e-icalendar)
(mu4e-icalendar-setup)
(setq mu4e-icalendar-trash-after-reply t)
(require 'gnus-icalendar)
(setq gnus-icalendar-org-capture-file (expand-file-name "calendar.org" org-directory))
(setq gnus-icalendar-org-capture-headline '("Inbox"))
(gnus-icalendar-org-setup))
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)))
Media
EMMS
(setq emms-mode-line-icon-enabled-p nil)
(use-package emms
:bind (("C-c e e" . emms-smart-browse)
("C-c e p" . emms-pause)
("C-c e s" . emms-stop)
("C-c e z" . emms-toggle-repeat-track)
("C-c e C-r" . emms-toggle-repeat-playlist)
("C-c e C-b" . emms-browser)
("C-c e C-p" . emms-playlist-mode-go)
("<XF86AudioPlay>" . emms-pause)
("<XF86AudioPrev>" . emms-previous)
("<XF86AudioNext>" . emms-next)
:map emms-browser-mode-map
("e" . emms-smart-browse)
("P" . emms-pause)
("S" . emms-stop)
("z" . emms-toggle-repeat-track)
:map emms-playlist-mode-map
("e" . emms-smart-browse))
: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-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)))