]> git.eshelyaron.com Git - emacs.git/commitdiff
Allow replacing 'search' matches via minibuffer alt action
authorEshel Yaron <me@eshelyaron.com>
Fri, 12 Jul 2024 14:31:39 +0000 (16:31 +0200)
committerEshel Yaron <me@eshelyaron.com>
Fri, 12 Jul 2024 17:52:32 +0000 (19:52 +0200)
lisp/search.el

index 84044cc0b20a7d0984c179bc869a7506645c2a38..366a2aaed072168309e527f456358606669887a1 100644 (file)
@@ -27,7 +27,6 @@
 
 ;; - Support multi-buffer `search'.
 ;; - Add regexp completion style and use it for `search' completion.
-;; - Add replace support.
 ;; - Isearch-to-search and vice versa.
 ;; - Search non-matches.
 ;; - Highlight subgroups in matches.
 
 (defface search-highlight
   '((t :inherit highlight :foreground "black"))
-  "Foo.")
-
-(defun search--go-to (target)
-  "Go to TARGET."
-  (let ((pos (string-to-number target)))
-    (unless (zerop pos) (goto-char pos))))
+  "Face for highlighting matches for current input during \\[search].")
 
 (defun search-read-target (&optional beg end re-or-fn)
   "Prompt for \\[search] target between BEG and END matching RE-OR-FN."
   (let* ((buffer (current-buffer))
          (beg (or beg (point-min)))
          (end (or end (point-max)))
-         (sfn (if (functionp re-or-fn) re-or-fn
-                (let ((r (or re-or-fn (read-regexp "Search regular expression"))))
-                  (lambda () (re-search-forward r end t)))))
+         (reg nil)
+         (sfn (if (functionp re-or-fn)
+                  (prog1 re-or-fn (setq reg "match"))
+                (setq reg (or re-or-fn (read-regexp "Search regular expression")))
+                (lambda () (re-search-forward reg end t))))
          (ovs nil)
          (ovz nil)
          (cur nil)
-         (trs nil))
+         (trs nil)
+         (tab (make-hash-table))
+         (ind 0)
+         (rep nil))
     (deactivate-mark)
     (save-excursion
       (goto-char beg)
               (setq done t)
             (if (<= (point) pos)
                 (forward-char)
-              (push (format "%d:%d:%s"
-                            (match-beginning 0)
-                            (match-end 0)
-                            (match-string 0))
-                    trs)
+              (push (format "%d:%s" (cl-incf ind) (match-string 0)) trs)
+              (puthash ind
+                       (list (copy-marker (match-beginning 0))
+                             (copy-marker (match-end 0)))
+                       tab)
               (push (make-overlay (match-beginning 0)
                                   (match-end 0))
                     ovs)
               (overlay-put (car ovs) 'search t)
               (overlay-put (car ovs) 'priority '(nil . 1)))
             (setq pos (point))))))
+    (setq trs (nreverse trs))
     (unwind-protect
-        (progn
-          (minibuffer-with-setup-hook
-              (lambda ()
-                (setq minibuffer-action
-                      (cons
-                       (lambda (c)
-                         (with-selected-window (minibuffer-selected-window)
-                           (unless (search--go-to c) (user-error "Invalid search target"))
-                           (when (overlayp cur) (overlay-put cur 'face 'lazy-highlight))
-                           (setq cur (seq-some
-                                      (lambda (ov) (and (overlay-get ov 'search) ov))
-                                      (overlays-at (point))))
-                           (overlay-put cur 'face 'isearch)))
-                       "search"))
-                (let ((hook-fn
-                       (lambda (input)
-                         (unless (string-empty-p input)
-                           (mapc #'delete-overlay ovz)
-                           (setq ovz nil)
-                           (with-current-buffer buffer
-                             (dolist (ov ovs)
-                               (save-excursion
-                                 (goto-char (overlay-start ov))
-                                 (let ((r (regexp-quote input))
-                                       (e (overlay-end ov)))
-                                   (while (re-search-forward r e t)
-                                     (push (make-overlay (match-beginning 0)
-                                                         (match-end 0))
-                                           ovz)
-                                     (overlay-put (car ovz) 'face 'search-highlight)
-                                     (overlay-put (car ovz) 'search-input t)
-                                     (overlay-put (car ovz) 'priority '(nil . 10)))))))))))
-                  (add-hook 'minibuffer-new-completion-input-hook
-                            (lambda () (funcall hook-fn (caar completion-history)))
-                            nil t)
-                  (add-hook 'completion-setup-hook
-                            (lambda () (funcall hook-fn (minibuffer-contents)))
-                            nil t)))
-            (completing-read
-             "Search: "
-             (completion-table-with-metadata
-              (nreverse trs)
-              `((category . search)
-                (group-function
-                 . ,(lambda (string &optional transform)
-                      (when transform (nth 2 (string-split string ":"))))))))))
+        (minibuffer-with-setup-hook
+            (lambda ()
+              (setq minibuffer-action
+                    (cons
+                     (lambda (c)
+                       (with-selected-window
+                           (or (get-buffer-window buffer) (display-buffer buffer))
+                         (goto-char (car (gethash (string-to-number c) tab)))
+                         (when (overlayp cur) (overlay-put cur 'face 'lazy-highlight))
+                         (setq cur (seq-some
+                                    (lambda (ov) (and (overlay-get ov 'search) ov))
+                                    (overlays-at (point))))
+                         (overlay-put cur 'face 'isearch)))
+                     "search"))
+              (setq minibuffer-alternative-action
+                    (cons
+                     (lambda (c)
+                       (if-let ((n (string-to-number c))
+                                (d (gethash n tab)))
+                           (with-selected-window
+                               (or (get-buffer-window buffer) (display-buffer buffer))
+                             (when (overlayp cur) (overlay-put cur 'face 'lazy-highlight))
+                             (set-match-data d)
+                             (let ((ov (seq-some
+                                        (lambda (ov) (and (overlay-get ov 'search) ov))
+                                        (overlays-at (match-beginning 0)))))
+                               (unless rep
+                                 (overlay-put ov 'face 'isearch)
+                                 (goto-char (match-beginning 0))
+                                 (setq rep (query-replace-read-to reg "Replace" t)))
+                               (setq ovs (delq ov ovs))
+                               (delete-overlay ov))
+                             (setq trs (delete c trs))
+                             (remhash n tab)
+                             (replace-match rep))
+                         (user-error "Already replaced")))
+                     "replace"))
+              (let ((hook-fn
+                     (lambda (input)
+                       (unless (string-empty-p input)
+                         (mapc #'delete-overlay ovz)
+                         (setq ovz nil)
+                         (with-current-buffer buffer
+                           (dolist (ov ovs)
+                             (save-excursion
+                               (goto-char (overlay-start ov))
+                               (let ((r (regexp-quote input))
+                                     (e (overlay-end ov)))
+                                 (while (re-search-forward r e t)
+                                   (push (make-overlay (match-beginning 0)
+                                                       (match-end 0))
+                                         ovz)
+                                   (overlay-put (car ovz) 'face 'search-highlight)
+                                   (overlay-put (car ovz) 'search-input t)
+                                   (overlay-put (car ovz) 'priority '(nil . 10))
+                                   (overlay-put (car ovz) 'evaporate t))))))))))
+                (add-hook 'minibuffer-new-completion-input-hook
+                          (lambda () (funcall hook-fn (caar completion-history)))
+                          nil t)
+                (add-hook 'completion-setup-hook
+                          (lambda () (funcall hook-fn (minibuffer-contents)))
+                          nil t)))
+          (gethash (string-to-number
+                    (completing-read
+                     "Search: "
+                     (completion-table-with-metadata
+                      (completion-table-dynamic (lambda (_) trs))
+                      `((category . search)
+                        (group-function
+                         . ,(lambda (string &optional transform)
+                              (when transform (nth 1 (string-split string ":")))))))
+                     nil t))
+                   tab))
       (mapc #'delete-overlay ovs)
       (mapc #'delete-overlay ovz))))
 
 ;;;###autoload
-(defun search (target)
-  "Search for TARGET."
+(defun search (beg end)
+  "Go to and pulse region starting at BEG and ending at END."
   (interactive
-   (list
-    (save-excursion (search-read-target (use-region-beginning) (use-region-end)))))
+   (save-excursion (search-read-target (use-region-beginning) (use-region-end))))
   (push-mark)
-  (unless (search--go-to target) (user-error "Invalid search target"))
-  (seq-let (beg end _) (split-string target ":")
-    (pulse-momentary-highlight-region (string-to-number beg)
-                                      (string-to-number end)
-                                      'isearch)))
+  (goto-char beg)
+  (pulse-momentary-highlight-region beg end 'isearch))
 
 ;;;###autoload
-(defun search-lines (target)
-  "Search for TARGET line."
+(defun search-lines (beg end)
+  "Go to and pulse line starting at BEG and ending at END."
   (interactive
-   (list (save-excursion
-           (search-read-target (use-region-beginning) (use-region-end) ".*"))))
-  (search target))
+   (save-excursion (search-read-target (use-region-beginning) (use-region-end) ".*")))
+  (search beg end))
 
 (provide 'search)
 ;;; refactor.el ends here