aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNoam Postavsky2017-04-23 10:43:05 -0400
committerNoam Postavsky2017-05-09 20:50:19 -0400
commite7b6751c0a74f24c14cd207d57a4e1a95f409256 (patch)
tree14f60933c9d78aabf42ba79abd911f9ef3e3d373
parent17e540aa428c5392f7a9b4c1f7495bac8a8fe5da (diff)
downloademacs-e7b6751c0a74f24c14cd207d57a4e1a95f409256.tar.gz
emacs-e7b6751c0a74f24c14cd207d57a4e1a95f409256.zip
Fix lisp-indent-region and indent-sexp (Bug#26619)
The new lisp-indent-region introduced in 2017-04-22 "Add new `lisp-indent-region' that doesn't reparse the code." is broken because it doesn't save the calculated indent amounts for already seen sexp depths. Fix this by unifying the indent-sexp and lisp-indent-region code. Furthermore, only preserve position 2 of the running parse when the depth doesn't change. * lisp/emacs-lisp/lisp-mode.el (lisp-ppss): Use an OLDSTATE that corresponds with the start point when calling parse-partial-sexp. (lisp-indent-state): New struct. (lisp-indent-calc-next): New function, extracted from indent-sexp. (indent-sexp, lisp-indent-region): Use it. (lisp-indent-line): Take indentation, instead of parse state. * test/lisp/emacs-lisp/lisp-mode-tests.el (lisp-mode-tests--correctly-indented-sexp): New constant. (lisp-indent-region, lisp-indent-region-defun-with-docstring): (lisp-indent-region-open-paren, lisp-indent-region-in-sexp): New tests.
-rw-r--r--lisp/emacs-lisp/lisp-mode.el176
-rw-r--r--test/lisp/emacs-lisp/lisp-mode-tests.el85
2 files changed, 171 insertions, 90 deletions
diff --git a/lisp/emacs-lisp/lisp-mode.el b/lisp/emacs-lisp/lisp-mode.el
index 7448864ff99..6287f27b139 100644
--- a/lisp/emacs-lisp/lisp-mode.el
+++ b/lisp/emacs-lisp/lisp-mode.el
@@ -754,49 +754,108 @@ function is `common-lisp-indent-function'."
754 754
755(defun lisp-ppss (&optional pos) 755(defun lisp-ppss (&optional pos)
756 "Return Parse-Partial-Sexp State at POS, defaulting to point. 756 "Return Parse-Partial-Sexp State at POS, defaulting to point.
757Like to `syntax-ppss' but includes the character address of the 757Like `syntax-ppss' but includes the character address of the last
758last complete sexp in the innermost containing list at position 758complete sexp in the innermost containing list at position
7592 (counting from 0). This is important for lisp indentation." 7592 (counting from 0). This is important for lisp indentation."
760 (unless pos (setq pos (point))) 760 (unless pos (setq pos (point)))
761 (let ((pss (syntax-ppss pos))) 761 (let ((pss (syntax-ppss pos)))
762 (if (nth 9 pss) 762 (if (nth 9 pss)
763 (parse-partial-sexp (car (last (nth 9 pss))) pos) 763 (let ((sexp-start (car (last (nth 9 pss)))))
764 (parse-partial-sexp sexp-start pos nil nil (syntax-ppss sexp-start)))
764 pss))) 765 pss)))
765 766
767(cl-defstruct (lisp-indent-state
768 (:constructor nil)
769 (:constructor lisp-indent-initial-state
770 (&aux (ppss (lisp-ppss))
771 (ppss-point (point))
772 (depth (car ppss))
773 (stack (make-list (1+ depth) nil)))))
774 stack ;; Cached indentation, per depth.
775 ppss
776 depth
777 ppss-point)
778
779(defun lisp-indent-calc-next (state)
780 "Move to next line and return calculated indent for it.
781STATE is updated by side effect, the first state should be
782created by `lisp-indent-initial-state'. This function may move
783by more than one line to cross a string literal."
784 (pcase-let (((cl-struct lisp-indent-state
785 (stack indent-stack) ppss depth ppss-point)
786 state))
787 ;; Parse this line so we can learn the state to indent the
788 ;; next line.
789 (while (let ((last-sexp (nth 2 ppss)))
790 (setq ppss (parse-partial-sexp
791 ppss-point (progn (end-of-line) (point))
792 nil nil ppss))
793 ;; Preserve last sexp of state (position 2) for
794 ;; `calculate-lisp-indent', if we're at the same depth.
795 (if (and (not (nth 2 ppss)) (= depth (car ppss)))
796 (setf (nth 2 ppss) last-sexp)
797 (setq last-sexp (nth 2 ppss)))
798 ;; Skip over newlines within strings.
799 (nth 3 ppss))
800 (let ((string-start (nth 8 ppss)))
801 (setq ppss (parse-partial-sexp (point) (point-max)
802 nil nil ppss 'syntax-table))
803 (setf (nth 2 ppss) string-start)) ; Finished a complete string.
804 (setq ppss-point (point)))
805 (setq ppss-point (point))
806 (let* ((next-depth (car ppss))
807 (depth-delta (- next-depth depth)))
808 (cond ((< depth-delta 0)
809 (setq indent-stack (nthcdr (- depth-delta) indent-stack)))
810 ((> depth-delta 0)
811 (setq indent-stack (nconc (make-list depth-delta nil)
812 indent-stack))))
813 (setq depth next-depth))
814 (prog1
815 (let (indent)
816 (cond ((= (forward-line 1) 1) nil)
817 ((car indent-stack))
818 ((integerp (setq indent (calculate-lisp-indent ppss)))
819 (setf (car indent-stack) indent))
820 ((consp indent) ; (COLUMN CONTAINING-SEXP-START)
821 (car indent))
822 ;; This only happens if we're in a string.
823 (t (error "This shouldn't happen"))))
824 (setf (lisp-indent-state-stack state) indent-stack)
825 (setf (lisp-indent-state-depth state) depth)
826 (setf (lisp-indent-state-ppss-point state) ppss-point)
827 (setf (lisp-indent-state-ppss state) ppss))))
828
766(defun lisp-indent-region (start end) 829(defun lisp-indent-region (start end)
767 "Indent region as Lisp code, efficiently." 830 "Indent region as Lisp code, efficiently."
768 (save-excursion 831 (save-excursion
769 (setq end (copy-marker end)) 832 (setq end (copy-marker end))
770 (goto-char start) 833 (goto-char start)
834 (beginning-of-line)
771 ;; The default `indent-region-line-by-line' doesn't hold a running 835 ;; The default `indent-region-line-by-line' doesn't hold a running
772 ;; parse state, which forces each indent call to reparse from the 836 ;; parse state, which forces each indent call to reparse from the
773 ;; beginning. That has O(n^2) complexity. 837 ;; beginning. That has O(n^2) complexity.
774 (let* ((parse-state (lisp-ppss start)) 838 (let* ((parse-state (lisp-indent-initial-state))
775 (last-syntax-point start)
776 (pr (unless (minibufferp) 839 (pr (unless (minibufferp)
777 (make-progress-reporter "Indenting region..." (point) end)))) 840 (make-progress-reporter "Indenting region..." (point) end))))
778 (while (< (point) end) 841 (let ((ppss (lisp-indent-state-ppss parse-state)))
779 (unless (and (bolp) (eolp)) 842 (unless (or (and (bolp) (eolp)) (nth 3 ppss))
780 (lisp-indent-line parse-state)) 843 (lisp-indent-line (calculate-lisp-indent ppss))))
781 (forward-line 1) 844 (let ((indent nil))
782 (let ((last-sexp (nth 2 parse-state))) 845 (while (progn (setq indent (lisp-indent-calc-next parse-state))
783 (setq parse-state (parse-partial-sexp last-syntax-point (point) 846 (< (point) end))
784 nil nil parse-state)) 847 (unless (or (and (bolp) (eolp)) (not indent))
785 ;; It's important to preserve last sexp location for 848 (lisp-indent-line indent))
786 ;; `calculate-lisp-indent'. 849 (and pr (progress-reporter-update pr (point)))))
787 (unless (nth 2 parse-state)
788 (setf (nth 2 parse-state) last-sexp))
789 (setq last-syntax-point (point)))
790 (and pr (progress-reporter-update pr (point))))
791 (and pr (progress-reporter-done pr)) 850 (and pr (progress-reporter-done pr))
792 (move-marker end nil)))) 851 (move-marker end nil))))
793 852
794(defun lisp-indent-line (&optional parse-state) 853(defun lisp-indent-line (&optional indent)
795 "Indent current line as Lisp code." 854 "Indent current line as Lisp code."
796 (interactive) 855 (interactive)
797 (let ((pos (- (point-max) (point))) 856 (let ((pos (- (point-max) (point)))
798 (indent (progn (beginning-of-line) 857 (indent (progn (beginning-of-line)
799 (calculate-lisp-indent (or parse-state (lisp-ppss)))))) 858 (or indent (calculate-lisp-indent (lisp-ppss))))))
800 (skip-chars-forward " \t") 859 (skip-chars-forward " \t")
801 (if (or (null indent) (looking-at "\\s<\\s<\\s<")) 860 (if (or (null indent) (looking-at "\\s<\\s<\\s<"))
802 ;; Don't alter indentation of a ;;; comment line 861 ;; Don't alter indentation of a ;;; comment line
@@ -1116,16 +1175,7 @@ Lisp function does not specify a special indentation."
1116If optional arg ENDPOS is given, indent each line, stopping when 1175If optional arg ENDPOS is given, indent each line, stopping when
1117ENDPOS is encountered." 1176ENDPOS is encountered."
1118 (interactive) 1177 (interactive)
1119 (let* ((indent-stack (list nil)) 1178 (let* ((parse-state (lisp-indent-initial-state)))
1120 ;; Use `syntax-ppss' to get initial state so we don't get
1121 ;; confused by starting inside a string. We don't use
1122 ;; `syntax-ppss' in the loop, because this is measurably
1123 ;; slower when we're called on a long list.
1124 (state (syntax-ppss))
1125 (init-depth (car state))
1126 (next-depth init-depth)
1127 (last-depth init-depth)
1128 (last-syntax-point (point)))
1129 ;; We need a marker because we modify the buffer 1179 ;; We need a marker because we modify the buffer
1130 ;; text preceding endpos. 1180 ;; text preceding endpos.
1131 (setq endpos (copy-marker 1181 (setq endpos (copy-marker
@@ -1135,64 +1185,20 @@ ENDPOS is encountered."
1135 (save-excursion (forward-sexp 1) (point))))) 1185 (save-excursion (forward-sexp 1) (point)))))
1136 (save-excursion 1186 (save-excursion
1137 (while (< (point) endpos) 1187 (while (< (point) endpos)
1138 ;; Parse this line so we can learn the state to indent the 1188 (let ((indent (lisp-indent-calc-next parse-state)))
1139 ;; next line. Preserve element 2 of the state (last sexp) for 1189 ;; If the line contains a comment indent it now with
1140 ;; `calculate-lisp-indent'. 1190 ;; `indent-for-comment'.
1141 (let ((last-sexp (nth 2 state))) 1191 (when (nth 4 (lisp-indent-state-ppss parse-state))
1142 (while (progn 1192 (save-excursion
1143 (setq state (parse-partial-sexp 1193 (goto-char (lisp-indent-state-ppss-point parse-state))
1144 last-syntax-point (progn (end-of-line) (point)) 1194 (indent-for-comment)
1145 nil nil state)) 1195 (setf (lisp-indent-state-ppss-point parse-state)
1146 (setq last-sexp (or (nth 2 state) last-sexp)) 1196 (line-end-position))))
1147 ;; Skip over newlines within strings.
1148 (nth 3 state))
1149 (setq state (parse-partial-sexp (point) (point-max)
1150 nil nil state 'syntax-table))
1151 (setq last-sexp (or (nth 2 state) last-sexp))
1152 (setq last-syntax-point (point)))
1153 (setf (nth 2 state) last-sexp))
1154 (setq next-depth (car state))
1155 ;; If the line contains a comment indent it now with
1156 ;; `indent-for-comment'.
1157 (when (nth 4 state)
1158 (indent-for-comment)
1159 (end-of-line))
1160 (setq last-syntax-point (point))
1161 (when (< next-depth init-depth)
1162 (setq indent-stack (nconc indent-stack
1163 (make-list (- init-depth next-depth) nil))
1164 last-depth (- last-depth next-depth)
1165 next-depth init-depth))
1166 ;; Now indent the next line according to what we learned from
1167 ;; parsing the previous one.
1168 (forward-line 1)
1169 (when (< (point) endpos)
1170 (let ((depth-delta (- next-depth last-depth)))
1171 (cond ((< depth-delta 0)
1172 (setq indent-stack (nthcdr (- depth-delta) indent-stack)))
1173 ((> depth-delta 0)
1174 (setq indent-stack (nconc (make-list depth-delta nil)
1175 indent-stack))))
1176 (setq last-depth next-depth))
1177 ;; But not if the line is blank, or just a comment (we 1197 ;; But not if the line is blank, or just a comment (we
1178 ;; already called `indent-for-comment' above). 1198 ;; already called `indent-for-comment' above).
1179 (skip-chars-forward " \t") 1199 (skip-chars-forward " \t")
1180 (unless (or (eolp) (eq (char-syntax (char-after)) ?<)) 1200 (unless (or (eolp) (eq (char-syntax (char-after)) ?<) (not indent))
1181 (indent-line-to 1201 (indent-line-to indent)))))
1182 (or (car indent-stack)
1183 ;; The state here is actually to the end of the
1184 ;; previous line, but that's fine for our purposes.
1185 ;; And parsing over the newline would only destroy
1186 ;; element 2 (last sexp position).
1187 (let ((val (calculate-lisp-indent state)))
1188 (cond ((integerp val)
1189 (setf (car indent-stack) val))
1190 ((consp val) ; (COLUMN CONTAINING-SEXP-START)
1191 (car val))
1192 ;; `calculate-lisp-indent' only returns nil
1193 ;; when we're in a string, but this won't
1194 ;; happen because we skip strings above.
1195 (t (error "This shouldn't happen!"))))))))))
1196 (move-marker endpos nil))) 1202 (move-marker endpos nil)))
1197 1203
1198(defun indent-pp-sexp (&optional arg) 1204(defun indent-pp-sexp (&optional arg)
diff --git a/test/lisp/emacs-lisp/lisp-mode-tests.el b/test/lisp/emacs-lisp/lisp-mode-tests.el
index 27f0bb5ec13..1f78eb30105 100644
--- a/test/lisp/emacs-lisp/lisp-mode-tests.el
+++ b/test/lisp/emacs-lisp/lisp-mode-tests.el
@@ -21,10 +21,7 @@
21(require 'cl-lib) 21(require 'cl-lib)
22(require 'lisp-mode) 22(require 'lisp-mode)
23 23
24(ert-deftest indent-sexp () 24(defconst lisp-mode-tests--correctly-indented-sexp "\
25 "Test basics of \\[indent-sexp]."
26 (with-temp-buffer
27 (insert "\
28\(a 25\(a
29 (prog1 26 (prog1
30 (prog1 27 (prog1
@@ -42,9 +39,14 @@ noindent\" 3
42 2) ; comment 39 2) ; comment
43 ;; comment 40 ;; comment
44 b)") 41 b)")
42
43(ert-deftest indent-sexp ()
44 "Test basics of \\[indent-sexp]."
45 (with-temp-buffer
46 (insert lisp-mode-tests--correctly-indented-sexp)
45 (goto-char (point-min)) 47 (goto-char (point-min))
46 (let ((indent-tabs-mode nil) 48 (let ((indent-tabs-mode nil)
47 (correct (buffer-string))) 49 (correct lisp-mode-tests--correctly-indented-sexp))
48 (dolist (mode '(fundamental-mode emacs-lisp-mode)) 50 (dolist (mode '(fundamental-mode emacs-lisp-mode))
49 (funcall mode) 51 (funcall mode)
50 (indent-sexp) 52 (indent-sexp)
@@ -97,5 +99,78 @@ noindent\" 3
97 (indent-sexp) 99 (indent-sexp)
98 (should (equal (buffer-string) correct))))) 100 (should (equal (buffer-string) correct)))))
99 101
102(ert-deftest lisp-indent-region ()
103 "Test basics of `lisp-indent-region'."
104 (with-temp-buffer
105 (insert lisp-mode-tests--correctly-indented-sexp)
106 (goto-char (point-min))
107 (let ((indent-tabs-mode nil)
108 (correct lisp-mode-tests--correctly-indented-sexp))
109 (emacs-lisp-mode)
110 (indent-region (point-min) (point-max))
111 ;; Don't mess up correctly indented code.
112 (should (string= (buffer-string) correct))
113 ;; Correctly add indentation.
114 (save-excursion
115 (while (not (eobp))
116 (delete-horizontal-space)
117 (forward-line)))
118 (indent-region (point-min) (point-max))
119 (should (equal (buffer-string) correct))
120 ;; Correctly remove indentation.
121 (save-excursion
122 (let ((n 0))
123 (while (not (eobp))
124 (unless (looking-at "noindent\\|^[[:blank:]]*$")
125 (insert (make-string n ?\s)))
126 (cl-incf n)
127 (forward-line))))
128 (indent-region (point-min) (point-max))
129 (should (equal (buffer-string) correct)))))
130
131
132(ert-deftest lisp-indent-region-defun-with-docstring ()
133 "Test Bug#26619."
134 (with-temp-buffer
135 (insert "\
136\(defun test ()
137 \"This is a test.
138Test indentation in emacs-lisp-mode\"
139 (message \"Hi!\"))")
140 (let ((indent-tabs-mode nil)
141 (correct (buffer-string)))
142 (emacs-lisp-mode)
143 (indent-region (point-min) (point-max))
144 (should (equal (buffer-string) correct)))))
145
146(ert-deftest lisp-indent-region-open-paren ()
147 (with-temp-buffer
148 (insert "\
149\(with-eval-after-load 'foo
150 (setq bar `(
151 baz)))")
152 (let ((indent-tabs-mode nil)
153 (correct (buffer-string)))
154 (emacs-lisp-mode)
155 (indent-region (point-min) (point-max))
156 (should (equal (buffer-string) correct)))))
157
158(ert-deftest lisp-indent-region-in-sexp ()
159 (with-temp-buffer
160 (insert "\
161\(when t
162 (when t
163 (list 1 2 3)
164 'etc)
165 (quote etc)
166 (quote etc))")
167 (let ((indent-tabs-mode nil)
168 (correct (buffer-string)))
169 (emacs-lisp-mode)
170 (search-backward "1")
171 (indent-region (point) (point-max))
172 (should (equal (buffer-string) correct)))))
173
174
100(provide 'lisp-mode-tests) 175(provide 'lisp-mode-tests)
101;;; lisp-mode-tests.el ends here 176;;; lisp-mode-tests.el ends here