├── .emacs
├── .gitignore
├── LICENSE
├── README.md
├── alternate_styles.js
├── compat.md
├── js.el
└── js2.el
/.emacs:
--------------------------------------------------------------------------------
1 | (autoload 'js-mode "js")
2 | (defun my-js2-indent-function ()
3 | (interactive)
4 | (save-restriction
5 | (widen)
6 | (let* ((inhibit-point-motion-hooks t)
7 | (parse-status (save-excursion (syntax-ppss (point-at-bol))))
8 | (offset (- (current-column) (current-indentation)))
9 | (indentation (js--proper-indentation parse-status))
10 | node)
11 |
12 | (indent-line-to indentation)
13 | (when (> offset 0) (forward-char offset)))))
14 |
15 | (defun my-js2-mode-hook ()
16 | (require 'js)
17 | (setq js-indent-level 2
18 | indent-tabs-mode nil
19 | c-basic-offset 2
20 | show-paren-mode t)
21 | (c-toggle-auto-state 0)
22 | (c-toggle-hungry-state 1)
23 | (set (make-local-variable 'indent-line-function) 'my-js2-indent-function)
24 | (if (featurep 'js2-highlight-vars)
25 | (js2-highlight-vars-mode))
26 | (message "My JS2 hook"))
27 |
28 | (add-hook 'js2-mode-hook 'my-js2-mode-hook)
29 | (autoload 'js2-mode "js2" nil t)
30 | (add-to-list 'auto-mode-alist '("\\.js$" . js2-mode))
31 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.elc
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 | Preamble
9 |
10 | The GNU General Public License is a free, copyleft license for
11 | software and other kinds of works.
12 |
13 | The licenses for most software and other practical works are designed
14 | to take away your freedom to share and change the works. By contrast,
15 | the GNU General Public License is intended to guarantee your freedom to
16 | share and change all versions of a program--to make sure it remains free
17 | software for all its users. We, the Free Software Foundation, use the
18 | GNU General Public License for most of our software; it applies also to
19 | any other work released this way by its authors. You can apply it to
20 | your programs, too.
21 |
22 | When we speak of free software, we are referring to freedom, not
23 | price. Our General Public Licenses are designed to make sure that you
24 | have the freedom to distribute copies of free software (and charge for
25 | them if you wish), that you receive source code or can get it if you
26 | want it, that you can change the software or use pieces of it in new
27 | free programs, and that you know you can do these things.
28 |
29 | To protect your rights, we need to prevent others from denying you
30 | these rights or asking you to surrender the rights. Therefore, you have
31 | certain responsibilities if you distribute copies of the software, or if
32 | you modify it: responsibilities to respect the freedom of others.
33 |
34 | For example, if you distribute copies of such a program, whether
35 | gratis or for a fee, you must pass on to the recipients the same
36 | freedoms that you received. You must make sure that they, too, receive
37 | or can get the source code. And you must show them these terms so they
38 | know their rights.
39 |
40 | Developers that use the GNU GPL protect your rights with two steps:
41 | (1) assert copyright on the software, and (2) offer you this License
42 | giving you legal permission to copy, distribute and/or modify it.
43 |
44 | For the developers' and authors' protection, the GPL clearly explains
45 | that there is no warranty for this free software. For both users' and
46 | authors' sake, the GPL requires that modified versions be marked as
47 | changed, so that their problems will not be attributed erroneously to
48 | authors of previous versions.
49 |
50 | Some devices are designed to deny users access to install or run
51 | modified versions of the software inside them, although the manufacturer
52 | can do so. This is fundamentally incompatible with the aim of
53 | protecting users' freedom to change the software. The systematic
54 | pattern of such abuse occurs in the area of products for individuals to
55 | use, which is precisely where it is most unacceptable. Therefore, we
56 | have designed this version of the GPL to prohibit the practice for those
57 | products. If such problems arise substantially in other domains, we
58 | stand ready to extend this provision to those domains in future versions
59 | of the GPL, as needed to protect the freedom of users.
60 |
61 | Finally, every program is threatened constantly by software patents.
62 | States should not allow patents to restrict development and use of
63 | software on general-purpose computers, but in those that do, we wish to
64 | avoid the special danger that patents applied to a free program could
65 | make it effectively proprietary. To prevent this, the GPL assures that
66 | patents cannot be used to render the program non-free.
67 |
68 | The precise terms and conditions for copying, distribution and
69 | modification follow.
70 |
71 | TERMS AND CONDITIONS
72 |
73 | 0. Definitions.
74 |
75 | "This License" refers to version 3 of the GNU General Public License.
76 |
77 | "Copyright" also means copyright-like laws that apply to other kinds of
78 | works, such as semiconductor masks.
79 |
80 | "The Program" refers to any copyrightable work licensed under this
81 | License. Each licensee is addressed as "you". "Licensees" and
82 | "recipients" may be individuals or organizations.
83 |
84 | To "modify" a work means to copy from or adapt all or part of the work
85 | in a fashion requiring copyright permission, other than the making of an
86 | exact copy. The resulting work is called a "modified version" of the
87 | earlier work or a work "based on" the earlier work.
88 |
89 | A "covered work" means either the unmodified Program or a work based
90 | on the Program.
91 |
92 | To "propagate" a work means to do anything with it that, without
93 | permission, would make you directly or secondarily liable for
94 | infringement under applicable copyright law, except executing it on a
95 | computer or modifying a private copy. Propagation includes copying,
96 | distribution (with or without modification), making available to the
97 | public, and in some countries other activities as well.
98 |
99 | To "convey" a work means any kind of propagation that enables other
100 | parties to make or receive copies. Mere interaction with a user through
101 | a computer network, with no transfer of a copy, is not conveying.
102 |
103 | An interactive user interface displays "Appropriate Legal Notices"
104 | to the extent that it includes a convenient and prominently visible
105 | feature that (1) displays an appropriate copyright notice, and (2)
106 | tells the user that there is no warranty for the work (except to the
107 | extent that warranties are provided), that licensees may convey the
108 | work under this License, and how to view a copy of this License. If
109 | the interface presents a list of user commands or options, such as a
110 | menu, a prominent item in the list meets this criterion.
111 |
112 | 1. Source Code.
113 |
114 | The "source code" for a work means the preferred form of the work
115 | for making modifications to it. "Object code" means any non-source
116 | form of a work.
117 |
118 | A "Standard Interface" means an interface that either is an official
119 | standard defined by a recognized standards body, or, in the case of
120 | interfaces specified for a particular programming language, one that
121 | is widely used among developers working in that language.
122 |
123 | The "System Libraries" of an executable work include anything, other
124 | than the work as a whole, that (a) is included in the normal form of
125 | packaging a Major Component, but which is not part of that Major
126 | Component, and (b) serves only to enable use of the work with that
127 | Major Component, or to implement a Standard Interface for which an
128 | implementation is available to the public in source code form. A
129 | "Major Component", in this context, means a major essential component
130 | (kernel, window system, and so on) of the specific operating system
131 | (if any) on which the executable work runs, or a compiler used to
132 | produce the work, or an object code interpreter used to run it.
133 |
134 | The "Corresponding Source" for a work in object code form means all
135 | the source code needed to generate, install, and (for an executable
136 | work) run the object code and to modify the work, including scripts to
137 | control those activities. However, it does not include the work's
138 | System Libraries, or general-purpose tools or generally available free
139 | programs which are used unmodified in performing those activities but
140 | which are not part of the work. For example, Corresponding Source
141 | includes interface definition files associated with source files for
142 | the work, and the source code for shared libraries and dynamically
143 | linked subprograms that the work is specifically designed to require,
144 | such as by intimate data communication or control flow between those
145 | subprograms and other parts of the work.
146 |
147 | The Corresponding Source need not include anything that users
148 | can regenerate automatically from other parts of the Corresponding
149 | Source.
150 |
151 | The Corresponding Source for a work in source code form is that
152 | same work.
153 |
154 | 2. Basic Permissions.
155 |
156 | All rights granted under this License are granted for the term of
157 | copyright on the Program, and are irrevocable provided the stated
158 | conditions are met. This License explicitly affirms your unlimited
159 | permission to run the unmodified Program. The output from running a
160 | covered work is covered by this License only if the output, given its
161 | content, constitutes a covered work. This License acknowledges your
162 | rights of fair use or other equivalent, as provided by copyright law.
163 |
164 | You may make, run and propagate covered works that you do not
165 | convey, without conditions so long as your license otherwise remains
166 | in force. You may convey covered works to others for the sole purpose
167 | of having them make modifications exclusively for you, or provide you
168 | with facilities for running those works, provided that you comply with
169 | the terms of this License in conveying all material for which you do
170 | not control copyright. Those thus making or running the covered works
171 | for you must do so exclusively on your behalf, under your direction
172 | and control, on terms that prohibit them from making any copies of
173 | your copyrighted material outside their relationship with you.
174 |
175 | Conveying under any other circumstances is permitted solely under
176 | the conditions stated below. Sublicensing is not allowed; section 10
177 | makes it unnecessary.
178 |
179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
180 |
181 | No covered work shall be deemed part of an effective technological
182 | measure under any applicable law fulfilling obligations under article
183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or
184 | similar laws prohibiting or restricting circumvention of such
185 | measures.
186 |
187 | When you convey a covered work, you waive any legal power to forbid
188 | circumvention of technological measures to the extent such circumvention
189 | is effected by exercising rights under this License with respect to
190 | the covered work, and you disclaim any intention to limit operation or
191 | modification of the work as a means of enforcing, against the work's
192 | users, your or third parties' legal rights to forbid circumvention of
193 | technological measures.
194 |
195 | 4. Conveying Verbatim Copies.
196 |
197 | You may convey verbatim copies of the Program's source code as you
198 | receive it, in any medium, provided that you conspicuously and
199 | appropriately publish on each copy an appropriate copyright notice;
200 | keep intact all notices stating that this License and any
201 | non-permissive terms added in accord with section 7 apply to the code;
202 | keep intact all notices of the absence of any warranty; and give all
203 | recipients a copy of this License along with the Program.
204 |
205 | You may charge any price or no price for each copy that you convey,
206 | and you may offer support or warranty protection for a fee.
207 |
208 | 5. Conveying Modified Source Versions.
209 |
210 | You may convey a work based on the Program, or the modifications to
211 | produce it from the Program, in the form of source code under the
212 | terms of section 4, provided that you also meet all of these conditions:
213 |
214 | a) The work must carry prominent notices stating that you modified
215 | it, and giving a relevant date.
216 |
217 | b) The work must carry prominent notices stating that it is
218 | released under this License and any conditions added under section
219 | 7. This requirement modifies the requirement in section 4 to
220 | "keep intact all notices".
221 |
222 | c) You must license the entire work, as a whole, under this
223 | License to anyone who comes into possession of a copy. This
224 | License will therefore apply, along with any applicable section 7
225 | additional terms, to the whole of the work, and all its parts,
226 | regardless of how they are packaged. This License gives no
227 | permission to license the work in any other way, but it does not
228 | invalidate such permission if you have separately received it.
229 |
230 | d) If the work has interactive user interfaces, each must display
231 | Appropriate Legal Notices; however, if the Program has interactive
232 | interfaces that do not display Appropriate Legal Notices, your
233 | work need not make them do so.
234 |
235 | A compilation of a covered work with other separate and independent
236 | works, which are not by their nature extensions of the covered work,
237 | and which are not combined with it such as to form a larger program,
238 | in or on a volume of a storage or distribution medium, is called an
239 | "aggregate" if the compilation and its resulting copyright are not
240 | used to limit the access or legal rights of the compilation's users
241 | beyond what the individual works permit. Inclusion of a covered work
242 | in an aggregate does not cause this License to apply to the other
243 | parts of the aggregate.
244 |
245 | 6. Conveying Non-Source Forms.
246 |
247 | You may convey a covered work in object code form under the terms
248 | of sections 4 and 5, provided that you also convey the
249 | machine-readable Corresponding Source under the terms of this License,
250 | in one of these ways:
251 |
252 | a) Convey the object code in, or embodied in, a physical product
253 | (including a physical distribution medium), accompanied by the
254 | Corresponding Source fixed on a durable physical medium
255 | customarily used for software interchange.
256 |
257 | b) Convey the object code in, or embodied in, a physical product
258 | (including a physical distribution medium), accompanied by a
259 | written offer, valid for at least three years and valid for as
260 | long as you offer spare parts or customer support for that product
261 | model, to give anyone who possesses the object code either (1) a
262 | copy of the Corresponding Source for all the software in the
263 | product that is covered by this License, on a durable physical
264 | medium customarily used for software interchange, for a price no
265 | more than your reasonable cost of physically performing this
266 | conveying of source, or (2) access to copy the
267 | Corresponding Source from a network server at no charge.
268 |
269 | c) Convey individual copies of the object code with a copy of the
270 | written offer to provide the Corresponding Source. This
271 | alternative is allowed only occasionally and noncommercially, and
272 | only if you received the object code with such an offer, in accord
273 | with subsection 6b.
274 |
275 | d) Convey the object code by offering access from a designated
276 | place (gratis or for a charge), and offer equivalent access to the
277 | Corresponding Source in the same way through the same place at no
278 | further charge. You need not require recipients to copy the
279 | Corresponding Source along with the object code. If the place to
280 | copy the object code is a network server, the Corresponding Source
281 | may be on a different server (operated by you or a third party)
282 | that supports equivalent copying facilities, provided you maintain
283 | clear directions next to the object code saying where to find the
284 | Corresponding Source. Regardless of what server hosts the
285 | Corresponding Source, you remain obligated to ensure that it is
286 | available for as long as needed to satisfy these requirements.
287 |
288 | e) Convey the object code using peer-to-peer transmission, provided
289 | you inform other peers where the object code and Corresponding
290 | Source of the work are being offered to the general public at no
291 | charge under subsection 6d.
292 |
293 | A separable portion of the object code, whose source code is excluded
294 | from the Corresponding Source as a System Library, need not be
295 | included in conveying the object code work.
296 |
297 | A "User Product" is either (1) a "consumer product", which means any
298 | tangible personal property which is normally used for personal, family,
299 | or household purposes, or (2) anything designed or sold for incorporation
300 | into a dwelling. In determining whether a product is a consumer product,
301 | doubtful cases shall be resolved in favor of coverage. For a particular
302 | product received by a particular user, "normally used" refers to a
303 | typical or common use of that class of product, regardless of the status
304 | of the particular user or of the way in which the particular user
305 | actually uses, or expects or is expected to use, the product. A product
306 | is a consumer product regardless of whether the product has substantial
307 | commercial, industrial or non-consumer uses, unless such uses represent
308 | the only significant mode of use of the product.
309 |
310 | "Installation Information" for a User Product means any methods,
311 | procedures, authorization keys, or other information required to install
312 | and execute modified versions of a covered work in that User Product from
313 | a modified version of its Corresponding Source. The information must
314 | suffice to ensure that the continued functioning of the modified object
315 | code is in no case prevented or interfered with solely because
316 | modification has been made.
317 |
318 | If you convey an object code work under this section in, or with, or
319 | specifically for use in, a User Product, and the conveying occurs as
320 | part of a transaction in which the right of possession and use of the
321 | User Product is transferred to the recipient in perpetuity or for a
322 | fixed term (regardless of how the transaction is characterized), the
323 | Corresponding Source conveyed under this section must be accompanied
324 | by the Installation Information. But this requirement does not apply
325 | if neither you nor any third party retains the ability to install
326 | modified object code on the User Product (for example, the work has
327 | been installed in ROM).
328 |
329 | The requirement to provide Installation Information does not include a
330 | requirement to continue to provide support service, warranty, or updates
331 | for a work that has been modified or installed by the recipient, or for
332 | the User Product in which it has been modified or installed. Access to a
333 | network may be denied when the modification itself materially and
334 | adversely affects the operation of the network or violates the rules and
335 | protocols for communication across the network.
336 |
337 | Corresponding Source conveyed, and Installation Information provided,
338 | in accord with this section must be in a format that is publicly
339 | documented (and with an implementation available to the public in
340 | source code form), and must require no special password or key for
341 | unpacking, reading or copying.
342 |
343 | 7. Additional Terms.
344 |
345 | "Additional permissions" are terms that supplement the terms of this
346 | License by making exceptions from one or more of its conditions.
347 | Additional permissions that are applicable to the entire Program shall
348 | be treated as though they were included in this License, to the extent
349 | that they are valid under applicable law. If additional permissions
350 | apply only to part of the Program, that part may be used separately
351 | under those permissions, but the entire Program remains governed by
352 | this License without regard to the additional permissions.
353 |
354 | When you convey a copy of a covered work, you may at your option
355 | remove any additional permissions from that copy, or from any part of
356 | it. (Additional permissions may be written to require their own
357 | removal in certain cases when you modify the work.) You may place
358 | additional permissions on material, added by you to a covered work,
359 | for which you have or can give appropriate copyright permission.
360 |
361 | Notwithstanding any other provision of this License, for material you
362 | add to a covered work, you may (if authorized by the copyright holders of
363 | that material) supplement the terms of this License with terms:
364 |
365 | a) Disclaiming warranty or limiting liability differently from the
366 | terms of sections 15 and 16 of this License; or
367 |
368 | b) Requiring preservation of specified reasonable legal notices or
369 | author attributions in that material or in the Appropriate Legal
370 | Notices displayed by works containing it; or
371 |
372 | c) Prohibiting misrepresentation of the origin of that material, or
373 | requiring that modified versions of such material be marked in
374 | reasonable ways as different from the original version; or
375 |
376 | d) Limiting the use for publicity purposes of names of licensors or
377 | authors of the material; or
378 |
379 | e) Declining to grant rights under trademark law for use of some
380 | trade names, trademarks, or service marks; or
381 |
382 | f) Requiring indemnification of licensors and authors of that
383 | material by anyone who conveys the material (or modified versions of
384 | it) with contractual assumptions of liability to the recipient, for
385 | any liability that these contractual assumptions directly impose on
386 | those licensors and authors.
387 |
388 | All other non-permissive additional terms are considered "further
389 | restrictions" within the meaning of section 10. If the Program as you
390 | received it, or any part of it, contains a notice stating that it is
391 | governed by this License along with a term that is a further
392 | restriction, you may remove that term. If a license document contains
393 | a further restriction but permits relicensing or conveying under this
394 | License, you may add to a covered work material governed by the terms
395 | of that license document, provided that the further restriction does
396 | not survive such relicensing or conveying.
397 |
398 | If you add terms to a covered work in accord with this section, you
399 | must place, in the relevant source files, a statement of the
400 | additional terms that apply to those files, or a notice indicating
401 | where to find the applicable terms.
402 |
403 | Additional terms, permissive or non-permissive, may be stated in the
404 | form of a separately written license, or stated as exceptions;
405 | the above requirements apply either way.
406 |
407 | 8. Termination.
408 |
409 | You may not propagate or modify a covered work except as expressly
410 | provided under this License. Any attempt otherwise to propagate or
411 | modify it is void, and will automatically terminate your rights under
412 | this License (including any patent licenses granted under the third
413 | paragraph of section 11).
414 |
415 | However, if you cease all violation of this License, then your
416 | license from a particular copyright holder is reinstated (a)
417 | provisionally, unless and until the copyright holder explicitly and
418 | finally terminates your license, and (b) permanently, if the copyright
419 | holder fails to notify you of the violation by some reasonable means
420 | prior to 60 days after the cessation.
421 |
422 | Moreover, your license from a particular copyright holder is
423 | reinstated permanently if the copyright holder notifies you of the
424 | violation by some reasonable means, this is the first time you have
425 | received notice of violation of this License (for any work) from that
426 | copyright holder, and you cure the violation prior to 30 days after
427 | your receipt of the notice.
428 |
429 | Termination of your rights under this section does not terminate the
430 | licenses of parties who have received copies or rights from you under
431 | this License. If your rights have been terminated and not permanently
432 | reinstated, you do not qualify to receive new licenses for the same
433 | material under section 10.
434 |
435 | 9. Acceptance Not Required for Having Copies.
436 |
437 | You are not required to accept this License in order to receive or
438 | run a copy of the Program. Ancillary propagation of a covered work
439 | occurring solely as a consequence of using peer-to-peer transmission
440 | to receive a copy likewise does not require acceptance. However,
441 | nothing other than this License grants you permission to propagate or
442 | modify any covered work. These actions infringe copyright if you do
443 | not accept this License. Therefore, by modifying or propagating a
444 | covered work, you indicate your acceptance of this License to do so.
445 |
446 | 10. Automatic Licensing of Downstream Recipients.
447 |
448 | Each time you convey a covered work, the recipient automatically
449 | receives a license from the original licensors, to run, modify and
450 | propagate that work, subject to this License. You are not responsible
451 | for enforcing compliance by third parties with this License.
452 |
453 | An "entity transaction" is a transaction transferring control of an
454 | organization, or substantially all assets of one, or subdividing an
455 | organization, or merging organizations. If propagation of a covered
456 | work results from an entity transaction, each party to that
457 | transaction who receives a copy of the work also receives whatever
458 | licenses to the work the party's predecessor in interest had or could
459 | give under the previous paragraph, plus a right to possession of the
460 | Corresponding Source of the work from the predecessor in interest, if
461 | the predecessor has it or can get it with reasonable efforts.
462 |
463 | You may not impose any further restrictions on the exercise of the
464 | rights granted or affirmed under this License. For example, you may
465 | not impose a license fee, royalty, or other charge for exercise of
466 | rights granted under this License, and you may not initiate litigation
467 | (including a cross-claim or counterclaim in a lawsuit) alleging that
468 | any patent claim is infringed by making, using, selling, offering for
469 | sale, or importing the Program or any portion of it.
470 |
471 | 11. Patents.
472 |
473 | A "contributor" is a copyright holder who authorizes use under this
474 | License of the Program or a work on which the Program is based. The
475 | work thus licensed is called the contributor's "contributor version".
476 |
477 | A contributor's "essential patent claims" are all patent claims
478 | owned or controlled by the contributor, whether already acquired or
479 | hereafter acquired, that would be infringed by some manner, permitted
480 | by this License, of making, using, or selling its contributor version,
481 | but do not include claims that would be infringed only as a
482 | consequence of further modification of the contributor version. For
483 | purposes of this definition, "control" includes the right to grant
484 | patent sublicenses in a manner consistent with the requirements of
485 | this License.
486 |
487 | Each contributor grants you a non-exclusive, worldwide, royalty-free
488 | patent license under the contributor's essential patent claims, to
489 | make, use, sell, offer for sale, import and otherwise run, modify and
490 | propagate the contents of its contributor version.
491 |
492 | In the following three paragraphs, a "patent license" is any express
493 | agreement or commitment, however denominated, not to enforce a patent
494 | (such as an express permission to practice a patent or covenant not to
495 | sue for patent infringement). To "grant" such a patent license to a
496 | party means to make such an agreement or commitment not to enforce a
497 | patent against the party.
498 |
499 | If you convey a covered work, knowingly relying on a patent license,
500 | and the Corresponding Source of the work is not available for anyone
501 | to copy, free of charge and under the terms of this License, through a
502 | publicly available network server or other readily accessible means,
503 | then you must either (1) cause the Corresponding Source to be so
504 | available, or (2) arrange to deprive yourself of the benefit of the
505 | patent license for this particular work, or (3) arrange, in a manner
506 | consistent with the requirements of this License, to extend the patent
507 | license to downstream recipients. "Knowingly relying" means you have
508 | actual knowledge that, but for the patent license, your conveying the
509 | covered work in a country, or your recipient's use of the covered work
510 | in a country, would infringe one or more identifiable patents in that
511 | country that you have reason to believe are valid.
512 |
513 | If, pursuant to or in connection with a single transaction or
514 | arrangement, you convey, or propagate by procuring conveyance of, a
515 | covered work, and grant a patent license to some of the parties
516 | receiving the covered work authorizing them to use, propagate, modify
517 | or convey a specific copy of the covered work, then the patent license
518 | you grant is automatically extended to all recipients of the covered
519 | work and works based on it.
520 |
521 | A patent license is "discriminatory" if it does not include within
522 | the scope of its coverage, prohibits the exercise of, or is
523 | conditioned on the non-exercise of one or more of the rights that are
524 | specifically granted under this License. You may not convey a covered
525 | work if you are a party to an arrangement with a third party that is
526 | in the business of distributing software, under which you make payment
527 | to the third party based on the extent of your activity of conveying
528 | the work, and under which the third party grants, to any of the
529 | parties who would receive the covered work from you, a discriminatory
530 | patent license (a) in connection with copies of the covered work
531 | conveyed by you (or copies made from those copies), or (b) primarily
532 | for and in connection with specific products or compilations that
533 | contain the covered work, unless you entered into that arrangement,
534 | or that patent license was granted, prior to 28 March 2007.
535 |
536 | Nothing in this License shall be construed as excluding or limiting
537 | any implied license or other defenses to infringement that may
538 | otherwise be available to you under applicable patent law.
539 |
540 | 12. No Surrender of Others' Freedom.
541 |
542 | If conditions are imposed on you (whether by court order, agreement or
543 | otherwise) that contradict the conditions of this License, they do not
544 | excuse you from the conditions of this License. If you cannot convey a
545 | covered work so as to satisfy simultaneously your obligations under this
546 | License and any other pertinent obligations, then as a consequence you may
547 | not convey it at all. For example, if you agree to terms that obligate you
548 | to collect a royalty for further conveying from those to whom you convey
549 | the Program, the only way you could satisfy both those terms and this
550 | License would be to refrain entirely from conveying the Program.
551 |
552 | 13. Use with the GNU Affero General Public License.
553 |
554 | Notwithstanding any other provision of this License, you have
555 | permission to link or combine any covered work with a work licensed
556 | under version 3 of the GNU Affero General Public License into a single
557 | combined work, and to convey the resulting work. The terms of this
558 | License will continue to apply to the part which is the covered work,
559 | but the special requirements of the GNU Affero General Public License,
560 | section 13, concerning interaction through a network will apply to the
561 | combination as such.
562 |
563 | 14. Revised Versions of this License.
564 |
565 | The Free Software Foundation may publish revised and/or new versions of
566 | the GNU General Public License from time to time. Such new versions will
567 | be similar in spirit to the present version, but may differ in detail to
568 | address new problems or concerns.
569 |
570 | Each version is given a distinguishing version number. If the
571 | Program specifies that a certain numbered version of the GNU General
572 | Public License "or any later version" applies to it, you have the
573 | option of following the terms and conditions either of that numbered
574 | version or of any later version published by the Free Software
575 | Foundation. If the Program does not specify a version number of the
576 | GNU General Public License, you may choose any version ever published
577 | by the Free Software Foundation.
578 |
579 | If the Program specifies that a proxy can decide which future
580 | versions of the GNU General Public License can be used, that proxy's
581 | public statement of acceptance of a version permanently authorizes you
582 | to choose that version for the Program.
583 |
584 | Later license versions may give you additional or different
585 | permissions. However, no additional obligations are imposed on any
586 | author or copyright holder as a result of your choosing to follow a
587 | later version.
588 |
589 | 15. Disclaimer of Warranty.
590 |
591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
599 |
600 | 16. Limitation of Liability.
601 |
602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
610 | SUCH DAMAGES.
611 |
612 | 17. Interpretation of Sections 15 and 16.
613 |
614 | If the disclaimer of warranty and limitation of liability provided
615 | above cannot be given local legal effect according to their terms,
616 | reviewing courts shall apply local law that most closely approximates
617 | an absolute waiver of all civil liability in connection with the
618 | Program, unless a warranty or assumption of liability accompanies a
619 | copy of the Program in return for a fee.
620 |
621 | END OF TERMS AND CONDITIONS
622 |
623 | How to Apply These Terms to Your New Programs
624 |
625 | If you develop a new program, and you want it to be of the greatest
626 | possible use to the public, the best way to achieve this is to make it
627 | free software which everyone can redistribute and change under these terms.
628 |
629 | To do so, attach the following notices to the program. It is safest
630 | to attach them to the start of each source file to most effectively
631 | state the exclusion of warranty; and each file should have at least
632 | the "copyright" line and a pointer to where the full notice is found.
633 |
634 |
635 | Copyright (C)
636 |
637 | This program is free software: you can redistribute it and/or modify
638 | it under the terms of the GNU General Public License as published by
639 | the Free Software Foundation, either version 3 of the License, or
640 | (at your option) any later version.
641 |
642 | This program is distributed in the hope that it will be useful,
643 | but WITHOUT ANY WARRANTY; without even the implied warranty of
644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
645 | GNU General Public License for more details.
646 |
647 | You should have received a copy of the GNU General Public License
648 | along with this program. If not, see .
649 |
650 | Also add information on how to contact you by electronic and paper mail.
651 |
652 | If the program does terminal interaction, make it output a short
653 | notice like this when it starts in an interactive mode:
654 |
655 | Copyright (C)
656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
657 | This is free software, and you are welcome to redistribute it
658 | under certain conditions; type `show c' for details.
659 |
660 | The hypothetical commands `show w' and `show c' should show the appropriate
661 | parts of the General Public License. Of course, your program's commands
662 | might be different; for a GUI interface, you would use an "about box".
663 |
664 | You should also get your employer (if you work as a programmer) or school,
665 | if any, to sign a "copyright disclaimer" for the program, if necessary.
666 | For more information on this, and how to apply and follow the GNU GPL, see
667 | .
668 |
669 | The GNU General Public License does not permit incorporating your program
670 | into proprietary programs. If your program is a subroutine library, you
671 | may consider it more useful to permit linking proprietary applications with
672 | the library. If this is what you want to do, use the GNU Lesser General
673 | Public License instead of this License. But first, please read
674 | .
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## DEPRECATED ##
2 |
3 | **NOTE:** This project has been deprecated in favor of [js3-mode](https://github.com/thomblake/js3-mode) which is much cleaner, smaller, less crufty, and easier to install.
4 |
5 | This is not and has never been the repo for the built-in js-mode in emacs.
6 |
7 | **This project will no longer be maintained here**
8 |
9 | An improved chimeric fork of js-mode and js2-mode that supports comma-first style and other quirks.
10 |
11 | The goal of this project was to get a javascript mode working that supports [npm style](https://github.com/isaacs/npm/blob/master/doc/coding-style.md), but it turns out this mode is compatible with other styles as well. Most of the credit for the indentation goes to js-mode, with some handy special cases put in by yours truly.
12 |
13 | ## Credits ##
14 |
15 | Created by [Thom Blake](https://github.com/thomblake).
16 |
17 | Forked from js-mode and js2-mode.
18 |
19 | Inspired by [A better coding convention for lists and object literals in Javascript](https://gist.github.com/357981) and [npm style](https://github.com/isaacs/npm/blob/master/doc/coding-style.md).
20 |
21 | With help from [Cheeso on StackOverflow](http://stackoverflow.com/questions/6144930/emacs-js-mode-for-npm-style) and [Mihai Bazon](http://mihai.bazon.net/projects/editing-javascript-with-emacs-js2-mode)
22 |
23 | The js2-mode included here is basically Steve Yegge's js2-mode version 20090723 findable [here](http://code.google.com/p/js2-mode/) with some reasonable defaults set. I expect to be improving this as time goes on.
24 |
25 | The js-mode included here is the one included with emacs version 24, with some modifications to the way it indents in certain cases and added backwards-compatibility for emacs version 23.2.
26 |
27 | ## Installation ##
28 |
29 | Both js.el and js2.el should be placed in your emacs include path. You'll need to byte-compile js2-mode before using it - in emacs, `M-x byte-compile-file RET RET`. If you want, js2-mode can be configured using `M-x customize-group RET js2-mode RET`. See [here](http://code.google.com/p/js2-mode/wiki/InstallationInstructions) for detailed installation instructions on js2-mode.
30 |
31 | The .emacs file included contains what you need to stick the 2 modes together.
32 |
33 | For known compatible/incompatible versions of emacs, see compat.md - feel free to let me know if it is compatible or incompatible with your version of emacs.
34 |
35 | A future version is likely to simply be a fork of js2-mode with better indentation.
36 |
37 | ## Notes ##
38 |
39 | Using this will entail having 2 separate JS parsers running, so sometimes it takes a while to 'catch up' - if the indentation on a line looks off, try pressing TAB again. Right now it looks like this only happens with non-comma-first continued var statements. A future version will be a single mode based on JS2-mode (which has a better JS parser) which should solve some of these problems.
40 |
41 | If your JS is in error, the indentation might look wrong. I tend to regard this as a feature.
42 |
43 | Remember - if you start a line with `(`, `[`, `+`, or `-`, strongly consider preceding it with a semicolon (`;`).
44 |
45 | I expect that there are still some bugs; if you see any, **please report them**. Feel free to **file issue reports on github** for things like "it indented like [code block] but I want it to be [code block]".
46 |
47 | ## License ##
48 |
49 | This program is free software; you can redistribute it and/or
50 | modify it under the terms of the GNU General Public License as
51 | published by the Free Software Foundation; either version 3 of
52 | the License, or (at your option) any later version.
53 |
54 | This program is distributed in the hope that it will be
55 | useful, but WITHOUT ANY WARRANTY; without even the implied
56 | warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
57 | PURPOSE. See the GNU General Public License for more details.
58 |
59 | You should have received a copy of the GNU General Public License
60 | along with this program. If not, see http://www.gnu.org/licenses/.
61 |
62 | (Several programs included and referenced here are GPL, so this is too
63 | why not.)
64 |
65 | ## DEPRECATED ##
66 |
67 | **NOTE:** This project has been deprecated in favor of [js3-mode](https://github.com/thomblake/js3-mode) which is much cleaner, smaller, less crufty, and easier to install.
68 |
--------------------------------------------------------------------------------
/alternate_styles.js:
--------------------------------------------------------------------------------
1 | //This file describes the js-lazy-commas, js-lazy-operators, and
2 | // js-lazy-dots config options.
3 | //It is recommended that if you use js-lazy-commas or js-lazy-operators,
4 | // then you should use both, due to the funky indentation it causes otherwise
5 |
6 | /*
7 |
8 | // To turn on all 3, add the following to your .emacs file:
9 |
10 | (custom-set-variables
11 | ;; Your init file should contain only one such instance.
12 | ;; If there is more than one, they won't work right.
13 | '(js-lazy-commas t)
14 | '(js-lazy-operators t)
15 | '(js-lazy-dots t)
16 | '(js-expr-indent-offset 2)
17 | '(js-paren-indent-offset 2)
18 | '(js-square-indent-offset 2)
19 | '(js-curly-indent-offset 2))
20 | */
21 |
22 | /*
23 | * js-lazy-commas
24 | */
25 |
26 | //By default, js-mode supports the following comma-first style:
27 |
28 | var obj1 = { prop1: { prop1: "val1"
29 | , prop2: "val2"
30 | }
31 | , prop2: "val3"
32 | }
33 |
34 | //By turning on js-lazy-commas with the recommended settings, you can use
35 | // the following style:
36 |
37 | var obj2 = {
38 | prop1: {
39 | prop1: "val1"
40 | , prop2: "val2"
41 | }
42 | , prop2: "val3"
43 | }
44 |
45 | //Turn this on by putting the following in your .emacs file:
46 | /*
47 |
48 | (custom-set-variables
49 | ;; Your init file should contain only one such instance.
50 | ;; If there is more than one, they won't work right.
51 | '(js-lazy-commas t)
52 | '(js-expr-indent-offset 2)
53 | '(js-paren-indent-offset 2)
54 | '(js-square-indent-offset 2)
55 | '(js-curly-indent-offset 2))
56 | */
57 |
58 | /*
59 | * js-lazy-operators
60 | */
61 |
62 | //By default, js-mode supports the following operator-first style:
63 |
64 | var result1 = ( 5
65 | + 7
66 | / 2
67 | * 5
68 | )
69 |
70 | //By turning on js-lazy-operators with the recommended settings, you can use
71 | // the following style:
72 |
73 | var result2 = (
74 | 5
75 | + 7
76 | / 2
77 | * 5
78 | )
79 |
80 | //Turn this on by putting the following in your .emacs file:
81 | /*
82 |
83 | (custom-set-variables
84 | ;; Your init file should contain only one such instance.
85 | ;; If there is more than one, they won't work right.
86 | '(js-lazy-operators t)
87 | '(js-expr-indent-offset 2)
88 | '(js-paren-indent-offset 2)
89 | '(js-square-indent-offset 2)
90 | '(js-curly-indent-offset 2))
91 | */
92 |
93 | /*
94 | * js-lazy-dots
95 | */
96 |
97 | //By default, js-mode supports the following dot-first style:
98 | //(indent to the previous dot)
99 |
100 | foo.bar()
101 | .baz()
102 |
103 | //By turning on js-lazy-dots, you can use the following style:
104 | //(indent once)
105 |
106 | foo.bar()
107 | .baz()
108 |
109 | //Turn this on by putting the following in your .emacs file:
110 | /*
111 |
112 | (custom-set-variables
113 | ;; Your init file should contain only one such instance.
114 | ;; If there is more than one, they won't work right.
115 | '(js-lazy-dots t)
116 | */
117 |
118 |
--------------------------------------------------------------------------------
/compat.md:
--------------------------------------------------------------------------------
1 | ## Compatibility ##
2 |
3 |
4 | emacs version | system |
5 | 23.2 | freeBSD |
6 | 24 | Ubuntu |
7 | 23.2 | Windows XP |
8 |
9 |
10 | I welcome feedback on compatibility / incompatibility for your version of emacs / system.
--------------------------------------------------------------------------------
/js.el:
--------------------------------------------------------------------------------
1 | ;;; js.el --- Major mode for editing JavaScript
2 |
3 | ;; Significantly modified by Thom Blake - see
4 | ;; https://github.com/thomblake/js-mode
5 |
6 | ;; Copyright (C) 2008-2011 Free Software Foundation, Inc.
7 |
8 | ;; Author: Karl Landstrom
9 | ;; Daniel Colascione
10 | ;; Maintainer: Daniel Colascione
11 | ;; Version: 9
12 | ;; Date: 2009-07-25
13 | ;; Keywords: languages, javascript
14 |
15 | ;; This file is part of GNU Emacs.
16 |
17 | ;; GNU Emacs is free software: you can redistribute it and/or modify
18 | ;; it under the terms of the GNU General Public License as published by
19 | ;; the Free Software Foundation, either version 3 of the License, or
20 | ;; (at your option) any later version.
21 |
22 | ;; GNU Emacs is distributed in the hope that it will be useful,
23 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
24 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 | ;; GNU General Public License for more details.
26 |
27 | ;; You should have received a copy of the GNU General Public License
28 | ;; along with GNU Emacs. If not, see .
29 |
30 | ;;; Commentary
31 |
32 | ;; This is based on Karl Landstrom's barebones javascript-mode. This
33 | ;; is much more robust and works with cc-mode's comment filling
34 | ;; (mostly).
35 | ;;
36 | ;; The main features of this JavaScript mode are syntactic
37 | ;; highlighting (enabled with `font-lock-mode' or
38 | ;; `global-font-lock-mode'), automatic indentation and filling of
39 | ;; comments, C preprocessor fontification, and MozRepl integration.
40 | ;;
41 | ;; General Remarks:
42 | ;;
43 | ;; XXX: This mode assumes that block comments are not nested inside block
44 | ;; XXX: comments
45 | ;;
46 | ;; Exported names start with "js-"; private names start with
47 | ;; "js--".
48 |
49 | ;;; Code:
50 |
51 |
52 | (require 'cc-mode)
53 | (require 'newcomment)
54 | (require 'thingatpt) ; forward-symbol etc
55 | (require 'imenu)
56 | (require 'moz nil t)
57 | (require 'json nil t)
58 |
59 | (eval-when-compile
60 | (require 'cl)
61 | (require 'comint)
62 | (require 'ido))
63 |
64 | (defvar inferior-moz-buffer)
65 | (defvar moz-repl-name)
66 | (defvar ido-cur-list)
67 | (declare-function ido-mode "ido")
68 | (declare-function inferior-moz-process "ext:mozrepl" ())
69 |
70 | ;;; Constants
71 |
72 | (defconst js--name-start-re "[a-zA-Z_$]"
73 | "Regexp matching the start of a JavaScript identifier, without grouping.")
74 |
75 | (defconst js--stmt-delim-chars "^;{}?:")
76 |
77 | (defconst js--name-re (concat js--name-start-re
78 | "\\(?:\\s_\\|\\sw\\)*")
79 | "Regexp matching a JavaScript identifier, without grouping.")
80 |
81 | (defconst js--objfield-re (concat js--name-re ":")
82 | "Regexp matching the start of a JavaScript object field.")
83 |
84 | (defconst js--dotted-name-re
85 | (concat js--name-re "\\(?:\\." js--name-re "\\)*")
86 | "Regexp matching a dot-separated sequence of JavaScript names.")
87 |
88 | (defconst js--cpp-name-re js--name-re
89 | "Regexp matching a C preprocessor name.")
90 |
91 | (defconst js--opt-cpp-start "^\\s-*#\\s-*\\([[:alnum:]]+\\)"
92 | "Regexp matching the prefix of a cpp directive.
93 | This includes the directive name, or nil in languages without
94 | preprocessor support. The first submatch surrounds the directive
95 | name.")
96 |
97 | (defconst js--plain-method-re
98 | (concat "^\\s-*?\\(" js--dotted-name-re "\\)\\.prototype"
99 | "\\.\\(" js--name-re "\\)\\s-*?=\\s-*?\\(function\\)\\_>")
100 | "Regexp matching an explicit JavaScript prototype \"method\" declaration.
101 | Group 1 is a (possibly-dotted) class name, group 2 is a method name,
102 | and group 3 is the 'function' keyword.")
103 |
104 | (defconst js--plain-class-re
105 | (concat "^\\s-*\\(" js--dotted-name-re "\\)\\.prototype"
106 | "\\s-*=\\s-*{")
107 | "Regexp matching a JavaScript explicit prototype \"class\" declaration.
108 | An example of this is \"Class.prototype = { method1: ...}\".")
109 |
110 | ;; var NewClass = BaseClass.extend(
111 | (defconst js--mp-class-decl-re
112 | (concat "^\\s-*var\\s-+"
113 | "\\(" js--name-re "\\)"
114 | "\\s-*=\\s-*"
115 | "\\(" js--dotted-name-re
116 | "\\)\\.extend\\(?:Final\\)?\\s-*(\\s-*{?\\s-*$"))
117 |
118 | ;; var NewClass = Class.create()
119 | (defconst js--prototype-obsolete-class-decl-re
120 | (concat "^\\s-*\\(?:var\\s-+\\)?"
121 | "\\(" js--dotted-name-re "\\)"
122 | "\\s-*=\\s-*Class\\.create()"))
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 |
134 | ;; var NewClass = Class.create({
135 | (defconst js--prototype-class-decl-re
136 | (concat "^\\s-*\\(?:var\\s-+\\)?"
137 | "\\(" js--name-re "\\)"
138 | "\\s-*=\\s-*Class\\.create\\s-*(\\s-*"
139 | "\\(?:\\(" js--dotted-name-re "\\)\\s-*,\\s-*\\)?{?"))
140 |
141 | ;; Parent class name(s) (yes, multiple inheritance in JavaScript) are
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 "\\)")
150 | "Regexp matching an ExtJS class declaration (style 1).")
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 "\\)")
157 | "Regexp matching an ExtJS class declaration (style 2).")
158 |
159 | (defconst js--mochikit-class-re
160 | (concat "^\\s-*MochiKit\\.Base\\.update\\s-*(\\s-*"
161 | "\\(" js--dotted-name-re "\\)")
162 | "Regexp matching a MochiKit class declaration.")
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 |
223 | "List of JavaScript class definition styles.
224 |
225 | A 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
230 | class. Its first group must match the name of its class. If there
231 | is a parent class, the second group should match, and it should be
232 | the name of the class.
233 |
234 | If :prototype is present and non-nil, the parser will merge
235 | declarations for this constructs with others at the same lexical
236 | level that have the same name. Otherwise, multiple definitions
237 | will create multiple top-level entries. Don't use :prototype
238 | unnecessarily: it has an associated cost in performance.
239 |
240 | If :strip-prototype is present and non-nil, then if the class
241 | name 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)
251 | "List of available JavaScript frameworks symbols.")
252 |
253 | (defconst js--function-heading-1-re
254 | (concat
255 | "^\\s-*function\\s-+\\(" js--name-re "\\)")
256 | "Regexp matching the start of a JavaScript function header.
257 | Match group 1 is the name of the function.")
258 |
259 | (defconst js--function-heading-2-re
260 | (concat
261 | "^\\s-*\\(" js--name-re "\\)\\s-*:\\s-*function\\_>")
262 | "Regexp matching the start of a function entry in an associative array.
263 | Match group 1 is the name of the function.")
264 |
265 | (defconst js--function-heading-3-re
266 | (concat
267 | "^\\s-*\\(?:var\\s-+\\)?\\(" js--dotted-name-re "\\)"
268 | "\\s-*=\\s-*function\\_>")
269 | "Regexp matching a line in the JavaScript form \"var MUMBLE = function\".
270 | Match group 1 is MUMBLE.")
271 |
272 | (defconst js--macro-decl-re
273 | (concat "^\\s-*#\\s-*define\\s-+\\(" js--cpp-name-re "\\)\\s-*(")
274 | "Regexp matching a CPP macro definition, up to the opening parenthesis.
275 | Match group 1 is the name of the macro.")
276 |
277 | (defun js--regexp-opt-symbol (list)
278 | "Like `regexp-opt', but surround the result with `\\\\_<' and `\\\\_>'."
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"))
292 | "Regexp matching any JavaScript keyword.")
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 | "\\_"
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))
312 | "Level one font lock keywords for `js-mode'.")
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 "\\_"
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)))
322 | "Level two font lock keywords for `js-mode'.")
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 |
415 | ;; The pitem we start parsing with.
416 | (defconst js--initial-pitem
417 | (make-js--pitem
418 | :paren-depth most-negative-fixnum
419 | :type 'toplevel))
420 |
421 | ;;; User Customization
422 |
423 | (defgroup js nil
424 | "Customization variables for JavaScript mode."
425 | :tag "JavaScript"
426 | :group 'languages)
427 |
428 | (defcustom js-indent-level 2
429 | "Number of spaces for each indentation step in `js-mode'."
430 | :type 'integer
431 | :group 'js)
432 |
433 | (defcustom js-expr-indent-offset 0
434 | "Number of additional spaces for indenting continued expressions.
435 | The value must be no less than minus `js-indent-level'."
436 | :type 'integer
437 | :group 'js)
438 |
439 | (defcustom js-paren-indent-offset 0
440 | "Number of additional spaces for indenting expressions in parentheses.
441 | The 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.
448 | The 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.
455 | The value must be no less than minus `js-indent-level'."
456 | :type 'integer
457 | :group 'js
458 | :version "24.1")
459 |
460 | (defcustom js-auto-indent-flag t
461 | "Whether to automatically indent when typing punctuation characters.
462 | If non-nil, the characters {}();,: also indent the current line
463 | in Javascript mode."
464 | :type 'boolean
465 | :group 'js)
466 |
467 | (defcustom js-flat-functions nil
468 | "Treat nested functions as top-level functions in `js-mode'.
469 | This applies to function movement, marking, and so on."
470 | :type 'boolean
471 | :group 'js)
472 |
473 | (defcustom js-comment-lineup-func #'c-lineup-C-comments
474 | "Lineup function for `cc-mode-style', for C comments in `js-mode'."
475 | :type 'function
476 | :group 'js)
477 |
478 | (defcustom js-enabled-frameworks js--available-frameworks
479 | "Frameworks recognized by `js-mode'.
480 | To improve performance, you may turn off some frameworks you
481 | seldom use, either globally or on a per-buffer basis."
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)
489 | "Whether `js-mode' should display tabs while selecting them.
490 | This is useful only if the windowing system has a good mechanism
491 | for preventing Firefox from stealing the keyboard focus."
492 | :type 'boolean
493 | :group 'js)
494 |
495 | (defcustom js-js-tmpdir
496 | "~/.emacs.d/js/js"
497 | "Temporary directory used by `js-mode' to communicate with Mozilla.
498 | This directory must be readable and writable by both Mozilla and Emacs."
499 | :type 'directory
500 | :group 'js)
501 |
502 | (defcustom js-js-timeout 5
503 | "Reply timeout for executing commands in Mozilla via `js-mode'.
504 | The value is given in seconds. Increase this value if you are
505 | getting timeout messages."
506 | :type 'integer
507 | :group 'js)
508 |
509 | (defcustom js-lazy-commas nil
510 | "Whether `js-mode' should line up commas to the indent-minus-2,
511 | rather than trying to line up to braces."
512 | :type 'boolean
513 | :group 'js)
514 |
515 | (defcustom js-lazy-operators nil
516 | "Whether `js-mode' should line up operators to the indent-minus-2,
517 | rather than trying to line up to braces."
518 | :type 'boolean
519 | :group 'js)
520 |
521 | (defcustom js-lazy-dots nil
522 | "Whether `js-mode' should line up dots to the next indent level,
523 | rather than trying to line up to dots."
524 | :type 'boolean
525 | :group 'js)
526 |
527 | ;;; KeyMap
528 |
529 | (defvar js-mode-map
530 | (let ((keymap (make-sparse-keymap)))
531 | (mapc (lambda (key)
532 | (define-key keymap key #'js-insert-and-indent))
533 | '("{" "}" "(" ")" ":" ";" ","))
534 | (define-key keymap [(control ?c) (meta ?:)] #'js-eval)
535 | (define-key keymap [(control ?c) (control ?j)] #'js-set-js-context)
536 | (define-key keymap [(control meta ?x)] #'js-eval-defun)
537 | (define-key keymap [(meta ?.)] #'js-find-symbol)
538 | (easy-menu-define nil keymap "Javascript Menu"
539 | '("Javascript"
540 | ["Select New Mozilla Context..." js-set-js-context
541 | (fboundp #'inferior-moz-process)]
542 | ["Evaluate Expression in Mozilla Context..." js-eval
543 | (fboundp #'inferior-moz-process)]
544 | ["Send Current Function to Mozilla..." js-eval-defun
545 | (fboundp #'inferior-moz-process)]))
546 | keymap)
547 | "Keymap for `js-mode'.")
548 |
549 | (defun js-insert-and-indent (key)
550 | "Run the command bound to KEY, and indent if necessary.
551 | Indentation does not take place if point is in a string or
552 | comment."
553 | (interactive (list (this-command-keys)))
554 | (call-interactively (lookup-key (current-global-map) key))
555 | (let ((syntax (save-restriction (widen) (syntax-ppss))))
556 | (when (or (and (not (nth 8 syntax))
557 | js-auto-indent-flag)
558 | (and (nth 4 syntax)
559 | (eq (current-column)
560 | (1+ (current-indentation)))))
561 | (indent-according-to-mode))))
562 |
563 |
564 | ;;; Syntax table and parsing
565 |
566 | (defvar js-mode-syntax-table
567 | (let ((table (make-syntax-table)))
568 | (c-populate-syntax-table table)
569 | (modify-syntax-entry ?$ "_" table)
570 | table)
571 | "Syntax table for `js-mode'.")
572 |
573 | (defvar js--quick-match-re nil
574 | "Autogenerated regexp used by `js-mode' to match buffer constructs.")
575 |
576 | (defvar js--quick-match-re-func nil
577 | "Autogenerated regexp used by `js-mode' to match constructs and functions.")
578 |
579 | (make-variable-buffer-local 'js--quick-match-re)
580 | (make-variable-buffer-local 'js--quick-match-re-func)
581 |
582 | (defvar js--cache-end 1
583 | "Last valid buffer position for the `js-mode' function cache.")
584 | (make-variable-buffer-local 'js--cache-end)
585 |
586 | (defvar js--last-parse-pos nil
587 | "Latest parse position reached by `js--ensure-cache'.")
588 | (make-variable-buffer-local 'js--last-parse-pos)
589 |
590 | (defvar js--state-at-last-parse-pos nil
591 | "Parse state at `js--last-parse-pos'.")
592 | (make-variable-buffer-local 'js--state-at-last-parse-pos)
593 |
594 | (defun js--flatten-list (list)
595 | (loop for item in list
596 | nconc (cond ((consp item)
597 | (js--flatten-list item))
598 | (item (list item)))))
599 |
600 | (defun js--maybe-join (prefix separator suffix &rest list)
601 | "Helper function for `js--update-quick-match-re'.
602 | If LIST contains any element that is not nil, return its non-nil
603 | elements, separated by SEPARATOR, prefixed by PREFIX, and ended
604 | with SUFFIX as with `concat'. Otherwise, if LIST is empty, return
605 | nil. If any element in LIST is itself a list, flatten that
606 | element."
607 | (setq list (js--flatten-list list))
608 | (when list
609 | (concat prefix (mapconcat #'identity list separator) suffix)))
610 |
611 | (defun js--update-quick-match-re ()
612 | "Internal function used by `js-mode' for caching buffer constructs.
613 | This updates `js--quick-match-re', based on the current set of
614 | enabled frameworks."
615 | (setq js--quick-match-re
616 | (js--maybe-join
617 | "^[ \t]*\\(?:" "\\|" "\\)"
618 |
619 | ;; #define mumble
620 | "#define[ \t]+[a-zA-Z_]"
621 |
622 | (when (memq 'extjs js-enabled-frameworks)
623 | "Ext\\.extend")
624 |
625 | (when (memq 'prototype js-enabled-frameworks)
626 | "Object\\.extend")
627 |
628 | ;; var mumble = THING (
629 | (js--maybe-join
630 | "\\(?:var[ \t]+\\)?[a-zA-Z_$0-9.]+[ \t]*=[ \t]*\\(?:"
631 | "\\|"
632 | "\\)[ \t]*\("
633 |
634 | (when (memq 'prototype js-enabled-frameworks)
635 | "Class\\.create")
636 |
637 | (when (memq 'extjs js-enabled-frameworks)
638 | "Ext\\.extend")
639 |
640 | (when (memq 'merrillpress js-enabled-frameworks)
641 | "[a-zA-Z_$0-9]+\\.extend\\(?:Final\\)?"))
642 |
643 | (when (memq 'dojo js-enabled-frameworks)
644 | "dojo\\.declare[ \t]*\(")
645 |
646 | (when (memq 'mochikit js-enabled-frameworks)
647 | "MochiKit\\.Base\\.update[ \t]*\(")
648 |
649 | ;; mumble.prototypeTHING
650 | (js--maybe-join
651 | "[a-zA-Z_$0-9.]+\\.prototype\\(?:" "\\|" "\\)"
652 |
653 | (when (memq 'javascript js-enabled-frameworks)
654 | '( ;; foo.prototype.bar = function(
655 | "\\.[a-zA-Z_$0-9]+[ \t]*=[ \t]*function[ \t]*\("
656 |
657 | ;; mumble.prototype = {
658 | "[ \t]*=[ \t]*{")))))
659 |
660 | (setq js--quick-match-re-func
661 | (concat "function\\|" js--quick-match-re)))
662 |
663 | (defun js--forward-text-property (propname)
664 | "Move over the next value of PROPNAME in the buffer.
665 | If found, return that value and leave point after the character
666 | having that value; otherwise, return nil and leave point at EOB."
667 | (let ((next-value (get-text-property (point) propname)))
668 | (if next-value
669 | (forward-char)
670 |
671 | (goto-char (next-single-property-change
672 | (point) propname nil (point-max)))
673 | (unless (eobp)
674 | (setq next-value (get-text-property (point) propname))
675 | (forward-char)))
676 |
677 | next-value))
678 |
679 | (defun js--backward-text-property (propname)
680 | "Move over the previous value of PROPNAME in the buffer.
681 | If found, return that value and leave point just before the
682 | character that has that value, otherwise return nil and leave
683 | point at BOB."
684 | (unless (bobp)
685 | (let ((prev-value (get-text-property (1- (point)) propname)))
686 | (if prev-value
687 | (backward-char)
688 |
689 | (goto-char (previous-single-property-change
690 | (point) propname nil (point-min)))
691 |
692 | (unless (bobp)
693 | (backward-char)
694 | (setq prev-value (get-text-property (point) propname))))
695 |
696 | prev-value)))
697 |
698 | (defsubst js--forward-pstate ()
699 | (js--forward-text-property 'js--pstate))
700 |
701 | (defsubst js--backward-pstate ()
702 | (js--backward-text-property 'js--pstate))
703 |
704 | (defun js--pitem-goto-h-end (pitem)
705 | (goto-char (js--pitem-h-begin pitem))
706 | (js--forward-pstate))
707 |
708 | (defun js--re-search-forward-inner (regexp &optional bound count)
709 | "Helper function for `js--re-search-forward'."
710 | (let ((parse)
711 | str-terminator
712 | (orig-macro-end (save-excursion
713 | (when (js--beginning-of-macro)
714 | (c-end-of-macro)
715 | (point)))))
716 | (while (> count 0)
717 | (re-search-forward regexp bound)
718 | (setq parse (syntax-ppss))
719 | (cond ((setq str-terminator (nth 3 parse))
720 | (when (eq str-terminator t)
721 | (setq str-terminator ?/))
722 | (re-search-forward
723 | (concat "\\([^\\]\\|^\\)" (string str-terminator))
724 | (point-at-eol) t))
725 | ((nth 7 parse)
726 | (forward-line))
727 | ((or (nth 4 parse)
728 | (and (eq (char-before) ?\/) (eq (char-after) ?\*)))
729 | (re-search-forward "\\*/"))
730 | ((and (not (and orig-macro-end
731 | (<= (point) orig-macro-end)))
732 | (js--beginning-of-macro))
733 | (c-end-of-macro))
734 | (t
735 | (setq count (1- count))))))
736 | (point))
737 |
738 |
739 | (defun js--re-search-forward (regexp &optional bound noerror count)
740 | "Search forward, ignoring strings, cpp macros, and comments.
741 | This function invokes `re-search-forward', but treats the buffer
742 | as if strings, cpp macros, and comments have been removed.
743 |
744 | If invoked while inside a macro, it treats the contents of the
745 | macro as normal text."
746 | (unless count (setq count 1))
747 | (let ((saved-point (point))
748 | (search-fun
749 | (cond ((< count 0) (setq count (- count))
750 | #'js--re-search-backward-inner)
751 | ((> count 0) #'js--re-search-forward-inner)
752 | (t #'ignore))))
753 | (condition-case err
754 | (funcall search-fun regexp bound count)
755 | (search-failed
756 | (goto-char saved-point)
757 | (unless noerror
758 | (signal (car err) (cdr err)))))))
759 |
760 |
761 | (defun js--re-search-backward-inner (regexp &optional bound count)
762 | "Auxiliary function for `js--re-search-backward'."
763 | (let ((parse)
764 | str-terminator
765 | (orig-macro-start
766 | (save-excursion
767 | (and (js--beginning-of-macro)
768 | (point)))))
769 | (while (> count 0)
770 | (re-search-backward regexp bound)
771 | (when (and (> (point) (point-min))
772 | (save-excursion (backward-char) (looking-at "/[/*]")))
773 | (forward-char))
774 | (setq parse (syntax-ppss))
775 | (cond ((setq str-terminator (nth 3 parse))
776 | (when (eq str-terminator t)
777 | (setq str-terminator ?/))
778 | (re-search-backward
779 | (concat "\\([^\\]\\|^\\)" (string str-terminator))
780 | (point-at-bol) t)
781 | (when (not (string= "" (match-string 1)))
782 | (forward-char)))
783 | ((nth 7 parse)
784 | (goto-char (nth 8 parse)))
785 | ((or (nth 4 parse)
786 | (and (eq (char-before) ?/) (eq (char-after) ?*)))
787 | (re-search-backward "/\\*"))
788 | ((and (not (and orig-macro-start
789 | (>= (point) orig-macro-start)))
790 | (js--beginning-of-macro)))
791 | (t
792 | (setq count (1- count))))))
793 | (point))
794 |
795 |
796 | (defun js--re-search-backward (regexp &optional bound noerror count)
797 | "Search backward, ignoring strings, preprocessor macros, and comments.
798 |
799 | This function invokes `re-search-backward' but treats the buffer
800 | as if strings, preprocessor macros, and comments have been
801 | removed.
802 |
803 | If invoked while inside a macro, treat the macro as normal text."
804 | (js--re-search-forward regexp bound noerror (if count (- count) -1)))
805 |
806 | (defun js--looking-back (regexp)
807 | "This function returns t if regexp matches text before point, ending at point, and nil otherwise.
808 |
809 | This function is similar to `looking-back' but ignores comments and strings"
810 | (save-excursion
811 | (let ((r (if (and (= ?\= (elt regexp (1- (length regexp))))
812 | (= ?\\ (elt regexp (- (length regexp) 2))))
813 | regexp
814 | (concat regexp "\\="))))
815 | (numberp (js--re-search-backward r (point-min) t)))))
816 |
817 | (defun js--forward-expression ()
818 | "Move forward over a whole JavaScript expression.
819 | This function doesn't move over expressions continued across
820 | lines."
821 | (loop
822 | ;; non-continued case; simplistic, but good enough?
823 | do (loop until (or (eolp)
824 | (progn
825 | (forward-comment most-positive-fixnum)
826 | (memq (char-after) '(?\, ?\; ?\] ?\) ?\}))))
827 | do (forward-sexp))
828 |
829 | while (and (eq (char-after) ?\n)
830 | (save-excursion
831 | (forward-char)
832 | (js--continued-expression-p)))))
833 |
834 | (defun js--forward-function-decl ()
835 | "Move forward over a JavaScript function declaration.
836 | This puts point at the 'function' keyword.
837 |
838 | If this is a syntactically-correct non-expression function,
839 | return the name of the function, or t if the name could not be
840 | determined. Otherwise, return nil."
841 | (assert (looking-at "\\_"))
842 | (let ((name t))
843 | (forward-word)
844 | (forward-comment most-positive-fixnum)
845 | (when (looking-at js--name-re)
846 | (setq name (match-string-no-properties 0))
847 | (goto-char (match-end 0)))
848 | (forward-comment most-positive-fixnum)
849 | (and (eq (char-after) ?\( )
850 | (ignore-errors (forward-list) t)
851 | (progn (forward-comment most-positive-fixnum)
852 | (and (eq (char-after) ?{)
853 | name)))))
854 |
855 | (defun js--function-prologue-beginning (&optional pos)
856 | "Return the start of the JavaScript function prologue containing POS.
857 | A function prologue is everything from start of the definition up
858 | to and including the opening brace. POS defaults to point.
859 | If POS is not in a function prologue, return nil."
860 | (let (prologue-begin)
861 | (save-excursion
862 | (if pos
863 | (goto-char pos)
864 | (setq pos (point)))
865 |
866 | (when (save-excursion
867 | (forward-line 0)
868 | (or (looking-at js--function-heading-2-re)
869 | (looking-at js--function-heading-3-re)))
870 |
871 | (setq prologue-begin (match-beginning 1))
872 | (when (<= prologue-begin pos)
873 | (goto-char (match-end 0))))
874 |
875 | (skip-syntax-backward "w_")
876 | (and (or (looking-at "\\_")
877 | (js--re-search-backward "\\_" nil t))
878 |
879 | (save-match-data (goto-char (match-beginning 0))
880 | (js--forward-function-decl))
881 |
882 | (<= pos (point))
883 | (or prologue-begin (match-beginning 0))))))
884 |
885 | (defun js--beginning-of-defun-raw ()
886 | "Helper function for `js-beginning-of-defun'.
887 | Go to previous defun-beginning and return the parse state for it,
888 | or nil if we went all the way back to bob and don't find
889 | anything."
890 | (js--ensure-cache)
891 | (let (pstate)
892 | (while (and (setq pstate (js--backward-pstate))
893 | (not (eq 'function (js--pitem-type (car pstate))))))
894 | (and (not (bobp)) pstate)))
895 |
896 | (defun js--pstate-is-toplevel-defun (pstate)
897 | "Helper function for `js--beginning-of-defun-nested'.
898 | If PSTATE represents a non-empty top-level defun, return the
899 | top-most pitem. Otherwise, return nil."
900 | (loop for pitem in pstate
901 | with func-depth = 0
902 | with func-pitem
903 | if (eq 'function (js--pitem-type pitem))
904 | do (incf func-depth)
905 | and do (setq func-pitem pitem)
906 | finally return (if (eq func-depth 1) func-pitem)))
907 |
908 | (defun js--beginning-of-defun-nested ()
909 | "Helper function for `js--beginning-of-defun'.
910 | Return the pitem of the function we went to the beginning of."
911 | (or
912 | ;; Look for the smallest function that encloses point...
913 | (loop for pitem in (js--parse-state-at-point)
914 | if (and (eq 'function (js--pitem-type pitem))
915 | (js--inside-pitem-p pitem))
916 | do (goto-char (js--pitem-h-begin pitem))
917 | and return pitem)
918 |
919 | ;; ...and if that isn't found, look for the previous top-level
920 | ;; defun
921 | (loop for pstate = (js--backward-pstate)
922 | while pstate
923 | if (js--pstate-is-toplevel-defun pstate)
924 | do (goto-char (js--pitem-h-begin it))
925 | and return it)))
926 |
927 | (defun js--beginning-of-defun-flat ()
928 | "Helper function for `js-beginning-of-defun'."
929 | (let ((pstate (js--beginning-of-defun-raw)))
930 | (when pstate
931 | (goto-char (js--pitem-h-begin (car pstate))))))
932 |
933 | (defun js-beginning-of-defun (&optional arg)
934 | "Value of `beginning-of-defun-function' for `js-mode'."
935 | (setq arg (or arg 1))
936 | (while (and (not (eobp)) (< arg 0))
937 | (incf arg)
938 | (when (and (not js-flat-functions)
939 | (or (eq (js-syntactic-context) 'function)
940 | (js--function-prologue-beginning)))
941 | (js-end-of-defun))
942 |
943 | (if (js--re-search-forward
944 | "\\_" nil t)
945 | (goto-char (js--function-prologue-beginning))
946 | (goto-char (point-max))))
947 |
948 | (while (> arg 0)
949 | (decf arg)
950 | ;; If we're just past the end of a function, the user probably wants
951 | ;; to go to the beginning of *that* function
952 | (when (eq (char-before) ?})
953 | (backward-char))
954 |
955 | (let ((prologue-begin (js--function-prologue-beginning)))
956 | (cond ((and prologue-begin (< prologue-begin (point)))
957 | (goto-char prologue-begin))
958 |
959 | (js-flat-functions
960 | (js--beginning-of-defun-flat))
961 | (t
962 | (js--beginning-of-defun-nested))))))
963 |
964 | (defun js--flush-caches (&optional beg ignored)
965 | "Flush the `js-mode' syntax cache after position BEG.
966 | BEG defaults to `point-min', meaning to flush the entire cache."
967 | (interactive)
968 | (setq beg (or beg (save-restriction (widen) (point-min))))
969 | (setq js--cache-end (min js--cache-end beg)))
970 |
971 | (defmacro js--debug (&rest _arguments)
972 | ;; `(message ,@arguments)
973 | )
974 |
975 | (defun js--ensure-cache--pop-if-ended (open-items paren-depth)
976 | (let ((top-item (car open-items)))
977 | (when (<= paren-depth (js--pitem-paren-depth top-item))
978 | (assert (not (get-text-property (1- (point)) 'js-pend)))
979 | (put-text-property (1- (point)) (point) 'js--pend top-item)
980 | (setf (js--pitem-b-end top-item) (point))
981 | (setq open-items
982 | ;; open-items must contain at least two items for this to
983 | ;; work, but because we push a dummy item to start with,
984 | ;; that assumption holds.
985 | (cons (js--pitem-add-child (second open-items) top-item)
986 | (cddr open-items)))))
987 | open-items)
988 |
989 | (defmacro js--ensure-cache--update-parse ()
990 | "Helper function for `js--ensure-cache'.
991 | Update parsing information up to point, referring to parse,
992 | prev-parse-point, goal-point, and open-items bound lexically in
993 | the body of `js--ensure-cache'."
994 | `(progn
995 | (setq goal-point (point))
996 | (goto-char prev-parse-point)
997 | (while (progn
998 | (setq open-items (js--ensure-cache--pop-if-ended
999 | open-items (car parse)))
1000 | ;; Make sure parse-partial-sexp doesn't stop because we *entered*
1001 | ;; the given depth -- i.e., make sure we're deeper than the target
1002 | ;; depth.
1003 | (assert (> (nth 0 parse)
1004 | (js--pitem-paren-depth (car open-items))))
1005 | (setq parse (parse-partial-sexp
1006 | prev-parse-point goal-point
1007 | (js--pitem-paren-depth (car open-items))
1008 | nil parse))
1009 |
1010 | ;;; (let ((overlay (make-overlay prev-parse-point (point))))
1011 | ;;; (overlay-put overlay 'face '(:background "red"))
1012 | ;;; (unwind-protect
1013 | ;;; (progn
1014 | ;;; (js--debug "parsed: %S" parse)
1015 | ;;; (sit-for 1))
1016 | ;;; (delete-overlay overlay)))
1017 |
1018 | (setq prev-parse-point (point))
1019 | (< (point) goal-point)))
1020 |
1021 | (setq open-items (js--ensure-cache--pop-if-ended
1022 | open-items (car parse)))))
1023 |
1024 | (defun js--show-cache-at-point ()
1025 | (interactive)
1026 | (require 'pp)
1027 | (let ((prop (get-text-property (point) 'js--pstate)))
1028 | (with-output-to-temp-buffer "*Help*"
1029 | (pp prop))))
1030 |
1031 | (defun js--split-name (string)
1032 | "Split a JavaScript name into its dot-separated parts.
1033 | This also removes any prototype parts from the split name
1034 | \(unless the name is just \"prototype\" to start with)."
1035 | (let ((name (save-match-data
1036 | (split-string string "\\." t))))
1037 | (unless (and (= (length name) 1)
1038 | (equal (car name) "prototype"))
1039 |
1040 | (setq name (remove "prototype" name)))))
1041 |
1042 | (defvar js--guess-function-name-start nil)
1043 |
1044 | (defun js--guess-function-name (position)
1045 | "Guess the name of the JavaScript function at POSITION.
1046 | POSITION should be just after the end of the word \"function\".
1047 | Return the name of the function, or nil if the name could not be
1048 | guessed.
1049 |
1050 | This function clobbers match data. If we find the preamble
1051 | begins earlier than expected while guessing the function name,
1052 | set `js--guess-function-name-start' to that position; otherwise,
1053 | set that variable to nil."
1054 | (setq js--guess-function-name-start nil)
1055 | (save-excursion
1056 | (goto-char position)
1057 | (forward-line 0)
1058 | (cond
1059 | ((looking-at js--function-heading-3-re)
1060 | (and (eq (match-end 0) position)
1061 | (setq js--guess-function-name-start (match-beginning 1))
1062 | (match-string-no-properties 1)))
1063 |
1064 | ((looking-at js--function-heading-2-re)
1065 | (and (eq (match-end 0) position)
1066 | (setq js--guess-function-name-start (match-beginning 1))
1067 | (match-string-no-properties 1))))))
1068 |
1069 | (defun js--clear-stale-cache ()
1070 | ;; Clear any endings that occur after point
1071 | (let (end-prop)
1072 | (save-excursion
1073 | (while (setq end-prop (js--forward-text-property
1074 | 'js--pend))
1075 | (setf (js--pitem-b-end end-prop) nil))))
1076 |
1077 | ;; Remove any cache properties after this point
1078 | (remove-text-properties (point) (point-max)
1079 | '(js--pstate t js--pend t)))
1080 |
1081 | (defun js--ensure-cache (&optional limit)
1082 | "Ensures brace cache is valid up to the character before LIMIT.
1083 | LIMIT defaults to point."
1084 | (setq limit (or limit (point)))
1085 | (when (< js--cache-end limit)
1086 |
1087 | (c-save-buffer-state
1088 | (open-items
1089 | orig-match-start
1090 | orig-match-end
1091 | orig-depth
1092 | parse
1093 | prev-parse-point
1094 | name
1095 | case-fold-search
1096 | filtered-class-styles
1097 | new-item
1098 | goal-point
1099 | end-prop)
1100 |
1101 | ;; Figure out which class styles we need to look for
1102 | (setq filtered-class-styles
1103 | (loop for style in js--class-styles
1104 | if (memq (plist-get style :framework)
1105 | js-enabled-frameworks)
1106 | collect style))
1107 |
1108 | (save-excursion
1109 | (save-restriction
1110 | (widen)
1111 |
1112 | ;; Find last known good position
1113 | (goto-char js--cache-end)
1114 | (unless (bobp)
1115 | (setq open-items (get-text-property
1116 | (1- (point)) 'js--pstate))
1117 |
1118 | (unless open-items
1119 | (goto-char (previous-single-property-change
1120 | (point) 'js--pstate nil (point-min)))
1121 |
1122 | (unless (bobp)
1123 | (setq open-items (get-text-property (1- (point))
1124 | 'js--pstate))
1125 | (assert open-items))))
1126 |
1127 | (unless open-items
1128 | ;; Make a placeholder for the top-level definition
1129 | (setq open-items (list js--initial-pitem)))
1130 |
1131 | (setq parse (syntax-ppss))
1132 | (setq prev-parse-point (point))
1133 |
1134 | (js--clear-stale-cache)
1135 |
1136 | (narrow-to-region (point-min) limit)
1137 |
1138 | (loop while (re-search-forward js--quick-match-re-func nil t)
1139 | for orig-match-start = (goto-char (match-beginning 0))
1140 | for orig-match-end = (match-end 0)
1141 | do (js--ensure-cache--update-parse)
1142 | for orig-depth = (nth 0 parse)
1143 |
1144 | ;; Each of these conditions should return non-nil if
1145 | ;; we should add a new item and leave point at the end
1146 | ;; of the new item's header (h-end in the
1147 | ;; js--pitem diagram). This point is the one
1148 | ;; after the last character we need to unambiguously
1149 | ;; detect this construct. If one of these evaluates to
1150 | ;; nil, the location of the point is ignored.
1151 | if (cond
1152 | ;; In comment or string
1153 | ((nth 8 parse) nil)
1154 |
1155 | ;; Regular function declaration
1156 | ((and (looking-at "\\_")
1157 | (setq name (js--forward-function-decl)))
1158 |
1159 | (when (eq name t)
1160 | (setq name (js--guess-function-name orig-match-end))
1161 | (if name
1162 | (when js--guess-function-name-start
1163 | (setq orig-match-start
1164 | js--guess-function-name-start))
1165 |
1166 | (setq name t)))
1167 |
1168 | (assert (eq (char-after) ?{))
1169 | (forward-char)
1170 | (make-js--pitem
1171 | :paren-depth orig-depth
1172 | :h-begin orig-match-start
1173 | :type 'function
1174 | :name (if (eq name t)
1175 | name
1176 | (js--split-name name))))
1177 |
1178 | ;; Macro
1179 | ((looking-at js--macro-decl-re)
1180 |
1181 | ;; Macros often contain unbalanced parentheses.
1182 | ;; Make sure that h-end is at the textual end of
1183 | ;; the macro no matter what the parenthesis say.
1184 | (c-end-of-macro)
1185 | (js--ensure-cache--update-parse)
1186 |
1187 | (make-js--pitem
1188 | :paren-depth (nth 0 parse)
1189 | :h-begin orig-match-start
1190 | :type 'macro
1191 | :name (list (match-string-no-properties 1))))
1192 |
1193 | ;; "Prototype function" declaration
1194 | ((looking-at js--plain-method-re)
1195 | (goto-char (match-beginning 3))
1196 | (when (save-match-data
1197 | (js--forward-function-decl))
1198 | (forward-char)
1199 | (make-js--pitem
1200 | :paren-depth orig-depth
1201 | :h-begin orig-match-start
1202 | :type 'function
1203 | :name (nconc (js--split-name
1204 | (match-string-no-properties 1))
1205 | (list (match-string-no-properties 2))))))
1206 |
1207 | ;; Class definition
1208 | ((loop with syntactic-context =
1209 | (js--syntactic-context-from-pstate open-items)
1210 | for class-style in filtered-class-styles
1211 | if (and (memq syntactic-context
1212 | (plist-get class-style :contexts))
1213 | (looking-at (plist-get class-style
1214 | :class-decl)))
1215 | do (goto-char (match-end 0))
1216 | and return
1217 | (make-js--pitem
1218 | :paren-depth orig-depth
1219 | :h-begin orig-match-start
1220 | :type class-style
1221 | :name (js--split-name
1222 | (match-string-no-properties 1))))))
1223 |
1224 | do (js--ensure-cache--update-parse)
1225 | and do (push it open-items)
1226 | and do (put-text-property
1227 | (1- (point)) (point) 'js--pstate open-items)
1228 | else do (goto-char orig-match-end))
1229 |
1230 | (goto-char limit)
1231 | (js--ensure-cache--update-parse)
1232 | (setq js--cache-end limit)
1233 | (setq js--last-parse-pos limit)
1234 | (setq js--state-at-last-parse-pos open-items)
1235 | )))))
1236 |
1237 | (defun js--end-of-defun-flat ()
1238 | "Helper function for `js-end-of-defun'."
1239 | (loop while (js--re-search-forward "}" nil t)
1240 | do (js--ensure-cache)
1241 | if (get-text-property (1- (point)) 'js--pend)
1242 | if (eq 'function (js--pitem-type it))
1243 | return t
1244 | finally do (goto-char (point-max))))
1245 |
1246 | (defun js--end-of-defun-nested ()
1247 | "Helper function for `js-end-of-defun'."
1248 | (message "test")
1249 | (let* (pitem
1250 | (this-end (save-excursion
1251 | (and (setq pitem (js--beginning-of-defun-nested))
1252 | (js--pitem-goto-h-end pitem)
1253 | (progn (backward-char)
1254 | (forward-list)
1255 | (point)))))
1256 | found)
1257 |
1258 | (if (and this-end (< (point) this-end))
1259 | ;; We're already inside a function; just go to its end.
1260 | (goto-char this-end)
1261 |
1262 | ;; Otherwise, go to the end of the next function...
1263 | (while (and (js--re-search-forward "\\_" nil t)
1264 | (not (setq found (progn
1265 | (goto-char (match-beginning 0))
1266 | (js--forward-function-decl))))))
1267 |
1268 | (if found (forward-list)
1269 | ;; ... or eob.
1270 | (goto-char (point-max))))))
1271 |
1272 | (defun js-end-of-defun (&optional arg)
1273 | "Value of `end-of-defun-function' for `js-mode'."
1274 | (setq arg (or arg 1))
1275 | (while (and (not (bobp)) (< arg 0))
1276 | (incf arg)
1277 | (js-beginning-of-defun)
1278 | (js-beginning-of-defun)
1279 | (unless (bobp)
1280 | (js-end-of-defun)))
1281 |
1282 | (while (> arg 0)
1283 | (decf arg)
1284 | ;; look for function backward. if we're inside it, go to that
1285 | ;; function's end. otherwise, search for the next function's end and
1286 | ;; go there
1287 | (if js-flat-functions
1288 | (js--end-of-defun-flat)
1289 |
1290 | ;; if we're doing nested functions, see whether we're in the
1291 | ;; prologue. If we are, go to the end of the function; otherwise,
1292 | ;; call js--end-of-defun-nested to do the real work
1293 | (let ((prologue-begin (js--function-prologue-beginning)))
1294 | (cond ((and prologue-begin (<= prologue-begin (point)))
1295 | (goto-char prologue-begin)
1296 | (re-search-forward "\\_"
1507 | (1 font-lock-constant-face))
1508 |
1509 | ;; Highlights class being declared, in parts
1510 | (js--class-decl-matcher
1511 | ,(concat "\\(" js--name-re "\\)\\(?:\\.\\|.*$\\)")
1512 | (goto-char (match-beginning 1))
1513 | nil
1514 | (1 font-lock-type-face))
1515 |
1516 | ;; Highlights parent class, in parts, if available
1517 | (js--class-decl-matcher
1518 | ,(concat "\\(" js--name-re "\\)\\(?:\\.\\|.*$\\)")
1519 | (if (match-beginning 2)
1520 | (progn
1521 | (setq js--tmp-location (match-end 2))
1522 | (goto-char js--tmp-location)
1523 | (insert "=")
1524 | (goto-char (match-beginning 2)))
1525 | (setq js--tmp-location nil)
1526 | (goto-char (point-at-eol)))
1527 | (when js--tmp-location
1528 | (save-excursion
1529 | (goto-char js--tmp-location)
1530 | (delete-char 1)))
1531 | (1 font-lock-type-face))
1532 |
1533 | ;; Highlights parent class
1534 | (js--class-decl-matcher
1535 | (2 font-lock-type-face nil t))
1536 |
1537 | ;; Dojo needs its own matcher to override the string highlighting
1538 | (,(js--make-framework-matcher
1539 | 'dojo
1540 | "^\\s-*dojo\\.declare\\s-*(\""
1541 | "\\(" js--dotted-name-re "\\)"
1542 | "\\(?:\"\\s-*,\\s-*\\(" js--dotted-name-re "\\)\\)?")
1543 | (1 font-lock-type-face t)
1544 | (2 font-lock-type-face nil t))
1545 |
1546 | ;; Match Dojo base classes. Of course Mojo has to be different
1547 | ;; from everything else under the sun...
1548 | (,(js--make-framework-matcher
1549 | 'dojo
1550 | "^\\s-*dojo\\.declare\\s-*(\""
1551 | "\\(" js--dotted-name-re "\\)\"\\s-*,\\s-*\\[")
1552 | ,(concat "[[,]\\s-*\\(" js--dotted-name-re "\\)\\s-*"
1553 | "\\(?:\\].*$\\)?")
1554 | (backward-char)
1555 | (end-of-line)
1556 | (1 font-lock-type-face))
1557 |
1558 | ;; continued Dojo base-class list
1559 | (,(js--make-framework-matcher
1560 | 'dojo
1561 | "^\\s-*" js--dotted-name-re "\\s-*[],]")
1562 | ,(concat "\\(" js--dotted-name-re "\\)"
1563 | "\\s-*\\(?:\\].*$\\)?")
1564 | (if (save-excursion (backward-char)
1565 | (js--inside-dojo-class-list-p))
1566 | (forward-symbol -1)
1567 | (end-of-line))
1568 | (end-of-line)
1569 | (1 font-lock-type-face))
1570 |
1571 | ;; variable declarations
1572 | ,(list
1573 | (concat "\\_<\\(const\\|var\\|let\\)\\_>\\|" js--basic-type-re)
1574 | (list #'js--variable-decl-matcher nil nil nil))
1575 |
1576 | ;; class instantiation
1577 | ,(list
1578 | (concat "\\_\\s-+\\(" js--dotted-name-re "\\)")
1579 | (list 1 'font-lock-type-face))
1580 |
1581 | ;; instanceof
1582 | ,(list
1583 | (concat "\\_\\s-+\\(" js--dotted-name-re "\\)")
1584 | (list 1 'font-lock-type-face))
1585 |
1586 | ;; formal parameters
1587 | ,(list
1588 | (concat
1589 | "\\_\\(\\s-+" js--name-re "\\)?\\s-*(\\s-*"
1590 | js--name-start-re)
1591 | (list (concat "\\(" js--name-re "\\)\\(\\s-*).*\\)?")
1592 | '(backward-char)
1593 | '(end-of-line)
1594 | '(1 font-lock-variable-name-face)))
1595 |
1596 | ;; continued formal parameter list
1597 | ,(list
1598 | (concat
1599 | "^\\s-*" js--name-re "\\s-*[,)]")
1600 | (list js--name-re
1601 | '(if (save-excursion (backward-char)
1602 | (js--inside-param-list-p))
1603 | (forward-symbol -1)
1604 | (end-of-line))
1605 | '(end-of-line)
1606 | '(0 font-lock-variable-name-face))))
1607 | "Level three font lock for `js-mode'.")
1608 |
1609 | (defun js--inside-pitem-p (pitem)
1610 | "Return whether point is inside the given pitem's header or body."
1611 | (js--ensure-cache)
1612 | (assert (js--pitem-h-begin pitem))
1613 | (assert (js--pitem-paren-depth pitem))
1614 |
1615 | (and (> (point) (js--pitem-h-begin pitem))
1616 | (or (null (js--pitem-b-end pitem))
1617 | (> (js--pitem-b-end pitem) (point)))))
1618 |
1619 | (defun js--parse-state-at-point ()
1620 | "Parse the JavaScript program state at point.
1621 | Return a list of `js--pitem' instances that apply to point, most
1622 | specific first. In the worst case, the current toplevel instance
1623 | will be returned."
1624 | (save-excursion
1625 | (save-restriction
1626 | (widen)
1627 | (js--ensure-cache)
1628 | (let ((pstate (or (save-excursion
1629 | (js--backward-pstate))
1630 | (list js--initial-pitem))))
1631 |
1632 | ;; Loop until we either hit a pitem at BOB or pitem ends after
1633 | ;; point (or at point if we're at eob)
1634 | (loop for pitem = (car pstate)
1635 | until (or (eq (js--pitem-type pitem)
1636 | 'toplevel)
1637 | (js--inside-pitem-p pitem))
1638 | do (pop pstate))
1639 |
1640 | pstate))))
1641 |
1642 | (defun js--syntactic-context-from-pstate (pstate)
1643 | "Return the JavaScript syntactic context corresponding to PSTATE."
1644 | (let ((type (js--pitem-type (car pstate))))
1645 | (cond ((memq type '(function macro))
1646 | type)
1647 | ((consp type)
1648 | 'class)
1649 | (t 'toplevel))))
1650 |
1651 | (defun js-syntactic-context ()
1652 | "Return the JavaScript syntactic context at point.
1653 | When called interatively, also display a message with that
1654 | context."
1655 | (interactive)
1656 | (let* ((syntactic-context (js--syntactic-context-from-pstate
1657 | (js--parse-state-at-point))))
1658 |
1659 | (when (called-interactively-p 'interactive)
1660 | (message "Syntactic context: %s" syntactic-context))
1661 |
1662 | syntactic-context))
1663 |
1664 | (defun js--class-decl-matcher (limit)
1665 | "Font lock function used by `js-mode'.
1666 | This performs fontification according to `js--class-styles'."
1667 | (loop initially (js--ensure-cache limit)
1668 | while (re-search-forward js--quick-match-re limit t)
1669 | for orig-end = (match-end 0)
1670 | do (goto-char (match-beginning 0))
1671 | if (loop for style in js--class-styles
1672 | for decl-re = (plist-get style :class-decl)
1673 | if (and (memq (plist-get style :framework)
1674 | js-enabled-frameworks)
1675 | (memq (js-syntactic-context)
1676 | (plist-get style :contexts))
1677 | decl-re
1678 | (looking-at decl-re))
1679 | do (goto-char (match-end 0))
1680 | and return t)
1681 | return t
1682 | else do (goto-char orig-end)))
1683 |
1684 | (defconst js--font-lock-keywords
1685 | '(js--font-lock-keywords-3 js--font-lock-keywords-1
1686 | js--font-lock-keywords-2
1687 | js--font-lock-keywords-3)
1688 | "Font lock keywords for `js-mode'. See `font-lock-keywords'.")
1689 |
1690 | ;; XXX: Javascript can continue a regexp literal across lines so long
1691 | ;; as the newline is escaped with \. Account for that in the regexp
1692 | ;; below.
1693 | (eval-and-compile
1694 | (defconst js--regexp-literal
1695 | "[=(,:]\\(?:\\s-\\|\n\\)*\\(/\\)\\(?:\\\\.\\|[^/*\\]\\)\\(?:\\\\.\\|[^/\\]\\)*\\(/\\)"
1696 | "Regexp matching a JavaScript regular expression literal.
1697 | Match groups 1 and 2 are the characters forming the beginning and
1698 | end of the literal."))
1699 |
1700 | (defconst js-font-lock-syntactic-keywords
1701 | `((,js--regexp-literal (1 "|") (2 "|")))
1702 | "Syntactic font lock keywords matching regexps in JavaScript.
1703 | See `font-lock-keywords'.")
1704 |
1705 | ;;; Indentation
1706 |
1707 | (defconst js--possibly-braceless-keyword-re
1708 | (js--regexp-opt-symbol
1709 | '("catch" "do" "else" "finally" "for" "if" "try" "while" "with"
1710 | "each"))
1711 | "Regexp matching keywords optionally followed by an opening brace.")
1712 |
1713 | (defconst js--indent-operator-re
1714 | (concat "[-+*/%<>=&^|?:]\\([^-+*/]\\|$\\)\\|"
1715 | (js--regexp-opt-symbol '("in" "instanceof")))
1716 | "Regexp matching operators that affect indentation of continued expressions.")
1717 |
1718 | (defconst js--indent-brace-re
1719 | "[[({]"
1720 | "Regexp matching opening braces that affect indentation.")
1721 |
1722 | (defconst js--skip-newlines-re
1723 | "[ \t\n]*"
1724 | "Regexp matching any amount of trailing whitespace and newlines.")
1725 |
1726 | (defun js--looking-at-operator-p ()
1727 | "Return non-nil if point is on a JavaScript operator, other than a comma."
1728 | (save-match-data
1729 | (and (looking-at js--indent-operator-re)
1730 | (or (not (= (following-char) ?\:))
1731 | (save-excursion
1732 | (and (js--re-search-backward "[?:{]\\|\\_" nil t)
1733 | (= (following-char) ?\?)))))))
1734 |
1735 |
1736 | (defun js--continued-expression-p ()
1737 | "Return non-nil if the current line continues an expression."
1738 | (save-excursion
1739 | (back-to-indentation)
1740 | (or (js--looking-at-operator-p)
1741 | (and (js--re-search-backward "\n" nil t)
1742 | (progn
1743 | (skip-chars-backward " \t")
1744 | (or (bobp) (backward-char))
1745 | (and (> (point) (point-min))
1746 | (save-excursion (backward-char) (not (looking-at "[/*]/")))
1747 | (js--looking-at-operator-p)
1748 | (and (progn (backward-char)
1749 | (not (looking-at "++\\|--\\|/[/*]"))))))))))
1750 |
1751 |
1752 | (defun js--end-of-do-while-loop-p ()
1753 | "Return non-nil if point is on the \"while\" of a do-while statement.
1754 | Otherwise, return nil. A braceless do-while statement spanning
1755 | several lines requires that the start of the loop is indented to
1756 | the same column as the current line."
1757 | (interactive)
1758 | (save-excursion
1759 | (save-match-data
1760 | (when (looking-at "\\s-*\\_")
1761 | (if (save-excursion
1762 | (skip-chars-backward (concat js--skip-newlines-re "}"))
1763 | (looking-at (concat js--skip-newlines-re "}")))
1764 | (save-excursion
1765 | (backward-list) (forward-symbol -1) (looking-at "\\_"))
1766 | (js--re-search-backward "\\_" (point-at-bol) t)
1767 | (or (looking-at "\\_")
1768 | (let ((saved-indent (current-indentation)))
1769 | (while (and (js--re-search-backward "^\\s-*\\_<" nil t)
1770 | (/= (current-indentation) saved-indent)))
1771 | (and (looking-at "\\s-*\\_")
1772 | (not (js--re-search-forward
1773 | "\\_" (point-at-eol) t))
1774 | (= (current-indentation) saved-indent)))))))))
1775 |
1776 |
1777 | (defun js--backward-whitespace ()
1778 | "Helper function for `js--proper-indentation'.
1779 | Skip backwards over whitespace and comments."
1780 | (let ((rv nil))
1781 | (when (js--looking-back "[ \t\n]")
1782 | (setq rv t)
1783 | (js--re-search-backward (concat "[^ \t\n]" js--skip-newlines-re)
1784 | (point-min) t)
1785 | (forward-char))
1786 | rv))
1787 |
1788 | (defun js--backward-sexp ()
1789 | "Helper function for `js--proper-indentation'.
1790 | Go backwards over matched braces, rather than whole expressions.
1791 | Only skip over strings while looking for braces.
1792 | Functionality does not exactly match backward-sexp."
1793 | (let ((brackets 0)
1794 | (rv nil))
1795 | (while (js--looking-back (concat "[]})]" js--skip-newlines-re))
1796 | (setq rv t)
1797 | (js--re-search-backward (concat "[]})]"
1798 | js--skip-newlines-re)
1799 | (point-min) t)
1800 | (cond
1801 | ((= (following-char) ?\])
1802 | (setq brackets (1+ brackets))
1803 | (while (/= brackets 0)
1804 | (js--re-search-backward "[][]" (point-min) t)
1805 | (cond
1806 | ((= (following-char) ?\])
1807 | (setq brackets (1+ brackets)))
1808 | ((= (following-char) ?\[)
1809 | (setq brackets (1- brackets))))))
1810 |
1811 | ((= (following-char) ?\})
1812 | (setq brackets (1+ brackets))
1813 | (while (/= brackets 0)
1814 | (js--re-search-backward "[}{]" (point-min) t)
1815 | (cond
1816 | ((= (following-char) ?\})
1817 | (setq brackets (1+ brackets)))
1818 | ((= (following-char) ?\{)
1819 | (setq brackets (1- brackets))))))
1820 |
1821 | ((= (following-char) ?\))
1822 | (setq brackets (1+ brackets))
1823 | (while (/= brackets 0)
1824 | (js--re-search-backward "[)(]" (point-min) t)
1825 | (cond
1826 | ((= (following-char) ?\))
1827 | (setq brackets (1+ brackets)))
1828 | ((= (following-char) ?\()
1829 | (setq brackets (1- brackets))))))))
1830 | rv))
1831 |
1832 | (defun js--backward-clean ()
1833 | "Helper function for `js--proper-indentation'.
1834 | Calls js--backward-sexp and js--backward-whitespace until they are done."
1835 | (let ((rv nil))
1836 | (while (or (js--backward-whitespace) (js--backward-sexp))
1837 | (setq rv t))
1838 | rv))
1839 |
1840 | (defun js--ctrl-statement-indentation ()
1841 | "Helper function for `js--proper-indentation'.
1842 | Return the proper indentation of the current line if it starts
1843 | the body of a control statement without braces; otherwise, return
1844 | nil."
1845 | (save-excursion
1846 | (back-to-indentation)
1847 | (when (save-excursion
1848 | (and (not (eq (point-at-bol) (point-min)))
1849 | (not (= (following-char) ?\{))
1850 | (progn
1851 | (js--re-search-backward "[[:graph:]]" nil t)
1852 | (or (eobp) (forward-char))
1853 | (when (= (char-before) ?\)) (backward-list))
1854 | (skip-syntax-backward " ")
1855 | (skip-syntax-backward "w_")
1856 | (looking-at js--possibly-braceless-keyword-re))
1857 | (not (js--end-of-do-while-loop-p))))
1858 | (save-excursion
1859 | (goto-char (match-beginning 0))
1860 | (+ (current-indentation) js-indent-level)))))
1861 |
1862 | (defun js--get-c-offset (symbol anchor)
1863 | (let ((c-offsets-alist
1864 | (list (cons 'c js-comment-lineup-func))))
1865 | (c-get-syntactic-indentation (list (cons symbol anchor)))))
1866 |
1867 | (defun js--proper-indentation (parse-status)
1868 | "Return the proper indentation for the current line."
1869 | (save-excursion
1870 | (back-to-indentation)
1871 | (cond
1872 |
1873 | ;;comma-first
1874 | ((and (not js-lazy-commas)
1875 | (= (following-char) ?\,))
1876 | (let ((spos
1877 | (save-excursion
1878 | (js--backward-clean)
1879 | (cond
1880 |
1881 | ((js--looking-back (concat "[,([{].*" js--skip-newlines-re))
1882 | (js--re-search-backward (concat "[,([{].*"
1883 | js--skip-newlines-re)
1884 | (point-min) t)
1885 | (current-column))
1886 |
1887 | ((js--looking-back (concat "\\.*" js--skip-newlines-re))
1888 | (js--re-search-backward (concat "\\.*"
1889 | js--skip-newlines-re)
1890 | (point-min) t)
1891 | (+ (current-column) 2))
1892 |
1893 | ((js--looking-back (concat "\\.*"
1894 | js--skip-newlines-re))
1895 | (js--re-search-backward (concat "\\.*"
1896 | js--skip-newlines-re)
1897 | (point-min) t)
1898 | (+ (current-column) 5))
1899 | (t
1900 | nil)))))
1901 | (if spos
1902 | spos
1903 | (+ js-indent-level js-expr-indent-offset))))
1904 |
1905 | ;;dot-first
1906 | ((and (not js-lazy-dots)
1907 | (= (following-char) ?\.))
1908 | (save-excursion
1909 | (js--backward-clean)
1910 | (if (not (js--looking-back (concat "^[ \t]*\\([]})]+\\|.*\\..*\\)"
1911 | js--skip-newlines-re)))
1912 | (progn
1913 | (js--re-search-backward (concat "\\<[^ \t]+" js--skip-newlines-re)
1914 | (point-min) t)
1915 | (js--re-search-backward "^" (point-min) t)
1916 | (back-to-indentation)
1917 | (+ (current-column) js-indent-level))
1918 | (progn
1919 | (js--re-search-backward (concat "\\..*" js--skip-newlines-re)
1920 | (point-min) t)
1921 | (current-column)))))
1922 |
1923 | ;;operator-first
1924 | ((and (not js-lazy-operators)
1925 | (looking-at js--indent-operator-re))
1926 | (let ((spos
1927 | (save-excursion
1928 | (js--backward-clean)
1929 | (cond
1930 | ((js--looking-back (concat "\\("
1931 | js--indent-brace-re
1932 | "\\|=\\).*"))
1933 | (js--re-search-backward (concat "\\("
1934 | js--indent-brace-re
1935 | "\\|=\\).*")
1936 | (point-min) t)
1937 | (current-column))
1938 |
1939 | ((js--looking-back (concat "^[^+*/-]*"
1940 | js--indent-operator-re ".*"))
1941 | (js--re-search-backward (concat "^[^+*/-]*"
1942 | js--indent-operator-re ".*")
1943 | (point-min) t)
1944 | (js--re-search-forward js--indent-operator-re nil t)
1945 | (js--re-search-backward js--indent-operator-re (point-min) t)
1946 | (current-column))
1947 |
1948 | (t
1949 | nil)))))
1950 | (if spos
1951 | spos
1952 | (+ js-indent-level js-expr-indent-offset))))
1953 |
1954 | ;;lazy comma-first
1955 | ((and js-lazy-commas
1956 | (= (following-char) ?\,))
1957 | (save-excursion
1958 | (js--backward-sexp)
1959 | (cond
1960 |
1961 | ((js--looking-back (concat "^[ \t]*,.*" js--skip-newlines-re))
1962 | (js--re-search-backward (concat "^[ \t],.*" js--skip-newlines-re)
1963 | (point-min) t)
1964 | (back-to-indentation)
1965 | (current-column))
1966 |
1967 | ((looking-back (concat "^[ \t]*[^ \t\n].*" js--skip-newlines-re))
1968 | (re-search-backward (concat "^[ \t]*[^ \t\n].*" js--skip-newlines-re)
1969 | (point-min) t)
1970 | (back-to-indentation)
1971 | (- (current-column) 2))
1972 |
1973 | (t
1974 | (+ js-indent-level js-expr-indent-offset)))))
1975 |
1976 | ;;lazy dot-first
1977 | ((and js-lazy-dots
1978 | (= (following-char) ?\.))
1979 | (save-excursion
1980 | (js--backward-sexp)
1981 | (if (looking-back (concat "^[ \t]*[^ \t\n].*" js--skip-newlines-re))
1982 | (progn
1983 | (re-search-backward (concat "^[ \t]*[^ \t\n].*"
1984 | js--skip-newlines-re)
1985 | (point-min) t)
1986 | (back-to-indentation)
1987 | (+ (current-column) js-indent-level))
1988 | (+ js-indent-level js-expr-indent-offset))))
1989 |
1990 | ;;lazy operator-first
1991 | ((and js-lazy-operators
1992 | (looking-at js--indent-operator-re))
1993 | (save-excursion
1994 | (js--backward-sexp)
1995 | (if (looking-back (concat "^[ \t]*[^ \t\n].*" js--skip-newlines-re))
1996 | (progn
1997 | (re-search-backward (concat "^[ \t]*[^ \t\n].*"
1998 | js--skip-newlines-re)
1999 | (point-min) t)
2000 | (back-to-indentation)
2001 | (- (current-column) 2))
2002 | (+ js-indent-level js-expr-indent-offset))))
2003 |
2004 | ;;var special case for non-comma-first continued var statements
2005 | ((and (looking-at "[^]})]")
2006 | (js2-node-at-point)
2007 | (js2-node-parent (js2-node-at-point))
2008 | (js2-node-type (js2-node-parent (js2-node-at-point)))
2009 | (= js2-VAR (js2-node-type (js2-node-parent (js2-node-at-point)))))
2010 | (save-excursion
2011 | (js--re-search-backward "\\" (point-min) t)
2012 | (+ (current-column) 4)))
2013 |
2014 | ((nth 4 parse-status)
2015 | (js--get-c-offset 'c (nth 8 parse-status)))
2016 | ((nth 8 parse-status) 0) ; inside string
2017 | ((js--ctrl-statement-indentation))
2018 | ((eq (char-after) ?#) 0)
2019 | ((save-excursion (js--beginning-of-macro)) 4)
2020 |
2021 | ((nth 1 parse-status)
2022 | ;; A single closing paren/bracket should be indented at the
2023 | ;; same level as the opening statement. Same goes for
2024 | ;; "case" and "default".
2025 | (let ((same-indent-p (looking-at
2026 | "[]})]\\|\\_\\|\\_"))
2027 | (continued-expr-p (js--continued-expression-p)))
2028 | (goto-char (nth 1 parse-status)) ; go to the opening char
2029 | (if (looking-at "[({[]\\s-*\\(/[/*]\\|$\\)")
2030 | (progn ; nothing following the opening paren/bracket
2031 | (skip-syntax-backward " ")
2032 | (when (eq (char-before) ?\)) (backward-list))
2033 | (back-to-indentation)
2034 | (cond (same-indent-p
2035 | (current-column))
2036 | (continued-expr-p
2037 | (+ (current-column) (* 2 js-indent-level)
2038 | js-expr-indent-offset))
2039 | (t
2040 | (+ (current-column) js-indent-level
2041 | (case (char-after (nth 1 parse-status))
2042 | (?\( js-paren-indent-offset)
2043 | (?\[ js-square-indent-offset)
2044 | (?\{ js-curly-indent-offset))))))
2045 | ;; If there is something following the opening
2046 | ;; paren/bracket, everything else should be indented at
2047 | ;; the same level.
2048 | (unless same-indent-p
2049 | (forward-char)
2050 | (skip-chars-forward " \t"))
2051 | (current-column))))
2052 |
2053 | ((js--continued-expression-p)
2054 | (+ js-indent-level js-expr-indent-offset))
2055 | (t 0))))
2056 |
2057 | (defun js-indent-line ()
2058 | "Indent the current line as JavaScript."
2059 | (interactive)
2060 | (save-restriction
2061 | (widen)
2062 | (let* ((parse-status
2063 | (save-excursion (syntax-ppss (point-at-bol))))
2064 | (offset (- (current-column) (current-indentation))))
2065 | (indent-line-to (js--proper-indentation parse-status))
2066 | (when (> offset 0) (forward-char offset)))))
2067 |
2068 | ;;; Filling
2069 |
2070 | (defun js-c-fill-paragraph (&optional justify)
2071 | "Fill the paragraph with `c-fill-paragraph'."
2072 | (interactive "*P")
2073 | (flet ((c-forward-sws
2074 | (&optional limit)
2075 | (js--forward-syntactic-ws limit))
2076 | (c-backward-sws
2077 | (&optional limit)
2078 | (js--backward-syntactic-ws limit))
2079 | (c-beginning-of-macro
2080 | (&optional limit)
2081 | (js--beginning-of-macro limit)))
2082 | (let ((fill-paragraph-function 'c-fill-paragraph))
2083 | (c-fill-paragraph justify))))
2084 |
2085 | ;;; Type database and Imenu
2086 |
2087 | ;; We maintain a cache of semantic information, i.e., the classes and
2088 | ;; functions we've encountered so far. In order to avoid having to
2089 | ;; re-parse the buffer on every change, we cache the parse state at
2090 | ;; each interesting point in the buffer. Each parse state is a
2091 | ;; modified copy of the previous one, or in the case of the first
2092 | ;; parse state, the empty state.
2093 | ;;
2094 | ;; The parse state itself is just a stack of js--pitem
2095 | ;; instances. It starts off containing one element that is never
2096 | ;; closed, that is initially js--initial-pitem.
2097 | ;;
2098 |
2099 |
2100 | (defun js--pitem-format (pitem)
2101 | (let ((name (js--pitem-name pitem))
2102 | (type (js--pitem-type pitem)))
2103 |
2104 | (format "name:%S type:%S"
2105 | name
2106 | (if (atom type)
2107 | type
2108 | (plist-get type :name)))))
2109 |
2110 | (defun js--make-merged-item (item child name-parts)
2111 | "Helper function for `js--splice-into-items'.
2112 | Return a new item that is the result of merging CHILD into
2113 | ITEM. NAME-PARTS is a list of parts of the name of CHILD
2114 | that we haven't consumed yet."
2115 | (js--debug "js--make-merged-item: {%s} into {%s}"
2116 | (js--pitem-format child)
2117 | (js--pitem-format item))
2118 |
2119 | ;; If the item we're merging into isn't a class, make it into one
2120 | (unless (consp (js--pitem-type item))
2121 | (js--debug "js--make-merged-item: changing dest into class")
2122 | (setq item (make-js--pitem
2123 | :children (list item)
2124 |
2125 | ;; Use the child's class-style if it's available
2126 | :type (if (atom (js--pitem-type child))
2127 | js--dummy-class-style
2128 | (js--pitem-type child))
2129 |
2130 | :name (js--pitem-strname item))))
2131 |
2132 | ;; Now we can merge either a function or a class into a class
2133 | (cons (cond
2134 | ((cdr name-parts)
2135 | (js--debug "js--make-merged-item: recursing")
2136 | ;; if we have more name-parts to go before we get to the
2137 | ;; bottom of the class hierarchy, call the merger
2138 | ;; recursively
2139 | (js--splice-into-items (car item) child
2140 | (cdr name-parts)))
2141 |
2142 | ((atom (js--pitem-type child))
2143 | (js--debug "js--make-merged-item: straight merge")
2144 | ;; Not merging a class, but something else, so just prepend
2145 | ;; it
2146 | (cons child (car item)))
2147 |
2148 | (t
2149 | ;; Otherwise, merge the new child's items into those
2150 | ;; of the new class
2151 | (js--debug "js--make-merged-item: merging class contents")
2152 | (append (car child) (car item))))
2153 | (cdr item)))
2154 |
2155 | (defun js--pitem-strname (pitem)
2156 | "Last part of the name of PITEM, as a string or symbol."
2157 | (let ((name (js--pitem-name pitem)))
2158 | (if (consp name)
2159 | (car (last name))
2160 | name)))
2161 |
2162 | (defun js--splice-into-items (items child name-parts)
2163 | "Splice CHILD into the `js--pitem' ITEMS at NAME-PARTS.
2164 | If a class doesn't exist in the tree, create it. Return
2165 | the new items list. NAME-PARTS is a list of strings given
2166 | the broken-down class name of the item to insert."
2167 |
2168 | (let ((top-name (car name-parts))
2169 | (item-ptr items)
2170 | new-items last-new-item new-cons)
2171 |
2172 | (js--debug "js--splice-into-items: name-parts: %S items:%S"
2173 | name-parts
2174 | (mapcar #'js--pitem-name items))
2175 |
2176 | (assert (stringp top-name))
2177 | (assert (> (length top-name) 0))
2178 |
2179 | ;; If top-name isn't found in items, then we build a copy of items
2180 | ;; and throw it away. But that's okay, since most of the time, we
2181 | ;; *will* find an instance.
2182 |
2183 | (while (and item-ptr
2184 | (cond ((equal (js--pitem-strname (car item-ptr)) top-name)
2185 | ;; Okay, we found an entry with the right name. Splice
2186 | ;; the merged item into the list...
2187 | (setq new-cons (cons (js--make-merged-item
2188 | (car item-ptr) child
2189 | name-parts)
2190 | (cdr item-ptr)))
2191 |
2192 | (if last-new-item
2193 | (setcdr last-new-item new-cons)
2194 | (setq new-items new-cons))
2195 |
2196 | ;; ...and terminate the loop
2197 | nil)
2198 |
2199 | (t
2200 | ;; Otherwise, copy the current cons and move onto the
2201 | ;; text. This is tricky; we keep track of the tail of
2202 | ;; the list that begins with new-items in
2203 | ;; last-new-item.
2204 | (setq new-cons (cons (car item-ptr) nil))
2205 | (if last-new-item
2206 | (setcdr last-new-item new-cons)
2207 | (setq new-items new-cons))
2208 | (setq last-new-item new-cons)
2209 |
2210 | ;; Go to the next cell in items
2211 | (setq item-ptr (cdr item-ptr))))))
2212 |
2213 | (if item-ptr
2214 | ;; Yay! We stopped because we found something, not because
2215 | ;; we ran out of items to search. Just return the new
2216 | ;; list.
2217 | (progn
2218 | (js--debug "search succeeded: %S" name-parts)
2219 | new-items)
2220 |
2221 | ;; We didn't find anything. If the child is a class and we don't
2222 | ;; have any classes to drill down into, just push that class;
2223 | ;; otherwise, make a fake class and carry on.
2224 | (js--debug "search failed: %S" name-parts)
2225 | (cons (if (cdr name-parts)
2226 | ;; We have name-parts left to process. Make a fake
2227 | ;; class for this particular part...
2228 | (make-js--pitem
2229 | ;; ...and recursively digest the rest of the name
2230 | :children (js--splice-into-items
2231 | nil child (cdr name-parts))
2232 | :type js--dummy-class-style
2233 | :name top-name)
2234 |
2235 | ;; Otherwise, this is the only name we have, so stick
2236 | ;; the item on the front of the list
2237 | child)
2238 | items))))
2239 |
2240 | (defun js--pitem-add-child (pitem child)
2241 | "Copy `js--pitem' PITEM, and push CHILD onto its list of children."
2242 | (assert (integerp (js--pitem-h-begin child)))
2243 | (assert (if (consp (js--pitem-name child))
2244 | (loop for part in (js--pitem-name child)
2245 | always (stringp part))
2246 | t))
2247 |
2248 | ;; This trick works because we know (based on our defstructs) that
2249 | ;; the child list is always the first element, and so the second
2250 | ;; element and beyond can be shared when we make our "copy".
2251 | (cons
2252 |
2253 | (let ((name (js--pitem-name child))
2254 | (type (js--pitem-type child)))
2255 |
2256 | (cond ((cdr-safe name) ; true if a list of at least two elements
2257 | ;; Use slow path because we need class lookup
2258 | (js--splice-into-items (car pitem) child name))
2259 |
2260 | ((and (consp type)
2261 | (plist-get type :prototype))
2262 |
2263 | ;; Use slow path because we need class merging. We know
2264 | ;; name is a list here because down in
2265 | ;; `js--ensure-cache', we made sure to only add
2266 | ;; class entries with lists for :name
2267 | (assert (consp name))
2268 | (js--splice-into-items (car pitem) child name))
2269 |
2270 | (t
2271 | ;; Fast path
2272 | (cons child (car pitem)))))
2273 |
2274 | (cdr pitem)))
2275 |
2276 | (defun js--maybe-make-marker (location)
2277 | "Return a marker for LOCATION if `imenu-use-markers' is non-nil."
2278 | (if imenu-use-markers
2279 | (set-marker (make-marker) location)
2280 | location))
2281 |
2282 | (defun js--pitems-to-imenu (pitems unknown-ctr)
2283 | "Convert PITEMS, a list of `js--pitem' structures, to imenu format."
2284 |
2285 | (let (imenu-items pitem pitem-type pitem-name subitems)
2286 |
2287 | (while (setq pitem (pop pitems))
2288 | (setq pitem-type (js--pitem-type pitem))
2289 | (setq pitem-name (js--pitem-strname pitem))
2290 | (when (eq pitem-name t)
2291 | (setq pitem-name (format "[unknown %s]"
2292 | (incf (car unknown-ctr)))))
2293 |
2294 | (cond
2295 | ((memq pitem-type '(function macro))
2296 | (assert (integerp (js--pitem-h-begin pitem)))
2297 | (push (cons pitem-name
2298 | (js--maybe-make-marker
2299 | (js--pitem-h-begin pitem)))
2300 | imenu-items))
2301 |
2302 | ((consp pitem-type) ; class definition
2303 | (setq subitems (js--pitems-to-imenu
2304 | (js--pitem-children pitem)
2305 | unknown-ctr))
2306 | (cond (subitems
2307 | (push (cons pitem-name subitems)
2308 | imenu-items))
2309 |
2310 | ((js--pitem-h-begin pitem)
2311 | (assert (integerp (js--pitem-h-begin pitem)))
2312 | (setq subitems (list
2313 | (cons "[empty]"
2314 | (js--maybe-make-marker
2315 | (js--pitem-h-begin pitem)))))
2316 | (push (cons pitem-name subitems)
2317 | imenu-items))))
2318 |
2319 | (t (error "Unknown item type: %S" pitem-type))))
2320 |
2321 | imenu-items))
2322 |
2323 | (defun js--imenu-create-index ()
2324 | "Return an imenu index for the current buffer."
2325 | (save-excursion
2326 | (save-restriction
2327 | (widen)
2328 | (goto-char (point-max))
2329 | (js--ensure-cache)
2330 | (assert (or (= (point-min) (point-max))
2331 | (eq js--last-parse-pos (point))))
2332 | (when js--last-parse-pos
2333 | (let ((state js--state-at-last-parse-pos)
2334 | (unknown-ctr (cons -1 nil)))
2335 |
2336 | ;; Make sure everything is closed
2337 | (while (cdr state)
2338 | (setq state
2339 | (cons (js--pitem-add-child (second state) (car state))
2340 | (cddr state))))
2341 |
2342 | (assert (= (length state) 1))
2343 |
2344 | ;; Convert the new-finalized state into what imenu expects
2345 | (js--pitems-to-imenu
2346 | (car (js--pitem-children state))
2347 | unknown-ctr))))))
2348 |
2349 | ;; Silence the compiler.
2350 | (defvar which-func-imenu-joiner-function)
2351 |
2352 | (defun js--which-func-joiner (parts)
2353 | (mapconcat #'identity parts "."))
2354 |
2355 | (defun js--imenu-to-flat (items prefix symbols)
2356 | (loop for item in items
2357 | if (imenu--subalist-p item)
2358 | do (js--imenu-to-flat
2359 | (cdr item) (concat prefix (car item) ".")
2360 | symbols)
2361 | else
2362 | do (let* ((name (concat prefix (car item)))
2363 | (name2 name)
2364 | (ctr 0))
2365 |
2366 | (while (gethash name2 symbols)
2367 | (setq name2 (format "%s<%d>" name (incf ctr))))
2368 |
2369 | (puthash name2 (cdr item) symbols))))
2370 |
2371 | (defun js--get-all-known-symbols ()
2372 | "Return a hash table of all JavaScript symbols.
2373 | This searches all existing `js-mode' buffers. Each key is the
2374 | name of a symbol (possibly disambiguated with , where N > 1),
2375 | and each value is a marker giving the location of that symbol."
2376 | (loop with symbols = (make-hash-table :test 'equal)
2377 | with imenu-use-markers = t
2378 | for buffer being the buffers
2379 | for imenu-index = (with-current-buffer buffer
2380 | (when (derived-mode-p 'js-mode)
2381 | (js--imenu-create-index)))
2382 | do (js--imenu-to-flat imenu-index "" symbols)
2383 | finally return symbols))
2384 |
2385 | (defvar js--symbol-history nil
2386 | "History of entered JavaScript symbols.")
2387 |
2388 | (defun js--read-symbol (symbols-table prompt &optional initial-input)
2389 | "Helper function for `js-find-symbol'.
2390 | Read a symbol from SYMBOLS-TABLE, which is a hash table like the
2391 | one from `js--get-all-known-symbols', using prompt PROMPT and
2392 | initial input INITIAL-INPUT. Return a cons of (SYMBOL-NAME
2393 | . LOCATION), where SYMBOL-NAME is a string and LOCATION is a
2394 | marker."
2395 | (unless ido-mode
2396 | (ido-mode 1)
2397 | (ido-mode -1))
2398 |
2399 | (let ((choice (ido-completing-read
2400 | prompt
2401 | (loop for key being the hash-keys of symbols-table
2402 | collect key)
2403 | nil t initial-input 'js--symbol-history)))
2404 | (cons choice (gethash choice symbols-table))))
2405 |
2406 | (defun js--guess-symbol-at-point ()
2407 | (let ((bounds (bounds-of-thing-at-point 'symbol)))
2408 | (when bounds
2409 | (save-excursion
2410 | (goto-char (car bounds))
2411 | (when (eq (char-before) ?.)
2412 | (backward-char)
2413 | (setf (car bounds) (point))))
2414 | (buffer-substring (car bounds) (cdr bounds)))))
2415 |
2416 | (defvar find-tag-marker-ring) ; etags
2417 |
2418 | (defun js-find-symbol (&optional arg)
2419 | "Read a JavaScript symbol and jump to it.
2420 | With a prefix argument, restrict symbols to those from the
2421 | current buffer. Pushes a mark onto the tag ring just like
2422 | `find-tag'."
2423 | (interactive "P")
2424 | (require 'etags)
2425 | (let (symbols marker)
2426 | (if (not arg)
2427 | (setq symbols (js--get-all-known-symbols))
2428 | (setq symbols (make-hash-table :test 'equal))
2429 | (js--imenu-to-flat (js--imenu-create-index)
2430 | "" symbols))
2431 |
2432 | (setq marker (cdr (js--read-symbol
2433 | symbols "Jump to: "
2434 | (js--guess-symbol-at-point))))
2435 |
2436 | (ring-insert find-tag-marker-ring (point-marker))
2437 | (switch-to-buffer (marker-buffer marker))
2438 | (push-mark)
2439 | (goto-char marker)))
2440 |
2441 | ;;; MozRepl integration
2442 |
2443 | (put 'js-moz-bad-rpc 'error-conditions '(error timeout))
2444 | (put 'js-moz-bad-rpc 'error-message "Mozilla RPC Error")
2445 |
2446 | (put 'js-js-error 'error-conditions '(error js-error))
2447 | (put 'js-js-error 'error-message "Javascript Error")
2448 |
2449 | (defun js--wait-for-matching-output
2450 | (process regexp timeout &optional start)
2451 | "Wait TIMEOUT seconds for PROCESS to output a match for REGEXP.
2452 | On timeout, return nil. On success, return t with match data
2453 | set. If START is non-nil, look for output starting from START.
2454 | Otherwise, use the current value of `process-mark'."
2455 | (with-current-buffer (process-buffer process)
2456 | (loop with start-pos = (or start
2457 | (marker-position (process-mark process)))
2458 | with end-time = (+ (float-time) timeout)
2459 | for time-left = (- end-time (float-time))
2460 | do (goto-char (point-max))
2461 | if (looking-back regexp start-pos) return t
2462 | while (> time-left 0)
2463 | do (accept-process-output process time-left nil t)
2464 | do (goto-char (process-mark process))
2465 | finally do (signal
2466 | 'js-moz-bad-rpc
2467 | (list (format "Timed out waiting for output matching %S" regexp))))))
2468 |
2469 | (defstruct js--js-handle
2470 | ;; Integer, mirrors the value we see in JS
2471 | (id nil :read-only t)
2472 |
2473 | ;; Process to which this thing belongs
2474 | (process nil :read-only t))
2475 |
2476 | (defun js--js-handle-expired-p (x)
2477 | (not (eq (js--js-handle-process x)
2478 | (inferior-moz-process))))
2479 |
2480 | (defvar js--js-references nil
2481 | "Maps Elisp JavaScript proxy objects to their JavaScript IDs.")
2482 |
2483 | (defvar js--js-process nil
2484 | "The most recent MozRepl process object.")
2485 |
2486 | (defvar js--js-gc-idle-timer nil
2487 | "Idle timer for cleaning up JS object references.")
2488 |
2489 | (defvar js--js-last-gcs-done nil)
2490 |
2491 | (defconst js--moz-interactor
2492 | (replace-regexp-in-string
2493 | "[ \n]+" " "
2494 | ; */" Make Emacs happy
2495 | "(function(repl) {
2496 | repl.defineInteractor('js', {
2497 | onStart: function onStart(repl) {
2498 | if(!repl._jsObjects) {
2499 | repl._jsObjects = {};
2500 | repl._jsLastID = 0;
2501 | repl._jsGC = this._jsGC;
2502 | }
2503 | this._input = '';
2504 | },
2505 |
2506 | _jsGC: function _jsGC(ids_in_use) {
2507 | var objects = this._jsObjects;
2508 | var keys = [];
2509 | var num_freed = 0;
2510 |
2511 | for(var pn in objects) {
2512 | keys.push(Number(pn));
2513 | }
2514 |
2515 | keys.sort(function(x, y) x - y);
2516 | ids_in_use.sort(function(x, y) x - y);
2517 | var i = 0;
2518 | var j = 0;
2519 |
2520 | while(i < ids_in_use.length && j < keys.length) {
2521 | var id = ids_in_use[i++];
2522 | while(j < keys.length && keys[j] !== id) {
2523 | var k_id = keys[j++];
2524 | delete objects[k_id];
2525 | ++num_freed;
2526 | }
2527 | ++j;
2528 | }
2529 |
2530 | while(j < keys.length) {
2531 | var k_id = keys[j++];
2532 | delete objects[k_id];
2533 | ++num_freed;
2534 | }
2535 |
2536 | return num_freed;
2537 | },
2538 |
2539 | _mkArray: function _mkArray() {
2540 | var result = [];
2541 | for(var i = 0; i < arguments.length; ++i) {
2542 | result.push(arguments[i]);
2543 | }
2544 | return result;
2545 | },
2546 |
2547 | _parsePropDescriptor: function _parsePropDescriptor(parts) {
2548 | if(typeof parts === 'string') {
2549 | parts = [ parts ];
2550 | }
2551 |
2552 | var obj = parts[0];
2553 | var start = 1;
2554 |
2555 | if(typeof obj === 'string') {
2556 | obj = window;
2557 | start = 0;
2558 | } else if(parts.length < 2) {
2559 | throw new Error('expected at least 2 arguments');
2560 | }
2561 |
2562 | for(var i = start; i < parts.length - 1; ++i) {
2563 | obj = obj[parts[i]];
2564 | }
2565 |
2566 | return [obj, parts[parts.length - 1]];
2567 | },
2568 |
2569 | _getProp: function _getProp(/*...*/) {
2570 | if(arguments.length === 0) {
2571 | throw new Error('no arguments supplied to getprop');
2572 | }
2573 |
2574 | if(arguments.length === 1 &&
2575 | (typeof arguments[0]) !== 'string')
2576 | {
2577 | return arguments[0];
2578 | }
2579 |
2580 | var [obj, propname] = this._parsePropDescriptor(arguments);
2581 | return obj[propname];
2582 | },
2583 |
2584 | _putProp: function _putProp(properties, value) {
2585 | var [obj, propname] = this._parsePropDescriptor(properties);
2586 | obj[propname] = value;
2587 | },
2588 |
2589 | _delProp: function _delProp(propname) {
2590 | var [obj, propname] = this._parsePropDescriptor(arguments);
2591 | delete obj[propname];
2592 | },
2593 |
2594 | _typeOf: function _typeOf(thing) {
2595 | return typeof thing;
2596 | },
2597 |
2598 | _callNew: function(constructor) {
2599 | if(typeof constructor === 'string')
2600 | {
2601 | constructor = window[constructor];
2602 | } else if(constructor.length === 1 &&
2603 | typeof constructor[0] !== 'string')
2604 | {
2605 | constructor = constructor[0];
2606 | } else {
2607 | var [obj,propname] = this._parsePropDescriptor(constructor);
2608 | constructor = obj[propname];
2609 | }
2610 |
2611 | /* Hacky, but should be robust */
2612 | var s = 'new constructor(';
2613 | for(var i = 1; i < arguments.length; ++i) {
2614 | if(i != 1) {
2615 | s += ',';
2616 | }
2617 |
2618 | s += 'arguments[' + i + ']';
2619 | }
2620 |
2621 | s += ')';
2622 | return eval(s);
2623 | },
2624 |
2625 | _callEval: function(thisobj, js) {
2626 | return eval.call(thisobj, js);
2627 | },
2628 |
2629 | getPrompt: function getPrompt(repl) {
2630 | return 'EVAL>'
2631 | },
2632 |
2633 | _lookupObject: function _lookupObject(repl, id) {
2634 | if(typeof id === 'string') {
2635 | switch(id) {
2636 | case 'global':
2637 | return window;
2638 | case 'nil':
2639 | return null;
2640 | case 't':
2641 | return true;
2642 | case 'false':
2643 | return false;
2644 | case 'undefined':
2645 | return undefined;
2646 | case 'repl':
2647 | return repl;
2648 | case 'interactor':
2649 | return this;
2650 | case 'NaN':
2651 | return NaN;
2652 | case 'Infinity':
2653 | return Infinity;
2654 | case '-Infinity':
2655 | return -Infinity;
2656 | default:
2657 | throw new Error('No object with special id:' + id);
2658 | }
2659 | }
2660 |
2661 | var ret = repl._jsObjects[id];
2662 | if(ret === undefined) {
2663 | throw new Error('No object with id:' + id + '(' + typeof id + ')');
2664 | }
2665 | return ret;
2666 | },
2667 |
2668 | _findOrAllocateObject: function _findOrAllocateObject(repl, value) {
2669 | if(typeof value !== 'object' && typeof value !== 'function') {
2670 | throw new Error('_findOrAllocateObject called on non-object('
2671 | + typeof(value) + '): '
2672 | + value)
2673 | }
2674 |
2675 | for(var id in repl._jsObjects) {
2676 | id = Number(id);
2677 | var obj = repl._jsObjects[id];
2678 | if(obj === value) {
2679 | return id;
2680 | }
2681 | }
2682 |
2683 | var id = ++repl._jsLastID;
2684 | repl._jsObjects[id] = value;
2685 | return id;
2686 | },
2687 |
2688 | _fixupList: function _fixupList(repl, list) {
2689 | for(var i = 0; i < list.length; ++i) {
2690 | if(list[i] instanceof Array) {
2691 | this._fixupList(repl, list[i]);
2692 | } else if(typeof list[i] === 'object') {
2693 | var obj = list[i];
2694 | if(obj.funcall) {
2695 | var parts = obj.funcall;
2696 | this._fixupList(repl, parts);
2697 | var [thisobj, func] = this._parseFunc(parts[0]);
2698 | list[i] = func.apply(thisobj, parts.slice(1));
2699 | } else if(obj.objid) {
2700 | list[i] = this._lookupObject(repl, obj.objid);
2701 | } else {
2702 | throw new Error('Unknown object type: ' + obj.toSource());
2703 | }
2704 | }
2705 | }
2706 | },
2707 |
2708 | _parseFunc: function(func) {
2709 | var thisobj = null;
2710 |
2711 | if(typeof func === 'string') {
2712 | func = window[func];
2713 | } else if(func instanceof Array) {
2714 | if(func.length === 1 && typeof func[0] !== 'string') {
2715 | func = func[0];
2716 | } else {
2717 | [thisobj, func] = this._parsePropDescriptor(func);
2718 | func = thisobj[func];
2719 | }
2720 | }
2721 |
2722 | return [thisobj,func];
2723 | },
2724 |
2725 | _encodeReturn: function(value, array_as_mv) {
2726 | var ret;
2727 |
2728 | if(value === null) {
2729 | ret = ['special', 'null'];
2730 | } else if(value === true) {
2731 | ret = ['special', 'true'];
2732 | } else if(value === false) {
2733 | ret = ['special', 'false'];
2734 | } else if(value === undefined) {
2735 | ret = ['special', 'undefined'];
2736 | } else if(typeof value === 'number') {
2737 | if(isNaN(value)) {
2738 | ret = ['special', 'NaN'];
2739 | } else if(value === Infinity) {
2740 | ret = ['special', 'Infinity'];
2741 | } else if(value === -Infinity) {
2742 | ret = ['special', '-Infinity'];
2743 | } else {
2744 | ret = ['atom', value];
2745 | }
2746 | } else if(typeof value === 'string') {
2747 | ret = ['atom', value];
2748 | } else if(array_as_mv && value instanceof Array) {
2749 | ret = ['array', value.map(this._encodeReturn, this)];
2750 | } else {
2751 | ret = ['objid', this._findOrAllocateObject(repl, value)];
2752 | }
2753 |
2754 | return ret;
2755 | },
2756 |
2757 | _handleInputLine: function _handleInputLine(repl, line) {
2758 | var ret;
2759 | var array_as_mv = false;
2760 |
2761 | try {
2762 | if(line[0] === '*') {
2763 | array_as_mv = true;
2764 | line = line.substring(1);
2765 | }
2766 | var parts = eval(line);
2767 | this._fixupList(repl, parts);
2768 | var [thisobj, func] = this._parseFunc(parts[0]);
2769 | ret = this._encodeReturn(
2770 | func.apply(thisobj, parts.slice(1)),
2771 | array_as_mv);
2772 | } catch(x) {
2773 | ret = ['error', x.toString() ];
2774 | }
2775 |
2776 | var JSON = Components.classes['@mozilla.org/dom/json;1'].createInstance(Components.interfaces.nsIJSON);
2777 | repl.print(JSON.encode(ret));
2778 | repl._prompt();
2779 | },
2780 |
2781 | handleInput: function handleInput(repl, chunk) {
2782 | this._input += chunk;
2783 | var match, line;
2784 | while(match = this._input.match(/.*\\n/)) {
2785 | line = match[0];
2786 |
2787 | if(line === 'EXIT\\n') {
2788 | repl.popInteractor();
2789 | repl._prompt();
2790 | return;
2791 | }
2792 |
2793 | this._input = this._input.substring(line.length);
2794 | this._handleInputLine(repl, line);
2795 | }
2796 | }
2797 | });
2798 | })
2799 | ")
2800 |
2801 | "String to set MozRepl up into a simple-minded evaluation mode.")
2802 |
2803 | (defun js--js-encode-value (x)
2804 | "Marshall the given value for JS.
2805 | Strings and numbers are JSON-encoded. Lists (including nil) are
2806 | made into JavaScript array literals and their contents encoded
2807 | with `js--js-encode-value'."
2808 | (cond ((stringp x) (json-encode-string x))
2809 | ((numberp x) (json-encode-number x))
2810 | ((symbolp x) (format "{objid:%S}" (symbol-name x)))
2811 | ((js--js-handle-p x)
2812 |
2813 | (when (js--js-handle-expired-p x)
2814 | (error "Stale JS handle"))
2815 |
2816 | (format "{objid:%s}" (js--js-handle-id x)))
2817 |
2818 | ((sequencep x)
2819 | (if (eq (car-safe x) 'js--funcall)
2820 | (format "{funcall:[%s]}"
2821 | (mapconcat #'js--js-encode-value (cdr x) ","))
2822 | (concat
2823 | "[" (mapconcat #'js--js-encode-value x ",") "]")))
2824 | (t
2825 | (error "Unrecognized item: %S" x))))
2826 |
2827 | (defconst js--js-prompt-regexp "\\(repl[0-9]*\\)> $")
2828 | (defconst js--js-repl-prompt-regexp "^EVAL>$")
2829 | (defvar js--js-repl-depth 0)
2830 |
2831 | (defun js--js-wait-for-eval-prompt ()
2832 | (js--wait-for-matching-output
2833 | (inferior-moz-process)
2834 | js--js-repl-prompt-regexp js-js-timeout
2835 |
2836 | ;; start matching against the beginning of the line in
2837 | ;; order to catch a prompt that's only partially arrived
2838 | (save-excursion (forward-line 0) (point))))
2839 |
2840 | (defun js--js-enter-repl ()
2841 | (inferior-moz-process) ; called for side-effect
2842 | (with-current-buffer inferior-moz-buffer
2843 | (goto-char (point-max))
2844 |
2845 | ;; Do some initialization the first time we see a process
2846 | (unless (eq (inferior-moz-process) js--js-process)
2847 | (setq js--js-process (inferior-moz-process))
2848 | (setq js--js-references (make-hash-table :test 'eq :weakness t))
2849 | (setq js--js-repl-depth 0)
2850 |
2851 | ;; Send interactor definition
2852 | (comint-send-string js--js-process js--moz-interactor)
2853 | (comint-send-string js--js-process
2854 | (concat "(" moz-repl-name ")\n"))
2855 | (js--wait-for-matching-output
2856 | (inferior-moz-process) js--js-prompt-regexp
2857 | js-js-timeout))
2858 |
2859 | ;; Sanity check
2860 | (when (looking-back js--js-prompt-regexp
2861 | (save-excursion (forward-line 0) (point)))
2862 | (setq js--js-repl-depth 0))
2863 |
2864 | (if (> js--js-repl-depth 0)
2865 | ;; If js--js-repl-depth > 0, we *should* be seeing an
2866 | ;; EVAL> prompt. If we don't, give Mozilla a chance to catch
2867 | ;; up with us.
2868 | (js--js-wait-for-eval-prompt)
2869 |
2870 | ;; Otherwise, tell Mozilla to enter the interactor mode
2871 | (insert (match-string-no-properties 1)
2872 | ".pushInteractor('js')")
2873 | (comint-send-input nil t)
2874 | (js--wait-for-matching-output
2875 | (inferior-moz-process) js--js-repl-prompt-regexp
2876 | js-js-timeout))
2877 |
2878 | (incf js--js-repl-depth)))
2879 |
2880 | (defun js--js-leave-repl ()
2881 | (assert (> js--js-repl-depth 0))
2882 | (when (= 0 (decf js--js-repl-depth))
2883 | (with-current-buffer inferior-moz-buffer
2884 | (goto-char (point-max))
2885 | (js--js-wait-for-eval-prompt)
2886 | (insert "EXIT")
2887 | (comint-send-input nil t)
2888 | (js--wait-for-matching-output
2889 | (inferior-moz-process) js--js-prompt-regexp
2890 | js-js-timeout))))
2891 |
2892 | (defsubst js--js-not (value)
2893 | (memq value '(nil null false undefined)))
2894 |
2895 | (defsubst js--js-true (value)
2896 | (not (js--js-not value)))
2897 |
2898 | (eval-and-compile
2899 | (defun js--optimize-arglist (arglist)
2900 | "Convert immediate js< and js! references to deferred ones."
2901 | (loop for item in arglist
2902 | if (eq (car-safe item) 'js<)
2903 | collect (append (list 'list ''js--funcall
2904 | '(list 'interactor "_getProp"))
2905 | (js--optimize-arglist (cdr item)))
2906 | else if (eq (car-safe item) 'js>)
2907 | collect (append (list 'list ''js--funcall
2908 | '(list 'interactor "_putProp"))
2909 |
2910 | (if (atom (cadr item))
2911 | (list (cadr item))
2912 | (list
2913 | (append
2914 | (list 'list ''js--funcall
2915 | '(list 'interactor "_mkArray"))
2916 | (js--optimize-arglist (cadr item)))))
2917 | (js--optimize-arglist (cddr item)))
2918 | else if (eq (car-safe item) 'js!)
2919 | collect (destructuring-bind (ignored function &rest body) item
2920 | (append (list 'list ''js--funcall
2921 | (if (consp function)
2922 | (cons 'list
2923 | (js--optimize-arglist function))
2924 | function))
2925 | (js--optimize-arglist body)))
2926 | else
2927 | collect item)))
2928 |
2929 | (defmacro js--js-get-service (class-name interface-name)
2930 | `(js! ("Components" "classes" ,class-name "getService")
2931 | (js< "Components" "interfaces" ,interface-name)))
2932 |
2933 | (defmacro js--js-create-instance (class-name interface-name)
2934 | `(js! ("Components" "classes" ,class-name "createInstance")
2935 | (js< "Components" "interfaces" ,interface-name)))
2936 |
2937 | (defmacro js--js-qi (object interface-name)
2938 | `(js! (,object "QueryInterface")
2939 | (js< "Components" "interfaces" ,interface-name)))
2940 |
2941 | (defmacro with-js (&rest forms)
2942 | "Run FORMS with the Mozilla repl set up for js commands.
2943 | Inside the lexical scope of `with-js', `js?', `js!',
2944 | `js-new', `js-eval', `js-list', `js<', `js>', `js-get-service',
2945 | `js-create-instance', and `js-qi' are defined."
2946 |
2947 | `(progn
2948 | (js--js-enter-repl)
2949 | (unwind-protect
2950 | (macrolet ((js? (&rest body) `(js--js-true ,@body))
2951 | (js! (function &rest body)
2952 | `(js--js-funcall
2953 | ,(if (consp function)
2954 | (cons 'list
2955 | (js--optimize-arglist function))
2956 | function)
2957 | ,@(js--optimize-arglist body)))
2958 |
2959 | (js-new (function &rest body)
2960 | `(js--js-new
2961 | ,(if (consp function)
2962 | (cons 'list
2963 | (js--optimize-arglist function))
2964 | function)
2965 | ,@body))
2966 |
2967 | (js-eval (thisobj js)
2968 | `(js--js-eval
2969 | ,@(js--optimize-arglist
2970 | (list thisobj js))))
2971 |
2972 | (js-list (&rest args)
2973 | `(js--js-list
2974 | ,@(js--optimize-arglist args)))
2975 |
2976 | (js-get-service (&rest args)
2977 | `(js--js-get-service
2978 | ,@(js--optimize-arglist args)))
2979 |
2980 | (js-create-instance (&rest args)
2981 | `(js--js-create-instance
2982 | ,@(js--optimize-arglist args)))
2983 |
2984 | (js-qi (&rest args)
2985 | `(js--js-qi
2986 | ,@(js--optimize-arglist args)))
2987 |
2988 | (js< (&rest body) `(js--js-get
2989 | ,@(js--optimize-arglist body)))
2990 | (js> (props value)
2991 | `(js--js-funcall
2992 | '(interactor "_putProp")
2993 | ,(if (consp props)
2994 | (cons 'list
2995 | (js--optimize-arglist props))
2996 | props)
2997 | ,@(js--optimize-arglist (list value))
2998 | ))
2999 | (js-handle? (arg) `(js--js-handle-p ,arg)))
3000 | ,@forms)
3001 | (js--js-leave-repl))))
3002 |
3003 | (defvar js--js-array-as-list nil
3004 | "Whether to listify any Array returned by a Mozilla function.
3005 | If nil, the whole Array is treated as a JS symbol.")
3006 |
3007 | (defun js--js-decode-retval (result)
3008 | (ecase (intern (first result))
3009 | (atom (second result))
3010 | (special (intern (second result)))
3011 | (array
3012 | (mapcar #'js--js-decode-retval (second result)))
3013 | (objid
3014 | (or (gethash (second result)
3015 | js--js-references)
3016 | (puthash (second result)
3017 | (make-js--js-handle
3018 | :id (second result)
3019 | :process (inferior-moz-process))
3020 | js--js-references)))
3021 |
3022 | (error (signal 'js-js-error (list (second result))))))
3023 |
3024 | (defun js--js-funcall (function &rest arguments)
3025 | "Call the Mozilla function FUNCTION with arguments ARGUMENTS.
3026 | If function is a string, look it up as a property on the global
3027 | object and use the global object for `this'.
3028 | If FUNCTION is a list with one element, use that element as the
3029 | function with the global object for `this', except that if that
3030 | single element is a string, look it up on the global object.
3031 | If FUNCTION is a list with more than one argument, use the list
3032 | up to the last value as a property descriptor and the last
3033 | argument as a function."
3034 |
3035 | (with-js
3036 | (let ((argstr (js--js-encode-value
3037 | (cons function arguments))))
3038 |
3039 | (with-current-buffer inferior-moz-buffer
3040 | ;; Actual funcall
3041 | (when js--js-array-as-list
3042 | (insert "*"))
3043 | (insert argstr)
3044 | (comint-send-input nil t)
3045 | (js--wait-for-matching-output
3046 | (inferior-moz-process) "EVAL>"
3047 | js-js-timeout)
3048 | (goto-char comint-last-input-end)
3049 |
3050 | ;; Read the result
3051 | (let* ((json-array-type 'list)
3052 | (result (prog1 (json-read)
3053 | (goto-char (point-max)))))
3054 | (js--js-decode-retval result))))))
3055 |
3056 | (defun js--js-new (constructor &rest arguments)
3057 | "Call CONSTRUCTOR as a constructor, with arguments ARGUMENTS.
3058 | CONSTRUCTOR is a JS handle, a string, or a list of these things."
3059 | (apply #'js--js-funcall
3060 | '(interactor "_callNew")
3061 | constructor arguments))
3062 |
3063 | (defun js--js-eval (thisobj js)
3064 | (js--js-funcall '(interactor "_callEval") thisobj js))
3065 |
3066 | (defun js--js-list (&rest arguments)
3067 | "Return a Lisp array resulting from evaluating each of ARGUMENTS."
3068 | (let ((js--js-array-as-list t))
3069 | (apply #'js--js-funcall '(interactor "_mkArray")
3070 | arguments)))
3071 |
3072 | (defun js--js-get (&rest props)
3073 | (apply #'js--js-funcall '(interactor "_getProp") props))
3074 |
3075 | (defun js--js-put (props value)
3076 | (js--js-funcall '(interactor "_putProp") props value))
3077 |
3078 | (defun js-gc (&optional force)
3079 | "Tell the repl about any objects we don't reference anymore.
3080 | With argument, run even if no intervening GC has happened."
3081 | (interactive)
3082 |
3083 | (when force
3084 | (setq js--js-last-gcs-done nil))
3085 |
3086 | (let ((this-gcs-done gcs-done) keys num)
3087 | (when (and js--js-references
3088 | (boundp 'inferior-moz-buffer)
3089 | (buffer-live-p inferior-moz-buffer)
3090 |
3091 | ;; Don't bother running unless we've had an intervening
3092 | ;; garbage collection; without a gc, nothing is deleted
3093 | ;; from the weak hash table, so it's pointless telling
3094 | ;; MozRepl about that references we still hold
3095 | (not (eq js--js-last-gcs-done this-gcs-done))
3096 |
3097 | ;; Are we looking at a normal prompt? Make sure not to
3098 | ;; interrupt the user if he's doing something
3099 | (with-current-buffer inferior-moz-buffer
3100 | (save-excursion
3101 | (goto-char (point-max))
3102 | (looking-back js--js-prompt-regexp
3103 | (save-excursion (forward-line 0) (point))))))
3104 |
3105 | (setq keys (loop for x being the hash-keys
3106 | of js--js-references
3107 | collect x))
3108 | (setq num (js--js-funcall '(repl "_jsGC") (or keys [])))
3109 |
3110 | (setq js--js-last-gcs-done this-gcs-done)
3111 | (when (called-interactively-p 'interactive)
3112 | (message "Cleaned %s entries" num))
3113 |
3114 | num)))
3115 |
3116 | (run-with-idle-timer 30 t #'js-gc)
3117 |
3118 | (defun js-eval (js)
3119 | "Evaluate the JavaScript in JS and return JSON-decoded result."
3120 | (interactive "MJavascript to evaluate: ")
3121 | (with-js
3122 | (let* ((content-window (js--js-content-window
3123 | (js--get-js-context)))
3124 | (result (js-eval content-window js)))
3125 | (when (called-interactively-p 'interactive)
3126 | (message "%s" (js! "String" result)))
3127 | result)))
3128 |
3129 | (defun js--get-tabs ()
3130 | "Enumerate all JavaScript contexts available.
3131 | Each context is a list:
3132 | (TITLE URL BROWSER TAB TABBROWSER) for content documents
3133 | (TITLE URL WINDOW) for windows
3134 |
3135 | All tabs of a given window are grouped together. The most recent
3136 | window is first. Within each window, the tabs are returned
3137 | left-to-right."
3138 | (with-js
3139 | (let (windows)
3140 |
3141 | (loop with window-mediator = (js! ("Components" "classes"
3142 | "@mozilla.org/appshell/window-mediator;1"
3143 | "getService")
3144 | (js< "Components" "interfaces"
3145 | "nsIWindowMediator"))
3146 | with enumerator = (js! (window-mediator "getEnumerator") nil)
3147 |
3148 | while (js? (js! (enumerator "hasMoreElements")))
3149 | for window = (js! (enumerator "getNext"))
3150 | for window-info = (js-list window
3151 | (js< window "document" "title")
3152 | (js! (window "location" "toString"))
3153 | (js< window "closed")
3154 | (js< window "windowState"))
3155 |
3156 | unless (or (js? (fourth window-info))
3157 | (eq (fifth window-info) 2))
3158 | do (push window-info windows))
3159 |
3160 | (loop for window-info in windows
3161 | for window = (first window-info)
3162 | collect (list (second window-info)
3163 | (third window-info)
3164 | window)
3165 |
3166 | for gbrowser = (js< window "gBrowser")
3167 | if (js-handle? gbrowser)
3168 | nconc (loop
3169 | for x below (js< gbrowser "browsers" "length")
3170 | collect (js-list (js< gbrowser
3171 | "browsers"
3172 | x
3173 | "contentDocument"
3174 | "title")
3175 |
3176 | (js! (gbrowser
3177 | "browsers"
3178 | x
3179 | "contentWindow"
3180 | "location"
3181 | "toString"))
3182 | (js< gbrowser
3183 | "browsers"
3184 | x)
3185 |
3186 | (js! (gbrowser
3187 | "tabContainer"
3188 | "childNodes"
3189 | "item")
3190 | x)
3191 |
3192 | gbrowser))))))
3193 |
3194 | (defvar js-read-tab-history nil)
3195 |
3196 | (defun js--read-tab (prompt)
3197 | "Read a Mozilla tab with prompt PROMPT.
3198 | Return a cons of (TYPE . OBJECT). TYPE is either 'window or
3199 | 'tab, and OBJECT is a JavaScript handle to a ChromeWindow or a
3200 | browser, respectively."
3201 |
3202 | ;; Prime IDO
3203 | (unless ido-mode
3204 | (ido-mode 1)
3205 | (ido-mode -1))
3206 |
3207 | (with-js
3208 | (lexical-let ((tabs (js--get-tabs)) selected-tab-cname
3209 | selected-tab prev-hitab)
3210 |
3211 | ;; Disambiguate names
3212 | (setq tabs (loop with tab-names = (make-hash-table :test 'equal)
3213 | for tab in tabs
3214 | for cname = (format "%s (%s)" (second tab) (first tab))
3215 | for num = (incf (gethash cname tab-names -1))
3216 | if (> num 0)
3217 | do (setq cname (format "%s <%d>" cname num))
3218 | collect (cons cname tab)))
3219 |
3220 | (labels ((find-tab-by-cname
3221 | (cname)
3222 | (loop for tab in tabs
3223 | if (equal (car tab) cname)
3224 | return (cdr tab)))
3225 |
3226 | (mogrify-highlighting
3227 | (hitab unhitab)
3228 |
3229 | ;; Hack to reduce the number of
3230 | ;; round-trips to mozilla
3231 | (let (cmds)
3232 | (cond
3233 | ;; Highlighting tab
3234 | ((fourth hitab)
3235 | (push '(js! ((fourth hitab) "setAttribute")
3236 | "style"
3237 | "color: red; font-weight: bold")
3238 | cmds)
3239 |
3240 | ;; Highlight window proper
3241 | (push '(js! ((third hitab)
3242 | "setAttribute")
3243 | "style"
3244 | "border: 8px solid red")
3245 | cmds)
3246 |
3247 | ;; Select tab, when appropriate
3248 | (when js-js-switch-tabs
3249 | (push
3250 | '(js> ((fifth hitab) "selectedTab") (fourth hitab))
3251 | cmds)))
3252 |
3253 | ;; Hilighting whole window
3254 | ((third hitab)
3255 | (push '(js! ((third hitab) "document"
3256 | "documentElement" "setAttribute")
3257 | "style"
3258 | (concat "-moz-appearance: none;"
3259 | "border: 8px solid red;"))
3260 | cmds)))
3261 |
3262 | (cond
3263 | ;; Unhighlighting tab
3264 | ((fourth unhitab)
3265 | (push '(js! ((fourth unhitab) "setAttribute") "style" "")
3266 | cmds)
3267 | (push '(js! ((third unhitab) "setAttribute") "style" "")
3268 | cmds))
3269 |
3270 | ;; Unhighlighting window
3271 | ((third unhitab)
3272 | (push '(js! ((third unhitab) "document"
3273 | "documentElement" "setAttribute")
3274 | "style" "")
3275 | cmds)))
3276 |
3277 | (eval (list 'with-js
3278 | (cons 'js-list (nreverse cmds))))))
3279 |
3280 | (command-hook
3281 | ()
3282 | (let* ((tab (find-tab-by-cname (car ido-matches))))
3283 | (mogrify-highlighting tab prev-hitab)
3284 | (setq prev-hitab tab)))
3285 |
3286 | (setup-hook
3287 | ()
3288 | ;; Fiddle with the match list a bit: if our first match
3289 | ;; is a tabbrowser window, rotate the match list until
3290 | ;; the active tab comes up
3291 | (let ((matched-tab (find-tab-by-cname (car ido-matches))))
3292 | (when (and matched-tab
3293 | (null (fourth matched-tab))
3294 | (equal "navigator:browser"
3295 | (js! ((third matched-tab)
3296 | "document"
3297 | "documentElement"
3298 | "getAttribute")
3299 | "windowtype")))
3300 |
3301 | (loop with tab-to-match = (js< (third matched-tab)
3302 | "gBrowser"
3303 | "selectedTab")
3304 |
3305 | with index = 0
3306 | for match in ido-matches
3307 | for candidate-tab = (find-tab-by-cname match)
3308 | if (eq (fourth candidate-tab) tab-to-match)
3309 | do (setq ido-cur-list (ido-chop ido-cur-list match))
3310 | and return t)))
3311 |
3312 | (add-hook 'post-command-hook #'command-hook t t)))
3313 |
3314 |
3315 | (unwind-protect
3316 | (setq selected-tab-cname
3317 | (let ((ido-minibuffer-setup-hook
3318 | (cons #'setup-hook ido-minibuffer-setup-hook)))
3319 | (ido-completing-read
3320 | prompt
3321 | (mapcar #'car tabs)
3322 | nil t nil
3323 | 'js-read-tab-history)))
3324 |
3325 | (when prev-hitab
3326 | (mogrify-highlighting nil prev-hitab)
3327 | (setq prev-hitab nil)))
3328 |
3329 | (add-to-history 'js-read-tab-history selected-tab-cname)
3330 |
3331 | (setq selected-tab (loop for tab in tabs
3332 | if (equal (car tab) selected-tab-cname)
3333 | return (cdr tab)))
3334 |
3335 | (if (fourth selected-tab)
3336 | (cons 'browser (third selected-tab))
3337 | (cons 'window (third selected-tab)))))))
3338 |
3339 | (defun js--guess-eval-defun-info (pstate)
3340 | "Helper function for `js-eval-defun'.
3341 | Return a list (NAME . CLASSPARTS), where CLASSPARTS is a list of
3342 | strings making up the class name and NAME is the name of the
3343 | function part."
3344 | (cond ((and (= (length pstate) 3)
3345 | (eq (js--pitem-type (first pstate)) 'function)
3346 | (= (length (js--pitem-name (first pstate))) 1)
3347 | (consp (js--pitem-type (second pstate))))
3348 |
3349 | (append (js--pitem-name (second pstate))
3350 | (list (first (js--pitem-name (first pstate))))))
3351 |
3352 | ((and (= (length pstate) 2)
3353 | (eq (js--pitem-type (first pstate)) 'function))
3354 |
3355 | (append
3356 | (butlast (js--pitem-name (first pstate)))
3357 | (list (car (last (js--pitem-name (first pstate)))))))
3358 |
3359 | (t (error "Function not a toplevel defun or class member"))))
3360 |
3361 | (defvar js--js-context nil
3362 | "The current JavaScript context.
3363 | This is a cons like the one returned from `js--read-tab'.
3364 | Change with `js-set-js-context'.")
3365 |
3366 | (defconst js--js-inserter
3367 | "(function(func_info,func) {
3368 | func_info.unshift('window');
3369 | var obj = window;
3370 | for(var i = 1; i < func_info.length - 1; ++i) {
3371 | var next = obj[func_info[i]];
3372 | if(typeof next !== 'object' && typeof next !== 'function') {
3373 | next = obj.prototype && obj.prototype[func_info[i]];
3374 | if(typeof next !== 'object' && typeof next !== 'function') {
3375 | alert('Could not find ' + func_info.slice(0, i+1).join('.') +
3376 | ' or ' + func_info.slice(0, i+1).join('.') + '.prototype');
3377 | return;
3378 | }
3379 |
3380 | func_info.splice(i+1, 0, 'prototype');
3381 | ++i;
3382 | }
3383 | }
3384 |
3385 | obj[func_info[i]] = func;
3386 | alert('Successfully updated '+func_info.join('.'));
3387 | })")
3388 |
3389 | (defun js-set-js-context (context)
3390 | "Set the JavaScript context to CONTEXT.
3391 | When called interactively, prompt for CONTEXT."
3392 | (interactive (list (js--read-tab "Javascript Context: ")))
3393 | (setq js--js-context context))
3394 |
3395 | (defun js--get-js-context ()
3396 | "Return a valid JavaScript context.
3397 | If one hasn't been set, or if it's stale, prompt for a new one."
3398 | (with-js
3399 | (when (or (null js--js-context)
3400 | (js--js-handle-expired-p (cdr js--js-context))
3401 | (ecase (car js--js-context)
3402 | (window (js? (js< (cdr js--js-context) "closed")))
3403 | (browser (not (js? (js< (cdr js--js-context)
3404 | "contentDocument"))))))
3405 | (setq js--js-context (js--read-tab "Javascript Context: ")))
3406 | js--js-context))
3407 |
3408 | (defun js--js-content-window (context)
3409 | (with-js
3410 | (ecase (car context)
3411 | (window (cdr context))
3412 | (browser (js< (cdr context)
3413 | "contentWindow" "wrappedJSObject")))))
3414 |
3415 | (defun js--make-nsilocalfile (path)
3416 | (with-js
3417 | (let ((file (js-create-instance "@mozilla.org/file/local;1"
3418 | "nsILocalFile")))
3419 | (js! (file "initWithPath") path)
3420 | file)))
3421 |
3422 | (defun js--js-add-resource-alias (alias path)
3423 | (with-js
3424 | (let* ((io-service (js-get-service "@mozilla.org/network/io-service;1"
3425 | "nsIIOService"))
3426 | (res-prot (js! (io-service "getProtocolHandler") "resource"))
3427 | (res-prot (js-qi res-prot "nsIResProtocolHandler"))
3428 | (path-file (js--make-nsilocalfile path))
3429 | (path-uri (js! (io-service "newFileURI") path-file)))
3430 | (js! (res-prot "setSubstitution") alias path-uri))))
3431 |
3432 | (defun* js-eval-defun ()
3433 | "Update a Mozilla tab using the JavaScript defun at point."
3434 | (interactive)
3435 |
3436 | ;; This function works by generating a temporary file that contains
3437 | ;; the function we'd like to insert. We then use the elisp-js bridge
3438 | ;; to command mozilla to load this file by inserting a script tag
3439 | ;; into the document we set. This way, debuggers and such will have
3440 | ;; a way to find the source of the just-inserted function.
3441 | ;;
3442 | ;; We delete the temporary file if there's an error, but otherwise
3443 | ;; we add an unload event listener on the Mozilla side to delete the
3444 | ;; file.
3445 |
3446 | (save-excursion
3447 | (let (begin end pstate defun-info temp-name defun-body)
3448 | (js-end-of-defun)
3449 | (setq end (point))
3450 | (js--ensure-cache)
3451 | (js-beginning-of-defun)
3452 | (re-search-forward "\\_")
3453 | (setq begin (match-beginning 0))
3454 | (setq pstate (js--forward-pstate))
3455 |
3456 | (when (or (null pstate)
3457 | (> (point) end))
3458 | (error "Could not locate function definition"))
3459 |
3460 | (setq defun-info (js--guess-eval-defun-info pstate))
3461 |
3462 | (let ((overlay (make-overlay begin end)))
3463 | (overlay-put overlay 'face 'highlight)
3464 | (unwind-protect
3465 | (unless (y-or-n-p (format "Send %s to Mozilla? "
3466 | (mapconcat #'identity defun-info ".")))
3467 | (message "") ; question message lingers until next command
3468 | (return-from js-eval-defun))
3469 | (delete-overlay overlay)))
3470 |
3471 | (setq defun-body (buffer-substring-no-properties begin end))
3472 |
3473 | (make-directory js-js-tmpdir t)
3474 |
3475 | ;; (Re)register a Mozilla resource URL to point to the
3476 | ;; temporary directory
3477 | (js--js-add-resource-alias "js" js-js-tmpdir)
3478 |
3479 | (setq temp-name (make-temp-file (concat js-js-tmpdir
3480 | "/js-")
3481 | nil ".js"))
3482 | (unwind-protect
3483 | (with-js
3484 | (with-temp-buffer
3485 | (insert js--js-inserter)
3486 | (insert "(")
3487 | (insert (json-encode-list defun-info))
3488 | (insert ",\n")
3489 | (insert defun-body)
3490 | (insert "\n)")
3491 | (write-region (point-min) (point-max) temp-name
3492 | nil 1))
3493 |
3494 | ;; Give Mozilla responsibility for deleting this file
3495 | (let* ((content-window (js--js-content-window
3496 | (js--get-js-context)))
3497 | (content-document (js< content-window "document"))
3498 | (head (if (js? (js< content-document "body"))
3499 | ;; Regular content
3500 | (js< (js! (content-document "getElementsByTagName")
3501 | "head")
3502 | 0)
3503 | ;; Chrome
3504 | (js< content-document "documentElement")))
3505 | (elem (js! (content-document "createElementNS")
3506 | "http://www.w3.org/1999/xhtml" "script")))
3507 |
3508 | (js! (elem "setAttribute") "type" "text/javascript")
3509 | (js! (elem "setAttribute") "src"
3510 | (format "resource://js/%s"
3511 | (file-name-nondirectory temp-name)))
3512 |
3513 | (js! (head "appendChild") elem)
3514 |
3515 | (js! (content-window "addEventListener") "unload"
3516 | (js! ((js-new
3517 | "Function" "file"
3518 | "return function() { file.remove(false) }"))
3519 | (js--make-nsilocalfile temp-name))
3520 | 'false)
3521 | (setq temp-name nil)
3522 |
3523 |
3524 |
3525 | ))
3526 |
3527 | ;; temp-name is set to nil on success
3528 | (when temp-name
3529 | (delete-file temp-name))))))
3530 |
3531 | ;;; Main Function
3532 |
3533 | ;;;###autoload
3534 | (define-derived-mode js-mode prog-mode "Javascript"
3535 | "Major mode for editing JavaScript."
3536 | :group 'js
3537 |
3538 | (set (make-local-variable 'indent-line-function) 'js-indent-line)
3539 | (set (make-local-variable 'beginning-of-defun-function)
3540 | 'js-beginning-of-defun)
3541 | (set (make-local-variable 'end-of-defun-function)
3542 | 'js-end-of-defun)
3543 |
3544 | (set (make-local-variable 'open-paren-in-column-0-is-defun-start) nil)
3545 | (set (make-local-variable 'font-lock-defaults)
3546 | (list js--font-lock-keywords
3547 | nil nil nil nil
3548 | '(font-lock-syntactic-keywords
3549 | . js-font-lock-syntactic-keywords)))
3550 |
3551 | (set (make-local-variable 'parse-sexp-ignore-comments) t)
3552 | (set (make-local-variable 'parse-sexp-lookup-properties) t)
3553 | (set (make-local-variable 'which-func-imenu-joiner-function)
3554 | #'js--which-func-joiner)
3555 |
3556 | ;; Comments
3557 | (setq comment-start "// ")
3558 | (setq comment-end "")
3559 | (set (make-local-variable 'fill-paragraph-function)
3560 | 'js-c-fill-paragraph)
3561 |
3562 | ;; Parse cache
3563 | (add-hook 'before-change-functions #'js--flush-caches t t)
3564 |
3565 | ;; Frameworks
3566 | (js--update-quick-match-re)
3567 |
3568 | ;; Imenu
3569 | (setq imenu-case-fold-search nil)
3570 | (set (make-local-variable 'imenu-create-index-function)
3571 | #'js--imenu-create-index)
3572 |
3573 | ;; for filling, pretend we're cc-mode
3574 | (setq c-comment-prefix-regexp "//+\\|\\**"
3575 | c-paragraph-start "$"
3576 | c-paragraph-separate "$"
3577 | c-block-comment-prefix "* "
3578 | c-line-comment-starter "//"
3579 | c-comment-start-regexp "/[*/]\\|\\s!"
3580 | comment-start-skip "\\(//+\\|/\\*+\\)\\s *")
3581 |
3582 | (let ((c-buffer-is-cc-mode t))
3583 | ;; FIXME: These are normally set by `c-basic-common-init'. Should
3584 | ;; we call it instead? (Bug#6071)
3585 | (make-local-variable 'paragraph-start)
3586 | (make-local-variable 'paragraph-separate)
3587 | (make-local-variable 'paragraph-ignore-fill-prefix)
3588 | (make-local-variable 'adaptive-fill-mode)
3589 | (make-local-variable 'adaptive-fill-regexp)
3590 | (c-setup-paragraph-variables))
3591 |
3592 | (set (make-local-variable 'syntax-begin-function)
3593 | #'js--syntax-begin-function)
3594 |
3595 | ;; Important to fontify the whole buffer syntactically! If we don't,
3596 | ;; then we might have regular expression literals that aren't marked
3597 | ;; as strings, which will screw up parse-partial-sexp, scan-lists,
3598 | ;; etc. and produce maddening "unbalanced parenthesis" errors.
3599 | ;; When we attempt to find the error and scroll to the portion of
3600 | ;; the buffer containing the problem, JIT-lock will apply the
3601 | ;; correct syntax to the regular expresion literal and the problem
3602 | ;; will mysteriously disappear.
3603 | (font-lock-set-defaults)
3604 |
3605 | (let (font-lock-keywords) ; leaves syntactic keywords intact
3606 | (font-lock-fontify-buffer)))
3607 |
3608 | ;;;###autoload
3609 | (defalias 'javascript-mode 'js-mode)
3610 |
3611 | (eval-after-load 'folding
3612 | '(when (fboundp 'folding-add-to-marks-list)
3613 | (folding-add-to-marks-list 'js-mode "// {{{" "// }}}" )))
3614 |
3615 | (provide 'js)
3616 |
3617 | ;; js.el ends here
3618 |
--------------------------------------------------------------------------------