Emacs Init File!

This is my emacs configuration. I keep it in org-mode and automatically tangle it to ~/.config/emacs/init.el on save (see Local vars). This makes it easy to document and organize.

Most of the functions and variables I define myself are prefixed with d/, just so I can keep track of them.

If you have a question about any of the built-in variables, functions, modes, etc. that you see here, M-x describe-symbol (bound to C-h o in vanilla Emacs) might be a good place to start.

Initial Setup

Settings

Lexical binding is a variable scope matter in Emacs lisp. I won't explain it here but I've linked the info page for it. Anyway, it's a good idea to turn this on by default.

This may be the default in a future version of Emacs.

;;; -*- lexical-binding: t -*-

I like to keep a local "lisp" directory in my emacs config for slightly more involved lisp projects that I don't want cluttering my init and may not want to officially package.

(add-to-list 'load-path (expand-file-name "lisp" user-emacs-directory))

Some libraries I find useful within my configuration.

(require 'cl-lib)
(require 'subr-x)

Utils

This is just a convenience macro around with-eval-after-load. Often I want do things after a package loads (and not in the same form as use-package, mostly for organizational reasons). I don't want errors to totally stop my startup.

(defmacro d/after (feature &rest body)
  "Load BODY after FEATURE, catching errors and displaying as warnings."
  (declare (indent defun))
  `(with-eval-after-load ,feature
     (condition-case-unless-debug err
         (progn
           ,@body)
       (error
        (display-warning
         'init
         (format "%s eval-after-load: %s "
                 (symbol-name ,feature)
                 (error-message-string err))
         :error)))))

Package management

elpaca

Elpaca is a source-based, parallel package manager. It's a kind of successor to straight.el. It's useful if you do a lot of hacking on elisp packages, or need to pin specific versions of packages. The parallelism is nice, too.

At some point, I may switch back to the built-in package.el with the :vc keyword to use-package in special cases. Or maybe not, we'll see.

(defvar elpaca-installer-version 0.12)
(defvar elpaca-directory (expand-file-name "elpaca/" user-emacs-directory))
(defvar elpaca-builds-directory (expand-file-name "builds/" elpaca-directory))
(defvar elpaca-sources-directory (expand-file-name "sources/" elpaca-directory))
(defvar elpaca-order '(elpaca :repo "https://github.com/progfolio/elpaca.git"
                              :ref nil :depth 1 :inherit ignore
                              :files (:defaults "elpaca-test.el" (:exclude "extensions"))
                              :build (:not elpaca-activate)))
(let* ((repo  (expand-file-name "elpaca/" elpaca-sources-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)
    (let ((load-source-file-function nil)) (load "./elpaca-autoloads"))))
(add-hook 'after-init-hook #'elpaca-process-queues)
(elpaca `(,@elpaca-order))

;; Install use-package support
(elpaca elpaca-use-package
  ;; Enable :ensure use-package keyword.
  (elpaca-use-package-mode))

;; Block until current queue processed.
(elpaca-wait)

use-package

An excellent utility for managing packages and package configuration in a neat and organized way, with advanced support for deferring, pre/post-loading configuration, time reporting, and more.

You can use the same init file across computers without keeping track of what's installed or not and it will ensure that any missing packages are installed. You can also conditionalize package load by system (or anything else). It's pretty neat.

Read the manual linked above!

(setq use-package-minimum-reported-time .001
      use-package-verbose t
      use-package-always-defer t
      use-package-always-ensure t
      use-package-compute-statistics t)

Packages

compat

Compat is a forwards-compatibility library. I have this here because the vertico stack recently broke on me and this fixed it. Honestly, I'm not exactly sure what that was about.

(use-package compat
  :ensure (:wait t)
  :demand t
  :config
  (require 'compat-31))
(elpaca-wait)

no-littering

Usually, a bunch of files are kept in your .emacs.d folder by both built-in emacs features and external packages. This package sets up a convention to store everything in either .emacs.d/var for data, or .emacs.d/etc for configuration.

(use-package no-littering
  :demand t
  :config
  (no-littering-theme-backups)
  (setq custom-file (no-littering-expand-etc-file-name "custom.el"))
  (add-hook 'elpaca-after-init-hook (lambda () (load custom-file 'noerror))))

hydra

Hydra lets you create small, transient keymaps with a persistent help menu. They tend to quit when you pressa key not in the hydra. I don't use this as much as I used to, but I still have some hydras defined in my init.

(use-package hydra
  :config
  (setq hydra-default-hint nil))

savehist

savehist is a built-in mode to save minibuffer (and other) histories between emacs sessions.

(use-package savehist
  :ensure nil
  :defer 5
  :config
  (savehist-mode 1)
  (add-to-list 'savehist-additional-variables 'kill-ring))

saveplace

saveplace is a built-in mode to save and re-visit your last position in files after you close them (or emacs).

(use-package saveplace
  :ensure nil
  :defer 5
  :config
  (save-place-mode 1))

midnight

midnight is a bulit-in mode to clean up saved buffers that haven't been displayed recently on some regular interval, by default a day.

(use-package midnight
  :ensure nil
  :defer 10
  :custom
  (clean-buffer-list-delay-general 1)
  (clean-buffer-list-kill-regexps '("\\`\\*Man " "\\`\\*helpful "))
  :config
  (midnight-mode))

Core

Settings

Don't show me the startup screen, just get me to the editor!

(setq inhibit-startup-screen t
      inhibit-startup-echo-area-message t)

Start the scratch buffer in fundamental-mode with no message. This is to avoid emacs-lisp-mode and prog-mode hooks from slowing down startup. We get back our old scratch buffer eventually with persistent-scratch anyway.

(setq initial-scratch-message ""
      initial-major-mode 'fundamental-mode)

Use TAB for indentation, then completion.

(setq tab-always-indent 'complete)

Stop beeping!

(setq ring-bell-function #'ignore)

Copy pasting settings:

  • Save current external clipboard item to kill ring
  • Enable the primary selection; this is… not so relevant to me these days.
(setq save-interprogram-paste-before-kill t
      select-enable-primary t)

Minibuffer settings:

  • Use the minibuffer… from the minibuffer
  • Resize minibuffer windows to fit content
  • Make the minibuffer prompt untouchable
  • Indicate minibuffer depth lest we lose our minds
(setq enable-recursive-minibuffers t
      resize-mini-windows t
      minibuffer-prompt-properties '(read-only t intangible t cursor-intangible t face minibuffer-prompt))

(add-hook 'minibuffer-setup-hook #'cursor-intangible-mode)

(minibuffer-depth-indicate-mode t)

More messages!

(setf message-log-max 10000)

A couple other things:

  • Don't put native comp warnings front and center; it can be noisy.
  • Don't disable any commands; I generally know what I'm doing.
  • When killing emacs, just kill it without asking to kill processes.
  • Disable view-hello-file, because I keep accidentally pressing C-h h somehow. This is silly.
(setf native-comp-async-report-warnings-errors 'silent
      disabled-command-function nil
      confirm-kill-processes nil)
(defalias #'view-hello-file #'ignore)

Packages

These are packages that I consider absolutely essential to my emacs workflow, or that enhance emacs at a deeper level than any regular mode.

darktooth-theme

…because MY EYES! Some people argue dark themes are worse for your vission or less legible. This may be true. Oh well.

This is my own fork of the most excellent emacsfodder/emacs-theme-darktooth with many of my own tweaks.

(use-package darktooth-theme
  :demand t
  :ensure (:host github
                 :repo "emacsfodder/emacs-theme-darktooth"
                 :remotes ("fork" :host github
                           :repo "dieggsy/emacs-theme-darktooth"))
  :custom
  (custom-safe-themes t)
  :config
  (load-theme 'darktooth))

general

general is a convenient, stable framework for defining your own key-bindings, especially in evil-mode. I use this to define "leader key" bindings under SPC in the style of Vim's <leader>, as well as a mode-specific leader bindings under ,.

(use-package general
  :demand t
  :config
  (general-evil-setup t)

  (general-override-mode)

  (general-create-definer
    d/mode-leader-keys
    :keymaps 'override
    :states '(emacs normal visual motion insert)
    :non-normal-prefix "C-,"
    :prefix ",")

  (general-create-definer
    d/leader-keys
    :keymaps 'override
    :states '(emacs normal visual motion insert)
    :non-normal-prefix "C-SPC"
    :prefix "SPC"))
(elpaca-wait)

evil stack

evil is an emacs package that emulates Vim key bindings. It's solid and quite extensible, and there are several packages that enhance the experience or make it more consistent across Emacs.

There may come a day when the ubiquity of Vim fails, when we forsake evil-mode, and break all bonds of verb-noun editing… but it is not this day!

I've considered switching to meow. Part of the problem is that vi/vim is everywhere as a program or as an emulation layer, which makes vim bindings quite a comfy default. Still, meow calls to me…

evil

This is the core evil package. I have some settings here to make it more vimmy, some that make it less I also set some cursor types for indicating what evil state (like vim mode) I'm in.

(use-package evil
  :demand t
  :general
  (mmap
    "-" 'negative-argument
    ;; Basically C-[ for a Dvorak keyboard (_ is for terminal).
    "C-_" 'keyboard-quit
    "C-/"  'keyboard-quit
    [escape]  'keyboard-quit)
  (:states '(insert replace visual)
   "C-_" 'evil-normal-state
   "C-/" 'evil-normal-state)
  (vmap [escape] 'keyboard-quit)
  :init
  (setq evil-search-module 'evil-search)
  :custom
  (evil-want-C-u-delete t)
  (evil-want-C-u-scroll t)
  (evil-want-keybinding nil)
  (evil-want-fine-undo t)
  (evil-undo-system 'undo-fu)
  (evil-symbol-word-search t)
  (evil-ex-search-vim-style-regexp t)
  (evil-respect-visual-line-mode t)
  (evil-split-window-below t)
  (evil-vsplit-window-right t)
  :config
  (setq evil-insert-state-cursor '(bar . 1) ;thinner cursors
        evil-emacs-state-cursor '(hbar . 1)
        ;; Set these cursor types for evil-terminal-cursor-changer
        evil-normal-state-cursor 'box
        evil-motion-state-cursor 'box
        evil-visual-state-cursor 'box))

This is a small evil-ex alias for delete-trailing-whitespace, which I use quite a bit, unless whitespace-cleanup-mode is doing the job for me.

(d/after 'evil
  (evil-ex-define-cmd "dtw" #'delete-trailing-whitespace)
  (evil-ex-define-cmd "bk" #'kill-current-buffer))

Ok, let there be Vim!

(d/after 'evil (evil-mode 1))
evil-nerd-commenter

This package provides emulation for Vim's Nerd Commenter; it it provides useful utilities for commenting things out.

(use-package evil-nerd-commenter
  :general
  (nmap
    "gc" 'evilnc-comment-operator
    "gy" 'evilnc-copy-and-comment-operator)
  (d/leader-keys
    "ci" 'd/comment-or-uncomment-lines-inverse
    "cl" 'evilnc-comment-or-uncomment-lines
    "cp" 'evilnc-comment-or-uncomment-paragraphs
    "ct" 'evilnc-comment-or-uncomment-to-the-line
    "cy" 'evilnc-copy-and-comment-lines)
  :config
  (defun d/comment-or-uncomment-lines-inverse (&optional arg)
    "Source: https://git.io/vQKza"
    (interactive "p")
    (let ((evilnc-invert-comment-line-by-line t))
      (evilnc-comment-or-uncomment-lines arg))))
evil-collection

evil-collection provides evil integration for a bunch of miscellaneous modes for a more consistent experience.

(use-package evil-collection
  :custom
  (evil-collection-setup-minibuffer t)
  (evil-collection-term-sync-state-and-mode-p t)
  (evil-collection-repl-submit-state 'insert)
  :init
  (evil-collection-init))
evil-indent-plus

This package provides evil text objects to manipulate text based on indentation. For example, you can delete the current line and all adjacent lines at the same indentation level.

(use-package evil-indent-plus
  :general
  (itomap
    "i" 'evil-indent-plus-i-indent
    "I" 'evil-indent-plus-i-indent-up
    "J" 'evil-indent-plus-i-indent-up-down)
  (otomap
    "i" 'evil-indent-plus-a-indent
    "I" 'evil-indent-plus-a-indent-up
    "J" 'evil-indent-plus-a-indent-up-down))
evil-lion

evil-lion is an evil-port of vim-lion. It can be used to align text at a particular delimiter.

(use-package evil-lion
  :general
  (nvmap
    "gl" 'evil-lion-left
    "gL" 'evil-lion-right))

For example:

this text is
not aligned at
space characters !

Then, press gl<space>:

this  text       is
not   aligned    at
space characters !
evil-matchit

evil-matchit is a port of matchit.vim. It provides a more generic and somewhat context-aware % binding for jumping between matching brackets, tags, if/else branches, etc.

(use-package evil-matchit
  :general
  (itomap "%" 'evilmi-inner-text-object)
  (otomap "%" 'evilmi-outer-text-object)
  (nvmap "%" 'evilmi-jump-items)
  :config (global-evil-matchit-mode 1))
evil-numbers

evil-numbers provides some operators for incrementing the number at point (or in selection). I don't actually use it very much, so it's disabled with org-mode's :tangle no.

(use-package evil-numbers
  :ensure (:host github
             :repo "juliapath/evil-numbers")
  :general
  (d/leader-keys
    "n-"  'd/numbers/evil-numbers/dec-at-pt
    "n="  'd/numbers/evil-numbers/inc-at-pt)
  :config
  (defhydra d/numbers ()
    "
╭─────────╮
│ numbers │
└─────────┘
  [_=_] inc
  [_-_] dec
───────────
"
    ("="  evil-numbers/inc-at-pt)
    ("-" evil-numbers/dec-at-pt)))
evil-org

evil-org-mode adds helpful evil-mode keybindings to org-mode for navigating and manipulating org elements.

(use-package evil-org
  :hook (org-mode . evil-org-mode)
  :general
  (nvmap evil-org-mode-map
    "TAB" 'org-cycle
    "S-TAB" 'org-cycle)
  :config
  (evil-org-set-key-theme)
  (require 'evil-org-agenda)
  (evil-org-agenda-set-keys)
  (nmap evil-org-mode-map
    [backtab] 'org-shifttab)
  (d/after 'org-src
    (define-key org-src-mode-map [remap evil-write] 'org-edit-src-save)
    (define-key org-src-mode-map [remap evil-save-and-close]
      (lambda () (interactive)
        (org-edit-src-save)
        (org-edit-src-exit)))
    (define-key org-src-mode-map [remap evil-save-modified-and-close]
      (lambda () (interactive)
        (org-edit-src-save)
        (org-edit-src-exit)))))
evil-cleverparens

I may try this again at some point, but this is disabled for now. I just use electric-pair

(use-package evil-cleverparens
  :hook (prog-mode . evil-cleverparens-mode))
evil-surround

evil-surround is an evil-mode port of vim-surround. It helps work with surrounding chars better by providing bindings for adding, deleting, or changing surrounding characters (like brackets).

(use-package evil-surround
  :general
  (omap
    "s" 'evil-surround-edit
    "S" 'evil-Surround-edit)
  (vmap
    "S" 'evil-surround-region
    "gS" 'evil-Surround-region)
  :config
  (add-to-list 'evil-surround-operator-alist
               '(evil-cp-change . change))
  (add-to-list 'evil-surround-operator-alist
               '(evil-cp-delete . delete)))

Ostensibly this was a more minimal integration layer for embrace.el with evil, but I'm not actually sure it's doing anyhting, so it's disabled. See also: evil-embrace.el.

(use-package embrace
  :after evil-surround
  :demand t
  :config
  (define-advice evil-surround-region (:around (fn beg end type char &optional force-new-line) d/embrace)
    (if (evil-surround--get-delims char)
        (funcall fn beg end type char force-new-line)
      (embrace--add-internal beg end char)))
  (define-advice evil-surround-delete (:around (fn char &optional outer inner) d/embrace)
    (if (or (and outer inner)
            (evil-surround--get-delims char))
        (funcall fn char outer inner)
      (embrace--delete char))))
evil-terminal-cursor-changer

This a package enables changing the cursor type terminals based on the evil-state (like Vim mode), because this can be surprisingly tricky and terminal-dependent.

(use-package evil-terminal-cursor-changer
  :init
  (unless (display-graphic-p)
    (etcc-on))
  (add-to-list 'after-make-frame-functions
               (defun d/maybe-turn-on-cursor-changer (frame)
                 (if (display-graphic-p)
                     (etcc-on)))))
evil-anzu

anzu is a little search match counter for the mode line. evil-anzu integrates it with evil's search.

(use-package evil-anzu
  :after evil
  :demand t
  :config
  (setq anzu-cons-mode-line-p nil)
  (global-anzu-mode 1))

vertico stack

The vertico stack is a minimalist and performant minibuffer completion UI that integrates well with the built-in Emacs completion framework. In some ways it's the spiritual successor to ivy and helm, and a more featureful alternative to the built-in icomplete.

More specifically, it can:

  • Suggest and help find emacs commands, file names, buffers, anything…
  • Annotate suggestions with extra information
  • Provide an alternate ergonomic navigation and search interface (in buffer or external like grep, ripgrep, find, etc.)

I find having a completion framework like this useful for discoverability and ease of use.

vertico

vertico is the main user interface for this stack. It displays completions vertically in the minibuffer.

(use-package vertico
  :general
  (nmap vertico-map
   [escape] 'keyboard-escape-quit
   "C-/" 'keyboard-escape-quit)
  (d/leader-keys
    "SPC" 'execute-extended-command
    "ff"  'find-file
    "fa"  'find-alternate-file
    "fF"  'find-file-other-window
    "fj"  'project-find-file
    "hF"  'describe-face)
  :init (vertico-mode))
consult

consult provides extra search and navigation commands that integrate with completion. I use these commands all the time:

  • consult-buffer for switching to buffer with preview
  • consult-ripgrep for searching in files with preview
  • consult-line for searching the current buffer (also see consult-line-multi).
  • consult-yank-pop for kill-ring (like the "clipboard") selection.

Also check out consult-theme for previewing installed themes!

(use-package consult
  :bind (:map minibuffer-local-map
              ("M-r" . consult-history))
  :general
  (d/leader-keys
    "fl"  'consult-locate
    ;; "hdb" 'counsel-descbinds
    ;; "iu"  'counsel-unicode-char
    "sr"  'consult-ripgrep
    "ss"  'consult-line
    "sm"  'consult-line-multi
    "y"   'consult-yank-pop
    "bb"  'consult-buffer
    )
  :custom
  (consult-locate-args "locate -i")
  :config
  (defun noct-consult-line-evil-history (&rest _)
    "Add latest `consult-line' search pattern to the evil search history ring.
This only works with orderless and for the first component of the search."
    (when (and (bound-and-true-p evil-mode)
               (eq evil-search-module 'evil-search))
      (let ((pattern (consult--join-regexps
                      (cdr (orderless-compile (car consult--line-history)))
                      'emacs)))
        (add-to-history 'evil-ex-search-history pattern)
        (setq evil-ex-search-pattern (list pattern t t))
        (setq evil-ex-search-direction 'forward)
        (when evil-ex-search-persistent-highlight
          (evil-ex-search-activate-highlight evil-ex-search-pattern)))))

  (advice-add #'consult-line :after #'noct-consult-line-evil-history))
orderless

This is basically a fuzzy completion engine, called a completion style in Emacs. It helps you find matches by entering space-separated substrings in any order and/or using regular expressions.

(use-package orderless
  :custom
  (completion-styles '(orderless basic))
  (completion-category-overrides '(file (styles basic partial-completion)))
  (orderless-component-separator #'orderless-escapable-split-on-space))
marginalia

marginalia provides details and annotations to minibuffer completions.

(use-package marginalia
  :init
  (marginalia-mode))
embark

embark provides alternate actions for minibuffer completions. I won't lie, this one hasn't entirely clicked for me.

I do find embark-export (bound here to C-c C-o) usefull for viewing consult-ripgrep or consult-line results in their own buffer.

(use-package embark
  :general
  (d/leader-keys
    "iu"  'embark-save-unicode-character)
  :bind
  (([S-return] . embark-act)         ;; pick some comfortable binding
   ;; ("C-;" . embark-dwim)        ;; good alternative: M-.
   ("C-h B" . embark-bindings)
   :map vertico-map
   ("C-c C-o" . embark-export))

  :init

  ;; Optionally replace the key help with a completing-read interface
  (setq prefix-help-command #'embark-prefix-help-command)

  ;; Show the Embark target at point via Eldoc.  You may adjust the Eldoc
  ;; strategy, if you want to see the documentation from multiple providers.
  ;; (add-hook 'eldoc-documentation-functions #'embark-eldoc-first-target)
  ;; (setq eldoc-documentation-strategy #'eldoc-documentation-compose-eagerly)

  :config

  ;; Hide the mode line of the Embark live/completions buffers
  (add-to-list 'display-buffer-alist
               '("\\`\\*Embark Collect \\(Live\\|Completions\\)\\*"
                 nil
                 (window-parameters (mode-line-format . none)))))

;; Consult users will also want the embark-consult package.
(use-package embark-consult
  :hook
  (embark-collect-mode . consult-preview-at-point-mode))

org-mode

org-mode is a special plain-text mode for taking notes, creating documents, literate programming, todo lists, agenda items, and more. It's fantastic, and one of the things that brings people to Emacs.

Of course, this config is written in org-mode. I also use it to publish my blog, take notes with denote (when I remember to), on occasion create presentations… it's a blast. Try org-mode.

Package
(use-package org
  :ensure nil
  :general
  (nmap org-mode-map "ga" 'org-archive-subtree)
  (mmap org-mode-map
    "RET" (general-predicate-dispatch nil
            (d/org-at-openable-item?) 'org-open-at-point
            (org-at-item-checkbox-p) 'org-toggle-checkbox
            (org-in-src-block-p) 'org-babel-execute-src-block))
  :custom
  (org-list-allow-alphabetical t))

(use-package org-mouse
  :ensure nil
  :after org
  :demand t)
Defaults
Files
(d/after 'org
  (setopt org-agenda-text-search-extra-files '(agenda-archives)
          org-agenda-files '("~/notes.org")
          org-default-notes-file "~/notes.org"
          org-archive-location "~/archive.org::* Archive"))
Todo/agenda
(d/after 'org
  (setopt org-enforce-todo-dependencies t
          org-enforce-todo-checkbox-dependencies t
          org-log-done 'time
          org-log-redeadline 'time
          org-log-reschedule 'time
          org-agenda-skip-scheduled-if-done t
          org-agenda-skip-deadline-if-done t
          org-agenda-hide-tags-regexp ".*"
          org-agenda-span 'week

          org-agenda-deadline-faces '((1.0 . org-warning)
                                      (0.5 . org-upcoming-deadline)
                                      (0.0 . '(:foreground "#A89984")))

          org-todo-keywords '((sequence "TODO(t)" "IN-PROGRESS(p)" "WAITING(w)" "|"
                                        "DONE(d)" "CANCELED(c)"))))
Behavior
(d/after 'org
  ;; Don't reposition the window when cycling
  (remove-hook 'org-cycle-hook 'org-cycle-optimize-window-after-visibility-change)
  (setopt org-confirm-babel-evaluate nil
          org-startup-indented t
          org-catch-invisible-edits 'error
          org-insert-heading-respect-content t
          org-src-window-setup 'current-window
          org-list-demote-modify-bullet '(("-" . "+") ("+" . "*") ("*" . "-"))
          org-export-in-background nil
          org-export-with-author nil
          org-export-babel-evaluate nil
          org-html-validation-link nil
          org-src-tab-acts-natively t
          org-M-RET-may-split-line nil
          org-list-use-circular-motion t
          org-log-into-drawer t
          org-imenu-depth 5
          org-goto-interface 'outline-path-completion
          org-outline-path-complete-in-steps nil
          org-link-search-must-match-exact-headline nil
          org-confirm-elisp-link-function 'y-or-n-p)

  (define-advice org-babel-execute-src-block (:before (&rest _) d/load-lang)
    "Load src language on demand.

This removes the need to add every language manually to
`org-babel-load-languages'. This also implies that any language
that supports execution can be executed. Executing src blocks is
an active enough action that I'm ok with this."
    (let ((language (intern
                     (org-element-property :language (org-element-at-point)))))
      (message "LANG: %s" language)
      (pcase language
        ('sh (setq language 'shell))
        ('C++ (setq language 'C)))
      (message "LANG: %s" language)
      (unless (alist-get language org-babel-load-languages)
        (add-to-list 'org-babel-load-languages (cons language t))
        (org-babel-do-load-languages
         'org-babel-load-languages
         org-babel-load-languages))))

  (define-advice org-src--construct-edit-buffer-name (:override (org-buffer-name lang))
    (format "%s → %s" org-buffer-name lang)))

(d/after 'ob-python
  (add-to-list 'org-babel-default-header-args:python '(:results . "output")))
Appearance
(d/after 'org
  ;; appearance
  (setopt org-src-fontify-natively t
          org-src-preserve-indentation nil
          org-edit-src-content-indentation 0
          org-fontify-quote-and-verse-blocks t
          org-hide-emphasis-markers t
          org-startup-with-inline-images t
          org-ellipsis " …"
          ;; This actually slows down org-mode a LOT(?)
          org-highlight-latex-and-related '(native entities)
          org-pretty-entities t
          org-pretty-entities-include-sub-superscripts nil
          org-hide-leading-stars t
          org-fontify-done-headline t
          org-image-actual-width 500
          org-latex-packages-alist '(("" "listings") ("" "color") ("" "tabularx"))
          org-latex-src-block-backend 'listings)
  ;; latex
  (setq org-format-latex-options (plist-put org-format-latex-options :scale 2.0)))
Export
(d/after 'org
  (setf org-html5-fancy t))
Functions
(d/after 'org
  (defun d/org-agenda-toggle-date (current-line)
    "Toggle `SCHEDULED' and `DEADLINE' tag in the capture buffer.

Source: https://git.io/vQK0I"
    (interactive "P")
    (save-excursion
      (let ((search-limit (if current-line
                              (line-end-position)
                            (point-max))))

        (if current-line (beginning-of-line)
          (goto-char (point-min)))
        (if (search-forward "DEADLINE:" search-limit t)
            (replace-match "SCHEDULED:")
          (and (search-forward "SCHEDULED:" search-limit t)
               (replace-match "DEADLINE:"))))))

  (defun d/org-insert-list-item-or-self (char)
    "If on column 0, insert space-padded CHAR; otherwise insert CHAR.

This has the effect of automatically creating a properly indented list
leader; like hyphen, asterisk, or plus sign; without having to use
list-specific key maps.

Source: https://git.io/vQK0s"
    (if (bolp)
        (insert (concat char " "))
      (insert char)))

  (defun d/org-swap-tags (tags)
    "Replace any tags on the current headline with TAGS.

The assumption is that TAGS will be a string conforming to Org Mode's
tag format specifications, or nil to remove all tags.

Source: https://git.io/vQKEE"
    (let ((old-tags (org-get-tags-string))
          (tags (if tags
                    (concat " " tags)
                  "")))
      (save-excursion
        (beginning-of-line)
        (re-search-forward
         (concat "[ \t]*" (regexp-quote old-tags) "[ \t]*$")
         (line-end-position) t)
        (replace-match tags)
        (org-set-tags t))))

  (defun d/org-set-tags (tag)
    "Add TAG if it is not in the list of tags, remove it otherwise.

TAG is chosen interactively from the global tags completion table.

Source: https://git.io/vQKEa"
    (interactive
     (list (let ((org-last-tags-completion-table
                  (if (derived-mode-p 'org-mode)
                      (org-uniquify
                       (delq nil (append (org-get-buffer-tags)
                                         (org-global-tags-completion-table))))
                    (org-global-tags-completion-table))))
             (completing-read
              "Tag: " 'org-tags-completion-function nil nil nil
              'org-tags-history))))
    (let* ((cur-list (org-get-tags))
           (new-tags (mapconcat 'identity
                                (if (member tag cur-list)
                                    (delete tag cur-list)
                                  (append cur-list (list tag)))
                                ":"))
           (new (if (> (length new-tags) 1) (concat " :" new-tags ":")
                  nil)))
      (d/org-swap-tags new)))

  (defun d/org-choose-bullet-type ()
    "Change the bullet type for org lists with a prompt."
    (interactive)
    (let ((char (read-char-choice
                 "Bullet type? (-|*|+|1|2|a|b|A|B): "
                 '(?* ?- ?+ ?1 ?2 ?a ?b ?A ?B))))
      (pcase char
        (?1 (org-cycle-list-bullet 3))
        (?2 (org-cycle-list-bullet 4))
        (?a (org-cycle-list-bullet 5))
        (?b (org-cycle-list-bullet 7))
        (?A (org-cycle-list-bullet 6))
        (?B (org-cycle-list-bullet 8))
        (_ (org-cycle-list-bullet (char-to-string char))))))

  (defun d/org-at-openable-item? ()
    "Return non-nil if item is openable. (Think link-like)"
    (let* ((context (org-element-lineage
                     (org-element-context)
                     '(clock footnote-definition footnote-reference headline
                             inlinetask link timestamp)
                     t))
           (type (org-element-type context)))
      (memq type '(footnote-definition
                   footnote-reference
                   headline inlinetask
                   link
                   timestamp))))

  (define-advice org-babel-do-key-sequence-in-edit-buffer (:override (key) d/evil-insert)
    (interactive "kEnter key-sequence to execute in edit buffer: ")
    (org-babel-do-in-edit-buffer
     (evil-insert-state)
     (call-interactively
      (key-binding (or key (read-key-sequence nil)))))))
Bindings
(d/mode-leader-keys org-mode-map
  "$"  'org-archive-subtree
  "'"  'org-edit-special
  "."  'org-time-stamp
  "/"  'org-sparse-tree
  ":"  'd/org-set-tags
  "-"  'org-decrypt-entry
  "A"  'org-archive-subtree
  "P"  'org-set-property
  "R"  'org-refile
  "^"  'org-sort
  "a"  'org-agenda
  "c"  'org-capture
  "d"  'org-deadline
  "g"  'consult-org-heading
  ;; "G"  'counsel-org-goto-all
  "l"  'd/org-choose-bullet-type
  "s"  'org-schedule

  "ic" 'org-table-insert-column
  "il" 'org-insert-link
  "if" 'org-footnote-new
  "id" 'org-insert-drawer

  "e" 'org-export-dispatch

  "b"  'org-babel-tangle

  "xe" 'org-emphasize
  "xx" 'org-cut-special
  "xy" 'org-copy-special
  "xp" 'org-paste-special

  ;; tables
  "tb"  'org-table-blank-field
  "tc"  'org-table-convert
  "tdc" 'org-table-delete-column
  "tf"  'org-table-eval-formula
  "te"  'org-table-export
  "tic" 'org-table-insert-column
  "tih" 'org-table-insert-hline
  "tiH" 'org-table-hline-and-move
  "tI"  'org-table-import
  "tH"  'org-table-move-column-left
  "tL"  'org-table-move-column-right
  "tn"  'org-table-create
  "tN"  'org-table-create-with-table.el
  "tr"  'org-table-recalculate
  "ts"  'org-table-sort-lines
  "ttf" 'org-table-toggle-formula-debugger
  "tto" 'org-table-toggle-coordinate-overlays
  "tw"  'org-table-wrap-region)

(d/after 'org
  (d/mode-leader-keys org-src-mode
    :definer 'minor-mode
    "'" 'org-edit-src-exit)

  (d/leader-keys org-src-mode
    :definer 'minor-mode
    "fs" 'org-edit-src-save))
Setup
(d/after 'org-agenda
  (setq org-habit-graph-column 50))

(imap org-capture-mode-mop
  "C-d" 'd/org-agenda-toggle-date)
(nmap org-capture-mode-map
  "C-d" 'd/org-agenda-toggle-date)

(d/after 'org
  (dolist (char '("+" "-"))
    (define-key org-mode-map (kbd char)
                `(lambda ()
                   (interactive)
                   (d/org-insert-list-item-or-self ,char)))))
Extras
(use-package org-appear
  :hook (org-mode . org-appear-mode)
  :custom
  (org-appear-autoemphasis t)
  (org-appear-autoentities t)
  (org-appear-autolinks t)
  (org-appear-autokeywords t)
  (org-appear-autosubmarkers t))

Bindings

(d/leader-keys
  "." 'abort-recursive-edit
  "qf" 'delete-frame
  "qq" 'save-buffers-kill-emacs

  "td" 'toggle-debug-on-error
  "tl" 'd/toggle-rlines

  "&"   'async-shell-command
  ":"   'eval-expression
  "r"   'repeat
  "u"   'universal-argument)

(general-def
 "<f11>" 'kmacro-start-macro-or-insert-counter
 "<f12>" 'kmacro-end-or-call-macro)

(general-def
  (minibuffer-local-map
   minibuffer-local-ns-map
   minibuffer-local-completion-map
   minibuffer-local-must-match-map
   minibuffer-local-isearch-map)
  [?\C-/]  'minibuffer-keyboard-quit
  [?\C-_]  'minibuffer-keyboard-quit
  [escape] 'minibuffer-keyboard-quit)

(general-def universal-argument-map
  "SPC u" 'universal-argument-more)

Mode line

(defvar d/selected-window nil)
(add-hook 'pre-redisplay-functions
          (defun d/set-selected-window (_)
            (unless (minibuffer-window-active-p (selected-window))
              (setq d/selected-window (frame-selected-window)))))

(defun d/window-active-p ()
  (eq (selected-window) d/selected-window))

(defun d/compact-mode-line-p ()
  (< (window-width) 80))

(defface d/mode-line-dim '((t (:inherit font-lock-comment-face :slant normal)))
  "Mode line face for dim segments")

(defface d/mode-line-half-dim '((t (:foreground "#BDAE93")))
  "Mode line face for dim segments")

(defface d/mode-line-highlight '((t (:inherit hl-line)))
  "Mode line mouse face")

(defun d/mode-line-face (&optional face inactive-face)
  (if (d/window-active-p)
      (or face 'mode-line-active)
    (or inactive-face 'mode-line-inactive)))

(defvar-local d/mode-line-position
    '((:eval
       `(:propertize " %4l:"
                     face
                     ,(d/mode-line-face 'd/mode-line-half-dim)))
      (:eval
       `(:propertize
         ,(format "%-2s " (format-mode-line "%c"))
         face
         ,(d/mode-line-face 'd/mode-line-half-dim)))))
(put 'd/mode-line-position 'risky-local-variable t)

(defvar-local d/mode-line-mode
    `((:propertize
       (:eval
        (if (listp mode-name) (car mode-name) mode-name))
       mouse-face d/mode-line-highlight
       local-map ,mode-line-major-mode-keymap)
      " "))
(put 'd/mode-line-mode 'risky-local-variable t)

(defvar-local d/mode-line-anzu
    '((anzu--state
       (:eval
        (propertize
         (let ((here anzu--current-position)
               (total anzu--total-matched))
           (cond ((eq anzu--state 'replace-query)
                  (format " %d replace " anzu--cached-count))
                 ((eq anzu--state 'replace)
                  (format " %2d/%d " (1+ here) total))
                 (anzu--overflow-p
                  (format " %s+ " total))
                 (t
                  (format " %2s/%d " here total))))
         'face
         (d/mode-line-face '(:foreground "#83A598")))))))
(put 'd/mode-line-anzu 'risky-local-variable t)

(defun d/mode-line-switch-buffer (click)
  (interactive "e")
  (when (and (eq (posn-area (event-end click))
                 'mode-line)
             (eq (posn-window (event-end click))
                 (posn-window (event-start click))))
    (with-selected-window (posn-window (event-start click))
      (call-interactively #'consult-buffer))))

(defvar-local d/mode-line-buffer-name
    '((:eval
       (propertize
        (cond ((get-buffer-process (current-buffer)) " ")
              ((buffer-narrowed-p) "▼")
              (buffer-read-only "⏺")
              ((buffer-modified-p) "⏺")
              (t  " "))
        'face
        (d/mode-line-face
         (cond (buffer-read-only
                '(:foreground "#D3869B"))
               ((buffer-modified-p)
                '(warning))
               (t '(:foreground "#B8BB26"))))
        ))
      " "
      (:propertize "%b"
                   face bold
                   help-echo "mouse-1: Switch buffer"
                   mouse-face d/mode-line-highlight
                   local-map (keymap (mode-line
                                      keymap
                                      (mouse-1 . d/mode-line-switch-buffer))))
      " "))
(put 'd/mode-line-buffer-name 'risky-local-variable t)

(defvar-local d/mode-line-remote
    '(:eval
      (let ((host (file-remote-p default-directory 'host))
            (user (file-remote-p default-directory 'user)))
        (when host
          (propertize
           (concat " " user "@" host)
           'face
           (d/mode-line-face  '(:foreground "#D3869B"  :slant italic) '(:slant italic)))))))
(put 'd/mode-line-remote 'risky-local-variable t)

(defvar-local d/mode-line-vc
    '(" "
      (vc-mode
       (:eval
        (let ((value (if (string-prefix-p " Git-" vc-mode)
                         (substring vc-mode 5)
                       vc-mode)))
          (if (d/window-active-p)
              value
            (propertize value 'face nil)))))))
(put 'd/mode-line-vc 'risky-local-variable t)

(defvar-local d/mode-line-process
    '(mode-line-process
      (:eval (propertize (concat (format-mode-line '((mode-line-process ("" mode-line-process "  ")))))
                         'face (d/mode-line-face '(:inherit font-lock-keyword-face :slant normal))))))
(put 'd/mode-line-process 'risky-local-variable t)

(defvar-local d/mode-line-coding
    '((:eval (propertize
              (pcase (coding-system-eol-type buffer-file-coding-system)
                (0 "LF")
                (1 "CRLF")
                (2 "CR")
                (_  ""))
              'face (d/mode-line-face 'd/mode-line-dim)
              'mouse-face 'd/mode-line-highlight
              'local-map (let ((map (make-sparse-keymap)))
                           (define-key map [mode-line mouse-1] 'mode-line-change-eol)
                           map)))
      " "
      (buffer-file-coding-system
       (:eval (propertize
               (upcase
                (symbol-name
                 (let ((coding-system-name
                        (plist-get (coding-system-plist buffer-file-coding-system) :name)))
                   (pcase coding-system-name
                     ((or 'prefer-utf-8 'undecided) 'utf-8)
                     (_ coding-system-name)))))
               'face
               (d/mode-line-face 'd/mode-line-dim)
               'help-echo 'mode-line-mule-info-help-echo
               'local-map mode-line-coding-system-map
               'mouse-face 'd/mode-line-highlight)))))
(put 'd/mode-line-coding 'risky-local-variable t)


(defvar d/mode-line-right '(" "
                            (:eval (if (d/compact-mode-line-p)
                                       ""
                                     mode-line-misc-info))
                            d/mode-line-process
                            d/mode-line-coding
                            "  "
                            d/mode-line-mode
                            ))
(put 'd/mode-line-right 'risky-local-variable t)

(setq-default mode-line-format `("%e"
                                 " "
                                 d/mode-line-buffer-name
                                 d/mode-line-position
                                 d/mode-line-remote
                                 d/mode-line-vc
                                 d/mode-line-anzu
                                 mode-line-format-right-align
                                 d/mode-line-right
                                 " "))

Help

Built-in

man

(use-package man
  :ensure nil
  :general
  (d/leader-keys
    "hm" 'man)
  :init
  (setq Man-notify-method 'aggressive
        manual-program
        (if (executable-find "gman")  "gman" "man")
        Man-sed-command
        (if (executable-find "gsed")  "gsed" "sed")))

woman

(use-package woman
  :ensure nil
  :general
  (d/leader-keys
    "hw" 'woman)
  :custom
  (woman-fill-column 79))

which-key

(use-package which-key
  :ensure nil
  :defer 10
  :config
  (which-key-mode))

Packages

helpful

(use-package helpful
  :custom
  (helpful-short-filenames t)
  :general
  (d/leader-keys
    "hf" 'helpful-callable
    "hv" 'helpful-variable
    "hs" 'helpful-symbol
    "hk" 'helpful-key)
  :config
  (evil-set-initial-state 'helpful-mode 'motion))

elisp-refs

(use-package elisp-refs
  :config
  (evil-set-initial-state 'elisp-refs-mode 'motion))

Bindings

(d/leader-keys
  "hh" 'help-for-help
  "hV" 'apropos-value
  "hc" 'describe-char
  "hM" 'describe-mode
  "ht" 'describe-text-properties
  "hn"  'view-emacs-news
  "hi"  'info
  "hev"  'version)

Files/Buffers

Settings

(setq uniquify-buffer-name-style 'forward
      uniquify-strip-common-suffix nil
      ns-pop-up-frames nil
      help-window-select t
      version-control t
      delete-old-versions t)

(add-hook 'after-save-hook 'executable-make-buffer-file-executable-if-script-p)

Built-in

tramp

(use-package tramp
  :ensure nil
  :custom
  (tramp-default-method "rsync")
  (tramp-verbose 2)
  (tramp-completion-reread-directory-timeout nil)
  (tramp-auto-save-directory (no-littering-expand-var-file-name "tramp-auto-save/"))
  (remote-file-name-inhibit-locks t)
  (tramp-use-scp-direct-remote-copying t)
  (remote-file-name-inhibit-auto-save-visited t)
  (tramp-copy-size-limit (* 1024 1024))
  (tramp-use-connection-share t)
  :config
  (setq tramp-ssh-controlmaster-options
        (concat
         "-o ControlPath=/tmp/ssh-ControlPath-%%r@%%h:%%p "
         "-o ControlMaster=auto "
         "-o ControlPersist=4h"))
  (add-to-list 'tramp-default-proxies-alist
               '("dieggsy\\.com\\'" "\\`root\\'" "/ssh:%h:"))
  (setq vc-ignore-dir-regexp (format "\\(%s\\)\\|\\(%s\\)"
                                     vc-ignore-dir-regexp
                                     tramp-file-name-regexp))
  (d/after 'ibuffer
    (setcdr (last (cdar ibuffer-saved-filter-groups))
            `(("Tramp"
               (filename . ,tramp-file-name-regexp)))))

  (connection-local-set-profile-variables
   'remote-direct-async-process
   '((tramp-direct-async-process . t)))

  (connection-local-set-profiles
   '(:application tramp :protocol "rsync")
   'remote-direct-async-process)

  (connection-local-set-profiles
   '(:application tramp :protocol "scp")
   'remote-direct-async-process)

  (connection-local-set-profiles
   '(:application tramp :protocol "ssh")
   'remote-direct-async-process)

  (with-eval-after-load 'compile
    (remove-hook 'compilation-mode-hook #'tramp-compile-disable-ssh-controlmaster-options)))
;; Could make tramp faster
;; (setq remote-file-name-inhibit-cache nil))

bookmark

(use-package bookmark
  :ensure nil
  :general
  (d/leader-keys
    "fB" 'bookmark-jump-other-window
    "fb" 'bookmark-jump))

dired

(use-package dired
  :ensure nil
  :general
  (d/leader-keys
    "ad" 'dired-other-window)
  (d/leader-keys wdired-mode-map
    ;; "fs" 'wdired-finish-edit
    "fs" (lambda ()
           (interactive)
           (message "Use ':w' or variant to finish editing ")))
  (d/mode-leader-keys dired-mode-map
    "h" 'dired-omit-mode
    "d" 'dired-du-mode
    "c" 'dired-collapse-mode)
  :custom
  (dired-dwim-target t)
  ;; (dired-kill-when-opening-new-dired-buffer t)
  (dired-mouse-drag-files t)
  (wdired-allow-to-change-permissions 'advanced)
  :init
  (setq dired-listing-switches "-DlhA --group-directories-first")
  (when (member system-type '(darwin berkeley-unix))
    (if (executable-find "gls")
        (setq insert-directory-program "gls")
      (setq dired-listing-switches "-lhA")))
  :config
  (defun d/dired-remote-listing-switches ()
    (when (file-remote-p dired-directory)
      (setq-local dired-actual-switches "-lhA")))

  (add-hook 'dired-mode-hook #'auto-revert-mode)
  (add-hook 'dired-mode-hook #'d/dired-remote-listing-switches)

  (nmap dired-mode-map
    "~" 'd/dired-home
    "q" 'd/dired-quit
    ;; got used to this from ranger
    "h" 'dired-up-directory
    "l" 'dired-find-file)

  (d/after 'dired-async
    (dired-async-mode 1))

  (defun d/dired-quit ()
    (interactive)
    (let ((prev-nondired
           (cl-find-if
            (lambda (spec)
              (not (eq (buffer-local-value 'major-mode (car spec))
                       'dired-mode)))
            (window-prev-buffers))))
      (if prev-nondired
          (switch-to-buffer (car prev-nondired))
        (delete-window))))

  (defun d/dired-home ()
    (interactive)
    (dired "~/")))

dired-x

(use-package dired-x
  :ensure nil
  :after dired
  :demand t
  :hook (dired-mode . d/dired-maybe-omit)
  :custom
  (dired-omit-verbose nil)
  (dired-omit-files (rx string-start "." (1+ nonl) string-end))
  (dired-clean-confirm-killing-deleted-buffers nil)
  :config
  (defun d/dired-maybe-omit ()
    (when (directory-files default-directory nil "^[^.]")
      (dired-omit-mode)))
  (add-to-list 'dired-omit-extensions ".import.scm")
  (add-to-list 'dired-omit-extensions ".link"))

ediff

(use-package ediff
  :ensure nil
  :custom
  (ediff-window-setup-function 'ediff-setup-windows-plain)
  (ediff-split-window-function 'split-window-horizontally)
  (ediff-diff-options "-w"))

ibuffer

(use-package ibuffer
  :ensure nil
  :general
  (nmap ibuffer-mode-filter-group-map
    "TAB" 'ibuffer-toggle-filter-group
    "<backtab>" 'ibuffer-toggle-filter-group
    "J" 'ibuffer-forward-filter-group
    "K" 'ibuffer-backward-filter-group)
  :config
  (setq ibuffer-saved-filter-groups
        '(("Default"
           ("Dired"
            (mode . dired-mode))
           ("ERC"
            (mode . erc-mode))
           ("Help"
            (or
             (mode . helpful-mode)
             (mode . Man-mode)
             (predicate . (member major-mode
                                  ibuffer-help-buffer-modes))))
           ("Emacs Package"
            (filename . ".*/dotfiles/emacs.d/elpaca/.*"))
           ("Built in"
            (and
             (not (mode . eshell-mode))
             (filename . "/gnu.*")))
           ("Magit" (derived-mode . magit-mode))
           ("Shell"
            (predicate . (member major-mode '(eshell-mode term-mode vterm-mode))))
           ("Process" (process))
           ("Other" (name . "^*.**$"))
           )))

  (defun d/setup-ibuffer ()
    (ibuffer-switch-to-saved-filter-groups "Default"))
  (add-hook 'ibuffer-mode-hook #'d/setup-ibuffer)

  (setq ibuffer-never-show-predicates '("\\*magit-\\(diff\\|process\\):")))

recentf

(use-package recentf
  :ensure nil
  :defer 5
  :custom
  (recentf-exclude '(file-remote-p))
  (recentf-max-saved-items 50)
  :config
  (recentf-mode))

Packages

dired-hacks

dired-open
(use-package dired-open
  :general
  (d/mode-leader-keys dired-mode-map
    "l" 'dired-open-xdg))
dired-rainbow
(use-package dired-rainbow
  :after dired
  :demand t
  :config
  (dired-rainbow-define-chmod executable-unix "#B8BB26" "-[rw-]+x.*")
  ;; Most of these are copied from eza
  (defvar d/image-extensions
    '("arw" "avif" "bmp" "cbr" "cbz" "cr2" "dvi" "eps" "fodg"
      "gif" "heic" "heif" "ico" "j2c" "j2k" "jfi" "jfif" "jif"
      "jp2" "jpe" "jpeg" "jpf" "jpg" "jpx" "jxl" "kra" "krz"
      "nef" "odg" "orf" "pbm" "pgm" "png" "pnm" "ppm" "ps"
      "psd" "pxm" "raw" "qoi" "svg" "tif" "tiff" "webp" "xcf" "xpm"))
  (defvar d/meta-files
    (rx (group (or (and (or "README" "Readme" "readme") (? "." (* (not " "))))
                   (and "Dockerfile" (? "." (* (not " "))))
                   "Brewfile" "bsconfig.json" "BUILD" "BUILD.bazel" "build.gradle"
                   "build.sbt" "build.xml" "Cargo.toml" "CMakeLists.txt" "composer.json"
                   "configure" "Containerfile" "Earthfile" "flake.nix" "Gemfile"
                   "GNUmakefile" "Gruntfile.coffee" "Gruntfile.js" "jsconfig.json"
                   "Justfile" "justfile" "Makefile" "makefile" "meson.build" "mix.exs"
                   "package.json" "Pipfile" "PKGBUILD" "Podfile" "pom.xml" "Procfile"
                   "pyproject.toml" "Rakefile" "RoboFile.php" "SConstruct" "tsconfig.json"
                   "Vagrantfile" "webpack.config.cjs" "webpack.config.js" "WORKSPACE"
                   "id_dsa" "id_ecdsa" "id_ecdsa_sk" "id_ed25519" "id_ed25519_sk" "id_rsa"))))
  (defvar d/video-extensions
    '("avi" "flv" "h264" "heics" "m2ts" "m2v" "m4v" "mkv" "mov" "mp4"
      "mpeg" "mpg" "ogm" "ogv" "video" "vob" "webm" "wmv"))
  (defvar d/music-extensions
    '("aac" "m4a" "mka" "mp2" "mp3" "ogg" "opus" "wma"))
  (defvar d/lossless-music-extensions
    '("aif" "aifc" "aiff" "alac" "ape" "flac" "pcm" "wav" "wv"))
  (defvar d/source-extensions
    '("applescript" "as" "asa" "awk" "c" "c++" "c++m" "cabal" "cc" "ccm" "clj" "cp"
      "cpp" "cppm" "cr" "cs" "css" "csx" "cu" "cxx" "cxxm" "cypher" "d" "dart" "di"
      "dpr" "el" "elm" "erl" "ex" "exs" "f" "f90" "fcmacro" "fcscript" "fnl" "for"
      "fs" "fsh" "fsi" "fsx" "gd" "go" "gradle" "groovy" "gvy" "h" "h++" "hh" "hpp"
      "hc" "hs" "htc" "hxx" "inc" "inl" "ino" "ipynb" "ixx" "java" "jl" "js" "jsx"
      "kt" "kts" "kusto" "less" "lhs" "lisp" "ltx" "lua" "m" "malloy" "matlab" "ml"
      "mli" "mn" "nb" "p" "pas" "php" "pl" "pm" "pod" "pp" "prql" "ps1" "psd1" "psm1"
      "purs" "py" "r" "rb" "rs" "rq" "sass" "scala" "scm" "scad" "scss" "sld" "sql"
      "ss" "swift" "tcl" "tex" "ts" "v" "vb" "vsh" "zig"))
  (defvar d/crypto-extensions
    '("age" "asc" "cer" "crt" "csr" "gpg" "kbx" "md5" "p12" "pem" "pfx" "pgp" "pub"
      "sha1" "sha224" "sha256" "sha384" "sha512" "sig" "signature"))
  (defvar d/document-extensions
    '("djvu" "doc" "docx" "eml" "fodp" "fods" "fodt" "fotd" "gdoc" "key" "keynote"
      "numbers" "odp" "ods" "odt" "pages" "pdf" "ppt" "pptx" "rtf" "xls" "xlsm" "xlsx"))

  (dired-rainbow-define meta (:foreground "#FABD2F" :underline t) d/meta-files)
  (dired-rainbow-define image "#af5faf" d/image-extensions)
  (dired-rainbow-define video "#af5fff" d/video-extensions)
  (dired-rainbow-define music "#8700d7" d/music-extensions)
  (dired-rainbow-define lossless-music "#8700ff" d/lossless-music-extensions)
  (dired-rainbow-define crypto "#87afaf" d/crypto-extensions)
  (dired-rainbow-define document "#8787ff" d/document-extensions)
  (dired-rainbow-define source (:foreground "#FABD2F" :bold t) d/source-extensions))
dired-collpase
(use-package dired-collapse
  :config
  (defvar dired-collapse-ignore-dirs
    (list "~/Music"
          "/ssh:"))
  (defun d/dired-collapse-mode ()
    (unless (cl-find-if
             (lambda (dir)
               (string-prefix-p
                (expand-file-name dir)
                (expand-file-name dired-directory)))
             dired-collapse-ignore-dirs)
      (dired-collapse-mode))))
dired-narrow
(use-package dired-narrow
  :general
  (d/mode-leader-keys dired-mode-map
    "n" 'dired-narrow))
diredfl
(use-package diredfl
  :hook (dired-mode . diredfl-mode)
  :custom
  (diredfl-ignore-compressed-flag nil)
  :init
  ;; Set this here because somehow diredfl interferes with this
  (defun dired-pretty-slashes ()
    (push '("\\\\" . ?\\) prettify-symbols-alist)
    (prettify-symbols-mode 1))
  (add-hook 'dired-mode-hook #'dired-pretty-slashes 100))
dired-du
(use-package dired-du
  :ensure (:host github :repo "emacsmirror/dired-du" :branch "master")
  :config
  (setq dired-du-size-format t))

nerd-icons-dired

Turn off for performance reasons

(use-package nerd-icons-dired
  :hook (dired-mode . nerd-icons-dired-mode))

whitespace-cleanup-mode

(use-package whitespace-cleanup-mode
  :defer 15
  :config
  (global-whitespace-cleanup-mode))

wgrep

(use-package wgrep
  :commands wgrep-change-to-wgrep-mode
  :custom (wgrep-auto-save-buffer t))

Functions

File/Buffer Manipulation

(defun d/copy-file ()
  "Copy file to another location.

Source: https://git.io/vQKES"
  (interactive)
  (call-interactively #'write-file))

Switching

(defun d/switch-to-scratch ()
  "Switch to scratch buffer."
  (interactive)
  (switch-to-buffer "*scratch*"))

(defun d/switch-to-messages ()
  "Switch to *Messages* buffer."
  (interactive)
  (switch-to-buffer "*Messages*"))

Narrowing

(defun d/narrow-and-set-normal ()
  "Narrow to the region and, if in a visual mode, set normal mode.

Source: https://git.io/vQKEx"
  (interactive)
  (narrow-to-region (region-beginning) (region-end))
  (if (string= evil-state "visual")
      (progn (evil-normal-state nil)
             (evil-goto-first-line))))

(defun d/narrow-to-region-or-subtree ()
  "Narrow to a region, if set, otherwise to an Org subtree, if present.

Source: https://git.io/vQKuf"
  (interactive)
  (cond ((and mark-active
              (not (= (region-beginning) (region-end))))
         (d/narrow-and-set-normal))
        ((derived-mode-p 'org-mode)
         (org-narrow-to-subtree))
        ((derived-mode-p 'prog-mode)
         (narrow-to-defun))))

(defun d/narrow-dwim ()
  "Narrow to a thing or widen based on context.
Attempts to follow the Do What I Mean philosophy.

Source: https://git.io/vQKuU"
  (interactive)
  (if (buffer-narrowed-p)
      (widen)
    (d/narrow-to-region-or-subtree)))

Bindings

(d/leader-keys
  "bK" 'kill-buffer
  "bM" 'd/switch-to-messages
  ;; "br" 'revert-buffer
  "br" (lambda ()
         (interactive)
         (message "Use ':e' to revert buffer."))
  "bR" 'rename-buffer
  "bS" 'd/switch-to-scratch
  "bc" 'clone-indirect-buffer-other-window
  "bi" 'ibuffer
  ;; "bk" 'kill-this-buffer
  "bk" (lambda ()
         (interactive)
         (message "Use ':bk' to kill buffer."))
  "bm" 'kill-matching-buffers
  "bq" 'kill-buffer-and-window
  "bv" 'view-mode

  "fc" 'd/copy-file
  ;; "fs" 'save-buffer
  "fs" (lambda ()
         (interactive)
         (message "Use ':w' to save buffer."))

  "nf" 'narrow-to-defun
  "nn" 'd/narrow-dwim
  "np" 'narrow-to-page
  "nr" 'narrow-to-region)

Editing

Settings

(setq-default major-mode 'text-mode
              fill-column 79
              indent-tabs-mode nil
              ;; tab-width 4
              )

(setq sentence-end-double-space nil)

(add-hook 'text-mode-hook 'auto-fill-mode)

Tools

Built-in

Input method (mule)
(use-package mule
  :ensure nil
  :custom
  (default-input-method "TeX")
  :config
  (defvar d/evil-input-method-title nil)
  (add-hook 'input-method-activate-hook
            (defun d/set-evil-input-method-title ()
              (setf d/evil-input-method-title
                    (nth 3 (assoc current-input-method input-method-alist)))))
  (add-hook 'input-method-deactivate-hook
            (defun d/unset-evil-input-method-title ()
              (setf d/evil-input-method-title nil)))

  (defvar-local d/mode-line-mule-info
      `(""
        (evil-input-method
         (:propertize ("" d/evil-input-method-title)
                      help-echo (concat
                                 ,(purecopy "Current input method: ")
                                 evil-input-method
                                 ,(purecopy "\n\
mouse-2: Disable input method\n\
mouse-3: Describe current input method"))
                      local-map ,mode-line-input-method-map
                      mouse-face mode-line-highlight))
        ,(propertize
          "%z"
          'help-echo 'mode-line-mule-info-help-echo
          'mouse-face 'mode-line-highlight
          'local-map mode-line-coding-system-map)
        (:eval (mode-line-eol-desc)))
    "Like `mode-line-mule-info' but using `evil-input-method'"))
paren
(use-package paren
  :ensure nil
  :hook ((emacs-lisp-mode scheme-mode lisp-mode) . show-paren-mode))
hippie-expand
(use-package hippie-exp
  :ensure nil
  :general
  ("M-/" #'hippie-expand))
electric
(use-package electric
  :ensure nil
  :defer 5
  :config
  (electric-indent-mode)
  (electric-layout-mode))
electric-pair

Okay, I've experimented with frameworks for dealing with parentheses and brackets quite a bit. I've used, tested, or am at least aware of:

And welp. I don't know. They all feel unpolished or heavy and I never quite get into the flow. Meanwhile, built-in electric-pair-mode stays out of may way, even if I have to do a bit more manual bracket management. evil-surround helps some here, and the rest is just good ol' fashined text editing.

(use-package elec-pair
  :ensure nil
  :demand t
  :config
  (electric-pair-mode))

Packages

corfu

Supposedly better than autocomplete.

(use-package corfu
  :defer 5
  :bind
  (:map corfu-map
        ("TAB" . corfu-next)
        ([tab] . corfu-next)
        ("S-TAB" . corfu-previous)
        ([backtab] . corfu-previous)
        ([return] . corfu-insert))
  :custom
  (corfu-cycle t)
  (corfu-auto t)
  (corfu-auto-prefix 1)
  (global-corfu-minibuffer nil)
  :config
  (global-corfu-mode))
cape
(use-package cape
  :init
  ;; Add to the global default value of `completion-at-point-functions' which is
  ;; used by `completion-at-point'.  The order of the functions matters, the
  ;; first function returning a result wins.  Note that the list of buffer-local
  ;; completion functions takes precedence over the global list.
  (add-hook 'completion-at-point-functions #'cape-dabbrev)
  (add-hook 'completion-at-point-functions #'cape-file)
  (add-hook 'completion-at-point-functions #'cape-elisp-block)
  (add-hook 'completion-at-point-functions #'cape-history)
  (add-hook 'completion-at-point-functions #'cape-elisp-symbol)
  ;; (add-hook 'completion-at-point-functions #'cape-emoji)
  (add-hook 'completion-at-point-functions #'cape-keyword)
  (add-hook 'completion-at-point-functions #'cape-rfc1345)
  (add-hook 'completion-at-point-functions #'cape-sgml)
  ;;(add-hook 'completion-at-point-functions #'cape-tex)
  ;;(add-hook 'completion-at-point-functions #'cape-abbrev)
  ;;(add-hook 'completion-at-point-functions #'cape-dict)
  ;;(add-hook 'completion-at-point-functions #'cape-line)
  )
evil-mc
(use-package evil-mc
  :config
  (global-evil-mc-mode))
flyspell
(use-package flyspell
  :ensure nil
  :general
  (d/mode-leader-keys text-mode-map
    "f" '(:def d/flyspell/body))
  :config
  (defun d/flyspell-add-to-dictionary ()
    "Add word at point to flyspell dictionary.

Source: http://tinyurl.com/k8g9sex"
    (interactive)
    (let ((current-location (point))
          (word (flyspell-get-word)))
      (when (consp word)
        (flyspell-do-correct 'save
                             nil
                             (car word)
                             current-location
                             (caddr word)
                             (caddr word)
                             current-location))))

  (defhydra d/flyspell ()
    "
╭──────────╮
│ flyspell │
└──────────┴───────────────────────────────────────────────────────
 [_>_] goto-next [_c_] correct-next [_a_] auto-correct-next [_b_] buffer
 [_<_] goto-prev [_C_] correct-prev [_A_] auto-correct-prev [_d_] dict add
───────────────────────────────────────────────────────────────────
"
    (">" flyspell-goto-next-error)
    ("<" flyspell-goto-prev-error)
    ("c" flyspell-correct-next)
    ("C" flyspell-correct-previous)
    ("a" flyspell-auto-correct-word)
    ("A" flyspell-auto-correct-previous-word)
    ("b" flyspell-buffer)
    ("d" d/flyspell-add-to-dictionary)))
hungry-delete
(use-package hungry-delete
  :defer 5
  :config
  (global-hungry-delete-mode))
undo-fu
(use-package undo-fu :ensure (:protocol https))
unfill
(use-package unfill
  :general
  (d/leader-keys "xq" 'unfill-toggle)
  ([remap fill-paragraph] 'unfill-toggle))
yasnippet
(use-package yasnippet
  :defer 5
  :general (d/leader-keys "iy" 'yas-insert-snippet)
  :config
  ;; (imap yas-minor-mode-map
  ;;   "SPC" yas-maybe-expand
  ;;   "S-SPC" (lambda () (interactive) (insert " ")))
  (setq yas-key-syntaxes (remove "w" yas-key-syntaxes))
  (yas-global-mode 1))

Modes

cmake-mode

(use-package cmake-mode)

conf-mode

(use-package conf-mode
  :ensure nil
  :hook (conf-mode . d/setup-prog-mode)
  :init
  (add-to-list 'auto-mode-alist '("\\.service\\'" . conf-mode)))

csv-mode

(use-package csv-mode
  :mode "\\.csv\\'"
  :hook (csv-mode . csv-align-mode))

json-mode

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

markdown-mode

Syntax highlighting for markdown files.

(use-package markdown-mode
  :mode "\\.md\\'")

yaml-mode

(use-package yaml-mode
  :mode "\\.yml\\'")

svnwiki-mode

(use-package svnwiki-mode
  :general
  (d/mode-leader-keys svnwiki-mode-map
    "c" 'd/svnwrap-with-code)
  :mode "\\.svnwiki\\'"
  :ensure (:repo "https://depp.brause.cc/svnwiki-mode.git")
  :config
  (defun d/svnwrap-with-code ()
    (interactive)
    (let ((bounds (if (region-active-p)
                      (cons (region-beginning)
                            (region-end))
                    (bounds-of-thing-at-point 'symbol))))
      (goto-char (car bounds))
      (insert "{{")
      (goto-char (+ (cdr bounds) 2))
      (insert "}}")
      (goto-char (car bounds)))))

dockerfile-mode

(use-package dockerfile-mode)

olivetti

(use-package olivetti
  :custom
  (olivetti-body-width 90))

Bindings

Make indent-rigidly more vimmy.

(general-def indent-rigidly-map
  "h" 'indent-rigidly-left
  "l" 'indent-rigidly-right
  "H" 'indent-rigidly-left-to-tab-stop
  "L" 'indent-rigidly-right-to-tab-stop)

Leader keys

(d/leader-keys
  "xii" 'indent-rigidly
  "xir" 'indent-region
  "xj"  '(:def d/justify/body)
  "xls" 'sort-lines
  "xt"  '(:def d/transpose/body)
  "xc"  'count-words

  "xs"  'd/shorten-url-at-point
  "xe"  'd/expand-url-at-point

  "im"  'insert-kbd-macro)

Navigation

Built-in

goto-addr

(use-package goto-addr
  :ensure nil
  :hook (((help-mode org-mode text-mode) . goto-address-mode)
         ((prog-mode conf-mode) . goto-address-prog-mode)))

display-line-numbers

(use-package display-line-numbers
  :if (version<= "26" emacs-version)
  :ensure nil
  :hook ((prog-mode conf-mode) . display-line-numbers-mode)
  :custom
  (display-line-numbers-type 'relative)
  (display-line-numbers-width-start t)
  (display-line-numbers-grow-only t)
  :config
  (defun d/relative-line-numbers ()
    (when display-line-numbers
      (setf display-line-numbers 'relative)))
  (defun d/absolute-line-numbers ()
    (when display-line-numbers
      (setf display-line-numbers t)))
  (add-hook 'evil-insert-state-entry-hook #'d/absolute-line-numbers)
  (add-hook 'evil-normal-state-entry-hook #'d/relative-line-numbers)
  (add-hook 'window-selection-change-functions
            (defun d/setup-window-selection-change (_)
              (let* ((old-window (old-selected-window))
                     (new-window (selected-window))
                     (new-buffer (window-buffer new-window))
                     (old-buffer (window-buffer old-window)))
                (when (not (eql evil-state 'insert))
                  (when display-line-numbers
                    (setf display-line-numbers 'relative)))
                (when (not (eq new-buffer old-buffer))
                  (when old-buffer
                    (with-current-buffer old-buffer
                      (when display-line-numbers
                        (setf display-line-numbers t)))))))))

winner

(use-package winner
  :ensure nil
  :defer 15
  :general
  (evil-window-map
   [right] 'd/winner/winner-redo
   [left] 'd/winner/winner-undo)
  :config
  (winner-mode)
  (defhydra d/winner ()
    "
╭────────╮
│ winner │
└────────┴────────────────────
 [_<left>_] undo [_<right>_] redo
──────────────────────────────
"
    ("<right>" winner-redo)
    ("<left>" winner-undo)))

hideshow

(use-package hideshow
  :ensure nil
  :config
  (setq hs-allow-nesting t))

xref

(use-package xref
  :ensure nil
  :general
  (d/leader-keys
    "jD" 'xref-find-definitions-other-window
    "jd" 'xref-find-definitions
    "jr" 'xref-find-references)
  :custom
  (xref-show-definitions-function #'xref-show-definitions-completing-read))

tab-bar

(use-package tab-bar
  :ensure nil
  :general
  (d/leader-keys
    "tt" 'tab-switch
    "tn" 'tab-new
    "tx" 'tab-close
    "tr" 'tab-rename)
  :custom
  (tab-bar-auto-width-max '(200 20))
  (tab-bar-close-button-show nil)
  (tab-bar-show 1)
  (tab-bar-separator "")
  :config
  ;; for some reason, :defer in the use-package declaration is doing nothing
  ;; here? Presumably some previous package requires tab-bar. So I'm just
  ;; directly using the idle timer since I do want to wait for the icons
  ;; setting. This is a bit jnk.
  (run-with-idle-timer 5 nil #'tab-bar-mode)
  (defface d/tab-bar-accent '((t (:background "#BDAE93")))
    "Face for active tab accent")
  (defface d/tab-bar-inactive-accent '((t :inherit 'tab-bar))
    "Face for inactive tab accent")
  (setf tab-bar-tab-name-format-function
        (defun d/tab-bar-tab-name-format (tab _index)
          (concat
           ;; This is a non-breaking space, because I want an accent at the
           ;; beginning of the tab, but tab-bar-mode uses the first face
           ;; present (if it's in tab-bar-auto-width-faces) to determine
           ;; whether to resize the tab and to style the padding.
           ;; (propertize "​" 'face (funcall tab-bar-tab-face-function tab))
           ;; (propertize " "
           ;;             'face (if (eq (car tab) 'current-tab)
           ;;                       'd/tab-bar-accent
           ;;                     'd/tab-bar-inactive-accent))
           (propertize (concat "  " (alist-get 'name tab))
                       'face (funcall tab-bar-tab-face-function tab))))))

Packages

ace-window

(use-package ace-window
  :general
  (nmap :keymaps 'override "\\" 'ace-window)
  (mmap :keymaps 'override "\\" 'ace-window)
  :config
  (setq aw-keys (string-to-list "aoeuidhtns")
        aw-scope 'visible))

avy

(use-package avy
  :general
  (d/leader-keys
    "jc" 'avy-goto-char-2
    "jl" 'avy-goto-line
    "jw" 'avy-goto-word-1
    "jt" 'avy-goto-char-timer)
  :config
  (setq avy-keys (string-to-list "aoeuidhtns")))

dumb-jump

(use-package dumb-jump
  :after xref
  :commands dumb-jump-xref-activate
  :custom
  (dumb-jump-prefer-searcher 'rg)
  (dumb-jump-force-searcher 'rg)
  :init
  (add-hook 'xref-backend-functions #'dumb-jump-xref-activate))

imenu-anywhere

imenu on steroids.

(use-package imenu-anywhere)

shackle

(use-package shackle
  :defer 15
  :demand t
  :custom
  (shackle-rules '((inferior-emacs-lisp-mode :popup t)
                   (inferior-scheme-mode :popup t)
                   ("\\*sly-mrepl" :regexp t :popup t)))
  :config
  (shackle-mode))

Bindings

(d/leader-keys
  "jI" 'imenu-anywhere
  "jf" 'find-function-other-window
  "ji" 'imenu
  "jv" 'find-variable-other-window
  "jj" 'find-library-other-window)

Appearance

Settings

(xterm-mouse-mode)

(setq frame-resize-pixelwise t
      window-divider-default-places t
      custom-raised-buttons nil
      use-dialog-box nil)

(setq-default truncate-lines t
              word-wrap t)

(setq-default cursor-in-non-selected-windows nil)
(setq highlight-nonselected-windows nil)

(add-to-list 'default-frame-alist '(internal-border-width . 8))

(setf frame-title-format nil)

(when (eql system-type 'darwin)
  (setf ns-use-proxy-icon nil)
  (add-to-list 'default-frame-alist '(ns-transparent-titlebar . t)))

Font

(add-to-list 'default-frame-alist '(font . "Iosevka Fixed SS14-11:weight=regular"))
(defun d/setup-fonts (&rest ignore)
  (setq inhibit-compacting-font-caches t
        use-default-font-for-symbols t)
  ;; Emoji
  (cond ((find-font (font-spec :name "Apple Color Emoji"))
         (set-fontset-font t 'emoji "Apple Color Emoji" nil 'prepend))
        ((find-font (font-spec :name "Noto Emoji"))
         (set-fontset-font t 'emoji "Noto Emoji" nil 'prepend))
        ((find-font (font-spec :name "Noto Color Emoji"))
         (set-fontset-font t 'emoji "Noto Color Emoji" nil 'prepend)))
  ;; Music
  (set-fontset-font t '(#x1D100 . #x1d1FF) (font-spec :name "FreeSerif") nil 'prepend))
(when (featurep 'fontset)
  (d/setup-fonts)
  (add-to-list 'after-make-frame-functions 'd/setup-fonts))

Built-in

hl-line

(use-package hl-line
  :ensure nil
  :hook ((prog-mode
          emms-browser-mode
          dired-mode
          conf-mode)
         . hl-line-mode)
  :custom (hl-line-sticky-flag nil))

whitespace

(use-package whitespace
  :ensure nil
  :defer 5
  :config
  (setq whitespace-style '(face trailing empty missing-newline-at-eof tabs tab-mark))
  (setq whitespace-global-modes
        '(not erc-mode ses-mode vterm-mode
              magit-status-mode magit-revision-mode
              magit-log-mode magit-diff-mode dired-mode))
  (global-whitespace-mode))

icons

(use-package icons
  :ensure nil
  :custom
  (icon-preference '(text)))

ansi-color

(use-package ansi-color
  :ensure nil
  :custom
  (ansi-color-bold-is-bright t))

Packages

rainbow-mode

(use-package rainbow-mode
  :ensure (:host github :repo "emacsmirror/rainbow-mode" :branch "master")
  :hook (help-mode conf-mode)
  :config
  (setq rainbow-x-colors-major-mode-list '(c-mode c++-mode java-mode)))

highlight-numbers

Neat-o

(use-package highlight-numbers
  :hook (((prog-mode conf-mode) . highlight-numbers-mode)
         (lisp-mode . (lambda () (highlight-numbers-mode -1)))
         (json-mode . highlight-numbers--turn-off)))

rainbow-delimiters

Better parentheses coloring

(use-package rainbow-delimiters
  :hook ((prog-mode
          conf-mode
          geiser-repl-mode
          inferior-scheme-mode
          inferior-python-mode
          ielm-mode
          sly-mrepl-mode)
         .
         rainbow-delimiters-mode))

xterm-color

(use-package xterm-color
  :commands xterm-color-filter
  :hook ((eshell-before-prompt . d/xterm-color-preserve-properties))
  :config
  (defun d/xterm-color-preserve-properties ()
    (setq xterm-color-preserve-properties t)))

ligature

(use-package ligature
  :demand t
  :config
  ;; Enable all Iosevka ligatures in programming modes
  (ligature-set-ligatures 'prog-mode '("<---" "<--"  "<<-" "<-" "->" "-->" "--->" "<->" "<-->" "<--->" "<---->" "<!--"
                                       "<==" "<===" "<=" "=>" "=>>" "==>" "===>" ">=" "<=>" "<==>" "<===>" "<====>" "<!---"
                                       "<~~" "<~" "~>" "~~>" "::" ":::" "==" "!=" "===" "!=="
                                       ":=" ":-" ":+" "<*" "<*>" "*>" "<|" "<|>" "|>" "+:" "-:" "=:" "<******>" "++" "+++"))
  ;; Enables ligature checks globally in all buffers. You can also do it
  ;; per mode with `ligature-mode'.
  (global-ligature-mode t))

comfy-mode

(use-package comfy-mode
  :ensure (:repo "git@git.sr.ht:~dieggsy/comfy-mode")
  :init
  (comfy-mode 1))

Development

Settings

(defun d/auto-fill-only-comments ()
  (setq-local comment-auto-fill-only-comments t))
(add-hook 'prog-mode-hook #'subword-mode)
(add-hook 'prog-mode-hook #'auto-fill-mode)
(add-hook 'prog-mode-hook #'d/auto-fill-only-comments)

(add-to-list 'auto-mode-alist '("PKGBUILD" . sh-mode))
(add-to-list 'auto-mode-alist '("\\.hook\\'" . conf-mode))

Misc

flycheck

(use-package flycheck
  :hook (python-mode . flycheck-mode)
  :general
  (d/mode-leader-keys prog-mode-map
    "f" '(:def d/flycheck/body))
  :config
  (define-fringe-bitmap 'flycheck-fringe-bitmap-double-arrow
    [#b10000000
     #b01000000
     #b11100000
     #b00010000
     #b11111000
     #b00000100
     #b11111110
     #b00000100
     #b11111000
     #b00010000
     #b11100000
     #b01000000
     #b10000000])

  (defhydra d/flycheck
    (:body-pre (progn
                 (flycheck-mode 1)
                 (flycheck-list-errors))
     :post (quit-windows-on "*Flycheck errors*"))
    "
╭──────────╮
│ flycheck │
└──────────┴────────
 [_>_] next [_H_] first
 [_<_] prev [_L_] last
────────────────────
"
    ("f"  flycheck-error-list-set-filter)
    (">"  flycheck-next-error)
    ("<"  flycheck-previous-error)
    ("H" flycheck-first-error)
    ("L"  (progn (goto-char (point-max))
                 (flycheck-previous-error)))))

hl-todo

(use-package hl-todo
  :hook (prog-mode . hl-todo-mode))

Language Support

treesit-auto

(use-package treesit-auto
  :demand t
  :custom
  (treesit-auto-install 'prompt)
  :config
  (treesit-auto-add-to-auto-mode-alist 'all)
  (global-treesit-auto-mode)
  (add-to-list 'treesit-auto-recipe-list
               (make-treesit-auto-recipe
                 :lang 'c
                 :ts-mode 'c-ts-mode
                 :remap 'c-mode
                 :url "https://github.com/tree-sitter/tree-sitter-c"
                 :requires 'cpp
                 :abi14-revision "v0.23.0"
                 :ext "\\.c\\'")

               )
  (add-to-list 'treesit-auto-recipe-list
               (make-treesit-auto-recipe
                :lang 'cpp
                :ts-mode 'c++-ts-mode
                :remap 'c++-mode
                :requires 'c
                :url "https://github.com/tree-sitter/tree-sitter-cpp"
                :revision "v0.22.0" ;; BUG: newer grammar breaks syntax highlighting in `c++-ts-mode'
                :abi14-revision "v0.23.0"
                :ext "\\.cpp\\'"))
  )

C++

Packages
irony
(use-package irony
  :hook ((c++-mode . irony-mode)
         (irony-mode . irony-cdb-autosetup-compile-options)))
company-irony
(use-package company-irony
  :after irony
  :demand
  :init
  (d/after 'company
    '(add-to-list 'company-backends 'company-irony)))
lsp-mode
(use-package lsp-mode
  :hook (c++-mode c-mode))
cquery
(use-package cquery
  :hook (c++-mode . lsp-cquery-enable)
  :config
  (setq cquery-executable "/usr/bin/cquery"))
ccls
(use-package ccls
  :hook (c++-mode . d/enable-ccls)
  :config
  (defun d/enable-ccls ()
    (require 'ccls)
    (lsp)))
lsp-ui
(use-package lsp-ui
  :hook (lsp-mode . lsp-ui-mode))
Setup
(defalias 'cpp-mode 'c++-mode)
(d/after 'cc-mode
  (setq-default c-basic-offset 4)
  (c-set-offset 'case-label '+))

Python

Built-in
(use-package python
  :ensure nil
  :hook (python-mode . eglot-ensure)
  :config
  (d/after 'eglot
    (add-to-list 'eglot-server-programs
                 `(python-mode . ("uvx" "--from" "basedpyright" "basedpyright-langserver" "--" "--stdio")))))
Packages
pyenv-mode
(use-package pyenv-mode
  :config
  (pyenv-mode))
pyenv-mode-auto
(use-package pyenv-mode-auto
  :after pyenv-mode
  :demand t)
Bindings
(d/mode-leader-keys python-mode-map
  "eb" 'python-shell-send-buffer
  "ef" 'python-shell-send-defun
  "er" 'python-shell-send-region
  "eF" 'python-shell-send-file
  "es" 'python-shell-send-statement)
Setup
(defun d/setup-python ()
  (set (make-local-variable 'comment-inline-offset) 2))
(add-hook 'python-mode-hook #'d/setup-python)

(d/after 'python
  (setq python-shell-completion-native-enable nil))

Lisps

Packages
request
(use-package request)

(use-package request-deferred)
flycheck-package
(use-package flycheck-package
  :after flycheck
  :demand t
  :config
  (flycheck-package-setup))
macrostep
(use-package macrostep
  :general
  (d/mode-leader-keys (emacs-lisp-mode-map lisp-mode-map)
    "m" 'macrostep-expand))
highlight-quoted
(use-package highlight-quoted
  :hook ((lisp-mode sly-mrepl-mode emacs-lisp-mode) . highlight-quoted-mode))
lispyville
(use-package lispyville
  ;; :hook ((emacs-lisp-mode scheme-mode lisp-mode) . lispyville-mode)
  :custom (lispyville-key-theme '(operators
                                  c-w
                                  c-u
                                  prettify
                                  (atom-movement t)
                                  slurp/barf-lispy
                                  additional
                                  additional-movement
                                  additional-insert)))
Functions
(defun d/eval-surrounding-sexp (levels)
  "Eval sexp around point, specifying depth with LEVELS.

Source: http://tinyurl.com/le6wxuo"
  (interactive "p")
  (save-excursion
    (up-list (abs levels))
    (eval-last-sexp nil)))
Bindings
(d/mode-leader-keys emacs-lisp-mode-map
  "eb" 'eval-buffer
  "ef" 'eval-defun
  "er" 'eval-region
  "es" 'd/eval-surrounding-sexp
  "el" 'eval-last-sexp)
Setup
(defun d/setup-lisp ()
  (hs-minor-mode)
  (d/after 'evil-surround
    (push '(? . ("`" . "'")) evil-surround-pairs-alist)))
(add-hook 'lisp-mode-hook #'d/setup-lisp)
(add-hook 'emacs-lisp-mode-hook #'d/setup-lisp)

Haskell

(use-package haskell-mode
  :mode "\\.hs\\'"
  :config
  (setq haskell-indentation-layout-offset 4
        haskell-indentation-left-offset 4
        haskell-indentation-ifte-offset 4))

Racket

(use-package racket-mode)

Rust

rust-mode
(use-package rust-mode)
racer
(use-package racer
  :hook (rust-mode . racer-mode))
ob-rust
(use-package ob-rust)

Scheme

Built-in
(use-package scheme
  :ensure nil
  :mode ("\\.sld\\'" . scheme-mode)
  :mode ("\\.egg\\'" . scheme-mode)
  :mode ("\\.release-info\\'" . scheme-mode)
  :interpreter (("chicken-scheme" . scheme-mode)
                ("csi" . scheme-mode))
  :general
  (d/mode-leader-keys scheme-mode-map
    "eb" 'd/scheme-send-buffer
    "ef" 'scheme-send-definition
    "er" 'scheme-send-region
    "el" 'scheme-send-last-sexp
    "es" 'd/scheme-send-surrounding-sexp)
  (nmap scheme-mode-map
    "C-c C-c" 'd/scheme-compile-region-or-definition)
  :init
  (defvar d/scheme-extra-keywords
    ;; Function-like keywords
    `((,(rx "(" (group (or "define-for-syntax"
                           "define-inline"
                           "define-external"
                           "define-constant"
                           "define-foreign-variable"))
            symbol-end (* space)
            (? "(") (? (group (1+ word))))
       (1 font-lock-keyword-face)
       (2 font-lock-function-name-face nil t))
      ;; Make consistent with define-syntax
      (,(rx "(" (group "define-syntax-rule")
            symbol-end (* space)
            (? "(") (? (group (1+ word))))
       (1 font-lock-keyword-face)
       (2 font-lock-variable-name-face nil t))
      ;; Highlight types
      (,(rx "(" (group "define-foreign-type")
            symbol-end (* space)
            (? (group (1+ word)))
            (? (* space))
            (? (group (1+ word))))
       (1 font-lock-keyword-face)
       (2 font-lock-type-face)
       (3 font-lock-type-face))
      ;; CHICKEN-style keywords
      ("\\<\\sw+:\\>" . 'font-lock-builtin-face)
      ;; Hashbang
      ("\\`\\(#!.*/\\)\\([^ \t\n]+\\)\\( .*\\)?"
       (1 font-lock-comment-face)
       (2 font-lock-keyword-face t)
       (3 font-lock-comment-face t t))
      ;; #!key, #!optional, #!rest etc.
      ("\\<#!\\sw+\\>" . 'font-lock-type-face)))

  (font-lock-add-keywords 'scheme-mode d/scheme-extra-keywords t)
  (font-lock-add-keywords 'inferior-scheme-mode d/scheme-extra-keywords t)

  (defvar d/scheme-simple-keywords
    `((,(format
         "[[(]%s\\>"
         (regexp-opt
          '("and-let*" "cut" "cute" "define-record-type" "define-values"
            "letrec*" "set!" "syntax-case" "unless" "when" "assume"
            "compiler-typecase" "cond-expand" "condition-case" "declare"
            "define-interface" "define-record" "define-specialization"
            "define-type" "fluid-let" "foreign-lambda" "foreign-lambda*"
            "foreign-primitive" "foreign-safe-lambda" "foreign-safe-lambda*"
            "functor" "handle-exceptions" "let-location" "let-optionals"
            "let-optionals*" "letrec-values" "module"
            "define-foreign-record-type" "foreign-value" "foreign-declare"
            "begin-for-syntax" "define-for-syntax" "include-relative"
            "import-for-syntax"
            ;; matchable
            "match" "match-lambda" "match-lambda*" "match-let" "match-let*"
            "match-letrec"
            ;; miscmacros
            "dotimes" "ecase" "select" "repeat" "while"
            ;; test
            "test-assert" "test-begin" "test-end" "test" "test-error"
            "test-group")
          1))
       . 1)))

  (font-lock-add-keywords 'scheme-mode d/scheme-simple-keywords t)
  (font-lock-add-keywords 'inferior-scheme-mode d/scheme-simple-keywords t)
  :config
  (defun d/scheme-send-buffer ()
    (interactive)
    (scheme-send-region (point-min) (point-max)))

  (defun d/scheme-send-surrounding-sexp (levels)
    (interactive "p")
    (save-excursion
      (up-list (abs levels))
      (scheme-send-last-sexp)))

  (defun d/scheme-compile-region-or-definition ()
    (interactive)
    (if (region-active-p)
        (scheme-compile-region (region-beginning)
                               (region-end))
      (scheme-compile-definition)))

  (setq scheme-program-name "csi -q")
  ;; chicken core
  (put 'module                    'scheme-indent-function 2)
  (put 'functor                   'scheme-indent-function 2)
  (put 'begin-for-syntax          'scheme-indent-function 0)
  (put 'and-let*                  'scheme-indent-function 1)
  (put 'let-optionals             'scheme-indent-function 1)
  (put 'let-optionals*            'scheme-indent-function 1)
  (put 'letrec-values             'scheme-indent-function 1)
  (put 'case-lambda               'scheme-indent-function 0)
  (put 'catch                     'scheme-indent-function 'defun)
  (put 'assume                    'scheme-indent-function 1)
  (put 'cond-expand               'scheme-indent-function 0)
  (put 'cut                       'scheme-indent-function 1)
  (put 'cute                      'scheme-indent-function 1)
  (put 'set-read-syntax!          'scheme-indnet-function 1)
  (put 'set-sharp-read-syntax!    'scheme-indnet-function 1)
  (put 'condition-case            'scheme-indent-function 1)
  (put 'handle-exceptions         'scheme-indent-function 2)
  (put 'with-exception-handler    'scheme-indent-function 1)
  (put 'with-input-from-pipe      'scheme-indent-function 1)
  (put 'with-output-to-pipe       'scheme-indent-function 1)
  (put 'compiler-typecase         'scheme-indent-function 1)
  (put 'foreign-lambda            'scheme-indent-function 2)
  (put 'foreign-lambda*           'scheme-indent-function 2)
  (put 'foreign-safe-lambda       'scheme-indent-function 2)
  (put 'foreign-safe-lambda*      'scheme-indent-function 2)
  (put 'foreign-primitive         'scheme-indent-function 2)
  (put 'let-location              'scheme-indent-function 1)
  (put 'with-error-output-to-port 'scheme-indent-function 1)
  ;; matchable
  (put 'match         'scheme-indent-function 1)
  (put 'match-let     'scheme-indent-function 1)
  (put 'match-let*    'scheme-indent-function 1)
  (put 'match-letrec  'scheme-indent-function 1)
  (put 'match-lambda  'scheme-indent-function 0)
  (put 'match-lambda* 'scheme-indent-function 0)
  ;; miscmacros
  (put 'while   'scheme-indent-function 1)
  (put 'select  'scheme-indent-function 1)
  (put 'dotimes 'scheme-indent-function 1)
  (put 'repeat  'scheme-indent-function 1)
  (put 'ecase   'scheme-indent-function 1)
  ;; test
  (put 'test-assert 'scheme-indent-function 1)
  (put 'test-error  'scheme-indent-function 1)
  (put 'test        'scheme-indent-function 1)
  (put 'test-group  'scheme-indent-function 1)
  ;; spiffy
  (put 'with-headers 'scheme-indent-function 1)
  (put 'with-egg 'scheme-indent-function 1))
Packages
chicken-doc.el
(use-package chicken-doc
  :general
  (d/mode-leader-keys scheme-mode-map
    "d" 'chicken-doc-describe)
  (d/mode-leader-keys inferior-scheme-mode-map
    "d" 'chicken-doc-describe)
  :ensure (:repo "https://depp.brause.cc/chicken-doc.el.git"))

Lisp

Built-in
(use-package lisp-mode
  :ensure nil
  :mode "\\.\\(tdf\\|tv-tic\\|tv-diag\\|tv-desc\\|cv-desc\\)\\'")

Packages
sly
(use-package sly
  :general
  (d/leader-keys
    "as" 'd/sly)
  (nmap sly-mode-map
    "gd" 'd/sly-edit-or-evil-goto-definition)
  (:keymaps 'sly-mrepl-mode-map
            "M-r" 'consult-history)
  (imap sly-mrepl-mode-map
    [up] 'sly-mrepl-previous-input-or-button
    [down] 'sly-mrepl-next-input-or-button)
  (d/mode-leader-keys lisp-mode-map
    "M"  'sly-macroexpand-1-inplace
    "eb" 'sly-eval-buffer
    "ef" 'sly-eval-defun
    "er" 'sly-eval-region
    ;; "es" 'd/eval-surrounding-sexp
    "el" 'sly-eval-last-expression)
  :config
  (defun d/sly ()
    (interactive)
    (let ((current-prefix-arg '-))
      (call-interactively #'sly)))
  (setq sly-lisp-implementations
        '((sbcl ("sbcl" "--dynamic-space-size" "8912"))
          (alisp ("alisp"))
          (ecl ("ecl"))
          (clisp ("clisp")))
        sly-contribs (append '(sly-tramp sly-macrostep)
                             sly-contribs))
  (d/after 'sly-mrepl
    (cl-pushnew 'ansi-color-apply sly-mrepl-output-filter-functions))
  (defun d/sly-edit-or-evil-goto-definition ()
    (interactive)
    (condition-case nil
        (call-interactively 'sly-edit-definition)
      (error (evil-goto-definition))))

  (define-advice sly--mode-line-format (:override () d/sly-mode-line)
    (let* ((conn (sly-current-connection))
           (conn (and (process-live-p conn) conn))
           (name (or (and conn
                          (sly-connection-name conn))
                     "*"))
           (pkg (sly-current-package))
           (package-name (and pkg
                              (sly--pretty-package-name pkg))))

      `((:eval (propertize ,name
                           'face (when (d/window-active-p) '(:inherit font-lock-keyword-face :slant normal))))
        (:eval (propertize ":"
                           'face (when (d/window-active-p) '(:inherit font-lock-comment-face :slant normal))))
        (:eval (propertize ,(or package-name "*")
                           'face (when (d/window-active-p) '(:inherit font-lock-constant-face :slant normal))))))))

(use-package sly-macrostep)
slime
(use-package slime
  :general
  (d/leader-keys
    "as" 'd/slime)
  (:keymaps 'slime-repl-mode-map
            "M-r" 'consult-history)
  (imap slime-repl-mode-map
    [up] 'slime-repl-previous-input
    [down] 'slime-repl-next-input)
  :hook
  (lisp-mode . slime-mode)
  :config
  (defun d/slime ()
    (interactive)
    (let ((current-prefix-arg '-))
      (call-interactively #'slime)))
  (d/after 'slime-presentations
    (define-key slime-presentation-map [return] 'slime-inspect-presentation-at-point)
    (define-key slime-presentation-map [mouse-1] 'slime-inspect-presentation-at-point))
  (require 'slime-macrostep)
  (add-to-list 'mode-line-misc-info
               ;; slime-repl-mode doesn't actually set the variable
               ;; slime-repl-mode for some reason
               '(:eval (when (or (eql major-mode 'slime-repl-mode)
                                 slime-mode)
                         (concat (slime-modeline-string) " "))))

  (setq inferior-lisp-program (executable-find "sbcl")
        slime-lisp-implementations '((sbcl ("sbcl" "--dynamic-space-size" "8912"))
                                     (alisp ("alisp"))
                                     (ecl ("ecl"))
                                     (clisp ("clisp")))
        slime-contribs (append '(slime-macrostep
                                 slime-tramp
                                 slime-cl-indent)
                               slime-contribs))

  (define-advice slime-maybe-complete-as-filename (:override ())
    "Don't hook into slime's file completion mechanism. It's a bit too
agressive and cape covers this anyway."
    nil))

(d/after 'consult
  (add-to-list 'consult-mode-histories
               '(slime-repl-mode slime-repl-input-history slime-repl-input-history-position nil)))

;; (use-package slime-company
;;   :after slime
;;   :autoload company-slime
;;   :custom
;;   (slime-company-completion 'fuzzy)
;;   :init
;;   (d/after 'cape
;;     (add-hook 'slime-repl-mode-hook (lambda () (add-to-list 'completion-at-point-functions (cape-company-to-capf #'company-slime))))
;;     (add-hook 'slime-mode-hook (lambda () (add-to-list 'completion-at-point-functions (cape-company-to-capf #'company-slime))))))
cl-fancy
(use-package cl-fancy
  :ensure (:repo "git@git.sr.ht:~dieggsy/cl-fancy")
  :hook lisp-mode
  :after lisp-mode
  :demand t)
Functions
(defun d/remote-alisp ()
  (interactive)
  (let* ((remote-host (read-from-minibuffer "Remote host: "))
         (name (generate-new-buffer-name "*remote-alisp*"))
         (forward-proc (concat name "_forward")))
    (make-process
     :name name
     :buffer name
     :command `("ssh" ,remote-host "alisp --qq -L /user/dmundo/remote-alisp/start-slynk.lisp")
     :filter (lambda (proc output)
               (when (string-match ";; Slynk started at port: \\([[:digit:]]+\\)" output)
                 (let ((port (match-string 1 output)))
                   (make-process
                    :name forward-proc
                    :command `("ssh" ,(format "-L%s:localhost:%s" port port) ,remote-host "echo port-ready")
                    :filter (lambda (proc output)
                              (when (string-match-p "port-ready" output)
                                (sly-connect "localhost" (string-to-number port)))))))
               (internal-default-process-filter proc output)))))

Ocaml

tuareg
(use-package tuareg
  :general
  (d/mode-leader-keys tuareg-mode-map
    "es" 'tuareg-eval-phrase
    "eb" 'tuareg-eval-buffer
    "er" 'tuareg-eval-region))
dune
(use-package dune)
merlin
(use-package merlin
  :hook (tuareg-mode . merlin-mode))

vimrc-mode

(use-package vimrc-mode)

web-mode

(use-package web-mode
  :mode ("\\.html?\\'" "\\.xml\\'" "\\.launch\\'")
  :config
  (setq web-mode-markup-indent-offset 2))

lua-mode

(use-package lua-mode)

fish-mode

(use-package fish-mode)

java

Packages
(use-package eglot-java
  :hook (java-mode . eglot-java-mode)
  :custom
  (eglot-java-user-init-opts-fn 'd/eglot-java-init)
  :init
  (defun d/eglot-java-init (server eglot-java-eclipse-jdt)
    '(:settings
      (:java
       (:configuration
                       :home
                       "/opt/homebrew/opt/openjdk@21/libexec/openjdk.jdk/Contents/Home"
                       )
       ;; (:home "/Library/Java/JavaVirtualMachines/temurin-17.jdk/Contents/Home")

       ))))

gitlab-ci

(use-package gitlab-ci-mode)

robot

(use-package robot-mode
  :config
  (defun d/setup-robot ()
    (make-local-variable 'whitespace-style)
    (add-to-list 'whitespace-style 'spaces 'append)
    (add-to-list 'whitespace-style 'space-mark 'append))
  (add-hook 'robot-mode-hook #'d/setup-robot)
  (d/after 'eglot
    (add-to-list 'eglot-server-programs '(robot-mode . ("robotframework_ls")))))

Version Control

diff-hl

(use-package diff-hl
  :defer 10
  :custom
  (diff-hl-fringe-bmp-function 'd/diff-hl-bitmap)
  (diff-hl-dired-fringe-bmp-function 'd/diff-hl-bitmap)
  :init
  (define-fringe-bitmap 'd/diff-hl-bitmap
    (make-vector 29 #b00001111))
  (defun d/diff-hl-bitmap (type pos)
    'd/diff-hl-bitmap)
  :config
  (global-diff-hl-mode)
  (add-hook 'dired-mode-hook #'diff-hl-dired-mode)
  (d/after 'magit
    (add-hook 'magit-post-refresh-hook #'diff-hl-magit-post-refresh)))

magit

Like git, for emacs. But cooler. (Just trust me on this one.)

(use-package transient
  :custom
  (transient-default-level 7))

(use-package magit
  :general
  (d/leader-keys
    "gB" 'magit-blame
    "gC" 'magit-clone
    "gL" 'magit-log-buffer-file
    "ga" 'magit-submodule-add
    "gb" 'magit-branch
    "gc" 'magit-checkout
    "gf" 'magit-find-file
    "gl" 'magit-log-all
    "gs" 'magit-status
    "gp" 'magit-file-dispatch)
  :init
  (setq vc-follow-symlinks t
        auto-revert-check-vc-info t)
  :custom
  (magit-repository-directories '(("~/dotfiles" . 0)
                                  ("~/src" . 1)
                                  ("~/src/chicken/chicken-eggs/" . 1)))
  (magit-diff-refine-hunk t)
  (git-commit-summary-max-length 50)
  (git-commit-major-mode 'org-mode)
  (magit-tramp-pipe-stty-settings 'pty)
  (magit-commit-show-diff nil)
  (magit-branch-direct-configure nil)
  (magit-tramp-pipe-stty-settings 'pty)
  (magit-process-apply-ansi-colors t)
  :config
  (defun d/setup-git-commit ()
    (setq-local org-hide-emphasis-markers nil
                org-pretty-entities nil))
  (add-hook 'git-commit-mode-hook #'d/setup-git-commit)
  (add-hook 'git-commit-setup-hook #'git-commit-turn-on-flyspell))

magit-todos

(use-package magit-todos
  :after magit
  :config
  (magit-todos-mode 1))

smerge

(use-package smerge-mode
  :ensure nil
  :general
  (nmap smerge-mode-map
    "]]" 'd/smerge/smerge-next
    "[[" 'd/smerge/smerge-prev)
  (smerge-mode-map
   "C-<return>" 'smerge-keep-current)
  :config
  (defhydra d/smerge ()
    "
╭────────╮
│ smerge │
└────────┴──────────────────────────────
 [_]]_] next [_>_] keep-lower [_._] keep-base
 [_[[_] prev [_<_] keep-upper [_._] keep-all
────────────────────────────────────────
"
    ("]]" smerge-next)
    ("[[" smerge-prev)
    (">" smerge-keep-lower)
    ("<" smerge-keep-upper)
    ("." smerge-keep-base)
    ("a" smerge-keep-all)))

Tools

proced

(use-package proced
  :ensure nil
  :custom
  (proced-auto-update-interval 1)
  (proced-format 'medium)
  (proced-filter 'user)
  (proced-auto-update-flag 'visible)
  (proced-enable-color-flag t)
  (proced-descend t))

comint

(setq comint-prompt-read-only t
      comint-highlight-input nil)

grep

(use-package grep
  :ensure nil
  :config
  (grep-apply-setting 'grep-use-null-device nil)
  (grep-apply-setting 'grep-template "rg --color=auto --no-heading -nH --null -g '<F>' -e <R>")
  (grep-apply-setting 'grep-command  "rg --color=auto --no-heading -nH --null -e ")
  (grep-apply-setting 'grep-find-command '("find . -type f -exec rg --color=auto --no-heading -nH --null -e  \\{\\} +" . 65))
  (grep-apply-setting 'grep-find-template "find -H <D> <X> -type f <F> -exec rg --no-heading -nH --null -e <R> \\{\\} +"))

calc

And it's amazing, to be quite honest. "The poor man's Mathematica", I once heard (and can confirm).

(use-package calc
  :ensure nil
  :general
  (d/leader-keys
    "ac" 'calc-dispatch)
  (emap calc-mode-map
    "x" (lambda () (interactive) (counsel-M-x "^calc-")))
  :init
  (add-hook 'calc-embedded-mode-hook #'d/calc-embedded-set-open-close)
  :config
  (evil-set-initial-state 'calc-mode 'emacs)
  (setq calc-multiplication-has-precedence nil
        calc-symbolic-mode t)
  (defun d/calc-embedded-set-open-close ()
    (when comment-start
      (setq-local calc-embedded-open-mode
                  (concat comment-start " ")))
    (when comment-end
      (setq-local calc-embedded-close-mode
                  (concat comment-end "\n")))))

async

(use-package async)

htmlize

(use-package htmlize)

eshell

Builtin

(use-package eshell
  :ensure nil
  :custom-face
  (eshell-prompt ((t (:foreground unspecified :inherit default))))
  (eshell-ls-backup ((t :foreground unspecified :inherit dired-ignored)))
  :custom
  (eshell-modules-list '(eshell-alias
                         eshell-banner eshell-basic eshell-cmpl eshell-dirs
                         eshell-elecslash eshell-extpipe eshell-glob eshell-hist
                         eshell-ls eshell-pred eshell-prompt eshell-script eshell-term
                         eshell-unix))
  (eshell-scroll-to-bottom-on-input t)
  (eshell-hist-ignore-dups t)
  (eshell-history-size 20000)
  (eshell-destroy-buffer-when-process-dies t)
  (eshell-cmpl-compare-entry-function #'string-lessp)
  :init
  (defun d/consult-completion ()
    (setq-local completion-in-region-function #'consult-completion-in-region))
  (add-hook 'eshell-mode-hook #'d/consult-completion)
  :config
  (require 'dired)
  (require 'diredfl)

  (defun d/disable-corfu ()
    (corfu-mode -1))
  (add-hook 'eshell-mode-hook #'d/disable-corfu)

  (d/after 'em-hist
    (define-key eshell-hist-mode-map (kbd "M-r") #'consult-history))

  (d/after 'em-term
    (dolist (visual-command '("ncdu"))
      (add-to-list 'eshell-visual-commands visual-command)))

  (defalias #'eshell/clear #'eshell/clear-scrollback)

  (d/after 'em-ls
    (defun d/esh-ls-match-meta (file attrs)
      (string-match-p (concat (rx bos) d/meta-files (rx eos)) file))
    (defun d/esh-ls-match-image (file attrs)
      (string-match-p (concat "\\." (regexp-opt d/image-extensions) "\\'") file))
    (defun d/esh-ls-match-video (file attrs)
      (string-match-p (concat "\\." (regexp-opt d/video-extensions) "\\'") file))
    (defun d/esh-ls-match-music (file attrs)
      (string-match-p (concat "\\." (regexp-opt d/music-extensions) "\\'") file))
    (defun d/esh-ls-match-lossless-music (file attrs)
      (string-match-p (concat "\\." (regexp-opt d/lossless-music-extensions) "\\'") file))
    (defun d/esh-ls-match-crypto (file attrs)
      (string-match-p (concat "\\." (regexp-opt d/crypto-extensions) "\\'") file))
    (defun d/esh-ls-match-document (file attrs)
      (string-match-p (concat "\\." (regexp-opt d/document-extensions) "\\'") file))
    (defun d/esh-ls-match-source (file attrs)
      (string-match-p (concat "\\." (regexp-opt d/source-extensions) "\\'") file))
    (add-to-list 'eshell-ls-highlight-alist '(d/esh-ls-match-meta . dired-rainbow-meta-face))
    (add-to-list 'eshell-ls-highlight-alist '(d/esh-ls-match-image . dired-rainbow-image-face))
    (add-to-list 'eshell-ls-highlight-alist '(d/esh-ls-match-video . dired-rainbow-video-face))
    (add-to-list 'eshell-ls-highlight-alist '(d/esh-ls-match-music . dired-rainbow-music-face))
    (add-to-list 'eshell-ls-highlight-alist '(d/esh-ls-match-lossless-music . dired-rainbow-lossless-music-face))
    (add-to-list 'eshell-ls-highlight-alist '(d/esh-ls-match-crypto . dired-rainbow-crypto-face))
    (add-to-list 'eshell-ls-highlight-alist '(d/esh-ls-match-document . dired-rainbow-document-face))
    (add-to-list 'eshell-ls-highlight-alist '(d/esh-ls-match-source . dired-rainbow-source-face)))

  (define-advice eshell-send-input (:before (_) d/expand-abbrev)
    (expand-abbrev))

  (defun d/eshell-expand-abbrev ()
    "Expand command abbrev sorta like fish abbreviations.

This should just be a copy of abbrev--default-expand that adds the
condition of only expanding abbrevs at the beginning of the line."
    (pcase-let ((`(,sym ,name ,wordstart ,wordend) (abbrev--before-point)))
      (when (and sym
                 (save-excursion
                   (goto-char wordstart)
                   (forward-comment (- (buffer-size)))
                   (or (save-excursion
                         (backward-char 1)
                         (looking-at-p "|"))
                       (save-excursion
                         (forward-char 1)
                         (= (point) (line-beginning-position))))))
        (abbrev--default-expand))))

  (defun d/eshell-abbrevs ()
    (setq-local abbrev-expand-function #'d/eshell-expand-abbrev))
  (add-hook 'eshell-mode-hook #'abbrev-mode)
  (add-hook 'eshell-mode-hook #'d/eshell-abbrevs))

egp

(use-package egp
  :after eshell
  :commands egp-theme
  :ensure nil
  :custom (eshell-prompt-function #'egp-theme))

esh-ls-colors

(use-package esh-ls-colors
  :after em-ls
  :demand t
  :ensure nil)

esh-more-commands

(use-package esh-more-commands
  :after eshell
  :demand t
  :ensure nil)

eshell-syntax-highlighting

(use-package eshell-syntax-highlighting
  :after eshell
  :demand t
  :hook eshell-mode)

esh-autosuggest

(use-package esh-autosuggest
  :hook (eshell-mode . esh-autosuggest-mode))

eat

(use-package eat
  :after eshell
  :demand t
  :init
  (with-eval-after-load 'eshell
    (eat-eshell-mode +1)
    (eat-eshell-visual-command-mode +1)))

denote

(use-package denote
  :custom
  (denote-directory (expand-file-name "~/denote/"))
  (denote-prompts '(subdirectory title keywords)))

(use-package denote-org)

(use-package consult-denote)

Emacs Enhancements

crux

(use-package crux
  :general
  (d/leader-keys
    "TAB" 'crux-switch-to-previous-buffer
    "fd" 'crux-delete-file-and-buffer
    "fr" 'crux-rename-file-and-buffer
    "bs" 'crux-sudo-edit)
  (d/mode-leader-keys emacs-lisp-mode-map
    "eR" 'crux-eval-and-replace)
  :config
  (crux-with-region-or-line eval-region)
  (crux-with-region-or-buffer indent-region)
  (crux-with-region-or-buffer untabify)
  (crux-with-region-or-buffer tabify)
  (crux-with-region-or-buffer fill-region))

persistent-scratch

(use-package persistent-scratch
  :defer 10
  :config
  (with-current-buffer "*scratch*"
    (emacs-lisp-mode))
  (persistent-scratch-setup-default))

Web

(defun d/paste-region-or-buffer ()
  (interactive)
  (let* ((buffer (current-buffer))
         (region-active (region-active-p))
         (start (if region-active (region-beginning) (point-min)))
         (end (if region-active (region-end) (point-max)))
         (file-name (buffer-file-name))
         (name (and file-name `("-n" ,file-name))))
    (with-temp-buffer
      (let ((temp-buffer (current-buffer)))
        (with-current-buffer buffer
          (call-process-region start end "hut" nil temp-buffer nil "paste" "create")))
      (kill-new (buffer-substring (point-min) (point-max))))))

ERC

(use-package erc
  :ensure nil
  :custom
  (erc-hide-list '("JOIN" "PART" "QUIT"))
  (erc-modules '(autojoin button completion fill imenu irccontrols
                          keep-place list match menu
                          move-to-prompt netsplit networks nicks notifications readonly ring
                          scrolltobottom spelling stamp track))
  :init
  (defun erc-connect ()
    (interactive)
    (erc-tls
     :nick "dieggsy"
     :server "chat.sr.ht"
     :port 6697
     :user "dieggsy/irc.libera.chat"
     :password (car (process-lines "op" "read" "op://Private/Sourcehut/chat.sr.ht token")))))

Local

At the end I load a file called local.el, which is local to each machine and not version controlled. I use this for simple configuration that isn't shared, like setting font sizes for particular displays or other computer-specific configuration (e.g. home desktop, personal server, work laptop, work server).

(let ((local-config (locate-user-emacs-file "local.el")))
  (when (file-exists-p local-config)
    (load local-config)))

Local vars

# Local Variables:
# eval: (add-hook 'after-save-hook 'org-babel-tangle 'append 'local)
# End: