From 164664f8fe04521be77449551403470695df302c Mon Sep 17 00:00:00 2001 From: Howard Abrams Date: Thu, 21 Dec 2023 11:51:21 -0800 Subject: [PATCH] Extract the Evil configuration into its own file --- README.org | 11 +- ha-config.org | 1357 +---------------------------------- ha-evil.org | 1410 +++++++++++++++++++++++++++++++++++++ screenshots/ha-leader.png | Bin 0 -> 63334 bytes 4 files changed, 1422 insertions(+), 1356 deletions(-) create mode 100644 ha-evil.org create mode 100644 screenshots/ha-leader.png diff --git a/README.org b/README.org index 5dd3aed..d12f70b 100644 --- a/README.org +++ b/README.org @@ -4,11 +4,13 @@ #+tags: emacs readme ** Introduction -My Emacs configuration, that I'm cheekily calling /hamacs/ is a literate programming model heavily inspired by my recent journey into [[https://www.youtube.com/watch?v=LKegZI9vWUU][Henrik Lissner's]] [[https://github.com/hlissner/doom-emacs][Doom Emacs]] and [[https://www.spacemacs.org/][Spacemacs]]. I used both extensively, but decided that I would /roll my own/ as Emacs people tend to be /control freaks/ (at least a little bit). +I’ve crafted my Emacs configuration, I cheekily call /hamacs/, in a literate programming model, heavily inspired by my recent journey into [[https://www.youtube.com/watch?v=LKegZI9vWUU][Henrik Lissner's]] [[https://github.com/hlissner/doom-emacs][Doom Emacs]] and [[https://www.spacemacs.org/][Spacemacs]]. While I used both extensively, I decided I would /roll my own/ as Emacs people like myself, tend to be /control freaks/ (at least a little bit). -The other advantage to rolling yer own is that you may /use/ what you add, leading to less bloat, and a more fun experience. +The advantage to rolling yer own is I tend to /use/ what I add, leading to less bloat. In reality, this is more fun. -One advantage of using [[https://howardism.org/Technical/Emacs/literate-devops.html][literate programming]] for my Emacs configuration is an easy way to /share/ my code. So yes, feel free to steal whatever you find interesting, as sharing is what makes our community great. Notice that functions and features that I have written begin with ~ha-~, but everything else is either /stock Emacs/ or a /package/ that I download using [[https://github.com/raxod502/straight.el][straight]] (see [[file:bootstrap.org][bootstrap]] for how) and configured with [[https://github.com/jwiegley/use-package][use-package]] (see either [[https://ianyepan.github.io/posts/setting-up-use-package/][this introduction]] or [[https://www.emacswiki.org/emacs/UsePackage][this wiki page]] for details)… meaning that most blocks of code should work on its own. +Using [[https://howardism.org/Technical/Emacs/literate-devops.html][literate programming]] for my Emacs configuration gives me an easy way to /share/ my code. So yes, feel free to steal whatever you find interesting, as sharing makes our community great. Notice that functions and features I have written begin with =ha-=, but everything else is either /stock Emacs/ or a /package/ I download using [[https://github.com/raxod502/straight.el][straight]] (see [[file:bootstrap.org][bootstrap]] for how for details) and configured with [[https://github.com/jwiegley/use-package][use-package]] (see either [[https://ianyepan.github.io/posts/setting-up-use-package/][this introduction]] or [[https://www.emacswiki.org/emacs/UsePackage][this wiki page]] for those details)… meaning most blocks of code should work on its own. + +My configuration is broken into /chapters/ around particular subjects, applications or programming languages. This allows me to /selectively load/ individual chapters. For instance, currently, I’m not doing much with Ruby, so I just remove it from the end of my [[file:bootstrap.org][bootstrap]]. I also going load my [[file:ha-display.org][UI configuration]] if I am using the Terminal (doesn’t happen much, actually). So jump to the chapters of interest. Hit me up with questions on Mastodon: [[https://emacs.ch/@howard][@howard@emacs.ch]]. @@ -20,10 +22,11 @@ And then, run: #+BEGIN_SRC sh ./initialize #+END_SRC -To create [[file:~/.emacs.d/init.el][~/.emacs.d/init.el]] that starts the process loading the files: +To create [[file:~/.emacs.d/init.el][~/.emacs.d/init.el]] which starts the process loading the files. ** Core Configuration - [[file:bootstrap.org][Bootstrap]] :: configures =straight= and loads basic libraries the rest of the code depends on. It then loads the following files in order. - [[file:ha-config.org][Configuration]] :: contains /most/ of my configuration, setting up my sequence key menus, evil, etc. + - [[file:ha-evil.org][Evilness]] :: configuration for using VI, er, ~vim~ keybindings in Emacs. - [[file:ha-display.org][GUI Display]] :: sets up the visual aspects of an Emacs GUI, including themes and fonts. - [[file:ha-dashboard.org][Dashboard]] :: sets up initial window layout of the =main= project with a dashboard. - [[file:ha-data.org][Data]] :: functions for dealing with a buffer-full of data. diff --git a/ha-config.org b/ha-config.org index 9818e75..99a0b9f 100644 --- a/ha-config.org +++ b/ha-config.org @@ -313,1358 +313,10 @@ Why use [[https://gitlab.com/ideasman42/emacs-undo-fu][undo-fu]] instead of the (global-set-key (kbd "s-z") 'undo-fu-only-undo) (global-set-key (kbd "s-S-z") 'undo-fu-only-redo)) #+end_src -** Evil-Specific Keybindings -Can we change Evil at this point? Some tips: - - [[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]] - +** On the Subject of Being Evil +I’m currently using the [[https://github.com/emacs-evil/evil][Extensible VI Layer]] for Emacs, aka /evil/. This configuration happens in [[file:ha-evil.org][ha-evil.org]], but because much of my configuration requires access to =ha-leader=, =general=, and other features, we need to include it here: #+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 - 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)) -#+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 "") '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 - -I’m 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 I’m 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), for instance: - - ~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. Probably ~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 - - ~a”~ :: a double quoted string - - ~i”~ :: inner double quoted string - - ~a'~ :: a single quoted string - - ~i'~ :: inner single quoted string - - ~a`~ :: a back quoted string - - ~i`~ :: inner back quoted string - -*Note:* The ~x~ in the above examples are ~d~ for delete, ~v~ for select, ~y~ for copying and ~c~ for changing. - -What text objects are known? - - ~w~ :: word - - ~s~ :: sentence - - ~p~ :: paragraph - - ~l~ :: lines, with the [[Evil Text Object Line][Text Object Line]] package - - ~o~ :: symbol, like a variable - - ~’~ :: a string, surround by quotes, also ~`~ for backticks - - ~)~ :: parenthesis, also ~}~ and ~]~, see ~g~ - - ~x~ :: within a brace, paren, etc., with the [[Better Parenthesis with Text Object][my extensions below]], see ~b~ and ~f~ for similar functionality. - - ~f~ :: a /defun/, or code block, see [[file:ha-programming.org::*Evil Text Object from Tree Sitter][definition here]]. - - ~i~ :: indention area, for YAML and Python, with the [[Text Objects based on Indentation][evil-indent-plus]] package - - ~t~ :: an HTML tag - - ~c~ :: for comments - - ~u~ :: for URLs - - ~a~ :: function arguments (probably a lot like symbol, ~o~) with the [[https://github.com/wcsmith/evil-args][evil-args]] extension (that I’m not bothering with) -*** Evil Text Object Line -Delete a line, ~d d~ is in basic VI. Since some commands use text objects, and the basic text object doesn’t 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 you’d 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~? I’m 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 -** 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 "") - - (general-create-definer ha-local-leader - :states '(normal visual motion) - :prefix "," - :global-prefix "" - :non-normal-prefix "S-SPC") - - (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 ") nil - (kbd "g ") nil - (kbd "g ") nil - (kbd "g ") nil - (kbd "g ") nil - (kbd "g ") 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 ") '("scroll left" . evil-scroll-column-left) - (kbd "z ") '("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, 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 ") 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) - "." '("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") - "o" '(:ignore t :which-key "org/open") - "o i" 'imenu - "m" '(:ignore t :which-key "mode") - "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 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 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 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 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 doesn’t 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 buffer’s 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 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 - -I’d 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 let’s 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 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")) - - ;; Old habits die hard ... - (define-key global-map [remap xref-find-references] 'rg-dwim) - - (ha-leader - "s" '(:ignore t :which-key "search") - "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 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 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 ." '("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 hasn’t 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 - -Let’s 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 I’m 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$

~ :: surrouds the line with HTML =

= 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. I’m 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))) + (ha-hamacs-load "ha-evil") #+end_src ** Additional Global Packages The following defines my use of the Emacs completion system. I’ve decided my /rules/ will be: @@ -1765,7 +417,8 @@ Since auto insertion requires entering data for particular fields, and for that (auto-insert-query nil) (yas-indent-line nil)) (yas/minor-mode 1) - (evil-insert-state) + (when (fboundp evil-insert-state) + (evil-insert-state)) (yas-expand-snippet (buffer-string) (point-min) (point-max)))) #+end_src diff --git a/ha-evil.org b/ha-evil.org new file mode 100644 index 0000000..e508e40 --- /dev/null +++ b/ha-evil.org @@ -0,0 +1,1410 @@ +#+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 + ;; 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, I’ve 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 Evil’s /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 doesn’t auto-complete. + - I don’t use ~:~ and instead use ~M-x~ or better yet, ~SPC SPC~ (typing the space key twice). + - The ~Space~ doesn’t 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 + 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)) +#+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 "") '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 + +I’m 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 I’m 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 doesn’t 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 you’d 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~? I’m 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 doesn’t 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 "") + + (general-create-definer ha-local-leader + :states '(normal visual motion) + :prefix "," + :global-prefix "" + :non-normal-prefix "S-SPC") + + (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 ") nil + (kbd "g ") nil + (kbd "g ") nil + (kbd "g ") nil + (kbd "g ") nil + (kbd "g ") 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 ") '("scroll left" . evil-scroll-column-left) + (kbd "z ") '("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 ") 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) + "." '("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") + "o" '(:ignore t :which-key "org/open") + "o i" 'imenu + "m" '(:ignore t :which-key "mode") + "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 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 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 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 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 doesn’t 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 buffer’s 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 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 + +I’d 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 let’s 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 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")) + + ;; Old habits die hard ... + (define-key global-map [remap xref-find-references] 'rg-dwim) + + (ha-leader + "s" '(:ignore t :which-key "search") + "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 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 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 ." '("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 hasn’t 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 + +Let’s 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 I’m 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$

~ :: surrouds the line with HTML =

= 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. I’m 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: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 diff --git a/screenshots/ha-leader.png b/screenshots/ha-leader.png new file mode 100644 index 0000000000000000000000000000000000000000..04f2de244d0054ebce8692a418a064c1dee58473 GIT binary patch literal 63334 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 literal 0 HcmV?d00001