unofficial mirror of emacs-devel@gnu.org 
 help / color / mirror / code / Atom feed
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

  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).