├── .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 | 5 | 6 | 7 | 8 |
emacs versionsystem
23.2freeBSD
24Ubuntu
23.2Windows XP
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 | --------------------------------------------------------------------------------