DrolleryMedieval drollery of a knight on a horse
flowery border with man falling
flowery border with man falling

French Vanilla

This is French Vanilla, my literate emacs config. The goal of this config is to rely upon vanilla emacs as much as possible. I hope to keep it light on packaging and alterations and instead lean upon emacs’ built-ins and defaults unless there is a compelling reason not to. This also means that when selecting what packages to use, the packages should embrace emacs concepts and in turn lean upon the emacs way of doing things.

Need an LSP? Use Emacs native Eglot.

Need tree sitter? Use Emacs native treesit.

Need a project tree? Dired exists, use it.

Additionally, it needs to be light. Org’s symbol faces aren’t horrid, we don’t need to prettify them or anything like that, just use them as they are.

Here are some emac configs I’ve found useful:

Boot Scootin Boogie

This isn’t quite bootstrappin’, but it’s near about the same idea. This gets my Emacs config to a point where I can then start using the things that make using Emacs easier. Most this is early-init.el stuff to speed up load times and fix some things needed for the remainder of the config.


;;; early-init.el --- Early Init File -*- lexical-binding: t; no-byte-compile: t -*-
;; NOTE: this file is generated from ~/org/emacs.org

Tame the GC.

(setq gc-cons-threshold 10000000)

Make things a little quieter.

(setq byte-compile-warnings '(not obsolete))
(setq warning-suppress-log-types '((comp) (bytecomp)))
(setq native-comp-async-report-warnings-errors 'silent)
(setq inhibit-startup-echo-area-message (user-login-name))

Make some changes to how the UI will load. We’ll do more later, this is just to prevent any early ugliness and flashing if emacs hangs sometime between early-init and when that stuff gets loaded in init.

(setq frame-inhibit-implied-resize t
      frame-resize-pixelwise t
      default-frame-alist '((ns-transparent-titlebar . t)
                            (full-screen . maximized)
                            (vertical-scroll-bar . nil)
                            (horizontal-scroll-bar . nil))
      visual-bell t
      display-time-default-load-average nil)
(scroll-bar-mode -1)
(tool-bar-mode -1)
(tooltip-mode -1)
(menu-bar-mode -1)
(set-fringe-mode 10)

Disable package.el

We disable package.el because we’re using elpaca.el. This is disabled early so it’s not accidentally used, we want elpaca to do all the work of figuring out our packaging needs.

(setq package-enable-at-startup nil
      package-quickstart nil)


The rest of this org file all goes into init.el unless specified otherwise.

;;; init.el --- Personal configuration file -*- lexical-binding: t; no-byte-compile: t; -*-
;; NOTE: this file is generated from ~/org/emacs.org

Initial Theme   tangleno

And set a background color early so on bootstrap there’s no jarring color changes after the theme is loaded in. We’ll set the background color to either black or white, depnding on what the system appearnce is.

  (defun fv--paint-it (fg bg)
    "A small function to entirely 'paint' emacs to `color'"
    (set-background-color bg)
    (set-foreground-color fg)
     '(mode-line-highlight ((t nil)))
     `(mode-line ((t (:foreground ,fg :background ,bg))))
     `(mode-line-inactive ((t (:background ,bg :foreground ,fg))))
     `(fringe ((t :background ,bg :foreground ,fg)))))

(if (boundp 'ns-system-appearance)
  (pcase ns-system-appearance
        ('light (fv--paint-it "black" "white"))
        ('dark (fv--paint-it "white" "black")))
  (fv--paint-it "black" "white"))


Define a hook that will report back on load time. This only loads on debug

(when debug-on-error
  (add-hook 'elpaca-after-init-hook
            (lambda ()
              (message "Emacs loaded in %s with %d garbage collections."
                       (format "%.2f seconds"
				(time-subtract (current-time) before-init-time)))

Enable the profiler to view the report after init

(when debug-on-error
    (profiler-start 'cpu+mem)
    (add-hook 'elpaca-after-init-hook (lambda () (profiler-stop) (profiler-report))))

Initial Buffer Selection

Open emacs in the scratch buffer.

(setq initial-buffer-choice t) ;;*scratch*
(setq inhibit-startup-message t)

Package Management With Elpaca


An elisp package manager

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

We need this to load the ssh keys for git

 (elpaca keychain-environment

Enable elpaca to act like use-package:

(elpaca elpaca-use-package
  (setq elpaca-use-package-by-default t))

Setup a macro to use-feature.

(defmacro use-feature (name &rest args)
  "Like `use-package' but accounting for asynchronous installation.
  NAME and ARGS are in `use-package'."
  (declare (indent defun))
  `(use-package ,name
     :elpaca nil

Setup a macro to use-builtin.

(defmacro use-builtin (name &rest args)
  "Like `use-package' but accounting for asynchronous installation.
  NAME and ARGS are in `use-package'."
   (cl-pushnew (quote ,name) elpaca-ignored-dependencies)
   (use-package ,name
     :elpaca nil

On debug we want to make elpaca verbose and we want to use benchmark-init.

(when debug-on-error
  (setq use-package-verbose t
        use-package-expand-minimally nil
        use-package-compute-statistics t
        use-package-verbose nil
      use-package-expand-minimally t)
  (use-package benchmark-init
    :ensure t
    (add-hook 'after-init-hook #'benchmark-init/deactivate))

Emacs Files

This section is concerned with taming and managing emacs’ files used for various things, like undo history, buffer backups, project lists. The goal of this section is to ensure that emacs does not use the emacs directory for anything other than the configuration, packages, and elc. Evertying else belongs in the .local/share/emacs directory where it won’t get nuked when I rm -rf and recompile my emacs config. To get this we’ll be using the no-littering package to help manage all the various emacs files.

First we set the directories this package uses. I’m setting this outside of the package because I’ll need these vars for a few other packages and this way they’re just “there”.

(setq no-littering-etc-directory (expand-file-name "~/.local/share/emacs/etc/")
      no-littering-var-directory (expand-file-name "~/.local/share/emacs/var/"))

Now to configure the package

(use-package no-littering
  (with-eval-after-load 'recentf
    (add-to-list 'recentf-exclude no-littering-etc-directory)
    (add-to-list 'recentf-exclude no-littering-var-directory))
  (setq custom-file (no-littering-expand-etc-file-name "custom.el"))


These are mostly setting up non-package things, such as the theme, handling files, and other goodies like that.

Personal Info

(setq user-full-name "Ian S. Pringle"
      user-mail-address "[email protected]"
      auth-sources '("~/.authinfo"))


This automatically breaks lines that are too long.

 (add-hook 'text-mode-hook 'turn-on-auto-fill)
 (setq-default fill-column 80)
 (setq colon-double-space nil
adaptive-fill-mode t)

Highlight the current line

(let ((hl-line-hooks '(text-mode-hook prog-mode-hook)))
  (mapc (lambda (hook) (add-hook hook 'hl-line-mode)) hl-line-hooks))

We need some modern, sane indentation settings.

(set-default 'indent-tabs-mode nil)

File Management

Re-read files on disk when there’s a change

(setq auto-revert-interval 1)
(setq auto-revert-check-vc-info t)
;; (setq global-auto-revert-non-file-buffers t)

Emacs can remember the last point of a previously visited file and restore that point when the file is re-opened, this enables that.

(save-place-mode 1)

In addition to remembering where in a file you were last, emacs should remember what files were recently visited, this makes sure emacs remembers recent files

(recentf-mode 1)

This isn’t really file related but it sorta is.



This section is for things pertaining to the writing of things.

When I select a region and then type I want the region to be killed.


This is currently bound to C-S-Backspace but that’s a rather awkward key-combo so I’m making it a bit closer to what vim uses.

(global-set-key (kbd "C-S-d") 'kill-whole-line)

Auto insert pairs and show pairs when at point.

(electric-pair-mode 1)
(setq electric-pair-pairs '(
                            (?\" . ?\")
                            (?\{ . ?\})))
(show-paren-mode 1)
(setq show-paren-style 'expression)



Grab our theme from melpa

(use-package moe-theme
  :ensure t
  (setq moe-theme-modeline-color 'blue
        moe-theme-highlight-buffer-id t))

I like moe-theme a lot, it matches my wacky aesthetic, but I also love brutally minimal themes and punpun is the most metal of all minimalist emacs themes that I have found thus far 🤟

(use-package punpun-themes
  :demand t
  (load-theme 'punpun-light t))

Set our default light and dark theme, then create a function to set the current theme based on system settings.

 (defvar fv--default-theme-light 'moe-light
   "The default light theme.")

 (defvar fv--default-theme-dark 'moe-dark
   "The default dark theme.")

 (defun fv--moe-light ()
   (custom-set-faces '(fringe ((t :background "#9e9e9e" :foreground "#d7d7af")))))

 (defun fv--moe-dark ()
   (custom-set-faces '(fringe ((t :background "#9e9e9e" :foreground "#87875f")))))
 (defun fv--load-theme (theme-name)
   "Loads the specified theme"
   (mapc #'disable-theme custom-enabled-themes)
   (pcase theme-name
     ('moe-dark (fv--moe-dark))
     ('moe-light (fv--moe-light))
     (_ (load-theme theme-name t))))

 (defun fv--set-theme (appearance)
   "Calls `load-theme' setting a theme based on system appearance."
   (pcase appearance
     ('light (fv--load-theme fv--default-theme-light))
     ('dark (fv--load-theme fv--default-theme-dark))))

(if (boundp 'ns-system-appearance)
 (add-hook 'ns-system-appearance-change-functions #'fv--set-theme)
 (add-hook 'elpaca-after-init-hook #'(fv--load-theme fv--default-theme-light)))

We also need a hook to know when the theme has changed, this can then be used to reapply any changes (like to the mode-line) that the theme might have over written. This was largely taken from the emacs manual and then I just renamed the hook and applied it to both the enable-theme and load-theme functions.

(defvar after-theme-applied-hook nil
  "Normal hook run after loading a theme.")

(defun run-after-theme-applied-hook (&rest args)
	"Run `after-theme-applied-hook'"
	(run-hooks 'after-theme-applied-hook))

(advice-add 'enable-theme :after #'run-after-theme-applied-hook)
(advice-add 'load-theme :after #'run-after-theme-applied-hook)


Some of emacs’s prompts in GUI mode are new OS windows, this disables that annoying feature.

(setq use-dialog-box nil)'

And of course line numbers!

(global-display-line-numbers-mode t)
(setq  line-number-mode t
       column-number-mode t)
(dolist (mode '(org-mode-hook
  (add-hook mode (lambda () (display-line-numbers-mode 0))))

Some misc. stuff…

(setq x-underline-at-descent-line nil
      switch-to-buffer-obey-display-actions t
      mouse-wheel-tilt-scroll t
      mouse-wheel-flip-direction t)

(setq-default show-trailing-whitespace nil)
(setq-default indicate-buffer-boundaries 'left)

(blink-cursor-mode -1)

This is the face that will be used in most places.

(set-face-attribute 'default nil :font "scientifica" :height 240)

kill vc   tangleno

I only use git and I only need magit for that. I have found that multiple lock-ups have been due to vc-* functions and processes, so just get rid of it.

(with-eval-after-load 'vc
  (remove-hook 'find-file-hook 'vc-find-file-hook)
  (remove-hook 'find-file-hook 'vc-refresh-state))
(setq vc-hangled-backends nil)

We still need those functions though because project.el relies on them, so we need to provide those functions.

(defun fv--find-dot-git (dir)
  "Find the .git project root"
  (let ((dgit (and (setq dir (locate-dominating-file dir ".git"))
                   (expand-file-name dir))))
    (and dgit
         (cons 'transient (file-name-directory dgit)))))
(add-hook 'project-find-functions 'fv--find-dot-git)


This is mostly completion/tab-key stuff but there’s a sprinkle of minibuffer config in here too since they sorta all go together…

(setq enable-recursive-minibuffers t
      completion-cycle-threshold 1
      completions-detailed t
      tab-always-indent t
      completion-auto-help 'always
      completions-max-height 20
      completions-detailed t
      completions-format 'one-column
      completions-group t
      completion-auto-select 'second-tab
      read-extended-command-predicate #'command-completion-default-include-p)
(keymap-set minibuffer-mode-map "TAB" 'minibuffer-complete)

Tab Bar

Do not show the tab bar until a new tab is created

(setq tab-bar-show 0)

If there is a tab bar move the modeline’s datetime to the tab bar instead.

(add-to-list 'tab-bar-format 'tab-bar-format-align-right 'append)
(add-to-list 'tab-bar-format 'tab-bar-format-global 'append)
(setq display-time-format "%a %F %T")
(setq display-time-interval 1)


This is the unsorted / undocumented

(windmove-default-keybindings 'control)
(setq sentence-end-double-space nil)
(when (display-graphic-p)

Local Lisp

This is for lisp functions and local packages.

Tangle Funcs

These are some functions I like to have to making tangling easier.

This first one is a function that can be passed to :tangle and will determine whether a block is tangled by tags on the headline the block is.

(defun fv--tangle-maybe (&optional dir)
               (if (member-ignore-case "tangleno" (org-get-tags))
                 (or dir ("yes"))))

Hide Modeline

This is a small minor mode taken from bzg that will hide the modeline.

;; See http://bzg.fr/emacs-hide-mode-line.html
(defvar-local hidden-mode-line-mode nil)
(defvar-local hide-mode-line nil)

(define-minor-mode hidden-mode-line-mode
  "Minor mode to hide the mode-line in the current buffer."
  :init-value nil
  :global nil
  :variable hidden-mode-line-mode
  :group 'editing-basics
  (if hidden-mode-line-mode
      (setq hide-mode-line mode-line-format
            mode-line-format nil)
    (setq mode-line-format hide-mode-line
          hide-mode-line nil))
  ;; Apparently force-mode-line-update is not always enough to
  ;; redisplay the mode-line
  (when (and (called-interactively-p 'interactive)
     0 nil 'message
     (concat "Hidden Mode Line Mode enabled.  "
             "Use M-x hidden-mode-line-mode to make the mode-line appear."))))

;; Activate hidden-mode-line-mode
;; (hidden-mode-line-mode 1)

Custom Bindings Mode

This is my mode for all my custom keybindings. I have this so that custom keybindings can be layered after all other modes are called which then ensures my bindings can never be overwritten.

This package doesn’t exist. It’s a pipe dream.


This is another non-existent package/pipe-dream.

Hasty is my theoretical HTML AST in elisp. The idea here is that if you can write an AST for HTML as elisp, then it’ll be simpler to write HTML and convert to and from HTML. For example, with an AST we could rewrite emmet-mode as a parser that converts the emmet strings to the AST and then from there we can output that emmet string to HTML, Hiccup, Org, etc..


Theoretical packages are a common denominator for me. h.el is a small library to express HTML in elisp as elisp.

(defun h (tag)
  (format "<%s></%s>" tag tag))


For the time, I’m listing each package as a header. I might consider changing this in the future. In my neovim config I group packages together, however the nvim package management tool makes this very easy to do since you can list packages as dependencies of one another. I might try “leaf” which is an alternative package management style to use-package, but that’d be a lot of work for what might be little gain. So for the time being… a package per heading (with some exceptions).

I’m tagging some packages, but not all of them. I’ve tagged in the past with something akin to the “category” of a package, but that’s hard sometimes. Instead I’m now tagging just packages that I think of as “core” packages or packages that are really very useless beyond like eye-candy. This is for a few reason, when it comes to debugging, I know I can disable all the “eye-candy” right away and if the problem is resolved well then something is getting deleted. With “core” that’s the last set of packages I’ll disable and if a core package ends up being the problem child then I need to solve that problem or find a different package to replace it. I’m also tagging built-ins because those are sort of special too.

Before kicking things off, we need to provide a place for the rare package that must be installed sooner than others. Those packages will be set to :tangle no and then will need to be put into the below src block with noweb. Furthermore, because these packages cannot be easily disabled with a tag on the headline, they’ll be tagged with :noweb: to signify that they need additional steps if they need to be disabled.



Quickly switch windows in emacs

Ace Window

(use-package ace-window
  (global-set-key (kbd "M-o") 'ace-window)
  (setq aw-scope 'frame))

anzu   candy


(use-package anzu
  :defer 5
  (global-anzu-mode +1)
  (global-set-key [remap query-replace] 'anzu-query-replace)
  (global-set-key [remap query-replace-regexp] 'anzu-query-replace-rexexp)
   '(anzu-mode-lighter "")
   '(anzu-deactivate-region t)
   '(anzu-replace-to-string-separator " => ")))


A better M-x I guess.

(use-package amx
  ("M-x" 'amx)
  ("C-x C-m" 'amx)
  ("C-x C-M" 'amx-major-mode-commands))


🌷 Run code formatter on buffer contents without moving point, using RCS patches and dynamic programming.


(use-package apheleia
  (apheleia-mode-lighter nil)
  (apheleia-global-mode +1))


Jump to things in Emacs tree-style


(use-package avy
  (global-set-key (kbd "M-g f") 'avy-goto-line)
  (global-set-key (kbd "C-c l") 'avy-copy-line)
  (global-set-key (kbd "C-c m") 'avy-move-line)
  (global-set-key (kbd "M-g w") 'avy-goto-word-1)
  (global-set-key (kbd "M-g W") 'avy-goto-word-2)
  (global-set-key [remap goto-char] 'avy-goto-char-timer)
  (global-set-key (kbd "M-g C") 'avy-goto-char)
  (setq avy-timeout 1.0))

beacon   candy

(use-package beacon
  :ensure t
  (beacon-color "#fd7ae1")
  (beacon-mode 1))



(use-package bug-hunter)


Save and restore frames and windows with their buffers in Emacs


(use-package burly)

cape   core

Cape provides Completion At Point Extensions which can be used in combination with Corfu, Company or the default completion UI.


(use-package cape
  :bind (("C-c p p" . completion-at-point) ;; capf
         ("C-c p t" . complete-tag)        ;; etags
         ("C-c p d" . cape-dabbrev)        ;; or dabbrev-completion
         ("C-c p h" . cape-history)
         ("C-c p f" . cape-file)
         ("C-c p k" . cape-keyword)
         ("C-c p s" . cape-symbol)
         ("C-c p a" . cape-abbrev)
         ("C-c p l" . cape-line)
         ("C-c p w" . cape-dict)
         ("C-c p \\" . cape-tex)
         ("C-c p _" . cape-tex)
         ("C-c p ^" . cape-tex)
         ("C-c p &" . cape-sgml)
         ("C-c p r" . cape-rfc1345))
  (add-to-list 'completion-at-point-functions #'cape-dabbrev)
  (add-to-list 'completion-at-point-functions #'cape-file)
  (add-to-list 'completion-at-point-functions #'cape-elisp-block)
  ;;(add-to-list 'completion-at-point-functions #'cape-history)
  ;;(add-to-list 'completion-at-point-functions #'cape-keyword)
  ;;(add-to-list 'completion-at-point-functions #'cape-tex)
  ;;(add-to-list 'completion-at-point-functions #'cape-sgml)
  ;;(add-to-list 'completion-at-point-functions #'cape-rfc1345)
  ;;(add-to-list 'completion-at-point-functions #'cape-abbrev)
  ;;(add-to-list 'completion-at-point-functions #'cape-dict)
  ;;(add-to-list 'completion-at-point-functions #'cape-symbol)
  ;;(add-to-list 'completion-at-point-functions #'cape-line)


Emacs version of vim’s ci and co commands.

Change Inner

(use-package change-inner
  (global-set-key (kbd "C-c i") 'change-inner)
  (global-set-key (kbd "C-c o") 'change-outter))

cider   lang clojure

The Clojure Interactive Development Environment that Rocks for Emacs


(use-package cider)

clojure-mode   lang clojure

Emacs support for the Clojure(Script) programming language


(use-package clojure-mode)


Combobulate is a package that adds structured editing and movement to a wide range of programming languages.


(use-package combobulate
  :elpaca (:host github :repo "mickeynp/combobulate")
  (setq combobulate-key-prefix "C-c o")
  :hook ((python-ts-mode . combobulate-mode)
	 (js-ts-mode . combobulate-mode)
	 (jsx-ts-mode . combobulate-mode)
	 (typescript-ts-mode . combobulate-mode)
	 (tsx-ts-mode . combobulate-mode)
	 (css-ts-mode . combobulate-mode)
         (yaml-ts-mode . combobulate-mode)
	 ;; (html-ts-mode . combobulate-mode)
         (json-ts-mode . combobulate-mode))

consult   core

Consult provides search and navigation commands based on the Emacs completion function completing-read.


(use-package consult
  :commands (consult-ripgrep)
  (setq xref-show-xrefs-function #'consult-xref
        xref-show-definitions-function #'consult-xref)  
  (setq consult-preview-key "C-l")
  (setq consult-narrow-key "<"
        consult-widen-key ">")
  (("C-c M-x" . consult-mode-command))
  (global-set-key [remap imenu] 'consult-imenu)
  (global-set-key [remap switch-to-buffer] 'consult-buffer)
  (global-set-key [remap goto-line] 'consult-goto-line)
  (consult-customize consult-theme
                   :debounce 0.5 "<up>" "<down>"
                   :debounce 1 any)))


Insert paths into the minibuffer prompt in Emacs

Consult Dir

(use-package consult-dir
  :ensure t
  :bind (("C-x C-d" . consult-dir)
         :map minibuffer-local-completion-map
         ("C-x C-d" . consult-dir)
         ("C-x C-j" . consult-dir-jump-file)))

corfu   core

Corfu enhances in-buffer completion with a small completion popup.


(use-package corfu
  :elpaca (corfu :files (:defaults "extensions/*")
		 :inclues (corfu-history
  :defer 5
  (corfu-auto nil)
  (corfu-auto-delay 0.1)
  (corfu-cycle t)
  (corfu-excluded-moes '(erc-mode
  (corfu-separator ?\s)
  (corfu-preview-current 'insert)
  (corfu-on-exact-match nil)
  (corfu-quit-on-boundary nil)
  (corfu-quit-no-match 'separator t)
  (defun corfu-enable-always-in-minibuffer ()
    "Enable Corfu in the minibuffer if Vertico/Mct are not active."
    (unless (or (bound-and-true-p mct--active)
		(bound-and-true-p vertico--input)
		(eq (current-local-map) read-passwd-map))
      ;; (setq-local corfu-auto nil) ;; Enable/disable auto completion
      (setq-local corfu-echo-delay nil ;; Disable automatic echo and popup
                  corfu-popupinfo-delay nil)
      (corfu-mode 1)))
  (add-hook 'minibuffer-setup-hook #'corfu-enable-always-in-minibuffer 1)
  (add-to-list 'savehist-additional-variables 'corfu-history)
  (add-hook 'corfu-mode #'corfu-popupinfo-mode)
  (global-corfu-mode 1))

corfu-candidate-overlay   candy

Show first Corfu’s completion candidate in an overlay while typing.

Corfu Candidate Overlay

(use-package corfu-candidate-overlay
  :elpaca (:type git
             :repo "https://code.bsdgeek.org/adam/corfu-candidate-overlay"
             :files (:defaults "*.el"))
  :after corfu
  (corfu-candidate-overlay-mode +1)
  (global-set-key (kbd "S-<tab>") #'corfu-complete))

delight   candy

Delight enables you to customise the mode names displayed in the mode line.


(use-package delight
  :defer 10)


Denote aims to be a simple-to-use, focused-in-scope, and effective note-taking and file-naming tool for Emacs.


I am trying out denote to see if it can replace org-roam

(use-package denote
  (denote-directory "~/org/grok/"))

devdocs   candy

devdocs.el is a documentation viewer for Emacs similar to the built-in Info browser, but geared towards documentation distributed by the DevDocs website.


(use-package devdocs
  (global-set-key (kbd "C-h D") 'devdocs-lookup))

diminish   candy

This package implements hiding or abbreviation of the mode line displays (lighters) of minor-modes.


(use-package diminish
  :defer 10)

dired   builtin

Normally I’d have a package per heading, but these are most/all built-ins and they are all basically just dired extensions, so I’m going to keep them under one heading together.

(use-builtin dired
	     (:map dired-mode-map
		   ("/" . dired-goto-file)
		   ("," . dired-create-directory)
		   ("." . dired-create-empty-file)
		   ("I" . dired-insert-subdir)
		   ("K" . dired-kill-subdir)
		   ("O" . dired-find-file-other-window)
		   ("[" . dired-prev-dirline)
		   ("]" . dired-next-dirline)
		   ("o" . dired-up-directory)
		   ("^" . mode-line-other-buffer)
		   ("x" . dired-do-delete)
		   ("X" . dired-do-flagged-delete)
		   ("y" . dired-do-copy))
	     (dired-listing-switches "-l --almost-all --human-readable --time-style=long-iso --group-directories-first --no-group")
	     (when (eq system-type 'darwin)
	       (setq insert-directory-program "gls")))
(use-builtin dired-x
	     (setq dired-omit-files (concat dired-omit-files "\\|^\\..*$")))
(use-builtin dired-aux)
(use-builtin image-dired
	     (image-dired-thumb-size 256)
	     (image-dired-marking-shows-next nil)
	     (:map image-dired-thumbnail-mode-map
		   ("n" . image-dired-next-line)
		   ("p" . image-dired-previous-line)
		   ("i" . image-dired-forward-image)
		   ("o" . image-dired-backward-image)))
(use-builtin wdired)
(use-package diredfl
  ('dired-mode-hook #'diredfl-mode)
  (set-face-attribute 'diredfl-dir-name nil :bold t))


Kill and Mark things easily in Emacs

Easy Kill

(use-package easy-kill
  (global-set-key [remap kill-ring-save] 'easy-kill)
  (global-set-key [remap mark-sexp] 'easy-mark))


[E]at is a [T]erminal [E]mulator.


(use-package eat
  :elpaca (:type git
		   :host codeberg
		   :repo "akib/emacs-eat"
		   :files ("*.el" ("term" "term/*.el") "*.texi"
			   "*.ti" ("terminfo/e" "terminfo/e/*")
			   ("terminfo/65" "terminfo/65/*")
			   ("integration" "integration/*")
			   (:exclude ".dir-locals.el" "*-tests.el")))
  :commands (eat eat-eshell-mode)

eglot   builtin

After Emacs 29, Eglot comes baked into Emacs and is thus the definite LSP choice for any config that wishes to stick close to a vanilla emacs experience. Because Eglot comes with Emacs and is ready to go, all we have to do is config it to start automatically (or not) and then config any settings.

(use-builtin eglot
  :after (corfu cape orderless)
  :defer t
  :hook ((js-ts-mode . eglot-ensure)
         (typescript-ts-mode. eglot-ensure))
  (setq completion-category-overrides '((eglot (styles orderless))))
  (advice-add 'eglot-completion-at-point :around #'cape-wrap-buster)
  (eglot-send-change-idle-time 0.1)
  (eglot-autoshutdown t)
  (eglot-extend-to-xref t)
     (:plugins [
		((:name "ts-lit-Plugin")
		 (:location "/Users/IPringle/.asdf/installs/nodejs/18.17.0/lib/node_modules/ts-lit-plugin/"))
		((:name "typescript-lit-html-plugin")
		 (:location "/Users/IPringle/.asdf/installs/nodejs/18.17.0/lib/node_modules/typescript-lit-html-plugin/"))
  (fset #'jsonrpc--log-event #'ignore)
  (defun fv--orderless-dispatch-passthrough-first ()
    "Return a function for orderless dispatch.
Lets all matches through for 1st component, unless it has
changed, in which case flex is used."
    (let (last-table initial-pattern)
      (lambda (pattern index _total)
	(if-let (((eq index 0))
		 (table (caddr completion-in-region--data)))
	     ((not (eq table last-table))
	      (setq last-table table
		    initial-pattern pattern)
	     ((and initial-pattern
		   (string-equal pattern initial-pattern))
	     (t 'orderless-flex))))))

  (defun fv--eglot-capf ()
    (setf (alist-get 'styles
		     (alist-get 'eglot completion-category-defaults))
    (make-local-variable 'orderless-style-dispatchers)
    (cl-pushnew (fv--orderless-dispatch-passthrough-first)
    (setq-local completion-at-point-functions
		(cl-nsubst (cape-capf-buster #'eglot-completion-at-point)
  (add-hook 'eglot-managed-mode-hook #'fv--eglot-capf)
  (add-to-list 'eglot-server-programs
               '(astro-ts-mode . ("astro-ls" "--stdio")))
  (add-to-list 'eglot-server-programs
                 '(js-mode js-ts-mode
                           tsx-ts-mode typescript-ts-mode typescript-mode)
                 '("typescript-language-server" "--stdio" :initializationOptions
                     ;; https://github.com/microsoft/TypeScript/blob/main/src/server/protocol.ts#L3410-L3539
                     :disableSuggestions                                    :json-false     ;; boolean
                     :quotePreference                                       "double"        ;; "auto" | "double" | "single"
                     :includeCompletionsForModuleExports                    t               ;; boolean
                     :includeCompletionsForImportStatements                 t               ;; boolean
                     :includeCompletionsWithSnippetText                     t               ;; boolean
                     :includeCompletionsWithInsertText                      t               ;; boolean
                     :includeAutomaticOptionalChainCompletions              t               ;; boolean
                     :includeCompletionsWithClassMemberSnippets             t               ;; boolean
                     :includeCompletionsWithObjectLiteralMethodSnippets     t               ;; boolean
                     :useLabelDetailsInCompletionEntries                    t               ;; boolean
                     :allowIncompleteCompletions                            t               ;; boolean
                     :importModuleSpecifierPreference                       "shortest"      ;; "shortest" | "project-relative" | "relative" | "non-relative"
                     :importModuleSpecifierEnding                           "minimal"       ;; "auto" | "minimal" | "index" | "js"
                     :allowTextChangesInNewFiles                            t               ;; boolean
                     :providePrefixAndSuffixTextForRename                   t               ;; boolean
                     :provideRefactorNotApplicableReason                    :json-false     ;; boolean
                     :allowRenameOfImportPath                               t               ;; boolean
                     :jsxAttributeCompletionStyle                           "auto"          ;; "auto" | "braces" | "none"
                     :displayPartsForJSDoc                                  t               ;; boolean
                     :generateReturnInDocTemplate                           t               ;; boolean
                     :includeInlayParameterNameHints                        "all"           ;; "none" | "literals" | "all"
                     :includeInlayParameterNameHintsWhenArgumentMatchesName t               ;; boolean
                     :includeInlayFunctionParameterTypeHints                t               ;; boolean,
                     :includeInlayVariableTypeHints                         t               ;; boolean
                     :includeInlayVariableTypeHintsWhenTypeMatchesName      t               ;; boolean
                     :includeInlayPropertyDeclarationTypeHints              t               ;; boolean
                     :includeInlayFunctionLikeReturnTypeHints               t               ;; boolean
                     :includeInlayEnumMemberValueHints                      t               ;; boolean
                     :disableLineTextInReferences                           :json-false     ;; boolean
                (lambda (server-program)
                   (listp (car server-program))
                   (member 'tsx-ts-mode (car server-program))))

  (defun /typescript-ts-base-mode-hook-function ()
    (add-hook 'before-save-hook #'/eglot-organize-imports nil t))
  (add-hook 'typescript-ts-base-mode-hook #'/typescript-ts-base-mode-hook-function)

  (defun /eglot-organize-imports ()
    (if (derived-mode-p major-mode #'typescript-ts-base-mode)
         (lambda (kind) (interactive)
             (eglot-code-actions (buffer-end 0) (buffer-end 1) kind t)))
         ;; https://github.com/typescript-language-server/typescript-language-server#code-actions-on-save
      (funcall-interactively #'eglot-code-action-organize-imports))))


This is a shim so eglot will use tempel instead of yas.

(use-package eglot-tempel
    :after eglot
    :elpaca (eglot-tempel
  	     :host github
             :repo "fejfighter/eglot-tempel"))

eldoc-box   candy

(use-package eldoc-box
  :after eglot
  (add-hook 'eglot-managed-mode-hook #'eldoc-box-hover-mode t))

embark   core


(use-package embark
  :ensure t
  (("C-." . embark-act)
   ("M-." . embark-dwim)
   ("C-h B" . embark-bindings))
  (prefix-help-command #'embark-prefix-help-command)
  :hook ('eldoc-documentation-function #'embark-eldoc-first-target)
  (add-to-list 'display-buffer-alist
               '("\\'\\*Embark Collect \\(Live\\|Completions\\)\\*"
                 (window-parameters (mode-line-format . none)))))


(use-package embark-consult
  :after (embark consult)
  :demand t ; only necessary if you have the hook below
  ;; auto-updating embark collect buffer
  (embark-collect-mode . embark-consult-preview-minor-mode))


Add/Change/Delete pairs based on `expand-region’, similar to `evil-surround’.


I’ve looked at smartparens which is a reimplementation of electirc, plus a lot of movements for navigation, and the ability to add, change, or swap pairs. It’s a very featureful package, but it was overwhelming. I’ve used embrace before and it’s a lot like the vim package by tpope “surround”. I’m going to stick with this unless it’s just not enough.

(use-package embrace
  (global-set-key (kbd "C-c s") #'embrace-commander))


I’m unsure if I’ll keep this, but I want to test it out, it seems like it might have potential, especially given that between Astro and Lit I write a lot of vanilla HTML.

(use-package emmet-mode
  :defer t
  (add-hook 'sgml-mode-hook 'emmet-mode)
  (add-hook 'css-mode-hook 'emmet-mode))

This is a capf for emmet so that I can expand it with a completion at point.

(defun fv--emmet-expand ()
  (let ((bounds (bounds-of-thing-at-point 'symbol)))
    (list (car bounds) (cdr bounds)
          (lambda (str pred action) (emmet-transform str))
          :exclusive 'no)))
(add-to-list 'completion-at-point-functions #'fv--emmet-expand


(use-package esup
  (esup-depth 0))


Emacs extension to increase selected region by semantic units.

Expand Region

(use-package expand-region
  (global-set-key (kbd "C-c e") 'er/expand-region))


Forge allows you to work with Git forges, such as Github and Gitlab, from the comfort of Magit and the rest of Emacs.


(use-package forge
  :after magit)


general.el provides a more convenient method for binding keys in emacs.

(use-package general
  :demand t

goggles   candy

(use-package goggles
  :hook ((prog-mode text-mode) . goggles-mode)
  (setq-default goggles-pulse t))

helpful   core

Helpful is an alternative to the built-in Emacs help that provides much more contextual information.


(use-package helpful
  :custom (elisp-refs-verbose nil)
  (("M-g M-d" . 'helpful-at-point)
  ([remap describe-key] . helpful-key)
  ([remap describe-function] . helpful-callable)
  ([remap describe-command] . helpful-command)
  ([remap describe-variable] . helpful-variable)))


Jinx is a fast just-in-time spell-checker for Emacs.


(use-package jinx
  (global-set-key [remap ispell-word] 'jinx-correct)
  (global-set-key (kbd "C-M-$") 'jinx-languages))


Completion kind text/icon prefix labelling for emacs in-region completion

Kind Icon

(use-package kind-icon
	     :after corfu
	     (kind-icon-use-icons t)
	     (kind-icon-default-face 'corfu-default)
	     (kind-icon-blend-background nil)
	     (kind-icon-blend-frac 0.08)
	     (add-to-list 'corfu-margin-formatters #'kind-icon-margin-formatter))

magit   core

(use-package magit
  :defer t

marginalia   core

(use-package marginalia
  (:map minibuffer-local-map
	   ("M-A" . 'marginalia-cycle))
  (marginalia-max-relative-age 0)
  (marginalia-align 'right)

mood-line   candy

This is my mode-line. There are many like it, but this one is mine. My mode-line is my best friend. It is my life. I must master it as I must master my life. Without me my mode-line is useless. Without my mode-line, I am useless.

(use-package mood-line
  (setq mood-line-glyph-alist mood-line-glyphs-fira-code)

orderless   core

 (use-package orderless
   :demand t

   (defun +orderless--consult-suffix ()
     "Regexp which matches the end of string with Consult tofu support."
     (if (and (boundp 'consult--tofu-char) (boundp 'consult--tofu-range))
  (format "[%c-%c]*$"
	  (+ consult--tofu-char consult--tofu-range -1))

   (defun +orderless-consult-dispatch (word _index _total)
      ((string-suffix-p "$" word)
`(orderless-regexp . ,(concat (substring word 0 -1) (+orderless--consult-suffix))))
      ((and (or minibuffer-completing-file-name
	 (derived-mode-p 'eshell-mode))
     (string-match-p "\\`\\.." word))
`(orderless-regexp . ,(concat "\\." (substring word 1) (+orderless--consult-suffix))))))

   (orderless-define-completion-style +orderless-with-initialism
     (orderless-matching-styles '(orderless-initialism orderless-literal orderless-regexp)))

   (setq completion-styles '(orderless basic)
  completion-category-defaults nil
  completion-category-overrides '((file (styles partial-completion))
				  (command (styles +orderless-with-initialism))
				  (variable (styles +orderless-with-initialism))
				  (symbol (styles +orderless-with-initialism)))
  orderless-component-separator #'orderless-escapable-split-on-space
  orderless-style-dispatchers (list #'+orderless-consult-dispatch

org   core

Set (auto-fill-mode) when org starts to automatically insert line breaks when a line gets too long.

(use-package org
  :defer t
  ('org-mode-hook (function fv--hidden-block))
  ("\\.org\\'" . org-mode)

Config Preamble

I’m going to try something different here because I want to better document my org config.



Now to fix some defaults:

(setq org-directory (concat (getenv "HOME") "/org"))
(setq org-directory org-directory)
(setq deft-directory org-directory)
(setq org-blog-directory (concat org-directory "/blog"))
(setq +org-capture-inbox (concat org-directory "/life.org"))
(setq +org-capture-weekly (concat org-directory "/weekly.org"))
(setq org-use-property-inheritance t
      org-log-done 'time
      org-list-allow-alphabetical t
      org-export-in-background t
      org-fold-catch-invisible-edits 'smart
      org-auto-align-tags nil
      org-tags-column 0
      org-special-ctrl-a/e t
      org-insert-heading-respect-content t
      org-hide-emphasis-markers t
      org-pretty-entities t
      org-ellipsis "…"
      org-agenda-tags-column 0
      org-agenda-block-separator ?─
      org-agenda-time-grid '((daily today require-timed)
                             (800 1000 1200 1400 1600 1800 2000)
                             " ┄┄┄┄┄ " "┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄")
      org-agenda-current-time-string "⭠ now ─────────────────────────────────────────────────")

This is needed for the time being because of an issue with Emacs 29 and Org.

(advice-remove 'org-display-inline-images 'org-display-user-inline-images)

And we need a few extra org-modules

(require 'org-id)

Auto Save

One minute before the start of the next hour, save all org buffers. I should consider making this happen more often…

(run-at-time "00:59" 3600 'org-save-all-org-buffers)

And then just used the baked in git sync feature:

(require 'org-attach-git)


The idea here is the minimize states, which minimizes the time I have to think about the state task should be in. Ultimately tasks either need to be started (TODO), are finished (DONE), or cannot be worked on (HOLD). The idea of a “NEXT” is better off-loaded to some algorithm that can determine the next best task to work on based on the effort required to finish it, the priority of the task, and whether it’s a blocker for another task. This is how Taskwarrior does it, and think this is a great thing to embrace. The KILL state is here mostly because it’s a almost zero-effort call on whether or not a task is KILL’d (ie no longer needs to be moved to a DONE state) and KILL’d, or cancelled, task is different enough from a DONE task to merit it’s own face..

Appointments (APPT) are like TODOs that are in HOLD until a specific time and at the appointed time are immediately the active task, until completed, when they immediately are DONE. I mostly added this APPT state so that I can easily see when I have appointments and because, like KILL, it requires next to no energy to know if a task is an APPT — and in fact this face would likely almost only ever been created through a capture template anyway.

Finally we have the last three faces — I don’t want to use ‘state’ for them because they’re really not even tasks. MEET is for, wait for it, meetings — but also other similar types of events. The goal with MEET is to open the capture template, keep it open for the duration of the MEET-thing and then close it. The opening of the MEET-thing would log the start time and then the closing would log the close. Why do I have NOTE and IDEA? No clue, I should probably just keep one or the other, but I feel like there is enough difference between the two concepts to warrant both. An IDEA is something to explore in detail later, like a blog post or a topic to look into. A NOTE is some knowledge or inkling I want to retain and perhaps develop later into a grok. Time will tell if I keep both faces or condense them into one.

Lastly, I have some faces for my reading list. I was controlling this with a file variable but it wasn’t working right and it doesn’t hurt anything to put this in here.

(setq org-todo-keywords
      (sequence "TODO(t)" "|" "DONE(d!/!)")
      (sequence "HOLD(h@/!)" "|" "KILL(k@/!)")
      (sequence "APPT(a)" "|" "DONE(d!)")
      (sequence "MEET(m)" "IDEA(i)" "NOTE(n)")
      (sequence "READ(r)" "READING(R)" "|" "DONE(d@/!)")))

(setq org-todo-keyword-faces
        ;; I like the default TODO color...
        ;; ("TODO" :foreground "red" :weight bold)
        ("DONE" :foreground "forest green" :weight bold)
        ("HOLD" :foreground "magenta" :weight bold)
        ("KILL" :foreground "forest green" :weight bold)
        ("MEET" :foreground "forest green" :weight bold)
        ("APPT" :foreground "magenta" :weight bold)
        ("IDEA" :foreground "gold" :weight bold)
        ("NOTE" :foreground "blue" :weight bold)
        ("READ" :foreground "red" :weight bold)
        ("READING" :foreground "magenta" :weight bold)))

(setq org-use-fast-todo-selection t)
(setq org-treat-S-cursor-todo-selection-as-state-change nil)
(setq org-todo-state-tags-triggers
      '(("KILL" ("KILL" . t) ("ARCHIVE" . t))
        ("HOLD" ("HOLD" . t) ("ARCHIVE"))
        (done ("HOLD") ("ARCHIVE". t))
        ("TODO" ("HOLD") ("KILL") ("ARCHIVE"))
        ("DONE" ("HOLD") ("KILL") ("ARCHIVE" . t))
        ("READ" ("ARCHIVE"))
        ("READING" ("ARCHIVE"))))


(setq org-tag-alist '(
                       ;; Related to the computer, but not specific to work
                       ("BLOG" . ?b)
                       ("ORG" . ?o)
                       ;; Related to work
                       ("@work" . ?w)
                       ("INCIDENT" . ?i)
                       ("REQUEST" . ?r)
                       ;; Related to IRL
                       ("@home" . ?h)
                       ("@farm" . ?f)
                       ;; Related to traveling
                       ("ERRAND" . ?e)
                       ("@Doniphan" . ?D)
                       ("@PoplarBluff" . ?P)
                       ("@WestPlain" . ?W)
                       ("@Thayer" . ?T)))

I use the :tangleno tag in my literate emacs config to switch blocks on and off. To make this tag easier to see I’m adding it to the list of org-tag-faces.

(add-to-list 'org-tag-faces
             '("tangleno" . (:foreground "white" :background "red")))

Capture Templates

I’m trying to keep everything to just one file. Or, at least most of everything. So we file everything into the “Inbox” heading, which is the holding tank for almost all my captures so I can later go through them and evaluate if they’re really valuable or not. Capturing is for the purpose of getting all my ideas, tasks, etc. out of my head and somewhere less ephemeral as quickly as possible. We can review the merit of the things that are captures later during a reivew period.

(setq org-capture-templates
      '(("t" "Task" entry (file+headline +org-capture-inbox "Inbox")
         "* TODO %? %(org-set-tags-command) \nCREATED: %U\n" :clock-in t :clock-resume t)
        ("T" "Task @ cursor" entry (file+headline +org-capture-inbox "Inbox")
         "* TODO %? %(org-set-tags-command) \nCREATED: %U\n%l\n" :clock-in t :clock-resume t)
        ("i" "Ideas" entry (file+headline +org-capture-inbox "Inbox")
         "* IDEA %?\nCREATED: %U\n" :clock-in t :clock-resume t)
        ("b" "Blog" plain (function fv--create-new-blog-buffer) "#+filetags: %^{filetags}\n\n%?")
        ("n" "Notes" entry (file+headline +org-capture-inbox "Inbox")
         "* NOTE %?\nCREATED: %U\n%l\n" :clock-in t :clock-resume t)
        ("a" "Appointment" entry (file+headline +org-capture-inbox "Inbox")
         "* APPT %? %^gAPPOINTMENT: \nSCHEDULED: %^T\nCREATED: %U\n" :clock-in t :clock-resume t)
        ("m" "Meeting" entry (file+headline +org-capture-inbox "Meeting")
         "* MEET with %? :MEETING:\nCREATED: %U\n" :clock-in t :clock-resume t)
        ("w" "Weekly Plan" entry (file+olp+datetree +org-capture-weekly)
         "* Goals\n* Changes\n* PTO\n* Notes\n" :clock-in t :clock-resume t :tree-type week)

Because we are clocking all captures, we could easily end up with a 0:00 clock, which we want to delete, but than that’d end up with an empty :LOGBOOK: and so we should delete those since they’re ugly and pointless.

(defun fv--remove-empty-drawer-on-clock-out ()
    (beginning-of-line 0)
    (org-remove-empty-drawer-at (point))))

(add-hook 'org-clock-out-hook 'fv--remove-empty-drawer-on-clock-out 'append)


(require 'org-agenda)
(setq org-agenda-files (apply 'append
                               (lambda (directory)
  				directory org-agenda-file-regexp))
                               (list org-directory))))


;; Resume clocking task when emacs is restarted
;; Show lot of clocking history so it's easy to pick items off the C-F11 list
(setq org-clock-history-length 23)

;; Resume clocking task on clock-in if the clock is open
(setq org-clock-in-resume t)

;; Separate drawers for clocking and logs
(setq org-drawers (quote ("PROPERTIES" "LOGBOOK")))

;; Save clock data and state changes and notes in the LOGBOOK drawer
(setq org-clock-into-drawer t)

;; Sometimes I change tasks I'm clocking quickly - this removes clocked tasks with 0:00 duration
(setq org-clock-out-remove-zero-time-clocks t)

;; Clock out when moving task to a done state
(setq org-clock-out-when-done t)

;; Save the running clock and all clock history when exiting Emacs, load it on startup
(setq org-clock-persist t)

;; Do not prompt to resume an active clock
(setq org-clock-persist-query-resume nil)

;; Enable auto clock resolution for finding open clocks
(setq org-clock-auto-clock-resolution (quote when-no-clock-is-running))

;; Include current clocking task in clock reports
(setq org-clock-report-include-clocking-task t)

Clocking functions stolen from Norang


(setq bh/keep-clock-running nil)

(defun bh/find-project-task ()
  "Move point to the parent (project) task if any"
    (let ((parent-task (save-excursion (org-back-to-heading 'invisible-ok) (point))))
      (while (org-up-heading-safe)
        (when (member (nth 2 (org-heading-components)) org-todo-keywords-1)
          (setq parent-task (point))))
      (goto-char parent-task)

(defun bh/punch-in (arg)
  "Start continuous clocking and set the default task to the
selected task.  If no task is selected set the Organization task
as the default task."
  (interactive "p")
  (setq bh/keep-clock-running t)
  (if (equal major-mode 'org-agenda-mode)
      ;; We're in the agenda
      (let* ((marker (org-get-at-bol 'org-hd-marker))
             (tags (org-with-point-at marker (org-get-tags-at))))
        (if (and (eq arg 4) tags)
            (org-agenda-clock-in '(16))
    ;; We are not in the agenda
      ; Find the tags on the current task
      (if (and (equal major-mode 'org-mode) (not (org-before-first-heading-p)) (eq arg 4))
          (org-clock-in '(16))

(defun bh/punch-out ()
  (setq bh/keep-clock-running nil)
  (when (org-clock-is-active)

(defun bh/clock-in-default-task ()
    (org-with-point-at org-clock-default-task

(defun bh/clock-in-parent-task ()
  "Move point to the parent (project) task if any and clock in"
  (let ((parent-task))
        (while (and (not parent-task) (org-up-heading-safe))
          (when (member (nth 2 (org-heading-components)) org-todo-keywords-1)
            (setq parent-task (point))))
        (if parent-task
            (org-with-point-at parent-task
          (when bh/keep-clock-running

(defvar bh/organization-task-id "0a6abfc7-3d86-4a11-8ed4-85154df397f8")

(defun bh/clock-in-organization-task-as-default ()
  (org-with-point-at (org-id-find bh/organization-task-id 'marker)
    (org-clock-in '(16))))

(defun bh/clock-out-maybe ()
  (when (and bh/keep-clock-running
             (not org-clock-clocking-in)
             (marker-buffer org-clock-default-task)
             (not org-clock-resolving-clocks-due-to-idleness))

(add-hook 'org-clock-out-hook 'bh/clock-out-maybe 'append)

(require 'org-id)
(defun bh/clock-in-task-by-id (id)
  "Clock in a task by id"
  (org-with-point-at (org-id-find id 'marker)
    (org-clock-in nil)))

(defun bh/clock-in-last-task (arg)
  "Clock in the interrupted task if there is one
Skip the default task and get the next one.
A prefix arg forces clock in of the default task."
  (interactive "p")
  (let ((clock-in-to-task
          ((eq arg 4) org-clock-default-task)
          ((and (org-clock-is-active)
                (equal org-clock-default-task (cadr org-clock-history)))
           (caddr org-clock-history))
          ((org-clock-is-active) (cadr org-clock-history))
          ((equal org-clock-default-task (car org-clock-history)) (cadr org-clock-history))
          (t (car org-clock-history)))))
    (org-with-point-at clock-in-to-task
      (org-clock-in nil))))

Utility functions stolen fron Norang to help with the clocking functions stolen from Norang

(defun bh/is-project-p ()
  "Any task with a todo keyword subtask"
    (let ((has-subtask)
          (subtree-end (save-excursion (org-end-of-subtree t)))
          (is-a-task (member (nth 2 (org-heading-components)) org-todo-keywords-1)))
        (forward-line 1)
        (while (and (not has-subtask)
                    (< (point) subtree-end)
                    (re-search-forward "^\*+ " subtree-end t))
          (when (member (org-get-todo-state) org-todo-keywords-1)
            (setq has-subtask t))))
      (and is-a-task has-subtask))))

(defun bh/is-project-subtree-p ()
  "Any task with a todo keyword that is in a project subtree.
Callers of this function already widen the buffer view."
  (let ((task (save-excursion (org-back-to-heading 'invisible-ok)
      (if (equal (point) task)

(defun bh/is-task-p ()
  "Any task with a todo keyword and no subtask"
    (let ((has-subtask)
          (subtree-end (save-excursion (org-end-of-subtree t)))
          (is-a-task (member (nth 2 (org-heading-components)) org-todo-keywords-1)))
        (forward-line 1)
        (while (and (not has-subtask)
                    (< (point) subtree-end)
                    (re-search-forward "^\*+ " subtree-end t))
          (when (member (org-get-todo-state) org-todo-keywords-1)
            (setq has-subtask t))))
      (and is-a-task (not has-subtask)))))

(defun bh/is-subproject-p ()
  "Any task which is a subtask of another project"
  (let ((is-subproject)
        (is-a-task (member (nth 2 (org-heading-components)) org-todo-keywords-1)))
      (while (and (not is-subproject) (org-up-heading-safe))
        (when (member (nth 2 (org-heading-components)) org-todo-keywords-1)
          (setq is-subproject t))))
    (and is-a-task is-subproject)))

(defun bh/list-sublevels-for-projects-indented ()
  "Set org-tags-match-list-sublevels so when restricted to a subtree we list all subtasks.
  This is normally used by skipping functions where this variable is already local to the agenda."
  (if (marker-buffer org-agenda-restrict-begin)
      (setq org-tags-match-list-sublevels 'indented)
    (setq org-tags-match-list-sublevels nil))

(defun bh/list-sublevels-for-projects ()
  "Set org-tags-match-list-sublevels so when restricted to a subtree we list all subtasks.
  This is normally used by skipping functions where this variable is already local to the agenda."
  (if (marker-buffer org-agenda-restrict-begin)
      (setq org-tags-match-list-sublevels t)
    (setq org-tags-match-list-sublevels nil))

(defvar bh/hide-scheduled-and-waiting-next-tasks t)

(defun bh/toggle-next-task-display ()
  (setq bh/hide-scheduled-and-waiting-next-tasks (not bh/hide-scheduled-and-waiting-next-tasks))
  (when  (equal major-mode 'org-agenda-mode)
  (message "%s WAITING and SCHEDULED NEXT Tasks" (if bh/hide-scheduled-and-waiting-next-tasks "Hide" "Show")))

(defun bh/skip-stuck-projects ()
  "Skip trees that are not stuck projects"
    (let ((next-headline (save-excursion (or (outline-next-heading) (point-max)))))
      (if (bh/is-project-p)
          (let* ((subtree-end (save-excursion (org-end-of-subtree t)))
                 (has-next ))
              (forward-line 1)
              (while (and (not has-next) (< (point) subtree-end) (re-search-forward "^\\*+ NEXT " subtree-end t))
                (unless (member "WAITING" (org-get-tags-at))
                  (setq has-next t))))
            (if has-next
              next-headline)) ; a stuck project, has subtasks but no next task

(defun bh/skip-non-stuck-projects ()
  "Skip trees that are not stuck projects"
  ;; (bh/list-sublevels-for-projects-indented)
    (let ((next-headline (save-excursion (or (outline-next-heading) (point-max)))))
      (if (bh/is-project-p)
          (let* ((subtree-end (save-excursion (org-end-of-subtree t)))
                 (has-next ))
              (forward-line 1)
              (while (and (not has-next) (< (point) subtree-end) (re-search-forward "^\\*+ NEXT " subtree-end t))
                (unless (member "WAITING" (org-get-tags-at))
                  (setq has-next t))))
            (if has-next
              nil)) ; a stuck project, has subtasks but no next task

(defun bh/skip-non-projects ()
  "Skip trees that are not projects"
  ;; (bh/list-sublevels-for-projects-indented)
  (if (save-excursion (bh/skip-non-stuck-projects))
        (let ((subtree-end (save-excursion (org-end-of-subtree t))))
           ((and (bh/is-project-subtree-p) (not (bh/is-task-p)))
    (save-excursion (org-end-of-subtree t))))

(defun bh/skip-non-tasks ()
  "Show non-project tasks.
Skip project and sub-project tasks, habits, and project related tasks."
    (let ((next-headline (save-excursion (or (outline-next-heading) (point-max)))))

(defun bh/skip-project-trees-and-habits ()
  "Skip trees that are projects"
    (let ((subtree-end (save-excursion (org-end-of-subtree t))))

(defun bh/skip-projects-and-habits-and-single-tasks ()
  "Skip trees that are projects, tasks that are habits, single non-project tasks"
    (let ((next-headline (save-excursion (or (outline-next-heading) (point-max)))))
       ((and bh/hide-scheduled-and-waiting-next-tasks
             (member "WAITING" (org-get-tags-at)))
       ((and (bh/is-task-p) (not (bh/is-project-subtree-p)))

(defun bh/skip-project-tasks-maybe ()
  "Show tasks related to the current restriction.
When restricted to a project, skip project and sub project tasks, habits, NEXT tasks, and loose tasks.
When not restricted, skip project and sub-project tasks, habits, and project related tasks."
    (let* ((subtree-end (save-excursion (org-end-of-subtree t)))
           (next-headline (save-excursion (or (outline-next-heading) (point-max))))
           (limit-to-project (marker-buffer org-agenda-restrict-begin)))
       ((and (not limit-to-project)
       ((and limit-to-project
             (member (org-get-todo-state) (list "NEXT")))

(defun bh/skip-project-tasks ()
  "Show non-project tasks.
Skip project and sub-project tasks, habits, and project related tasks."
    (let* ((subtree-end (save-excursion (org-end-of-subtree t))))

(defun bh/skip-non-project-tasks ()
  "Show project tasks.
Skip project and sub-project tasks, habits, and loose non-project tasks."
    (let* ((subtree-end (save-excursion (org-end-of-subtree t)))
           (next-headline (save-excursion (or (outline-next-heading) (point-max)))))
       ((and (bh/is-project-subtree-p)
             (member (org-get-todo-state) (list "NEXT")))
       ((not (bh/is-project-subtree-p))

(defun bh/skip-projects-and-habits ()
  "Skip trees that are projects and tasks that are habits"
    (let ((subtree-end (save-excursion (org-end-of-subtree t))))

(defun bh/skip-non-subprojects ()
  "Skip trees that are not projects"
  (let ((next-headline (save-excursion (outline-next-heading))))
    (if (bh/is-subproject-p)

Additional clock functions

This will toggle between a “break” task and the previously clocked task.

(defvar fv--previous-clocked-task-id "")
(defvar fv--break-task-id "855c4e50-a1ff-4ef8-8615-cd593acdbf4a")

(defun fv--clock-in-break-task (_)
  "Clock in the break-task-id task"
  (interactive "p")
  (bh/clock-in-task-by-id fv--break-task-id))

(defun fv--toggle-clock-on-break-task (_)
  "Toggle the break-task-id task"
  (interactive "p")
  (bh/clock-in-task-by-id fv--break-task-id))
(map! :map org-mode-map
      :after org
      (:prefix "c"
      :desc "Clock in previous task" "m" #'bh/clock-in-last-task
      :desc "Punch-In" "p" #'bh/punch-in
      :desc "Punch-Out" "P" #'bh/punch-out
      :desc "Clock in break" "b" #'0x44/clock-in-break-task))

Config Postamble

This needs to be at the end of the config to close the use-package. org config.


Org Functions

These are some functions, mostly/all autoloads, that work with org in some way, shape, or form.

Tangle Into Dir

This is a helper function to make tangling a bit less verbose. This function can be called inside of a block header that uses the tangle command. If the root of the document has a header for tangle-dir the block’s output will be prepended with that path. This lets you specify the output for all tangle blocks in a document and then only change the name of a file in each block.

(defun fv--org-tangle-into-dir (sub-path)
  "Expand the SUB-PATH into the directory given by the tangle-dir
   property if that property exists, else use the
  (expand-file-name sub-path
                     (org-entry-get (point) "tangle-dir" 'inherit)

Blog Template

This function will create a new orgfile for a blog post. This can be called alone, but it also works as the function argument to an org-capture template.

(defun fv--create-new-blog-buffer ()
  "Created a new blog from the specified template in a new buffer"
  (let* ((title (read-from-minibuffer "Post Title: "))
         ($timestamp (format-time-string "<%Y-%m-%d %a %H:%M>" ))
         (fname (concat org-blog-directory "/" (fv--slugify title) ".org")))
    (set-buffer (org-capture-target-buffer fname))
    (insert (format
             ":PROPERTIES:\n:AUTHOR: %s\n:CREATED: %s\n:MODIFIED: %s\n:TYPE: blog\n:END:\n#+title: %s\n"
             user-full-name $timestamp $timestamp title))
    (goto-char (point-max))))

This is the “slugify” function that the blog template uses.

(require 'ffap)
(defun fv--slugify (str)
  "Convert string STR to a `slug' and return that string."
  (let* (;; All lower-case
         (str (downcase str))
         ;; Remove "<FOO>..</FOO>" HTML tags if present.
         (str (replace-regexp-in-string "<\\(?1:[a-z]+\\)[^>]*>.*</\\1>" "" str))
         ;; Remove URLs if present in the string.  The ")" in the
         ;; below regexp is the closing parenthesis of a Markdown
         ;; link: [Desc](Link).
         (str (replace-regexp-in-string (concat "\\](" ffap-url-regexp "[^)]+)") "]" str))
         ;; Replace "&" with " and ", "." with " dot ", "+" with
         ;; " plus ".
         (str (replace-regexp-in-string
               "&" " and "
                "\\." " dot "
                 "\\+" " plus " str))))
         ;; Replace all characters except alphabets, numbers and
         ;; parentheses with spaces.
         (str (replace-regexp-in-string "[^[:alnum:]()]" " " str))
         ;; Remove leading and trailing whitespace.
         (str (replace-regexp-in-string "\\(^[[:space:]]*\\|[[:space:]]*$\\)" "" str))
         ;; Replace 2 or more spaces with a single space.
         (str (replace-regexp-in-string "[[:space:]]\\{2,\\}" " " str))
         ;; Replace parentheses with double-hyphens.
         (str (replace-regexp-in-string "\\s-*([[:space:]]*\\([^)]+?\\)[[:space:]]*)\\s-*" " -\\1- " str))
         ;; Remove any remaining parentheses character.
         (str (replace-regexp-in-string "[()]" "" str))
         ;; Replace spaces with hyphens.
         (str (replace-regexp-in-string " " "-" str))
         ;; Remove leading and trailing hyphens.
         (str (replace-regexp-in-string "\\(^[-]*\\|[-]*$\\)" "" str)))

Hidden Blocks

This function will start blocks taged as :hidden folded. It’s handy for really long source blocks, or blocks that are important to have but not important to look at. This came from here but it’s not working right at the moment…

(defun fv--hidden-block ()
  "Fold some blocks in the current buffer."
   (lambda ()
     (let ((case-fold-search t))
       (when (and
                (beginning-of-line 1)
                (looking-at org-block-regexp))


Toggle visibility of hidden Org mode element parts upon entering and leaving an element.


(use-package org-appear
  :after org
  :hook (org-mode . org-appear-mode)
  (setq org-appear-autoemphasis t
	org-appear-autolinks t
	org-appear-autosubmarks t))


(use-package org-contrib
  :elpaca (org-contrib :repo "https://git.sr.ht/~bzg/org-contrib"))


Preserve the scratch buffer across Emacs sessions.

Persistent Scratch

(use-package persistent-scratch


Might switch this out to plz, but it’s what I’ve been using so… https://github.com/tkf/emacs-request

(use-package request)


It’s a better snippet system, I’ve been told.

(use-package tempel
  (defun tempel-setup-capf ()
    (setq-local completion-at-point-functions
                (cons #'tempel-expand
  (add-hook 'conf-mode-hook 'tempel-setup-capf)
  (add-hook 'prog-mode-hook 'tempel-setup-capf)
  (add-hook 'prog-mode-hook 'tempel-abbrev-mode)
  (add-hook 'text-mode-hook 'tempel-setup-capf)

(use-package tempel-collection
  :after tempel)

These are my tempel templates.

;; ~/.config/fv/templates

treesit   builtin

(use-feature treesit
  (add-to-list 'auto-mode-alist '("\\.tsx?$" . typescript-ts-mode))
  (add-to-list 'auto-mode-alist '("\\.jsx?$" . js-ts-mode)))


This gets us free configs for most/all TS langs that come natively with treesit and also adds in the hooks to trigger TS modes.

(use-package treesit-auto
  :after (treesit)
  (setq treesit-auto-install 'prompt)


I’m going to keep treesit specific packages in here, if those packages are for new grammars. Other packages that use treesit but are not grammars will live elsewhere, like Combobulate.


(use-package astro-ts-mode
  :after (treesit-auto)
  (setq astro-ts-auto-recipe
         :lang 'astro
         :ts-mode 'astro-ts-mode
         :url "https://github.com/virchau13/tree-sitter-astro"
         :revision "master"
         :source-dir "src"))
  (add-to-list 'treesit-auto-recipe-list astro-ts-auto-recipe))

undo-fu   core

Simple, stable linear undo with redo for Emacs.

Undo Fu

(use-package undo-fu
  (global-set-key [remap undo] 'undo-fu-only-undo)
  (global-set-key [remap undo-redo] 'undo-fu-only-redo)
  (global-set-key (kbd "C-S-/") 'undo-fu-only-redo))

vertico   core

(use-package vertico
  :elpaca (vertico :files (:defaults "extensions/*"))
  (vertico-count 13)
  (vertico-resize t)
  (vertico-cycle t)
  (vertico-multiform-categories '((embark-keybinding grid)))
:bind (:map vertico-map
              ("RET" . vertico-directory-enter)
              ("DEL" . vertico-directory-delete-char)
              ("S-DEL" . vertico-directory-delete-word))
  ('rfn-eshadow-update-overlay #'vertico-directory-tidy)


(use-package vterm
  :elpaca (vterm :post-build
                   (setq vterm-always-compile-module t)
                   (require 'vterm)
                   (with-current-buffer (get-buffer-create vterm-install-buffer-name)
                     (goto-char (point-min))
                     (while (not (eobp))
                       (message "%S"
                                (buffer-substring (line-beginning-position)
                   (when-let ((so (expand-file-name "./vterm-module.so"))
                              ((file-exists-p so)))
                      so (expand-file-name (file-name-nondirectory so)
  :commands (vterm vterm-other-window)
  (vterm-max-scrollback 100000)
  (vterm-buffer-name "vterm")
  ("M-o t" 'vterm)
  ("M-o T" 'vterm-other-window)
  (setq vterm-shell (executable-find "zsh")))

which-key   core

which-key is a minor mode for Emacs that displays the key bindings following your currently entered incomplete command ( a prefix) in a popup.


(use-package which-key
  :demand t
  (which-key-side-window-location 'bottom)
  (which-key-sort-order 'which-key-key-order-alpha)
  (which-key-idle-delay 0.05)
  :diminish which-key-mode)

wiki-summary.el   candy

Grab the Wikipedia summary for a term and display the result in a buffer.

Wiki Summary

(use-package wiki-summary
  ("M-g w" . 'wiki-summary)

Test Packages

These are packages I’m testing and might or might not keep.