hamacs/ha-evil.org
2024-01-19 17:43:24 -08:00

1426 lines
64 KiB
Org Mode
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#+TITLE: On the Subject of Being Evil
#+AUTHOR: Howard X. Abrams
#+DATE: 2023-12-21
#+FILETAGS: :emacs:
A literate programming file for configuring Evil mode in Emacs.
#+begin_src emacs-lisp :exports none
;;; ha-evil --- configuring Evil mode in Emacs. -*- lexical-binding: t; -*-
;;
;; © 2023 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: December 21, 2023
;;
;; 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-evil.org
;; And tangle the file to recreate this one.
;;
;;; Code:
#+end_src
* Introduction
As a grizzled veteran of the Emacs-VI Wars, Ive decided to take advantage of both by using VI keybindings on top of Emacs. However, after thirty years of Emacs, my interface follows different goals:
- Most buffers begin in Evils /normal state/, e.g. normal mode for VIers.
- Pressing ~i~ or ~a~ jumps into a state of total Emacs, with the exception of ~Escape~ going back to Evil. This means, that while typing ~C-p~ goes up a line, and doesnt auto-complete.
- I dont use ~:~ and instead use ~M-x~ or better yet, ~SPC SPC~ (typing the space key twice).
- The ~Space~ doesnt advance a letter, but instead displays a tree of highly-customized functions, displayable at the bottom of my screen, e.g.
[[file:screenshots/ha-leader.png]]
Some advice that I followed:
- [[https://github.com/noctuid/evil-guide][Evil Guide]]
- [[https://nathantypanski.com/blog/2014-08-03-a-vim-like-emacs-config.html][A Vim-like Emacs Configuration from Nathan Typanski]]
- [[https://stackoverflow.com/questions/25542097/emacs-evil-mode-how-to-change-insert-state-to-emacs-state-automatically][Evil insert state is really Emacs?]] Real answer to that is to set [[help:evil-disable-insert-state-bindings][evil-disable-insert-state-bindings]]
* Evil-Specific Keybindings
I split the configuration of Evil mode into sections. First, global settings:
#+begin_src emacs-lisp
(use-package evil
:init
(setq evil-undo-system 'undo-fu
evil-auto-indent t
evil-respect-visual-line-mode t
evil-want-fine-undo t ; Be more like Emacs
evil-disable-insert-state-bindings t
evil-want-keybinding nil ; work with evil-collection
evil-want-integration t
evil-want-C-u-scroll nil
evil-want-C-i-jump nil
evil-escape-key-sequence "jk"
evil-escape-unordered-key-sequence t)
;; This is _essentially_ the Ctrl-g sequence for getting out of jail:
(global-set-key (kbd "<escape>") 'keyboard-escape-quit))
#+end_src
The Escape key act like ~C-g~ and always go back to normal mode?
#+begin_src emacs-lisp
(use-package evil
:config
(global-set-key (kbd "<escape>") 'keyboard-escape-quit)
(evil-mode))
#+end_src
Even with the [[Evil Collection]], some modes should be Emacs:
#+begin_src emacs-lisp
(use-package evil
:config
(dolist (mode '(custom-mode
eshell-mode
git-rebase-mode
erc-mode
circe-server-mode
circe-chat-mode
circe-query-mode
vterm-mode))
(add-to-list 'evil-emacs-state-modes mode)))
#+end_src
Im not a long term VI user, and I generally like /easy keys/, e.g. ~w~, have larger jumps, and /harder keys/, e.g. ~W~ (shifted), have smaller, fine-grained jumps. So I am switching these around:
#+begin_src emacs-lisp
(use-package evil
:config
(require 'evil-commands)
(evil-define-key '(normal visual motion operator) 'global
"w" 'evil-forward-WORD-begin
"W" 'evil-forward-word-begin
"e" 'evil-forward-WORD-end
"E" 'evil-forward-word-end
;; This may be an absolute heresy to most VI users,
;; but I'm Evil and really, I use M-x and SPC instead.
;; Besides, I don't know any : colon commands...
":" 'evil-repeat-find-char-reverse)
;; The `b' key seems to need its own configuration setting:
(evil-define-key '(normal visual motion operator) 'global
"b" 'evil-backward-WORD-begin)
(evil-define-key '(normal visual motion operator) 'global
"B" 'evil-backward-word-begin)
;; Note that evil-backward-word-end is on the `g e':
;; Not a long-term VI user, so let's Emacsify some other keybindings:
(evil-define-key '(normal visual motion operator) 'global
(kbd "C-b") 'scroll-up-command
(kbd "C-f") 'scroll-down-command
(kbd "C-p") 'previous-line
(kbd "C-n") 'next-line
;; I have better window control:
(kbd "C-w") 'sp-kill-region))
#+end_src
Testing:
- =word-subword-subword=
- =word_subword_subword=
This clever hack from [[https://manueluberti.eu//emacs/2022/10/16/back-last-edit/][Manuel Uberti]] got me finding these useful bindings:
- ~g ;~ :: [[help:goto-last-change][goto-last-change]]
- ~g ,~ :: [[help:goto-last-change-reverse][goto-last-change-reverse]]
Keybindings I would like to use more:
- ~*~ :: jumps to the next instance of the word under point
- ~#~ :: jumps to the previous instance of the word under point
While Im pretty good with the VIM keybindings, I would like to play around with the [[https://evil.readthedocs.io/en/latest/extension.html#text-objects][text objects]] and how it compares to others (including the surround).
- ~diw~ :: deletes a word, but can be anywhere in it, while ~de~ deletes to the end of the word.
- ~daw~ :: deletes a word, plus the surrounding space, but not punctuation.
- ~xis~ :: changes a /sentence,/ and if ~i~ is ~a~, it gets rid of the surrounding whitespace as well. For instance, I mainly use ~das~ and ~cis~.
- ~xip~ :: changes a /paragraph/.
- ~xio~ :: changes a /symbol/, which can change for each mode, but works with =snake_case= and other larger-than-word variables.
- Surrounding punctuation, like quotes, parenthesis, brackets, etc. also work, so ~ci)~ changes all the parameters to a function call, for instance
- ~xa”~ :: a double quoted string
- ~xi”~ :: inner double quoted string
- ~xa'~ :: a single quoted string
- ~xi'~ :: inner single quoted string
- ~xa`~ :: a back quoted string
- ~xi`~ :: inner back quoted string
*Note:* The ~x~ in the above examples are /operations/, e.g. ~d~ for /delete,/ ~v~ for /select,/ ~y~ for /copy/ and ~c~ for /change/.
What text objects are known?
- ~w~ :: word
- ~s~ :: sentence
- ~p~ :: paragraph
- ~l~ :: lines, with the [[Evil Text Object Line][Text Object Line]] package, configured below.
- ~o~ :: symbol, like a variable, but also words, so ~vio~ is an easy sequence for selecting a word.
- ~~ :: a string, surround by quotes, also ~`~ for backticks
- ~)~ :: parenthesis, also ~}~ and ~]~, see ~x~
- ~x~ :: within a brace, paren, etc., with the [[Better Parenthesis with Text Object][my extensions below]], see ~b~ and ~f~ offer similar functionality.
- ~d~ / ~f~ :: a /defun/, or code block, see Tree-Sitter approach [[file:ha-programming.org::*Evil Text Object from Tree Sitter][defined here]], or the old Emacs approach defined below.
- ~i~ :: indention area, for YAML and Python, with the [[Text Objects based on Indentation][evil-indent-plus]] package, configured below.
- ~t~ :: an HTML tag
- ~c~ :: for comments
- ~u~ :: for URLs, really? Useful much?
- ~a~ :: function arguments (probably a lot like symbol, ~o~), but the ~a~ can include commas. This comes from [[https://github.com/wcsmith/evil-args][evil-args]] extension (see below).
** Evil Text Object Line
Delete a line, ~d d~ is in basic VI. Since some commands use text objects, and the basic text object doesnt include lines, the [[https://github.com/emacsorphanage/evil-textobj-line][evil-textobj-line]] project adds that:
#+begin_src emacs-lisp
(use-package evil-textobj-line)
#+end_src
Now ~v i l~ and ~v a l~ works as youd expect, but does this improve on ~S-v~?
** Text Objects based on Indentation
The [[https://github.com/TheBB/evil-indent-plus][evil-indent-plus]] project creates text objects based on the indentation level, similar to how the ~b~ works with “blocks” of code.
#+begin_src emacs-lisp
(use-package evil-indent-plus)
#+end_src
This can be handy for Python, YAML, and lists in org files. Note that ~i~ works for the current indent, but ~k~ includes one line above and ~j~ includes one line above and below.
** Arguments as Text Objects
The [[https://github.com/wcsmith/evil-args][evil-args]] projects creates text objects for symbols, but with trailing ~,~ or other syntax.
#+begin_src emacs-lisp
(use-package evil-args
:config
;; bind evil-args text objects
(define-key evil-inner-text-objects-map "a" 'evil-inner-arg)
(define-key evil-outer-text-objects-map "a" 'evil-outer-arg)
;; bind evil-forward/backward-args
(define-key evil-normal-state-map "L" 'evil-forward-arg)
(define-key evil-normal-state-map "H" 'evil-backward-arg)
(define-key evil-motion-state-map "L" 'evil-forward-arg)
(define-key evil-motion-state-map "H" 'evil-backward-arg)
;; bind evil-jump-out-args
(define-key evil-normal-state-map "K" 'evil-jump-out-args))
#+end_src
For a function, like this Python example, with the cursor on =b=:
#+begin_src python :tangle no
def foobar(a, b, c):
return a + b + c
#+end_src
Typing ~d a a~ will delete the argument leaving:
#+begin_src python :tangle no
def foobar(a, c):
return a + b + c
#+end_src
** Better Parenthesis with Text Object
I took the following clever idea and code from [[http://blog.binchen.org/posts/code-faster-by-extending-emacs-evil-text-object/][this essay]] from Chen Bin for creating a ~xix~ to grab code within any grouping characters, like parens, braces and brackets. For instance, ~dix~ cuts the content inside brackets, etc. First, we need a function to do the work (I changed the original from =my-= to =ha-= so that it is easier for me to distinguish functions from my configuration):
#+begin_src emacs-lisp
(defun ha-evil-paren-range (count beg end type inclusive)
"Get minimum range of paren text object.
COUNT, BEG, END, TYPE follow Evil interface, passed to
the `evil-select-paren' function.
If INCLUSIVE is t, the text object is inclusive."
(let* ((open-rx (rx (any "(" "[" "{" "<")))
(close-rx (rx (any ")" "]" "}" ">")))
(range (condition-case nil
(evil-select-paren
open-rx close-rx
beg end type count inclusive)
(error nil)))
found-range)
(when range
(cond
(found-range
(when (< (- (nth 1 range) (nth 0 range))
(- (nth 1 found-range) (nth 0 found-range)))
(setf (nth 0 found-range) (nth 0 range))
(setf (nth 1 found-range) (nth 1 range))))
(t
(setq found-range range))))
found-range))
#+end_src
Extend the text object to call this function for both /inner/ and /outer/:
#+begin_src emacs-lisp
(evil-define-text-object ha-evil-a-paren (count &optional beg end type)
"Select a paren."
:extend-selection t
(ha-evil-paren-range count beg end type t))
(evil-define-text-object ha-evil-inner-paren (count &optional beg end type)
"Select 'inner' paren."
:extend-selection nil
(ha-evil-paren-range count beg end type nil))
#+end_src
And the keybindings:
#+begin_src emacs-lisp
(define-key evil-inner-text-objects-map "x" #'ha-evil-inner-paren)
(define-key evil-outer-text-objects-map "x" #'ha-evil-a-paren)
#+end_src
** Text Object for Functions
While Emacs has the ability to recognize functions, the Evil text object does not. But text objects have both an /inner/ and /outer/ form, and what does that mean for a function? The /inner/ will be the /function itself/ and the /outer/ (like words) would be the surrounding /non-function/ stuff … in other words, the distance between the next functions.
#+begin_src emacs-lisp
(defun ha-evil-defun-range (count beg end type inclusive)
"Get minimum range of `defun` as a text object.
COUNT, is the number of _following_ defuns to count. BEG, END,
TYPE are not used. If INCLUSIVE is t, the text object is
inclusive acquiring the areas between the surrounding defuns."
(let ((start (save-excursion
(beginning-of-defun)
(when inclusive
(beginning-of-defun)
(end-of-defun))
(point)))
(end (save-excursion
(end-of-defun count)
(when inclusive
(end-of-defun)
(beginning-of-defun))
(point))))
(list start end)))
#+end_src
Extend the text object to call this function for both /inner/ and /outer/:
#+begin_src emacs-lisp
(evil-define-text-object ha-evil-a-defun (count &optional beg end type)
"Select a defun and surrounding non-defun content."
:extend-selection t
(ha-evil-defun-range count beg end type t))
(evil-define-text-object ha-evil-inner-defun (count &optional beg end type)
"Select 'inner' (actual) defun."
:extend-selection nil
(ha-evil-defun-range count beg end type nil))
#+end_src
And the keybindings:
#+begin_src emacs-lisp
(define-key evil-inner-text-objects-map "d" #'ha-evil-inner-defun)
(define-key evil-outer-text-objects-map "d" #'ha-evil-a-defun)
#+end_src
Why not use ~f~? Im reserving the ~f~ for a tree-sitter version that is not always available for all modes… yet.
** Key Chord
Using the key-chord project allows me to make Escape be on two key combo presses on both sides of my keyboard:
#+begin_src emacs-lisp
(use-package key-chord
:config
(key-chord-mode t)
(key-chord-define-global "fd" 'evil-normal-state)
(key-chord-define-global "jk" 'evil-normal-state)
(key-chord-define-global "JK" 'evil-normal-state))
#+end_src
This has been a frustrating feature that doesnt always work, and usually just when I get really used to it.
* General Leader Key Sequences
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 ","
:global-prefix "<f17>"
:non-normal-prefix "S-SPC")
(general-nmap "SPC m" (general-simulate-key "," :which-key "major mode")))
#+end_src
** Relabel the G Keys
Cant remember all the shortcuts on the ~g~ key, and =which-key= displays the entire function, so lets /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, lets 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 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)))
(message "Copied path to clipboard: %s"
(kill-new
(f-relative filename (or root (projectile-project-root filename)))))
(error "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))
(consult-projectile-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" . consult-projectile-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 D" '("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" . dirvish)
"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
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 B" '("switch" . persp-switch-to-buffer)
"b o" '("switch" . switch-to-buffer-other-window)
"b O" '("other" . projectile-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 doesnt honor either /projects/ or /perspectives/, but I can make a =projectile=-specific filter and use that to jump to only bookmarks in the current project. Likewise, if I want to jump to /any/ bookmark, I can switch to that buffers perspective.
#+begin_src emacs-lisp
(defun projectile-bookmark-jump (bmark)
"Jump to the bookmark, BMARK, showing a filtered list based on current project."
(interactive (list (completing-read "Jump to Bookmark: " (projectile-bookmarks))))
(bookmark-jump bmark))
(defun projectile-bookmarks ()
"Return a list of bookmarks associated with the current projectile project."
(let ((bmarks (bookmark-all-names)))
(cl-remove-if-not #'projectile-bookmark-p bmarks)))
(defun projectile-bookmark-p (bmark)
"Use as a filter to compare bookmark, BMARK with current project."
(let ((bmark-path (expand-file-name (bookmark-location bmark))))
(string-prefix-p (projectile-project-root) bmark-path)))
(defun persp-bookmark-jump (bmark)
"Jump to bookmkar, BMARK, but switch to its perspective first."
(interactive (list (completing-read "Jump to Bookmark:" (bookmark-all-names))))
(bookmark-jump bmark 'persp-switch-to-buffer))
(ha-leader
"b m" '("set bookmark" . bookmark-set)
"b g" '("goto proj bookmark" . projectile-bookmark-jump)
"b G" '("goto any bookmark" . persp-bookmark-jump)
"b M" '("delete mark" . bookmark-delete))
#+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 t" '("truncate" . toggle-truncate-lines)
"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=.
The ~0~ key/window should be always associated with a project-specific tree window of =dired= (or [[Dirvish][Dirvish]]):
#+begin_src emacs-lisp
(use-package winum
:config
(winum-mode +1)
(add-to-list 'winum-assign-functions
(lambda () (when (eq major-mode 'dired-mode) 10))))
#+end_src
Id like to have dirvish show in Window 0:
#+begin_src emacs-lisp
(defun dirvish-show-or-switch ()
"As it says on the tin. Show or start Dirvish.
If `divish' is showing, that is, is window 0 is showing,
switch to it, otherwise, start 'er up."
(interactive)
(if (seq-contains (winum--available-numbers) 0)
(winum-select-window-0-or-10)
(dirvish-side (projectile-project-root))))
#+end_src
And lets bind Command-0 to select the window that shows dirvish, or open drvish:
#+begin_src emacs-lisp
(use-package winum
:bind ("s-0" . dirvish-show-or-switch))
#+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)
("0" dirvish-dwim)
;; 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 'consult-projectile-find-file))
(:buffer (call-interactively 'consult-projectile-switch-to-buffer))
(:term (ha-shell (projectile-project-root)))))
#+end_src
Shame that hydra doesnt 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"))
;; Old habits die hard ...
(define-key global-map [remap xref-find-references] 'rg-dwim)
(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) ; or projectile-ripgrep
"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 dont 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 I" '("info manual" . info-display-manual)
"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)
:init
;; 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)
"o i" '("imenu" . consult-imenu)
"x y" '("preview yank" . consult-yank-pop))
:bind ("s-v" . consult-yank-pop)
:general
(:states 'normal
"gp" '("preview paste" . 'consult-yank-pop)
"gs" '("go to line" . 'consult-line)))
#+end_src
** Consult for Projects
One of the reasons that Consult hasnt been too important to me, is that I often narrow my searching based on projectile. The [[https://gitlab.com/OlMon/consult-projectile][consult-projectile]] can help with this.
#+begin_src emacs-lisp
(use-package consult-projectile
:after (consult general projectile)
:straight (:host gitlab :repo "OlMon/consult-projectile" :branch "master")
:config
(ha-leader
"p ." '("switch to..." . consult-projectile)
"b b" '("switch buffer" . consult-projectile-switch-to-buffer)
"p p" '("switch project" . consult-projectile-switch-project)
"p f" '("find file" . consult-projectile-find-file)
"p r" '("find recent file" . consult-projectile-recentf)))
#+end_src
The advantage of [[help:persp-switch-to-buffer][persp-switch-to-buffer]] over =consult-projectile-switch-to-buffer= is that is shows non-file buffers.
** 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
* Evil Extensions
** Evil Exchange
I often use the Emacs commands, ~M-t~ and whatnot to exchange words and whatnot, but this requires a drop out of normal state mode. The [[https://github.com/Dewdrops/evil-exchange][evil-exchange]] project attempts to do something similar, but in a VI-way, and the /objects/ do not need to be adjacent.
#+begin_src emacs-lisp
(use-package evil-exchange
:init
(setq evil-exchange-key (kbd "gx")
evil-exchange-cancel-key (kbd "gX"))
:general (:states 'normal
"g x" '("exchange" . 'evil-exchange)
"g X" '("cancel exchange" . 'evil-exchange-cancel)
;; What about a "normal mode" binding to regular emacs transpose?
"z w" '("transpose words" . transpose-words)
"z x" '("transpose sexps" . transpose-sexps)
"z k" '("transpose lines" . transpose-lines))
:config (evil-exchange-install))
#+end_src
Lets explain how this works as the documentation assumes some previous knowledge. If you had a sentence:
The ball was blue and the boy was red.
Move the point to the word, /red/, and type ~g x i w~ (anywhere since we are using the inner text object). Next, jump to the word /blue/, and type the sequence, ~g x i w~ again, and you have:
The ball was blue and the boy was red.
The idea is that you can exchange anything. The ~g x~ marks something (like what we would normally do in /visual mode/), and then by marking something else with a ~g x~ sequence, it swaps them.
Notice that you can swap:
- ~gx i w~ :: words, ~W~ words with dashes, or ~o~ for programming symbols (like variables)
- ~gx i s~ :: sentences
- ~gx i p~ :: paragraphs
- ~gx i x~ :: programming s-expressions between parens, braces, etc.
- ~gx i l~ :: lines, with the [[Evil Text Object Line][line-based text object]] project installed
** Evil Lion
The [[https://github.com/edkolev/evil-lion][evil-lion]] package is a wrapper around Emacs [[help:align][align]] function. Just a little easier to use. Primary sequence is ~g a i p =~ to align along all the equal characters in the paragraph (block), or ~g a i b RET~ to use a built in rule to align (see below), or ~g a i b /~ to specify a regular expression, similar to [[help:align-regexp][align-regexp]].
#+begin_src emacs-lisp
(use-package evil-lion
:after evil
:general
(:states '(normal visual)
"g a" '("lion ←" . evil-lion-left)
"g A" '("lion →" . evil-lion-right)))
#+end_src
Lion sounds like /align/ … get it?
Where I like to align, is on variable assignments, e.g.
#+begin_src emacs-lisp :tangle no
(let ((foobar "Something something")
(a 42)
(very-long-var "odd string"))
;;
)
#+end_src
If you press ~RETURN~ for the /character/ to align, =evil-lion= package simply calls the built-in [[help:align][align]] function. This function chooses a regular expression based on a list of /rules/, and aligning Lisp variables requires a complicated regular expression. Extend [[elisp:(describe-variable 'align-rules-list)][align-rules-list]]:
#+begin_src emacs-lisp
(use-package align
:straight (:type built-in)
:config
(add-to-list 'align-rules-list
`("lisp-assignments"
(regexp . ,(rx (group (one-or-more space))
(or
(seq "\"" (zero-or-more any) "\"")
(one-or-more (not space)))
(one-or-more ")") (zero-or-more space) eol))
(group . 1)
(modes . align-lisp-modes))))
#+end_src
** Evil Commentary
The [[https://github.com/linktohack/evil-commentary][evil-commentary]] is a VI-like way of commenting text. Yeah, I typically type ~M-;~ to call Emacs originally functionality, but in this case, ~g c c~ comments out a line(s), and ~g c~ comments text objects and whatnot. For instance, ~g c $~ comments to the end of the line.
#+begin_src emacs-lisp
(use-package evil-commentary
:config (evil-commentary-mode)
:general
(:states '(normal visual motion operator)
"g c" '("comments" . evil-commentary)
"g y" '("yank comment" . evil-commentary-yank)))
#+end_src
** Evil Collection
Dropping into Emacs state is better than pure Evil state for applications, however, [[https://github.com/emacs-evil/evil-collection][the evil-collection package]] creates a hybrid between the two, that I like.
#+begin_src emacs-lisp
(use-package evil-collection
:after evil
:config
(evil-collection-init))
#+end_src
Do I want to specify the list of modes to change for =evil-collection-init=, e.g.
#+begin_src emacs-lisp :tangle no :eval no
'(eww magit dired notmuch term wdired)
#+end_src
** Evil Owl
Not sure what is in a register? Have it show you when you hit ~”~ or ~@~ with [[https://github.com/mamapanda/evil-owl][evil-owl]]:
#+begin_src emacs-lisp
(use-package posframe)
(use-package evil-owl
:after posframe
:config
(setq evil-owl-display-method 'posframe
evil-owl-extra-posframe-args '(:width 50 :height 20 :background-color "#444")
evil-owl-max-string-length 50)
(evil-owl-mode))
#+end_src
** Evil Surround
I like both [[https://github.com/emacs-evil/evil-surround][evil-surround]] and Henrik's [[https://github.com/hlissner/evil-snipe][evil-snipe]], but they both start with ~s~, and conflict, and getting them to work together means I have to remember when does ~s~ call sniper and when it calls surround. As an original Emacs person, I am not bound by that key history, but I do need them consistent, so Im choosing the ~s~ to be /surround/.
#+begin_src emacs-lisp
(use-package evil-surround
:config
(defun evil-surround-elisp ()
(push '(?\` . ("`" . "'")) evil-surround-pairs-alist))
(defun evil-surround-org ()
(push '(?\" . ("" . "")) evil-surround-pairs-alist)
(push '(?\' . ("" . "")) evil-surround-pairs-alist)
(push '(?b . ("*" . "*")) evil-surround-pairs-alist)
(push '(?* . ("*" . "*")) evil-surround-pairs-alist)
(push '(?i . ("/" . "/")) evil-surround-pairs-alist)
(push '(?/ . ("/" . "/")) evil-surround-pairs-alist)
(push '(?= . ("=" . "=")) evil-surround-pairs-alist)
(push '(?~ . ("~" . "~")) evil-surround-pairs-alist))
(global-evil-surround-mode 1)
:hook
(org-mode . evil-surround-org)
(emacs-lisp-mode . evil-surround-elisp))
#+end_src
Notes:
- ~cs'"~ :: to convert surrounding single quote string to double quotes.
- ~ds"~ :: to delete the surrounding double quotes.
- ~yse"~ :: puts single quotes around the next word.
- ~ysiw'~ :: puts single quotes around the word, no matter the points position.
- ~yS$<p>~ :: surrouds the line with HTML =<p>= tag (with extra carriage returns).
- ~ysiw'~ :: puts single quotes around the word, no matter the points position.
- ~(~ :: puts spaces /inside/ the surrounding parens, but ~)~ doesn't. Same with ~[~ and ~]~.
** Evil Jump, er Better Jump
The [[https//github.com/gilbertw1/better-jumper][better-jumper project]] replaces the [[https://github.com/bling/evil-jumper][evil-jumper project]], essentially allowing you jump back to various movements. While I already use ~g ;~ to jump to the last change, this jumps /to the jumps/ … kinda. Im having a difficult time determining /what jumps/ are remembered.
#+begin_src emacs-lisp
(use-package better-jumper
:config
(better-jumper-mode +1)
(with-eval-after-load 'evil-maps
(define-key evil-motion-state-map (kbd "C-o") 'better-jumper-jump-backward)
(define-key evil-motion-state-map (kbd "C-i") 'better-jumper-jump-forward)))
#+end_src
* Technical Artifacts :noexport:
Let's =provide= a name so we can =require= this file:
#+begin_src emacs-lisp :exports none
(provide 'ha-evil)
;;; ha-evil.el ends here
#+end_src
#+DESCRIPTION: configuring Evil mode in Emacs.
#+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: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:nil ltoc:t mouse:underline buttons:0 path:http://orgmode.org/org-info.js