emacs-elpa-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[elpa] scratch/mheerdegen-preview dc25f93 09/33: WIP: qr: Make shown rep


From: Michael Heerdegen
Subject: [elpa] scratch/mheerdegen-preview dc25f93 09/33: WIP: qr: Make shown replacement editable and ediffable; r twice restores match; stop for problematic comments
Date: Wed, 24 Oct 2018 18:30:47 -0400 (EDT)

branch: scratch/mheerdegen-preview
commit dc25f93a1e806aede892ce939be4813d65ce8ad7
Author: Michael Heerdegen <address@hidden>
Commit: Michael Heerdegen <address@hidden>

    WIP: qr: Make shown replacement editable and ediffable; r twice restores 
match; stop for problematic comments
---
 packages/el-search/el-search.el | 359 ++++++++++++++++++++++++++++------------
 1 file changed, 254 insertions(+), 105 deletions(-)

diff --git a/packages/el-search/el-search.el b/packages/el-search/el-search.el
index bfab694..30169fb 100644
--- a/packages/el-search/el-search.el
+++ b/packages/el-search/el-search.el
@@ -304,9 +304,10 @@
 ;;    `(foo ,b ,a . ,rest) RET
 ;;
 ;; Type y to replace a match and go to the next one, r to replace
-;; without moving, SPC or n to go to the next match and ! to replace
-;; all remaining matches automatically.  q quits.  And ? shows a quick
-;; help summarizing all of these keys.
+;; without moving (hitting r again restores the match), SPC or n to go
+;; to the next match and ! to replace all remaining matches
+;; automatically.  q quits.  And ? shows a quick help summarizing all
+;; of these keys.
 ;;
 ;; It is possible to replace a match with an arbitrary number of
 ;; expressions using "splicing mode".  When it is active, the
@@ -314,6 +315,18 @@
 ;; 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 while doing a query replace:
+;;
+;; (1) Without suspending the search: hit e from the query-replace
+;; prompt to edit the replacement string of the current replacement in
+;; a separate buffer, then hit C-c C-c when done.  This will make
+;; el-search insert the contents of this buffer for this replacement
+;; after confirmation.
+;;
+;; (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
 ;; ===================
@@ -385,18 +398,6 @@
 ;;   to reading-printing.  "Some" because we can handle this problem
 ;;   in most cases.
 ;;
-;; - Similar: comments are normally preserved (where it makes sense).
-;;   But when replacing like `(foo ,a ,b) -> `(foo ,b ,a)
-;;
-;;   in a content like
-;;
-;;     (foo
-;;       a
-;;       ;; comment
-;;       b)
-;;
-;;   the comment will be lost.
-;;
 ;; - Something like (1 #1#) is unmatchable (because it is un`read'able
 ;;   without context).
 ;;
@@ -429,10 +430,6 @@
 ;;   already suffice using only syntax tables, sexp scanning and
 ;;   font-lock?
 ;;
-;; - Replace: pause and warn when replacement might be wrong
-;;   (ambiguous reader syntaxes; lost comments, comments that can't
-;;   non-ambiguously be assigned to rewritten code)
-;;
 ;;
 ;; NEWS:
 ;;
@@ -541,6 +538,32 @@ The default value is ask-multi."
                  (const :tag "Ask" ask)
                  (const :tag "Ask when multibuffer" ask-multi)))
 
+(defcustom el-search-query-replace-stop-for-comments 'ask
+  "Whether `el-search-query-replace' should stop for problematic comments.
+
+It's not always clear how comments in a match should be mapped to
+the replacement.  If it can't be done automatically, the value of this
+option decides how to proceed in such a case.
+
+When nil, comments will likely be messed up or lost.  You should
+check the results after `el-search-query-replace' is done.
+
+A non-nil value means to stop when encountering problematic
+comments.  When the non-nil value is the symbol ask (the
+default), a prompt will appear that will ask how to proceed.  You
+may then choose to edit the replacement manually, or ignore the
+problem for this case to fix it later.
+
+Any other non-nil value will not prompt and just directly pop to
+a buffer where you can edit the replacement to adjust the
+comments.
+
+When ask, you can still choose the answer for all following cases
+from the prompt."
+  :type '(choice (const :tag "Off" nil)
+                 (const :tag "On"  t)
+                 (const :tag "Ask" ask)))
+
 (defvar el-search-use-transient-map nil
   "Whether el-search should make commands repeatable."
   ;; I originally wanted to make commands repeatable by looking at the
@@ -3600,7 +3623,9 @@ 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 replacement."
   (atomic-change-group
     (let* ((inhibit-message t)
            (message-log-max nil)
@@ -3635,23 +3660,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
@@ -3733,6 +3759,50 @@ 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--matched-sexp)
+
+(declare-function ediff-make-cloned-buffer 'ediff-util)
+(declare-function ediff-regions-internal   'ediff)
+(defun el-search-query-replace-ediff-regions ()
+  (interactive)
+  (let* ((buffer-orig (generate-new-buffer "El-search Orig"))
+         (buffer-b (ediff-make-cloned-buffer (current-buffer) "El-search 
Replacement"))
+         (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--matched-sexp)
+      (indent-region (point-min) (point-max)))
+    (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 (list (lambda () (add-hook 'ediff-quit-hook 
delete-temp-buffers t t)))
+                  'ediff-regions-linewise nil nil)))))
+
+(defun el-search-query-replace--comments-preserved-p (from to)
+  (cl-flet ((get-comments
+             (lambda (text)
+               (let ((comments '()))
+                 (with-temp-buffer
+                   (insert text)
+                   (goto-char (point-min))
+                   (emacs-lisp-mode)
+                   (while (search-forward-regexp comment-start-skip nil t)
+                     (let ((comment-text (buffer-substring (point) 
(line-end-position))))
+                       (unless (string= comment-text "")
+                         (push comment-text comments)))
+                     (forward-line +1))
+                   (sort comments #'string<))))))
+    (null (apply #'cl-set-exclusive-or
+                 (mapcar #'get-comments (list from to))))))
+
 (defun el-search--search-and-replace-pattern
     (pattern replacement &optional splice to-input-string use-current-search)
   (unless use-current-search
@@ -3760,7 +3830,8 @@ exactly you did?  Thanks!"))))
           (matcher (el-search-make-matcher pattern))
           (heuristic-matcher (el-search--current-heuristic-matcher))
           (save-all-answered nil)
-          (should-quit nil))
+          (should-quit nil)
+          (stop-for-comments el-search-query-replace-stop-for-comments))
       (let ((replace-in-current-buffer
              (lambda ()
                (setq nbr-replaced 0)
@@ -3796,15 +3867,26 @@ 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 ()
+                                 (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))
@@ -3819,6 +3901,52 @@ exactly you did?  Thanks!"))))
                                           (el-search-head-buffer head))
                                       (/ (* 100 (- (point) start-point -1))
                                          (- (point-max) start-point -1)))))))
+                              (edit-replacement
+                               (lambda ()
+                                 (save-excursion ;user may copy stuff from 
base buffer etc.
+                                   (let* ((owin (selected-window))
+                                          (buffer (get-buffer-create
+                                                   (generate-new-buffer-name 
"*Replacement*")))
+                                          (window (display-buffer buffer)))
+                                     (select-window window)
+                                     (emacs-lisp-mode)
+                                     (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-e to Ediff the current match with this buffer's content.
+;; Type C-c C-c when done.  If you have modified this buffer, you will
+;; be prompted whether to use the edited replacement expression."
+                                                  'read-only t 'field t
+                                                  'front-sticky t 
'rear-nonsticky t)
+                                      "\n\n")
+                                     (save-excursion (insert to-insert))
+                                     (use-local-map
+                                      (let ((map (make-sparse-keymap)))
+                                        (set-keymap-parent map 
(current-local-map))
+                                        (define-key map [(control ?c) (control 
?c)]
+                                          #'exit-recursive-edit)
+                                        (define-key map [(control ?c) (control 
?e)]
+                                          
#'el-search-query-replace-ediff-regions)
+                                        map))
+                                     (let 
((el-search-query-replace--matched-sexp
+                                            original-text))
+                                       (recursive-edit))
+                                     (let ((content-now
+                                            (with-current-buffer buffer
+                                              (goto-char (point-min))
+                                              (while (looking-at "^;;\\|^$")
+                                                (forward-line))
+                                              (buffer-substring (point) 
(point-max)))))
+                                       (when (and (not (string= to-insert 
content-now))
+                                                  (y-or-n-p "Use modified 
buffer content?"))
+                                         (setq to-insert content-now)))
+                                     (delete-window window)
+                                     (kill-buffer buffer)
+                                     (select-window owin)
+                                     (el-search--after-scroll 
(selected-window) (window-start))
+                                     nil))))
                               (query
                                (lambda ()
                                  (car
@@ -3835,8 +3963,10 @@ exactly you did?  Thanks!"))))
                                           (list ?n
                                                 (if replaced-this "next" "n")
                                                 "Go to the next match")
-                                          (and (not replaced-this)
-                                               '(?r "r" "Replace this match 
but don't move"))
+                                          `(?r "r"
+                                               ,(if (not replaced-this)
+                                                    "Replace this match but 
don't move"
+                                                  "Restore match"))
                                           '(?! "all" "Replace all remaining 
matches in this buffer")
                                           '(?b "skip buf"
                                                "Skip this buffer and any 
remaining matches in it")
@@ -3848,74 +3978,93 @@ exactly you did?  Thanks!"))))
                                                                 " splice")
                                                      (substitute-command-keys 
"\
 Toggle splicing mode (\\[describe-function] el-search-query-replace for 
details)")))
-                                          '(?o "show" "Show replacement in a 
buffer")
+                                          '(?e "edit" "\
+Show current replacement in a separate buffer, with the option to \
+modify it")
                                           '(?q  "quit")
                                           '(?\r "quit"))))))))
+                         (when (and
+                                stop-for-comments
+                                (not 
(el-search-query-replace--comments-preserved-p
+                                      original-text to-insert)))
+                           (pcase (if (eq stop-for-comments 'ask)
+                                      (car (read-multiple-choice
+                                            (propertize
+                                             "Problems with adjusting comments 
- edit now? "
+                                             'face 
'el-search-highlight-in-prompt-face)
+                                            (list
+                                             '(?y "yes" "Edit the replacement 
now")
+                                             '(?n "no"  "Just replace and mess 
up comments ")
+                                             '(?Y "always Yes" "Yes, now and 
later - don't ask again")
+                                             '(?N "always No"  "No, not now 
and not later")
+                                             '(?q  "quit"))))
+                                    (progn
+                                      (message "%s" (propertize
+                                                     "Problems with adjusting 
comments, please edit"
+                                                     'face 
'el-search-highlight-in-prompt-face))
+                                      (sit-for 1)
+                                      ?y))
+                             (?n)
+                             (?N (setq stop-for-comments nil))
+                             (?y (funcall edit-replacement))
+                             (?Y (setq stop-for-comments t)
+                                 (funcall edit-replacement))
+                             ((or ?q ?\C-g) (signal 'quit t))))
                          (if replace-all
                              (funcall do-replace)
-                           (while (not (pcase (funcall query)
-                                         (?r (funcall do-replace)
-                                             nil)
-                                         (?y (funcall do-replace)
-                                             t)
-                                         (?n
-                                          (unless replaced-this (cl-incf 
nbr-skipped))
-                                          t)
-                                         (?!
-                                          (when (and use-current-search
-                                                     (not (alist-get 
'is-single-buffer
-                                                                     
(el-search-object-properties
-                                                                      
el-search--current-search)))
-                                                     (eq (car 
(read-multiple-choice
-                                                               "Replace in all 
following buffers?"
-                                                               '((?! "Only 
this"
-                                                                     "\
+                           (let ((handle (prepare-change-group)))
+                             (while (not (pcase (funcall query)
+                                           (?r
+                                            (if (not replaced-this)
+                                                (progn
+                                                  (activate-change-group 
handle)
+                                                  (funcall do-replace))
+                                              (cancel-change-group handle)
+                                              (setq replaced-this nil)
+                                              (setq handle 
(prepare-change-group))
+                                              (cl-decf nbr-replaced)
+                                              (cl-decf nbr-replaced-total))
+                                            nil)
+                                           (?y (funcall do-replace)
+                                               t)
+                                           (?n
+                                            (unless replaced-this (cl-incf 
nbr-skipped))
+                                            t)
+                                           (?!
+                                            (when (and use-current-search
+                                                       (not (alist-get 
'is-single-buffer
+                                                                       
(el-search-object-properties
+                                                                        
el-search--current-search)))
+                                                       (eq (car 
(read-multiple-choice
+                                                                 "Replace in 
all following buffers?"
+                                                                 '((?! "Only 
this"
+                                                                       "\
 Replace only remaining matches in this buffer")
-                                                                 (?A "All 
buffers"
-                                                                     "\
+                                                                   (?A "All 
buffers"
+                                                                       "\
 Replace all matches in all buffers"))))
-                                                         ?A))
-                                            (setq replace-all-and-following t))
-                                          (setq replace-all t)
-                                          (unless replaced-this (funcall 
do-replace))
-                                          t)
-                                         (?b (goto-char (point-max))
-                                             (message "Skipping this buffer")
-                                             (sit-for 1)
-                                             ;; FIXME: add #skipped matches to 
nbr-skipped?
-                                             t)
-                                         (?d (call-interactively 
#'el-search-skip-directory)
-                                             t)
-                                         (?s
-                                          (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))
-                                         ((or ?q ?\C-g ?\r) (signal 'quit 
t))))))
+                                                           ?A))
+                                              (setq replace-all-and-following 
t))
+                                            (setq replace-all t)
+                                            (unless replaced-this (funcall 
do-replace))
+                                            t)
+                                           (?b (goto-char (point-max))
+                                               (message "Skipping this buffer")
+                                               (sit-for 1)
+                                               ;; FIXME: add #skipped matches 
to nbr-skipped?
+                                               t)
+                                           (?d (call-interactively 
#'el-search-skip-directory)
+                                               t)
+                                           (?s
+                                            (setq splice    (not splice)
+                                                  to-insert (funcall 
get-replacement-string))
+                                            nil)
+                                           (?e (funcall edit-replacement)
+                                               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



reply via email to

[Prev in Thread] Current Thread [Next in Thread]