From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.org!.POSTED!not-for-mail From: npostavs@users.sourceforge.net Newsgroups: gmane.emacs.bugs Subject: bug#26619: 26.0.50; Wrong indentation in emacs-lisp-mode Date: Fri, 28 Apr 2017 22:20:55 -0400 Message-ID: <87efwcned4.fsf@users.sourceforge.net> References: <87shkzsidm.fsf@calancha-pc> <87pofzontp.fsf@users.sourceforge.net> <87vaprlucs.fsf@drachen> <87mvb3omd0.fsf@users.sourceforge.net> <87h91aol5y.fsf@users.sourceforge.net> <87wpa62hlc.fsf@drachen> NNTP-Posting-Host: blaine.gmane.org Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" X-Trace: blaine.gmane.org 1493432446 10582 195.159.176.226 (29 Apr 2017 02:20:46 GMT) X-Complaints-To: usenet@blaine.gmane.org NNTP-Posting-Date: Sat, 29 Apr 2017 02:20:46 +0000 (UTC) User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.2 (gnu/linux) Cc: 26619@debbugs.gnu.org, Kaushal Modi To: Michael Heerdegen Original-X-From: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane.org@gnu.org Sat Apr 29 04:20:41 2017 Return-path: Envelope-to: geb-bug-gnu-emacs@m.gmane.org Original-Received: from lists.gnu.org ([208.118.235.17]) by blaine.gmane.org with esmtp (Exim 4.84_2) (envelope-from ) id 1d4I00-0002cN-MQ for geb-bug-gnu-emacs@m.gmane.org; Sat, 29 Apr 2017 04:20:41 +0200 Original-Received: from localhost ([::1]:39601 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1d4I06-0008MF-6m for geb-bug-gnu-emacs@m.gmane.org; Fri, 28 Apr 2017 22:20:46 -0400 Original-Received: from eggs.gnu.org ([2001:4830:134:3::10]:52714) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1d4HzS-000808-2S for bug-gnu-emacs@gnu.org; Fri, 28 Apr 2017 22:20:08 -0400 Original-Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1d4HzO-00052P-SQ for bug-gnu-emacs@gnu.org; Fri, 28 Apr 2017 22:20:06 -0400 Original-Received: from debbugs.gnu.org ([208.118.235.43]:47098) by eggs.gnu.org with esmtps (TLS1.0:RSA_AES_128_CBC_SHA1:16) (Exim 4.71) (envelope-from ) id 1d4HzO-000520-MJ for bug-gnu-emacs@gnu.org; Fri, 28 Apr 2017 22:20:02 -0400 Original-Received: from Debian-debbugs by debbugs.gnu.org with local (Exim 4.84_2) (envelope-from ) id 1d4HzO-0005ri-Ga for bug-gnu-emacs@gnu.org; Fri, 28 Apr 2017 22:20:02 -0400 X-Loop: help-debbugs@gnu.org Resent-From: npostavs@users.sourceforge.net Original-Sender: "Debbugs-submit" Resent-CC: bug-gnu-emacs@gnu.org Resent-Date: Sat, 29 Apr 2017 02:20:02 +0000 Resent-Message-ID: Resent-Sender: help-debbugs@gnu.org X-GNU-PR-Message: followup 26619 X-GNU-PR-Package: emacs X-GNU-PR-Keywords: patch Original-Received: via spool by 26619-submit@debbugs.gnu.org id=B26619.149343237322470 (code B ref 26619); Sat, 29 Apr 2017 02:20:02 +0000 Original-Received: (at 26619) by debbugs.gnu.org; 29 Apr 2017 02:19:33 +0000 Original-Received: from localhost ([127.0.0.1]:45297 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1d4Hyu-0005qM-HZ for submit@debbugs.gnu.org; Fri, 28 Apr 2017 22:19:33 -0400 Original-Received: from mail-it0-f68.google.com ([209.85.214.68]:35923) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1d4Hys-0005pz-7b for 26619@debbugs.gnu.org; Fri, 28 Apr 2017 22:19:30 -0400 Original-Received: by mail-it0-f68.google.com with SMTP id x188so7620371itb.3 for <26619@debbugs.gnu.org>; Fri, 28 Apr 2017 19:19:30 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=sender:from:to:cc:subject:references:date:in-reply-to:message-id :user-agent:mime-version; bh=gLHpLTxJWr+qFydShNpVchML4h/RfBznCbc22dGxz2w=; b=SgZvpVQ3LP/YzsI3EOQNFD/ssmLctbN4EBMLZ0FzwrYowKtkoMv+NhyE37FEwSoNwW kQf6qLS35E5P7leyZNCPrHvUBVrZzPtvPjGNUef5U0gZ12dDQ6B6nLJGQqUwttn5y7LI SIlcJQoqhyyO7ZblESnlt89SxCsyLVvZn3lWsBEb+v0/csVJv4tfbEFy4c+5fPPkByf3 qG4Gv3V5aPR6YO69tnkCRiJxZCqoFr7Kq5gA5X2xQOU2G9GcUaaQuI96rXFxPBp5DX1n GZmCQzRmsvvvOCJj509HIYT95Mqog2OC4Zu3rqDdyYS74ZZV0w3fN8rSuamsu5aKEdu7 2BwQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:sender:from:to:cc:subject:references:date :in-reply-to:message-id:user-agent:mime-version; bh=gLHpLTxJWr+qFydShNpVchML4h/RfBznCbc22dGxz2w=; b=cXqkiiRS1Xs4DITVTuTh2zaPR08Hm0sB0MvQ4VyF/DqQb7YjZbVcnQeA3RltG/jmyH CjF/4a4Hw197t31xHC75W4CbWnPra1+JMC+4b+yuhEWgagjJ1jqhEYnPSvas2YePI0p/ m+ILkX2iejJiI1y535ZvUulY8+KgI8DvXU4k0s9nS0J8ZPa6I8pXEjQYj18c8BaHMQR4 efNq/FttMj0i5t9uzEspfyK8uHJ6A318uuKfu/3oN8dyPNvKP1o/N4OIjiUdm7QowNLL NXaq0HrmJc6+YOJzPTLt2gfvGjZWT150CovZ8zOWispVCO1oavv7vmmaFhWunfmZpqed 8L7w== X-Gm-Message-State: AN3rC/5Yc/Dxas0tAd6FDG7+U/xA34xPq885G8i84pNMMQNFQuGaIchY JvjXiO6FCb4VNg== X-Received: by 10.36.3.13 with SMTP id e13mr12341638ite.112.1493432364672; Fri, 28 Apr 2017 19:19:24 -0700 (PDT) Original-Received: from zony ([45.2.7.65]) by smtp.googlemail.com with ESMTPSA id w192sm637891ith.5.2017.04.28.19.19.22 (version=TLS1_2 cipher=ECDHE-RSA-CHACHA20-POLY1305 bits=256/256); Fri, 28 Apr 2017 19:19:23 -0700 (PDT) In-Reply-To: <87wpa62hlc.fsf@drachen> (Michael Heerdegen's message of "Thu, 27 Apr 2017 13:52:15 +0200") X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] X-Received-From: 208.118.235.43 X-BeenThere: bug-gnu-emacs@gnu.org List-Id: "Bug reports for GNU Emacs, the Swiss army knife of text editors" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane.org@gnu.org Original-Sender: "bug-gnu-emacs" Xref: news.gmane.org gmane.emacs.bugs:132089 Archived-At: --=-=-= Content-Type: text/plain I think I got it this time. --=-=-= Content-Type: text/x-diff Content-Disposition: inline; filename=v3-0001-Fix-lisp-indent-region-and-indent-sexp-Bug-26619.patch Content-Description: patch >From 4b50ab92dddba5178ed5ea16a93ee5b53fec8f02 Mon Sep 17 00:00:00 2001 From: Noam Postavsky Date: Sun, 23 Apr 2017 10:43:05 -0400 Subject: [PATCH v3] Fix lisp-indent-region and indent-sexp (Bug#26619) * lisp/emacs-lisp/lisp-mode.el (lisp-ppss): Use an OLDSTATE correct that corresponds with the start point when calling parse-partial-sexp. (lisp-indent-state): New struct. (lisp-indent--next-line): New function. (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. --- lisp/emacs-lisp/lisp-mode.el | 168 ++++++++++++++++---------------- test/lisp/emacs-lisp/lisp-mode-tests.el | 85 +++++++++++++++- 2 files changed, 165 insertions(+), 88 deletions(-) diff --git a/lisp/emacs-lisp/lisp-mode.el b/lisp/emacs-lisp/lisp-mode.el index fa931e76ad..b1e73de5d1 100644 --- a/lisp/emacs-lisp/lisp-mode.el +++ b/lisp/emacs-lisp/lisp-mode.el @@ -755,49 +755,104 @@ lisp-indent-function (defun lisp-ppss (&optional pos) "Return Parse-Partial-Sexp State at POS, defaulting to point. -Like to `syntax-ppss' but includes the character address of the -last complete sexp in the innermost containing list at position +Like `syntax-ppss' but includes the character address of the last +complete sexp in the innermost containing list at position 2 (counting from 0). This is important for lisp indentation." (unless pos (setq pos (point))) (let ((pss (syntax-ppss pos))) (if (nth 9 pss) - (parse-partial-sexp (car (last (nth 9 pss))) pos) + (let ((sexp-start (car (last (nth 9 pss))))) + (parse-partial-sexp sexp-start pos nil nil (syntax-ppss sexp-start))) pss))) +(cl-defstruct (lisp-indent-state + (:constructor nil) + (:constructor lisp-indent-initial-state + (&aux (ppss (lisp-ppss)) + (last-syntax-point (point)) + (last-depth (car ppss)) + (indent-stack (make-list (1+ last-depth) nil))))) + indent-stack ; Cached indentation, per depth. + ppss + last-depth + last-syntax-point) + +(defun lisp-indent--next-line (state) + "Pass depth to update cache, or parse state for indentation." + (pcase-let (((cl-struct lisp-indent-state + indent-stack ppss last-depth last-syntax-point) + state)) + ;; Parse this line so we can learn the state to indent the + ;; next line. + (while (let ((last-sexp (nth 2 ppss))) + (setq ppss (parse-partial-sexp + last-syntax-point (progn (end-of-line) (point)) + nil nil ppss)) + ;; Preserve last sexp of state (position 2) for + ;; `calculate-lisp-indent', if we're at the same depth. + (if (and (not (nth 2 ppss)) (= last-depth (car ppss))) + (setf (nth 2 ppss) last-sexp) + (setq last-sexp (nth 2 ppss))) + ;; Skip over newlines within strings. + (nth 3 ppss)) + (let ((string-start (nth 8 ppss))) + (setq ppss (parse-partial-sexp (point) (point-max) + nil nil ppss 'syntax-table)) + (setf (nth 2 ppss) string-start)) ; Finished a complete string. + (setq last-syntax-point (point))) + (setq last-syntax-point (point)) + (let* ((depth (car ppss)) + (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)) + (prog1 + (let (indent) + (cond ((= (forward-line 1) 1) nil) + ((car indent-stack)) + ((integerp (setq indent (calculate-lisp-indent ppss))) + (setf (car indent-stack) indent)) + ((consp indent) ; (COLUMN CONTAINING-SEXP-START) + (car indent)) + ;; This only happens if we're in a string. + (t (error "This shouldn't happen")))) + (setf (lisp-indent-state-indent-stack state) indent-stack) + (setf (lisp-indent-state-last-depth state) last-depth) + (setf (lisp-indent-state-last-syntax-point state) last-syntax-point) + (setf (lisp-indent-state-ppss state) ppss)))) + (defun lisp-indent-region (start end) "Indent region as Lisp code, efficiently." (save-excursion (setq end (copy-marker end)) (goto-char start) + (beginning-of-line) ;; 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 (lisp-ppss start)) - (last-syntax-point start) + (let* ((parse-state (lisp-indent-initial-state)) (pr (unless (minibufferp) (make-progress-reporter "Indenting region..." (point) end)))) + (let ((ppss (lisp-indent-state-ppss parse-state))) + (unless (or (and (bolp) (eolp)) (nth 3 ppss)) + (lisp-indent-line (calculate-lisp-indent ppss)))) (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))) + (let ((indent (lisp-indent--next-line parse-state))) + (unless (or (and (bolp) (eolp)) (not indent)) + (lisp-indent-line indent))) (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,16 +1172,7 @@ 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 - ;; 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) - (last-syntax-point (point))) + (let* ((parse-state (lisp-indent-initial-state))) ;; We need a marker because we modify the buffer ;; text preceding endpos. (setq endpos (copy-marker @@ -1136,64 +1182,20 @@ indent-sexp (save-excursion (forward-sexp 1) (point))))) (save-excursion (while (< (point) endpos) - ;; 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))) - (while (progn - (setq state (parse-partial-sexp - last-syntax-point (progn (end-of-line) (point)) - nil nil state)) - (setq last-sexp (or (nth 2 state) last-sexp)) - ;; Skip over newlines within strings. - (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)) - (setq last-syntax-point (point))) - (setf (nth 2 state) last-sexp)) - (setq next-depth (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)) + (let ((indent (lisp-indent--next-line parse-state))) + ;; If the line contains a comment indent it now with + ;; `indent-for-comment'. + (when (nth 4 (lisp-indent-state-ppss parse-state)) + (save-excursion + (goto-char (lisp-indent-state-last-syntax-point parse-state)) + (indent-for-comment) + (setf (lisp-indent-state-last-syntax-point parse-state) + (line-end-position)))) ;; 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!")))))))))) + (unless (or (eolp) (eq (char-syntax (char-after)) ?<) (not indent)) + (indent-line-to indent))))) (move-marker endpos nil))) (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 27f0bb5ec1..1f78eb3010 100644 --- a/test/lisp/emacs-lisp/lisp-mode-tests.el +++ b/test/lisp/emacs-lisp/lisp-mode-tests.el @@ -21,10 +21,7 @@ (require 'cl-lib) (require 'lisp-mode) -(ert-deftest indent-sexp () - "Test basics of \\[indent-sexp]." - (with-temp-buffer - (insert "\ +(defconst lisp-mode-tests--correctly-indented-sexp "\ \(a (prog1 (prog1 @@ -42,9 +39,14 @@ 2) ; comment ;; comment b)") + +(ert-deftest indent-sexp () + "Test basics of \\[indent-sexp]." + (with-temp-buffer + (insert lisp-mode-tests--correctly-indented-sexp) (goto-char (point-min)) (let ((indent-tabs-mode nil) - (correct (buffer-string))) + (correct lisp-mode-tests--correctly-indented-sexp)) (dolist (mode '(fundamental-mode emacs-lisp-mode)) (funcall mode) (indent-sexp) @@ -97,5 +99,78 @@ (indent-sexp) (should (equal (buffer-string) correct))))) +(ert-deftest lisp-indent-region () + "Test basics of `lisp-indent-region'." + (with-temp-buffer + (insert lisp-mode-tests--correctly-indented-sexp) + (goto-char (point-min)) + (let ((indent-tabs-mode nil) + (correct lisp-mode-tests--correctly-indented-sexp)) + (emacs-lisp-mode) + (indent-region (point-min) (point-max)) + ;; Don't mess up correctly indented code. + (should (string= (buffer-string) correct)) + ;; Correctly add indentation. + (save-excursion + (while (not (eobp)) + (delete-horizontal-space) + (forward-line))) + (indent-region (point-min) (point-max)) + (should (equal (buffer-string) correct)) + ;; Correctly remove indentation. + (save-excursion + (let ((n 0)) + (while (not (eobp)) + (unless (looking-at "noindent\\|^[[:blank:]]*$") + (insert (make-string n ?\s))) + (cl-incf n) + (forward-line)))) + (indent-region (point-min) (point-max)) + (should (equal (buffer-string) correct))))) + + +(ert-deftest lisp-indent-region-defun-with-docstring () + "Test Bug#26619." + (with-temp-buffer + (insert "\ +\(defun test () + \"This is a test. +Test indentation in emacs-lisp-mode\" + (message \"Hi!\"))") + (let ((indent-tabs-mode nil) + (correct (buffer-string))) + (emacs-lisp-mode) + (indent-region (point-min) (point-max)) + (should (equal (buffer-string) correct))))) + +(ert-deftest lisp-indent-region-open-paren () + (with-temp-buffer + (insert "\ +\(with-eval-after-load 'foo + (setq bar `( + baz)))") + (let ((indent-tabs-mode nil) + (correct (buffer-string))) + (emacs-lisp-mode) + (indent-region (point-min) (point-max)) + (should (equal (buffer-string) correct))))) + +(ert-deftest lisp-indent-region-in-sexp () + (with-temp-buffer + (insert "\ +\(when t + (when t + (list 1 2 3) + 'etc) + (quote etc) + (quote etc))") + (let ((indent-tabs-mode nil) + (correct (buffer-string))) + (emacs-lisp-mode) + (search-backward "1") + (indent-region (point) (point-max)) + (should (equal (buffer-string) correct))))) + + (provide 'lisp-mode-tests) ;;; lisp-mode-tests.el ends here -- 2.11.1 --=-=-=--