Merge from origin/emacs-24
[emacs.git] / lisp / progmodes / js.el
blobc25e52cdc6a0f498681d10176b3362ad1e021796
1 ;;; js.el --- Major mode for editing JavaScript -*- lexical-binding: t -*-
3 ;; Copyright (C) 2008-2015 Free Software Foundation, Inc.
5 ;; Author: Karl Landstrom <karl.landstrom@brgeight.se>
6 ;; Daniel Colascione <dan.colascione@gmail.com>
7 ;; Maintainer: Daniel Colascione <dan.colascione@gmail.com>
8 ;; Version: 9
9 ;; Date: 2009-07-25
10 ;; Keywords: languages, javascript
12 ;; This file is part of GNU Emacs.
14 ;; GNU Emacs is free software: you can redistribute it and/or modify
15 ;; it under the terms of the GNU General Public License as published by
16 ;; the Free Software Foundation, either version 3 of the License, or
17 ;; (at your option) any later version.
19 ;; GNU Emacs is distributed in the hope that it will be useful,
20 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
21 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 ;; GNU General Public License for more details.
24 ;; You should have received a copy of the GNU General Public License
25 ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
27 ;;; Commentary
29 ;; This is based on Karl Landstrom's barebones javascript-mode. This
30 ;; is much more robust and works with cc-mode's comment filling
31 ;; (mostly).
33 ;; The main features of this JavaScript mode are syntactic
34 ;; highlighting (enabled with `font-lock-mode' or
35 ;; `global-font-lock-mode'), automatic indentation and filling of
36 ;; comments, C preprocessor fontification, and MozRepl integration.
38 ;; General Remarks:
40 ;; XXX: This mode assumes that block comments are not nested inside block
41 ;; XXX: comments
43 ;; Exported names start with "js-"; private names start with
44 ;; "js--".
46 ;;; Code:
49 (require 'cc-mode)
50 (require 'newcomment)
51 (require 'thingatpt) ; forward-symbol etc
52 (require 'imenu)
53 (require 'moz nil t)
54 (require 'json nil t)
56 (eval-when-compile
57 (require 'cl-lib)
58 (require 'ido))
60 (defvar inferior-moz-buffer)
61 (defvar moz-repl-name)
62 (defvar ido-cur-list)
63 (defvar electric-layout-rules)
64 (declare-function ido-mode "ido")
65 (declare-function inferior-moz-process "ext:mozrepl" ())
67 ;;; Constants
69 (defconst js--name-start-re "[a-zA-Z_$]"
70 "Regexp matching the start of a JavaScript identifier, without grouping.")
72 (defconst js--stmt-delim-chars "^;{}?:")
74 (defconst js--name-re (concat js--name-start-re
75 "\\(?:\\s_\\|\\sw\\)*")
76 "Regexp matching a JavaScript identifier, without grouping.")
78 (defconst js--objfield-re (concat js--name-re ":")
79 "Regexp matching the start of a JavaScript object field.")
81 (defconst js--dotted-name-re
82 (concat js--name-re "\\(?:\\." js--name-re "\\)*")
83 "Regexp matching a dot-separated sequence of JavaScript names.")
85 (defconst js--cpp-name-re js--name-re
86 "Regexp matching a C preprocessor name.")
88 (defconst js--opt-cpp-start "^\\s-*#\\s-*\\([[:alnum:]]+\\)"
89 "Regexp matching the prefix of a cpp directive.
90 This includes the directive name, or nil in languages without
91 preprocessor support. The first submatch surrounds the directive
92 name.")
94 (defconst js--plain-method-re
95 (concat "^\\s-*?\\(" js--dotted-name-re "\\)\\.prototype"
96 "\\.\\(" js--name-re "\\)\\s-*?=\\s-*?\\(function\\)\\_>")
97 "Regexp matching an explicit JavaScript prototype \"method\" declaration.
98 Group 1 is a (possibly-dotted) class name, group 2 is a method name,
99 and group 3 is the 'function' keyword.")
101 (defconst js--plain-class-re
102 (concat "^\\s-*\\(" js--dotted-name-re "\\)\\.prototype"
103 "\\s-*=\\s-*{")
104 "Regexp matching a JavaScript explicit prototype \"class\" declaration.
105 An example of this is \"Class.prototype = { method1: ...}\".")
107 ;; var NewClass = BaseClass.extend(
108 (defconst js--mp-class-decl-re
109 (concat "^\\s-*var\\s-+"
110 "\\(" js--name-re "\\)"
111 "\\s-*=\\s-*"
112 "\\(" js--dotted-name-re
113 "\\)\\.extend\\(?:Final\\)?\\s-*(\\s-*{?\\s-*$"))
115 ;; var NewClass = Class.create()
116 (defconst js--prototype-obsolete-class-decl-re
117 (concat "^\\s-*\\(?:var\\s-+\\)?"
118 "\\(" js--dotted-name-re "\\)"
119 "\\s-*=\\s-*Class\\.create()"))
121 (defconst js--prototype-objextend-class-decl-re-1
122 (concat "^\\s-*Object\\.extend\\s-*("
123 "\\(" js--dotted-name-re "\\)"
124 "\\s-*,\\s-*{"))
126 (defconst js--prototype-objextend-class-decl-re-2
127 (concat "^\\s-*\\(?:var\\s-+\\)?"
128 "\\(" js--dotted-name-re "\\)"
129 "\\s-*=\\s-*Object\\.extend\\s-*\("))
131 ;; var NewClass = Class.create({
132 (defconst js--prototype-class-decl-re
133 (concat "^\\s-*\\(?:var\\s-+\\)?"
134 "\\(" js--name-re "\\)"
135 "\\s-*=\\s-*Class\\.create\\s-*(\\s-*"
136 "\\(?:\\(" js--dotted-name-re "\\)\\s-*,\\s-*\\)?{?"))
138 ;; Parent class name(s) (yes, multiple inheritance in JavaScript) are
139 ;; matched with dedicated font-lock matchers
140 (defconst js--dojo-class-decl-re
141 (concat "^\\s-*dojo\\.declare\\s-*(\"\\(" js--dotted-name-re "\\)"))
143 (defconst js--extjs-class-decl-re-1
144 (concat "^\\s-*Ext\\.extend\\s-*("
145 "\\s-*\\(" js--dotted-name-re "\\)"
146 "\\s-*,\\s-*\\(" js--dotted-name-re "\\)")
147 "Regexp matching an ExtJS class declaration (style 1).")
149 (defconst js--extjs-class-decl-re-2
150 (concat "^\\s-*\\(?:var\\s-+\\)?"
151 "\\(" js--name-re "\\)"
152 "\\s-*=\\s-*Ext\\.extend\\s-*(\\s-*"
153 "\\(" js--dotted-name-re "\\)")
154 "Regexp matching an ExtJS class declaration (style 2).")
156 (defconst js--mochikit-class-re
157 (concat "^\\s-*MochiKit\\.Base\\.update\\s-*(\\s-*"
158 "\\(" js--dotted-name-re "\\)")
159 "Regexp matching a MochiKit class declaration.")
161 (defconst js--dummy-class-style
162 '(:name "[Automatically Generated Class]"))
164 (defconst js--class-styles
165 `((:name "Plain"
166 :class-decl ,js--plain-class-re
167 :prototype t
168 :contexts (toplevel)
169 :framework javascript)
171 (:name "MochiKit"
172 :class-decl ,js--mochikit-class-re
173 :prototype t
174 :contexts (toplevel)
175 :framework mochikit)
177 (:name "Prototype (Obsolete)"
178 :class-decl ,js--prototype-obsolete-class-decl-re
179 :contexts (toplevel)
180 :framework prototype)
182 (:name "Prototype (Modern)"
183 :class-decl ,js--prototype-class-decl-re
184 :contexts (toplevel)
185 :framework prototype)
187 (:name "Prototype (Object.extend)"
188 :class-decl ,js--prototype-objextend-class-decl-re-1
189 :prototype t
190 :contexts (toplevel)
191 :framework prototype)
193 (:name "Prototype (Object.extend) 2"
194 :class-decl ,js--prototype-objextend-class-decl-re-2
195 :prototype t
196 :contexts (toplevel)
197 :framework prototype)
199 (:name "Dojo"
200 :class-decl ,js--dojo-class-decl-re
201 :contexts (toplevel)
202 :framework dojo)
204 (:name "ExtJS (style 1)"
205 :class-decl ,js--extjs-class-decl-re-1
206 :prototype t
207 :contexts (toplevel)
208 :framework extjs)
210 (:name "ExtJS (style 2)"
211 :class-decl ,js--extjs-class-decl-re-2
212 :contexts (toplevel)
213 :framework extjs)
215 (:name "Merrill Press"
216 :class-decl ,js--mp-class-decl-re
217 :contexts (toplevel)
218 :framework merrillpress))
220 "List of JavaScript class definition styles.
222 A class definition style is a plist with the following keys:
224 :name is a human-readable name of the class type
226 :class-decl is a regular expression giving the start of the
227 class. Its first group must match the name of its class. If there
228 is a parent class, the second group should match, and it should be
229 the name of the class.
231 If :prototype is present and non-nil, the parser will merge
232 declarations for this constructs with others at the same lexical
233 level that have the same name. Otherwise, multiple definitions
234 will create multiple top-level entries. Don't use :prototype
235 unnecessarily: it has an associated cost in performance.
237 If :strip-prototype is present and non-nil, then if the class
238 name as matched contains
241 (defconst js--available-frameworks
242 (cl-loop for style in js--class-styles
243 for framework = (plist-get style :framework)
244 unless (memq framework available-frameworks)
245 collect framework into available-frameworks
246 finally return available-frameworks)
247 "List of available JavaScript frameworks symbols.")
249 (defconst js--function-heading-1-re
250 (concat
251 "^\\s-*function\\(?:\\s-\\|\\*\\)+\\(" js--name-re "\\)")
252 "Regexp matching the start of a JavaScript function header.
253 Match group 1 is the name of the function.")
255 (defconst js--function-heading-2-re
256 (concat
257 "^\\s-*\\(" js--name-re "\\)\\s-*:\\s-*function\\_>")
258 "Regexp matching the start of a function entry in an associative array.
259 Match group 1 is the name of the function.")
261 (defconst js--function-heading-3-re
262 (concat
263 "^\\s-*\\(?:var\\s-+\\)?\\(" js--dotted-name-re "\\)"
264 "\\s-*=\\s-*function\\_>")
265 "Regexp matching a line in the JavaScript form \"var MUMBLE = function\".
266 Match group 1 is MUMBLE.")
268 (defconst js--macro-decl-re
269 (concat "^\\s-*#\\s-*define\\s-+\\(" js--cpp-name-re "\\)\\s-*(")
270 "Regexp matching a CPP macro definition, up to the opening parenthesis.
271 Match group 1 is the name of the macro.")
273 (defun js--regexp-opt-symbol (list)
274 "Like `regexp-opt', but surround the result with `\\\\_<' and `\\\\_>'."
275 (concat "\\_<" (regexp-opt list t) "\\_>"))
277 (defconst js--keyword-re
278 (js--regexp-opt-symbol
279 '("abstract" "break" "case" "catch" "class" "const"
280 "continue" "debugger" "default" "delete" "do" "else"
281 "enum" "export" "extends" "final" "finally" "for"
282 "function" "goto" "if" "implements" "import" "in"
283 "instanceof" "interface" "native" "new" "package"
284 "private" "protected" "public" "return" "static"
285 "super" "switch" "synchronized" "throw"
286 "throws" "transient" "try" "typeof" "var" "void" "let"
287 "yield" "volatile" "while" "with"))
288 "Regexp matching any JavaScript keyword.")
290 (defconst js--basic-type-re
291 (js--regexp-opt-symbol
292 '("boolean" "byte" "char" "double" "float" "int" "long"
293 "short" "void"))
294 "Regular expression matching any predefined type in JavaScript.")
296 (defconst js--constant-re
297 (js--regexp-opt-symbol '("false" "null" "undefined"
298 "Infinity" "NaN"
299 "true" "arguments" "this"))
300 "Regular expression matching any future reserved words in JavaScript.")
303 (defconst js--font-lock-keywords-1
304 (list
305 "\\_<import\\_>"
306 (list js--function-heading-1-re 1 font-lock-function-name-face)
307 (list js--function-heading-2-re 1 font-lock-function-name-face))
308 "Level one font lock keywords for `js-mode'.")
310 (defconst js--font-lock-keywords-2
311 (append js--font-lock-keywords-1
312 (list (list js--keyword-re 1 font-lock-keyword-face)
313 (list "\\_<for\\_>"
314 "\\s-+\\(each\\)\\_>" nil nil
315 (list 1 'font-lock-keyword-face))
316 (cons js--basic-type-re font-lock-type-face)
317 (cons js--constant-re font-lock-constant-face)))
318 "Level two font lock keywords for `js-mode'.")
320 ;; js--pitem is the basic building block of the lexical
321 ;; database. When one refers to a real part of the buffer, the region
322 ;; of text to which it refers is split into a conceptual header and
323 ;; body. Consider the (very short) block described by a hypothetical
324 ;; js--pitem:
326 ;; function foo(a,b,c) { return 42; }
327 ;; ^ ^ ^
328 ;; | | |
329 ;; +- h-begin +- h-end +- b-end
331 ;; (Remember that these are buffer positions, and therefore point
332 ;; between characters, not at them. An arrow drawn to a character
333 ;; indicates the corresponding position is between that character and
334 ;; the one immediately preceding it.)
336 ;; The header is the region of text [h-begin, h-end], and is
337 ;; the text needed to unambiguously recognize the start of the
338 ;; construct. If the entire header is not present, the construct is
339 ;; not recognized at all. No other pitems may be nested inside the
340 ;; header.
342 ;; The body is the region [h-end, b-end]. It may contain nested
343 ;; js--pitem instances. The body of a pitem may be empty: in
344 ;; that case, b-end is equal to header-end.
346 ;; The three points obey the following relationship:
348 ;; h-begin < h-end <= b-end
350 ;; We put a text property in the buffer on the character *before*
351 ;; h-end, and if we see it, on the character *before* b-end.
353 ;; The text property for h-end, js--pstate, is actually a list
354 ;; of all js--pitem instances open after the marked character.
356 ;; The text property for b-end, js--pend, is simply the
357 ;; js--pitem that ends after the marked character. (Because
358 ;; pitems always end when the paren-depth drops below a critical
359 ;; value, and because we can only drop one level per character, only
360 ;; one pitem may end at a given character.)
362 ;; In the structure below, we only store h-begin and (sometimes)
363 ;; b-end. We can trivially and quickly find h-end by going to h-begin
364 ;; and searching for an js--pstate text property. Since no other
365 ;; js--pitem instances can be nested inside the header of a
366 ;; pitem, the location after the character with this text property
367 ;; must be h-end.
369 ;; js--pitem instances are never modified (with the exception
370 ;; of the b-end field). Instead, modified copies are added at
371 ;; subsequence parse points.
372 ;; (The exception for b-end and its caveats is described below.)
375 (cl-defstruct (js--pitem (:type list))
376 ;; IMPORTANT: Do not alter the position of fields within the list.
377 ;; Various bits of code depend on their positions, particularly
378 ;; anything that manipulates the list of children.
380 ;; List of children inside this pitem's body
381 (children nil :read-only t)
383 ;; When we reach this paren depth after h-end, the pitem ends
384 (paren-depth nil :read-only t)
386 ;; Symbol or class-style plist if this is a class
387 (type nil :read-only t)
389 ;; See above
390 (h-begin nil :read-only t)
392 ;; List of strings giving the parts of the name of this pitem (e.g.,
393 ;; '("MyClass" "myMethod"), or t if this pitem is anonymous
394 (name nil :read-only t)
396 ;; THIS FIELD IS MUTATED, and its value is shared by all copies of
397 ;; this pitem: when we copy-and-modify pitem instances, we share
398 ;; their tail structures, so all the copies actually have the same
399 ;; terminating cons cell. We modify that shared cons cell directly.
401 ;; The field value is either a number (buffer location) or nil if
402 ;; unknown.
404 ;; If the field's value is greater than `js--cache-end', the
405 ;; value is stale and must be treated as if it were nil. Conversely,
406 ;; if this field is nil, it is guaranteed that this pitem is open up
407 ;; to at least `js--cache-end'. (This property is handy when
408 ;; computing whether we're inside a given pitem.)
410 (b-end nil))
412 ;; The pitem we start parsing with.
413 (defconst js--initial-pitem
414 (make-js--pitem
415 :paren-depth most-negative-fixnum
416 :type 'toplevel))
418 ;;; User Customization
420 (defgroup js nil
421 "Customization variables for JavaScript mode."
422 :tag "JavaScript"
423 :group 'languages)
425 (defcustom js-indent-level 4
426 "Number of spaces for each indentation step in `js-mode'."
427 :type 'integer
428 :safe 'integerp
429 :group 'js)
431 (defcustom js-expr-indent-offset 0
432 "Number of additional spaces for indenting continued expressions.
433 The value must be no less than minus `js-indent-level'."
434 :type 'integer
435 :safe 'integerp
436 :group 'js)
438 (defcustom js-paren-indent-offset 0
439 "Number of additional spaces for indenting expressions in parentheses.
440 The value must be no less than minus `js-indent-level'."
441 :type 'integer
442 :safe 'integerp
443 :group 'js
444 :version "24.1")
446 (defcustom js-square-indent-offset 0
447 "Number of additional spaces for indenting expressions in square braces.
448 The value must be no less than minus `js-indent-level'."
449 :type 'integer
450 :safe 'integerp
451 :group 'js
452 :version "24.1")
454 (defcustom js-curly-indent-offset 0
455 "Number of additional spaces for indenting expressions in curly braces.
456 The value must be no less than minus `js-indent-level'."
457 :type 'integer
458 :safe 'integerp
459 :group 'js
460 :version "24.1")
462 (defcustom js-switch-indent-offset 0
463 "Number of additional spaces for indenting the contents of a switch block.
464 The value must not be negative."
465 :type 'integer
466 :safe 'integerp
467 :group 'js
468 :version "24.4")
470 (defcustom js-flat-functions nil
471 "Treat nested functions as top-level functions in `js-mode'.
472 This applies to function movement, marking, and so on."
473 :type 'boolean
474 :group 'js)
476 (defcustom js-comment-lineup-func #'c-lineup-C-comments
477 "Lineup function for `cc-mode-style', for C comments in `js-mode'."
478 :type 'function
479 :group 'js)
481 (defcustom js-enabled-frameworks js--available-frameworks
482 "Frameworks recognized by `js-mode'.
483 To improve performance, you may turn off some frameworks you
484 seldom use, either globally or on a per-buffer basis."
485 :type (cons 'set (mapcar (lambda (x)
486 (list 'const x))
487 js--available-frameworks))
488 :group 'js)
490 (defcustom js-js-switch-tabs
491 (and (memq system-type '(darwin)) t)
492 "Whether `js-mode' should display tabs while selecting them.
493 This is useful only if the windowing system has a good mechanism
494 for preventing Firefox from stealing the keyboard focus."
495 :type 'boolean
496 :group 'js)
498 (defcustom js-js-tmpdir
499 "~/.emacs.d/js/js"
500 "Temporary directory used by `js-mode' to communicate with Mozilla.
501 This directory must be readable and writable by both Mozilla and Emacs."
502 :type 'directory
503 :group 'js)
505 (defcustom js-js-timeout 5
506 "Reply timeout for executing commands in Mozilla via `js-mode'.
507 The value is given in seconds. Increase this value if you are
508 getting timeout messages."
509 :type 'integer
510 :group 'js)
512 ;;; KeyMap
514 (defvar js-mode-map
515 (let ((keymap (make-sparse-keymap)))
516 (define-key keymap [(control ?c) (meta ?:)] #'js-eval)
517 (define-key keymap [(control ?c) (control ?j)] #'js-set-js-context)
518 (define-key keymap [(control meta ?x)] #'js-eval-defun)
519 (define-key keymap [(meta ?.)] #'js-find-symbol)
520 (easy-menu-define nil keymap "Javascript Menu"
521 '("Javascript"
522 ["Select New Mozilla Context..." js-set-js-context
523 (fboundp #'inferior-moz-process)]
524 ["Evaluate Expression in Mozilla Context..." js-eval
525 (fboundp #'inferior-moz-process)]
526 ["Send Current Function to Mozilla..." js-eval-defun
527 (fboundp #'inferior-moz-process)]))
528 keymap)
529 "Keymap for `js-mode'.")
531 ;;; Syntax table and parsing
533 (defvar js-mode-syntax-table
534 (let ((table (make-syntax-table)))
535 (c-populate-syntax-table table)
536 (modify-syntax-entry ?$ "_" table)
537 table)
538 "Syntax table for `js-mode'.")
540 (defvar js--quick-match-re nil
541 "Autogenerated regexp used by `js-mode' to match buffer constructs.")
543 (defvar js--quick-match-re-func nil
544 "Autogenerated regexp used by `js-mode' to match constructs and functions.")
546 (make-variable-buffer-local 'js--quick-match-re)
547 (make-variable-buffer-local 'js--quick-match-re-func)
549 (defvar js--cache-end 1
550 "Last valid buffer position for the `js-mode' function cache.")
551 (make-variable-buffer-local 'js--cache-end)
553 (defvar js--last-parse-pos nil
554 "Latest parse position reached by `js--ensure-cache'.")
555 (make-variable-buffer-local 'js--last-parse-pos)
557 (defvar js--state-at-last-parse-pos nil
558 "Parse state at `js--last-parse-pos'.")
559 (make-variable-buffer-local 'js--state-at-last-parse-pos)
561 (defun js--flatten-list (list)
562 (cl-loop for item in list
563 nconc (cond ((consp item)
564 (js--flatten-list item))
565 (item (list item)))))
567 (defun js--maybe-join (prefix separator suffix &rest list)
568 "Helper function for `js--update-quick-match-re'.
569 If LIST contains any element that is not nil, return its non-nil
570 elements, separated by SEPARATOR, prefixed by PREFIX, and ended
571 with SUFFIX as with `concat'. Otherwise, if LIST is empty, return
572 nil. If any element in LIST is itself a list, flatten that
573 element."
574 (setq list (js--flatten-list list))
575 (when list
576 (concat prefix (mapconcat #'identity list separator) suffix)))
578 (defun js--update-quick-match-re ()
579 "Internal function used by `js-mode' for caching buffer constructs.
580 This updates `js--quick-match-re', based on the current set of
581 enabled frameworks."
582 (setq js--quick-match-re
583 (js--maybe-join
584 "^[ \t]*\\(?:" "\\|" "\\)"
586 ;; #define mumble
587 "#define[ \t]+[a-zA-Z_]"
589 (when (memq 'extjs js-enabled-frameworks)
590 "Ext\\.extend")
592 (when (memq 'prototype js-enabled-frameworks)
593 "Object\\.extend")
595 ;; var mumble = THING (
596 (js--maybe-join
597 "\\(?:var[ \t]+\\)?[a-zA-Z_$0-9.]+[ \t]*=[ \t]*\\(?:"
598 "\\|"
599 "\\)[ \t]*\("
601 (when (memq 'prototype js-enabled-frameworks)
602 "Class\\.create")
604 (when (memq 'extjs js-enabled-frameworks)
605 "Ext\\.extend")
607 (when (memq 'merrillpress js-enabled-frameworks)
608 "[a-zA-Z_$0-9]+\\.extend\\(?:Final\\)?"))
610 (when (memq 'dojo js-enabled-frameworks)
611 "dojo\\.declare[ \t]*\(")
613 (when (memq 'mochikit js-enabled-frameworks)
614 "MochiKit\\.Base\\.update[ \t]*\(")
616 ;; mumble.prototypeTHING
617 (js--maybe-join
618 "[a-zA-Z_$0-9.]+\\.prototype\\(?:" "\\|" "\\)"
620 (when (memq 'javascript js-enabled-frameworks)
621 '( ;; foo.prototype.bar = function(
622 "\\.[a-zA-Z_$0-9]+[ \t]*=[ \t]*function[ \t]*\("
624 ;; mumble.prototype = {
625 "[ \t]*=[ \t]*{")))))
627 (setq js--quick-match-re-func
628 (concat "function\\|" js--quick-match-re)))
630 (defun js--forward-text-property (propname)
631 "Move over the next value of PROPNAME in the buffer.
632 If found, return that value and leave point after the character
633 having that value; otherwise, return nil and leave point at EOB."
634 (let ((next-value (get-text-property (point) propname)))
635 (if next-value
636 (forward-char)
638 (goto-char (next-single-property-change
639 (point) propname nil (point-max)))
640 (unless (eobp)
641 (setq next-value (get-text-property (point) propname))
642 (forward-char)))
644 next-value))
646 (defun js--backward-text-property (propname)
647 "Move over the previous value of PROPNAME in the buffer.
648 If found, return that value and leave point just before the
649 character that has that value, otherwise return nil and leave
650 point at BOB."
651 (unless (bobp)
652 (let ((prev-value (get-text-property (1- (point)) propname)))
653 (if prev-value
654 (backward-char)
656 (goto-char (previous-single-property-change
657 (point) propname nil (point-min)))
659 (unless (bobp)
660 (backward-char)
661 (setq prev-value (get-text-property (point) propname))))
663 prev-value)))
665 (defsubst js--forward-pstate ()
666 (js--forward-text-property 'js--pstate))
668 (defsubst js--backward-pstate ()
669 (js--backward-text-property 'js--pstate))
671 (defun js--pitem-goto-h-end (pitem)
672 (goto-char (js--pitem-h-begin pitem))
673 (js--forward-pstate))
675 (defun js--re-search-forward-inner (regexp &optional bound count)
676 "Helper function for `js--re-search-forward'."
677 (let ((parse)
678 str-terminator
679 (orig-macro-end (save-excursion
680 (when (js--beginning-of-macro)
681 (c-end-of-macro)
682 (point)))))
683 (while (> count 0)
684 (re-search-forward regexp bound)
685 (setq parse (syntax-ppss))
686 (cond ((setq str-terminator (nth 3 parse))
687 (when (eq str-terminator t)
688 (setq str-terminator ?/))
689 (re-search-forward
690 (concat "\\([^\\]\\|^\\)" (string str-terminator))
691 (point-at-eol) t))
692 ((nth 7 parse)
693 (forward-line))
694 ((or (nth 4 parse)
695 (and (eq (char-before) ?\/) (eq (char-after) ?\*)))
696 (re-search-forward "\\*/"))
697 ((and (not (and orig-macro-end
698 (<= (point) orig-macro-end)))
699 (js--beginning-of-macro))
700 (c-end-of-macro))
702 (setq count (1- count))))))
703 (point))
706 (defun js--re-search-forward (regexp &optional bound noerror count)
707 "Search forward, ignoring strings, cpp macros, and comments.
708 This function invokes `re-search-forward', but treats the buffer
709 as if strings, cpp macros, and comments have been removed.
711 If invoked while inside a macro, it treats the contents of the
712 macro as normal text."
713 (unless count (setq count 1))
714 (let ((saved-point (point))
715 (search-fun
716 (cond ((< count 0) (setq count (- count))
717 #'js--re-search-backward-inner)
718 ((> count 0) #'js--re-search-forward-inner)
719 (t #'ignore))))
720 (condition-case err
721 (funcall search-fun regexp bound count)
722 (search-failed
723 (goto-char saved-point)
724 (unless noerror
725 (signal (car err) (cdr err)))))))
728 (defun js--re-search-backward-inner (regexp &optional bound count)
729 "Auxiliary function for `js--re-search-backward'."
730 (let ((parse)
731 str-terminator
732 (orig-macro-start
733 (save-excursion
734 (and (js--beginning-of-macro)
735 (point)))))
736 (while (> count 0)
737 (re-search-backward regexp bound)
738 (when (and (> (point) (point-min))
739 (save-excursion (backward-char) (looking-at "/[/*]")))
740 (forward-char))
741 (setq parse (syntax-ppss))
742 (cond ((setq str-terminator (nth 3 parse))
743 (when (eq str-terminator t)
744 (setq str-terminator ?/))
745 (re-search-backward
746 (concat "\\([^\\]\\|^\\)" (string str-terminator))
747 (point-at-bol) t))
748 ((nth 7 parse)
749 (goto-char (nth 8 parse)))
750 ((or (nth 4 parse)
751 (and (eq (char-before) ?/) (eq (char-after) ?*)))
752 (re-search-backward "/\\*"))
753 ((and (not (and orig-macro-start
754 (>= (point) orig-macro-start)))
755 (js--beginning-of-macro)))
757 (setq count (1- count))))))
758 (point))
761 (defun js--re-search-backward (regexp &optional bound noerror count)
762 "Search backward, ignoring strings, preprocessor macros, and comments.
764 This function invokes `re-search-backward' but treats the buffer
765 as if strings, preprocessor macros, and comments have been
766 removed.
768 If invoked while inside a macro, treat the macro as normal text."
769 (js--re-search-forward regexp bound noerror (if count (- count) -1)))
771 (defun js--forward-expression ()
772 "Move forward over a whole JavaScript expression.
773 This function doesn't move over expressions continued across
774 lines."
775 (cl-loop
776 ;; non-continued case; simplistic, but good enough?
777 do (cl-loop until (or (eolp)
778 (progn
779 (forward-comment most-positive-fixnum)
780 (memq (char-after) '(?\, ?\; ?\] ?\) ?\}))))
781 do (forward-sexp))
783 while (and (eq (char-after) ?\n)
784 (save-excursion
785 (forward-char)
786 (js--continued-expression-p)))))
788 (defun js--forward-function-decl ()
789 "Move forward over a JavaScript function declaration.
790 This puts point at the 'function' keyword.
792 If this is a syntactically-correct non-expression function,
793 return the name of the function, or t if the name could not be
794 determined. Otherwise, return nil."
795 (cl-assert (looking-at "\\_<function\\_>"))
796 (let ((name t))
797 (forward-word)
798 (forward-comment most-positive-fixnum)
799 (when (eq (char-after) ?*)
800 (forward-char)
801 (forward-comment most-positive-fixnum))
802 (when (looking-at js--name-re)
803 (setq name (match-string-no-properties 0))
804 (goto-char (match-end 0)))
805 (forward-comment most-positive-fixnum)
806 (and (eq (char-after) ?\( )
807 (ignore-errors (forward-list) t)
808 (progn (forward-comment most-positive-fixnum)
809 (and (eq (char-after) ?{)
810 name)))))
812 (defun js--function-prologue-beginning (&optional pos)
813 "Return the start of the JavaScript function prologue containing POS.
814 A function prologue is everything from start of the definition up
815 to and including the opening brace. POS defaults to point.
816 If POS is not in a function prologue, return nil."
817 (let (prologue-begin)
818 (save-excursion
819 (if pos
820 (goto-char pos)
821 (setq pos (point)))
823 (when (save-excursion
824 (forward-line 0)
825 (or (looking-at js--function-heading-2-re)
826 (looking-at js--function-heading-3-re)))
828 (setq prologue-begin (match-beginning 1))
829 (when (<= prologue-begin pos)
830 (goto-char (match-end 0))))
832 (skip-syntax-backward "w_")
833 (and (or (looking-at "\\_<function\\_>")
834 (js--re-search-backward "\\_<function\\_>" nil t))
836 (save-match-data (goto-char (match-beginning 0))
837 (js--forward-function-decl))
839 (<= pos (point))
840 (or prologue-begin (match-beginning 0))))))
842 (defun js--beginning-of-defun-raw ()
843 "Helper function for `js-beginning-of-defun'.
844 Go to previous defun-beginning and return the parse state for it,
845 or nil if we went all the way back to bob and don't find
846 anything."
847 (js--ensure-cache)
848 (let (pstate)
849 (while (and (setq pstate (js--backward-pstate))
850 (not (eq 'function (js--pitem-type (car pstate))))))
851 (and (not (bobp)) pstate)))
853 (defun js--pstate-is-toplevel-defun (pstate)
854 "Helper function for `js--beginning-of-defun-nested'.
855 If PSTATE represents a non-empty top-level defun, return the
856 top-most pitem. Otherwise, return nil."
857 (cl-loop for pitem in pstate
858 with func-depth = 0
859 with func-pitem
860 if (eq 'function (js--pitem-type pitem))
861 do (cl-incf func-depth)
862 and do (setq func-pitem pitem)
863 finally return (if (eq func-depth 1) func-pitem)))
865 (defun js--beginning-of-defun-nested ()
866 "Helper function for `js--beginning-of-defun'.
867 Return the pitem of the function we went to the beginning of."
869 ;; Look for the smallest function that encloses point...
870 (cl-loop for pitem in (js--parse-state-at-point)
871 if (and (eq 'function (js--pitem-type pitem))
872 (js--inside-pitem-p pitem))
873 do (goto-char (js--pitem-h-begin pitem))
874 and return pitem)
876 ;; ...and if that isn't found, look for the previous top-level
877 ;; defun
878 (cl-loop for pstate = (js--backward-pstate)
879 while pstate
880 if (js--pstate-is-toplevel-defun pstate)
881 do (goto-char (js--pitem-h-begin it))
882 and return it)))
884 (defun js--beginning-of-defun-flat ()
885 "Helper function for `js-beginning-of-defun'."
886 (let ((pstate (js--beginning-of-defun-raw)))
887 (when pstate
888 (goto-char (js--pitem-h-begin (car pstate))))))
890 (defun js-beginning-of-defun (&optional arg)
891 "Value of `beginning-of-defun-function' for `js-mode'."
892 (setq arg (or arg 1))
893 (while (and (not (eobp)) (< arg 0))
894 (cl-incf arg)
895 (when (and (not js-flat-functions)
896 (or (eq (js-syntactic-context) 'function)
897 (js--function-prologue-beginning)))
898 (js-end-of-defun))
900 (if (js--re-search-forward
901 "\\_<function\\_>" nil t)
902 (goto-char (js--function-prologue-beginning))
903 (goto-char (point-max))))
905 (while (> arg 0)
906 (cl-decf arg)
907 ;; If we're just past the end of a function, the user probably wants
908 ;; to go to the beginning of *that* function
909 (when (eq (char-before) ?})
910 (backward-char))
912 (let ((prologue-begin (js--function-prologue-beginning)))
913 (cond ((and prologue-begin (< prologue-begin (point)))
914 (goto-char prologue-begin))
916 (js-flat-functions
917 (js--beginning-of-defun-flat))
919 (js--beginning-of-defun-nested))))))
921 (defun js--flush-caches (&optional beg ignored)
922 "Flush the `js-mode' syntax cache after position BEG.
923 BEG defaults to `point-min', meaning to flush the entire cache."
924 (interactive)
925 (setq beg (or beg (save-restriction (widen) (point-min))))
926 (setq js--cache-end (min js--cache-end beg)))
928 (defmacro js--debug (&rest _arguments)
929 ;; `(message ,@arguments)
932 (defun js--ensure-cache--pop-if-ended (open-items paren-depth)
933 (let ((top-item (car open-items)))
934 (when (<= paren-depth (js--pitem-paren-depth top-item))
935 (cl-assert (not (get-text-property (1- (point)) 'js-pend)))
936 (put-text-property (1- (point)) (point) 'js--pend top-item)
937 (setf (js--pitem-b-end top-item) (point))
938 (setq open-items
939 ;; open-items must contain at least two items for this to
940 ;; work, but because we push a dummy item to start with,
941 ;; that assumption holds.
942 (cons (js--pitem-add-child (cl-second open-items) top-item)
943 (cddr open-items)))))
944 open-items)
946 (defmacro js--ensure-cache--update-parse ()
947 "Helper function for `js--ensure-cache'.
948 Update parsing information up to point, referring to parse,
949 prev-parse-point, goal-point, and open-items bound lexically in
950 the body of `js--ensure-cache'."
951 `(progn
952 (setq goal-point (point))
953 (goto-char prev-parse-point)
954 (while (progn
955 (setq open-items (js--ensure-cache--pop-if-ended
956 open-items (car parse)))
957 ;; Make sure parse-partial-sexp doesn't stop because we *entered*
958 ;; the given depth -- i.e., make sure we're deeper than the target
959 ;; depth.
960 (cl-assert (> (nth 0 parse)
961 (js--pitem-paren-depth (car open-items))))
962 (setq parse (parse-partial-sexp
963 prev-parse-point goal-point
964 (js--pitem-paren-depth (car open-items))
965 nil parse))
967 ;; (let ((overlay (make-overlay prev-parse-point (point))))
968 ;; (overlay-put overlay 'face '(:background "red"))
969 ;; (unwind-protect
970 ;; (progn
971 ;; (js--debug "parsed: %S" parse)
972 ;; (sit-for 1))
973 ;; (delete-overlay overlay)))
975 (setq prev-parse-point (point))
976 (< (point) goal-point)))
978 (setq open-items (js--ensure-cache--pop-if-ended
979 open-items (car parse)))))
981 (defun js--show-cache-at-point ()
982 (interactive)
983 (require 'pp)
984 (let ((prop (get-text-property (point) 'js--pstate)))
985 (with-output-to-temp-buffer "*Help*"
986 (pp prop))))
988 (defun js--split-name (string)
989 "Split a JavaScript name into its dot-separated parts.
990 This also removes any prototype parts from the split name
991 \(unless the name is just \"prototype\" to start with)."
992 (let ((name (save-match-data
993 (split-string string "\\." t))))
994 (unless (and (= (length name) 1)
995 (equal (car name) "prototype"))
997 (setq name (remove "prototype" name)))))
999 (defvar js--guess-function-name-start nil)
1001 (defun js--guess-function-name (position)
1002 "Guess the name of the JavaScript function at POSITION.
1003 POSITION should be just after the end of the word \"function\".
1004 Return the name of the function, or nil if the name could not be
1005 guessed.
1007 This function clobbers match data. If we find the preamble
1008 begins earlier than expected while guessing the function name,
1009 set `js--guess-function-name-start' to that position; otherwise,
1010 set that variable to nil."
1011 (setq js--guess-function-name-start nil)
1012 (save-excursion
1013 (goto-char position)
1014 (forward-line 0)
1015 (cond
1016 ((looking-at js--function-heading-3-re)
1017 (and (eq (match-end 0) position)
1018 (setq js--guess-function-name-start (match-beginning 1))
1019 (match-string-no-properties 1)))
1021 ((looking-at js--function-heading-2-re)
1022 (and (eq (match-end 0) position)
1023 (setq js--guess-function-name-start (match-beginning 1))
1024 (match-string-no-properties 1))))))
1026 (defun js--clear-stale-cache ()
1027 ;; Clear any endings that occur after point
1028 (let (end-prop)
1029 (save-excursion
1030 (while (setq end-prop (js--forward-text-property
1031 'js--pend))
1032 (setf (js--pitem-b-end end-prop) nil))))
1034 ;; Remove any cache properties after this point
1035 (remove-text-properties (point) (point-max)
1036 '(js--pstate t js--pend t)))
1038 (defun js--ensure-cache (&optional limit)
1039 "Ensures brace cache is valid up to the character before LIMIT.
1040 LIMIT defaults to point."
1041 (setq limit (or limit (point)))
1042 (when (< js--cache-end limit)
1044 (c-save-buffer-state
1045 (open-items
1046 parse
1047 prev-parse-point
1048 name
1049 case-fold-search
1050 filtered-class-styles
1051 goal-point)
1053 ;; Figure out which class styles we need to look for
1054 (setq filtered-class-styles
1055 (cl-loop for style in js--class-styles
1056 if (memq (plist-get style :framework)
1057 js-enabled-frameworks)
1058 collect style))
1060 (save-excursion
1061 (save-restriction
1062 (widen)
1064 ;; Find last known good position
1065 (goto-char js--cache-end)
1066 (unless (bobp)
1067 (setq open-items (get-text-property
1068 (1- (point)) 'js--pstate))
1070 (unless open-items
1071 (goto-char (previous-single-property-change
1072 (point) 'js--pstate nil (point-min)))
1074 (unless (bobp)
1075 (setq open-items (get-text-property (1- (point))
1076 'js--pstate))
1077 (cl-assert open-items))))
1079 (unless open-items
1080 ;; Make a placeholder for the top-level definition
1081 (setq open-items (list js--initial-pitem)))
1083 (setq parse (syntax-ppss))
1084 (setq prev-parse-point (point))
1086 (js--clear-stale-cache)
1088 (narrow-to-region (point-min) limit)
1090 (cl-loop while (re-search-forward js--quick-match-re-func nil t)
1091 for orig-match-start = (goto-char (match-beginning 0))
1092 for orig-match-end = (match-end 0)
1093 do (js--ensure-cache--update-parse)
1094 for orig-depth = (nth 0 parse)
1096 ;; Each of these conditions should return non-nil if
1097 ;; we should add a new item and leave point at the end
1098 ;; of the new item's header (h-end in the
1099 ;; js--pitem diagram). This point is the one
1100 ;; after the last character we need to unambiguously
1101 ;; detect this construct. If one of these evaluates to
1102 ;; nil, the location of the point is ignored.
1103 if (cond
1104 ;; In comment or string
1105 ((nth 8 parse) nil)
1107 ;; Regular function declaration
1108 ((and (looking-at "\\_<function\\_>")
1109 (setq name (js--forward-function-decl)))
1111 (when (eq name t)
1112 (setq name (js--guess-function-name orig-match-end))
1113 (if name
1114 (when js--guess-function-name-start
1115 (setq orig-match-start
1116 js--guess-function-name-start))
1118 (setq name t)))
1120 (cl-assert (eq (char-after) ?{))
1121 (forward-char)
1122 (make-js--pitem
1123 :paren-depth orig-depth
1124 :h-begin orig-match-start
1125 :type 'function
1126 :name (if (eq name t)
1127 name
1128 (js--split-name name))))
1130 ;; Macro
1131 ((looking-at js--macro-decl-re)
1133 ;; Macros often contain unbalanced parentheses.
1134 ;; Make sure that h-end is at the textual end of
1135 ;; the macro no matter what the parenthesis say.
1136 (c-end-of-macro)
1137 (js--ensure-cache--update-parse)
1139 (make-js--pitem
1140 :paren-depth (nth 0 parse)
1141 :h-begin orig-match-start
1142 :type 'macro
1143 :name (list (match-string-no-properties 1))))
1145 ;; "Prototype function" declaration
1146 ((looking-at js--plain-method-re)
1147 (goto-char (match-beginning 3))
1148 (when (save-match-data
1149 (js--forward-function-decl))
1150 (forward-char)
1151 (make-js--pitem
1152 :paren-depth orig-depth
1153 :h-begin orig-match-start
1154 :type 'function
1155 :name (nconc (js--split-name
1156 (match-string-no-properties 1))
1157 (list (match-string-no-properties 2))))))
1159 ;; Class definition
1160 ((cl-loop
1161 with syntactic-context =
1162 (js--syntactic-context-from-pstate open-items)
1163 for class-style in filtered-class-styles
1164 if (and (memq syntactic-context
1165 (plist-get class-style :contexts))
1166 (looking-at (plist-get class-style
1167 :class-decl)))
1168 do (goto-char (match-end 0))
1169 and return
1170 (make-js--pitem
1171 :paren-depth orig-depth
1172 :h-begin orig-match-start
1173 :type class-style
1174 :name (js--split-name
1175 (match-string-no-properties 1))))))
1177 do (js--ensure-cache--update-parse)
1178 and do (push it open-items)
1179 and do (put-text-property
1180 (1- (point)) (point) 'js--pstate open-items)
1181 else do (goto-char orig-match-end))
1183 (goto-char limit)
1184 (js--ensure-cache--update-parse)
1185 (setq js--cache-end limit)
1186 (setq js--last-parse-pos limit)
1187 (setq js--state-at-last-parse-pos open-items)
1188 )))))
1190 (defun js--end-of-defun-flat ()
1191 "Helper function for `js-end-of-defun'."
1192 (cl-loop while (js--re-search-forward "}" nil t)
1193 do (js--ensure-cache)
1194 if (get-text-property (1- (point)) 'js--pend)
1195 if (eq 'function (js--pitem-type it))
1196 return t
1197 finally do (goto-char (point-max))))
1199 (defun js--end-of-defun-nested ()
1200 "Helper function for `js-end-of-defun'."
1201 (message "test")
1202 (let* (pitem
1203 (this-end (save-excursion
1204 (and (setq pitem (js--beginning-of-defun-nested))
1205 (js--pitem-goto-h-end pitem)
1206 (progn (backward-char)
1207 (forward-list)
1208 (point)))))
1209 found)
1211 (if (and this-end (< (point) this-end))
1212 ;; We're already inside a function; just go to its end.
1213 (goto-char this-end)
1215 ;; Otherwise, go to the end of the next function...
1216 (while (and (js--re-search-forward "\\_<function\\_>" nil t)
1217 (not (setq found (progn
1218 (goto-char (match-beginning 0))
1219 (js--forward-function-decl))))))
1221 (if found (forward-list)
1222 ;; ... or eob.
1223 (goto-char (point-max))))))
1225 (defun js-end-of-defun (&optional arg)
1226 "Value of `end-of-defun-function' for `js-mode'."
1227 (setq arg (or arg 1))
1228 (while (and (not (bobp)) (< arg 0))
1229 (cl-incf arg)
1230 (js-beginning-of-defun)
1231 (js-beginning-of-defun)
1232 (unless (bobp)
1233 (js-end-of-defun)))
1235 (while (> arg 0)
1236 (cl-decf arg)
1237 ;; look for function backward. if we're inside it, go to that
1238 ;; function's end. otherwise, search for the next function's end and
1239 ;; go there
1240 (if js-flat-functions
1241 (js--end-of-defun-flat)
1243 ;; if we're doing nested functions, see whether we're in the
1244 ;; prologue. If we are, go to the end of the function; otherwise,
1245 ;; call js--end-of-defun-nested to do the real work
1246 (let ((prologue-begin (js--function-prologue-beginning)))
1247 (cond ((and prologue-begin (<= prologue-begin (point)))
1248 (goto-char prologue-begin)
1249 (re-search-forward "\\_<function")
1250 (goto-char (match-beginning 0))
1251 (js--forward-function-decl)
1252 (forward-list))
1254 (t (js--end-of-defun-nested)))))))
1256 (defun js--beginning-of-macro (&optional lim)
1257 (let ((here (point)))
1258 (save-restriction
1259 (if lim (narrow-to-region lim (point-max)))
1260 (beginning-of-line)
1261 (while (eq (char-before (1- (point))) ?\\)
1262 (forward-line -1))
1263 (back-to-indentation)
1264 (if (and (<= (point) here)
1265 (looking-at js--opt-cpp-start))
1267 (goto-char here)
1268 nil))))
1270 (defun js--backward-syntactic-ws (&optional lim)
1271 "Simple implementation of `c-backward-syntactic-ws' for `js-mode'."
1272 (save-restriction
1273 (when lim (narrow-to-region lim (point-max)))
1275 (let ((in-macro (save-excursion (js--beginning-of-macro)))
1276 (pos (point)))
1278 (while (progn (unless in-macro (js--beginning-of-macro))
1279 (forward-comment most-negative-fixnum)
1280 (/= (point)
1281 (prog1
1283 (setq pos (point)))))))))
1285 (defun js--forward-syntactic-ws (&optional lim)
1286 "Simple implementation of `c-forward-syntactic-ws' for `js-mode'."
1287 (save-restriction
1288 (when lim (narrow-to-region (point-min) lim))
1289 (let ((pos (point)))
1290 (while (progn
1291 (forward-comment most-positive-fixnum)
1292 (when (eq (char-after) ?#)
1293 (c-end-of-macro))
1294 (/= (point)
1295 (prog1
1297 (setq pos (point)))))))))
1299 ;; Like (up-list -1), but only considers lists that end nearby"
1300 (defun js--up-nearby-list ()
1301 (save-restriction
1302 ;; Look at a very small region so our computation time doesn't
1303 ;; explode in pathological cases.
1304 (narrow-to-region (max (point-min) (- (point) 500)) (point))
1305 (up-list -1)))
1307 (defun js--inside-param-list-p ()
1308 "Return non-nil if point is in a function parameter list."
1309 (ignore-errors
1310 (save-excursion
1311 (js--up-nearby-list)
1312 (and (looking-at "(")
1313 (progn (forward-symbol -1)
1314 (or (looking-at "function")
1315 (progn (forward-symbol -1)
1316 (looking-at "function"))))))))
1318 (defun js--inside-dojo-class-list-p ()
1319 "Return non-nil if point is in a Dojo multiple-inheritance class block."
1320 (ignore-errors
1321 (save-excursion
1322 (js--up-nearby-list)
1323 (let ((list-begin (point)))
1324 (forward-line 0)
1325 (and (looking-at js--dojo-class-decl-re)
1326 (goto-char (match-end 0))
1327 (looking-at "\"\\s-*,\\s-*\\[")
1328 (eq (match-end 0) (1+ list-begin)))))))
1330 (defun js--syntax-begin-function ()
1331 (when (< js--cache-end (point))
1332 (goto-char (max (point-min) js--cache-end)))
1334 (let ((pitem))
1335 (while (and (setq pitem (car (js--backward-pstate)))
1336 (not (eq 0 (js--pitem-paren-depth pitem)))))
1338 (when pitem
1339 (goto-char (js--pitem-h-begin pitem )))))
1341 ;;; Font Lock
1342 (defun js--make-framework-matcher (framework &rest regexps)
1343 "Helper function for building `js--font-lock-keywords'.
1344 Create a byte-compiled function for matching a concatenation of
1345 REGEXPS, but only if FRAMEWORK is in `js-enabled-frameworks'."
1346 (setq regexps (apply #'concat regexps))
1347 (byte-compile
1348 `(lambda (limit)
1349 (when (memq (quote ,framework) js-enabled-frameworks)
1350 (re-search-forward ,regexps limit t)))))
1352 (defvar js--tmp-location nil)
1353 (make-variable-buffer-local 'js--tmp-location)
1355 (defun js--forward-destructuring-spec (&optional func)
1356 "Move forward over a JavaScript destructuring spec.
1357 If FUNC is supplied, call it with no arguments before every
1358 variable name in the spec. Return true if this was actually a
1359 spec. FUNC must preserve the match data."
1360 (pcase (char-after)
1361 (?\[
1362 (forward-char)
1363 (while
1364 (progn
1365 (forward-comment most-positive-fixnum)
1366 (cond ((memq (char-after) '(?\[ ?\{))
1367 (js--forward-destructuring-spec func))
1369 ((eq (char-after) ?,)
1370 (forward-char)
1373 ((looking-at js--name-re)
1374 (and func (funcall func))
1375 (goto-char (match-end 0))
1376 t))))
1377 (when (eq (char-after) ?\])
1378 (forward-char)
1381 (?\{
1382 (forward-char)
1383 (forward-comment most-positive-fixnum)
1384 (while
1385 (when (looking-at js--objfield-re)
1386 (goto-char (match-end 0))
1387 (forward-comment most-positive-fixnum)
1388 (and (cond ((memq (char-after) '(?\[ ?\{))
1389 (js--forward-destructuring-spec func))
1390 ((looking-at js--name-re)
1391 (and func (funcall func))
1392 (goto-char (match-end 0))
1394 (progn (forward-comment most-positive-fixnum)
1395 (when (eq (char-after) ?\,)
1396 (forward-char)
1397 (forward-comment most-positive-fixnum)
1398 t)))))
1399 (when (eq (char-after) ?\})
1400 (forward-char)
1401 t))))
1403 (defun js--variable-decl-matcher (limit)
1404 "Font-lock matcher for variable names in a variable declaration.
1405 This is a cc-mode-style matcher that *always* fails, from the
1406 point of view of font-lock. It applies highlighting directly with
1407 `font-lock-apply-highlight'."
1408 (condition-case nil
1409 (save-restriction
1410 (narrow-to-region (point-min) limit)
1412 (let ((first t))
1413 (forward-comment most-positive-fixnum)
1414 (while
1415 (and (or first
1416 (when (eq (char-after) ?,)
1417 (forward-char)
1418 (forward-comment most-positive-fixnum)
1420 (cond ((looking-at js--name-re)
1421 (font-lock-apply-highlight
1422 '(0 font-lock-variable-name-face))
1423 (goto-char (match-end 0)))
1425 ((save-excursion
1426 (js--forward-destructuring-spec))
1428 (js--forward-destructuring-spec
1429 (lambda ()
1430 (font-lock-apply-highlight
1431 '(0 font-lock-variable-name-face)))))))
1433 (forward-comment most-positive-fixnum)
1434 (when (eq (char-after) ?=)
1435 (forward-char)
1436 (js--forward-expression)
1437 (forward-comment most-positive-fixnum))
1439 (setq first nil))))
1441 ;; Conditions to handle
1442 (scan-error nil)
1443 (end-of-buffer nil))
1445 ;; Matcher always "fails"
1446 nil)
1448 (defconst js--font-lock-keywords-3
1450 ;; This goes before keywords-2 so it gets used preferentially
1451 ;; instead of the keywords in keywords-2. Don't use override
1452 ;; because that will override syntactic fontification too, which
1453 ;; will fontify commented-out directives as if they weren't
1454 ;; commented out.
1455 ,@cpp-font-lock-keywords ; from font-lock.el
1457 ,@js--font-lock-keywords-2
1459 ("\\.\\(prototype\\)\\_>"
1460 (1 font-lock-constant-face))
1462 ;; Highlights class being declared, in parts
1463 (js--class-decl-matcher
1464 ,(concat "\\(" js--name-re "\\)\\(?:\\.\\|.*$\\)")
1465 (goto-char (match-beginning 1))
1467 (1 font-lock-type-face))
1469 ;; Highlights parent class, in parts, if available
1470 (js--class-decl-matcher
1471 ,(concat "\\(" js--name-re "\\)\\(?:\\.\\|.*$\\)")
1472 (if (match-beginning 2)
1473 (progn
1474 (setq js--tmp-location (match-end 2))
1475 (goto-char js--tmp-location)
1476 (insert "=")
1477 (goto-char (match-beginning 2)))
1478 (setq js--tmp-location nil)
1479 (goto-char (point-at-eol)))
1480 (when js--tmp-location
1481 (save-excursion
1482 (goto-char js--tmp-location)
1483 (delete-char 1)))
1484 (1 font-lock-type-face))
1486 ;; Highlights parent class
1487 (js--class-decl-matcher
1488 (2 font-lock-type-face nil t))
1490 ;; Dojo needs its own matcher to override the string highlighting
1491 (,(js--make-framework-matcher
1492 'dojo
1493 "^\\s-*dojo\\.declare\\s-*(\""
1494 "\\(" js--dotted-name-re "\\)"
1495 "\\(?:\"\\s-*,\\s-*\\(" js--dotted-name-re "\\)\\)?")
1496 (1 font-lock-type-face t)
1497 (2 font-lock-type-face nil t))
1499 ;; Match Dojo base classes. Of course Mojo has to be different
1500 ;; from everything else under the sun...
1501 (,(js--make-framework-matcher
1502 'dojo
1503 "^\\s-*dojo\\.declare\\s-*(\""
1504 "\\(" js--dotted-name-re "\\)\"\\s-*,\\s-*\\[")
1505 ,(concat "[[,]\\s-*\\(" js--dotted-name-re "\\)\\s-*"
1506 "\\(?:\\].*$\\)?")
1507 (backward-char)
1508 (end-of-line)
1509 (1 font-lock-type-face))
1511 ;; continued Dojo base-class list
1512 (,(js--make-framework-matcher
1513 'dojo
1514 "^\\s-*" js--dotted-name-re "\\s-*[],]")
1515 ,(concat "\\(" js--dotted-name-re "\\)"
1516 "\\s-*\\(?:\\].*$\\)?")
1517 (if (save-excursion (backward-char)
1518 (js--inside-dojo-class-list-p))
1519 (forward-symbol -1)
1520 (end-of-line))
1521 (end-of-line)
1522 (1 font-lock-type-face))
1524 ;; variable declarations
1525 ,(list
1526 (concat "\\_<\\(const\\|var\\|let\\)\\_>\\|" js--basic-type-re)
1527 (list #'js--variable-decl-matcher nil nil nil))
1529 ;; class instantiation
1530 ,(list
1531 (concat "\\_<new\\_>\\s-+\\(" js--dotted-name-re "\\)")
1532 (list 1 'font-lock-type-face))
1534 ;; instanceof
1535 ,(list
1536 (concat "\\_<instanceof\\_>\\s-+\\(" js--dotted-name-re "\\)")
1537 (list 1 'font-lock-type-face))
1539 ;; formal parameters
1540 ,(list
1541 (concat
1542 "\\_<function\\_>\\(\\s-+" js--name-re "\\)?\\s-*(\\s-*"
1543 js--name-start-re)
1544 (list (concat "\\(" js--name-re "\\)\\(\\s-*).*\\)?")
1545 '(backward-char)
1546 '(end-of-line)
1547 '(1 font-lock-variable-name-face)))
1549 ;; continued formal parameter list
1550 ,(list
1551 (concat
1552 "^\\s-*" js--name-re "\\s-*[,)]")
1553 (list js--name-re
1554 '(if (save-excursion (backward-char)
1555 (js--inside-param-list-p))
1556 (forward-symbol -1)
1557 (end-of-line))
1558 '(end-of-line)
1559 '(0 font-lock-variable-name-face))))
1560 "Level three font lock for `js-mode'.")
1562 (defun js--inside-pitem-p (pitem)
1563 "Return whether point is inside the given pitem's header or body."
1564 (js--ensure-cache)
1565 (cl-assert (js--pitem-h-begin pitem))
1566 (cl-assert (js--pitem-paren-depth pitem))
1568 (and (> (point) (js--pitem-h-begin pitem))
1569 (or (null (js--pitem-b-end pitem))
1570 (> (js--pitem-b-end pitem) (point)))))
1572 (defun js--parse-state-at-point ()
1573 "Parse the JavaScript program state at point.
1574 Return a list of `js--pitem' instances that apply to point, most
1575 specific first. In the worst case, the current toplevel instance
1576 will be returned."
1577 (save-excursion
1578 (save-restriction
1579 (widen)
1580 (js--ensure-cache)
1581 (let ((pstate (or (save-excursion
1582 (js--backward-pstate))
1583 (list js--initial-pitem))))
1585 ;; Loop until we either hit a pitem at BOB or pitem ends after
1586 ;; point (or at point if we're at eob)
1587 (cl-loop for pitem = (car pstate)
1588 until (or (eq (js--pitem-type pitem)
1589 'toplevel)
1590 (js--inside-pitem-p pitem))
1591 do (pop pstate))
1593 pstate))))
1595 (defun js--syntactic-context-from-pstate (pstate)
1596 "Return the JavaScript syntactic context corresponding to PSTATE."
1597 (let ((type (js--pitem-type (car pstate))))
1598 (cond ((memq type '(function macro))
1599 type)
1600 ((consp type)
1601 'class)
1602 (t 'toplevel))))
1604 (defun js-syntactic-context ()
1605 "Return the JavaScript syntactic context at point.
1606 When called interactively, also display a message with that
1607 context."
1608 (interactive)
1609 (let* ((syntactic-context (js--syntactic-context-from-pstate
1610 (js--parse-state-at-point))))
1612 (when (called-interactively-p 'interactive)
1613 (message "Syntactic context: %s" syntactic-context))
1615 syntactic-context))
1617 (defun js--class-decl-matcher (limit)
1618 "Font lock function used by `js-mode'.
1619 This performs fontification according to `js--class-styles'."
1620 (cl-loop initially (js--ensure-cache limit)
1621 while (re-search-forward js--quick-match-re limit t)
1622 for orig-end = (match-end 0)
1623 do (goto-char (match-beginning 0))
1624 if (cl-loop for style in js--class-styles
1625 for decl-re = (plist-get style :class-decl)
1626 if (and (memq (plist-get style :framework)
1627 js-enabled-frameworks)
1628 (memq (js-syntactic-context)
1629 (plist-get style :contexts))
1630 decl-re
1631 (looking-at decl-re))
1632 do (goto-char (match-end 0))
1633 and return t)
1634 return t
1635 else do (goto-char orig-end)))
1637 (defconst js--font-lock-keywords
1638 '(js--font-lock-keywords-3 js--font-lock-keywords-1
1639 js--font-lock-keywords-2
1640 js--font-lock-keywords-3)
1641 "Font lock keywords for `js-mode'. See `font-lock-keywords'.")
1643 (defconst js--syntax-propertize-regexp-syntax-table
1644 (let ((st (make-char-table 'syntax-table (string-to-syntax "."))))
1645 (modify-syntax-entry ?\[ "(]" st)
1646 (modify-syntax-entry ?\] ")[" st)
1647 (modify-syntax-entry ?\\ "\\" st)
1648 st))
1650 (defun js-syntax-propertize-regexp (end)
1651 (let ((ppss (syntax-ppss)))
1652 (when (eq (nth 3 ppss) ?/)
1653 ;; A /.../ regexp.
1654 (while
1655 (when (re-search-forward "\\(?:\\=\\|[^\\]\\)\\(?:\\\\\\\\\\)*/"
1656 end 'move)
1657 (if (nth 1 (with-syntax-table
1658 js--syntax-propertize-regexp-syntax-table
1659 (let ((parse-sexp-lookup-properties nil))
1660 (parse-partial-sexp (nth 8 ppss) (point)))))
1661 ;; A / within a character class is not the end of a regexp.
1663 (put-text-property (1- (point)) (point)
1664 'syntax-table (string-to-syntax "\"/"))
1665 nil))))))
1667 (defun js-syntax-propertize (start end)
1668 ;; Javascript allows immediate regular expression objects, written /.../.
1669 (goto-char start)
1670 (js-syntax-propertize-regexp end)
1671 (funcall
1672 (syntax-propertize-rules
1673 ;; Distinguish /-division from /-regexp chars (and from /-comment-starter).
1674 ;; FIXME: Allow regexps after infix ops like + ...
1675 ;; https://developer.mozilla.org/en/JavaScript/Reference/Operators
1676 ;; We can probably just add +, -, !, <, >, %, ^, ~, |, &, ?, : at which
1677 ;; point I think only * and / would be missing which could also be added,
1678 ;; but need care to avoid affecting the // and */ comment markers.
1679 ("\\(?:^\\|[=([{,:;]\\|\\_<return\\_>\\)\\(?:[ \t]\\)*\\(/\\)[^/*]"
1680 (1 (ignore
1681 (forward-char -1)
1682 (when (or (not (memq (char-after (match-beginning 0)) '(?\s ?\t)))
1683 ;; If the / is at the beginning of line, we have to check
1684 ;; the end of the previous text.
1685 (save-excursion
1686 (goto-char (match-beginning 0))
1687 (forward-comment (- (point)))
1688 (memq (char-before)
1689 (eval-when-compile (append "=({[,:;" '(nil))))))
1690 (put-text-property (match-beginning 1) (match-end 1)
1691 'syntax-table (string-to-syntax "\"/"))
1692 (js-syntax-propertize-regexp end))))))
1693 (point) end))
1695 ;;; Indentation
1697 (defconst js--possibly-braceless-keyword-re
1698 (js--regexp-opt-symbol
1699 '("catch" "do" "else" "finally" "for" "if" "try" "while" "with"
1700 "each"))
1701 "Regexp matching keywords optionally followed by an opening brace.")
1703 (defconst js--declaration-keyword-re
1704 (regexp-opt '("var" "let" "const") 'words)
1705 "Regular expression matching variable declaration keywords.")
1707 (defconst js--indent-operator-re
1708 (concat "[-+*/%<>&^|?:.]\\([^-+*/]\\|$\\)\\|!?=\\|"
1709 (js--regexp-opt-symbol '("in" "instanceof")))
1710 "Regexp matching operators that affect indentation of continued expressions.")
1712 (defun js--looking-at-operator-p ()
1713 "Return non-nil if point is on a JavaScript operator, other than a comma."
1714 (save-match-data
1715 (and (looking-at js--indent-operator-re)
1716 (or (not (looking-at ":"))
1717 (save-excursion
1718 (and (js--re-search-backward "[?:{]\\|\\_<case\\_>" nil t)
1719 (looking-at "?")))))))
1722 (defun js--continued-expression-p ()
1723 "Return non-nil if the current line continues an expression."
1724 (save-excursion
1725 (back-to-indentation)
1726 (or (js--looking-at-operator-p)
1727 (and (js--re-search-backward "\n" nil t)
1728 (progn
1729 (skip-chars-backward " \t")
1730 (or (bobp) (backward-char))
1731 (and (> (point) (point-min))
1732 (save-excursion (backward-char) (not (looking-at "[/*]/")))
1733 (js--looking-at-operator-p)
1734 (and (progn (backward-char)
1735 (not (looking-at "+\\+\\|--\\|/[/*]"))))))))))
1738 (defun js--end-of-do-while-loop-p ()
1739 "Return non-nil if point is on the \"while\" of a do-while statement.
1740 Otherwise, return nil. A braceless do-while statement spanning
1741 several lines requires that the start of the loop is indented to
1742 the same column as the current line."
1743 (interactive)
1744 (save-excursion
1745 (save-match-data
1746 (when (looking-at "\\s-*\\_<while\\_>")
1747 (if (save-excursion
1748 (skip-chars-backward "[ \t\n]*}")
1749 (looking-at "[ \t\n]*}"))
1750 (save-excursion
1751 (backward-list) (forward-symbol -1) (looking-at "\\_<do\\_>"))
1752 (js--re-search-backward "\\_<do\\_>" (point-at-bol) t)
1753 (or (looking-at "\\_<do\\_>")
1754 (let ((saved-indent (current-indentation)))
1755 (while (and (js--re-search-backward "^\\s-*\\_<" nil t)
1756 (/= (current-indentation) saved-indent)))
1757 (and (looking-at "\\s-*\\_<do\\_>")
1758 (not (js--re-search-forward
1759 "\\_<while\\_>" (point-at-eol) t))
1760 (= (current-indentation) saved-indent)))))))))
1763 (defun js--ctrl-statement-indentation ()
1764 "Helper function for `js--proper-indentation'.
1765 Return the proper indentation of the current line if it starts
1766 the body of a control statement without braces; otherwise, return
1767 nil."
1768 (save-excursion
1769 (back-to-indentation)
1770 (when (save-excursion
1771 (and (not (eq (point-at-bol) (point-min)))
1772 (not (looking-at "[{]"))
1773 (js--re-search-backward "[[:graph:]]" nil t)
1774 (progn
1775 (or (eobp) (forward-char))
1776 (when (= (char-before) ?\)) (backward-list))
1777 (skip-syntax-backward " ")
1778 (skip-syntax-backward "w_")
1779 (looking-at js--possibly-braceless-keyword-re))
1780 (not (js--end-of-do-while-loop-p))))
1781 (save-excursion
1782 (goto-char (match-beginning 0))
1783 (+ (current-indentation) js-indent-level)))))
1785 (defun js--get-c-offset (symbol anchor)
1786 (let ((c-offsets-alist
1787 (list (cons 'c js-comment-lineup-func))))
1788 (c-get-syntactic-indentation (list (cons symbol anchor)))))
1790 (defun js--same-line (pos)
1791 (and (>= pos (point-at-bol))
1792 (<= pos (point-at-eol))))
1794 (defun js--multi-line-declaration-indentation ()
1795 "Helper function for `js--proper-indentation'.
1796 Return the proper indentation of the current line if it belongs to a declaration
1797 statement spanning multiple lines; otherwise, return nil."
1798 (let (at-opening-bracket)
1799 (save-excursion
1800 (back-to-indentation)
1801 (when (not (looking-at js--declaration-keyword-re))
1802 (when (looking-at js--indent-operator-re)
1803 (goto-char (match-end 0)))
1804 (while (and (not at-opening-bracket)
1805 (not (bobp))
1806 (let ((pos (point)))
1807 (save-excursion
1808 (js--backward-syntactic-ws)
1809 (or (eq (char-before) ?,)
1810 (and (not (eq (char-before) ?\;))
1811 (prog2
1812 (skip-syntax-backward ".")
1813 (looking-at js--indent-operator-re)
1814 (js--backward-syntactic-ws))
1815 (not (eq (char-before) ?\;)))
1816 (js--same-line pos)))))
1817 (condition-case nil
1818 (backward-sexp)
1819 (scan-error (setq at-opening-bracket t))))
1820 (when (looking-at js--declaration-keyword-re)
1821 (goto-char (match-end 0))
1822 (1+ (current-column)))))))
1824 (defun js--indent-in-array-comp (bracket)
1825 "Return non-nil if we think we're in an array comprehension.
1826 In particular, return the buffer position of the first `for' kwd."
1827 (let ((end (point)))
1828 (save-excursion
1829 (goto-char bracket)
1830 (when (looking-at "\\[")
1831 (forward-char 1)
1832 (js--forward-syntactic-ws)
1833 (if (looking-at "[[{]")
1834 (let (forward-sexp-function) ; Use Lisp version.
1835 (forward-sexp) ; Skip destructuring form.
1836 (js--forward-syntactic-ws)
1837 (if (and (/= (char-after) ?,) ; Regular array.
1838 (looking-at "for"))
1839 (match-beginning 0)))
1840 ;; To skip arbitrary expressions we need the parser,
1841 ;; so we'll just guess at it.
1842 (if (and (> end (point)) ; Not empty literal.
1843 (re-search-forward "[^,]]* \\(for\\) " end t)
1844 ;; Not inside comment or string literal.
1845 (not (nth 8 (parse-partial-sexp bracket (point)))))
1846 (match-beginning 1)))))))
1848 (defun js--array-comp-indentation (bracket for-kwd)
1849 (if (js--same-line for-kwd)
1850 ;; First continuation line.
1851 (save-excursion
1852 (goto-char bracket)
1853 (forward-char 1)
1854 (skip-chars-forward " \t")
1855 (current-column))
1856 (save-excursion
1857 (goto-char for-kwd)
1858 (current-column))))
1860 (defun js--proper-indentation (parse-status)
1861 "Return the proper indentation for the current line."
1862 (save-excursion
1863 (back-to-indentation)
1864 (cond ((nth 4 parse-status) ; inside comment
1865 (js--get-c-offset 'c (nth 8 parse-status)))
1866 ((nth 3 parse-status) 0) ; inside string
1867 ((eq (char-after) ?#) 0)
1868 ((save-excursion (js--beginning-of-macro)) 4)
1869 ;; Indent array comprehension continuation lines specially.
1870 ((let ((bracket (nth 1 parse-status))
1871 beg)
1872 (and bracket
1873 (not (js--same-line bracket))
1874 (setq beg (js--indent-in-array-comp bracket))
1875 ;; At or after the first loop?
1876 (>= (point) beg)
1877 (js--array-comp-indentation bracket beg))))
1878 ((js--ctrl-statement-indentation))
1879 ((js--multi-line-declaration-indentation))
1880 ((nth 1 parse-status)
1881 ;; A single closing paren/bracket should be indented at the
1882 ;; same level as the opening statement. Same goes for
1883 ;; "case" and "default".
1884 (let ((same-indent-p (looking-at "[]})]"))
1885 (switch-keyword-p (looking-at "default\\_>\\|case\\_>[^:]"))
1886 (continued-expr-p (js--continued-expression-p)))
1887 (goto-char (nth 1 parse-status)) ; go to the opening char
1888 (if (looking-at "[({[]\\s-*\\(/[/*]\\|$\\)")
1889 (progn ; nothing following the opening paren/bracket
1890 (skip-syntax-backward " ")
1891 (when (eq (char-before) ?\)) (backward-list))
1892 (back-to-indentation)
1893 (let* ((in-switch-p (unless same-indent-p
1894 (looking-at "\\_<switch\\_>")))
1895 (same-indent-p (or same-indent-p
1896 (and switch-keyword-p
1897 in-switch-p)))
1898 (indent
1899 (cond (same-indent-p
1900 (current-column))
1901 (continued-expr-p
1902 (+ (current-column) (* 2 js-indent-level)
1903 js-expr-indent-offset))
1905 (+ (current-column) js-indent-level
1906 (pcase (char-after (nth 1 parse-status))
1907 (?\( js-paren-indent-offset)
1908 (?\[ js-square-indent-offset)
1909 (?\{ js-curly-indent-offset)))))))
1910 (if in-switch-p
1911 (+ indent js-switch-indent-offset)
1912 indent)))
1913 ;; If there is something following the opening
1914 ;; paren/bracket, everything else should be indented at
1915 ;; the same level.
1916 (unless same-indent-p
1917 (forward-char)
1918 (skip-chars-forward " \t"))
1919 (current-column))))
1921 ((js--continued-expression-p)
1922 (+ js-indent-level js-expr-indent-offset))
1923 (t 0))))
1925 (defun js-indent-line ()
1926 "Indent the current line as JavaScript."
1927 (interactive)
1928 (let* ((parse-status
1929 (save-excursion (syntax-ppss (point-at-bol))))
1930 (offset (- (point) (save-excursion (back-to-indentation) (point)))))
1931 (indent-line-to (js--proper-indentation parse-status))
1932 (when (> offset 0) (forward-char offset))))
1934 ;;; Filling
1936 (defvar js--filling-paragraph nil)
1938 ;; FIXME: Such redefinitions are bad style. We should try and use some other
1939 ;; way to get the same result.
1940 (defadvice c-forward-sws (around js-fill-paragraph activate)
1941 (if js--filling-paragraph
1942 (setq ad-return-value (js--forward-syntactic-ws (ad-get-arg 0)))
1943 ad-do-it))
1945 (defadvice c-backward-sws (around js-fill-paragraph activate)
1946 (if js--filling-paragraph
1947 (setq ad-return-value (js--backward-syntactic-ws (ad-get-arg 0)))
1948 ad-do-it))
1950 (defadvice c-beginning-of-macro (around js-fill-paragraph activate)
1951 (if js--filling-paragraph
1952 (setq ad-return-value (js--beginning-of-macro (ad-get-arg 0)))
1953 ad-do-it))
1955 (defun js-c-fill-paragraph (&optional justify)
1956 "Fill the paragraph with `c-fill-paragraph'."
1957 (interactive "*P")
1958 (let ((js--filling-paragraph t)
1959 (fill-paragraph-function 'c-fill-paragraph))
1960 (c-fill-paragraph justify)))
1962 ;;; Type database and Imenu
1964 ;; We maintain a cache of semantic information, i.e., the classes and
1965 ;; functions we've encountered so far. In order to avoid having to
1966 ;; re-parse the buffer on every change, we cache the parse state at
1967 ;; each interesting point in the buffer. Each parse state is a
1968 ;; modified copy of the previous one, or in the case of the first
1969 ;; parse state, the empty state.
1971 ;; The parse state itself is just a stack of js--pitem
1972 ;; instances. It starts off containing one element that is never
1973 ;; closed, that is initially js--initial-pitem.
1977 (defun js--pitem-format (pitem)
1978 (let ((name (js--pitem-name pitem))
1979 (type (js--pitem-type pitem)))
1981 (format "name:%S type:%S"
1982 name
1983 (if (atom type)
1984 type
1985 (plist-get type :name)))))
1987 (defun js--make-merged-item (item child name-parts)
1988 "Helper function for `js--splice-into-items'.
1989 Return a new item that is the result of merging CHILD into
1990 ITEM. NAME-PARTS is a list of parts of the name of CHILD
1991 that we haven't consumed yet."
1992 (js--debug "js--make-merged-item: {%s} into {%s}"
1993 (js--pitem-format child)
1994 (js--pitem-format item))
1996 ;; If the item we're merging into isn't a class, make it into one
1997 (unless (consp (js--pitem-type item))
1998 (js--debug "js--make-merged-item: changing dest into class")
1999 (setq item (make-js--pitem
2000 :children (list item)
2002 ;; Use the child's class-style if it's available
2003 :type (if (atom (js--pitem-type child))
2004 js--dummy-class-style
2005 (js--pitem-type child))
2007 :name (js--pitem-strname item))))
2009 ;; Now we can merge either a function or a class into a class
2010 (cons (cond
2011 ((cdr name-parts)
2012 (js--debug "js--make-merged-item: recursing")
2013 ;; if we have more name-parts to go before we get to the
2014 ;; bottom of the class hierarchy, call the merger
2015 ;; recursively
2016 (js--splice-into-items (car item) child
2017 (cdr name-parts)))
2019 ((atom (js--pitem-type child))
2020 (js--debug "js--make-merged-item: straight merge")
2021 ;; Not merging a class, but something else, so just prepend
2022 ;; it
2023 (cons child (car item)))
2026 ;; Otherwise, merge the new child's items into those
2027 ;; of the new class
2028 (js--debug "js--make-merged-item: merging class contents")
2029 (append (car child) (car item))))
2030 (cdr item)))
2032 (defun js--pitem-strname (pitem)
2033 "Last part of the name of PITEM, as a string or symbol."
2034 (let ((name (js--pitem-name pitem)))
2035 (if (consp name)
2036 (car (last name))
2037 name)))
2039 (defun js--splice-into-items (items child name-parts)
2040 "Splice CHILD into the `js--pitem' ITEMS at NAME-PARTS.
2041 If a class doesn't exist in the tree, create it. Return
2042 the new items list. NAME-PARTS is a list of strings given
2043 the broken-down class name of the item to insert."
2045 (let ((top-name (car name-parts))
2046 (item-ptr items)
2047 new-items last-new-item new-cons)
2049 (js--debug "js--splice-into-items: name-parts: %S items:%S"
2050 name-parts
2051 (mapcar #'js--pitem-name items))
2053 (cl-assert (stringp top-name))
2054 (cl-assert (> (length top-name) 0))
2056 ;; If top-name isn't found in items, then we build a copy of items
2057 ;; and throw it away. But that's okay, since most of the time, we
2058 ;; *will* find an instance.
2060 (while (and item-ptr
2061 (cond ((equal (js--pitem-strname (car item-ptr)) top-name)
2062 ;; Okay, we found an entry with the right name. Splice
2063 ;; the merged item into the list...
2064 (setq new-cons (cons (js--make-merged-item
2065 (car item-ptr) child
2066 name-parts)
2067 (cdr item-ptr)))
2069 (if last-new-item
2070 (setcdr last-new-item new-cons)
2071 (setq new-items new-cons))
2073 ;; ...and terminate the loop
2074 nil)
2077 ;; Otherwise, copy the current cons and move onto the
2078 ;; text. This is tricky; we keep track of the tail of
2079 ;; the list that begins with new-items in
2080 ;; last-new-item.
2081 (setq new-cons (cons (car item-ptr) nil))
2082 (if last-new-item
2083 (setcdr last-new-item new-cons)
2084 (setq new-items new-cons))
2085 (setq last-new-item new-cons)
2087 ;; Go to the next cell in items
2088 (setq item-ptr (cdr item-ptr))))))
2090 (if item-ptr
2091 ;; Yay! We stopped because we found something, not because
2092 ;; we ran out of items to search. Just return the new
2093 ;; list.
2094 (progn
2095 (js--debug "search succeeded: %S" name-parts)
2096 new-items)
2098 ;; We didn't find anything. If the child is a class and we don't
2099 ;; have any classes to drill down into, just push that class;
2100 ;; otherwise, make a fake class and carry on.
2101 (js--debug "search failed: %S" name-parts)
2102 (cons (if (cdr name-parts)
2103 ;; We have name-parts left to process. Make a fake
2104 ;; class for this particular part...
2105 (make-js--pitem
2106 ;; ...and recursively digest the rest of the name
2107 :children (js--splice-into-items
2108 nil child (cdr name-parts))
2109 :type js--dummy-class-style
2110 :name top-name)
2112 ;; Otherwise, this is the only name we have, so stick
2113 ;; the item on the front of the list
2114 child)
2115 items))))
2117 (defun js--pitem-add-child (pitem child)
2118 "Copy `js--pitem' PITEM, and push CHILD onto its list of children."
2119 (cl-assert (integerp (js--pitem-h-begin child)))
2120 (cl-assert (if (consp (js--pitem-name child))
2121 (cl-loop for part in (js--pitem-name child)
2122 always (stringp part))
2125 ;; This trick works because we know (based on our defstructs) that
2126 ;; the child list is always the first element, and so the second
2127 ;; element and beyond can be shared when we make our "copy".
2128 (cons
2130 (let ((name (js--pitem-name child))
2131 (type (js--pitem-type child)))
2133 (cond ((cdr-safe name) ; true if a list of at least two elements
2134 ;; Use slow path because we need class lookup
2135 (js--splice-into-items (car pitem) child name))
2137 ((and (consp type)
2138 (plist-get type :prototype))
2140 ;; Use slow path because we need class merging. We know
2141 ;; name is a list here because down in
2142 ;; `js--ensure-cache', we made sure to only add
2143 ;; class entries with lists for :name
2144 (cl-assert (consp name))
2145 (js--splice-into-items (car pitem) child name))
2148 ;; Fast path
2149 (cons child (car pitem)))))
2151 (cdr pitem)))
2153 (defun js--maybe-make-marker (location)
2154 "Return a marker for LOCATION if `imenu-use-markers' is non-nil."
2155 (if imenu-use-markers
2156 (set-marker (make-marker) location)
2157 location))
2159 (defun js--pitems-to-imenu (pitems unknown-ctr)
2160 "Convert PITEMS, a list of `js--pitem' structures, to imenu format."
2162 (let (imenu-items pitem pitem-type pitem-name subitems)
2164 (while (setq pitem (pop pitems))
2165 (setq pitem-type (js--pitem-type pitem))
2166 (setq pitem-name (js--pitem-strname pitem))
2167 (when (eq pitem-name t)
2168 (setq pitem-name (format "[unknown %s]"
2169 (cl-incf (car unknown-ctr)))))
2171 (cond
2172 ((memq pitem-type '(function macro))
2173 (cl-assert (integerp (js--pitem-h-begin pitem)))
2174 (push (cons pitem-name
2175 (js--maybe-make-marker
2176 (js--pitem-h-begin pitem)))
2177 imenu-items))
2179 ((consp pitem-type) ; class definition
2180 (setq subitems (js--pitems-to-imenu
2181 (js--pitem-children pitem)
2182 unknown-ctr))
2183 (cond (subitems
2184 (push (cons pitem-name subitems)
2185 imenu-items))
2187 ((js--pitem-h-begin pitem)
2188 (cl-assert (integerp (js--pitem-h-begin pitem)))
2189 (setq subitems (list
2190 (cons "[empty]"
2191 (js--maybe-make-marker
2192 (js--pitem-h-begin pitem)))))
2193 (push (cons pitem-name subitems)
2194 imenu-items))))
2196 (t (error "Unknown item type: %S" pitem-type))))
2198 imenu-items))
2200 (defun js--imenu-create-index ()
2201 "Return an imenu index for the current buffer."
2202 (save-excursion
2203 (save-restriction
2204 (widen)
2205 (goto-char (point-max))
2206 (js--ensure-cache)
2207 (cl-assert (or (= (point-min) (point-max))
2208 (eq js--last-parse-pos (point))))
2209 (when js--last-parse-pos
2210 (let ((state js--state-at-last-parse-pos)
2211 (unknown-ctr (cons -1 nil)))
2213 ;; Make sure everything is closed
2214 (while (cdr state)
2215 (setq state
2216 (cons (js--pitem-add-child (cl-second state) (car state))
2217 (cddr state))))
2219 (cl-assert (= (length state) 1))
2221 ;; Convert the new-finalized state into what imenu expects
2222 (js--pitems-to-imenu
2223 (car (js--pitem-children state))
2224 unknown-ctr))))))
2226 ;; Silence the compiler.
2227 (defvar which-func-imenu-joiner-function)
2229 (defun js--which-func-joiner (parts)
2230 (mapconcat #'identity parts "."))
2232 (defun js--imenu-to-flat (items prefix symbols)
2233 (cl-loop for item in items
2234 if (imenu--subalist-p item)
2235 do (js--imenu-to-flat
2236 (cdr item) (concat prefix (car item) ".")
2237 symbols)
2238 else
2239 do (let* ((name (concat prefix (car item)))
2240 (name2 name)
2241 (ctr 0))
2243 (while (gethash name2 symbols)
2244 (setq name2 (format "%s<%d>" name (cl-incf ctr))))
2246 (puthash name2 (cdr item) symbols))))
2248 (defun js--get-all-known-symbols ()
2249 "Return a hash table of all JavaScript symbols.
2250 This searches all existing `js-mode' buffers. Each key is the
2251 name of a symbol (possibly disambiguated with <N>, where N > 1),
2252 and each value is a marker giving the location of that symbol."
2253 (cl-loop with symbols = (make-hash-table :test 'equal)
2254 with imenu-use-markers = t
2255 for buffer being the buffers
2256 for imenu-index = (with-current-buffer buffer
2257 (when (derived-mode-p 'js-mode)
2258 (js--imenu-create-index)))
2259 do (js--imenu-to-flat imenu-index "" symbols)
2260 finally return symbols))
2262 (defvar js--symbol-history nil
2263 "History of entered JavaScript symbols.")
2265 (defun js--read-symbol (symbols-table prompt &optional initial-input)
2266 "Helper function for `js-find-symbol'.
2267 Read a symbol from SYMBOLS-TABLE, which is a hash table like the
2268 one from `js--get-all-known-symbols', using prompt PROMPT and
2269 initial input INITIAL-INPUT. Return a cons of (SYMBOL-NAME
2270 . LOCATION), where SYMBOL-NAME is a string and LOCATION is a
2271 marker."
2272 (unless ido-mode
2273 (ido-mode 1)
2274 (ido-mode -1))
2276 (let ((choice (ido-completing-read
2277 prompt
2278 (cl-loop for key being the hash-keys of symbols-table
2279 collect key)
2280 nil t initial-input 'js--symbol-history)))
2281 (cons choice (gethash choice symbols-table))))
2283 (defun js--guess-symbol-at-point ()
2284 (let ((bounds (bounds-of-thing-at-point 'symbol)))
2285 (when bounds
2286 (save-excursion
2287 (goto-char (car bounds))
2288 (when (eq (char-before) ?.)
2289 (backward-char)
2290 (setf (car bounds) (point))))
2291 (buffer-substring (car bounds) (cdr bounds)))))
2293 (defvar find-tag-marker-ring) ; etags
2295 ;; etags loads ring.
2296 (declare-function ring-insert "ring" (ring item))
2298 (defun js-find-symbol (&optional arg)
2299 "Read a JavaScript symbol and jump to it.
2300 With a prefix argument, restrict symbols to those from the
2301 current buffer. Pushes a mark onto the tag ring just like
2302 `find-tag'."
2303 (interactive "P")
2304 (require 'etags)
2305 (let (symbols marker)
2306 (if (not arg)
2307 (setq symbols (js--get-all-known-symbols))
2308 (setq symbols (make-hash-table :test 'equal))
2309 (js--imenu-to-flat (js--imenu-create-index)
2310 "" symbols))
2312 (setq marker (cdr (js--read-symbol
2313 symbols "Jump to: "
2314 (js--guess-symbol-at-point))))
2316 (ring-insert find-tag-marker-ring (point-marker))
2317 (switch-to-buffer (marker-buffer marker))
2318 (push-mark)
2319 (goto-char marker)))
2321 ;;; MozRepl integration
2323 (define-error 'js-moz-bad-rpc "Mozilla RPC Error") ;; '(timeout error))
2324 (define-error 'js-js-error "Javascript Error") ;; '(js-error error))
2326 (defun js--wait-for-matching-output
2327 (process regexp timeout &optional start)
2328 "Wait TIMEOUT seconds for PROCESS to output a match for REGEXP.
2329 On timeout, return nil. On success, return t with match data
2330 set. If START is non-nil, look for output starting from START.
2331 Otherwise, use the current value of `process-mark'."
2332 (with-current-buffer (process-buffer process)
2333 (cl-loop with start-pos = (or start
2334 (marker-position (process-mark process)))
2335 with end-time = (+ (float-time) timeout)
2336 for time-left = (- end-time (float-time))
2337 do (goto-char (point-max))
2338 if (looking-back regexp start-pos) return t
2339 while (> time-left 0)
2340 do (accept-process-output process time-left nil t)
2341 do (goto-char (process-mark process))
2342 finally do (signal
2343 'js-moz-bad-rpc
2344 (list (format "Timed out waiting for output matching %S" regexp))))))
2346 (cl-defstruct js--js-handle
2347 ;; Integer, mirrors the value we see in JS
2348 (id nil :read-only t)
2350 ;; Process to which this thing belongs
2351 (process nil :read-only t))
2353 (defun js--js-handle-expired-p (x)
2354 (not (eq (js--js-handle-process x)
2355 (inferior-moz-process))))
2357 (defvar js--js-references nil
2358 "Maps Elisp JavaScript proxy objects to their JavaScript IDs.")
2360 (defvar js--js-process nil
2361 "The most recent MozRepl process object.")
2363 (defvar js--js-gc-idle-timer nil
2364 "Idle timer for cleaning up JS object references.")
2366 (defvar js--js-last-gcs-done nil)
2368 (defconst js--moz-interactor
2369 (replace-regexp-in-string
2370 "[ \n]+" " "
2371 ; */" Make Emacs happy
2372 "(function(repl) {
2373 repl.defineInteractor('js', {
2374 onStart: function onStart(repl) {
2375 if(!repl._jsObjects) {
2376 repl._jsObjects = {};
2377 repl._jsLastID = 0;
2378 repl._jsGC = this._jsGC;
2380 this._input = '';
2383 _jsGC: function _jsGC(ids_in_use) {
2384 var objects = this._jsObjects;
2385 var keys = [];
2386 var num_freed = 0;
2388 for(var pn in objects) {
2389 keys.push(Number(pn));
2392 keys.sort(function(x, y) x - y);
2393 ids_in_use.sort(function(x, y) x - y);
2394 var i = 0;
2395 var j = 0;
2397 while(i < ids_in_use.length && j < keys.length) {
2398 var id = ids_in_use[i++];
2399 while(j < keys.length && keys[j] !== id) {
2400 var k_id = keys[j++];
2401 delete objects[k_id];
2402 ++num_freed;
2404 ++j;
2407 while(j < keys.length) {
2408 var k_id = keys[j++];
2409 delete objects[k_id];
2410 ++num_freed;
2413 return num_freed;
2416 _mkArray: function _mkArray() {
2417 var result = [];
2418 for(var i = 0; i < arguments.length; ++i) {
2419 result.push(arguments[i]);
2421 return result;
2424 _parsePropDescriptor: function _parsePropDescriptor(parts) {
2425 if(typeof parts === 'string') {
2426 parts = [ parts ];
2429 var obj = parts[0];
2430 var start = 1;
2432 if(typeof obj === 'string') {
2433 obj = window;
2434 start = 0;
2435 } else if(parts.length < 2) {
2436 throw new Error('expected at least 2 arguments');
2439 for(var i = start; i < parts.length - 1; ++i) {
2440 obj = obj[parts[i]];
2443 return [obj, parts[parts.length - 1]];
2446 _getProp: function _getProp(/*...*/) {
2447 if(arguments.length === 0) {
2448 throw new Error('no arguments supplied to getprop');
2451 if(arguments.length === 1 &&
2452 (typeof arguments[0]) !== 'string')
2454 return arguments[0];
2457 var [obj, propname] = this._parsePropDescriptor(arguments);
2458 return obj[propname];
2461 _putProp: function _putProp(properties, value) {
2462 var [obj, propname] = this._parsePropDescriptor(properties);
2463 obj[propname] = value;
2466 _delProp: function _delProp(propname) {
2467 var [obj, propname] = this._parsePropDescriptor(arguments);
2468 delete obj[propname];
2471 _typeOf: function _typeOf(thing) {
2472 return typeof thing;
2475 _callNew: function(constructor) {
2476 if(typeof constructor === 'string')
2478 constructor = window[constructor];
2479 } else if(constructor.length === 1 &&
2480 typeof constructor[0] !== 'string')
2482 constructor = constructor[0];
2483 } else {
2484 var [obj,propname] = this._parsePropDescriptor(constructor);
2485 constructor = obj[propname];
2488 /* Hacky, but should be robust */
2489 var s = 'new constructor(';
2490 for(var i = 1; i < arguments.length; ++i) {
2491 if(i != 1) {
2492 s += ',';
2495 s += 'arguments[' + i + ']';
2498 s += ')';
2499 return eval(s);
2502 _callEval: function(thisobj, js) {
2503 return eval.call(thisobj, js);
2506 getPrompt: function getPrompt(repl) {
2507 return 'EVAL>'
2510 _lookupObject: function _lookupObject(repl, id) {
2511 if(typeof id === 'string') {
2512 switch(id) {
2513 case 'global':
2514 return window;
2515 case 'nil':
2516 return null;
2517 case 't':
2518 return true;
2519 case 'false':
2520 return false;
2521 case 'undefined':
2522 return undefined;
2523 case 'repl':
2524 return repl;
2525 case 'interactor':
2526 return this;
2527 case 'NaN':
2528 return NaN;
2529 case 'Infinity':
2530 return Infinity;
2531 case '-Infinity':
2532 return -Infinity;
2533 default:
2534 throw new Error('No object with special id:' + id);
2538 var ret = repl._jsObjects[id];
2539 if(ret === undefined) {
2540 throw new Error('No object with id:' + id + '(' + typeof id + ')');
2542 return ret;
2545 _findOrAllocateObject: function _findOrAllocateObject(repl, value) {
2546 if(typeof value !== 'object' && typeof value !== 'function') {
2547 throw new Error('_findOrAllocateObject called on non-object('
2548 + typeof(value) + '): '
2549 + value)
2552 for(var id in repl._jsObjects) {
2553 id = Number(id);
2554 var obj = repl._jsObjects[id];
2555 if(obj === value) {
2556 return id;
2560 var id = ++repl._jsLastID;
2561 repl._jsObjects[id] = value;
2562 return id;
2565 _fixupList: function _fixupList(repl, list) {
2566 for(var i = 0; i < list.length; ++i) {
2567 if(list[i] instanceof Array) {
2568 this._fixupList(repl, list[i]);
2569 } else if(typeof list[i] === 'object') {
2570 var obj = list[i];
2571 if(obj.funcall) {
2572 var parts = obj.funcall;
2573 this._fixupList(repl, parts);
2574 var [thisobj, func] = this._parseFunc(parts[0]);
2575 list[i] = func.apply(thisobj, parts.slice(1));
2576 } else if(obj.objid) {
2577 list[i] = this._lookupObject(repl, obj.objid);
2578 } else {
2579 throw new Error('Unknown object type: ' + obj.toSource());
2585 _parseFunc: function(func) {
2586 var thisobj = null;
2588 if(typeof func === 'string') {
2589 func = window[func];
2590 } else if(func instanceof Array) {
2591 if(func.length === 1 && typeof func[0] !== 'string') {
2592 func = func[0];
2593 } else {
2594 [thisobj, func] = this._parsePropDescriptor(func);
2595 func = thisobj[func];
2599 return [thisobj,func];
2602 _encodeReturn: function(value, array_as_mv) {
2603 var ret;
2605 if(value === null) {
2606 ret = ['special', 'null'];
2607 } else if(value === true) {
2608 ret = ['special', 'true'];
2609 } else if(value === false) {
2610 ret = ['special', 'false'];
2611 } else if(value === undefined) {
2612 ret = ['special', 'undefined'];
2613 } else if(typeof value === 'number') {
2614 if(isNaN(value)) {
2615 ret = ['special', 'NaN'];
2616 } else if(value === Infinity) {
2617 ret = ['special', 'Infinity'];
2618 } else if(value === -Infinity) {
2619 ret = ['special', '-Infinity'];
2620 } else {
2621 ret = ['atom', value];
2623 } else if(typeof value === 'string') {
2624 ret = ['atom', value];
2625 } else if(array_as_mv && value instanceof Array) {
2626 ret = ['array', value.map(this._encodeReturn, this)];
2627 } else {
2628 ret = ['objid', this._findOrAllocateObject(repl, value)];
2631 return ret;
2634 _handleInputLine: function _handleInputLine(repl, line) {
2635 var ret;
2636 var array_as_mv = false;
2638 try {
2639 if(line[0] === '*') {
2640 array_as_mv = true;
2641 line = line.substring(1);
2643 var parts = eval(line);
2644 this._fixupList(repl, parts);
2645 var [thisobj, func] = this._parseFunc(parts[0]);
2646 ret = this._encodeReturn(
2647 func.apply(thisobj, parts.slice(1)),
2648 array_as_mv);
2649 } catch(x) {
2650 ret = ['error', x.toString() ];
2653 var JSON = Components.classes['@mozilla.org/dom/json;1'].createInstance(Components.interfaces.nsIJSON);
2654 repl.print(JSON.encode(ret));
2655 repl._prompt();
2658 handleInput: function handleInput(repl, chunk) {
2659 this._input += chunk;
2660 var match, line;
2661 while(match = this._input.match(/.*\\n/)) {
2662 line = match[0];
2664 if(line === 'EXIT\\n') {
2665 repl.popInteractor();
2666 repl._prompt();
2667 return;
2670 this._input = this._input.substring(line.length);
2671 this._handleInputLine(repl, line);
2678 "String to set MozRepl up into a simple-minded evaluation mode.")
2680 (defun js--js-encode-value (x)
2681 "Marshall the given value for JS.
2682 Strings and numbers are JSON-encoded. Lists (including nil) are
2683 made into JavaScript array literals and their contents encoded
2684 with `js--js-encode-value'."
2685 (cond ((stringp x) (json-encode-string x))
2686 ((numberp x) (json-encode-number x))
2687 ((symbolp x) (format "{objid:%S}" (symbol-name x)))
2688 ((js--js-handle-p x)
2690 (when (js--js-handle-expired-p x)
2691 (error "Stale JS handle"))
2693 (format "{objid:%s}" (js--js-handle-id x)))
2695 ((sequencep x)
2696 (if (eq (car-safe x) 'js--funcall)
2697 (format "{funcall:[%s]}"
2698 (mapconcat #'js--js-encode-value (cdr x) ","))
2699 (concat
2700 "[" (mapconcat #'js--js-encode-value x ",") "]")))
2702 (error "Unrecognized item: %S" x))))
2704 (defconst js--js-prompt-regexp "\\(repl[0-9]*\\)> $")
2705 (defconst js--js-repl-prompt-regexp "^EVAL>$")
2706 (defvar js--js-repl-depth 0)
2708 (defun js--js-wait-for-eval-prompt ()
2709 (js--wait-for-matching-output
2710 (inferior-moz-process)
2711 js--js-repl-prompt-regexp js-js-timeout
2713 ;; start matching against the beginning of the line in
2714 ;; order to catch a prompt that's only partially arrived
2715 (save-excursion (forward-line 0) (point))))
2717 ;; Presumably "inferior-moz-process" loads comint.
2718 (declare-function comint-send-string "comint" (process string))
2719 (declare-function comint-send-input "comint"
2720 (&optional no-newline artificial))
2722 (defun js--js-enter-repl ()
2723 (inferior-moz-process) ; called for side-effect
2724 (with-current-buffer inferior-moz-buffer
2725 (goto-char (point-max))
2727 ;; Do some initialization the first time we see a process
2728 (unless (eq (inferior-moz-process) js--js-process)
2729 (setq js--js-process (inferior-moz-process))
2730 (setq js--js-references (make-hash-table :test 'eq :weakness t))
2731 (setq js--js-repl-depth 0)
2733 ;; Send interactor definition
2734 (comint-send-string js--js-process js--moz-interactor)
2735 (comint-send-string js--js-process
2736 (concat "(" moz-repl-name ")\n"))
2737 (js--wait-for-matching-output
2738 (inferior-moz-process) js--js-prompt-regexp
2739 js-js-timeout))
2741 ;; Sanity check
2742 (when (looking-back js--js-prompt-regexp
2743 (save-excursion (forward-line 0) (point)))
2744 (setq js--js-repl-depth 0))
2746 (if (> js--js-repl-depth 0)
2747 ;; If js--js-repl-depth > 0, we *should* be seeing an
2748 ;; EVAL> prompt. If we don't, give Mozilla a chance to catch
2749 ;; up with us.
2750 (js--js-wait-for-eval-prompt)
2752 ;; Otherwise, tell Mozilla to enter the interactor mode
2753 (insert (match-string-no-properties 1)
2754 ".pushInteractor('js')")
2755 (comint-send-input nil t)
2756 (js--wait-for-matching-output
2757 (inferior-moz-process) js--js-repl-prompt-regexp
2758 js-js-timeout))
2760 (cl-incf js--js-repl-depth)))
2762 (defun js--js-leave-repl ()
2763 (cl-assert (> js--js-repl-depth 0))
2764 (when (= 0 (cl-decf js--js-repl-depth))
2765 (with-current-buffer inferior-moz-buffer
2766 (goto-char (point-max))
2767 (js--js-wait-for-eval-prompt)
2768 (insert "EXIT")
2769 (comint-send-input nil t)
2770 (js--wait-for-matching-output
2771 (inferior-moz-process) js--js-prompt-regexp
2772 js-js-timeout))))
2774 (defsubst js--js-not (value)
2775 (memq value '(nil null false undefined)))
2777 (defsubst js--js-true (value)
2778 (not (js--js-not value)))
2780 ;; The somewhat complex code layout confuses the byte-compiler into
2781 ;; thinking this function "might not be defined at runtime".
2782 (declare-function js--optimize-arglist "js" (arglist))
2784 (eval-and-compile
2785 (defun js--optimize-arglist (arglist)
2786 "Convert immediate js< and js! references to deferred ones."
2787 (cl-loop for item in arglist
2788 if (eq (car-safe item) 'js<)
2789 collect (append (list 'list ''js--funcall
2790 '(list 'interactor "_getProp"))
2791 (js--optimize-arglist (cdr item)))
2792 else if (eq (car-safe item) 'js>)
2793 collect (append (list 'list ''js--funcall
2794 '(list 'interactor "_putProp"))
2796 (if (atom (cadr item))
2797 (list (cadr item))
2798 (list
2799 (append
2800 (list 'list ''js--funcall
2801 '(list 'interactor "_mkArray"))
2802 (js--optimize-arglist (cadr item)))))
2803 (js--optimize-arglist (cddr item)))
2804 else if (eq (car-safe item) 'js!)
2805 collect (pcase-let ((`(,_ ,function . ,body) item))
2806 (append (list 'list ''js--funcall
2807 (if (consp function)
2808 (cons 'list
2809 (js--optimize-arglist function))
2810 function))
2811 (js--optimize-arglist body)))
2812 else
2813 collect item)))
2815 (defmacro js--js-get-service (class-name interface-name)
2816 `(js! ("Components" "classes" ,class-name "getService")
2817 (js< "Components" "interfaces" ,interface-name)))
2819 (defmacro js--js-create-instance (class-name interface-name)
2820 `(js! ("Components" "classes" ,class-name "createInstance")
2821 (js< "Components" "interfaces" ,interface-name)))
2823 (defmacro js--js-qi (object interface-name)
2824 `(js! (,object "QueryInterface")
2825 (js< "Components" "interfaces" ,interface-name)))
2827 (defmacro with-js (&rest forms)
2828 "Run FORMS with the Mozilla repl set up for js commands.
2829 Inside the lexical scope of `with-js', `js?', `js!',
2830 `js-new', `js-eval', `js-list', `js<', `js>', `js-get-service',
2831 `js-create-instance', and `js-qi' are defined."
2833 `(progn
2834 (js--js-enter-repl)
2835 (unwind-protect
2836 (cl-macrolet ((js? (&rest body) `(js--js-true ,@body))
2837 (js! (function &rest body)
2838 `(js--js-funcall
2839 ,(if (consp function)
2840 (cons 'list
2841 (js--optimize-arglist function))
2842 function)
2843 ,@(js--optimize-arglist body)))
2845 (js-new (function &rest body)
2846 `(js--js-new
2847 ,(if (consp function)
2848 (cons 'list
2849 (js--optimize-arglist function))
2850 function)
2851 ,@body))
2853 (js-eval (thisobj js)
2854 `(js--js-eval
2855 ,@(js--optimize-arglist
2856 (list thisobj js))))
2858 (js-list (&rest args)
2859 `(js--js-list
2860 ,@(js--optimize-arglist args)))
2862 (js-get-service (&rest args)
2863 `(js--js-get-service
2864 ,@(js--optimize-arglist args)))
2866 (js-create-instance (&rest args)
2867 `(js--js-create-instance
2868 ,@(js--optimize-arglist args)))
2870 (js-qi (&rest args)
2871 `(js--js-qi
2872 ,@(js--optimize-arglist args)))
2874 (js< (&rest body) `(js--js-get
2875 ,@(js--optimize-arglist body)))
2876 (js> (props value)
2877 `(js--js-funcall
2878 '(interactor "_putProp")
2879 ,(if (consp props)
2880 (cons 'list
2881 (js--optimize-arglist props))
2882 props)
2883 ,@(js--optimize-arglist (list value))
2885 (js-handle? (arg) `(js--js-handle-p ,arg)))
2886 ,@forms)
2887 (js--js-leave-repl))))
2889 (defvar js--js-array-as-list nil
2890 "Whether to listify any Array returned by a Mozilla function.
2891 If nil, the whole Array is treated as a JS symbol.")
2893 (defun js--js-decode-retval (result)
2894 (pcase (intern (cl-first result))
2895 (`atom (cl-second result))
2896 (`special (intern (cl-second result)))
2897 (`array
2898 (mapcar #'js--js-decode-retval (cl-second result)))
2899 (`objid
2900 (or (gethash (cl-second result)
2901 js--js-references)
2902 (puthash (cl-second result)
2903 (make-js--js-handle
2904 :id (cl-second result)
2905 :process (inferior-moz-process))
2906 js--js-references)))
2908 (`error (signal 'js-js-error (list (cl-second result))))
2909 (x (error "Unmatched case in js--js-decode-retval: %S" x))))
2911 (defvar comint-last-input-end)
2913 (defun js--js-funcall (function &rest arguments)
2914 "Call the Mozilla function FUNCTION with arguments ARGUMENTS.
2915 If function is a string, look it up as a property on the global
2916 object and use the global object for `this'.
2917 If FUNCTION is a list with one element, use that element as the
2918 function with the global object for `this', except that if that
2919 single element is a string, look it up on the global object.
2920 If FUNCTION is a list with more than one argument, use the list
2921 up to the last value as a property descriptor and the last
2922 argument as a function."
2924 (with-js
2925 (let ((argstr (js--js-encode-value
2926 (cons function arguments))))
2928 (with-current-buffer inferior-moz-buffer
2929 ;; Actual funcall
2930 (when js--js-array-as-list
2931 (insert "*"))
2932 (insert argstr)
2933 (comint-send-input nil t)
2934 (js--wait-for-matching-output
2935 (inferior-moz-process) "EVAL>"
2936 js-js-timeout)
2937 (goto-char comint-last-input-end)
2939 ;; Read the result
2940 (let* ((json-array-type 'list)
2941 (result (prog1 (json-read)
2942 (goto-char (point-max)))))
2943 (js--js-decode-retval result))))))
2945 (defun js--js-new (constructor &rest arguments)
2946 "Call CONSTRUCTOR as a constructor, with arguments ARGUMENTS.
2947 CONSTRUCTOR is a JS handle, a string, or a list of these things."
2948 (apply #'js--js-funcall
2949 '(interactor "_callNew")
2950 constructor arguments))
2952 (defun js--js-eval (thisobj js)
2953 (js--js-funcall '(interactor "_callEval") thisobj js))
2955 (defun js--js-list (&rest arguments)
2956 "Return a Lisp array resulting from evaluating each of ARGUMENTS."
2957 (let ((js--js-array-as-list t))
2958 (apply #'js--js-funcall '(interactor "_mkArray")
2959 arguments)))
2961 (defun js--js-get (&rest props)
2962 (apply #'js--js-funcall '(interactor "_getProp") props))
2964 (defun js--js-put (props value)
2965 (js--js-funcall '(interactor "_putProp") props value))
2967 (defun js-gc (&optional force)
2968 "Tell the repl about any objects we don't reference anymore.
2969 With argument, run even if no intervening GC has happened."
2970 (interactive)
2972 (when force
2973 (setq js--js-last-gcs-done nil))
2975 (let ((this-gcs-done gcs-done) keys num)
2976 (when (and js--js-references
2977 (boundp 'inferior-moz-buffer)
2978 (buffer-live-p inferior-moz-buffer)
2980 ;; Don't bother running unless we've had an intervening
2981 ;; garbage collection; without a gc, nothing is deleted
2982 ;; from the weak hash table, so it's pointless telling
2983 ;; MozRepl about that references we still hold
2984 (not (eq js--js-last-gcs-done this-gcs-done))
2986 ;; Are we looking at a normal prompt? Make sure not to
2987 ;; interrupt the user if he's doing something
2988 (with-current-buffer inferior-moz-buffer
2989 (save-excursion
2990 (goto-char (point-max))
2991 (looking-back js--js-prompt-regexp
2992 (save-excursion (forward-line 0) (point))))))
2994 (setq keys (cl-loop for x being the hash-keys
2995 of js--js-references
2996 collect x))
2997 (setq num (js--js-funcall '(repl "_jsGC") (or keys [])))
2999 (setq js--js-last-gcs-done this-gcs-done)
3000 (when (called-interactively-p 'interactive)
3001 (message "Cleaned %s entries" num))
3003 num)))
3005 (run-with-idle-timer 30 t #'js-gc)
3007 (defun js-eval (js)
3008 "Evaluate the JavaScript in JS and return JSON-decoded result."
3009 (interactive "MJavascript to evaluate: ")
3010 (with-js
3011 (let* ((content-window (js--js-content-window
3012 (js--get-js-context)))
3013 (result (js-eval content-window js)))
3014 (when (called-interactively-p 'interactive)
3015 (message "%s" (js! "String" result)))
3016 result)))
3018 (defun js--get-tabs ()
3019 "Enumerate all JavaScript contexts available.
3020 Each context is a list:
3021 (TITLE URL BROWSER TAB TABBROWSER) for content documents
3022 (TITLE URL WINDOW) for windows
3024 All tabs of a given window are grouped together. The most recent
3025 window is first. Within each window, the tabs are returned
3026 left-to-right."
3027 (with-js
3028 (let (windows)
3030 (cl-loop with window-mediator = (js! ("Components" "classes"
3031 "@mozilla.org/appshell/window-mediator;1"
3032 "getService")
3033 (js< "Components" "interfaces"
3034 "nsIWindowMediator"))
3035 with enumerator = (js! (window-mediator "getEnumerator") nil)
3037 while (js? (js! (enumerator "hasMoreElements")))
3038 for window = (js! (enumerator "getNext"))
3039 for window-info = (js-list window
3040 (js< window "document" "title")
3041 (js! (window "location" "toString"))
3042 (js< window "closed")
3043 (js< window "windowState"))
3045 unless (or (js? (cl-fourth window-info))
3046 (eq (cl-fifth window-info) 2))
3047 do (push window-info windows))
3049 (cl-loop for window-info in windows
3050 for window = (cl-first window-info)
3051 collect (list (cl-second window-info)
3052 (cl-third window-info)
3053 window)
3055 for gbrowser = (js< window "gBrowser")
3056 if (js-handle? gbrowser)
3057 nconc (cl-loop
3058 for x below (js< gbrowser "browsers" "length")
3059 collect (js-list (js< gbrowser
3060 "browsers"
3062 "contentDocument"
3063 "title")
3065 (js! (gbrowser
3066 "browsers"
3068 "contentWindow"
3069 "location"
3070 "toString"))
3071 (js< gbrowser
3072 "browsers"
3075 (js! (gbrowser
3076 "tabContainer"
3077 "childNodes"
3078 "item")
3081 gbrowser))))))
3083 (defvar js-read-tab-history nil)
3085 (declare-function ido-chop "ido" (items elem))
3087 (defun js--read-tab (prompt)
3088 "Read a Mozilla tab with prompt PROMPT.
3089 Return a cons of (TYPE . OBJECT). TYPE is either 'window or
3090 'tab, and OBJECT is a JavaScript handle to a ChromeWindow or a
3091 browser, respectively."
3093 ;; Prime IDO
3094 (unless ido-mode
3095 (ido-mode 1)
3096 (ido-mode -1))
3098 (with-js
3099 (let ((tabs (js--get-tabs)) selected-tab-cname
3100 selected-tab prev-hitab)
3102 ;; Disambiguate names
3103 (setq tabs
3104 (cl-loop with tab-names = (make-hash-table :test 'equal)
3105 for tab in tabs
3106 for cname = (format "%s (%s)"
3107 (cl-second tab) (cl-first tab))
3108 for num = (cl-incf (gethash cname tab-names -1))
3109 if (> num 0)
3110 do (setq cname (format "%s <%d>" cname num))
3111 collect (cons cname tab)))
3113 (cl-labels
3114 ((find-tab-by-cname
3115 (cname)
3116 (cl-loop for tab in tabs
3117 if (equal (car tab) cname)
3118 return (cdr tab)))
3120 (mogrify-highlighting
3121 (hitab unhitab)
3123 ;; Hack to reduce the number of
3124 ;; round-trips to mozilla
3125 (let (cmds)
3126 (cond
3127 ;; Highlighting tab
3128 ((cl-fourth hitab)
3129 (push '(js! ((cl-fourth hitab) "setAttribute")
3130 "style"
3131 "color: red; font-weight: bold")
3132 cmds)
3134 ;; Highlight window proper
3135 (push '(js! ((cl-third hitab)
3136 "setAttribute")
3137 "style"
3138 "border: 8px solid red")
3139 cmds)
3141 ;; Select tab, when appropriate
3142 (when js-js-switch-tabs
3143 (push
3144 '(js> ((cl-fifth hitab) "selectedTab") (cl-fourth hitab))
3145 cmds)))
3147 ;; Highlighting whole window
3148 ((cl-third hitab)
3149 (push '(js! ((cl-third hitab) "document"
3150 "documentElement" "setAttribute")
3151 "style"
3152 (concat "-moz-appearance: none;"
3153 "border: 8px solid red;"))
3154 cmds)))
3156 (cond
3157 ;; Unhighlighting tab
3158 ((cl-fourth unhitab)
3159 (push '(js! ((cl-fourth unhitab) "setAttribute") "style" "")
3160 cmds)
3161 (push '(js! ((cl-third unhitab) "setAttribute") "style" "")
3162 cmds))
3164 ;; Unhighlighting window
3165 ((cl-third unhitab)
3166 (push '(js! ((cl-third unhitab) "document"
3167 "documentElement" "setAttribute")
3168 "style" "")
3169 cmds)))
3171 (eval (list 'with-js
3172 (cons 'js-list (nreverse cmds))))))
3174 (command-hook
3176 (let* ((tab (find-tab-by-cname (car ido-matches))))
3177 (mogrify-highlighting tab prev-hitab)
3178 (setq prev-hitab tab)))
3180 (setup-hook
3182 ;; Fiddle with the match list a bit: if our first match
3183 ;; is a tabbrowser window, rotate the match list until
3184 ;; the active tab comes up
3185 (let ((matched-tab (find-tab-by-cname (car ido-matches))))
3186 (when (and matched-tab
3187 (null (cl-fourth matched-tab))
3188 (equal "navigator:browser"
3189 (js! ((cl-third matched-tab)
3190 "document"
3191 "documentElement"
3192 "getAttribute")
3193 "windowtype")))
3195 (cl-loop with tab-to-match = (js< (cl-third matched-tab)
3196 "gBrowser"
3197 "selectedTab")
3199 for match in ido-matches
3200 for candidate-tab = (find-tab-by-cname match)
3201 if (eq (cl-fourth candidate-tab) tab-to-match)
3202 do (setq ido-cur-list
3203 (ido-chop ido-cur-list match))
3204 and return t)))
3206 (add-hook 'post-command-hook #'command-hook t t)))
3209 (unwind-protect
3210 (setq selected-tab-cname
3211 (let ((ido-minibuffer-setup-hook
3212 (cons #'setup-hook ido-minibuffer-setup-hook)))
3213 (ido-completing-read
3214 prompt
3215 (mapcar #'car tabs)
3216 nil t nil
3217 'js-read-tab-history)))
3219 (when prev-hitab
3220 (mogrify-highlighting nil prev-hitab)
3221 (setq prev-hitab nil)))
3223 (add-to-history 'js-read-tab-history selected-tab-cname)
3225 (setq selected-tab (cl-loop for tab in tabs
3226 if (equal (car tab) selected-tab-cname)
3227 return (cdr tab)))
3229 (cons (if (cl-fourth selected-tab) 'browser 'window)
3230 (cl-third selected-tab))))))
3232 (defun js--guess-eval-defun-info (pstate)
3233 "Helper function for `js-eval-defun'.
3234 Return a list (NAME . CLASSPARTS), where CLASSPARTS is a list of
3235 strings making up the class name and NAME is the name of the
3236 function part."
3237 (cond ((and (= (length pstate) 3)
3238 (eq (js--pitem-type (cl-first pstate)) 'function)
3239 (= (length (js--pitem-name (cl-first pstate))) 1)
3240 (consp (js--pitem-type (cl-second pstate))))
3242 (append (js--pitem-name (cl-second pstate))
3243 (list (cl-first (js--pitem-name (cl-first pstate))))))
3245 ((and (= (length pstate) 2)
3246 (eq (js--pitem-type (cl-first pstate)) 'function))
3248 (append
3249 (butlast (js--pitem-name (cl-first pstate)))
3250 (list (car (last (js--pitem-name (cl-first pstate)))))))
3252 (t (error "Function not a toplevel defun or class member"))))
3254 (defvar js--js-context nil
3255 "The current JavaScript context.
3256 This is a cons like the one returned from `js--read-tab'.
3257 Change with `js-set-js-context'.")
3259 (defconst js--js-inserter
3260 "(function(func_info,func) {
3261 func_info.unshift('window');
3262 var obj = window;
3263 for(var i = 1; i < func_info.length - 1; ++i) {
3264 var next = obj[func_info[i]];
3265 if(typeof next !== 'object' && typeof next !== 'function') {
3266 next = obj.prototype && obj.prototype[func_info[i]];
3267 if(typeof next !== 'object' && typeof next !== 'function') {
3268 alert('Could not find ' + func_info.slice(0, i+1).join('.') +
3269 ' or ' + func_info.slice(0, i+1).join('.') + '.prototype');
3270 return;
3273 func_info.splice(i+1, 0, 'prototype');
3274 ++i;
3278 obj[func_info[i]] = func;
3279 alert('Successfully updated '+func_info.join('.'));
3280 })")
3282 (defun js-set-js-context (context)
3283 "Set the JavaScript context to CONTEXT.
3284 When called interactively, prompt for CONTEXT."
3285 (interactive (list (js--read-tab "Javascript Context: ")))
3286 (setq js--js-context context))
3288 (defun js--get-js-context ()
3289 "Return a valid JavaScript context.
3290 If one hasn't been set, or if it's stale, prompt for a new one."
3291 (with-js
3292 (when (or (null js--js-context)
3293 (js--js-handle-expired-p (cdr js--js-context))
3294 (pcase (car js--js-context)
3295 (`window (js? (js< (cdr js--js-context) "closed")))
3296 (`browser (not (js? (js< (cdr js--js-context)
3297 "contentDocument"))))
3298 (x (error "Unmatched case in js--get-js-context: %S" x))))
3299 (setq js--js-context (js--read-tab "Javascript Context: ")))
3300 js--js-context))
3302 (defun js--js-content-window (context)
3303 (with-js
3304 (pcase (car context)
3305 (`window (cdr context))
3306 (`browser (js< (cdr context)
3307 "contentWindow" "wrappedJSObject"))
3308 (x (error "Unmatched case in js--js-content-window: %S" x)))))
3310 (defun js--make-nsilocalfile (path)
3311 (with-js
3312 (let ((file (js-create-instance "@mozilla.org/file/local;1"
3313 "nsILocalFile")))
3314 (js! (file "initWithPath") path)
3315 file)))
3317 (defun js--js-add-resource-alias (alias path)
3318 (with-js
3319 (let* ((io-service (js-get-service "@mozilla.org/network/io-service;1"
3320 "nsIIOService"))
3321 (res-prot (js! (io-service "getProtocolHandler") "resource"))
3322 (res-prot (js-qi res-prot "nsIResProtocolHandler"))
3323 (path-file (js--make-nsilocalfile path))
3324 (path-uri (js! (io-service "newFileURI") path-file)))
3325 (js! (res-prot "setSubstitution") alias path-uri))))
3327 (cl-defun js-eval-defun ()
3328 "Update a Mozilla tab using the JavaScript defun at point."
3329 (interactive)
3331 ;; This function works by generating a temporary file that contains
3332 ;; the function we'd like to insert. We then use the elisp-js bridge
3333 ;; to command mozilla to load this file by inserting a script tag
3334 ;; into the document we set. This way, debuggers and such will have
3335 ;; a way to find the source of the just-inserted function.
3337 ;; We delete the temporary file if there's an error, but otherwise
3338 ;; we add an unload event listener on the Mozilla side to delete the
3339 ;; file.
3341 (save-excursion
3342 (let (begin end pstate defun-info temp-name defun-body)
3343 (js-end-of-defun)
3344 (setq end (point))
3345 (js--ensure-cache)
3346 (js-beginning-of-defun)
3347 (re-search-forward "\\_<function\\_>")
3348 (setq begin (match-beginning 0))
3349 (setq pstate (js--forward-pstate))
3351 (when (or (null pstate)
3352 (> (point) end))
3353 (error "Could not locate function definition"))
3355 (setq defun-info (js--guess-eval-defun-info pstate))
3357 (let ((overlay (make-overlay begin end)))
3358 (overlay-put overlay 'face 'highlight)
3359 (unwind-protect
3360 (unless (y-or-n-p (format "Send %s to Mozilla? "
3361 (mapconcat #'identity defun-info ".")))
3362 (message "") ; question message lingers until next command
3363 (cl-return-from js-eval-defun))
3364 (delete-overlay overlay)))
3366 (setq defun-body (buffer-substring-no-properties begin end))
3368 (make-directory js-js-tmpdir t)
3370 ;; (Re)register a Mozilla resource URL to point to the
3371 ;; temporary directory
3372 (js--js-add-resource-alias "js" js-js-tmpdir)
3374 (setq temp-name (make-temp-file (concat js-js-tmpdir
3375 "/js-")
3376 nil ".js"))
3377 (unwind-protect
3378 (with-js
3379 (with-temp-buffer
3380 (insert js--js-inserter)
3381 (insert "(")
3382 (insert (json-encode-list defun-info))
3383 (insert ",\n")
3384 (insert defun-body)
3385 (insert "\n)")
3386 (write-region (point-min) (point-max) temp-name
3387 nil 1))
3389 ;; Give Mozilla responsibility for deleting this file
3390 (let* ((content-window (js--js-content-window
3391 (js--get-js-context)))
3392 (content-document (js< content-window "document"))
3393 (head (if (js? (js< content-document "body"))
3394 ;; Regular content
3395 (js< (js! (content-document "getElementsByTagName")
3396 "head")
3398 ;; Chrome
3399 (js< content-document "documentElement")))
3400 (elem (js! (content-document "createElementNS")
3401 "http://www.w3.org/1999/xhtml" "script")))
3403 (js! (elem "setAttribute") "type" "text/javascript")
3404 (js! (elem "setAttribute") "src"
3405 (format "resource://js/%s"
3406 (file-name-nondirectory temp-name)))
3408 (js! (head "appendChild") elem)
3410 (js! (content-window "addEventListener") "unload"
3411 (js! ((js-new
3412 "Function" "file"
3413 "return function() { file.remove(false) }"))
3414 (js--make-nsilocalfile temp-name))
3415 'false)
3416 (setq temp-name nil)
3422 ;; temp-name is set to nil on success
3423 (when temp-name
3424 (delete-file temp-name))))))
3426 ;;; Main Function
3428 ;;;###autoload
3429 (define-derived-mode js-mode prog-mode "Javascript"
3430 "Major mode for editing JavaScript."
3431 :group 'js
3432 (setq-local indent-line-function 'js-indent-line)
3433 (setq-local beginning-of-defun-function 'js-beginning-of-defun)
3434 (setq-local end-of-defun-function 'js-end-of-defun)
3435 (setq-local open-paren-in-column-0-is-defun-start nil)
3436 (setq-local font-lock-defaults (list js--font-lock-keywords))
3437 (setq-local syntax-propertize-function #'js-syntax-propertize)
3439 (setq-local parse-sexp-ignore-comments t)
3440 (setq-local parse-sexp-lookup-properties t)
3441 (setq-local which-func-imenu-joiner-function #'js--which-func-joiner)
3443 ;; Comments
3444 (setq-local comment-start "// ")
3445 (setq-local comment-end "")
3446 (setq-local fill-paragraph-function 'js-c-fill-paragraph)
3448 ;; Parse cache
3449 (add-hook 'before-change-functions #'js--flush-caches t t)
3451 ;; Frameworks
3452 (js--update-quick-match-re)
3454 ;; Imenu
3455 (setq imenu-case-fold-search nil)
3456 (setq imenu-create-index-function #'js--imenu-create-index)
3458 ;; for filling, pretend we're cc-mode
3459 (setq c-comment-prefix-regexp "//+\\|\\**"
3460 c-paragraph-start "$"
3461 c-paragraph-separate "$"
3462 c-block-comment-prefix "* "
3463 c-line-comment-starter "//"
3464 c-comment-start-regexp "/[*/]\\|\\s!"
3465 comment-start-skip "\\(//+\\|/\\*+\\)\\s *")
3467 (setq-local electric-indent-chars
3468 (append "{}():;," electric-indent-chars)) ;FIXME: js2-mode adds "[]*".
3469 (setq-local electric-layout-rules
3470 '((?\; . after) (?\{ . after) (?\} . before)))
3472 (let ((c-buffer-is-cc-mode t))
3473 ;; FIXME: These are normally set by `c-basic-common-init'. Should
3474 ;; we call it instead? (Bug#6071)
3475 (make-local-variable 'paragraph-start)
3476 (make-local-variable 'paragraph-separate)
3477 (make-local-variable 'paragraph-ignore-fill-prefix)
3478 (make-local-variable 'adaptive-fill-mode)
3479 (make-local-variable 'adaptive-fill-regexp)
3480 (c-setup-paragraph-variables))
3482 (setq-local syntax-begin-function #'js--syntax-begin-function)
3484 ;; Important to fontify the whole buffer syntactically! If we don't,
3485 ;; then we might have regular expression literals that aren't marked
3486 ;; as strings, which will screw up parse-partial-sexp, scan-lists,
3487 ;; etc. and produce maddening "unbalanced parenthesis" errors.
3488 ;; When we attempt to find the error and scroll to the portion of
3489 ;; the buffer containing the problem, JIT-lock will apply the
3490 ;; correct syntax to the regular expression literal and the problem
3491 ;; will mysteriously disappear.
3492 ;; FIXME: We should actually do this fontification lazily by adding
3493 ;; calls to syntax-propertize wherever it's really needed.
3494 (syntax-propertize (point-max)))
3496 ;;;###autoload (defalias 'javascript-mode 'js-mode)
3498 (eval-after-load 'folding
3499 '(when (fboundp 'folding-add-to-marks-list)
3500 (folding-add-to-marks-list 'js-mode "// {{{" "// }}}" )))
3502 ;;;###autoload
3503 (dolist (name (list "node" "nodejs" "gjs" "rhino"))
3504 (add-to-list 'interpreter-mode-alist (cons (purecopy name) 'js-mode)))
3506 (provide 'js)
3508 ;; js.el ends here