From: Ihor Radchenko <yantar92@gmail.com>
To: Stefan Monnier <monnier@iro.umontreal.ca>
Cc: Eli Zaretskii <eliz@gnu.org>,
Jeff Norden <jnorden@math.tntech.edu>,
emacs-devel@gnu.org
Subject: Re: A question about overlays and performance.
Date: Mon, 27 Jul 2020 19:20:30 +0800 [thread overview]
Message-ID: <87tuxttda9.fsf@localhost> (raw)
In-Reply-To: <jwvft9el4n8.fsf-monnier+emacs@gnu.org>
[-- Attachment #1: Type: text/plain, Size: 755 bytes --]
> The recommending way to avoid this problem is to provide a patch which
> extends Isearch's handling of invisible text to text-properties.
> There's no reason to presume it would be hard to do.
See the attached.
> And the way I recommend to avoid this problem is to stay clear of
> indirect buffers (which I consider as an attractive nuisance).
It is not always possible. I am currently working on re-implementing
org-mode folding from using overlays to text-properties. However, it is
not possible to stay clear of indirect buffers in org-mode. Many
user packages and core code depend on indirect buffer functionality
(including different folding state). I will have to go ahead using my
"hacky" solution with char-property-alias-alist.
Best,
Ihor
[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: iseach-text-properties.patch --]
[-- Type: text/x-diff, Size: 12399 bytes --]
diff --git a/lisp/isearch.el b/lisp/isearch.el
index 81e83d7950..e87f7f6756 100644
--- a/lisp/isearch.el
+++ b/lisp/isearch.el
@@ -145,6 +145,9 @@ search-invisible
has a non-nil property `isearch-open-invisible', then incremental
search will show the hidden text. (This applies when using `outline.el'
and `hideshow.el'.)
+When the value is `open-all', consider text made invisible by text
+property as well. Text property `isearch-open-invisible' will be
+considered in the same way as for overlays.
To temporarily change the value for an active incremental search,
use \\<isearch-mode-map>\\[isearch-toggle-invisible].
@@ -942,6 +945,7 @@ isearch-new-forward
;; Accumulate here the overlays opened during searching.
(defvar isearch-opened-overlays nil)
+(defvar isearch-opened-regions nil)
;; Non-nil if the string exists but is invisible.
(defvar isearch-hidden nil)
@@ -1218,6 +1222,7 @@ isearch-mode
isearch-opoint (point)
search-ring-yank-pointer nil
isearch-opened-overlays nil
+ isearch-opened-regions nil
isearch-input-method-function input-method-function
isearch-input-method-local-p (local-variable-p 'input-method-function)
regexp-search-ring-yank-pointer nil
@@ -1551,6 +1556,7 @@ isearch-exit
(let ((isearch-nonincremental t))
(isearch-edit-string)) ;; this calls isearch-done as well
(isearch-done))
+ (isearch-clean-regions)
(isearch-clean-overlays))
(defun isearch-fail-pos (&optional msg)
@@ -1807,6 +1813,7 @@ isearch-cancel
(isearch--set-state (car isearch-cmds)))
(goto-char isearch-opoint))
(isearch-done t) ; Exit isearch..
+ (isearch-clean-regions)
(isearch-clean-overlays)
(signal 'quit nil)) ; ..and pass on quit signal.
@@ -2271,6 +2278,7 @@ isearch-query-replace
(isearch-string-propertized
(isearch-string-propertize isearch-string)))
(isearch-done nil t)
+ (isearch-clean-regions)
(isearch-clean-overlays)
(if (and isearch-other-end
(if backward
@@ -2376,6 +2384,7 @@ isearch--highlight-regexp-or-lines
;; We call `exit-recursive-edit' explicitly at the end below.
(isearch-recursive-edit nil))
(isearch-done nil t)
+ (isearch-clean-regions)
(isearch-clean-overlays))
(require 'hi-lock nil t)
(let ((regexp (cond ((functionp isearch-regexp-function)
@@ -2958,6 +2967,7 @@ isearch-pre-command-hook
;; Other characters terminate the search and are then executed normally.
(search-exit-option
(isearch-done)
+ (isearch-clean-regions)
(isearch-clean-overlays)))))
(defun isearch-post-command-hook ()
@@ -3312,6 +3322,22 @@ isearch-search-fun-function
This returned function will be used by `isearch-search-string' to
search for the first occurrence of STRING.")
+(defun isearch--find-text-property-region (pos prop)
+ "Find a region around POS containing PROP text property.
+Return nil when PROP is not set at POS."
+ (let* ((beg (and (get-text-property pos prop) pos))
+ (end beg))
+ (when beg
+ (unless (or (equal beg (point-min))
+ (not (eq (get-text-property beg prop)
+ (get-text-property (1- beg) prop))))
+ (setq beg (previous-single-property-change pos prop nil (point-min))))
+ (unless (or (equal end (point-max))
+ (not (eq (get-text-property end prop)
+ (get-text-property (1+ end) prop))))
+ (setq end (next-single-property-change pos prop nil (point-max))))
+ (cons beg end))))
+
(defun isearch-search-fun ()
"Return the function to use for the search.
Can be changed via `isearch-search-fun-function' for special needs."
@@ -3421,8 +3447,9 @@ isearch-search
(if (or (not isearch-success)
(bobp) (eobp)
(= (match-beginning 0) (match-end 0))
- (funcall isearch-filter-predicate
- (match-beginning 0) (match-end 0)))
+ (save-match-data
+ (funcall isearch-filter-predicate
+ (match-beginning 0) (match-end 0))))
(setq retry nil)))
(setq isearch-just-started nil)
(if isearch-success
@@ -3481,6 +3508,20 @@ isearch-open-overlay-temporary
(overlay-put ov 'isearch-invisible (overlay-get ov 'invisible))
(overlay-put ov 'invisible nil)))
+;; Same as `isearch-open-overlay-temporary', but for text hidden via text
+;; properties.
+(defun isearch-open-region-temporary (hidden-region)
+ (if (not (null (get-text-property (car hidden-region) 'isearch-open-invisible-temporary)))
+ (funcall (get-text-property (car hidden-region) 'isearch-open-invisible-temporary) hidden-region nil)
+ (with-silent-modifications
+ (put-text-property (car hidden-region)
+ (cdr hidden-region)
+ 'isearch-invisible
+ (get-text-property (point) 'invisible))
+ (put-text-property (car hidden-region)
+ (cdr hidden-region)
+ 'invisible
+ nil))))
;; This is called at the end of isearch. It will open the overlays
;; that contain the latest match. Obviously in case of a C-g the
@@ -3501,6 +3542,23 @@ isearch-open-necessary-overlays
(if fct-temp
(funcall fct-temp ov t)))))
+;; Equivalent of `isearch-open-necessary-overlays' for text hidden via text
+;; properties.
+(defun isearch-open-necessary-region (region)
+ (let ((inside-region (and (> (point) (car region))
+ (<= (point) (cdr region))))
+ (fct-temp (get-text-property (car region) 'isearch-open-invisible-temporary)))
+ (when (or inside-region (not fct-temp))
+ (with-silent-modifications
+ (put-text-property (car region)
+ (cdr region)
+ 'invisible
+ (get-text-property (car region) 'isearch-invisible))
+ (remove-text-properties (car region) (cdr region) '('isearch-invisible nil))))
+ (if inside-region
+ (funcall (get-text-property (car region) 'isearch-open-invisible) region)
+ (when fct-temp (funcall fct-temp region t)))))
+
;; This is called when exiting isearch. It closes the temporary
;; opened overlays, except the ones that contain the latest match.
(defun isearch-clean-overlays ()
@@ -3508,6 +3566,10 @@ isearch-clean-overlays
(mapc 'isearch-open-necessary-overlays isearch-opened-overlays)
(setq isearch-opened-overlays nil)))
+(defun isearch-clean-regions ()
+ (when isearch-opened-regions
+ (mapc 'isearch-open-necessary-region isearch-opened-regions)
+ (setq isearch-opened-regions nil)))
(defun isearch-intersects-p (start0 end0 start1 end1)
"Return t if regions START0..END0 and START1..END1 intersect."
@@ -3535,6 +3597,21 @@ isearch-close-unnecessary-overlays
(overlay-put ov 'invisible (overlay-get ov 'isearch-invisible))
(overlay-put ov 'isearch-invisible nil)))))))
+(defun isearch-close-unnecessary-regions (begin end)
+ (let ((regions isearch-opened-regions))
+ (setq isearch-opened-regions nil)
+ (dolist (region regions)
+ (if (isearch-intersects-p begin end (car region) (cdr region))
+ (push region isearch-opened-regions)
+ (let ((fct-temp (get-text-property (car region) 'isearch-open-invisible-temporary)))
+ (if fct-temp
+ (funcall fct-temp region t)
+ (with-silent-modifications
+ (put-text-property (car region)
+ (cdr region)
+ 'invisible
+ (get-text-property (car region) 'isearch-invisible))
+ (remove-text-properties (car region) (cdr region) '('isearch-invisible nil)))))))))
(defun isearch-range-invisible (beg end)
"Return t if all the text from BEG to END is invisible."
@@ -3542,52 +3619,70 @@ isearch-range-invisible
;; Check that invisibility runs up to END.
(save-excursion
(goto-char beg)
- (let (;; can-be-opened keeps track if we can open some overlays.
- (can-be-opened (eq search-invisible 'open))
+ (let (;; can-be-opened keeps track if we can open some hidden text.
+ (can-be-opened (memq search-invisible '(open open-all)))
+ (region-can-be-opened (eq search-invisible 'open-all))
;; the list of overlays that could be opened
- (crt-overlays nil))
+ (crt-overlays nil)
+ (crt-regions nil))
(when (and can-be-opened isearch-hide-immediately)
- (isearch-close-unnecessary-overlays beg end))
+ (isearch-close-unnecessary-overlays beg end)
+ (when region-can-be-opened (isearch-close-unnecessary-regions beg end)))
;; If the following character is currently invisible,
;; skip all characters with that same `invisible' property value.
;; Do that over and over.
(while (and (< (point) end) (invisible-p (point)))
- (if (invisible-p (get-text-property (point) 'invisible))
- (progn
- (goto-char (next-single-property-change (point) 'invisible
- nil end))
- ;; if text is hidden by an `invisible' text property
- ;; we cannot open it at all.
- (setq can-be-opened nil))
- (when can-be-opened
- (let ((overlays (overlays-at (point)))
- ov-list
- o
- invis-prop)
- (while overlays
- (setq o (car overlays)
- invis-prop (overlay-get o 'invisible))
- (if (invisible-p invis-prop)
- (if (overlay-get o 'isearch-open-invisible)
- (setq ov-list (cons o ov-list))
- ;; We found one overlay that cannot be
- ;; opened, that means the whole chunk
- ;; cannot be opened.
- (setq can-be-opened nil)))
- (setq overlays (cdr overlays)))
- (if can-be-opened
- ;; It makes sense to append to the open
- ;; overlays list only if we know that this is
- ;; t.
- (setq crt-overlays (append ov-list crt-overlays)))))
- (goto-char (next-overlay-change (point)))))
- ;; See if invisibility reaches up thru END.
- (if (>= (point) end)
- (if (and can-be-opened (consp crt-overlays))
- (progn
+ (when (invisible-p (get-text-property (point) 'invisible))
+ (if (and can-be-opened region-can-be-opened)
+ (let ((region (isearch--find-text-property-region (point) 'invisible)))
+ ;; If the region info is passed to user-defined
+ ;; function, tell that function what we actually
+ ;; want to reveal.
+ (when (< (car region) beg) (setcar region beg))
+ (when (> (cdr region) end) (setcdr region end))
+ (if (get-text-property (point) 'isearch-open-invisible)
+ (push region crt-regions)
+ (goto-char (next-single-property-change (point) 'invisible
+ nil end))
+ (setq can-be-opened nil)))
+ (goto-char (next-single-property-change (point) 'invisible
+ nil end))
+ ;; if text is hidden by an `invisible' text property
+ ;; we cannot open it at all.
+ (setq can-be-opened nil)))
+ (when can-be-opened
+ (let ((overlays (overlays-at (point)))
+ ov-list
+ o
+ invis-prop)
+ (while overlays
+ (setq o (car overlays)
+ invis-prop (overlay-get o 'invisible))
+ (if (invisible-p invis-prop)
+ (if (overlay-get o 'isearch-open-invisible)
+ (setq ov-list (cons o ov-list))
+ ;; We found one overlay that cannot be
+ ;; opened, that means the whole chunk
+ ;; cannot be opened.
+ (setq can-be-opened nil)))
+ (setq overlays (cdr overlays)))
+ (if can-be-opened
+ ;; It makes sense to append to the open
+ ;; overlays list only if we know that this is
+ ;; t.
+ (setq crt-overlays (append ov-list crt-overlays)))))
+ (goto-char (next-single-char-property-change (point) 'invisible)))
+ ;; See if invisibility reaches up thru END.
+ (if (>= (point) end)
+ (if (and can-be-opened (or (consp crt-overlays)
+ (consp crt-regions)))
+ (progn
(setq isearch-opened-overlays
- (append isearch-opened-overlays crt-overlays))
+ (append isearch-opened-overlays crt-overlays))
(mapc 'isearch-open-overlay-temporary crt-overlays)
+ (setq isearch-opened-regions
+ (append isearch-opened-regions crt-regions))
+ (mapc 'isearch-open-region-temporary crt-regions)
nil)
(setq isearch-hidden t)))))))
[-- Attachment #3: Type: text/plain, Size: 1206 bytes --]
Stefan Monnier <monnier@iro.umontreal.ca> writes:
>> - text hidden via text properties is not well-handled by isearch. For
>> overlays, isearch can temporary reveal the hidden text, which is not
>> the case for text hidden using 'invisible text property.
>
> The recommending way to avoid this problem is to provide a patch which
> extends Isearch's handling of invisible text to text-properties.
> There's no reason to presume it would be hard to do.
>
>> - text properties are not buffer-local in indirect buffers. With
>> overlays, it is possible to have define hidden text in basic and
>> indirect buffer independently. However, text properties will always be
>> shared and the text hidden in indirect buffer will automatically be
>> hidden in the base buffer as well.
>
> And the way I recommend to avoid this problem is to stay clear of
> indirect buffers (which I consider as an attractive nuisance).
>
>
> Stefan
>
--
Ihor Radchenko,
PhD,
Center for Advancing Materials Performance from the Nanoscale (CAMP-nano)
State Key Laboratory for Mechanical Behavior of Materials, Xi'an Jiaotong University, Xi'an, China
Email: yantar92@gmail.com, ihor_radchenko@alumni.sutd.edu.sg
next prev parent reply other threads:[~2020-07-27 11:20 UTC|newest]
Thread overview: 17+ messages / expand[flat|nested] mbox.gz Atom feed top
2020-07-25 16:40 A question about overlays and performance Jeff Norden
2020-07-25 16:53 ` Eli Zaretskii
2020-07-25 17:24 ` Jeff Norden
2020-07-25 17:29 ` Eli Zaretskii
2020-07-26 1:33 ` Ihor Radchenko
2020-07-26 13:56 ` Eli Zaretskii
2020-07-26 14:19 ` Ihor Radchenko
2020-07-26 14:44 ` Stefan Monnier
2020-07-27 11:20 ` Ihor Radchenko [this message]
2020-07-28 16:43 ` Jeff Norden
2020-07-28 16:58 ` Yuan Fu
2020-07-28 17:05 ` Eric Abrahamsen
2020-07-29 1:52 ` Ihor Radchenko
2020-07-30 18:21 ` Jeff Norden
2020-07-28 23:53 ` Juri Linkov
2020-07-29 1:36 ` Ihor Radchenko
2020-07-30 23:04 ` Juri Linkov
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
List information: https://www.gnu.org/software/emacs/
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=87tuxttda9.fsf@localhost \
--to=yantar92@gmail.com \
--cc=eliz@gnu.org \
--cc=emacs-devel@gnu.org \
--cc=jnorden@math.tntech.edu \
--cc=monnier@iro.umontreal.ca \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
Code repositories for project(s) associated with this public inbox
https://git.savannah.gnu.org/cgit/emacs.git
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).