#+TITLE:  General Emacs Configuration
#+AUTHOR: Howard X. Abrams
#+DATE:   2020-09-10
#+FILETAGS: emacs
#+STARTUP: inlineimages

A literate programming file for configuring Emacs.

#+begin_src emacs-lisp :exports none
  ;;; ha-config --- Emacs configuration. -*- lexical-binding: t; -*-
  ;;
  ;; © 2020-2023 Howard X. Abrams
  ;;   Licensed under a Creative Commons Attribution 4.0 International License.
  ;;   See http://creativecommons.org/licenses/by/4.0/
  ;;
  ;; Author: Howard X. Abrams <http://gitlab.com/howardabrams>
  ;; Maintainer: Howard X. Abrams
  ;; Created: September 10, 2020
  ;;
  ;; This file is not part of GNU Emacs.
  ;;
  ;; *NB:* Do not edit this file. Instead, edit the original literate file at:
  ;;          ~/src/hamacs/ha-config.org
  ;;       Using `find-file-at-point', and tangle the file to recreate this.
  ;;
  ;;; Commentary:
  ;;
  ;; Basic Emacs configuration settings, ran near the beginning of the
  ;; loading sequence.
  ;;
  ;;; Code:

  ;; Used functions defined elsewhere:
  (defun font-icons (collection label &rest args)
    (or (plist-get args :title) label))
#+end_src
* Basic Configuration
I begin configuration of Emacs that isn’t /package-specific/.

I hate to fat-finger a single letter that could stop Emacs:
#+begin_src emacs-lisp
  (setq confirm-kill-emacs 'yes-or-no-p)
#+end_src

I like the rendering of curved quotes using [[help:text-quoting-style][text-quoting-style]], because it improves the readability of documentation strings in the =∗Help∗= buffer and whatnot.
#+begin_src emacs-lisp
  (setq text-quoting-style 'curve
        truncate-string-ellipsis "…")
#+end_src
When typing prose in Org documents, I need to [[file:ha-org-word-processor.org::*Typographic Quotes][do something else]] for rounded quotes and ellipsis.

Changes and settings I like introduced in Emacs 28:

#+begin_src emacs-lisp
  (setq use-short-answers t
        describe-bindings-outline t
        completions-detailed t)
#+end_src

I’ve got preferences for how I like scrolling, and with my org files, I need a little more of the of the context, so this increases from =2= to =3=, but I like to keep the cursor in place when I can:

#+begin_src emacs-lisp
  (setq next-screen-context-lines 30
            scroll-error-top-bottom t
            scroll-preserve-screen-position t)
#+end_src

Emacs has some new code to display line-numbers, and the =visual= value works well with my Org files, allowing a jump to a line via ~6 j~:
#+begin_src emacs-lisp
  (setq display-line-numbers-type 'visual)
#+end_src

But sometimes we want to jump to /absolute/ line numbers, so I have a toggling function:
#+begin_src emacs-lisp
  (defun ha-toggle-relative-line-numbers ()
    "Change line numbering from relative to visual to off."
    (interactive)
    (cond
     ((null display-line-numbers)  (display-line-numbers-mode))
     ((or (eq display-line-numbers 'relative)
          (eq display-line-numbers 'visual))
      (setq display-line-numbers t))
     (t   (display-line-numbers-mode -1))))
#+end_src

In Emacs version 28, we can hide commands in ~M-x~ which do not apply to the current mode.
#+begin_src emacs-lisp
  (setq read-extended-command-predicate
        #'command-completion-default-include-p)
#+end_src
Before that, we used ~M-X~ (capital ~X~).

When I get an error, I need a stack trace to figure out the problem. Yeah, when I stop fiddling with Emacs, this should go off:
#+begin_src emacs-lisp
  (setq debug-on-error t)
#+end_src

I like being able to enable local variables in =.dir-local.el= files:
#+begin_src emacs-lisp
  (setq enable-local-variables t)
#+end_src

And some Mac-specific settings:
#+begin_src emacs-lisp
  (when (ha-running-on-macos?)
    (setq mac-option-modifier 'meta
          mac-command-modifier 'super)
    (add-to-list 'default-frame-alist '(ns-transparent-titlebar . t))
    (add-to-list 'default-frame-alist '(ns-appearance . dark)))
#+end_src

** Make C-g a little more useful

Protesilaos has a simple fix for [[https://protesilaos.com/codelog/2024-11-28-basic-emacs-configuration/#h:1e468b2a-9bee-4571-8454-e3f5462d9321][closing minibuffer with C-g]].  His explanation:

#+begin_quote
Do-What-I-Mean behaviour for a general keyboard-quit. The generic keyboard-quit does not do the expected thing when the minibuffer is open. Whereas we want it to close the minibuffer, even without explicitly focusing it. The DWIM behaviour of this command is as follows:

  - When the region is active, disable it.
  - When a minibuffer is open, but not focused, close the minibuffer.
  - When the Completions buffer is selected, close it.
  - In every other case use the regular keyboard-quit.
#+end_quote

And my copy of his code:

#+begin_src emacs-lisp
  (defun keyboard-quit-dwim ()
    "Cancel everything, even in the mini-buffer."
    (interactive)
    (cond
     ((region-active-p)
      (keyboard-quit))
     ((derived-mode-p 'completion-list-mode)
      (delete-completion-window))
     ((> (minibuffer-depth) 0)
      (abort-recursive-edit))
     (t
      (keyboard-quit))))

  (define-key global-map (kbd "C-g") #'keyboard-quit-dwim)
#+end_src

** Emacs Everywhere
After reading [[https://irreal.org/blog/?p=12139][Jon Sander’s essay]] as well as [[https://mbork.pl/2024-04-27_Emacs_everywhere][Marcin Borkowski's essay]], I decided to try out Tecosaur’s [[https://github.com/tecosaur/emacs-everywhere][Emacs Everywhere]] approach:

#+begin_src emacs-lisp
  (use-package emacs-everywhere
    :straight (:host github :repo "tecosaur/emacs-everywhere"))
#+end_src

This package /called outside of Emacs/, so I bound a keybinding to iCanHazShortcut:

#+begin_src sh
  emacsclient --socket-name personal --eval "(emacs-everywhere)"
#+end_src

When you type ~C-c C-c~ to close a window, it /doesn’t always/ paste back into the original window, but the text is saved to the clipboard, a quick paste works. And now, I don’t scream when I need to use those Electron apps, like Slack and Discord.

** Indexed Menu Navigation (imenu)

I’ve often called =imenu= to easily jump to a function definition in a file (or header in an org file), but after reading [[http://yummymelon.com/devnull/til-imenu.html][this essay]] by Charles Choi, I decided to increase =imenu='s utility.

#+begin_src emacs-lisp
  (defun ha-imenu-setup ()
    "Set up the imenu customization. Use in hooks."
    (condition-case err
        (imenu-add-menubar-index)
      (setq-local imenu-auto-rescan t)
      (when (derived-mode-p 'prog-mode)
        (setq-local imenu-sort-function 'imenu--sort-by-name))

      (imenu-unavailable
       (let ((inhibit-message t))
         (message "Warning: %s" (error-message-string err))))))

  (add-hook 'org-mode-hook 'ha-imenu-setup)
  (add-hook 'markdown-mode-hook 'ha-imenu-setup)
  (add-hook 'makefile-mode-hook 'ha-imenu-setup)
  (add-hook 'prog-mode-hook 'ha-imenu-setup)
#+end_src
** File Access
*** Remote Files
To speed up TRAMP access, let’s disabled lock files, you know, the ones that have the =#= surrounding characters:
#+begin_src emacs-lisp
  (setq remote-file-name-inhibit-locks t
        tramp-use-scp-direct-remote-copying t
        remote-file-name-inhibit-auto-save-visited t)
#+end_src

According to [[https://coredumped.dev/2025/06/18/making-tramp-go-brrrr./][this essay]], Emacs should copy much larger files than the default:
#+BEGIN_SRC emacs-lisp
  (setq tramp-copy-size-limit (* 1024 1024) ;; 1MB
        tramp-verbose 2)
#+END_SRC

What do I think about [[elisp:(describe-variable 'remote-file-name-inhibit-auto-save-visited)][remote-file-name-inhibit-auto-save-visited]]?

During remote access, TRAMP can slow down performing Git operations. Let’s turn that off as well:
#+begin_src emacs-lisp
  (defun turn-off-vc-for-remote-files ()
    "Disable version control for remote files.
  Use with the `find-file-hook'."
    (when (file-remote-p (buffer-file-name))
      (setq-local vc-handled-backends nil)))

  (add-hook 'find-file-hook 'turn-off-vc-for-remote-files)
#+end_src
*** Changes on Save
Always spaces and never tabs. Note that we use =setq-default= since [[elisp:(describe-variable 'indent-tabs-mode)][indent-tabs-mode]] is a /buffer-local/ variable, meaning using =setq=,  sets it for /that buffer file/. We want this globally the default:
#+begin_src emacs-lisp
  (setq-default indent-tabs-mode nil)
#+end_src

When I push changes to my files to Gerrit and other code review, I don’t want trailing spaces or any tabs to appear, so let’s fix all files when I [[elisp:(describe-variable 'before-save-hook)][save them]]:
#+begin_src emacs-lisp
  (defun ha-cleanup-buffer-file ()
    "Cleanup a file, often done before a file save."
    (interactive)
    (ignore-errors
      (unless (or (equal major-mode 'makefile-mode)
                  (equal major-mode 'makefile-bsdmake-mode))
        (untabify (point-min) (point-max)))
      (delete-trailing-whitespace)))

  (add-hook 'before-save-hook #'ha-cleanup-buffer-file)
#+end_src
*** Recent Files
The [[https://www.emacswiki.org/emacs/RecentFiles][recentf]] feature has been in Emacs for a long time, but it has a problem with Tramp, as we need to turn off the cleanup feature that attempts to =stat= all the files and remove them from the =recent= accessed list if they are readable. The requires recentf to open up a remote files which blocks Emacs at the most inopportune times… like when trying to reboot the machine.
#+begin_src emacs-lisp
  (use-package recentf
    :straight (:type built-in)
    :config
    (setq recentf-auto-cleanup 'never) ;; disable before we start recentf!
    (recentf-mode 1))
#+end_src
*** File Backups
While I use git as much as I can, sometimes Emacs’ built-in file backup and versioning feature has saved me for files that aren’t.

As [[https://philjackson.github.io//emacs/backups/2022/01/31/keeping-backups-of-every-edited-file/][Phil Jackson]] mentioned, Emacs has a lot of variations to its file backup strategy, and either change the [[help:backup-directory-alist][backup-directory-alist]] to put individual file backups elsewhere, e.g.
#+begin_src emacs-lisp
  (setq backup-directory-alist `(("." . ,(concat user-emacs-directory "backups"))))
#+end_src

Or leave them in the current directory, but create an alias so =ls= doesn’t display them, e.g.
#+begin_src sh
  alias ls="ls --color=auto --hide='*~'"
#+end_src

I'm leaving them side-by-side, but I am keeping some extra copies:
#+begin_src emacs-lisp
  (setq create-lockfiles nil   ; Having .# files around ain't helpful
        auto-save-default t
        delete-old-versions t
        kept-new-versions 6
        kept-old-versions 2
        version-control t)
#+end_src
The [[help:version-control][version-control]] variable affect backups (not some sort of global VC setting), this makes numeric backups.
*** Auto Save of Files
Save the file whenever I move away from Emacs (see [[https://irreal.org/blog/?p=10314][this essay]]):
#+begin_src emacs-lisp
  (defun save-all-buffers ()
    "Save all buffers, because, why not?"
    (interactive)
    (save-some-buffers t))

  ;; See 'after-focus-change-function?
  (add-hook 'focus-out-hook 'save-all-buffers)
#+end_src
*** Download Files via URL
Might be nice to have a =url-download= function that just grabs a file from a website without fuss (or other dependencies). Easy enough to prototype, but dealing with errors are another thing …
#+begin_src emacs-lisp
  (defun url-download (url dest)
    "Download the file as URL and save in file, DEST.
  Note that this doesn't do any error checking ATM."
    (interactive "sURL: \nDDestination: ")
    (let* ((url-parts (url-generic-parse-url url))
           (url-path  (url-filename url-parts))
           (filename  (file-name-nondirectory url-path))
           (target    (if (file-directory-p dest)
                          (file-name-concat dest filename)
                        dest))
           (callback (lambda (status destination)
                       (unwind-protect
                           (pcase status
                             (`(:error . ,_)
                              (message "Error downloading %s: %s" url (plist-get status :error)))
                             (_ (progn
                                  ;; (switch-to-buffer (current-buffer))
                                  (delete-region (point-min) (1+ url-http-end-of-headers))
                                  (write-file destination)
                                  (kill-buffer)
                                  (when (called-interactively-p 'any)
                                    (kill-new destination)))))))))
      (message "Retrieving %s into %s" url target)
      (url-retrieve url callback (list target))))
#+end_src

This function can be called interactively with a URL and a directory (and it attempts to create the name of the destination file based on the latter-part of the URL), or called programmatically, like:
#+begin_src emacs-lisp :tangle no
  (url-download "https://www.emacswiki.org/emacs/download/bookmark+.el"
                "~/Downloads/bookmark-plus.el")
#+end_src
** Completing Read User Interface
After using Ivy, I am going the route of a =completing-read= interface that extends the original Emacs API, as opposed to implementing backend-engines or complete replacements.

One enhancement to =completing-read= is to allow either a property list or an associate list for choices, and then return the /value/.

#+BEGIN_SRC emacs-lisp
  (defun completing-read-alist (prompt collection
                                       &optional predicate require-match
                                       initial-input hist def inherit-input-method)
    "List `completing-read', but COLLECTION is an alist, and it returns value.
  The is, the _associative bit_.

  PROMPT is a string to prompt with; normally it ends in a colon and a space.
  PREDICATE, REQUIRE-MATCH, HIST and INHERIT-INPUT-METHOD is the same.
  DEF is the default return without a match."
    (let ((x (completing-read prompt collection predicate require-match
                              initial-input hist def inherit-input-method)))
      (alist-get x collection x nil 'equal)))
#+END_SRC

This means (and I use this fairly often), that the /key/ is shows as a choice, the function returns the /value/.

#+BEGIN_SRC emacs-lisp :tangle no
  (completing-read-alist "Choose a language: "
                         '(("Emacs Lisp" . "elisp.org")
                           ("Python" . "python.org")
                           ("Visual Basic" . "visual-basic.org")
                           ;; ...
                           ))
#+END_SRC


*** Vertico
The [[https://github.com/minad/vertico][vertico]] package puts the completing read in a vertical format, and like [[https://github.com/raxod502/selectrum#vertico][Selectrum]], it extends Emacs’ built-in functionality, instead of adding a new process. This means all these projects work together.
#+begin_src emacs-lisp
  (use-package vertico
    :config (vertico-mode))
#+end_src
My issue with Vertico is when calling =find-file=, the Return key opens =dired=, instead of inserting the directory at point. This package addresses this:
#+begin_src emacs-lisp
  (use-package vertico-directory
    :straight (el-patch :files ("~/.emacs.d/straight/repos/vertico/extensions/vertico-directory.el"))
    ;; More convenient directory navigation commands
    :bind (:map vertico-map
                ("RET" . vertico-directory-enter)
                ; ("DEL" . vertico-directory-delete-word)
                ("M-RET" . minibuffer-force-complete-and-exit)
                ("M-TAB" . minibuffer-complete))
    ;; Tidy shadowed file names
    :hook (rfn-eshadow-update-overlay . vertico-directory-tidy))
#+end_src
*** Hotfuzz
This fuzzy completion style is like the built-in =flex= style, but has a better scoring algorithm, non-greedy and ranks completions that match at word; path component; or camelCase boundaries higher.

#+begin_src emacs-lisp
  (use-package hotfuzz)
#+end_src
While flexible at matching, you have to get the /order/ correct. For instance, ~alireg~ matches with [[help:align-regexp][align-regexp]], but ~regali~ does not, so we will use =hotfuzz= for scoring, and not use this as a completion-project (see the =fussy= project below).
*** Orderless
While the space can be use to separate words (acting a bit like a =.*= regular expression), the [[https://github.com/oantolin/orderless][orderless]] project allows those words to be in any order.
#+begin_src emacs-lisp
  (use-package orderless
    :commands (orderless-filter)
    :custom
    (completion-ignore-case t)
    (completion-category-defaults nil)
    (completion-category-overrides '((file (styles partial-completion))))

    :init
    (defvar orderless-skip-highlighting nil
       "Not sure why this is being accessed.")

    (push 'orderless completion-styles))
#+end_src
*Note:* Open more than one file at once with =find-file= with a wildcard. We may also give the =initials= completion style a try.
*** Fussy Filtering and Matching
The [[https://github.com/jojojames/fussy][fussy]] project is a fuzzy pattern matching extension for the normal [[help:completing-read][completing-read]] interface. By default, it uses [[https://github.com/lewang/flx][flx]], but we can specify other sorting and filtering algorithms.

How does it compare? Once upon a time, I enjoyed typing ~plp~ for =package-list-packages=, and when I switched to [[https://github.com/oantolin/orderless][orderless]], I would need to put a space between the words. While I will continue to play with the different mechanism, I’ll combine =hotfuzz= and =orderless=.

#+begin_src emacs-lisp
  (use-package fussy
    ;; :straight (:host github :repo "jojojames/fussy")
    :config
    (push 'fussy completion-styles)
    (setq completion-category-defaults nil
          completion-category-overrides nil
          fussy-filter-fn 'fussy-filter-orderless-flex
          fussy-score-fn 'fussy-hotfuzz-score))
#+end_src
*** Savehist
Persist history over Emacs restarts using the built-in [[https://www.emacswiki.org/emacs/SaveHist][savehist]] project. Since both Vertico and Selectrum sorts by history position, this should make the choice /smarter/ with time.
#+begin_src emacs-lisp
  (use-package savehist
    :init
    (savehist-mode))
#+end_src
*** Marginalia
The [[https://github.com/minad/marginalia][marginalia]] package gives a preview of =M-x= functions with a one line description, extra information when selecting files, etc. Nice enhancement without learning any new keybindings.

#+begin_src emacs-lisp
  ;; Enable richer annotations using the Marginalia package
  (use-package marginalia
    :init
    (setq marginalia-annotators-heavy t)
    :config
    (add-to-list 'marginalia-command-categories '(project-find-file . file))
    (marginalia-mode))
#+end_src

* Key Bindings
The [[https://github.com/justbur/emacs-which-key][which-key]] project shows a menu of available key-bindings based on what you have already typed. For instance, if you remember that Org Goto function (like most Org-related functions) began with ~C-c~, after typing that sequence, all possible keybindings and their functions are shown. Useful for discovering new features.
#+begin_src emacs-lisp
  (use-package which-key
    :init   (setq which-key-popup-type 'minibuffer)
    :config (which-key-mode))
#+end_src

Why would I ever quit Emacs with a simple keybinding on a Mac? Let’s override it:
#+begin_src emacs-lisp
  (global-set-key (kbd "s-q") 'bury-buffer)
#+end_src
** Undo
The [[https://gitlab.com/ideasman42/emacs-undo-fu][undo-fu]] isn’t much to the project (that’s a good thing), but It doesn’t /cycle/ around the redo ring, which can be annoying.

#+begin_src emacs-lisp
  (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-unset-key (kbd "s-z"))
    (global-set-key (kbd "s-z")   'undo-fu-only-undo)
    (global-set-key (kbd "s-S-z") 'undo-fu-only-redo))
#+end_src

While I generally use ~C-/~ for [[help:undo][undo]] (and ~C-?~ for [[help:undo-redo][redo]]), when I’m on the Mac, I need to cover my bases.

** Leader Sequences
Pressing the ~SPACE~ can activate a /leader key sequence/ I define in my [[file:ha-leader.org][ha-leader]] file.
#+begin_src emacs-lisp
  (ha-hamacs-load "ha-general.org")
#+end_src
This extends the =use-package= to include a =:general= keybinding section.

Since I seldom remember keybindings, or even function names, for major-modes, I pull them all together into a nice table using Jerry Peng’s  [[https://github.com/jerrypnz/major-mode-hydrajjj0.el][Major Mode Hydra]] project:

#+begin_src emacs-lisp
  (use-package major-mode-hydra
    :bind ("s-," . major-mode-hydra)
    :init
    (defun major-mode-hydra-title (&optional mode)
      "Create a pleasant-looking title for MODE."
      (let ((mode-str (cond
                        ((null mode)    (pp-to-string major-mode))
                        ((symbolp mode) (symbol-name mode))
                        (t              (pp-to-string major-mode)))))
        (thread-last mode-str
                     (string-replace "-" " ")
                     (string-replace " mode" "")
                     (s-titleize))))

    (defvar major-mode-hydra-title-generator
      '(lambda (&optional mode)
         (let ((title (major-mode-hydra-title mode)))
           (concat "ϻ " title " Commands")))))
#+end_src

Scattered throughout my configuration, I use =major-mode-hydra-define= where I’m configuring that mode. For instance, for the built-in Info, I can make:

#+begin_src emacs-lisp
  (use-package major-mode-hydra
    :config
    (major-mode-hydra-define Info-mode (:quit-key "q")
       ("Overview"
         (("d" Info-directory "Directory")
          ("t" Info-top-node "Top")
          ("T" Info-toc "Contents"))
         "Goto"
         (("m" link-hint-open-link "Menu...")
          ("n" Info-goto-node "Node...")
          ("i" Info-index "Index..."))
         "History"
         (("M-h" Info-history "List")
          ("H" Info-history-back "Back" :color pink)
          ("L" Info-history-forward "Forward" :color pink))
         "Navigation"
         (("u" Info-up "Up" :color pink)
          ("p" Info-backward-node "Backward" :color pink)
          ("n" Info-forward-node "Forward" :color pink))
         "References"
         (("l" Info-follow-reference "Choose")
          ("j" Info-next-reference "Next" :color pink)
          ("k" Info-prev-reference "Previous" :color pink))
         "Scroll"
         (("SPC" Info-scroll-up "Up" :color pink)
          ("DEL" Info-scroll-down "Down" :color pink)
          ("RET" Info-follow-nearest-node "Open"))
         "Misc"
         (("o" org-store-link "Store link")
          ("b" Info-bookmark-jump "Bookmark")
          ("w" Info-goto-node-web "View on Web")))))
#+end_src

** Text Expanders and Completion
The following defines my use of the Emacs completion system. I’ve decided my /rules/ will be:
  - Nothing should automatically appear; that is annoying and distracting.
  - Spelling in org files (abbrev or hippie expander) and code completion are separate, but I’m not sure if I can split them
  - IDEs overuse the ~TAB~ binding, and may I should re-think the bindings.

I don’t find the Emacs completion system obvious, with different interfaces, some distinct, some connected. As ~TAB~ is often overloaded. Emacs can have a cascade of functions. Here’s the summary as I understand (as well as the overriding keybindings I use):

#+BEGIN_EXAMPLE
     ╭─▷ indent-for-tab-command          ╭───────╮
     │     ╷                          ╭──┤ M-TAB │  ╭─────╮
     │     ╰─▶ completion-at-point ◁──╯  ╰───────╯  │ M-/ │
  ╭──┴──╮      (completion-at-point-functions)      ╰──┬──╯
  │ TAB │        ╷                                     │
  ╰─────╯        ╰─▶ hippie and dabbrev ◁──────────────╯
#+END_EXAMPLE

In =org-mode=, ~TAB~ calls [[help:org-cycle][org-cycle]], which is even more overload and context-specific. In the context of typing text, calls the binding for ~TAB~, which is the [[help:indent-for-tab-command][indent-for-tab-command]]. If the line is /indented/, it then completes the word:

#+begin_src emacs-lisp
  (setq tab-always-indent 'complete
        tab-first-completion 'word-or-paren
        completion-cycle-threshold 2)
#+end_src

Note that no matter the setting for =tab-first-completion=, hitting ~TAB~ twice, results in completion.

This calls [[help:completion-at-point][completion-at-point]]. This code (from mini-buffer) doubles with the other [[Vertico][completing processes]] (like [[help:completing-read][completing-read]]) and presents choices based on a series of functions (see [[https://with-emacs.com/posts/tutorials/customize-completion-at-point/][this essay]] for details). This will call into the CAPF function list (see the variable, =completion-at-point-functions= and the [[*Cape][Cape]] section for details).
*** Hippie Expand
The venerable [[help:hippie-expand][hippie-expand]] function does a better job than the default, [[help:dabbrev-expand][dabbrev-expand]], so let’s swap it out (see this [[https://www.masteringemacs.org/article/text-expansion-hippie-expand][essay]] by Mickey Petersen) with its default key of ~M-/~ (easy to type on the laptop) as well as ~C-Tab~ (easier on mechanical keyboards):
#+begin_src emacs-lisp
  (global-set-key [remap dabbrev-expand] 'hippie-expand)
  (global-set-key (kbd "M-<tab>") 'completion-at-point)
#+end_src

Details on its job? We need to update its [[help:hippie-expand-try-functions-list][list of expanders]]. I don’t care much for [[help:try-expand-line][try-expand-line]], so that is not on the list.

#+begin_src emacs-lisp
  (setq hippie-expand-try-functions-list
        '(try-complete-file-name-partially   ; complete filenames, start with /
          try-complete-file-name
          yas-hippie-try-expand              ; expand matching snippets
          try-expand-all-abbrevs
          try-expand-list                    ; help when args repeated another's args
          try-expand-dabbrev
          try-expand-dabbrev-all-buffers
          try-expand-whole-kill              ; grab text from the kill ring
          try-expand-dabbrev-from-kill       ; as above
          try-complete-lisp-symbol-partially
          try-complete-lisp-symbol))
#+end_src

In the shell, IDEs and other systems, the key binding is typically ~TAB~. In modes other than =org-mode=, ~TAB~ re-indents the line with [[help:indent-for-tab-command][indent-for-tab-command]], but I find that I want that feature when I’m in Evil’s =normal state= and hit the ~=~ key, so changing this sounds good. But why not /have both/?

This screws up the [[file:ha-programming-elisp.org::*Lispyville][lispyville]], so I want automatic expansion limited to text files, like Org and Markdown:

#+begin_src emacs-lisp
  (defun hippie-expand-in-org (&rest ignored)
    "Calls `hippie-expand', limited to text buffers."
    (when (derived-mode-p 'text-mode)
      (call-interactively #'hippie-expand)))

  (advice-add #'indent-for-tab-command :after #'hippie-expand-in-org)
#+end_src

*** Cape
The [[https://github.com/minad/cape][Cape project]] deliver particular [[help:completion-at-point][completion-at-point]] functions that can be /hooked/ in. Since I already have the hippie working as I like, I don’t need =cape-dabbrev= or =cape-dict= hooked in, but for /general modes/ I will choose the emoji capf:

#+BEGIN_SRC emacs-lisp :tangle no
  (use-package cape
    :straight (:host github :repo "minad/cape")
    :init
    (setq completion-at-point-functions (list #'cape-emoji)))
#+END_SRC

Each programming environment might need some particular love. For instance:
#+BEGIN_SRC emacs-lisp
  (add-hook 'emacs-lisp-mode :after
            (lambda ()
              (setq-local completion-at-point-functions
                          (list #'cape-elisp-symbol))))
#+END_SRC


*** Company
The [[http://company-mode.github.io/][company project]] for completion back-ends work well with LSP.

Do I want it to display whenever there is a pause in the conversation (set =company-minimum-prefix-length= to a large number, or set =company-idle-delay=), or do I want it to only show when I push the =TAB= or other key (see =company-indent-or-complete-common=).

#+BEGIN_SRC emacs-lisp
  (use-package company
    :custom
    (company-minimum-prefix-length 3) ; default
    (company-idle-delay 0.5)
    (company-tooltip-align-annotations t)
    (company-tooltip-limit 9)
    (company-tooltip-flip-when-above t)
    (company-show-quick-access 'left)

    :bind (("M-/" . company-complete)
           :map company-mode-map
           ("M-/" . company-other-backend))

    :config
    (add-to-list 'company-backends 'company-yasnippet)

    (set-face-attribute 'company-tooltip nil
                        :family "Cascadia Code NF"
                        :height 120)

    :hook (after-init . global-company-mode))
#+END_SRC

Another idea, is I can trigger the company with a M-/ but then view /other backends/ by re-hitting that keybinding.
*** Corfu
The default completion system either inserts the first option directly in the text (without cycling, so let’s hope it gets it right the first time), or presents choices in another buffer (who wants to hop to it to select an expansion).

After using  [[http://company-mode.github.io/][company]] for my completion back-end, I switched to [[https://github.com/minad/corfu][corfu]] as it works with the variable-spaced font of my org files (also see [[https://takeonrules.com/2022/01/17/switching-from-company-to-corfu-for-emacs-completion/][this essay]] for my initial motivation).

#+begin_src emacs-lisp :tangle no
  (use-package corfu
    :custom
    (corfu-cycle t)
    (corfu-separator ?\s)
    :init
    (global-corfu-mode))
#+end_src

*** Snippets
Using [[https://github.com/joaotavora/yasnippet][yasnippet]] to expand templates into text:

#+begin_src emacs-lisp
  (use-package yasnippet
    :config
    (add-to-list 'yas-snippet-dirs
                 (expand-file-name "snippets" user-emacs-directory))
    (yas-global-mode +1))
#+end_src

Check out [[http://joaotavora.github.io/yasnippet/][the documentation]] for writing them.

Since I have troubles installing Doom’s [[https://github.com/hlissner/doom-snippets][collection of snippets]], lets use the [[http://github.com/AndreaCrotti/yasnippet-snippets][yasnippet-snippets]] package:

#+begin_src emacs-lisp
  (use-package yasnippet-snippets)
#+end_src
*** Auto Insert Templates
The [[https://www.emacswiki.org/emacs/AutoInsertMode][auto-insert]] feature is a wee bit complicated. All I want is to associate a filename regular expression with a YASnippet template. I'm stealing some ideas from Henrik Lissner's [[https://github.com/hlissner/doom-emacs/blob/develop/modules/editor/file-templates/autoload.el][set-file-template!]] macro, but simpler?

#+begin_src emacs-lisp
  (use-package autoinsert
    :init
    (setq auto-insert-directory (expand-file-name "templates" user-emacs-directory))
    ;; Don't prompt before insertion:
    (setq auto-insert-query nil)

    (add-hook 'find-file-hook 'auto-insert)
    (auto-insert-mode t))
#+end_src
Since auto insertion requires entering data for particular fields, and for that Yasnippet is better, so in this case, we combine them:
#+begin_src emacs-lisp
  (defun ha-autoinsert-yas-expand()
    "Replace text in yasnippet template."
    (let ((auto-insert-query nil)
          (yas-indent-line nil))
      (yas-minor-mode +1)
      (when (fboundp 'evil-insert-state)
        (evil-insert-state))
      (yas-expand-snippet (buffer-string) (point-min) (point-max))))
#+end_src

And since I'll be associating snippets with new files all over my configuration, let's make a helper function:

#+begin_src emacs-lisp
  (defun ha-auto-insert-file (filename-re snippet-name)
    "Autofill file buffer matching FILENAME-RE regular expression.
  The contents inserted from the YAS SNIPPET-NAME."
    ;; The define-auto-insert takes a regular expression and an ACTION:
    ;; ACTION may also be a vector containing _successive_ single actions.
    ;; This means running this twice results in two repeated expansions, so:
    (unless (assoc filename-re auto-insert-alist 'equal)
      (define-auto-insert filename-re
        (vector snippet-name 'ha-autoinsert-yas-expand))))
#+end_src

As an example of its use, any Org files loaded in /this project/ should insert my config file:
#+begin_src emacs-lisp
  (ha-auto-insert-file (rx "hamacs/" (one-or-more any) ".org" eol) "hamacs-config")
  (ha-auto-insert-file (rx ".dir-locals.el") "dir-locals")
#+end_src
** Additional Global Packages
*** Function Call Notifications
As I've mentioned [[http://www.howardism.org/Technical/Emacs/beep-for-emacs.html][on my website]], I've created a [[file:~/website/Technical/Emacs/beep-for-emacs.org][beep function]] that notifies when long running processes complete.

#+begin_src emacs-lisp
  (use-package alert
    :init
    (setq alert-default-style
          (if (ha-running-on-macos?)
              'osx-notifier
            'libnotify)))

  (use-package beep
    :straight (:local-repo "~/src/hamacs/elisp")
    :hook (after-init . ha-random-startup-message)
    :commands (beep-when-finished beep-when-run-too-long)
    :config
    (dolist (func '(org-publish
                    org-publish-all
                    org-publish-project
                    compile
                    shell-command))
      (advice-add func :around #'beep-when-runs-too-long)))

  (defun ha-random-startup-message ()
    "State a random message on startup."
    (let ((msg (seq-random-elt '("You are good to go"
                                 "Yippee"
                                 "What's next?"
                                 "Notification complete. Happy?"
                                 "Beware, lest the dragons fly"))))
      (beep-when-finished msg)))
#+end_src

While that code /advices/ the publishing and compile commands, I may want to add more.
*** Visual Replacing Regular Expressions
I appreciated the [[https://github.com/benma/visual-regexp.el][visual-regexp package]] to see what you want to change /before/ executing the replace.
#+begin_src emacs-lisp
  (use-package visual-regexp
    :bind (("C-c r" . vr/replace)
           ("C-c q" . vr/query-replace))
    :general (:states 'normal "g r" '("replace" . vr/replace))
    :config (ha-leader
              "r" '("replace" . vr/replace)
              "R" '("query replace" . vr/query-replace)))
#+end_src

For all other functions that use regular expressions, many call the function, =read-regexp=, and thought it would be helpful if I could type =rx:…= and allow me to take advantage of the =rx= macro.
#+begin_src emacs-lisp
  (defun read-regexp-with-rx (input)
    "Advice for `read-regexp' to allow specifying `rx' expressions.
  If INPUT starts with rx: then the rest of the input is given to
  the `rx' macro, and function returns that regular expression.
  Otherwise, return INPUT."
    (if (string-match (rx bos "rx:" (zero-or-more space)
                          (group (one-or-more any)))
                      input)
        (let* ((rx-input (match-string 1 input))
               (rx-expr  (format "(rx %s)" rx-input)))
          (message "%s and %s" rx-input rx-expr)
          (eval (read rx-expr)))
      input))
#+end_src

Let’s right a little test case to make sure it works:
#+begin_src emacs-lisp :tangle no
  (ert-deftest read-regexp-with-rx-test ()
    (should (equal (read-regexp-with-rx "foo|bar") "foo|bar"))
    (should (equal (read-regexp-with-rx "rx:\"foobar\"") "foobar"))
    (should (equal (read-regexp-with-rx "rx:bol (zero-or-more space) eol") "^[[:space:]]*$")))
#+end_src

Now we just need to filter the results from the built-in Emacs function:
#+begin_src emacs-lisp
  (advice-add 'read-regexp :filter-return 'read-regexp-with-rx)
#+end_src
*** Jump with Avy
While I grew up on =Control S=, I am liking the /mental model/ associated with the [[https://github.com/abo-abo/avy][avy project]] that allows a /jump/ among matches across all visible windows. I use the ~F18~ key on my keyboard that should be easy to use, but ~g o~ seems obvious.

#+begin_src emacs-lisp
  (use-package avy
    :init
    (setq avy-all-windows t
          avy-single-candidate-jump nil   ; May want to yank the candidate
          avy-orders-alist
          '((avy-goto-char . avy-order-closest)
            (avy-goto-word-0 . avy-order-closest)))

    :config (ha-leader "j" '("jump" . avy-goto-char-timer))

    :general
    (:states 'normal "go" '("avy goto" . avy-goto-char-timer)
                     "s"  '("avy word" . avy-goto-subword-1))

    :bind (("<f18>" . avy-goto-char-timer)
           ("s-g"   . avy-goto-char-timer)
           ("s-e"   . avy-next)
           ("s-a"   . avy-prev)
           :map isearch-mode-map ("s-g" . avy-isearch)))
#+end_src
*Note:* The links should be shorter near the point as opposed to starting from the top of the window.

If you hit the following keys /before/ you select a target, you get special actions (check out this [[https://karthinks.com/software/avy-can-do-anything/][great essay]] about this understated feature):
  - ~n~ :: copies the matching target word, well, from the target to the end of the word, so match at the beginning.
  - ~x~ :: =kill-word= … which puts it in the kill-ring to be pasted later.
  - ~X~ :: =kill-stay= … kills the target, but leaves the cursor in the current place.
  - ~t~ :: =teleport= … bring the word at the target to the current point … great in the shell.
  - ~m~ :: =mark= … select the word at target
  - ~y~ :: =yank= … puts any word on the screen on the clipbard.
  - ~Y~ :: =yank-line= … puts the entire target line on the clipboard.
  - ~i~ :: =ispell= … fix spelling from a distance.
  - ~z~ :: =zap-to-char= … kill from current point to the target
I’m not thinking of ideas of what would be useful, e.g. ~v~ to highlight from cursor to target, etc.

What is missing is copying the entire line:
#+BEGIN_SRC emacs-lisp
  (use-package avy
    :config
    (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-copy-rest-line (pt)
      (save-excursion
        (goto-char pt)
        (let ((start pt)
              (end (line-end-position)))
          (copy-region-as-kill start end)))
      (select-window
       (cdr
        (ring-ref avy-ring 0)))
      t)

    (setf (alist-get ?w avy-dispatch-alist) 'avy-action-copy
          (alist-get ?W avy-dispatch-alist) 'avy-action-copy-whole-line
          (alist-get ?N avy-dispatch-alist) 'avy-action-copy-rest-line))
#+END_SRC

Want to know something amazing. In a Terminal, like =vterm= or =eshell=, I run ~s-g~ and pinpoint the UUID in the output of a long command. Then type ~y~ and then ~C-y~ to paste that ID without even moving the mouse.
*** Link Hint, the Link Jumper
The [[info:emacs#Goto Address mode][Goto Address]] mode (see this [[https://www.gnu.org/software/emacs/manual/html_node/emacs/Goto-Address-mode.html][online link]]) turns URLs into clickable links. Nice feature and built into Emacs, but it requires using the mouse or moving to the URL and hitting ~Return~ (if you like this idea, check out [[https://xenodium.com/actionable-urls-in-emacs-buffers/][Álvaro Ramírez's configuration]] for this).

I appreciated [[https://github.com/abo-abo/ace-link][ace-link]]’s idea for hyperlinks on Org, EWW and Info pages, as it allowed you to jump to a URL from any location on the screen.  The [[https://github.com/noctuid/link-hint.el][link-hint]] project does this, but works with more types of files and links:
#+begin_src emacs-lisp
  (use-package link-hint
    :bind
    ("s-o" . link-hint-open-link)
    ("s-y" . link-hint-copy-link)
    :general
    (:states 'normal
             "gl" '("open link" . link-hint-open-link)
             "gL" '("open link→window" . link-hint-open-link-ace-window)
             "gm" '("copy link" . link-hint-copy-link))
    (:states 'normal :keymaps 'eww-mode-map
             "o" 'link-hint-open-link)
    (:states 'normal :keymaps 'Info-mode-map
             "o" 'link-hint-open-link))
#+end_src

Can I open a link in another window? The idea with this is that I can select a link, and with multiple windows open, I can specify where the =*eww*= window should show the link. If only two windows, then the new EWW buffer shows in the /src/ one.

#+begin_src emacs-lisp
  (defun link-hint-open-link-ace-window ()
    "Select link via avy, and open link in other window."
    (interactive)
    (link-hint-copy-link)
    (ace-select-window)
    (eww (current-kill 0)))
#+end_src
*** Expand Region
Magnar Sveen's [[https://github.com/magnars/expand-region.el][expand-region]] project allows me to hit ~v~ in =visual= mode, and have the selection grow by syntactical units.
#+begin_src emacs-lisp
  (use-package expand-region
    :bind ("C-=" . er/expand-region)

    :general
    ;; Use escape to get out of visual mode, but hitting v again expands the selection.
    (:states 'visual
             "v" 'er/expand-region
             "V" 'er/contract-region
             "-" 'er/contract-region))
#+end_src
*** iSearch
The built-in =isearch= is fantastically simple and useful, bound to ~C-s~, but why not bind searching for the current symbol?

#+BEGIN_SRC emacs-lisp
  (global-set-key (kbd "M-s M-s") 'isearch-forward-thing-at-point)
#+END_SRC

I like Charles Choi’s [[https://github.com/kickingvegas/casual][Casual Suite]], and his original [[https://github.com/kickingvegas/cc-isearch-menu][cc-isearch-menu]] was great at seeing the /buried/ features. I’ve duplicated the features using [[https://github.com/jerrypnz/major-mode-hydra.el][pretty-hydra]]. In the middle of a search, type ~⌘-s~ (Command-s), and menu of options I don’t remember appear.

#+begin_src emacs-lisp
  (pretty-hydra-define isearch-mode
    (:color amaranth :quit-key "C-s" :title "  Search Options")
    ("Movement"
     (("n" isearch-repeat-forward "Forward")
      ("p" isearch-repeat-backward "Backward")
      ("j" avy-isearch "Jump" :color blue))
     "Expand"
     ((">" isearch-yank-symbol-or-char "Full symbol")
      ("z" isearch-yank-until-char "Until char")
      ("$" isearch-yank-line "Full line"))
     "Replace"
     (("R" isearch-query-replace "Literal")
      ("Q" isearch-query-replace-regexp "Regexp")
      ("<return>" isearch-exit "Stay" :color blue))
     "Toggles"
     (("w" isearch-toggle-word "Word only" :toggle t)
      ("s" isearch-toggle-symbol "Full symbol" :toggle t)
      ("r" isearch-toggle-regexp "Regexp" :toggle t)
      ("c" isearch-toggle-case-fold "Case Sensitive" :toggle t))
     "Highlight"
     (("H" isearch-highlight-regexp "Matches" :color blue)
      ("L" isearch-highlight-lines-matching-regexp "Lines" :color blue))
     "Other"
     (("e" isearch-edit-string "Edit")
      ("o" isearch-occur "Occur" :color blue)
      ("C-g" isearch-abort "Abort" :color blue))))

  (define-key isearch-mode-map (kbd "s-s") 'isearch-mode/body)
#+end_src

Pressing ~C-g~ aborts, and ~Return~ exits leaving the point in place. Typing ~C-s~ in the menu stops the menu to continue searching.

#+ATTR_HTML: :width 800
[[file:screenshots/isearch-hydra.png]]

How did I figure out the available bindings to make this? To see /all/ the bindings, start the =isearch= minor mode (using ~C-s~), then type ~F1~ twice. This fancy feature lets you select ~b~ to see all the keybindings (along with other features).
*** Bookmarks
I like the idea of dropping returnable bookmarks, but with /good defaults/ for the names of these global bookmarks:

#+BEGIN_SRC emacs-lisp
  (defun ha-bookmark-label-default ()
    "Return label for bookmarks based on thing-at-point."
    (concat
     (if-let ((filename (buffer-file-name)))
         (thread-last filename
                      (string-replace (getenv "HOME") "~")
                      (format "%s: ")))
     (which-function)))

  ;; (equal (ha-bookmark-label-default) "~/src/hamacs/ha-config.org :: Bookmarks")

  (defun ha-bookmark-set (args)
    "Drop a bookmark based on the current context.
  If ARGS is non-nil, prompt for the bookmark's label.
  Replaces `bookmark-set'."
    (interactive "P")
    (let* ((default (ha-bookmark-label-default))
           (label   (if args
                        (read-string "Bookmark label: " default)
                      default)))
      (bookmark-set label)))
#+END_SRC

The built-in behavior doesn’t honor either /projects/ or /perspectives/, but I use [[https://codeberg.org/ideasman42/emacs-bookmark-in-project][bookmark-in-project]] package to make a =project=-specific bookmarks and use that to jump to only bookmarks in the current project.

#+BEGIN_SRC emacs-lisp
  (use-package bookmark-in-project
    :bind
    (("C-x r m" . bookmark-in-project-toggle)
     ("C-x r M" . ha-bookmark-set)))
#+END_SRC


** Minor Keybinding Annoys
I like ~C-a~ to go to the beginning of the line, but what about getting to the beginning of text on that line? In Evil, you have ~^~ for beginning of line, and ~0~ for first text. Why not have ~C-a~ toggle between them both:

#+begin_src emacs-lisp
  (defun ha-beginning-of-line (&optional n)
    "Toggle between the beginning of line and first of text."
    (interactive "^p")
    (if (= (point) (line-beginning-position))
        (beginning-of-line-text n)
      (beginning-of-line n)))

  (global-set-key (kbd "C-a") 'ha-beginning-of-line)
#+end_src

* Working Layout
While editing any file on disk is easy enough, I like the mental context switch associated with a full-screen window frame showing all the buffers of a /project task/ (often a direct link to a repository project, but not always).
** Projects
Since I wasn’t using all the features that [[https://github.com/bbatsov/projectile][projectile]] provides, I have switched to the built-in =project= functions.

#+begin_src emacs-lisp
  (use-package emacs
    :config
    (ha-leader
      "p"  '(:ignore t :which-key "projects")
      "p p" '("switch project" . ha-tab-bar-new-project)

      "p !" '("run cmd in project root" . project-shell-command)
      "p &" '("run cmd async" . project-async-shell-command)
      "p a" '("add new project" . project-remember-projects-under)
      "p d" '("dired" . project-dired)
      "p k" '("kill project buffers" . project-kill-buffers)
      "p x" '("remove known project" . project-forget-project)

      "p f" '("find file" . project-find-file)
      "p F" '("find file o/win" . project-find-file-other-window)
      "p b" '("switch to project buffer" . project-switch-to-buffer)

      "p C" '("compile in project" . compile-project)
      "p c" '("recompile" . recompile)

      "p e" '("project shell" . project-eshell)
      "p s" '("project shell" . project-shell)))
#+end_src
** Workspaces
A /workspace/ (at least to me) requires a quick jump to a collection of buffer windows organized around a project or task. Later versions of Emacs use [[https://www.gnu.org/software/emacs/manual/html_node/emacs/Tab-Bars.html][Tab Bars]] which group windows and buffers in a perspective. The code that follows is a Poor Person’s Workspace package. Also let’s dive into the end section of [[https://www.masteringemacs.org/article/demystifying-emacs-window-manager][Mickey Petersen's essay]] on the subject.

Couple notes:
  - Function, =tab-bar-switch-to-tab=, switches or /creates/ a tab. We will always use this.
  - We can switch to a tab by number with =tab-bar-select-tab=

#+BEGIN_SRC emacs-lisp
  (setq tab-bar-show 1                       ; hide bar if <= 1 tabs open
        tab-bar-close-button-show nil        ; hide tab close / X button
        tab-bar-new-tab-choice "*dashboard*" ; buffer to show in new tabs
        tab-bar-tab-hints t                  ; show tab numbers
        tab-bar-separator "   "
        tab-bar-button-relief 0

        ;; Jump to a tab by numbers (see the keybindings set later):
        tab-bar-select-tab-modifiers '(super control))
#+END_SRC

I’ve struggled to /programmatically/ create sane workspaces, so let’s just save them off:

#+BEGIN_SRC emacs-lisp
  (desktop-save-mode 1)
#+END_SRC

New workspace is a tab with a specific name that opens up a specific buffer or application. My motive for such a complicated function allows me to pre-create tabs with already running applications or files.

#+begin_src emacs-lisp
  (defun ha-tab-bar-new (name &optional bff)
    "Create a new tab with a NAME.
  With a non-nil BFF, call BFF as a function or switch
  to the BFF buffer or the files listed."
    (interactive "sWorkspace Name: ")
    (tab-bar-switch-to-tab name)
    (when bff
      (cond
       ((listp bff) (find-file (car bff))
        (dolist (f (cdr bff))
          (split-window-right)
          (find-file f)))
       ((fboundp bff) (call-interactively bff))
       ((bufferp bff) (switch-to-buffer bff)))))
#+end_src

Create a new tab associated with a project:

#+begin_src emacs-lisp
  (defun ha-tab-bar-new-project (project-dir)
    "Create a new tab/workspace based on a project.
  The project is defined by the PROJECT-DIR directory."
    (interactive (list (completing-read "Project: " (project-known-project-roots))))
    (let ((name (project-name (project-current nil project-dir)))
          (default-directory project-dir))
      (ha-tab-bar-new name)
      (project-switch-project project-dir)))
#+end_src

If we close a tab that is a project, we want to close all the buffers associated with it. I wouldn’t do this if it wasn’t so easy to re-create them:

#+begin_src emacs-lisp
  (defun ha-tab-bar-delete (tab-name)
    "Delete a tab, TAB-NAME, and all buffers associated with it."
    (interactive
     (list (completing-read "Close tab by name: "
                            (mapcar (lambda (tab)
                                      (alist-get 'name tab))
                                    (funcall tab-bar-tabs-function)))))
    (dolist (buf (ha-tab-bar-buffers tab-name))
      (kill-buffer buf))
    (tab-bar-close-tab-by-name tab-name))

  (defun ha-tab-bar-buffers (tab-name)
    "Return list of buffers associated with TAB-NAME."
    (seq-filter (lambda (b)
                  (thread-last b
                               (tab-bar-get-buffer-tab)
                               (alist-get 'name)
                               (string-equal tab-name)))
                (buffer-list)))
#+end_src

And let’s make one tab to be /special/:

#+BEGIN_SRC emacs-lisp
  (defun ha-switch-to-special ()
    "Change the perspective to the Sprint/Notes project."
    (interactive)
    (tab-bar-switch-to-tab "main"))
#+END_SRC

And some shortcut keys from the =general= project:

#+BEGIN_SRC emacs-lisp
  (use-package emacs
    :config
    (general-nmap :prefix "SPC"
      "<tab>"  '(:ignore t :which-key "workspaces")
      "<tab> <tab>" '("switch" . tab-switch)
      "<tab> p" '("new project" . ha-tab-bar-new-project)
      "<tab> n" '("new space" . ha-tab-bar-new)
      "<tab> u" '("update names" . ha-tab-bar-update-names)
      "<tab> d" '("delete space" . ha-tab-bar-delete)
      "<tab> t" '("toggle tab-bar" . tab-bar-mode)
      "<tab> w" '("special space" . ha-switch-to-special)
      "<tab> `" '("recent" . tab-bar-switch-to-recent-tab))

    (global-set-key (kbd "s-C-t") 'ha-tab-bar-new)
    (global-set-key (kbd "s-C-[") 'tab-bar-switch-to-prev-tab)
    (global-set-key (kbd "s-C-]") 'tab-bar-switch-to-next-tab)

    (tab-bar-mode 1))
#+END_SRC

I want to quickly jump, by the number shown on the tab, to that grouping. The following two functions create leader sequences with the name of the tab group:

#+BEGIN_SRC emacs-lisp
  (defun ha-tab-bar-update-names (&optional changed-tab)
    "Create normal-mode keybindings for the tab groupings.
  This creates `SPC TAB 1' to jump to the first tab, etc."
    (interactive)
    ;; Remove all previously created keybindings:
    (ignore-errors
      (dolist (indx (number-sequence 0 9))
        (general-nmap :prefix "SPC" (format "<tab> %d" indx) nil)))

    ;; Loop through the existing tabs, create keys for each:
    (seq-do-indexed 'ha-tab-bar-update-tab-keybinding (tab-bar-tabs)))

  (defun ha-tab-bar-update-tab-keybinding (tab-deets indx)
    "Create a keybinding to jump to tab described by TAB-DEETS.
  The key sequence, `SPC' `TAB' then INDX."
    (when (< indx 10)
      (let ((name (alist-get 'name tab-deets)))
        (general-nmap :prefix "SPC"
          ;; As indx is starts with 0, we need to create keybindings to
          ;; match the tab labels by incrementing it by one ... unless,
          ;; we are at 10, where we use 0 instead:
          (format "<tab> %d" (if (= indx 9) 0 (1+ indx)))
          `(,name . (lambda () (interactive)
                      "Switch to a tab-bar by number"
                      (setq ha-tab-bar-previous
                            (tab-bar-tab-name-current))
                      (tab-bar-select-tab ,(1+ indx))))))))
#+END_SRC

Any time I create or delete a new tab, we can call =ha-tab-bar-update-names=:

#+BEGIN_SRC emacs-lisp
  (advice-add #'tab-bar-new-tab :after #'ha-tab-bar-update-names)
  (advice-add #'tab-bar-close-tab :after #'ha-tab-bar-update-names)
  (advice-add #'tab-bar-close-other-tabs :after #'ha-tab-bar-update-names)

  (add-hook #'desktop-after-read-hook #'ha-tab-bar-update-names)
#+END_SRC
*** Add Numbers ot Tab Bar Mode
Christian Tietze had a [[https://christiantietze.de/posts/2022/02/emacs-tab-bar-numbered-tabs/][great idea]] for making the tab-bar numbers more distinguished from the labels on the tabs (he has a [[https://christiantietze.de/posts/2022/12/sf-symbols-emacs-tab-numbers/][follow up essay]] about using a different font, but since I need the portability of Unicode for Linux, I may need to synthesize both).

#+BEGIN_SRC emacs-lisp
  (defface ha/tab-bar-numbers
    (if (ha-running-on-macos?)
        '((t
           :inherit tab-bar-tab-face
           :family "SF Compact"
           :weight light))
      ;; On Linux, we use the dingbat unicode with default font:
      '((t
           :inherit tab-bar-tab-face
           :weight light)))
   "Face for tab numbers in both active and inactive tabs.")
#+END_SRC

First, create a variable that contains the Unicode values for the numbers. I am using the negative versions of the circle numbers from the Unicode dingbats collection:

#+BEGIN_SRC emacs-lisp
  (defvar ha/circle-numbers-alist
    (if (ha-running-on-macos?)
        '((0 . "􀃈")
          (1 . "􀃊")
          (2 . "􀃌")
          (3 . "􀃎")
          (4 . "􀘙")
          (5 . "􀃒")
          (6 . "􀑵")
          (7 . "􀃖")
          (8 . "􀃘")
          (9 . "􀑷")
          (10 . "􀃈"))
      ;; Keeping these around for the Mac just in case.
      ;; '((0 . "⓿")
      ;;   (1 . "❶")
      ;;   (2 . "❷")
      ;;   (3 . "❸")
      ;;   (4 . "❹")
      ;;   (5 . "❺")
      ;;   (6 . "❻")
      ;;   (7 . "❼")
      ;;   (8 . "❽")
      ;;   (9 . "❾"))
      '((0 . "🄌")
        (1 . "➊")
        (2 . "➋")
        (3 . "➌")
        (4 . "➍")
        (5 . "➎")
        (6 . "➏")
        (7 . "➐")
        (8 . "➑")
        (9 . "➒")))
    "Alist of integers to strings of circled unicode numbers.")
#+END_SRC

And then use this function to replace the standard  =tab-bar-tab-name-format-function=:

#+BEGIN_SRC emacs-lisp
  (defun ha/tab-bar-tab-name-format-default (tab i)
    "Replacement for `tab-bar-tab-name-format-function'.
    Places a special symbol for the initial digit."
    (let ((current-p (eq (car tab) 'current-tab)))
      (concat
       ;; First, add the tab number with a custom face
       (propertize
        (if (and tab-bar-tab-hints (< i 10)) (alist-get i ha/circle-numbers-alist) "")
        'face 'ha/tab-bar-numbers)
       ;; Fixed-width gap so active (variable-pitch) and inactive tabs match
       (propertize " " 'face 'tab-bar)
       ;; Add tab name with the face returned by tab-bar-tab-face-function
       (propertize
        (concat
         " "
         (alist-get 'name tab)
         (or (and tab-bar-close-button-show
                  (not (eq tab-bar-close-button-show
                           (if current-p 'non-selected 'selected)))
                  tab-bar-close-button)
             "")
         " ")
        'face (funcall tab-bar-tab-face-function tab)))))

  (setq tab-bar-tab-name-format-function
        #'ha/tab-bar-tab-name-format-default)
#+END_SRC

* Pretty Good Encryption
For details on using GnuPG in Emacs, see Mickey Petersen’s [[https://www.masteringemacs.org/article/keeping-secrets-in-emacs-gnupg-auth-sources][GnuPG Essay]].

On Linux, GPG is pretty straight-forward, but on the Mac, I often have troubles doing:
#+begin_src sh
  brew install gpg
#+end_src
Next, on every reboot, start the agent:
#+begin_src sh
  /opt/homebrew/bin/gpg-agent --daemon
#+end_src

Also, as [[https://www.bytedude.com/gpg-in-emacs/][bytedude]] mentions, I need to use the =epa-pineentry-mode= to =loopback= to actually get a prompt for the password, instead of an error. Also let's cache as much as possible, as my home machine is pretty safe, and my laptop is shutdown a lot.

#+begin_src emacs-lisp
  (use-package epa-file
    :straight (:type built-in)
    :custom
    (epg-debug t)
    (auth-source-debug t)
    ;; Since I normally want symmetric encryption, and don't want
    ;; to use the "key selection":
    (epa-file-select-keys 'symmetric-only)
    ;; Make sure we prompt in the minibuffer for the password:
    (epg-pinentry-mode 'loopback)
    ;; I trust my Emacs session, so I don't bother expiring my pass:
    (auth-source-cache-expiry nil)

    :config
    (setenv "GPG_AGENT_INFO" nil)
    (ignore-error (epa-file-enable)))
#+end_src

Since I already (at this point in my file) have Org installed and running, the following code configures the encryption of certain header sections (see [[https://orgmode.org/worg/org-tutorials/encrypting-files.html][this tutorial]]). Headers with a =:crypt:tag (see =org-crypt-tag-matcher= to change it) will be encrypted.

To temporarily read an encrypted part, and call =M-x org-decrypt-entry= when the cursor is inside that section. Saving the file, will re-encrypt it.

#+begin_src emacs-lisp :tangle no
  (use-package org
    :config
    (require 'org-crypt)

    (org-crypt-use-before-save-magic)
    (setq org-tags-exclude-from-inheritance (quote ("crypt")))
    ;; GPG key to use for encryption
    ;; Either the Key ID or set to nil to use symmetric encryption.
    (setq org-crypt-key nil))
#+end_src

* Technical Artifacts :noexport:
Let's provide a name so we can =require= this file:
#+begin_src emacs-lisp :exports none
  (provide 'ha-config)
  ;;; ha-config.el ends here
#+end_src

Before you can build this on a new system, make sure that you put the cursor over any of these properties, and hit: ~C-c C-c~

#+description: A literate programming file for configuring Emacs.

#+property:    header-args:sh :tangle no
#+property:    header-args:emacs-lisp :tangle yes
#+property:    header-args   :results none   :eval no-export   :comments no

#+options:     num:nil toc:t todo:nil tasks:nil tags:nil date:nil
#+options:     skip:nil author:nil email:nil creator:nil timestamp:nil
#+infojs_opt:  view:nil toc:t ltoc:t mouse:underline buttons:0 path:http://orgmode.org/org-info.js
