[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
bug#20896: patch to add chained indentation
From: |
Tom Tromey |
Subject: |
bug#20896: 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
- bug#20896: patch to add chained indentation,
Tom Tromey <=