2-char searching ala vim-sneak & vim-seek, for evil-mode
Evil-snipe emulates vim-seek and/or vim-sneak in evil-mode.
It provides 2-character motions for quickly (and more accurately) jumping around text, compared to evil's built-in f/F/t/T motions, incrementally highlighting candidate targets as you type.
Evil-snipe is available on MELPA.
M-x package-install evil-snipe
(require 'evil-snipe)
evil-snipe
comes with two global modes: evil-snipe-mode
and
evil-snipe-override-mode
, and two local modes: evil-snipe-local-mode
and
evil-snipe-override-local-mode
.
You can either a) enable one or both globally:
(evil-snipe-mode +1)
(evil-snipe-override-mode +1)
;; and disable in specific modes
(push 'python-mode evil-snipe-disabled-modes)
;; or disable it manually
(add-hook 'python-mode-hook #'turn-off-evil-snipe-mode)
(add-hook 'python-mode-hook #'turn-off-evil-snipe-override-mode)
Or b) enable one or both locally, where you need it:
(add-hook 'python-mode-hook 'turn-on-evil-snipe-mode)
(add-hook 'python-mode-hook 'turn-on-evil-snipe-override-local-mode)
By default, snipe only binds s (forward)/S (backward) to
evil-snipe-s
and evil-snipe-S
, respectively. In operator mode, snipe is
bound to z/Z and x/X (exclusive).
The last snipe can be repeated with s/S after a successful snipe (or with s+RET).
Evil-snipe can override evil-mode's native motions with 1-char sniping:
;; Globally
(evil-snipe-override-mode 1)
;; Or locally
(add-hook 'ruby-mode-hook 'evil-snipe-override-local-mode)
The benefit of this is:
These three variables determine the scope of snipes (and the incremental highlighter):
evil-snipe-scope
(default: line
)evil-snipe-repeat-scope
(default: whole-line
) Scope while repeating
searches with evil-snipe-repeat
or evil-snipe-repeat-reverse
.evil-snipe-spillover-scope
(default: nil
) Scope to expand to when a snipe
fails. Only useful if set to a broader scope than evil-snipe-scope
.These are the possible settings:
Value | Description |
---|---|
'line | rest of the current line after cursor (vim-seek behavior) |
'buffer | rest of the buffer after cursor (vim-sneak behavior) |
'visible | the rest of the visible buffer after cursor |
'whole-line | same as 'line , but highlights on either side of cursor |
'whole-buffer | same as 'buffer , but highlights all matches in buffer |
'whole-visible | same as 'visible , but highlights all visible matches in buffer |
Specific characters can be aliased to regex patterns by modifying evil-snipe-aliases
.
To map [ to any opening parentheses or bracket in all modes:
(push '(?\[ "[[{(]") evil-snipe-aliases)
Therefore, sa[ will match a[
, a{
or a(
To map : to a python function (but only in python-mode
):
(add-hook 'python-mode-hook
(lambda ()
(make-variable-buffer-local 'evil-snipe-aliases)
(push '(?: "def .+:") evil-snipe-aliases)))
evil-snipe-first-match-face
: The first highlighted match.evil-snipe-matches-face
: The rest of the highlighted matches.To avoid binding conflicts, evil-snipe has no visual mode bindings. You can add them with:
(evil-define-key 'visual evil-snipe-local-mode-map "z" 'evil-snipe-s)
(evil-define-key 'visual evil-snipe-local-mode-map "Z" 'evil-snipe-S)
This will allow you to quickly hop into avy/evil-easymotion right after a snipe.
(define-key evil-snipe-parent-transient-map (kbd "C-;")
(evilem-create 'evil-snipe-repeat
:bind ((evil-snipe-scope 'buffer)
(evil-snipe-enable-highlight)
(evil-snipe-enable-incremental-highlight))))
(Thanks to PythonNut for this. More info here)
It seems evil-snipe-override-mode
causes problems in Magit buffers, to fix this:
(add-hook 'magit-mode-hook 'turn-off-evil-snipe-override-mode)
evil-snipe-enable-highlight
(default: t
) Highlight first match.evil-snipe-enable-incremental-highlight
(default: t
) Incrementally highlight all
matches in scope.evil-snipe-override-evil-repeat-keys
(default: t
) Whether or not evil-snipe will
override evil's default ; and , mappings with snipe's (when
evil-snipe-override-mode
is on).evil-snipe-repeat-keys
(default t
) If non-nil, pressing s/S
after a search will repeat it. If evil-snipe-override-evil
is non-nil, this applies
to f/F/t/T as well.evil-snipe-show-prompt
(default t
) Whether or not to show the "N>" prompt.evil-snipe-smart-case
(default nil
) If non-nil, searches will be case-insenstive
unless your search contains a capital letter.evil-snipe-auto-scroll
(default nil
) If non-nil, the window will scroll to follow
the cursor.evil-snipe-auto-disable-substitute
(default: t
) Whether or not evil's
default substitute mappings (s/S) are unset. They can sometimes interfere with
snipe. Must be set before evil-snipe is loaded.evil-snipe-skip-leading-whitespace
(default t
) If non-nil, sniping will skip over
leading whitespace when you search for whitespace.evil-snipe-tab-increment
(default nil
) If non-nil, pressing TAB in the snipe
prompt will increase the size of the snipe buffer.evil-snipe-use-vim-sneak-bindings
(default nil
) If non-nil, evil-snipe
binds z/Z to exclusive sniping in operator state, but leaves the x/X bindings
free. This mirrors the default bindings of vim-sneak, and frees up cx/cX to be
used by evil-exchange.evil-snipe-mode
/ evil-snipe-local-mode
evil-snipe-override-mode
/ evil-snipe-override-local-mode
evil-snipe-repeat
/ evil-snipe-repeat-reverse
evil-snipe-s
/ evil-snipe-S
: inclusive 2-char snipingevil-snipe-x
/ evil-snipe-X
: exclusive 2-char snipingevil-snipe-f
/ evil-snipe-F
: inclusive 1-char snipingevil-snipe-t
/ evil-snipe-T
: exclusive 1-char sniping(evil-define-key '(normal motion) evil-snipe-local-mode-map
"s" 'evil-snipe-s
"S" 'evil-snipe-S)
(evil-define-key 'operator evil-snipe-local-mode-map
"z" 'evil-snipe-s
"Z" 'evil-snipe-S
"x" 'evil-snipe-x
"X" 'evil-snipe-X)
(evil-define-key 'motion evil-snipe-override-local-mode-map
"f" 'evil-snipe-f
"F" 'evil-snipe-F
"t" 'evil-snipe-t
"T" 'evil-snipe-T)
(when evil-snipe-override-evil-repeat-keys
(evil-define-key 'motion map
";" 'evil-snipe-repeat
"," 'evil-snipe-repeat-reverse))