Emacs Bankruptcy
Welcome back to the church, my child.

Table of Contents

Introduction

This is another emacs config. My last one has lost organisation and was very slow to load. This configuration is meant to be used with James Cherti's minimal emacs.d. I will try to incorporate best-practices for config and package management as well as trust the sensible defaults set by minimal-emacs.d. The old config had lots of packages that I tried but never got around to use regularly. The goal here is to build this configuration up step by step and keep only what I actually use.

General settings

Tangle paths and file headers

To work with minimal-emacs.d, customisations are written to pre-init.el, post-init.el, post-early-init.el, and pre-early-init.el files. We will add a header comment to activate lexical binding and prevent byte-compiling.

;;; pre-early-init.el --- This file is loaded before early-init.el -*- no-byte-compile: t; lexical-binding: t; -*-
;;; post-early-init.el --- This file is loaded after early-init.el but before init.el -*- no-byte-compile: t; lexical-binding: t; -*-
;;; pre-init.el --- This file is loaded before init.el  -*- no-byte-compile: t; lexical-binding: t; -*-
;;; post-init.el --- This file is loaded after init.el -*- no-byte-compile: t; lexical-binding: t; -*-

A macro to only run code on macOS

(defmacro with-mac (&rest body)
  "Evaluate BODY only if running on macOS."
  `(when (eq system-type 'darwin)
     ,@body))

(with-mac
 (setq default-frame-alist
       (append
        '((ns-transparent-titlebar . t)
          (ns-appearance . light)) ;; or light
        default-frame-alist)))

Debug on error

This is useful to turn on during development and testing of the config. To turn it of, just move the cursor on the above heading and hit M-; to comment it out. Org will not tangle source blocks under commented headings.

(setq debug-on-error t)

UI Elements

Emacs has various UI elements that one might want to enable or disable. Since I am on macOS and not completely opposed to the mouse, I keep some enabled.

(setq minimal-emacs-ui-features '(
                                  context-menu
                                  ; tool-bar
                                  ; menu-bar
                                  dialogs
                                  tooltips
                                  ))

Reduce clutter in emacs.d

Emacs stores lots of cache, backups and other data in .emacs.d, which makes it unwieldy for backups and navigation. This can be mitigated by changeging the emacs default directory to .emacs.d/var

;;; Reducing clutter in ~/.emacs.d by redirecting files to ~/.emacs.d/var/
;; NOTE: This must be placed in 'pre-early-init.el'.
(setq user-emacs-directory (expand-file-name "var/" minimal-emacs-user-directory))
(setq package-user-dir (expand-file-name "elpa" user-emacs-directory))

Benchmark-init

(use-package benchmark-init
  :ensure t
  :hook
  ;; To disable collection of benchmark data after init is done.
  (after-init . benchmark-init/deactivate))

(defun xcvh/display-startup-info ()
  "Display package count and startup time in the echo area."
  (message "%d packages loaded in %.2f seconds"
           (length package-activated-list)
           (float-time (time-subtract after-init-time before-init-time))))

(add-hook 'emacs-startup-hook #'xcvh/display-startup-info)

Native compilation

Native compilation improves performance through converting Elisp into native machine code,

Add the following compile-angel ues-packge declaration at the very beginning of your post-init.el file, before all other packages.

(use-package compile-angel
  :demand t
  :ensure t
  :custom
  ;; Set `compile-angel-verbose` to nil to suppress output from compile-angel.
  ;; Drawback: The minibuffer will not display compile-angel's actions.
  (compile-angel-verbose t)

  :config
  ;; The following directive prevents compile-angel from compiling your init
  ;; files. If you choose to remove this push to `compile-angel-excluded-files'
  ;; and compile your pre/post-init files, ensure you understand the
  ;; implications and thoroughly test your code. For example, if you're using
  ;; the `use-package' macro, you'll need to explicitly add:
  ;; (eval-when-compile (require 'use-package))
  ;; at the top of your init file.
  (push "/init.el" compile-angel-excluded-files)
  (push "/early-init.el" compile-angel-excluded-files)
  (push "/pre-init.el" compile-angel-excluded-files)
  (push "/post-init.el" compile-angel-excluded-files)
  (push "/pre-early-init.el" compile-angel-excluded-files)
  (push "/post-early-init.el" compile-angel-excluded-files)

  ;; A local mode that compiles .el files whenever the user saves them.
  ;; (add-hook 'emacs-lisp-mode-hook #'compile-angel-on-save-local-mode)

  ;; A global mode that compiles .el files prior to loading them via `load' or
  ;; `require'. Additionally, it compiles all packages that were loaded before
  ;; the mode `compile-angel-on-load-mode' was activated.
  (compile-angel-on-load-mode 1))

Save bookmarks outside of .emacs.d

Sometimes you want or need to start with a fresh .emacs.d folder and run all init files without the cruft that accumulated there. In this case it's useful to save bookmarks outside of that folder.

(setq bookmark-file "~/emacsbookmarks")

QoL

Auto-revert

A feature that automatically updates the contents of a buffer to reflect changes made to the file on disk.

;; Auto-revert in Emacs is a feature that automatically updates the
;; contents of a buffer to reflect changes made to the underlying file
;; on disk.
(use-package autorevert
  :ensure nil
  :commands (auto-revert-mode global-auto-revert-mode)
  :hook
  (after-init . global-auto-revert-mode)
  :custom
  (auto-revert-interval 3)
  (auto-revert-remote-files nil)
  (auto-revert-use-notify t)
  (auto-revert-avoid-polling nil)
  (auto-revert-verbose t))

;; Recentf is an Emacs package that maintains a list of recently
;; accessed files, making it easier to reopen files you have worked on
;; recently.
(use-package recentf
  :ensure nil
  :commands (recentf-mode recentf-cleanup)
  :hook
  (after-init . recentf-mode)

  :custom
  (recentf-auto-cleanup (if (daemonp) 300 'never))
  (recentf-exclude
   (list "\\.tar$" "\\.tbz2$" "\\.tbz$" "\\.tgz$" "\\.bz2$"
         "\\.bz$" "\\.gz$" "\\.gzip$" "\\.xz$" "\\.zip$"
         "\\.7z$" "\\.rar$"
         "COMMIT_EDITMSG\\'"
         "\\.\\(?:gz\\|gif\\|svg\\|png\\|jpe?g\\|bmp\\|xpm\\)$"
         "-autoloads\\.el$" "autoload\\.el$"))

  :config
  ;; A cleanup depth of -90 ensures that `recentf-cleanup' runs before
  ;; `recentf-save-list', allowing stale entries to be removed before the list
  ;; is saved by `recentf-save-list', which is automatically added to
  ;; `kill-emacs-hook' by `recentf-mode'.
  (add-hook 'kill-emacs-hook #'recentf-cleanup -90))

;; savehist is an Emacs feature that preserves the minibuffer history between
;; sessions. It saves the history of inputs in the minibuffer, such as commands,
;; search strings, and other prompts, to a file. This allows users to retain
;; their minibuffer history across Emacs restarts.
(use-package savehist
  :ensure nil
  :commands (savehist-mode savehist-save)
  :hook
  (after-init . savehist-mode)
  :custom
  (savehist-autosave-interval 600)
  (savehist-additional-variables
   '(kill-ring                        ; clipboard
     register-alist                   ; macros
     mark-ring global-mark-ring       ; marks
     search-ring regexp-search-ring)))

;; save-place-mode enables Emacs to remember the last location within a file
;; upon reopening. This feature is particularly beneficial for resuming work at
;; the precise point where you previously left off.
(use-package saveplace
  :ensure nil
  :commands (save-place-mode save-place-local-mode)
  :hook
  (after-init . save-place-mode)
  :custom
  (save-place-limit 400))

Auto-save-visited

;; When auto-save-visited-mode is enabled, Emacs will auto-save file-visiting
;; buffers after a certain amount of idle time if the user forgets to save it
;; with save-buffer or C-x s for example.
;;
;; This is different from auto-save-mode: auto-save-mode periodically saves
;; all modified buffers, creating backup files, including those not associated
;; with a file, while auto-save-visited-mode only saves file-visiting buffers
;; after a period of idle time, directly saving to the file itself without
;; creating backup files.
(setq auto-save-visited-interval 5)   ; Save after 5 seconds if inactivity
(auto-save-visited-mode 1)

Stripspace

;; The stripspace Emacs package provides stripspace-local-mode, a minor mode
;; that automatically removes trailing whitespace and blank lines at the end of
;; the buffer when saving.
(use-package stripspace
  :ensure t
  :commands stripspace-local-mode

  ;; Enable for prog-mode-hook, text-mode-hook, conf-mode-hook
  :hook ((prog-mode . stripspace-local-mode)
         (text-mode . stripspace-local-mode)
         (conf-mode . stripspace-local-mode))

  :custom
  ;; The `stripspace-only-if-initially-clean' option:
  ;; - nil to always delete trailing whitespace.
  ;; - Non-nil to only delete whitespace when the buffer is clean initially.
  ;; (The initial cleanliness check is performed when `stripspace-local-mode'
  ;; is enabled.)
  (stripspace-only-if-initially-clean nil)

  ;; Enabling `stripspace-restore-column' preserves the cursor's column position
  ;; even after stripping spaces. This is useful in scenarios where you add
  ;; extra spaces and then save the file. Although the spaces are removed in the
  ;; saved file, the cursor remains in the same position, ensuring a consistent
  ;; editing experience without affecting cursor placement.
  (stripspace-restore-column t))

Undo/redo

;; The undo-fu package is a lightweight wrapper around Emacs' built-in undo
;; system, providing more convenient undo/redo functionality.
(use-package undo-fu
  :ensure t
  :commands (undo-fu-only-undo
             undo-fu-only-redo
             undo-fu-only-redo-all
             undo-fu-disable-checkpoint)
  :config
  (global-unset-key (kbd "C-z"))
  (global-set-key (kbd "C-z") 'undo-fu-only-undo)
  (global-set-key (kbd "C-S-z") 'undo-fu-only-redo))

;; The undo-fu-session package complements undo-fu by enabling the saving
;; and restoration of undo history across Emacs sessions, even after restarting.
(use-package undo-fu-session
  :ensure t
  :commands undo-fu-session-global-mode
  :hook (after-init . undo-fu-session-global-mode))

;; Visual tree on demand
(use-package vundo
  :bind (("C-x u" . vundo)))

Bufferterminator

(use-package buffer-terminator
  :ensure t
  :custom
  ;; Enable/Disable verbose mode to log buffer cleanup events
  (buffer-terminator-verbose nil)

  ;; Set the inactivity timeout (in seconds) after which buffers are considered
  ;; inactive (default is 30 minutes):
  (buffer-terminator-inactivity-timeout (* 30 60)) ; 30 minutes

  ;; Define how frequently the cleanup process should run (default is every 10
  ;; minutes):
  (buffer-terminator-interval (* 10 60)) ; 10 minutes

  :config
  (buffer-terminator-mode 1))

Easysession

;; The easysession Emacs package is a session manager for Emacs that can persist
;; and restore file editing buffers, indirect buffers/clones, Dired buffers,
;; windows/splits, the built-in tab-bar (including tabs, their buffers, and
;; windows), and Emacs frames. It offers a convenient and effortless way to
;; manage Emacs editing sessions and utilizes built-in Emacs functions to
;; persist and restore frames.
(use-package easysession
  :ensure t
  :commands (easysession-switch-to
             easysession-save
             easysession-save-mode
             easysession-load-including-geometry)

  :custom
  (easysession-mode-line-misc-info t)  ; Display the session in the modeline
  (easysession-save-interval (* 10 60))  ; Save every 10 minutes

  :init
  ;; Key mappings:
  ;; C-c l for switching sessions
  ;; and C-c s for saving the current session
  (global-set-key (kbd "C-c l") 'easysession-switch-to)
  (global-set-key (kbd "C-c s") 'easysession-save)

  ;; The depth 102 and 103 have been added to to `add-hook' to ensure that the
  ;; session is loaded after all other packages. (Using 103/102 is particularly
  ;; useful for those using minimal-emacs.d, where some optimizations restore
  ;; `file-name-handler-alist` at depth 101 during `emacs-startup-hook`.)
  (add-hook 'emacs-startup-hook #'easysession-load-including-geometry 102)
  (add-hook 'emacs-startup-hook #'easysession-save-mode 103))

Aesthetics

Fontaine

(use-package fontaine
  :demand t
  :bind ("C-c f" . fontaine-set-preset)
  :custom
  (fontaine-latest-state-file
   (locate-user-emacs-file "fontaine-latest-state.eld"))
  (fontaine-presets
   '(;; Base preset — inherited by all others
     (t
      :default-family "PragmataPro Mono Liga"
      :default-weight regular
      :default-height 140
      :fixed-pitch-family nil
      :fixed-pitch-weight nil
      :fixed-pitch-height 1.0
      :fixed-pitch-serif-family nil
      :fixed-pitch-serif-weight nil
      :fixed-pitch-serif-height 1.0
      :variable-pitch-family "PragmataPro Liga"
      :variable-pitch-weight nil
      :variable-pitch-height 1.0
      :bold-family nil
      :bold-weight bold
      :italic-family nil
      :italic-slant italic
      :line-spacing nil)

     ;; —— Small (height 120) ——
     (pragmata-small
      :default-height 120)
     (noliga-small
      :inherit pragmata-small
      :default-family "PragmataPro Mono"
      :variable-pitch-family "PragmataPro")
     (plex-small
      :inherit pragmata-small
      :default-family "IBM Plex Mono"
      :variable-pitch-family "IBM Plex Sans")
     (berkeley-small
      :inherit pragmata-small
      :default-family "Berkeley Mono Variable"
      :variable-pitch-family "Berkeley Mono Variable")

     ;; —— Regular (height 140, inherits t) ——
     (pragmata-regular)
     (noliga-regular
      :default-family "PragmataPro Mono"
      :variable-pitch-family "PragmataPro")
     (plex-regular
      :default-family "IBM Plex Mono"
      :variable-pitch-family "IBM Plex Sans")
     (berkeley-regular
      :default-family "Berkeley Mono Variable"
      :variable-pitch-family "Berkeley Mono Variable")

     ;; —— Medium (height 155) ——
     (pragmata-medium
      :default-weight semilight
      :default-height 155
      :bold-weight extrabold)
     (noliga-medium
      :inherit pragmata-medium
      :default-family "PragmataPro Mono"
      :variable-pitch-family "PragmataPro")
     (plex-medium
      :inherit pragmata-medium
      :default-family "IBM Plex Mono"
      :variable-pitch-family "IBM Plex Sans")
     (berkeley-medium
      :inherit pragmata-medium
      :default-family "Berkeley Mono Variable"
      :variable-pitch-family "Berkeley Mono Variable")

     ;; —— Large (height 190) ——
     (pragmata-large
      :inherit pragmata-medium
      :default-height 190)
     (noliga-large
      :inherit noliga-medium
      :default-height 190)
     (plex-large
      :inherit plex-medium
      :default-height 190)
     (berkeley-large
      :inherit berkeley-medium
      :default-height 190)

     ;; —— Presentation (height 220) ——
     (pragmata-presentation
      :default-height 220)
     (noliga-presentation
      :inherit pragmata-presentation
      :default-family "PragmataPro Mono"
      :variable-pitch-family "PragmataPro")
     (plex-presentation
      :inherit pragmata-presentation
      :default-family "IBM Plex Mono"
      :variable-pitch-family "IBM Plex Sans")
     (berkeley-presentation
      :inherit pragmata-presentation
      :default-family "Berkeley Mono Variable"
      :variable-pitch-family "Berkeley Mono Variable"))))

Text display

(global-visual-line-mode 1)

Theme

(mapc #'disable-theme custom-enabled-themes)
(load-theme 'leuven t)
(setq org-fontify-whole-heading-line t)

Modeline

(use-package minions :ensure t
  :hook (after-init . minions-mode)
  :custom
  (minions-mode-line-lighter "--")
  (minions-prominent-modes '(wc-mode)))

Navigating

The minad completion framework (plus Embark)

;; Vertico provides a vertical completion interface, making it easier to
;; navigate and select from completion candidates (e.g., when `M-x` is pressed).
(use-package vertico
  ;; (Note: It is recommended to also enable the savehist package.)
  :ensure t
  :config
  (vertico-mode))

;; Vertico leverages Orderless' flexible matching capabilities, allowing users
;; to input multiple patterns separated by spaces, which Orderless then
;; matches in any order against the candidates.
(use-package orderless
  :ensure t
  :custom
  (completion-styles '(orderless basic))
  (completion-category-defaults nil)
  (completion-category-overrides '((file (styles partial-completion)))))

;; Marginalia allows Embark to offer you preconfigured actions in more contexts.
;; In addition to that, Marginalia also enhances Vertico by adding rich
;; annotations to the completion candidates displayed in Vertico's interface.
(use-package marginalia
  :ensure t
  :commands (marginalia-mode marginalia-cycle)
  :hook (after-init . marginalia-mode))

;; Embark integrates with Consult and Vertico to provide context-sensitive
;; actions and quick access to commands based on the current selection, further
;; improving user efficiency and workflow within Emacs. Together, they create a
;; cohesive and powerful environment for managing completions and interactions.
(use-package embark
  ;; Embark is an Emacs package that acts like a context menu, allowing
  ;; users to perform context-sensitive actions on selected items
  ;; directly from the completion interface.
  :ensure t
  :commands (embark-act
             embark-dwim
             embark-export
             embark-collect
             embark-bindings
             embark-prefix-help-command)
  :bind
  (("C-." . embark-act)         ;; pick some comfortable binding
   ("C-;" . embark-dwim)        ;; good alternative: M-.
   ("C-h B" . embark-bindings)) ;; alternative for `describe-bindings'

  :init
  (setq prefix-help-command #'embark-prefix-help-command)

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

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

;; Consult offers a suite of commands for efficient searching, previewing, and
;; interacting with buffers, file contents, and more, improving various tasks.
(use-package consult
  :ensure t
  :bind (;; C-c bindings in `mode-specific-map'
         ("C-c M-x" . consult-mode-command)
         ("C-c h" . consult-history)
         ("C-c k" . consult-kmacro)
         ("C-c m" . consult-man)
         ("C-c i" . consult-info)
         ([remap Info-search] . consult-info)
         ;; C-x bindings in `ctl-x-map'
         ("C-x M-:" . consult-complex-command)
         ("C-x b" . consult-buffer)
         ("C-x 4 b" . consult-buffer-other-window)
         ("C-x 5 b" . consult-buffer-other-frame)
         ("C-x t b" . consult-buffer-other-tab)
         ("C-x r b" . consult-bookmark)
         ("C-x p b" . consult-project-buffer)
         ;; Custom M-# bindings for fast register access
         ("M-#" . consult-register-load)
         ("M-'" . consult-register-store)
         ("C-M-#" . consult-register)
         ;; Other custom bindings
         ("M-y" . consult-yank-pop)
         ;; M-g bindings in `goto-map'
         ("M-g e" . consult-compile-error)
         ("M-g f" . consult-flymake)
         ("M-g g" . consult-goto-line)
         ("M-g M-g" . consult-goto-line)
         ("M-g o" . consult-outline)
         ("M-g m" . consult-mark)
         ("M-g k" . consult-global-mark)
         ("M-g i" . consult-imenu)
         ("M-g I" . consult-imenu-multi)
         ;; M-s bindings in `search-map'
         ("M-s d" . consult-find)
         ("M-s c" . consult-locate)
         ("M-s g" . consult-grep)
         ("M-s G" . consult-git-grep)
         ("M-s r" . consult-ripgrep)
         ("M-s l" . consult-line)
         ("M-s L" . consult-line-multi)
         ("M-s k" . consult-keep-lines)
         ("M-s u" . consult-focus-lines)
         ;; Isearch integration
         ("M-s e" . consult-isearch-history)
         :map isearch-mode-map
         ("M-e" . consult-isearch-history)
         ("M-s e" . consult-isearch-history)
         ("M-s l" . consult-line)
         ("M-s L" . consult-line-multi)
         ;; Minibuffer history
         :map minibuffer-local-map
         ("M-s" . consult-history)
         ("M-r" . consult-history))

  ;; Enable automatic preview at point in the *Completions* buffer.
  :hook (completion-list-mode . consult-preview-at-point-mode)

  :init
  ;; Optionally configure the register formatting. This improves the register
  (setq register-preview-delay 0.5
        register-preview-function #'consult-register-format)

  ;; Optionally tweak the register preview window.
  (advice-add #'register-preview :override #'consult-register-window)

  ;; Use Consult to select xref locations with preview
  (setq xref-show-xrefs-function #'consult-xref
        xref-show-definitions-function #'consult-xref)

  ;; Aggressive asynchronous that yield instantaneous results. (suitable for
  ;; high-performance systems.) Note: Minad, the author of Consult, does not
  ;; recommend aggressive values.
  ;; Read: https://github.com/minad/consult/discussions/951
  ;;
  ;; However, the author of minimal-emacs.d uses these parameters to achieve
  ;; immediate feedback from Consult.
  ;; (setq consult-async-input-debounce 0.02
  ;;       consult-async-input-throttle 0.05
  ;;       consult-async-refresh-delay 0.02)

  :config
  (consult-customize
   consult-theme :preview-key '(:debounce 0.2 any)
   consult-ripgrep consult-git-grep consult-grep
   consult-bookmark consult-recent-file consult-xref
   consult-source-bookmark consult-source-file-register
   consult-source-recent-file consult-source-project-recent-file
   ;; :preview-key "M-."
   :preview-key '(:debounce 0.4 any))
  (setq consult-narrow-key "<"))

Coding

Code completions

Corfu enhances in-buffer completion by displaying a compact popup with current candidates, positioned either below or above the point. Candidates can be selected by navigating up or down.

Cape, or Completion At Point Extensions, extends the capabilities of in-buffer completion. It integrates with Corfu or the default completion UI, by providing additional backends through completion-at-point-functions.

;; Corfu enhances in-buffer completion by displaying a compact popup with
;; current candidates, positioned either below or above the point. Candidates
;; can be selected by navigating up or down.
(use-package corfu
  :ensure t
  :commands (corfu-mode global-corfu-mode)

  :hook ((prog-mode . corfu-mode)
         (shell-mode . corfu-mode)
         (eshell-mode . corfu-mode))

  :custom
  ;; Hide commands in M-x which do not apply to the current mode.
  (read-extended-command-predicate #'command-completion-default-include-p)
  ;; Disable Ispell completion function. As an alternative try `cape-dict'.
  (text-mode-ispell-word-completion nil)
  (tab-always-indent 'complete)

  ;; Enable Corfu
  :config
  (global-corfu-mode))

;; Cape, or Completion At Point Extensions, extends the capabilities of
;; in-buffer completion. It integrates with Corfu or the default completion UI,
;; by providing additional backends through completion-at-point-functions.
(use-package cape
  :ensure t
  :commands (cape-dabbrev cape-file cape-elisp-block cape-emoji cape-dict)
  :bind ("C-c p" . cape-prefix-map)
  :init
  ;; Add to the global default value of `completion-at-point-functions' which is
  ;; used by `completion-at-point'.
  (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-emoji)
  (add-hook 'completion-at-point-functions #'cape-dict))

Lisps

(use-package paredit
  :ensure t
  :hook ((clojure-mode clojure-ts-mode emacs-lisp-mode) . paredit-mode)
  :bind
  ("M-w" . paredit-copy-as-kill)
  ("{" . paredit-open-curly)
  ("}" . paredit-close-curly)
  :config
  ;; (defun my/paredit-meow-bindings ()
  ;;   (when (bound-and-true-p meow-normal-mode)
  ;;     (meow-normal-define-key
  ;;      '(")" . paredit-forward-slurp-sexp)
  ;;      '("(" . paredit-forward-barf-sexp))))
  ;; (add-hook 'paredit-mode-hook #'my/paredit-meow-bindings)
  )

Writing

Notes and tasks

Howm

(use-package howm
:ensure t
:init
(require 'howm-org)

(setq howm-directory
      (if (eq system-type 'windows-nt)
          "C:/Users/xcvh/Documents/howm"
        "~/Documents/Howm/"))

(setq howm-home-directory howm-directory)

(setq howm-keyword-file (expand-file-name ".howm-keys" howm-home-directory)
      howm-history-file (expand-file-name ".howm-history" howm-home-directory))

(setq howm-follow-theme t)
;; Use ripgrep
;; (setq howm-view-use-grep t)
;; (setq howm-view-grep-command "rg")
;; (setq howm-view-grep-option "-nH --no-heading --color never")
;; (setq howm-view-grep-extended-option nil)
;; (setq howm-view-grep-fixed-option "-F")
;; (setq howm-view-grep-expr-option nil)
;; (setq howm-view-grep-file-stdin-option nil)
)

Org

Org and org-tempo allow for easy adding and editing of sourceblocks. Here they are tweaked to easyly allow to tangle to the correct minimal-emacs.d init files.

The following shortcuts are set up:

Binding Action
<pre TAB Add a sourceblock that tangles to pre-init.el
<post TAB Add a sourceblock that tangles to post-init.el
<epre TAB Add a sourceblock that tangles to pre-early-init.el
<epost TAB Add a sourceblock that tangles to post-early-init.el
(use-package org
  :hook (org-mode . xcvh/org-load-tempo)
  :config
  (defun xcvh/org-load-tempo ()
    (require 'org-tempo))
  (add-to-list 'org-structure-template-alist '("pre" . "src emacs-lisp :tangle ~/.emacs.d/pre-init.el"))
  (add-to-list 'org-structure-template-alist '("post" . "src emacs-lisp :tangle ~/.emacs.d/post-init.el")
  (add-to-list 'org-structure-template-alist '("epre" . "src emacs-lisp :tangle ~/.emacs.d/pre-early-init.el")
  (add-to-list 'org-structure-template-alist '("epost" . "src emacs-lisp :tangle ~/.emacs.d/post-early-init.el")
     ))))

(defun xcvh/org-tempo-auto-edit-special (&rest _)
  "Auto-jump to `org-edit-special' after inserting elisp src block."
  (when (and (org-in-src-block-p)
             (string= (org-element-property :language (org-element-at-point)) "emacs-lisp"))
    (org-edit-special)))

(advice-add 'org-tempo-complete-tag :after #'xcvh/org-tempo-auto-edit-special)

(defun xcvh/org-src-block-hooks ()
  (when (eq major-mode 'emacs-lisp-mode)
    ;;(meow-insert)
    (paredit-mode 1)))

(add-hook 'org-src-mode-hook #'xcvh/org-src-block-hooks)

To auto-tangle org files on save we can use org-auto-tangle.

(use-package org-auto-tangle
  ; :load-path "site-lisp/org-auto-tangle/"    ;; this line is necessary only if you cloned the repo in your site-lisp directory
  :defer t
  :hook (org-mode . org-auto-tangle-mode))

In org-src edit buffers, C-c ' confirms changes—but C-c C-c is more intuitive given its ubiquitous "do the thing" role in Magit, Org capture, and elsewhere. This rebinds C-c C-c to exit the edit buffer. Use C-u C-c C-c to pass through to the major mode's original binding when needed, which is not often for me.

(defun xcvh/org-src-exit-or-passthrough (arg)
  "Exit org-src edit, or with prefix ARG, call major mode's C-c C-c."
  (interactive "P")
  (if arg
      (when-let ((cmd (lookup-key (current-local-map) (kbd "C-c C-c"))))
        (when (commandp cmd)
          (call-interactively cmd)))
    (org-edit-src-exit)))

(with-eval-after-load 'org-src
  (define-key org-src-mode-map (kbd "C-c C-c") #'xcvh/org-src-exit-or-passthrough))

(defun xcvh/org-src-fix-header-line ()
  (setq header-line-format "Edit, then exit with C-c C-c or abort with C-c C-k"))

(add-hook 'org-src-mode-hook #'xcvh/org-src-fix-header-line)

Prose and Documentation

Markdown

;; The markdown-mode package provides a major mode for Emacs for syntax
;; highlighting, editing commands, and preview support for Markdown documents.
;; It supports core Markdown syntax as well as extensions like GitHub Flavored
;; Markdown (GFM).
(use-package markdown-mode
  :commands (gfm-mode
             gfm-view-mode
             markdown-mode
             markdown-view-mode)
  :mode (("\\.markdown\\'" . markdown-mode)
         ("\\.md\\'" . markdown-mode)
         ("README\\.md\\'" . gfm-mode))
  :bind
  (:map markdown-mode-map
        ("C-c C-e" . markdown-do)))

Version control

(use-package magit
  :ensure t
  :bind (("C-x g" . magit-status)
         ("C-x M-g" . magit-dispatch))
  :config
  (setq magit-display-buffer-function #'magit-display-buffer-same-window-except-diff-v1))

Reading

Mail

Notmuch

(with-mac
 (use-package notmuch
   :load-path "/opt/homebrew/share/emacs/site-lisp/notmuch/"
   :ensure nil
   :demand t
   :bind ((:map notmuch-search-mode-map
                ("F" . xcvh/notmuch-filter-by-from)
                  ("f" . xcvh/notmuch-search-by-from)
                ("A" . xcvh/notmuch-archive-all-visible)
          ("D" . xcvh/notmuch-delete-all-visible))
          (:map notmuch-show-mode-map
                ("A" . notmuch-show-archive-thread-then-next)))
   :config
   (setq notmuch-archive-tags '("-inbox")
         mm-text-html-renderer 'shr
         shr-width 80
         shr-use-colors nil
         notmuch-multipart/alternative-discouraged '("text/plain" "text/html")
         notmuch-search-oldest-first nil)

   (defun xcvh/notmuch-filter-by-from ()
     "Filter this view by messages from the sender of the current thread."
     (interactive)
     (let* ((authors (notmuch-search-find-authors))
            (from (car (split-string authors "[,<]" t " "))))
       (notmuch-search-filter (format "from:%s" from))))

   (defun xcvh/notmuch-search-by-from ()
     "Search all messages from the sender of the current thread."
     (interactive)
     (let* ((authors (notmuch-search-find-authors))
            (from (car (split-string authors "[,<]" t " "))))
       (notmuch-search (format "from:%s" from))))

   (defun xcvh/notmuch-archive-all-visible ()
     "Archive all visible messages."
     (interactive)
     (notmuch-search-tag-all '("-inbox")))

   (defun xcvh/notmuch-delete-all-visible ()
     "Delete all visible messages after confirmation."
     (interactive)
     (when (yes-or-no-p "Delete all visible messages? ")
            (notmuch-search-tag-all '("+deleted"))))

   (defun xcvh/notmuch-search-from-sender (email)
          "Search notmuch for emails from EMAIL."
          (notmuch-search (concat "from:" email)))))

Frontends

Here live some packages that act as frontends to other applications

Bookmarks with ebuku

(use-package ebuku
  :bind
  (("C-c b b" . ebuku)))

Since ebuku does not (at least I think) come with a way to use completing read to quickly filter and open a bookmark, this tiny helper function will do just that. Room for improvement.

(defun xcvh/open-buku-bookmark ()
  "Call buku with to get all bookmarks as JSON, then feed them to completing-read and call buku again to open the index of the selected bookmark."
  (interactive)
  (let*
    ((json-data (json-parse-string (shell-command-to-string "buku --nostdin -p -j") :object-type 'alist))
     (candidates
      (mapcar (lambda (item)
                (cons (format "%s <%s>"
                              (alist-get 'title item)
                              (alist-get 'tags item)) item))
              json-data))
     (selected (completing-read "Search buku: " candidates nil t))
     (id (alist-get 'index (cdr (assoc selected candidates)))))
  (start-process "buku-open" nil "buku" "-o" (number-to-string id))))

YouTube with yeetube

(use-package yeetube
  :ensure t
  :init (define-prefix-command 'my/yeetube-map)
  :custom (yeetube-download-directory "~/Media/Videos")
  :bind (("C-c y" . 'my/yeetube-map)
          :map my/yeetube-map
                  ("s" . 'yeetube-search)
                  ("b" . 'yeetube-play-saved-video)
                  ("d" . 'yeetube-download-videos)
                  ("p" . 'yeetube-mpv-toggle-pause)
                  ("v" . 'yeetube-mpv-toggle-video)
                  ("V" . 'yeetube-mpv-toggle-no-video-flag)
                  ("k" . 'yeetube-remove-saved-video)))

mpv with ready-player

(use-package ready-player
  :ensure t
  :custom
  (ready-player-my-media-collection-location "~/Media/Music")
  :config
  (ready-player-mode +1))

Proposals

(use-package whitespace
  :config
  (setq whitespace-style (remove 'lines (remove 'lines-tail whitespace-style))))

(use-package wc-mode
  :ensure t
  :hook (atomic-chrome-edit-mode . wc-mode)
  :config
  (setq wc-modeline-format "C[%tc]"))

(use-package atomic-chrome
  :ensure t
  :demand t                             ; force immediate loading
  :hook
  (atomic-chrome-edit-mode . whitespace-mode)
  (atomic-chrome-edit-mode . highlight-placeholders-mode)
  :config
  (add-hook 'atomic-chrome-edit-mode-hook #'xcvh/clean-whitespace)
  (add-hook 'atomic-chrome-edit-mode-hook #'delete-other-windows)
  (atomic-chrome-start-server))

(defun xcvh/replace-bullets-with-dashes ()
  "Query-replace common list bullets with em-dashes, preserving indentation."
  (interactive)
  (let ((start (if (use-region-p) (region-beginning) (point-min)))
        (end (if (use-region-p) (region-end) (point-max))))
    (save-excursion
      (goto-char start)
      (query-replace-regexp
       "^\\([[:space:]]*\\)[*+•◦▪▸-–—][[:space:]]+"
       "\\1– "
       nil start end))))

(defun xcvh/clean-whitespace ()
  "Remove unnecessary whitespace while preserving indentation."
  (interactive)
  (save-excursion
    ;; Remove three-or-four-digit/three-or-four-digit pattern from first line
    (goto-char (point-min))
    (when (re-search-forward "[0-9]\\{3,4\\}/[0-9]\\{3,4\\}" (line-end-position) t)
      (replace-match "")
      ;; If line is now empty or whitespace-only, delete it
      (beginning-of-line)
      (when (looking-at "^[[:space:]]*$")
        (delete-region (point) (progn (forward-line 1) (point)))))

    ;; First pass: remove trailing whitespace and clean whitespace-only lines
    (goto-char (point-min))
    (while (not (eobp))
      (beginning-of-line)
      ;; If line has only whitespace, delete it all
      (when (looking-at "^[[:space:]]+$")
        (delete-region (point) (line-end-position)))
      ;; Remove trailing whitespace
      (end-of-line)
      (skip-chars-backward " \t")
      (delete-region (point) (line-end-position))
      (forward-line 1))

    ;; Convert tabs to spaces
    (untabify (point-min) (point-max))

    ;; Collapse multiple consecutive blank lines to one
    (goto-char (point-min))
    (while (re-search-forward "\\(\n\\)\n\\{2,\\}" nil t)
      (replace-match "\\1\n")))
  (goto-char 0))

(global-set-key (kbd "C-c w") 'xcvh/clean-whitespace)

(defface my-placeholder-face
  '((t :background "orange" :foreground "white" :weight bold))
  "Face for placeholder words.")


(defvar highlight-placeholders-list
  '("xxx" "tbd" "bla" "level-up" "Level-up" "Text box")
  "List of placeholder strings to highlight (case-sensitive).")

(defun highlight-placeholders--build-regexp ()
  "Build regexp from `highlight-placeholders-list'."
  (regexp-opt highlight-placeholders-list))

(define-minor-mode highlight-placeholders-mode
  "Highlight placeholder words defined in `highlight-placeholders-list'."
  :lighter " PH"
  (let ((regexp (highlight-placeholders--build-regexp)))
    (if highlight-placeholders-mode
        (progn
          (setq-local font-lock-keywords-case-fold-search nil)
          (font-lock-add-keywords nil `((,regexp 0 'my-placeholder-face prepend)) t))
      (font-lock-remove-keywords nil `((,regexp 0 'my-placeholder-face prepend))))
    (font-lock-flush)))