Subject: bug#25122: 24.5; function describe-variable hangs on large variables

From: [email-masked]
Date: Wed, 15 Mar 2017 22:54:24 -0400

>> [email-masked] writes:
>>
>> Okay, I think I found the real fix now:

Same issue with indent-region.  The gains are not so dramatic for
typical lisp code that has normal size sexps, but C-x h C-M-\ on
subr.el's text runs twice as fast for me with the new lisp-indent-region
function.

>From 1a5464d36582b5efcea43aac37bf0b9ddd4c1ff1 Mon Sep 17 00:00:00 2001
From: Noam Postavsky <[email-masked]>
Date: Wed, 15 Mar 2017 21:59:13 -0400
Subject: [PATCH v2 2/4] Remove ignored argument from lisp-indent-line

* lisp/emacs-lisp/lisp-mode.el (lisp-indent-line): Remove WHOLE-EXP
argument, the behvior has long since been handled in
`indent-for-tab-command'.
---
 lisp/emacs-lisp/lisp-mode.el | 8 +++-----
 1 file changed, 3 insertions(+), 5 deletions(-)

diff --git a/lisp/emacs-lisp/lisp-mode.el b/lisp/emacs-lisp/lisp-mode.el
index 3fefb69066..7eb8b986be 100644
--- a/lisp/emacs-lisp/lisp-mode.el
+++ b/lisp/emacs-lisp/lisp-mode.el
@@ -744,11 +744,9 @@ lisp-indent-function
   :type 'function
   :group 'lisp)
 
-(defun lisp-indent-line (&optional _whole-exp)
-  "Indent current line as Lisp code.
-With argument, indent any additional lines of the same expression
-rigidly along with this one."
-  (interactive "P")
+(defun lisp-indent-line ()
+  "Indent current line as Lisp code."
+  (interactive)
   (let ((indent (calculate-lisp-indent)) shift-amt
 	(pos (- (point-max) (point)))
 	(beg (progn (beginning-of-line) (point))))
-- 
2.11.1

>From 9cf8f32910c0c899f108c3907d099a18fff2cec4 Mon Sep 17 00:00:00 2001
From: Noam Postavsky <[email-masked]>
Date: Wed, 15 Mar 2017 22:27:27 -0400
Subject: [PATCH v2 3/4] Add new `lisp-indent-region' that doesn't reparse the
 code.

* lisp/emacs-lisp/lisp-mode.el (lisp-indent-region): New function,
like `indent-region-line-by-line' but additionally keep a running
parse state to avoid reparsing the code repeatedly.
(lisp-indent-line): Take optional PARSE-STATE argument, pass it to
`calculate-lisp-indent'.
(lisp-mode-variables): Set `indent-region-function' to
`lisp-indent-region'.
---
 lisp/emacs-lisp/lisp-mode.el | 33 +++++++++++++++++++++++++++++++--
 1 file changed, 31 insertions(+), 2 deletions(-)

diff --git a/lisp/emacs-lisp/lisp-mode.el b/lisp/emacs-lisp/lisp-mode.el
index 7eb8b986be..7577267903 100644
--- a/lisp/emacs-lisp/lisp-mode.el
+++ b/lisp/emacs-lisp/lisp-mode.el
@@ -586,6 +586,7 @@ lisp-mode-variables
   ;;  I believe that newcomment's auto-fill code properly deals with it  -stef
   ;;(set (make-local-variable 'adaptive-fill-mode) nil)
   (setq-local indent-line-function 'lisp-indent-line)
+  (setq-local indent-region-function 'lisp-indent-region)
   (setq-local outline-regexp ";;;\\(;* [^ \t\n]\\|###autoload\\)\\|(")
   (setq-local outline-level 'lisp-outline-level)
   (setq-local add-log-current-defun-function #'lisp-current-defun-name)
@@ -744,10 +745,38 @@ lisp-indent-function
   :type 'function
   :group 'lisp)
 
-(defun lisp-indent-line ()
+(defun lisp-indent-region (start end)
+  "Indent region as Lisp code, efficiently."
+  (save-excursion
+    (setq end (copy-marker end))
+    (goto-char start)
+    ;; The default `indent-region-line-by-line' doesn't hold a running
+    ;; parse state, which forces each indent call to reparse from the
+    ;; beginning.  That has O(n^2) complexity.
+    (let* ((parse-state (syntax-ppss start))
+           (last-syntax-point start)
+           (pr (unless (minibufferp)
+                 (make-progress-reporter "Indenting region..." (point) end))))
+      (while (< (point) end)
+        (unless (and (bolp) (eolp))
+          (lisp-indent-line parse-state))
+        (forward-line 1)
+        (let ((last-sexp (nth 2 parse-state)))
+          (setq parse-state (parse-partial-sexp last-syntax-point (point)
+                                                nil nil parse-state))
+          ;; It's important to preserve last sexp location for
+          ;; `calculate-lisp-indent'.
+          (unless (nth 2 parse-state)
+            (setf (nth 2 parse-state) last-sexp))
+          (setq last-syntax-point (point)))
+        (and pr (progress-reporter-update pr (point))))
+      (and pr (progress-reporter-done pr))
+      (move-marker end nil))))
+
+(defun lisp-indent-line (&optional parse-state)
   "Indent current line as Lisp code."
   (interactive)
-  (let ((indent (calculate-lisp-indent)) shift-amt
+  (let ((indent (calculate-lisp-indent parse-state)) shift-amt
 	(pos (- (point-max) (point)))
 	(beg (progn (beginning-of-line) (point))))
     (skip-chars-forward " \t")
-- 
2.11.1

>From 4dbe8fd84064b1b3fed4188f0a2a84de5550cf11 Mon Sep 17 00:00:00 2001
From: Noam Postavsky <[email-masked]>
Date: Wed, 15 Mar 2017 22:35:47 -0400
Subject: [PATCH v2 4/4] * lisp/emacs-lisp/lisp-mode.el (indent-sexp): Clean up
 marker.

---
 lisp/emacs-lisp/lisp-mode.el | 16 +++++++++-------
 1 file changed, 9 insertions(+), 7 deletions(-)

diff --git a/lisp/emacs-lisp/lisp-mode.el b/lisp/emacs-lisp/lisp-mode.el
index 7577267903..4fa9eb97de 100644
--- a/lisp/emacs-lisp/lisp-mode.el
+++ b/lisp/emacs-lisp/lisp-mode.el
@@ -1112,12 +1112,13 @@ indent-sexp
          (next-depth init-depth)
          (last-depth init-depth)
          (last-syntax-point (point)))
-    (unless endpos
-      ;; Get error now if we don't have a complete sexp after point.
-      (save-excursion (forward-sexp 1)
-                      ;; We need a marker because we modify the buffer
-                      ;; text preceding endpos.
-                      (setq endpos (point-marker))))
+    ;; We need a marker because we modify the buffer
+    ;; text preceding endpos.
+    (setq endpos (copy-marker
+                  (if endpos endpos
+                    ;; Get error now if we don't have a complete sexp
+                    ;; after point.
+                    (save-excursion (forward-sexp 1) (point)))))
     (save-excursion
       (while (< (point) endpos)
         ;; Parse this line so we can learn the state to indent the
@@ -1177,7 +1178,8 @@ indent-sexp
                          ;; `calculate-lisp-indent' only returns nil
                          ;; when we're in a string, but this won't
                          ;; happen because we skip strings above.
-                         (t (error "This shouldn't happen!"))))))))))))
+                         (t (error "This shouldn't happen!"))))))))))
+    (move-marker endpos nil)))
 
 (defun indent-pp-sexp (&optional arg)
   "Indent each line of the list starting just after point, or prettyprint it.
-- 
2.11.1