diff options
| author | Noam Postavsky | 2017-04-22 14:10:58 -0400 |
|---|---|---|
| committer | Noam Postavsky | 2017-04-22 14:18:46 -0400 |
| commit | 6fa9cc0593150a318f0e08e69ec10672d548a7c1 (patch) | |
| tree | 5f3ba3603a4bc724180d9b872b50c183c39a664b | |
| parent | 66dc8dd6d13d37ef23b52873293d95d87dca497f (diff) | |
| parent | 4713dd425beac5cb459704e67dcb8f6faf714375 (diff) | |
| download | emacs-6fa9cc0593150a318f0e08e69ec10672d548a7c1.tar.gz emacs-6fa9cc0593150a318f0e08e69ec10672d548a7c1.zip | |
; Merge: improve indent-sexp and lisp-indent-region performance
| -rw-r--r-- | lisp/emacs-lisp/lisp-mode.el | 150 | ||||
| -rw-r--r-- | test/lisp/emacs-lisp/lisp-mode-tests.el | 5 |
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. |
| 753 | With argument, indent any additional lines of the same expression | 754 | Like to `syntax-ppss' but includes the character address of the |
| 754 | rigidly along with this one." | 755 | last complete sexp in the innermost containing list at position |
| 755 | (interactive "P") | 756 | 2 (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. | |||
| 785 | If the value is nil, that means don't change the indentation | 819 | If the value is nil, that means don't change the indentation |
| 786 | because the line starts inside a string. | 820 | because the line starts inside a string. |
| 787 | 821 | ||
| 822 | PARSE-START may be a buffer position to start parsing from, or a | ||
| 823 | parse state as returned by calling `parse-partial-sexp' up to the | ||
| 824 | beginning of the current line. | ||
| 825 | |||
| 788 | The value can also be a list of the form (COLUMN CONTAINING-SEXP-START). | 826 | The value can also be a list of the form (COLUMN CONTAINING-SEXP-START). |
| 789 | This means that following lines at the same level of indentation | 827 | This means that following lines at the same level of indentation |
| 790 | should not necessarily be indented the same as this line. | 828 | should 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 | |||
| 1074 | ENDPOS is encountered." | 1114 | ENDPOS 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 |
| 36 | noindent\" (\"string2 | 39 | noindent\" (\"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)))) |