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

French Vanilla

#+blockquote Abstain from every form of evil. 1 Thessalonians 5:22 #+endquote

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.

Scripts

These are scripts that exist in the emacs directory but are used outside of emacs, for example with a call to emacs --quick --script foo.el.

gen-env-file

This script generates a static env file from the shell env. This is needed because emacs on MacOS doesn’t “see” your env values when called from the GUI, like with Finder or the icon on the Dock. This results in lots of breakage. This came from https://roamingbytes.substack.com/p/emacs-on-macos-preserving-the-correct-environment-1735d2e8cb88

(defun gen-env-file (path)
  "Save envvars to a file at path"
  (let ((dirname (file-name-directory path)))
    (make-directory dirname t))
  (with-temp-file path
    (setq-local coding-system-to-write 'utf-8-unix)
    (insert
     ";; -*- mode: emacs-lisp -*-\n"
     ";; This file was automatically genereated and will be overwritten.\n")
    (insert (pp-to-string process-environment))))

(gen-env-file "~/.config/emacs/local/env.el")
emacs --quick --script $HOME/.config/emacs/scripts/gen-env-file.el

early-init.el

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

HACK Work around native compilation on macOS failing with ‘ld: library not found for -lemutlsw‘. https://github.com/d12frosted/homebrew-emacs-plus/issues/554

(setenv "LIBRARY_PATH"
	(string-join
	 '("/opt/homebrew/opt/gcc/lib/gcc/13"
	   "/opt/homebrew/opt/libgccjit/lib/gcc/13"
	   "/opt/homebrew/opt/gcc/lib/gcc/13/gcc/aarch64-apple-darwin23/13")
	 ":"))

Tame the GC.

(setq gc-cons-threshold 10000000)
(setq read-process-output-max (* 1024 1024 3) ; 3mb 

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)

https://emacs-lsp.github.io/lsp-mode/page/performance/#use-plists-for-deserialization

(setenv "LSP_USE_PLISTS" "true")

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)

init.el

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)
    (custom-set-faces
     '(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"))

Profilin’

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"
                               (float-time
				(time-subtract (current-time) before-init-time)))
                       gcs-done))))

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

Initdial Buffer Selection

Open emacs in the scratch buffer.

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

Load env file

(defun load-env-file (file)
  "Read and set envvars from FILE."
  (if (null (file-exists-p file))
      (signal 'file-error
              (list "No envvar file exists." file
                    "Run `emacs --script ~/.config/emacs/scripts/gen-env-file.el`."))
    (with-temp-buffer
      (insert-file-contents file)
      (when-let (env (read (current-buffer)))
        (let ((tz (getenv-internal "TZ")))
          (setq-default
           process-environment
           (append env (default-value 'process-environment))
           exec-path
           (append (split-string (getenv "PATH") path-separator t)
                   (list exec-directory))
           shell-file-name
           (or (getenv "SHELL")
               (default-value 'shell-file-name)))
          (when-let (newtz (getenv-internal "TZ"))
            (unless (equal tz newtz)
              (set-time-zone-rule newtz))))
        env))))

(load-env-file "~/.config/emacs/local/env.el")

Package Management With Elpaca

Elpaca

An elisp package manager

;; (setq elpaca-core-date '(20231211))
(defvar elpaca-installer-version 0.7)
(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-queue
 (elpaca keychain-environment
   (keychain-refresh-environment)))

Enable elpaca to act like use-package:

(elpaca elpaca-use-package
  (elpaca-use-package-mode)
  (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
     :ensure nil
     ,@args))

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'."
`(progn
   (cl-pushnew (quote ,name) elpaca-ignored-dependencies)
   (use-package ,name
     :ensure nil
     ,@args)))
(elpaca-wait)

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
    :config
    (add-hook 'after-init-hook #'benchmark-init/deactivate))
  (elpaca-wait))

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
  :demand
  :config
  (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"))
  (no-littering-theme-backups))
  (elpaca-wait)

Settings

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

Lines

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)
(global-auto-revert-mode)

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.

(savehist-mode)

Editor

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.

(delete-selection-mode)

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 '(
                            (?\" . ?\")
                            (?\{ . ?\})))

Visuals

Themes

Grab our theme from melpa

(use-package moe-theme
  :ensure t
  :init
  (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 ()
    (moe-light)
    (custom-set-faces '(fringe ((t :background "#9e9e9e" :foreground "#d7d7af")))))
  (defun fv--moe-dark ()
    (moe-light)
    (custom-set-faces '(fringe ((t :background "#9e9e9e" :foreground "#87875f")))))
  :config
  (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)

I’ve just discovered this gem called “stimmung-themes” and it’s even more metal than punpun.

(use-package stimmung-themes
  :demand t
  :ensure t
  :init
  (defvar fv--default-theme-light 'stimmung-light
    "The default light theme.")
  (defvar fv--default-theme-dark 'stimmung-dark
    "The default dark theme.")
  (defun fv--stimmung-light ()
    (stimmung-themes-load-light))
  (defun fv--stimmung-dark ()
    (stimmung-themes-load-dark))
  :config
  (setq stimmung-themes-comment 'foreground))
(use-package ef-themes
  :demand t
  :ensure t
  :init
  (defvar fv--default-theme-light 'ef-light
    "The default light theme.")
  (defvar fv--default-theme-dark 'ef-dark
    "The default dark theme.")
  (defun fv--ef-light ()
    (ef-themes-select 'ef-cyprus))
  (defun fv--ef-dark ()
    (ef-themes-select 'ef-elea-dark)))

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

 (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))
     ('stimmung-dark (fv--stimmung-dark))
     ('stimmung-light (fv--stimmung-light))
     ('ef-dark (fv--ef-dark))
     ('ef-light (fv--ef-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)

System

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!

(column-number-mode)
(global-display-line-numbers-mode t)
(setq  line-number-mode t
       column-number-mode t)
(dolist (mode '(org-mode-hook
                term-mode-hook
                eshell-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)
(pixel-scroll-precision-mode)

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

(set-face-attribute 'default nil :font "CozetteVector" :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)

Completion

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)
(display-time-mode)

Misc.

This is the unsorted / undocumented

(setq sentence-end-double-space nil)
(when (display-graphic-p)
  (context-menu-mode))

C-[   tangleno

The C-[ binding in Emacs defaults to the ESC keycode. This isn’t just an emacs thing, it’s a terminal thing. But it’s not like other default bindings, you can’t just unbind it, and if you try to weird stuff happens…

I found a solution in this config that should do the trick though (it doesn’t…)

(global-set-key (kbd "C-[") (kbd "ESC"))
(defun fv--fix-C-open-bracket (&optional frame)
  (with-selected-frame (or frame (selected-frame))
    (define-key input-decode-map [?\C-\[] (kbd "<C-[>"))))
(fv--fix-C-open-bracket)
(add-hook 'after-make-frame-functions 'fv--fix-C-open-bracket)

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))
"no"
     (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))
  (force-mode-line-update)
  ;; Apparently force-mode-line-update is not always enough to
  ;; redisplay the mode-line
  (redraw-display)
  (when (and (called-interactively-p 'interactive)
             hidden-mode-line-mode)
    (run-with-idle-timer
     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.

Hasty

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..

h.el

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

Packages

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.

<<seq-el>>
<<transient>>
<<general-el>>
<<which-key>>
(elpaca-wait)

ace-window

Quickly switch windows in emacs

Ace Window

(use-package ace-window
  :general
  ("C-x o" 'ace-window)
  ("M-x" 'ace-window)
  :config
  (setq aw-scope 'frame
        aw-ignore-current t
        aw-keys '(?a ?s ?d ?f ?g ?h ?j ?k ?l ?\;))
  (defun fv--set-other-window-scroll-buffer (window)
    "Set buffer in window to other-window-scroll-buffer"
    (setq other-window-scroll-buffer (window-buffer window)))
  (push '(?r fv--set-other-window-scroll-buffer "Set other-window-scroll-buffer")
        aw-dispatch-alist))

anzu   candy

https://github.com/emacsorphanage/anzu

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

amx

A better M-x I guess.

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

apheleia

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

apheleia

(use-package apheleia
  :defer t
  :custom
  (apheleia-mode-lighter nil)
  (js-indent-level 2)
  (standard-indent 2)
  :config
  ;; (require apheleia-formatters)
  ;; (dolist (formatter-cmds '((eslint . (npx "eslint" "--fix-to-stdout" "--stdin" "--stdin-filename" file))))
  ;;   (add-to-list #'apheleia-formatters formatter-cmds))
  ;;       (dolist (formatter-modes '((typescript-mode . apheleia-js-formatter)
  ;;                              (javascript-mode . apheleia-js-formatter)
  ;;                              (javascript-ts-mode . apheleia-js-formatter)
  ;;                              (typescript-ts-mode . apheleia-js-formatter)))
  ;;     (add-to-list #'apheleia-mode-alist formatter-modes))
  :init
  (apheleia-global-mode)
  ;; (defvar apheleia-js-formatter 'prettier)
  ;; (defun apheleia-toggle-js-formatter ()
  ;;   (interactive)
  ;;   (if (equal apheleia-js-formatter 'prettier)
  ;;       (setq apheleia-js-formatter 'eslint)
  ;;     (setq apheleia-js-formatter 'prettier))
  ;;   (dolist (formatter-modes '((typescript-mode . apheleia-js-formatter)
  ;;                              (javascript-mode . apheleia-js-formatter)
  ;;                              (javascript-ts-mode . apheleia-js-formatter)
  ;;                              (typescript-ts-mode . apheleia-js-formatter)))
  ;;     (add-to-list #'apheleia-mode-alist formatter-modes)))
  :general
  ("C-c f" 'apheleia-format-buffer))
  
  

avy

Jump to things in Emacs tree-style

Avy

https://www.youtube.com/watch?v=o_TlE_U_X3c

(use-package avy
  :commands (avy-goto-word-1 avy-goto-char-2 avy-goto-char-timer)
  :config
  (setq avy-timeout-seconds 0.20)
  (setq avy-keys '(?a ?s ?d ?f ?g ?j ?l ?o
                      ?v ?b ?n ?, ?/ ?u ?p ?e ?.
                      ?c ?q ?2 ?3 ?' ?\;))
  (setq avy-single-candidate-jump nil)
  (setq avy-dispatch-alist '((?m . avy-action-mark)
                             (?i . avy-action-ispell)
                             (?z . avy-action-zap-to-char)
                             (?  . avy-action-embark)
                             (?= . avy-action-define)
                             (67108896 . avy-action-mark-to-char)
                             (67108925 . avy-action-tuxi)
                             ;; (?W . avy-action-tuxi)
                             (?h . avy-action-helpful)
                             (?x . avy-action-exchange)
                             
                             (11 . avy-action-kill-line)
                             (25 . avy-action-yank-line)
                             
                             (?w . avy-action-easy-copy)
                             ;; (134217847  . avy-action-easy-copy)
                             (?k . avy-action-kill-stay)
                             (?y . avy-action-yank)
                             (?t . avy-action-teleport)
                             
                             (?W . avy-action-copy-whole-line)
                             (?K . avy-action-kill-whole-line)
                             (?Y . avy-action-yank-whole-line)
                             (?T . avy-action-teleport-whole-line)))
  
  ;; (advice-add 'avy-goto-char-timer :around
  ;;             (defun my/avy-with-single-candidate-jump (orig-fn &optional arg)
  ;;               (let ((avy-single-candidate-jump t))
  ;;                 (funcall orig-fn arg))))
  (defun avy-action-easy-copy (pt)
    (unless (require 'easy-kill nil t)
      (user-error "Easy Kill not found, please install."))
    (goto-char pt)
    (cl-letf (((symbol-function 'easy-kill-activate-keymap)
               (lambda ()
                 (let ((map (easy-kill-map)))
                   (set-transient-map
                    map
                    (lambda ()
                      ;; Prevent any error from activating the keymap forever.
                      (condition-case err
                          (or (and (not (easy-kill-exit-p this-command))
                                   (or (eq this-command
                                           (lookup-key map (this-single-command-keys)))
                                       (let ((cmd (key-binding
                                                   (this-single-command-keys) nil t)))
                                         (command-remapping cmd nil (list map)))))
                              (ignore
                               (easy-kill-destroy-candidate)
                               (unless (or (easy-kill-get mark) (easy-kill-exit-p this-command))
                                 (easy-kill-save-candidate))))
                        (error (message "%s:%s" this-command (error-message-string err))
                               nil)))
                    (lambda ()
                      (let ((dat (ring-ref avy-ring 0)))
                        (select-frame-set-input-focus
                         (window-frame (cdr dat)))
                        (select-window (cdr dat))
                        (goto-char (car dat)))))))))
      (easy-kill)))
  
  (defun avy-action-exchange (pt)
    "Exchange sexp at PT with the one at point."
    (set-mark pt)
    (transpose-sexps 0))
  
  (defun avy-action-helpful (pt)
    (save-excursion
      (goto-char pt)
      ;; (helpful-at-point)
      (my/describe-symbol-at-point)
      )
    (select-window
     (cdr (ring-ref avy-ring 0)))
    t)
  
  (defun avy-action-define (pt)
    (cl-letf (((symbol-function 'keyboard-quit)
               #'abort-recursive-edit))
      (save-excursion
        (goto-char pt)
        (dictionary-search-dwim))
      (select-window
       (cdr (ring-ref avy-ring 0))))
    t)
  
  (defun avy-action-tuxi (pt)
    (cl-letf (((symbol-function 'keyboard-quit)
               #'abort-recursive-edit))
      (save-excursion
        (goto-char pt)
        (google-search-at-point))
      (select-window
       (cdr (ring-ref avy-ring 0))))
    t)
  
  (defun avy-action-embark (pt)
    (unwind-protect
        (save-excursion
          (goto-char pt)
          (embark-act))
      (select-window
       (cdr (ring-ref avy-ring 0))))
    t)
  
  (defun avy-action-kill-line (pt)
    (save-excursion
      (goto-char pt)
      (kill-line))
    (select-window
     (cdr (ring-ref avy-ring 0)))
    t)
  
  (defun avy-action-copy-whole-line (pt)
    (save-excursion
      (goto-char pt)
      (cl-destructuring-bind (start . end)
          (bounds-of-thing-at-point 'line)
        (copy-region-as-kill start end)))
    (select-window
     (cdr
      (ring-ref avy-ring 0)))
    t)
  
  (defun avy-action-kill-whole-line (pt)
    (save-excursion
      (goto-char pt)
      (kill-whole-line))
    (select-window
     (cdr
      (ring-ref avy-ring 0)))
    t)
  
  (defun avy-action-yank-whole-line (pt)
    (avy-action-copy-whole-line pt)
    (save-excursion (yank))
    t)
  
  (defun avy-action-teleport-whole-line (pt)
    (avy-action-kill-whole-line pt)
    (save-excursion (yank)) t)
  
  (defun avy-action-mark-to-char (pt)
    (activate-mark)
    (goto-char pt))
  
  (defun my/avy-goto-char-this-window (&optional arg)
    "Goto char in this window with hints."
    (interactive "P")
    (let ((avy-all-windows t)
          (current-prefix-arg (if arg 4)))
      (call-interactively 'avy-goto-word-1)))
  
  (defun my/avy-isearch (&optional arg)
    "Goto isearch candidate in this window with hints."
    (interactive "P")
    (let ((avy-all-windows)
          (current-prefix-arg (if arg 4)))
      (call-interactively 'avy-isearch)))
  
  (defun my/avy--read-char-2 (char1 char2)
    "Read two characters from the minibuffer."
    (interactive (list (let ((c1 (read-char "char 1: " t)))
                         (if (memq c1 '(? ?\b))
                             (keyboard-quit)
                           c1))
                       (let ((c2 (read-char "char 2: " t)))
                         (cond ((eq c2 ?)
                                (keyboard-quit))
                               ((memq c2 '(8 127))
                                (keyboard-escape-quit)
                                (call-interactively 'my/avy-next-char-2))
                               (t
                                c2)))))

    (when (eq char1 ?
              ) (setq char1 ?\n))
    (when (eq char2 ?
              ) (setq char2 ?\n))
    (string char1 char2))

  (defun my/avy-next-char-2 (&optional str2 arg)
    "Go to the next occurrence of two characters"
    (interactive (list
                  (call-interactively 'my/avy--read-char-2)
                  current-prefix-arg))
    (let* ((ev last-command-event)
           (echo-keystrokes nil))
      (push-mark (point) t)
      (if (search-forward str2 nil t
                          (+ (if (looking-at (regexp-quote str2))
                                 1 0)
                             (or arg 1)))
          (backward-char 2)
        (pop-mark)))

    (set-transient-map
     (let ((map (make-sparse-keymap)))
       (define-key map (kbd ";") (lambda (&optional arg) (interactive)
                                   (my/avy-next-char-2 str2 arg)))
       (define-key map (kbd ",") (lambda (&optional arg) (interactive)
                                   (my/avy-previous-char-2 str2 arg)))
       map)))

  (defun my/avy-previous-char-2 (&optional str2 arg)
    "Go to the next occurrence of two characters"
    (interactive (list
                  (call-interactively 'my/avy--read-char-2)
                  current-prefix-arg))
    (let* ((ev last-command-event)
           (echo-keystrokes nil))
      (push-mark (point) t)
      (unless (search-backward str2 nil t (or arg 1))
        (pop-mark)))

    (set-transient-map
     (let ((map (make-sparse-keymap)))
       (define-key map (kbd ";") (lambda (&optional arg) (interactive)
                                   (my/avy-next-char-2 str2 arg)))
       (define-key map (kbd ",") (lambda (&optional arg) (interactive)
                                   (my/avy-previous-char-2 str2 arg)))
       map)))
  
  (defun my/avy-copy-line-no-prompt (arg)
    (interactive "p")
    (avy-copy-line arg)
    (beginning-of-line)
    (zap-to-char 1 32)
    (delete-forward-char 1)
    (move-end-of-line 1))

  (defun my/avy-link-hint (&optional win)
    "Find all visible buttons and links in window WIN and open one with Avy.

The current window is chosen if WIN is not specified." 
    (with-selected-window (or win
                              (setq win (selected-window)))
      (let* ((avy-single-candidate-jump t)
             match shr-buttons ov-buttons all-buttons)

        ;; SHR links
        (save-excursion
          (goto-char (window-start))
          (while (and
                  (<= (point) (window-end))
                  (setq match
                        (text-property-search-forward 'category 'shr t nil)))
            (let ((st (prop-match-beginning match)))
              (push
               `((,st . ,(1+ st)) . ,win)
               all-buttons))))

        ;; Collapsed sections
        (thread-last (overlays-in (window-start) (window-end))
                     (mapc (lambda (ov)
                             (when (or (overlay-get ov 'button)
                                       (eq (overlay-get ov 'face)
                                           'link))
                               (let ((st (overlay-start ov)))
                                 (push 
                                  `((,st . ,(1+ st)) . ,win)
                                  all-buttons))))))
        
        (when-let
            ((_ all-buttons) 
             (avy-action
              (lambda (pt)
                (goto-char pt)
                (let (b link)
                  (cond
                   ((and (setq b (button-at (1+ pt)))
                         (button-type b))
                    (button-activate b))
                   ((shr-url-at-point pt)
                    (shr-browse-url))
                   ((setq link (or (get-text-property pt 'shr-url)
                                   (thing-at-point 'url)))
                    (browse-url link)))))))
          (let ((cursor-type nil))
            (avy-process all-buttons))))))
  
  (custom-set-faces
   '(avy-lead-face
     ((((background dark))
       :foreground "LightCoral" :background "Black"
       :weight bold :underline t)
      (((background light))
       :foreground "DarkRed" :background unspecified :box (:line-width (1 . -1)) :height 0.95
       :weight bold)))
   '(avy-lead-face-0 ((t :background unspecified :inherit avy-lead-face)))
   '(avy-lead-face-1 ((t :background unspecified :inherit avy-lead-face)))
   '(avy-lead-face-2 ((t :background unspecified :inherit avy-lead-face))))

  ;; Jump to all paren types with [ and ]
  (advice-add 'avy-jump :filter-args
              (defun my/avy-jump-parens (args)
                (let ((new-regex
                       (my/avy-replace-syntax-class (car args))))
                  (cons new-regex (cdr args)))))

  (defun my/avy-replace-syntax-class (regex)
    (thread-last regex
                 (string-replace "\\[" "\\s(")
                 (string-replace "\\]" "\\s)")
                 (string-replace ";" "\\(?:;\\|:\\)")
                 (string-replace "'" "\\(?:'\\|\"\\)")))

  (defun my/avy-goto-char-timer (&optional arg)
    "Read one or many consecutive chars and jump to the first one.
The window scope is determined by `avy-all-windows' (ARG negates it).

This differs from Avy's goto-char-timer in how it processes parens."
    (interactive "P")
    (let ((avy-all-windows (if arg
                               (not avy-all-windows)
                             avy-all-windows)))
      (avy-with avy-goto-char-timer
        (setq avy--old-cands (avy--read-candidates
                              (lambda (str) (my/avy-replace-syntax-class
                                             (regexp-quote str)))))
        (avy-process avy--old-cands))))
  
  :general
  ("C-M-'"      'avy-resume
   "C-'"        '(my/avy-goto-char-this-window :wk "Avy goto char")
   "M-j"        '(my/avy-goto-char-timer       :wk "Avy goto char timer")
   "M-s y"      '(avy-copy-line                :wk "Avy copy line above")
   "M-s M-y"    '(avy-copy-region              :wk "Avy copy region above")
   "M-s M-k"    '(avy-kill-whole-line          :wk "Avy copy line as kill")
   "M-s j"      '(avy-goto-char-2              :wk "Avy goto char 2")
   "M-s M-p"    '(avy-goto-line-above          :wk "Avy goto line above")
   "M-s M-n"    '(avy-goto-line-below          :wk "Avy goto line below")
   "M-s C-w"    '(avy-kill-region              :wk "Avy kill region")
   "M-s M-w"    '(avy-kill-ring-save-region    :wk "Avy copy as kill")
   "M-s t"      '(avy-move-line                :wk "Avy move line")
   "M-s M-t"    '(avy-move-region              :wk "Avy move region")
   "M-s s"      '(my/avy-next-char-2           :wk "Avy snipe forward")
   "M-s r"      '(my/avy-previous-char-2       :wk "Avy snipe backward")
   "M-g l"      '(avy-goto-end-of-line         :wk "Avy goto line")
   "M-s z"      '(my/avy-copy-line-no-prompt   :wk "Avy copy and zap"))
  ;; (:states '(normal visual)
  ;;  :prefix "g"
  ;;  "s" 'avy-goto-char-timer)
  :bind (:map isearch-mode-map
              ("C-'" . my/avy-isearch)
              ("M-j" . my/avy-isearch)))

beacon   candy

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

bug-hunter

https://github.com/Malabarba/elisp-bug-hunter

(use-package bug-hunter)

burly.el

Save and restore frames and windows with their buffers in Emacs

Burly

(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.

Cape

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

change-inner

Emacs version of vim’s ci and co commands.

Change Inner

(use-package change-inner
  :general
  ("C-c i" '(change-inner :wk "Change inside surround")
   "C-c o" '(change-outer :wk "Delete surround")))
  

cider   lang clojure

The Clojure Interactive Development Environment that Rocks for Emacs

CIDER

(use-package cider)

clojure-mode   lang clojure

Emacs support for the Clojure(Script) programming language

clojure-mode

(use-package clojure-mode)

consult   core

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

Consult

(use-package consult
  :demand
  :init
  (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 ">")
  :bind
  (("C-c M-x" . consult-mode-command))
  :config
  (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
                 :preview-key
                 '("M-."
                   :debounce 0.5 "<up>" "<down>"
                   :debounce 1 any)))

consult-dir

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.

Corfu

(use-package corfu
  :ensure (corfu :files (:defaults "extensions/*")
		 :inclues (corfu-history
			   corfu-popupinfo))
  :defer 5
  :custom
  (corfu-auto nil)
  (corfu-auto-delay 0.1)
  (corfu-cycle t)
  (corfu-excluded-modes '(erc-mode
			 circe-mode
			 help-mode
			 gud-mode))
  (corfu-separator ?\s)
  (corfu-preview-current 'insert)
  (corfu-on-exact-match nil)
  (corfu-quit-on-boundary nil)
  (corfu-quit-no-match 'separator t)
  :config
  (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)
  (corfu-history-mode)
  (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
  :ensure (:type git
             :repo "https://code.bsdgeek.org/adam/corfu-candidate-overlay"
             :files (:defaults "*.el"))
  :after corfu
  :config
  (corfu-candidate-overlay-mode +1)
  (global-set-key (kbd "S-<tab>") #'corfu-complete))

crux

(use-package crux
  :general
  ("C-k" 'crux-smart-kill-line)
  ("C-RET" 'crux-smart-open-line-above)
  ("S-RET" 'crux-smart-open-line)
  ("C-c TAB" 'crux-cleanup-buffer-or-region)
  ("C-c D" 'crux-delete-file-and-buffer)
  ("C-c d" 'crux-duplicate-current-line-or-region)
  ("C-c M-d" 'crux-duplicate-and-comment-current-line-or-region))

delight   candy

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

Delight

(use-package delight
  :defer 10)

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.

Devdocs

(use-package devdocs
  :config
  (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.

Diminish

(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
	     :bind
	     (: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))
	     :custom
	     (dired-listing-switches "-l --almost-all --human-readable --time-style=long-iso --group-directories-first --no-group")
	     :config
	     (when (eq system-type 'darwin)
	       (setq insert-directory-program "gls")))
(use-builtin dired-x
	     :config
	     (setq dired-omit-files (concat dired-omit-files "\\|^\\..*$")))
(use-builtin dired-aux)
(use-builtin image-dired
	     :custom
	     (image-dired-thumb-size 256)
	     (image-dired-marking-shows-next nil)
	     :bind
	     (: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
  :hook
  ('dired-mode-hook #'diredfl-mode)
  :config
  (set-face-attribute 'diredfl-dir-name nil :bold t))

easy-kill

Kill and Mark things easily in Emacs

Easy Kill

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

EAT

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

EAT

(use-package eat
  :ensure (: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)
  ;; :general
  ;; ("M-o t" 'eat)
  ;; ("M-o T" 'eat-other-window)
  :config
  (eat-compile-terminfo))

eglot   tangleno 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))
  :init
  (setq completion-category-overrides '((eglot (styles orderless))))
  (advice-add 'eglot-completion-at-point :around #'cape-wrap-buster)
  :custom
  (eglot-send-change-idle-time 0.1)
  (eglot-autoshutdown t)
  (eglot-extend-to-xref t)
  (eglot-ignored-server-capabilities
   '(:documentHighlightProvider
     :documentFormattingProvider
     :documentRangeFormattingProvider
     :documentOnTypeFormattingProvider
     :colorProvider))
  (eglot-workspace-configuration
   '(:typescript-language-server
     (: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/"))
		])))
  :config
  (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)))
	    (cond
	     ((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))
	  '(orderless))
    (make-local-variable 'orderless-style-dispatchers)
    (cl-pushnew (fv--orderless-dispatch-passthrough-first)
		orderless-style-dispatchers)
    (setq-local completion-at-point-functions
		(cl-nsubst (cape-capf-buster #'eglot-completion-at-point)
			   'eglot-completion-at-point
			   completion-at-point-functions)))
  
  (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
               (cl-substitute-if
                (cons
                 '(js-mode js-ts-mode
                           tsx-ts-mode typescript-ts-mode typescript-mode)
                 '("typescript-language-server" "--stdio" :initializationOptions
                   (:preferences
                    (
                     ;; 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)
                  (and
                   (listp (car server-program))
                   (member 'tsx-ts-mode (car server-program))))
                eglot-server-programs))

  (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 ()
    (interactive)
    (if (derived-mode-p major-mode #'typescript-ts-base-mode)
        (seq-do
         (lambda (kind) (interactive)
           (ignore-errors
             (eglot-code-actions (buffer-end 0) (buffer-end 1) kind t)))
         ;; https://github.com/typescript-language-server/typescript-language-server#code-actions-on-save
         (list
          "source.addMissingImports.ts"
          "source.fixAll.ts"
          "source.addMissingImports.ts"
          "source.removeUnusedImports.ts"))
      (funcall-interactively #'eglot-code-action-organize-imports))))

eglot-tempel   tangleno

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

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

eldoc-box   candy

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

embark   core

https://github.com/oantolin/embark

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

embark-consult

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

embrace.el

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

Embrace

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
  :general
  ("C-c s" '(embrace-commander :wk "Embrace")
   "C-c C-s a" '(embrace-add :wk "Add surround")
   "C-c C-s d" '(embrace-delete :wk "Delete surround")
   "C-c C-s c" '(embrace-change :wk "Change surround")))
  

emmet-mode

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

esup

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

evil-nerd-commenter

(use-package evil-nerd-commenter
  :general
  ("M-;" 'evilnc-comment-or-uncomment-lines))

exec-path-from-shell

(use-package exec-path-from-shell
  :init
  (when (memq window-system '(mac ns x))
    (exec-path-from-shell-initialize)))

expand-region

Emacs extension to increase selected region by semantic units.

Expand Region

(use-package expand-region
  :commands expand-region
  :general
  ("C-," 'er/expand-region)
  :config
  (add-to-list 'expand-region-exclude-text-mode-expansions 'org-mode)
  (set-default 'er--show-expansion-message nil)
  (setq expand-region-show-usage-message nil
        expand-region-fast-keys-enabled nil)
  (defvar expand-region-repeat-map
    (let ((map (make-sparse-keymap)))
      (define-key map "," #'er/expand-region)
      (define-key map "." #'er/contract-region)
      (dotimes (i 10)
        (define-key map
                    (kbd (number-to-string i))
                    (lambda ()
                      (interactive)
                      (er/expand-region i)
                      (setq this-command 'er/expand-region))))
      map))
  (put 'er/expand-region 'repeat-map 'expand-region-repeat-map)
  (put 'er/contract-region 'repeat-map 'expand-region-repeat-map)
  (advice-add 'er--first-invocation
              :override
              (defun fv--er--first-invocation ()
                "true if this is the first invocation of er/expand-region or er/contract-region"
                (not (memq last-command
                           '(er/expand-region
                             er/contract-region
                             easy-kill-expand-region
                             easy-kill-contract-region))))))

forge

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

Forge

(use-package forge
  :after magit)

general.el

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

(use-package general
  :demand t
  :config
  (general-override-mode)
  (general-auto-unbind-keys))

goggles   candy

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

highlight-parenthes

(use-package highlight-parentheses
  :demand t
  :ensure t
  :hook
  ((prog-mode . highlight-parentheses-mode)
   (minibuffer-setup . highlight-parentheses-mode)))

helpful   core

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

Helpful

(use-package helpful
  :custom (elisp-refs-verbose nil)
  :bind
  (("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

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

Jinx

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

kind-icon

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

Kind Icon

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

orderless

magit   core

(use-package magit
  :defer t
  :config
  (transient-bind-q-to-quit))

marginalia   core

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

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
  :custom
  (mood-line-glyph-alist mood-line-glyphs-fira-code)
  :config
  (mood-line-mode))

nov.el

(use-package nov
  :mode (("\\.epub\\'" . nov-mode))
  :hook ((nov-mode . view-mode)))

orderless   core

(use-package orderless
  :demand t
  :config
  (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-char consult--tofu-range -1))
      "$"))

  (defun +orderless-consult-dispatch (word _index _total)
    (cond
     ((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
      				    #'orderless-affix-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
  :hook
  ('org-mode-hook (function fv--hidden-block))
  :mode
  ("\\.org\\'" . org-mode)

Config Preamble

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

:config

Defaults

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-journal-directory (concat org-directory "/journal"))
(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)

Keywords

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

Tags

(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/life.org" "Inbox")
         "* TODO %? %(org-set-tags-command) \nCREATED: %U\n" :clock-in t :clock-resume t)
        ("T" "Task @ cursor" entry (file+headline "~/org/life.org" "Inbox")
         "* TODO %? %(org-set-tags-command) \nCREATED: %U\n%l\n" :clock-in t :clock-resume t)
        ("i" "Ideas" entry (file+headline "~/org/life.org" "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/life.org" "Inbox")
         "* NOTE %?\nCREATED: %U\n%l\n" :clock-in t :clock-resume t)
        ("a" "Appointment" entry (file+headline "~/org/life.org" "Inbox")
         "* APPT %? %^gAPPOINTMENT: \nSCHEDULED: %^T\nCREATED: %U\n" :clock-in t :clock-resume t)
        ("m" "Meeting" entry (file+headline "~/org/life.org" "Meeting")
         "* MEET with %? :MEETING:\nCREATED: %U\n" :clock-in t :clock-resume t)
        ("w" "Weekly Plan" entry (file+olp+datetree "~/org/weekly.org")
         "* 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 ()
  (interactive)
  (save-excursion
    (beginning-of-line 0)
    (org-remove-empty-drawer-at (point))))

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

Agenda

(require 'org-agenda)
(setq org-agenda-files '("~/org/life.org")
      ;; org-agenda-files (apply 'append
      ;;                         (mapcar
      ;;                          (lambda (directory)
      ;;                            (directory-files-recursively
  	;; 			directory org-agenda-file-regexp))
      ;;                          (list org-directory)))
      org-agenda-start-with-log-mode t)

Clocking

;;
;; Resume clocking task when emacs is restarted
(org-clock-persistence-insinuate)
;;
;; 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

Source

(setq bh/keep-clock-running nil)

(defun bh/find-project-task ()
  "Move point to the parent (project) task if any"
  (save-restriction
    (widen)
    (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)
      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))
          (bh/clock-in-organization-task-as-default)))
    ;;
    ;; We are not in the agenda
    ;;
    (save-restriction
      (widen)
      ; 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))
        (bh/clock-in-organization-task-as-default)))))

(defun bh/punch-out ()
  (interactive)
  (setq bh/keep-clock-running nil)
  (when (org-clock-is-active)
    (org-clock-out))
  (org-agenda-remove-restriction-lock))

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

(defun bh/clock-in-parent-task ()
  "Move point to the parent (project) task if any and clock in"
  (let ((parent-task))
    (save-excursion
      (save-restriction
        (widen)
        (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
              (org-clock-in))
          (when bh/keep-clock-running
            (bh/clock-in-default-task)))))))

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

(defun bh/clock-in-organization-task-as-default ()
  (interactive)
  (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))
    (bh/clock-in-parent-task)))

(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
         (cond
          ((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)))))
    (widen)
    (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"
  (save-restriction
    (widen)
    (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)))
      (save-excursion
        (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)
                              (point))))
    (save-excursion
      (bh/find-project-task)
      (if (equal (point) task)
          nil
        t))))

(defun bh/is-task-p ()
  "Any task with a todo keyword and no subtask"
  (save-restriction
    (widen)
    (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)))
      (save-excursion
        (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)))
    (save-excursion
      (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))
  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))
  nil)

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

(defun bh/toggle-next-task-display ()
  (interactive)
  (setq bh/hide-scheduled-and-waiting-next-tasks (not bh/hide-scheduled-and-waiting-next-tasks))
  (when  (equal major-mode 'org-agenda-mode)
    (org-agenda-redo))
  (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"
  (save-restriction
    (widen)
    (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 ))
            (save-excursion
              (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
              next-headline)) ; a stuck project, has subtasks but no next task
        nil))))

(defun bh/skip-non-stuck-projects ()
  "Skip trees that are not stuck projects"
  ;; (bh/list-sublevels-for-projects-indented)
  (save-restriction
    (widen)
    (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 ))
            (save-excursion
              (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
              nil)) ; a stuck project, has subtasks but no next task
        next-headline))))

(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))
      (save-restriction
        (widen)
        (let ((subtree-end (save-excursion (org-end-of-subtree t))))
          (cond
           ((bh/is-project-p)
            nil)
           ((and (bh/is-project-subtree-p) (not (bh/is-task-p)))
            nil)
           (t
            subtree-end))))
    (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."
  (save-restriction
    (widen)
    (let ((next-headline (save-excursion (or (outline-next-heading) (point-max)))))
      (cond
       ((bh/is-task-p)
        nil)
       (t
        next-headline)))))

(defun bh/skip-project-trees-and-habits ()
  "Skip trees that are projects"
  (save-restriction
    (widen)
    (let ((subtree-end (save-excursion (org-end-of-subtree t))))
      (cond
       ((bh/is-project-p)
        subtree-end)
       ((org-is-habit-p)
        subtree-end)
       (t
        nil)))))

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

(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."
  (save-restriction
    (widen)
    (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)))
      (cond
       ((bh/is-project-p)
        next-headline)
       ((org-is-habit-p)
        subtree-end)
       ((and (not limit-to-project)
             (bh/is-project-subtree-p))
        subtree-end)
       ((and limit-to-project
             (bh/is-project-subtree-p)
             (member (org-get-todo-state) (list "NEXT")))
        subtree-end)
       (t
        nil)))))

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

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

(defun bh/skip-projects-and-habits ()
  "Skip trees that are projects and tasks that are habits"
  (save-restriction
    (widen)
    (let ((subtree-end (save-excursion (org-end-of-subtree t))))
      (cond
       ((bh/is-project-p)
        subtree-end)
       ((org-is-habit-p)
        subtree-end)
       (t
        nil)))))

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

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

Keybindings

:general
("C-x c" 'org-capture
"C-c l" 'org-store-link
"C-c a" 'org-agenda)

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
   `default-directory'."
  (expand-file-name sub-path
                    (or
                     (org-entry-get (point) "tangle-dir" 'inherit)
                     (default-directory))))

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.

;;(require 'org-capture)
(defun fv--create-new-blog-buffer ()
  "Created a new blog from the specified template in a new buffer"
  (interactive)
  (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 "
               (replace-regexp-in-string
                "\\." " dot "
                (replace-regexp-in-string
                 "\\+" " 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)))
    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…

;;;###autoload
(defun fv--hidden-block ()
  "Fold some blocks in the current buffer."
  (interactive)
  (org-show-block-all)
  (org-block-map
   (lambda ()
     (let ((case-fold-search t))
       (when (and
              (save-excursion
                (beginning-of-line 1)
                (looking-at org-block-regexp))
              (cl-assoc
               ':hidden
               (cl-third
                (org-babel-get-src-block-info))))
         (org-hide-block-toggle)))))
  (org-mode))

org-appear

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

org-appear

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

org-contrib

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

org-journal

(use-package org-journal
  :after org
  :init
  (setq org-journal-prefix-key "C-c j")
  :config
  (setq org-journal-dir org-journal-directory
        org-journal-file-type 'daily))

ob-mermaid

(use-package ob-mermaid
  :after org
  :init
  (setq ob-mermaid-cli-path "~/.asdf/shims/mmdc")
  (org-babel-do-load-languages
   'org-babel-load-languages
   '((mermaid .t))))

persistent-scratch

Preserve the scratch buffer across Emacs sessions.

Persistent Scratch

(use-package persistent-scratch
  :demand
  :config
  (persistent-scratch-setup-default))

perspective.el

(use-package perspective
  :after (consult)
  :general
  ;; ("C-x C-b" 'persp-counsel-switch-buffer)
  ;; ("C-x b" 'persp-switch-to-buffer*)
  ("C-x k" 'persp-kill-buffer*)
  :custom
  (persp-mode-prefix-key (kbd "C-z"))
  :init
  (persp-mode)
  (consult-customize consult--source-buffer :hidden t :default nil)
  (add-to-list 'consult-buffer-sources persp-consult-source))

rainbow-delimiters

(use-package rainbow-delimiters
  :demand t
  :ensure t
  :hook
  ((prog-mode . rainbow-delimiters-mode)
   (minibuffer-setup . rainbow-delimiters-mode)))

repeat-mode

(use-builtin repeat-mode
             :general ("s-r repeat")
             :hook (after-init . repeat-mode)
             :config
             (setq repeat-keep-prefix t
                   repeat-echo-function #'repeat-echo-mode-line
                   repeat-echo-mode-line-string (propertize "[R]" 'face 'mode-line-emphasis)))

request.el

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

(use-package request)

seq.el

This ensures we’re using the latest version of seq and not the one bundled with Emacs. This is important as some packages, like magit, want the dev version of this built-in.

(defun +elpaca-unload-seq (e)
  (and (featurep 'seq) (unload-feature 'seq t))
  (elpaca--continue-build e))

;; You could embed this code directly in the reicpe, I just abstracted it into a function.
(defun +elpaca-seq-build-steps ()
  (append (butlast (if (file-exists-p (expand-file-name "seq" elpaca-builds-directory))
                       elpaca--pre-built-steps elpaca-build-steps))
          (list '+elpaca-unload-seq 'elpaca--activate-package)))
(use-package seq :ensure `(seq :build ,(+elpaca-seq-build-steps)))

sly

(use-package sly
  :defer
  :init
  (setq inferior-lisp-program "sbcl"))

sly-overlay

(use-package sly-overlay
  :general
  (:keymaps 'sly-mode-map
            "C-M-x" 'sly-overlay-eval-defun
            ))

sly-quicklisp

(use-package sly-quicklisp)

sly-asdf   tangleno

(use-package sly-asdf
  :after sly
  :ensure
  :init
  (add-to-list 'sly-contribs 'sly-asdf 'append))

sly-macrosetp

(use-package sly-macrostep)

sly-reply-ansi-color

(use-package sly-repl-ansi-color
  :init
  (add-to-list 'sly-contribs 'sly-repl-ansi-color 'append))

tempel

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

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

(use-package tempel-collection
  :after tempel)

These are my tempel templates.

;; ~/.config/fv/templates

transient

This ensures we’re using the latest version of seq and not the one bundled with Emacs. This is important as some packages, like magit, want the dev version of this built-in.

(defun +elpaca-unload-transient (e)
  (and (featurep 'transient) (unload-feature 'transient t))
  (elpaca--continue-build e))

;; You could embed this code directly in the reicpe, I just abstracted it into a function.
(defun +elpaca-transient-build-steps ()
  (append (butlast (if (file-exists-p (expand-file-name "transient" elpaca-builds-directory))
                       elpaca--pre-built-steps elpaca-build-steps))
          (list '+elpaca-unload-transient 'elpaca--activate-package)))
(use-package seq :ensure `(transient :build ,(+elpaca-transient-build-steps)))

treesit   builtin

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

treesit-auto

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)
  :config
  (setq treesit-auto-install 'prompt)
  (global-treesit-auto-mode))

Grammars

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.

Astro

(use-package astro-ts-mode
  :after (treesit-auto)
  :config
  (setq astro-ts-auto-recipe
        (make-treesit-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
  :config
  (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
  :ensure (vertico :files (:defaults "extensions/*"))
  :custom
  (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))
  :hook
  ('rfn-eshadow-update-overlay #'vertico-directory-tidy)
  :init
  (vertico-mode))

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.

which-key

(use-package which-key
  :demand t
  :config
  (which-key-mode)
  :custom
  (which-key-side-window-location 'bottom)
  (which-key-sort-order 'which-key-key-order-alpha)
  ;;(which-key-idle-delay 0.1/)
  :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
  :bind
  ("M-g w" . 'wiki-summary))

Test Packages

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

lsp-mode

(use-package lsp-mode
  :init
  (setq lsp-keymap-prefix "C-c C-c")
  :custom
  (lsp-log-io nil) ; ensure this is off when not debugging
  (lsp-completion-backend :none)
  :init
  (defun fv--lsp-mode-capf-setup ()
    (setf (alist-get 'styles (alist-get 'lsp-capf completion-category-defaults))
          '(flex)))
  :hook ((lsp-mode . fv--lsp-mode-capf-setup)
         (lsp-mode . lsp-enable-which-key-integration))
  :commands (lsp lsp-deferred))
(use-package lsp-ui
  :commands lsp-ui-mode)

treesitter   tangleno

(use-package tree-sitter
  :hook (tree-sitter-after-on-hook . tree-sitter-hl-mode)
  :init (global-tree-sitter-mode))
(use-package tree-sitter-langs)

org-clock-convenience

https://github.com/dfeich/org-clock-convenience

(use-package org-clock-convenience
  :ensure t
  :after org
  :general
  ('org-agenda-mode-map
   ("s-p" 'org-clock-convenience-timestamp-up)
   ("s-n" 'org-clock-convenience-timestamp-down)
   ("o" 'org-clock-convenience-fill-gap)
   ("e" 'org-clock-convenience-fill-gap-both)))

lispy

(use-package lispy
  :init
  (defun conditionally-enable-lispy ()
    (when (eq this-command 'eval-expression)
      (lispy-mode 1)))
  :hook
  ((lisp-mode . lispy-mode)
   (minibuffer-setup . conditionally-enable-lispy)))

puni   tangleno

(use-package puni
          :hook ((lisp-mode . puni-mode))
          :general
          ('puni-mode-map
           ("(" 'puni-wrap-round)
           ("[" 'puni-wrap-square)
           ("<" 'puni-wrap-angle)
           ("{" 'puni-wrap-curly)
           ;; Sexp nav
           ("C-M-f" 'puni-forward-sexp-or-up-list)
           ("C-M-b" 'puni-backward-sexp-or-up-list)
           ("C-M-a" 'puni-beginning-of-sexp)
           ("C-M-e" 'puni-end-of-sexp)
           ("C-M-t" 'puni-transpose)
           ;; Barf
           ("s-f" 'puni-barf-forward)
           ("M-]" 'puni-barf-forward)
           ("s-b" 'puni-barf-backward)
           ("M-[" 'puni-barf--backward)
           ;;Slurp
           ("M-s-f" 'puni-slurp-forward)
           ("C-)" 'puni-slurp-forward)
           ("M-s-b" 'puni-slurp-backward)
           ("C-(" 'puni-slurp-backward)
           ;; Other
           ("M-p" 'puni-raise)
           ("M-n" 'puni-splice)
           ("M-s-p" 'puni-splice-killing-forward)
           ("M-s-n" 'puni-splice-killing-backward)))

paredit   tangleno

(use-package paredit
  :hook (lisp-mode enable-paredit-mode))

bufler.el   tangleno

(use-package bufler
  :init
  (bufler-mode)
  :general
  ("C-x b" 'bufler-switch-buffer)
  )

combobulate   tangleno

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

Combobulate

(use-package combobulate
  :ensure (:host github :repo "mickeynp/combobulate")
  :custom
  (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))
  )

hydra   tangleno

This is an experiment using hydra, not sure if I’ll keep it around though.

keyfunctioncolumn
<lispy-barf
Alispy-beginning-of-defun
jlispy-down
Zlispy-edebug-stop
Blispy-ediff-regions
Glispy-goto-local
hlispy-left
Nlispy-narrow
ylispy-occur
olispy-other-mode
Jlispy-outline-next
Klispy-outline-prev
Plispy-paste
llispy-right
Ilispy-shifttab
>lispy-slurp
SPClispy-space
xBlispy-store-region-and-buffer
ulispy-undo
klispy-up
vlispy-view
Vlispy-visit
Wlispy-widen
Dpop-tag-mark
xsee
Lunbound
Uunbound
Xunbound
Yunbound
Hlispy-ace-symbol-replaceEdit
clispy-cloneEdit
Clispy-convoluteEdit
nlispy-new-copyEdit
Olispy-onelineEdit
rlispy-raiseEdit
Rlispy-raise-someEdit
\lispy-spliceEdit
Slispy-stringifyEdit
ilispy-tabEdit
xjlispy-debug-step-inEval
xelispy-edebugEval
xTlispy-ertEval
elispy-evalEval
Elispy-eval-and-insertEval
xrlispy-eval-and-replaceEval
plispy-eval-other-windowEval
qlispy-ace-parenMove
zlispy-knightMove
slispy-move-downMove
wlispy-move-upMove
tlispy-teleportMove
Qlispy-ace-charNav
lispy-ace-subwordNav
alispy-ace-symbolNav
blispy-backNav
dlispy-differentNav
flispy-flowNav
Flispy-followNav
glispy-gotoNav
xblispy-bind-variableRefactor
xflispy-flattenRefactor
xclispy-to-condRefactor
xdlispy-to-defunRefactor
xilispy-to-ifsRefactor
xllispy-to-lambdaRefactor
xulispy-unbind-variableRefactor
Mlispy-multilineOther
xhlispy-describeOther
mlispy-mark-listOther
(use-package hydra
  :ensure t
  :config
  (eval
   (append
    '(defhydra my/lispy-cheat-sheet (:hint nil :foreign-keys run)
       ("<f10>" nil :exit t))
    (cl-loop for x in bindings
             unless (string= "" (elt x 2))
             collect
             (list (car x)
                   (intern (elt x 1))
                   (when (string-match "lispy-\\(?:eval-\\)?\\(.+\\)"
                                       (elt x 1))
                     (match-string 1 (elt x 1)))
                   :column
                   (elt x 2)))))
  (with-eval-after-load "lispy"
    (define-key lispy-mode-map (kbd "<f10>") 'my/lispy-cheat-sheet/body)))

meow-mode   tangleno

(use-package meow
  :demand t
  :init
  <<meow-init>>
  :config
  (meow-setup)
  (meow-global-mode))

Init

  (defun meow-setup ()
    ;; -------------------- ;;
;;      THING TABLE     ;;
;; -------------------- ;;
(meow-thing-register 'angle
                     '(pair ("<") (">"))
                     '(pair ("<") (">")))

(setq meow-char-thing-table
      '((?f . round)
        (?d . square)
        (?s . curly)
        (?a . angle)
        (?r . string)
        (?v . paragraph)
        (?c . line)
        (?x . buffer)))

;; -------------------- ;;
;;       MAPPINGS       ;;
;; -------------------- ;;
(meow-define-keys 'normal
 ; expansion
 '("0" . meow-expand-0)
 '("1" . meow-expand-1)
 '("2" . meow-expand-2)
 '("3" . meow-expand-3)
 '("4" . meow-expand-4)
 '("5" . meow-expand-5)
 '("6" . meow-expand-6)
 '("7" . meow-expand-7)
 '("8" . meow-expand-8)
 '("9" . meow-expand-9)
 '("'" . meow-reverse)

 ; movement
 '("i" . meow-prev)
 '("k" . meow-next)
 '("j" . meow-left)
 '("l" . meow-right)

 '("y" . meow-search)
 '("/" . meow-visit)

 ; expansion
 '("I" . meow-prev-expand)
 '("K" . meow-next-expand)
 '("J" . meow-left-expand)
 '("L" . meow-right-expand)

 '("u" . meow-back-word)
 '("U" . meow-back-symbol)
 '("o" . meow-next-word)
 '("O" . meow-next-symbol)

 '("a" . meow-mark-word)
 '("A" . meow-mark-symbol)
 '("s" . meow-line)
 '("S" . meow-goto-line)
 '("w" . meow-block)
 '("q" . meow-join)
 '("g" . meow-grab)
 '("G" . meow-pop-grab)
 '("m" . meow-swap-grab)
 '("M" . meow-sync-grab)
 '("p" . meow-cancel-selection)
 '("P" . meow-pop-selection)

 '("x" . meow-till)
 '("z" . meow-find)

 '("," . meow-beginning-of-thing)
 '("." . meow-end-of-thing)
 '("<" . meow-inner-of-thing)
 '(">" . meow-bounds-of-thing)

 ; editing
 '("d" . meow-kill)
 '("f" . meow-change)
 '("t" . meow-delete)
 '("c" . meow-save)
 '("v" . meow-yank)
 '("V" . meow-yank-pop)

 '("e" . meow-insert)
 '("E" . meow-open-above)
 '("r" . meow-append)
 '("R" . meow-open-below)

 '("h" . undo-only)
 '("H" . undo-redo)

 '("b" . open-line)
 '("B" . split-line)

 '("[" . indent-rigidly-left-to-tab-stop)
 '("]" . indent-rigidly-right-to-tab-stop)

 ; prefix n
 '("nf" . meow-comment)
 '("nt" . meow-start-kmacro-or-insert-counter)
 '("nr" . meow-start-kmacro)
 '("ne" . meow-end-or-call-kmacro)
 ;; ...etc

 ; prefix ;
 '(";f" . save-buffer)
 '(";F" . save-some-buffers)
 '(";d" . meow-query-replace-regexp)
 ;; ... etc

 ; ignore escape
 '("<escape>" . ignore))
(require 'expand-region)
(setq er/try-expand-list
    '(er/mark-inside-quotes
      er/mark-outside-quotes
      er/mark-inside-pairs
      er/mark-outside-pairs))
(setq meow-cheatsheet-layout meow-cheatsheet-layout-qwerty))

Archive   ARCHIVE

vterm   tangleno

(use-package vterm
  :ensure (vterm :post-build
                 (progn
                   (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)
                                                  (line-end-position)))
                       (forward-line)))
                   (when-let ((so (expand-file-name "./vterm-module.so"))
                              ((file-exists-p so)))
                     (make-symbolic-link
                      so (expand-file-name (file-name-nondirectory so)
                                           "../../builds/vterm")
                      'ok-if-already-exists))))
  :commands (vterm vterm-other-window)
  :custom
  (vterm-max-scrollback 100000)
  (vterm-buffer-name "vterm")
  :general
  ("M-o t" 'vterm)
  ("M-o T" 'vterm-other-window)
  :config
  (setq vterm-shell (executable-find "zsh")))