summaryrefslogtreecommitdiff
path: root/lisp/replace.el
diff options
context:
space:
mode:
Diffstat (limited to 'lisp/replace.el')
-rw-r--r--lisp/replace.el142
1 files changed, 122 insertions, 20 deletions
diff --git a/lisp/replace.el b/lisp/replace.el
index 752f3bdebf7..ff917344453 100644
--- a/lisp/replace.el
+++ b/lisp/replace.el
@@ -1,4 +1,4 @@
-;;; replace.el --- replace commands for Emacs
+;;; replace.el --- replace commands for Emacs -*- lexical-binding: t -*-
;; Copyright (C) 1985-1987, 1992, 1994, 1996-1997, 2000-2017 Free
;; Software Foundation, Inc.
@@ -28,6 +28,8 @@
;;; Code:
+(eval-when-compile (require 'cl-lib))
+
(defcustom case-replace t
"Non-nil means `query-replace' should preserve case in replacements."
:type 'boolean
@@ -172,7 +174,7 @@ wants to replace FROM with TO."
(propertize "\0"
'display query-replace-from-to-separator
'separator t)))
- (query-replace-from-to-history
+ (minibuffer-history
(append
(when separator
(mapcar (lambda (from-to)
@@ -184,7 +186,7 @@ wants to replace FROM with TO."
(minibuffer-allow-text-properties t) ; separator uses text-properties
(prompt
(if (and query-replace-defaults separator)
- (format "%s (default %s): " prompt (car query-replace-from-to-history))
+ (format "%s (default %s): " prompt (car minibuffer-history))
(format "%s: " prompt)))
(from
;; The save-excursion here is in case the user marks and copies
@@ -196,9 +198,9 @@ wants to replace FROM with TO."
(setq-local text-property-default-nonsticky
(cons '(separator . t) text-property-default-nonsticky)))
(if regexp-flag
- (read-regexp prompt nil 'query-replace-from-to-history)
+ (read-regexp prompt nil 'minibuffer-history)
(read-from-minibuffer
- prompt nil nil nil 'query-replace-from-to-history
+ prompt nil nil nil nil
(car (if regexp-flag regexp-search-ring search-ring)) t)))))
(to))
(if (and (zerop (length from)) query-replace-defaults)
@@ -1408,7 +1410,7 @@ See also `multi-occur-in-matching-buffers'."
"Next buffer to search (RET to end): ")
nil t))
""))
- (add-to-list 'bufs buf)
+ (cl-pushnew buf bufs)
(setq ido-ignore-item-temp-list bufs))
(nreverse (mapcar #'get-buffer bufs)))
(occur-read-primary-args)))
@@ -1835,6 +1837,8 @@ C-w to delete match and recursive edit,
C-l to clear the screen, redisplay, and offer same replacement again,
! to replace all remaining matches in this buffer with no more questions,
^ to move point back to previous match,
+u to undo previous replacement,
+U to undo all replacements,
E to edit the replacement string.
In multi-buffer replacements type `Y' to replace all remaining
matches in all remaining buffers with no more questions,
@@ -1864,6 +1868,8 @@ in the current buffer."
(define-key map "\C-l" 'recenter)
(define-key map "!" 'automatic)
(define-key map "^" 'backup)
+ (define-key map "u" 'undo)
+ (define-key map "U" 'undo-all)
(define-key map "\C-h" 'help)
(define-key map [f1] 'help)
(define-key map [help] 'help)
@@ -1889,7 +1895,7 @@ The valid answers include `act', `skip', `act-and-show',
`act-and-exit', `exit', `exit-prefix', `recenter', `scroll-up',
`scroll-down', `scroll-other-window', `scroll-other-window-down',
`edit', `edit-replacement', `delete-and-edit', `automatic',
-`backup', `quit', and `help'.
+`backup', `undo', `undo-all', `quit', and `help'.
This keymap is used by `y-or-n-p' as well as `query-replace'.")
@@ -1941,7 +1947,6 @@ type them using Lisp syntax."
(defun replace-eval-replacement (expression count)
(let* ((replace-count count)
- err
(replacement
(condition-case err
(eval expression)
@@ -2042,7 +2047,7 @@ It is called with three arguments, as if it were
`re-search-forward'.")
(defun replace-search (search-string limit regexp-flag delimited-flag
- case-fold-search &optional backward)
+ case-fold &optional backward)
"Search for the next occurrence of SEARCH-STRING to replace."
;; Let-bind global isearch-* variables to values used
;; to search the next replacement. These let-bindings
@@ -2061,7 +2066,7 @@ It is called with three arguments, as if it were
replace-lax-whitespace)
(isearch-regexp-lax-whitespace
replace-regexp-lax-whitespace)
- (isearch-case-fold-search case-fold-search)
+ (isearch-case-fold-search case-fold)
(isearch-adjusted nil)
(isearch-nonincremental t) ; don't use lax word mode
(isearch-forward (not backward))
@@ -2076,7 +2081,7 @@ It is called with three arguments, as if it were
(defun replace-highlight (match-beg match-end range-beg range-end
search-string regexp-flag delimited-flag
- case-fold-search &optional backward)
+ case-fold &optional backward)
(if query-replace-highlight
(if replace-overlay
(move-overlay replace-overlay match-beg match-end (current-buffer))
@@ -2091,7 +2096,7 @@ It is called with three arguments, as if it were
replace-lax-whitespace)
(isearch-regexp-lax-whitespace
replace-regexp-lax-whitespace)
- (isearch-case-fold-search case-fold-search)
+ (isearch-case-fold-search case-fold)
(isearch-forward (not backward))
(isearch-other-end match-beg)
(isearch-error nil))
@@ -2145,6 +2150,10 @@ It must return a string."
(noedit nil)
(keep-going t)
(stack nil)
+ (search-string-replaced nil) ; last string matching `from-string'
+ (next-replacement-replaced nil) ; replacement string
+ ; (substituted regexp)
+ (last-was-undo)
(replace-count 0)
(skip-read-only-count 0)
(skip-filtered-count 0)
@@ -2341,8 +2350,28 @@ It must return a string."
(match-beginning 0) (match-end 0)
start end search-string
regexp-flag delimited-flag case-fold-search backward)
- ;; Bind message-log-max so we don't fill up the message log
- ;; with a bunch of identical messages.
+ ;; Obtain the matched groups: needed only when
+ ;; regexp-flag non nil.
+ (when (and last-was-undo regexp-flag)
+ (setq last-was-undo nil
+ real-match-data
+ (save-excursion
+ (goto-char (match-beginning 0))
+ (looking-at search-string)
+ (match-data t real-match-data))))
+ ;; Matched string and next-replacement-replaced
+ ;; stored in stack.
+ (setq search-string-replaced (buffer-substring-no-properties
+ (match-beginning 0)
+ (match-end 0))
+ next-replacement-replaced
+ (query-replace-descr
+ (save-match-data
+ (set-match-data real-match-data)
+ (match-substitute-replacement
+ next-replacement nocasify literal))))
+ ;; Bind message-log-max so we don't fill up the
+ ;; message log with a bunch of identical messages.
(let ((message-log-max nil)
(replacement-presentation
(if query-replace-show-replacement
@@ -2355,8 +2384,8 @@ It must return a string."
(query-replace-descr from-string)
(query-replace-descr replacement-presentation)))
(setq key (read-event))
- ;; Necessary in case something happens during read-event
- ;; that clobbers the match data.
+ ;; Necessary in case something happens during
+ ;; read-event that clobbers the match data.
(set-match-data real-match-data)
(setq key (vector key))
(setq def (lookup-key map key))
@@ -2367,7 +2396,8 @@ It must return a string."
(concat "Query replacing "
(if delimited-flag
(or (and (symbolp delimited-flag)
- (get delimited-flag 'isearch-message-prefix))
+ (get delimited-flag
+ 'isearch-message-prefix))
"word ") "")
(if regexp-flag "regexp " "")
(if backward "backward " "")
@@ -2394,6 +2424,73 @@ It must return a string."
(message "No previous match")
(ding 'no-terminate)
(sit-for 1)))
+ ((or (eq def 'undo) (eq def 'undo-all))
+ (if (null stack)
+ (progn
+ (message "Nothing to undo")
+ (ding 'no-terminate)
+ (sit-for 1))
+ (let ((stack-idx 0)
+ (stack-len (length stack))
+ (num-replacements 0)
+ search-string
+ next-replacement)
+ (while (and (< stack-idx stack-len)
+ stack
+ (null replaced))
+ (let* ((elt (nth stack-idx stack)))
+ (setq
+ stack-idx (1+ stack-idx)
+ replaced (nth 1 elt)
+ ;; Bind swapped values
+ ;; (search-string <--> replacement)
+ search-string (nth (if replaced 4 3) elt)
+ next-replacement (nth (if replaced 3 4) elt)
+ search-string-replaced search-string
+ next-replacement-replaced next-replacement)
+
+ (when (and (= stack-idx stack-len)
+ (null replaced)
+ (zerop num-replacements))
+ (message "Nothing to undo")
+ (ding 'no-terminate)
+ (sit-for 1))
+
+ (when replaced
+ (setq stack (nthcdr stack-idx stack))
+ (goto-char (nth 0 elt))
+ (set-match-data (nth 2 elt))
+ (setq real-match-data
+ (save-excursion
+ (goto-char (match-beginning 0))
+ (looking-at search-string)
+ (match-data t (nth 2 elt)))
+ noedit
+ (replace-match-maybe-edit
+ next-replacement nocasify literal
+ noedit real-match-data backward)
+ replace-count (1- replace-count)
+ real-match-data
+ (save-excursion
+ (goto-char (match-beginning 0))
+ (looking-at next-replacement)
+ (match-data t (nth 2 elt))))
+ ;; Set replaced nil to keep in loop
+ (when (eq def 'undo-all)
+ (setq replaced nil
+ stack-len (- stack-len stack-idx)
+ stack-idx 0
+ num-replacements
+ (1+ num-replacements))))))
+ (when (and (eq def 'undo-all)
+ (null (zerop num-replacements)))
+ (message "Undid %d %s" num-replacements
+ (if (= num-replacements 1)
+ "replacement"
+ "replacements"))
+ (ding 'no-terminate)
+ (sit-for 1)))
+ (setq replaced nil last-was-undo t)))
((eq def 'act)
(or replaced
(setq noedit
@@ -2516,9 +2613,12 @@ It must return a string."
(match-beginning 0)
(match-end 0)
(current-buffer))
- (match-data t)))
- stack))))))
-
+ (match-data t))
+ search-string-replaced
+ next-replacement-replaced)
+ stack)
+ (setq next-replacement-replaced nil
+ search-string-replaced nil))))))
(replace-dehighlight))
(or unread-command-events
(message "Replaced %d occurrence%s%s"
@@ -2544,4 +2644,6 @@ It must return a string."
"")))
(or (and keep-going stack) multi-buffer)))
+(provide 'replace)
+
;;; replace.el ends here