GNU bug report logs - #20896
25.0.50; [js-mode][FR] support chain syntax indentation

Previous Next

Package: emacs;

Reported by: Rasmus <rasmus <at> gmx.us>

Date: Thu, 25 Jun 2015 15:39:02 UTC

Severity: wishlist

Found in version 25.0.50

Done: Tom Tromey <tom <at> tromey.com>

Bug is archived. No further changes may be made.

To add a comment to this bug, you must first unarchive it, by sending
a message to control AT debbugs.gnu.org, with unarchive 20896 in the body.
You can then email your comments to 20896 AT debbugs.gnu.org in the normal way.

Toggle the display of automated, internal messages from the tracker.

View this report as an mbox folder, status mbox, maintainer mbox


Report forwarded to bug-gnu-emacs <at> gnu.org:
bug#20896; Package emacs. (Thu, 25 Jun 2015 15:39:02 GMT) Full text and rfc822 format available.

Acknowledgement sent to Rasmus <rasmus <at> gmx.us>:
New bug report received and forwarded. Copy sent to bug-gnu-emacs <at> gnu.org. (Thu, 25 Jun 2015 15:39:02 GMT) Full text and rfc822 format available.

Message #5 received at submit <at> debbugs.gnu.org (full text, mbox):

From: Rasmus <rasmus <at> gmx.us>
To: bug-gnu-emacs <at> gnu.org
Subject: 25.0.50; [js-mode][FR] support chain syntax indentation
Date: Thu, 25 Jun 2015 17:37:51 +0200
Hi,

I would be lovely if js-mode would support the chain-syntax used by the
d3.js library.  d3.js is used to making svg graphics.  The syntax is very
readable if indentation is right:


var points = svg.selectAll(".scatter-dots")
               .data(data)
               .enter().append("path")
               .more_funs();
               

var an_axis = axes.append("g")
                  .call(d3.svg.axis()
                          .scale(Scale)
                          .orient("bottom"));
   
Usually calls are factored out so the latter example is not so important
to be able to match.  Just aligning on the first dot on the previous line
would be a big step forward.

I don't know if this is useful for other JS libraries than d3.  Some d3
documentation mentioned this selector-mechanism was inspired by jQuery,
but I don't know this library well enough to say whether it would be
useful with that.

Thanks,
Rasmus

-- 
Not everything that goes around comes back around, you know




Information forwarded to bug-gnu-emacs <at> gnu.org:
bug#20896; Package emacs. (Fri, 26 Jun 2015 00:31:02 GMT) Full text and rfc822 format available.

Message #8 received at submit <at> debbugs.gnu.org (full text, mbox):

From: Rasmus <rasmus <at> gmx.us>
To: bug-gnu-emacs <at> gnu.org
Subject: Re: bug#20896: 25.0.50; [js-mode][FR] support chain syntax indentation
Date: Fri, 26 Jun 2015 02:14:51 +0200
Hi,

Rasmus <rasmus <at> gmx.us> writes:

> var an_axis = axes.append("g")
>                   .call(d3.svg.axis()
>                           .scale(Scale)
>                           .orient("bottom"));


Actually, this case is not as crazy as first assumed and should ideally be
supported.  Here's a pretty reasonable example (except for names being too
long):

      plot.axes.yScale = d3.scale.ordinal()
                           .domain(d3.range(bar.ybins).map(function(d){return d*10;}))
                           .range(d3.range(bar.ybins)
                                    .map(function(d,i) {
                                        return plot.height-plot.height/bar.ybins * i;}));


Also, notice there's another, potential, indentation typo in this code.
The last "return" should be one character to the right IMO, i.e.

    .map(function(d,i) {
         return plot.height-plot.height/bar.ybins * i;}));

Thanks,
Rasmus

-- 
Dung makes an excellent fertilizer





Information forwarded to bug-gnu-emacs <at> gnu.org:
bug#20896; Package emacs. (Tue, 10 Jan 2017 06:30:03 GMT) Full text and rfc822 format available.

Message #11 received at 20896 <at> debbugs.gnu.org (full text, mbox):

From: Tom Tromey <tom <at> tromey.com>
To: 20896 <at> debbugs.gnu.org
Cc: Daniel Colascione <dan.colascione <at> gmail.com>
Subject: patch to add chained indentation
Date: Mon, 09 Jan 2017 23:29:20 -0700
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




Information forwarded to bug-gnu-emacs <at> gnu.org:
bug#20896; Package emacs. (Thu, 12 Jan 2017 01:58:01 GMT) Full text and rfc822 format available.

Message #14 received at 20896 <at> debbugs.gnu.org (full text, mbox):

From: Dmitry Gutov <dgutov <at> yandex.ru>
To: Tom Tromey <tom <at> tromey.com>, 20896 <at> debbugs.gnu.org
Cc: Daniel Colascione <dan.colascione <at> gmail.com>
Subject: Re: bug#20896: patch to add chained indentation
Date: Thu, 12 Jan 2017 04:57:49 +0300
On 10.01.2017 09:29, Tom Tromey wrote:
> This patch adds chained indentation, as requested in the bug.

Thanks.

> It comes
> with some tests

I'd just like to point out that it's much better to write indentation 
tests in the format used by test/manual/indent/js*.

> (added to a file that first appears in patch in another
> bug

Not sure which patch you mean. This file seems new.

As for review: js--skip-term-backward seems to be doing something 
similar to the loop in js--multi-line-declaration-indentation.

Maybe a extraction and unification is in order.




Information forwarded to bug-gnu-emacs <at> gnu.org:
bug#20896; Package emacs. (Thu, 12 Jan 2017 04:03:02 GMT) Full text and rfc822 format available.

Message #17 received at 20896 <at> debbugs.gnu.org (full text, mbox):

From: Tom Tromey <tom <at> tromey.com>
To: Dmitry Gutov <dgutov <at> yandex.ru>
Cc: Daniel Colascione <dan.colascione <at> gmail.com>, Tom Tromey <tom <at> tromey.com>,
 20896 <at> debbugs.gnu.org
Subject: Re: bug#20896: patch to add chained indentation
Date: Wed, 11 Jan 2017 21:01:53 -0700
>> It comes with some tests

Dmitry> I'd just like to point out that it's much better to write indentation
Dmitry> tests in the format used by test/manual/indent/js*.

Thanks, I wasn't aware of this.  I'll redo the test this way.

>> (added to a file that first appears in patch in another
>> bug

Dmitry> Not sure which patch you mean. This file seems new.

I added js-tests.el in the bug#19399/bug#22431 patch, then further
amended it in bug#15582.  None of these have landed yet.  (There's also
bug#25389, which is related, but doesn't touch the test file.)

Dmitry> As for review: js--skip-term-backward seems to be doing something
Dmitry> similar to the loop in js--multi-line-declaration-indentation.
Dmitry> Maybe a extraction and unification is in order.

I don't really see it.  Could you explain more?

Tom




Information forwarded to bug-gnu-emacs <at> gnu.org:
bug#20896; Package emacs. (Fri, 13 Jan 2017 01:10:01 GMT) Full text and rfc822 format available.

Message #20 received at 20896 <at> debbugs.gnu.org (full text, mbox):

From: Dmitry Gutov <dgutov <at> yandex.ru>
To: Tom Tromey <tom <at> tromey.com>
Cc: Daniel Colascione <dan.colascione <at> gmail.com>, 20896 <at> debbugs.gnu.org
Subject: Re: bug#20896: patch to add chained indentation
Date: Fri, 13 Jan 2017 04:09:18 +0300
On 12.01.2017 07:01, Tom Tromey wrote:

> I'll redo the test this way.

Thanks.

> Dmitry> As for review: js--skip-term-backward seems to be doing something
> Dmitry> similar to the loop in js--multi-line-declaration-indentation.
> Dmitry> Maybe a extraction and unification is in order.
>
> I don't really see it.  Could you explain more?

The `while' loop jumps to the beginning of the current "assignment 
expression".

`js--skip-terms-backward' is similar because it skips to the beginning 
of the call chain. It's more narrow, though (the other function also 
jumps over binary operators).

Anyway, I'm not so sure there's much value in unifying the 
implementations anymore.




Reply sent to Tom Tromey <tom <at> tromey.com>:
You have taken responsibility. (Sat, 14 Jan 2017 17:46:02 GMT) Full text and rfc822 format available.

Notification sent to Rasmus <rasmus <at> gmx.us>:
bug acknowledged by developer. (Sat, 14 Jan 2017 17:46:02 GMT) Full text and rfc822 format available.

Message #25 received at 20896-done <at> debbugs.gnu.org (full text, mbox):

From: Tom Tromey <tom <at> tromey.com>
To: 20896-done <at> debbugs.gnu.org
Subject: done
Date: Sat, 14 Jan 2017 10:45:19 -0700
This was fixed by 502390822f9c0068898ae41285b37568bf0e4d1c.

Tom




bug archived. Request was from Debbugs Internal Request <help-debbugs <at> gnu.org> to internal_control <at> debbugs.gnu.org. (Sun, 12 Feb 2017 12:24:04 GMT) Full text and rfc822 format available.

This bug report was last modified 8 years and 188 days ago.

Previous Next


GNU bug tracking system
Copyright (C) 1999 Darren O. Benham, 1997,2003 nCipher Corporation Ltd, 1994-97 Ian Jackson.