520 lines
24 KiB
Org Mode
520 lines
24 KiB
Org Mode
#+title: On the Subject of Being Evil
|
||
#+author: Howard X. Abrams
|
||
#+date: 2023-12-21
|
||
#+filetags: emacs hamacs
|
||
#+startup: inlineimages
|
||
|
||
A literate programming file for configuring Evil mode in Emacs.
|
||
#+begin_src emacs-lisp :exports none
|
||
;;; ha-evil --- configuring Evil mode in Emacs. -*- lexical-binding: t; -*-
|
||
;;
|
||
;; © 2023 Howard X. Abrams
|
||
;; This work is licensed under a Creative Commons Attribution 4.0 International License.
|
||
;; See http://creativecommons.org/licenses/by/4.0/
|
||
;;
|
||
;; Author: Howard X. Abrams <http://gitlab.com/howardabrams>
|
||
;; Maintainer: Howard X. Abrams
|
||
;; Created: December 21, 2023
|
||
;;
|
||
;; While obvious, GNU Emacs does not include this file or project.
|
||
;;
|
||
;; *NB:* Do not edit this file. Instead, edit the original literate file at:
|
||
;; ~/src/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 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) from [[file:ha-general.org][General project]].
|
||
- 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]]
|
||
|
||
TODO: Rebind the ~z~ keys
|
||
* Evil-Specific Keybindings
|
||
I split the configuration of Evil mode into sections. First, global settings:
|
||
#+begin_src emacs-lisp
|
||
(use-package evil
|
||
:init
|
||
(setq evil-undo-system 'undo-fu
|
||
evil-auto-indent t
|
||
evil-respect-visual-line-mode t
|
||
evil-want-fine-undo t ; Be more like Emacs
|
||
evil-disable-insert-state-bindings t
|
||
evil-want-keybinding nil ; work with evil-collection
|
||
evil-want-integration t
|
||
evil-want-C-u-scroll nil
|
||
evil-want-C-i-jump nil
|
||
evil-escape-key-sequence "jk"
|
||
evil-escape-unordered-key-sequence t))
|
||
#+end_src
|
||
|
||
The Escape key act like ~C-g~ and always go back to normal mode?
|
||
#+begin_src emacs-lisp
|
||
(use-package evil
|
||
:config
|
||
;; (global-set-key (kbd "<escape>") 'keyboard-escape-quit)
|
||
|
||
;; Let's connect my major-mode-hydra to a global keybinding:
|
||
(evil-define-key 'normal 'global "," 'major-mode-hydra)
|
||
|
||
(evil-mode))
|
||
#+end_src
|
||
|
||
Even with the [[Evil Collection]], some modes should start in the Emacs state:
|
||
#+begin_src emacs-lisp
|
||
(use-package evil
|
||
:config
|
||
(dolist (mode '(custom-mode
|
||
dired-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)
|
||
#+END_SRC
|
||
|
||
The ~b~ key bindings seems to need their own call, and I’m not sure why I can’t include it in the ~w~ and ~e~ bindings.
|
||
|
||
#+BEGIN_SRC emacs-lisp
|
||
(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)
|
||
#+end_src
|
||
|
||
In other words, with the above settings in place, ~w~ and ~e~ should jump from front to back of the entire line, but ~W~ and ~E~ should stop as /subword/:
|
||
- =word-subword-subword=
|
||
- =word_subword_subword=
|
||
|
||
Note I don’t bind =evil-backward-word-end= with a single key, but bind it to ~g e~ below.
|
||
|
||
While an absolute heresy to most VI users, I'm Evil and use ~M-x~ and ~SPC~ instead of ~:~ for running commands. I bind the ~:~ as a reverse of the ~;~ which continues the search from ~f~ and ~t~:
|
||
#+BEGIN_SRC emacs-lisp
|
||
(evil-define-key
|
||
'(normal visual motion operator) 'global
|
||
":" 'evil-repeat-find-char-reverse))
|
||
#+END_SRC
|
||
|
||
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).
|
||
|
||
TODO: Search for a plugin, like [[https://github.com/coderifous/textobj-word-column.vim][textobj-word-column]] for text objects based on “columns”.
|
||
|
||
I am not a long term VI user, and don’t have much need for any of its control sequences (well, not all), so I made the following more Emacsy. I’ll admit, I like ~C-v~ (and use that all the time), so I need to futz around with the scrolling:
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package evil
|
||
:config
|
||
(evil-define-key '(normal visual motion operator) 'global
|
||
(kbd "C-a") 'ha-beginning-of-line
|
||
(kbd "C-e") 'move-end-of-line
|
||
|
||
;; Since C-y scrolls the window down, Shifted Y goes up:
|
||
(kbd "C-y") 'evil-scroll-line-down
|
||
(kbd "C-b") 'evil-scroll-line-up
|
||
(kbd "C-S-y") 'evil-scroll-line-up
|
||
|
||
(kbd "C-d") 'scroll-down-command
|
||
(kbd "C-S-d") 'scroll-other-window-down
|
||
(kbd "C-f") 'scroll-up-command
|
||
(kbd "C-S-f") 'scroll-other-window
|
||
|
||
(kbd "C-o") 'open-line ; matches evil's o
|
||
(kbd "C-p") 'previous-line
|
||
(kbd "C-n") 'next-line
|
||
|
||
;; I have better window control:
|
||
(kbd "C-w") 'sp-kill-region))
|
||
#+end_src
|
||
** 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)
|
||
(point)))
|
||
(end (save-excursion
|
||
(end-of-defun count)
|
||
(point))))
|
||
(when inclusive
|
||
;; Let's see if we can grab more text ...
|
||
(save-excursion
|
||
;; Don't bother if we are at the start of buffer:
|
||
(when (> start (point-min))
|
||
(goto-char start)
|
||
;; go to the end of the previous function:
|
||
(beginning-of-defun)
|
||
(end-of-defun count)
|
||
;; if we found some more text to grab, reset start:
|
||
(if (< (point) start)
|
||
(setq start (point))))
|
||
;; Same approach with the end:
|
||
(when (< end (point-max))
|
||
(goto-char end)
|
||
(end-of-defun)
|
||
(beginning-of-defun)
|
||
(if (> (point) end)
|
||
(setq end (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.
|
||
* 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$<p>~ :: surrouds the line with HTML =<p>= tag (with extra carriage returns).
|
||
- ~ysiw'~ :: puts single quotes around the word, no matter the points position.
|
||
- ~(~ :: puts spaces /inside/ the surrounding parens, but ~)~ doesn't. Same with ~[~ and ~]~.
|
||
** Evil Jump, er Better Jump
|
||
The [[https//github.com/gilbertw1/better-jumper][better-jumper project]] replaces the [[https://github.com/bling/evil-jumper][evil-jumper project]], essentially allowing you jump back to various movements. While I already use ~g ;~ to jump to the last change, this jumps /to the jumps/ … kinda. 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 "M-[") 'better-jumper-jump-backward)
|
||
(define-key evil-motion-state-map (kbd "M-]") 'better-jumper-jump-forward)))
|
||
#+end_src
|
||
|
||
* Technical Artifacts :noexport:
|
||
|
||
Let's =provide= a name so we can =require= this file:
|
||
|
||
#+begin_src emacs-lisp :exports none
|
||
(provide 'ha-evil)
|
||
;;; ha-evil.el ends here
|
||
#+end_src
|
||
|
||
#+description: configuring Evil mode in Emacs.
|
||
|
||
#+property: header-args:sh :tangle no
|
||
#+property: header-args:emacs-lisp :tangle yes
|
||
#+property: header-args :results none :eval no-export :comments no mkdirp yes
|
||
|
||
#+options: num:nil toc:t todo:nil tasks:nil tags:nil date:nil
|
||
#+options: skip:nil author:nil email:nil creator:nil timestamp:nil
|
||
#+infojs_opt: view:nil toc:t ltoc:t mouse:underline buttons:0 path:http://orgmode.org/org-info.js
|