visual-regexp.el 43.7 KB
Newer Older
1
;;; visual-regexp.el --- A regexp/replace command for Emacs with interactive visual feedback
Marko Bencun's avatar
Marko Bencun committed
2

3
;; Copyright (C) 2013-2016 Marko Bencun
Marko Bencun's avatar
Marko Bencun committed
4

5 6
;; Author: Marko Bencun <mbencun@gmail.com>
;; URL: https://github.com/benma/visual-regexp.el/
7
;; Version: 1.0
8
;; Package-Requires: ((cl-lib "0.2"))
9
;; Keywords: regexp, replace, visual, feedback
Marko Bencun's avatar
Marko Bencun committed
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26

;; This file is part of visual-regexp.

;; visual-regexp 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.

;; visual-regexp 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 visual-regexp.  If not, see <http://www.gnu.org/licenses/>.

;;; WHAT'S NEW
27 28 29 30 31
;; 1.0: Add support for one prompt for search/replace, using query-replace-from-to-separator
;;      (query-replace history like in Emacs 25).
;;      Breaking changes:
;;       - vr/minibuffer-(regexp|replace)-keymap have been collapsed to vr/minibuffer-keymap
;;       - vr/minibuffer-help-(regexp|replace) have been replaced by vr--minibuffer-help-text
32
;; 0.9: Fix warnings regarding free variables.
33
;; 0.8: Error handling for vr--get-regexp-string. Bug-fixes regarding error display.
34
;; 0.7: Customizable separator (arrow) string and face.
Marko Bencun's avatar
Marko Bencun committed
35
;; 0.6: distinguish prompts in vr/replace, vr/query-replace, vr/mc-mark.
36
;; 0.5: emulate case-conversion of replace-regexp.
Marko Bencun's avatar
Marko Bencun committed
37
;; 0.4: vr/mc-mark: interface to multiple-cursors.
38
;; 0.3: use the same history as the regular Emacs replace commands;
Marko Bencun's avatar
Marko Bencun committed
39
;; 0.2: support for lisp expressions in the replace string, same as in (query-)replace-regexp
Marko Bencun's avatar
Marko Bencun committed
40 41
;; 0.1: initial release

Marko Bencun's avatar
Marko Bencun committed
42 43 44 45
;;; Tip Jar
;; If you found this useful, please consider donating.
;; BTC: 1BxauiLGMQPb2pavkkQkuFe5CgrGMrUat2

Marko Bencun's avatar
Marko Bencun committed
46 47 48
;;; What's This?

;; visual-regexp for Emacs is like `replace-regexp`, but with live  visual feedback directly in the buffer.
Marko Bencun's avatar
Marko Bencun committed
49 50
;; While constructing the regexp in the minibuffer, you get live visual feedback for the matches, including group matches.
;; While constructing the replacement in the minibuffer, you get live visual feedback for the replacements.
Marko Bencun's avatar
Marko Bencun committed
51 52
;; It can be used to replace all matches in one go (like `replace-regexp`), or a decision can be made on each match (like `query-replace-regexp`).
;; Thanks to Detlev Zundel for his re-builder.
Marko Bencun's avatar
Marko Bencun committed
53

Marko Bencun's avatar
Marko Bencun committed
54
;;; Where does visual-regexp come from?
Marko Bencun's avatar
Marko Bencun committed
55 56 57 58 59
;;
;; I was not happy with the way I used emacs' replace-regexp before. Constructing the regular expression is error prone and emacs' regular expressions are limited
;; (for example, no lookaheads, named groups, etc.).
;; Using re-builder to interactively build regular expressions was a step into the right direction, but manually copying over the regexp
;; to the minibuffer is cumbersome.
Marko Bencun's avatar
Marko Bencun committed
60 61
;; Using the idea of interactive feedback of re-builder, this package makes it possible to use just the minibuffer to construct (with live visual feedback) the regexp and replacement,
;; using Emacs style regular expressions, or optionally, regular expressions powered by other (mode modern) engines, for the replacement. For the latter part, see the package visual-regexp-steroids.
Marko Bencun's avatar
Marko Bencun committed
62 63

;;; Installation
Marko Bencun's avatar
Marko Bencun committed
64

benma's avatar
benma committed
65
;; If you are using Emacs 24, you can get visual-regexp from [melpa](https://melpa.org/) with the package manager.
Marko Bencun's avatar
Marko Bencun committed
66
;; Add the following code to your init file. Of course you can select your own key bindings.
Marko Bencun's avatar
Marko Bencun committed
67
;; ----------------------------------------------------------
Marko Bencun's avatar
Marko Bencun committed
68
;; (add-to-list 'load-path "folder-in-which-visual-regexp-files-are-in/") ;; if the files are not already in the load path
Marko Bencun's avatar
Marko Bencun committed
69 70 71
;; (require 'visual-regexp)
;; (define-key global-map (kbd "C-c r") 'vr/replace)
;; (define-key global-map (kbd "C-c q") 'vr/query-replace)
Marko Bencun's avatar
Marko Bencun committed
72 73
;; ;; if you use multiple-cursors, this is for you:
;; (define-key global-map (kbd "C-c m") 'vr/mc-mark)
Marko Bencun's avatar
Marko Bencun committed
74
;; ----------------------------------------------------------
Steve Purcell's avatar
Steve Purcell committed
75
;; To customize, use `M-x customize-group [RET] visual-regexp`.
Marko Bencun's avatar
Marko Bencun committed
76

77
;;; Code:
Marko Bencun's avatar
Marko Bencun committed
78 79 80
(unless (fboundp 'make-overlay)
  (require 'overlay))

81
;; cl is used for the (loop ...) macro
Marko Bencun's avatar
Marko Bencun committed
82
(require 'cl-lib)
Marko Bencun's avatar
Marko Bencun committed
83 84 85

;;; faces

86 87 88 89 90 91 92 93 94 95 96 97 98 99
(defcustom vr/match-separator-use-custom-face nil
  "If activated, vr/match-separator-face is used to display the separator. Otherwise, use the same face as the current match."
  :type 'boolean
  :group 'visual-regexp)

(defface vr/match-separator-face
  '((((class color))
     :foreground "red"
     :bold t)
    (t
     :inverse-video t))
  "Face for the arrow between match and replacement. To use this, you must activate vr/match-separator-use-custom-face"
  :group 'visual-regexp)

100 101 102 103 104 105 106 107 108 109 110 111
;; For Emacs < 25.0, this variable is not yet defined.
;; Copy pasted from Emacs 25.0 replace.el.
(unless (boundp 'query-replace-from-to-separator)
  (defcustom query-replace-from-to-separator
    (propertize (if (char-displayable-p ?) " → " " -> ")
		'face 'minibuffer-prompt)
    "String that separates FROM and TO in the history of replacement pairs."
    ;; Avoids error when attempt to autoload char-displayable-p fails
    ;; while preparing to dump, also stops customize-rogue listing this.
    :initialize 'custom-initialize-delay
    :type 'sexp))

112 113 114 115
(defcustom vr/match-separator-string
  (progn
    (custom-reevaluate-setting 'query-replace-from-to-separator)
    (substring-no-properties query-replace-from-to-separator))
116
  "This string is used to separate a match from the replacement during feedback."
117 118
  :type 'sexp
  :initialize 'custom-initialize-delay
119 120
  :group 'visual-regexp)

Marko Bencun's avatar
Marko Bencun committed
121 122 123 124 125 126 127 128 129 130 131 132
(defface vr/match-0
  '((((class color) (background light))
     :background "lightblue")
    (((class color) (background dark))
     :background "steelblue4")
    (t
     :inverse-video t))
  "First face for displaying a whole match."
  :group 'visual-regexp)

(defface vr/match-1
  '((((class color) (background light))
133
     :background "pale turquoise")
Marko Bencun's avatar
Marko Bencun committed
134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180
    (((class color) (background dark))
     :background "dodgerblue4")
    (t
     :inverse-video t))
  "Second face for displaying a whole match."
  :group 'visual-regexp)

(defface vr/group-0
  '((((class color) (background light))
     :background "aquamarine")
    (((class color) (background dark))
     :background "blue3")
    (t
     :inverse-video t))
  "First face for displaying a matching group."
  :group 'visual-regexp)

(defface vr/group-1
  '((((class color) (background light))
     :background "springgreen")
    (((class color) (background dark))
     :background "chartreuse4")
    (t
     :inverse-video t))
  "Second face for displaying a matching group."
  :group 'visual-regexp)

(defface vr/group-2
  '((((min-colors 88) (class color) (background light))
     :background "yellow1")
    (((class color) (background light))
     :background "yellow")
    (((class color) (background dark))
     :background "sienna4")
    (t
     :inverse-video t))
  "Third face for displaying a matching group."
  :group 'visual-regexp)

;;; variables

(defcustom vr/auto-show-help t
  "Show help message automatically when the minibuffer is entered."
  :type 'boolean
  :group 'visual-regexp)

(defcustom vr/default-feedback-limit 50
Steve Purcell's avatar
Steve Purcell committed
181
  "Limit number of matches shown in visual feedback.
Marko Bencun's avatar
Marko Bencun committed
182 183 184 185 186 187 188 189 190
If nil, don't limit the number of matches shown in visual feedback."
  :type 'integer
  :group 'visual-regexp)

(defcustom vr/default-replace-preview nil
  "Preview of replacement activated by default? If activated, the original is not shown alongside the replacement."
  :type 'boolean
  :group 'visual-regexp)

191 192 193 194 195 196 197 198 199 200
(defcustom vr/query-replace-from-history-variable query-replace-from-history-variable
  "History list to use for the FROM argument. The default is to use the same history as Emacs' query-replace commands."
  :type 'symbol
  :group 'visual-regexp)

(defcustom vr/query-replace-to-history-variable query-replace-to-history-variable
  "History list to use for the TO argument. The default is to use the same history as Emacs' query-replace commands."
  :type 'symbol
  :group 'visual-regexp)

201 202 203 204 205 206 207 208 209
(setq vr--is-emacs24 (version< emacs-version "25"))

(defvar vr--query-replace-defaults nil
  "Same as query-replace-defaults from Emacs 25, for compatibility with Emacs 24.")

(defcustom vr/query-replace-defaults-variable
  (if vr--is-emacs24
      'vr--query-replace-defaults
    'query-replace-defaults)
210 211 212 213
  "History of search/replace pairs"
  :type 'symbol
  :group 'visual-regexp)

214 215
(defvar vr/initialize-hook nil
  "Hook called before vr/replace and vr/query-replace")
Marko Bencun's avatar
Marko Bencun committed
216 217 218 219 220 221 222 223 224 225 226 227 228 229 230

;;; private variables

(defconst vr--match-faces '(vr/match-0 vr/match-1)
  "Faces in list for convenience")

(defconst vr--group-faces '(vr/group-0 vr/group-1 vr/group-2)
  "Faces in list for convenience")

(defconst vr--overlay-priority 1001
  "Starting priority of visual-regexp overlays.")

(defvar vr--in-minibuffer nil
  "Is visual-regexp currently being used?")

231 232 233
(defvar vr--calling-func nil
  "Which function invoked vr--interactive-get-args?")

Marko Bencun's avatar
Marko Bencun committed
234 235 236 237 238 239 240 241 242
(defvar vr--last-minibuffer-contents nil
  "Keeping track of minibuffer changes")

(defvar vr--target-buffer-start nil
  "Starting position in target buffer.")

(defvar vr--target-buffer-end nil
  "Ending position in target buffer.")

243 244
(defvar vr--limit-reached)

Marko Bencun's avatar
Marko Bencun committed
245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265
(defvar vr--regexp-string nil
  "Entered regexp.")

(defvar vr--replace-string nil
  "Entered replacement.")

(defvar vr--feedback-limit nil
  "Feedback limit currently in use.")

(defvar vr--replace-preview nil
  "Preview of replacement activated?")

(defvar vr--target-buffer nil
  "Buffer to which visual-regexp is applied to.")

(defvar vr--overlays (make-hash-table :test 'equal)
  "Overlays used in target buffer.")

(defvar vr--visible-overlays (list)
  "Overlays currently visible.")

266 267
(defvar vr--minibuffer-message-overlay nil)

Marko Bencun's avatar
Marko Bencun committed
268 269
;;; keymap

270
(defvar vr/minibuffer-keymap
Marko Bencun's avatar
Marko Bencun committed
271
  (let ((map (copy-keymap minibuffer-local-map)))
272 273 274
    (define-key map (kbd "C-c ?") 'vr--minibuffer-help)
    (define-key map (kbd "C-c a") 'vr--shortcut-toggle-limit)
    (define-key map (kbd "C-c p") 'vr--shortcut-toggle-preview)
Marko Bencun's avatar
Marko Bencun committed
275 276 277 278 279
    map)
  "Keymap used while using visual-regexp,")

;;; helper functions

280 281
(defun vr--shortcut-toggle-preview ()
  (interactive)
282 283 284 285
  (when (vr--in-replace)
    (setq vr--replace-preview (not vr--replace-preview))
    (vr--update-minibuffer-prompt)
    (vr--do-replace-feedback)))
286 287

(defun vr--shortcut-toggle-limit ()
Marko Bencun's avatar
Marko Bencun committed
288 289 290 291 292
  "Toggle the limit of overlays shown (default limit / no limit)"
  (interactive)
  (if vr--feedback-limit
      (setq vr--feedback-limit nil)
    (setq vr--feedback-limit vr/default-feedback-limit))
293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313
  (vr--show-feedback))

(defun vr--get-regexp-string-full ()
  (if (equal vr--in-minibuffer 'vr--minibuffer-regexp)
      (minibuffer-contents)
    vr--regexp-string))

(defun vr--query-replace--split-string (string)
  "Copy/paste of query-replace--split-string, removing the assertion."
  (let* ((length (length string))
         (split-pos (text-property-any 0 length 'separator t string)))
    (if (not split-pos)
        (substring-no-properties string)
      (cons (substring-no-properties string 0 split-pos)
            (substring-no-properties string (1+ split-pos) length)))))

(defun vr--in-from ()
  "Returns t if the we are in the regexp prompt. Returns nil if we are in the replace prompt. Call only if (and vr--in-minibuffer (minibufferp))"
  (equal vr--in-minibuffer 'vr--minibuffer-regexp))

(defun vr--in-replace ()
314
  "Returns t if we are either in the replace prompt, or in the regexp prompt containing a replacement (separated by query-replace-from-to-separator)"
315 316
  (or (not (vr--in-from))
      (consp (vr--query-replace--split-string (vr--get-regexp-string-full)))))
Marko Bencun's avatar
Marko Bencun committed
317

318
(defun vr--get-regexp-string (&optional for-display)
319 320 321 322 323 324 325 326
  (let ((split (vr--query-replace--split-string (vr--get-regexp-string-full))))
    (if (consp split) (car split) split)))

(defun vr--get-replace-string ()
  (if (equal vr--in-minibuffer 'vr--minibuffer-replace)
      (minibuffer-contents-no-properties)
    (let ((split (vr--query-replace--split-string (vr--get-regexp-string-full))))
      (if (consp split) (cdr split) vr--replace-string))))
Marko Bencun's avatar
Marko Bencun committed
327

328 329 330 331
(defun vr--format-error (err)
  (if (eq (car err) 'error)
      (car (cdr err))
    (format "%s" err)))
Marko Bencun's avatar
Marko Bencun committed
332 333 334

;;; minibuffer functions

335 336 337 338 339 340 341 342 343 344 345 346 347
(defun vr--set-minibuffer-prompt ()
  (let ((prompt (cond ((equal vr--calling-func 'vr--calling-func-query-replace)
                       "Query replace")
                      ((equal vr--calling-func 'vr--calling-func-mc-mark)
                       "Mark")
                      (t
                       "Replace"))))
    (when (and (vr--in-replace) vr--replace-preview)
      (setq prompt (concat prompt " (preview)")))
    (when (not (vr--in-from))
      (setq prompt (concat prompt " " (vr--get-regexp-string t))))
    (setq prompt (concat prompt (if (vr--in-from) ": " " with: ")))
    prompt))
348

Marko Bencun's avatar
Marko Bencun committed
349 350
(defun vr--update-minibuffer-prompt ()
  (when (and vr--in-minibuffer (minibufferp))
351 352 353
    (let ((inhibit-read-only t)
          (prompt (vr--set-minibuffer-prompt)))
      (put-text-property (point-min) (minibuffer-prompt-end) 'display prompt))))
Marko Bencun's avatar
Marko Bencun committed
354 355


356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377
(defun vr--minibuffer-message (message &rest args)
  "Adaptation of minibuffer-message that does not use sit-for
to make the message disappear. The problem with this was that during sit-for,
 the cursor was shown at the beginning of the message regardless of whether
 the point was actually there or not. Workaround: we let the message stay
visible all the time in the minibuffer."
  (if (not (and vr--in-minibuffer (minibufferp (current-buffer))))
      ;; fallback
      (apply 'minibuffer-message message args)
    ;; Clear out any old echo-area message to make way for our new thing.
    (message nil)
    (setq message (concat " [" message "]"))
    (when args (setq message (apply 'format message args)))
    (unless (zerop (length message))
      ;; The current C cursor code doesn't know to use the overlay's
      ;; marker's stickiness to figure out whether to place the cursor
      ;; before or after the string, so let's spoon-feed it the pos.
      (put-text-property 0 1 'cursor t message))
    (unless (overlayp vr--minibuffer-message-overlay)
      (setq vr--minibuffer-message-overlay (make-overlay (point-max) (point-max) nil t t)))
    (move-overlay vr--minibuffer-message-overlay (point-max) (point-max))
    (overlay-put vr--minibuffer-message-overlay 'after-string message)))
378

379 380 381 382 383 384 385
(defun vr--minibuffer-help-text ()
  (let ((help ""))
    (setq help (concat help (substitute-command-keys "\\<vr/minibuffer-keymap>\\[vr--minibuffer-help]: help, \\[vr--shortcut-toggle-limit]: toggle show all")))
    (when (vr--in-replace)
      (setq help (concat help (substitute-command-keys ", \\[vr--shortcut-toggle-preview]: toggle preview"))))
    help
    ))
386

Marko Bencun's avatar
Marko Bencun committed
387
(defun vr--minibuffer-help ()
Marko Bencun's avatar
Marko Bencun committed
388
  (interactive)
389
  (vr--minibuffer-message (vr--minibuffer-help-text)))
Marko Bencun's avatar
Marko Bencun committed
390 391 392 393 394 395 396 397

;;; overlay functions

(defun vr--get-overlay (i j)
  "i: match index, j: submatch index"
  (let (overlay)
    (setq overlay (gethash (list i j) vr--overlays))
    (unless overlay ;; create new one if overlay does not exist yet
398 399
      (setq overlay (make-overlay 0 0))
      (if (= 0 j)
Steve Purcell's avatar
Steve Purcell committed
400 401
          (overlay-put overlay 'face (nth (mod i (length vr--match-faces)) vr--match-faces))
        (overlay-put overlay 'face (nth (mod j (length vr--group-faces)) vr--group-faces)))
402 403 404
      (overlay-put overlay 'priority (+ vr--overlay-priority (if (= j 0) 0 1)))
      (overlay-put overlay 'vr-ij (list i j))
      (puthash (list i j) overlay vr--overlays))
Marko Bencun's avatar
Marko Bencun committed
405 406 407 408 409
    overlay))

(defun vr--delete-overlays ()
  "Delete all visible overlays."
  (mapc (lambda (overlay)
410 411
          (delete-overlay overlay))
        vr--visible-overlays)
Marko Bencun's avatar
Marko Bencun committed
412 413
  (setq vr--visible-overlays (list)))

414 415 416 417 418
(defun vr--delete-overlay-display (overlay)
  (overlay-put overlay 'display nil)
  (overlay-put overlay 'after-string nil)
  (overlay-put overlay 'priority vr--overlay-priority))

Marko Bencun's avatar
Marko Bencun committed
419 420 421
(defun vr--delete-overlay-displays ()
  "Delete the display of all visible overlays. Call before vr--delete-overlays."
  (mapc (lambda (overlay)
422
          (cl-multiple-value-bind (i j) (overlay-get overlay 'vr-ij)
Steve Purcell's avatar
Steve Purcell committed
423 424 425
            (when (= 0 j)
              (vr--delete-overlay-display overlay))))
        vr--visible-overlays))
Marko Bencun's avatar
Marko Bencun committed
426 427 428

;;; hooks

429 430 431 432 433 434
(defun vr--show-feedback ()
  (if (vr--in-replace)
      (vr--do-replace-feedback)
    (vr--feedback)))

(defun vr--after-change (beg end len)
Marko Bencun's avatar
Marko Bencun committed
435 436 437 438
  (when (and vr--in-minibuffer (minibufferp))
    ;; minibuffer-up temporarily deletes minibuffer contents before inserting new one.
    ;; don't do anything then as the messages shown by visual-regexp are irritating while browsing the history.
    (unless (and (string= "" (minibuffer-contents-no-properties))
Steve Purcell's avatar
Steve Purcell committed
439
                 (equal last-command 'previous-history-element))
Marko Bencun's avatar
Marko Bencun committed
440 441
      ;; do something when minibuffer contents changes
      (unless (string= vr--last-minibuffer-contents (minibuffer-contents-no-properties))
Steve Purcell's avatar
Steve Purcell committed
442 443 444 445
        (setq vr--last-minibuffer-contents (minibuffer-contents-no-properties))
        ;; minibuffer contents has changed, update visual feedback.
        ;; not using after-change-hook because this hook applies to the whole minibuffer, including minibuffer-messages
        ;; that disappear after a while.
446 447 448
        (vr--update-minibuffer-prompt)
        (vr--show-feedback)))))
(add-hook 'after-change-functions 'vr--after-change)
Marko Bencun's avatar
Marko Bencun committed
449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468

(defun vr--minibuffer-setup ()
  "Setup prompt and help when entering minibuffer."
  (when vr--in-minibuffer
    (progn
      (vr--update-minibuffer-prompt)
      (when vr/auto-show-help (vr--minibuffer-help)))))
(add-hook 'minibuffer-setup-hook 'vr--minibuffer-setup)

;;; helper functions

(defun vr--target-window ()
  (if vr--target-buffer
      (get-buffer-window vr--target-buffer)
    nil))

(defun vr--compose-messages (&rest msgs)
  (mapconcat 'identity (delq nil (mapcar (lambda (msg) (if (or (not msg) (string= "" msg)) nil msg)) msgs)) " - "))

;;; show feedback functions
469
(defun vr--feedback-function (regexp-string forward feedback-limit callback)
Marko Bencun's avatar
Marko Bencun committed
470
  "Feedback function for emacs-style regexp search"
471
  (let ((message-line "")
Steve Purcell's avatar
Steve Purcell committed
472
        (err))
Marko Bencun's avatar
Marko Bencun committed
473 474
    (with-current-buffer vr--target-buffer
      (save-excursion
Steve Purcell's avatar
Steve Purcell committed
475 476 477 478 479 480 481 482
        (goto-char (if forward vr--target-buffer-start vr--target-buffer-end))
        (let ((i 0)
              (looping t))
          (while (and looping
                      (condition-case err
                          (if forward
                              (re-search-forward regexp-string vr--target-buffer-end t)
                            (re-search-backward regexp-string vr--target-buffer-start t))
483
                        (invalid-regexp (progn (setq message-line (car (cdr err))) nil))))
Steve Purcell's avatar
Steve Purcell committed
484
            (when (or (not feedback-limit) (< i feedback-limit)) ;; let outer loop finish so we can get the matches count
485
              (cl-loop for (start end) on (match-data) by 'cddr
486 487 488
                       for j from 0
                       when (and start end)
                       do
489
                       (funcall callback i j start end)))
Steve Purcell's avatar
Steve Purcell committed
490 491 492 493 494 495
            (when (= (match-beginning 0) (match-end 0))
              (cond ;; don't get stuck on zero-width matches
               ((and forward (> vr--target-buffer-end (point))) (forward-char))
               ((and (not forward) (< vr--target-buffer-start (point))) (backward-char))
               (t (setq looping nil))))
            (setq i (1+ i)))
496
          (if (string= "" message-line)
497
              (setq message-line (format "%s matches" i))))))
Marko Bencun's avatar
Marko Bencun committed
498 499
    message-line))

500
(defun vr--feedback-match-callback (i j begin end)
Marko Bencun's avatar
Marko Bencun committed
501
  (with-current-buffer vr--target-buffer
502
    (save-excursion
503
      (when (= 0 i) ;; make first match visible
504 505
        (with-selected-window (vr--target-window)
          (goto-char end)))
506
      (let ((overlay (vr--get-overlay i j)))
507 508 509 510 511
        (move-overlay overlay begin end vr--target-buffer)
        (if (and (= 0 j) (= begin end)) ;; empty match; indicate by a pipe
            (overlay-put overlay 'after-string (propertize "|" 'face (nth (mod i (length vr--match-faces)) vr--match-faces) 'help-echo "empty match"))
          (overlay-put overlay 'after-string nil))
        (setq vr--visible-overlays (cons overlay vr--visible-overlays)))
512 513
      ;; mark if we have reached the specified feedback limit
      (when (and vr--feedback-limit (= vr--feedback-limit (1+ i)) )
514
        (setq vr--limit-reached t)))))
Marko Bencun's avatar
Marko Bencun committed
515

516
(defun vr--feedback (&optional inhibit-message)
Marko Bencun's avatar
Marko Bencun committed
517 518
  "Show visual feedback for matches."
  (vr--delete-overlays)
519 520
  (setq vr--limit-reached nil)
  (let (message-line)
521
    (setq message-line
522 523 524 525
          (condition-case err
              (progn
                (vr--feedback-function (vr--get-regexp-string) t vr--feedback-limit 'vr--feedback-match-callback))
            (error (vr--format-error err))))
Marko Bencun's avatar
Marko Bencun committed
526
    (unless inhibit-message
527
      (let ((msg (vr--compose-messages message-line (when vr--limit-reached (format "%s matches shown, hit C-c a to show all" vr--feedback-limit)))))
Steve Purcell's avatar
Steve Purcell committed
528 529
        (unless (string= "" msg)
          (vr--minibuffer-message msg))))))
Marko Bencun's avatar
Marko Bencun committed
530

531 532
(defun vr--get-replacement (replacement match-data i)
  (with-current-buffer vr--target-buffer
533
    (let*
534 535
        ;; emulate case-conversion of (perform-replace)
        ((case-fold-search (if (and case-fold-search search-upper-case)
536
                               (ignore-errors (isearch-no-upper-case-p (vr--get-regexp-string) t))
537 538
                             case-fold-search))
         (nocasify (not (and case-replace case-fold-search))))
539 540 541
      ;; we need to set the match data again, s.t. match-substitute-replacement works correctly. 
      ;; (match-data) could have been modified in the meantime, e.g. by vr--get-regexp-string->pcre-to-elisp.
      (set-match-data match-data)
542
      (if (stringp replacement)
543 544
          (match-substitute-replacement replacement nocasify)
        (match-substitute-replacement (funcall (car replacement) (cdr replacement) i) nocasify)))))
545 546 547

(defun vr--do-replace-feedback-match-callback (replacement match-data i)
  (let ((begin (cl-first match-data))
548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566
        (end (cl-second match-data))
        (replacement (vr--get-replacement replacement match-data i)))
    (let* ((overlay (vr--get-overlay i 0))
           (empty-match (= begin end)))
      (move-overlay overlay begin end vr--target-buffer)
      (vr--delete-overlay-display overlay)
      (let ((current-face (nth (mod i (length vr--match-faces)) vr--match-faces)))
        (if (or empty-match vr--replace-preview)
            (progn
              (overlay-put overlay (if empty-match 'after-string 'display) (propertize replacement 'face current-face))
              (overlay-put overlay 'priority (+ vr--overlay-priority 2)))
          (progn
            (overlay-put overlay 'after-string
                         (concat (propertize vr/match-separator-string 'face
                                             (if vr/match-separator-use-custom-face
                                                 'vr/match-separator-face
                                               current-face))
                                 (propertize replacement 'face current-face)))
            (overlay-put overlay 'priority (+ vr--overlay-priority 0))))))))
567

568
(defun vr--get-replacements (feedback feedback-limit)
Marko Bencun's avatar
Marko Bencun committed
569
  "Get replacements using emacs-style regexp."
570
  (setq vr--limit-reached nil)
571
  (let ((regexp-string)
572
        (replace-string (vr--get-replace-string))
Steve Purcell's avatar
Steve Purcell committed
573
        (message-line "")
574
        (i 0)
Steve Purcell's avatar
Steve Purcell committed
575 576 577 578
        (replacements (list))
        (err)
        (buffer-contents (with-current-buffer vr--target-buffer
                           (buffer-substring-no-properties (point-min) (point-max)))))
579 580

    (condition-case err
581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599
        (progn
          ;; can signal invalid-regexp
          (setq regexp-string (vr--get-regexp-string))

          (with-current-buffer vr--target-buffer
            (goto-char vr--target-buffer-start)
            (let ((looping t))
              (while (and
                      looping
                      (condition-case err
                          (re-search-forward regexp-string vr--target-buffer-end t)
                        ('invalid-regexp (progn (setq message-line (car (cdr err))) nil))))
                (condition-case err
                    (progn
                      (if (or (not feedback) (not feedback-limit) (< i feedback-limit))
                          (setq replacements (cons
                                              (let ((match-data (mapcar 'marker-position (match-data))))
                                                (list (query-replace-compile-replacement replace-string t) match-data i))
                                              replacements))
600
                        (setq vr--limit-reached t))
601 602 603 604 605 606 607 608 609
                      (when (= (match-beginning 0) (match-end 0))
                        (if (> vr--target-buffer-end (point))
                            (forward-char) ;; don't get stuck on zero-width matches
                          (setq looping nil)))
                      (setq i (1+ i)))
                  ('error (progn
                            (setq message-line (vr--format-error err))
                            (setq replacements (list))
                            (setq looping nil))))))))
610
      (invalid-regexp (setq message-line (car (cdr err))))
611
      (error (setq message-line (vr--format-error err))))
612
    (if feedback
613
        (if (string= "" message-line)
614
            (setq message-line (vr--compose-messages (format "%s matches" i) (when vr--limit-reached (format "%s matches shown, hit C-c a to show all" feedback-limit)))))
615
      (setq message-line (format "replaced %d matches" i)))
Marko Bencun's avatar
Marko Bencun committed
616 617 618 619
    (list replacements message-line)))

(defun vr--do-replace-feedback ()
  "Show visual feedback for replacements."
620
  (vr--feedback t) ;; only really needed when regexp has not been changed from default (=> no overlays have been created)
621 622
  (custom-reevaluate-setting 'vr/match-separator-string)
  (cl-multiple-value-bind (replacements message-line) (vr--get-replacements t vr--feedback-limit)
623 624 625 626 627 628
    ;; visual feedback for matches
    (condition-case err
        (mapc (lambda (replacement-info) (apply 'vr--do-replace-feedback-match-callback replacement-info)) replacements)
      ('error (setq message-line (vr--format-error err))))
    (unless (string= "" message-line)
      (vr--minibuffer-message message-line))))
Marko Bencun's avatar
Marko Bencun committed
629

Marko Bencun's avatar
Marko Bencun committed
630
;;; vr/replace
Marko Bencun's avatar
Marko Bencun committed
631 632 633 634 635

(defun vr--do-replace (&optional silent)
  "Replace matches."
  (vr--delete-overlay-displays)
  (vr--delete-overlays)
636
  (cl-multiple-value-bind (replacements message-line) (vr--get-replacements nil nil)
637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663
    (let ((replace-count 0)
          (cumulative-offset 0)
          last-match-data)
      (cl-loop for replacement-info in replacements
               for counter from 0 do
               (setq replace-count (1+ replace-count))
               (cl-multiple-value-bind (replacement match-data i) replacement-info
                 ;; replace match
                 (let* ((replacement (vr--get-replacement replacement match-data i))
                        (begin (cl-first match-data))
                        (end (cl-second match-data)))
                   (with-current-buffer vr--target-buffer
                     (save-excursion
                       ;; first insert, then delete
                       ;; this ensures that if we had an active region before, the replaced match is still part of the region
                       (goto-char begin)
                       (insert replacement)
                       (setq cumulative-offset (+ cumulative-offset (- (point) end)))
                       (delete-char (- end begin))))
                   (when (= 0 counter)
                     (setq last-match-data match-data))
                   )))
      (unless (or silent (string= "" message-line))
        (vr--minibuffer-message message-line))
      ;; needed to correctly position the mark after query replace (finished with 'automatic ('!'))
      (set-match-data (mapcar (lambda (el) (+ cumulative-offset el)) last-match-data))
      replace-count)))
Marko Bencun's avatar
Marko Bencun committed
664

665 666 667 668 669 670 671 672 673 674 675 676 677 678
(defun vr--set-target-buffer-start-end ()
  (setq vr--target-buffer-start (if (region-active-p)
                                    (region-beginning)
                                  (point)))
  (setq vr--target-buffer-end (if (region-active-p)
                                  (region-end)
                                (point-max))))

(defun vr--set-regexp-string ()
  (save-excursion
    ;; deactivate mark so that we can see our faces instead of region-face.
    (deactivate-mark)
    (setq vr--in-minibuffer 'vr--minibuffer-regexp)
    (setq vr--last-minibuffer-contents "")
679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709
    (custom-reevaluate-setting 'query-replace-from-to-separator)
    (let* ((minibuffer-allow-text-properties t)
           (history-add-new-input nil)
           (text-property-default-nonsticky
            (cons '(separator . t) text-property-default-nonsticky))
           ;; seperator and query-replace-from-to-history copy/pasted from replace.el
           (separator
            (when query-replace-from-to-separator
              (propertize "\0"
                          'display query-replace-from-to-separator
                          'separator t)))
           (query-replace-from-to-history
            (append
             (when separator
               (mapcar (lambda (from-to)
                         (concat (query-replace-descr (car from-to))
                                 separator
                                 (query-replace-descr (cdr from-to))))
                       (symbol-value vr/query-replace-defaults-variable)))
             (symbol-value vr/query-replace-from-history-variable))))
      (setq vr--regexp-string
            (read-from-minibuffer
             " " ;; prompt will be set in vr--minibuffer-setup
             nil vr/minibuffer-keymap
             nil 'query-replace-from-to-history))
      (let ((split (vr--query-replace--split-string vr--regexp-string)))
        (if (not (consp split))
            (add-to-history vr/query-replace-from-history-variable vr--regexp-string nil t)
          (add-to-history vr/query-replace-from-history-variable (car split) nil t)
          (add-to-history vr/query-replace-to-history-variable (cdr split) nil t)
          (add-to-history vr/query-replace-defaults-variable split nil t))))))
710 711 712 713 714

(defun vr--set-replace-string ()
  (save-excursion
    ;; deactivate mark so that we can see our faces instead of region-face.
    (deactivate-mark)
715 716 717 718 719 720 721 722 723 724 725
    (let ((split (vr--query-replace--split-string vr--regexp-string)))
      (unless (consp split)
        (setq vr--in-minibuffer 'vr--minibuffer-replace)
        (setq vr--last-minibuffer-contents "")
        (let ((history-add-new-input nil))
          (setq vr--replace-string
                (read-from-minibuffer
                 " " ;; prompt will be set in vr--minibuffer-setup
                 nil vr/minibuffer-keymap
                 nil vr/query-replace-to-history-variable))
          (add-to-history vr/query-replace-to-history-variable vr--replace-string nil t)
726
          (add-to-history vr/query-replace-defaults-variable (cons vr--regexp-string vr--replace-string)))))))
727

728
(defun vr--interactive-get-args (mode calling-func)
729
  "Get interactive args for the vr/replace and vr/query-replace functions."
Steve Purcell's avatar
Steve Purcell committed
730
  (unwind-protect
Marko Bencun's avatar
Marko Bencun committed
731
      (progn
Steve Purcell's avatar
Steve Purcell committed
732 733
        (let ((buffer-read-only t)) ;; make target buffer
          (when vr--in-minibuffer (error "visual-regexp already in use."))
734
          (setq vr--calling-func calling-func)
Steve Purcell's avatar
Steve Purcell committed
735
          (setq vr--target-buffer (current-buffer))
736
          (vr--set-target-buffer-start-end)
Steve Purcell's avatar
Steve Purcell committed
737 738 739 740 741 742

          (run-hooks 'vr/initialize-hook)
          (setq vr--feedback-limit vr/default-feedback-limit)

          (setq vr--replace-preview vr/default-replace-preview)

743 744 745 746
          (vr--set-regexp-string)
          (when (equal mode 'vr--mode-regexp-replace)
            (vr--set-replace-string))
          
Steve Purcell's avatar
Steve Purcell committed
747 748
          ;; Successfully got the args, deactivate mark now. If the command was aborted (C-g), the mark (region) would remain active.
          (deactivate-mark)
749 750 751 752 753 754 755 756 757
          (cond ((equal mode 'vr--mode-regexp-replace)
                 (list vr--regexp-string
                       vr--replace-string
                       vr--target-buffer-start
                       vr--target-buffer-end))
                ((equal mode 'vr--mode-regexp)
                 (list vr--regexp-string
                       vr--target-buffer-start
                       vr--target-buffer-end)))))
Marko Bencun's avatar
Marko Bencun committed
758 759
    (progn ;; execute on finish
      (setq vr--in-minibuffer nil)
760
      (setq vr--calling-func nil)
761
      (unless (overlayp vr--minibuffer-message-overlay)
762
        (delete-overlay vr--minibuffer-message-overlay))
Marko Bencun's avatar
Marko Bencun committed
763 764 765
      (vr--delete-overlay-displays)
      (vr--delete-overlays))))

766
(add-hook 'multiple-cursors-mode-enabled-hook
767 768 769 770
          ;; run vr/mc-mark once per cursor by default (do not ask the user)
          (lambda ()
            (when (boundp 'mc--default-cmds-to-run-once)
              (add-to-list 'mc--default-cmds-to-run-once 'vr/mc-mark))))
771 772 773 774

;;;###autoload
(defun vr/mc-mark (regexp start end)
  "Convert regexp selection to multiple cursors."
775
  (require 'multiple-cursors)
776
  (interactive
777
   (vr--interactive-get-args 'vr--mode-regexp 'vr--calling-func-mc-mark))
778 779 780
  (with-current-buffer vr--target-buffer
    (mc/remove-fake-cursors)
    (activate-mark)
781
    (let (;; disable deactivating of mark after buffer-editing commands
782 783 784 785
          ;; (which happens for example in visual-regexp-steroids/vr--parse-matches
          ;; during the callback).
          (deactivate-mark nil)
          (first-fake-cursor nil))
786 787 788 789 790 791 792 793 794
      (vr--feedback-function (vr--get-regexp-string) t nil (lambda (i j begin end)
                                                             (with-current-buffer vr--target-buffer
                                                               (goto-char end)
                                                               (push-mark begin)
                                                               ;; temporarily enable transient mark mode
                                                               (activate-mark)
                                                               (let ((fc (mc/create-fake-cursor-at-point)))
                                                                 (unless first-fake-cursor
                                                                   (setq first-fake-cursor fc))))))
795

796 797 798
      ;; one fake cursor too many, replace first one with
      ;; the regular cursor.
      (when first-fake-cursor
799
        (mc/pop-state-from-overlay first-fake-cursor)))
800 801
    (mc/maybe-multiple-cursors-mode)))

802
;;;###autoload
803
(defun vr/replace (regexp replace start end)
804
  "Regexp-replace with live visual feedback."
Steve Purcell's avatar
Steve Purcell committed
805
  (interactive
806
   (vr--interactive-get-args 'vr--mode-regexp-replace 'vr--calling-func-replace))
Steve Purcell's avatar
Steve Purcell committed
807 808 809 810 811 812 813 814 815 816
  (unwind-protect
      (progn
        (when vr--in-minibuffer (error "visual-regexp already in use."))
        (setq vr--target-buffer (current-buffer)
              vr--target-buffer-start start
              vr--target-buffer-end end
              vr--regexp-string regexp
              vr--replace-string replace)
        ;; do replacement
        (vr--do-replace))
Marko Bencun's avatar
Marko Bencun committed
817 818 819 820 821 822 823 824 825 826 827 828
    ;; execute on finish
    (setq vr--in-minibuffer nil)))

;; query-replace-regexp starts here

(defvar vr--query-replacements nil)
;; we redefine the help text from replace.el to remove the commands we don't support.

(defconst vr--query-replace-help
  "Type Space or `y' to replace one match, Delete or `n' to skip to next,
RET or `q' to exit, Period to replace one match and exit,
Comma to replace but not move point immediately,
829
p to preview the replacement (like 'C-c p' during construction of the regexp),
Marko Bencun's avatar
Marko Bencun committed
830
C-r [not supported in visual-regexp],
831
C-w [not supported in visual-regexp],
Marko Bencun's avatar
Marko Bencun committed
832 833 834 835 836 837
C-l to clear the screen, redisplay, and offer same replacement again,
! to replace all remaining matches with no more questions,
^ [not supported in visual-regexp],
E [not supported in visual-regexp]"
  "Help message while in `vr/query-replace'.")

Steve Purcell's avatar
Steve Purcell committed
838
(defvar vr--query-replace-map
Marko Bencun's avatar
Marko Bencun committed
839 840 841 842 843 844 845 846
  (let ((map (make-sparse-keymap)))
    (set-keymap-parent map query-replace-map)
    ;; the following replace.el commands are not supported by visual-regexp.
    (define-key map "e" nil)
    (define-key map "E" nil)
    (define-key map "\C-r" nil)
    (define-key map "\C-w" nil)
    (define-key map "^" nil)
847
    (define-key map "p" 'toggle-preview)
Marko Bencun's avatar
Marko Bencun committed
848 849 850
    map
    ))

851
;;;###autoload
852
(defun vr/query-replace (regexp replace start end)
Marko Bencun's avatar
Marko Bencun committed
853
  "Use vr/query-replace like you would use query-replace-regexp."
Steve Purcell's avatar
Steve Purcell committed
854
  (interactive
855
   (vr--interactive-get-args 'vr--mode-regexp-replace 'vr--calling-func-query-replace))
Steve Purcell's avatar
Steve Purcell committed
856 857 858 859 860 861 862
  (unwind-protect
      (progn
        (when vr--in-minibuffer (error "visual-regexp already in use."))
        (setq vr--target-buffer (current-buffer)
              vr--target-buffer-start start
              vr--target-buffer-end end
              vr--regexp-string regexp
863 864
              vr--replace-string replace)
        (vr--perform-query-replace))
Marko Bencun's avatar
Marko Bencun committed
865 866 867
    ;; execute on finish
    (setq vr--in-minibuffer nil)))

868
(defun vr--perform-query-replace ()
Marko Bencun's avatar
Marko Bencun committed
869 870 871 872
  ;; This function is a heavily modified version of (perform-replace) from replace.el.
  ;; The original plan was to use the original perform-replace, but various issues stood in the way.
  (and minibuffer-auto-raise
       (raise-frame (window-frame (minibuffer-window))))
873 874 875 876
  (let* ((from-string (vr--get-regexp-string))
         (map vr--query-replace-map)
         (vr--query-replacements (nreverse (car (vr--get-replacements nil nil))))
         (next-replacement nil) ;; replacement string for current match
Steve Purcell's avatar
Steve Purcell committed
877 878 879 880
         (keep-going t)
         (replace-count 0)
         ;; a match can be replaced by a longer/shorter replacement. cumulate the difference
         (cumulative-offset 0)
881
         (recenter-last-op nil) ; Start cycling order with initial position.
Steve Purcell's avatar
Steve Purcell committed
882
         (message
883 884 885 886 887 888 889 890
          (concat
           (propertize "Replacing " 'read-only t)
           (propertize "%s" 'read-only t 'face 'font-lock-keyword-face)
           (propertize " with " 'read-only t)
           (propertize "%s" 'read-only t 'face 'font-lock-keyword-face)
           (propertize (substitute-command-keys
                        " (\\<vr--query-replace-map>\\[help] for help) ")
                       'read-only t))))
Marko Bencun's avatar
Marko Bencun committed
891

892 893
    ;; show visual feedback for all matches
    (mapc (lambda (replacement-info)
894 895
            (cl-multiple-value-bind (replacement match-data i) replacement-info
              (vr--feedback-match-callback i 0 (cl-first match-data) (cl-second match-data))))
Steve Purcell's avatar
Steve Purcell committed
896
          vr--query-replacements)
Marko Bencun's avatar
Marko Bencun committed
897 898 899 900 901

    (goto-char vr--target-buffer-start)
    (push-mark)
    (undo-boundary)
    (unwind-protect
Steve Purcell's avatar
Steve Purcell committed
902 903 904
        ;; Loop finding occurrences that perhaps should be replaced.
        (while (and keep-going vr--query-replacements)
          ;; Advance replacement list
905
          (cl-multiple-value-bind (replacement match-data i) (car vr--query-replacements)
906 907
            (setq match-data (mapcar (lambda (el) (+ cumulative-offset el)) match-data))
            (let ((begin (cl-first match-data))
908 909
                  (end (cl-second match-data))
                  (next-replacement-orig replacement))
910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005
              (setq next-replacement (vr--get-replacement replacement match-data replace-count))
              (goto-char begin)
              (setq vr--query-replacements (cdr vr--query-replacements))

              ;; default for new occurrence: no preview
              (setq vr--replace-preview nil)

              (undo-boundary)
              (let (done replaced key def)
                ;; Loop reading commands until one of them sets done,
                ;; which means it has finished handling this
                ;; occurrence.
                (while (not done)
                  ;; show replacement feedback for current occurrence
                  (unless replaced
                    (vr--do-replace-feedback-match-callback next-replacement-orig match-data i))
                  ;; Bind message-log-max so we don't fill up the message log
                  ;; with a bunch of identical messages.
                  (let ((message-log-max nil))
                    (message message from-string next-replacement))
                  (setq key (read-event))
                  (setq key (vector key))
                  (setq def (lookup-key map key))

                  ;; can use replace-match afterwards
                  (set-match-data match-data)

                  ;; Restore the match data while we process the command.
                  (cond ((eq def 'help)
                         (with-output-to-temp-buffer "*Help*"
                           (princ
                            (concat "Query replacing visual-regexp "
                                    from-string " with "
                                    next-replacement ".\n\n"
                                    (substitute-command-keys
                                     vr--query-replace-help)))
                           (with-current-buffer standard-output
                             (help-mode))))
                        ((eq def 'exit)
                         (setq keep-going nil
                               done t))
                        ((eq def 'act)
                         (unless replaced
                           (replace-match next-replacement t t)
                           (setq replace-count (1+ replace-count)))
                         (setq done t
                               replaced t))
                        ((eq def 'act-and-exit)
                         (unless replaced
                           (replace-match next-replacement t t)
                           (setq replace-count (1+ replace-count)))
                         (setq keep-going nil
                               done t
                               replaced t))
                        ((eq def 'act-and-show)
                         (unless replaced
                           (replace-match next-replacement t t)
                           (setq replace-count (1+ replace-count))
                           (setq replaced t)))
                        ((eq def 'toggle-preview)
                         (setq vr--replace-preview (not vr--replace-preview)))
                        ((eq def 'automatic)
                         (setq vr--target-buffer-start (match-beginning 0)
                               vr--target-buffer-end (+ cumulative-offset vr--target-buffer-end))
                         (setq replace-count (+ replace-count (vr--do-replace t)))
                         (setq done t
                               replaced t
                               keep-going nil))
                        ((eq def 'skip)
                         (setq done t))
                        ((eq def 'recenter)
                         ;; `this-command' has the value `query-replace',
                         ;; so we need to bind it to `recenter-top-bottom'
                         ;; to allow it to detect a sequence of `C-l'.
                         (let ((this-command 'recenter-top-bottom)
                               (last-command 'recenter-top-bottom))
                           (recenter-top-bottom)))
                        (t
                         (setq this-command 'mode-exited)
                         (setq keep-going nil)
                         (setq unread-command-events
                               (append (listify-key-sequence key)
                                       unread-command-events))
                         (setq done t)))
                  (when replaced
                    (setq cumulative-offset (+ cumulative-offset (- (length next-replacement) (- end begin)))))
                  (unless (eq def 'recenter)
                    ;; Reset recenter cycling order to initial position.
                    (setq recenter-last-op nil))
                  ;; in case of 'act-and-show: delete overlay display or it will still be
                  ;; visible even though the replacement has been made
                  (when replaced (vr--delete-overlay-display (vr--get-overlay i 0)))))

              ;; occurrence has been handled
              ;; delete feedback overlay
              (delete-overlay (vr--get-overlay i 0)))))
1006 1007

      ;; unwind
Steve Purcell's avatar
Steve Purcell committed
1008 1009 1010 1011 1012
      (progn
        (vr--delete-overlay-displays)
        (vr--delete-overlays)
        ;; (replace-dehighlight)
        ))
Marko Bencun's avatar
Marko Bencun committed
1013 1014 1015 1016
    (unless unread-command-events
      ;; point is set to the end of the last occurrence.
      (goto-char (match-end 0))
      (message "Replaced %d occurrence%s"
Steve Purcell's avatar
Steve Purcell committed
1017 1018
               replace-count
               (if (= replace-count 1) "" "s")))))
Marko Bencun's avatar
Marko Bencun committed
1019 1020 1021 1022

(provide 'visual-regexp)

;;; visual-regexp.el ends here