]> code.delx.au - gnu-emacs-elpa/blob - js2-old-indent.el
Don't misindent generator methods
[gnu-emacs-elpa] / js2-old-indent.el
1 ;;; js2-old-indent.el --- Indentation code kept for compatibility
2
3 ;; Copyright (C) 2015 Free Software Foundation, Inc.
4
5 ;; This file is part of GNU Emacs.
6
7 ;; GNU Emacs is free software: you can redistribute it and/or modify
8 ;; it under the terms of the GNU General Public License as published by
9 ;; the Free Software Foundation, either version 3 of the License, or
10 ;; (at your option) any later version.
11
12 ;; GNU Emacs is distributed in the hope that it will be useful,
13 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
14 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 ;; GNU General Public License for more details.
16
17 ;; You should have received a copy of the GNU General Public License
18 ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
19
20 ;;; Commentary:
21
22 ;; All features of this indentation code have been ported to Emacs's
23 ;; built-in `js-mode' by now, so we derive from it. An older
24 ;; commentary follows.
25
26 ;; This code is kept for Emacs 24.5 and ealier.
27
28 ;; This indenter is based on Karl Landström's "javascript.el" indenter.
29 ;; Karl cleverly deduces that the desired indentation level is often a
30 ;; function of paren/bracket/brace nesting depth, which can be determined
31 ;; quickly via the built-in `parse-partial-sexp' function. His indenter
32 ;; then does some equally clever checks to see if we're in the context of a
33 ;; substatement of a possibly braceless statement keyword such as if, while,
34 ;; or finally. This approach yields pretty good results.
35
36 ;; The indenter is often "wrong", however, and needs to be overridden.
37 ;; The right long-term solution is probably to emulate (or integrate
38 ;; with) cc-engine, but it's a nontrivial amount of coding. Even when a
39 ;; parse tree from `js2-parse' is present, which is not true at the
40 ;; moment the user is typing, computing indentation is still thousands
41 ;; of lines of code to handle every possible syntactic edge case.
42
43 ;; In the meantime, the compromise solution is that we offer a "bounce
44 ;; indenter", configured with `js2-bounce-indent-p', which cycles the
45 ;; current line indent among various likely guess points. This approach
46 ;; is far from perfect, but should at least make it slightly easier to
47 ;; move the line towards its desired indentation when manually
48 ;; overriding Karl's heuristic nesting guesser.
49
50 ;; I've made miscellaneous tweaks to Karl's code to handle some Ecma
51 ;; extensions such as `let' and Array comprehensions. Major kudos to
52 ;; Karl for coming up with the initial approach, which packs a lot of
53 ;; punch for so little code. -- Steve
54
55 ;;; Code:
56
57 (defvar js2-language-version)
58
59 (declare-function js2-mark-safe-local "js2-mode")
60 (declare-function js2-backward-sws "js2-mode")
61 (declare-function js2-forward-sws "js2-mode")
62 (declare-function js2-same-line "js2-mode")
63
64 (defcustom js2-basic-offset (if (and (boundp 'c-basic-offset)
65 (numberp c-basic-offset))
66 c-basic-offset
67 4)
68 "Number of spaces to indent nested statements.
69 Similar to `c-basic-offset'."
70 :group 'js2-mode
71 :safe 'integerp
72 :type 'integer)
73
74 (defcustom js2-pretty-multiline-declarations t
75 "Non-nil to line up multiline declarations vertically:
76
77 var a = 10,
78 b = 20,
79 c = 30;
80
81 If the value is t, and the first assigned value in the
82 declaration is a function/array/object literal spanning several
83 lines, it won't be indented additionally:
84
85 var o = { var bar = 2,
86 foo: 3 vs. o = {
87 }, foo: 3
88 bar = 2; };
89
90 If the value is `all', it will always be indented additionally:
91
92 var o = {
93 foo: 3
94 };
95
96 var o = {
97 foo: 3
98 },
99 bar = 2;
100
101 If the value is `dynamic', it will be indented additionally only
102 if the declaration contains more than one variable:
103
104 var o = {
105 foo: 3
106 };
107
108 var o = {
109 foo: 3
110 },
111 bar = 2;"
112 :group 'js2-mode
113 :safe 'symbolp
114 :type 'symbol)
115
116 (defcustom js2-indent-switch-body nil
117 "When nil, case labels are indented on the same level as the
118 containing switch statement. Otherwise, all lines inside
119 switch statement body are indented one additional level."
120 :type 'boolean
121 :safe 'booleanp
122 :group 'js2-mode)
123
124 (defconst js2-possibly-braceless-keywords-re
125 (concat "else[ \t]+if\\|for[ \t]+each\\|"
126 (regexp-opt '("catch" "do" "else" "finally" "for" "if"
127 "try" "while" "with" "let")))
128 "Regular expression matching keywords that are optionally
129 followed by an opening brace.")
130
131 (defconst js2-indent-operator-re
132 (concat "[-+*/%<>&^|?:.]\\([^-+*/]\\|$\\)\\|!?=\\|"
133 (regexp-opt '("in" "instanceof") 'words))
134 "Regular expression matching operators that affect indentation
135 of continued expressions.")
136
137 (defconst js2-declaration-keyword-re
138 (regexp-opt '("var" "let" "const") 'words)
139 "Regular expression matching variable declaration keywords.")
140
141 (defun js2-re-search-forward-inner (regexp &optional bound count)
142 "Auxiliary function for `js2-re-search-forward'."
143 (let (parse saved-point)
144 (while (> count 0)
145 (re-search-forward regexp bound)
146 (setq parse (if saved-point
147 (parse-partial-sexp saved-point (point))
148 (syntax-ppss (point))))
149 (cond ((nth 3 parse)
150 (re-search-forward
151 (concat "\\(\\=\\|[^\\]\\|^\\)" (string (nth 3 parse)))
152 (save-excursion (end-of-line) (point)) t))
153 ((nth 7 parse)
154 (forward-line))
155 ((or (nth 4 parse)
156 (and (eq (char-before) ?\/) (eq (char-after) ?\*)))
157 (re-search-forward "\\*/"))
158 (t
159 (setq count (1- count))))
160 (setq saved-point (point))))
161 (point))
162
163 (defun js2-re-search-forward (regexp &optional bound noerror count)
164 "Search forward but ignore strings and comments.
165 Invokes `re-search-forward' but treats the buffer as if strings
166 and comments have been removed."
167 (let ((saved-point (point)))
168 (condition-case err
169 (cond ((null count)
170 (js2-re-search-forward-inner regexp bound 1))
171 ((< count 0)
172 (js2-re-search-backward-inner regexp bound (- count)))
173 ((> count 0)
174 (js2-re-search-forward-inner regexp bound count)))
175 (search-failed
176 (goto-char saved-point)
177 (unless noerror
178 (error (error-message-string err)))))))
179
180 (defun js2-re-search-backward-inner (regexp &optional bound count)
181 "Auxiliary function for `js2-re-search-backward'."
182 (let (parse)
183 (while (> count 0)
184 (re-search-backward regexp bound)
185 (setq parse (syntax-ppss (point)))
186 (cond ((nth 3 parse)
187 (re-search-backward
188 (concat "\\([^\\]\\|^\\)" (string (nth 3 parse)))
189 (line-beginning-position) t))
190 ((nth 7 parse)
191 (goto-char (nth 8 parse)))
192 ((or (nth 4 parse)
193 (and (eq (char-before) ?/) (eq (char-after) ?*)))
194 (re-search-backward "/\\*"))
195 (t
196 (setq count (1- count))))))
197 (point))
198
199 (defun js2-re-search-backward (regexp &optional bound noerror count)
200 "Search backward but ignore strings and comments.
201 Invokes `re-search-backward' but treats the buffer as if strings
202 and comments have been removed."
203 (let ((saved-point (point)))
204 (condition-case err
205 (cond ((null count)
206 (js2-re-search-backward-inner regexp bound 1))
207 ((< count 0)
208 (js2-re-search-forward-inner regexp bound (- count)))
209 ((> count 0)
210 (js2-re-search-backward-inner regexp bound count)))
211 (search-failed
212 (goto-char saved-point)
213 (unless noerror
214 (error (error-message-string err)))))))
215
216 (defun js2-looking-at-operator-p ()
217 "Return non-nil if text after point is a non-comma operator."
218 (defvar js2-mode-identifier-re)
219 (and (looking-at js2-indent-operator-re)
220 (or (not (eq (char-after) ?:))
221 (save-excursion
222 (and (js2-re-search-backward "[?:{]\\|\\_<case\\_>" nil t)
223 (eq (char-after) ??))))
224 (not (and
225 (eq (char-after) ?*)
226 (looking-at (concat "\\* *" js2-mode-identifier-re " *("))
227 (save-excursion
228 (goto-char (1- (match-end 0)))
229 (let (forward-sexp-function) (forward-sexp))
230 (js2-forward-sws)
231 (eq (char-after) ?{))))))
232
233 (defun js2-continued-expression-p ()
234 "Return non-nil if the current line continues an expression."
235 (save-excursion
236 (back-to-indentation)
237 (or (js2-looking-at-operator-p)
238 (when (catch 'found
239 (while (and (re-search-backward "\n" nil t)
240 (let ((state (syntax-ppss)))
241 (when (nth 4 state)
242 (goto-char (nth 8 state))) ;; skip comments
243 (skip-chars-backward " \t")
244 (if (bolp)
245 t
246 (throw 'found t))))))
247 (backward-char)
248 (when (js2-looking-at-operator-p)
249 (backward-char)
250 (not (looking-at "\\*\\|\\+\\+\\|--\\|/[/*]")))))))
251
252 (defun js2-end-of-do-while-loop-p ()
253 "Return non-nil if word after point is `while' of a do-while
254 statement, else returns nil. A braceless do-while statement
255 spanning several lines requires that the start of the loop is
256 indented to the same column as the current line."
257 (interactive)
258 (save-excursion
259 (when (looking-at "\\s-*\\_<while\\_>")
260 (if (save-excursion
261 (skip-chars-backward "[ \t\n]*}")
262 (looking-at "[ \t\n]*}"))
263 (save-excursion
264 (backward-list) (backward-word 1) (looking-at "\\_<do\\_>"))
265 (js2-re-search-backward "\\_<do\\_>" (point-at-bol) t)
266 (or (looking-at "\\_<do\\_>")
267 (let ((saved-indent (current-indentation)))
268 (while (and (js2-re-search-backward "^[ \t]*\\_<" nil t)
269 (/= (current-indentation) saved-indent)))
270 (and (looking-at "[ \t]*\\_<do\\_>")
271 (not (js2-re-search-forward
272 "\\_<while\\_>" (point-at-eol) t))
273 (= (current-indentation) saved-indent))))))))
274
275 (defun js2-multiline-decl-indentation ()
276 "Return the declaration indentation column if the current line belongs
277 to a multiline declaration statement. See `js2-pretty-multiline-declarations'."
278 (let (forward-sexp-function ; use Lisp version
279 at-opening-bracket)
280 (save-excursion
281 (back-to-indentation)
282 (when (not (looking-at js2-declaration-keyword-re))
283 (when (looking-at js2-indent-operator-re)
284 (goto-char (match-end 0))) ; continued expressions are ok
285 (while (and (not at-opening-bracket)
286 (not (bobp))
287 (let ((pos (point)))
288 (save-excursion
289 (js2-backward-sws)
290 (or (eq (char-before) ?,)
291 (and (not (eq (char-before) ?\;))
292 (prog2 (skip-syntax-backward ".")
293 (looking-at js2-indent-operator-re)
294 (js2-backward-sws))
295 (not (eq (char-before) ?\;)))
296 (js2-same-line pos)))))
297 (condition-case _
298 (backward-sexp)
299 (scan-error (setq at-opening-bracket t))))
300 (when (looking-at js2-declaration-keyword-re)
301 (goto-char (match-end 0))
302 (1+ (current-column)))))))
303
304 (defun js2-ctrl-statement-indentation ()
305 "Return the proper indentation of current line if it is a control statement.
306 Returns an indentation if this line starts the body of a control
307 statement without braces, else returns nil."
308 (let (forward-sexp-function)
309 (save-excursion
310 (back-to-indentation)
311 (when (and (not (js2-same-line (point-min)))
312 (not (looking-at "{"))
313 (js2-re-search-backward "[[:graph:]]" nil t)
314 (not (looking-at "[{([]"))
315 (progn
316 (forward-char)
317 (when (= (char-before) ?\))
318 ;; scan-sexps sometimes throws an error
319 (ignore-errors (backward-sexp))
320 (skip-chars-backward " \t" (point-at-bol)))
321 (let ((pt (point)))
322 (back-to-indentation)
323 (when (looking-at "}[ \t]*")
324 (goto-char (match-end 0)))
325 (and (looking-at js2-possibly-braceless-keywords-re)
326 (= (match-end 0) pt)
327 (not (js2-end-of-do-while-loop-p))))))
328 (+ (current-indentation) js2-basic-offset)))))
329
330 (defun js2-indent-in-array-comp (parse-status)
331 "Return non-nil if we think we're in an array comprehension.
332 In particular, return the buffer position of the first `for' kwd."
333 (let ((bracket (nth 1 parse-status))
334 (end (point)))
335 (when bracket
336 (save-excursion
337 (goto-char bracket)
338 (when (looking-at "\\[")
339 (forward-char 1)
340 (js2-forward-sws)
341 (if (looking-at "[[{]")
342 (let (forward-sexp-function) ; use Lisp version
343 (forward-sexp) ; skip destructuring form
344 (js2-forward-sws)
345 (if (and (/= (char-after) ?,) ; regular array
346 (looking-at "for"))
347 (match-beginning 0)))
348 ;; to skip arbitrary expressions we need the parser,
349 ;; so we'll just guess at it.
350 (if (and (> end (point)) ; not empty literal
351 (re-search-forward "[^,]]* \\(for\\) " end t)
352 ;; not inside comment or string literal
353 (let ((state (parse-partial-sexp bracket (point))))
354 (not (or (nth 3 state) (nth 4 state)))))
355 (match-beginning 1))))))))
356
357 (defun js2-array-comp-indentation (parse-status for-kwd)
358 (if (js2-same-line for-kwd)
359 ;; first continuation line
360 (save-excursion
361 (goto-char (nth 1 parse-status))
362 (forward-char 1)
363 (skip-chars-forward " \t")
364 (current-column))
365 (save-excursion
366 (goto-char for-kwd)
367 (current-column))))
368
369 (defun js2-maybe-goto-declaration-keyword-end (bracket)
370 "Helper function for `js2-proper-indentation'.
371 Depending on the value of `js2-pretty-multiline-declarations',
372 move point to the end of a variable declaration keyword so that
373 indentation is aligned to that column."
374 (cond
375 ((eq js2-pretty-multiline-declarations 'all)
376 (when (looking-at js2-declaration-keyword-re)
377 (goto-char (1+ (match-end 0)))))
378 ((eq js2-pretty-multiline-declarations 'dynamic)
379 (let (declaration-keyword-end
380 at-closing-bracket-p
381 comma-p)
382 (when (looking-at js2-declaration-keyword-re)
383 ;; Preserve the match data lest it somehow be overridden.
384 (setq declaration-keyword-end (match-end 0))
385 (save-excursion
386 (goto-char bracket)
387 (setq at-closing-bracket-p
388 ;; Handle scan errors gracefully.
389 (condition-case nil
390 (progn
391 ;; Use the regular `forward-sexp-function' because the
392 ;; normal one for this mode uses the AST.
393 (let (forward-sexp-function)
394 (forward-sexp))
395 t)
396 (error nil)))
397 (when at-closing-bracket-p
398 (js2-forward-sws)
399 (setq comma-p (looking-at-p ","))))
400 (when comma-p
401 (goto-char (1+ declaration-keyword-end))))))))
402
403 (cl-defun js2-proper-indentation (parse-status)
404 "Return the proper indentation for the current line."
405 (save-excursion
406 (back-to-indentation)
407 (when (nth 4 parse-status)
408 (cl-return-from js2-proper-indentation (js2--comment-indent parse-status)))
409 (let* ((at-closing-bracket (looking-at "[]})]"))
410 (same-indent-p (or at-closing-bracket
411 (looking-at "\\_<case\\_>[^:]")
412 (and (looking-at "\\_<default:")
413 (save-excursion
414 (js2-backward-sws)
415 (not (memq (char-before) '(?, ?{)))))))
416 (continued-expr-p (js2-continued-expression-p))
417 (declaration-indent (and js2-pretty-multiline-declarations
418 (js2-multiline-decl-indentation)))
419 (bracket (nth 1 parse-status))
420 beg indent)
421 (cond
422 ;; indent array comprehension continuation lines specially
423 ((and bracket
424 (>= js2-language-version 170)
425 (not (js2-same-line bracket))
426 (setq beg (js2-indent-in-array-comp parse-status))
427 (>= (point) (save-excursion
428 (goto-char beg)
429 (point-at-bol)))) ; at or after first loop?
430 (js2-array-comp-indentation parse-status beg))
431
432 ((js2-ctrl-statement-indentation))
433
434 ((and declaration-indent continued-expr-p)
435 (+ declaration-indent js2-basic-offset))
436
437 (declaration-indent)
438
439 (bracket
440 (goto-char bracket)
441 (cond
442 ((looking-at "[({[][ \t]*\\(/[/*]\\|$\\)")
443 (when (save-excursion (skip-chars-backward " \t)")
444 (looking-at ")"))
445 (backward-list))
446 (back-to-indentation)
447 (js2-maybe-goto-declaration-keyword-end bracket)
448 (setq indent
449 (cond (same-indent-p
450 (current-column))
451 (continued-expr-p
452 (+ (current-column) (* 2 js2-basic-offset)))
453 (t
454 (+ (current-column) js2-basic-offset))))
455 (if (and js2-indent-switch-body
456 (not at-closing-bracket)
457 (looking-at "\\_<switch\\_>"))
458 (+ indent js2-basic-offset)
459 indent))
460 (t
461 (unless same-indent-p
462 (forward-char)
463 (skip-chars-forward " \t"))
464 (current-column))))
465
466 (continued-expr-p js2-basic-offset)
467
468 (t 0)))))
469
470 (defun js2--comment-indent (parse-status)
471 "Indentation inside a multi-line block comment continuation line."
472 (save-excursion
473 (goto-char (nth 8 parse-status))
474 (if (looking-at "/\\*")
475 (+ 1 (current-column))
476 0)))
477
478 (defun js2-indent-line (&optional bounce-backwards)
479 "Indent the current line as JavaScript source text."
480 (interactive)
481 (let (parse-status offset
482 ;; Don't whine about errors/warnings when we're indenting.
483 ;; This has to be set before calling parse-partial-sexp below.
484 (inhibit-point-motion-hooks t))
485 (setq parse-status (save-excursion
486 (syntax-ppss (point-at-bol)))
487 offset (- (point) (save-excursion
488 (back-to-indentation)
489 (point))))
490 ;; Don't touch multiline strings.
491 (unless (nth 3 parse-status)
492 (indent-line-to (js2-proper-indentation parse-status))
493 (when (cl-plusp offset)
494 (forward-char offset)))))
495
496 (provide 'js2-old-indent)
497
498 ;;; js2-old-indent.el ends here