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: Sat, 26 Feb 2022 17:13:31 +0100 Message-ID: <87mtidip1w.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> 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="23999"; 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 Sat Feb 26 17:14:15 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 1nNzi2-00061u-Q2 for geb-bug-gnu-emacs@m.gmane-mx.org; Sat, 26 Feb 2022 17:14:14 +0100 Original-Received: from localhost ([::1]:51354 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1nNzi1-0007PK-UR for geb-bug-gnu-emacs@m.gmane-mx.org; Sat, 26 Feb 2022 11:14:13 -0500 Original-Received: from eggs.gnu.org ([209.51.188.92]:51288) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1nNzhq-0007P7-BF for bug-gnu-emacs@gnu.org; Sat, 26 Feb 2022 11:14:02 -0500 Original-Received: from debbugs.gnu.org ([209.51.188.43]:34071) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1nNzhq-0005Eg-2l for bug-gnu-emacs@gnu.org; Sat, 26 Feb 2022 11:14:02 -0500 Original-Received: from Debian-debbugs by debbugs.gnu.org with local (Exim 4.84_2) (envelope-from ) id 1nNzhp-00010p-TB for bug-gnu-emacs@gnu.org; Sat, 26 Feb 2022 11:14:01 -0500 X-Loop: help-debbugs@gnu.org Resent-From: Augusto Stoffel Original-Sender: "Debbugs-submit" Resent-CC: bug-gnu-emacs@gnu.org Resent-Date: Sat, 26 Feb 2022 16:14:01 +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.16458920243856 (code B ref 53126); Sat, 26 Feb 2022 16:14:01 +0000 Original-Received: (at 53126) by debbugs.gnu.org; 26 Feb 2022 16:13:44 +0000 Original-Received: from localhost ([127.0.0.1]:56201 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1nNzhX-000102-Cs for submit@debbugs.gnu.org; Sat, 26 Feb 2022 11:13:44 -0500 Original-Received: from mail-ej1-f46.google.com ([209.85.218.46]:46726) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1nNzhU-0000zj-Dc for 53126@debbugs.gnu.org; Sat, 26 Feb 2022 11:13:41 -0500 Original-Received: by mail-ej1-f46.google.com with SMTP id qx21so16567727ejb.13 for <53126@debbugs.gnu.org>; Sat, 26 Feb 2022 08:13:40 -0800 (PST) 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=a9Qyv/kb6+ntgdfNYgnJKiAQsT0LeWGTWhFE1p227Js=; b=KUrV5epJfoDz7RR9Us1ZsZhhNptdxZSs3ekwze2mloZ+G/pMTNeOcAieAhgOHz71TA FFUPIux/begJIPdy+z3lMSAB1ZQk305kSNKaq9UW7Gl7SRErz7Kl+dVseqKL3aWIDRu+ d76gxCGiGTkXbqJx+pOyxgg0k0iPXOfFotRpGJ4C63kGkqG1ep9BNIWCE1COtSrJF7hm CisafuIyPbEutin18VHliFo74TVw4Bras74lt04Xl0AEApezceXGj/9cr6cDEczf0z3J hBoXwKjdP5CozopafAkHM+Qrpct/wBIsF6D4Az5dUbtptdAZ1d5yaZCM1AHhNPtbfygz 7K3A== 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=a9Qyv/kb6+ntgdfNYgnJKiAQsT0LeWGTWhFE1p227Js=; b=02+6/dptwLR2O+NTN5NUsIQHNwLQpD7WjyKnkZ0dAlU24wXtQnAgXG6FCaZBsAnHzR hYNJUMmD8omx7ihGhqGhJFsyzmm0XigFPPfcuxPx5QM/swfvHLWaFw6GjZreJaztLzwm qvGbcbbFh2h1HeoUhtFW54UzNCdMv64+T1apO/cslx4dU6qj3NNoGiLWQcS1RMcu2ita 2ZPdMA4ZwYlM9sgcZZJIp8Jd2fgdp9JN534JmV/6T7WAGtmNFrCgjS0uZuoFLSZa7xAI j2+5wL6g4xCvXCsZpvrHciUSJI4KVIXa9kzOhzNVm7x7iqiVZevxFJ50y87QOeSN4ukw 7RCQ== X-Gm-Message-State: AOAM532iRhQQTpX+6kkcT4b1h1OAtahiqS7HsWxJw9L7oawXTv6F1IAb tccLIHsiIJ3hK/uTedlK/EidyZG9EFg9YA== X-Google-Smtp-Source: ABdhPJzwoM4PpTwluMtVwuVXwlpqyRiJyVaMvnKzYKdOEhbGuSWIZHFl9aZQ1pCskdJJoP2ayquwLQ== X-Received: by 2002:a17:906:3588:b0:6a7:7ac1:cac8 with SMTP id o8-20020a170906358800b006a77ac1cac8mr9652694ejb.342.1645892014069; Sat, 26 Feb 2022 08:13:34 -0800 (PST) Original-Received: from ars3 ([2a02:8109:8ac0:56d0::758e]) by smtp.gmail.com with ESMTPSA id n13-20020a170906724d00b006cedd6d7e24sm2367283ejk.119.2022.02.26.08.13.32 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 26 Feb 2022 08:13:33 -0800 (PST) In-Reply-To: <8635lvif0r.fsf@mail.linkov.net> (Juri Linkov's message of "Mon, 10 Jan 2022 21:09:40 +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:227681 Archived-At: --=-=-= Content-Type: text/plain Hi Juri, 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. Inlined below some further comments. --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=0001-Display-lazy-highlight-and-match-count-in-when-readi.patch >From 87ad2a45293e2fa1796a39f368d9ec16c0c4c070 Mon Sep 17 00:00:00 2001 From: Augusto Stoffel Date: Sat, 8 Jan 2022 11:08:46 +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-from): Add lazy highlighting. (replace--region-filter): New function, extracted from 'perform-replace'. --- lisp/isearch.el | 84 +++++++++++++++++++++++++++++++++++++++++++++---- lisp/replace.el | 58 ++++++++++++++++++++++++---------- 2 files changed, 120 insertions(+), 22 deletions(-) diff --git a/lisp/isearch.el b/lisp/isearch.el index 8970216398..6076cc4b5e 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) @@ -3990,6 +3992,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 +4052,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 +4071,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 +4127,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 +4336,79 @@ 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 + 'after-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) + (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..bd5a694110 100644 --- a/lisp/replace.el +++ b/lisp/replace.el @@ -254,11 +254,31 @@ query-replace-read-from (query-replace-descr (cdar query-replace-defaults))))) (t (format-prompt prompt nil)))) + ;; Variables controlling lazy highlighting while reading + ;; FROM regexp, which is set up below. + (isearch-lazy-highlight query-replace-lazy-highlight) + (isearch-regexp regexp-flag) + (isearch-regexp-function nil) + (isearch-case-fold-search case-fold-search) ;; FIXME: the case-folding rule here is complicated... + (minibuffer-lazy-highlight-transform + (lambda (string) + (let ((from (query-replace--split-string string))) + (if (consp from) (car from) from)))) (from ;; The save-excursion here is in case the user marks and copies ;; a region in order to specify the minibuffer input. ;; That should not clobber the region for the query-replace itself. (save-excursion + (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)))) (minibuffer-with-setup-hook (lambda () (setq-local text-property-default-nonsticky @@ -2773,6 +2793,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 +2897,8 @@ 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)))) ;; If region is active, in Transient Mark mode, operate on region. (if backward -- 2.35.1 --=-=-= Content-Type: text/plain On Mon, 10 Jan 2022 at 21:09, Juri Linkov wrote: >> I attached a new patch (still a sketch) that requires no changes in >> comint.el and simple.el. Perhaps you will find this approach more >> acceptable. > > Thanks, no changes in other files is certainly a big plus. > >>> It would be great to use your new variable with a function >>> to show replacement counts in perform-replace. IIUC, >>> let-binding isearch-lazy-count-display-function to >>> isearch-read-with-highlight-count will suppress isearch-message? >> >> I tried this and it's relatively simple to do, but there is a problem. >> Suppose you want to replace all "a" with "z", and your buffer has 20 >> "a"s initially. Then, as you keep hitting "y" to confirm a replacement >> the count will be >> >> 1/20, 1/19, ..., 1/1 > > This is an interesting question. I tried in other editors, > and e.g. in the editor xed that is installed by default, > this is exactly what is displayed: 1/20, 1/19, ..., 1/1. Let's do this at a later point, to keep this patch smaller and more focused. Note that there is one further fancy feature from anzu that we could add eventually, namely the preview of the replacement text during replace. This was requested in some recent bug. I think it's not so often that such a thing would be useful, but it can be very handy occasionally. >> since the number of "a"s decrease, and the point is always at the first >> of the still-existing ones. But probably one should count the number of >> prompts, so >> >> 1/20, 2/20, ..., 20/20 >> >> I think this means `perform-replace' has to implement its own way to >> display a count. > > Maybe this makes more sense, when the users will learn > what do these numbers mean. > >>> I meant using simply >>> >>> (add-hook 'minibuffer-setup-hook 'isearch-read-with-highlight-setup) >>> >>> But it seems isearch-read-with-highlight-setup doesn't set >>> isearch-lazy-count-display-function. What used to be 'isearch-read-with-highlight-setup' is now 'minibuffer-lazy-highlight-setup'. Just to make sure I understood you: your suggestion is for this function to remove itself automagically from the minibuffer setup hook, to dispense with the need of 'minibuffer-with-setup-hook'? This seems handy but unusual, hence my question. >> I guess this could be done. > > Maybe two separate hooks could be defined? One highlights like > lazy-highlight, and another counts like lazy-count does: > > (add-hook 'minibuffer-setup-hook 'isearch-read-with-highlight-setup) > (add-hook 'minibuffer-setup-hook 'isearch-read-with-count-setup) The highlight without counting can be achieved by binding a suitable new variable. Counting without highlight is not supported by isearch AFAIU. --=-=-=--