From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.io!.POSTED.blaine.gmane.org!not-for-mail From: Ihor Radchenko Newsgroups: gmane.emacs.devel Subject: Re: A question about overlays and performance. Date: Mon, 27 Jul 2020 19:20:30 +0800 Message-ID: <87tuxttda9.fsf@localhost> References: <835zabcz8d.fsf@gnu.org> <87365foyam.fsf@localhost> 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="21681"; mail-complaints-to="usenet@ciao.gmane.io" Cc: Eli Zaretskii , Jeff Norden , emacs-devel@gnu.org To: Stefan Monnier Original-X-From: emacs-devel-bounces+ged-emacs-devel=m.gmane-mx.org@gnu.org Mon Jul 27 13:22:32 2020 Return-path: Envelope-to: ged-emacs-devel@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 1k01DD-0005VR-N5 for ged-emacs-devel@m.gmane-mx.org; Mon, 27 Jul 2020 13:22:31 +0200 Original-Received: from localhost ([::1]:38230 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1k01DC-0003xF-P3 for ged-emacs-devel@m.gmane-mx.org; Mon, 27 Jul 2020 07:22:30 -0400 Original-Received: from eggs.gnu.org ([2001:470:142:3::10]:51614) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1k01CH-0003DK-VO for emacs-devel@gnu.org; Mon, 27 Jul 2020 07:21:33 -0400 Original-Received: from mail-pf1-x429.google.com ([2607:f8b0:4864:20::429]:46739) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1k01CF-0006yO-HJ; Mon, 27 Jul 2020 07:21:33 -0400 Original-Received: by mail-pf1-x429.google.com with SMTP id 74so643512pfx.13; Mon, 27 Jul 2020 04:21:29 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:in-reply-to:references:date:message-id :mime-version; bh=DdHfhE7LgCsFE0ymo6fF9cq80nKFGmgGOBNcgSbT0uk=; b=Pv4M2fWY34lbPQiCx7B4gbH9P/EFhgAlDm66evFEPV5t/aKsvVdmp5NQLvMkxSEXzB gMCIu73o6SKbppahmR/isbHPIfRwvJ7eVOnY//JhNezPBJCyOx9r0oJapgTS3Mv6lx6M bkydjMyezLRtCgwbv4W1obvwQ5urvFchqSSQVlmRN7ZqjCWAr4EmIUdQkOX7gqYhWKkb Ni/ZyvecaNBgbjV80B5dBhJTeCWn5PUW5SXzkjNsGNUlYR59bPpFhqAuo2CiUdnaUR5V 3ZWIkTTZgR2eaI/1W3gqcqmFt2h6mnEh5nwQrCtcl2WsQ0I2TQm35ucg4yhNJudtweu4 dUPA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:in-reply-to:references:date :message-id:mime-version; bh=DdHfhE7LgCsFE0ymo6fF9cq80nKFGmgGOBNcgSbT0uk=; b=t/d2EugMPTGE0t+h8M32xBVXwVjsaMJtDpiHea57BzmqkxIHjD7BeHx2QItLWW6KK4 lPojgWmN1snnz+cIkgPB+d0kST5+DyUZziClw43AaQPzKxB0o2u4Smpa7iFcZZgBdrdL s0PJ6YqR935UkvSuxaJWaaN6WL7Xi5IRXYc5Ry459MoZhAbmt+3xWCY3Ubn+ded9nd6J H6jAP92lszZAvLPsAkVo2+3Uk1joo8gj+OEBiHyMAFoKTDoHwKR/Z1hW7w9OyomqZWWd VvhK28oURTMaQEcN8hqK3FMK+alBBoOM1HyTZq8XLDThDLx1x9rrzRNkJvmt1hl8oiJH w09w== X-Gm-Message-State: AOAM530jgra+nMyzQMSt7MYOuecK8ZW1bRKfOe31p6wqqblkausEhTeK m7ZJ1ucxm3e4zxNaX+Dc8TLMbKEZ83tkTw== X-Google-Smtp-Source: ABdhPJwOBobgwfcnJdGX4a0UZYB5vzySB7sdH66BFJoAlwdldmn0uHsTspnbfX1TsfAPoDT55o0HAg== X-Received: by 2002:a63:7c42:: with SMTP id l2mr19970283pgn.35.1595848888087; Mon, 27 Jul 2020 04:21:28 -0700 (PDT) Original-Received: from localhost ([111.19.38.244]) by smtp.gmail.com with ESMTPSA id q5sm14921837pfc.130.2020.07.27.04.21.24 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 27 Jul 2020 04:21:26 -0700 (PDT) In-Reply-To: Received-SPF: pass client-ip=2607:f8b0:4864:20::429; envelope-from=yantar92@gmail.com; helo=mail-pf1-x429.google.com X-detected-operating-system: by eggs.gnu.org: No matching host in p0f cache. That's all we know. X-Spam_score_int: -17 X-Spam_score: -1.8 X-Spam_bar: - X-Spam_report: (-1.8 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, FREEMAIL_ENVFROM_END_DIGIT=0.25, FREEMAIL_FROM=0.001, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: emacs-devel@gnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: "Emacs development discussions." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: emacs-devel-bounces+ged-emacs-devel=m.gmane-mx.org@gnu.org Original-Sender: "Emacs-devel" Xref: news.gmane.io gmane.emacs.devel:253285 Archived-At: --=-=-= Content-Type: text/plain > 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 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=iseach-text-properties.patch 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-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))))))) --=-=-= Content-Type: text/plain Stefan Monnier 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 --=-=-=--