all messages for Emacs-related lists mirrored at yhetil.org
 help / color / mirror / code / Atom feed
From: Tom Tromey <tom@tromey.com>
To: 20896@debbugs.gnu.org
Cc: Daniel Colascione <dan.colascione@gmail.com>
Subject: bug#20896: patch to add chained indentation
Date: Mon, 09 Jan 2017 23:29:20 -0700	[thread overview]
Message-ID: <87tw97zav3.fsf@tromey.com> (raw)
In-Reply-To: <87a8vnlrxs.fsf@gmx.us>

This patch adds chained indentation, as requested in the bug.  It comes
with some tests (added to a file that first appears in patch in another
bug -- I can commit these when the time comes, mostly I'm interested in
review).

Tom

diff --git a/lisp/progmodes/js.el b/lisp/progmodes/js.el
index 0551f2a..1211631 100644
--- a/lisp/progmodes/js.el
+++ b/lisp/progmodes/js.el
@@ -552,6 +552,20 @@ js-indent-first-init
   :safe 'symbolp
   :group 'js)
 
+(defcustom js-chain-indent nil
+  "Use \"chained\" indentation.
+Chained indentation applies when the current line starts with \".\".
+If the previous expression also contains a \".\" at the same level,
+then the \".\"s will be lined up:
+
+  let x = svg.mumble()
+             .chained;
+"
+  :version "26.1"
+  :type 'boolean
+  :safe 'booleanp
+  :group 'js)
+
 ;;; KeyMap
 
 (defvar js-mode-map
@@ -1808,6 +1822,62 @@ js--continued-expression-p
                   (and (progn (backward-char)
                               (not (looking-at "+\\+\\|--\\|/[/*]"))))))))))
 
+(defun js--skip-term-backward ()
+  "Skip a term before point; return t if a term was skipped."
+  (let ((term-skipped nil))
+    ;; Skip backward over balanced parens.
+    (let ((progress t))
+      (while progress
+        (setq progress nil)
+        ;; First skip whitespace.
+        (skip-syntax-backward " ")
+        ;; Now if we're looking at closing paren, skip to the opener.
+        ;; This doesn't strictly follow JS syntax, in that we might
+        ;; skip something nonsensical like "()[]{}", but it is enough
+        ;; if it works ok for valid input.
+        (when (memq (char-before) '(?\] ?\) ?\}))
+          (setq progress t term-skipped t)
+          (backward-list))))
+    ;; Maybe skip over a symbol.
+    (let ((save-point (point)))
+      (if (and (< (skip-syntax-backward "w_") 0)
+                 (looking-at js--name-re))
+          ;; Skipped.
+          (progn
+            (setq term-skipped t)
+            (skip-syntax-backward " "))
+        ;; Did not skip, so restore point.
+        (goto-char save-point)))
+    (when (and term-skipped (> (point) (point-min)))
+      (backward-char)
+      (eq (char-after) ?.))))
+
+(defun js--skip-terms-backward ()
+  "Skip any number of terms backward.
+Move point to the earliest \".\" without changing paren levels.
+Returns t if successful, nil if no term was found."
+  (when (js--skip-term-backward)
+    ;; Found at least one.
+    (let ((last-point (point)))
+      (while (js--skip-term-backward)
+        (setq last-point (point)))
+      (goto-char last-point)
+      t)))
+
+(defun js--chained-expression-p ()
+  "A helper for js--proper-indentation that handles chained expressions.
+A chained expression is when the current line starts with '.' and the
+previous line also has a '.' expression.
+This function returns the indentation for the current line if it is
+a chained expression line; otherwise nil.
+This should only be called while point is at the start of the line."
+  (when js-chain-indent
+    (save-excursion
+      (when (and (eq (char-after) ?.)
+                 (js--continued-expression-p)
+                 (js--find-newline-backward)
+                 (js--skip-terms-backward))
+        (current-column)))))
 
 (defun js--end-of-do-while-loop-p ()
   "Return non-nil if point is on the \"while\" of a do-while statement.
@@ -1984,6 +2054,7 @@ js--proper-indentation
                   ;; At or after the first loop?
                   (>= (point) beg)
                   (js--array-comp-indentation bracket beg))))
+          ((js--chained-expression-p))
           ((js--ctrl-statement-indentation))
           ((js--multi-line-declaration-indentation))
           ((nth 1 parse-status)
diff --git a/test/lisp/progmodes/js-tests.el b/test/lisp/progmodes/js-tests.el
index de322f2..effd58c 100644
--- a/test/lisp/progmodes/js-tests.el
+++ b/test/lisp/progmodes/js-tests.el
@@ -69,6 +69,77 @@
     (should (equal (buffer-substring (point-at-bol) (point-at-eol))
                    "\tdo_something();"))))
 
+(ert-deftest js-mode-indent-bug-20896-chain ()
+  (with-temp-buffer
+    (js-mode)
+    (setq-local js-chain-indent t)
+    (setq-local indent-tabs-mode nil)
+    (insert "let x = svg.mumble()\n.zzz")
+    (js-indent-line)
+    (should (equal (buffer-substring (point-at-bol) (point-at-eol))
+                   "           .zzz"))))
+
+(ert-deftest js-mode-indent-bug-20896-chain-comment ()
+  (with-temp-buffer
+    (js-mode)
+    (setq-local js-chain-indent t)
+    (setq-local indent-tabs-mode nil)
+    (insert "let x = svg.mumble() // line comment\n.zzz")
+    (js-indent-line)
+    (should (equal (buffer-substring (point-at-bol) (point-at-eol))
+                   "           .zzz"))))
+
+(ert-deftest js-mode-indent-bug-20896-chain-multi ()
+  (with-temp-buffer
+    (js-mode)
+    (setq-local js-chain-indent t)
+    (setq-local indent-tabs-mode nil)
+    ;; Must line up to the first "." at the same level.
+    (insert "let x = svg.selectAll().something()\n.zzz")
+    (js-indent-line)
+    (should (equal (buffer-substring (point-at-bol) (point-at-eol))
+                   "           .zzz"))))
+
+(ert-deftest js-mode-indent-bug-20896-chain-nested ()
+  (with-temp-buffer
+    (js-mode)
+    (setq-local js-chain-indent t)
+    (setq-local indent-tabs-mode nil)
+    ;; Must line up to the first "." at the same level.
+    (insert "let x = svg.selectAll(d3.svg.something()\n.zzz")
+    (js-indent-line)
+    (should (equal (buffer-substring (point-at-bol) (point-at-eol))
+                   "                        .zzz"))))
+
+(ert-deftest js-mode-indent-bug-20896-no-chain-1 ()
+  (with-temp-buffer
+    (js-mode)
+    ;; Don't set js-chain-indent.
+    (insert "let x = svg.mumble()\n.zzz")
+    (js-indent-line)
+    (should (equal (buffer-substring (point-at-bol) (point-at-eol))
+                   "    .zzz"))))
+
+(ert-deftest js-mode-indent-bug-20896-no-chain-2 ()
+  (with-temp-buffer
+    (js-mode)
+    (setq-local js-chain-indent t)
+    ;; Nothing to chain to.
+    (insert "let x = svg()\n.zzz")
+    (js-indent-line)
+    (should (equal (buffer-substring (point-at-bol) (point-at-eol))
+                   "    .zzz"))))
+
+(ert-deftest js-mode-indent-bug-20896-no-chain-2 ()
+  (with-temp-buffer
+    (js-mode)
+    (setq-local js-chain-indent t)
+    ;; Nothing to chain to.
+    (insert "let x = svg().mumble.x() + 73\n.zzz")
+    (js-indent-line)
+    (should (equal (buffer-substring (point-at-bol) (point-at-eol))
+                   "    .zzz"))))
+
 (provide 'js-tests)
 
 ;;; js-tests.el ends here





  parent reply	other threads:[~2017-01-10  6:29 UTC|newest]

Thread overview: 7+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2015-06-25 15:37 bug#20896: 25.0.50; [js-mode][FR] support chain syntax indentation Rasmus
2015-06-26  0:14 ` Rasmus
2017-01-10  6:29 ` Tom Tromey [this message]
2017-01-12  1:57   ` bug#20896: patch to add chained indentation Dmitry Gutov
2017-01-12  4:01     ` Tom Tromey
2017-01-13  1:09       ` Dmitry Gutov
2017-01-14 17:45 ` bug#20896: done Tom Tromey

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=87tw97zav3.fsf@tromey.com \
    --to=tom@tromey.com \
    --cc=20896@debbugs.gnu.org \
    --cc=dan.colascione@gmail.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this external index

	https://git.savannah.gnu.org/cgit/emacs.git
	https://git.savannah.gnu.org/cgit/emacs/org-mode.git

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.