Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
test/.packages
*.elc
138 changes: 75 additions & 63 deletions evil-snipe.el
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
;;; evil-snipe.el --- emulate vim-sneak & vim-seek
;;; evil-snipe.el --- Emulate vim-sneak & vim-seek -*- lexical-binding: t; -*-
;;
;; Copyright (C) 2014-20 Henrik Lissner
;;
Expand All @@ -7,9 +7,9 @@
;; Created: December 5, 2014
;; Modified: August 21, 2023
;; Version: 2.1.3
;; Keywords: emulation, vim, evil, sneak, seek
;; Keywords: convenience, emulations, vim, evil, sneak, seek
;; Homepage: https://github.com/hlissner/evil-snipe
;; Package-Requires: ((emacs "24.4") (evil "1.2.12") (cl-lib "0.5"))
;; Package-Requires: ((emacs "25.1") (evil "1.2.12") (cl-lib "0.5"))
;;
;; This file is not part of GNU Emacs.

Expand Down Expand Up @@ -72,19 +72,19 @@ for repeating snipes."
(defcustom evil-snipe-scope 'line
"Dictates the scope of searches, which can be one of:

'line
\='line
Search line after the cursor (this is vim-seek behavior) (default)
'buffer
\='buffer
Search rest of the buffer after the cursor (vim-sneak behavior)
'visible
Search rest of visible buffer (Is more performant than 'buffer, but will not
\='visible
Search rest of visible buffer (Is more performant than \='buffer, but will not
highlight/jump past the visible buffer)
'whole-line
Same as 'line, but highlight matches on either side of cursor
'whole-buffer
Same as 'buffer, but highlight *all* matches in buffer
'whole-visible
Same as 'visible, but highlight *all* visible matches in buffer
\='whole-line
Same as \='line, but highlight matches on either side of cursor
\='whole-buffer
Same as \='buffer, but highlight *all* matches in buffer
\='whole-visible
Same as \='visible, but highlight *all* visible matches in buffer

See `evil-snipe-repeat-scope' to change scope only when repeating snipes.

Expand Down Expand Up @@ -121,7 +121,7 @@ If `evil-snipe-override-evil' is non-nil, this applies to f/F/t/T as well."
:type 'boolean)

(defcustom evil-snipe-show-prompt t
"If non-nil, show 'N>' prompt while sniping."
"If non-nil, show N> prompt while sniping."
:group 'evil-snipe
:type 'boolean)

Expand All @@ -137,19 +137,20 @@ search contains capital letters."
:group 'evil-snipe
:type 'boolean)

(define-obsolete-variable-alias 'evil-snipe-symbol-groups 'evil-snipe-aliases "v2.0.0")

(defcustom evil-snipe-aliases '()
"A list of characters mapped to regexps '(CHAR REGEX).
"A list of characters mapped to regexps \='(CHAR REGEX).
If CHAR is used in a snipe, it will be replaced with REGEX. These aliases apply
globally. To set an alias for a specific mode use:

(add-hook 'c++-mode-hook
(add-hook \='c++-mode-hook
(lambda ()
(make-local-variable 'evil-snipe-aliases)
(push '(?\[ \"[[{(]\") evil-snipe-aliases)))"
(make-local-variable \='evil-snipe-aliases)
(push \='(?\[ \"[[{(]\") evil-snipe-aliases)))"
:group 'evil-snipe
:type '(repeat (cons (character :tag "Key")
(regexp :tag "Pattern"))))
(define-obsolete-variable-alias 'evil-snipe-symbol-groups 'evil-snipe-aliases "v2.0.0")

(defcustom evil-snipe-disabled-modes
'(org-agenda-mode magit-mode git-rebase-mode elfeed-show-mode
Expand All @@ -173,27 +174,28 @@ evil-exchange or anything else.
MUST BE SET BEFORE EVIL-SNIPE IS LOADED.")

(defcustom evil-snipe-skip-leading-whitespace t
"If non-nil, single char sniping (f/F/t/T) will skip over leading whitespaces
in a line (when you snipe for whitespace, e.g. f<space> or f<tab>)."
"If non-nil, single char sniping (f/F/t/T) will skip over leading whitespaces.
It skips leading whitespaces when you snipe for whitespace,
e.g. f<space> or f<tab>."
:group 'evil-snipe
:type 'boolean)

(defcustom evil-snipe-tab-increment nil
"If non-nil, pressing TAB while sniping will add another character to your
current search. For example, typing sab will search for 'ab'. In order to search
for 'abcd', you do sa<tab><tab>bcd.
"If non-nil, pressing TAB while sniping adds another character to the search.
For example, typing sab will search for \='ab\='. In order to search
for \='abcd\=', you do sa<tab><tab>bcd.

If nil, TAB will search for literal tab characters."
:group 'evil-snipe
:type 'boolean)

(defcustom evil-snipe-char-fold nil
"If non-nil, uses `char-fold-to-regexp' to include other ascii variants of a
search string. CURRENTLY EXPERIMENTAL.
"Non-nil to use `char-fold-to-regexp' to include ASCII variants search string.
CURRENTLY EXPERIMENTAL.

e.g. The letter 'a' will match all of its accented cousins, even those composed
of multiple characters, as well as many other symbols like U+249C (PARENTHESIZED
LATIN SMALL LETTER A).
e.g. The letter \='a\=' will match all of its accented cousins, even those
composed of multiple characters, as well as many other symbols like
U+249C (PARENTHESIZED LATIN SMALL LETTER A).

Only works in Emacs 25.1+."
:group 'evil-snipe
Expand All @@ -210,7 +212,14 @@ Can be let-bound to create motions that search for N characters. Do not set
directly, unless you want to change the default number of characters to
search.")
(defvar evil-snipe--transient-map-func nil)
(defvar evil-snipe-keymap nil
"Keymap for transient state.")

(defvar evil-snipe-parent-transient-map
(let ((map (make-sparse-keymap)))
(define-key map ";" #'evil-snipe-repeat)
(define-key map "," #'evil-snipe-repeat-reverse)
map))

;;
;;; Faces
Expand All @@ -234,6 +243,7 @@ search.")
(not (string-match-p "[A-Z]" (mapconcat #'cdr data ""))))))

(defun evil-snipe--process-key (key)
"Process KEY to return a cons cell of keystr and its regexp."
(let ((keystr (char-to-string key)))
(cons keystr (cond ((car (cdr (assoc key evil-snipe-aliases))))
(evil-snipe-char-fold (char-fold-to-regexp keystr))
Expand Down Expand Up @@ -333,8 +343,8 @@ If FIRST-P is t, then use `evil-snipe-first-p-match-face'"
'evil-snipe-matches-face))
overlay))

(defun evil-snipe--highlight-all (count forward-p data)
"Highlight instances of keys in DATA at COUNT intervals.
(defun evil-snipe--highlight-all (_count forward-p data)
"Highlight instances of keys in DATA intervals.
Goes backward if FORWARD-P is nil."
(let ((case-fold-search (evil-snipe--case-p data))
(match (mapconcat #'cdr data ""))
Expand Down Expand Up @@ -363,6 +373,9 @@ Goes backward if FORWARD-P is nil."
overlays)))))))
overlays))

(defvar evil-snipe-local-mode)
(defvar evil-snipe-override-local-mode)

(defun evil-snipe--cleanup ()
"Disable overlays and clean up after evil-snipe."
(when (or evil-snipe-local-mode evil-snipe-override-local-mode)
Expand All @@ -376,6 +389,7 @@ Goes backward if FORWARD-P is nil."
(setq evil-snipe--transient-map-func nil)))

(defun evil-snipe--transient-map (forward-key backward-key)
"Return a transient map with FORWARD-KEY and BACKWARD-KEY."
(let ((map (make-sparse-keymap)))
(set-keymap-parent map evil-snipe-parent-transient-map)
(when evil-snipe-repeat-keys
Expand All @@ -386,8 +400,8 @@ Goes backward if FORWARD-P is nil."

(defun evil-snipe-seek (count keys &optional keymap)
"Perform a snipe with KEYS at COUNT intervals.
KEYS is a list of characters provided by <-c> and <+c> interactive codes. KEYMAP
is the transient map to activate afterwards."
KEYS is a list of characters provided by <-c> and <+c> interactive codes.
KEYMAP is the transient map to activate afterwards."
(pcase keys
(`abort (setq evil-inhibit-operator t))
;; if <enter>, repeat last search
Expand All @@ -401,15 +415,16 @@ is the transient map to activate afterwards."
(let ((data (mapcar #'evil-snipe--process-key keys)))
(let ((case-fold-search (evil-snipe--case-p data))
(count (or count (if evil-snipe--last-direction 1 -1)))
(keymap (if (keymapp keymap) keymap)))
(evil-snipe-keymap (if (keymapp keymap) keymap)))
(unless evil-snipe--last-repeat
(setq evil-snipe--last (list count keys keymap
(setq evil-snipe--last (list count keys evil-snipe-keymap
evil-snipe--consume-match
evil-snipe--match-count)))
(evil-snipe--seek count data)
(point))))))

(defun evil-snipe--seek-re (data scope count)
"Search for DATA regex within SCOPE and COUNT."
(let ((regex (mapconcat #'cdr data ""))
result)
(when (and evil-snipe-skip-leading-whitespace
Expand All @@ -423,10 +438,10 @@ is the transient map to activate afterwards."
result))))

(defun evil-snipe--seek (count data &optional internal-p)
"Perform a snipe and adjust cursor position depending on mode."
"Perform a snipe using COUNT and DATA, adjusting cursor position.
INTERNAL-P is non-nil if this is an internal call."
(let ((orig-point (point))
(forward-p (> count 0))
(match (mapconcat #'cdr data "")))
(forward-p (> count 0)))
;; Adjust search starting point
(if forward-p (forward-char))
(unless evil-snipe--consume-match
Expand Down Expand Up @@ -471,10 +486,10 @@ is the transient map to activate afterwards."
(when evil-snipe-enable-highlight
(evil-snipe--highlight beg end t)))
;; Activate the repeat keymap
(when (and (boundp 'keymap) keymap)
(when (and (boundp 'evil-snipe-keymap) evil-snipe-keymap)
(evil-snipe--disable-transient-map)
(setq evil-snipe--transient-map-func
(set-transient-map keymap))))))
(set-transient-map evil-snipe-keymap))))))

;; Try to "spill over" into new scope on failed search
(evil-snipe-spillover-scope
Expand All @@ -486,10 +501,10 @@ is the transient map to activate afterwards."
;; If, at last, it fails...
(t
(goto-char orig-point)
(when (and evil-snipe--last-repeat (boundp 'keymap) keymap)
(when (and evil-snipe--last-repeat (boundp 'evil-snipe-keymap) evil-snipe-keymap)
(evil-snipe--disable-transient-map)
(setq evil-snipe--transient-map-func
(set-transient-map keymap)))
(set-transient-map evil-snipe-keymap)))
(user-error "Can't find %s" ; show invisible keys
(replace-regexp-in-string
"\t" "<TAB>"
Expand Down Expand Up @@ -520,7 +535,7 @@ is the transient map to activate afterwards."
last-keys last-keymap))))

(evil-define-motion evil-snipe-repeat-reverse (count)
"Repeat the inverse of the last evil-snipe `count' times"
"Repeat the inverse of the last evil-snipe `count' times."
(interactive "<c>")
(evil-snipe-repeat (or (and (integerp count) (- count)) -1)))

Expand All @@ -537,7 +552,8 @@ explicitly choose the function names."
`(progn
(evil-define-motion ,forward-fn (count keys)
,(concat "Jumps to the next " (int-to-string n)
"-char match COUNT matches away. Including KEYS is a list of character codes.")
"-char match COUNT matches away.
Including KEYS is a list of character codes.")
:jump t
(interactive
(let ((count (if current-prefix-arg (prefix-numeric-value current-prefix-arg))))
Expand Down Expand Up @@ -579,6 +595,7 @@ explicitly choose the function names."
;;;###autoload (autoload 'evil-snipe-T "evil-snipe" nil t)
(evil-snipe-def 1 'exclusive "t" "T")

(define-obsolete-variable-alias 'evil-snipe-mode-map 'evil-snipe-local-mode-map "2.0.8")

(defvar evil-snipe-local-mode-map
(let ((map (make-sparse-keymap)))
Expand All @@ -596,6 +613,8 @@ explicitly choose the function names."
"X" #'evil-snipe-X))
map))

(define-obsolete-variable-alias 'evil-snipe-override-mode-map 'evil-snipe-override-local-mode-map "2.0.8")

(defvar evil-snipe-override-local-mode-map
(let ((map (make-sparse-keymap)))
(evil-define-key* 'motion map
Expand All @@ -609,40 +628,36 @@ explicitly choose the function names."
"," #'evil-snipe-repeat-reverse))
map))

(defvar evil-snipe-parent-transient-map
(let ((map (make-sparse-keymap)))
(define-key map ";" #'evil-snipe-repeat)
(define-key map "," #'evil-snipe-repeat-reverse)
map))

;;;###autoload
(defun turn-on-evil-snipe-mode ()
"Enable evil-snipe-mode in the current buffer."
(defun evil-snipe-turn-on-mode ()
"Enable `evil-snipe-mode' in the current buffer."
(unless (apply #'derived-mode-p evil-snipe-disabled-modes)
(evil-snipe-local-mode +1)))

;;;###autoload
(defun turn-on-evil-snipe-override-mode ()
"Enable evil-snipe-mode in the current buffer."
(defun evil-snipe-turn-on-override-mode ()
"Enable `evil-snipe-override-mode' in the current buffer."
(unless (apply #'derived-mode-p evil-snipe-disabled-modes)
(evil-snipe-override-local-mode +1)))

;;;###autoload
(defun turn-off-evil-snipe-mode ()
(defun evil-snipe-turn-off-mode ()
"Disable `evil-snipe-local-mode' in the current buffer."
(evil-snipe-local-mode -1))

;;;###autoload
(defun turn-off-evil-snipe-override-mode ()
"Disable evil-snipe-override-mode in the current buffer."
(defun evil-snipe-turn-off-override-mode ()
"Disable `evil-snipe-override-local-mode' in the current buffer."
(evil-snipe-override-local-mode -1))

(when (fboundp 'advice-add)
(advice-add #'evil-force-normal-state :before #'evil-snipe--cleanup))
(add-hook 'evil-insert-state-entry-hook #'evil-snipe--disable-transient-map)

(defvar evil-snipe--keymaps-init nil)
(defvar evil-snipe--keymaps-init nil
"Non-nil if keymaps are initialized.")
(defun evil-snipe--normalize-keymaps ()
"Normalize keymaps."
(unless evil-snipe--keymaps-init
(set (make-local-variable 'evil-snipe--keymaps-init)
(and (evil-normalize-keymaps) t))))
Expand All @@ -662,14 +677,11 @@ explicitly choose the function names."

;;;###autoload
(define-globalized-minor-mode evil-snipe-mode
evil-snipe-local-mode turn-on-evil-snipe-mode)
evil-snipe-local-mode evil-snipe-turn-on-mode)

;;;###autoload
(define-globalized-minor-mode evil-snipe-override-mode
evil-snipe-override-local-mode turn-on-evil-snipe-override-mode)

(define-obsolete-variable-alias 'evil-snipe-mode-map 'evil-snipe-local-mode-map "2.0.8")
(define-obsolete-variable-alias 'evil-snipe-override-mode-map 'evil-snipe-override-local-mode-map "2.0.8")
evil-snipe-override-local-mode evil-snipe-turn-on-override-mode)

(provide 'evil-snipe)
;;; evil-snipe.el ends here
1 change: 1 addition & 0 deletions test/.nosearch
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@