]> code.delx.au - gnu-emacs-elpa/blobdiff - packages/el-search/el-search.el
Rename two functions
[gnu-emacs-elpa] / packages / el-search / el-search.el
index 26857efdb191f5eab4c4792b56124a6a74fec114..db57a5e12b17c23b041d7df700876f3608260cd2 100644 (file)
@@ -65,7 +65,7 @@
 ;;   `(defvar ,_)
 ;;
 ;; you search for all defvar forms that don't specify an init value.
-;; 
+;;
 ;; The following will search for defvar forms with a docstring whose
 ;; first line is longer than 70 characters:
 ;;
 ;;    (define-key isearch-mode-map [(control ?%)] #'el-search-replace-from-isearch)
 ;;
 ;; The bindings in `isearch-mode-map' let you conveniently switch to
-;; elisp searching from isearch.
+;; "el-search" searching from isearch.
 ;;
 ;;
 ;; Bugs, Known Limitations
 ;;
 ;; the comment will be lost.
 ;;
+;; FIXME: when we have resumable sessions, pause and warn about this case.
+;;
 ;;
 ;;  Acknowledgments
 ;;  ===============
 ;;
 ;; TODO:
 ;;
-;; - detect infloops when replacing automatically (e.g. for 1 -> '(1))
-;;
 ;; - implement backward searching
 ;;
+;; - Make `el-search-pattern' accept an &optional limit, at least for
+;;   the non-interactive use case?
+;;
 ;; - improve docstrings
 ;;
 ;; - handle more reader syntaxes, e.g. #n, #n#
@@ -345,6 +348,7 @@ error."
 Don't move if already at beginning of a sexp.  Point must not be
 inside a string or comment.  `read' the expression at that point
 and return it."
+  ;; This doesn't catch end-of-buffer to keep the return value non-ambiguous
   (let ((not-done t) res)
     (while not-done
       (let ((stop-here nil)
@@ -680,7 +684,7 @@ of any kind matched by all PATTERNs are also matched.
    ((null (cdr patterns))
     (let ((pattern (car patterns)))
       `(app ,(apply-partially #'el-search--contains-p (el-search--matcher pattern))
-            (,'\`  (t (,'\, ,pattern))))))
+            (,'\` (t (,'\, ,pattern))))))
    (t `(and ,@(mapcar (lambda (pattern) `(contains ,pattern)) patterns)))))
 
 (el-search-defpattern not (pattern)
@@ -943,10 +947,11 @@ s         Toggle splicing mode.  When splicing mode is
 Hit any key to proceed."
   "Help string for ? in `el-search-query-replace'.")
 
-(defun el-search-search-and-replace-pattern (pattern replacement &optional splice to-input-string)
+(defun el-search--search-and-replace-pattern (pattern replacement &optional splice to-input-string)
   (let ((replace-all nil) (nbr-replaced 0) (nbr-skipped 0) (done nil)
         (el-search-keep-hl t) (opoint (point))
-        (get-replacement (el-search--matcher pattern replacement)))
+        (get-replacement (el-search--matcher pattern replacement))
+        (skip-matches-in-replacement 'ask))
     (unwind-protect
         (while (and (not done) (el-search--search-pattern pattern t))
           (setq opoint (point))
@@ -962,6 +967,16 @@ Hit any key to proceed."
                  (get-replacement-string
                   (lambda () (el-search--format-replacement new-expr substring to-input-string splice)))
                  (to-insert (funcall get-replacement-string))
+                 (replacement-contains-another-match
+                  (with-temp-buffer
+                    (emacs-lisp-mode)
+                    (insert to-insert)
+                    (goto-char 1)
+                    (el-search--skip-expression new-expr)
+                    (condition-case nil
+                        (progn (el-search--ensure-sexp-start)
+                               (el-search--search-pattern pattern t))
+                      (end-of-buffer nil))))
                  (do-replace (lambda ()
                                (atomic-change-group
                                  (apply #'delete-region region)
@@ -1003,10 +1018,29 @@ Hit any key to proceed."
                                 nil)
                             ((or ?q ?\C-g)
                              (setq done t)
-                                t)
+                             t)
                             (?? (ignore (read-char el-search-search-and-replace-help-string))
                                 nil)))))
-            (unless (or done (eobp)) (el-search--skip-expression nil t)))))
+            (unless (or done (eobp))
+              (cond
+               ((not (and replaced-this replacement-contains-another-match))
+                (el-search--skip-expression nil t))
+               ((eq skip-matches-in-replacement 'ask)
+                (if (setq skip-matches-in-replacement
+                          (yes-or-no-p "Match in replacement - always skip? "))
+                    (forward-sexp)
+                  (el-search--skip-expression nil t)
+                  (when replace-all
+                    (setq replace-all nil)
+                    (message "Falling back to interactive mode")
+                    (sit-for 3.))))
+               (skip-matches-in-replacement (forward-sexp))
+               (t
+                (el-search--skip-expression nil t)
+                (message "Replacement contains another match%s"
+                         (if replace-all " - falling back to interactive mode" ""))
+                (setq replace-all nil)
+                (sit-for 3.)))))))
     (el-search-hl-remove)
     (goto-char opoint)
     (message "Replaced %d matches%s"
@@ -1014,21 +1048,28 @@ Hit any key to proceed."
              (if (zerop nbr-skipped)  ""
                (format "   (%d skipped)" nbr-skipped)))))
 
-(defun el-search-query-replace-read-args ()
+(defun el-search-query-replace--read-args ()
   (barf-if-buffer-read-only)
-  (let* ((from (el-search--read-pattern "Replace from: "))
+  (let* ((from (el-search--read-pattern "Query replace pattern: "))
          (to   (let ((el-search--initial-mb-contents nil))
                  (el-search--read-pattern "Replace with result of evaluation of: " from))))
     (list (el-search--wrap-pattern (read from)) (read to) to)))
 
 ;;;###autoload
-(defun el-search-query-replace (from to &optional to-input-string)
-  "Replace some occurrences of FROM pattern with evaluated TO."
-  (interactive (el-search-query-replace-read-args))
+(defun el-search-query-replace (from-pattern to-expr &optional textual-to)
+  "Replace some matches of \"el-search\" pattern FROM-PATTERN.
+
+TO-EXPR is an Elisp expression that is evaluated repeatedly for
+each match with bindings created in FROM-PATTERN in effect to
+produce a replacement expression.
+
+As each match is found, the user must type a character saying
+what to do with it.  For directions, type ? at that time."
+  (interactive (el-search-query-replace--read-args))
   (setq this-command 'el-search-query-replace) ;in case we come from isearch
-  (setq el-search-current-pattern from)
+  (setq el-search-current-pattern from-pattern)
   (barf-if-buffer-read-only)
-  (el-search-search-and-replace-pattern from to nil to-input-string))
+  (el-search--search-and-replace-pattern from-pattern to-expr nil textual-to))
 
 (defun el-search--take-over-from-isearch (&optional goto-left-end)
   (let ((other-end (and goto-left-end isearch-other-end))