diff --git c/lisp/emacs-lisp/lisp-mode.el w/lisp/emacs-lisp/lisp-mode.el index fa931e76ad..12fd53140c 100644 --- c/lisp/emacs-lisp/lisp-mode.el +++ w/lisp/emacs-lisp/lisp-mode.el @@ -764,6 +764,36 @@ lisp-ppss (parse-partial-sexp (car (last (nth 9 pss))) pos) pss))) +(defun lisp-mode--indent-cache (init-depth) + "Returns a closure that computes indentation, caching by depth." + (let ((indent-stack (list nil)) + (last-depth init-depth)) + (lambda (&optional depth-or-state) + "Pass depth to update cache, or parse state for indentation." + (if (listp depth-or-state) ; It's a parse state. + (let ((val (if (car indent-stack) indent-stack + (calculate-lisp-indent depth-or-state)))) + (cond ((nth 3 depth-or-state) nil) ; Inside a string. + ((integerp val) + (setf (car indent-stack) val)) + ((consp val) ; (COLUMN CONTAINING-SEXP-START) + (car val)) + ;; This only happens if we're in a string. + (t (error "This shouldn't happen")))) + (let ((depth depth-or-state)) ; It's a depth. + (when (< depth init-depth) + (setq indent-stack (nconc indent-stack + (make-list (- init-depth depth) nil)) + last-depth (- last-depth depth) + depth init-depth)) + (let ((depth-delta (- depth last-depth))) + (cond ((< depth-delta 0) + (setq indent-stack (nthcdr (- depth-delta) indent-stack))) + ((> depth-delta 0) + (setq indent-stack (nconc (make-list depth-delta nil) + indent-stack)))) + (setq last-depth depth))))))) + (defun lisp-indent-region (start end) "Indent region as Lisp code, efficiently." (save-excursion @@ -773,31 +803,45 @@ lisp-indent-region ;; parse state, which forces each indent call to reparse from the ;; beginning. That has O(n^2) complexity. (let* ((parse-state (lisp-ppss start)) + (calc-indent (lisp-mode--indent-cache (car parse-state))) (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)) + (lisp-indent-line (funcall calc-indent 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))) + (let ((oldstate parse-state) + (target-point (point))) + (while + (progn + (setq parse-state (parse-partial-sexp last-syntax-point target-point + nil t oldstate)) + (if (>= (point) target-point) + nil ; Done. + (when (= (nth 0 parse-state) (nth 0 oldstate)) ; Stopped before open paren. + (setq parse-state (parse-partial-sexp last-syntax-point target-point + (1+ (nth 0 parse-state)) nil parse-state))) + (setq last-syntax-point (point)) + ;; It's important to preserve last sexp location for + ;; `calculate-lisp-indent', but it's only relevant at the + ;; same depth. + (unless (or (nth 2 parse-state) (/= (nth 0 parse-state) (nth 0 oldstate))) + (setf (nth 2 parse-state) (nth 2 oldstate))) + t)) + (setq oldstate parse-state))) + ;; Update cache's depth stack. + (funcall calc-indent (car parse-state)) (and pr (progress-reporter-update pr (point)))) (and pr (progress-reporter-done pr)) (move-marker end nil)))) -(defun lisp-indent-line (&optional parse-state) +(defun lisp-indent-line (&optional indent) "Indent current line as Lisp code." (interactive) (let ((pos (- (point-max) (point))) (indent (progn (beginning-of-line) - (calculate-lisp-indent (or parse-state (lisp-ppss)))))) + (or indent (calculate-lisp-indent (lisp-ppss)))))) (skip-chars-forward " \t") (if (or (null indent) (looking-at "\\s<\\s<\\s<")) ;; Don't alter indentation of a ;;; comment line @@ -1117,15 +1161,12 @@ indent-sexp If optional arg ENDPOS is given, indent each line, stopping when ENDPOS is encountered." (interactive) - (let* ((indent-stack (list nil)) - ;; Use `syntax-ppss' to get initial state so we don't get + (let* (;; Use `syntax-ppss' to get initial state so we don't get ;; confused by starting inside a string. We don't use ;; `syntax-ppss' in the loop, because this is measurably ;; slower when we're called on a long list. (state (syntax-ppss)) - (init-depth (car state)) - (next-depth init-depth) - (last-depth init-depth) + (calc-indent (lisp-mode--indent-cache (car state))) (last-syntax-point (point))) ;; We need a marker because we modify the buffer ;; text preceding endpos. @@ -1139,7 +1180,8 @@ indent-sexp ;; Parse this line so we can learn the state to indent the ;; next line. Preserve element 2 of the state (last sexp) for ;; `calculate-lisp-indent'. - (let ((last-sexp (nth 2 state))) + (let ((last-depth (nth 0 state)) + (last-sexp (nth 2 state))) (while (progn (setq state (parse-partial-sexp last-syntax-point (progn (end-of-line) (point)) @@ -1149,51 +1191,33 @@ indent-sexp (nth 3 state)) (setq state (parse-partial-sexp (point) (point-max) nil nil state 'syntax-table)) - (setq last-sexp (or (nth 2 state) last-sexp)) + (when (nth 2 state) + (setq last-sexp (nth 2 state)) + (setq last-depth (nth 0 state))) (setq last-syntax-point (point))) (setf (nth 2 state) last-sexp)) - (setq next-depth (car state)) + ;; Update cache's depth stack. + (funcall calc-indent (car state)) ;; If the line contains a comment indent it now with ;; `indent-for-comment'. (when (nth 4 state) (indent-for-comment) (end-of-line)) (setq last-syntax-point (point)) - (when (< next-depth init-depth) - (setq indent-stack (nconc indent-stack - (make-list (- init-depth next-depth) nil)) - last-depth (- last-depth next-depth) - next-depth init-depth)) ;; Now indent the next line according to what we learned from ;; parsing the previous one. (forward-line 1) (when (< (point) endpos) - (let ((depth-delta (- next-depth last-depth))) - (cond ((< depth-delta 0) - (setq indent-stack (nthcdr (- depth-delta) indent-stack))) - ((> depth-delta 0) - (setq indent-stack (nconc (make-list depth-delta nil) - indent-stack)))) - (setq last-depth next-depth)) ;; But not if the line is blank, or just a comment (we ;; already called `indent-for-comment' above). (skip-chars-forward " \t") (unless (or (eolp) (eq (char-syntax (char-after)) ?<)) (indent-line-to - (or (car indent-stack) - ;; The state here is actually to the end of the - ;; previous line, but that's fine for our purposes. - ;; And parsing over the newline would only destroy - ;; element 2 (last sexp position). - (let ((val (calculate-lisp-indent state))) - (cond ((integerp val) - (setf (car indent-stack) val)) - ((consp val) ; (COLUMN CONTAINING-SEXP-START) - (car val)) - ;; `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!")))))))))) + ;; The state here is actually to the end of the + ;; previous line, but that's fine for our purposes. + ;; And parsing over the newline would only destroy + ;; element 2 (last sexp position). + (funcall calc-indent state)))))) (move-marker endpos nil))) (defun indent-pp-sexp (&optional arg)