From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.io!.POSTED.blaine.gmane.org!not-for-mail From: Augusto Stoffel Newsgroups: gmane.emacs.bugs Subject: bug#53126: 29.0.50; [PATCH] Lazy highlight/count when reading query-replace string, etc. Date: Tue, 15 Mar 2022 22:21:12 +0100 Message-ID: <87ilseyks7.fsf@gmail.com> References: <87sftyweb2.fsf@gmail.com> <861r1iyrvw.fsf@mail.linkov.net> <87zgo6owaf.fsf@gmail.com> <86k0f9xnrn.fsf@mail.linkov.net> <87tuedp6pl.fsf@gmail.com> <861r1g7n3b.fsf@mail.linkov.net> <87o84jcx5x.fsf@gmail.com> <8635lvif0r.fsf@mail.linkov.net> <87mtidip1w.fsf@gmail.com> <86sfrjia5u.fsf@mail.linkov.net> Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" Injection-Info: ciao.gmane.io; posting-host="blaine.gmane.org:116.202.254.214"; logging-data="38020"; mail-complaints-to="usenet@ciao.gmane.io" User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/28.0.91 (gnu/linux) Cc: 53126@debbugs.gnu.org To: Juri Linkov Original-X-From: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane-mx.org@gnu.org Tue Mar 15 22:22:34 2022 Return-path: Envelope-to: geb-bug-gnu-emacs@m.gmane-mx.org Original-Received: from lists.gnu.org ([209.51.188.17]) by ciao.gmane.io with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1nUEck-0009jo-M6 for geb-bug-gnu-emacs@m.gmane-mx.org; Tue, 15 Mar 2022 22:22:34 +0100 Original-Received: from localhost ([::1]:56012 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1nUEcj-0002uA-OG for geb-bug-gnu-emacs@m.gmane-mx.org; Tue, 15 Mar 2022 17:22:33 -0400 Original-Received: from eggs.gnu.org ([209.51.188.92]:60304) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1nUEcH-0002jJ-PR for bug-gnu-emacs@gnu.org; Tue, 15 Mar 2022 17:22:06 -0400 Original-Received: from debbugs.gnu.org ([209.51.188.43]:55813) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1nUEcE-0006BT-Rc for bug-gnu-emacs@gnu.org; Tue, 15 Mar 2022 17:22:05 -0400 Original-Received: from Debian-debbugs by debbugs.gnu.org with local (Exim 4.84_2) (envelope-from ) id 1nUEcE-0003Gd-O2 for bug-gnu-emacs@gnu.org; Tue, 15 Mar 2022 17:22:02 -0400 X-Loop: help-debbugs@gnu.org Resent-From: Augusto Stoffel Original-Sender: "Debbugs-submit" Resent-CC: bug-gnu-emacs@gnu.org Resent-Date: Tue, 15 Mar 2022 21:22:02 +0000 Resent-Message-ID: Resent-Sender: help-debbugs@gnu.org X-GNU-PR-Message: followup 53126 X-GNU-PR-Package: emacs X-GNU-PR-Keywords: patch Original-Received: via spool by 53126-submit@debbugs.gnu.org id=B53126.164737928612496 (code B ref 53126); Tue, 15 Mar 2022 21:22:02 +0000 Original-Received: (at 53126) by debbugs.gnu.org; 15 Mar 2022 21:21:26 +0000 Original-Received: from localhost ([127.0.0.1]:49708 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1nUEbd-0003FT-AB for submit@debbugs.gnu.org; Tue, 15 Mar 2022 17:21:26 -0400 Original-Received: from mail-ej1-f50.google.com ([209.85.218.50]:40623) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1nUEbZ-0003FC-Ts for 53126@debbugs.gnu.org; Tue, 15 Mar 2022 17:21:23 -0400 Original-Received: by mail-ej1-f50.google.com with SMTP id p15so297266ejc.7 for <53126@debbugs.gnu.org>; Tue, 15 Mar 2022 14:21:21 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=from:to:cc:subject:references:date:in-reply-to:message-id :user-agent:mime-version; bh=Wxyht4IfTZ28TJAkCaZCWS2I2W1mpubXSCdAKrF0YQc=; b=RtqYTHRKvZNmFI4DCRoCV5GhpF1dA5FvoskavNKMAS5zS3UOTmStFF9mQxw6eLit3B MzayMK4/7CYsBJ8g3pGrthutywgxpSYVsOa7zExC2ocpx6oLL1OJhHel1r6SVtQwlDo1 kVtI82lEOl3McPggGoK+OR+G4dRoha8hTrb4exlSXb7i3O2Lx9wVF6LTwq6djd2HrChe zvR9J3DCHdK7e4bDDJx9EhSB1yXwEKyQvPmW4Pn0FJIQAQ9dExFZY6OLA5WzXBcuhNGd 0PT4MkplqrT6Ss0zJLcEHht1wKGMLq3iF6CYhiBchzZX/XbPa1hH1UD5Ysn1oaDj41LO rn4w== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:from:to:cc:subject:references:date:in-reply-to :message-id:user-agent:mime-version; bh=Wxyht4IfTZ28TJAkCaZCWS2I2W1mpubXSCdAKrF0YQc=; b=E4yYrq4cCQI01Rj6HBtJmxET5rc9vj8KqGPOqZAyfMCJSdo9EUqOlF2mxHB7WH+i20 rUDYNirLfANmMdq5M18kabMJNhZgZXmndTWPrTushIAY1g2Yj2lYcSKLquqkAFSgT393 az+HelYW8nItJoO+YCXf8tfJF0yzs9mmCVDpiu7+YA0GsHj85DsJGyrVfvH5AOxvcv3c Ql33q1JwY/spA+Y8TW5bcgIXYvP5Dob5GTep1Q8aeGFMDJnPirSXckAQbITkQ/abMZKo CTsbAvBRQB7VL/omQWN7oeAhTj4RLCd0cTbXNI+J+OQfBdn7BLHurs2jKJPk+t9ziSrk S/4g== X-Gm-Message-State: AOAM5335B1mTC9cQeEFSrAgVptz4CGXaJwY7nPKIsnJS2tHKTwADtr9B mAnakTIPUR0KIKvOZX4NbL2CPENmipg= X-Google-Smtp-Source: ABdhPJzvgjRFdy5Sg5oeWQKbRqIYZt83o8CcnoOi9fDxgU4mABMLyjZaXCv5l1KaEUIDZPRVd9enTQ== X-Received: by 2002:a17:906:16cc:b0:6ce:e607:ff02 with SMTP id t12-20020a17090616cc00b006cee607ff02mr23497777ejd.418.1647379275316; Tue, 15 Mar 2022 14:21:15 -0700 (PDT) Original-Received: from ars3 ([2a02:8109:8ac0:56d0::758e]) by smtp.gmail.com with ESMTPSA id f3-20020a1709067f8300b006ce051bf215sm60153ejr.192.2022.03.15.14.21.13 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 15 Mar 2022 14:21:14 -0700 (PDT) In-Reply-To: <86sfrjia5u.fsf@mail.linkov.net> (Juri Linkov's message of "Tue, 15 Mar 2022 19:24:01 +0200") X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list X-BeenThere: bug-gnu-emacs@gnu.org List-Id: "Bug reports for GNU Emacs, the Swiss army knife of text editors" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane-mx.org@gnu.org Original-Sender: "bug-gnu-emacs" Xref: news.gmane.io gmane.emacs.bugs:228422 Archived-At: --=-=-= Content-Type: text/plain On Tue, 15 Mar 2022 at 19:24, Juri Linkov wrote: >> Sorry for getting back to this after such a long time. I've attached a >> new patch that hopefully is good to merge, except for adding some NEWS >> entry. Let me know what you think. > > Please see comments for your latest patch below: > >> @@ -1812,6 +1812,8 @@ isearch-edit-string >> (minibuffer-history-symbol) >> ;; Search string might have meta information on text properties. >> (minibuffer-allow-text-properties t)) >> + (when isearch-lazy-highlight >> + (add-hook 'minibuffer-setup-hook #'minibuffer-lazy-highlight-setup)) > > Since this does both highlighting and counting, shouldn't the condition be > > (when (and isearch-lazy-highlight isearch-lazy-count)) > > Or maybe new separate customizable options are needed, e.g. > 'minibuffer-lazy-highlight' and 'minibuffer-lazy-count'? isearch-edit-string already has the behavior your expect: if isearch-lazy-count is nil but isearch-lazy-highlight is t, then the matches are highlighted but the count is not displayed. This happens because the new lazy-count-update-hook is only run when isearch-lazy-highlight is non-nil. I see no need for a customization variable, since the user already gets exactly what they asked for. >> @@ -2350,7 +2352,9 @@ isearch-query-replace >> (isearch-recursive-edit nil) >> (isearch-string-propertized >> (isearch-string-propertize isearch-string))) >> - (isearch-done nil t) >> + (let ((lazy-highlight-cleanup (and lazy-highlight-cleanup >> + (not query-replace-lazy-highlight)))) >> + (isearch-done nil t)) > > Is this some optimization? It seems it's intended to leave > some existing highlighting? Is this to avoid double highlighting? > > Also maybe this condition could use a new variable as well. The weird let-binding for lazy-highlight-cleanup is indeed an optimization. Without it, you can see the lazy highlights briefly flash off an on again after you hit RET to confirm the replacement string. (Same remark explains why condition-case is used instead of a unwind-protect in query-replace-read-args). >> @@ -4048,7 +4056,7 @@ isearch-lazy-highlight-new-loop >> isearch-lazy-highlight-window-end)))))) >> ;; something important did indeed change >> (lazy-highlight-cleanup t (not (equal isearch-string ""))) ;stop old timer >> - (when (and isearch-lazy-count isearch-mode (null isearch-message-function)) >> + (when isearch-lazy-count >> ... >> @@ -4067,7 +4075,10 @@ isearch-lazy-highlight-new-loop >> (setq isearch-lazy-count-current nil >> isearch-lazy-count-total nil) >> ;; Delay updating the message if possible, to avoid flicker >> - (when (string-equal isearch-string "") (isearch-message)))) >> + (when (string-equal isearch-string "") >> + (when (and isearch-mode (null isearch-message-function)) >> + (isearch-message)) >> ... >> @@ -4120,13 +4131,15 @@ isearch-lazy-highlight-new-loop >> 'isearch-lazy-highlight-start)))) >> ;; Update the current match number only in isearch-mode and >> ;; unless isearch-mode is used specially with isearch-message-function >> - (when (and isearch-lazy-count isearch-mode (null isearch-message-function)) >> + (when isearch-lazy-count > > The problem is that when these conditions 'isearch-mode (null isearch-message-function)' > are removed, now this shows wrong counts in the minibuffer history search > (e.g. 'M-! C-r s C-r C-r ...') and the shell history search > (e.g. 'M-x shell RET M-r s C-r C-r ...'). Before this change > counting was disabled in the history search because it shows wrong numbers. > Okay, so this means we should bind isearch-lazy-count to nil in these commands, do you agree? It has always looked like a hack to me to check for (null isearch-message-function). >> +(defun minibuffer-lazy-highlight--count () >> + "Display total match count in the minibuffer prompt." >> + (when minibuffer-lazy-highlight--overlay >> + (overlay-put minibuffer-lazy-highlight--overlay >> + 'after-string >> + (and isearch-lazy-count-total >> + (not isearch-error) >> + (format minibuffer-lazy-count-format >> + isearch-lazy-count-total))))) >> ... >> + (setq minibuffer-lazy-highlight--overlay >> + (and minibuffer-lazy-count-format >> + (make-overlay (point-min) (point-min) (current-buffer) t))) > > For some reasons the package lisp/mb-depth.el uses 'after-string' > instead of 'before-string', and (make-overlay (point-min) (1+ (point-min))) > instead of (make-overlay (point-min) (point-min)), > so maybe better to do the same? > Okay, but do you know the reasons? I've changed to before-string, but I don't like to make the overlay 1 char longer than it has to be :-P >> @@ -365,14 +372,44 @@ query-replace-read-args >> + (condition-case error >> + (let (;; Variables controlling lazy highlighting while reading >> + ;; FROM and TO. >> + (lazy-highlight-cleanup nil) >> + (isearch-lazy-highlight query-replace-lazy-highlight) >> + (isearch-regexp regexp-flag) >> + (isearch-regexp-function nil) > > Highlighting is still incorrect for word replacement ('C-u M-%') > and for non-nil 'replace-char-fold'. To handle these cases correctly, > 'replace-highlight' uses: > > (isearch-regexp-function (or replace-regexp-function > delimited-flag > (and replace-char-fold > (not regexp-flag) > #'char-fold-to-regexp))) Okay, fixed this. (BTW, where is replace-regexp-function used? It's not set anywhere in Emacs, and it's not a defcustom either.) >> @@ -2857,22 +2914,8 @@ perform-replace >> (when region-noncontiguous-p >> - (let ((region-bounds >> - (mapcar (lambda (position) >> - (cons (copy-marker (car position)) >> - (copy-marker (cdr position)))) >> - (funcall region-extract-function 'bounds)))) >> - (setq region-filter >> - (lambda (start end) >> - (delq nil (mapcar >> - (lambda (bounds) >> - (and >> - (>= start (car bounds)) >> - (<= start (cdr bounds)) >> - (>= end (car bounds)) >> - (<= end (cdr bounds)))) >> - region-bounds)))) >> - (add-function :after-while isearch-filter-predicate region-filter))) >> + (setq region-filter (replace--region-filter >> + (funcall region-extract-function 'bounds)))) > > I wonder why (add-function :after-while isearch-filter-predicate region-filter) > is removed? Oops, that was a very serious typo. I've fixed it now. Here are the changes I applied on top of my previous patch: --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=0001-Changes-after-Juri-s-comments.patch >From 216c32ad2f74abf08ea10eb2b11a5c93a146fbe1 Mon Sep 17 00:00:00 2001 From: Augusto Stoffel Date: Tue, 15 Mar 2022 22:11:36 +0100 Subject: [PATCH] Changes after Juri's comments * lisp/comint.el (comint-history-isearch-setup, comint-history-isearch-end): Make sure no lazy count is displayed. * lisp/simple.el (minibuffer-history-isearch-setup): Make sure no lazy count is displayed. --- lisp/comint.el | 2 ++ lisp/isearch.el | 2 +- lisp/replace.el | 10 ++++++++-- lisp/simple.el | 1 + 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/lisp/comint.el b/lisp/comint.el index 4c82e74e4b..56082f622a 100644 --- a/lisp/comint.el +++ b/lisp/comint.el @@ -1515,6 +1515,7 @@ comint-history-isearch-setup #'comint-history-isearch-wrap) (setq-local isearch-push-state-function #'comint-history-isearch-push-state) + (setq-local isearch-lazy-count nil) (add-hook 'isearch-mode-end-hook 'comint-history-isearch-end nil t))) (defun comint-history-isearch-end () @@ -1526,6 +1527,7 @@ comint-history-isearch-end (setq isearch-message-function nil) (setq isearch-wrap-function nil) (setq isearch-push-state-function nil) + (kill-local-variable 'isearch-lazy-count) (remove-hook 'isearch-mode-end-hook 'comint-history-isearch-end t) (unless isearch-suspended (custom-reevaluate-setting 'comint-history-isearch))) diff --git a/lisp/isearch.el b/lisp/isearch.el index e8e3218256..bd337f38b7 100644 --- a/lisp/isearch.el +++ b/lisp/isearch.el @@ -4372,7 +4372,7 @@ minibuffer-lazy-highlight--count "Display total match count in the minibuffer prompt." (when minibuffer-lazy-highlight--overlay (overlay-put minibuffer-lazy-highlight--overlay - 'after-string + 'before-string (and isearch-lazy-count-total (not isearch-error) (format minibuffer-lazy-count-format diff --git a/lisp/replace.el b/lisp/replace.el index 3e1be6f940..f45fb5fb25 100644 --- a/lisp/replace.el +++ b/lisp/replace.el @@ -378,7 +378,12 @@ query-replace-read-args (lazy-highlight-cleanup nil) (isearch-lazy-highlight query-replace-lazy-highlight) (isearch-regexp regexp-flag) - (isearch-regexp-function nil) + (isearch-regexp-function (or replace-regexp-function + (and current-prefix-arg + (not (eq current-prefix-arg '-))) + (and replace-char-fold + (not regexp-flag) + #'char-fold-to-regexp))) (isearch-case-fold-search case-fold-search) (minibuffer-lazy-highlight-transform (lambda (string) @@ -2915,7 +2920,8 @@ perform-replace ;; Unless a single contiguous chunk is selected, operate on multiple chunks. (when region-noncontiguous-p (setq region-filter (replace--region-filter - (funcall region-extract-function 'bounds)))) + (funcall region-extract-function 'bounds))) + (add-function :after-while isearch-filter-predicate region-filter)) ;; If region is active, in Transient Mark mode, operate on region. (if backward diff --git a/lisp/simple.el b/lisp/simple.el index accc119e2b..61319b6060 100644 --- a/lisp/simple.el +++ b/lisp/simple.el @@ -2840,6 +2840,7 @@ minibuffer-history-isearch-setup #'minibuffer-history-isearch-wrap) (setq-local isearch-push-state-function #'minibuffer-history-isearch-push-state) + (setq-local isearch-lazy-count nil) (add-hook 'isearch-mode-end-hook 'minibuffer-history-isearch-end nil t)) (defun minibuffer-history-isearch-end () -- 2.35.1 --=-=-= Content-Type: text/plain And this is the aggregated patch for the entire feature: --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=0001-Display-lazy-highlight-and-match-count-in-when-readi.patch >From 3e08f33fe45e41419278a614f7edb4f184ca9f5b Mon Sep 17 00:00:00 2001 From: Augusto Stoffel Date: Sat, 12 Mar 2022 10:43:54 +0100 Subject: [PATCH] Display lazy highlight and match count in when reading regexps * lisp/isearch.el (lazy-count-update-hook): New hook allowing to display the lazy count in special ways. (isearch-edit-string): Add lazy highlight and count of matching text. (isearch-lazy-highlight-new-loop, isearch-lazy-highlight-buffer-update): Use `isearch-lazy-count-display-function' instead of hardcoded call to `isearch-message'. (minibuffer-lazy-count-format, minibuffer-lazy-highlight-transform, minibuffer-lazy-highlight--overlay, minibuffer-lazy-highlight--count, minibuffer-lazy-highlight--after-change, minibuffer-lazy-highlight--exit, minibuffer-lazy-highlight-setup): Variables and functions implementing the lazy highlight functionality while reading from minibuffer. * lisp/replace.el (query-replace-read-args): Add lazy highlighting. (replace--region-filter): New function, extracted from 'perform-replace'. (perform-replace): Use 'replace--region-filter'. * lisp/comint.el (comint-history-isearch-setup, comint-history-isearch-end): Make sure no lazy count is displayed. * lisp/simple.el (minibuffer-history-isearch-setup): Make sure no lazy count is displayed. --- lisp/comint.el | 2 + lisp/isearch.el | 106 +++++++++++++++++++++++++++++++++++++++++------- lisp/replace.el | 99 ++++++++++++++++++++++++++++++++------------ lisp/simple.el | 1 + 4 files changed, 169 insertions(+), 39 deletions(-) diff --git a/lisp/comint.el b/lisp/comint.el index 4c82e74e4b..56082f622a 100644 --- a/lisp/comint.el +++ b/lisp/comint.el @@ -1515,6 +1515,7 @@ comint-history-isearch-setup #'comint-history-isearch-wrap) (setq-local isearch-push-state-function #'comint-history-isearch-push-state) + (setq-local isearch-lazy-count nil) (add-hook 'isearch-mode-end-hook 'comint-history-isearch-end nil t))) (defun comint-history-isearch-end () @@ -1526,6 +1527,7 @@ comint-history-isearch-end (setq isearch-message-function nil) (setq isearch-wrap-function nil) (setq isearch-push-state-function nil) + (kill-local-variable 'isearch-lazy-count) (remove-hook 'isearch-mode-end-hook 'comint-history-isearch-end t) (unless isearch-suspended (custom-reevaluate-setting 'comint-history-isearch))) diff --git a/lisp/isearch.el b/lisp/isearch.el index 8970216398..bd337f38b7 100644 --- a/lisp/isearch.el +++ b/lisp/isearch.el @@ -1812,6 +1812,8 @@ isearch-edit-string (minibuffer-history-symbol) ;; Search string might have meta information on text properties. (minibuffer-allow-text-properties t)) + (when isearch-lazy-highlight + (add-hook 'minibuffer-setup-hook #'minibuffer-lazy-highlight-setup)) (setq isearch-new-string (read-from-minibuffer (isearch-message-prefix nil isearch-nonincremental) @@ -2350,7 +2352,9 @@ isearch-query-replace (isearch-recursive-edit nil) (isearch-string-propertized (isearch-string-propertize isearch-string))) - (isearch-done nil t) + (let ((lazy-highlight-cleanup (and lazy-highlight-cleanup + (not query-replace-lazy-highlight)))) + (isearch-done nil t)) (isearch-clean-overlays) (if (and isearch-other-end (if backward @@ -2366,13 +2370,15 @@ isearch-query-replace (symbol-value query-replace-from-history-variable))) (perform-replace isearch-string-propertized - (query-replace-read-to - isearch-string-propertized - (concat "Query replace" - (isearch--describe-regexp-mode (or delimited isearch-regexp-function) t) - (if backward " backward" "") - (if (use-region-p) " in region" "")) - isearch-regexp) + (unwind-protect + (query-replace-read-to + isearch-string-propertized + (concat "Query replace" + (isearch--describe-regexp-mode (or delimited isearch-regexp-function) t) + (if backward " backward" "") + (if (use-region-p) " in region" "")) + isearch-regexp) + (lazy-highlight-cleanup lazy-highlight-cleanup)) t isearch-regexp (or delimited isearch-regexp-function) nil nil (if (use-region-p) (region-beginning)) (if (use-region-p) (region-end)) @@ -3990,6 +3996,8 @@ isearch-lazy-highlight-error (defvar isearch-lazy-count-current nil) (defvar isearch-lazy-count-total nil) (defvar isearch-lazy-count-hash (make-hash-table)) +(defvar lazy-count-update-hook nil + "Hook run after new lazy count results are computed.") (defun lazy-highlight-cleanup (&optional force procrastinate) "Stop lazy highlighting and remove extra highlighting from current buffer. @@ -4048,7 +4056,7 @@ isearch-lazy-highlight-new-loop isearch-lazy-highlight-window-end)))))) ;; something important did indeed change (lazy-highlight-cleanup t (not (equal isearch-string ""))) ;stop old timer - (when (and isearch-lazy-count isearch-mode (null isearch-message-function)) + (when isearch-lazy-count (when (or (equal isearch-string "") ;; Check if this place was reached by a condition above ;; other than changed window boundaries (that shouldn't @@ -4067,7 +4075,10 @@ isearch-lazy-highlight-new-loop (setq isearch-lazy-count-current nil isearch-lazy-count-total nil) ;; Delay updating the message if possible, to avoid flicker - (when (string-equal isearch-string "") (isearch-message)))) + (when (string-equal isearch-string "") + (when (and isearch-mode (null isearch-message-function)) + (isearch-message)) + (run-hooks 'lazy-count-update-hook)))) (setq isearch-lazy-highlight-window-start-changed nil) (setq isearch-lazy-highlight-window-end-changed nil) (setq isearch-lazy-highlight-error isearch-error) @@ -4120,13 +4131,15 @@ isearch-lazy-highlight-new-loop 'isearch-lazy-highlight-start)))) ;; Update the current match number only in isearch-mode and ;; unless isearch-mode is used specially with isearch-message-function - (when (and isearch-lazy-count isearch-mode (null isearch-message-function)) + (when isearch-lazy-count ;; Update isearch-lazy-count-current only when it was already set ;; at the end of isearch-lazy-highlight-buffer-update (when isearch-lazy-count-current (setq isearch-lazy-count-current (gethash (point) isearch-lazy-count-hash 0)) - (isearch-message)))) + (when (and isearch-mode (null isearch-message-function)) + (isearch-message)) + (run-hooks 'lazy-count-update-hook)))) (defun isearch-lazy-highlight-search (string bound) "Search ahead for the next or previous match, for lazy highlighting. @@ -4327,16 +4340,81 @@ isearch-lazy-highlight-buffer-update (setq looping nil nomore t)))) (if nomore - (when (and isearch-lazy-count isearch-mode (null isearch-message-function)) + (when isearch-lazy-count (unless isearch-lazy-count-total (setq isearch-lazy-count-total 0)) (setq isearch-lazy-count-current (gethash opoint isearch-lazy-count-hash 0)) - (isearch-message)) + (when (and isearch-mode (null isearch-message-function)) + (isearch-message)) + (run-hooks 'lazy-count-update-hook)) (setq isearch-lazy-highlight-timer (run-at-time lazy-highlight-interval nil 'isearch-lazy-highlight-buffer-update))))))))) + +;; Reading from minibuffer with lazy highlight and match count + +(defcustom minibuffer-lazy-count-format "%s " + "Format of the total number of matches for the prompt prefix." + :type '(choice (const :tag "Don't display a count" nil) + (string :tag "Display match count" "%s ")) + :group 'lazy-count + :version "29.1") + +(defvar minibuffer-lazy-highlight-transform #'identity + "Function to transform minibuffer text into a `isearch-string' for highlighting.") + +(defvar minibuffer-lazy-highlight--overlay nil + "Overlay for minibuffer prompt updates.") + +(defun minibuffer-lazy-highlight--count () + "Display total match count in the minibuffer prompt." + (when minibuffer-lazy-highlight--overlay + (overlay-put minibuffer-lazy-highlight--overlay + 'before-string + (and isearch-lazy-count-total + (not isearch-error) + (format minibuffer-lazy-count-format + isearch-lazy-count-total))))) + +(defun minibuffer-lazy-highlight--after-change (_beg _end _len) + "Update lazy highlight state in minibuffer selected window." + (when isearch-lazy-highlight + (let ((inhibit-redisplay t) ;; Avoid cursor flickering + (string (minibuffer-contents))) + (with-minibuffer-selected-window + (setq isearch-string (funcall minibuffer-lazy-highlight-transform string)) + (isearch-lazy-highlight-new-loop))))) + +(defun minibuffer-lazy-highlight--exit () + "Unwind changes from `minibuffer-lazy-highlight-setup'." + (remove-hook 'after-change-functions + #'minibuffer-lazy-highlight--after-change) + (remove-hook 'lazy-count-update-hook #'minibuffer-lazy-highlight--count) + (remove-hook 'minibuffer-exit-hook #'minibuffer-lazy-highlight--exit) + (setq minibuffer-lazy-highlight--overlay nil) + (when lazy-highlight-cleanup + (lazy-highlight-cleanup))) + +(defun minibuffer-lazy-highlight-setup () + "Set up minibuffer for lazy highlight of matches in the original window. + +This function is intended to be added to `minibuffer-setup-hook'. +Note that several other isearch variables influence the lazy +highlighting, including `isearch-regexp', +`isearch-lazy-highlight' and `isearch-lazy-count'." + (remove-hook 'minibuffer-setup-hook #'minibuffer-lazy-highlight-setup) + (add-hook 'after-change-functions + #'minibuffer-lazy-highlight--after-change) + (add-hook 'lazy-count-update-hook #'minibuffer-lazy-highlight--count) + (add-hook 'minibuffer-exit-hook #'minibuffer-lazy-highlight--exit) + (setq minibuffer-lazy-highlight--overlay + (and minibuffer-lazy-count-format + (make-overlay (point-min) (point-min) (current-buffer) t))) + (minibuffer-lazy-highlight--after-change nil nil nil)) + + (defun isearch-resume (string regexp word forward message case-fold) "Resume an incremental search. STRING is the string or regexp searched for. diff --git a/lisp/replace.el b/lisp/replace.el index 06be597855..f45fb5fb25 100644 --- a/lisp/replace.el +++ b/lisp/replace.el @@ -352,8 +352,15 @@ query-replace-read-to (query-replace-compile-replacement (save-excursion (let* ((history-add-new-input nil) + (count (if (and query-replace-lazy-highlight + minibuffer-lazy-count-format + isearch-lazy-count + isearch-lazy-count-total) + (format minibuffer-lazy-count-format + isearch-lazy-count-total) + "")) (to (read-from-minibuffer - (format "%s %s with: " prompt (query-replace-descr from)) + (format "%s%s %s with: " count prompt (query-replace-descr from)) nil nil nil query-replace-to-history-variable from t))) (add-to-history query-replace-to-history-variable to nil t) @@ -365,14 +372,49 @@ query-replace-read-args (unless noerror (barf-if-buffer-read-only)) (save-mark-and-excursion - (let* ((from (query-replace-read-from prompt regexp-flag)) - (to (if (consp from) (prog1 (cdr from) (setq from (car from))) - (query-replace-read-to from prompt regexp-flag)))) - (list from to - (or (and current-prefix-arg (not (eq current-prefix-arg '-))) - (and (plist-member (text-properties-at 0 from) 'isearch-regexp-function) - (get-text-property 0 'isearch-regexp-function from))) - (and current-prefix-arg (eq current-prefix-arg '-)))))) + (condition-case error + (let (;; Variables controlling lazy highlighting while reading + ;; FROM and TO. + (lazy-highlight-cleanup nil) + (isearch-lazy-highlight query-replace-lazy-highlight) + (isearch-regexp regexp-flag) + (isearch-regexp-function (or replace-regexp-function + (and current-prefix-arg + (not (eq current-prefix-arg '-))) + (and replace-char-fold + (not regexp-flag) + #'char-fold-to-regexp))) + (isearch-case-fold-search case-fold-search) + (minibuffer-lazy-highlight-transform + (lambda (string) + (let* ((split (query-replace--split-string string)) + (from-string (if (consp split) (car split) split))) + (when (and case-fold-search search-upper-case) + (setq isearch-case-fold-search + (isearch-no-upper-case-p from-string regexp-flag))) + from-string))) + from to) + (when query-replace-lazy-highlight + (add-hook 'minibuffer-setup-hook #'minibuffer-lazy-highlight-setup) + (when (use-region-p) + (letrec ((region-filter (replace--region-filter + (funcall region-extract-function 'bounds))) + (cleanup (lambda () + (remove-function isearch-filter-predicate region-filter) + (remove-hook 'minibuffer-exit-hook cleanup)))) + (add-function :after-while isearch-filter-predicate region-filter) + (add-hook 'minibuffer-exit-hook cleanup)))) + (setq from (query-replace-read-from prompt regexp-flag)) + (setq to (if (consp from) + (prog1 (cdr from) (setq from (car from))) + (query-replace-read-to from prompt regexp-flag))) + (list from to + (or (and current-prefix-arg (not (eq current-prefix-arg '-))) + (and (plist-member (text-properties-at 0 from) 'isearch-regexp-function) + (get-text-property 0 'isearch-regexp-function from))) + (and current-prefix-arg (eq current-prefix-arg '-)))) + (t (lazy-highlight-cleanup) + (signal (car error) (cdr error)))))) (defun query-replace (from-string to-string &optional delimited start end backward region-noncontiguous-p) "Replace some occurrences of FROM-STRING with TO-STRING. @@ -2773,6 +2815,26 @@ replace--push-stack ,search-str ,next-replace) ,stack)) +(defun replace--region-filter (bounds) + "Return a function that decides if a region is inside BOUNDS. +BOUNDS is a list of cons cells of the form (START . END). The +returned function takes as argument two buffer positions, START +and END." + (let ((region-bounds + (mapcar (lambda (position) + (cons (copy-marker (car position)) + (copy-marker (cdr position)))) + bounds))) + (lambda (start end) + (delq nil (mapcar + (lambda (bounds) + (and + (>= start (car bounds)) + (<= start (cdr bounds)) + (>= end (car bounds)) + (<= end (cdr bounds)))) + region-bounds))))) + (defun perform-replace (from-string replacements query-flag regexp-flag delimited-flag &optional repeat-count map start end backward region-noncontiguous-p) @@ -2857,22 +2919,9 @@ perform-replace ;; Unless a single contiguous chunk is selected, operate on multiple chunks. (when region-noncontiguous-p - (let ((region-bounds - (mapcar (lambda (position) - (cons (copy-marker (car position)) - (copy-marker (cdr position)))) - (funcall region-extract-function 'bounds)))) - (setq region-filter - (lambda (start end) - (delq nil (mapcar - (lambda (bounds) - (and - (>= start (car bounds)) - (<= start (cdr bounds)) - (>= end (car bounds)) - (<= end (cdr bounds)))) - region-bounds)))) - (add-function :after-while isearch-filter-predicate region-filter))) + (setq region-filter (replace--region-filter + (funcall region-extract-function 'bounds))) + (add-function :after-while isearch-filter-predicate region-filter)) ;; If region is active, in Transient Mark mode, operate on region. (if backward diff --git a/lisp/simple.el b/lisp/simple.el index accc119e2b..61319b6060 100644 --- a/lisp/simple.el +++ b/lisp/simple.el @@ -2840,6 +2840,7 @@ minibuffer-history-isearch-setup #'minibuffer-history-isearch-wrap) (setq-local isearch-push-state-function #'minibuffer-history-isearch-push-state) + (setq-local isearch-lazy-count nil) (add-hook 'isearch-mode-end-hook 'minibuffer-history-isearch-end nil t)) (defun minibuffer-history-isearch-end () -- 2.35.1 --=-=-=--