aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNoam Postavsky2017-04-22 14:10:58 -0400
committerNoam Postavsky2017-04-22 14:18:46 -0400
commit6fa9cc0593150a318f0e08e69ec10672d548a7c1 (patch)
tree5f3ba3603a4bc724180d9b872b50c183c39a664b
parent66dc8dd6d13d37ef23b52873293d95d87dca497f (diff)
parent4713dd425beac5cb459704e67dcb8f6faf714375 (diff)
downloademacs-6fa9cc0593150a318f0e08e69ec10672d548a7c1.tar.gz
emacs-6fa9cc0593150a318f0e08e69ec10672d548a7c1.zip
; Merge: improve indent-sexp and lisp-indent-region performance
-rw-r--r--lisp/emacs-lisp/lisp-mode.el150
-rw-r--r--test/lisp/emacs-lisp/lisp-mode-tests.el5
2 files changed, 98 insertions, 57 deletions
diff --git a/lisp/emacs-lisp/lisp-mode.el b/lisp/emacs-lisp/lisp-mode.el
index 2e6e13f1dd1..54d916887cd 100644
--- a/lisp/emacs-lisp/lisp-mode.el
+++ b/lisp/emacs-lisp/lisp-mode.el
@@ -594,6 +594,7 @@ font-lock keywords will not be case sensitive."
594 ;; I believe that newcomment's auto-fill code properly deals with it -stef 594 ;; I believe that newcomment's auto-fill code properly deals with it -stef
595 ;;(set (make-local-variable 'adaptive-fill-mode) nil) 595 ;;(set (make-local-variable 'adaptive-fill-mode) nil)
596 (setq-local indent-line-function 'lisp-indent-line) 596 (setq-local indent-line-function 'lisp-indent-line)
597 (setq-local indent-region-function 'lisp-indent-region)
597 (setq-local outline-regexp ";;;\\(;* [^ \t\n]\\|###autoload\\)\\|(") 598 (setq-local outline-regexp ";;;\\(;* [^ \t\n]\\|###autoload\\)\\|(")
598 (setq-local outline-level 'lisp-outline-level) 599 (setq-local outline-level 'lisp-outline-level)
599 (setq-local add-log-current-defun-function #'lisp-current-defun-name) 600 (setq-local add-log-current-defun-function #'lisp-current-defun-name)
@@ -748,14 +749,51 @@ function is `common-lisp-indent-function'."
748 :type 'function 749 :type 'function
749 :group 'lisp) 750 :group 'lisp)
750 751
751(defun lisp-indent-line (&optional _whole-exp) 752(defun lisp-ppss (&optional pos)
752 "Indent current line as Lisp code. 753 "Return Parse-Partial-Sexp State at POS, defaulting to point.
753With argument, indent any additional lines of the same expression 754Like to `syntax-ppss' but includes the character address of the
754rigidly along with this one." 755last complete sexp in the innermost containing list at position
755 (interactive "P") 7562 (counting from 0). This is important for lisp indentation."
756 (let ((indent (calculate-lisp-indent)) shift-amt 757 (unless pos (setq pos (point)))
757 (pos (- (point-max) (point))) 758 (let ((pss (syntax-ppss pos)))
758 (beg (progn (beginning-of-line) (point)))) 759 (if (nth 9 pss)
760 (parse-partial-sexp (car (last (nth 9 pss))) pos)
761 pss)))
762
763(defun lisp-indent-region (start end)
764 "Indent region as Lisp code, efficiently."
765 (save-excursion
766 (setq end (copy-marker end))
767 (goto-char start)
768 ;; The default `indent-region-line-by-line' doesn't hold a running
769 ;; parse state, which forces each indent call to reparse from the
770 ;; beginning. That has O(n^2) complexity.
771 (let* ((parse-state (lisp-ppss start))
772 (last-syntax-point start)
773 (pr (unless (minibufferp)
774 (make-progress-reporter "Indenting region..." (point) end))))
775 (while (< (point) end)
776 (unless (and (bolp) (eolp))
777 (lisp-indent-line parse-state))
778 (forward-line 1)
779 (let ((last-sexp (nth 2 parse-state)))
780 (setq parse-state (parse-partial-sexp last-syntax-point (point)
781 nil nil parse-state))
782 ;; It's important to preserve last sexp location for
783 ;; `calculate-lisp-indent'.
784 (unless (nth 2 parse-state)
785 (setf (nth 2 parse-state) last-sexp))
786 (setq last-syntax-point (point)))
787 (and pr (progress-reporter-update pr (point))))
788 (and pr (progress-reporter-done pr))
789 (move-marker end nil))))
790
791(defun lisp-indent-line (&optional parse-state)
792 "Indent current line as Lisp code."
793 (interactive)
794 (let ((pos (- (point-max) (point)))
795 (indent (progn (beginning-of-line)
796 (calculate-lisp-indent (or parse-state (lisp-ppss))))))
759 (skip-chars-forward " \t") 797 (skip-chars-forward " \t")
760 (if (or (null indent) (looking-at "\\s<\\s<\\s<")) 798 (if (or (null indent) (looking-at "\\s<\\s<\\s<"))
761 ;; Don't alter indentation of a ;;; comment line 799 ;; Don't alter indentation of a ;;; comment line
@@ -767,11 +805,7 @@ rigidly along with this one."
767 ;; as comment lines, not as code. 805 ;; as comment lines, not as code.
768 (progn (indent-for-comment) (forward-char -1)) 806 (progn (indent-for-comment) (forward-char -1))
769 (if (listp indent) (setq indent (car indent))) 807 (if (listp indent) (setq indent (car indent)))
770 (setq shift-amt (- indent (current-column))) 808 (indent-line-to indent))
771 (if (zerop shift-amt)
772 nil
773 (delete-region beg (point))
774 (indent-to indent)))
775 ;; If initial point was within line's indentation, 809 ;; If initial point was within line's indentation,
776 ;; position after the indentation. Else stay at same point in text. 810 ;; position after the indentation. Else stay at same point in text.
777 (if (> (- (point-max) pos) (point)) 811 (if (> (- (point-max) pos) (point))
@@ -785,6 +819,10 @@ In usual case returns an integer: the column to indent to.
785If the value is nil, that means don't change the indentation 819If the value is nil, that means don't change the indentation
786because the line starts inside a string. 820because the line starts inside a string.
787 821
822PARSE-START may be a buffer position to start parsing from, or a
823parse state as returned by calling `parse-partial-sexp' up to the
824beginning of the current line.
825
788The value can also be a list of the form (COLUMN CONTAINING-SEXP-START). 826The value can also be a list of the form (COLUMN CONTAINING-SEXP-START).
789This means that following lines at the same level of indentation 827This means that following lines at the same level of indentation
790should not necessarily be indented the same as this line. 828should not necessarily be indented the same as this line.
@@ -798,12 +836,14 @@ is the buffer position of the start of the containing expression."
798 (desired-indent nil) 836 (desired-indent nil)
799 (retry t) 837 (retry t)
800 calculate-lisp-indent-last-sexp containing-sexp) 838 calculate-lisp-indent-last-sexp containing-sexp)
801 (if parse-start 839 (cond ((or (markerp parse-start) (integerp parse-start))
802 (goto-char parse-start) 840 (goto-char parse-start))
803 (beginning-of-defun)) 841 ((null parse-start) (beginning-of-defun))
804 ;; Find outermost containing sexp 842 (t (setq state parse-start)))
805 (while (< (point) indent-point) 843 (unless state
806 (setq state (parse-partial-sexp (point) indent-point 0))) 844 ;; Find outermost containing sexp
845 (while (< (point) indent-point)
846 (setq state (parse-partial-sexp (point) indent-point 0))))
807 ;; Find innermost containing sexp 847 ;; Find innermost containing sexp
808 (while (and retry 848 (while (and retry
809 state 849 state
@@ -1074,11 +1114,6 @@ If optional arg ENDPOS is given, indent each line, stopping when
1074ENDPOS is encountered." 1114ENDPOS is encountered."
1075 (interactive) 1115 (interactive)
1076 (let* ((indent-stack (list nil)) 1116 (let* ((indent-stack (list nil))
1077 ;; If ENDPOS is non-nil, use beginning of defun as STARTING-POINT.
1078 ;; If ENDPOS is nil, it is safe not to scan before point
1079 ;; since every line we indent is more deeply nested than point is.
1080 (starting-point (save-excursion (if endpos (beginning-of-defun))
1081 (point)))
1082 ;; Use `syntax-ppss' to get initial state so we don't get 1117 ;; Use `syntax-ppss' to get initial state so we don't get
1083 ;; confused by starting inside a string. We don't use 1118 ;; confused by starting inside a string. We don't use
1084 ;; `syntax-ppss' in the loop, because this is measurably 1119 ;; `syntax-ppss' in the loop, because this is measurably
@@ -1087,27 +1122,32 @@ ENDPOS is encountered."
1087 (init-depth (car state)) 1122 (init-depth (car state))
1088 (next-depth init-depth) 1123 (next-depth init-depth)
1089 (last-depth init-depth) 1124 (last-depth init-depth)
1090 (last-syntax-point (point)) 1125 (last-syntax-point (point)))
1091 (real-endpos endpos)) 1126 ;; We need a marker because we modify the buffer
1092 (unless endpos 1127 ;; text preceding endpos.
1093 ;; Get error now if we don't have a complete sexp after point. 1128 (setq endpos (copy-marker
1094 (save-excursion (forward-sexp 1) 1129 (if endpos endpos
1095 ;; We need a marker because we modify the buffer 1130 ;; Get error now if we don't have a complete sexp
1096 ;; text preceding endpos. 1131 ;; after point.
1097 (setq endpos (point-marker)))) 1132 (save-excursion (forward-sexp 1) (point)))))
1098 (save-excursion 1133 (save-excursion
1099 (while (< (point) endpos) 1134 (while (< (point) endpos)
1100 ;; Parse this line so we can learn the state to indent the 1135 ;; Parse this line so we can learn the state to indent the
1101 ;; next line. 1136 ;; next line. Preserve element 2 of the state (last sexp) for
1102 (while (progn 1137 ;; `calculate-lisp-indent'.
1103 (setq state (parse-partial-sexp 1138 (let ((last-sexp (nth 2 state)))
1104 last-syntax-point (progn (end-of-line) (point)) 1139 (while (progn
1105 nil nil state)) 1140 (setq state (parse-partial-sexp
1106 ;; Skip over newlines within strings. 1141 last-syntax-point (progn (end-of-line) (point))
1107 (nth 3 state)) 1142 nil nil state))
1108 (setq state (parse-partial-sexp (point) (point-max) 1143 (setq last-sexp (or (nth 2 state) last-sexp))
1109 nil nil state 'syntax-table)) 1144 ;; Skip over newlines within strings.
1110 (setq last-syntax-point (point))) 1145 (nth 3 state))
1146 (setq state (parse-partial-sexp (point) (point-max)
1147 nil nil state 'syntax-table))
1148 (setq last-sexp (or (nth 2 state) last-sexp))
1149 (setq last-syntax-point (point)))
1150 (setf (nth 2 state) last-sexp))
1111 (setq next-depth (car state)) 1151 (setq next-depth (car state))
1112 ;; If the line contains a comment indent it now with 1152 ;; If the line contains a comment indent it now with
1113 ;; `indent-for-comment'. 1153 ;; `indent-for-comment'.
@@ -1120,9 +1160,9 @@ ENDPOS is encountered."
1120 (make-list (- init-depth next-depth) nil)) 1160 (make-list (- init-depth next-depth) nil))
1121 last-depth (- last-depth next-depth) 1161 last-depth (- last-depth next-depth)
1122 next-depth init-depth)) 1162 next-depth init-depth))
1163 ;; Now indent the next line according to what we learned from
1164 ;; parsing the previous one.
1123 (forward-line 1) 1165 (forward-line 1)
1124 (when (and (not real-endpos) (<= next-depth init-depth))
1125 (goto-char endpos))
1126 (when (< (point) endpos) 1166 (when (< (point) endpos)
1127 (let ((depth-delta (- next-depth last-depth))) 1167 (let ((depth-delta (- next-depth last-depth)))
1128 (cond ((< depth-delta 0) 1168 (cond ((< depth-delta 0)
@@ -1131,28 +1171,26 @@ ENDPOS is encountered."
1131 (setq indent-stack (nconc (make-list depth-delta nil) 1171 (setq indent-stack (nconc (make-list depth-delta nil)
1132 indent-stack)))) 1172 indent-stack))))
1133 (setq last-depth next-depth)) 1173 (setq last-depth next-depth))
1134 ;; Now indent the next line according
1135 ;; to what we learned from parsing the previous one.
1136 (skip-chars-forward " \t")
1137 ;; But not if the line is blank, or just a comment (we 1174 ;; But not if the line is blank, or just a comment (we
1138 ;; already called `indent-for-comment' above). 1175 ;; already called `indent-for-comment' above).
1176 (skip-chars-forward " \t")
1139 (unless (or (eolp) (eq (char-syntax (char-after)) ?<)) 1177 (unless (or (eolp) (eq (char-syntax (char-after)) ?<))
1140 (let ((this-indent (car indent-stack))) 1178 (indent-line-to
1141 (when (listp this-indent) 1179 (or (car indent-stack)
1142 (let ((val (calculate-lisp-indent 1180 ;; The state here is actually to the end of the
1143 (or (car this-indent) starting-point)))) 1181 ;; previous line, but that's fine for our purposes.
1144 (setq 1182 ;; And parsing over the newline would only destroy
1145 this-indent 1183 ;; element 2 (last sexp position).
1184 (let ((val (calculate-lisp-indent state)))
1146 (cond ((integerp val) 1185 (cond ((integerp val)
1147 (setf (car indent-stack) val)) 1186 (setf (car indent-stack) val))
1148 ((consp val) ; (COLUMN CONTAINING-SEXP-START) 1187 ((consp val) ; (COLUMN CONTAINING-SEXP-START)
1149 (setf (car indent-stack) (cdr val))
1150 (car val)) 1188 (car val))
1151 ;; `calculate-lisp-indent' only returns nil 1189 ;; `calculate-lisp-indent' only returns nil
1152 ;; when we're in a string, but this won't 1190 ;; when we're in a string, but this won't
1153 ;; happen because we skip strings above. 1191 ;; happen because we skip strings above.
1154 (t (error "This shouldn't happen!")))))) 1192 (t (error "This shouldn't happen!"))))))))))
1155 (indent-line-to this-indent)))))))) 1193 (move-marker endpos nil)))
1156 1194
1157(defun indent-pp-sexp (&optional arg) 1195(defun indent-pp-sexp (&optional arg)
1158 "Indent each line of the list starting just after point, or prettyprint it. 1196 "Indent each line of the list starting just after point, or prettyprint it.
diff --git a/test/lisp/emacs-lisp/lisp-mode-tests.el b/test/lisp/emacs-lisp/lisp-mode-tests.el
index 8e3f2e185cf..27f0bb5ec13 100644
--- a/test/lisp/emacs-lisp/lisp-mode-tests.el
+++ b/test/lisp/emacs-lisp/lisp-mode-tests.el
@@ -31,6 +31,9 @@
31 1 31 1
32 2) 32 2)
33 2) 33 2)
34 (fun arg1
35
36 arg2)
34 (1 37 (1
35 \"string 38 \"string
36noindent\" (\"string2 39noindent\" (\"string2
@@ -58,7 +61,7 @@ noindent\" 3
58 (save-excursion 61 (save-excursion
59 (let ((n 0)) 62 (let ((n 0))
60 (while (not (eobp)) 63 (while (not (eobp))
61 (unless (looking-at "noindent") 64 (unless (looking-at "noindent\\|^[[:blank:]]*$")
62 (insert (make-string n ?\s))) 65 (insert (make-string n ?\s)))
63 (cl-incf n) 66 (cl-incf n)
64 (forward-line)))) 67 (forward-line))))