[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[elpa] externals/el-search 4961830 273/332: [el-search] Make replacement
From: |
Stefan Monnier |
Subject: |
[elpa] externals/el-search 4961830 273/332: [el-search] Make replacement editable and ediff'able |
Date: |
Tue, 1 Dec 2020 15:49:02 -0500 (EST) |
branch: externals/el-search
commit 49618307c14b34414db913cbaa86e1667a3f3797
Author: Michael Heerdegen <michael_heerdegen@web.de>
Commit: Michael Heerdegen <michael_heerdegen@web.de>
[el-search] Make replacement editable and ediff'able
Make the 'el-search-query-replace' replacement editable and ediff'able
using a temporary buffer. Update header comment accordingly.
* packages/el-search/el-search.el (el-search--replace-hunk): Return a
marker pointing to the end of the inserted text.
(el-search-query-replace-ediff-replacement): New function.
(el-search--search-and-replace-pattern): Add and implement keys to
edit the replacement in a separate buffer and to ediff the current
match with the current replacement.
---
el-search.el | 270 ++++++++++++++++++++++++++++++++++++++++++++++-------------
1 file changed, 210 insertions(+), 60 deletions(-)
diff --git a/el-search.el b/el-search.el
index bc7bc36..6f64f1c 100644
--- a/el-search.el
+++ b/el-search.el
@@ -309,6 +309,19 @@
;; the buffer for any match. Hit s from the prompt to toggle splicing
;; mode in an `el-search-query-replace' session.
;;
+;; There are two ways to edit replacements directly while performing
+;; an el-search-query-replace:
+;;
+;; (1) Without suspending the search: hit o at the prompt to show the
+;; replacement of the current match in a separate buffer. You can
+;; edit the replacement in this buffer. Confirming with C-c C-c will
+;; make el-search replace the current match with this buffer's
+;; contents.
+;;
+;; (2) At any time you can interrupt a query-replace session by
+;; hitting RET. Make your edits, then resume the query-replace
+;; session by hitting C-S-j C-% or M-s e j %.
+;;
;;
;; Multi query-replace
;; ===================
@@ -3482,7 +3495,10 @@ clone with an individual state."
(defun el-search--replace-hunk (region to-insert)
"Replace the text in REGION in current buffer with string TO-INSERT.
Add line breaks before and after TO-INSERT when appropriate and
-reindent."
+reindent.
+
+The return value is a marker pointing to the end of the inserted
+text."
(atomic-change-group
(let* ((inhibit-message t)
(message-log-max nil)
@@ -3517,23 +3533,24 @@ reindent."
(insert to-insert)
(when insert-newline-after
(insert "\n"))
- (if (string= to-insert "")
- ;; We deleted the match. Clean up.
- (if (save-excursion (goto-char (line-beginning-position))
- (looking-at (rx bol (* space) eol)))
- (delete-region (match-beginning 0) (min (1+ (match-end 0))
(point-max)))
- (save-excursion
- (skip-chars-backward " \t")
- (when (looking-at (rx (+ space) eol))
- (delete-region (match-beginning 0) (match-end 0))))
- (when (and (looking-back (rx space) (1- (point)))
- (looking-at (rx (+ space))))
- (delete-region (match-beginning 0) (match-end 0)))
- (indent-according-to-mode))
- (save-excursion
- ;; the whole enclosing sexp might need re-indenting
- (condition-case nil (up-list) (scan-error))
- (indent-region opoint (1+ (point))))))))
+ (prog1 (copy-marker (point))
+ (if (string= to-insert "")
+ ;; We deleted the match. Clean up.
+ (if (save-excursion (goto-char (line-beginning-position))
+ (looking-at (rx bol (* space) eol)))
+ (delete-region (match-beginning 0) (min (1+ (match-end 0))
(point-max)))
+ (save-excursion
+ (skip-chars-backward " \t")
+ (when (looking-at (rx (+ space) eol))
+ (delete-region (match-beginning 0) (match-end 0))))
+ (when (and (looking-back (rx space) (1- (point)))
+ (looking-at (rx (+ space))))
+ (delete-region (match-beginning 0) (match-end 0)))
+ (indent-according-to-mode))
+ (save-excursion
+ ;; the whole enclosing sexp might need re-indenting
+ (condition-case nil (up-list) (scan-error))
+ (indent-region opoint (1+ (point)))))))))
(defun el-search--format-replacement (replacement original replace-expr-input
splice)
;; Return a printed representation of REPLACEMENT. Try to reuse the
@@ -3615,6 +3632,42 @@ Can you please make a bug report including a recipe of
what
exactly you did? Thanks!"))))
(kill-buffer orig-buffer)))))
+(defvar el-search-query-replace--current-match-string nil
+ "Holds the current match as a string.")
+
+(declare-function ediff-make-cloned-buffer 'ediff-util)
+(declare-function ediff-regions-internal 'ediff)
+(defun el-search-query-replace-ediff-replacement (&rest hook-funs)
+ ;; Assumes that the *Replacement* buffer is current
+ ;; FIXME: should we make this ediff3 with prefix arg?
+ (interactive)
+ (let* ((buffer-orig (generate-new-buffer "*El-search Orig*"))
+ (buffer-b (make-indirect-buffer
+ (current-buffer)
+ (generate-new-buffer-name "*El-search Replacement*")
+ 'clone))
+ (delete-temp-buffers
+ (lambda () (mapc #'kill-buffer (list buffer-orig buffer-b)))))
+ (with-current-buffer buffer-orig
+ (emacs-lisp-mode)
+ (insert el-search-query-replace--current-match-string)
+ (indent-region (point-min) (point-max))
+ (setq buffer-read-only t))
+ (require 'ediff)
+ (apply #'ediff-regions-internal
+ (nconc
+ (with-current-buffer buffer-orig (list buffer-orig (point-min)
(point-max)))
+ (with-current-buffer buffer-b
+ (save-excursion
+ (goto-char (point-min))
+ (while (looking-at "^;;\\|^$")
+ (forward-line))
+ (list (current-buffer) (point) (point-max))))
+ (list (apply #'list
+ (lambda () (add-hook 'ediff-quit-hook
delete-temp-buffers t t))
+ hook-funs)
+ 'ediff-regions-linewise nil nil)))))
+
(defun el-search--search-and-replace-pattern
(pattern replacement &optional splice to-input-string use-current-search)
(unless use-current-search
@@ -3678,15 +3731,28 @@ exactly you did? Thanks!"))))
(lambda () (el-search--format-replacement
new-expr original-text
to-input-string splice)))
(to-insert (funcall get-replacement-string))
- (void-replacement-p (lambda () (and splice (null
new-expr))))
+ (void-replacement-p
+ (lambda ()
+ ;; We can't just test "(and splice (null
new-expr))" because the
+ ;; replacement could have been edited with o
+ (with-temp-buffer
+ (emacs-lisp-mode)
+ (insert to-insert)
+ (goto-char (point-min))
+ (condition-case nil
+ (progn (el-search--ensure-sexp-start)
+ nil)
+ (end-of-buffer t)))))
+ replacement-end-pos
(do-replace
(lambda ()
(save-excursion
(save-restriction
(widen)
- (el-search--replace-hunk
- (list (point) (el-search--end-of-sexp))
- to-insert)))
+ (setq replacement-end-pos
+ (el-search--replace-hunk
+ (list (point)
(el-search--end-of-sexp))
+ to-insert))))
(unless (funcall void-replacement-p)
;;skip potentially newly added whitespace
(el-search--ensure-sexp-start))
@@ -3701,6 +3767,93 @@ exactly you did? Thanks!"))))
(el-search-head-buffer head))
(/ (* 100 (- (point) start-point -1))
(- (point-max) start-point -1)))))))
+ (edit-replacement
+ (lambda (&optional ediff-only)
+ (save-excursion ;user may copy stuff from
base buffer etc.
+ (let* ((buffer (get-buffer-create
+ (generate-new-buffer-name
"*Replacement*")))
+ (window (display-buffer buffer)))
+ (select-window window)
+ (emacs-lisp-mode)
+ (unless ediff-only
+ (insert
+ (propertize "\
+;; This buffer shows the individual replacement for the current match.
+;; You may edit it here while query-replace is interrupted by a
+;; `recursive-edit'.
+;; Type C-c C-q to quit, dismissing any changes, or C-c C-c to confirm
+;; changes.
+;; Type C-c C-e to Ediff the current match with this buffer's content.
+;; Type C-c C-r to restart editing with the calculated replacement."
+ 'read-only t 'field t
+ 'front-sticky t
'rear-nonsticky t)
+ "\n\n"))
+ (save-excursion (insert to-insert))
+ (let* ((owconf
(current-window-configuration))
+ (make-cleanup-fun
+ (lambda (&optional do)
+ (lambda ()
+ (interactive)
+ ;; Ediff may change the
window configuration
+ (set-window-configuration
owconf)
+ (when do (funcall do)))))
+ (make-ediff-startup-hook-fun
+ (lambda (&optional do)
+ (let ((e (funcall
make-cleanup-fun do)))
+ (lambda () (add-hook
'ediff-quit-hook e t t))))))
+ (use-local-map
+ (let ((map (make-sparse-keymap))
+ (abort (funcall
+ make-cleanup-fun
+ (lambda ()
+ (let
((inhibit-read-only t))
+ (delete-region
(point-min) (point-max)))
+
(exit-recursive-edit)))))
+ (set-keymap-parent map
(current-local-map))
+ (define-key map [(control ?c)
(control ?c)]
+ (funcall make-cleanup-fun
#'exit-recursive-edit))
+ (define-key map [(control ?c)
(control ?q)]
+ abort)
+ (define-key map [(control ?c)
(control ?a)] ;"Abort"
+ abort)
+ (define-key map [(control ?c)
(control ?e)]
+ (lambda ()
+ (interactive)
+
(el-search-query-replace-ediff-replacement
+ (funcall
make-ediff-startup-hook-fun))))
+ (define-key map [(control ?c)
(control ?r)]
+ (lambda ()
+ (interactive)
+ (goto-char (point-min))
+ (while (and (not (eobp))
+ (looking-at
"^;;\\|^$"))
+ (forward-line))
+ (delete-region (point)
(point-max))
+ (insert (funcall
get-replacement-string))))
+ map))
+ (let
((el-search-query-replace--current-match-string
+ original-text))
+ (when ediff-only
+
(el-search-query-replace-ediff-replacement
+ (funcall
make-ediff-startup-hook-fun
+ #'exit-recursive-edit)))
+ (recursive-edit)))
+ (let ((content-now
+ (with-current-buffer buffer
+ (goto-char (point-min))
+ (while (and (not (eobp))
+ (looking-at
"^;;\\|^$"))
+ (forward-line))
+ (buffer-substring (point)
(point-max)))))
+ (when (and (not (or (string= to-insert
content-now)
+ (string-match-p (rx
bos (* space) eos)
+
content-now)))
+ (y-or-n-p "Use modified
version?"))
+ (setq to-insert content-now)))
+ (delete-window window)
+ (kill-buffer buffer))
+ (el-search--after-scroll (selected-window)
(window-start))
+ nil)))
(query
(lambda ()
(car
@@ -3732,26 +3885,42 @@ Replace match but don't move or restore match if
already replaced")
" splice")
(substitute-command-keys
"\
Toggle splicing mode (\\[describe-function] el-search-query-replace for
details)")))
- '(?o "show" "Show replacement in a
buffer")
+ '(?o "show" "\
+Show current replacement in a separate buffer - you can modify it there")
+ '(?e "ediff" "\
+Ediff match with replacement")
'(?q "quit")
'(?\r "quit"))))))))
(if replace-all
(funcall do-replace)
- (let ((handle nil))
+ (let* ((handle nil)
+ (replace-or-restore
+ (lambda ()
+ (if (not replaced-this)
+ (progn
+ (activate-change-group
+ (setq handle
(prepare-change-group)))
+ (funcall do-replace))
+ (cancel-change-group handle)
+ (setq handle nil)
+ (setq replaced-this nil)
+ (cl-decf nbr-replaced)
+ (cl-decf nbr-replaced-total))))
+ (edit-and-update
+ (lambda (&optional ediff-only)
+ (let ((old-to-insert to-insert))
+ (funcall edit-replacement ediff-only)
+ (unless (string= old-to-insert
to-insert)
+ (if (not replaced-this)
+ (funcall replace-or-restore)
+ (el-search--message-no-log
+ "Already replaced this match - hit
r r to update")
+ (sit-for 2))))
+ nil)))
(unwind-protect
(while (not (pcase (funcall query)
- (?r
- (if (not replaced-this)
- (progn
- (activate-change-group
- (setq handle
(prepare-change-group)))
- (funcall do-replace))
- (cancel-change-group handle)
- (setq handle nil)
- (setq replaced-this nil)
- (cl-decf nbr-replaced)
- (cl-decf nbr-replaced-total))
- nil)
+ (?r (funcall replace-or-restore)
+ nil)
(?y
(unless replaced-this (funcall
do-replace))
t)
@@ -3788,33 +3957,14 @@ Replace all matches in all buffers"))))
(setq splice (not splice)
to-insert (funcall
get-replacement-string))
nil)
- (?o
- ;; FIXME: Should we allow to
edit the replacement?
- (let* ((buffer
(get-buffer-create
-
(generate-new-buffer-name "*Replacement*")))
- (window (display-buffer
buffer)))
- (with-selected-window window
- (emacs-lisp-mode)
- (save-excursion
- (insert
- "\
-;; This buffer shows the replacement for the current match.
-;; Please hit any key to proceed.\n\n"
- (funcall
get-replacement-string)))
- (read-char " "))
- (delete-window window)
- (kill-buffer buffer)
- (el-search--after-scroll
(selected-window) (window-start))
- nil))
+ (?o (funcall edit-and-update)
+ nil)
+ (?e (funcall edit-and-update
'ediff-only)
+ nil)
((or ?q ?\C-g ?\r) (signal
'quit t)))))
(when handle (accept-change-group handle)))))
(unless (eobp)
- (let* ((replacement-end-pos
- (and replaced-this
- (save-excursion
- (forward-sexp (if splice (length
replacement) 1))
- (point))))
- (replacement-contains-another-match
+ (let* ((replacement-contains-another-match
(and replaced-this
;; This intentionally includes the
replacement itself
(save-excursion
- [elpa] externals/el-search 47ee6b1 226/332: Improve working of `el-search-kill-left-over-search-buffers', (continued)
- [elpa] externals/el-search 47ee6b1 226/332: Improve working of `el-search-kill-left-over-search-buffers', Stefan Monnier, 2020/12/01
- [elpa] externals/el-search e21a53d 240/332: [el-search] Use current buffer to check for matches in replacement, Stefan Monnier, 2020/12/01
- [elpa] externals/el-search 188fb4a 259/332: [el-search] Minor tweaks and bump version to 1.7.5, Stefan Monnier, 2020/12/01
- [elpa] externals/el-search d909f2e 260/332: [el-search] Follow-ups to transient map handling, Stefan Monnier, 2020/12/01
- [elpa] externals/el-search d9950e1 264/332: [el-search] Fix nested match issues in *El Occur*, Stefan Monnier, 2020/12/01
- [elpa] externals/el-search d9b2ff6 257/332: [el-search] Extend meaning of C-J prefix arg, Stefan Monnier, 2020/12/01
- [elpa] externals/el-search 656869c 235/332: * el-search/el-search.el: Fix heuristic matcher for 'nil, Stefan Monnier, 2020/12/01
- [elpa] externals/el-search adbf76d 262/332: [el-search] Open invisible text, Stefan Monnier, 2020/12/01
- [elpa] externals/el-search 60ad12e 268/332: [el-search] Small fix in el-search--reset-wrap-flag, Stefan Monnier, 2020/12/01
- [elpa] externals/el-search 73a15d2 274/332: [el-search] Stop for problematic comments, Stefan Monnier, 2020/12/01
- [elpa] externals/el-search 4961830 273/332: [el-search] Make replacement editable and ediff'able,
Stefan Monnier <=
- [elpa] externals/el-search da1f46b 282/332: [el-search] Make mouse clicks not abort the search, Stefan Monnier, 2020/12/01
- [elpa] externals/el-search 2a098da 293/332: [el-search] Unify go-to-previous/next-match key bindings, Stefan Monnier, 2020/12/01
- [elpa] externals/el-search d4f8b3b 289/332: [el-search] Tweak my last commit "Add menus", Stefan Monnier, 2020/12/01
- [elpa] externals/el-search 4d81139 299/332: [el-search] Fine tune separator for splicing replace, Stefan Monnier, 2020/12/01
- [elpa] externals/el-search 424138b 307/332: [el-search] Tweak 'display-buffer' actions, Stefan Monnier, 2020/12/01
- [elpa] externals/el-search d9acf3a 314/332: [el-search] Fix sanity check regarding comments, Stefan Monnier, 2020/12/01
- [elpa] externals/el-search 13fc32d 323/332: [el-search] Fix a highlighting corner case, Stefan Monnier, 2020/12/01
- [elpa] externals/el-search 9e95e79 325/332: [el-search] Also overwrite replaced matches automatically, Stefan Monnier, 2020/12/01
- [elpa] externals/el-search f7e4c5b 327/332: [el-search] Small fix in occur filter-buffer-substring-function, Stefan Monnier, 2020/12/01
- [elpa] externals/el-search 82b807f 231/332: * el-search/el-search.el: New user option `el-search-allow-scroll', Stefan Monnier, 2020/12/01