aboutsummaryrefslogtreecommitdiffstats
path: root/elpa/evil-20220503.1314/evil-search.el
diff options
context:
space:
mode:
Diffstat (limited to 'elpa/evil-20220503.1314/evil-search.el')
-rw-r--r--elpa/evil-20220503.1314/evil-search.el1336
1 files changed, 1336 insertions, 0 deletions
diff --git a/elpa/evil-20220503.1314/evil-search.el b/elpa/evil-20220503.1314/evil-search.el
new file mode 100644
index 0000000..0cc9c1b
--- /dev/null
+++ b/elpa/evil-20220503.1314/evil-search.el
@@ -0,0 +1,1336 @@
+;;; evil-search.el --- Search and substitute -*- lexical-binding: t -*-
+
+;; Author: Vegard Øye <vegard_oye at hotmail.com>
+;; Maintainer: Vegard Øye <vegard_oye at hotmail.com>
+
+;; Version: 1.15.0
+
+;;
+;; This file is NOT part of GNU Emacs.
+
+;;; License:
+
+;; This file is part of Evil.
+;;
+;; Evil is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+;;
+;; Evil is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+;;
+;; You should have received a copy of the GNU General Public License
+;; along with Evil. If not, see <http://www.gnu.org/licenses/>.
+
+(require 'evil-core)
+(require 'evil-common)
+(require 'evil-ex)
+
+;;; Code:
+
+(defun evil-select-search-module (option module)
+ "Change the search module according to MODULE.
+If MODULE is `isearch', then Emacs' isearch module is used.
+If MODULE is `evil-search', then Evil's own interactive
+search module is used."
+ (let ((search-functions
+ '(forward
+ backward
+ word-forward
+ word-backward
+ unbounded-word-forward
+ unbounded-word-backward
+ next
+ previous)))
+ (dolist (fun search-functions)
+ (let ((isearch (intern (format "evil-search-%s" fun)))
+ (evil-search (intern (format "evil-ex-search-%s" fun))))
+ (if (eq module 'isearch)
+ (substitute-key-definition
+ evil-search isearch evil-motion-state-map)
+ (substitute-key-definition
+ isearch evil-search evil-motion-state-map)))))
+ (set-default option module))
+
+;; this customization is here because it requires
+;; the knowledge of `evil-select-search-mode'
+(defcustom evil-search-module 'isearch
+ "The search module to be used. May be either `isearch', for
+Emacs' isearch module, or `evil-search', for Evil's own
+interactive search module. N.b. changing this will not affect keybindings.
+To swap out relevant keybindings, see `evil-select-search-module' function."
+ :type '(radio (const :tag "Emacs built-in isearch." :value isearch)
+ (const :tag "Evil interactive search." :value evil-search))
+ :group 'evil
+ :set 'evil-select-search-module
+ :initialize 'evil-custom-initialize-pending-reset)
+
+(defun evil-push-search-history (string forward)
+ "Push STRING into the appropriate search history (determined by FORWARD)."
+ (let* ((history-var (if forward
+ 'evil-search-forward-history
+ 'evil-search-backward-history))
+ (history (symbol-value history-var)))
+ (unless (equal (car-safe history) string)
+ (set history-var (cons string history)))))
+
+(defun evil-search-incrementally (forward regexp-p)
+ "Search incrementally for user-entered text."
+ (let ((evil-search-prompt (evil-search-prompt forward))
+ (isearch-search-fun-function 'evil-isearch-function)
+ (point (point))
+ search-nonincremental-instead)
+ (setq isearch-forward forward)
+ (evil-save-echo-area
+ (evil-without-input-method-hooks
+ ;; set the input method locally rather than globally to ensure that
+ ;; isearch clears the input method when it's finished
+ (setq current-input-method evil-input-method)
+ (if forward
+ (isearch-forward regexp-p)
+ (isearch-backward regexp-p))
+ (evil-push-search-history isearch-string forward)
+ (setq current-input-method nil))
+ (when (/= (point) point)
+ ;; position the point at beginning of the match only if the call to
+ ;; `isearch' has really moved the point. `isearch' doesn't move the
+ ;; point only if "C-g" is hit twice to exit the search, in which case we
+ ;; shouldn't move the point either.
+ (when (and forward isearch-other-end)
+ (goto-char isearch-other-end))
+ (when (and (eq point (point))
+ (not (string= isearch-string "")))
+ (if forward
+ (isearch-repeat-forward)
+ (isearch-repeat-backward))
+ (isearch-exit)
+ (when (and forward isearch-other-end)
+ (goto-char isearch-other-end)))
+ (evil-flash-search-pattern
+ (evil-search-message isearch-string forward))))))
+
+(defun evil-flash-search-pattern (string &optional all)
+ "Flash last search matches for duration of `evil-flash-delay'.
+If ALL is non-nil, flash all matches. STRING is a message
+to display in the echo area."
+ (let ((lazy-highlight-initial-delay 0)
+ (isearch-search-fun-function 'evil-isearch-function)
+ (isearch-case-fold-search case-fold-search)
+ (disable #'(lambda (&optional _arg) (evil-flash-hook t))))
+ (when evil-flash-timer
+ (cancel-timer evil-flash-timer))
+ (unless (or (null string)
+ (string= string ""))
+ (evil-echo-area-save)
+ (evil-echo "%s" string)
+ (isearch-highlight (match-beginning 0) (match-end 0))
+ (when all
+ (setq isearch-lazy-highlight-wrapped nil
+ isearch-lazy-highlight-start (point)
+ isearch-lazy-highlight-end (point))
+ (isearch-lazy-highlight-new-loop)
+ (unless isearch-lazy-highlight-overlays
+ (isearch-lazy-highlight-update)))
+ (add-hook 'pre-command-hook #'evil-flash-hook nil t)
+ (add-hook 'evil-operator-state-exit-hook #'evil-flash-hook nil t)
+ (add-hook 'pre-command-hook #'evil-clean-isearch-overlays nil t)
+ (setq evil-flash-timer
+ (run-at-time evil-flash-delay nil disable)))))
+
+(defun evil-clean-isearch-overlays ()
+ "Clean isearch overlays unless `this-command' is search."
+ (remove-hook 'pre-command-hook #'evil-clean-isearch-overlays t)
+ (unless (memq this-command
+ '(evil-search-backward
+ evil-search-forward
+ evil-search-next
+ evil-search-previous
+ evil-search-word-backward
+ evil-search-word-forward))
+ (isearch-clean-overlays)))
+(put 'evil-clean-isearch-overlays 'permanent-local-hook t)
+
+(defun evil-flash-hook (&optional force)
+ "Disable hightlighting if `this-command' is not search.
+Disable anyway if FORCE is t."
+ (when (or force
+ ;; to avoid flicker, don't disable highlighting
+ ;; if the next command is also a search command
+ (not (memq this-command
+ '(evil-search-backward
+ evil-search-forward
+ evil-search-next
+ evil-search-previous
+ evil-search-word-backward
+ evil-search-word-forward))))
+ (evil-echo-area-restore)
+ (isearch-dehighlight)
+ (setq isearch-lazy-highlight-last-string nil)
+ (lazy-highlight-cleanup t)
+ (when evil-flash-timer
+ (cancel-timer evil-flash-timer)))
+ (remove-hook 'pre-command-hook #'evil-flash-hook t)
+ (remove-hook 'evil-operator-state-exit-hook #'evil-flash-hook t))
+(put 'evil-flash-hook 'permanent-local-hook t)
+
+(defun evil-search-with-predicate (search-fun pred string bound noerror count)
+ "Execute a search with a predicate function.
+SEARCH-FUN is a search function (e.g. `re-search-forward') and
+PREDICATE is a two-argument function satisfying the interface of
+`isearch-filter-predicate', or `nil'. STRING, BOUND, NOERROR and
+COUNT are passed unchanged to SEARCH-FUN. The first match
+satisfying the predicate (or `nil') is returned."
+ (catch 'done
+ (while t
+ (let ((result (funcall search-fun string bound noerror count)))
+ (cond
+ ((not result) (throw 'done nil))
+ ((not pred) (throw 'done result))
+ ((funcall pred (match-beginning 0) (match-end 0)) (throw 'done result)))))))
+
+(defun evil-search-function (&optional forward regexp-p wrap predicate)
+ "Return a search function.
+If FORWARD is nil, search backward, otherwise forward.
+If REGEXP-P is non-nil, the input is a regular expression.
+If WRAP is non-nil, the search wraps around the top or bottom
+of the buffer.
+If PREDICATE is non-nil, it must be a function accepting two
+arguments: the bounds of a match, returning non-nil if that match is
+acceptable."
+ `(lambda (string &optional bound noerror count)
+ (let ((start (point))
+ (search-fun ',(if regexp-p
+ (if forward
+ 're-search-forward
+ 're-search-backward)
+ (if forward
+ 'search-forward
+ 'search-backward)))
+ result)
+ (setq result (evil-search-with-predicate
+ search-fun ,predicate string
+ bound ,(if wrap t 'noerror) count))
+ (when (and ,wrap (null result))
+ (goto-char ,(if forward '(point-min) '(point-max)))
+ (unwind-protect
+ (setq result (evil-search-with-predicate
+ search-fun ,predicate string bound noerror count))
+ (unless result
+ (goto-char start))))
+ result)))
+
+(defun evil-isearch-function ()
+ "Return a search function for use with isearch.
+Based on `isearch-regexp' and `isearch-forward'."
+ (evil-search-function isearch-forward evil-regexp-search evil-search-wrap 'isearch-filter-predicate))
+
+(defun evil-search (string forward &optional regexp-p start)
+ "Search for STRING and highlight matches.
+If FORWARD is nil, search backward, otherwise forward.
+If REGEXP-P is non-nil, STRING is taken to be a regular expression.
+START is the position to search from; if unspecified, it is
+one more than the current position."
+ (when (and (stringp string)
+ (not (string= string "")))
+ (let* ((orig (point))
+ (start (or start
+ (if forward
+ (min (point-max) (1+ orig))
+ orig)))
+ (isearch-regexp regexp-p)
+ (isearch-forward forward)
+ (case-fold-search
+ (unless (and search-upper-case
+ (not (isearch-no-upper-case-p string nil)))
+ case-fold-search))
+ (search-func (evil-search-function
+ forward regexp-p evil-search-wrap 'isearch-filter-predicate)))
+ ;; no text properties, thank you very much
+ (set-text-properties 0 (length string) nil string)
+ ;; position to search from
+ (goto-char start)
+ (setq isearch-string string)
+ (isearch-update-ring string regexp-p)
+ (condition-case nil
+ (funcall search-func string)
+ (search-failed
+ (goto-char orig)
+ (user-error "\"%s\": %s not found"
+ string (if regexp-p "pattern" "string"))))
+ ;; always position point at the beginning of the match
+ (goto-char (match-beginning 0))
+ ;; determine message for echo area
+ (cond
+ ((and forward (< (point) start))
+ (when evil-search-wrap-ring-bell (ding))
+ (setq string "Search wrapped around BOTTOM of buffer"))
+ ((and (not forward) (> (point) start))
+ (when evil-search-wrap-ring-bell (ding))
+ (setq string "Search wrapped around TOP of buffer"))
+ (t
+ (setq string (evil-search-message string forward))))
+ (evil-flash-search-pattern string t))))
+
+(defun evil-search-word (forward unbounded symbol)
+ "Search for word near point.
+If FORWARD is nil, search backward, otherwise forward. If SYMBOL
+is non-nil then the functions searches for the symbol at point,
+otherwise for the word at point."
+ (let ((string (car-safe regexp-search-ring)))
+ (setq isearch-forward forward)
+ (cond
+ ((and (memq last-command
+ '(evil-search-word-forward
+ evil-search-word-backward))
+ (stringp string)
+ (not (string= string "")))
+ (evil-search string forward t))
+ (t
+ (setq string (evil-find-thing forward (if symbol 'symbol 'evil-word)))
+ (cond
+ ((null string)
+ (user-error "No word under point"))
+ (unbounded
+ (setq string (regexp-quote string)))
+ (t
+ (setq string
+ (format (if symbol "\\_<%s\\_>" "\\<%s\\>")
+ (regexp-quote string)))))
+ (evil-push-search-history string forward)
+ (evil-search string forward t)))))
+
+(defun evil--find-thing (forward thing)
+ "Return a cons of THING near point as a string and its position.
+THING should be a symbol understood by `thing-at-point',
+e.g. 'symbol or 'word. If FORWARD is nil, search backward,
+otherwise forward. Returns nil if nothing is found."
+ (let ((move (if forward #'forward-char #'backward-char))
+ (end (if forward #'eobp #'bobp))
+ string)
+ (save-excursion
+ (setq string (thing-at-point thing))
+ ;; if there's nothing under point, go forwards
+ ;; (or backwards) to find it
+ (while (and (null string) (not (funcall end)))
+ (funcall move)
+ (setq string (thing-at-point thing)))
+ (when (stringp string)
+ (set-text-properties 0 (length string) nil string))
+ (when (> (length string) 0)
+ (cons string (point))))))
+
+(defun evil-find-thing (forward thing)
+ "Return a THING near point as a string.
+THING should be a symbol understood by `thing-at-point',
+e.g. 'symbol or 'word. If FORWARD is nil, search backward,
+otherwise forward. Returns nil if nothing is found."
+ (car (evil--find-thing forward thing)))
+
+(defun evil-find-word (forward)
+ "Return word near point as a string.
+If FORWARD is nil, search backward, otherwise forward. Returns
+nil if nothing is found."
+ (evil-find-thing forward 'word))
+
+(defun evil-find-symbol (forward)
+ "Return word near point as a string.
+If FORWARD is nil, search backward, otherwise forward. Returns
+nil if nothing is found."
+ (evil-find-thing forward 'symbol))
+
+(defun evil-search-prompt (forward)
+ "Return the search prompt for the given direction."
+ (if forward "/" "?"))
+
+(defun evil-search-message (string forward)
+ "Prefix STRING with the search prompt."
+ (format "%s%s" (evil-search-prompt forward) string))
+
+(defadvice isearch-message-prefix (around evil activate)
+ "Use `evil-search-prompt'."
+ (if evil-search-prompt
+ (setq ad-return-value evil-search-prompt)
+ ad-do-it))
+
+(defadvice isearch-delete-char (around evil activate)
+ "Exit search if no search string."
+ (cond
+ ((and evil-search-prompt (string= isearch-string ""))
+ (let (search-nonincremental-instead)
+ (setq isearch-success nil)
+ (isearch-exit)))
+ (t
+ ad-do-it)))
+
+(defadvice isearch-lazy-highlight-search (around evil activate)
+ "Never wrap the search in this context."
+ (let (evil-search-wrap)
+ ad-do-it))
+
+;;; Ex search
+
+(defun evil-ex-regex-without-case (re)
+ "Return the regular expression without all occurrences of \\c and \\C."
+ (evil-transform-regexp re '((?c . "") (?C . ""))))
+
+(defun evil-ex-regex-case (re default-case)
+ "Return the case as implied by \\c or \\C in regular expression RE.
+If \\c appears anywhere in the pattern, the pattern is case
+insensitive. If \\C appears, the pattern is case sensitive.
+Only the first occurrence of \\c or \\C is used, all others are
+ignored. If neither \\c nor \\C appears in the pattern, the case
+specified by DEFAULT-CASE is used. DEFAULT-CASE should be either
+`sensitive', `insensitive' or `smart'. In the latter case, the pattern
+will be case-sensitive if and only if it contains an upper-case
+letter, otherwise it will be case-insensitive."
+ (cond
+ ((string-match "\\(?:^\\|[^\\\\]\\)\\(?:\\\\\\\\\\)*\\\\\\([cC]\\)" re)
+ (if (eq (aref (match-string 1 re) 0) ?c) 'insensitive 'sensitive))
+ ((eq default-case 'smart)
+ (if (isearch-no-upper-case-p re t)
+ 'insensitive
+ 'sensitive))
+ (t default-case)))
+
+;; a pattern
+(defun evil-ex-make-substitute-pattern (regexp flags)
+ "Creates a PATTERN for substitution with FLAGS.
+This function respects the values of `evil-ex-substitute-case'
+and `evil-ex-substitute-global'."
+ (evil-ex-make-pattern regexp
+ (cond
+ ((memq ?i flags) 'insensitive)
+ ((memq ?I flags) 'sensitive)
+ ((not evil-ex-substitute-case)
+ evil-ex-search-case)
+ (t evil-ex-substitute-case))
+ (or (and evil-ex-substitute-global
+ (not (memq ?g flags)))
+ (and (not evil-ex-substitute-global)
+ (memq ?g flags)))))
+
+(defun evil-ex-make-search-pattern (regexp)
+ "Creates a PATTERN for search.
+This function respects the values of `evil-ex-search-case'."
+ (evil-ex-make-pattern regexp evil-ex-search-case t))
+
+(defun evil-ex-make-pattern (regexp case whole-line)
+ "Create a new search pattern.
+REGEXP is the regular expression to be searched for. CASE should
+be either 'sensitive, 'insensitive for case-sensitive and
+case-insensitive search, respectively, or anything else. In the
+latter case the pattern is smart-case, i.e. it is automatically
+sensitive of the pattern contains one upper case letter,
+otherwise it is insensitive. The input REGEXP is considered a
+Vim-style regular expression if `evil-ex-search-vim-style-regexp'
+is non-nil, in which case it is transformed to an Emacs style
+regular expression (i.e. certain backslash-codes are
+transformed. Otherwise REGEXP must be an Emacs style regular
+expression and is not transformed."
+ (let ((re (evil-ex-regex-without-case regexp))
+ (ignore-case (eq (evil-ex-regex-case regexp case) 'insensitive)))
+ ;; possibly transform regular expression from vim-style to
+ ;; Emacs-style.
+ (if (and evil-ex-search-vim-style-regexp
+ (not (or (string-match-p "\\`\\\\_?<" regexp)
+ (string-match-p "\\\\_?>\\'" regexp))))
+ (setq re (evil-transform-vim-style-regexp re))
+ ;; Even for Emacs regular expressions we translate certain
+ ;; whitespace sequences
+ (setq re (evil-transform-regexp re
+ '((?t . "\t")
+ (?n . "\n")
+ (?r . "\r")))))
+ (list re ignore-case whole-line)))
+
+(defun evil-ex-pattern-regex (pattern)
+ "Return the regular expression of a search PATTERN."
+ (nth 0 pattern))
+
+(defun evil-ex-pattern-ignore-case (pattern)
+ "Return t if and only if PATTERN should ignore case."
+ (nth 1 pattern))
+
+(defun evil-ex-pattern-whole-line (pattern)
+ "Return t if and only if PATTERN should match all occurences of a line.
+Otherwise PATTERN matches only the first occurence."
+ (nth 2 pattern))
+
+;; Highlight
+(defun evil-ex-make-hl (name &rest args)
+ "Create a new highlight object with name NAME and properties ARGS.
+The following properties are supported:
+:face The face to be used for the highlighting overlays.
+:win The window in which the highlighting should be shown.
+ Note that the highlight will be visible in all windows showing
+ the corresponding buffer, but only the matches visible in the
+ specified window will actually be highlighted. If :win is nil,
+ the matches in all windows will be highlighted.
+:min The minimal buffer position for highlighted matches.
+:max The maximal buffer position for highlighted matches.
+:match-hook A hook to be called once for each highlight.
+ The hook must take two arguments, the highlight and
+ the overlay for that highlight.
+:update-hook A hook called once after updating the highlighting
+ with two arguments, the highlight and a message string
+ describing the current match status."
+ (unless (symbolp name)
+ (user-error "Expected symbol as name of highlight"))
+ (let ((face 'evil-ex-lazy-highlight)
+ (win (selected-window))
+ min max match-hook update-hook)
+ (while args
+ (let ((key (pop args))
+ (val (pop args)))
+ (cond
+ ((eq key :face) (setq face val))
+ ((eq key :win) (setq win val))
+ ((eq key :min) (setq min val))
+ ((eq key :max) (setq max val))
+ ((eq key :match-hook) (setq match-hook val))
+ ((eq key :update-hook) (setq update-hook val))
+ (t (user-error "Unexpected keyword: %s" key)))))
+ (when (assoc name evil-ex-active-highlights-alist)
+ (evil-ex-delete-hl name))
+ (when (null evil-ex-active-highlights-alist)
+ (add-hook 'window-scroll-functions
+ #'evil-ex-hl-update-highlights-scroll nil t)
+ (add-hook 'window-size-change-functions
+ #'evil-ex-hl-update-highlights-resize nil))
+ (push (cons name (vector name
+ nil
+ face
+ win
+ min
+ max
+ match-hook
+ update-hook
+ nil))
+ evil-ex-active-highlights-alist)))
+
+(defun evil-ex-hl-name (hl)
+ "Return the name of the highlight HL."
+ (aref hl 0))
+
+(defun evil-ex-hl-pattern (hl)
+ "Return the pattern of the highlight HL."
+ (aref hl 1))
+
+(defun evil-ex-hl-set-pattern (hl pattern)
+ "Set the pattern of the highlight HL to PATTERN."
+ (aset hl 1 pattern))
+
+(defun evil-ex-hl-face (hl)
+ "Return the face of the highlight HL."
+ (aref hl 2))
+
+(defun evil-ex-hl-window (hl)
+ "Return the window of the highlight HL."
+ (aref hl 3))
+
+(defun evil-ex-hl-min (hl)
+ "Return the minimal buffer position of the highlight HL."
+ (aref hl 4))
+
+(defun evil-ex-hl-set-min (hl min)
+ "Set the minimal buffer position of the highlight HL to MIN."
+ (aset hl 4 min))
+
+(defun evil-ex-hl-max (hl)
+ "Return the maximal buffer position of the highlight HL."
+ (aref hl 5))
+
+(defun evil-ex-hl-set-max (hl max)
+ "Set the minimal buffer position of the highlight HL to MAX."
+ (aset hl 5 max))
+
+(defun evil-ex-hl-match-hook (hl)
+ "Return the match-hook of the highlight HL."
+ (aref hl 6))
+
+(defun evil-ex-hl-update-hook (hl)
+ "Return the update-hook of the highlight HL."
+ (aref hl 7))
+
+(defun evil-ex-hl-overlays (hl)
+ "Return the list of active overlays of the highlight HL."
+ (aref hl 8))
+
+(defun evil-ex-hl-set-overlays (hl overlays)
+ "Set the list of active overlays of the highlight HL to OVERLAYS."
+ (aset hl 8 overlays))
+
+(defun evil-ex-delete-hl (name)
+ "Remove the highlighting object with a certain NAME."
+ (let ((hl (cdr-safe (assoc name evil-ex-active-highlights-alist))))
+ (when hl
+ (mapc #'delete-overlay (evil-ex-hl-overlays hl))
+ (setq evil-ex-active-highlights-alist
+ (assq-delete-all name evil-ex-active-highlights-alist))
+ (evil-ex-hl-update-highlights))
+ (when (null evil-ex-active-highlights-alist)
+ (remove-hook 'window-scroll-functions
+ #'evil-ex-hl-update-highlights-scroll t)
+ (remove-hook 'window-size-change-functions
+ #'evil-ex-hl-update-highlights-resize))))
+
+(defun evil-ex-hl-active-p (name)
+ "Whether the highlight with a certain NAME is active."
+ (and (assoc name evil-ex-active-highlights-alist) t))
+
+(defun evil-ex-hl-change (name pattern)
+ "Set the regular expression of highlight NAME to PATTERN."
+ (let ((hl (cdr-safe (assoc name evil-ex-active-highlights-alist))))
+ (when hl
+ (evil-ex-hl-set-pattern hl
+ (if (zerop (length pattern))
+ nil
+ pattern))
+ (evil-ex-hl-idle-update))))
+
+(defun evil-ex-hl-set-region (name beg end &optional _type)
+ "Set minimal and maximal position of highlight NAME to BEG and END."
+ (let ((hl (cdr-safe (assoc name evil-ex-active-highlights-alist))))
+ (when hl
+ (evil-ex-hl-set-min hl beg)
+ (evil-ex-hl-set-max hl end)
+ (evil-ex-hl-idle-update))))
+
+(defun evil-ex-hl-get-max (name)
+ "Return the maximal position of the highlight with name NAME."
+ (let ((hl (cdr-safe (assoc name evil-ex-active-highlights-alist))))
+ (and hl (evil-ex-hl-max hl))))
+
+(defun evil-ex-hl-update-highlights ()
+ "Update the overlays of all active highlights."
+ (dolist (hl (mapcar #'cdr evil-ex-active-highlights-alist))
+ (let* ((old-ovs (evil-ex-hl-overlays hl))
+ new-ovs
+ (pattern (evil-ex-hl-pattern hl))
+ (case-fold-search (evil-ex-pattern-ignore-case pattern))
+ (case-replace case-fold-search)
+ (face (evil-ex-hl-face hl))
+ (match-hook (evil-ex-hl-match-hook hl))
+ result)
+ (if pattern
+ ;; collect all visible ranges
+ (let (ranges sranges)
+ (dolist (win (if (eq evil-ex-interactive-search-highlight
+ 'all-windows)
+ (get-buffer-window-list (current-buffer) nil t)
+ (list (evil-ex-hl-window hl))))
+ (when (window-live-p win)
+ (let ((beg (max (window-start win)
+ (or (evil-ex-hl-min hl) (point-min))))
+ (end (min (window-end win)
+ (or (evil-ex-hl-max hl) (point-max)))))
+ (when (< beg end)
+ (push (cons beg end) ranges)))))
+ (setq ranges
+ (sort ranges #'(lambda (r1 r2) (< (car r1) (car r2)))))
+ (while ranges
+ (let ((r1 (pop ranges))
+ (r2 (pop ranges)))
+ (cond
+ ;; last range
+ ((null r2)
+ (push r1 sranges))
+ ;; ranges overlap, union
+ ((>= (cdr r1) (car r2))
+ (push (cons (car r1)
+ (max (cdr r1) (cdr r2)))
+ ranges))
+ ;; ranges distinct
+ (t
+ (push r1 sranges)
+ (push r2 ranges)))))
+
+ ;; run through all ranges
+ (condition-case lossage
+ (save-match-data
+ (dolist (r sranges)
+ (let ((beg (car r))
+ (end (cdr r)))
+ (save-excursion
+ (goto-char beg)
+ ;; set the overlays for the current highlight,
+ ;; reusing old overlays (if possible)
+ (while (and (not (eobp))
+ (evil-ex-search-find-next-pattern pattern)
+ (<= (match-end 0) end)
+ (not (and (= (match-end 0) end)
+ (string= (evil-ex-pattern-regex pattern)
+ "^"))))
+ (let ((ov (or (pop old-ovs) (make-overlay 0 0))))
+ (move-overlay ov (match-beginning 0) (match-end 0))
+ (overlay-put ov 'face face)
+ (overlay-put ov 'evil-ex-hl (evil-ex-hl-name hl))
+ (overlay-put ov 'priority 1000)
+ (push ov new-ovs)
+ (when match-hook (funcall match-hook hl ov)))
+ (cond
+ ((and (not (evil-ex-pattern-whole-line pattern))
+ (not (string-match-p "\n" (buffer-substring-no-properties
+ (match-beginning 0)
+ (match-end 0)))))
+ (forward-line))
+ ((= (match-beginning 0) (match-end 0))
+ (forward-char))
+ (t (goto-char (match-end 0))))))))
+ (mapc #'delete-overlay old-ovs)
+ (evil-ex-hl-set-overlays hl new-ovs)
+ (if (or (null pattern) new-ovs)
+ (setq result t)
+ ;; Maybe the match could just not be found somewhere else?
+ (save-excursion
+ (goto-char (or (evil-ex-hl-min hl) (point-min)))
+ (if (and (evil-ex-search-find-next-pattern pattern)
+ (< (match-end 0) (or (evil-ex-hl-max hl)
+ (point-max))))
+ (setq result (format "Match in line %d"
+ (line-number-at-pos
+ (match-beginning 0))))
+ (setq result "No match")))))
+
+ (invalid-regexp
+ (setq result (cadr lossage)))
+
+ (search-failed
+ (setq result (nth 2 lossage)))
+
+ (error
+ (setq result (format "%s" (cadr lossage))))
+
+ (user-error
+ (setq result (format "%s" (cadr lossage))))))
+ ;; no pattern, remove all highlights
+ (mapc #'delete-overlay old-ovs)
+ (evil-ex-hl-set-overlays hl new-ovs))
+ (when (evil-ex-hl-update-hook hl)
+ (funcall (evil-ex-hl-update-hook hl) hl result)))))
+
+(defun evil-ex-search-find-next-pattern (pattern &optional direction)
+ "Look for the next occurrence of PATTERN in a certain DIRECTION.
+Note that this function ignores the whole-line property of PATTERN."
+ (setq direction (or direction 'forward))
+ (let ((case-fold-search (evil-ex-pattern-ignore-case pattern)))
+ (cond
+ ((eq direction 'forward)
+ (re-search-forward (evil-ex-pattern-regex pattern) nil t))
+ ((eq direction 'backward)
+ (let* ((pnt (point))
+ (ret (re-search-backward (evil-ex-pattern-regex pattern) nil t))
+ (m (and ret (match-data))))
+ (if ret
+ (forward-char)
+ (goto-char (point-min)))
+ (let ((fwdret
+ (re-search-forward (evil-ex-pattern-regex pattern) nil t)))
+ (cond
+ ((and fwdret (< (match-beginning 0) pnt))
+ (setq ret fwdret)
+ (goto-char (match-beginning 0)))
+ (ret
+ (set-match-data m)
+ (goto-char (match-beginning 0)))
+ (t
+ (goto-char pnt)
+ ret)))))
+ (t
+ (user-error "Unknown search direction: %s" direction)))))
+
+(defun evil-ex-hl-idle-update ()
+ "Triggers the timer to update the highlights in the current buffer."
+ (when (and evil-ex-interactive-search-highlight
+ evil-ex-active-highlights-alist)
+ (when evil-ex-hl-update-timer
+ (cancel-timer evil-ex-hl-update-timer))
+ (setq evil-ex-hl-update-timer
+ (run-at-time evil-ex-hl-update-delay nil
+ #'evil-ex-hl-do-update-highlight
+ (current-buffer)))))
+
+(defun evil-ex-hl-do-update-highlight (&optional buffer)
+ "Timer function for updating the highlights."
+ (when (buffer-live-p buffer)
+ (with-current-buffer buffer
+ (evil-ex-hl-update-highlights)))
+ (setq evil-ex-hl-update-timer nil))
+
+(defun evil-ex-hl-update-highlights-scroll (win _beg)
+ "Update highlights after scrolling in some window."
+ (with-current-buffer (window-buffer win)
+ (evil-ex-hl-idle-update)))
+(put 'evil-ex-hl-update-highlights-scroll 'permanent-local-hook t)
+
+(defun evil-ex-hl-update-highlights-resize (frame)
+ "Update highlights after resizing a window."
+ (let ((buffers (delete-dups (mapcar #'window-buffer (window-list frame)))))
+ (dolist (buf buffers)
+ (with-current-buffer buf
+ (evil-ex-hl-idle-update)))))
+(put 'evil-ex-hl-update-highlights-resize 'permanent-local-hook t)
+
+;; interactive search
+(defun evil-ex-search-activate-highlight (pattern)
+ "Activate highlighting of the search pattern set to PATTERN.
+This function does nothing if `evil-ex-search-interactive' or
+`evil-ex-search-highlight-all' is nil. "
+ (when (and evil-ex-search-interactive evil-ex-search-highlight-all)
+ (with-current-buffer (or evil-ex-current-buffer (current-buffer))
+ (unless (evil-ex-hl-active-p 'evil-ex-search)
+ (evil-ex-make-hl 'evil-ex-search
+ :win (or (minibuffer-selected-window) (selected-window))))
+ (if pattern
+ (evil-ex-hl-change 'evil-ex-search pattern)))))
+
+(defun evil-ex-search (&optional count)
+ "Search forward or backward COUNT times for the current ex search pattern.
+The search pattern is determined by `evil-ex-search-pattern' and
+the direcion is determined by `evil-ex-search-direction'."
+ (setq evil-ex-search-start-point (point)
+ evil-ex-last-was-search t
+ count (or count 1))
+ (let ((orig (point))
+ wrapped)
+ (dotimes (_ (or count 1))
+ (when (eq evil-ex-search-direction 'forward)
+ (unless (eobp) (forward-char))
+ ;; maybe skip end-of-line
+ (when (and (not evil-move-beyond-eol) (eolp) (not (eobp)))
+ (forward-char)))
+ (let ((res (evil-ex-find-next nil nil (not evil-search-wrap))))
+ (cond
+ ((not res)
+ (goto-char orig)
+ (signal 'search-failed
+ (list (evil-ex-pattern-regex evil-ex-search-pattern))))
+ ((eq res 'wrapped) (setq wrapped t)))))
+ (if wrapped
+ (let (message-log-max)
+ (message "Search wrapped")))
+ (goto-char (match-beginning 0))
+ (setq evil-ex-search-match-beg (match-beginning 0)
+ evil-ex-search-match-end (match-end 0))
+ (evil-ex-search-goto-offset evil-ex-search-offset)
+ (evil-ex-search-activate-highlight evil-ex-search-pattern)))
+
+(defun evil-ex-find-next (&optional pattern direction nowrap)
+ "Search for the next occurrence of the PATTERN in DIRECTION.
+PATTERN must be created using `evil-ex-make-pattern', DIRECTION
+is either 'forward or 'backward. If NOWRAP is non nil, the search
+does not wrap at buffer boundaries. Furthermore this function
+only searches invisible text if `search-invisible' is t. If
+PATTERN is not specified the current global pattern
+`evil-ex-search-pattern' and if DIRECTION is not specified the
+current global direction `evil-ex-search-direction' is used.
+This function returns t if the search was successful, nil if it
+was unsuccessful and 'wrapped if the search was successful but
+has been wrapped at the buffer boundaries."
+ (setq pattern (or pattern evil-ex-search-pattern)
+ direction (or direction evil-ex-search-direction))
+ (unless (and pattern (evil-ex-pattern-regex pattern))
+ (signal 'search-failed (list "No search pattern")))
+ (catch 'done
+ (let (wrapped)
+ (while t
+ (let ((search-result (evil-ex-search-find-next-pattern pattern
+ direction)))
+ (cond
+ ((and search-result
+ (or (eq search-invisible t)
+ (not (isearch-range-invisible
+ (match-beginning 0) (match-end 0)))))
+ ;; successful search and not invisible
+ (throw 'done (if wrapped 'wrapped t)))
+ ((not search-result)
+ ;; unsuccessful search
+ (if nowrap
+ (throw 'done nil)
+ (setq nowrap t
+ wrapped t)
+ (goto-char (if (eq direction 'forward)
+ (point-min)
+ (point-max)))))))))))
+
+(defun evil-ex-search-update (pattern offset beg end message)
+ "Update the highlighting and info-message for the search pattern.
+PATTERN is the search pattern and OFFSET the associated offset.
+BEG and END specifiy the current match, MESSAGE is the info
+message to be shown. This function does nothing if
+`evil-ex-search-interactive' is nil."
+ (when evil-ex-search-interactive
+ (cond
+ ((and beg end)
+ ;; update overlay
+ (if evil-ex-search-overlay
+ (move-overlay evil-ex-search-overlay beg end)
+ (setq evil-ex-search-overlay
+ (make-overlay beg end))
+ (overlay-put evil-ex-search-overlay 'priority 1001)
+ (overlay-put evil-ex-search-overlay 'face 'evil-ex-search))
+ ;; move point
+ (goto-char beg)
+ (evil-ex-search-goto-offset offset)
+ ;; update highlights
+ (when evil-ex-search-highlight-all
+ (evil-ex-hl-change 'evil-ex-search pattern)))
+ (t
+ ;; no match
+ (when evil-ex-search-overlay
+ ;; remove overlay
+ (delete-overlay evil-ex-search-overlay)
+ (setq evil-ex-search-overlay nil))
+ ;; no highlights
+ (when evil-ex-search-highlight-all
+ (evil-ex-hl-change 'evil-ex-search nil))
+ ;; and go to initial position
+ (goto-char evil-ex-search-start-point)))
+ (when (stringp message)
+ (evil-ex-echo "%s" message))))
+
+(defun evil-ex-search-start-session ()
+ "Initialize Ex for interactive search."
+ (remove-hook 'minibuffer-setup-hook #'evil-ex-search-start-session)
+ (when evil-ex-search-incremental
+ (add-hook 'after-change-functions #'evil-ex-search-update-pattern nil t))
+ (add-hook 'minibuffer-exit-hook #'evil-ex-search-stop-session)
+ (add-hook 'mouse-leave-buffer-hook #'evil-ex-search-exit)
+ (evil-ex-search-activate-highlight nil))
+(put 'evil-ex-search-start-session 'permanent-local-hook t)
+
+(defun evil-ex-search-stop-session ()
+ "Stop interactive search."
+ (with-current-buffer evil-ex-current-buffer
+ ;; TODO: This is a bad fix to remove duplicates. The duplicates
+ ;; exist because `isearch-range-invisible' may add a single
+ ;; overlay multiple times if we are in an unlucky situation
+ ;; of overlapping overlays. This happens in our case because
+ ;; of the overlays that are used for (lazy) highlighting.
+ ;; Perhaps it would be better to disable those overlays
+ ;; temporarily before calling `isearch-range-invisible'.
+ (setq isearch-opened-overlays (delete-dups isearch-opened-overlays))
+ (isearch-clean-overlays))
+ (remove-hook 'minibuffer-exit-hook #'evil-ex-search-stop-session)
+ (remove-hook 'mouse-leave-buffer-hook #'evil-ex-search-exit)
+ (remove-hook 'after-change-functions #'evil-ex-search-update-pattern t)
+ (when evil-ex-search-overlay
+ (delete-overlay evil-ex-search-overlay)
+ (setq evil-ex-search-overlay nil)))
+(put 'evil-ex-search-stop-session 'permanent-local-hook t)
+
+(defun evil-ex-split-search-pattern (pattern direction)
+ "Split PATTERN in regexp, offset and next-pattern parts.
+Returns a triple (regexp offset next-search)."
+ (save-match-data
+ (if (or (and (eq direction 'forward)
+ (string-match "\\(?:^\\|[^\\\\]\\)\\(?:\\\\\\\\\\)*\\(/\\([^;]*\\)\\(?:;\\([/?].*\\)?\\)?\\)?$"
+ pattern))
+ (and (eq direction 'backward)
+ (string-match "\\(?:^\\|[^\\\\]\\)\\(?:\\\\\\\\\\)*\\(\\?\\([^;]*\\)\\(?:;\\([/?].*\\)?\\)?\\)?$"
+ pattern)))
+ (list (substring pattern 0 (match-beginning 1))
+ (match-string 2 pattern)
+ (match-string 3 pattern))
+ (list pattern nil nil))))
+
+(defun evil-ex-search-full-pattern (pattern-string count direction)
+ "Search for a full search pattern PATTERN-STRING in DIRECTION.
+This function split PATTERN-STRING in
+pattern/offset/;next-pattern parts and performs the search in
+DIRECTION which must be either 'forward or 'backward. The first
+search is repeated COUNT times. If the pattern part of
+PATTERN-STRING is empty, the last global pattern stored in
+`evil-ex-search-pattern' is used instead if in addition the
+offset part is nil (i.e. no pattern/offset separator), the last
+global offset stored in `evil-ex-search-offset' is used as
+offset. The current match data will correspond to the last
+successful match. This function returns a triple (RESULT PATTERN
+OFFSET) where RESULT is
+
+ t the search has been successful without wrap
+ 'wrap the search has been successful with wrap
+ 'empty-pattern the last pattern has been empty
+ nil the search has not been successful
+
+and PATTERN and OFFSET are the last pattern and offset this
+function searched for. Note that this function does not handle
+any error conditions."
+ (setq count (or count 1))
+ (catch 'done
+ (while t
+ (let* ((res (evil-ex-split-search-pattern pattern-string direction))
+ (pat (pop res))
+ (offset (pop res))
+ (next-pat (pop res))
+ (orig-pat pat))
+ ;; use last pattern if no new pattern has been specified
+ (if (not (zerop (length pat)))
+ (setq pat (evil-ex-make-search-pattern pat))
+ (setq pat evil-ex-search-pattern
+ offset (or offset evil-ex-search-offset)))
+ (when (zerop (length pat))
+ (throw 'done (list 'empty-pattern pat offset)))
+ (let (new-dir repeat-last search-result)
+ (while (> count 0)
+ (let ((result (evil-ex-find-next pat direction
+ (not evil-search-wrap))))
+ (if (not result) (setq search-result nil count 0)
+ (setq search-result
+ (if (or (eq result 'wrap)
+ (eq search-result 'wrap))
+ 'wrap t)
+ count (1- count)))))
+ (cond
+ ;; search failed
+ ((not search-result) (throw 'done (list nil pat offset)))
+ ;; no next pattern, search complete
+ ((zerop (length next-pat))
+ (evil-ex-search-goto-offset offset)
+ (throw 'done (list search-result pat offset)))
+ ;; single `?' or `/' means repeat last pattern and finish
+ ((= 1 (length next-pat))
+ (evil-ex-search-goto-offset offset)
+ (setq new-dir (if (string= "/" next-pat) 'forward 'backward)
+ count (if (eq direction new-dir) 1 2)
+ pattern-string orig-pat
+ direction new-dir))
+ ;; next non-empty pattern, next search iteration
+ (t
+ (evil-ex-search-goto-offset offset)
+ (setq new-dir (if (= (aref next-pat 0) ?/) 'forward 'backward)
+ repeat-last (and (<= 2 (length next-pat))
+ (member (substring next-pat 0 2) '("//" "??")))
+ count (if (or (eq direction new-dir) (not repeat-last)) 1 2)
+ pattern-string (if repeat-last
+ (concat orig-pat (substring next-pat 1))
+ (substring next-pat 1))
+ direction new-dir))))))))
+
+(defun evil-ex-search-update-pattern (_beg _end _range)
+ "Update the current search pattern."
+ (save-match-data
+ (let ((pattern-string (minibuffer-contents)))
+ (with-current-buffer evil-ex-current-buffer
+ (with-selected-window (minibuffer-selected-window)
+ (goto-char (1+ evil-ex-search-start-point))
+ (condition-case err
+ (let* ((result (evil-ex-search-full-pattern pattern-string
+ (or evil-ex-search-count 1)
+ evil-ex-search-direction))
+ (success (pop result))
+ (pattern (pop result))
+ (offset (pop result)))
+ (cond
+ ((eq success 'wrap)
+ (evil-ex-search-update pattern offset
+ (match-beginning 0) (match-end 0)
+ "Wrapped"))
+ ((eq success 'empty-pattern)
+ (evil-ex-search-update nil nil nil nil nil))
+ (success
+ (evil-ex-search-update pattern offset
+ (match-beginning 0) (match-end 0)
+ nil))
+ (t
+ (evil-ex-search-update nil nil
+ nil nil
+ "search failed"))))
+ (invalid-regexp
+ (evil-ex-search-update nil nil nil nil (cadr err)))
+ (error
+ (evil-ex-search-update nil nil nil nil (format "%s" err)))))))))
+(put 'evil-ex-search-update-pattern 'permanent-local-hook t)
+
+(defun evil-ex-search-exit ()
+ "Exit interactive search, keeping lazy highlighting active."
+ (interactive)
+ (evil-ex-search-stop-session)
+ (exit-minibuffer))
+
+(defun evil-ex-search-abort ()
+ "Abort interactive search, disabling lazy highlighting."
+ (interactive)
+ (evil-ex-search-stop-session)
+ (evil-ex-delete-hl 'evil-ex-search)
+ (abort-recursive-edit))
+
+(defun evil-ex-search-goto-offset (offset)
+ "Move point according to search OFFSET and set `evil-this-type' accordingly.
+This function assumes that the current match data represents the
+current search result."
+ (unless (zerop (length offset))
+ (let ((beg (match-beginning 0))
+ (end (match-end 0)))
+ (save-match-data
+ (unless
+ (string-match
+ "^\\([esb]\\)?\\(\\([-+]\\)?\\([0-9]*\\)\\)$"
+ offset)
+ (user-error "Invalid search offset: %s" offset))
+ (let ((count (if (= (match-beginning 4) (match-end 4))
+ (cond
+ ((not (match-beginning 3)) 0)
+ ((= (aref offset (match-beginning 3)) ?+) +1)
+ (t -1))
+ (string-to-number (match-string 2 offset)))))
+ (cond
+ ((not (match-beginning 1))
+ (setq evil-this-type 'line)
+ (forward-line count))
+ ((= (aref offset (match-beginning 1)) ?e)
+ (goto-char (+ end count -1))
+ (setq evil-this-type 'inclusive))
+ ((memq (aref offset (match-beginning 1)) '(?s ?b))
+ (goto-char (+ beg count))
+ (setq evil-this-type 'inclusive))))))))
+
+(defun evil-ex-search-setup ()
+ "Hook to initialize the minibuffer for ex search."
+ (add-hook 'pre-command-hook #'evil-ex-remove-default))
+
+(defun evil-ex-start-search (direction count)
+ "Start a new search in a certain DIRECTION."
+ ;; store buffer and window where the search started
+ (let ((evil-ex-current-buffer (current-buffer)))
+ (setq evil-ex-search-count count
+ evil-ex-search-direction direction
+ evil-ex-search-start-point (point)
+ evil-ex-last-was-search t)
+ (progn
+ ;; ensure minibuffer is initialized accordingly
+ (add-hook 'minibuffer-setup-hook #'evil-ex-search-start-session)
+ ;; read the search string
+ (let* ((minibuffer-local-map evil-ex-search-keymap)
+ (search-string
+ (condition-case err
+ (minibuffer-with-setup-hook
+ #'evil-ex-search-setup
+ (read-string (if (eq evil-ex-search-direction 'forward)
+ "/" "?")
+ (and evil-ex-search-history
+ (propertize
+ (car evil-ex-search-history)
+ 'face 'shadow))
+ 'evil-ex-search-history))
+ (quit
+ (evil-ex-search-stop-session)
+ (evil-ex-delete-hl 'evil-ex-search)
+ (goto-char evil-ex-search-start-point)
+ (signal (car err) (cdr err))))))
+ ;; pattern entered successful
+ (goto-char (if (eq evil-ex-search-direction 'forward)
+ (1+ evil-ex-search-start-point)
+ (1- evil-ex-search-start-point)))
+ (let* ((result
+ (evil-ex-search-full-pattern search-string
+ evil-ex-search-count
+ evil-ex-search-direction))
+ (success (pop result))
+ (pattern (pop result))
+ (offset (pop result)))
+ (setq evil-ex-search-pattern pattern
+ evil-ex-search-offset offset)
+ (cond
+ ((memq success '(t wrap))
+ (goto-char (match-beginning 0))
+ (setq evil-ex-search-match-beg (match-beginning 0)
+ evil-ex-search-match-end (match-end 0))
+ (evil-ex-search-goto-offset offset)
+ (evil-push-search-history search-string (eq direction 'forward))
+ (when (and (not evil-ex-search-incremental) evil-ex-search-highlight-all)
+ (evil-ex-search-activate-highlight pattern))
+ (when (and evil-ex-search-incremental (not evil-ex-search-persistent-highlight))
+ (evil-ex-delete-hl 'evil-ex-search)))
+ (t
+ (goto-char evil-ex-search-start-point)
+ (evil-ex-delete-hl 'evil-ex-search)
+ (signal 'search-failed (list search-string)))))))))
+
+(defun evil-ex-start-word-search (unbounded direction count &optional symbol)
+ "Search for the symbol under point.
+The search matches the COUNT-th occurrence of the word. If the
+UNBOUNDED argument is nil, the search matches only at symbol
+boundaries, otherwise it matches anywhere. The DIRECTION
+argument should be either `forward' or `backward', determining
+the search direction. If SYMBOL is non-nil then the functions
+searches for the symbol at point, otherwise for the word at
+point."
+ (let ((string (evil-find-thing (eq direction 'forward)
+ (if symbol 'symbol 'word))))
+ (if (null string)
+ (user-error "No word under point")
+ (let ((regex (if unbounded
+ (regexp-quote string)
+ (format (if symbol "\\_<%s\\_>" "\\<%s\\>")
+ (regexp-quote string)))))
+ (setq evil-ex-search-count count
+ evil-ex-search-direction direction
+ evil-ex-search-pattern
+ (let (evil-ex-search-vim-style-regexp)
+ (evil-ex-make-search-pattern regex))
+ evil-ex-search-offset nil
+ evil-ex-last-was-search t)
+ ;; update search history unless this pattern equals the
+ ;; previous pattern
+ (unless (equal (car-safe evil-ex-search-history) regex)
+ (push regex evil-ex-search-history))
+ (evil-push-search-history regex (eq direction 'forward)))
+ (evil-ex-delete-hl 'evil-ex-search)
+ (when (fboundp 'evil-ex-search-next)
+ (evil-ex-search-next count)))))
+
+;; substitute
+(evil-ex-define-argument-type substitution
+ "A substitution pattern argument /pattern/replacement/flags.
+This handler highlights the pattern of the current substitution."
+ :runner
+ (lambda (flag &optional arg)
+ (with-selected-window (minibuffer-selected-window)
+ (with-current-buffer evil-ex-current-buffer
+ (cond
+ ((eq flag 'start)
+ (evil-ex-make-hl
+ 'evil-ex-substitute
+ :face 'evil-ex-substitute-matches
+ :update-hook #'evil-ex-pattern-update-ex-info
+ :match-hook (and evil-ex-substitute-interactive-replace
+ #'evil-ex-pattern-update-replacement))
+ (setq flag 'update))
+
+ ((eq flag 'stop)
+ (evil-ex-delete-hl 'evil-ex-substitute))))
+
+ (when (and (eq flag 'update)
+ evil-ex-substitute-highlight-all
+ (not (zerop (length arg))))
+ (condition-case lossage
+ (let* ((result (evil-ex-get-substitute-info arg t))
+ (pattern (pop result))
+ (replacement (pop result))
+ (range (or (evil-copy-range evil-ex-range)
+ (evil-range (line-beginning-position)
+ (line-end-position)
+ 'line
+ :expanded t))))
+ (setq evil-ex-substitute-current-replacement replacement)
+ (evil-expand-range range)
+ (evil-ex-hl-set-region 'evil-ex-substitute
+ (evil-range-beginning range)
+ (evil-range-end range))
+ (evil-ex-hl-change 'evil-ex-substitute pattern))
+ (end-of-file
+ (evil-ex-pattern-update-ex-info nil
+ "incomplete replacement"))
+ (user-error
+ (evil-ex-pattern-update-ex-info nil
+ (format "%s" lossage))))))))
+
+(defun evil-ex-pattern-update-ex-info (_hl result)
+ "Update the Ex info string."
+ (when (stringp result)
+ (evil-ex-echo "%s" result)))
+
+(defun evil-ex-pattern-update-replacement (_hl overlay)
+ "Update the replacement display."
+ (when (fboundp 'match-substitute-replacement)
+ (let ((fixedcase (not case-replace))
+ repl)
+ (setq repl (if evil-ex-substitute-current-replacement
+ (evil-match-substitute-replacement
+ evil-ex-substitute-current-replacement
+ fixedcase)
+ ""))
+ (put-text-property 0 (length repl)
+ 'face 'evil-ex-substitute-replacement
+ repl)
+ (overlay-put overlay 'after-string repl))))
+
+(defun evil-ex-parse-global (string)
+ "Parse STRING as a global argument."
+ (let* ((pattern (nth 0 (evil-delimited-arguments string 2)))
+ (command (and pattern
+ (>= (- (length string) (length pattern)) 2)
+ (substring string (+ (length pattern) 2)))))
+ ;; use last pattern if none given
+ (when (zerop (length pattern))
+ (setq pattern
+ (cond
+ ((and (eq evil-search-module 'evil-search) evil-ex-search-pattern)
+ (evil-ex-pattern-regex evil-ex-search-pattern))
+ ((and (eq evil-search-module 'isearch) (not (zerop (length isearch-string))))
+ isearch-string)
+ (t (user-error "No previous pattern")))))
+ (list pattern command)))
+
+(defun evil-ex-get-substitute-info (string &optional implicit-r)
+ "Returns the substitution info of command line STRING.
+This function returns a three-element list \(PATTERN REPLACEMENT
+FLAGS) consisting of the substitution parts of STRING. PATTERN is
+a ex-pattern (see `evil-ex-make-pattern') and REPLACEMENT in a
+compiled replacement expression (see `evil-compile-replacement').
+The information returned is the actual substitution information
+w.r.t. to special situations like empty patterns or repetition of
+previous substitution commands. If IMPLICIT-R is non-nil, then
+the flag 'r' is assumed, i.e. in the case of an empty pattern the
+last search pattern is used. This will be used when called from
+a :substitute command with arguments."
+ (let (pattern replacement flags)
+ (cond
+ ((or (null string) (string-match "^[a-zA-Z]" string))
+ ;; starts with letter so there is no pattern because the
+ ;; separator must not be a letter repeat last substitute
+ (setq replacement evil-ex-substitute-replacement)
+ ;; flags are everything that is not a white space
+ (when (and string (string-match "[^[:space:]]+" string))
+ (setq flags (match-string 0 string))))
+ (t
+ (let ((args (evil-delimited-arguments string 3)))
+ (setq pattern (pop args)
+ replacement (pop args)
+ flags (pop args))
+ ;; if replacment equals "~" use previous replacement
+ (if (equal replacement "~")
+ (setq replacement evil-ex-substitute-replacement)
+ (setq replacement (evil-compile-replacement replacement)))
+ ;; append implicit "r" flag if required
+ (when (and implicit-r (not (memq ?r (append flags nil))))
+ (setq flags (concat flags "r"))))))
+ ;; if flags equals "&" add previous flags
+ (if (and (not (zerop (length flags)))
+ (= (aref flags 0) ?&))
+ (setq flags (append (substring flags 1)
+ evil-ex-substitute-flags))
+ (setq flags (append flags nil)))
+ ;; if no pattern, use previous pattern, either search or
+ ;; substitute pattern depending on `evil-ex-last-was-search' and
+ ;; the r flag
+ (when (zerop (length pattern))
+ (setq pattern
+ (if (eq evil-search-module 'evil-search)
+ (if (and evil-ex-last-was-search (memq ?r flags))
+ (and evil-ex-search-pattern
+ (evil-ex-pattern-regex evil-ex-search-pattern))
+ (and evil-ex-substitute-pattern
+ (evil-ex-pattern-regex evil-ex-substitute-pattern)))
+ (if (eq case-fold-search t)
+ isearch-string
+ (concat isearch-string "\\C")))
+ flags (remq ?r flags)))
+ ;; generate pattern
+ (when pattern
+ (setq pattern (evil-ex-make-substitute-pattern pattern flags)))
+ (list pattern replacement flags)))
+
+(defun evil-ex-nohighlight ()
+ "Disable the active search highlightings."
+ (interactive)
+ (evil-ex-delete-hl 'evil-ex-substitute)
+ (evil-ex-delete-hl 'evil-ex-search))
+
+(provide 'evil-search)
+
+;;; evil-search.el ends here