diff options
| author | Tom Tromey | 2017-01-12 23:15:00 -0700 |
|---|---|---|
| committer | Tom Tromey | 2017-01-13 12:38:36 -0700 |
| commit | 502390822f9c0068898ae41285b37568bf0e4d1c (patch) | |
| tree | 08d55c4c60eeb803935779d6541db7ef171b02bd | |
| parent | b47f97218efb8d9966e084bdfd8a86e8c47cf81d (diff) | |
| download | emacs-502390822f9c0068898ae41285b37568bf0e4d1c.tar.gz emacs-502390822f9c0068898ae41285b37568bf0e4d1c.zip | |
Add chained indentation to js-mode
Bug#20896
* lisp/progmodes/js.el (js-chain-indent): New variable.
(js--skip-term-backward, js--skip-terms-backward)
(js--chained-expression-p): New functions.
(js--proper-indentation): Call js--chained-expression-p.
* test/manual/indent/js-chain.js: New file.
* test/manual/indent/js.js: Add (non-)chained indentation test.
| -rw-r--r-- | lisp/progmodes/js.el | 72 | ||||
| -rw-r--r-- | test/manual/indent/js-chain.js | 29 | ||||
| -rw-r--r-- | test/manual/indent/js.js | 4 |
3 files changed, 105 insertions, 0 deletions
diff --git a/lisp/progmodes/js.el b/lisp/progmodes/js.el index e84215d4301..54df3913fc6 100644 --- a/lisp/progmodes/js.el +++ b/lisp/progmodes/js.el | |||
| @@ -552,6 +552,20 @@ don't indent the first one's initializer; otherwise, indent it. | |||
| 552 | :safe 'symbolp | 552 | :safe 'symbolp |
| 553 | :group 'js) | 553 | :group 'js) |
| 554 | 554 | ||
| 555 | (defcustom js-chain-indent nil | ||
| 556 | "Use \"chained\" indentation. | ||
| 557 | Chained indentation applies when the current line starts with \".\". | ||
| 558 | If the previous expression also contains a \".\" at the same level, | ||
| 559 | then the \".\"s will be lined up: | ||
| 560 | |||
| 561 | let x = svg.mumble() | ||
| 562 | .chained; | ||
| 563 | " | ||
| 564 | :version "26.1" | ||
| 565 | :type 'boolean | ||
| 566 | :safe 'booleanp | ||
| 567 | :group 'js) | ||
| 568 | |||
| 555 | ;;; KeyMap | 569 | ;;; KeyMap |
| 556 | 570 | ||
| 557 | (defvar js-mode-map | 571 | (defvar js-mode-map |
| @@ -1808,6 +1822,63 @@ This performs fontification according to `js--class-styles'." | |||
| 1808 | (and (progn (backward-char) | 1822 | (and (progn (backward-char) |
| 1809 | (not (looking-at "+\\+\\|--\\|/[/*]")))))))))) | 1823 | (not (looking-at "+\\+\\|--\\|/[/*]")))))))))) |
| 1810 | 1824 | ||
| 1825 | (defun js--skip-term-backward () | ||
| 1826 | "Skip a term before point; return t if a term was skipped." | ||
| 1827 | (let ((term-skipped nil)) | ||
| 1828 | ;; Skip backward over balanced parens. | ||
| 1829 | (let ((progress t)) | ||
| 1830 | (while progress | ||
| 1831 | (setq progress nil) | ||
| 1832 | ;; First skip whitespace. | ||
| 1833 | (skip-syntax-backward " ") | ||
| 1834 | ;; Now if we're looking at closing paren, skip to the opener. | ||
| 1835 | ;; This doesn't strictly follow JS syntax, in that we might | ||
| 1836 | ;; skip something nonsensical like "()[]{}", but it is enough | ||
| 1837 | ;; if it works ok for valid input. | ||
| 1838 | (when (memq (char-before) '(?\] ?\) ?\})) | ||
| 1839 | (setq progress t term-skipped t) | ||
| 1840 | (backward-list)))) | ||
| 1841 | ;; Maybe skip over a symbol. | ||
| 1842 | (let ((save-point (point))) | ||
| 1843 | (if (and (< (skip-syntax-backward "w_") 0) | ||
| 1844 | (looking-at js--name-re)) | ||
| 1845 | ;; Skipped. | ||
| 1846 | (progn | ||
| 1847 | (setq term-skipped t) | ||
| 1848 | (skip-syntax-backward " ")) | ||
| 1849 | ;; Did not skip, so restore point. | ||
| 1850 | (goto-char save-point))) | ||
| 1851 | (when (and term-skipped (> (point) (point-min))) | ||
| 1852 | (backward-char) | ||
| 1853 | (eq (char-after) ?.)))) | ||
| 1854 | |||
| 1855 | (defun js--skip-terms-backward () | ||
| 1856 | "Skip any number of terms backward. | ||
| 1857 | Move point to the earliest \".\" without changing paren levels. | ||
| 1858 | Returns t if successful, nil if no term was found." | ||
| 1859 | (when (js--skip-term-backward) | ||
| 1860 | ;; Found at least one. | ||
| 1861 | (let ((last-point (point))) | ||
| 1862 | (while (js--skip-term-backward) | ||
| 1863 | (setq last-point (point))) | ||
| 1864 | (goto-char last-point) | ||
| 1865 | t))) | ||
| 1866 | |||
| 1867 | (defun js--chained-expression-p () | ||
| 1868 | "A helper for js--proper-indentation that handles chained expressions. | ||
| 1869 | A chained expression is when the current line starts with '.' and the | ||
| 1870 | previous line also has a '.' expression. | ||
| 1871 | This function returns the indentation for the current line if it is | ||
| 1872 | a chained expression line; otherwise nil. | ||
| 1873 | This should only be called while point is at the start of the line's content, | ||
| 1874 | as determined by `back-to-indentation'." | ||
| 1875 | (when js-chain-indent | ||
| 1876 | (save-excursion | ||
| 1877 | (when (and (eq (char-after) ?.) | ||
| 1878 | (js--continued-expression-p) | ||
| 1879 | (js--find-newline-backward) | ||
| 1880 | (js--skip-terms-backward)) | ||
| 1881 | (current-column))))) | ||
| 1811 | 1882 | ||
| 1812 | (defun js--end-of-do-while-loop-p () | 1883 | (defun js--end-of-do-while-loop-p () |
| 1813 | "Return non-nil if point is on the \"while\" of a do-while statement. | 1884 | "Return non-nil if point is on the \"while\" of a do-while statement. |
| @@ -1984,6 +2055,7 @@ indentation is aligned to that column." | |||
| 1984 | ;; At or after the first loop? | 2055 | ;; At or after the first loop? |
| 1985 | (>= (point) beg) | 2056 | (>= (point) beg) |
| 1986 | (js--array-comp-indentation bracket beg)))) | 2057 | (js--array-comp-indentation bracket beg)))) |
| 2058 | ((js--chained-expression-p)) | ||
| 1987 | ((js--ctrl-statement-indentation)) | 2059 | ((js--ctrl-statement-indentation)) |
| 1988 | ((js--multi-line-declaration-indentation)) | 2060 | ((js--multi-line-declaration-indentation)) |
| 1989 | ((nth 1 parse-status) | 2061 | ((nth 1 parse-status) |
diff --git a/test/manual/indent/js-chain.js b/test/manual/indent/js-chain.js new file mode 100644 index 00000000000..2a290294026 --- /dev/null +++ b/test/manual/indent/js-chain.js | |||
| @@ -0,0 +1,29 @@ | |||
| 1 | // Normal chaining. | ||
| 2 | let x = svg.mumble() | ||
| 3 | .zzz; | ||
| 4 | |||
| 5 | // Chaining with an intervening line comment. | ||
| 6 | let x = svg.mumble() // line comment | ||
| 7 | .zzz; | ||
| 8 | |||
| 9 | // Chaining with multiple dots. | ||
| 10 | let x = svg.selectAll().something() | ||
| 11 | .zzz; | ||
| 12 | |||
| 13 | // Nested chaining. | ||
| 14 | let x = svg.selectAll(d3.svg.something() | ||
| 15 | .zzz); | ||
| 16 | |||
| 17 | // Nothing to chain to. | ||
| 18 | let x = svg() | ||
| 19 | .zzz; | ||
| 20 | |||
| 21 | // Nothing to chain to. | ||
| 22 | let x = svg().mumble.x() + 73 | ||
| 23 | .zzz; | ||
| 24 | |||
| 25 | // Local Variables: | ||
| 26 | // indent-tabs-mode: nil | ||
| 27 | // js-chain-indent: t | ||
| 28 | // js-indent-level: 2 | ||
| 29 | // End: | ||
diff --git a/test/manual/indent/js.js b/test/manual/indent/js.js index d004b82f8bc..846c3a1a5c2 100644 --- a/test/manual/indent/js.js +++ b/test/manual/indent/js.js | |||
| @@ -124,6 +124,10 @@ if (x > 72 && | |||
| 124 | do_something(); | 124 | do_something(); |
| 125 | } | 125 | } |
| 126 | 126 | ||
| 127 | // Test that chaining doesn't happen when js-chain-indent is nil. | ||
| 128 | let x = svg.mumble() | ||
| 129 | .zzz; | ||
| 130 | |||
| 127 | // Local Variables: | 131 | // Local Variables: |
| 128 | // indent-tabs-mode: nil | 132 | // indent-tabs-mode: nil |
| 129 | // js-indent-level: 2 | 133 | // js-indent-level: 2 |