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