#+title:  Leader Key Sequences
#+author: Howard X. Abrams
#+date:   2024-01-31
#+filetags: emacs hamacs

A literate programming file for defining leaders with general

#+begin_src emacs-lisp :exports none
  ;;; ha-leader --- defining leaders with general -*- lexical-binding: t; -*-
  ;;
  ;; © 2024 Howard X. Abrams
  ;;   This work is 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: January 31, 2024
  ;;
  ;; While obvious, GNU Emacs does not include this file or project.
  ;;
  ;; *NB:* Do not edit this file. Instead, edit the original literate file at:
  ;;            /Users/howard.abrams/other/hamacs/ha-leader.org
  ;;       And tangle the file to recreate this one.
  ;;
  ;;; Code:
#+end_src

* Introduction
The one thing that both Spacemacs and Doom taught me, is how much I like the /key sequences/ that begin with a leader key. In both of those systems, the key sequences begin in the /normal state/ with a space key. This means, while typing in /insert state/, I have to escape to /normal state/ and then hit the space.

I'm not trying an experiment where specially-placed function keys on my fancy ergodox keyboard can kick these off using [[https://github.com/noctuid/general.el][General Leader]] project. Essentially, I want a set of leader keys for Evil's /normal state/ as well as a global leader in all modes.

#+begin_src emacs-lisp
  (use-package general
    :config
    (setq general-use-package-emit-autoloads t)

    (general-evil-setup t)

    (general-create-definer ha-leader
      :states '(normal visual motion)
      :keymaps 'override
      :prefix "SPC"
      :non-normal-prefix "M-SPC"
      :global-prefix "<f13>")

    (general-create-definer ha-local-leader
      :states '(normal visual motion)
      :prefix "<f17>")

    (general-nmap "SPC m" (general-simulate-key "," :which-key "major mode")))
#+end_src
* Relabel the G Keys
Can’t remember all the shortcuts on the ~g~ key, and =which-key= displays the entire function, so let’s /re-add/ those keybindings, but with labels. The ~g~ is extemely convenient, yet I realize that I will never use some of the default keybindings (like ~g m~ to go to the middle of the line? Too imprecise). So I am also going to delete some of them.

#+begin_src emacs-lisp
  (use-package evil
    :general
    (:states '(normal visual motion operator)
             ;; These go into operator mode, so the key sequence, g U i o
             ;; upper cases the symbol at point:
             "g u" '("downcase" . evil-downcase)
             "g U" '("upcase" . evil-upcase)
             "g ~" '("invert case" . evil-invert-case)

             ;; Use this ALL the time:
             "g ;" '("last change →" . evil-goto-last-change)
             "g :" '("last change ←" . evil-goto-last-change-reverse)
             "g d" '("goto def" . evil-goto-definition)
             "g i" '("resume insert" . evil-insert-resume)
             "g v" '("resume visual" . evil-visual-restore)

             "g g" '("goto first line" . evil-goto-first-line)
             "g f" '("find file" . find-file-at-point)

             "g e" '("← WORD end" . evil-backward-WORD-end) ; like b
             "g E" '("← word end" . evil-backward-word-end) ; like B
             "g w" '("→ WORD end" . evil-forward-WORD-end)
             "g W" '("→ word end" . evil-forward-word-end)

             ;; Not sure how to use these two as they need text objs
             "g n" '("next match" , evil-next-match)
             "g N" '("prev match" , evil-previous-match)

             "g P" '("paste after" . evil-paste-before-cursor-after)

             ;; Let's clean out keybindings already in normal mode
             ;; without the initial g:
             "g #" nil   ; evil-search-unbounded-word-backward
             "g *" nil   ; evil-search-unbounded-word-forward
             "g ^" nil   ; evil-first-non-blank
             "g $" nil   ; evil-end-of-line
             "g _" nil   ; evil-last-non-blank ... eh
             "g 0" nil   ; evil-beginning-of-line
             "g &" nil   ; evil-ex-repeat-global-substitute
             "g 8" nil   ; what-cursor-position
             "g F" nil   ; evil-find-file-at-point-with-line
             "g J" nil   ; evil-join-whitespace
             "g I" nil   ; evil-insert-0-line ... just use I
             "g m" nil   ; evil-middle-of-visual-line
             "g M" nil   ; evil-percentage-of-line ... middle?
             "g T" nil   ; tab-bar-switch-to-prev-tab
             "g t" nil   ; tab-bar-switch-to-next-tab

             "g j" nil   ; This will be a major-mode-specific keybinding
             "g k" nil

             (kbd "g C-]") nil
             (kbd "g <up>") nil
             (kbd "g <down>") nil
             (kbd "g <left>") nil
             (kbd "g <right>") nil
             (kbd "g <home>") nil
             (kbd "g <end>") nil))
#+end_src

While we are at it, let’s readd, and relabel the ~z~ command functions:
#+begin_src emacs-lisp
  (use-package evil
    :general
    (:states '(normal visual motion operator)
             "z q" '("fill para" . fill-paragraph)
             "z Q" '("unfill para" . unfill-paragraph)
             "z p" '("unfill para" . unfill-paragraph)

             "z m" '("scroll to center" . evil-scroll-line-to-center)
             "z t" '("scroll to top" . evil-scroll-line-to-top)
             "z b" '("scroll to bottom" . evil-scroll-line-to-bottom)
             (kbd "z <left>") '("scroll left" . evil-scroll-column-left)
             (kbd "z <right>") '("scroll right" . evil-scroll-column-right)

             "z a" '("toggle fold" . evil-toggle-fold)
             "z f" '("close fold" . evil-close-fold)
             "z o" '("open fold" . evil-open-fold)
             "z F" '("close all folds" . evil-close-folds)
             "z O" '("open all folds" . evil-open-folds)
             ;; Open a fold at point recursively? Never see a need:

             ;; Since I have overridden z-l and whatnot, why have z-h?
             "z e" nil   ; evil-scroll-end-column
             "z h" nil   ; evil-scroll-column-left
             "z l" nil   ; evil-scroll-column-right
             "z r" nil
             "z s" nil   ; evil-scroll-start-column
             "z ^" nil   ; evil-scroll-top-line-to-bottom
             "z +" nil   ; evil-scroll-bottom-line-to-top
             "z -" nil   ; evil-scroll-line-to-bottom-first-non-blank
             "z ." nil   ; evil-scroll-line-to-center-first-non-blank
             (kbd "z RET") nil ; evil-scroll-line-to-top
             (kbd "z <return>") nil)) ; evil-scroll-line-to-top
#+end_src
* Top-Level Operations
Let's try this general "space" prefix by defining some top-level operations, including hitting ~space~ twice to bring up the =M-x= collection of functions:
#+begin_src emacs-lisp
  (ha-leader
    "SPC" '("M-x" . execute-extended-command)
    "<escape>" '(keyboard-escape-quit :which-key t)
    "."   '("repeat" . repeat)
    "!"   '("shell command" . shell-command)
    "|"   'piper
    "X"   '("org capture" . org-capture)
    "L"   '("store org link" . org-store-link)
    "RET" 'bookmark-jump
    "a"   '(:ignore t :which-key "apps")
    "a <escape>" '(keyboard-escape-quit :which-key t)
    "m"   '(:ignore t :which-key "mode")
    "m <escape>" '(keyboard-escape-quit :which-key t)
    "o"   '(:ignore t :which-key "org/open")
    "o <escape>" '(keyboard-escape-quit :which-key t)
    "o i" 'imenu
    "u"   'universal-argument)
#+end_src
And ways to stop the system:
#+begin_src emacs-lisp
  (ha-leader
    "q"  '(:ignore t :which-key "quit/session")
    "q <escape>" '(keyboard-escape-quit :which-key t)
    "q b" '("bury buffer" . bury-buffer)
    "q w" '("close window" . delete-window)
    "q K" '("kill emacs (and dæmon)" . save-buffers-kill-emacs)
    "q q" '("quit emacs" . save-buffers-kill-terminal)
    "q Q" '("quit without saving" . evil-quit-all-with-error-code))
#+end_src
And ways to load my tangled org-files:
#+begin_src emacs-lisp
  (ha-leader
    "h h"   '(:ignore t :which-key "hamacs")
    "h h <escape>" '(keyboard-escape-quit :which-key t)
    "h h f" '("features"    . ha-hamacs-features)
    "h h e" '("edit"        . ha-hamacs-find-file)
    "h h h" '("reload"      . ha-hamacs-load)
    "h h a" '("reload all"  . ha-hamacs-reload-all))
#+end_src
* File Operations
While =find-file= is still my bread and butter,  I like getting information about the file associated with the buffer. For instance, the file path:
#+begin_src emacs-lisp
  (defun ha-relative-filepath (filepath)
    "Return the FILEPATH without the HOME directory and typical filing locations.
  The expectation is that this will return a filepath with the proejct name."
    (let* ((home-re (rx (literal (getenv "HOME")) "/"))
           (work-re (rx (regexp home-re)
                        (or "work" "other" "projects") ; Typical organization locations
                        "/"
                        (optional (or "4" "5" "xway") "/") ; Sub-organization locations
                        )))
      (cond
       ((string-match work-re filepath) (substring filepath (match-end 0)))
       ((string-match home-re filepath) (substring filepath (match-end 0)))
       (t filepath))))

  (defun ha-yank-buffer-path (&optional root)
    "Copy the file path of the buffer relative to my 'work' directory, ROOT."
    (interactive)
    (if-let (filename (buffer-file-name (buffer-base-buffer)))
        (message "Copied path to clipboard: %s"
                 (kill-new (abbreviate-file-name
                            (if root
                                (file-relative-name filename root)
                              (ha-relative-filepath filename)))))
      (error "Couldn't find filename in current buffer")))

  (defun ha-yank-project-buffer-path (&optional root)
    "Copy the file path of the buffer relative to the file's project.
  When given ROOT, this copies the filepath relative to that."
    (interactive)
    (if-let* ((filename (buffer-file-name (buffer-base-buffer)))
              (relative (f-relative filename (or nil (project-root (project-current))))))
        (progn
          (kill-new relative)
          (message "Copied path to clipboard: %s" relative))
      (message "Couldn't find filename in current buffer")))
#+end_src

This simple function allows me to load a project-specific file in a numbered window, based on winum:
#+begin_src emacs-lisp
  (defun find-file-in-window (win)
    "Change the buffer in a particular window number."
    (interactive)
    (if (windowp win)
        (aw-switch-to-window win)
      (winum-select-window-by-number win))
    (project-find-file))
#+end_src

With these helper functions in place, I can create a leader collection for file-related functions:

#+begin_src emacs-lisp
  (ha-leader
    "f"  '(:ignore t :which-key "files")
    "f <escape>" '(keyboard-escape-quit :which-key t)
    "f a" '("load any" . find-file)
    "f f" '("load" . project-find-file)
    "f F" '("load new window" . find-file-other-window)
    "f l" '("locate" . locate)
    "f s" '("save" . save-buffer)
    "f S" '("save as" . write-buffer)
    "f r" '("recent" . recentf-open-files)
    "f c" '("copy" . copy-file)
    "f R" '("rename" . rename-file)
    "f x" '("delete" . delete-file)
    "f y" '("yank path" . ha-yank-buffer-path)
    "f Y" '("yank path from project" . ha-yank-project-buffer-path)
    "f d" '("dired" . dired)
    "f D" '("find dired" . find-dired)

    "f 1" '("load win-1" . ha-find-file-window-1)
    "f 2" '("load win-2" . ha-find-file-window-2)
    "f 3" '("load win-3" . ha-find-file-window-3)
    "f 4" '("load win-4" . ha-find-file-window-4)
    "f 5" '("load win-5" . ha-find-file-window-5)
    "f 6" '("load win-6" . ha-find-file-window-6)
    "f 7" '("load win-7" . ha-find-file-window-7)
    "f 8" '("load win-8" . ha-find-file-window-8)
    "f 9" '("load win-9" . ha-find-file-window-9))
#+end_src

The ~d~ brings up [[file:ha-applications.org::*Dired][Dired]], and ~D~ pulls up a =dired=, not on a single directory, but based on a pattern given to =find= (see [[https://www.masteringemacs.org/article/working-multiple-files-dired][this discussion on Mastering Emacs]]).

On Unix systems, the =locate= command is faster than =find= when searching the whole system, since it uses a pre-computed database, and =find= is faster if you need to search a specific directory instead of the whole system. On the Mac, we need to change the =locate= command:

#+begin_src emacs-lisp
  (when (ha-running-on-macos?)
    (setq locate-command "mdfind"))
#+end_src

The advantage of =mdfind= is that is searches for filename /and/ its contents of your search string.

Trying the [[https://github.com/benmaughan/spotlight.el][spotlight]] project, as it has a slick interface for selecting files:

#+begin_src emacs-lisp
  (use-package spotlight
    :config (ha-leader "f /" '("search files" . spotlight)))
#+end_src
* Buffer Operations
This section groups buffer-related operations under the "SPC b" sequence.

Putting the entire visible contents of the buffer on the clipboard is often useful:
#+begin_src emacs-lisp
  (defun ha-yank-buffer-contents ()
    "Copy narrowed contents of the buffer to the clipboard."
    (interactive)
    (kill-new (buffer-substring-no-properties
               (point-min) (point-max))))
#+end_src

This simple function allows me to switch to a buffer in a numbered window, based on winum:
#+begin_src emacs-lisp
  (defun switch-buffer-in-window (win)
    "Change the buffer in a particular window number."
    (interactive)
    (if (windowp win)
        (aw-switch-to-window win)
      (winum-select-window-by-number win))
    (consult-project-buffer))
#+end_src

And the collection of useful operations:
#+begin_src emacs-lisp
  (ha-leader
    "b"  '(:ignore t :which-key "buffers")
    "b <escape>" '(keyboard-escape-quit :which-key t)
    "b O" '("other" . project-switch-buffer-to-other-window)
    "b i" '("ibuffer" . ibuffer)
    "b I" '("ibuffer" . ibuffer-other-window)
    "b k" '("persp remove" . persp-remove-buffer)
    "b N" '("new" . evil-buffer-new)
    "b d" '("delete" . persp-kill-buffer*)
    "b r" '("revert" . revert-buffer)
    "b s" '("save" . save-buffer)
    "b S" '("save all" . evil-write-all)
    "b n" '("next" . next-buffer)
    "b p" '("previous" . previous-buffer)
    "b y" '("copy contents" . ha-yank-buffer-contents)
    "b z" '("bury" . bury-buffer)
    "b Z" '("unbury" . unbury-buffer)

    "b 1" '("load win-1" . (lambda () (interactive) (switch-buffer-in-window 1)))
    "b 2" '("load win-2" . (lambda () (interactive) (switch-buffer-in-window 2)))
    "b 3" '("load win-3" . (lambda () (interactive) (switch-buffer-in-window 3)))
    "b 4" '("load win-4" . (lambda () (interactive) (switch-buffer-in-window 4)))
    "b 5" '("load win-5" . (lambda () (interactive) (switch-buffer-in-window 5)))
    "b 6" '("load win-6" . (lambda () (interactive) (switch-buffer-in-window 6)))
    "b 7" '("load win-7" . (lambda () (interactive) (switch-buffer-in-window 7)))
    "b 8" '("load win-8" . (lambda () (interactive) (switch-buffer-in-window 8)))
    "b 9" '("load win-9" . (lambda () (interactive) (switch-buffer-in-window 9))))
#+end_src
* Bookmarks
I like the idea of dropping returnable bookmarks, however, 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
    :config
    (ha-leader
      ;; Set or delete a bookmark associated with project:
      "b m" '("set proj mark" . bookmark-in-project-toggle)
      "b M" '("set global mark" . bookmark-set)
      "b X" '("delete mark" . bookmark-delete)
      "b g" '("goto proj mark" . bookmark-in-project-jump)
      "b <down>" '("next mark" . bookmark-in-project-jump-next)
      "b <up>" '("next mark" . bookmark-in-project-jump-previous)))
#+end_src
* Toggle Switches
The goal here is toggle switches and other miscellaneous settings.
#+begin_src emacs-lisp
  (ha-leader
    "t"   '(:ignore t :which-key "toggles")
    "t <escape>" '(keyboard-escape-quit :which-key t)
    "t a" '("abbrev"         . abbrev-mode)
    "t d" '("debug"          . toggle-debug-on-error)
    "t F" '("show functions" . which-function-mode)
    "t f" '("auto-fill"      . auto-fill-mode)
    "t o" '("overwrite"      . overwrite-mode)
    "t l" '("line numbers"   . display-line-numbers-mode)
    "t R" '("read only"      . read-only-mode)
    "t r" '("recentf mode"   . recentf-mode)
    "t t" '("truncate"       . toggle-truncate-lines)
    "t T" '("tramp mode"     . tramp-mode)
    "t v" '("visual"         . visual-line-mode)
    "t w" '("whitespace"     . whitespace-mode))
#+end_src

* Line Numbers
Since we can't automatically toggle between relative and absolute line numbers, we create this function:
#+begin_src emacs-lisp
  (defun ha-toggle-relative-line-numbers ()
    (interactive)
    (if (eq display-line-numbers 'relative)
        (setq display-line-numbers t)
      (setq display-line-numbers 'relative)))
#+end_src
Add it to the toggle menu:
#+begin_src emacs-lisp
  (ha-leader
    "t r" '("relative lines" . ha-toggle-relative-line-numbers))
#+end_src
* Narrowing
I like the focus the [[info:emacs#Narrowing][Narrowing features]] offer, but what a /dwim/ aspect:
#+begin_src emacs-lisp
  (defun ha-narrow-dwim ()
    "Narrow to region or org-tree or widen if already narrowed."
    (interactive)
    (cond
     ((buffer-narrowed-p) (widen))
     ((region-active-p)  (narrow-to-region (region-beginning) (region-end)))
     ((and (fboundp 'logos-focus-mode)
           (seq-contains local-minor-modes 'logos-focus-mode 'eq))
      (logos-narrow-dwim))
     ((eq major-mode 'org-mode) (org-narrow-to-subtree))
     (t  (narrow-to-defun))))
#+end_src
And put it on the toggle menu:
#+begin_src emacs-lisp
  (ha-leader "t n" '("narrow" . ha-narrow-dwim))
#+end_src
* Window Operations
While it comes with Emacs, I use [[https://www.emacswiki.org/emacs/WinnerMode][winner-mode]] to undo window-related changes:
#+begin_src emacs-lisp
  (use-package winner
    :custom
    (winner-dont-bind-my-keys t)
    :config
    (winner-mode +1))
#+end_src
** Ace Window
Use the [[https://github.com/abo-abo/ace-window][ace-window]] project to jump to any window you see.

Often transient buffers show in other windows, obscuring my carefully crafted display. Instead of jumping into a window, typing ~q~ (to either call [[help:quit-buffer][quit-buffer]]) if available, or [[help:bury-buffer][bury-buffer]] otherwise. This function hooks to =ace-window=
#+begin_src emacs-lisp
  (defun ha-quit-buffer (window)
    "Quit or bury buffer in a given WINDOW."
    (interactive)
    (aw-switch-to-window window)
    (unwind-protect
        (condition-case nil
            (quit-buffer)
          (error
           (bury-buffer))))
    (aw-flip-window))
#+end_src

Since I use numbers for the window, I can make the commands more mnemonic, and add my own:
#+begin_src emacs-lisp
  (use-package ace-window
    :init
    (setq aw-dispatch-alist
          '((?d aw-delete-window "Delete Window")
            (?m aw-swap-window "Swap Windows")
            (?M aw-move-window "Move Window")
            (?c aw-copy-window "Copy Window")
            (?b switch-buffer-in-window "Select Buffer")
            (?f find-file-in-window "Find File")
            (?n aw-flip-window)
            (?c aw-split-window-fair "Split Fair Window")
            (?s aw-split-window-vert "Split Vert Window")
            (?v aw-split-window-horz "Split Horz Window")
            (?o delete-other-windows "Delete Other Windows")
            (?q ha-quit-buffer "Quit Buffer")
            (?w aw-execute-command-other-window "Execute Command")
            (?? aw-show-dispatch-help)))

    :bind ("s-w" . ace-window))
#+end_src
Keep in mind, these shortcuts work with more than two windows open. For instance, ~SPC w w d 3~ closes the "3" window.
** Transpose Windows
My office at work has a monitor oriented vertically, and to move an Emacs with “three columned format” to a “stacked format” I use the [[https://www.emacswiki.org/emacs/TransposeFrame][transpose-frame]] package:
#+begin_src emacs-lisp
  (use-package transpose-frame)
#+end_src
** Winum
To jump to a window even quicker, use the [[https://github.com/deb0ch/emacs-winum][winum package]]:
#+begin_src emacs-lisp
  (use-package winum
    :bind (("s-1" . winum-select-window-1)
           ("s-2" . winum-select-window-2)
           ("s-3" . winum-select-window-3)
           ("s-4" . winum-select-window-4)
           ("s-5" . winum-select-window-5)
           ("s-6" . winum-select-window-6)
           ("s-7" . winum-select-window-7)
           ("s-8" . winum-select-window-8)
           ("s-9" . winum-select-window-9)))
#+end_src

This is nice since the window numbers are always present on a Doom modeline, but they sometime order the window numbers /differently/ than =ace-window=.

#+begin_src emacs-lisp
  (use-package winum
    :config (winum-mode +1))
#+end_src

Let's try this out with a Hydra since some I can /repeat/ some commands (e.g. enlarge window). It also allows me to organize the helper text.
#+begin_src emacs-lisp
  (use-package hydra
    :config
    (defhydra hydra-window-resize (:color blue :hint nil) "
  _w_: select _m_: move/swap _u_: undo  _^_: taller (t)  _+_: text larger
  _j_: go up  _d_: delete    _U_: undo+ _v_: shorter (T) _-_: text smaller
  _k_: down   _e_: balance   _r_: redo  _>_: wider       _F_: font larger
  _h_: left   _n_: v-split   _R_: redo+ _<_: narrower    _f_: font smaller
  _l_: right  _s_: split   _o_: only this window     _c_: choose (also 1-9)"
      ("w" ace-window)
      ("c" other-window                 :color pink) ; change window
      ("o" delete-other-windows)          ; “Only” this window
      ("d" delete-window)     ("x" delete-window)

      ;; Ace Windows ... select the window to affect:
      ("m" ace-swap-window)
      ("D" ace-delete-window)
      ("O" ace-delete-other-windows)

      ("u" winner-undo)
      ("U" winner-undo                 :color pink)
      ("C-r" winner-redo)
      ("r" winner-redo)
      ("R" winner-redo                 :color pink)

      ("J" evil-window-down            :color pink)
      ("K" evil-window-up              :color pink)
      ("H" evil-window-left            :color pink)
      ("L" evil-window-right           :color pink)

      ("j" evil-window-down)
      ("k" evil-window-up)
      ("h" evil-window-left)
      ("l" evil-window-right)

      ("x" transpose-frame)
      ("s" hydra-window-split/body)
      ("n" hydra-window-split/body)

      ("F" font-size-increase          :color pink)
      ("f" font-size-decrease          :color pink)
      ("+" text-scale-increase         :color pink)
      ("=" text-scale-increase         :color pink)
      ("-" text-scale-decrease         :color pink)
      ("^" evil-window-increase-height :color pink)
      ("v" evil-window-decrease-height :color pink)
      ("t" evil-window-increase-height :color pink)
      ("T" evil-window-decrease-height :color pink)
      (">" evil-window-increase-width  :color pink)
      ("<" evil-window-decrease-width  :color pink)
      ("." evil-window-increase-width  :color pink)
      ("," evil-window-decrease-width  :color pink)
      ("e" balance-windows)

      ("1" winum-select-window-1)
      ("2" winum-select-window-2)
      ("3" winum-select-window-3)
      ("4" winum-select-window-4)
      ("5" winum-select-window-5)
      ("6" winum-select-window-6)
      ("7" winum-select-window-7)
      ("8" winum-select-window-8)
      ("9" winum-select-window-9)

      ;; Extra bindings:
      ("q" nil :color blue)))

  (ha-leader "w" '("windows" . hydra-window-resize/body))
#+end_src
** Window Splitting
When I split a window, I have a following intentions:
  - Split and open a file from the prespective/project in the new window
  - Split and change to a buffer from the prespective in the new window
  - Split and move focus to the new window … you know, to await a new command

And when creating new windows, why isn't the new window selected? Also, when I create a new window, I typically want a different buffer or file shown.
#+begin_src emacs-lisp
  (defun ha-new-window (side file-or-buffer)
    (pcase side
      (:left  (split-window-horizontally))
      (:right (split-window-horizontally)
              (other-window 1))
      (:above (split-window-vertically))
      (:below (split-window-vertically)
              (other-window 1)))
    (pcase file-or-buffer
      (:file   (call-interactively 'project-find-file))
      (:buffer (call-interactively 'project-switch-to-buffer))
      (:term   (ha-shell (project-root (project-current))))))
#+end_src

Shame that hydra doesn’t have an /ignore-case/ feature.
#+begin_src emacs-lisp
  (use-package hydra
    :config
    (defhydra hydra-window-split (:color blue :hint nil)
      ("s" hydra-window-split-below/body "below")
      ("j" hydra-window-split-below/body "below")
      ("k" hydra-window-split-above/body "above")
      ("h" hydra-window-split-left/body "left")
      ("l" hydra-window-split-right/body "right")
      ("n" hydra-window-split-right/body "right"))

    (defhydra hydra-window-split-above (:color blue :hint nil)
      ("b" (lambda () (interactive) (ha-new-window :above :buffer)) "switch buffer")
      ("f" (lambda () (interactive) (ha-new-window :above :file))   "load file")
      ("t" (lambda () (interactive) (ha-new-window :above :term))   "terminal")
      ("k" split-window-below                                  "split window"))

    (defhydra hydra-window-split-below (:color blue :hint nil)
      ("b" (lambda () (interactive) (ha-new-window :below :buffer))        "switch buffer")
      ("f" (lambda () (interactive) (ha-new-window :below :file))          "load file    ")
      ("t" (lambda () (interactive) (ha-new-window :below :term))          "terminal")
      ("j" (lambda () (interactive) (split-window-below) (other-window 1)) "split window ")
      ("s" (lambda () (interactive) (split-window-below) (other-window 1)) "split window "))

    (defhydra hydra-window-split-right (:color blue :hint nil)
      ("b" (lambda () (interactive) (ha-new-window :right :buffer))        "switch buffer")
      ("f" (lambda () (interactive) (ha-new-window :right :file))          "load file")
      ("t" (lambda () (interactive) (ha-new-window :right :term))          "terminal")
      ("l" (lambda () (interactive) (split-window-right) (other-window 1)) "split window ")
      ("n" (lambda () (interactive) (split-window-right) (other-window 1)) "split window "))

    (defhydra hydra-window-split-left (:color blue :hint nil)
      ("b" (lambda () (interactive) (ha-new-window :left :buffer))         "switch buffer")
      ("f" (lambda () (interactive) (ha-new-window :left :file))           "load file    ")
      ("t" (lambda () (interactive) (ha-new-window :left :term))           "terminal")
      ("h" split-window-right                                         "split window")))
#+end_src
This means that, without thinking, the following just works:
  - ~SPC w s s s~ :: creates a window directly below this.
  - ~SPC w n n n~ :: creates a window directly to the right.
But, more importantly, the prefix ~w s~ gives me more precision to view what I need.
* Search Operations
Ways to search for information goes under the ~s~ key. The venerable sage has always been =grep=, but we now have new-comers, like [[https://github.com/BurntSushi/ripgrep][ripgrep]], which are really fast.
** ripgrep
Install the [[https://github.com/dajva/rg.el][rg]] package, which builds on the internal =grep= system, and creates a =*rg*= window with =compilation= mode, so ~C-j~ and ~C-k~ will move and show the results by loading those files.

#+begin_src emacs-lisp
  (use-package rg
    :config
    ;; Make an interesting Magit-like menu of options, which I don't use much:
    (rg-enable-default-bindings (kbd "M-R"))

    (ha-leader
      "s"  '(:ignore t :which-key "search")
      "s <escape>" '(keyboard-escape-quit :which-key t)
      "s q" '("close" . ha-rg-close-results-buffer)
      "s r" '("dwim" . rg-dwim)
      "s s" '("search" . rg)
      "s S" '("literal" . rg-literal)
      "s p" '("project" . rg-project)
      "s d" '("directory" . rg-dwim-project-dir)
      "s f" '("file only" . rg-dwim-current-file)
      "s j" '("next results" . ha-rg-go-next-results)
      "s k" '("prev results" . ha-rg-go-previous-results)
      "s b" '("results buffer" . ha-rg-go-results-buffer))

    (defun ha-rg-close-results-buffer ()
      "Close to the `*rg*' buffer that `rg' creates."
      (interactive)
      (kill-buffer "*rg*"))

    (defun ha-rg-go-results-buffer ()
      "Pop to the `*rg*' buffer that `rg' creates."
      (interactive)
      (pop-to-buffer "*rg*"))

    (defun ha-rg-go-next-results ()
      "Bring the next file results into view."
      (interactive)
      (ha-rg-go-results-buffer)
      (next-error-no-select)
      (compile-goto-error))

    (defun ha-rg-go-previous-results ()
      "Bring the previous file results into view."
      (interactive)
      (ha-rg-go-results-buffer)
      (previous-error-no-select)
      (compile-goto-error)))
#+end_src
Note we bind the key ~M-R~ to the [[help:rg-menu][rg-menu]], which is a Magit-like interface to =ripgrep=.

I don’t understand the bug associated with the =:general= extension to =use-package=, but it /works/, but stops everything else from working, so pulling it out into its own =use-package= section addresses that issue:
#+begin_src emacs-lisp
  (use-package rg
    :general (:states 'normal "gS" 'rg-dwim))
#+end_src
** wgrep
The [[https://github.com/mhayashi1120/Emacs-wgrep][wgrep package]] integrates with =ripgrep=. Typically, you hit ~i~ to automatically go into =wgrep-mode= and edit away, but since I typically want to edit everything at the same time, I have a toggle that should work as well:
#+begin_src emacs-lisp
  (use-package wgrep
    :after rg
    :commands wgrep-rg-setup
    :hook (rg-mode-hook . wgrep-rg-setup)
    :config
    (ha-leader
      :keymaps 'rg-mode-map  ; Actually, `i' works!
      "s w" '("wgrep-mode" . wgrep-change-to-wgrep-mode)
      "t w" '("wgrep-mode" . wgrep-change-to-wgrep-mode)))
#+end_src
* Text Operations
Stealing much of this from Spacemacs.
#+begin_src emacs-lisp
  (ha-leader
    "x"  '(:ignore t :which-key "text")
    "x <escape>" '(keyboard-escape-quit :which-key t)
    "x a" '("align"            . align-regexp)
    "x q" '("fill paragraph"   . fill-paragraph)
    "x p" '("unfill paragraph" . unfill-paragraph))
#+end_src

Unfilling a paragraph joins all the lines in a paragraph into a single line. Taken [[http://www.emacswiki.org/UnfillParagraph][from here]] … I use this all the time:
#+begin_src emacs-lisp
  (defun unfill-paragraph ()
    "Convert a multi-line paragraph into a single line of text."
    (interactive)
    (let ((fill-column (point-max)))
      (fill-paragraph nil)))
#+end_src
* Help Operations
While the ~C-h~ is easy enough, I am now in the habit of typing ~SPC h~ instead.
Since I tweaked the help menu, I craft my own menu:
#+begin_src emacs-lisp
  (ha-leader
    "h"  '(:ignore t :which-key "help")
    "h <escape>" '(keyboard-escape-quit :which-key t)
    "h ." '("cursor position"  . what-cursor-position)
    "h a" '("apropos"          . apropos-command)
    "h c" '("elisp cheatsheet" . shortdoc-display-group)
    "h e" '("errors"           . view-echo-area-messages)
    "h f" '("function"         . helpful-callable)
    "h F" '("font"             . describe-font)
    "h =" '("face"             . describe-face)
    "h k" '("key binding"      . helpful-key)
    "h K" '("key map"          . describe-keymap)
    "h m" '("mode"             . describe-mode)
    "h o" '("symbol"           . describe-symbol)
    "h p" '("package"          . describe-package)
    "h s" '("info symbol"      . info-lookup-symbol)
    "h v" '("variable"         . helpful-variable)
    "h i" '("info"             . info)
    "h j" '("info jump"        . info-apropos)

    "h E" '("emacs info"       . (lambda () (interactive) (info "emacs")))
    "h L" '("emacs-lisp"       . (lambda () (interactive) (info "elisp")))
    "h O" '("org info"         . (lambda () (interactive) (info "org")))
    ;; Since I do a lot of literate programming, I appreciate a quick
    ;; jump directly into the Info manual...
    "h B" '("org babel"        . (lambda () (interactive)
                                   (org-info-open "org#Working with Source Code" nil))))
#+end_src

Remember these keys in the *Help* buffer:
  - ~s~ :: view source of the function
  - ~i~ :: view info manual of the function

Let's make Info behave a little more VI-like:
#+begin_src emacs-lisp
  (use-package info
    :straight (:type built-in)
    :general
    (:states 'normal :keymaps 'Info-mode-map
             "B" 'Info-bookmark-jump
             "Y" 'org-store-link
             "H" 'Info-history-back
             "L" 'Info-history-forward
             "u" 'Info-up
             "U" 'Info-directory
             "T" 'Info-top-node
             "p" 'Info-backward-node
             "n" 'Info-forward-node))    ; Old habit die hard
#+end_src
* Consult
The [[https://github.com/minad/consult][consult project]] aims to use libraries like [[*Vertico][Vertico]] to enhance specific, built-in, Emacs functions. I appreciate this project that when selecting an element in the minibuffer, it displays what you are looking at… for instance, it previews a buffer before choosing it. Unlike /Vertico/ and /Orderless/, you need to bind keys to its special functions (or rebind existing keys that do something similar).

#+begin_src emacs-lisp
  (use-package consult
    :after general
    ;; Enable automatic preview at point in the *Completions* buffer. This is
    ;; relevant when you use the default completion UI.
    :hook (completion-list-mode . consult-preview-at-point-mode)

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

    (ha-leader
      "RET" '("bookmark"            . consult-bookmark)
      "k"   '("marks"               . consult-mark)
      "K"   '("global marks"        . consult-global-mark)
      "b b" '("switch"              . consult-buffer)
      "b B" '("proj switch"         . consult-project-buffer)
      "b o" '("switch win"          . consult-buffer-other-window)
      "f g" '("find grep"           . consult-ripgrep)
      "h I" '("info manual"         . consult-info)
      "h O" '("org info"            . (lambda () (interactive) (consult-info "org")))
      "h M" '("man pages"           . consult-man)
      "t m" '("minor mods"          . consult-minor-mode-menu)
      "x i" '("choose from imenu"   . consult-imenu)
      "x I" '("choose from outline" . consult-outline)
      "x r" '("registers"           . consult-register)
      "x y" '("preview yank"        . consult-yank-pop))

    :bind (("s-v" . consult-yank-pop)
           ("M-X" . consult-mode-command)) ; Hrm...

    :general
    (:states 'normal
             "gp" '("preview paste" . 'consult-yank-pop)
             "gs" '("go to line" . 'consult-line)))
#+end_src

If found the =consult-mark= as part of [[https://arialdomartini.github.io/emacs-mark-ring][this essay]] about the =mark=.

An under-appreciated version of Consult is the /changing your mind/ aspect. Type ~SPC b b~ to switch to a different buffer, and change your mind, “oh, I really need a file!” Type ~f SPC~ and it switches to a file browser. Nope, I did need the buffer, type ~b SPC~ and your back to buffer switching. Other /narrowing/ keys:

    - ~b~ :: Buffers
    - ~SPC~ :: Hidden buffers
    - ~*~ :: Modified buffers
    - ~f~ :: Files (Requires recentf-mode)
    - ~r~ :: File registers
    - ~m~ :: Bookmarks
    - ~p~ :: Project

* Embark
The [[https://github.com/oantolin/embark/][embark]] project offers /actions/ on /targets/. I'm primarily thinking of acting on selected items in the minibuffer, but these commands act anywhere.  I need an easy-to-use keybinding that doesn't conflict. Hey, that is what the Super key is for, right?
#+begin_src emacs-lisp
  (use-package embark
    :bind
    (("s-." . embark-act)               ; Work in minibuffer and elsewhere
     ("s-/" . embark-dwim))

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

    :config
    (ha-leader "h K" '("keybindings" . embark-bindings)))
#+end_src

In [[https://karthinks.com/software/fifteen-ways-to-use-embark/][15 Ways to Use Embark]], Karthik Chikmagalur suggests a nifty macro for integrating Embark with [[Ace Window][Ace Window]]:
#+begin_src emacs-lisp
  (use-package embark
    :after ace-window
    :config
    (defmacro my/embark-ace-action (fn)
      `(defun ,(intern (concat "my/embark-ace-" (symbol-name fn))) ()
         (interactive)
         (with-demoted-errors "%s"
           (require 'ace-window)
           (let ((aw-dispatch-always t))
             (aw-switch-to-window (aw-select nil))
             (call-interactively (symbol-function ',fn))))))

    (defmacro my/embark-split-action (fn split-type)
      `(defun ,(intern (concat "my/embark-"
                               (symbol-name fn)
                               "-"
                               (car (last  (split-string
                                            (symbol-name split-type) "-"))))) ()
         (interactive)
         (funcall #',split-type)
         (call-interactively #',fn)))

    ;; Use the macros to define some helper functions:
    (my/embark-ace-action find-file)                             ; --> my/embark-ace-find-file
    (my/embark-ace-action switch-to-buffer)                      ; --> my/embark-ace-switch-to-buffer
    (my/embark-ace-action bookmark-jump)                         ; --> my/embark-ace-bookmark-jump
    (my/embark-split-action find-file split-window-below)        ; --> my/embark-find-file-below
    (my/embark-split-action find-file split-window-right)        ; --> my/embark-find-file-right
    (my/embark-split-action switch-to-buffer split-window-below) ; --> my/embark-switch-to-buffer-below
    (my/embark-split-action switch-to-buffer split-window-right) ; --> my/embark-switch-to-buffer-right
    (my/embark-split-action bookmark-jump split-window-below)    ; --> my/embark-bookmark-jump-below
    (my/embark-split-action bookmark-jump split-window-right))   ; --> my/embark-bookmark-jump-right
#+end_src

We can rebind the various =embark-xyz-map= with calls to our macroized functions:
#+begin_src emacs-lisp
  (use-package embark
    :bind
    (:map embark-file-map
     ("y" . embark-copy-as-kill)
     ("Y" . embark-save-relative-path)
     ("W" . nil)
     ("w" . my/embark-ace-find-file)
     ("2" . my/embark-find-file-below)
     ("3" . my/embark-find-file-right)
     :map embark-buffer-map
     ("y" . embark-copy-as-kill)
     ("w" . my/embark-ace-switch-to-buffer)
     ("2" . my/embark-switch-to-buffer-below)
     ("3" . my/embark-switch-to-buffer-right)
     :map embark-file-map
     ("y" . embark-copy-as-kill)
     ("w" . my/embark-ace-bookmark-jump)
     ("2" . my/embark-bookmark-jump-below)
     ("3" . my/embark-bookmark-jump-right)))
#+end_src

According to [[https://elpa.gnu.org/packages/embark-consult.html#orgc76b5de][this essay]], Embark cooperates well with the [[https://github.com/minad/marginalia][Marginalia]] and [[https://github.com/minad/consult][Consult]] packages. Neither of those packages is a dependency of Embark, but Embark supplies a hook for Consult where Consult previews can be done from Embark Collect buffers:
#+begin_src emacs-lisp
  (use-package embark-consult
    :after (embark consult)
    :demand t ; only necessary if you have the hook below
    ;; if you want to have consult previews as you move around an
    ;; auto-updating embark collect buffer
    :hook
    (embark-collect-mode . consult-preview-at-point-mode))
#+end_src

According to the [[https://elpa.gnu.org/packages/embark-consult.html][Embark-Consult page]]:
#+begin_quote
Users of the popular [[https://github.com/justbur/emacs-which-key][which-key]] package may prefer to use the =embark-which-key-indicator= from the [[https://github.com/oantolin/embark/wiki/Additional-Configuration#use-which-key-like-a-key-menu-prompt][Embark wiki]]. Just copy its definition from the wiki into your configuration and customize the =embark-indicators= user option to exclude the mixed and verbose indicators and to include =embark-which-key-indicator=.
#+end_quote
In other words, typing ~s-.~ to call Embark, specifies the options in a buffer, but the following code puts them in a smaller configuration directly above the selections.

#+begin_src emacs-lisp
  (defun embark-which-key-indicator ()
    "An embark indicator that displays keymaps using which-key.
  The which-key help message will show the type and value of the
  current target followed by an ellipsis if there are further
  targets."
    (lambda (&optional keymap targets prefix)
      (if (null keymap)
          (which-key--hide-popup-ignore-command)
        (which-key--show-keymap
         (if (eq (plist-get (car targets) :type) 'embark-become)
             "Become"
           (format "Act on %s '%s'%s"
                   (plist-get (car targets) :type)
                   (embark--truncate-target (plist-get (car targets) :target))
                   (if (cdr targets) "…" "")))
         (if prefix
             (pcase (lookup-key keymap prefix 'accept-default)
               ((and (pred keymapp) km) km)
               (_ (key-binding prefix 'accept-default)))
           keymap)
         nil nil t (lambda (binding)
                     (not (string-suffix-p "-argument" (cdr binding))))))))

  (setq embark-indicators
        '(embark-which-key-indicator
          embark-highlight-indicator
          embark-isearch-highlight-indicator))

  (defun embark-hide-which-key-indicator (fn &rest args)
    "Hide the which-key indicator immediately when using the completing-read prompter."
    (which-key--hide-popup-ignore-command)
    (let ((embark-indicators
           (remq #'embark-which-key-indicator embark-indicators)))
      (apply fn args)))

  (advice-add #'embark-completing-read-prompter
              :around #'embark-hide-which-key-indicator)
#+end_src


* Technical Artifacts                                :noexport:

Let's =provide= a name so we can =require= this file:

#+begin_src emacs-lisp :exports none
  (provide 'ha-leader)
  ;;; ha-leader.el ends here
#+end_src

#+description: defining leaders with general

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

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