aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStefan Monnier2025-06-04 16:36:27 -0400
committerStefan Monnier2025-06-04 16:37:17 -0400
commit77a4c63fda5ca5d4c6d82092eaa06f1eb9b51302 (patch)
tree5cef8842a675ca73945c4f06fe0cbdc77e2ddf38
parent6f24725323dce786dd06ecf82b49c20eff8a98f7 (diff)
downloademacs-77a4c63fda5ca5d4c6d82092eaa06f1eb9b51302.tar.gz
emacs-77a4c63fda5ca5d4c6d82092eaa06f1eb9b51302.zip
(outline--hidden-headings-paths): Fix slow saves (bug#78665)
* lisp/outline.el: Prefer #' to quote function names. (outline--end-of-previous): New function, extracted from `outline-end-of-subtree`. (outline-end-of-subtree): Use it. (outline--hidden-headings-paths): Distinguish headings where just the entry is hidden from those where a whole subtree is hidden (bug#78673). (outline--hidden-headings-restore-paths): Adjust accordingly and don't delegate to functions like `outline-hide-subtree` so as to avoid an O(N²) behavior.
-rw-r--r--lisp/outline.el101
-rw-r--r--lisp/transient.el48
2 files changed, 90 insertions, 59 deletions
diff --git a/lisp/outline.el b/lisp/outline.el
index 9d453881b7e..dc2b5b32685 100644
--- a/lisp/outline.el
+++ b/lisp/outline.el
@@ -235,10 +235,10 @@ The argument MAP is optional and defaults to `outline-minor-mode-cycle-map'."
235 (let ((map (make-sparse-keymap))) 235 (let ((map (make-sparse-keymap)))
236 (outline-minor-mode-cycle--bind map (kbd "TAB") #'outline-cycle) 236 (outline-minor-mode-cycle--bind map (kbd "TAB") #'outline-cycle)
237 (outline-minor-mode-cycle--bind map (kbd "<backtab>") #'outline-cycle-buffer) 237 (outline-minor-mode-cycle--bind map (kbd "<backtab>") #'outline-cycle-buffer)
238 (keymap-set map "<left-margin> <mouse-1>" 'outline-cycle) 238 (keymap-set map "<left-margin> <mouse-1>" #'outline-cycle)
239 (keymap-set map "<right-margin> <mouse-1>" 'outline-cycle) 239 (keymap-set map "<right-margin> <mouse-1>" #'outline-cycle)
240 (keymap-set map "<left-margin> S-<mouse-1>" 'outline-cycle-buffer) 240 (keymap-set map "<left-margin> S-<mouse-1>" #'outline-cycle-buffer)
241 (keymap-set map "<right-margin> S-<mouse-1>" 'outline-cycle-buffer) 241 (keymap-set map "<right-margin> S-<mouse-1>" #'outline-cycle-buffer)
242 map) 242 map)
243 "Keymap used as a parent of the `outline-minor-mode' keymap. 243 "Keymap used as a parent of the `outline-minor-mode' keymap.
244It contains key bindings that can be used to cycle visibility. 244It contains key bindings that can be used to cycle visibility.
@@ -483,7 +483,7 @@ Turning on outline mode calls the value of `text-mode-hook' and then of
483The value of this variable is checked as part of loading Outline mode. 483The value of this variable is checked as part of loading Outline mode.
484After that, changing the prefix key requires manipulating keymaps." 484After that, changing the prefix key requires manipulating keymaps."
485 :type 'key-sequence 485 :type 'key-sequence
486 :initialize 'custom-initialize-default 486 :initialize #'custom-initialize-default
487 :set (lambda (sym val) 487 :set (lambda (sym val)
488 (define-key outline-minor-mode-map outline-minor-mode-prefix nil) 488 (define-key outline-minor-mode-map outline-minor-mode-prefix nil)
489 (define-key outline-minor-mode-map val outline-mode-prefix-map) 489 (define-key outline-minor-mode-map val outline-mode-prefix-map)
@@ -685,6 +685,7 @@ at the end of the buffer."
685 (goto-char (match-beginning 0)) 685 (goto-char (match-beginning 0))
686 ;; Compensate "\n" from the beginning of regexp 686 ;; Compensate "\n" from the beginning of regexp
687 (when (and outline-search-function (not (bobp))) (forward-char -1))) 687 (when (and outline-search-function (not (bobp))) (forward-char -1)))
688 ;; FIXME: Use `outline--end-of-previous'.
688 (when (and (bolp) (or outline-blank-line (eobp)) (not (bobp))) 689 (when (and (bolp) (or outline-blank-line (eobp)) (not (bobp)))
689 (forward-char -1))) 690 (forward-char -1)))
690 691
@@ -1287,6 +1288,16 @@ This also unhides the top heading-less body, if any."
1287 (progn (outline-end-of-subtree) (point)) 1288 (progn (outline-end-of-subtree) (point))
1288 flag))) 1289 flag)))
1289 1290
1291(defun outline--end-of-previous ()
1292 "Go back from BOH (or EOB) to end of previous element."
1293 (if (eobp)
1294 (if (bolp) (forward-char -1))
1295 ;; Go to end of line before heading
1296 (forward-char -1)
1297 (if (and outline-blank-line (bolp))
1298 ;; leave blank line before heading
1299 (forward-char -1))))
1300
1290(defun outline-end-of-subtree () 1301(defun outline-end-of-subtree ()
1291 "Move to the end of the current subtree." 1302 "Move to the end of the current subtree."
1292 (outline-back-to-heading) 1303 (outline-back-to-heading)
@@ -1298,12 +1309,7 @@ This also unhides the top heading-less body, if any."
1298 (outline-next-heading)) 1309 (outline-next-heading))
1299 (if (and (bolp) (not (eolp))) 1310 (if (and (bolp) (not (eolp)))
1300 ;; We stopped at a nonempty line (the next heading). 1311 ;; We stopped at a nonempty line (the next heading).
1301 (progn 1312 (outline--end-of-previous))))
1302 ;; Go to end of line before heading
1303 (forward-char -1)
1304 (if (and outline-blank-line (bolp))
1305 ;; leave blank line before heading
1306 (forward-char -1))))))
1307 1313
1308(defun outline-show-branches () 1314(defun outline-show-branches ()
1309 "Show all subheadings of this heading, but not their bodies." 1315 "Show all subheadings of this heading, but not their bodies."
@@ -1717,12 +1723,17 @@ LEVEL, decides of subtree visibility according to
1717 (run-hooks 'outline-view-change-hook)) 1723 (run-hooks 'outline-view-change-hook))
1718 1724
1719(defun outline--hidden-headings-paths () 1725(defun outline--hidden-headings-paths ()
1720 "Return a hash with headings of currently hidden outlines. 1726 "Return (HASH-TABLE CURRENT-HEADING).
1721Every hash key is a list whose elements compose a complete path 1727HASH-TABLE holds the headings of currently hidden outlines.
1728Every key is a list whose elements compose a complete path
1722of headings descending from the top level down to the bottom level. 1729of headings descending from the top level down to the bottom level.
1730Every entry's value is non-nil if that entry should be hidden.
1731The specific non-nil vale can be t to hide just the entry, or a number
1732LEVEL to mean that not just the entry should be hidden but also all the
1733subsequent elements of level higher or equal to LEVEL.
1723This is useful to save the hidden outlines and restore them later 1734This is useful to save the hidden outlines and restore them later
1724after reverting the buffer. Also return the outline where point 1735after reverting the buffer.
1725was located before reverting the buffer." 1736CURRENT-HEADING is the heading where point is located."
1726 (let* ((paths (make-hash-table :test #'equal)) 1737 (let* ((paths (make-hash-table :test #'equal))
1727 path current-path 1738 path current-path
1728 (current-heading-p (outline-on-heading-p)) 1739 (current-heading-p (outline-on-heading-p))
@@ -1730,40 +1741,60 @@ was located before reverting the buffer."
1730 (current-end (when current-heading-p (pos-eol)))) 1741 (current-end (when current-heading-p (pos-eol))))
1731 (outline-map-region 1742 (outline-map-region
1732 (lambda () 1743 (lambda ()
1733 (let* ((level (funcall outline-level)) 1744 (let ((level (funcall outline-level)))
1734 (heading (buffer-substring-no-properties (pos-bol) (pos-eol)))) 1745 (if (outline-invisible-p)
1735 (while (and path (>= (cdar path) level)) 1746 ;; Covered by "the" previous heading.
1736 (pop path)) 1747 (cl-callf (lambda (l) (if (numberp l) (min l level) level))
1737 (push (cons heading level) path) 1748 (gethash (mapcar #'car path) paths))
1738 (when (save-excursion 1749 (let ((heading (buffer-substring-no-properties (pos-bol) (pos-eol))))
1739 (outline-end-of-heading) 1750 (while (and path (>= (cdar path) level))
1740 (seq-some (lambda (o) (eq (overlay-get o 'invisible) 1751 (pop path))
1741 'outline)) 1752 (push (cons heading level) path)
1742 (overlays-at (point)))) 1753 (when (save-excursion
1743 (setf (gethash (mapcar #'car path) paths) t)) 1754 (outline-end-of-heading)
1755 (outline-invisible-p))
1756 (setf (gethash (mapcar #'car path) paths) t))))
1744 (when (and current-heading-p (<= current-beg (point) current-end)) 1757 (when (and current-heading-p (<= current-beg (point) current-end))
1745 (setq current-path (mapcar #'car path))))) 1758 (setq current-path (mapcar #'car path)))))
1746 (point-min) (point-max)) 1759 (point-min) (point-max))
1747 (list paths current-path))) 1760 (list paths current-path)))
1748 1761
1749(defun outline--hidden-headings-restore-paths (paths current-path) 1762(defun outline--hidden-headings-restore-paths (paths current-path)
1750 "Restore hidden outlines from a hash of hidden headings. 1763 "Restore hidden outlines from a hash-table of hidden headings.
1751This is useful after reverting the buffer to restore the outlines 1764This is useful after reverting the buffer to restore the outlines
1752hidden by `outline--hidden-headings-paths'. Also restore point 1765hidden by `outline--hidden-headings-paths'. Also restore point
1753on the same outline where point was before reverting the buffer." 1766on the same outline where point was before reverting the buffer."
1754 (let (path current-point outline-view-change-hook) 1767 (let ((hidelevel nil) (hidestart nil)
1768 path current-point outline-view-change-hook)
1755 (outline-map-region 1769 (outline-map-region
1756 (lambda () 1770 (lambda ()
1757 (let* ((level (funcall outline-level)) 1771 (let ((level (funcall outline-level)))
1758 (heading (buffer-substring (pos-bol) (pos-eol)))) 1772 (if (and (numberp hidelevel) (<= hidelevel level))
1759 (while (and path (>= (cdar path) level)) 1773 nil
1760 (pop path)) 1774 (when hidestart
1761 (push (cons heading level) path) 1775 (outline-flag-region hidestart
1762 (when (gethash (mapcar #'car path) paths) 1776 (save-excursion (outline--end-of-previous)
1763 (outline-hide-subtree)) 1777 (point))
1778 t)
1779 (setq hidestart nil))
1780 (let* ((heading (buffer-substring-no-properties
1781 (pos-bol) (pos-eol))))
1782 (while (and path (>= (cdar path) level))
1783 (pop path))
1784 (push (cons heading level) path)
1785 (when (setq hidelevel (gethash (mapcar #'car path) paths))
1786 (setq hidestart (save-excursion (outline-end-of-heading)
1787 (point))))))
1764 (when (and current-path (equal current-path (mapcar #'car path))) 1788 (when (and current-path (equal current-path (mapcar #'car path)))
1765 (setq current-point (point))))) 1789 (setq current-point (point)))))
1766 (point-min) (point-max)) 1790 (point-min) (point-max))
1791 (when hidestart
1792 (outline-flag-region hidestart
1793 (save-excursion
1794 (goto-char (point-max))
1795 (outline--end-of-previous)
1796 (point))
1797 t))
1767 (when current-point (goto-char current-point)))) 1798 (when current-point (goto-char current-point))))
1768 1799
1769(defun outline-revert-buffer-restore-visibility () 1800(defun outline-revert-buffer-restore-visibility ()
diff --git a/lisp/transient.el b/lisp/transient.el
index 686dc469463..e0c834564c6 100644
--- a/lisp/transient.el
+++ b/lisp/transient.el
@@ -72,7 +72,7 @@
72 (transient--emergency-exit :debugger) 72 (transient--emergency-exit :debugger)
73 (apply #'debug args)) 73 (apply #'debug args))
74 74
75;;; Options 75;;;; Options
76 76
77(defgroup transient nil 77(defgroup transient nil
78 "Transient commands." 78 "Transient commands."
@@ -507,7 +507,7 @@ give you as many additional suffixes as you hoped.)"
507 :group 'transient 507 :group 'transient
508 :type 'boolean) 508 :type 'boolean)
509 509
510;;; Faces 510;;;; Faces
511 511
512(defgroup transient-faces nil 512(defgroup transient-faces nil
513 "Faces used by Transient." 513 "Faces used by Transient."
@@ -655,7 +655,7 @@ See also option `transient-highlight-mismatched-keys'."
655See also option `transient-highlight-mismatched-keys'." 655See also option `transient-highlight-mismatched-keys'."
656 :group 'transient-faces) 656 :group 'transient-faces)
657 657
658;;; Persistence 658;;;; Persistence
659 659
660(defun transient--read-file-contents (file) 660(defun transient--read-file-contents (file)
661 (with-demoted-errors "Transient error: %S" 661 (with-demoted-errors "Transient error: %S"
@@ -718,7 +718,7 @@ If `transient-save-history' is nil, then do nothing."
718(unless noninteractive 718(unless noninteractive
719 (add-hook 'kill-emacs-hook #'transient-maybe-save-history)) 719 (add-hook 'kill-emacs-hook #'transient-maybe-save-history))
720 720
721;;; Classes 721;;;; Classes
722;;;; Prefix 722;;;; Prefix
723 723
724(defclass transient-prefix () 724(defclass transient-prefix ()
@@ -965,7 +965,7 @@ commands or strings. This group inserts an empty line between
965subgroups. The subgroups are responsible for displaying their 965subgroups. The subgroups are responsible for displaying their
966elements themselves.") 966elements themselves.")
967 967
968;;; Define 968;;;; Define
969 969
970(defmacro transient-define-prefix (name arglist &rest args) 970(defmacro transient-define-prefix (name arglist &rest args)
971 "Define NAME as a transient prefix command. 971 "Define NAME as a transient prefix command.
@@ -1482,7 +1482,7 @@ Intended for use in a group's `:setup-children' function."
1482 (setq prefix (oref prefix command))) 1482 (setq prefix (oref prefix command)))
1483 (mapcar (apply-partially #'transient-parse-suffix prefix) suffixes)) 1483 (mapcar (apply-partially #'transient-parse-suffix prefix) suffixes))
1484 1484
1485;;; Edit 1485;;;; Edit
1486 1486
1487(defun transient--insert-suffix (prefix loc suffix action &optional keep-other) 1487(defun transient--insert-suffix (prefix loc suffix action &optional keep-other)
1488 (pcase-let* ((suf (cl-etypecase suffix 1488 (pcase-let* ((suf (cl-etypecase suffix
@@ -1699,7 +1699,7 @@ using `transient-define-suffix', `transient-define-infix' or
1699 (user-error "Cannot set level for `%s'; no prototype object exists" 1699 (user-error "Cannot set level for `%s'; no prototype object exists"
1700 command))) 1700 command)))
1701 1701
1702;;; Variables 1702;;;; Variables
1703 1703
1704(defvar transient-current-prefix nil 1704(defvar transient-current-prefix nil
1705 "The transient from which this suffix command was invoked. 1705 "The transient from which this suffix command was invoked.
@@ -1806,7 +1806,7 @@ This is bound while the suffixes are drawn in the transient buffer.")
1806 mwheel-scroll 1806 mwheel-scroll
1807 scroll-bar-toolkit-scroll)) 1807 scroll-bar-toolkit-scroll))
1808 1808
1809;;; Identities 1809;;;; Identities
1810 1810
1811(defun transient-active-prefix (&optional prefixes) 1811(defun transient-active-prefix (&optional prefixes)
1812 "Return the active transient object. 1812 "Return the active transient object.
@@ -1944,7 +1944,7 @@ probably use this instead:
1944 (seq-some (lambda (cmd) (get cmd 'transient--suffix)) 1944 (seq-some (lambda (cmd) (get cmd 'transient--suffix))
1945 (function-alias-p command)))) 1945 (function-alias-p command))))
1946 1946
1947;;; Keymaps 1947;;;; Keymaps
1948 1948
1949(defvar-keymap transient-base-map 1949(defvar-keymap transient-base-map
1950 :doc "Parent of other keymaps used by Transient. 1950 :doc "Parent of other keymaps used by Transient.
@@ -2306,7 +2306,7 @@ of the corresponding object."
2306 transient--transient-map)) 2306 transient--transient-map))
2307 topmap)) 2307 topmap))
2308 2308
2309;;; Setup 2309;;;; Setup
2310 2310
2311(defun transient-setup (&optional name layout edit &rest params) 2311(defun transient-setup (&optional name layout edit &rest params)
2312 "Setup the transient specified by NAME. 2312 "Setup the transient specified by NAME.
@@ -2587,7 +2587,7 @@ value. Otherwise return CHILDREN as is.")
2587 (transient--debug " autoload %s" cmd) 2587 (transient--debug " autoload %s" cmd)
2588 (autoload-do-load fn))) 2588 (autoload-do-load fn)))
2589 2589
2590;;; Flow-Control 2590;;;; Flow-Control
2591 2591
2592(defun transient--setup-transient () 2592(defun transient--setup-transient ()
2593 (transient--debug 'setup-transient) 2593 (transient--debug 'setup-transient)
@@ -2981,7 +2981,7 @@ identifying the exit."
2981 (transient--pre-exit) 2981 (transient--pre-exit)
2982 (transient--post-exit this-command))) 2982 (transient--post-exit this-command)))
2983 2983
2984;;; Pre-Commands 2984;;;; Pre-Commands
2985 2985
2986(defun transient--call-pre-command () 2986(defun transient--call-pre-command ()
2987 (if-let* ((fn (transient--get-pre-command this-command 2987 (if-let* ((fn (transient--get-pre-command this-command
@@ -3164,7 +3164,7 @@ prefix argument and pivot to `transient-update'."
3164(put 'transient--do-move 'transient-face 'transient-key-stay) 3164(put 'transient--do-move 'transient-face 'transient-key-stay)
3165(put 'transient--do-minus 'transient-face 'transient-key-stay) 3165(put 'transient--do-minus 'transient-face 'transient-key-stay)
3166 3166
3167;;; Commands 3167;;;; Commands
3168;;;; Noop 3168;;;; Noop
3169 3169
3170(defun transient-noop () 3170(defun transient-noop ()
@@ -3487,7 +3487,7 @@ such as when suggesting a new feature or reporting an issue."
3487 arguments " ")) 3487 arguments " "))
3488 (message "%s: %S" (key-description (this-command-keys)) arguments))) 3488 (message "%s: %S" (key-description (this-command-keys)) arguments)))
3489 3489
3490;;; Value 3490;;;; Value
3491;;;; Init 3491;;;; Init
3492 3492
3493(cl-defgeneric transient-init-value (obj) 3493(cl-defgeneric transient-init-value (obj)
@@ -4000,7 +4000,7 @@ Append \"=\ to ARG to indicate that it is an option."
4000 (or (match-string 1 match) ""))) 4000 (or (match-string 1 match) "")))
4001 (and (member arg args) t))) 4001 (and (member arg args) t)))
4002 4002
4003;;; Return 4003;;;; Return
4004 4004
4005(defun transient-init-return (obj) 4005(defun transient-init-return (obj)
4006 (when-let* ((transient--stack) 4006 (when-let* ((transient--stack)
@@ -4012,7 +4012,7 @@ Append \"=\ to ARG to indicate that it is an option."
4012 (list t 'recurse #'transient--do-recurse)))) 4012 (list t 'recurse #'transient--do-recurse))))
4013 (oset obj return t))) 4013 (oset obj return t)))
4014 4014
4015;;; Scope 4015;;;; Scope
4016;;;; Init 4016;;;; Init
4017 4017
4018(cl-defgeneric transient-init-scope (obj) 4018(cl-defgeneric transient-init-scope (obj)
@@ -4084,7 +4084,7 @@ If no prefix matches, return nil."
4084 (and-let* ((obj (transient-prefix-object))) 4084 (and-let* ((obj (transient-prefix-object)))
4085 (oref obj scope)))) 4085 (oref obj scope))))
4086 4086
4087;;; History 4087;;;; History
4088 4088
4089(cl-defgeneric transient--history-key (obj) 4089(cl-defgeneric transient--history-key (obj)
4090 "Return OBJ's history key.") 4090 "Return OBJ's history key.")
@@ -4116,7 +4116,7 @@ have a history of their own.")
4116 (cons val (delete val (alist-get (transient--history-key obj) 4116 (cons val (delete val (alist-get (transient--history-key obj)
4117 transient-history)))))) 4117 transient-history))))))
4118 4118
4119;;; Display 4119;;;; Display
4120 4120
4121(defun transient--show-hint () 4121(defun transient--show-hint ()
4122 (let ((message-log-max nil)) 4122 (let ((message-log-max nil))
@@ -4190,7 +4190,7 @@ have a history of their own.")
4190 (window-body-width window t) 4190 (window-body-width window t)
4191 (window-body-height window t)))) 4191 (window-body-height window t))))
4192 4192
4193;;; Delete 4193;;;; Delete
4194 4194
4195(defun transient--delete-window () 4195(defun transient--delete-window ()
4196 (when (window-live-p transient--window) 4196 (when (window-live-p transient--window)
@@ -4224,7 +4224,7 @@ have a history of their own.")
4224 (setq show (natnump show))) 4224 (setq show (natnump show)))
4225 show)) 4225 show))
4226 4226
4227;;; Format 4227;;;; Format
4228 4228
4229(defun transient--format-hint () 4229(defun transient--format-hint ()
4230 (if (and transient-show-popup (<= transient-show-popup 0)) 4230 (if (and transient-show-popup (<= transient-show-popup 0))
@@ -4728,7 +4728,7 @@ a prefix command, while porting a regular keymap to a transient."
4728 (propertize (car (split-string doc "\n")) 'face 'font-lock-doc-face) 4728 (propertize (car (split-string doc "\n")) 'face 'font-lock-doc-face)
4729 (propertize (symbol-name command) 'face 'font-lock-function-name-face)))) 4729 (propertize (symbol-name command) 'face 'font-lock-function-name-face))))
4730 4730
4731;;; Help 4731;;;; Help
4732 4732
4733(cl-defgeneric transient-show-help (obj) 4733(cl-defgeneric transient-show-help (obj)
4734 "Show documentation for the command represented by OBJ.") 4734 "Show documentation for the command represented by OBJ.")
@@ -5169,7 +5169,7 @@ as stand-in for elements of exhausted lists."
5169 (setq lists (mapcar #'cdr lists))) 5169 (setq lists (mapcar #'cdr lists)))
5170 (nreverse result))) 5170 (nreverse result)))
5171 5171
5172;;; Font-Lock 5172;;;; Font-Lock
5173 5173
5174(defconst transient-font-lock-keywords 5174(defconst transient-font-lock-keywords
5175 (eval-when-compile 5175 (eval-when-compile
@@ -5187,7 +5187,7 @@ as stand-in for elements of exhausted lists."
5187 5187
5188(font-lock-add-keywords 'emacs-lisp-mode transient-font-lock-keywords) 5188(font-lock-add-keywords 'emacs-lisp-mode transient-font-lock-keywords)
5189 5189
5190;;; Auxiliary Classes 5190;;;; Auxiliary Classes
5191;;;; `transient-lisp-variable' 5191;;;; `transient-lisp-variable'
5192 5192
5193(defclass transient-lisp-variable (transient-variable) 5193(defclass transient-lisp-variable (transient-variable)
@@ -5250,4 +5250,4 @@ as stand-in for elements of exhausted lists."
5250;; indent-tabs-mode: nil 5250;; indent-tabs-mode: nil
5251;; checkdoc-symbol-words: ("command-line" "edit-mode" "help-mode") 5251;; checkdoc-symbol-words: ("command-line" "edit-mode" "help-mode")
5252;; End: 5252;; End:
5253;;; transient.el ends here 5253;;;; transient.el ends here