diff --git a/lisp/replace.el b/lisp/replace.el index 3a908ac..7ada519 100644 --- a/lisp/replace.el +++ b/lisp/replace.el @@ -1802,6 +1802,7 @@ query-replace-help 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, 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, @@ -1831,6 +1832,8 @@ query-replace-map (define-key map "\C-l" 'recenter) (define-key map "!" 'automatic) (define-key map "^" 'backup) + (define-key map "u" 'undo) + (define-key map "U" 'undo) (define-key map "\C-h" 'help) (define-key map [f1] 'help) (define-key map [help] 'help) @@ -1856,7 +1859,7 @@ query-replace-map `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', `quit', and `help'. This keymap is used by `y-or-n-p' as well as `query-replace'.") @@ -2105,6 +2108,9 @@ perform-replace (noedit nil) (keep-going t) (stack nil) + (search-string-solved-regexp nil) ; last string matching `from-string' + (next-replacement-solved-regexp nil) ; replacement string (substituted regexp) + (last-was-undo) (replace-count 0) (skip-read-only-count 0) (skip-filtered-count 0) @@ -2279,6 +2285,22 @@ perform-replace (match-beginning 0) (match-end 0) start end search-string regexp-flag delimited-flag case-fold-search backward) + ;; 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 (subtituted matched groups) stored in stack. + (setq search-string-solved-regexp (buffer-substring-no-properties (match-beginning 0) + (match-end 0)) + next-replacement-solved-regexp + (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) @@ -2332,6 +2354,45 @@ perform-replace (message "No previous match") (ding 'no-terminate) (sit-for 1))) + ((eq def 'undo) + (if (null stack) + (progn + (message "No previous match") + (ding 'no-terminate) + (sit-for 1)) + (let* (search-string next-replacement (elt (pop stack))) + (goto-char (nth 0 elt)) + (setq replaced (nth 1 elt) + ;; Bind locally swapped values (search-string <---> replacement). + search-string (nth (if replaced 1 0) (nth 3 elt)) + next-replacement (nth (if replaced 0 1) (nth 3 elt)) + search-string-solved-regexp search-string + next-replacement-solved-regexp next-replacement) + + (cond ((null replaced) ; nothing to undo + (setq real-match-data + (replace-match-data + t real-match-data + (nth 2 elt)))) + (t ; undo replacement + (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)))))) + (setq replaced nil + last-was-undo t)))) ((eq def 'act) (or replaced (setq noedit @@ -2454,8 +2515,12 @@ perform-replace (match-beginning 0) (match-end 0) (current-buffer)) - (match-data t))) - stack)))))) + (match-data t)) + (list search-string-solved-regexp + next-replacement-solved-regexp)) + stack) + (setq next-replacement-solved-regexp nil + search-string-solved-regexp nil)))))) (replace-dehighlight)) (or unread-command-events