From: Eshel Yaron Date: Fri, 12 Jul 2024 14:31:39 +0000 (+0200) Subject: Allow replacing 'search' matches via minibuffer alt action X-Git-Url: http://git.eshelyaron.com/gitweb/?a=commitdiff_plain;h=8fcbbf3965612ec5e8fec0fb17c957855792a271;p=emacs.git Allow replacing 'search' matches via minibuffer alt action --- diff --git a/lisp/search.el b/lisp/search.el index 84044cc0b20..366a2aaed07 100644 --- a/lisp/search.el +++ b/lisp/search.el @@ -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. @@ -39,25 +38,25 @@ (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) @@ -67,11 +66,11 @@ (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) @@ -79,76 +78,99 @@ (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