Browse Source

First commit

Gerald 12 years ago
commit
10bde6a232

+ 674 - 0
LICENSE

@@ -0,0 +1,674 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.  We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors.  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights.  Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received.  You must make sure that they, too, receive
+or can get the source code.  And you must show them these terms so they
+know their rights.
+
+  Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+  For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software.  For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+  Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so.  This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software.  The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable.  Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products.  If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+  Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary.  To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Use with the GNU Affero General Public License.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+    <program>  Copyright (C) <year>  <name of author>
+    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+  The GNU General Public License does not permit incorporating your program
+into proprietary programs.  If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.  But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.

+ 9 - 0
README.md

@@ -0,0 +1,9 @@
+Violentmonkey
+=============
+
+Violentmonkey, userscripts support for Chromium-based Opera.
+
+* Donate
+  * [![捐助](https://img.alipay.com/sys/personalprod/style/mc/btn-index.png)](http://me.alipay.com/gera2ld)
+
+Author: Gerald &lt;<[email protected]>&gt;

+ 330 - 0
_locales/en/messages.json

@@ -0,0 +1,330 @@
+{
+	"extName": {
+		"message": "Violentmonkey", 
+		"description": "name of this extension"
+	}, 
+	"extAuthor": {
+		"description": "Designed by <a href=mailto:[email protected]>Gerald</a>", 
+		"message": ""
+	},
+	"extTranslator": {
+		"message": "Gerald <[email protected]>", 
+		"description": "information of the translator"
+	}, 
+	"extDescription": {
+		"message": "UserScript support for Opera.", 
+		"description": "description for this extension"
+	}, 
+	"css": {
+		"message": "html{font-family:Calibri,Arial;}", 
+		"description": "additional CSS for all pages"
+	}, 
+	"defaultSearch": {
+		"message": "http://www.google.com/search?q=*", 
+		"description": "URL for searching scripts, * stands for the keywords"
+	}, 
+	"msgUpdated": {
+		"message": "Script updated.", 
+		"description": ""
+	}, 
+	"msgErrorFetchingScript": {
+		"message": "Error fetching script!", 
+		"description": ""
+	}, 
+	"msgInstalled": {
+		"message": "Script installed.", 
+		"description": ""
+	}, 
+	"msgUpdating": {
+		"message": "Updating...", 
+		"description": ""
+	}, 
+	"msgNewVersion": {
+		"message": "New version found.", 
+		"description": ""
+	}, 
+	"msgCheckingForUpdate": {
+		"message": "Checking for updates...", 
+		"description": ""
+	}, 
+	"msgErrorFetchingUpdateInfo": {
+		"message": "Failed fetching update information.", 
+		"description": ""
+	}, 
+	"msgNoUpdate": {
+		"message": "No update found.", 
+		"description": ""
+	}, 
+	"labelInstall": {
+		"message": "Install script...", 
+		"description": ""
+	}, 
+	"buttonConfirmInstall": {
+		"message": "Confirm install", 
+		"description": ""
+	}, 
+	"buttonClose": {
+		"message": "Close", 
+		"description": ""
+	}, 
+	"anchorSupportPage": {
+		"message": "<a href=https://github.com/gera2ld/Violentmonkey/wiki>Violentmonkey</a>", 
+		"description": ""
+	}, 
+	"labelAuthor": {
+		"message": "Author: ",
+		"description": ""
+	}, 
+	"msgNotJS": {
+		"message": "URL seems not to be a Javascript file. It will be reloaded directly in 3 seconds.", 
+		"description": ""
+	}, 
+	"msgLoadedJS": {
+		"message": "Javascript loaded: $1", 
+		"description": ""
+	}, 
+	"msgErrorLoadingURL": {
+		"message": "Error loading URL: $1", 
+		"description": ""
+	}, 
+	"msgLoadingURL": {
+		"message": "Loading URL: $1", 
+		"description": ""
+	}, 
+	"labelOptions": {
+		"message": "Options", 
+		"description": ""
+	}, 
+	"buttonNew": {
+		"message": "New", 
+		"description": ""
+	}, 
+	"anchorGetMoreScripts": {
+		"message": "<a href=http://userscripts.org target=_blank class=i18n>Get more scripts from userscripts.org</a>", 
+		"description": ""
+	}, 
+	"anchorUpdateAll": {
+		"message": "Check all for updates", 
+		"description": ""
+	}, 
+	"buttonAdvanced": {
+		"message": "Advanced", 
+		"description": ""
+	}, 
+	"labelInstalledScripts": {
+		"message": "Installed scripts", 
+		"description": ""
+	}, 
+	"labelShowDetails": {
+		"message": "Show details", 
+		"description": ""
+	}, 
+	"labelScriptEditor": {
+		"message": "Script Editor", 
+		"description": ""
+	}, 
+	"buttonCustomMeta": {
+		"message": "Custom meta data", 
+		"description": ""
+	}, 
+	"labelAllowUpdate": {
+		"message": "Allow update", 
+		"description": ""
+	}, 
+	"buttonSave": {
+		"message": "Save", 
+		"description": ""
+	}, 
+	"buttonSaveClose": {
+		"message": "Save & Close", 
+		"description": ""
+	}, 
+	"labelName": {
+		"message": "Name:", 
+		"description": ""
+	}, 
+	"labelRunAt": {
+		"message": "Run-At:", 
+		"description": ""
+	}, 
+	"labelRunAtDefault": {
+		"message": "(Default)", 
+		"description": ""
+	}, 
+	"labelHomepage": {
+		"message": "Homepage:", 
+		"description": ""
+	}, 
+	"labelUpdateURL": {
+		"message": "Update URL:", 
+		"description": ""
+	}, 
+	"labelDownloadURL": {
+		"message": "Download URL:", 
+		"description": ""
+	}, 
+	"labelInclude": {
+		"message": "Includes", 
+		"description": ""
+	}, 
+	"labelKeepInclude": {
+		"message": "Keep original include rules", 
+		"description": ""
+	}, 
+	"labelCustomInclude": {
+		"message": "Custom include rules: <em>(One per line)</em>", 
+		"description": ""
+	}, 
+	"labelMatch": {
+		"message": "Matches", 
+		"description": ""
+	}, 
+	"labelKeepMatch": {
+		"message": "Keep original match rules", 
+		"description": ""
+	}, 
+	"labelCustomMatch": {
+		"message": "Custom match rules: <em>(One per line)</em>", 
+		"description": ""
+	}, 
+	"labelExclude": {
+		"message": "Excludes", 
+		"description": ""
+	}, 
+	"labelKeepExclude": {
+		"message": "Keep original exclude rules", 
+		"description": ""
+	}, 
+	"labelCustomExclude": {
+		"message": "Custom exclude rules: <em>(One per line)</em>", 
+		"description": ""
+	}, 
+	"buttonOK": {
+		"message": "OK", 
+		"description": ""
+	}, 
+	"buttonCancel": {
+		"message": "Cancel", 
+		"description": ""
+	}, 
+	"labelExport": {
+		"message": "Export", 
+		"description": ""
+	}, 
+	"anchorExportOptions": {
+		"message": "Option", 
+		"description": ""
+	}, 
+	"labelExportScriptData": {
+		"message": "Export script data", 
+		"description": ""
+	}, 
+	"buttonAllNone": {
+		"message": "All/None", 
+		"description": ""
+	}, 
+	"labelAdvanced": {
+		"message": "Advanced", 
+		"description": ""
+	}, 
+	"labelAutoUpdate": {
+		"message": "Automatically check scripts for updates every day", 
+		"description": ""
+	}, 
+	"labelSearchLink": {
+		"message": "Search URL: ", 
+		"description": ""
+	}, 
+	"labelSearchHint": {
+		"message": "<a title='Search URL must be a URL including *. * stands for the key words generated by Violentmonkey.'>(?)</a>", 
+		"description": ""
+	}, 
+	"buttonDefault": {
+		"message": "Default", 
+		"description": ""
+	}, 
+	"labelData": {
+		"message": "Data", 
+		"description": ""
+	}, 
+	"buttonExport": {
+		"message": "Export to zip", 
+		"description": ""
+	}, 
+	"buttonImport": {
+		"message": "Import from zip", 
+		"description": ""
+	}, 
+	"buttonVacuum": {
+		"message": "Vacuum data", 
+		"description": ""
+	}, 
+	"labelVacuumHint": {
+		"message": "<a title='Discard the redundancy and try to reload the missing resources in cache.'>(?)</a>", 
+		"description": ""
+	}, 
+	"anchorUpdate": {
+		"message": "Check for updates", 
+		"description": ""
+	}, 
+	"buttonEdit": {
+		"message": "Edit", 
+		"description": ""
+	}, 
+	"buttonRemove": {
+		"message": "Remove", 
+		"description": ""
+	}, 
+	"labelNoName": {
+		"message": "No Name", 
+		"description": ""
+	}, 
+	"buttonDisable": {
+		"message": "Disable", 
+		"description": ""
+	}, 
+	"buttonEnable": {
+		"message": "Enable", 
+		"description": ""
+	}, 
+	"confirmNotSaved": {
+		"message": "Modifications are not saved!\nClick OK to discard them or cancel to stay.", 
+		"description": ""
+	}, 
+	"msgImported": {
+		"message": "$1 item(s) are imported.", 
+		"description": ""
+	}, 
+	"buttonVacuuming": {
+		"message": "Vacuuming data...", 
+		"description": ""
+	}, 
+	"labelExporting": {
+		"message": "Exporting data...", 
+		"description": ""
+	}, 
+	"buttonVacuumed": {
+		"message": "Data vacuumed", 
+		"description": ""
+	}, 
+	"menuManageScripts": {
+		"message": "Manage scripts", 
+		"description": ""
+	}, 
+	"menuFindScripts": {
+		"message": "Find scripts for this site", 
+		"description": ""
+	}, 
+	"menuScriptEnabled": {
+		"message": "Scripts enabled", 
+		"description": ""
+	}, 
+	"menuBack": {
+		"message": "Back", 
+		"description": ""
+	}, 
+	"menuCommands": {
+		"message": "Script commands...", 
+		"description": ""
+	}
+}

+ 1 - 0
background.html

@@ -0,0 +1 @@
+<!DOCTYPE html><html lang="en"><head><meta charset="utf-8" /><script src="common.js"></script><script src="background.js"></script><title>ViolentMonkey</title></head><body></body></html>

+ 612 - 0
background.js

@@ -0,0 +1,612 @@
+var db,port=null,pos=0;
+function initDb(callback) {
+	var request=indexedDB.open('Violentmonkey',1);
+	request.onsuccess=function(e){db=request.result;if(callback) callback();};
+	request.onerror=function(e){console.log('IndexedDB error: '+e.target.webkitErrorMessage);};
+	request.onupgradeneeded=function(e){
+		var r=e.currentTarget.result,o,i;
+		// settings: key value
+		o=r.createObjectStore('settings',{keyPath:'key'});
+		[
+			{key:'showDetails',value:false},
+			{key:'lastUpdate',value:0},
+			{key:'maxPosition',value:0},
+			{key:'withData',value:true},
+			{key:'showButton',value:true},
+			{key:'isApplied',value:true},
+			{key:'autoUpdate',value:true},
+			{key:'search',value:_('defaultSearch')},
+		].forEach(function(i){o.add(i);});
+		// scripts: id uri custom meta enabled update code position
+		o=r.createObjectStore('scripts',{keyPath:'id',autoIncrement:true});
+		o.createIndex('uri','uri',{unique:true});
+		o.createIndex('update','update',{unique:false});
+		o.createIndex('position','position',{unique:false});	// should be unique at last
+		// require: uri code
+		o=r.createObjectStore('require',{keyPath:'uri'});
+		// cache: uri data
+		o=r.createObjectStore('cache',{keyPath:'uri'});
+		// values: uri values
+		o=r.createObjectStore('values',{keyPath:'uri'});
+	};
+}
+function newPosition(){
+	setOption({key:'maxPosition',value:++pos});
+	return pos;
+}
+function getNameURI(i) {
+  var ns=i.meta.namespace||'',n=i.meta.name||'',k=escape(ns)+':'+escape(n)+':';
+  if(!ns&&!n) k+=i.id;
+  return k;
+}
+function getMeta(j){return {id:j.id,custom:j.custom,meta:j.meta,enabled:j.enabled,update:j.update};}
+function parseMeta(d){
+	var o=-1,meta={include:[],exclude:[],match:[],require:[],resources:{}};
+	meta.resource=[];
+	d.replace(/(?:^|\n)\/\/\s*([@=]\S+)(.*)/g,function(m,k,v){
+		if(o<0&&k=='==UserScript==') o=1;
+		else if(k=='==/UserScript==') o=0;
+		if(o==1&&k[0]=='@') k=k.slice(1); else return;
+		v=v.replace(/^\s+|\s+$/g,'');
+		if(meta[k]&&meta[k].push) meta[k].push(v);	// multiple values allowed
+		else if(!(k in meta)) meta[k]=v;	// only first value will be stored
+	});
+	meta.resource.forEach(function(i){
+		o=i.match(/^(\w+)\s+(.*)/);
+		if(o) meta.resources[o[1]]=o[2];
+	});
+	delete meta.resource;
+	return meta;
+}
+function newScript() {
+  var r={
+    custom: {},
+    enabled: 1,
+    update: 1,
+    code: '// ==UserScript==\n// @name New Script\n// ==/UserScript==\n'
+  };
+  r.meta=parseMeta(r.code);
+  return r;
+}
+function removeScript(i,src,callback) {
+	var o=db.transaction('scripts','readwrite').objectStore('scripts');
+	o.delete(i);
+	if(callback) callback();
+}
+function saveScript(i,src,callback) {
+	var o=db.transaction('scripts','readwrite').objectStore('scripts');
+	i.enabled=i.enabled?1:0;
+	i.update=i.update?1:0;
+	if(!i.position) i.position=newPosition();
+	if(callback) callback();
+	return o.put(i);
+}
+function vacuum(o,src,callback) {
+	var cc={},rq={},vl={},w=0;
+	function vacuumPosition(){
+		var o=db.transaction('scripts','readwrite').objectStore('scripts'),p=0;
+		o.index('position').openCursor().onsuccess=function(e){
+			var r=e.target.result,v,i;
+			if(r) {
+				v=r.value;p++;
+				if(v.position!=p) {v.position=p;o.put(v);}
+				v.meta.require.forEach(function(i){rq[i]=1;});
+				for(i in v.meta.resources) cc[i]=1;
+				if(v.meta.icon) cc[v.meta.icon]=1;vl[v.uri]=1;
+				r.continue();
+			} else {
+				vacuumDB('require',rq);
+				vacuumDB('cache',cc);
+				vacuumDB('values',vl);
+			}
+		};
+	}
+	function vacuumDB(dbName,dic){
+		w++;
+		var o=db.transaction(dbName,'readwrite').objectStore(dbName);
+		o.openCursor().onsuccess=function(e){
+			var r=e.target.result,v;
+			if(r) {
+				v=r.value;
+				if(!dic[v.uri]) o.delete(v.uri);
+				r.continue();
+			} else finish();
+		};
+	}
+	function finish(){
+		if(!--w) chrome.tabs.sendMessage(src.tab.id,{cmd:'Vacuumed'});
+	}
+	vacuumPosition();
+	if(callback) callback();
+}
+function move(data,src,callback){
+	var o=db.transaction('scripts','readwrite').objectStore('scripts');
+	o.get(data.id).onsuccess=function(e){
+		var r=e.target.result,k,s,x=r.position;
+		if(data.offset<0) {
+			k=IDBKeyRange.upperBound(x,true);
+			s='prev';
+			data.offset=-data.offset;
+		} else {
+			k=IDBKeyRange.lowerBound(x,true);
+			s='next';
+		}
+		o.index('position').openCursor(k,s).onsuccess=function(e){
+			var p=e.target.result,v,y;
+			if(p) {
+				data.offset--;
+				v=p.value;y=v.position;
+				v.position=x;o.put(v);x=y;
+				if(data.offset) p.continue();
+				else {r.position=x;o.put(r);}
+			}
+		};
+	};
+	if(callback) callback();
+}
+function str2RE(s){return s.replace(/(\.|\?|\/)/g,'\\$1').replace(/\*/g,'.*?');}
+function autoReg(s, w) {
+  if(!w&&s[0]=='/'&&s.slice(-1)=='/') return RegExp(s.slice(1,-1));
+  return RegExp('^'+str2RE(s)+'$');
+}
+var match_reg=/(.*?):\/\/([^\/]*)\/(.*)/;
+function matchTest(s,u) {
+  var m=s.match(match_reg);
+  if(!m) return false;
+  if(m[1]=='*') {
+    if(u[1]!='http'&&u[1]!='https') return false;
+  } else if(m[1]!=u[1]) return false;
+  if(m[2]!='*') {
+    if(m[2].slice(0,2)=='*.') {
+      if(u[2]!=m[2].slice(2)&&u[2].slice(1-m[2].length)!=m[2].slice(1)) return false;
+    } else if(m[2]!=u[2]) return false;
+  }
+  if(!autoReg(m[3],1).test(u[3])) return false;
+  return true;
+}
+function testURL(url,e) {
+  var f=true,i,inc=[],exc=[],mat=[],u=url.match(match_reg);
+  if(e.custom._match!=false&&e.meta.match) mat=mat.concat(e.meta.match);
+  if(e.custom.match) mat=mat.concat(e.custom.match);
+  if(e.custom._include!=false&&e.meta.include) inc=inc.concat(e.meta.include);
+  if(e.custom.include) inc=inc.concat(e.custom.include);
+  if(e.custom._exclude!=false&&e.meta.exclude) exc=exc.concat(e.meta.exclude);
+  if(e.custom.exclude) exc=exc.concat(e.custom.exclude);
+  if(mat.length) {
+    for(i=0;i<mat.length;i++) if(f=matchTest(mat[i],u)) break;
+  } else for(i=0;i<inc.length;i++) if(f=autoReg(inc[i]).test(url)) break;
+  if(f) for(i=0;i<exc.length;i++) if(!(f=!autoReg(exc[i]).test(url))) break;
+  return f;
+}
+function getScript(id,src,callback) {	// for user edit
+	var o=db.transaction('scripts').objectStore('scripts');
+	o.get(id).onsuccess=function(e){
+		var r=e.target.result,v;
+		if(r) {
+			v=getMeta(r);
+			v.code=r.code;
+			if(callback) callback(v);
+		}
+	};
+}
+function getMetas(ids,src,callback) {	// for popup menu
+	var o=db.transaction('scripts').objectStore('scripts'),data=[],id;
+	function getOne(){
+		var id=ids.shift();
+		if(id) o.get(id).onsuccess=function(e){
+			var r=e.target.result;
+			if(r) data.push(getMeta(r));
+			getOne();
+		}; else callback(data);
+	}
+	getOne();
+}
+function findScript(o,src,callback) {	// for injected
+	function getSettings(){
+		var o=db.transaction('settings').objectStore('settings');
+		o.get('isApplied').onsuccess=function(e){
+			var r=e.target.result;
+			if(data.isApplied=r.value) getScripts();
+			else finish();
+		};
+	}
+	function getScripts(){
+		function addCache(i,c,d){
+			if(!(i in d)) {c.push(i);d[i]=null;}
+		}
+		var o=db.transaction('scripts').objectStore('scripts'),n=0;
+		o.index('position').openCursor().onsuccess=function(e){
+			var i,r=e.target.result,v;
+			if(r) {
+				v=r.value;
+				if(testURL(src.tab.url,v)) {
+					data.scripts.push(v);if(v.enabled) n++;
+					values.push(v.uri);
+					v.meta.require.forEach(function(i){addCache(i,require,data.require);});
+					for(i in v.meta.resources) addCache(v.meta.resources[i],cache,data.cache);
+				}
+				r.continue();
+			} else {
+				if(n) {
+					chrome.browserAction.setBadgeBackgroundColor({color:'#808',tabId:src.tab.id});
+					chrome.browserAction.setBadgeText({text:n.toString(),tabId:src.tab.id});
+				}
+				getRequire();
+			}
+		};
+	}
+	function getRequire(){
+		function loop(){
+			var i=require.pop();
+			if(i) o.get(i).onsuccess=function(e){
+				var r=e.target.result;
+				if(r) data.require[i]=r.code;
+				loop();
+			}; else getCache();
+		}
+		var o=db.transaction('require').objectStore('require');
+		loop();
+	}
+	function getCache(){
+		function loop(){
+			var i=cache.pop();
+			if(i) o.get(i).onsuccess=function(e){
+				var r=e.target.result;
+				if(r) data.cache[i]=new Int8Array(r.data);
+				loop();
+			}; else getValues();
+		}
+		var o=db.transaction('cache').objectStore('cache');
+		loop();
+	}
+	function getValues(){
+		function loop(){
+			var i=values.pop();
+			if(i) o.get(i).onsuccess=function(e){
+				var r=e.target.result;
+				if(r) data.values[i]=r.values;
+				loop();
+			}; else finish();
+		}
+		var o=db.transaction('values').objectStore('values');
+		loop();
+	}
+	function finish(){callback(data);}
+	var data={isApplied:true,scripts:[],require:{},cache:{},values:{}},cache=[],values=[],require=[];
+	getSettings();
+}
+function fetchURL(url, cb, type) {
+  var req = new XMLHttpRequest();
+  req.open('GET', url, true);
+  if (type) req.responseType = type;
+  if (cb) req.onloadend = cb;
+  req.send();
+}
+function fetchCache(url) {
+	fetchURL(url, function() {
+		if (this.status!=200) return;
+		var o=db.transaction('cache','readwrite').objectStore('cache');
+		o.put({uri:url,data:this.response});
+	}, 'arraybuffer');
+}
+function fetchRequire(url) {
+	fetchURL(url, function() {
+		if (this.status!=200) return;
+		var o=db.transaction('require','readwrite').objectStore('require');
+		o.put({uri:url,code:this.responseText});
+	});
+}
+function updateItem(r){
+	if(port) try{
+		port.postMessage(r);
+	}catch(e){
+		port=null;
+		console.log(e);
+	}
+}
+function queryScript(id,meta,callback){
+	var o=db.transaction('scripts').objectStore('scripts');
+	function queryMeta() {
+		var uri=getNameURI({id:'',meta:meta});
+		if(uri!='::') o.index('uri').get(uri).onsuccess=function(e){
+			var r=e.target.result;
+			if(r) callback(r); else callback(newScript());
+		}; else callback(newScript());
+	}
+	function queryId() {
+		if(id) o.get(id).onsuccess=function(e){
+			var r=e.target.result;
+			if(r) callback(r); else queryMeta();
+		}; else queryMeta();
+	}
+	queryId();
+}
+function parseScript(o,src,callback) {
+	var i,r={status:0,message:'message' in o?o.message:_('msgUpdated')};
+	function finish(){
+		if(src) chrome.tabs.sendMessage(src.tab.id,{cmd:'ShowMessage',data:r});
+		updateItem(r);
+	}
+	if(o.status&&o.status!=200||o.code=='') {	// net error
+		r.status=-1;r.message=_('msgErrorFetchingScript');finish();
+	} else {	// store script
+		var meta=parseMeta(o.code);
+		queryScript(o.id,meta,function(c){
+			if(!c.id){r.status=1;r.message=_('msgInstalled');}
+			if(o.more) for(i in o.more) c[i]=o.more[i];	// for import and user edit
+			c.meta=meta;c.code=o.code;r.obj=getMeta(c);c.uri=getNameURI(c);
+			if(o.from&&!c.meta.homepage&&!c.custom.homepage&&!/^(file|data):/.test(o.from)) c.custom.homepage=o.from;
+			if(o.url&&!c.meta.downloadURL&&!c.custom.downloadURL) c.custom.downloadURL=o.url;
+			saveScript(c,src).onsuccess=function(e){
+				r.id=c.id=e.target.result;finish();
+			};
+		});
+		meta.require.forEach(fetchRequire);	// @require
+		for(d in meta.resources) fetchCache(meta.resources[d]);	// @resource
+		if(meta.icon) fetchCache(meta.icon);	// @icon
+	}
+	if(callback) callback();
+}
+function canUpdate(o,n){
+  o=(o||'').split('.');
+  n=(n||'').split('.');
+  var r=/(\d*)([a-z]*)(\d*)([a-z]*)/i;
+  while(o.length&&n.length){
+    var vo=o.shift().match(r),vn=n.shift().match(r);
+    vo.shift();vn.shift();
+    vo[0]=parseInt(vo[0]||0,10);
+    vo[2]=parseInt(vo[2]||0,10);
+    vn[0]=parseInt(vn[0]||0,10);
+    vn[2]=parseInt(vn[2]||0,10);
+    while(vo.length&&vn.length){
+      var eo=vo.shift(),en=vn.shift();
+      if(eo!=en) return eo<en;
+    }
+  }
+  return n.length;
+}
+function setValue(data,src,callback){
+	var o=db.transaction('values','readwrite').objectStore('values');
+	o.put({uri:data.uri,values:data.values});
+	if(callback) callback();	// it seems that CALLBACK does not work with READWRITE transaction
+}
+function getOption(data,src,callback){
+	var o=db.transaction('settings').objectStore('settings');
+	o.get(data).onsuccess=function(e){
+		var r=e.target.result;
+		if(callback) callback(r.value);
+	};
+}
+function setOption(data,src,callback){
+	var o=db.transaction('settings','readwrite').objectStore('settings');
+	o.put({key:data.key,value:data.value});
+	if(callback) callback(data.value);
+}
+function enableScript(d,src,callback) {
+	var o=db.transaction('scripts','readwrite').objectStore('scripts');
+	o.get(d.id).onsuccess=function(e){
+		var r=e.target.result;
+		if(!r) return;
+		r.enabled=d.data?1:0;
+		o.put(r).onsuccess=function(e){	// store script without another transaction
+		//saveScript(r,src).onsuccess=function(e){
+			updateItem({id:d.id,obj:getMeta(r),status:0});
+		};
+	};
+	if(callback) callback();
+}
+function checkUpdateO(o) {
+  var r={id: o.id, hideUpdate: 1, status: 2};
+  function update() {
+    var u = o.custom.downloadURL || o.meta.downloadURL;
+    if (u) {
+      r.message = _('msgUpdating');
+      fetchURL(u, function() {
+        parseScript({
+					id: o.id,
+          status: this.status,
+          code: this.responseText
+        });
+      });
+    } else r.message = '<span class=new>' + _('msgNewVersion') + '</span>';
+    updateItem(r);
+  }
+  var u = o.custom.updateURL || o.meta.updateURL;
+  if (u) {
+    r.message = _('msgCheckingForUpdate');
+    updateItem(r);
+    fetchURL(u, function() {
+      r.message = _('msgErrorFetchingUpdateInfo');
+      if (this.status == 200) try {
+        var m = parseMeta(this.responseText);
+        if (canUpdate(o.meta.version, m.version)) return update();
+        r.message = _('msgNoUpdate');
+      } catch (e) {}
+      delete r.hideUpdate;
+      updateItem(r);
+    });
+  }
+}
+function checkUpdate(id,src,callback) {
+	var o=db.transaction('scripts').objectStore('scripts');
+	o.get(id).onsuccess=function(e){
+		var r=e.target.result;
+		if(r) checkUpdateO(r);
+		if(callback) callback();
+	};
+}
+function checkUpdateAll(e,src,callback) {
+	setOption({key:'lastUpdate',value:Date.now()});
+	var o=db.transaction('scripts').objectStore('scripts');
+	o.index('update').openCursor(1).onsuccess=function(e){
+		var r=e.target.result;
+		if(!r) {
+			if(callback) callback();
+			return;
+		}
+		checkUpdateO(r.value);
+		r.continue();
+	};
+}
+var checking=false;
+function autoCheck() {
+  function check() {
+		getOption('autoUpdate',null,function(a){
+			if(a) getOption('lastUpdate',null,function(l){
+				if(Date.now()-l>=864e5) checkUpdateAll();
+				setTimeout(check,36e5);
+			}); else checking=false;
+		});
+  }
+  if(!checking) {checking=true;check();}
+}
+function autoUpdate(o,src,callback){
+	o=!!o;
+	setOption({key:'autoUpdate',value:o},src,autoCheck);
+	if(callback) callback(o);
+}
+function getData(data,src,callback) {
+	function getSettings(){
+		var o=db.transaction('settings').objectStore('settings');
+		o.openCursor().onsuccess=function(e){
+			var r=e.target.result,v;
+			if(r) {
+				v=r.value;
+				settings[v.key]=v.value;
+				r.continue();
+			} else getScripts();
+		};
+	}
+	function getScripts(){
+		var o=db.transaction('scripts').objectStore('scripts');
+		o.index('position').openCursor().onsuccess=function(e){
+			var r=e.target.result,v;
+			if(r) {
+				v=r.value;
+				if(v.meta.icon&&!(v.meta.icon in settings.cache)) {
+					cache.push(v.meta.icon);
+					settings.cache[v.meta.icon]=null;
+				}
+				settings.scripts.push(getMeta(v));
+				r.continue();
+			} else getCache();
+		};
+	}
+	function getCache(){
+		var o=db.transaction('cache').objectStore('cache');
+		function loop(){
+			var i=cache.pop();
+			if(i) {
+				o.get(i).onsuccess=function(e){
+					var r=e.target.result;
+					if(r) {
+						var b=new Blob([r.data],{type:'image/png'});
+						settings.cache[i]=URL.createObjectURL(b);
+						URL.revokeObjectURL(b);
+					}
+					loop();
+				};
+			} else callback(settings);
+		}
+		loop();
+	}
+	var settings={scripts:[],cache:{}},cache=[];
+	getSettings();
+}
+function exportZip(z,src,callback){
+	function getScripts(){
+		function loop(){
+			var i=z.data.shift();
+			if(i) o.get(i).onsuccess=function(e){
+				var r=e.target.result;
+				if(r) {
+					d.data.push(r);
+					if(z.values) values.push(r.uri);
+				}
+				loop();
+			}; else getValues();
+		}
+		var o=db.transaction('scripts').objectStore('scripts');
+		loop();
+	}
+	function getValues(){
+		function loop(){
+			var i=values.shift();
+			if(i) o.get(i).onsuccess=function(e){
+				var r=e.target.result;
+				if(r) d.values[i]=r.values;
+				loop();
+			}; else finish();
+		}
+		if(z.values) {
+			var o=db.transaction('values').objectStore('values');
+			d.values={};loop();
+		} else finish();
+	}
+	function finish(){callback(d);}
+	var d={data:[]},values=[];
+	getScripts();
+}
+
+chrome.runtime.onConnect.addListener(function(p){
+	port=p;
+	p.onDisconnect.addListener(function(){port=null;});
+});
+chrome.runtime.onMessage.addListener(function(req,src,callback) {
+	var maps={
+		NewScript:function(o,src,callback){
+			o=newScript();
+			saveScript(o,src).onsuccess=function(e){
+				o.id=e.target.result;
+				chrome.tabs.sendMessage(src.tab.id,{cmd:'NewScript',data:o});
+			};
+			if(callback) callback();
+		},
+		RemoveScript: removeScript,
+		GetData: getData,
+		FindScript: findScript,
+		CheckUpdate: checkUpdate,
+		CheckUpdateAll: checkUpdateAll,
+		SaveScript: saveScript,
+		EnableScript: enableScript,
+		SetValue: setValue,
+		GetOption: getOption,
+		SetOption: setOption,
+		ExportZip: exportZip,
+		ParseScript: parseScript,
+		GetScript: getScript,	// for user edit
+		GetMetas: getMetas,	// for popup menu
+		AutoUpdate: autoUpdate,
+		Vacuum: vacuum,
+		Move: move,
+		LoadDirectly:loadDirectly,
+	},f=maps[req.cmd];
+	if(f) f(req.data,src,callback);
+	return true;
+});
+initDb(function(){
+	getOption('maxPosition',function(p){
+		if(p) pos=p;
+	});
+	getOption('isApplied',null,function(o){
+		chrome.browserAction.setIcon({path:'images/icon19'+(o?'':'w')+'.png'});
+	});
+	setTimeout(autoCheck,2e3);
+});
+var directUrls={};
+function loadDirectly(o,src,callback){
+	directUrls[o]=1;
+	if(callback) callback();
+}
+chrome.webRequest.onBeforeRequest.addListener(function(o){
+	if(directUrls[o.url]) delete directUrls[o.url];
+	else if(/\.user\.js(\?|$)/.test(o.url)) {
+		if(o.tabId<0) chrome.tabs.create({url:chrome.extension.getURL('/confirm.html')+'?url='+encodeURIComponent(o.url)});
+		else chrome.tabs.get(o.tabId,function(t){
+			chrome.tabs.create({url:chrome.extension.getURL('/confirm.html')+'?url='+encodeURIComponent(o.url)+'&from='+encodeURIComponent(t.url)});
+		});
+		return {redirectUrl:'javascript:history.back()'};
+	}
+},{
+	urls:['*://*/*','file://*/*'],types:['main_frame']
+},['blocking']);

+ 24 - 0
common.js

@@ -0,0 +1,24 @@
+function format() {
+  var a = arguments;
+  if (a[0]) return a[0].replace(/\$(?:\{(\d+)\}|(\d+))/g, function(v, g1, g2) {
+    g1 = a[g1 || g2];
+    if (g1 == undefined) g1 = v;
+    return g1;
+  });
+}
+function initCSS(){
+	var s=document.createElement('style');
+	s.innerHTML=_('css');
+	document.head.appendChild(s);
+}
+function _(){
+	var t=chrome.i18n.getMessage.apply(this,arguments);
+	if(!t) t=arguments[0];
+	return t;
+}
+function initI18n(){
+	window.addEventListener('DOMContentLoaded',function(){
+		var nodes=document.querySelectorAll('.i18n'),i,t;
+		for(i=0;i<nodes.length;i++) nodes[i].innerHTML=_(nodes[i].innerHTML);
+	},true);
+}

+ 30 - 0
confirm.html

@@ -0,0 +1,30 @@
+<!doctype html>
+<html class=fill>
+	<head>
+		<meta http-equiv="content-type" content="text/html;charset=utf-8">
+		<link rel="stylesheet" type="text/css" href="style.css" />
+		<link rel="stylesheet" type="text/css" href="lib/codemirror/codemirror.css" />
+		<link rel="shortcut icon" type="image/png" href="icons/icon_16.png" />
+		<script type="text/javascript" src="lib/codemirror/codemirror.js"></script>
+		<script type="text/javascript" src="lib/codemirror/javascript.js"></script>
+		<script type="text/javascript" src="lib/codemirror/continuecomment.js"></script>
+		<script type="text/javascript" src="lib/codemirror/matchbrackets.js"></script>
+		<script type="text/javascript" src="lib/codemirror/match-highlighter.js"></script>
+		<script type="text/javascript" src="lib/codemirror/search.js"></script>
+		<script type="text/javascript" src="lib/codemirror/searchcursor.js"></script>
+		<title class=i18n>extName</title>
+	</head>
+	<body class=fill>
+		<table class=frame>
+			<tr>
+				<td><h2><span class=i18n>labelInstall</span> - <span class=i18n>extName</span></h2></td>
+				<td class=buttons><button class=i18n id=bInstall disabled=disabled>buttonConfirmInstall</button> <button class=i18n id=bClose>buttonClose</button></td>
+			</tr>
+			<tr><td colspan=2 id=msg></td></tr>
+			<tr class=expand><td colspan=2 class=expandr><textarea id=eCode class=expanda></textarea></td></tr>
+			<tr><td colspan=2 class=center><span class=i18n>anchorSupportPage</span> - <span class=i18n>labelAuthor</span> - 2013</td></tr>
+		</table>
+	</body>
+</html>
+<script src=common.js></script>
+<script src=confirm.js></script>

+ 61 - 0
confirm.js

@@ -0,0 +1,61 @@
+function checkScript(t){
+	if(/^\s*</.test(t)) {	// seems to be HTML code
+		M.innerHTML=_('msgNotJS',[data.url]);
+		chrome.runtime.sendMessage({cmd:'LoadDirectly',data:data.url},function(){
+			setTimeout(function(){location.replace(data.url);},3000);
+		});
+	} else {	// may be JS code
+		M.innerHTML=_('msgLoadedJS',[data.url]);
+		T.setValue(t);
+		I.disabled=false;
+	}
+}
+var $=document.getElementById.bind(document),M=$('msg'),I=$('bInstall'),data={},
+		T=CodeMirror.fromTextArea($('eCode'),{
+			lineNumbers:true,
+			matchBrackets:true,
+			mode:'text/typescript',
+			lineWrapping:true,
+			indentUnit:4,
+			indentWithTabs:true,
+			readOnly:true,
+		});
+initCSS();initI18n();
+$('bClose').onclick=function(){window.close();};
+I.onclick=function(){
+	chrome.runtime.sendMessage({
+		cmd:'ParseScript',
+		data:{
+			url:data.url,
+			from:data.from,
+			code:T.getValue(),
+		},
+	});
+	I.disabled=true;
+};
+chrome.runtime.onMessage.addListener(function(req,src,callback) {
+	var maps={
+		ShowMessage: function(o){
+			M.innerHTML=o.message;
+			if(callback) callback();
+		},
+	},f=maps[req.cmd];
+	if(f) f(req.data,src,callback);
+	return true;
+});
+(function(s){
+	s.split('&').forEach(function(i){
+		i.replace(/^([^=]*)=(.*)$/,function(r,g1,g2){data[g1]=decodeURIComponent(g2);});
+	});
+	function error(){M.innerHTML=_('msgErrorLoadingURL',[data.url]);}
+	if(!data.url) error(); else {
+		M.innerHTML=_('msgLoadingURL',[data.url]);
+		var x=new XMLHttpRequest();
+		x.open('GET',data.url,true);
+		x.onloadend=function(){
+			if(this.status==200&&this.responseText) checkScript(this.responseText);
+			else error();
+		};
+		x.send();
+	}
+})(location.search.slice(1));

BIN
images/icon128.png


BIN
images/icon16.png


BIN
images/icon19.png


BIN
images/icon19w.png


BIN
images/icon48.png


+ 343 - 0
injected.js

@@ -0,0 +1,343 @@
+/**
+* http://www.webtoolkit.info/javascript-utf8.html
+*/
+function utf8decode (utftext) {
+	var string = "";
+	var i = 0;
+	var c = 0, c1 = 0, c2 = 0, c3 = 0;
+	while ( i < utftext.length ) {
+		c = utftext.charCodeAt(i);
+		if (c < 128) {string += String.fromCharCode(c);i++;}
+		else if((c > 191) && (c < 224)) {
+			c2 = utftext.charCodeAt(i+1);
+			string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
+			i += 2;
+		} else {
+			c2 = utftext.charCodeAt(i+1);
+			c3 = utftext.charCodeAt(i+2);
+			string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
+			i += 3;
+		}
+	}
+	return string;
+}
+
+// Messages
+var id=Math.random();
+function post(data,callback){
+	if(!data.data) data.data={};
+	data.data['frameid']=id;
+	chrome.runtime.sendMessage(data);
+}
+chrome.runtime.onMessage.addListener(function(req,src) {
+	if(req.frameid&&req.frameid!=id) return;
+	var maps={
+		Command:command,
+		GetPopup: getPopup,
+	},f=maps[req.cmd];
+	if(f) f(req.data,src);
+});
+function getPopup(){
+	chrome.runtime.sendMessage({cmd:'SetPopup',data:[menu,ids]});
+}
+
+// Communicator
+var comm={
+	vmid:'VM'+Math.random(),
+	sid:null,
+	did:null,
+	init:function(s,d){
+		comm.sid=comm.vmid+s;
+		comm.did=comm.vmid+d;
+		document.addEventListener(comm.sid,comm['handle'+s],false);
+	},
+	post:function(d){
+		var e=document.createEvent("MutationEvent");
+		e.initMutationEvent(comm.did,false,false,null,null,null,JSON.stringify(d),e.ADDITION);
+		document.dispatchEvent(e);
+	},
+	handleR:function(e){
+		var o=JSON.parse(e.attrName),maps={
+			LoadScript:comm.loadScript,
+			Command:function(o){
+				var f=comm.command[o];
+				if(f) f();
+			},
+			GotRequestId:function(o){comm.qrequests.shift().start(o);},
+			HttpRequested:function(o){
+				var c=comm.requests[o.id];
+				if(c) c.callback(o);
+			},
+		},f=maps[o.cmd];
+		if(f) f(o.data);
+	},
+	loadScript:function(o){
+		var start=[],body=[],end=[],cache,require,values,elements;
+		comm.command={};comm.requests={};comm.qrequests=[];
+		function wrapper(c){
+			var t=this,value=values[c.uri];if(!value) value={};
+
+			// functions and properties
+			function wrapFunction(o,i,c){
+				var f=function(){
+					var r;
+					try{r=Function.apply.apply(o[i],[o,arguments]);}
+					catch(e){console.log('Error calling '+i+': \n'+e.stack);}
+					if(c) r=c(r);return r;
+				};
+				f.__proto__=o[i];f.prototype=o[i].prototype;
+				return f;
+			}
+			function wrapWindow(w){return w==window?t:w;}
+			function wrapItem(i){
+				try{	// avoid reading protected data
+					if(typeof window[i]=='function') {
+						if(itemWrapper) t[i]=itemWrapper(window,i,wrapWindow);
+						else t[i]=window[i];
+					} else Object.defineProperty(t,i,{
+						get:function(){return wrapWindow(window[i]);},
+						set:function(v){window[i]=v;},
+					});
+				}catch(e){}
+			}
+			var itemWrapper=null;
+			Object.getOwnPropertyNames(window).forEach(wrapItem);
+			itemWrapper=wrapFunction;
+			n=window;while(n=Object.getPrototypeOf(n)) Object.getOwnPropertyNames(n).forEach(wrapItem);
+
+			function getCache(name){for(var i in resources) if(name==i) return cache[resources[i]];}
+			function propertyToString(){return 'Property for Violentmonkey: designed by Gerald';}
+			function addProperty(name,prop,obj){
+				if('value' in prop) prop.writable=false;
+				prop.configurable=false;
+				if(!obj) {obj=t;elements.push(name);}
+				Object.defineProperty(obj,name,prop);
+				if(typeof obj[name]=='function') obj[name].toString=propertyToString;
+			}
+			var resources=c.meta.resources||{};elements=[];
+			addProperty('unsafeWindow',{value:window});
+			// GM functions
+			// Reference: http://wiki.greasespot.net/Greasemonkey_Manual:API
+			addProperty('GM_info',{get:function(){
+				var m=c.code.match(/\/\/\s+==UserScript==\s+([\s\S]*?)\/\/\s+==\/UserScript==\s/),
+						script={
+							description:c.meta.description||'',
+							excludes:c.meta.exclude.concat(),
+							includes:c.meta.include.concat(),
+							matches:c.meta.match.concat(),
+							name:c.meta.name||'',
+							namespace:c.meta.namespace||'',
+							resources:{},
+							'run-at':c.meta['run-at']||'document-end',
+							unwrap:false,
+							version:c.meta.version||'',
+						},
+						o={};
+				addProperty('script',{value:{}},o);
+				addProperty('scriptMetaStr',{value:m?m[1]:''},o);
+				addProperty('scriptWillUpdate',{value:c.update},o);
+				addProperty('version',{value:undefined},o);
+				for(m in script) addProperty(m,{value:script[m]},o.script);
+				for(m in c.meta.resources) addProperty(m,{value:c.meta.resources[m]},o.script.resources);
+				return o;
+			}});
+			addProperty('GM_deleteValue',{value:function(key){delete value[key];comm.post({cmd:'SetValue',data:{uri:c.uri,values:value}});}});
+			addProperty('GM_getValue',{value:function(k,d){
+				var v=value[k];
+				if(v) {
+					k=v[0];
+					v=v.slice(1);
+					switch(k){
+						case 'n': d=Number(v);break;
+						case 'b': d=v=='true';break;
+						case 'o': try{d=JSON.parse(v);}catch(e){console.log(e);}break;
+						default: d=v;
+					}
+				}
+				return d;
+			}});
+			addProperty('GM_listValues',{value:function(){return Object.getOwnPropertyNames(value);}});
+			addProperty('GM_setValue',{value:function(key,val){
+				var t=(typeof val)[0];
+				switch(t){
+					case 'o':val=t+JSON.stringify(val);break;
+					default:val=t+val;
+				}
+				value[key]=val;comm.post({cmd:'SetValue',data:{uri:c.uri,values:value}});
+			}});
+			addProperty('GM_getResourceText',{value:function(name){
+				var b=getCache(name);
+				if(b) b=utf8decode(b);
+				return b;
+			}});
+			addProperty('GM_getResourceURL',{value:function(name){
+				return getCache(name);
+			}});
+			addProperty('GM_addStyle',{value:function(css){
+				if(!document.head) return;
+				var v=document.createElement('style');
+				v.innerHTML=css;
+				document.head.appendChild(v);
+				return v;
+			}});
+			addProperty('GM_log',{value:console.log});
+			addProperty('GM_openInTab',{value:function(url){window.open(url);}});
+			addProperty('GM_registerMenuCommand',{value:function(cap,func,acc){
+				comm.command[cap]=func;comm.post({cmd:'RegisterMenu',data:[cap,acc]});
+			}});
+			function Request(details){
+				this.callback=function(d){
+					var c=details['on'+d.type];
+					if(c) c(d.data);
+					if(!this.id) for(var i in d.data) this.req[i]=d.data[i];
+					if(d.type=='load') delete comm.requests[this.id];
+				};
+				this.start=function(id){
+					this.id=id;
+					comm.requests[id]=this;
+					comm.post({cmd:'HttpRequest',data:{
+						id:id,
+						method:details.method,
+						url:details.url,
+						data:details.data,
+						async:!details.synchronous,
+						user:details.user,
+						password:details.password,
+						headers:details.headers,
+						overrideMimeType:details.overrideMimeType,
+					}});
+				};
+				this.req={
+					abort:function(){comm.post({cmd:'AbortRequest',data:this.id});}
+				};
+				comm.qrequests.push(this);
+				comm.post({cmd:'GetRequestId'});
+			};
+			addProperty('GM_xmlhttpRequest',{value:function(details){
+				var r=new Request(details);
+				return r.req;
+			}});
+		}
+		function runStart(){while(start.length) runCode(start.shift());}
+		function runBody(){
+			if(document.body) {
+				window.removeEventListener('DOMNodeInserted',runBody,true);
+				while(body.length) runCode(body.shift());
+			}
+		}
+		function runEnd(){while(end.length) runCode(end.shift());}
+		function runCode(c){
+			var w=new wrapper(c),req=c.meta.require||[],i,r,f,code=[];
+			elements.forEach(function(i){code.push(i+'=window.'+i);});
+			code=['(function(){var '+code.join(',')+';'];
+			for(i=0;i<req.length;i++) if(r=require[req[i]]) code.push(r);
+			code.push(c.code);code.push('})();');
+			code=code.join('\n');
+			f=new Function('w','with(w) eval('+JSON.stringify(code)+');');
+			try{f.call(w,w);}catch(e){
+				console.log('Error running script: '+(c.custom.name||c.meta.name||c.id)+'\n'+e);
+			}
+		}
+
+		var l;
+		o.scripts.forEach(function(i){
+			if(i&&i.enabled) {
+				switch(i.custom['run-at']||i.meta['run-at']){
+					case 'document-start': l=start;break;
+					case 'document-body': l=body;break;
+					default: l=end;
+				}
+				l.push(i);
+			}
+		});
+		require=o.require;
+		cache=o.cache;
+		values=o.values;
+		runStart();
+		window.addEventListener('DOMNodeInserted',runBody,true);
+		window.addEventListener('DOMContentLoaded',runEnd,false);
+		runBody();
+		if(document.readyState=='complete') runEnd();
+	},
+},menu=[],ids=[];
+function handleC(e){
+	var o=JSON.parse(e.attrName),maps={
+		SetValue:function(o){post({cmd:'SetValue',data:o});},
+		RegisterMenu:menu.push.bind(menu),
+		GetRequestId:getRequestId,
+		HttpRequest:httpRequest,
+		AbortRequest:abortRequest,
+	},f=maps[o.cmd];
+	if(f) f(o.data);
+}
+function command(o){
+	comm.post({cmd:'Command',data:o});
+}
+
+// Requests
+var requests={};
+function getRequestId() {
+  var id=Date.now()+Math.random().toString().slice(1);
+  requests[id]=new XMLHttpRequest();
+	comm.post({cmd:'GotRequestId',data:id});
+}
+function httpRequest(details) {
+  function callback(evt) {
+    comm.post({
+      cmd: 'HttpRequested',
+      data: {
+        id: details.id,
+        type: evt.type,
+        data: {
+          readyState: req.readyState,
+          responseHeaders: req.getAllResponseHeaders(),
+          responseText: req.responseText,
+          status: req.status,
+          statusText: req.statusText
+        }
+      }
+    });
+  }
+  var i,req;
+  if(details.id) req=requests[details.id]; else req=new XMLHttpRequest();
+  try {
+    req.open(details.method,details.url,details.async,details.user,details.password);
+    if(details.headers) for(i in details.headers) req.setRequestHeader(i,details.headers[i]);
+    if(details.overrideMimeType) req.overrideMimeType(details.overrideMimeType);
+    ['abort','error','load','progress','readystatechange','timeout'].forEach(function(i) {
+      req['on'+i]=callback;
+    });
+    req.send(details.data);
+    if(!details.id) callback({type:'load'});
+  } catch (e) {
+		console.log(e);
+  }
+}
+function abortRequest(id) {
+  var req=requests[id];
+  if(req) req.abort();
+  delete requests[id];
+}
+
+// For injected scripts
+function objEncode(o){
+	var t=[],i;
+	for(i in o) {
+		if(!o.hasOwnProperty(i)) continue;
+		if(typeof o[i]=='function') t.push(i+':'+o[i].toString());
+		else t.push(i+':'+JSON.stringify(o[i]));
+	}
+	return '{'+t.join(',')+'}';
+}
+function initCommunicator(){
+	var s=document.createElement('script'),d=document.documentElement,C='C',R='R';
+	s.innerHTML='(function(){var comm='+objEncode(comm)+';comm.init("'+R+'","'+C+'");})();';
+	d.appendChild(s);d.removeChild(s);
+	comm.handleC=handleC;comm.init(C,R);
+	chrome.runtime.sendMessage({cmd:'FindScript'},loadScript);
+}
+function loadScript(o){
+	o.scripts.forEach(function(i){ids.push(i.id);});
+	comm.post({cmd:'LoadScript',data:o});
+}
+initCommunicator();

+ 19 - 0
lib/codemirror/LICENSE

@@ -0,0 +1,19 @@
+Copyright (C) 2013 by Marijn Haverbeke <[email protected]> and others
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.

+ 263 - 0
lib/codemirror/codemirror.css

@@ -0,0 +1,263 @@
+/* BASICS */
+
+.CodeMirror {
+  /* Set height, width, borders, and global font properties here */
+  font-family: monospace;
+  height: 300px;
+}
+.CodeMirror-scroll {
+  /* Set scrolling behaviour here */
+  overflow: auto;
+}
+
+/* PADDING */
+
+.CodeMirror-lines {
+  padding: 4px 0; /* Vertical padding around content */
+}
+.CodeMirror pre {
+  padding: 0 4px; /* Horizontal padding of content */
+}
+
+.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
+  background-color: white; /* The little square between H and V scrollbars */
+}
+
+/* GUTTER */
+
+.CodeMirror-gutters {
+  border-right: 1px solid #ddd;
+  background-color: #f7f7f7;
+  white-space: nowrap;
+}
+.CodeMirror-linenumbers {}
+.CodeMirror-linenumber {
+  padding: 0 3px 0 5px;
+  min-width: 20px;
+  text-align: right;
+  color: #999;
+}
+
+/* CURSOR */
+
+.CodeMirror div.CodeMirror-cursor {
+  border-left: 1px solid black;
+  z-index: 3;
+}
+/* Shown when moving in bi-directional text */
+.CodeMirror div.CodeMirror-secondarycursor {
+  border-left: 1px solid silver;
+}
+.CodeMirror.cm-keymap-fat-cursor div.CodeMirror-cursor {
+  width: auto;
+  border: 0;
+  background: #7e7;
+  z-index: 1;
+}
+/* Can style cursor different in overwrite (non-insert) mode */
+.CodeMirror div.CodeMirror-cursor.CodeMirror-overwrite {}
+
+.cm-tab { display: inline-block; }
+
+/* DEFAULT THEME */
+
+.cm-s-default .cm-keyword {color: #708;}
+.cm-s-default .cm-atom {color: #219;}
+.cm-s-default .cm-number {color: #164;}
+.cm-s-default .cm-def {color: #00f;}
+.cm-s-default .cm-variable {color: black;}
+.cm-s-default .cm-variable-2 {color: #05a;}
+.cm-s-default .cm-variable-3 {color: #085;}
+.cm-s-default .cm-property {color: black;}
+.cm-s-default .cm-operator {color: black;}
+.cm-s-default .cm-comment {color: #a50;}
+.cm-s-default .cm-string {color: #a11;}
+.cm-s-default .cm-string-2 {color: #f50;}
+.cm-s-default .cm-meta {color: #555;}
+.cm-s-default .cm-error {color: #f00;}
+.cm-s-default .cm-qualifier {color: #555;}
+.cm-s-default .cm-builtin {color: #30a;}
+.cm-s-default .cm-bracket {color: #997;}
+.cm-s-default .cm-tag {color: #170;}
+.cm-s-default .cm-attribute {color: #00c;}
+.cm-s-default .cm-header {color: blue;}
+.cm-s-default .cm-quote {color: #090;}
+.cm-s-default .cm-hr {color: #999;}
+.cm-s-default .cm-link {color: #00c;}
+
+.cm-negative {color: #d44;}
+.cm-positive {color: #292;}
+.cm-header, .cm-strong {font-weight: bold;}
+.cm-em {font-style: italic;}
+.cm-link {text-decoration: underline;}
+
+.cm-invalidchar {color: #f00;}
+
+div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;}
+div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
+.CodeMirror-activeline-background {background: #e8f2ff;}
+
+/* STOP */
+
+/* The rest of this file contains styles related to the mechanics of
+   the editor. You probably shouldn't touch them. */
+
+.CodeMirror {
+  line-height: 1;
+  position: relative;
+  overflow: hidden;
+  background: white;
+  color: black;
+}
+
+.CodeMirror-scroll {
+  /* 30px is the magic margin used to hide the element's real scrollbars */
+  /* See overflow: hidden in .CodeMirror */
+  margin-bottom: -30px; margin-right: -30px;
+  padding-bottom: 30px; padding-right: 30px;
+  height: 100%;
+  outline: none; /* Prevent dragging from highlighting the element */
+  position: relative;
+  -moz-box-sizing: content-box;
+  box-sizing: content-box;
+}
+.CodeMirror-sizer {
+  position: relative;
+}
+
+/* The fake, visible scrollbars. Used to force redraw during scrolling
+   before actuall scrolling happens, thus preventing shaking and
+   flickering artifacts. */
+.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
+  position: absolute;
+  z-index: 6;
+  display: none;
+}
+.CodeMirror-vscrollbar {
+  right: 0; top: 0;
+  overflow-x: hidden;
+  overflow-y: scroll;
+}
+.CodeMirror-hscrollbar {
+  bottom: 0; left: 0;
+  overflow-y: hidden;
+  overflow-x: scroll;
+}
+.CodeMirror-scrollbar-filler {
+  right: 0; bottom: 0;
+}
+.CodeMirror-gutter-filler {
+  left: 0; bottom: 0;
+}
+
+.CodeMirror-gutters {
+  position: absolute; left: 0; top: 0;
+  padding-bottom: 30px;
+  z-index: 3;
+}
+.CodeMirror-gutter {
+  white-space: normal;
+  height: 100%;
+  -moz-box-sizing: content-box;
+  box-sizing: content-box;
+  padding-bottom: 30px;
+  margin-bottom: -32px;
+  display: inline-block;
+  /* Hack to make IE7 behave */
+  *zoom:1;
+  *display:inline;
+}
+.CodeMirror-gutter-elt {
+  position: absolute;
+  cursor: default;
+  z-index: 4;
+}
+
+.CodeMirror-lines {
+  cursor: text;
+}
+.CodeMirror pre {
+  /* Reset some styles that the rest of the page might have set */
+  -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
+  border-width: 0;
+  background: transparent;
+  font-family: inherit;
+  font-size: inherit;
+  margin: 0;
+  white-space: pre;
+  word-wrap: normal;
+  line-height: inherit;
+  color: inherit;
+  z-index: 2;
+  position: relative;
+  overflow: visible;
+}
+.CodeMirror-wrap pre {
+  word-wrap: break-word;
+  white-space: pre-wrap;
+  word-break: normal;
+}
+.CodeMirror-code pre {
+  border-right: 30px solid transparent;
+  width: -webkit-fit-content;
+  width: -moz-fit-content;
+  width: fit-content;
+}
+.CodeMirror-wrap .CodeMirror-code pre {
+  border-right: none;
+  width: auto;
+}
+.CodeMirror-linebackground {
+  position: absolute;
+  left: 0; right: 0; top: 0; bottom: 0;
+  z-index: 0;
+}
+
+.CodeMirror-linewidget {
+  position: relative;
+  z-index: 2;
+  overflow: auto;
+}
+
+.CodeMirror-widget {}
+
+.CodeMirror-wrap .CodeMirror-scroll {
+  overflow-x: hidden;
+}
+
+.CodeMirror-measure {
+  position: absolute;
+  width: 100%;
+  height: 0;
+  overflow: hidden;
+  visibility: hidden;
+}
+.CodeMirror-measure pre { position: static; }
+
+.CodeMirror div.CodeMirror-cursor {
+  position: absolute;
+  visibility: hidden;
+  border-right: none;
+  width: 0;
+}
+.CodeMirror-focused div.CodeMirror-cursor {
+  visibility: visible;
+}
+
+.CodeMirror-selected { background: #d9d9d9; }
+.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
+
+.cm-searching {
+  background: #ffa;
+  background: rgba(255, 255, 0, .4);
+}
+
+/* IE7 hack to prevent it from returning funny offsetTops on the spans */
+.CodeMirror span { *vertical-align: text-bottom; }
+
+@media print {
+  /* Hide the cursor when printing */
+  .CodeMirror div.CodeMirror-cursor {
+    visibility: hidden;
+  }
+}

+ 5862 - 0
lib/codemirror/codemirror.js

@@ -0,0 +1,5862 @@
+// CodeMirror is the only global var we claim
+window.CodeMirror = (function() {
+  "use strict";
+
+  // BROWSER SNIFFING
+
+  // Crude, but necessary to handle a number of hard-to-feature-detect
+  // bugs and behavior differences.
+  var gecko = /gecko\/\d/i.test(navigator.userAgent);
+  var ie = /MSIE \d/.test(navigator.userAgent);
+  var ie_lt8 = ie && (document.documentMode == null || document.documentMode < 8);
+  var ie_lt9 = ie && (document.documentMode == null || document.documentMode < 9);
+  var webkit = /WebKit\//.test(navigator.userAgent);
+  var qtwebkit = webkit && /Qt\/\d+\.\d+/.test(navigator.userAgent);
+  var chrome = /Chrome\//.test(navigator.userAgent);
+  var opera = /Opera\//.test(navigator.userAgent);
+  var safari = /Apple Computer/.test(navigator.vendor);
+  var khtml = /KHTML\//.test(navigator.userAgent);
+  var mac_geLion = /Mac OS X 1\d\D([7-9]|\d\d)\D/.test(navigator.userAgent);
+  var mac_geMountainLion = /Mac OS X 1\d\D([8-9]|\d\d)\D/.test(navigator.userAgent);
+  var phantom = /PhantomJS/.test(navigator.userAgent);
+
+  var ios = /AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent);
+  // This is woefully incomplete. Suggestions for alternative methods welcome.
+  var mobile = ios || /Android|webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(navigator.userAgent);
+  var mac = ios || /Mac/.test(navigator.platform);
+  var windows = /win/i.test(navigator.platform);
+
+  var opera_version = opera && navigator.userAgent.match(/Version\/(\d*\.\d*)/);
+  if (opera_version) opera_version = Number(opera_version[1]);
+  if (opera_version && opera_version >= 15) { opera = false; webkit = true; }
+  // Some browsers use the wrong event properties to signal cmd/ctrl on OS X
+  var flipCtrlCmd = mac && (qtwebkit || opera && (opera_version == null || opera_version < 12.11));
+  var captureMiddleClick = gecko || (ie && !ie_lt9);
+
+  // Optimize some code when these features are not used
+  var sawReadOnlySpans = false, sawCollapsedSpans = false;
+
+  // CONSTRUCTOR
+
+  function CodeMirror(place, options) {
+    if (!(this instanceof CodeMirror)) return new CodeMirror(place, options);
+
+    this.options = options = options || {};
+    // Determine effective options based on given values and defaults.
+    for (var opt in defaults) if (!options.hasOwnProperty(opt) && defaults.hasOwnProperty(opt))
+      options[opt] = defaults[opt];
+    setGuttersForLineNumbers(options);
+
+    var docStart = typeof options.value == "string" ? 0 : options.value.first;
+    var display = this.display = makeDisplay(place, docStart);
+    display.wrapper.CodeMirror = this;
+    updateGutters(this);
+    if (options.autofocus && !mobile) focusInput(this);
+
+    this.state = {keyMaps: [],
+                  overlays: [],
+                  modeGen: 0,
+                  overwrite: false, focused: false,
+                  suppressEdits: false, pasteIncoming: false,
+                  draggingText: false,
+                  highlight: new Delayed()};
+
+    themeChanged(this);
+    if (options.lineWrapping)
+      this.display.wrapper.className += " CodeMirror-wrap";
+
+    var doc = options.value;
+    if (typeof doc == "string") doc = new Doc(options.value, options.mode);
+    operation(this, attachDoc)(this, doc);
+
+    // Override magic textarea content restore that IE sometimes does
+    // on our hidden textarea on reload
+    if (ie) setTimeout(bind(resetInput, this, true), 20);
+
+    registerEventHandlers(this);
+    // IE throws unspecified error in certain cases, when
+    // trying to access activeElement before onload
+    var hasFocus; try { hasFocus = (document.activeElement == display.input); } catch(e) { }
+    if (hasFocus || (options.autofocus && !mobile)) setTimeout(bind(onFocus, this), 20);
+    else onBlur(this);
+
+    operation(this, function() {
+      for (var opt in optionHandlers)
+        if (optionHandlers.propertyIsEnumerable(opt))
+          optionHandlers[opt](this, options[opt], Init);
+      for (var i = 0; i < initHooks.length; ++i) initHooks[i](this);
+    })();
+  }
+
+  // DISPLAY CONSTRUCTOR
+
+  function makeDisplay(place, docStart) {
+    var d = {};
+
+    var input = d.input = elt("textarea", null, null, "position: absolute; padding: 0; width: 1px; height: 1em; outline: none; font-size: 4px;");
+    if (webkit) input.style.width = "1000px";
+    else input.setAttribute("wrap", "off");
+    // if border: 0; -- iOS fails to open keyboard (issue #1287)
+    if (ios) input.style.border = "1px solid black";
+    input.setAttribute("autocorrect", "off"); input.setAttribute("autocapitalize", "off"); input.setAttribute("spellcheck", "false");
+
+    // Wraps and hides input textarea
+    d.inputDiv = elt("div", [input], null, "overflow: hidden; position: relative; width: 3px; height: 0px;");
+    // The actual fake scrollbars.
+    d.scrollbarH = elt("div", [elt("div", null, null, "height: 1px")], "CodeMirror-hscrollbar");
+    d.scrollbarV = elt("div", [elt("div", null, null, "width: 1px")], "CodeMirror-vscrollbar");
+    d.scrollbarFiller = elt("div", null, "CodeMirror-scrollbar-filler");
+    d.gutterFiller = elt("div", null, "CodeMirror-gutter-filler");
+    // DIVs containing the selection and the actual code
+    d.lineDiv = elt("div", null, "CodeMirror-code");
+    d.selectionDiv = elt("div", null, null, "position: relative; z-index: 1");
+    // Blinky cursor, and element used to ensure cursor fits at the end of a line
+    d.cursor = elt("div", "\u00a0", "CodeMirror-cursor");
+    // Secondary cursor, shown when on a 'jump' in bi-directional text
+    d.otherCursor = elt("div", "\u00a0", "CodeMirror-cursor CodeMirror-secondarycursor");
+    // Used to measure text size
+    d.measure = elt("div", null, "CodeMirror-measure");
+    // Wraps everything that needs to exist inside the vertically-padded coordinate system
+    d.lineSpace = elt("div", [d.measure, d.selectionDiv, d.lineDiv, d.cursor, d.otherCursor],
+                         null, "position: relative; outline: none");
+    // Moved around its parent to cover visible view
+    d.mover = elt("div", [elt("div", [d.lineSpace], "CodeMirror-lines")], null, "position: relative");
+    // Set to the height of the text, causes scrolling
+    d.sizer = elt("div", [d.mover], "CodeMirror-sizer");
+    // D is needed because behavior of elts with overflow: auto and padding is inconsistent across browsers
+    d.heightForcer = elt("div", null, null, "position: absolute; height: " + scrollerCutOff + "px; width: 1px;");
+    // Will contain the gutters, if any
+    d.gutters = elt("div", null, "CodeMirror-gutters");
+    d.lineGutter = null;
+    // Provides scrolling
+    d.scroller = elt("div", [d.sizer, d.heightForcer, d.gutters], "CodeMirror-scroll");
+    d.scroller.setAttribute("tabIndex", "-1");
+    // The element in which the editor lives.
+    d.wrapper = elt("div", [d.inputDiv, d.scrollbarH, d.scrollbarV,
+                            d.scrollbarFiller, d.gutterFiller, d.scroller], "CodeMirror");
+    // Work around IE7 z-index bug
+    if (ie_lt8) { d.gutters.style.zIndex = -1; d.scroller.style.paddingRight = 0; }
+    if (place.appendChild) place.appendChild(d.wrapper); else place(d.wrapper);
+
+    // Needed to hide big blue blinking cursor on Mobile Safari
+    if (ios) input.style.width = "0px";
+    if (!webkit) d.scroller.draggable = true;
+    // Needed to handle Tab key in KHTML
+    if (khtml) { d.inputDiv.style.height = "1px"; d.inputDiv.style.position = "absolute"; }
+    // Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8).
+    else if (ie_lt8) d.scrollbarH.style.minWidth = d.scrollbarV.style.minWidth = "18px";
+
+    // Current visible range (may be bigger than the view window).
+    d.viewOffset = d.lastSizeC = 0;
+    d.showingFrom = d.showingTo = docStart;
+
+    // Used to only resize the line number gutter when necessary (when
+    // the amount of lines crosses a boundary that makes its width change)
+    d.lineNumWidth = d.lineNumInnerWidth = d.lineNumChars = null;
+    // See readInput and resetInput
+    d.prevInput = "";
+    // Set to true when a non-horizontal-scrolling widget is added. As
+    // an optimization, widget aligning is skipped when d is false.
+    d.alignWidgets = false;
+    // Flag that indicates whether we currently expect input to appear
+    // (after some event like 'keypress' or 'input') and are polling
+    // intensively.
+    d.pollingFast = false;
+    // Self-resetting timeout for the poller
+    d.poll = new Delayed();
+
+    d.cachedCharWidth = d.cachedTextHeight = null;
+    d.measureLineCache = [];
+    d.measureLineCachePos = 0;
+
+    // Tracks when resetInput has punted to just putting a short
+    // string instead of the (large) selection.
+    d.inaccurateSelection = false;
+
+    // Tracks the maximum line length so that the horizontal scrollbar
+    // can be kept static when scrolling.
+    d.maxLine = null;
+    d.maxLineLength = 0;
+    d.maxLineChanged = false;
+
+    // Used for measuring wheel scrolling granularity
+    d.wheelDX = d.wheelDY = d.wheelStartX = d.wheelStartY = null;
+
+    return d;
+  }
+
+  // STATE UPDATES
+
+  // Used to get the editor into a consistent state again when options change.
+
+  function loadMode(cm) {
+    cm.doc.mode = CodeMirror.getMode(cm.options, cm.doc.modeOption);
+    cm.doc.iter(function(line) {
+      if (line.stateAfter) line.stateAfter = null;
+      if (line.styles) line.styles = null;
+    });
+    cm.doc.frontier = cm.doc.first;
+    startWorker(cm, 100);
+    cm.state.modeGen++;
+    if (cm.curOp) regChange(cm);
+  }
+
+  function wrappingChanged(cm) {
+    if (cm.options.lineWrapping) {
+      cm.display.wrapper.className += " CodeMirror-wrap";
+      cm.display.sizer.style.minWidth = "";
+    } else {
+      cm.display.wrapper.className = cm.display.wrapper.className.replace(" CodeMirror-wrap", "");
+      computeMaxLength(cm);
+    }
+    estimateLineHeights(cm);
+    regChange(cm);
+    clearCaches(cm);
+    setTimeout(function(){updateScrollbars(cm);}, 100);
+  }
+
+  function estimateHeight(cm) {
+    var th = textHeight(cm.display), wrapping = cm.options.lineWrapping;
+    var perLine = wrapping && Math.max(5, cm.display.scroller.clientWidth / charWidth(cm.display) - 3);
+    return function(line) {
+      if (lineIsHidden(cm.doc, line))
+        return 0;
+      else if (wrapping)
+        return (Math.ceil(line.text.length / perLine) || 1) * th;
+      else
+        return th;
+    };
+  }
+
+  function estimateLineHeights(cm) {
+    var doc = cm.doc, est = estimateHeight(cm);
+    doc.iter(function(line) {
+      var estHeight = est(line);
+      if (estHeight != line.height) updateLineHeight(line, estHeight);
+    });
+  }
+
+  function keyMapChanged(cm) {
+    var map = keyMap[cm.options.keyMap], style = map.style;
+    cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-keymap-\S+/g, "") +
+      (style ? " cm-keymap-" + style : "");
+    cm.state.disableInput = map.disableInput;
+  }
+
+  function themeChanged(cm) {
+    cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-s-\S+/g, "") +
+      cm.options.theme.replace(/(^|\s)\s*/g, " cm-s-");
+    clearCaches(cm);
+  }
+
+  function guttersChanged(cm) {
+    updateGutters(cm);
+    regChange(cm);
+    setTimeout(function(){alignHorizontally(cm);}, 20);
+  }
+
+  function updateGutters(cm) {
+    var gutters = cm.display.gutters, specs = cm.options.gutters;
+    removeChildren(gutters);
+    for (var i = 0; i < specs.length; ++i) {
+      var gutterClass = specs[i];
+      var gElt = gutters.appendChild(elt("div", null, "CodeMirror-gutter " + gutterClass));
+      if (gutterClass == "CodeMirror-linenumbers") {
+        cm.display.lineGutter = gElt;
+        gElt.style.width = (cm.display.lineNumWidth || 1) + "px";
+      }
+    }
+    gutters.style.display = i ? "" : "none";
+  }
+
+  function lineLength(doc, line) {
+    if (line.height == 0) return 0;
+    var len = line.text.length, merged, cur = line;
+    while (merged = collapsedSpanAtStart(cur)) {
+      var found = merged.find();
+      cur = getLine(doc, found.from.line);
+      len += found.from.ch - found.to.ch;
+    }
+    cur = line;
+    while (merged = collapsedSpanAtEnd(cur)) {
+      var found = merged.find();
+      len -= cur.text.length - found.from.ch;
+      cur = getLine(doc, found.to.line);
+      len += cur.text.length - found.to.ch;
+    }
+    return len;
+  }
+
+  function computeMaxLength(cm) {
+    var d = cm.display, doc = cm.doc;
+    d.maxLine = getLine(doc, doc.first);
+    d.maxLineLength = lineLength(doc, d.maxLine);
+    d.maxLineChanged = true;
+    doc.iter(function(line) {
+      var len = lineLength(doc, line);
+      if (len > d.maxLineLength) {
+        d.maxLineLength = len;
+        d.maxLine = line;
+      }
+    });
+  }
+
+  // Make sure the gutters options contains the element
+  // "CodeMirror-linenumbers" when the lineNumbers option is true.
+  function setGuttersForLineNumbers(options) {
+    var found = false;
+    for (var i = 0; i < options.gutters.length; ++i) {
+      if (options.gutters[i] == "CodeMirror-linenumbers") {
+        if (options.lineNumbers) found = true;
+        else options.gutters.splice(i--, 1);
+      }
+    }
+    if (!found && options.lineNumbers)
+      options.gutters.push("CodeMirror-linenumbers");
+  }
+
+  // SCROLLBARS
+
+  // Re-synchronize the fake scrollbars with the actual size of the
+  // content. Optionally force a scrollTop.
+  function updateScrollbars(cm) {
+    var d = cm.display, docHeight = cm.doc.height;
+    var totalHeight = docHeight + paddingVert(d);
+    d.sizer.style.minHeight = d.heightForcer.style.top = totalHeight + "px";
+    d.gutters.style.height = Math.max(totalHeight, d.scroller.clientHeight - scrollerCutOff) + "px";
+    var scrollHeight = Math.max(totalHeight, d.scroller.scrollHeight);
+    var needsH = d.scroller.scrollWidth > (d.scroller.clientWidth + 1);
+    var needsV = scrollHeight > (d.scroller.clientHeight + 1);
+    if (needsV) {
+      d.scrollbarV.style.display = "block";
+      d.scrollbarV.style.bottom = needsH ? scrollbarWidth(d.measure) + "px" : "0";
+      d.scrollbarV.firstChild.style.height =
+        (scrollHeight - d.scroller.clientHeight + d.scrollbarV.clientHeight) + "px";
+    } else {
+      d.scrollbarV.style.display = "";
+      d.scrollbarV.firstChild.style.height = "0";
+    }
+    if (needsH) {
+      d.scrollbarH.style.display = "block";
+      d.scrollbarH.style.right = needsV ? scrollbarWidth(d.measure) + "px" : "0";
+      d.scrollbarH.firstChild.style.width =
+        (d.scroller.scrollWidth - d.scroller.clientWidth + d.scrollbarH.clientWidth) + "px";
+    } else {
+      d.scrollbarH.style.display = "";
+      d.scrollbarH.firstChild.style.width = "0";
+    }
+    if (needsH && needsV) {
+      d.scrollbarFiller.style.display = "block";
+      d.scrollbarFiller.style.height = d.scrollbarFiller.style.width = scrollbarWidth(d.measure) + "px";
+    } else d.scrollbarFiller.style.display = "";
+    if (needsH && cm.options.coverGutterNextToScrollbar && cm.options.fixedGutter) {
+      d.gutterFiller.style.display = "block";
+      d.gutterFiller.style.height = scrollbarWidth(d.measure) + "px";
+      d.gutterFiller.style.width = d.gutters.offsetWidth + "px";
+    } else d.gutterFiller.style.display = "";
+
+    if (mac_geLion && scrollbarWidth(d.measure) === 0)
+      d.scrollbarV.style.minWidth = d.scrollbarH.style.minHeight = mac_geMountainLion ? "18px" : "12px";
+  }
+
+  function visibleLines(display, doc, viewPort) {
+    var top = display.scroller.scrollTop, height = display.wrapper.clientHeight;
+    if (typeof viewPort == "number") top = viewPort;
+    else if (viewPort) {top = viewPort.top; height = viewPort.bottom - viewPort.top;}
+    top = Math.floor(top - paddingTop(display));
+    var bottom = Math.ceil(top + height);
+    return {from: lineAtHeight(doc, top), to: lineAtHeight(doc, bottom)};
+  }
+
+  // LINE NUMBERS
+
+  function alignHorizontally(cm) {
+    var display = cm.display;
+    if (!display.alignWidgets && (!display.gutters.firstChild || !cm.options.fixedGutter)) return;
+    var comp = compensateForHScroll(display) - display.scroller.scrollLeft + cm.doc.scrollLeft;
+    var gutterW = display.gutters.offsetWidth, l = comp + "px";
+    for (var n = display.lineDiv.firstChild; n; n = n.nextSibling) if (n.alignable) {
+      for (var i = 0, a = n.alignable; i < a.length; ++i) a[i].style.left = l;
+    }
+    if (cm.options.fixedGutter)
+      display.gutters.style.left = (comp + gutterW) + "px";
+  }
+
+  function maybeUpdateLineNumberWidth(cm) {
+    if (!cm.options.lineNumbers) return false;
+    var doc = cm.doc, last = lineNumberFor(cm.options, doc.first + doc.size - 1), display = cm.display;
+    if (last.length != display.lineNumChars) {
+      var test = display.measure.appendChild(elt("div", [elt("div", last)],
+                                                 "CodeMirror-linenumber CodeMirror-gutter-elt"));
+      var innerW = test.firstChild.offsetWidth, padding = test.offsetWidth - innerW;
+      display.lineGutter.style.width = "";
+      display.lineNumInnerWidth = Math.max(innerW, display.lineGutter.offsetWidth - padding);
+      display.lineNumWidth = display.lineNumInnerWidth + padding;
+      display.lineNumChars = display.lineNumInnerWidth ? last.length : -1;
+      display.lineGutter.style.width = display.lineNumWidth + "px";
+      return true;
+    }
+    return false;
+  }
+
+  function lineNumberFor(options, i) {
+    return String(options.lineNumberFormatter(i + options.firstLineNumber));
+  }
+  function compensateForHScroll(display) {
+    return getRect(display.scroller).left - getRect(display.sizer).left;
+  }
+
+  // DISPLAY DRAWING
+
+  function updateDisplay(cm, changes, viewPort, forced) {
+    var oldFrom = cm.display.showingFrom, oldTo = cm.display.showingTo, updated;
+    var visible = visibleLines(cm.display, cm.doc, viewPort);
+    for (;;) {
+      if (!updateDisplayInner(cm, changes, visible, forced)) break;
+      forced = false;
+      updated = true;
+      updateSelection(cm);
+      updateScrollbars(cm);
+
+      // Clip forced viewport to actual scrollable area
+      if (viewPort)
+        viewPort = Math.min(cm.display.scroller.scrollHeight - cm.display.scroller.clientHeight,
+                            typeof viewPort == "number" ? viewPort : viewPort.top);
+      visible = visibleLines(cm.display, cm.doc, viewPort);
+      if (visible.from >= cm.display.showingFrom && visible.to <= cm.display.showingTo)
+        break;
+      changes = [];
+    }
+
+    if (updated) {
+      signalLater(cm, "update", cm);
+      if (cm.display.showingFrom != oldFrom || cm.display.showingTo != oldTo)
+        signalLater(cm, "viewportChange", cm, cm.display.showingFrom, cm.display.showingTo);
+    }
+    return updated;
+  }
+
+  // Uses a set of changes plus the current scroll position to
+  // determine which DOM updates have to be made, and makes the
+  // updates.
+  function updateDisplayInner(cm, changes, visible, forced) {
+    var display = cm.display, doc = cm.doc;
+    if (!display.wrapper.clientWidth) {
+      display.showingFrom = display.showingTo = doc.first;
+      display.viewOffset = 0;
+      return;
+    }
+
+    // Bail out if the visible area is already rendered and nothing changed.
+    if (!forced && changes.length == 0 &&
+        visible.from > display.showingFrom && visible.to < display.showingTo)
+      return;
+
+    if (maybeUpdateLineNumberWidth(cm))
+      changes = [{from: doc.first, to: doc.first + doc.size}];
+    var gutterW = display.sizer.style.marginLeft = display.gutters.offsetWidth + "px";
+    display.scrollbarH.style.left = cm.options.fixedGutter ? gutterW : "0";
+
+    // Used to determine which lines need their line numbers updated
+    var positionsChangedFrom = Infinity;
+    if (cm.options.lineNumbers)
+      for (var i = 0; i < changes.length; ++i)
+        if (changes[i].diff && changes[i].from < positionsChangedFrom) { positionsChangedFrom = changes[i].from; }
+
+    var end = doc.first + doc.size;
+    var from = Math.max(visible.from - cm.options.viewportMargin, doc.first);
+    var to = Math.min(end, visible.to + cm.options.viewportMargin);
+    if (display.showingFrom < from && from - display.showingFrom < 20) from = Math.max(doc.first, display.showingFrom);
+    if (display.showingTo > to && display.showingTo - to < 20) to = Math.min(end, display.showingTo);
+    if (sawCollapsedSpans) {
+      from = lineNo(visualLine(doc, getLine(doc, from)));
+      while (to < end && lineIsHidden(doc, getLine(doc, to))) ++to;
+    }
+
+    // Create a range of theoretically intact lines, and punch holes
+    // in that using the change info.
+    var intact = [{from: Math.max(display.showingFrom, doc.first),
+                   to: Math.min(display.showingTo, end)}];
+    if (intact[0].from >= intact[0].to) intact = [];
+    else intact = computeIntact(intact, changes);
+    // When merged lines are present, we might have to reduce the
+    // intact ranges because changes in continued fragments of the
+    // intact lines do require the lines to be redrawn.
+    if (sawCollapsedSpans)
+      for (var i = 0; i < intact.length; ++i) {
+        var range = intact[i], merged;
+        while (merged = collapsedSpanAtEnd(getLine(doc, range.to - 1))) {
+          var newTo = merged.find().from.line;
+          if (newTo > range.from) range.to = newTo;
+          else { intact.splice(i--, 1); break; }
+        }
+      }
+
+    // Clip off the parts that won't be visible
+    var intactLines = 0;
+    for (var i = 0; i < intact.length; ++i) {
+      var range = intact[i];
+      if (range.from < from) range.from = from;
+      if (range.to > to) range.to = to;
+      if (range.from >= range.to) intact.splice(i--, 1);
+      else intactLines += range.to - range.from;
+    }
+    if (!forced && intactLines == to - from && from == display.showingFrom && to == display.showingTo) {
+      updateViewOffset(cm);
+      return;
+    }
+    intact.sort(function(a, b) {return a.from - b.from;});
+
+    // Avoid crashing on IE's "unspecified error" when in iframes
+    try {
+      var focused = document.activeElement;
+    } catch(e) {}
+    if (intactLines < (to - from) * .7) display.lineDiv.style.display = "none";
+    patchDisplay(cm, from, to, intact, positionsChangedFrom);
+    display.lineDiv.style.display = "";
+    if (focused && document.activeElement != focused && focused.offsetHeight) focused.focus();
+
+    var different = from != display.showingFrom || to != display.showingTo ||
+      display.lastSizeC != display.wrapper.clientHeight;
+    // This is just a bogus formula that detects when the editor is
+    // resized or the font size changes.
+    if (different) {
+      display.lastSizeC = display.wrapper.clientHeight;
+      startWorker(cm, 400);
+    }
+    display.showingFrom = from; display.showingTo = to;
+
+    updateHeightsInViewport(cm);
+    updateViewOffset(cm);
+
+    return true;
+  }
+
+  function updateHeightsInViewport(cm) {
+    var display = cm.display;
+    var prevBottom = display.lineDiv.offsetTop;
+    for (var node = display.lineDiv.firstChild, height; node; node = node.nextSibling) if (node.lineObj) {
+      if (ie_lt8) {
+        var bot = node.offsetTop + node.offsetHeight;
+        height = bot - prevBottom;
+        prevBottom = bot;
+      } else {
+        var box = getRect(node);
+        height = box.bottom - box.top;
+      }
+      var diff = node.lineObj.height - height;
+      if (height < 2) height = textHeight(display);
+      if (diff > .001 || diff < -.001) {
+        updateLineHeight(node.lineObj, height);
+        var widgets = node.lineObj.widgets;
+        if (widgets) for (var i = 0; i < widgets.length; ++i)
+          widgets[i].height = widgets[i].node.offsetHeight;
+      }
+    }
+  }
+
+  function updateViewOffset(cm) {
+    var off = cm.display.viewOffset = heightAtLine(cm, getLine(cm.doc, cm.display.showingFrom));
+    // Position the mover div to align with the current virtual scroll position
+    cm.display.mover.style.top = off + "px";
+  }
+
+  function computeIntact(intact, changes) {
+    for (var i = 0, l = changes.length || 0; i < l; ++i) {
+      var change = changes[i], intact2 = [], diff = change.diff || 0;
+      for (var j = 0, l2 = intact.length; j < l2; ++j) {
+        var range = intact[j];
+        if (change.to <= range.from && change.diff) {
+          intact2.push({from: range.from + diff, to: range.to + diff});
+        } else if (change.to <= range.from || change.from >= range.to) {
+          intact2.push(range);
+        } else {
+          if (change.from > range.from)
+            intact2.push({from: range.from, to: change.from});
+          if (change.to < range.to)
+            intact2.push({from: change.to + diff, to: range.to + diff});
+        }
+      }
+      intact = intact2;
+    }
+    return intact;
+  }
+
+  function getDimensions(cm) {
+    var d = cm.display, left = {}, width = {};
+    for (var n = d.gutters.firstChild, i = 0; n; n = n.nextSibling, ++i) {
+      left[cm.options.gutters[i]] = n.offsetLeft;
+      width[cm.options.gutters[i]] = n.offsetWidth;
+    }
+    return {fixedPos: compensateForHScroll(d),
+            gutterTotalWidth: d.gutters.offsetWidth,
+            gutterLeft: left,
+            gutterWidth: width,
+            wrapperWidth: d.wrapper.clientWidth};
+  }
+
+  function patchDisplay(cm, from, to, intact, updateNumbersFrom) {
+    var dims = getDimensions(cm);
+    var display = cm.display, lineNumbers = cm.options.lineNumbers;
+    if (!intact.length && (!webkit || !cm.display.currentWheelTarget))
+      removeChildren(display.lineDiv);
+    var container = display.lineDiv, cur = container.firstChild;
+
+    function rm(node) {
+      var next = node.nextSibling;
+      if (webkit && mac && cm.display.currentWheelTarget == node) {
+        node.style.display = "none";
+        node.lineObj = null;
+      } else {
+        node.parentNode.removeChild(node);
+      }
+      return next;
+    }
+
+    var nextIntact = intact.shift(), lineN = from;
+    cm.doc.iter(from, to, function(line) {
+      if (nextIntact && nextIntact.to == lineN) nextIntact = intact.shift();
+      if (lineIsHidden(cm.doc, line)) {
+        if (line.height != 0) updateLineHeight(line, 0);
+        if (line.widgets && cur && cur.previousSibling) for (var i = 0; i < line.widgets.length; ++i) {
+          var w = line.widgets[i];
+          if (w.showIfHidden) {
+            var prev = cur.previousSibling;
+            if (/pre/i.test(prev.nodeName)) {
+              var wrap = elt("div", null, null, "position: relative");
+              prev.parentNode.replaceChild(wrap, prev);
+              wrap.appendChild(prev);
+              prev = wrap;
+            }
+            var wnode = prev.appendChild(elt("div", [w.node], "CodeMirror-linewidget"));
+            if (!w.handleMouseEvents) wnode.ignoreEvents = true;
+            positionLineWidget(w, wnode, prev, dims);
+          }
+        }
+      } else if (nextIntact && nextIntact.from <= lineN && nextIntact.to > lineN) {
+        // This line is intact. Skip to the actual node. Update its
+        // line number if needed.
+        while (cur.lineObj != line) cur = rm(cur);
+        if (lineNumbers && updateNumbersFrom <= lineN && cur.lineNumber)
+          setTextContent(cur.lineNumber, lineNumberFor(cm.options, lineN));
+        cur = cur.nextSibling;
+      } else {
+        // For lines with widgets, make an attempt to find and reuse
+        // the existing element, so that widgets aren't needlessly
+        // removed and re-inserted into the dom
+        if (line.widgets) for (var j = 0, search = cur, reuse; search && j < 20; ++j, search = search.nextSibling)
+          if (search.lineObj == line && /div/i.test(search.nodeName)) { reuse = search; break; }
+        // This line needs to be generated.
+        var lineNode = buildLineElement(cm, line, lineN, dims, reuse);
+        if (lineNode != reuse) {
+          container.insertBefore(lineNode, cur);
+        } else {
+          while (cur != reuse) cur = rm(cur);
+          cur = cur.nextSibling;
+        }
+
+        lineNode.lineObj = line;
+      }
+      ++lineN;
+    });
+    while (cur) cur = rm(cur);
+  }
+
+  function buildLineElement(cm, line, lineNo, dims, reuse) {
+    var lineElement = lineContent(cm, line);
+    var markers = line.gutterMarkers, display = cm.display, wrap;
+
+    if (!cm.options.lineNumbers && !markers && !line.bgClass && !line.wrapClass && !line.widgets)
+      return lineElement;
+
+    // Lines with gutter elements, widgets or a background class need
+    // to be wrapped again, and have the extra elements added to the
+    // wrapper div
+
+    if (reuse) {
+      reuse.alignable = null;
+      var isOk = true, widgetsSeen = 0, insertBefore = null;
+      for (var n = reuse.firstChild, next; n; n = next) {
+        next = n.nextSibling;
+        if (!/\bCodeMirror-linewidget\b/.test(n.className)) {
+          reuse.removeChild(n);
+        } else {
+          for (var i = 0; i < line.widgets.length; ++i) {
+            var widget = line.widgets[i];
+            if (widget.node == n.firstChild) {
+              if (!widget.above && !insertBefore) insertBefore = n;
+              positionLineWidget(widget, n, reuse, dims);
+              ++widgetsSeen;
+              break;
+            }
+          }
+          if (i == line.widgets.length) { isOk = false; break; }
+        }
+      }
+      reuse.insertBefore(lineElement, insertBefore);
+      if (isOk && widgetsSeen == line.widgets.length) {
+        wrap = reuse;
+        reuse.className = line.wrapClass || "";
+      }
+    }
+    if (!wrap) {
+      wrap = elt("div", null, line.wrapClass, "position: relative");
+      wrap.appendChild(lineElement);
+    }
+    // Kludge to make sure the styled element lies behind the selection (by z-index)
+    if (line.bgClass)
+      wrap.insertBefore(elt("div", null, line.bgClass + " CodeMirror-linebackground"), wrap.firstChild);
+    if (cm.options.lineNumbers || markers) {
+      var gutterWrap = wrap.insertBefore(elt("div", null, null, "position: absolute; left: " +
+                                             (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + "px"),
+                                         wrap.firstChild);
+      if (cm.options.fixedGutter) (wrap.alignable || (wrap.alignable = [])).push(gutterWrap);
+      if (cm.options.lineNumbers && (!markers || !markers["CodeMirror-linenumbers"]))
+        wrap.lineNumber = gutterWrap.appendChild(
+          elt("div", lineNumberFor(cm.options, lineNo),
+              "CodeMirror-linenumber CodeMirror-gutter-elt",
+              "left: " + dims.gutterLeft["CodeMirror-linenumbers"] + "px; width: "
+              + display.lineNumInnerWidth + "px"));
+      if (markers)
+        for (var k = 0; k < cm.options.gutters.length; ++k) {
+          var id = cm.options.gutters[k], found = markers.hasOwnProperty(id) && markers[id];
+          if (found)
+            gutterWrap.appendChild(elt("div", [found], "CodeMirror-gutter-elt", "left: " +
+                                       dims.gutterLeft[id] + "px; width: " + dims.gutterWidth[id] + "px"));
+        }
+    }
+    if (ie_lt8) wrap.style.zIndex = 2;
+    if (line.widgets && wrap != reuse) for (var i = 0, ws = line.widgets; i < ws.length; ++i) {
+      var widget = ws[i], node = elt("div", [widget.node], "CodeMirror-linewidget");
+      if (!widget.handleMouseEvents) node.ignoreEvents = true;
+      positionLineWidget(widget, node, wrap, dims);
+      if (widget.above)
+        wrap.insertBefore(node, cm.options.lineNumbers && line.height != 0 ? gutterWrap : lineElement);
+      else
+        wrap.appendChild(node);
+      signalLater(widget, "redraw");
+    }
+    return wrap;
+  }
+
+  function positionLineWidget(widget, node, wrap, dims) {
+    if (widget.noHScroll) {
+      (wrap.alignable || (wrap.alignable = [])).push(node);
+      var width = dims.wrapperWidth;
+      node.style.left = dims.fixedPos + "px";
+      if (!widget.coverGutter) {
+        width -= dims.gutterTotalWidth;
+        node.style.paddingLeft = dims.gutterTotalWidth + "px";
+      }
+      node.style.width = width + "px";
+    }
+    if (widget.coverGutter) {
+      node.style.zIndex = 5;
+      node.style.position = "relative";
+      if (!widget.noHScroll) node.style.marginLeft = -dims.gutterTotalWidth + "px";
+    }
+  }
+
+  // SELECTION / CURSOR
+
+  function updateSelection(cm) {
+    var display = cm.display;
+    var collapsed = posEq(cm.doc.sel.from, cm.doc.sel.to);
+    if (collapsed || cm.options.showCursorWhenSelecting)
+      updateSelectionCursor(cm);
+    else
+      display.cursor.style.display = display.otherCursor.style.display = "none";
+    if (!collapsed)
+      updateSelectionRange(cm);
+    else
+      display.selectionDiv.style.display = "none";
+
+    // Move the hidden textarea near the cursor to prevent scrolling artifacts
+    if (cm.options.moveInputWithCursor) {
+      var headPos = cursorCoords(cm, cm.doc.sel.head, "div");
+      var wrapOff = getRect(display.wrapper), lineOff = getRect(display.lineDiv);
+      display.inputDiv.style.top = Math.max(0, Math.min(display.wrapper.clientHeight - 10,
+                                                        headPos.top + lineOff.top - wrapOff.top)) + "px";
+      display.inputDiv.style.left = Math.max(0, Math.min(display.wrapper.clientWidth - 10,
+                                                         headPos.left + lineOff.left - wrapOff.left)) + "px";
+    }
+  }
+
+  // No selection, plain cursor
+  function updateSelectionCursor(cm) {
+    var display = cm.display, pos = cursorCoords(cm, cm.doc.sel.head, "div");
+    display.cursor.style.left = pos.left + "px";
+    display.cursor.style.top = pos.top + "px";
+    display.cursor.style.height = Math.max(0, pos.bottom - pos.top) * cm.options.cursorHeight + "px";
+    display.cursor.style.display = "";
+
+    if (pos.other) {
+      display.otherCursor.style.display = "";
+      display.otherCursor.style.left = pos.other.left + "px";
+      display.otherCursor.style.top = pos.other.top + "px";
+      display.otherCursor.style.height = (pos.other.bottom - pos.other.top) * .85 + "px";
+    } else { display.otherCursor.style.display = "none"; }
+  }
+
+  // Highlight selection
+  function updateSelectionRange(cm) {
+    var display = cm.display, doc = cm.doc, sel = cm.doc.sel;
+    var fragment = document.createDocumentFragment();
+    var clientWidth = display.lineSpace.offsetWidth, pl = paddingLeft(cm.display);
+
+    function add(left, top, width, bottom) {
+      if (top < 0) top = 0;
+      fragment.appendChild(elt("div", null, "CodeMirror-selected", "position: absolute; left: " + left +
+                               "px; top: " + top + "px; width: " + (width == null ? clientWidth - left : width) +
+                               "px; height: " + (bottom - top) + "px"));
+    }
+
+    function drawForLine(line, fromArg, toArg) {
+      var lineObj = getLine(doc, line);
+      var lineLen = lineObj.text.length;
+      var start, end;
+      function coords(ch, bias) {
+        return charCoords(cm, Pos(line, ch), "div", lineObj, bias);
+      }
+
+      iterateBidiSections(getOrder(lineObj), fromArg || 0, toArg == null ? lineLen : toArg, function(from, to, dir) {
+        var leftPos = coords(from, "left"), rightPos, left, right;
+        if (from == to) {
+          rightPos = leftPos;
+          left = right = leftPos.left;
+        } else {
+          rightPos = coords(to - 1, "right");
+          if (dir == "rtl") { var tmp = leftPos; leftPos = rightPos; rightPos = tmp; }
+          left = leftPos.left;
+          right = rightPos.right;
+        }
+        if (fromArg == null && from == 0) left = pl;
+        if (rightPos.top - leftPos.top > 3) { // Different lines, draw top part
+          add(left, leftPos.top, null, leftPos.bottom);
+          left = pl;
+          if (leftPos.bottom < rightPos.top) add(left, leftPos.bottom, null, rightPos.top);
+        }
+        if (toArg == null && to == lineLen) right = clientWidth;
+        if (!start || leftPos.top < start.top || leftPos.top == start.top && leftPos.left < start.left)
+          start = leftPos;
+        if (!end || rightPos.bottom > end.bottom || rightPos.bottom == end.bottom && rightPos.right > end.right)
+          end = rightPos;
+        if (left < pl + 1) left = pl;
+        add(left, rightPos.top, right - left, rightPos.bottom);
+      });
+      return {start: start, end: end};
+    }
+
+    if (sel.from.line == sel.to.line) {
+      drawForLine(sel.from.line, sel.from.ch, sel.to.ch);
+    } else {
+      var fromLine = getLine(doc, sel.from.line), toLine = getLine(doc, sel.to.line);
+      var singleVLine = visualLine(doc, fromLine) == visualLine(doc, toLine);
+      var leftEnd = drawForLine(sel.from.line, sel.from.ch, singleVLine ? fromLine.text.length : null).end;
+      var rightStart = drawForLine(sel.to.line, singleVLine ? 0 : null, sel.to.ch).start;
+      if (singleVLine) {
+        if (leftEnd.top < rightStart.top - 2) {
+          add(leftEnd.right, leftEnd.top, null, leftEnd.bottom);
+          add(pl, rightStart.top, rightStart.left, rightStart.bottom);
+        } else {
+          add(leftEnd.right, leftEnd.top, rightStart.left - leftEnd.right, leftEnd.bottom);
+        }
+      }
+      if (leftEnd.bottom < rightStart.top)
+        add(pl, leftEnd.bottom, null, rightStart.top);
+    }
+
+    removeChildrenAndAdd(display.selectionDiv, fragment);
+    display.selectionDiv.style.display = "";
+  }
+
+  // Cursor-blinking
+  function restartBlink(cm) {
+    if (!cm.state.focused) return;
+    var display = cm.display;
+    clearInterval(display.blinker);
+    var on = true;
+    display.cursor.style.visibility = display.otherCursor.style.visibility = "";
+    if (cm.options.cursorBlinkRate > 0)
+      display.blinker = setInterval(function() {
+        display.cursor.style.visibility = display.otherCursor.style.visibility = (on = !on) ? "" : "hidden";
+      }, cm.options.cursorBlinkRate);
+  }
+
+  // HIGHLIGHT WORKER
+
+  function startWorker(cm, time) {
+    if (cm.doc.mode.startState && cm.doc.frontier < cm.display.showingTo)
+      cm.state.highlight.set(time, bind(highlightWorker, cm));
+  }
+
+  function highlightWorker(cm) {
+    var doc = cm.doc;
+    if (doc.frontier < doc.first) doc.frontier = doc.first;
+    if (doc.frontier >= cm.display.showingTo) return;
+    var end = +new Date + cm.options.workTime;
+    var state = copyState(doc.mode, getStateBefore(cm, doc.frontier));
+    var changed = [], prevChange;
+    doc.iter(doc.frontier, Math.min(doc.first + doc.size, cm.display.showingTo + 500), function(line) {
+      if (doc.frontier >= cm.display.showingFrom) { // Visible
+        var oldStyles = line.styles;
+        line.styles = highlightLine(cm, line, state);
+        var ischange = !oldStyles || oldStyles.length != line.styles.length;
+        for (var i = 0; !ischange && i < oldStyles.length; ++i) ischange = oldStyles[i] != line.styles[i];
+        if (ischange) {
+          if (prevChange && prevChange.end == doc.frontier) prevChange.end++;
+          else changed.push(prevChange = {start: doc.frontier, end: doc.frontier + 1});
+        }
+        line.stateAfter = copyState(doc.mode, state);
+      } else {
+        processLine(cm, line, state);
+        line.stateAfter = doc.frontier % 5 == 0 ? copyState(doc.mode, state) : null;
+      }
+      ++doc.frontier;
+      if (+new Date > end) {
+        startWorker(cm, cm.options.workDelay);
+        return true;
+      }
+    });
+    if (changed.length)
+      operation(cm, function() {
+        for (var i = 0; i < changed.length; ++i)
+          regChange(this, changed[i].start, changed[i].end);
+      })();
+  }
+
+  // Finds the line to start with when starting a parse. Tries to
+  // find a line with a stateAfter, so that it can start with a
+  // valid state. If that fails, it returns the line with the
+  // smallest indentation, which tends to need the least context to
+  // parse correctly.
+  function findStartLine(cm, n, precise) {
+    var minindent, minline, doc = cm.doc, maxScan = cm.doc.mode.innerMode ? 1000 : 100;
+    for (var search = n, lim = n - maxScan; search > lim; --search) {
+      if (search <= doc.first) return doc.first;
+      var line = getLine(doc, search - 1);
+      if (line.stateAfter && (!precise || search <= doc.frontier)) return search;
+      var indented = countColumn(line.text, null, cm.options.tabSize);
+      if (minline == null || minindent > indented) {
+        minline = search - 1;
+        minindent = indented;
+      }
+    }
+    return minline;
+  }
+
+  function getStateBefore(cm, n, precise) {
+    var doc = cm.doc, display = cm.display;
+    if (!doc.mode.startState) return true;
+    var pos = findStartLine(cm, n, precise), state = pos > doc.first && getLine(doc, pos-1).stateAfter;
+    if (!state) state = startState(doc.mode);
+    else state = copyState(doc.mode, state);
+    doc.iter(pos, n, function(line) {
+      processLine(cm, line, state);
+      var save = pos == n - 1 || pos % 5 == 0 || pos >= display.showingFrom && pos < display.showingTo;
+      line.stateAfter = save ? copyState(doc.mode, state) : null;
+      ++pos;
+    });
+    return state;
+  }
+
+  // POSITION MEASUREMENT
+
+  function paddingTop(display) {return display.lineSpace.offsetTop;}
+  function paddingVert(display) {return display.mover.offsetHeight - display.lineSpace.offsetHeight;}
+  function paddingLeft(display) {
+    var e = removeChildrenAndAdd(display.measure, elt("pre", null, null, "text-align: left")).appendChild(elt("span", "x"));
+    return e.offsetLeft;
+  }
+
+  function measureChar(cm, line, ch, data, bias) {
+    var dir = -1;
+    data = data || measureLine(cm, line);
+    if (data.crude) {
+      var left = data.left + ch * data.width;
+      return {left: left, right: left + data.width, top: data.top, bottom: data.bottom};
+    }
+
+    for (var pos = ch;; pos += dir) {
+      var r = data[pos];
+      if (r) break;
+      if (dir < 0 && pos == 0) dir = 1;
+    }
+    bias = pos > ch ? "left" : pos < ch ? "right" : bias;
+    if (bias == "left" && r.leftSide) r = r.leftSide;
+    else if (bias == "right" && r.rightSide) r = r.rightSide;
+    return {left: pos < ch ? r.right : r.left,
+            right: pos > ch ? r.left : r.right,
+            top: r.top,
+            bottom: r.bottom};
+  }
+
+  function findCachedMeasurement(cm, line) {
+    var cache = cm.display.measureLineCache;
+    for (var i = 0; i < cache.length; ++i) {
+      var memo = cache[i];
+      if (memo.text == line.text && memo.markedSpans == line.markedSpans &&
+          cm.display.scroller.clientWidth == memo.width &&
+          memo.classes == line.textClass + "|" + line.bgClass + "|" + line.wrapClass)
+        return memo;
+    }
+  }
+
+  function clearCachedMeasurement(cm, line) {
+    var exists = findCachedMeasurement(cm, line);
+    if (exists) exists.text = exists.measure = exists.markedSpans = null;
+  }
+
+  function measureLine(cm, line) {
+    // First look in the cache
+    var cached = findCachedMeasurement(cm, line);
+    if (cached) return cached.measure;
+
+    // Failing that, recompute and store result in cache
+    var measure = measureLineInner(cm, line);
+    var cache = cm.display.measureLineCache;
+    var memo = {text: line.text, width: cm.display.scroller.clientWidth,
+                markedSpans: line.markedSpans, measure: measure,
+                classes: line.textClass + "|" + line.bgClass + "|" + line.wrapClass};
+    if (cache.length == 16) cache[++cm.display.measureLineCachePos % 16] = memo;
+    else cache.push(memo);
+    return measure;
+  }
+
+  function measureLineInner(cm, line) {
+    if (!cm.options.lineWrapping && line.text.length >= cm.options.crudeMeasuringFrom)
+      return crudelyMeasureLine(cm, line);
+
+    var display = cm.display, measure = emptyArray(line.text.length);
+    var pre = lineContent(cm, line, measure, true);
+
+    // IE does not cache element positions of inline elements between
+    // calls to getBoundingClientRect. This makes the loop below,
+    // which gathers the positions of all the characters on the line,
+    // do an amount of layout work quadratic to the number of
+    // characters. When line wrapping is off, we try to improve things
+    // by first subdividing the line into a bunch of inline blocks, so
+    // that IE can reuse most of the layout information from caches
+    // for those blocks. This does interfere with line wrapping, so it
+    // doesn't work when wrapping is on, but in that case the
+    // situation is slightly better, since IE does cache line-wrapping
+    // information and only recomputes per-line.
+    if (ie && !ie_lt8 && !cm.options.lineWrapping && pre.childNodes.length > 100) {
+      var fragment = document.createDocumentFragment();
+      var chunk = 10, n = pre.childNodes.length;
+      for (var i = 0, chunks = Math.ceil(n / chunk); i < chunks; ++i) {
+        var wrap = elt("div", null, null, "display: inline-block");
+        for (var j = 0; j < chunk && n; ++j) {
+          wrap.appendChild(pre.firstChild);
+          --n;
+        }
+        fragment.appendChild(wrap);
+      }
+      pre.appendChild(fragment);
+    }
+
+    removeChildrenAndAdd(display.measure, pre);
+
+    var outer = getRect(display.lineDiv);
+    var vranges = [], data = emptyArray(line.text.length), maxBot = pre.offsetHeight;
+    // Work around an IE7/8 bug where it will sometimes have randomly
+    // replaced our pre with a clone at this point.
+    if (ie_lt9 && display.measure.first != pre)
+      removeChildrenAndAdd(display.measure, pre);
+
+    function measureRect(rect) {
+      var top = rect.top - outer.top, bot = rect.bottom - outer.top;
+      if (bot > maxBot) bot = maxBot;
+      if (top < 0) top = 0;
+      for (var i = vranges.length - 2; i >= 0; i -= 2) {
+        var rtop = vranges[i], rbot = vranges[i+1];
+        if (rtop > bot || rbot < top) continue;
+        if (rtop <= top && rbot >= bot ||
+            top <= rtop && bot >= rbot ||
+            Math.min(bot, rbot) - Math.max(top, rtop) >= (bot - top) >> 1) {
+          vranges[i] = Math.min(top, rtop);
+          vranges[i+1] = Math.max(bot, rbot);
+          break;
+        }
+      }
+      if (i < 0) { i = vranges.length; vranges.push(top, bot); }
+      return {left: rect.left - outer.left,
+              right: rect.right - outer.left,
+              top: i, bottom: null};
+    }
+    function finishRect(rect) {
+      rect.bottom = vranges[rect.top+1];
+      rect.top = vranges[rect.top];
+    }
+
+    for (var i = 0, cur; i < measure.length; ++i) if (cur = measure[i]) {
+      var node = cur, rect = null;
+      // A widget might wrap, needs special care
+      if (/\bCodeMirror-widget\b/.test(cur.className) && cur.getClientRects) {
+        if (cur.firstChild.nodeType == 1) node = cur.firstChild;
+        var rects = node.getClientRects();
+        if (rects.length > 1) {
+          rect = data[i] = measureRect(rects[0]);
+          rect.rightSide = measureRect(rects[rects.length - 1]);
+        }
+      }
+      if (!rect) rect = data[i] = measureRect(getRect(node));
+      if (cur.measureRight) rect.right = getRect(cur.measureRight).left;
+      if (cur.leftSide) rect.leftSide = measureRect(getRect(cur.leftSide));
+    }
+    removeChildren(cm.display.measure);
+    for (var i = 0, cur; i < data.length; ++i) if (cur = data[i]) {
+      finishRect(cur);
+      if (cur.leftSide) finishRect(cur.leftSide);
+      if (cur.rightSide) finishRect(cur.rightSide);
+    }
+    return data;
+  }
+
+  function crudelyMeasureLine(cm, line) {
+    var copy = new Line(line.text.slice(0, 100), null);
+    if (line.textClass) copy.textClass = line.textClass;
+    var measure = measureLineInner(cm, copy);
+    var left = measureChar(cm, copy, 0, measure, "left");
+    var right = measureChar(cm, copy, 99, measure, "right");
+    return {crude: true, top: left.top, left: left.left, bottom: left.bottom, width: (right.right - left.left) / 100};
+  }
+
+  function measureLineWidth(cm, line) {
+    var hasBadSpan = false;
+    if (line.markedSpans) for (var i = 0; i < line.markedSpans; ++i) {
+      var sp = line.markedSpans[i];
+      if (sp.collapsed && (sp.to == null || sp.to == line.text.length)) hasBadSpan = true;
+    }
+    var cached = !hasBadSpan && findCachedMeasurement(cm, line);
+    if (cached || line.text.length >= cm.options.crudeMeasuringFrom)
+      return measureChar(cm, line, line.text.length, cached && cached.measure, "right").right;
+
+    var pre = lineContent(cm, line, null, true);
+    var end = pre.appendChild(zeroWidthElement(cm.display.measure));
+    removeChildrenAndAdd(cm.display.measure, pre);
+    return getRect(end).right - getRect(cm.display.lineDiv).left;
+  }
+
+  function clearCaches(cm) {
+    cm.display.measureLineCache.length = cm.display.measureLineCachePos = 0;
+    cm.display.cachedCharWidth = cm.display.cachedTextHeight = null;
+    if (!cm.options.lineWrapping) cm.display.maxLineChanged = true;
+    cm.display.lineNumChars = null;
+  }
+
+  function pageScrollX() { return window.pageXOffset || (document.documentElement || document.body).scrollLeft; }
+  function pageScrollY() { return window.pageYOffset || (document.documentElement || document.body).scrollTop; }
+
+  // Context is one of "line", "div" (display.lineDiv), "local"/null (editor), or "page"
+  function intoCoordSystem(cm, lineObj, rect, context) {
+    if (lineObj.widgets) for (var i = 0; i < lineObj.widgets.length; ++i) if (lineObj.widgets[i].above) {
+      var size = widgetHeight(lineObj.widgets[i]);
+      rect.top += size; rect.bottom += size;
+    }
+    if (context == "line") return rect;
+    if (!context) context = "local";
+    var yOff = heightAtLine(cm, lineObj);
+    if (context == "local") yOff += paddingTop(cm.display);
+    else yOff -= cm.display.viewOffset;
+    if (context == "page" || context == "window") {
+      var lOff = getRect(cm.display.lineSpace);
+      yOff += lOff.top + (context == "window" ? 0 : pageScrollY());
+      var xOff = lOff.left + (context == "window" ? 0 : pageScrollX());
+      rect.left += xOff; rect.right += xOff;
+    }
+    rect.top += yOff; rect.bottom += yOff;
+    return rect;
+  }
+
+  // Context may be "window", "page", "div", or "local"/null
+  // Result is in "div" coords
+  function fromCoordSystem(cm, coords, context) {
+    if (context == "div") return coords;
+    var left = coords.left, top = coords.top;
+    // First move into "page" coordinate system
+    if (context == "page") {
+      left -= pageScrollX();
+      top -= pageScrollY();
+    } else if (context == "local" || !context) {
+      var localBox = getRect(cm.display.sizer);
+      left += localBox.left;
+      top += localBox.top;
+    }
+
+    var lineSpaceBox = getRect(cm.display.lineSpace);
+    return {left: left - lineSpaceBox.left, top: top - lineSpaceBox.top};
+  }
+
+  function charCoords(cm, pos, context, lineObj, bias) {
+    if (!lineObj) lineObj = getLine(cm.doc, pos.line);
+    return intoCoordSystem(cm, lineObj, measureChar(cm, lineObj, pos.ch, null, bias), context);
+  }
+
+  function cursorCoords(cm, pos, context, lineObj, measurement) {
+    lineObj = lineObj || getLine(cm.doc, pos.line);
+    if (!measurement) measurement = measureLine(cm, lineObj);
+    function get(ch, right) {
+      var m = measureChar(cm, lineObj, ch, measurement, right ? "right" : "left");
+      if (right) m.left = m.right; else m.right = m.left;
+      return intoCoordSystem(cm, lineObj, m, context);
+    }
+    function getBidi(ch, partPos) {
+      var part = order[partPos], right = part.level % 2;
+      if (ch == bidiLeft(part) && partPos && part.level < order[partPos - 1].level) {
+        part = order[--partPos];
+        ch = bidiRight(part) - (part.level % 2 ? 0 : 1);
+        right = true;
+      } else if (ch == bidiRight(part) && partPos < order.length - 1 && part.level < order[partPos + 1].level) {
+        part = order[++partPos];
+        ch = bidiLeft(part) - part.level % 2;
+        right = false;
+      }
+      if (right && ch == part.to && ch > part.from) return get(ch - 1);
+      return get(ch, right);
+    }
+    var order = getOrder(lineObj), ch = pos.ch;
+    if (!order) return get(ch);
+    var partPos = getBidiPartAt(order, ch);
+    var val = getBidi(ch, partPos);
+    if (bidiOther != null) val.other = getBidi(ch, bidiOther);
+    return val;
+  }
+
+  function PosWithInfo(line, ch, outside, xRel) {
+    var pos = new Pos(line, ch);
+    pos.xRel = xRel;
+    if (outside) pos.outside = true;
+    return pos;
+  }
+
+  // Coords must be lineSpace-local
+  function coordsChar(cm, x, y) {
+    var doc = cm.doc;
+    y += cm.display.viewOffset;
+    if (y < 0) return PosWithInfo(doc.first, 0, true, -1);
+    var lineNo = lineAtHeight(doc, y), last = doc.first + doc.size - 1;
+    if (lineNo > last)
+      return PosWithInfo(doc.first + doc.size - 1, getLine(doc, last).text.length, true, 1);
+    if (x < 0) x = 0;
+
+    for (;;) {
+      var lineObj = getLine(doc, lineNo);
+      var found = coordsCharInner(cm, lineObj, lineNo, x, y);
+      var merged = collapsedSpanAtEnd(lineObj);
+      var mergedPos = merged && merged.find();
+      if (merged && (found.ch > mergedPos.from.ch || found.ch == mergedPos.from.ch && found.xRel > 0))
+        lineNo = mergedPos.to.line;
+      else
+        return found;
+    }
+  }
+
+  function coordsCharInner(cm, lineObj, lineNo, x, y) {
+    var innerOff = y - heightAtLine(cm, lineObj);
+    var wrongLine = false, adjust = 2 * cm.display.wrapper.clientWidth;
+    var measurement = measureLine(cm, lineObj);
+
+    function getX(ch) {
+      var sp = cursorCoords(cm, Pos(lineNo, ch), "line",
+                            lineObj, measurement);
+      wrongLine = true;
+      if (innerOff > sp.bottom) return sp.left - adjust;
+      else if (innerOff < sp.top) return sp.left + adjust;
+      else wrongLine = false;
+      return sp.left;
+    }
+
+    var bidi = getOrder(lineObj), dist = lineObj.text.length;
+    var from = lineLeft(lineObj), to = lineRight(lineObj);
+    var fromX = getX(from), fromOutside = wrongLine, toX = getX(to), toOutside = wrongLine;
+
+    if (x > toX) return PosWithInfo(lineNo, to, toOutside, 1);
+    // Do a binary search between these bounds.
+    for (;;) {
+      if (bidi ? to == from || to == moveVisually(lineObj, from, 1) : to - from <= 1) {
+        var ch = x < fromX || x - fromX <= toX - x ? from : to;
+        var xDiff = x - (ch == from ? fromX : toX);
+        while (isExtendingChar.test(lineObj.text.charAt(ch))) ++ch;
+        var pos = PosWithInfo(lineNo, ch, ch == from ? fromOutside : toOutside,
+                              xDiff < 0 ? -1 : xDiff ? 1 : 0);
+        return pos;
+      }
+      var step = Math.ceil(dist / 2), middle = from + step;
+      if (bidi) {
+        middle = from;
+        for (var i = 0; i < step; ++i) middle = moveVisually(lineObj, middle, 1);
+      }
+      var middleX = getX(middle);
+      if (middleX > x) {to = middle; toX = middleX; if (toOutside = wrongLine) toX += 1000; dist = step;}
+      else {from = middle; fromX = middleX; fromOutside = wrongLine; dist -= step;}
+    }
+  }
+
+  var measureText;
+  function textHeight(display) {
+    if (display.cachedTextHeight != null) return display.cachedTextHeight;
+    if (measureText == null) {
+      measureText = elt("pre");
+      // Measure a bunch of lines, for browsers that compute
+      // fractional heights.
+      for (var i = 0; i < 49; ++i) {
+        measureText.appendChild(document.createTextNode("x"));
+        measureText.appendChild(elt("br"));
+      }
+      measureText.appendChild(document.createTextNode("x"));
+    }
+    removeChildrenAndAdd(display.measure, measureText);
+    var height = measureText.offsetHeight / 50;
+    if (height > 3) display.cachedTextHeight = height;
+    removeChildren(display.measure);
+    return height || 1;
+  }
+
+  function charWidth(display) {
+    if (display.cachedCharWidth != null) return display.cachedCharWidth;
+    var anchor = elt("span", "x");
+    var pre = elt("pre", [anchor]);
+    removeChildrenAndAdd(display.measure, pre);
+    var width = anchor.offsetWidth;
+    if (width > 2) display.cachedCharWidth = width;
+    return width || 10;
+  }
+
+  // OPERATIONS
+
+  // Operations are used to wrap changes in such a way that each
+  // change won't have to update the cursor and display (which would
+  // be awkward, slow, and error-prone), but instead updates are
+  // batched and then all combined and executed at once.
+
+  var nextOpId = 0;
+  function startOperation(cm) {
+    cm.curOp = {
+      // An array of ranges of lines that have to be updated. See
+      // updateDisplay.
+      changes: [],
+      forceUpdate: false,
+      updateInput: null,
+      userSelChange: null,
+      textChanged: null,
+      selectionChanged: false,
+      cursorActivity: false,
+      updateMaxLine: false,
+      updateScrollPos: false,
+      id: ++nextOpId
+    };
+    if (!delayedCallbackDepth++) delayedCallbacks = [];
+  }
+
+  function endOperation(cm) {
+    var op = cm.curOp, doc = cm.doc, display = cm.display;
+    cm.curOp = null;
+
+    if (op.updateMaxLine) computeMaxLength(cm);
+    if (display.maxLineChanged && !cm.options.lineWrapping && display.maxLine) {
+      var width = measureLineWidth(cm, display.maxLine);
+      display.sizer.style.minWidth = Math.max(0, width + 3 + scrollerCutOff) + "px";
+      display.maxLineChanged = false;
+      var maxScrollLeft = Math.max(0, display.sizer.offsetLeft + display.sizer.offsetWidth - display.scroller.clientWidth);
+      if (maxScrollLeft < doc.scrollLeft && !op.updateScrollPos)
+        setScrollLeft(cm, Math.min(display.scroller.scrollLeft, maxScrollLeft), true);
+    }
+    var newScrollPos, updated;
+    if (op.updateScrollPos) {
+      newScrollPos = op.updateScrollPos;
+    } else if (op.selectionChanged && display.scroller.clientHeight) { // don't rescroll if not visible
+      var coords = cursorCoords(cm, doc.sel.head);
+      newScrollPos = calculateScrollPos(cm, coords.left, coords.top, coords.left, coords.bottom);
+    }
+    if (op.changes.length || op.forceUpdate || newScrollPos && newScrollPos.scrollTop != null) {
+      updated = updateDisplay(cm, op.changes, newScrollPos && newScrollPos.scrollTop, op.forceUpdate);
+      if (cm.display.scroller.offsetHeight) cm.doc.scrollTop = cm.display.scroller.scrollTop;
+    }
+    if (!updated && op.selectionChanged) updateSelection(cm);
+    if (op.updateScrollPos) {
+      display.scroller.scrollTop = display.scrollbarV.scrollTop = doc.scrollTop = newScrollPos.scrollTop;
+      display.scroller.scrollLeft = display.scrollbarH.scrollLeft = doc.scrollLeft = newScrollPos.scrollLeft;
+      alignHorizontally(cm);
+      if (op.scrollToPos)
+        scrollPosIntoView(cm, clipPos(cm.doc, op.scrollToPos), op.scrollToPosMargin);
+    } else if (newScrollPos) {
+      scrollCursorIntoView(cm);
+    }
+    if (op.selectionChanged) restartBlink(cm);
+
+    if (cm.state.focused && op.updateInput)
+      resetInput(cm, op.userSelChange);
+
+    var hidden = op.maybeHiddenMarkers, unhidden = op.maybeUnhiddenMarkers;
+    if (hidden) for (var i = 0; i < hidden.length; ++i)
+      if (!hidden[i].lines.length) signal(hidden[i], "hide");
+    if (unhidden) for (var i = 0; i < unhidden.length; ++i)
+      if (unhidden[i].lines.length) signal(unhidden[i], "unhide");
+
+    var delayed;
+    if (!--delayedCallbackDepth) {
+      delayed = delayedCallbacks;
+      delayedCallbacks = null;
+    }
+    if (op.textChanged)
+      signal(cm, "change", cm, op.textChanged);
+    if (op.cursorActivity) signal(cm, "cursorActivity", cm);
+    if (delayed) for (var i = 0; i < delayed.length; ++i) delayed[i]();
+  }
+
+  // Wraps a function in an operation. Returns the wrapped function.
+  function operation(cm1, f) {
+    return function() {
+      var cm = cm1 || this, withOp = !cm.curOp;
+      if (withOp) startOperation(cm);
+      try { var result = f.apply(cm, arguments); }
+      finally { if (withOp) endOperation(cm); }
+      return result;
+    };
+  }
+  function docOperation(f) {
+    return function() {
+      var withOp = this.cm && !this.cm.curOp, result;
+      if (withOp) startOperation(this.cm);
+      try { result = f.apply(this, arguments); }
+      finally { if (withOp) endOperation(this.cm); }
+      return result;
+    };
+  }
+  function runInOp(cm, f) {
+    var withOp = !cm.curOp, result;
+    if (withOp) startOperation(cm);
+    try { result = f(); }
+    finally { if (withOp) endOperation(cm); }
+    return result;
+  }
+
+  function regChange(cm, from, to, lendiff) {
+    if (from == null) from = cm.doc.first;
+    if (to == null) to = cm.doc.first + cm.doc.size;
+    cm.curOp.changes.push({from: from, to: to, diff: lendiff});
+  }
+
+  // INPUT HANDLING
+
+  function slowPoll(cm) {
+    if (cm.display.pollingFast) return;
+    cm.display.poll.set(cm.options.pollInterval, function() {
+      readInput(cm);
+      if (cm.state.focused) slowPoll(cm);
+    });
+  }
+
+  function fastPoll(cm) {
+    var missed = false;
+    cm.display.pollingFast = true;
+    function p() {
+      var changed = readInput(cm);
+      if (!changed && !missed) {missed = true; cm.display.poll.set(60, p);}
+      else {cm.display.pollingFast = false; slowPoll(cm);}
+    }
+    cm.display.poll.set(20, p);
+  }
+
+  // prevInput is a hack to work with IME. If we reset the textarea
+  // on every change, that breaks IME. So we look for changes
+  // compared to the previous content instead. (Modern browsers have
+  // events that indicate IME taking place, but these are not widely
+  // supported or compatible enough yet to rely on.)
+  function readInput(cm) {
+    var input = cm.display.input, prevInput = cm.display.prevInput, doc = cm.doc, sel = doc.sel;
+    if (!cm.state.focused || hasSelection(input) || isReadOnly(cm) || cm.state.disableInput) return false;
+    if (cm.state.pasteIncoming && cm.state.fakedLastChar) {
+      input.value = input.value.substring(0, input.value.length - 1);
+      cm.state.fakedLastChar = false;
+    }
+    var text = input.value;
+    if (text == prevInput && posEq(sel.from, sel.to)) return false;
+    if (ie && !ie_lt9 && cm.display.inputHasSelection === text) {
+      resetInput(cm, true);
+      return false;
+    }
+
+    var withOp = !cm.curOp;
+    if (withOp) startOperation(cm);
+    sel.shift = false;
+    var same = 0, l = Math.min(prevInput.length, text.length);
+    while (same < l && prevInput.charCodeAt(same) == text.charCodeAt(same)) ++same;
+    var from = sel.from, to = sel.to;
+    if (same < prevInput.length)
+      from = Pos(from.line, from.ch - (prevInput.length - same));
+    else if (cm.state.overwrite && posEq(from, to) && !cm.state.pasteIncoming)
+      to = Pos(to.line, Math.min(getLine(doc, to.line).text.length, to.ch + (text.length - same)));
+
+    var updateInput = cm.curOp.updateInput;
+    var changeEvent = {from: from, to: to, text: splitLines(text.slice(same)),
+                       origin: cm.state.pasteIncoming ? "paste" : "+input"};
+    makeChange(cm.doc, changeEvent, "end");
+    cm.curOp.updateInput = updateInput;
+    signalLater(cm, "inputRead", cm, changeEvent);
+
+    if (text.length > 1000 || text.indexOf("\n") > -1) input.value = cm.display.prevInput = "";
+    else cm.display.prevInput = text;
+    if (withOp) endOperation(cm);
+    cm.state.pasteIncoming = false;
+    return true;
+  }
+
+  function resetInput(cm, user) {
+    var minimal, selected, doc = cm.doc;
+    if (!posEq(doc.sel.from, doc.sel.to)) {
+      cm.display.prevInput = "";
+      minimal = hasCopyEvent &&
+        (doc.sel.to.line - doc.sel.from.line > 100 || (selected = cm.getSelection()).length > 1000);
+      var content = minimal ? "-" : selected || cm.getSelection();
+      cm.display.input.value = content;
+      if (cm.state.focused) selectInput(cm.display.input);
+      if (ie && !ie_lt9) cm.display.inputHasSelection = content;
+    } else if (user) {
+      cm.display.prevInput = cm.display.input.value = "";
+      if (ie && !ie_lt9) cm.display.inputHasSelection = null;
+    }
+    cm.display.inaccurateSelection = minimal;
+  }
+
+  function focusInput(cm) {
+    if (cm.options.readOnly != "nocursor" && (!mobile || document.activeElement != cm.display.input))
+      cm.display.input.focus();
+  }
+
+  function isReadOnly(cm) {
+    return cm.options.readOnly || cm.doc.cantEdit;
+  }
+
+  // EVENT HANDLERS
+
+  function registerEventHandlers(cm) {
+    var d = cm.display;
+    on(d.scroller, "mousedown", operation(cm, onMouseDown));
+    if (ie)
+      on(d.scroller, "dblclick", operation(cm, function(e) {
+        if (signalDOMEvent(cm, e)) return;
+        var pos = posFromMouse(cm, e);
+        if (!pos || clickInGutter(cm, e) || eventInWidget(cm.display, e)) return;
+        e_preventDefault(e);
+        var word = findWordAt(getLine(cm.doc, pos.line).text, pos);
+        extendSelection(cm.doc, word.from, word.to);
+      }));
+    else
+      on(d.scroller, "dblclick", function(e) { signalDOMEvent(cm, e) || e_preventDefault(e); });
+    on(d.lineSpace, "selectstart", function(e) {
+      if (!eventInWidget(d, e)) e_preventDefault(e);
+    });
+    // Gecko browsers fire contextmenu *after* opening the menu, at
+    // which point we can't mess with it anymore. Context menu is
+    // handled in onMouseDown for Gecko.
+    if (!captureMiddleClick) on(d.scroller, "contextmenu", function(e) {onContextMenu(cm, e);});
+
+    on(d.scroller, "scroll", function() {
+      if (d.scroller.clientHeight) {
+        setScrollTop(cm, d.scroller.scrollTop);
+        setScrollLeft(cm, d.scroller.scrollLeft, true);
+        signal(cm, "scroll", cm);
+      }
+    });
+    on(d.scrollbarV, "scroll", function() {
+      if (d.scroller.clientHeight) setScrollTop(cm, d.scrollbarV.scrollTop);
+    });
+    on(d.scrollbarH, "scroll", function() {
+      if (d.scroller.clientHeight) setScrollLeft(cm, d.scrollbarH.scrollLeft);
+    });
+
+    on(d.scroller, "mousewheel", function(e){onScrollWheel(cm, e);});
+    on(d.scroller, "DOMMouseScroll", function(e){onScrollWheel(cm, e);});
+
+    function reFocus() { if (cm.state.focused) setTimeout(bind(focusInput, cm), 0); }
+    on(d.scrollbarH, "mousedown", reFocus);
+    on(d.scrollbarV, "mousedown", reFocus);
+    // Prevent wrapper from ever scrolling
+    on(d.wrapper, "scroll", function() { d.wrapper.scrollTop = d.wrapper.scrollLeft = 0; });
+
+    var resizeTimer;
+    function onResize() {
+      if (resizeTimer == null) resizeTimer = setTimeout(function() {
+        resizeTimer = null;
+        // Might be a text scaling operation, clear size caches.
+        d.cachedCharWidth = d.cachedTextHeight = knownScrollbarWidth = null;
+        clearCaches(cm);
+        runInOp(cm, bind(regChange, cm));
+      }, 100);
+    }
+    on(window, "resize", onResize);
+    // Above handler holds on to the editor and its data structures.
+    // Here we poll to unregister it when the editor is no longer in
+    // the document, so that it can be garbage-collected.
+    function unregister() {
+      for (var p = d.wrapper.parentNode; p && p != document.body; p = p.parentNode) {}
+      if (p) setTimeout(unregister, 5000);
+      else off(window, "resize", onResize);
+    }
+    setTimeout(unregister, 5000);
+
+    on(d.input, "keyup", operation(cm, function(e) {
+      if (signalDOMEvent(cm, e) || cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return;
+      if (e.keyCode == 16) cm.doc.sel.shift = false;
+    }));
+    on(d.input, "input", bind(fastPoll, cm));
+    on(d.input, "keydown", operation(cm, onKeyDown));
+    on(d.input, "keypress", operation(cm, onKeyPress));
+    on(d.input, "focus", bind(onFocus, cm));
+    on(d.input, "blur", bind(onBlur, cm));
+
+    function drag_(e) {
+      if (signalDOMEvent(cm, e) || cm.options.onDragEvent && cm.options.onDragEvent(cm, addStop(e))) return;
+      e_stop(e);
+    }
+    if (cm.options.dragDrop) {
+      on(d.scroller, "dragstart", function(e){onDragStart(cm, e);});
+      on(d.scroller, "dragenter", drag_);
+      on(d.scroller, "dragover", drag_);
+      on(d.scroller, "drop", operation(cm, onDrop));
+    }
+    on(d.scroller, "paste", function(e) {
+      if (eventInWidget(d, e)) return;
+      focusInput(cm);
+      fastPoll(cm);
+    });
+    on(d.input, "paste", function() {
+      // Workaround for webkit bug https://bugs.webkit.org/show_bug.cgi?id=90206
+      // Add a char to the end of textarea before paste occur so that
+      // selection doesn't span to the end of textarea.
+      if (webkit && !cm.state.fakedLastChar && !(new Date - cm.state.lastMiddleDown < 200)) {
+        var start = d.input.selectionStart, end = d.input.selectionEnd;
+        d.input.value += "$";
+        d.input.selectionStart = start;
+        d.input.selectionEnd = end;
+        cm.state.fakedLastChar = true;
+      }
+      cm.state.pasteIncoming = true;
+      fastPoll(cm);
+    });
+
+    function prepareCopy() {
+      if (d.inaccurateSelection) {
+        d.prevInput = "";
+        d.inaccurateSelection = false;
+        d.input.value = cm.getSelection();
+        selectInput(d.input);
+      }
+    }
+    on(d.input, "cut", prepareCopy);
+    on(d.input, "copy", prepareCopy);
+
+    // Needed to handle Tab key in KHTML
+    if (khtml) on(d.sizer, "mouseup", function() {
+        if (document.activeElement == d.input) d.input.blur();
+        focusInput(cm);
+    });
+  }
+
+  function eventInWidget(display, e) {
+    for (var n = e_target(e); n != display.wrapper; n = n.parentNode) {
+      if (!n || n.ignoreEvents || n.parentNode == display.sizer && n != display.mover) return true;
+    }
+  }
+
+  function posFromMouse(cm, e, liberal) {
+    var display = cm.display;
+    if (!liberal) {
+      var target = e_target(e);
+      if (target == display.scrollbarH || target == display.scrollbarH.firstChild ||
+          target == display.scrollbarV || target == display.scrollbarV.firstChild ||
+          target == display.scrollbarFiller || target == display.gutterFiller) return null;
+    }
+    var x, y, space = getRect(display.lineSpace);
+    // Fails unpredictably on IE[67] when mouse is dragged around quickly.
+    try { x = e.clientX; y = e.clientY; } catch (e) { return null; }
+    return coordsChar(cm, x - space.left, y - space.top);
+  }
+
+  var lastClick, lastDoubleClick;
+  function onMouseDown(e) {
+    if (signalDOMEvent(this, e)) return;
+    var cm = this, display = cm.display, doc = cm.doc, sel = doc.sel;
+    sel.shift = e.shiftKey;
+
+    if (eventInWidget(display, e)) {
+      if (!webkit) {
+        display.scroller.draggable = false;
+        setTimeout(function(){display.scroller.draggable = true;}, 100);
+      }
+      return;
+    }
+    if (clickInGutter(cm, e)) return;
+    var start = posFromMouse(cm, e);
+
+    switch (e_button(e)) {
+    case 3:
+      if (captureMiddleClick) onContextMenu.call(cm, cm, e);
+      return;
+    case 2:
+      if (webkit) cm.state.lastMiddleDown = +new Date;
+      if (start) extendSelection(cm.doc, start);
+      setTimeout(bind(focusInput, cm), 20);
+      e_preventDefault(e);
+      return;
+    }
+    // For button 1, if it was clicked inside the editor
+    // (posFromMouse returning non-null), we have to adjust the
+    // selection.
+    if (!start) {if (e_target(e) == display.scroller) e_preventDefault(e); return;}
+
+    if (!cm.state.focused) onFocus(cm);
+
+    var now = +new Date, type = "single";
+    if (lastDoubleClick && lastDoubleClick.time > now - 400 && posEq(lastDoubleClick.pos, start)) {
+      type = "triple";
+      e_preventDefault(e);
+      setTimeout(bind(focusInput, cm), 20);
+      selectLine(cm, start.line);
+    } else if (lastClick && lastClick.time > now - 400 && posEq(lastClick.pos, start)) {
+      type = "double";
+      lastDoubleClick = {time: now, pos: start};
+      e_preventDefault(e);
+      var word = findWordAt(getLine(doc, start.line).text, start);
+      extendSelection(cm.doc, word.from, word.to);
+    } else { lastClick = {time: now, pos: start}; }
+
+    var last = start;
+    if (cm.options.dragDrop && dragAndDrop && !isReadOnly(cm) && !posEq(sel.from, sel.to) &&
+        !posLess(start, sel.from) && !posLess(sel.to, start) && type == "single") {
+      var dragEnd = operation(cm, function(e2) {
+        if (webkit) display.scroller.draggable = false;
+        cm.state.draggingText = false;
+        off(document, "mouseup", dragEnd);
+        off(display.scroller, "drop", dragEnd);
+        if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) < 10) {
+          e_preventDefault(e2);
+          extendSelection(cm.doc, start);
+          focusInput(cm);
+        }
+      });
+      // Let the drag handler handle this.
+      if (webkit) display.scroller.draggable = true;
+      cm.state.draggingText = dragEnd;
+      // IE's approach to draggable
+      if (display.scroller.dragDrop) display.scroller.dragDrop();
+      on(document, "mouseup", dragEnd);
+      on(display.scroller, "drop", dragEnd);
+      return;
+    }
+    e_preventDefault(e);
+    if (type == "single") extendSelection(cm.doc, clipPos(doc, start));
+
+    var startstart = sel.from, startend = sel.to, lastPos = start;
+
+    function doSelect(cur) {
+      if (posEq(lastPos, cur)) return;
+      lastPos = cur;
+
+      if (type == "single") {
+        extendSelection(cm.doc, clipPos(doc, start), cur);
+        return;
+      }
+
+      startstart = clipPos(doc, startstart);
+      startend = clipPos(doc, startend);
+      if (type == "double") {
+        var word = findWordAt(getLine(doc, cur.line).text, cur);
+        if (posLess(cur, startstart)) extendSelection(cm.doc, word.from, startend);
+        else extendSelection(cm.doc, startstart, word.to);
+      } else if (type == "triple") {
+        if (posLess(cur, startstart)) extendSelection(cm.doc, startend, clipPos(doc, Pos(cur.line, 0)));
+        else extendSelection(cm.doc, startstart, clipPos(doc, Pos(cur.line + 1, 0)));
+      }
+    }
+
+    var editorSize = getRect(display.wrapper);
+    // Used to ensure timeout re-tries don't fire when another extend
+    // happened in the meantime (clearTimeout isn't reliable -- at
+    // least on Chrome, the timeouts still happen even when cleared,
+    // if the clear happens after their scheduled firing time).
+    var counter = 0;
+
+    function extend(e) {
+      var curCount = ++counter;
+      var cur = posFromMouse(cm, e, true);
+      if (!cur) return;
+      if (!posEq(cur, last)) {
+        if (!cm.state.focused) onFocus(cm);
+        last = cur;
+        doSelect(cur);
+        var visible = visibleLines(display, doc);
+        if (cur.line >= visible.to || cur.line < visible.from)
+          setTimeout(operation(cm, function(){if (counter == curCount) extend(e);}), 150);
+      } else {
+        var outside = e.clientY < editorSize.top ? -20 : e.clientY > editorSize.bottom ? 20 : 0;
+        if (outside) setTimeout(operation(cm, function() {
+          if (counter != curCount) return;
+          display.scroller.scrollTop += outside;
+          extend(e);
+        }), 50);
+      }
+    }
+
+    function done(e) {
+      counter = Infinity;
+      e_preventDefault(e);
+      focusInput(cm);
+      off(document, "mousemove", move);
+      off(document, "mouseup", up);
+    }
+
+    var move = operation(cm, function(e) {
+      if (!ie && !e_button(e)) done(e);
+      else extend(e);
+    });
+    var up = operation(cm, done);
+    on(document, "mousemove", move);
+    on(document, "mouseup", up);
+  }
+
+  function gutterEvent(cm, e, type, prevent, signalfn) {
+    try { var mX = e.clientX, mY = e.clientY; }
+    catch(e) { return false; }
+    if (mX >= Math.floor(getRect(cm.display.gutters).right)) return false;
+    if (prevent) e_preventDefault(e);
+
+    var display = cm.display;
+    var lineBox = getRect(display.lineDiv);
+
+    if (mY > lineBox.bottom || !hasHandler(cm, type)) return e_defaultPrevented(e);
+    mY -= lineBox.top - display.viewOffset;
+
+    for (var i = 0; i < cm.options.gutters.length; ++i) {
+      var g = display.gutters.childNodes[i];
+      if (g && getRect(g).right >= mX) {
+        var line = lineAtHeight(cm.doc, mY);
+        var gutter = cm.options.gutters[i];
+        signalfn(cm, type, cm, line, gutter, e);
+        return e_defaultPrevented(e);
+      }
+    }
+  }
+
+  function contextMenuInGutter(cm, e) {
+    if (!hasHandler(cm, "gutterContextMenu")) return false;
+    return gutterEvent(cm, e, "gutterContextMenu", false, signal);
+  }
+
+  function clickInGutter(cm, e) {
+    return gutterEvent(cm, e, "gutterClick", true, signalLater);
+  }
+
+  // Kludge to work around strange IE behavior where it'll sometimes
+  // re-fire a series of drag-related events right after the drop (#1551)
+  var lastDrop = 0;
+
+  function onDrop(e) {
+    var cm = this;
+    if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e) || (cm.options.onDragEvent && cm.options.onDragEvent(cm, addStop(e))))
+      return;
+    e_preventDefault(e);
+    if (ie) lastDrop = +new Date;
+    var pos = posFromMouse(cm, e, true), files = e.dataTransfer.files;
+    if (!pos || isReadOnly(cm)) return;
+    if (files && files.length && window.FileReader && window.File) {
+      var n = files.length, text = Array(n), read = 0;
+      var loadFile = function(file, i) {
+        var reader = new FileReader;
+        reader.onload = function() {
+          text[i] = reader.result;
+          if (++read == n) {
+            pos = clipPos(cm.doc, pos);
+            makeChange(cm.doc, {from: pos, to: pos, text: splitLines(text.join("\n")), origin: "paste"}, "around");
+          }
+        };
+        reader.readAsText(file);
+      };
+      for (var i = 0; i < n; ++i) loadFile(files[i], i);
+    } else {
+      // Don't do a replace if the drop happened inside of the selected text.
+      if (cm.state.draggingText && !(posLess(pos, cm.doc.sel.from) || posLess(cm.doc.sel.to, pos))) {
+        cm.state.draggingText(e);
+        // Ensure the editor is re-focused
+        setTimeout(bind(focusInput, cm), 20);
+        return;
+      }
+      try {
+        var text = e.dataTransfer.getData("Text");
+        if (text) {
+          var curFrom = cm.doc.sel.from, curTo = cm.doc.sel.to;
+          setSelection(cm.doc, pos, pos);
+          if (cm.state.draggingText) replaceRange(cm.doc, "", curFrom, curTo, "paste");
+          cm.replaceSelection(text, null, "paste");
+          focusInput(cm);
+          onFocus(cm);
+        }
+      }
+      catch(e){}
+    }
+  }
+
+  function onDragStart(cm, e) {
+    if (ie && (!cm.state.draggingText || +new Date - lastDrop < 100)) { e_stop(e); return; }
+    if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) return;
+
+    var txt = cm.getSelection();
+    e.dataTransfer.setData("Text", txt);
+
+    // Use dummy image instead of default browsers image.
+    // Recent Safari (~6.0.2) have a tendency to segfault when this happens, so we don't do it there.
+    if (e.dataTransfer.setDragImage && !safari) {
+      var img = elt("img", null, null, "position: fixed; left: 0; top: 0;");
+      if (opera) {
+        img.width = img.height = 1;
+        cm.display.wrapper.appendChild(img);
+        // Force a relayout, or Opera won't use our image for some obscure reason
+        img._top = img.offsetTop;
+      }
+      e.dataTransfer.setDragImage(img, 0, 0);
+      if (opera) img.parentNode.removeChild(img);
+    }
+  }
+
+  function setScrollTop(cm, val) {
+    if (Math.abs(cm.doc.scrollTop - val) < 2) return;
+    cm.doc.scrollTop = val;
+    if (!gecko) updateDisplay(cm, [], val);
+    if (cm.display.scroller.scrollTop != val) cm.display.scroller.scrollTop = val;
+    if (cm.display.scrollbarV.scrollTop != val) cm.display.scrollbarV.scrollTop = val;
+    if (gecko) updateDisplay(cm, []);
+    startWorker(cm, 100);
+  }
+  function setScrollLeft(cm, val, isScroller) {
+    if (isScroller ? val == cm.doc.scrollLeft : Math.abs(cm.doc.scrollLeft - val) < 2) return;
+    val = Math.min(val, cm.display.scroller.scrollWidth - cm.display.scroller.clientWidth);
+    cm.doc.scrollLeft = val;
+    alignHorizontally(cm);
+    if (cm.display.scroller.scrollLeft != val) cm.display.scroller.scrollLeft = val;
+    if (cm.display.scrollbarH.scrollLeft != val) cm.display.scrollbarH.scrollLeft = val;
+  }
+
+  // Since the delta values reported on mouse wheel events are
+  // unstandardized between browsers and even browser versions, and
+  // generally horribly unpredictable, this code starts by measuring
+  // the scroll effect that the first few mouse wheel events have,
+  // and, from that, detects the way it can convert deltas to pixel
+  // offsets afterwards.
+  //
+  // The reason we want to know the amount a wheel event will scroll
+  // is that it gives us a chance to update the display before the
+  // actual scrolling happens, reducing flickering.
+
+  var wheelSamples = 0, wheelPixelsPerUnit = null;
+  // Fill in a browser-detected starting value on browsers where we
+  // know one. These don't have to be accurate -- the result of them
+  // being wrong would just be a slight flicker on the first wheel
+  // scroll (if it is large enough).
+  if (ie) wheelPixelsPerUnit = -.53;
+  else if (gecko) wheelPixelsPerUnit = 15;
+  else if (chrome) wheelPixelsPerUnit = -.7;
+  else if (safari) wheelPixelsPerUnit = -1/3;
+
+  function onScrollWheel(cm, e) {
+    var dx = e.wheelDeltaX, dy = e.wheelDeltaY;
+    if (dx == null && e.detail && e.axis == e.HORIZONTAL_AXIS) dx = e.detail;
+    if (dy == null && e.detail && e.axis == e.VERTICAL_AXIS) dy = e.detail;
+    else if (dy == null) dy = e.wheelDelta;
+
+    var display = cm.display, scroll = display.scroller;
+    // Quit if there's nothing to scroll here
+    if (!(dx && scroll.scrollWidth > scroll.clientWidth ||
+          dy && scroll.scrollHeight > scroll.clientHeight)) return;
+
+    // Webkit browsers on OS X abort momentum scrolls when the target
+    // of the scroll event is removed from the scrollable element.
+    // This hack (see related code in patchDisplay) makes sure the
+    // element is kept around.
+    if (dy && mac && webkit) {
+      for (var cur = e.target; cur != scroll; cur = cur.parentNode) {
+        if (cur.lineObj) {
+          cm.display.currentWheelTarget = cur;
+          break;
+        }
+      }
+    }
+
+    // On some browsers, horizontal scrolling will cause redraws to
+    // happen before the gutter has been realigned, causing it to
+    // wriggle around in a most unseemly way. When we have an
+    // estimated pixels/delta value, we just handle horizontal
+    // scrolling entirely here. It'll be slightly off from native, but
+    // better than glitching out.
+    if (dx && !gecko && !opera && wheelPixelsPerUnit != null) {
+      if (dy)
+        setScrollTop(cm, Math.max(0, Math.min(scroll.scrollTop + dy * wheelPixelsPerUnit, scroll.scrollHeight - scroll.clientHeight)));
+      setScrollLeft(cm, Math.max(0, Math.min(scroll.scrollLeft + dx * wheelPixelsPerUnit, scroll.scrollWidth - scroll.clientWidth)));
+      e_preventDefault(e);
+      display.wheelStartX = null; // Abort measurement, if in progress
+      return;
+    }
+
+    if (dy && wheelPixelsPerUnit != null) {
+      var pixels = dy * wheelPixelsPerUnit;
+      var top = cm.doc.scrollTop, bot = top + display.wrapper.clientHeight;
+      if (pixels < 0) top = Math.max(0, top + pixels - 50);
+      else bot = Math.min(cm.doc.height, bot + pixels + 50);
+      updateDisplay(cm, [], {top: top, bottom: bot});
+    }
+
+    if (wheelSamples < 20) {
+      if (display.wheelStartX == null) {
+        display.wheelStartX = scroll.scrollLeft; display.wheelStartY = scroll.scrollTop;
+        display.wheelDX = dx; display.wheelDY = dy;
+        setTimeout(function() {
+          if (display.wheelStartX == null) return;
+          var movedX = scroll.scrollLeft - display.wheelStartX;
+          var movedY = scroll.scrollTop - display.wheelStartY;
+          var sample = (movedY && display.wheelDY && movedY / display.wheelDY) ||
+            (movedX && display.wheelDX && movedX / display.wheelDX);
+          display.wheelStartX = display.wheelStartY = null;
+          if (!sample) return;
+          wheelPixelsPerUnit = (wheelPixelsPerUnit * wheelSamples + sample) / (wheelSamples + 1);
+          ++wheelSamples;
+        }, 200);
+      } else {
+        display.wheelDX += dx; display.wheelDY += dy;
+      }
+    }
+  }
+
+  function doHandleBinding(cm, bound, dropShift) {
+    if (typeof bound == "string") {
+      bound = commands[bound];
+      if (!bound) return false;
+    }
+    // Ensure previous input has been read, so that the handler sees a
+    // consistent view of the document
+    if (cm.display.pollingFast && readInput(cm)) cm.display.pollingFast = false;
+    var doc = cm.doc, prevShift = doc.sel.shift, done = false;
+    try {
+      if (isReadOnly(cm)) cm.state.suppressEdits = true;
+      if (dropShift) doc.sel.shift = false;
+      done = bound(cm) != Pass;
+    } finally {
+      doc.sel.shift = prevShift;
+      cm.state.suppressEdits = false;
+    }
+    return done;
+  }
+
+  function allKeyMaps(cm) {
+    var maps = cm.state.keyMaps.slice(0);
+    if (cm.options.extraKeys) maps.push(cm.options.extraKeys);
+    maps.push(cm.options.keyMap);
+    return maps;
+  }
+
+  var maybeTransition;
+  function handleKeyBinding(cm, e) {
+    // Handle auto keymap transitions
+    var startMap = getKeyMap(cm.options.keyMap), next = startMap.auto;
+    clearTimeout(maybeTransition);
+    if (next && !isModifierKey(e)) maybeTransition = setTimeout(function() {
+      if (getKeyMap(cm.options.keyMap) == startMap) {
+        cm.options.keyMap = (next.call ? next.call(null, cm) : next);
+        keyMapChanged(cm);
+      }
+    }, 50);
+
+    var name = keyName(e, true), handled = false;
+    if (!name) return false;
+    var keymaps = allKeyMaps(cm);
+
+    if (e.shiftKey) {
+      // First try to resolve full name (including 'Shift-'). Failing
+      // that, see if there is a cursor-motion command (starting with
+      // 'go') bound to the keyname without 'Shift-'.
+      handled = lookupKey("Shift-" + name, keymaps, function(b) {return doHandleBinding(cm, b, true);})
+             || lookupKey(name, keymaps, function(b) {
+                  if (typeof b == "string" ? /^go[A-Z]/.test(b) : b.motion)
+                    return doHandleBinding(cm, b);
+                });
+    } else {
+      handled = lookupKey(name, keymaps, function(b) { return doHandleBinding(cm, b); });
+    }
+
+    if (handled) {
+      e_preventDefault(e);
+      restartBlink(cm);
+      if (ie_lt9) { e.oldKeyCode = e.keyCode; e.keyCode = 0; }
+      signalLater(cm, "keyHandled", cm, name, e);
+    }
+    return handled;
+  }
+
+  function handleCharBinding(cm, e, ch) {
+    var handled = lookupKey("'" + ch + "'", allKeyMaps(cm),
+                            function(b) { return doHandleBinding(cm, b, true); });
+    if (handled) {
+      e_preventDefault(e);
+      restartBlink(cm);
+      signalLater(cm, "keyHandled", cm, "'" + ch + "'", e);
+    }
+    return handled;
+  }
+
+  var lastStoppedKey = null;
+  function onKeyDown(e) {
+    var cm = this;
+    if (!cm.state.focused) onFocus(cm);
+    if (signalDOMEvent(cm, e) || cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return;
+    if (ie && e.keyCode == 27) e.returnValue = false;
+    var code = e.keyCode;
+    // IE does strange things with escape.
+    cm.doc.sel.shift = code == 16 || e.shiftKey;
+    // First give onKeyEvent option a chance to handle this.
+    var handled = handleKeyBinding(cm, e);
+    if (opera) {
+      lastStoppedKey = handled ? code : null;
+      // Opera has no cut event... we try to at least catch the key combo
+      if (!handled && code == 88 && !hasCopyEvent && (mac ? e.metaKey : e.ctrlKey))
+        cm.replaceSelection("");
+    }
+  }
+
+  function onKeyPress(e) {
+    var cm = this;
+    if (signalDOMEvent(cm, e) || cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return;
+    var keyCode = e.keyCode, charCode = e.charCode;
+    if (opera && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return;}
+    if (((opera && (!e.which || e.which < 10)) || khtml) && handleKeyBinding(cm, e)) return;
+    var ch = String.fromCharCode(charCode == null ? keyCode : charCode);
+    if (this.options.electricChars && this.doc.mode.electricChars &&
+        this.options.smartIndent && !isReadOnly(this) &&
+        this.doc.mode.electricChars.indexOf(ch) > -1)
+      setTimeout(operation(cm, function() {indentLine(cm, cm.doc.sel.to.line, "smart");}), 75);
+    if (handleCharBinding(cm, e, ch)) return;
+    if (ie && !ie_lt9) cm.display.inputHasSelection = null;
+    fastPoll(cm);
+  }
+
+  function onFocus(cm) {
+    if (cm.options.readOnly == "nocursor") return;
+    if (!cm.state.focused) {
+      signal(cm, "focus", cm);
+      cm.state.focused = true;
+      if (cm.display.wrapper.className.search(/\bCodeMirror-focused\b/) == -1)
+        cm.display.wrapper.className += " CodeMirror-focused";
+      if (!cm.curOp) {
+        resetInput(cm, true);
+        if (webkit) setTimeout(bind(resetInput, cm, true), 0); // Issue #1730
+      }
+    }
+    slowPoll(cm);
+    restartBlink(cm);
+  }
+  function onBlur(cm) {
+    if (cm.state.focused) {
+      signal(cm, "blur", cm);
+      cm.state.focused = false;
+      cm.display.wrapper.className = cm.display.wrapper.className.replace(" CodeMirror-focused", "");
+    }
+    clearInterval(cm.display.blinker);
+    setTimeout(function() {if (!cm.state.focused) cm.doc.sel.shift = false;}, 150);
+  }
+
+  var detectingSelectAll;
+  function onContextMenu(cm, e) {
+    if (signalDOMEvent(cm, e, "contextmenu")) return;
+    var display = cm.display, sel = cm.doc.sel;
+    if (eventInWidget(display, e) || contextMenuInGutter(cm, e)) return;
+
+    var pos = posFromMouse(cm, e), scrollPos = display.scroller.scrollTop;
+    if (!pos || opera) return; // Opera is difficult.
+    if (posEq(sel.from, sel.to) || posLess(pos, sel.from) || !posLess(pos, sel.to))
+      operation(cm, setSelection)(cm.doc, pos, pos);
+
+    var oldCSS = display.input.style.cssText;
+    display.inputDiv.style.position = "absolute";
+    display.input.style.cssText = "position: fixed; width: 30px; height: 30px; top: " + (e.clientY - 5) +
+      "px; left: " + (e.clientX - 5) + "px; z-index: 1000; background: white; outline: none;" +
+      "border-width: 0; outline: none; overflow: hidden; opacity: .05; -ms-opacity: .05; filter: alpha(opacity=5);";
+    focusInput(cm);
+    resetInput(cm, true);
+    // Adds "Select all" to context menu in FF
+    if (posEq(sel.from, sel.to)) display.input.value = display.prevInput = " ";
+
+    function prepareSelectAllHack() {
+      if (display.input.selectionStart != null) {
+        var extval = display.input.value = "\u200b" + (posEq(sel.from, sel.to) ? "" : display.input.value);
+        display.prevInput = "\u200b";
+        display.input.selectionStart = 1; display.input.selectionEnd = extval.length;
+      }
+    }
+    function rehide() {
+      display.inputDiv.style.position = "relative";
+      display.input.style.cssText = oldCSS;
+      if (ie_lt9) display.scrollbarV.scrollTop = display.scroller.scrollTop = scrollPos;
+      slowPoll(cm);
+
+      // Try to detect the user choosing select-all
+      if (display.input.selectionStart != null) {
+        if (!ie || ie_lt9) prepareSelectAllHack();
+        clearTimeout(detectingSelectAll);
+        var i = 0, poll = function(){
+          if (display.prevInput == " " && display.input.selectionStart == 0)
+            operation(cm, commands.selectAll)(cm);
+          else if (i++ < 10) detectingSelectAll = setTimeout(poll, 500);
+          else resetInput(cm);
+        };
+        detectingSelectAll = setTimeout(poll, 200);
+      }
+    }
+
+    if (ie && !ie_lt9) prepareSelectAllHack();
+    if (captureMiddleClick) {
+      e_stop(e);
+      var mouseup = function() {
+        off(window, "mouseup", mouseup);
+        setTimeout(rehide, 20);
+      };
+      on(window, "mouseup", mouseup);
+    } else {
+      setTimeout(rehide, 50);
+    }
+  }
+
+  // UPDATING
+
+  var changeEnd = CodeMirror.changeEnd = function(change) {
+    if (!change.text) return change.to;
+    return Pos(change.from.line + change.text.length - 1,
+               lst(change.text).length + (change.text.length == 1 ? change.from.ch : 0));
+  };
+
+  // Make sure a position will be valid after the given change.
+  function clipPostChange(doc, change, pos) {
+    if (!posLess(change.from, pos)) return clipPos(doc, pos);
+    var diff = (change.text.length - 1) - (change.to.line - change.from.line);
+    if (pos.line > change.to.line + diff) {
+      var preLine = pos.line - diff, lastLine = doc.first + doc.size - 1;
+      if (preLine > lastLine) return Pos(lastLine, getLine(doc, lastLine).text.length);
+      return clipToLen(pos, getLine(doc, preLine).text.length);
+    }
+    if (pos.line == change.to.line + diff)
+      return clipToLen(pos, lst(change.text).length + (change.text.length == 1 ? change.from.ch : 0) +
+                       getLine(doc, change.to.line).text.length - change.to.ch);
+    var inside = pos.line - change.from.line;
+    return clipToLen(pos, change.text[inside].length + (inside ? 0 : change.from.ch));
+  }
+
+  // Hint can be null|"end"|"start"|"around"|{anchor,head}
+  function computeSelAfterChange(doc, change, hint) {
+    if (hint && typeof hint == "object") // Assumed to be {anchor, head} object
+      return {anchor: clipPostChange(doc, change, hint.anchor),
+              head: clipPostChange(doc, change, hint.head)};
+
+    if (hint == "start") return {anchor: change.from, head: change.from};
+
+    var end = changeEnd(change);
+    if (hint == "around") return {anchor: change.from, head: end};
+    if (hint == "end") return {anchor: end, head: end};
+
+    // hint is null, leave the selection alone as much as possible
+    var adjustPos = function(pos) {
+      if (posLess(pos, change.from)) return pos;
+      if (!posLess(change.to, pos)) return end;
+
+      var line = pos.line + change.text.length - (change.to.line - change.from.line) - 1, ch = pos.ch;
+      if (pos.line == change.to.line) ch += end.ch - change.to.ch;
+      return Pos(line, ch);
+    };
+    return {anchor: adjustPos(doc.sel.anchor), head: adjustPos(doc.sel.head)};
+  }
+
+  function filterChange(doc, change, update) {
+    var obj = {
+      canceled: false,
+      from: change.from,
+      to: change.to,
+      text: change.text,
+      origin: change.origin,
+      cancel: function() { this.canceled = true; }
+    };
+    if (update) obj.update = function(from, to, text, origin) {
+      if (from) this.from = clipPos(doc, from);
+      if (to) this.to = clipPos(doc, to);
+      if (text) this.text = text;
+      if (origin !== undefined) this.origin = origin;
+    };
+    signal(doc, "beforeChange", doc, obj);
+    if (doc.cm) signal(doc.cm, "beforeChange", doc.cm, obj);
+
+    if (obj.canceled) return null;
+    return {from: obj.from, to: obj.to, text: obj.text, origin: obj.origin};
+  }
+
+  // Replace the range from from to to by the strings in replacement.
+  // change is a {from, to, text [, origin]} object
+  function makeChange(doc, change, selUpdate, ignoreReadOnly) {
+    if (doc.cm) {
+      if (!doc.cm.curOp) return operation(doc.cm, makeChange)(doc, change, selUpdate, ignoreReadOnly);
+      if (doc.cm.state.suppressEdits) return;
+    }
+
+    if (hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange")) {
+      change = filterChange(doc, change, true);
+      if (!change) return;
+    }
+
+    // Possibly split or suppress the update based on the presence
+    // of read-only spans in its range.
+    var split = sawReadOnlySpans && !ignoreReadOnly && removeReadOnlyRanges(doc, change.from, change.to);
+    if (split) {
+      for (var i = split.length - 1; i >= 1; --i)
+        makeChangeNoReadonly(doc, {from: split[i].from, to: split[i].to, text: [""]});
+      if (split.length)
+        makeChangeNoReadonly(doc, {from: split[0].from, to: split[0].to, text: change.text}, selUpdate);
+    } else {
+      makeChangeNoReadonly(doc, change, selUpdate);
+    }
+  }
+
+  function makeChangeNoReadonly(doc, change, selUpdate) {
+    var selAfter = computeSelAfterChange(doc, change, selUpdate);
+    addToHistory(doc, change, selAfter, doc.cm ? doc.cm.curOp.id : NaN);
+
+    makeChangeSingleDoc(doc, change, selAfter, stretchSpansOverChange(doc, change));
+    var rebased = [];
+
+    linkedDocs(doc, function(doc, sharedHist) {
+      if (!sharedHist && indexOf(rebased, doc.history) == -1) {
+        rebaseHist(doc.history, change);
+        rebased.push(doc.history);
+      }
+      makeChangeSingleDoc(doc, change, null, stretchSpansOverChange(doc, change));
+    });
+  }
+
+  function makeChangeFromHistory(doc, type) {
+    if (doc.cm && doc.cm.state.suppressEdits) return;
+
+    var hist = doc.history;
+    var event = (type == "undo" ? hist.done : hist.undone).pop();
+    if (!event) return;
+
+    var anti = {changes: [], anchorBefore: event.anchorAfter, headBefore: event.headAfter,
+                anchorAfter: event.anchorBefore, headAfter: event.headBefore,
+                generation: hist.generation};
+    (type == "undo" ? hist.undone : hist.done).push(anti);
+    hist.generation = event.generation || ++hist.maxGeneration;
+
+    var filter = hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange");
+
+    for (var i = event.changes.length - 1; i >= 0; --i) {
+      var change = event.changes[i];
+      change.origin = type;
+      if (filter && !filterChange(doc, change, false)) {
+        (type == "undo" ? hist.done : hist.undone).length = 0;
+        return;
+      }
+
+      anti.changes.push(historyChangeFromChange(doc, change));
+
+      var after = i ? computeSelAfterChange(doc, change, null)
+                    : {anchor: event.anchorBefore, head: event.headBefore};
+      makeChangeSingleDoc(doc, change, after, mergeOldSpans(doc, change));
+      var rebased = [];
+
+      linkedDocs(doc, function(doc, sharedHist) {
+        if (!sharedHist && indexOf(rebased, doc.history) == -1) {
+          rebaseHist(doc.history, change);
+          rebased.push(doc.history);
+        }
+        makeChangeSingleDoc(doc, change, null, mergeOldSpans(doc, change));
+      });
+    }
+  }
+
+  function shiftDoc(doc, distance) {
+    function shiftPos(pos) {return Pos(pos.line + distance, pos.ch);}
+    doc.first += distance;
+    if (doc.cm) regChange(doc.cm, doc.first, doc.first, distance);
+    doc.sel.head = shiftPos(doc.sel.head); doc.sel.anchor = shiftPos(doc.sel.anchor);
+    doc.sel.from = shiftPos(doc.sel.from); doc.sel.to = shiftPos(doc.sel.to);
+  }
+
+  function makeChangeSingleDoc(doc, change, selAfter, spans) {
+    if (doc.cm && !doc.cm.curOp)
+      return operation(doc.cm, makeChangeSingleDoc)(doc, change, selAfter, spans);
+
+    if (change.to.line < doc.first) {
+      shiftDoc(doc, change.text.length - 1 - (change.to.line - change.from.line));
+      return;
+    }
+    if (change.from.line > doc.lastLine()) return;
+
+    // Clip the change to the size of this doc
+    if (change.from.line < doc.first) {
+      var shift = change.text.length - 1 - (doc.first - change.from.line);
+      shiftDoc(doc, shift);
+      change = {from: Pos(doc.first, 0), to: Pos(change.to.line + shift, change.to.ch),
+                text: [lst(change.text)], origin: change.origin};
+    }
+    var last = doc.lastLine();
+    if (change.to.line > last) {
+      change = {from: change.from, to: Pos(last, getLine(doc, last).text.length),
+                text: [change.text[0]], origin: change.origin};
+    }
+
+    change.removed = getBetween(doc, change.from, change.to);
+
+    if (!selAfter) selAfter = computeSelAfterChange(doc, change, null);
+    if (doc.cm) makeChangeSingleDocInEditor(doc.cm, change, spans, selAfter);
+    else updateDoc(doc, change, spans, selAfter);
+  }
+
+  function makeChangeSingleDocInEditor(cm, change, spans, selAfter) {
+    var doc = cm.doc, display = cm.display, from = change.from, to = change.to;
+
+    var recomputeMaxLength = false, checkWidthStart = from.line;
+    if (!cm.options.lineWrapping) {
+      checkWidthStart = lineNo(visualLine(doc, getLine(doc, from.line)));
+      doc.iter(checkWidthStart, to.line + 1, function(line) {
+        if (line == display.maxLine) {
+          recomputeMaxLength = true;
+          return true;
+        }
+      });
+    }
+
+    if (!posLess(doc.sel.head, change.from) && !posLess(change.to, doc.sel.head))
+      cm.curOp.cursorActivity = true;
+
+    updateDoc(doc, change, spans, selAfter, estimateHeight(cm));
+
+    if (!cm.options.lineWrapping) {
+      doc.iter(checkWidthStart, from.line + change.text.length, function(line) {
+        var len = lineLength(doc, line);
+        if (len > display.maxLineLength) {
+          display.maxLine = line;
+          display.maxLineLength = len;
+          display.maxLineChanged = true;
+          recomputeMaxLength = false;
+        }
+      });
+      if (recomputeMaxLength) cm.curOp.updateMaxLine = true;
+    }
+
+    // Adjust frontier, schedule worker
+    doc.frontier = Math.min(doc.frontier, from.line);
+    startWorker(cm, 400);
+
+    var lendiff = change.text.length - (to.line - from.line) - 1;
+    // Remember that these lines changed, for updating the display
+    regChange(cm, from.line, to.line + 1, lendiff);
+
+    if (hasHandler(cm, "change")) {
+      var changeObj = {from: from, to: to,
+                       text: change.text,
+                       removed: change.removed,
+                       origin: change.origin};
+      if (cm.curOp.textChanged) {
+        for (var cur = cm.curOp.textChanged; cur.next; cur = cur.next) {}
+        cur.next = changeObj;
+      } else cm.curOp.textChanged = changeObj;
+    }
+  }
+
+  function replaceRange(doc, code, from, to, origin) {
+    if (!to) to = from;
+    if (posLess(to, from)) { var tmp = to; to = from; from = tmp; }
+    if (typeof code == "string") code = splitLines(code);
+    makeChange(doc, {from: from, to: to, text: code, origin: origin}, null);
+  }
+
+  // POSITION OBJECT
+
+  function Pos(line, ch) {
+    if (!(this instanceof Pos)) return new Pos(line, ch);
+    this.line = line; this.ch = ch;
+  }
+  CodeMirror.Pos = Pos;
+
+  function posEq(a, b) {return a.line == b.line && a.ch == b.ch;}
+  function posLess(a, b) {return a.line < b.line || (a.line == b.line && a.ch < b.ch);}
+  function copyPos(x) {return Pos(x.line, x.ch);}
+
+  // SELECTION
+
+  function clipLine(doc, n) {return Math.max(doc.first, Math.min(n, doc.first + doc.size - 1));}
+  function clipPos(doc, pos) {
+    if (pos.line < doc.first) return Pos(doc.first, 0);
+    var last = doc.first + doc.size - 1;
+    if (pos.line > last) return Pos(last, getLine(doc, last).text.length);
+    return clipToLen(pos, getLine(doc, pos.line).text.length);
+  }
+  function clipToLen(pos, linelen) {
+    var ch = pos.ch;
+    if (ch == null || ch > linelen) return Pos(pos.line, linelen);
+    else if (ch < 0) return Pos(pos.line, 0);
+    else return pos;
+  }
+  function isLine(doc, l) {return l >= doc.first && l < doc.first + doc.size;}
+
+  // If shift is held, this will move the selection anchor. Otherwise,
+  // it'll set the whole selection.
+  function extendSelection(doc, pos, other, bias) {
+    if (doc.sel.shift || doc.sel.extend) {
+      var anchor = doc.sel.anchor;
+      if (other) {
+        var posBefore = posLess(pos, anchor);
+        if (posBefore != posLess(other, anchor)) {
+          anchor = pos;
+          pos = other;
+        } else if (posBefore != posLess(pos, other)) {
+          pos = other;
+        }
+      }
+      setSelection(doc, anchor, pos, bias);
+    } else {
+      setSelection(doc, pos, other || pos, bias);
+    }
+    if (doc.cm) doc.cm.curOp.userSelChange = true;
+  }
+
+  function filterSelectionChange(doc, anchor, head) {
+    var obj = {anchor: anchor, head: head};
+    signal(doc, "beforeSelectionChange", doc, obj);
+    if (doc.cm) signal(doc.cm, "beforeSelectionChange", doc.cm, obj);
+    obj.anchor = clipPos(doc, obj.anchor); obj.head = clipPos(doc, obj.head);
+    return obj;
+  }
+
+  // Update the selection. Last two args are only used by
+  // updateDoc, since they have to be expressed in the line
+  // numbers before the update.
+  function setSelection(doc, anchor, head, bias, checkAtomic) {
+    if (!checkAtomic && hasHandler(doc, "beforeSelectionChange") || doc.cm && hasHandler(doc.cm, "beforeSelectionChange")) {
+      var filtered = filterSelectionChange(doc, anchor, head);
+      head = filtered.head;
+      anchor = filtered.anchor;
+    }
+
+    var sel = doc.sel;
+    sel.goalColumn = null;
+    if (bias == null) bias = posLess(head, sel.head) ? -1 : 1;
+    // Skip over atomic spans.
+    if (checkAtomic || !posEq(anchor, sel.anchor))
+      anchor = skipAtomic(doc, anchor, bias, checkAtomic != "push");
+    if (checkAtomic || !posEq(head, sel.head))
+      head = skipAtomic(doc, head, bias, checkAtomic != "push");
+
+    if (posEq(sel.anchor, anchor) && posEq(sel.head, head)) return;
+
+    sel.anchor = anchor; sel.head = head;
+    var inv = posLess(head, anchor);
+    sel.from = inv ? head : anchor;
+    sel.to = inv ? anchor : head;
+
+    if (doc.cm)
+      doc.cm.curOp.updateInput = doc.cm.curOp.selectionChanged =
+        doc.cm.curOp.cursorActivity = true;
+
+    signalLater(doc, "cursorActivity", doc);
+  }
+
+  function reCheckSelection(cm) {
+    setSelection(cm.doc, cm.doc.sel.from, cm.doc.sel.to, null, "push");
+  }
+
+  function skipAtomic(doc, pos, bias, mayClear) {
+    var flipped = false, curPos = pos;
+    var dir = bias || 1;
+    doc.cantEdit = false;
+    search: for (;;) {
+      var line = getLine(doc, curPos.line);
+      if (line.markedSpans) {
+        for (var i = 0; i < line.markedSpans.length; ++i) {
+          var sp = line.markedSpans[i], m = sp.marker;
+          if ((sp.from == null || (m.inclusiveLeft ? sp.from <= curPos.ch : sp.from < curPos.ch)) &&
+              (sp.to == null || (m.inclusiveRight ? sp.to >= curPos.ch : sp.to > curPos.ch))) {
+            if (mayClear) {
+              signal(m, "beforeCursorEnter");
+              if (m.explicitlyCleared) {
+                if (!line.markedSpans) break;
+                else {--i; continue;}
+              }
+            }
+            if (!m.atomic) continue;
+            var newPos = m.find()[dir < 0 ? "from" : "to"];
+            if (posEq(newPos, curPos)) {
+              newPos.ch += dir;
+              if (newPos.ch < 0) {
+                if (newPos.line > doc.first) newPos = clipPos(doc, Pos(newPos.line - 1));
+                else newPos = null;
+              } else if (newPos.ch > line.text.length) {
+                if (newPos.line < doc.first + doc.size - 1) newPos = Pos(newPos.line + 1, 0);
+                else newPos = null;
+              }
+              if (!newPos) {
+                if (flipped) {
+                  // Driven in a corner -- no valid cursor position found at all
+                  // -- try again *with* clearing, if we didn't already
+                  if (!mayClear) return skipAtomic(doc, pos, bias, true);
+                  // Otherwise, turn off editing until further notice, and return the start of the doc
+                  doc.cantEdit = true;
+                  return Pos(doc.first, 0);
+                }
+                flipped = true; newPos = pos; dir = -dir;
+              }
+            }
+            curPos = newPos;
+            continue search;
+          }
+        }
+      }
+      return curPos;
+    }
+  }
+
+  // SCROLLING
+
+  function scrollCursorIntoView(cm) {
+    var coords = scrollPosIntoView(cm, cm.doc.sel.head, cm.options.cursorScrollMargin);
+    if (!cm.state.focused) return;
+    var display = cm.display, box = getRect(display.sizer), doScroll = null;
+    if (coords.top + box.top < 0) doScroll = true;
+    else if (coords.bottom + box.top > (window.innerHeight || document.documentElement.clientHeight)) doScroll = false;
+    if (doScroll != null && !phantom) {
+      var hidden = display.cursor.style.display == "none";
+      if (hidden) {
+        display.cursor.style.display = "";
+        display.cursor.style.left = coords.left + "px";
+        display.cursor.style.top = (coords.top - display.viewOffset) + "px";
+      }
+      display.cursor.scrollIntoView(doScroll);
+      if (hidden) display.cursor.style.display = "none";
+    }
+  }
+
+  function scrollPosIntoView(cm, pos, margin) {
+    if (margin == null) margin = 0;
+    for (;;) {
+      var changed = false, coords = cursorCoords(cm, pos);
+      var scrollPos = calculateScrollPos(cm, coords.left, coords.top - margin, coords.left, coords.bottom + margin);
+      var startTop = cm.doc.scrollTop, startLeft = cm.doc.scrollLeft;
+      if (scrollPos.scrollTop != null) {
+        setScrollTop(cm, scrollPos.scrollTop);
+        if (Math.abs(cm.doc.scrollTop - startTop) > 1) changed = true;
+      }
+      if (scrollPos.scrollLeft != null) {
+        setScrollLeft(cm, scrollPos.scrollLeft);
+        if (Math.abs(cm.doc.scrollLeft - startLeft) > 1) changed = true;
+      }
+      if (!changed) return coords;
+    }
+  }
+
+  function scrollIntoView(cm, x1, y1, x2, y2) {
+    var scrollPos = calculateScrollPos(cm, x1, y1, x2, y2);
+    if (scrollPos.scrollTop != null) setScrollTop(cm, scrollPos.scrollTop);
+    if (scrollPos.scrollLeft != null) setScrollLeft(cm, scrollPos.scrollLeft);
+  }
+
+  function calculateScrollPos(cm, x1, y1, x2, y2) {
+    var display = cm.display, snapMargin = textHeight(cm.display);
+    if (y1 < 0) y1 = 0;
+    var screen = display.scroller.clientHeight - scrollerCutOff, screentop = display.scroller.scrollTop, result = {};
+    var docBottom = cm.doc.height + paddingVert(display);
+    var atTop = y1 < snapMargin, atBottom = y2 > docBottom - snapMargin;
+    if (y1 < screentop) {
+      result.scrollTop = atTop ? 0 : y1;
+    } else if (y2 > screentop + screen) {
+      var newTop = Math.min(y1, (atBottom ? docBottom : y2) - screen);
+      if (newTop != screentop) result.scrollTop = newTop;
+    }
+
+    var screenw = display.scroller.clientWidth - scrollerCutOff, screenleft = display.scroller.scrollLeft;
+    x1 += display.gutters.offsetWidth; x2 += display.gutters.offsetWidth;
+    var gutterw = display.gutters.offsetWidth;
+    var atLeft = x1 < gutterw + 10;
+    if (x1 < screenleft + gutterw || atLeft) {
+      if (atLeft) x1 = 0;
+      result.scrollLeft = Math.max(0, x1 - 10 - gutterw);
+    } else if (x2 > screenw + screenleft - 3) {
+      result.scrollLeft = x2 + 10 - screenw;
+    }
+    return result;
+  }
+
+  function updateScrollPos(cm, left, top) {
+    cm.curOp.updateScrollPos = {scrollLeft: left == null ? cm.doc.scrollLeft : left,
+                                scrollTop: top == null ? cm.doc.scrollTop : top};
+  }
+
+  function addToScrollPos(cm, left, top) {
+    var pos = cm.curOp.updateScrollPos || (cm.curOp.updateScrollPos = {scrollLeft: cm.doc.scrollLeft, scrollTop: cm.doc.scrollTop});
+    var scroll = cm.display.scroller;
+    pos.scrollTop = Math.max(0, Math.min(scroll.scrollHeight - scroll.clientHeight, pos.scrollTop + top));
+    pos.scrollLeft = Math.max(0, Math.min(scroll.scrollWidth - scroll.clientWidth, pos.scrollLeft + left));
+  }
+
+  // API UTILITIES
+
+  function indentLine(cm, n, how, aggressive) {
+    var doc = cm.doc;
+    if (how == null) how = "add";
+    if (how == "smart") {
+      if (!cm.doc.mode.indent) how = "prev";
+      else var state = getStateBefore(cm, n);
+    }
+
+    var tabSize = cm.options.tabSize;
+    var line = getLine(doc, n), curSpace = countColumn(line.text, null, tabSize);
+    var curSpaceString = line.text.match(/^\s*/)[0], indentation;
+    if (how == "smart") {
+      indentation = cm.doc.mode.indent(state, line.text.slice(curSpaceString.length), line.text);
+      if (indentation == Pass) {
+        if (!aggressive) return;
+        how = "prev";
+      }
+    }
+    if (how == "prev") {
+      if (n > doc.first) indentation = countColumn(getLine(doc, n-1).text, null, tabSize);
+      else indentation = 0;
+    } else if (how == "add") {
+      indentation = curSpace + cm.options.indentUnit;
+    } else if (how == "subtract") {
+      indentation = curSpace - cm.options.indentUnit;
+    } else if (typeof how == "number") {
+      indentation = curSpace + how;
+    }
+    indentation = Math.max(0, indentation);
+
+    var indentString = "", pos = 0;
+    if (cm.options.indentWithTabs)
+      for (var i = Math.floor(indentation / tabSize); i; --i) {pos += tabSize; indentString += "\t";}
+    if (pos < indentation) indentString += spaceStr(indentation - pos);
+
+    if (indentString != curSpaceString)
+      replaceRange(cm.doc, indentString, Pos(n, 0), Pos(n, curSpaceString.length), "+input");
+    line.stateAfter = null;
+  }
+
+  function changeLine(cm, handle, op) {
+    var no = handle, line = handle, doc = cm.doc;
+    if (typeof handle == "number") line = getLine(doc, clipLine(doc, handle));
+    else no = lineNo(handle);
+    if (no == null) return null;
+    if (op(line, no)) regChange(cm, no, no + 1);
+    else return null;
+    return line;
+  }
+
+  function findPosH(doc, pos, dir, unit, visually) {
+    var line = pos.line, ch = pos.ch, origDir = dir;
+    var lineObj = getLine(doc, line);
+    var possible = true;
+    function findNextLine() {
+      var l = line + dir;
+      if (l < doc.first || l >= doc.first + doc.size) return (possible = false);
+      line = l;
+      return lineObj = getLine(doc, l);
+    }
+    function moveOnce(boundToLine) {
+      var next = (visually ? moveVisually : moveLogically)(lineObj, ch, dir, true);
+      if (next == null) {
+        if (!boundToLine && findNextLine()) {
+          if (visually) ch = (dir < 0 ? lineRight : lineLeft)(lineObj);
+          else ch = dir < 0 ? lineObj.text.length : 0;
+        } else return (possible = false);
+      } else ch = next;
+      return true;
+    }
+
+    if (unit == "char") moveOnce();
+    else if (unit == "column") moveOnce(true);
+    else if (unit == "word" || unit == "group") {
+      var sawType = null, group = unit == "group";
+      for (var first = true;; first = false) {
+        if (dir < 0 && !moveOnce(!first)) break;
+        var cur = lineObj.text.charAt(ch) || "\n";
+        var type = isWordChar(cur) ? "w"
+          : !group ? null
+          : /\s/.test(cur) ? null
+          : "p";
+        if (sawType && sawType != type) {
+          if (dir < 0) {dir = 1; moveOnce();}
+          break;
+        }
+        if (type) sawType = type;
+        if (dir > 0 && !moveOnce(!first)) break;
+      }
+    }
+    var result = skipAtomic(doc, Pos(line, ch), origDir, true);
+    if (!possible) result.hitSide = true;
+    return result;
+  }
+
+  function findPosV(cm, pos, dir, unit) {
+    var doc = cm.doc, x = pos.left, y;
+    if (unit == "page") {
+      var pageSize = Math.min(cm.display.wrapper.clientHeight, window.innerHeight || document.documentElement.clientHeight);
+      y = pos.top + dir * (pageSize - (dir < 0 ? 1.5 : .5) * textHeight(cm.display));
+    } else if (unit == "line") {
+      y = dir > 0 ? pos.bottom + 3 : pos.top - 3;
+    }
+    for (;;) {
+      var target = coordsChar(cm, x, y);
+      if (!target.outside) break;
+      if (dir < 0 ? y <= 0 : y >= doc.height) { target.hitSide = true; break; }
+      y += dir * 5;
+    }
+    return target;
+  }
+
+  function findWordAt(line, pos) {
+    var start = pos.ch, end = pos.ch;
+    if (line) {
+      if ((pos.xRel < 0 || end == line.length) && start) --start; else ++end;
+      var startChar = line.charAt(start);
+      var check = isWordChar(startChar) ? isWordChar
+        : /\s/.test(startChar) ? function(ch) {return /\s/.test(ch);}
+        : function(ch) {return !/\s/.test(ch) && !isWordChar(ch);};
+      while (start > 0 && check(line.charAt(start - 1))) --start;
+      while (end < line.length && check(line.charAt(end))) ++end;
+    }
+    return {from: Pos(pos.line, start), to: Pos(pos.line, end)};
+  }
+
+  function selectLine(cm, line) {
+    extendSelection(cm.doc, Pos(line, 0), clipPos(cm.doc, Pos(line + 1, 0)));
+  }
+
+  // PROTOTYPE
+
+  // The publicly visible API. Note that operation(null, f) means
+  // 'wrap f in an operation, performed on its `this` parameter'
+
+  CodeMirror.prototype = {
+    constructor: CodeMirror,
+    focus: function(){window.focus(); focusInput(this); onFocus(this); fastPoll(this);},
+
+    setOption: function(option, value) {
+      var options = this.options, old = options[option];
+      if (options[option] == value && option != "mode") return;
+      options[option] = value;
+      if (optionHandlers.hasOwnProperty(option))
+        operation(this, optionHandlers[option])(this, value, old);
+    },
+
+    getOption: function(option) {return this.options[option];},
+    getDoc: function() {return this.doc;},
+
+    addKeyMap: function(map, bottom) {
+      this.state.keyMaps[bottom ? "push" : "unshift"](map);
+    },
+    removeKeyMap: function(map) {
+      var maps = this.state.keyMaps;
+      for (var i = 0; i < maps.length; ++i)
+        if (maps[i] == map || (typeof maps[i] != "string" && maps[i].name == map)) {
+          maps.splice(i, 1);
+          return true;
+        }
+    },
+
+    addOverlay: operation(null, function(spec, options) {
+      var mode = spec.token ? spec : CodeMirror.getMode(this.options, spec);
+      if (mode.startState) throw new Error("Overlays may not be stateful.");
+      this.state.overlays.push({mode: mode, modeSpec: spec, opaque: options && options.opaque});
+      this.state.modeGen++;
+      regChange(this);
+    }),
+    removeOverlay: operation(null, function(spec) {
+      var overlays = this.state.overlays;
+      for (var i = 0; i < overlays.length; ++i) {
+        var cur = overlays[i].modeSpec;
+        if (cur == spec || typeof spec == "string" && cur.name == spec) {
+          overlays.splice(i, 1);
+          this.state.modeGen++;
+          regChange(this);
+          return;
+        }
+      }
+    }),
+
+    indentLine: operation(null, function(n, dir, aggressive) {
+      if (typeof dir != "string" && typeof dir != "number") {
+        if (dir == null) dir = this.options.smartIndent ? "smart" : "prev";
+        else dir = dir ? "add" : "subtract";
+      }
+      if (isLine(this.doc, n)) indentLine(this, n, dir, aggressive);
+    }),
+    indentSelection: operation(null, function(how) {
+      var sel = this.doc.sel;
+      if (posEq(sel.from, sel.to)) return indentLine(this, sel.from.line, how);
+      var e = sel.to.line - (sel.to.ch ? 0 : 1);
+      for (var i = sel.from.line; i <= e; ++i) indentLine(this, i, how);
+    }),
+
+    // Fetch the parser token for a given character. Useful for hacks
+    // that want to inspect the mode state (say, for completion).
+    getTokenAt: function(pos, precise) {
+      var doc = this.doc;
+      pos = clipPos(doc, pos);
+      var state = getStateBefore(this, pos.line, precise), mode = this.doc.mode;
+      var line = getLine(doc, pos.line);
+      var stream = new StringStream(line.text, this.options.tabSize);
+      while (stream.pos < pos.ch && !stream.eol()) {
+        stream.start = stream.pos;
+        var style = mode.token(stream, state);
+      }
+      return {start: stream.start,
+              end: stream.pos,
+              string: stream.current(),
+              className: style || null, // Deprecated, use 'type' instead
+              type: style || null,
+              state: state};
+    },
+
+    getTokenTypeAt: function(pos) {
+      pos = clipPos(this.doc, pos);
+      var styles = getLineStyles(this, getLine(this.doc, pos.line));
+      var before = 0, after = (styles.length - 1) / 2, ch = pos.ch;
+      if (ch == 0) return styles[2];
+      for (;;) {
+        var mid = (before + after) >> 1;
+        if ((mid ? styles[mid * 2 - 1] : 0) >= ch) after = mid;
+        else if (styles[mid * 2 + 1] < ch) before = mid + 1;
+        else return styles[mid * 2 + 2];
+      }
+    },
+
+    getModeAt: function(pos) {
+      var mode = this.doc.mode;
+      if (!mode.innerMode) return mode;
+      return CodeMirror.innerMode(mode, this.getTokenAt(pos).state).mode;
+    },
+
+    getHelper: function(pos, type) {
+      if (!helpers.hasOwnProperty(type)) return;
+      var help = helpers[type], mode = this.getModeAt(pos);
+      return mode[type] && help[mode[type]] ||
+        mode.helperType && help[mode.helperType] ||
+        help[mode.name];
+    },
+
+    getStateAfter: function(line, precise) {
+      var doc = this.doc;
+      line = clipLine(doc, line == null ? doc.first + doc.size - 1: line);
+      return getStateBefore(this, line + 1, precise);
+    },
+
+    cursorCoords: function(start, mode) {
+      var pos, sel = this.doc.sel;
+      if (start == null) pos = sel.head;
+      else if (typeof start == "object") pos = clipPos(this.doc, start);
+      else pos = start ? sel.from : sel.to;
+      return cursorCoords(this, pos, mode || "page");
+    },
+
+    charCoords: function(pos, mode) {
+      return charCoords(this, clipPos(this.doc, pos), mode || "page");
+    },
+
+    coordsChar: function(coords, mode) {
+      coords = fromCoordSystem(this, coords, mode || "page");
+      return coordsChar(this, coords.left, coords.top);
+    },
+
+    lineAtHeight: function(height, mode) {
+      height = fromCoordSystem(this, {top: height, left: 0}, mode || "page").top;
+      return lineAtHeight(this.doc, height + this.display.viewOffset);
+    },
+    heightAtLine: function(line, mode) {
+      var end = false, last = this.doc.first + this.doc.size - 1;
+      if (line < this.doc.first) line = this.doc.first;
+      else if (line > last) { line = last; end = true; }
+      var lineObj = getLine(this.doc, line);
+      return intoCoordSystem(this, getLine(this.doc, line), {top: 0, left: 0}, mode || "page").top +
+        (end ? lineObj.height : 0);
+    },
+
+    defaultTextHeight: function() { return textHeight(this.display); },
+    defaultCharWidth: function() { return charWidth(this.display); },
+
+    setGutterMarker: operation(null, function(line, gutterID, value) {
+      return changeLine(this, line, function(line) {
+        var markers = line.gutterMarkers || (line.gutterMarkers = {});
+        markers[gutterID] = value;
+        if (!value && isEmpty(markers)) line.gutterMarkers = null;
+        return true;
+      });
+    }),
+
+    clearGutter: operation(null, function(gutterID) {
+      var cm = this, doc = cm.doc, i = doc.first;
+      doc.iter(function(line) {
+        if (line.gutterMarkers && line.gutterMarkers[gutterID]) {
+          line.gutterMarkers[gutterID] = null;
+          regChange(cm, i, i + 1);
+          if (isEmpty(line.gutterMarkers)) line.gutterMarkers = null;
+        }
+        ++i;
+      });
+    }),
+
+    addLineClass: operation(null, function(handle, where, cls) {
+      return changeLine(this, handle, function(line) {
+        var prop = where == "text" ? "textClass" : where == "background" ? "bgClass" : "wrapClass";
+        if (!line[prop]) line[prop] = cls;
+        else if (new RegExp("(?:^|\\s)" + cls + "(?:$|\\s)").test(line[prop])) return false;
+        else line[prop] += " " + cls;
+        return true;
+      });
+    }),
+
+    removeLineClass: operation(null, function(handle, where, cls) {
+      return changeLine(this, handle, function(line) {
+        var prop = where == "text" ? "textClass" : where == "background" ? "bgClass" : "wrapClass";
+        var cur = line[prop];
+        if (!cur) return false;
+        else if (cls == null) line[prop] = null;
+        else {
+          var found = cur.match(new RegExp("(?:^|\\s+)" + cls + "(?:$|\\s+)"));
+          if (!found) return false;
+          var end = found.index + found[0].length;
+          line[prop] = cur.slice(0, found.index) + (!found.index || end == cur.length ? "" : " ") + cur.slice(end) || null;
+        }
+        return true;
+      });
+    }),
+
+    addLineWidget: operation(null, function(handle, node, options) {
+      return addLineWidget(this, handle, node, options);
+    }),
+
+    removeLineWidget: function(widget) { widget.clear(); },
+
+    lineInfo: function(line) {
+      if (typeof line == "number") {
+        if (!isLine(this.doc, line)) return null;
+        var n = line;
+        line = getLine(this.doc, line);
+        if (!line) return null;
+      } else {
+        var n = lineNo(line);
+        if (n == null) return null;
+      }
+      return {line: n, handle: line, text: line.text, gutterMarkers: line.gutterMarkers,
+              textClass: line.textClass, bgClass: line.bgClass, wrapClass: line.wrapClass,
+              widgets: line.widgets};
+    },
+
+    getViewport: function() { return {from: this.display.showingFrom, to: this.display.showingTo};},
+
+    addWidget: function(pos, node, scroll, vert, horiz) {
+      var display = this.display;
+      pos = cursorCoords(this, clipPos(this.doc, pos));
+      var top = pos.bottom, left = pos.left;
+      node.style.position = "absolute";
+      display.sizer.appendChild(node);
+      if (vert == "over") {
+        top = pos.top;
+      } else if (vert == "above" || vert == "near") {
+        var vspace = Math.max(display.wrapper.clientHeight, this.doc.height),
+        hspace = Math.max(display.sizer.clientWidth, display.lineSpace.clientWidth);
+        // Default to positioning above (if specified and possible); otherwise default to positioning below
+        if ((vert == 'above' || pos.bottom + node.offsetHeight > vspace) && pos.top > node.offsetHeight)
+          top = pos.top - node.offsetHeight;
+        else if (pos.bottom + node.offsetHeight <= vspace)
+          top = pos.bottom;
+        if (left + node.offsetWidth > hspace)
+          left = hspace - node.offsetWidth;
+      }
+      node.style.top = top + "px";
+      node.style.left = node.style.right = "";
+      if (horiz == "right") {
+        left = display.sizer.clientWidth - node.offsetWidth;
+        node.style.right = "0px";
+      } else {
+        if (horiz == "left") left = 0;
+        else if (horiz == "middle") left = (display.sizer.clientWidth - node.offsetWidth) / 2;
+        node.style.left = left + "px";
+      }
+      if (scroll)
+        scrollIntoView(this, left, top, left + node.offsetWidth, top + node.offsetHeight);
+    },
+
+    triggerOnKeyDown: operation(null, onKeyDown),
+
+    execCommand: function(cmd) {return commands[cmd](this);},
+
+    findPosH: function(from, amount, unit, visually) {
+      var dir = 1;
+      if (amount < 0) { dir = -1; amount = -amount; }
+      for (var i = 0, cur = clipPos(this.doc, from); i < amount; ++i) {
+        cur = findPosH(this.doc, cur, dir, unit, visually);
+        if (cur.hitSide) break;
+      }
+      return cur;
+    },
+
+    moveH: operation(null, function(dir, unit) {
+      var sel = this.doc.sel, pos;
+      if (sel.shift || sel.extend || posEq(sel.from, sel.to))
+        pos = findPosH(this.doc, sel.head, dir, unit, this.options.rtlMoveVisually);
+      else
+        pos = dir < 0 ? sel.from : sel.to;
+      extendSelection(this.doc, pos, pos, dir);
+    }),
+
+    deleteH: operation(null, function(dir, unit) {
+      var sel = this.doc.sel;
+      if (!posEq(sel.from, sel.to)) replaceRange(this.doc, "", sel.from, sel.to, "+delete");
+      else replaceRange(this.doc, "", sel.from, findPosH(this.doc, sel.head, dir, unit, false), "+delete");
+      this.curOp.userSelChange = true;
+    }),
+
+    findPosV: function(from, amount, unit, goalColumn) {
+      var dir = 1, x = goalColumn;
+      if (amount < 0) { dir = -1; amount = -amount; }
+      for (var i = 0, cur = clipPos(this.doc, from); i < amount; ++i) {
+        var coords = cursorCoords(this, cur, "div");
+        if (x == null) x = coords.left;
+        else coords.left = x;
+        cur = findPosV(this, coords, dir, unit);
+        if (cur.hitSide) break;
+      }
+      return cur;
+    },
+
+    moveV: operation(null, function(dir, unit) {
+      var sel = this.doc.sel;
+      var pos = cursorCoords(this, sel.head, "div");
+      if (sel.goalColumn != null) pos.left = sel.goalColumn;
+      var target = findPosV(this, pos, dir, unit);
+
+      if (unit == "page") addToScrollPos(this, 0, charCoords(this, target, "div").top - pos.top);
+      extendSelection(this.doc, target, target, dir);
+      sel.goalColumn = pos.left;
+    }),
+
+    toggleOverwrite: function(value) {
+      if (value != null && value == this.state.overwrite) return;
+      if (this.state.overwrite = !this.state.overwrite)
+        this.display.cursor.className += " CodeMirror-overwrite";
+      else
+        this.display.cursor.className = this.display.cursor.className.replace(" CodeMirror-overwrite", "");
+    },
+    hasFocus: function() { return this.state.focused; },
+
+    scrollTo: operation(null, function(x, y) {
+      updateScrollPos(this, x, y);
+    }),
+    getScrollInfo: function() {
+      var scroller = this.display.scroller, co = scrollerCutOff;
+      return {left: scroller.scrollLeft, top: scroller.scrollTop,
+              height: scroller.scrollHeight - co, width: scroller.scrollWidth - co,
+              clientHeight: scroller.clientHeight - co, clientWidth: scroller.clientWidth - co};
+    },
+
+    scrollIntoView: operation(null, function(pos, margin) {
+      if (typeof pos == "number") pos = Pos(pos, 0);
+      if (!margin) margin = 0;
+      var coords = pos;
+
+      if (!pos || pos.line != null) {
+        this.curOp.scrollToPos = pos ? clipPos(this.doc, pos) : this.doc.sel.head;
+        this.curOp.scrollToPosMargin = margin;
+        coords = cursorCoords(this, this.curOp.scrollToPos);
+      }
+      var sPos = calculateScrollPos(this, coords.left, coords.top - margin, coords.right, coords.bottom + margin);
+      updateScrollPos(this, sPos.scrollLeft, sPos.scrollTop);
+    }),
+
+    setSize: operation(null, function(width, height) {
+      function interpret(val) {
+        return typeof val == "number" || /^\d+$/.test(String(val)) ? val + "px" : val;
+      }
+      if (width != null) this.display.wrapper.style.width = interpret(width);
+      if (height != null) this.display.wrapper.style.height = interpret(height);
+      if (this.options.lineWrapping)
+        this.display.measureLineCache.length = this.display.measureLineCachePos = 0;
+      this.curOp.forceUpdate = true;
+    }),
+
+    operation: function(f){return runInOp(this, f);},
+
+    refresh: operation(null, function() {
+      clearCaches(this);
+      updateScrollPos(this, this.doc.scrollLeft, this.doc.scrollTop);
+      regChange(this);
+    }),
+
+    swapDoc: operation(null, function(doc) {
+      var old = this.doc;
+      old.cm = null;
+      attachDoc(this, doc);
+      clearCaches(this);
+      resetInput(this, true);
+      updateScrollPos(this, doc.scrollLeft, doc.scrollTop);
+      return old;
+    }),
+
+    getInputField: function(){return this.display.input;},
+    getWrapperElement: function(){return this.display.wrapper;},
+    getScrollerElement: function(){return this.display.scroller;},
+    getGutterElement: function(){return this.display.gutters;}
+  };
+  eventMixin(CodeMirror);
+
+  // OPTION DEFAULTS
+
+  var optionHandlers = CodeMirror.optionHandlers = {};
+
+  // The default configuration options.
+  var defaults = CodeMirror.defaults = {};
+
+  function option(name, deflt, handle, notOnInit) {
+    CodeMirror.defaults[name] = deflt;
+    if (handle) optionHandlers[name] =
+      notOnInit ? function(cm, val, old) {if (old != Init) handle(cm, val, old);} : handle;
+  }
+
+  var Init = CodeMirror.Init = {toString: function(){return "CodeMirror.Init";}};
+
+  // These two are, on init, called from the constructor because they
+  // have to be initialized before the editor can start at all.
+  option("value", "", function(cm, val) {
+    cm.setValue(val);
+  }, true);
+  option("mode", null, function(cm, val) {
+    cm.doc.modeOption = val;
+    loadMode(cm);
+  }, true);
+
+  option("indentUnit", 2, loadMode, true);
+  option("indentWithTabs", false);
+  option("smartIndent", true);
+  option("tabSize", 4, function(cm) {
+    loadMode(cm);
+    clearCaches(cm);
+    regChange(cm);
+  }, true);
+  option("electricChars", true);
+  option("rtlMoveVisually", !windows);
+
+  option("theme", "default", function(cm) {
+    themeChanged(cm);
+    guttersChanged(cm);
+  }, true);
+  option("keyMap", "default", keyMapChanged);
+  option("extraKeys", null);
+
+  option("onKeyEvent", null);
+  option("onDragEvent", null);
+
+  option("lineWrapping", false, wrappingChanged, true);
+  option("gutters", [], function(cm) {
+    setGuttersForLineNumbers(cm.options);
+    guttersChanged(cm);
+  }, true);
+  option("fixedGutter", true, function(cm, val) {
+    cm.display.gutters.style.left = val ? compensateForHScroll(cm.display) + "px" : "0";
+    cm.refresh();
+  }, true);
+  option("coverGutterNextToScrollbar", false, updateScrollbars, true);
+  option("lineNumbers", false, function(cm) {
+    setGuttersForLineNumbers(cm.options);
+    guttersChanged(cm);
+  }, true);
+  option("firstLineNumber", 1, guttersChanged, true);
+  option("lineNumberFormatter", function(integer) {return integer;}, guttersChanged, true);
+  option("showCursorWhenSelecting", false, updateSelection, true);
+
+  option("readOnly", false, function(cm, val) {
+    if (val == "nocursor") {onBlur(cm); cm.display.input.blur();}
+    else if (!val) resetInput(cm, true);
+  });
+  option("dragDrop", true);
+
+  option("cursorBlinkRate", 530);
+  option("cursorScrollMargin", 0);
+  option("cursorHeight", 1);
+  option("workTime", 100);
+  option("workDelay", 100);
+  option("flattenSpans", true);
+  option("pollInterval", 100);
+  option("undoDepth", 40, function(cm, val){cm.doc.history.undoDepth = val;});
+  option("historyEventDelay", 500);
+  option("viewportMargin", 10, function(cm){cm.refresh();}, true);
+  option("maxHighlightLength", 10000, function(cm){loadMode(cm); cm.refresh();}, true);
+  option("crudeMeasuringFrom", 10000);
+  option("moveInputWithCursor", true, function(cm, val) {
+    if (!val) cm.display.inputDiv.style.top = cm.display.inputDiv.style.left = 0;
+  });
+
+  option("tabindex", null, function(cm, val) {
+    cm.display.input.tabIndex = val || "";
+  });
+  option("autofocus", null);
+
+  // MODE DEFINITION AND QUERYING
+
+  // Known modes, by name and by MIME
+  var modes = CodeMirror.modes = {}, mimeModes = CodeMirror.mimeModes = {};
+
+  CodeMirror.defineMode = function(name, mode) {
+    if (!CodeMirror.defaults.mode && name != "null") CodeMirror.defaults.mode = name;
+    if (arguments.length > 2) {
+      mode.dependencies = [];
+      for (var i = 2; i < arguments.length; ++i) mode.dependencies.push(arguments[i]);
+    }
+    modes[name] = mode;
+  };
+
+  CodeMirror.defineMIME = function(mime, spec) {
+    mimeModes[mime] = spec;
+  };
+
+  CodeMirror.resolveMode = function(spec) {
+    if (typeof spec == "string" && mimeModes.hasOwnProperty(spec)) {
+      spec = mimeModes[spec];
+    } else if (spec && typeof spec.name == "string" && mimeModes.hasOwnProperty(spec.name)) {
+      var found = mimeModes[spec.name];
+      spec = createObj(found, spec);
+      spec.name = found.name;
+    } else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+xml$/.test(spec)) {
+      return CodeMirror.resolveMode("application/xml");
+    }
+    if (typeof spec == "string") return {name: spec};
+    else return spec || {name: "null"};
+  };
+
+  CodeMirror.getMode = function(options, spec) {
+    var spec = CodeMirror.resolveMode(spec);
+    var mfactory = modes[spec.name];
+    if (!mfactory) return CodeMirror.getMode(options, "text/plain");
+    var modeObj = mfactory(options, spec);
+    if (modeExtensions.hasOwnProperty(spec.name)) {
+      var exts = modeExtensions[spec.name];
+      for (var prop in exts) {
+        if (!exts.hasOwnProperty(prop)) continue;
+        if (modeObj.hasOwnProperty(prop)) modeObj["_" + prop] = modeObj[prop];
+        modeObj[prop] = exts[prop];
+      }
+    }
+    modeObj.name = spec.name;
+
+    return modeObj;
+  };
+
+  CodeMirror.defineMode("null", function() {
+    return {token: function(stream) {stream.skipToEnd();}};
+  });
+  CodeMirror.defineMIME("text/plain", "null");
+
+  var modeExtensions = CodeMirror.modeExtensions = {};
+  CodeMirror.extendMode = function(mode, properties) {
+    var exts = modeExtensions.hasOwnProperty(mode) ? modeExtensions[mode] : (modeExtensions[mode] = {});
+    copyObj(properties, exts);
+  };
+
+  // EXTENSIONS
+
+  CodeMirror.defineExtension = function(name, func) {
+    CodeMirror.prototype[name] = func;
+  };
+  CodeMirror.defineDocExtension = function(name, func) {
+    Doc.prototype[name] = func;
+  };
+  CodeMirror.defineOption = option;
+
+  var initHooks = [];
+  CodeMirror.defineInitHook = function(f) {initHooks.push(f);};
+
+  var helpers = CodeMirror.helpers = {};
+  CodeMirror.registerHelper = function(type, name, value) {
+    if (!helpers.hasOwnProperty(type)) helpers[type] = CodeMirror[type] = {};
+    helpers[type][name] = value;
+  };
+
+  // UTILITIES
+
+  CodeMirror.isWordChar = isWordChar;
+
+  // MODE STATE HANDLING
+
+  // Utility functions for working with state. Exported because modes
+  // sometimes need to do this.
+  function copyState(mode, state) {
+    if (state === true) return state;
+    if (mode.copyState) return mode.copyState(state);
+    var nstate = {};
+    for (var n in state) {
+      var val = state[n];
+      if (val instanceof Array) val = val.concat([]);
+      nstate[n] = val;
+    }
+    return nstate;
+  }
+  CodeMirror.copyState = copyState;
+
+  function startState(mode, a1, a2) {
+    return mode.startState ? mode.startState(a1, a2) : true;
+  }
+  CodeMirror.startState = startState;
+
+  CodeMirror.innerMode = function(mode, state) {
+    while (mode.innerMode) {
+      var info = mode.innerMode(state);
+      if (!info || info.mode == mode) break;
+      state = info.state;
+      mode = info.mode;
+    }
+    return info || {mode: mode, state: state};
+  };
+
+  // STANDARD COMMANDS
+
+  var commands = CodeMirror.commands = {
+    selectAll: function(cm) {cm.setSelection(Pos(cm.firstLine(), 0), Pos(cm.lastLine()));},
+    killLine: function(cm) {
+      var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
+      if (!sel && cm.getLine(from.line).length == from.ch)
+        cm.replaceRange("", from, Pos(from.line + 1, 0), "+delete");
+      else cm.replaceRange("", from, sel ? to : Pos(from.line), "+delete");
+    },
+    deleteLine: function(cm) {
+      var l = cm.getCursor().line;
+      cm.replaceRange("", Pos(l, 0), Pos(l), "+delete");
+    },
+    delLineLeft: function(cm) {
+      var cur = cm.getCursor();
+      cm.replaceRange("", Pos(cur.line, 0), cur, "+delete");
+    },
+    undo: function(cm) {cm.undo();},
+    redo: function(cm) {cm.redo();},
+    goDocStart: function(cm) {cm.extendSelection(Pos(cm.firstLine(), 0));},
+    goDocEnd: function(cm) {cm.extendSelection(Pos(cm.lastLine()));},
+    goLineStart: function(cm) {
+      cm.extendSelection(lineStart(cm, cm.getCursor().line));
+    },
+    goLineStartSmart: function(cm) {
+      var cur = cm.getCursor(), start = lineStart(cm, cur.line);
+      var line = cm.getLineHandle(start.line);
+      var order = getOrder(line);
+      if (!order || order[0].level == 0) {
+        var firstNonWS = Math.max(0, line.text.search(/\S/));
+        var inWS = cur.line == start.line && cur.ch <= firstNonWS && cur.ch;
+        cm.extendSelection(Pos(start.line, inWS ? 0 : firstNonWS));
+      } else cm.extendSelection(start);
+    },
+    goLineEnd: function(cm) {
+      cm.extendSelection(lineEnd(cm, cm.getCursor().line));
+    },
+    goLineRight: function(cm) {
+      var top = cm.charCoords(cm.getCursor(), "div").top + 5;
+      cm.extendSelection(cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, "div"));
+    },
+    goLineLeft: function(cm) {
+      var top = cm.charCoords(cm.getCursor(), "div").top + 5;
+      cm.extendSelection(cm.coordsChar({left: 0, top: top}, "div"));
+    },
+    goLineUp: function(cm) {cm.moveV(-1, "line");},
+    goLineDown: function(cm) {cm.moveV(1, "line");},
+    goPageUp: function(cm) {cm.moveV(-1, "page");},
+    goPageDown: function(cm) {cm.moveV(1, "page");},
+    goCharLeft: function(cm) {cm.moveH(-1, "char");},
+    goCharRight: function(cm) {cm.moveH(1, "char");},
+    goColumnLeft: function(cm) {cm.moveH(-1, "column");},
+    goColumnRight: function(cm) {cm.moveH(1, "column");},
+    goWordLeft: function(cm) {cm.moveH(-1, "word");},
+    goGroupRight: function(cm) {cm.moveH(1, "group");},
+    goGroupLeft: function(cm) {cm.moveH(-1, "group");},
+    goWordRight: function(cm) {cm.moveH(1, "word");},
+    delCharBefore: function(cm) {cm.deleteH(-1, "char");},
+    delCharAfter: function(cm) {cm.deleteH(1, "char");},
+    delWordBefore: function(cm) {cm.deleteH(-1, "word");},
+    delWordAfter: function(cm) {cm.deleteH(1, "word");},
+    delGroupBefore: function(cm) {cm.deleteH(-1, "group");},
+    delGroupAfter: function(cm) {cm.deleteH(1, "group");},
+    indentAuto: function(cm) {cm.indentSelection("smart");},
+    indentMore: function(cm) {cm.indentSelection("add");},
+    indentLess: function(cm) {cm.indentSelection("subtract");},
+    insertTab: function(cm) {cm.replaceSelection("\t", "end", "+input");},
+    defaultTab: function(cm) {
+      if (cm.somethingSelected()) cm.indentSelection("add");
+      else cm.replaceSelection("\t", "end", "+input");
+    },
+    transposeChars: function(cm) {
+      var cur = cm.getCursor(), line = cm.getLine(cur.line);
+      if (cur.ch > 0 && cur.ch < line.length - 1)
+        cm.replaceRange(line.charAt(cur.ch) + line.charAt(cur.ch - 1),
+                        Pos(cur.line, cur.ch - 1), Pos(cur.line, cur.ch + 1));
+    },
+    newlineAndIndent: function(cm) {
+      operation(cm, function() {
+        cm.replaceSelection("\n", "end", "+input");
+        cm.indentLine(cm.getCursor().line, null, true);
+      })();
+    },
+    toggleOverwrite: function(cm) {cm.toggleOverwrite();}
+  };
+
+  // STANDARD KEYMAPS
+
+  var keyMap = CodeMirror.keyMap = {};
+  keyMap.basic = {
+    "Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goLineDown",
+    "End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageDown": "goPageDown",
+    "Delete": "delCharAfter", "Backspace": "delCharBefore", "Tab": "defaultTab", "Shift-Tab": "indentAuto",
+    "Enter": "newlineAndIndent", "Insert": "toggleOverwrite"
+  };
+  // Note that the save and find-related commands aren't defined by
+  // default. Unknown commands are simply ignored.
+  keyMap.pcDefault = {
+    "Ctrl-A": "selectAll", "Ctrl-D": "deleteLine", "Ctrl-Z": "undo", "Shift-Ctrl-Z": "redo", "Ctrl-Y": "redo",
+    "Ctrl-Home": "goDocStart", "Alt-Up": "goDocStart", "Ctrl-End": "goDocEnd", "Ctrl-Down": "goDocEnd",
+    "Ctrl-Left": "goGroupLeft", "Ctrl-Right": "goGroupRight", "Alt-Left": "goLineStart", "Alt-Right": "goLineEnd",
+    "Ctrl-Backspace": "delGroupBefore", "Ctrl-Delete": "delGroupAfter", "Ctrl-S": "save", "Ctrl-F": "find",
+    "Ctrl-G": "findNext", "Shift-Ctrl-G": "findPrev", "Shift-Ctrl-F": "replace", "Shift-Ctrl-R": "replaceAll",
+    "Ctrl-[": "indentLess", "Ctrl-]": "indentMore",
+    fallthrough: "basic"
+  };
+  keyMap.macDefault = {
+    "Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z": "redo", "Cmd-Y": "redo",
+    "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cmd-Down": "goDocEnd", "Alt-Left": "goGroupLeft",
+    "Alt-Right": "goGroupRight", "Cmd-Left": "goLineStart", "Cmd-Right": "goLineEnd", "Alt-Backspace": "delGroupBefore",
+    "Ctrl-Alt-Backspace": "delGroupAfter", "Alt-Delete": "delGroupAfter", "Cmd-S": "save", "Cmd-F": "find",
+    "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift-Cmd-Alt-F": "replaceAll",
+    "Cmd-[": "indentLess", "Cmd-]": "indentMore", "Cmd-Backspace": "delLineLeft",
+    fallthrough: ["basic", "emacsy"]
+  };
+  keyMap["default"] = mac ? keyMap.macDefault : keyMap.pcDefault;
+  keyMap.emacsy = {
+    "Ctrl-F": "goCharRight", "Ctrl-B": "goCharLeft", "Ctrl-P": "goLineUp", "Ctrl-N": "goLineDown",
+    "Alt-F": "goWordRight", "Alt-B": "goWordLeft", "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd",
+    "Ctrl-V": "goPageDown", "Shift-Ctrl-V": "goPageUp", "Ctrl-D": "delCharAfter", "Ctrl-H": "delCharBefore",
+    "Alt-D": "delWordAfter", "Alt-Backspace": "delWordBefore", "Ctrl-K": "killLine", "Ctrl-T": "transposeChars"
+  };
+
+  // KEYMAP DISPATCH
+
+  function getKeyMap(val) {
+    if (typeof val == "string") return keyMap[val];
+    else return val;
+  }
+
+  function lookupKey(name, maps, handle) {
+    function lookup(map) {
+      map = getKeyMap(map);
+      var found = map[name];
+      if (found === false) return "stop";
+      if (found != null && handle(found)) return true;
+      if (map.nofallthrough) return "stop";
+
+      var fallthrough = map.fallthrough;
+      if (fallthrough == null) return false;
+      if (Object.prototype.toString.call(fallthrough) != "[object Array]")
+        return lookup(fallthrough);
+      for (var i = 0, e = fallthrough.length; i < e; ++i) {
+        var done = lookup(fallthrough[i]);
+        if (done) return done;
+      }
+      return false;
+    }
+
+    for (var i = 0; i < maps.length; ++i) {
+      var done = lookup(maps[i]);
+      if (done) return done != "stop";
+    }
+  }
+  function isModifierKey(event) {
+    var name = keyNames[event.keyCode];
+    return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod";
+  }
+  function keyName(event, noShift) {
+    if (opera && event.keyCode == 34 && event["char"]) return false;
+    var name = keyNames[event.keyCode];
+    if (name == null || event.altGraphKey) return false;
+    if (event.altKey) name = "Alt-" + name;
+    if (flipCtrlCmd ? event.metaKey : event.ctrlKey) name = "Ctrl-" + name;
+    if (flipCtrlCmd ? event.ctrlKey : event.metaKey) name = "Cmd-" + name;
+    if (!noShift && event.shiftKey) name = "Shift-" + name;
+    return name;
+  }
+  CodeMirror.lookupKey = lookupKey;
+  CodeMirror.isModifierKey = isModifierKey;
+  CodeMirror.keyName = keyName;
+
+  // FROMTEXTAREA
+
+  CodeMirror.fromTextArea = function(textarea, options) {
+    if (!options) options = {};
+    options.value = textarea.value;
+    if (!options.tabindex && textarea.tabindex)
+      options.tabindex = textarea.tabindex;
+    if (!options.placeholder && textarea.placeholder)
+      options.placeholder = textarea.placeholder;
+    // Set autofocus to true if this textarea is focused, or if it has
+    // autofocus and no other element is focused.
+    if (options.autofocus == null) {
+      var hasFocus = document.body;
+      // doc.activeElement occasionally throws on IE
+      try { hasFocus = document.activeElement; } catch(e) {}
+      options.autofocus = hasFocus == textarea ||
+        textarea.getAttribute("autofocus") != null && hasFocus == document.body;
+    }
+
+    function save() {textarea.value = cm.getValue();}
+    if (textarea.form) {
+      on(textarea.form, "submit", save);
+      // Deplorable hack to make the submit method do the right thing.
+      if (!options.leaveSubmitMethodAlone) {
+        var form = textarea.form, realSubmit = form.submit;
+        try {
+          var wrappedSubmit = form.submit = function() {
+            save();
+            form.submit = realSubmit;
+            form.submit();
+            form.submit = wrappedSubmit;
+          };
+        } catch(e) {}
+      }
+    }
+
+    textarea.style.display = "none";
+    var cm = CodeMirror(function(node) {
+      textarea.parentNode.insertBefore(node, textarea.nextSibling);
+    }, options);
+    cm.save = save;
+    cm.getTextArea = function() { return textarea; };
+    cm.toTextArea = function() {
+      save();
+      textarea.parentNode.removeChild(cm.getWrapperElement());
+      textarea.style.display = "";
+      if (textarea.form) {
+        off(textarea.form, "submit", save);
+        if (typeof textarea.form.submit == "function")
+          textarea.form.submit = realSubmit;
+      }
+    };
+    return cm;
+  };
+
+  // STRING STREAM
+
+  // Fed to the mode parsers, provides helper functions to make
+  // parsers more succinct.
+
+  // The character stream used by a mode's parser.
+  function StringStream(string, tabSize) {
+    this.pos = this.start = 0;
+    this.string = string;
+    this.tabSize = tabSize || 8;
+    this.lastColumnPos = this.lastColumnValue = 0;
+  }
+
+  StringStream.prototype = {
+    eol: function() {return this.pos >= this.string.length;},
+    sol: function() {return this.pos == 0;},
+    peek: function() {return this.string.charAt(this.pos) || undefined;},
+    next: function() {
+      if (this.pos < this.string.length)
+        return this.string.charAt(this.pos++);
+    },
+    eat: function(match) {
+      var ch = this.string.charAt(this.pos);
+      if (typeof match == "string") var ok = ch == match;
+      else var ok = ch && (match.test ? match.test(ch) : match(ch));
+      if (ok) {++this.pos; return ch;}
+    },
+    eatWhile: function(match) {
+      var start = this.pos;
+      while (this.eat(match)){}
+      return this.pos > start;
+    },
+    eatSpace: function() {
+      var start = this.pos;
+      while (/[\s\u00a0]/.test(this.string.charAt(this.pos))) ++this.pos;
+      return this.pos > start;
+    },
+    skipToEnd: function() {this.pos = this.string.length;},
+    skipTo: function(ch) {
+      var found = this.string.indexOf(ch, this.pos);
+      if (found > -1) {this.pos = found; return true;}
+    },
+    backUp: function(n) {this.pos -= n;},
+    column: function() {
+      if (this.lastColumnPos < this.start) {
+        this.lastColumnValue = countColumn(this.string, this.start, this.tabSize, this.lastColumnPos, this.lastColumnValue);
+        this.lastColumnPos = this.start;
+      }
+      return this.lastColumnValue;
+    },
+    indentation: function() {return countColumn(this.string, null, this.tabSize);},
+    match: function(pattern, consume, caseInsensitive) {
+      if (typeof pattern == "string") {
+        var cased = function(str) {return caseInsensitive ? str.toLowerCase() : str;};
+        var substr = this.string.substr(this.pos, pattern.length);
+        if (cased(substr) == cased(pattern)) {
+          if (consume !== false) this.pos += pattern.length;
+          return true;
+        }
+      } else {
+        var match = this.string.slice(this.pos).match(pattern);
+        if (match && match.index > 0) return null;
+        if (match && consume !== false) this.pos += match[0].length;
+        return match;
+      }
+    },
+    current: function(){return this.string.slice(this.start, this.pos);}
+  };
+  CodeMirror.StringStream = StringStream;
+
+  // TEXTMARKERS
+
+  function TextMarker(doc, type) {
+    this.lines = [];
+    this.type = type;
+    this.doc = doc;
+  }
+  CodeMirror.TextMarker = TextMarker;
+  eventMixin(TextMarker);
+
+  TextMarker.prototype.clear = function() {
+    if (this.explicitlyCleared) return;
+    var cm = this.doc.cm, withOp = cm && !cm.curOp;
+    if (withOp) startOperation(cm);
+    if (hasHandler(this, "clear")) {
+      var found = this.find();
+      if (found) signalLater(this, "clear", found.from, found.to);
+    }
+    var min = null, max = null;
+    for (var i = 0; i < this.lines.length; ++i) {
+      var line = this.lines[i];
+      var span = getMarkedSpanFor(line.markedSpans, this);
+      if (span.to != null) max = lineNo(line);
+      line.markedSpans = removeMarkedSpan(line.markedSpans, span);
+      if (span.from != null)
+        min = lineNo(line);
+      else if (this.collapsed && !lineIsHidden(this.doc, line) && cm)
+        updateLineHeight(line, textHeight(cm.display));
+    }
+    if (cm && this.collapsed && !cm.options.lineWrapping) for (var i = 0; i < this.lines.length; ++i) {
+      var visual = visualLine(cm.doc, this.lines[i]), len = lineLength(cm.doc, visual);
+      if (len > cm.display.maxLineLength) {
+        cm.display.maxLine = visual;
+        cm.display.maxLineLength = len;
+        cm.display.maxLineChanged = true;
+      }
+    }
+
+    if (min != null && cm) regChange(cm, min, max + 1);
+    this.lines.length = 0;
+    this.explicitlyCleared = true;
+    if (this.atomic && this.doc.cantEdit) {
+      this.doc.cantEdit = false;
+      if (cm) reCheckSelection(cm);
+    }
+    if (withOp) endOperation(cm);
+  };
+
+  TextMarker.prototype.find = function() {
+    var from, to;
+    for (var i = 0; i < this.lines.length; ++i) {
+      var line = this.lines[i];
+      var span = getMarkedSpanFor(line.markedSpans, this);
+      if (span.from != null || span.to != null) {
+        var found = lineNo(line);
+        if (span.from != null) from = Pos(found, span.from);
+        if (span.to != null) to = Pos(found, span.to);
+      }
+    }
+    if (this.type == "bookmark") return from;
+    return from && {from: from, to: to};
+  };
+
+  TextMarker.prototype.changed = function() {
+    var pos = this.find(), cm = this.doc.cm;
+    if (!pos || !cm) return;
+    if (this.type != "bookmark") pos = pos.from;
+    var line = getLine(this.doc, pos.line);
+    clearCachedMeasurement(cm, line);
+    if (pos.line >= cm.display.showingFrom && pos.line < cm.display.showingTo) {
+      for (var node = cm.display.lineDiv.firstChild; node; node = node.nextSibling) if (node.lineObj == line) {
+        if (node.offsetHeight != line.height) updateLineHeight(line, node.offsetHeight);
+        break;
+      }
+      runInOp(cm, function() {
+        cm.curOp.selectionChanged = cm.curOp.forceUpdate = cm.curOp.updateMaxLine = true;
+      });
+    }
+  };
+
+  TextMarker.prototype.attachLine = function(line) {
+    if (!this.lines.length && this.doc.cm) {
+      var op = this.doc.cm.curOp;
+      if (!op.maybeHiddenMarkers || indexOf(op.maybeHiddenMarkers, this) == -1)
+        (op.maybeUnhiddenMarkers || (op.maybeUnhiddenMarkers = [])).push(this);
+    }
+    this.lines.push(line);
+  };
+  TextMarker.prototype.detachLine = function(line) {
+    this.lines.splice(indexOf(this.lines, line), 1);
+    if (!this.lines.length && this.doc.cm) {
+      var op = this.doc.cm.curOp;
+      (op.maybeHiddenMarkers || (op.maybeHiddenMarkers = [])).push(this);
+    }
+  };
+
+  function markText(doc, from, to, options, type) {
+    if (options && options.shared) return markTextShared(doc, from, to, options, type);
+    if (doc.cm && !doc.cm.curOp) return operation(doc.cm, markText)(doc, from, to, options, type);
+
+    var marker = new TextMarker(doc, type);
+    if (type == "range" && !posLess(from, to)) return marker;
+    if (options) copyObj(options, marker);
+    if (marker.replacedWith) {
+      marker.collapsed = true;
+      marker.replacedWith = elt("span", [marker.replacedWith], "CodeMirror-widget");
+      if (!options.handleMouseEvents) marker.replacedWith.ignoreEvents = true;
+    }
+    if (marker.collapsed) sawCollapsedSpans = true;
+
+    if (marker.addToHistory)
+      addToHistory(doc, {from: from, to: to, origin: "markText"},
+                   {head: doc.sel.head, anchor: doc.sel.anchor}, NaN);
+
+    var curLine = from.line, size = 0, collapsedAtStart, collapsedAtEnd, cm = doc.cm, updateMaxLine;
+    doc.iter(curLine, to.line + 1, function(line) {
+      if (cm && marker.collapsed && !cm.options.lineWrapping && visualLine(doc, line) == cm.display.maxLine)
+        updateMaxLine = true;
+      var span = {from: null, to: null, marker: marker};
+      size += line.text.length;
+      if (curLine == from.line) {span.from = from.ch; size -= from.ch;}
+      if (curLine == to.line) {span.to = to.ch; size -= line.text.length - to.ch;}
+      if (marker.collapsed) {
+        if (curLine == to.line) collapsedAtEnd = collapsedSpanAt(line, to.ch);
+        if (curLine == from.line) collapsedAtStart = collapsedSpanAt(line, from.ch);
+        else updateLineHeight(line, 0);
+      }
+      addMarkedSpan(line, span);
+      ++curLine;
+    });
+    if (marker.collapsed) doc.iter(from.line, to.line + 1, function(line) {
+      if (lineIsHidden(doc, line)) updateLineHeight(line, 0);
+    });
+
+    if (marker.clearOnEnter) on(marker, "beforeCursorEnter", function() { marker.clear(); });
+
+    if (marker.readOnly) {
+      sawReadOnlySpans = true;
+      if (doc.history.done.length || doc.history.undone.length)
+        doc.clearHistory();
+    }
+    if (marker.collapsed) {
+      if (collapsedAtStart != collapsedAtEnd)
+        throw new Error("Inserting collapsed marker overlapping an existing one");
+      marker.size = size;
+      marker.atomic = true;
+    }
+    if (cm) {
+      if (updateMaxLine) cm.curOp.updateMaxLine = true;
+      if (marker.className || marker.title || marker.startStyle || marker.endStyle || marker.collapsed)
+        regChange(cm, from.line, to.line + 1);
+      if (marker.atomic) reCheckSelection(cm);
+    }
+    return marker;
+  }
+
+  // SHARED TEXTMARKERS
+
+  function SharedTextMarker(markers, primary) {
+    this.markers = markers;
+    this.primary = primary;
+    for (var i = 0, me = this; i < markers.length; ++i) {
+      markers[i].parent = this;
+      on(markers[i], "clear", function(){me.clear();});
+    }
+  }
+  CodeMirror.SharedTextMarker = SharedTextMarker;
+  eventMixin(SharedTextMarker);
+
+  SharedTextMarker.prototype.clear = function() {
+    if (this.explicitlyCleared) return;
+    this.explicitlyCleared = true;
+    for (var i = 0; i < this.markers.length; ++i)
+      this.markers[i].clear();
+    signalLater(this, "clear");
+  };
+  SharedTextMarker.prototype.find = function() {
+    return this.primary.find();
+  };
+
+  function markTextShared(doc, from, to, options, type) {
+    options = copyObj(options);
+    options.shared = false;
+    var markers = [markText(doc, from, to, options, type)], primary = markers[0];
+    var widget = options.replacedWith;
+    linkedDocs(doc, function(doc) {
+      if (widget) options.replacedWith = widget.cloneNode(true);
+      markers.push(markText(doc, clipPos(doc, from), clipPos(doc, to), options, type));
+      for (var i = 0; i < doc.linked.length; ++i)
+        if (doc.linked[i].isParent) return;
+      primary = lst(markers);
+    });
+    return new SharedTextMarker(markers, primary);
+  }
+
+  // TEXTMARKER SPANS
+
+  function getMarkedSpanFor(spans, marker) {
+    if (spans) for (var i = 0; i < spans.length; ++i) {
+      var span = spans[i];
+      if (span.marker == marker) return span;
+    }
+  }
+  function removeMarkedSpan(spans, span) {
+    for (var r, i = 0; i < spans.length; ++i)
+      if (spans[i] != span) (r || (r = [])).push(spans[i]);
+    return r;
+  }
+  function addMarkedSpan(line, span) {
+    line.markedSpans = line.markedSpans ? line.markedSpans.concat([span]) : [span];
+    span.marker.attachLine(line);
+  }
+
+  function markedSpansBefore(old, startCh, isInsert) {
+    if (old) for (var i = 0, nw; i < old.length; ++i) {
+      var span = old[i], marker = span.marker;
+      var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= startCh : span.from < startCh);
+      if (startsBefore || marker.type == "bookmark" && span.from == startCh && (!isInsert || !span.marker.insertLeft)) {
+        var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= startCh : span.to > startCh);
+        (nw || (nw = [])).push({from: span.from,
+                                to: endsAfter ? null : span.to,
+                                marker: marker});
+      }
+    }
+    return nw;
+  }
+
+  function markedSpansAfter(old, endCh, isInsert) {
+    if (old) for (var i = 0, nw; i < old.length; ++i) {
+      var span = old[i], marker = span.marker;
+      var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= endCh : span.to > endCh);
+      if (endsAfter || marker.type == "bookmark" && span.from == endCh && (!isInsert || span.marker.insertLeft)) {
+        var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= endCh : span.from < endCh);
+        (nw || (nw = [])).push({from: startsBefore ? null : span.from - endCh,
+                                to: span.to == null ? null : span.to - endCh,
+                                marker: marker});
+      }
+    }
+    return nw;
+  }
+
+  function stretchSpansOverChange(doc, change) {
+    var oldFirst = isLine(doc, change.from.line) && getLine(doc, change.from.line).markedSpans;
+    var oldLast = isLine(doc, change.to.line) && getLine(doc, change.to.line).markedSpans;
+    if (!oldFirst && !oldLast) return null;
+
+    var startCh = change.from.ch, endCh = change.to.ch, isInsert = posEq(change.from, change.to);
+    // Get the spans that 'stick out' on both sides
+    var first = markedSpansBefore(oldFirst, startCh, isInsert);
+    var last = markedSpansAfter(oldLast, endCh, isInsert);
+
+    // Next, merge those two ends
+    var sameLine = change.text.length == 1, offset = lst(change.text).length + (sameLine ? startCh : 0);
+    if (first) {
+      // Fix up .to properties of first
+      for (var i = 0; i < first.length; ++i) {
+        var span = first[i];
+        if (span.to == null) {
+          var found = getMarkedSpanFor(last, span.marker);
+          if (!found) span.to = startCh;
+          else if (sameLine) span.to = found.to == null ? null : found.to + offset;
+        }
+      }
+    }
+    if (last) {
+      // Fix up .from in last (or move them into first in case of sameLine)
+      for (var i = 0; i < last.length; ++i) {
+        var span = last[i];
+        if (span.to != null) span.to += offset;
+        if (span.from == null) {
+          var found = getMarkedSpanFor(first, span.marker);
+          if (!found) {
+            span.from = offset;
+            if (sameLine) (first || (first = [])).push(span);
+          }
+        } else {
+          span.from += offset;
+          if (sameLine) (first || (first = [])).push(span);
+        }
+      }
+    }
+    if (sameLine && first) {
+      // Make sure we didn't create any zero-length spans
+      for (var i = 0; i < first.length; ++i)
+        if (first[i].from != null && first[i].from == first[i].to && first[i].marker.type != "bookmark")
+          first.splice(i--, 1);
+      if (!first.length) first = null;
+    }
+
+    var newMarkers = [first];
+    if (!sameLine) {
+      // Fill gap with whole-line-spans
+      var gap = change.text.length - 2, gapMarkers;
+      if (gap > 0 && first)
+        for (var i = 0; i < first.length; ++i)
+          if (first[i].to == null)
+            (gapMarkers || (gapMarkers = [])).push({from: null, to: null, marker: first[i].marker});
+      for (var i = 0; i < gap; ++i)
+        newMarkers.push(gapMarkers);
+      newMarkers.push(last);
+    }
+    return newMarkers;
+  }
+
+  function mergeOldSpans(doc, change) {
+    var old = getOldSpans(doc, change);
+    var stretched = stretchSpansOverChange(doc, change);
+    if (!old) return stretched;
+    if (!stretched) return old;
+
+    for (var i = 0; i < old.length; ++i) {
+      var oldCur = old[i], stretchCur = stretched[i];
+      if (oldCur && stretchCur) {
+        spans: for (var j = 0; j < stretchCur.length; ++j) {
+          var span = stretchCur[j];
+          for (var k = 0; k < oldCur.length; ++k)
+            if (oldCur[k].marker == span.marker) continue spans;
+          oldCur.push(span);
+        }
+      } else if (stretchCur) {
+        old[i] = stretchCur;
+      }
+    }
+    return old;
+  }
+
+  function removeReadOnlyRanges(doc, from, to) {
+    var markers = null;
+    doc.iter(from.line, to.line + 1, function(line) {
+      if (line.markedSpans) for (var i = 0; i < line.markedSpans.length; ++i) {
+        var mark = line.markedSpans[i].marker;
+        if (mark.readOnly && (!markers || indexOf(markers, mark) == -1))
+          (markers || (markers = [])).push(mark);
+      }
+    });
+    if (!markers) return null;
+    var parts = [{from: from, to: to}];
+    for (var i = 0; i < markers.length; ++i) {
+      var mk = markers[i], m = mk.find();
+      for (var j = 0; j < parts.length; ++j) {
+        var p = parts[j];
+        if (posLess(p.to, m.from) || posLess(m.to, p.from)) continue;
+        var newParts = [j, 1];
+        if (posLess(p.from, m.from) || !mk.inclusiveLeft && posEq(p.from, m.from))
+          newParts.push({from: p.from, to: m.from});
+        if (posLess(m.to, p.to) || !mk.inclusiveRight && posEq(p.to, m.to))
+          newParts.push({from: m.to, to: p.to});
+        parts.splice.apply(parts, newParts);
+        j += newParts.length - 1;
+      }
+    }
+    return parts;
+  }
+
+  function collapsedSpanAt(line, ch) {
+    var sps = sawCollapsedSpans && line.markedSpans, found;
+    if (sps) for (var sp, i = 0; i < sps.length; ++i) {
+      sp = sps[i];
+      if (!sp.marker.collapsed) continue;
+      if ((sp.from == null || sp.from < ch) &&
+          (sp.to == null || sp.to > ch) &&
+          (!found || found.width < sp.marker.width))
+        found = sp.marker;
+    }
+    return found;
+  }
+  function collapsedSpanAtStart(line) { return collapsedSpanAt(line, -1); }
+  function collapsedSpanAtEnd(line) { return collapsedSpanAt(line, line.text.length + 1); }
+
+  function visualLine(doc, line) {
+    var merged;
+    while (merged = collapsedSpanAtStart(line))
+      line = getLine(doc, merged.find().from.line);
+    return line;
+  }
+
+  function lineIsHidden(doc, line) {
+    var sps = sawCollapsedSpans && line.markedSpans;
+    if (sps) for (var sp, i = 0; i < sps.length; ++i) {
+      sp = sps[i];
+      if (!sp.marker.collapsed) continue;
+      if (sp.from == null) return true;
+      if (sp.marker.replacedWith) continue;
+      if (sp.from == 0 && sp.marker.inclusiveLeft && lineIsHiddenInner(doc, line, sp))
+        return true;
+    }
+  }
+  function lineIsHiddenInner(doc, line, span) {
+    if (span.to == null) {
+      var end = span.marker.find().to, endLine = getLine(doc, end.line);
+      return lineIsHiddenInner(doc, endLine, getMarkedSpanFor(endLine.markedSpans, span.marker));
+    }
+    if (span.marker.inclusiveRight && span.to == line.text.length)
+      return true;
+    for (var sp, i = 0; i < line.markedSpans.length; ++i) {
+      sp = line.markedSpans[i];
+      if (sp.marker.collapsed && !sp.marker.replacedWith && sp.from == span.to &&
+          (sp.marker.inclusiveLeft || span.marker.inclusiveRight) &&
+          lineIsHiddenInner(doc, line, sp)) return true;
+    }
+  }
+
+  function detachMarkedSpans(line) {
+    var spans = line.markedSpans;
+    if (!spans) return;
+    for (var i = 0; i < spans.length; ++i)
+      spans[i].marker.detachLine(line);
+    line.markedSpans = null;
+  }
+
+  function attachMarkedSpans(line, spans) {
+    if (!spans) return;
+    for (var i = 0; i < spans.length; ++i)
+      spans[i].marker.attachLine(line);
+    line.markedSpans = spans;
+  }
+
+  // LINE WIDGETS
+
+  var LineWidget = CodeMirror.LineWidget = function(cm, node, options) {
+    if (options) for (var opt in options) if (options.hasOwnProperty(opt))
+      this[opt] = options[opt];
+    this.cm = cm;
+    this.node = node;
+  };
+  eventMixin(LineWidget);
+  function widgetOperation(f) {
+    return function() {
+      var withOp = !this.cm.curOp;
+      if (withOp) startOperation(this.cm);
+      try {var result = f.apply(this, arguments);}
+      finally {if (withOp) endOperation(this.cm);}
+      return result;
+    };
+  }
+  LineWidget.prototype.clear = widgetOperation(function() {
+    var ws = this.line.widgets, no = lineNo(this.line);
+    if (no == null || !ws) return;
+    for (var i = 0; i < ws.length; ++i) if (ws[i] == this) ws.splice(i--, 1);
+    if (!ws.length) this.line.widgets = null;
+    var aboveVisible = heightAtLine(this.cm, this.line) < this.cm.doc.scrollTop;
+    updateLineHeight(this.line, Math.max(0, this.line.height - widgetHeight(this)));
+    if (aboveVisible) addToScrollPos(this.cm, 0, -this.height);
+    regChange(this.cm, no, no + 1);
+  });
+  LineWidget.prototype.changed = widgetOperation(function() {
+    var oldH = this.height;
+    this.height = null;
+    var diff = widgetHeight(this) - oldH;
+    if (!diff) return;
+    updateLineHeight(this.line, this.line.height + diff);
+    var no = lineNo(this.line);
+    regChange(this.cm, no, no + 1);
+  });
+
+  function widgetHeight(widget) {
+    if (widget.height != null) return widget.height;
+    if (!widget.node.parentNode || widget.node.parentNode.nodeType != 1)
+      removeChildrenAndAdd(widget.cm.display.measure, elt("div", [widget.node], null, "position: relative"));
+    return widget.height = widget.node.offsetHeight;
+  }
+
+  function addLineWidget(cm, handle, node, options) {
+    var widget = new LineWidget(cm, node, options);
+    if (widget.noHScroll) cm.display.alignWidgets = true;
+    changeLine(cm, handle, function(line) {
+      var widgets = line.widgets || (line.widgets = []);
+      if (widget.insertAt == null) widgets.push(widget);
+      else widgets.splice(Math.min(widgets.length - 1, Math.max(0, widget.insertAt)), 0, widget);
+      widget.line = line;
+      if (!lineIsHidden(cm.doc, line) || widget.showIfHidden) {
+        var aboveVisible = heightAtLine(cm, line) < cm.doc.scrollTop;
+        updateLineHeight(line, line.height + widgetHeight(widget));
+        if (aboveVisible) addToScrollPos(cm, 0, widget.height);
+      }
+      return true;
+    });
+    return widget;
+  }
+
+  // LINE DATA STRUCTURE
+
+  // Line objects. These hold state related to a line, including
+  // highlighting info (the styles array).
+  var Line = CodeMirror.Line = function(text, markedSpans, estimateHeight) {
+    this.text = text;
+    attachMarkedSpans(this, markedSpans);
+    this.height = estimateHeight ? estimateHeight(this) : 1;
+  };
+  eventMixin(Line);
+
+  function updateLine(line, text, markedSpans, estimateHeight) {
+    line.text = text;
+    if (line.stateAfter) line.stateAfter = null;
+    if (line.styles) line.styles = null;
+    if (line.order != null) line.order = null;
+    detachMarkedSpans(line);
+    attachMarkedSpans(line, markedSpans);
+    var estHeight = estimateHeight ? estimateHeight(line) : 1;
+    if (estHeight != line.height) updateLineHeight(line, estHeight);
+  }
+
+  function cleanUpLine(line) {
+    line.parent = null;
+    detachMarkedSpans(line);
+  }
+
+  // Run the given mode's parser over a line, update the styles
+  // array, which contains alternating fragments of text and CSS
+  // classes.
+  function runMode(cm, text, mode, state, f) {
+    var flattenSpans = mode.flattenSpans;
+    if (flattenSpans == null) flattenSpans = cm.options.flattenSpans;
+    var curStart = 0, curStyle = null;
+    var stream = new StringStream(text, cm.options.tabSize), style;
+    if (text == "" && mode.blankLine) mode.blankLine(state);
+    while (!stream.eol()) {
+      if (stream.pos > cm.options.maxHighlightLength) {
+        flattenSpans = false;
+        stream.pos = text.length;
+        style = null;
+      } else {
+        style = mode.token(stream, state);
+      }
+      if (!flattenSpans || curStyle != style) {
+        if (curStart < stream.start) f(stream.start, curStyle);
+        curStart = stream.start; curStyle = style;
+      }
+      stream.start = stream.pos;
+    }
+    while (curStart < stream.pos) {
+      // Webkit seems to refuse to render text nodes longer than 57444 characters
+      var pos = Math.min(stream.pos, curStart + 50000);
+      f(pos, curStyle);
+      curStart = pos;
+    }
+  }
+
+  function highlightLine(cm, line, state) {
+    // A styles array always starts with a number identifying the
+    // mode/overlays that it is based on (for easy invalidation).
+    var st = [cm.state.modeGen];
+    // Compute the base array of styles
+    runMode(cm, line.text, cm.doc.mode, state, function(end, style) {st.push(end, style);});
+
+    // Run overlays, adjust style array.
+    for (var o = 0; o < cm.state.overlays.length; ++o) {
+      var overlay = cm.state.overlays[o], i = 1, at = 0;
+      runMode(cm, line.text, overlay.mode, true, function(end, style) {
+        var start = i;
+        // Ensure there's a token end at the current position, and that i points at it
+        while (at < end) {
+          var i_end = st[i];
+          if (i_end > end)
+            st.splice(i, 1, end, st[i+1], i_end);
+          i += 2;
+          at = Math.min(end, i_end);
+        }
+        if (!style) return;
+        if (overlay.opaque) {
+          st.splice(start, i - start, end, style);
+          i = start + 2;
+        } else {
+          for (; start < i; start += 2) {
+            var cur = st[start+1];
+            st[start+1] = cur ? cur + " " + style : style;
+          }
+        }
+      });
+    }
+
+    return st;
+  }
+
+  function getLineStyles(cm, line) {
+    if (!line.styles || line.styles[0] != cm.state.modeGen)
+      line.styles = highlightLine(cm, line, line.stateAfter = getStateBefore(cm, lineNo(line)));
+    return line.styles;
+  }
+
+  // Lightweight form of highlight -- proceed over this line and
+  // update state, but don't save a style array.
+  function processLine(cm, line, state) {
+    var mode = cm.doc.mode;
+    var stream = new StringStream(line.text, cm.options.tabSize);
+    if (line.text == "" && mode.blankLine) mode.blankLine(state);
+    while (!stream.eol() && stream.pos <= cm.options.maxHighlightLength) {
+      mode.token(stream, state);
+      stream.start = stream.pos;
+    }
+  }
+
+  var styleToClassCache = {};
+  function styleToClass(style) {
+    if (!style) return null;
+    return styleToClassCache[style] ||
+      (styleToClassCache[style] = "cm-" + style.replace(/ +/g, " cm-"));
+  }
+
+  function lineContent(cm, realLine, measure, copyWidgets) {
+    var merged, line = realLine, empty = true;
+    while (merged = collapsedSpanAtStart(line))
+      line = getLine(cm.doc, merged.find().from.line);
+
+    var builder = {pre: elt("pre"), col: 0, pos: 0,
+                   measure: null, measuredSomething: false, cm: cm,
+                   copyWidgets: copyWidgets};
+    if (line.textClass) builder.pre.className = line.textClass;
+
+    do {
+      if (line.text) empty = false;
+      builder.measure = line == realLine && measure;
+      builder.pos = 0;
+      builder.addToken = builder.measure ? buildTokenMeasure : buildToken;
+      if ((ie || webkit) && cm.getOption("lineWrapping"))
+        builder.addToken = buildTokenSplitSpaces(builder.addToken);
+      var next = insertLineContent(line, builder, getLineStyles(cm, line));
+      if (measure && line == realLine && !builder.measuredSomething) {
+        measure[0] = builder.pre.appendChild(zeroWidthElement(cm.display.measure));
+        builder.measuredSomething = true;
+      }
+      if (next) line = getLine(cm.doc, next.to.line);
+    } while (next);
+
+    if (measure && !builder.measuredSomething && !measure[0])
+      measure[0] = builder.pre.appendChild(empty ? elt("span", "\u00a0") : zeroWidthElement(cm.display.measure));
+    if (!builder.pre.firstChild && !lineIsHidden(cm.doc, realLine))
+      builder.pre.appendChild(document.createTextNode("\u00a0"));
+
+    var order;
+    // Work around problem with the reported dimensions of single-char
+    // direction spans on IE (issue #1129). See also the comment in
+    // cursorCoords.
+    if (measure && ie && (order = getOrder(line))) {
+      var l = order.length - 1;
+      if (order[l].from == order[l].to) --l;
+      var last = order[l], prev = order[l - 1];
+      if (last.from + 1 == last.to && prev && last.level < prev.level) {
+        var span = measure[builder.pos - 1];
+        if (span) span.parentNode.insertBefore(span.measureRight = zeroWidthElement(cm.display.measure),
+                                               span.nextSibling);
+      }
+    }
+
+    signal(cm, "renderLine", cm, realLine, builder.pre);
+    return builder.pre;
+  }
+
+  var tokenSpecialChars = /[\t\u0000-\u0019\u00ad\u200b\u2028\u2029\uFEFF]/g;
+  function buildToken(builder, text, style, startStyle, endStyle, title) {
+    if (!text) return;
+    if (!tokenSpecialChars.test(text)) {
+      builder.col += text.length;
+      var content = document.createTextNode(text);
+    } else {
+      var content = document.createDocumentFragment(), pos = 0;
+      while (true) {
+        tokenSpecialChars.lastIndex = pos;
+        var m = tokenSpecialChars.exec(text);
+        var skipped = m ? m.index - pos : text.length - pos;
+        if (skipped) {
+          content.appendChild(document.createTextNode(text.slice(pos, pos + skipped)));
+          builder.col += skipped;
+        }
+        if (!m) break;
+        pos += skipped + 1;
+        if (m[0] == "\t") {
+          var tabSize = builder.cm.options.tabSize, tabWidth = tabSize - builder.col % tabSize;
+          content.appendChild(elt("span", spaceStr(tabWidth), "cm-tab"));
+          builder.col += tabWidth;
+        } else {
+          var token = elt("span", "\u2022", "cm-invalidchar");
+          token.title = "\\u" + m[0].charCodeAt(0).toString(16);
+          content.appendChild(token);
+          builder.col += 1;
+        }
+      }
+    }
+    if (style || startStyle || endStyle || builder.measure) {
+      var fullStyle = style || "";
+      if (startStyle) fullStyle += startStyle;
+      if (endStyle) fullStyle += endStyle;
+      var token = elt("span", [content], fullStyle);
+      if (title) token.title = title;
+      return builder.pre.appendChild(token);
+    }
+    builder.pre.appendChild(content);
+  }
+
+  function buildTokenMeasure(builder, text, style, startStyle, endStyle) {
+    var wrapping = builder.cm.options.lineWrapping;
+    for (var i = 0; i < text.length; ++i) {
+      var ch = text.charAt(i), start = i == 0;
+      if (ch >= "\ud800" && ch < "\udbff" && i < text.length - 1) {
+        ch = text.slice(i, i + 2);
+        ++i;
+      } else if (i && wrapping && spanAffectsWrapping(text, i)) {
+        builder.pre.appendChild(elt("wbr"));
+      }
+      var old = builder.measure[builder.pos];
+      var span = builder.measure[builder.pos] =
+        buildToken(builder, ch, style,
+                   start && startStyle, i == text.length - 1 && endStyle);
+      if (old) span.leftSide = old.leftSide || old;
+      // In IE single-space nodes wrap differently than spaces
+      // embedded in larger text nodes, except when set to
+      // white-space: normal (issue #1268).
+      if (ie && wrapping && ch == " " && i && !/\s/.test(text.charAt(i - 1)) &&
+          i < text.length - 1 && !/\s/.test(text.charAt(i + 1)))
+        span.style.whiteSpace = "normal";
+      builder.pos += ch.length;
+    }
+    if (text.length) builder.measuredSomething = true;
+  }
+
+  function buildTokenSplitSpaces(inner) {
+    function split(old) {
+      var out = " ";
+      for (var i = 0; i < old.length - 2; ++i) out += i % 2 ? " " : "\u00a0";
+      out += " ";
+      return out;
+    }
+    return function(builder, text, style, startStyle, endStyle, title) {
+      return inner(builder, text.replace(/ {3,}/, split), style, startStyle, endStyle, title);
+    };
+  }
+
+  function buildCollapsedSpan(builder, size, marker, ignoreWidget) {
+    var widget = !ignoreWidget && marker.replacedWith;
+    if (widget) {
+      if (builder.copyWidgets) widget = widget.cloneNode(true);
+      builder.pre.appendChild(widget);
+      if (builder.measure) {
+        if (size) {
+          builder.measure[builder.pos] = widget;
+        } else {
+          var elt = zeroWidthElement(builder.cm.display.measure);
+          if (marker.type == "bookmark" && !marker.insertLeft)
+            builder.measure[builder.pos] = builder.pre.appendChild(elt);
+          else if (builder.measure[builder.pos])
+            return;
+          else
+            builder.measure[builder.pos] = builder.pre.insertBefore(elt, widget);
+        }
+        builder.measuredSomething = true;
+      }
+    }
+    builder.pos += size;
+  }
+
+  // Outputs a number of spans to make up a line, taking highlighting
+  // and marked text into account.
+  function insertLineContent(line, builder, styles) {
+    var spans = line.markedSpans, allText = line.text, at = 0;
+    if (!spans) {
+      for (var i = 1; i < styles.length; i+=2)
+        builder.addToken(builder, allText.slice(at, at = styles[i]), styleToClass(styles[i+1]));
+      return;
+    }
+
+    var len = allText.length, pos = 0, i = 1, text = "", style;
+    var nextChange = 0, spanStyle, spanEndStyle, spanStartStyle, title, collapsed;
+    for (;;) {
+      if (nextChange == pos) { // Update current marker set
+        spanStyle = spanEndStyle = spanStartStyle = title = "";
+        collapsed = null; nextChange = Infinity;
+        var foundBookmarks = [];
+        for (var j = 0; j < spans.length; ++j) {
+          var sp = spans[j], m = sp.marker;
+          if (sp.from <= pos && (sp.to == null || sp.to > pos)) {
+            if (sp.to != null && nextChange > sp.to) { nextChange = sp.to; spanEndStyle = ""; }
+            if (m.className) spanStyle += " " + m.className;
+            if (m.startStyle && sp.from == pos) spanStartStyle += " " + m.startStyle;
+            if (m.endStyle && sp.to == nextChange) spanEndStyle += " " + m.endStyle;
+            if (m.title && !title) title = m.title;
+            if (m.collapsed && (!collapsed || collapsed.marker.size < m.size))
+              collapsed = sp;
+          } else if (sp.from > pos && nextChange > sp.from) {
+            nextChange = sp.from;
+          }
+          if (m.type == "bookmark" && sp.from == pos && m.replacedWith) foundBookmarks.push(m);
+        }
+        if (collapsed && (collapsed.from || 0) == pos) {
+          buildCollapsedSpan(builder, (collapsed.to == null ? len : collapsed.to) - pos,
+                             collapsed.marker, collapsed.from == null);
+          if (collapsed.to == null) return collapsed.marker.find();
+        }
+        if (!collapsed && foundBookmarks.length) for (var j = 0; j < foundBookmarks.length; ++j)
+          buildCollapsedSpan(builder, 0, foundBookmarks[j]);
+      }
+      if (pos >= len) break;
+
+      var upto = Math.min(len, nextChange);
+      while (true) {
+        if (text) {
+          var end = pos + text.length;
+          if (!collapsed) {
+            var tokenText = end > upto ? text.slice(0, upto - pos) : text;
+            builder.addToken(builder, tokenText, style ? style + spanStyle : spanStyle,
+                             spanStartStyle, pos + tokenText.length == nextChange ? spanEndStyle : "", title);
+          }
+          if (end >= upto) {text = text.slice(upto - pos); pos = upto; break;}
+          pos = end;
+          spanStartStyle = "";
+        }
+        text = allText.slice(at, at = styles[i++]);
+        style = styleToClass(styles[i++]);
+      }
+    }
+  }
+
+  // DOCUMENT DATA STRUCTURE
+
+  function updateDoc(doc, change, markedSpans, selAfter, estimateHeight) {
+    function spansFor(n) {return markedSpans ? markedSpans[n] : null;}
+    function update(line, text, spans) {
+      updateLine(line, text, spans, estimateHeight);
+      signalLater(line, "change", line, change);
+    }
+
+    var from = change.from, to = change.to, text = change.text;
+    var firstLine = getLine(doc, from.line), lastLine = getLine(doc, to.line);
+    var lastText = lst(text), lastSpans = spansFor(text.length - 1), nlines = to.line - from.line;
+
+    // First adjust the line structure
+    if (from.ch == 0 && to.ch == 0 && lastText == "") {
+      // This is a whole-line replace. Treated specially to make
+      // sure line objects move the way they are supposed to.
+      for (var i = 0, e = text.length - 1, added = []; i < e; ++i)
+        added.push(new Line(text[i], spansFor(i), estimateHeight));
+      update(lastLine, lastLine.text, lastSpans);
+      if (nlines) doc.remove(from.line, nlines);
+      if (added.length) doc.insert(from.line, added);
+    } else if (firstLine == lastLine) {
+      if (text.length == 1) {
+        update(firstLine, firstLine.text.slice(0, from.ch) + lastText + firstLine.text.slice(to.ch), lastSpans);
+      } else {
+        for (var added = [], i = 1, e = text.length - 1; i < e; ++i)
+          added.push(new Line(text[i], spansFor(i), estimateHeight));
+        added.push(new Line(lastText + firstLine.text.slice(to.ch), lastSpans, estimateHeight));
+        update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0));
+        doc.insert(from.line + 1, added);
+      }
+    } else if (text.length == 1) {
+      update(firstLine, firstLine.text.slice(0, from.ch) + text[0] + lastLine.text.slice(to.ch), spansFor(0));
+      doc.remove(from.line + 1, nlines);
+    } else {
+      update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0));
+      update(lastLine, lastText + lastLine.text.slice(to.ch), lastSpans);
+      for (var i = 1, e = text.length - 1, added = []; i < e; ++i)
+        added.push(new Line(text[i], spansFor(i), estimateHeight));
+      if (nlines > 1) doc.remove(from.line + 1, nlines - 1);
+      doc.insert(from.line + 1, added);
+    }
+
+    signalLater(doc, "change", doc, change);
+    setSelection(doc, selAfter.anchor, selAfter.head, null, true);
+  }
+
+  function LeafChunk(lines) {
+    this.lines = lines;
+    this.parent = null;
+    for (var i = 0, e = lines.length, height = 0; i < e; ++i) {
+      lines[i].parent = this;
+      height += lines[i].height;
+    }
+    this.height = height;
+  }
+
+  LeafChunk.prototype = {
+    chunkSize: function() { return this.lines.length; },
+    removeInner: function(at, n) {
+      for (var i = at, e = at + n; i < e; ++i) {
+        var line = this.lines[i];
+        this.height -= line.height;
+        cleanUpLine(line);
+        signalLater(line, "delete");
+      }
+      this.lines.splice(at, n);
+    },
+    collapse: function(lines) {
+      lines.splice.apply(lines, [lines.length, 0].concat(this.lines));
+    },
+    insertInner: function(at, lines, height) {
+      this.height += height;
+      this.lines = this.lines.slice(0, at).concat(lines).concat(this.lines.slice(at));
+      for (var i = 0, e = lines.length; i < e; ++i) lines[i].parent = this;
+    },
+    iterN: function(at, n, op) {
+      for (var e = at + n; at < e; ++at)
+        if (op(this.lines[at])) return true;
+    }
+  };
+
+  function BranchChunk(children) {
+    this.children = children;
+    var size = 0, height = 0;
+    for (var i = 0, e = children.length; i < e; ++i) {
+      var ch = children[i];
+      size += ch.chunkSize(); height += ch.height;
+      ch.parent = this;
+    }
+    this.size = size;
+    this.height = height;
+    this.parent = null;
+  }
+
+  BranchChunk.prototype = {
+    chunkSize: function() { return this.size; },
+    removeInner: function(at, n) {
+      this.size -= n;
+      for (var i = 0; i < this.children.length; ++i) {
+        var child = this.children[i], sz = child.chunkSize();
+        if (at < sz) {
+          var rm = Math.min(n, sz - at), oldHeight = child.height;
+          child.removeInner(at, rm);
+          this.height -= oldHeight - child.height;
+          if (sz == rm) { this.children.splice(i--, 1); child.parent = null; }
+          if ((n -= rm) == 0) break;
+          at = 0;
+        } else at -= sz;
+      }
+      if (this.size - n < 25) {
+        var lines = [];
+        this.collapse(lines);
+        this.children = [new LeafChunk(lines)];
+        this.children[0].parent = this;
+      }
+    },
+    collapse: function(lines) {
+      for (var i = 0, e = this.children.length; i < e; ++i) this.children[i].collapse(lines);
+    },
+    insertInner: function(at, lines, height) {
+      this.size += lines.length;
+      this.height += height;
+      for (var i = 0, e = this.children.length; i < e; ++i) {
+        var child = this.children[i], sz = child.chunkSize();
+        if (at <= sz) {
+          child.insertInner(at, lines, height);
+          if (child.lines && child.lines.length > 50) {
+            while (child.lines.length > 50) {
+              var spilled = child.lines.splice(child.lines.length - 25, 25);
+              var newleaf = new LeafChunk(spilled);
+              child.height -= newleaf.height;
+              this.children.splice(i + 1, 0, newleaf);
+              newleaf.parent = this;
+            }
+            this.maybeSpill();
+          }
+          break;
+        }
+        at -= sz;
+      }
+    },
+    maybeSpill: function() {
+      if (this.children.length <= 10) return;
+      var me = this;
+      do {
+        var spilled = me.children.splice(me.children.length - 5, 5);
+        var sibling = new BranchChunk(spilled);
+        if (!me.parent) { // Become the parent node
+          var copy = new BranchChunk(me.children);
+          copy.parent = me;
+          me.children = [copy, sibling];
+          me = copy;
+        } else {
+          me.size -= sibling.size;
+          me.height -= sibling.height;
+          var myIndex = indexOf(me.parent.children, me);
+          me.parent.children.splice(myIndex + 1, 0, sibling);
+        }
+        sibling.parent = me.parent;
+      } while (me.children.length > 10);
+      me.parent.maybeSpill();
+    },
+    iterN: function(at, n, op) {
+      for (var i = 0, e = this.children.length; i < e; ++i) {
+        var child = this.children[i], sz = child.chunkSize();
+        if (at < sz) {
+          var used = Math.min(n, sz - at);
+          if (child.iterN(at, used, op)) return true;
+          if ((n -= used) == 0) break;
+          at = 0;
+        } else at -= sz;
+      }
+    }
+  };
+
+  var nextDocId = 0;
+  var Doc = CodeMirror.Doc = function(text, mode, firstLine) {
+    if (!(this instanceof Doc)) return new Doc(text, mode, firstLine);
+    if (firstLine == null) firstLine = 0;
+
+    BranchChunk.call(this, [new LeafChunk([new Line("", null)])]);
+    this.first = firstLine;
+    this.scrollTop = this.scrollLeft = 0;
+    this.cantEdit = false;
+    this.history = makeHistory();
+    this.cleanGeneration = 1;
+    this.frontier = firstLine;
+    var start = Pos(firstLine, 0);
+    this.sel = {from: start, to: start, head: start, anchor: start, shift: false, extend: false, goalColumn: null};
+    this.id = ++nextDocId;
+    this.modeOption = mode;
+
+    if (typeof text == "string") text = splitLines(text);
+    updateDoc(this, {from: start, to: start, text: text}, null, {head: start, anchor: start});
+  };
+
+  Doc.prototype = createObj(BranchChunk.prototype, {
+    constructor: Doc,
+    iter: function(from, to, op) {
+      if (op) this.iterN(from - this.first, to - from, op);
+      else this.iterN(this.first, this.first + this.size, from);
+    },
+
+    insert: function(at, lines) {
+      var height = 0;
+      for (var i = 0, e = lines.length; i < e; ++i) height += lines[i].height;
+      this.insertInner(at - this.first, lines, height);
+    },
+    remove: function(at, n) { this.removeInner(at - this.first, n); },
+
+    getValue: function(lineSep) {
+      var lines = getLines(this, this.first, this.first + this.size);
+      if (lineSep === false) return lines;
+      return lines.join(lineSep || "\n");
+    },
+    setValue: function(code) {
+      var top = Pos(this.first, 0), last = this.first + this.size - 1;
+      makeChange(this, {from: top, to: Pos(last, getLine(this, last).text.length),
+                        text: splitLines(code), origin: "setValue"},
+                 {head: top, anchor: top}, true);
+    },
+    replaceRange: function(code, from, to, origin) {
+      from = clipPos(this, from);
+      to = to ? clipPos(this, to) : from;
+      replaceRange(this, code, from, to, origin);
+    },
+    getRange: function(from, to, lineSep) {
+      var lines = getBetween(this, clipPos(this, from), clipPos(this, to));
+      if (lineSep === false) return lines;
+      return lines.join(lineSep || "\n");
+    },
+
+    getLine: function(line) {var l = this.getLineHandle(line); return l && l.text;},
+    setLine: function(line, text) {
+      if (isLine(this, line))
+        replaceRange(this, text, Pos(line, 0), clipPos(this, Pos(line)));
+    },
+    removeLine: function(line) {
+      if (line) replaceRange(this, "", clipPos(this, Pos(line - 1)), clipPos(this, Pos(line)));
+      else replaceRange(this, "", Pos(0, 0), clipPos(this, Pos(1, 0)));
+    },
+
+    getLineHandle: function(line) {if (isLine(this, line)) return getLine(this, line);},
+    getLineNumber: function(line) {return lineNo(line);},
+
+    getLineHandleVisualStart: function(line) {
+      if (typeof line == "number") line = getLine(this, line);
+      return visualLine(this, line);
+    },
+
+    lineCount: function() {return this.size;},
+    firstLine: function() {return this.first;},
+    lastLine: function() {return this.first + this.size - 1;},
+
+    clipPos: function(pos) {return clipPos(this, pos);},
+
+    getCursor: function(start) {
+      var sel = this.sel, pos;
+      if (start == null || start == "head") pos = sel.head;
+      else if (start == "anchor") pos = sel.anchor;
+      else if (start == "end" || start === false) pos = sel.to;
+      else pos = sel.from;
+      return copyPos(pos);
+    },
+    somethingSelected: function() {return !posEq(this.sel.head, this.sel.anchor);},
+
+    setCursor: docOperation(function(line, ch, extend) {
+      var pos = clipPos(this, typeof line == "number" ? Pos(line, ch || 0) : line);
+      if (extend) extendSelection(this, pos);
+      else setSelection(this, pos, pos);
+    }),
+    setSelection: docOperation(function(anchor, head, bias) {
+      setSelection(this, clipPos(this, anchor), clipPos(this, head || anchor), bias);
+    }),
+    extendSelection: docOperation(function(from, to, bias) {
+      extendSelection(this, clipPos(this, from), to && clipPos(this, to), bias);
+    }),
+
+    getSelection: function(lineSep) {return this.getRange(this.sel.from, this.sel.to, lineSep);},
+    replaceSelection: function(code, collapse, origin) {
+      makeChange(this, {from: this.sel.from, to: this.sel.to, text: splitLines(code), origin: origin}, collapse || "around");
+    },
+    undo: docOperation(function() {makeChangeFromHistory(this, "undo");}),
+    redo: docOperation(function() {makeChangeFromHistory(this, "redo");}),
+
+    setExtending: function(val) {this.sel.extend = val;},
+
+    historySize: function() {
+      var hist = this.history;
+      return {undo: hist.done.length, redo: hist.undone.length};
+    },
+    clearHistory: function() {this.history = makeHistory(this.history.maxGeneration);},
+
+    markClean: function() {
+      this.cleanGeneration = this.changeGeneration();
+    },
+    changeGeneration: function() {
+      this.history.lastOp = this.history.lastOrigin = null;
+      return this.history.generation;
+    },
+    isClean: function (gen) {
+      return this.history.generation == (gen || this.cleanGeneration);
+    },
+
+    getHistory: function() {
+      return {done: copyHistoryArray(this.history.done),
+              undone: copyHistoryArray(this.history.undone)};
+    },
+    setHistory: function(histData) {
+      var hist = this.history = makeHistory(this.history.maxGeneration);
+      hist.done = histData.done.slice(0);
+      hist.undone = histData.undone.slice(0);
+    },
+
+    markText: function(from, to, options) {
+      return markText(this, clipPos(this, from), clipPos(this, to), options, "range");
+    },
+    setBookmark: function(pos, options) {
+      var realOpts = {replacedWith: options && (options.nodeType == null ? options.widget : options),
+                      insertLeft: options && options.insertLeft};
+      pos = clipPos(this, pos);
+      return markText(this, pos, pos, realOpts, "bookmark");
+    },
+    findMarksAt: function(pos) {
+      pos = clipPos(this, pos);
+      var markers = [], spans = getLine(this, pos.line).markedSpans;
+      if (spans) for (var i = 0; i < spans.length; ++i) {
+        var span = spans[i];
+        if ((span.from == null || span.from <= pos.ch) &&
+            (span.to == null || span.to >= pos.ch))
+          markers.push(span.marker.parent || span.marker);
+      }
+      return markers;
+    },
+    getAllMarks: function() {
+      var markers = [];
+      this.iter(function(line) {
+        var sps = line.markedSpans;
+        if (sps) for (var i = 0; i < sps.length; ++i)
+          if (sps[i].from != null) markers.push(sps[i].marker);
+      });
+      return markers;
+    },
+
+    posFromIndex: function(off) {
+      var ch, lineNo = this.first;
+      this.iter(function(line) {
+        var sz = line.text.length + 1;
+        if (sz > off) { ch = off; return true; }
+        off -= sz;
+        ++lineNo;
+      });
+      return clipPos(this, Pos(lineNo, ch));
+    },
+    indexFromPos: function (coords) {
+      coords = clipPos(this, coords);
+      var index = coords.ch;
+      if (coords.line < this.first || coords.ch < 0) return 0;
+      this.iter(this.first, coords.line, function (line) {
+        index += line.text.length + 1;
+      });
+      return index;
+    },
+
+    copy: function(copyHistory) {
+      var doc = new Doc(getLines(this, this.first, this.first + this.size), this.modeOption, this.first);
+      doc.scrollTop = this.scrollTop; doc.scrollLeft = this.scrollLeft;
+      doc.sel = {from: this.sel.from, to: this.sel.to, head: this.sel.head, anchor: this.sel.anchor,
+                 shift: this.sel.shift, extend: false, goalColumn: this.sel.goalColumn};
+      if (copyHistory) {
+        doc.history.undoDepth = this.history.undoDepth;
+        doc.setHistory(this.getHistory());
+      }
+      return doc;
+    },
+
+    linkedDoc: function(options) {
+      if (!options) options = {};
+      var from = this.first, to = this.first + this.size;
+      if (options.from != null && options.from > from) from = options.from;
+      if (options.to != null && options.to < to) to = options.to;
+      var copy = new Doc(getLines(this, from, to), options.mode || this.modeOption, from);
+      if (options.sharedHist) copy.history = this.history;
+      (this.linked || (this.linked = [])).push({doc: copy, sharedHist: options.sharedHist});
+      copy.linked = [{doc: this, isParent: true, sharedHist: options.sharedHist}];
+      return copy;
+    },
+    unlinkDoc: function(other) {
+      if (other instanceof CodeMirror) other = other.doc;
+      if (this.linked) for (var i = 0; i < this.linked.length; ++i) {
+        var link = this.linked[i];
+        if (link.doc != other) continue;
+        this.linked.splice(i, 1);
+        other.unlinkDoc(this);
+        break;
+      }
+      // If the histories were shared, split them again
+      if (other.history == this.history) {
+        var splitIds = [other.id];
+        linkedDocs(other, function(doc) {splitIds.push(doc.id);}, true);
+        other.history = makeHistory();
+        other.history.done = copyHistoryArray(this.history.done, splitIds);
+        other.history.undone = copyHistoryArray(this.history.undone, splitIds);
+      }
+    },
+    iterLinkedDocs: function(f) {linkedDocs(this, f);},
+
+    getMode: function() {return this.mode;},
+    getEditor: function() {return this.cm;}
+  });
+
+  Doc.prototype.eachLine = Doc.prototype.iter;
+
+  // The Doc methods that should be available on CodeMirror instances
+  var dontDelegate = "iter insert remove copy getEditor".split(" ");
+  for (var prop in Doc.prototype) if (Doc.prototype.hasOwnProperty(prop) && indexOf(dontDelegate, prop) < 0)
+    CodeMirror.prototype[prop] = (function(method) {
+      return function() {return method.apply(this.doc, arguments);};
+    })(Doc.prototype[prop]);
+
+  eventMixin(Doc);
+
+  function linkedDocs(doc, f, sharedHistOnly) {
+    function propagate(doc, skip, sharedHist) {
+      if (doc.linked) for (var i = 0; i < doc.linked.length; ++i) {
+        var rel = doc.linked[i];
+        if (rel.doc == skip) continue;
+        var shared = sharedHist && rel.sharedHist;
+        if (sharedHistOnly && !shared) continue;
+        f(rel.doc, shared);
+        propagate(rel.doc, doc, shared);
+      }
+    }
+    propagate(doc, null, true);
+  }
+
+  function attachDoc(cm, doc) {
+    if (doc.cm) throw new Error("This document is already in use.");
+    cm.doc = doc;
+    doc.cm = cm;
+    estimateLineHeights(cm);
+    loadMode(cm);
+    if (!cm.options.lineWrapping) computeMaxLength(cm);
+    cm.options.mode = doc.modeOption;
+    regChange(cm);
+  }
+
+  // LINE UTILITIES
+
+  function getLine(chunk, n) {
+    n -= chunk.first;
+    while (!chunk.lines) {
+      for (var i = 0;; ++i) {
+        var child = chunk.children[i], sz = child.chunkSize();
+        if (n < sz) { chunk = child; break; }
+        n -= sz;
+      }
+    }
+    return chunk.lines[n];
+  }
+
+  function getBetween(doc, start, end) {
+    var out = [], n = start.line;
+    doc.iter(start.line, end.line + 1, function(line) {
+      var text = line.text;
+      if (n == end.line) text = text.slice(0, end.ch);
+      if (n == start.line) text = text.slice(start.ch);
+      out.push(text);
+      ++n;
+    });
+    return out;
+  }
+  function getLines(doc, from, to) {
+    var out = [];
+    doc.iter(from, to, function(line) { out.push(line.text); });
+    return out;
+  }
+
+  function updateLineHeight(line, height) {
+    var diff = height - line.height;
+    for (var n = line; n; n = n.parent) n.height += diff;
+  }
+
+  function lineNo(line) {
+    if (line.parent == null) return null;
+    var cur = line.parent, no = indexOf(cur.lines, line);
+    for (var chunk = cur.parent; chunk; cur = chunk, chunk = chunk.parent) {
+      for (var i = 0;; ++i) {
+        if (chunk.children[i] == cur) break;
+        no += chunk.children[i].chunkSize();
+      }
+    }
+    return no + cur.first;
+  }
+
+  function lineAtHeight(chunk, h) {
+    var n = chunk.first;
+    outer: do {
+      for (var i = 0, e = chunk.children.length; i < e; ++i) {
+        var child = chunk.children[i], ch = child.height;
+        if (h < ch) { chunk = child; continue outer; }
+        h -= ch;
+        n += child.chunkSize();
+      }
+      return n;
+    } while (!chunk.lines);
+    for (var i = 0, e = chunk.lines.length; i < e; ++i) {
+      var line = chunk.lines[i], lh = line.height;
+      if (h < lh) break;
+      h -= lh;
+    }
+    return n + i;
+  }
+
+  function heightAtLine(cm, lineObj) {
+    lineObj = visualLine(cm.doc, lineObj);
+
+    var h = 0, chunk = lineObj.parent;
+    for (var i = 0; i < chunk.lines.length; ++i) {
+      var line = chunk.lines[i];
+      if (line == lineObj) break;
+      else h += line.height;
+    }
+    for (var p = chunk.parent; p; chunk = p, p = chunk.parent) {
+      for (var i = 0; i < p.children.length; ++i) {
+        var cur = p.children[i];
+        if (cur == chunk) break;
+        else h += cur.height;
+      }
+    }
+    return h;
+  }
+
+  function getOrder(line) {
+    var order = line.order;
+    if (order == null) order = line.order = bidiOrdering(line.text);
+    return order;
+  }
+
+  // HISTORY
+
+  function makeHistory(startGen) {
+    return {
+      // Arrays of history events. Doing something adds an event to
+      // done and clears undo. Undoing moves events from done to
+      // undone, redoing moves them in the other direction.
+      done: [], undone: [], undoDepth: Infinity,
+      // Used to track when changes can be merged into a single undo
+      // event
+      lastTime: 0, lastOp: null, lastOrigin: null,
+      // Used by the isClean() method
+      generation: startGen || 1, maxGeneration: startGen || 1
+    };
+  }
+
+  function attachLocalSpans(doc, change, from, to) {
+    var existing = change["spans_" + doc.id], n = 0;
+    doc.iter(Math.max(doc.first, from), Math.min(doc.first + doc.size, to), function(line) {
+      if (line.markedSpans)
+        (existing || (existing = change["spans_" + doc.id] = {}))[n] = line.markedSpans;
+      ++n;
+    });
+  }
+
+  function historyChangeFromChange(doc, change) {
+    var from = { line: change.from.line, ch: change.from.ch };
+    var histChange = {from: from, to: changeEnd(change), text: getBetween(doc, change.from, change.to)};
+    attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1);
+    linkedDocs(doc, function(doc) {attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1);}, true);
+    return histChange;
+  }
+
+  function addToHistory(doc, change, selAfter, opId) {
+    var hist = doc.history;
+    hist.undone.length = 0;
+    var time = +new Date, cur = lst(hist.done);
+
+    if (cur &&
+        (hist.lastOp == opId ||
+         hist.lastOrigin == change.origin && change.origin &&
+         ((change.origin.charAt(0) == "+" && doc.cm && hist.lastTime > time - doc.cm.options.historyEventDelay) ||
+          change.origin.charAt(0) == "*"))) {
+      // Merge this change into the last event
+      var last = lst(cur.changes);
+      if (posEq(change.from, change.to) && posEq(change.from, last.to)) {
+        // Optimized case for simple insertion -- don't want to add
+        // new changesets for every character typed
+        last.to = changeEnd(change);
+      } else {
+        // Add new sub-event
+        cur.changes.push(historyChangeFromChange(doc, change));
+      }
+      cur.anchorAfter = selAfter.anchor; cur.headAfter = selAfter.head;
+    } else {
+      // Can not be merged, start a new event.
+      cur = {changes: [historyChangeFromChange(doc, change)],
+             generation: hist.generation,
+             anchorBefore: doc.sel.anchor, headBefore: doc.sel.head,
+             anchorAfter: selAfter.anchor, headAfter: selAfter.head};
+      hist.done.push(cur);
+      hist.generation = ++hist.maxGeneration;
+      while (hist.done.length > hist.undoDepth)
+        hist.done.shift();
+    }
+    hist.lastTime = time;
+    hist.lastOp = opId;
+    hist.lastOrigin = change.origin;
+  }
+
+  function removeClearedSpans(spans) {
+    if (!spans) return null;
+    for (var i = 0, out; i < spans.length; ++i) {
+      if (spans[i].marker.explicitlyCleared) { if (!out) out = spans.slice(0, i); }
+      else if (out) out.push(spans[i]);
+    }
+    return !out ? spans : out.length ? out : null;
+  }
+
+  function getOldSpans(doc, change) {
+    var found = change["spans_" + doc.id];
+    if (!found) return null;
+    for (var i = 0, nw = []; i < change.text.length; ++i)
+      nw.push(removeClearedSpans(found[i]));
+    return nw;
+  }
+
+  // Used both to provide a JSON-safe object in .getHistory, and, when
+  // detaching a document, to split the history in two
+  function copyHistoryArray(events, newGroup) {
+    for (var i = 0, copy = []; i < events.length; ++i) {
+      var event = events[i], changes = event.changes, newChanges = [];
+      copy.push({changes: newChanges, anchorBefore: event.anchorBefore, headBefore: event.headBefore,
+                 anchorAfter: event.anchorAfter, headAfter: event.headAfter});
+      for (var j = 0; j < changes.length; ++j) {
+        var change = changes[j], m;
+        newChanges.push({from: change.from, to: change.to, text: change.text});
+        if (newGroup) for (var prop in change) if (m = prop.match(/^spans_(\d+)$/)) {
+          if (indexOf(newGroup, Number(m[1])) > -1) {
+            lst(newChanges)[prop] = change[prop];
+            delete change[prop];
+          }
+        }
+      }
+    }
+    return copy;
+  }
+
+  // Rebasing/resetting history to deal with externally-sourced changes
+
+  function rebaseHistSel(pos, from, to, diff) {
+    if (to < pos.line) {
+      pos.line += diff;
+    } else if (from < pos.line) {
+      pos.line = from;
+      pos.ch = 0;
+    }
+  }
+
+  // Tries to rebase an array of history events given a change in the
+  // document. If the change touches the same lines as the event, the
+  // event, and everything 'behind' it, is discarded. If the change is
+  // before the event, the event's positions are updated. Uses a
+  // copy-on-write scheme for the positions, to avoid having to
+  // reallocate them all on every rebase, but also avoid problems with
+  // shared position objects being unsafely updated.
+  function rebaseHistArray(array, from, to, diff) {
+    for (var i = 0; i < array.length; ++i) {
+      var sub = array[i], ok = true;
+      for (var j = 0; j < sub.changes.length; ++j) {
+        var cur = sub.changes[j];
+        if (!sub.copied) { cur.from = copyPos(cur.from); cur.to = copyPos(cur.to); }
+        if (to < cur.from.line) {
+          cur.from.line += diff;
+          cur.to.line += diff;
+        } else if (from <= cur.to.line) {
+          ok = false;
+          break;
+        }
+      }
+      if (!sub.copied) {
+        sub.anchorBefore = copyPos(sub.anchorBefore); sub.headBefore = copyPos(sub.headBefore);
+        sub.anchorAfter = copyPos(sub.anchorAfter); sub.readAfter = copyPos(sub.headAfter);
+        sub.copied = true;
+      }
+      if (!ok) {
+        array.splice(0, i + 1);
+        i = 0;
+      } else {
+        rebaseHistSel(sub.anchorBefore); rebaseHistSel(sub.headBefore);
+        rebaseHistSel(sub.anchorAfter); rebaseHistSel(sub.headAfter);
+      }
+    }
+  }
+
+  function rebaseHist(hist, change) {
+    var from = change.from.line, to = change.to.line, diff = change.text.length - (to - from) - 1;
+    rebaseHistArray(hist.done, from, to, diff);
+    rebaseHistArray(hist.undone, from, to, diff);
+  }
+
+  // EVENT OPERATORS
+
+  function stopMethod() {e_stop(this);}
+  // Ensure an event has a stop method.
+  function addStop(event) {
+    if (!event.stop) event.stop = stopMethod;
+    return event;
+  }
+
+  function e_preventDefault(e) {
+    if (e.preventDefault) e.preventDefault();
+    else e.returnValue = false;
+  }
+  function e_stopPropagation(e) {
+    if (e.stopPropagation) e.stopPropagation();
+    else e.cancelBubble = true;
+  }
+  function e_defaultPrevented(e) {
+    return e.defaultPrevented != null ? e.defaultPrevented : e.returnValue == false;
+  }
+  function e_stop(e) {e_preventDefault(e); e_stopPropagation(e);}
+  CodeMirror.e_stop = e_stop;
+  CodeMirror.e_preventDefault = e_preventDefault;
+  CodeMirror.e_stopPropagation = e_stopPropagation;
+
+  function e_target(e) {return e.target || e.srcElement;}
+  function e_button(e) {
+    var b = e.which;
+    if (b == null) {
+      if (e.button & 1) b = 1;
+      else if (e.button & 2) b = 3;
+      else if (e.button & 4) b = 2;
+    }
+    if (mac && e.ctrlKey && b == 1) b = 3;
+    return b;
+  }
+
+  // EVENT HANDLING
+
+  function on(emitter, type, f) {
+    if (emitter.addEventListener)
+      emitter.addEventListener(type, f, false);
+    else if (emitter.attachEvent)
+      emitter.attachEvent("on" + type, f);
+    else {
+      var map = emitter._handlers || (emitter._handlers = {});
+      var arr = map[type] || (map[type] = []);
+      arr.push(f);
+    }
+  }
+
+  function off(emitter, type, f) {
+    if (emitter.removeEventListener)
+      emitter.removeEventListener(type, f, false);
+    else if (emitter.detachEvent)
+      emitter.detachEvent("on" + type, f);
+    else {
+      var arr = emitter._handlers && emitter._handlers[type];
+      if (!arr) return;
+      for (var i = 0; i < arr.length; ++i)
+        if (arr[i] == f) { arr.splice(i, 1); break; }
+    }
+  }
+
+  function signal(emitter, type /*, values...*/) {
+    var arr = emitter._handlers && emitter._handlers[type];
+    if (!arr) return;
+    var args = Array.prototype.slice.call(arguments, 2);
+    for (var i = 0; i < arr.length; ++i) arr[i].apply(null, args);
+  }
+
+  var delayedCallbacks, delayedCallbackDepth = 0;
+  function signalLater(emitter, type /*, values...*/) {
+    var arr = emitter._handlers && emitter._handlers[type];
+    if (!arr) return;
+    var args = Array.prototype.slice.call(arguments, 2);
+    if (!delayedCallbacks) {
+      ++delayedCallbackDepth;
+      delayedCallbacks = [];
+      setTimeout(fireDelayed, 0);
+    }
+    function bnd(f) {return function(){f.apply(null, args);};};
+    for (var i = 0; i < arr.length; ++i)
+      delayedCallbacks.push(bnd(arr[i]));
+  }
+
+  function signalDOMEvent(cm, e, override) {
+    signal(cm, override || e.type, cm, e);
+    return e_defaultPrevented(e) || e.codemirrorIgnore;
+  }
+
+  function fireDelayed() {
+    --delayedCallbackDepth;
+    var delayed = delayedCallbacks;
+    delayedCallbacks = null;
+    for (var i = 0; i < delayed.length; ++i) delayed[i]();
+  }
+
+  function hasHandler(emitter, type) {
+    var arr = emitter._handlers && emitter._handlers[type];
+    return arr && arr.length > 0;
+  }
+
+  CodeMirror.on = on; CodeMirror.off = off; CodeMirror.signal = signal;
+
+  function eventMixin(ctor) {
+    ctor.prototype.on = function(type, f) {on(this, type, f);};
+    ctor.prototype.off = function(type, f) {off(this, type, f);};
+  }
+
+  // MISC UTILITIES
+
+  // Number of pixels added to scroller and sizer to hide scrollbar
+  var scrollerCutOff = 30;
+
+  // Returned or thrown by various protocols to signal 'I'm not
+  // handling this'.
+  var Pass = CodeMirror.Pass = {toString: function(){return "CodeMirror.Pass";}};
+
+  function Delayed() {this.id = null;}
+  Delayed.prototype = {set: function(ms, f) {clearTimeout(this.id); this.id = setTimeout(f, ms);}};
+
+  // Counts the column offset in a string, taking tabs into account.
+  // Used mostly to find indentation.
+  function countColumn(string, end, tabSize, startIndex, startValue) {
+    if (end == null) {
+      end = string.search(/[^\s\u00a0]/);
+      if (end == -1) end = string.length;
+    }
+    for (var i = startIndex || 0, n = startValue || 0; i < end; ++i) {
+      if (string.charAt(i) == "\t") n += tabSize - (n % tabSize);
+      else ++n;
+    }
+    return n;
+  }
+  CodeMirror.countColumn = countColumn;
+
+  var spaceStrs = [""];
+  function spaceStr(n) {
+    while (spaceStrs.length <= n)
+      spaceStrs.push(lst(spaceStrs) + " ");
+    return spaceStrs[n];
+  }
+
+  function lst(arr) { return arr[arr.length-1]; }
+
+  function selectInput(node) {
+    if (ios) { // Mobile Safari apparently has a bug where select() is broken.
+      node.selectionStart = 0;
+      node.selectionEnd = node.value.length;
+    } else {
+      // Suppress mysterious IE10 errors
+      try { node.select(); }
+      catch(_e) {}
+    }
+  }
+
+  function indexOf(collection, elt) {
+    if (collection.indexOf) return collection.indexOf(elt);
+    for (var i = 0, e = collection.length; i < e; ++i)
+      if (collection[i] == elt) return i;
+    return -1;
+  }
+
+  function createObj(base, props) {
+    function Obj() {}
+    Obj.prototype = base;
+    var inst = new Obj();
+    if (props) copyObj(props, inst);
+    return inst;
+  }
+
+  function copyObj(obj, target) {
+    if (!target) target = {};
+    for (var prop in obj) if (obj.hasOwnProperty(prop)) target[prop] = obj[prop];
+    return target;
+  }
+
+  function emptyArray(size) {
+    for (var a = [], i = 0; i < size; ++i) a.push(undefined);
+    return a;
+  }
+
+  function bind(f) {
+    var args = Array.prototype.slice.call(arguments, 1);
+    return function(){return f.apply(null, args);};
+  }
+
+  var nonASCIISingleCaseWordChar = /[\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/;
+  function isWordChar(ch) {
+    return /\w/.test(ch) || ch > "\x80" &&
+      (ch.toUpperCase() != ch.toLowerCase() || nonASCIISingleCaseWordChar.test(ch));
+  }
+
+  function isEmpty(obj) {
+    for (var n in obj) if (obj.hasOwnProperty(n) && obj[n]) return false;
+    return true;
+  }
+
+  var isExtendingChar = /[\u0300-\u036F\u0483-\u0487\u0488-\u0489\u0591-\u05BD\u05BF\u05C1-\u05C2\u05C4-\u05C5\u05C7\u0610-\u061A\u064B-\u065F\u0670\u06D6-\u06DC\u06DF-\u06E4\u06E7-\u06E8\u06EA-\u06ED\uA66F\uA670-\uA672\uA674-\uA67D\uA69F\udc00-\udfff]/;
+
+  // DOM UTILITIES
+
+  function elt(tag, content, className, style) {
+    var e = document.createElement(tag);
+    if (className) e.className = className;
+    if (style) e.style.cssText = style;
+    if (typeof content == "string") setTextContent(e, content);
+    else if (content) for (var i = 0; i < content.length; ++i) e.appendChild(content[i]);
+    return e;
+  }
+
+  function removeChildren(e) {
+    for (var count = e.childNodes.length; count > 0; --count)
+      e.removeChild(e.firstChild);
+    return e;
+  }
+
+  function removeChildrenAndAdd(parent, e) {
+    return removeChildren(parent).appendChild(e);
+  }
+
+  function setTextContent(e, str) {
+    if (ie_lt9) {
+      e.innerHTML = "";
+      e.appendChild(document.createTextNode(str));
+    } else e.textContent = str;
+  }
+
+  function getRect(node) {
+    return node.getBoundingClientRect();
+  }
+  CodeMirror.replaceGetRect = function(f) { getRect = f; };
+
+  // FEATURE DETECTION
+
+  // Detect drag-and-drop
+  var dragAndDrop = function() {
+    // There is *some* kind of drag-and-drop support in IE6-8, but I
+    // couldn't get it to work yet.
+    if (ie_lt9) return false;
+    var div = elt('div');
+    return "draggable" in div || "dragDrop" in div;
+  }();
+
+  // For a reason I have yet to figure out, some browsers disallow
+  // word wrapping between certain characters *only* if a new inline
+  // element is started between them. This makes it hard to reliably
+  // measure the position of things, since that requires inserting an
+  // extra span. This terribly fragile set of tests matches the
+  // character combinations that suffer from this phenomenon on the
+  // various browsers.
+  function spanAffectsWrapping() { return false; }
+  if (gecko) // Only for "$'"
+    spanAffectsWrapping = function(str, i) {
+      return str.charCodeAt(i - 1) == 36 && str.charCodeAt(i) == 39;
+    };
+  else if (safari && !/Version\/([6-9]|\d\d)\b/.test(navigator.userAgent))
+    spanAffectsWrapping = function(str, i) {
+      return /\-[^ \-?]|\?[^ !\'\"\),.\-\/:;\?\]\}]/.test(str.slice(i - 1, i + 1));
+    };
+  else if (webkit && /Chrome\/(?:29|[3-9]\d|\d\d\d)\./.test(navigator.userAgent))
+    spanAffectsWrapping = function(str, i) {
+      var code = str.charCodeAt(i - 1);
+      return code >= 8208 && code <= 8212;
+    };
+  else if (webkit)
+    spanAffectsWrapping = function(str, i) {
+      if (i > 1 && str.charCodeAt(i - 1) == 45) {
+        if (/\w/.test(str.charAt(i - 2)) && /[^\-?\.]/.test(str.charAt(i))) return true;
+        if (i > 2 && /[\d\.,]/.test(str.charAt(i - 2)) && /[\d\.,]/.test(str.charAt(i))) return false;
+      }
+      return /[~!#%&*)=+}\]|\"\.>,:;][({[<]|-[^\-?\.\u2010-\u201f\u2026]|\?[\w~`@#$%\^&*(_=+{[|><]|…[\w~`@#$%\^&*(_=+{[><]/.test(str.slice(i - 1, i + 1));
+    };
+
+  var knownScrollbarWidth;
+  function scrollbarWidth(measure) {
+    if (knownScrollbarWidth != null) return knownScrollbarWidth;
+    var test = elt("div", null, null, "width: 50px; height: 50px; overflow-x: scroll");
+    removeChildrenAndAdd(measure, test);
+    if (test.offsetWidth)
+      knownScrollbarWidth = test.offsetHeight - test.clientHeight;
+    return knownScrollbarWidth || 0;
+  }
+
+  var zwspSupported;
+  function zeroWidthElement(measure) {
+    if (zwspSupported == null) {
+      var test = elt("span", "\u200b");
+      removeChildrenAndAdd(measure, elt("span", [test, document.createTextNode("x")]));
+      if (measure.firstChild.offsetHeight != 0)
+        zwspSupported = test.offsetWidth <= 1 && test.offsetHeight > 2 && !ie_lt8;
+    }
+    if (zwspSupported) return elt("span", "\u200b");
+    else return elt("span", "\u00a0", null, "display: inline-block; width: 1px; margin-right: -1px");
+  }
+
+  // See if "".split is the broken IE version, if so, provide an
+  // alternative way to split lines.
+  var splitLines = "\n\nb".split(/\n/).length != 3 ? function(string) {
+    var pos = 0, result = [], l = string.length;
+    while (pos <= l) {
+      var nl = string.indexOf("\n", pos);
+      if (nl == -1) nl = string.length;
+      var line = string.slice(pos, string.charAt(nl - 1) == "\r" ? nl - 1 : nl);
+      var rt = line.indexOf("\r");
+      if (rt != -1) {
+        result.push(line.slice(0, rt));
+        pos += rt + 1;
+      } else {
+        result.push(line);
+        pos = nl + 1;
+      }
+    }
+    return result;
+  } : function(string){return string.split(/\r\n?|\n/);};
+  CodeMirror.splitLines = splitLines;
+
+  var hasSelection = window.getSelection ? function(te) {
+    try { return te.selectionStart != te.selectionEnd; }
+    catch(e) { return false; }
+  } : function(te) {
+    try {var range = te.ownerDocument.selection.createRange();}
+    catch(e) {}
+    if (!range || range.parentElement() != te) return false;
+    return range.compareEndPoints("StartToEnd", range) != 0;
+  };
+
+  var hasCopyEvent = (function() {
+    var e = elt("div");
+    if ("oncopy" in e) return true;
+    e.setAttribute("oncopy", "return;");
+    return typeof e.oncopy == 'function';
+  })();
+
+  // KEY NAMING
+
+  var keyNames = {3: "Enter", 8: "Backspace", 9: "Tab", 13: "Enter", 16: "Shift", 17: "Ctrl", 18: "Alt",
+                  19: "Pause", 20: "CapsLock", 27: "Esc", 32: "Space", 33: "PageUp", 34: "PageDown", 35: "End",
+                  36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "PrintScrn", 45: "Insert",
+                  46: "Delete", 59: ";", 91: "Mod", 92: "Mod", 93: "Mod", 109: "-", 107: "=", 127: "Delete",
+                  186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\",
+                  221: "]", 222: "'", 63276: "PageUp", 63277: "PageDown", 63275: "End", 63273: "Home",
+                  63234: "Left", 63232: "Up", 63235: "Right", 63233: "Down", 63302: "Insert", 63272: "Delete"};
+  CodeMirror.keyNames = keyNames;
+  (function() {
+    // Number keys
+    for (var i = 0; i < 10; i++) keyNames[i + 48] = String(i);
+    // Alphabetic keys
+    for (var i = 65; i <= 90; i++) keyNames[i] = String.fromCharCode(i);
+    // Function keys
+    for (var i = 1; i <= 12; i++) keyNames[i + 111] = keyNames[i + 63235] = "F" + i;
+  })();
+
+  // BIDI HELPERS
+
+  function iterateBidiSections(order, from, to, f) {
+    if (!order) return f(from, to, "ltr");
+    var found = false;
+    for (var i = 0; i < order.length; ++i) {
+      var part = order[i];
+      if (part.from < to && part.to > from || from == to && part.to == from) {
+        f(Math.max(part.from, from), Math.min(part.to, to), part.level == 1 ? "rtl" : "ltr");
+        found = true;
+      }
+    }
+    if (!found) f(from, to, "ltr");
+  }
+
+  function bidiLeft(part) { return part.level % 2 ? part.to : part.from; }
+  function bidiRight(part) { return part.level % 2 ? part.from : part.to; }
+
+  function lineLeft(line) { var order = getOrder(line); return order ? bidiLeft(order[0]) : 0; }
+  function lineRight(line) {
+    var order = getOrder(line);
+    if (!order) return line.text.length;
+    return bidiRight(lst(order));
+  }
+
+  function lineStart(cm, lineN) {
+    var line = getLine(cm.doc, lineN);
+    var visual = visualLine(cm.doc, line);
+    if (visual != line) lineN = lineNo(visual);
+    var order = getOrder(visual);
+    var ch = !order ? 0 : order[0].level % 2 ? lineRight(visual) : lineLeft(visual);
+    return Pos(lineN, ch);
+  }
+  function lineEnd(cm, lineN) {
+    var merged, line;
+    while (merged = collapsedSpanAtEnd(line = getLine(cm.doc, lineN)))
+      lineN = merged.find().to.line;
+    var order = getOrder(line);
+    var ch = !order ? line.text.length : order[0].level % 2 ? lineLeft(line) : lineRight(line);
+    return Pos(lineN, ch);
+  }
+
+  function compareBidiLevel(order, a, b) {
+    var linedir = order[0].level;
+    if (a == linedir) return true;
+    if (b == linedir) return false;
+    return a < b;
+  }
+  var bidiOther;
+  function getBidiPartAt(order, pos) {
+    for (var i = 0, found; i < order.length; ++i) {
+      var cur = order[i];
+      if (cur.from < pos && cur.to > pos) { bidiOther = null; return i; }
+      if (cur.from == pos || cur.to == pos) {
+        if (found == null) {
+          found = i;
+        } else if (compareBidiLevel(order, cur.level, order[found].level)) {
+          bidiOther = found;
+          return i;
+        } else {
+          bidiOther = i;
+          return found;
+        }
+      }
+    }
+    bidiOther = null;
+    return found;
+  }
+
+  function moveInLine(line, pos, dir, byUnit) {
+    if (!byUnit) return pos + dir;
+    do pos += dir;
+    while (pos > 0 && isExtendingChar.test(line.text.charAt(pos)));
+    return pos;
+  }
+
+  // This is somewhat involved. It is needed in order to move
+  // 'visually' through bi-directional text -- i.e., pressing left
+  // should make the cursor go left, even when in RTL text. The
+  // tricky part is the 'jumps', where RTL and LTR text touch each
+  // other. This often requires the cursor offset to move more than
+  // one unit, in order to visually move one unit.
+  function moveVisually(line, start, dir, byUnit) {
+    var bidi = getOrder(line);
+    if (!bidi) return moveLogically(line, start, dir, byUnit);
+    var pos = getBidiPartAt(bidi, start), part = bidi[pos];
+    var target = moveInLine(line, start, part.level % 2 ? -dir : dir, byUnit);
+
+    for (;;) {
+      if (target > part.from && target < part.to) return target;
+      if (target == part.from || target == part.to) {
+        if (getBidiPartAt(bidi, target) == pos) return target;
+        part = bidi[pos += dir];
+        return (dir > 0) == part.level % 2 ? part.to : part.from;
+      } else {
+        part = bidi[pos += dir];
+        if (!part) return null;
+        if ((dir > 0) == part.level % 2)
+          target = moveInLine(line, part.to, -1, byUnit);
+        else
+          target = moveInLine(line, part.from, 1, byUnit);
+      }
+    }
+  }
+
+  function moveLogically(line, start, dir, byUnit) {
+    var target = start + dir;
+    if (byUnit) while (target > 0 && isExtendingChar.test(line.text.charAt(target))) target += dir;
+    return target < 0 || target > line.text.length ? null : target;
+  }
+
+  // Bidirectional ordering algorithm
+  // See http://unicode.org/reports/tr9/tr9-13.html for the algorithm
+  // that this (partially) implements.
+
+  // One-char codes used for character types:
+  // L (L):   Left-to-Right
+  // R (R):   Right-to-Left
+  // r (AL):  Right-to-Left Arabic
+  // 1 (EN):  European Number
+  // + (ES):  European Number Separator
+  // % (ET):  European Number Terminator
+  // n (AN):  Arabic Number
+  // , (CS):  Common Number Separator
+  // m (NSM): Non-Spacing Mark
+  // b (BN):  Boundary Neutral
+  // s (B):   Paragraph Separator
+  // t (S):   Segment Separator
+  // w (WS):  Whitespace
+  // N (ON):  Other Neutrals
+
+  // Returns null if characters are ordered as they appear
+  // (left-to-right), or an array of sections ({from, to, level}
+  // objects) in the order in which they occur visually.
+  var bidiOrdering = (function() {
+    // Character types for codepoints 0 to 0xff
+    var lowTypes = "bbbbbbbbbtstwsbbbbbbbbbbbbbbssstwNN%%%NNNNNN,N,N1111111111NNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNbbbbbbsbbbbbbbbbbbbbbbbbbbbbbbbbb,N%%%%NNNNLNNNNN%%11NLNNN1LNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLL";
+    // Character types for codepoints 0x600 to 0x6ff
+    var arabicTypes = "rrrrrrrrrrrr,rNNmmmmmmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmrrrrrrrnnnnnnnnnn%nnrrrmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmmmmmmNmmmmrrrrrrrrrrrrrrrrrr";
+    function charType(code) {
+      if (code <= 0xff) return lowTypes.charAt(code);
+      else if (0x590 <= code && code <= 0x5f4) return "R";
+      else if (0x600 <= code && code <= 0x6ff) return arabicTypes.charAt(code - 0x600);
+      else if (0x700 <= code && code <= 0x8ac) return "r";
+      else return "L";
+    }
+
+    var bidiRE = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/;
+    var isNeutral = /[stwN]/, isStrong = /[LRr]/, countsAsLeft = /[Lb1n]/, countsAsNum = /[1n]/;
+    // Browsers seem to always treat the boundaries of block elements as being L.
+    var outerType = "L";
+
+    return function(str) {
+      if (!bidiRE.test(str)) return false;
+      var len = str.length, types = [];
+      for (var i = 0, type; i < len; ++i)
+        types.push(type = charType(str.charCodeAt(i)));
+
+      // W1. Examine each non-spacing mark (NSM) in the level run, and
+      // change the type of the NSM to the type of the previous
+      // character. If the NSM is at the start of the level run, it will
+      // get the type of sor.
+      for (var i = 0, prev = outerType; i < len; ++i) {
+        var type = types[i];
+        if (type == "m") types[i] = prev;
+        else prev = type;
+      }
+
+      // W2. Search backwards from each instance of a European number
+      // until the first strong type (R, L, AL, or sor) is found. If an
+      // AL is found, change the type of the European number to Arabic
+      // number.
+      // W3. Change all ALs to R.
+      for (var i = 0, cur = outerType; i < len; ++i) {
+        var type = types[i];
+        if (type == "1" && cur == "r") types[i] = "n";
+        else if (isStrong.test(type)) { cur = type; if (type == "r") types[i] = "R"; }
+      }
+
+      // W4. A single European separator between two European numbers
+      // changes to a European number. A single common separator between
+      // two numbers of the same type changes to that type.
+      for (var i = 1, prev = types[0]; i < len - 1; ++i) {
+        var type = types[i];
+        if (type == "+" && prev == "1" && types[i+1] == "1") types[i] = "1";
+        else if (type == "," && prev == types[i+1] &&
+                 (prev == "1" || prev == "n")) types[i] = prev;
+        prev = type;
+      }
+
+      // W5. A sequence of European terminators adjacent to European
+      // numbers changes to all European numbers.
+      // W6. Otherwise, separators and terminators change to Other
+      // Neutral.
+      for (var i = 0; i < len; ++i) {
+        var type = types[i];
+        if (type == ",") types[i] = "N";
+        else if (type == "%") {
+          for (var end = i + 1; end < len && types[end] == "%"; ++end) {}
+          var replace = (i && types[i-1] == "!") || (end < len - 1 && types[end] == "1") ? "1" : "N";
+          for (var j = i; j < end; ++j) types[j] = replace;
+          i = end - 1;
+        }
+      }
+
+      // W7. Search backwards from each instance of a European number
+      // until the first strong type (R, L, or sor) is found. If an L is
+      // found, then change the type of the European number to L.
+      for (var i = 0, cur = outerType; i < len; ++i) {
+        var type = types[i];
+        if (cur == "L" && type == "1") types[i] = "L";
+        else if (isStrong.test(type)) cur = type;
+      }
+
+      // N1. A sequence of neutrals takes the direction of the
+      // surrounding strong text if the text on both sides has the same
+      // direction. European and Arabic numbers act as if they were R in
+      // terms of their influence on neutrals. Start-of-level-run (sor)
+      // and end-of-level-run (eor) are used at level run boundaries.
+      // N2. Any remaining neutrals take the embedding direction.
+      for (var i = 0; i < len; ++i) {
+        if (isNeutral.test(types[i])) {
+          for (var end = i + 1; end < len && isNeutral.test(types[end]); ++end) {}
+          var before = (i ? types[i-1] : outerType) == "L";
+          var after = (end < len - 1 ? types[end] : outerType) == "L";
+          var replace = before || after ? "L" : "R";
+          for (var j = i; j < end; ++j) types[j] = replace;
+          i = end - 1;
+        }
+      }
+
+      // Here we depart from the documented algorithm, in order to avoid
+      // building up an actual levels array. Since there are only three
+      // levels (0, 1, 2) in an implementation that doesn't take
+      // explicit embedding into account, we can build up the order on
+      // the fly, without following the level-based algorithm.
+      var order = [], m;
+      for (var i = 0; i < len;) {
+        if (countsAsLeft.test(types[i])) {
+          var start = i;
+          for (++i; i < len && countsAsLeft.test(types[i]); ++i) {}
+          order.push({from: start, to: i, level: 0});
+        } else {
+          var pos = i, at = order.length;
+          for (++i; i < len && types[i] != "L"; ++i) {}
+          for (var j = pos; j < i;) {
+            if (countsAsNum.test(types[j])) {
+              if (pos < j) order.splice(at, 0, {from: pos, to: j, level: 1});
+              var nstart = j;
+              for (++j; j < i && countsAsNum.test(types[j]); ++j) {}
+              order.splice(at, 0, {from: nstart, to: j, level: 2});
+              pos = j;
+            } else ++j;
+          }
+          if (pos < i) order.splice(at, 0, {from: pos, to: i, level: 1});
+        }
+      }
+      if (order[0].level == 1 && (m = str.match(/^\s+/))) {
+        order[0].from = m[0].length;
+        order.unshift({from: 0, to: m[0].length, level: 0});
+      }
+      if (lst(order).level == 1 && (m = str.match(/\s+$/))) {
+        lst(order).to -= m[0].length;
+        order.push({from: len - m[0].length, to: len, level: 0});
+      }
+      if (order[0].level != lst(order).level)
+        order.push({from: len, to: len, level: order[0].level});
+
+      return order;
+    };
+  })();
+
+  // THE END
+
+  CodeMirror.version = "3.16.1";
+
+  return CodeMirror;
+})();

+ 44 - 0
lib/codemirror/continuecomment.js

@@ -0,0 +1,44 @@
+(function() {
+  var modes = ["clike", "css", "javascript"];
+  for (var i = 0; i < modes.length; ++i)
+    CodeMirror.extendMode(modes[i], {blockCommentStart: "/*",
+                                     blockCommentEnd: "*/",
+                                     blockCommentContinue: " * "});
+
+  function continueComment(cm) {
+    var pos = cm.getCursor(), token = cm.getTokenAt(pos);
+    var mode = CodeMirror.innerMode(cm.getMode(), token.state).mode;
+    var space;
+
+    if (token.type == "comment" && mode.blockCommentStart && mode.blockCommentContinue) {
+      var end = token.string.indexOf(mode.blockCommentEnd);
+      var full = cm.getRange(CodeMirror.Pos(pos.line, 0), CodeMirror.Pos(pos.line, token.end)), found;
+      if (end != -1 && end == token.string.length - mode.blockCommentEnd.length) {
+        // Comment ended, don't continue it
+      } else if (token.string.indexOf(mode.blockCommentStart) == 0) {
+        space = full.slice(0, token.start);
+        if (!/^\s*$/.test(space)) {
+          space = "";
+          for (var i = 0; i < token.start; ++i) space += " ";
+        }
+      } else if ((found = full.indexOf(mode.blockCommentContinue)) != -1 &&
+                 found + mode.blockCommentContinue.length > token.start &&
+                 /^\s*$/.test(full.slice(0, found))) {
+        space = full.slice(0, found);
+      }
+    }
+
+    if (space != null)
+      cm.replaceSelection("\n" + space + mode.blockCommentContinue, "end");
+    else
+      return CodeMirror.Pass;
+  }
+
+  CodeMirror.defineOption("continueComments", null, function(cm, val, prev) {
+    if (prev && prev != CodeMirror.Init)
+      cm.removeKeyMap("continueComment");
+    var map = {name: "continueComment"};
+    map[typeof val == "string" ? val : "Enter"] = continueComment;
+    cm.addKeyMap(map);
+  });
+})();

+ 480 - 0
lib/codemirror/javascript.js

@@ -0,0 +1,480 @@
+// TODO actually recognize syntax of TypeScript constructs
+
+CodeMirror.defineMode("javascript", function(config, parserConfig) {
+  var indentUnit = config.indentUnit;
+  var statementIndent = parserConfig.statementIndent;
+  var jsonMode = parserConfig.json;
+  var isTS = parserConfig.typescript;
+
+  // Tokenizer
+
+  var keywords = function(){
+    function kw(type) {return {type: type, style: "keyword"};}
+    var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c");
+    var operator = kw("operator"), atom = {type: "atom", style: "atom"};
+
+    var jsKeywords = {
+      "if": kw("if"), "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B,
+      "return": C, "break": C, "continue": C, "new": C, "delete": C, "throw": C,
+      "var": kw("var"), "const": kw("var"), "let": kw("var"),
+      "function": kw("function"), "catch": kw("catch"),
+      "for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"),
+      "in": operator, "typeof": operator, "instanceof": operator,
+      "true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom,
+      "this": kw("this")
+    };
+
+    // Extend the 'normal' keywords with the TypeScript language extensions
+    if (isTS) {
+      var type = {type: "variable", style: "variable-3"};
+      var tsKeywords = {
+        // object-like things
+        "interface": kw("interface"),
+        "class": kw("class"),
+        "extends": kw("extends"),
+        "constructor": kw("constructor"),
+
+        // scope modifiers
+        "public": kw("public"),
+        "private": kw("private"),
+        "protected": kw("protected"),
+        "static": kw("static"),
+
+        "super": kw("super"),
+
+        // types
+        "string": type, "number": type, "bool": type, "any": type
+      };
+
+      for (var attr in tsKeywords) {
+        jsKeywords[attr] = tsKeywords[attr];
+      }
+    }
+
+    return jsKeywords;
+  }();
+
+  var isOperatorChar = /[+\-*&%=<>!?|~^]/;
+
+  function chain(stream, state, f) {
+    state.tokenize = f;
+    return f(stream, state);
+  }
+
+  function nextUntilUnescaped(stream, end) {
+    var escaped = false, next;
+    while ((next = stream.next()) != null) {
+      if (next == end && !escaped)
+        return false;
+      escaped = !escaped && next == "\\";
+    }
+    return escaped;
+  }
+
+  // Used as scratch variables to communicate multiple values without
+  // consing up tons of objects.
+  var type, content;
+  function ret(tp, style, cont) {
+    type = tp; content = cont;
+    return style;
+  }
+  function jsTokenBase(stream, state) {
+    var ch = stream.next();
+    if (ch == '"' || ch == "'")
+      return chain(stream, state, jsTokenString(ch));
+    else if (ch == "." && stream.match(/^\d+(?:[eE][+\-]?\d+)?/))
+      return ret("number", "number");
+    else if (/[\[\]{}\(\),;\:\.]/.test(ch))
+      return ret(ch);
+    else if (ch == "0" && stream.eat(/x/i)) {
+      stream.eatWhile(/[\da-f]/i);
+      return ret("number", "number");
+    }
+    else if (/\d/.test(ch)) {
+      stream.match(/^\d*(?:\.\d*)?(?:[eE][+\-]?\d+)?/);
+      return ret("number", "number");
+    }
+    else if (ch == "/") {
+      if (stream.eat("*")) {
+        return chain(stream, state, jsTokenComment);
+      }
+      else if (stream.eat("/")) {
+        stream.skipToEnd();
+        return ret("comment", "comment");
+      }
+      else if (state.lastType == "operator" || state.lastType == "keyword c" ||
+               /^[\[{}\(,;:]$/.test(state.lastType)) {
+        nextUntilUnescaped(stream, "/");
+        stream.eatWhile(/[gimy]/); // 'y' is "sticky" option in Mozilla
+        return ret("regexp", "string-2");
+      }
+      else {
+        stream.eatWhile(isOperatorChar);
+        return ret("operator", null, stream.current());
+      }
+    }
+    else if (ch == "#") {
+      stream.skipToEnd();
+      return ret("error", "error");
+    }
+    else if (isOperatorChar.test(ch)) {
+      stream.eatWhile(isOperatorChar);
+      return ret("operator", null, stream.current());
+    }
+    else {
+      stream.eatWhile(/[\w\$_]/);
+      var word = stream.current(), known = keywords.propertyIsEnumerable(word) && keywords[word];
+      return (known && state.lastType != ".") ? ret(known.type, known.style, word) :
+                     ret("variable", "variable", word);
+    }
+  }
+
+  function jsTokenString(quote) {
+    return function(stream, state) {
+      if (!nextUntilUnescaped(stream, quote))
+        state.tokenize = jsTokenBase;
+      return ret("string", "string");
+    };
+  }
+
+  function jsTokenComment(stream, state) {
+    var maybeEnd = false, ch;
+    while (ch = stream.next()) {
+      if (ch == "/" && maybeEnd) {
+        state.tokenize = jsTokenBase;
+        break;
+      }
+      maybeEnd = (ch == "*");
+    }
+    return ret("comment", "comment");
+  }
+
+  // Parser
+
+  var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true, "this": true};
+
+  function JSLexical(indented, column, type, align, prev, info) {
+    this.indented = indented;
+    this.column = column;
+    this.type = type;
+    this.prev = prev;
+    this.info = info;
+    if (align != null) this.align = align;
+  }
+
+  function inScope(state, varname) {
+    for (var v = state.localVars; v; v = v.next)
+      if (v.name == varname) return true;
+  }
+
+  function parseJS(state, style, type, content, stream) {
+    var cc = state.cc;
+    // Communicate our context to the combinators.
+    // (Less wasteful than consing up a hundred closures on every call.)
+    cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc;
+
+    if (!state.lexical.hasOwnProperty("align"))
+      state.lexical.align = true;
+
+    while(true) {
+      var combinator = cc.length ? cc.pop() : jsonMode ? expression : statement;
+      if (combinator(type, content)) {
+        while(cc.length && cc[cc.length - 1].lex)
+          cc.pop()();
+        if (cx.marked) return cx.marked;
+        if (type == "variable" && inScope(state, content)) return "variable-2";
+        return style;
+      }
+    }
+  }
+
+  // Combinator utils
+
+  var cx = {state: null, column: null, marked: null, cc: null};
+  function pass() {
+    for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]);
+  }
+  function cont() {
+    pass.apply(null, arguments);
+    return true;
+  }
+  function register(varname) {
+    function inList(list) {
+      for (var v = list; v; v = v.next)
+        if (v.name == varname) return true;
+      return false;
+    }
+    var state = cx.state;
+    if (state.context) {
+      cx.marked = "def";
+      if (inList(state.localVars)) return;
+      state.localVars = {name: varname, next: state.localVars};
+    } else {
+      if (inList(state.globalVars)) return;
+      state.globalVars = {name: varname, next: state.globalVars};
+    }
+  }
+
+  // Combinators
+
+  var defaultVars = {name: "this", next: {name: "arguments"}};
+  function pushcontext() {
+    cx.state.context = {prev: cx.state.context, vars: cx.state.localVars};
+    cx.state.localVars = defaultVars;
+  }
+  function popcontext() {
+    cx.state.localVars = cx.state.context.vars;
+    cx.state.context = cx.state.context.prev;
+  }
+  function pushlex(type, info) {
+    var result = function() {
+      var state = cx.state, indent = state.indented;
+      if (state.lexical.type == "stat") indent = state.lexical.indented;
+      state.lexical = new JSLexical(indent, cx.stream.column(), type, null, state.lexical, info);
+    };
+    result.lex = true;
+    return result;
+  }
+  function poplex() {
+    var state = cx.state;
+    if (state.lexical.prev) {
+      if (state.lexical.type == ")")
+        state.indented = state.lexical.indented;
+      state.lexical = state.lexical.prev;
+    }
+  }
+  poplex.lex = true;
+
+  function expect(wanted) {
+    return function(type) {
+      if (type == wanted) return cont();
+      else if (wanted == ";") return pass();
+      else return cont(arguments.callee);
+    };
+  }
+
+  function statement(type) {
+    if (type == "var") return cont(pushlex("vardef"), vardef1, expect(";"), poplex);
+    if (type == "keyword a") return cont(pushlex("form"), expression, statement, poplex);
+    if (type == "keyword b") return cont(pushlex("form"), statement, poplex);
+    if (type == "{") return cont(pushlex("}"), block, poplex);
+    if (type == ";") return cont();
+    if (type == "if") return cont(pushlex("form"), expression, statement, poplex, maybeelse);
+    if (type == "function") return cont(functiondef);
+    if (type == "for") return cont(pushlex("form"), expect("("), pushlex(")"), forspec1, expect(")"),
+                                   poplex, statement, poplex);
+    if (type == "variable") return cont(pushlex("stat"), maybelabel);
+    if (type == "switch") return cont(pushlex("form"), expression, pushlex("}", "switch"), expect("{"),
+                                      block, poplex, poplex);
+    if (type == "case") return cont(expression, expect(":"));
+    if (type == "default") return cont(expect(":"));
+    if (type == "catch") return cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"),
+                                     statement, poplex, popcontext);
+    return pass(pushlex("stat"), expression, expect(";"), poplex);
+  }
+  function expression(type) {
+    return expressionInner(type, false);
+  }
+  function expressionNoComma(type) {
+    return expressionInner(type, true);
+  }
+  function expressionInner(type, noComma) {
+    var maybeop = noComma ? maybeoperatorNoComma : maybeoperatorComma;
+    if (atomicTypes.hasOwnProperty(type)) return cont(maybeop);
+    if (type == "function") return cont(functiondef);
+    if (type == "keyword c") return cont(noComma ? maybeexpressionNoComma : maybeexpression);
+    if (type == "(") return cont(pushlex(")"), maybeexpression, expect(")"), poplex, maybeop);
+    if (type == "operator") return cont(noComma ? expressionNoComma : expression);
+    if (type == "[") return cont(pushlex("]"), commasep(expressionNoComma, "]"), poplex, maybeop);
+    if (type == "{") return cont(pushlex("}"), commasep(objprop, "}"), poplex, maybeop);
+    return cont();
+  }
+  function maybeexpression(type) {
+    if (type.match(/[;\}\)\],]/)) return pass();
+    return pass(expression);
+  }
+  function maybeexpressionNoComma(type) {
+    if (type.match(/[;\}\)\],]/)) return pass();
+    return pass(expressionNoComma);
+  }
+
+  function maybeoperatorComma(type, value) {
+    if (type == ",") return cont(expression);
+    return maybeoperatorNoComma(type, value, false);
+  }
+  function maybeoperatorNoComma(type, value, noComma) {
+    var me = noComma == false ? maybeoperatorComma : maybeoperatorNoComma;
+    var expr = noComma == false ? expression : expressionNoComma;
+    if (type == "operator") {
+      if (/\+\+|--/.test(value)) return cont(me);
+      if (value == "?") return cont(expression, expect(":"), expr);
+      return cont(expr);
+    }
+    if (type == ";") return;
+    if (type == "(") return cont(pushlex(")", "call"), commasep(expressionNoComma, ")"), poplex, me);
+    if (type == ".") return cont(property, me);
+    if (type == "[") return cont(pushlex("]"), maybeexpression, expect("]"), poplex, me);
+  }
+  function maybelabel(type) {
+    if (type == ":") return cont(poplex, statement);
+    return pass(maybeoperatorComma, expect(";"), poplex);
+  }
+  function property(type) {
+    if (type == "variable") {cx.marked = "property"; return cont();}
+  }
+  function objprop(type, value) {
+    if (type == "variable") {
+      cx.marked = "property";
+      if (value == "get" || value == "set") return cont(getterSetter);
+    } else if (type == "number" || type == "string") {
+      cx.marked = type + " property";
+    }
+    if (atomicTypes.hasOwnProperty(type)) return cont(expect(":"), expressionNoComma);
+  }
+  function getterSetter(type) {
+    if (type == ":") return cont(expression);
+    if (type != "variable") return cont(expect(":"), expression);
+    cx.marked = "property";
+    return cont(functiondef);
+  }
+  function commasep(what, end) {
+    function proceed(type) {
+      if (type == ",") {
+        var lex = cx.state.lexical;
+        if (lex.info == "call") lex.pos = (lex.pos || 0) + 1;
+        return cont(what, proceed);
+      }
+      if (type == end) return cont();
+      return cont(expect(end));
+    }
+    return function(type) {
+      if (type == end) return cont();
+      else return pass(what, proceed);
+    };
+  }
+  function block(type) {
+    if (type == "}") return cont();
+    return pass(statement, block);
+  }
+  function maybetype(type) {
+    if (type == ":") return cont(typedef);
+    return pass();
+  }
+  function typedef(type) {
+    if (type == "variable"){cx.marked = "variable-3"; return cont();}
+    return pass();
+  }
+  function vardef1(type, value) {
+    if (type == "variable") {
+      register(value);
+      return isTS ? cont(maybetype, vardef2) : cont(vardef2);
+    }
+    return pass();
+  }
+  function vardef2(type, value) {
+    if (value == "=") return cont(expressionNoComma, vardef2);
+    if (type == ",") return cont(vardef1);
+  }
+  function maybeelse(type, value) {
+    if (type == "keyword b" && value == "else") return cont(pushlex("form"), statement, poplex);
+  }
+  function forspec1(type) {
+    if (type == "var") return cont(vardef1, expect(";"), forspec2);
+    if (type == ";") return cont(forspec2);
+    if (type == "variable") return cont(formaybein);
+    return pass(expression, expect(";"), forspec2);
+  }
+  function formaybein(_type, value) {
+    if (value == "in") return cont(expression);
+    return cont(maybeoperatorComma, forspec2);
+  }
+  function forspec2(type, value) {
+    if (type == ";") return cont(forspec3);
+    if (value == "in") return cont(expression);
+    return pass(expression, expect(";"), forspec3);
+  }
+  function forspec3(type) {
+    if (type != ")") cont(expression);
+  }
+  function functiondef(type, value) {
+    if (type == "variable") {register(value); return cont(functiondef);}
+    if (type == "(") return cont(pushlex(")"), pushcontext, commasep(funarg, ")"), poplex, statement, popcontext);
+  }
+  function funarg(type, value) {
+    if (type == "variable") {register(value); return isTS ? cont(maybetype) : cont();}
+  }
+
+  // Interface
+
+  return {
+    startState: function(basecolumn) {
+      return {
+        tokenize: jsTokenBase,
+        lastType: null,
+        cc: [],
+        lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false),
+        localVars: parserConfig.localVars,
+        globalVars: parserConfig.globalVars,
+        context: parserConfig.localVars && {vars: parserConfig.localVars},
+        indented: 0
+      };
+    },
+
+    token: function(stream, state) {
+      if (stream.sol()) {
+        if (!state.lexical.hasOwnProperty("align"))
+          state.lexical.align = false;
+        state.indented = stream.indentation();
+      }
+      if (state.tokenize != jsTokenComment && stream.eatSpace()) return null;
+      var style = state.tokenize(stream, state);
+      if (type == "comment") return style;
+      state.lastType = type == "operator" && (content == "++" || content == "--") ? "incdec" : type;
+      return parseJS(state, style, type, content, stream);
+    },
+
+    indent: function(state, textAfter) {
+      if (state.tokenize == jsTokenComment) return CodeMirror.Pass;
+      if (state.tokenize != jsTokenBase) return 0;
+      var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical;
+      // Kludge to prevent 'maybelse' from blocking lexical scope pops
+      for (var i = state.cc.length - 1; i >= 0; --i) {
+        var c = state.cc[i];
+        if (c == poplex) lexical = lexical.prev;
+        else if (c != maybeelse || /^else\b/.test(textAfter)) break;
+      }
+      if (lexical.type == "stat" && firstChar == "}") lexical = lexical.prev;
+      if (statementIndent && lexical.type == ")" && lexical.prev.type == "stat")
+        lexical = lexical.prev;
+      var type = lexical.type, closing = firstChar == type;
+
+      if (type == "vardef") return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? 4 : 0);
+      else if (type == "form" && firstChar == "{") return lexical.indented;
+      else if (type == "form") return lexical.indented + indentUnit;
+      else if (type == "stat")
+        return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? statementIndent || indentUnit : 0);
+      else if (lexical.info == "switch" && !closing && parserConfig.doubleIndentSwitch != false)
+        return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit);
+      else if (lexical.align) return lexical.column + (closing ? 0 : 1);
+      else return lexical.indented + (closing ? 0 : indentUnit);
+    },
+
+    electricChars: ":{}",
+    blockCommentStart: jsonMode ? null : "/*",
+    blockCommentEnd: jsonMode ? null : "*/",
+    lineComment: jsonMode ? null : "//",
+    fold: "brace",
+
+    helperType: jsonMode ? "json" : "javascript",
+    jsonMode: jsonMode
+  };
+});
+
+CodeMirror.defineMIME("text/javascript", "javascript");
+CodeMirror.defineMIME("text/ecmascript", "javascript");
+CodeMirror.defineMIME("application/javascript", "javascript");
+CodeMirror.defineMIME("application/ecmascript", "javascript");
+CodeMirror.defineMIME("application/json", {name: "javascript", json: true});
+CodeMirror.defineMIME("application/x-json", {name: "javascript", json: true});
+CodeMirror.defineMIME("text/typescript", { name: "javascript", typescript: true });
+CodeMirror.defineMIME("application/typescript", { name: "javascript", typescript: true });

+ 91 - 0
lib/codemirror/match-highlighter.js

@@ -0,0 +1,91 @@
+// Highlighting text that matches the selection
+//
+// Defines an option highlightSelectionMatches, which, when enabled,
+// will style strings that match the selection throughout the
+// document.
+//
+// The option can be set to true to simply enable it, or to a
+// {minChars, style, showToken} object to explicitly configure it.
+// minChars is the minimum amount of characters that should be
+// selected for the behavior to occur, and style is the token style to
+// apply to the matches. This will be prefixed by "cm-" to create an
+// actual CSS class name. showToken, when enabled, will cause the
+// current token to be highlighted when nothing is selected.
+
+(function() {
+  var DEFAULT_MIN_CHARS = 2;
+  var DEFAULT_TOKEN_STYLE = "matchhighlight";
+  var DEFAULT_DELAY = 100;
+
+  function State(options) {
+    if (typeof options == "object") {
+      this.minChars = options.minChars;
+      this.style = options.style;
+      this.showToken = options.showToken;
+      this.delay = options.delay;
+    }
+    if (this.style == null) this.style = DEFAULT_TOKEN_STYLE;
+    if (this.minChars == null) this.minChars = DEFAULT_MIN_CHARS;
+    if (this.delay == null) this.delay = DEFAULT_DELAY;
+    this.overlay = this.timeout = null;
+  }
+
+  CodeMirror.defineOption("highlightSelectionMatches", false, function(cm, val, old) {
+    if (old && old != CodeMirror.Init) {
+      var over = cm.state.matchHighlighter.overlay;
+      if (over) cm.removeOverlay(over);
+      clearTimeout(cm.state.matchHighlighter.timeout);
+      cm.state.matchHighlighter = null;
+      cm.off("cursorActivity", cursorActivity);
+    }
+    if (val) {
+      cm.state.matchHighlighter = new State(val);
+      highlightMatches(cm);
+      cm.on("cursorActivity", cursorActivity);
+    }
+  });
+
+  function cursorActivity(cm) {
+    var state = cm.state.matchHighlighter;
+    clearTimeout(state.timeout);
+    state.timeout = setTimeout(function() {highlightMatches(cm);}, state.delay);
+  }
+
+  function highlightMatches(cm) {
+    cm.operation(function() {
+      var state = cm.state.matchHighlighter;
+      if (state.overlay) {
+        cm.removeOverlay(state.overlay);
+        state.overlay = null;
+      }
+      if (!cm.somethingSelected() && state.showToken) {
+        var re = state.showToken === true ? /[\w$]/ : state.showToken;
+        var cur = cm.getCursor(), line = cm.getLine(cur.line), start = cur.ch, end = start;
+        while (start && re.test(line.charAt(start - 1))) --start;
+        while (end < line.length && re.test(line.charAt(end))) ++end;
+        if (start < end)
+          cm.addOverlay(state.overlay = makeOverlay(line.slice(start, end), re, state.style));
+        return;
+      }
+      if (cm.getCursor("head").line != cm.getCursor("anchor").line) return;
+      var selection = cm.getSelection().replace(/^\s+|\s+$/g, "");
+      if (selection.length >= state.minChars)
+        cm.addOverlay(state.overlay = makeOverlay(selection, false, state.style));
+    });
+  }
+
+  function boundariesAround(stream, re) {
+    return (!stream.start || !re.test(stream.string.charAt(stream.start - 1))) &&
+      (stream.pos == stream.string.length || !re.test(stream.string.charAt(stream.pos)));
+  }
+
+  function makeOverlay(query, hasBoundary, style) {
+    return {token: function(stream) {
+      if (stream.match(query) &&
+          (!hasBoundary || boundariesAround(stream, hasBoundary)))
+        return style;
+      stream.next();
+      stream.skipTo(query.charAt(0)) || stream.skipToEnd();
+    }};
+  }
+})();

+ 86 - 0
lib/codemirror/matchbrackets.js

@@ -0,0 +1,86 @@
+(function() {
+  var ie_lt8 = /MSIE \d/.test(navigator.userAgent) &&
+    (document.documentMode == null || document.documentMode < 8);
+
+  var Pos = CodeMirror.Pos;
+
+  var matching = {"(": ")>", ")": "(<", "[": "]>", "]": "[<", "{": "}>", "}": "{<"};
+  function findMatchingBracket(cm, where, strict) {
+    var state = cm.state.matchBrackets;
+    var maxScanLen = (state && state.maxScanLineLength) || 10000;
+
+    var cur = where || cm.getCursor(), line = cm.getLineHandle(cur.line), pos = cur.ch - 1;
+    var match = (pos >= 0 && matching[line.text.charAt(pos)]) || matching[line.text.charAt(++pos)];
+    if (!match) return null;
+    var forward = match.charAt(1) == ">", d = forward ? 1 : -1;
+    if (strict && forward != (pos == cur.ch)) return null;
+    var style = cm.getTokenTypeAt(Pos(cur.line, pos + 1));
+
+    var stack = [line.text.charAt(pos)], re = /[(){}[\]]/;
+    function scan(line, lineNo, start) {
+      if (!line.text) return;
+      var pos = forward ? 0 : line.text.length - 1, end = forward ? line.text.length : -1;
+      if (line.text.length > maxScanLen) return null;
+      if (start != null) pos = start + d;
+      for (; pos != end; pos += d) {
+        var ch = line.text.charAt(pos);
+        if (re.test(ch) && cm.getTokenTypeAt(Pos(lineNo, pos + 1)) == style) {
+          var match = matching[ch];
+          if (match.charAt(1) == ">" == forward) stack.push(ch);
+          else if (stack.pop() != match.charAt(0)) return {pos: pos, match: false};
+          else if (!stack.length) return {pos: pos, match: true};
+        }
+      }
+    }
+    for (var i = cur.line, found, e = forward ? Math.min(i + 100, cm.lineCount()) : Math.max(-1, i - 100); i != e; i+=d) {
+      if (i == cur.line) found = scan(line, i, pos);
+      else found = scan(cm.getLineHandle(i), i);
+      if (found) break;
+    }
+    return {from: Pos(cur.line, pos), to: found && Pos(i, found.pos),
+            match: found && found.match, forward: forward};
+  }
+
+  function matchBrackets(cm, autoclear) {
+    // Disable brace matching in long lines, since it'll cause hugely slow updates
+    var maxHighlightLen = cm.state.matchBrackets.maxHighlightLineLength || 1000;
+    var found = findMatchingBracket(cm);
+    if (!found || cm.getLine(found.from.line).length > maxHighlightLen ||
+       found.to && cm.getLine(found.to.line).length > maxHighlightLen)
+      return;
+
+    var style = found.match ? "CodeMirror-matchingbracket" : "CodeMirror-nonmatchingbracket";
+    var one = cm.markText(found.from, Pos(found.from.line, found.from.ch + 1), {className: style});
+    var two = found.to && cm.markText(found.to, Pos(found.to.line, found.to.ch + 1), {className: style});
+    // Kludge to work around the IE bug from issue #1193, where text
+    // input stops going to the textare whever this fires.
+    if (ie_lt8 && cm.state.focused) cm.display.input.focus();
+    var clear = function() {
+      cm.operation(function() { one.clear(); two && two.clear(); });
+    };
+    if (autoclear) setTimeout(clear, 800);
+    else return clear;
+  }
+
+  var currentlyHighlighted = null;
+  function doMatchBrackets(cm) {
+    cm.operation(function() {
+      if (currentlyHighlighted) {currentlyHighlighted(); currentlyHighlighted = null;}
+      if (!cm.somethingSelected()) currentlyHighlighted = matchBrackets(cm, false);
+    });
+  }
+
+  CodeMirror.defineOption("matchBrackets", false, function(cm, val, old) {
+    if (old && old != CodeMirror.Init)
+      cm.off("cursorActivity", doMatchBrackets);
+    if (val) {
+      cm.state.matchBrackets = typeof val == "object" ? val : {};
+      cm.on("cursorActivity", doMatchBrackets);
+    }
+  });
+
+  CodeMirror.defineExtension("matchBrackets", function() {matchBrackets(this, true);});
+  CodeMirror.defineExtension("findMatchingBracket", function(pos, strict){
+    return findMatchingBracket(this, pos, strict);
+  });
+})();

+ 131 - 0
lib/codemirror/search.js

@@ -0,0 +1,131 @@
+// Define search commands. Depends on dialog.js or another
+// implementation of the openDialog method.
+
+// Replace works a little oddly -- it will do the replace on the next
+// Ctrl-G (or whatever is bound to findNext) press. You prevent a
+// replace by making sure the match is no longer selected when hitting
+// Ctrl-G.
+
+(function() {
+  function searchOverlay(query) {
+    if (typeof query == "string") return {token: function(stream) {
+      if (stream.match(query)) return "searching";
+      stream.next();
+      stream.skipTo(query.charAt(0)) || stream.skipToEnd();
+    }};
+    return {token: function(stream) {
+      if (stream.match(query)) return "searching";
+      while (!stream.eol()) {
+        stream.next();
+        if (stream.match(query, false)) break;
+      }
+    }};
+  }
+
+  function SearchState() {
+    this.posFrom = this.posTo = this.query = null;
+    this.overlay = null;
+  }
+  function getSearchState(cm) {
+    return cm.state.search || (cm.state.search = new SearchState());
+  }
+  function getSearchCursor(cm, query, pos) {
+    // Heuristic: if the query string is all lowercase, do a case insensitive search.
+    return cm.getSearchCursor(query, pos, typeof query == "string" && query == query.toLowerCase());
+  }
+  function dialog(cm, text, shortText, f) {
+    if (cm.openDialog) cm.openDialog(text, f);
+    else f(prompt(shortText, ""));
+  }
+  function confirmDialog(cm, text, shortText, fs) {
+    if (cm.openConfirm) cm.openConfirm(text, fs);
+    else if (confirm(shortText)) fs[0]();
+  }
+  function parseQuery(query) {
+    var isRE = query.match(/^\/(.*)\/([a-z]*)$/);
+    return isRE ? new RegExp(isRE[1], isRE[2].indexOf("i") == -1 ? "" : "i") : query;
+  }
+  var queryDialog =
+    'Search: <input type="text" style="width: 10em"/> <span style="color: #888">(Use /re/ syntax for regexp search)</span>';
+  function doSearch(cm, rev) {
+    var state = getSearchState(cm);
+    if (state.query) return findNext(cm, rev);
+    dialog(cm, queryDialog, "Search for:", function(query) {
+      cm.operation(function() {
+        if (!query || state.query) return;
+        state.query = parseQuery(query);
+        cm.removeOverlay(state.overlay);
+        state.overlay = searchOverlay(state.query);
+        cm.addOverlay(state.overlay);
+        state.posFrom = state.posTo = cm.getCursor();
+        findNext(cm, rev);
+      });
+    });
+  }
+  function findNext(cm, rev) {cm.operation(function() {
+    var state = getSearchState(cm);
+    var cursor = getSearchCursor(cm, state.query, rev ? state.posFrom : state.posTo);
+    if (!cursor.find(rev)) {
+      cursor = getSearchCursor(cm, state.query, rev ? CodeMirror.Pos(cm.lastLine()) : CodeMirror.Pos(cm.firstLine(), 0));
+      if (!cursor.find(rev)) return;
+    }
+    cm.setSelection(cursor.from(), cursor.to());
+    state.posFrom = cursor.from(); state.posTo = cursor.to();
+  });}
+  function clearSearch(cm) {cm.operation(function() {
+    var state = getSearchState(cm);
+    if (!state.query) return;
+    state.query = null;
+    cm.removeOverlay(state.overlay);
+  });}
+
+  var replaceQueryDialog =
+    'Replace: <input type="text" style="width: 10em"/> <span style="color: #888">(Use /re/ syntax for regexp search)</span>';
+  var replacementQueryDialog = 'With: <input type="text" style="width: 10em"/>';
+  var doReplaceConfirm = "Replace? <button>Yes</button> <button>No</button> <button>Stop</button>";
+  function replace(cm, all) {
+    dialog(cm, replaceQueryDialog, "Replace:", function(query) {
+      if (!query) return;
+      query = parseQuery(query);
+      dialog(cm, replacementQueryDialog, "Replace with:", function(text) {
+        if (all) {
+          cm.operation(function() {
+            for (var cursor = getSearchCursor(cm, query); cursor.findNext();) {
+              if (typeof query != "string") {
+                var match = cm.getRange(cursor.from(), cursor.to()).match(query);
+                cursor.replace(text.replace(/\$(\d)/, function(_, i) {return match[i];}));
+              } else cursor.replace(text);
+            }
+          });
+        } else {
+          clearSearch(cm);
+          var cursor = getSearchCursor(cm, query, cm.getCursor());
+          var advance = function() {
+            var start = cursor.from(), match;
+            if (!(match = cursor.findNext())) {
+              cursor = getSearchCursor(cm, query);
+              if (!(match = cursor.findNext()) ||
+                  (start && cursor.from().line == start.line && cursor.from().ch == start.ch)) return;
+            }
+            cm.setSelection(cursor.from(), cursor.to());
+            confirmDialog(cm, doReplaceConfirm, "Replace?",
+                          [function() {doReplace(match);}, advance]);
+          };
+          var doReplace = function(match) {
+            cursor.replace(typeof query == "string" ? text :
+                           text.replace(/\$(\d)/, function(_, i) {return match[i];}));
+            advance();
+          };
+          advance();
+        }
+      });
+    });
+  }
+
+  CodeMirror.commands.find = function(cm) {clearSearch(cm); doSearch(cm);};
+  CodeMirror.commands.findNext = doSearch;
+  CodeMirror.commands.findPrev = function(cm) {doSearch(cm, true);};
+  CodeMirror.commands.clearSearch = clearSearch;
+  CodeMirror.commands.replace = replace;
+  CodeMirror.commands.replaceAll = function(cm) {replace(cm, true);};
+})();

+ 143 - 0
lib/codemirror/searchcursor.js

@@ -0,0 +1,143 @@
+(function(){
+  var Pos = CodeMirror.Pos;
+
+  function SearchCursor(doc, query, pos, caseFold) {
+    this.atOccurrence = false; this.doc = doc;
+    if (caseFold == null && typeof query == "string") caseFold = false;
+
+    pos = pos ? doc.clipPos(pos) : Pos(0, 0);
+    this.pos = {from: pos, to: pos};
+
+    // The matches method is filled in based on the type of query.
+    // It takes a position and a direction, and returns an object
+    // describing the next occurrence of the query, or null if no
+    // more matches were found.
+    if (typeof query != "string") { // Regexp match
+      if (!query.global) query = new RegExp(query.source, query.ignoreCase ? "ig" : "g");
+      this.matches = function(reverse, pos) {
+        if (reverse) {
+          query.lastIndex = 0;
+          var line = doc.getLine(pos.line).slice(0, pos.ch), cutOff = 0, match, start;
+          for (;;) {
+            query.lastIndex = cutOff;
+            var newMatch = query.exec(line);
+            if (!newMatch) break;
+            match = newMatch;
+            start = match.index;
+            cutOff = match.index + (match[0].length || 1);
+            if (cutOff == line.length) break;
+          }
+          var matchLen = (match && match[0].length) || 0;
+          if (!matchLen) {
+            if (start == 0 && line.length == 0) {match = undefined;}
+            else if (start != doc.getLine(pos.line).length) {
+              matchLen++;
+            }
+          }
+        } else {
+          query.lastIndex = pos.ch;
+          var line = doc.getLine(pos.line), match = query.exec(line);
+          var matchLen = (match && match[0].length) || 0;
+          var start = match && match.index;
+          if (start + matchLen != line.length && !matchLen) matchLen = 1;
+        }
+        if (match && matchLen)
+          return {from: Pos(pos.line, start),
+                  to: Pos(pos.line, start + matchLen),
+                  match: match};
+      };
+    } else { // String query
+      if (caseFold) query = query.toLowerCase();
+      var fold = caseFold ? function(str){return str.toLowerCase();} : function(str){return str;};
+      var target = query.split("\n");
+      // Different methods for single-line and multi-line queries
+      if (target.length == 1) {
+        if (!query.length) {
+          // Empty string would match anything and never progress, so
+          // we define it to match nothing instead.
+          this.matches = function() {};
+        } else {
+          this.matches = function(reverse, pos) {
+            var line = fold(doc.getLine(pos.line)), len = query.length, match;
+            if (reverse ? (pos.ch >= len && (match = line.lastIndexOf(query, pos.ch - len)) != -1)
+                        : (match = line.indexOf(query, pos.ch)) != -1)
+              return {from: Pos(pos.line, match),
+                      to: Pos(pos.line, match + len)};
+          };
+        }
+      } else {
+        this.matches = function(reverse, pos) {
+          var ln = pos.line, idx = (reverse ? target.length - 1 : 0), match = target[idx], line = fold(doc.getLine(ln));
+          var offsetA = (reverse ? line.indexOf(match) + match.length : line.lastIndexOf(match));
+          if (reverse ? offsetA >= pos.ch || offsetA != match.length
+              : offsetA <= pos.ch || offsetA != line.length - match.length)
+            return;
+          for (;;) {
+            if (reverse ? !ln : ln == doc.lineCount() - 1) return;
+            line = fold(doc.getLine(ln += reverse ? -1 : 1));
+            match = target[reverse ? --idx : ++idx];
+            if (idx > 0 && idx < target.length - 1) {
+              if (line != match) return;
+              else continue;
+            }
+            var offsetB = (reverse ? line.lastIndexOf(match) : line.indexOf(match) + match.length);
+            if (reverse ? offsetB != line.length - match.length : offsetB != match.length)
+              return;
+            var start = Pos(pos.line, offsetA), end = Pos(ln, offsetB);
+            return {from: reverse ? end : start, to: reverse ? start : end};
+          }
+        };
+      }
+    }
+  }
+
+  SearchCursor.prototype = {
+    findNext: function() {return this.find(false);},
+    findPrevious: function() {return this.find(true);},
+
+    find: function(reverse) {
+      var self = this, pos = this.doc.clipPos(reverse ? this.pos.from : this.pos.to);
+      function savePosAndFail(line) {
+        var pos = Pos(line, 0);
+        self.pos = {from: pos, to: pos};
+        self.atOccurrence = false;
+        return false;
+      }
+
+      for (;;) {
+        if (this.pos = this.matches(reverse, pos)) {
+          if (!this.pos.from || !this.pos.to) { console.log(this.matches, this.pos); }
+          this.atOccurrence = true;
+          return this.pos.match || true;
+        }
+        if (reverse) {
+          if (!pos.line) return savePosAndFail(0);
+          pos = Pos(pos.line-1, this.doc.getLine(pos.line-1).length);
+        }
+        else {
+          var maxLine = this.doc.lineCount();
+          if (pos.line == maxLine - 1) return savePosAndFail(maxLine);
+          pos = Pos(pos.line + 1, 0);
+        }
+      }
+    },
+
+    from: function() {if (this.atOccurrence) return this.pos.from;},
+    to: function() {if (this.atOccurrence) return this.pos.to;},
+
+    replace: function(newText) {
+      if (!this.atOccurrence) return;
+      var lines = CodeMirror.splitLines(newText);
+      this.doc.replaceRange(lines, this.pos.from, this.pos.to);
+      this.pos.to = Pos(this.pos.from.line + lines.length - 1,
+                        lines[lines.length - 1].length + (lines.length == 1 ? this.pos.from.ch : 0));
+    }
+  };
+
+  CodeMirror.defineExtension("getSearchCursor", function(query, pos, caseFold) {
+    return new SearchCursor(this.doc, query, pos, caseFold);
+  });
+  CodeMirror.defineDocExtension("getSearchCursor", function(query, pos, caseFold) {
+    return new SearchCursor(this, query, pos, caseFold);
+  });
+})();

+ 2088 - 0
lib/zip.js/deflate.js

@@ -0,0 +1,2088 @@
+/*
+ Copyright (c) 2013 Gildas Lormeau. All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above copyright 
+ notice, this list of conditions and the following disclaimer in 
+ the documentation and/or other materials provided with the distribution.
+
+ 3. The names of the authors may not be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
+ INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
+ INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
+ INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
+ OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * This program is based on JZlib 1.0.2 ymnk, JCraft,Inc.
+ * JZlib is based on zlib-1.1.3, so all credit should go authors
+ * Jean-loup Gailly([email protected]) and Mark Adler([email protected])
+ * and contributors of zlib.
+ */
+
+(function(obj) {
+
+	// Global
+
+	var MAX_BITS = 15;
+	var D_CODES = 30;
+	var BL_CODES = 19;
+
+	var LENGTH_CODES = 29;
+	var LITERALS = 256;
+	var L_CODES = (LITERALS + 1 + LENGTH_CODES);
+	var HEAP_SIZE = (2 * L_CODES + 1);
+
+	var END_BLOCK = 256;
+
+	// Bit length codes must not exceed MAX_BL_BITS bits
+	var MAX_BL_BITS = 7;
+
+	// repeat previous bit length 3-6 times (2 bits of repeat count)
+	var REP_3_6 = 16;
+
+	// repeat a zero length 3-10 times (3 bits of repeat count)
+	var REPZ_3_10 = 17;
+
+	// repeat a zero length 11-138 times (7 bits of repeat count)
+	var REPZ_11_138 = 18;
+
+	// The lengths of the bit length codes are sent in order of decreasing
+	// probability, to avoid transmitting the lengths for unused bit
+	// length codes.
+
+	var Buf_size = 8 * 2;
+
+	// JZlib version : "1.0.2"
+	var Z_DEFAULT_COMPRESSION = -1;
+
+	// compression strategy
+	var Z_FILTERED = 1;
+	var Z_HUFFMAN_ONLY = 2;
+	var Z_DEFAULT_STRATEGY = 0;
+
+	var Z_NO_FLUSH = 0;
+	var Z_PARTIAL_FLUSH = 1;
+	var Z_FULL_FLUSH = 3;
+	var Z_FINISH = 4;
+
+	var Z_OK = 0;
+	var Z_STREAM_END = 1;
+	var Z_NEED_DICT = 2;
+	var Z_STREAM_ERROR = -2;
+	var Z_DATA_ERROR = -3;
+	var Z_BUF_ERROR = -5;
+
+	// Tree
+
+	// see definition of array dist_code below
+	var _dist_code = [ 0, 1, 2, 3, 4, 4, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+			10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
+			12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+			13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,
+			14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,
+			14, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
+			15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 16, 17, 18, 18, 19, 19,
+			20, 20, 20, 20, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22, 22, 22, 23, 23, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
+			24, 24, 24, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26,
+			26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
+			27, 27, 27, 27, 27, 27, 27, 27, 27, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28,
+			28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 29,
+			29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
+			29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29 ];
+
+	function Tree() {
+		var that = this;
+
+		// dyn_tree; // the dynamic tree
+		// max_code; // largest code with non zero frequency
+		// stat_desc; // the corresponding static tree
+
+		// Compute the optimal bit lengths for a tree and update the total bit
+		// length
+		// for the current block.
+		// IN assertion: the fields freq and dad are set, heap[heap_max] and
+		// above are the tree nodes sorted by increasing frequency.
+		// OUT assertions: the field len is set to the optimal bit length, the
+		// array bl_count contains the frequencies for each bit length.
+		// The length opt_len is updated; static_len is also updated if stree is
+		// not null.
+		function gen_bitlen(s) {
+			var tree = that.dyn_tree;
+			var stree = that.stat_desc.static_tree;
+			var extra = that.stat_desc.extra_bits;
+			var base = that.stat_desc.extra_base;
+			var max_length = that.stat_desc.max_length;
+			var h; // heap index
+			var n, m; // iterate over the tree elements
+			var bits; // bit length
+			var xbits; // extra bits
+			var f; // frequency
+			var overflow = 0; // number of elements with bit length too large
+
+			for (bits = 0; bits <= MAX_BITS; bits++)
+				s.bl_count[bits] = 0;
+
+			// In a first pass, compute the optimal bit lengths (which may
+			// overflow in the case of the bit length tree).
+			tree[s.heap[s.heap_max] * 2 + 1] = 0; // root of the heap
+
+			for (h = s.heap_max + 1; h < HEAP_SIZE; h++) {
+				n = s.heap[h];
+				bits = tree[tree[n * 2 + 1] * 2 + 1] + 1;
+				if (bits > max_length) {
+					bits = max_length;
+					overflow++;
+				}
+				tree[n * 2 + 1] = bits;
+				// We overwrite tree[n*2+1] which is no longer needed
+
+				if (n > that.max_code)
+					continue; // not a leaf node
+
+				s.bl_count[bits]++;
+				xbits = 0;
+				if (n >= base)
+					xbits = extra[n - base];
+				f = tree[n * 2];
+				s.opt_len += f * (bits + xbits);
+				if (stree)
+					s.static_len += f * (stree[n * 2 + 1] + xbits);
+			}
+			if (overflow === 0)
+				return;
+
+			// This happens for example on obj2 and pic of the Calgary corpus
+			// Find the first bit length which could increase:
+			do {
+				bits = max_length - 1;
+				while (s.bl_count[bits] === 0)
+					bits--;
+				s.bl_count[bits]--; // move one leaf down the tree
+				s.bl_count[bits + 1] += 2; // move one overflow item as its brother
+				s.bl_count[max_length]--;
+				// The brother of the overflow item also moves one step up,
+				// but this does not affect bl_count[max_length]
+				overflow -= 2;
+			} while (overflow > 0);
+
+			for (bits = max_length; bits !== 0; bits--) {
+				n = s.bl_count[bits];
+				while (n !== 0) {
+					m = s.heap[--h];
+					if (m > that.max_code)
+						continue;
+					if (tree[m * 2 + 1] != bits) {
+						s.opt_len += (bits - tree[m * 2 + 1]) * tree[m * 2];
+						tree[m * 2 + 1] = bits;
+					}
+					n--;
+				}
+			}
+		}
+
+		// Reverse the first len bits of a code, using straightforward code (a
+		// faster
+		// method would use a table)
+		// IN assertion: 1 <= len <= 15
+		function bi_reverse(code, // the value to invert
+		len // its bit length
+		) {
+			var res = 0;
+			do {
+				res |= code & 1;
+				code >>>= 1;
+				res <<= 1;
+			} while (--len > 0);
+			return res >>> 1;
+		}
+
+		// Generate the codes for a given tree and bit counts (which need not be
+		// optimal).
+		// IN assertion: the array bl_count contains the bit length statistics for
+		// the given tree and the field len is set for all tree elements.
+		// OUT assertion: the field code is set for all tree elements of non
+		// zero code length.
+		function gen_codes(tree, // the tree to decorate
+		max_code, // largest code with non zero frequency
+		bl_count // number of codes at each bit length
+		) {
+			var next_code = []; // next code value for each
+			// bit length
+			var code = 0; // running code value
+			var bits; // bit index
+			var n; // code index
+			var len;
+
+			// The distribution counts are first used to generate the code values
+			// without bit reversal.
+			for (bits = 1; bits <= MAX_BITS; bits++) {
+				next_code[bits] = code = ((code + bl_count[bits - 1]) << 1);
+			}
+
+			// Check that the bit counts in bl_count are consistent. The last code
+			// must be all ones.
+			// Assert (code + bl_count[MAX_BITS]-1 == (1<<MAX_BITS)-1,
+			// "inconsistent bit counts");
+			// Tracev((stderr,"\ngen_codes: max_code %d ", max_code));
+
+			for (n = 0; n <= max_code; n++) {
+				len = tree[n * 2 + 1];
+				if (len === 0)
+					continue;
+				// Now reverse the bits
+				tree[n * 2] = bi_reverse(next_code[len]++, len);
+			}
+		}
+
+		// Construct one Huffman tree and assigns the code bit strings and lengths.
+		// Update the total bit length for the current block.
+		// IN assertion: the field freq is set for all tree elements.
+		// OUT assertions: the fields len and code are set to the optimal bit length
+		// and corresponding code. The length opt_len is updated; static_len is
+		// also updated if stree is not null. The field max_code is set.
+		that.build_tree = function(s) {
+			var tree = that.dyn_tree;
+			var stree = that.stat_desc.static_tree;
+			var elems = that.stat_desc.elems;
+			var n, m; // iterate over heap elements
+			var max_code = -1; // largest code with non zero frequency
+			var node; // new node being created
+
+			// Construct the initial heap, with least frequent element in
+			// heap[1]. The sons of heap[n] are heap[2*n] and heap[2*n+1].
+			// heap[0] is not used.
+			s.heap_len = 0;
+			s.heap_max = HEAP_SIZE;
+
+			for (n = 0; n < elems; n++) {
+				if (tree[n * 2] !== 0) {
+					s.heap[++s.heap_len] = max_code = n;
+					s.depth[n] = 0;
+				} else {
+					tree[n * 2 + 1] = 0;
+				}
+			}
+
+			// The pkzip format requires that at least one distance code exists,
+			// and that at least one bit should be sent even if there is only one
+			// possible code. So to avoid special checks later on we force at least
+			// two codes of non zero frequency.
+			while (s.heap_len < 2) {
+				node = s.heap[++s.heap_len] = max_code < 2 ? ++max_code : 0;
+				tree[node * 2] = 1;
+				s.depth[node] = 0;
+				s.opt_len--;
+				if (stree)
+					s.static_len -= stree[node * 2 + 1];
+				// node is 0 or 1 so it does not have extra bits
+			}
+			that.max_code = max_code;
+
+			// The elements heap[heap_len/2+1 .. heap_len] are leaves of the tree,
+			// establish sub-heaps of increasing lengths:
+
+			for (n = Math.floor(s.heap_len / 2); n >= 1; n--)
+				s.pqdownheap(tree, n);
+
+			// Construct the Huffman tree by repeatedly combining the least two
+			// frequent nodes.
+
+			node = elems; // next internal node of the tree
+			do {
+				// n = node of least frequency
+				n = s.heap[1];
+				s.heap[1] = s.heap[s.heap_len--];
+				s.pqdownheap(tree, 1);
+				m = s.heap[1]; // m = node of next least frequency
+
+				s.heap[--s.heap_max] = n; // keep the nodes sorted by frequency
+				s.heap[--s.heap_max] = m;
+
+				// Create a new node father of n and m
+				tree[node * 2] = (tree[n * 2] + tree[m * 2]);
+				s.depth[node] = Math.max(s.depth[n], s.depth[m]) + 1;
+				tree[n * 2 + 1] = tree[m * 2 + 1] = node;
+
+				// and insert the new node in the heap
+				s.heap[1] = node++;
+				s.pqdownheap(tree, 1);
+			} while (s.heap_len >= 2);
+
+			s.heap[--s.heap_max] = s.heap[1];
+
+			// At this point, the fields freq and dad are set. We can now
+			// generate the bit lengths.
+
+			gen_bitlen(s);
+
+			// The field len is now set, we can generate the bit codes
+			gen_codes(tree, that.max_code, s.bl_count);
+		};
+
+	}
+
+	Tree._length_code = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16,
+			16, 16, 16, 16, 17, 17, 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 19, 19, 19, 19, 19, 19, 19, 19, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+			20, 20, 20, 20, 20, 20, 20, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
+			22, 22, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
+			24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
+			25, 25, 25, 25, 25, 25, 25, 25, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26,
+			26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 28 ];
+
+	Tree.base_length = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 20, 24, 28, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 0 ];
+
+	Tree.base_dist = [ 0, 1, 2, 3, 4, 6, 8, 12, 16, 24, 32, 48, 64, 96, 128, 192, 256, 384, 512, 768, 1024, 1536, 2048, 3072, 4096, 6144, 8192, 12288, 16384,
+			24576 ];
+
+	// Mapping from a distance to a distance code. dist is the distance - 1 and
+	// must not have side effects. _dist_code[256] and _dist_code[257] are never
+	// used.
+	Tree.d_code = function(dist) {
+		return ((dist) < 256 ? _dist_code[dist] : _dist_code[256 + ((dist) >>> 7)]);
+	};
+
+	// extra bits for each length code
+	Tree.extra_lbits = [ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0 ];
+
+	// extra bits for each distance code
+	Tree.extra_dbits = [ 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13 ];
+
+	// extra bits for each bit length code
+	Tree.extra_blbits = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 3, 7 ];
+
+	Tree.bl_order = [ 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 ];
+
+	// StaticTree
+
+	function StaticTree(static_tree, extra_bits, extra_base, elems, max_length) {
+		var that = this;
+		that.static_tree = static_tree;
+		that.extra_bits = extra_bits;
+		that.extra_base = extra_base;
+		that.elems = elems;
+		that.max_length = max_length;
+	}
+
+	StaticTree.static_ltree = [ 12, 8, 140, 8, 76, 8, 204, 8, 44, 8, 172, 8, 108, 8, 236, 8, 28, 8, 156, 8, 92, 8, 220, 8, 60, 8, 188, 8, 124, 8, 252, 8, 2, 8,
+			130, 8, 66, 8, 194, 8, 34, 8, 162, 8, 98, 8, 226, 8, 18, 8, 146, 8, 82, 8, 210, 8, 50, 8, 178, 8, 114, 8, 242, 8, 10, 8, 138, 8, 74, 8, 202, 8, 42,
+			8, 170, 8, 106, 8, 234, 8, 26, 8, 154, 8, 90, 8, 218, 8, 58, 8, 186, 8, 122, 8, 250, 8, 6, 8, 134, 8, 70, 8, 198, 8, 38, 8, 166, 8, 102, 8, 230, 8,
+			22, 8, 150, 8, 86, 8, 214, 8, 54, 8, 182, 8, 118, 8, 246, 8, 14, 8, 142, 8, 78, 8, 206, 8, 46, 8, 174, 8, 110, 8, 238, 8, 30, 8, 158, 8, 94, 8,
+			222, 8, 62, 8, 190, 8, 126, 8, 254, 8, 1, 8, 129, 8, 65, 8, 193, 8, 33, 8, 161, 8, 97, 8, 225, 8, 17, 8, 145, 8, 81, 8, 209, 8, 49, 8, 177, 8, 113,
+			8, 241, 8, 9, 8, 137, 8, 73, 8, 201, 8, 41, 8, 169, 8, 105, 8, 233, 8, 25, 8, 153, 8, 89, 8, 217, 8, 57, 8, 185, 8, 121, 8, 249, 8, 5, 8, 133, 8,
+			69, 8, 197, 8, 37, 8, 165, 8, 101, 8, 229, 8, 21, 8, 149, 8, 85, 8, 213, 8, 53, 8, 181, 8, 117, 8, 245, 8, 13, 8, 141, 8, 77, 8, 205, 8, 45, 8,
+			173, 8, 109, 8, 237, 8, 29, 8, 157, 8, 93, 8, 221, 8, 61, 8, 189, 8, 125, 8, 253, 8, 19, 9, 275, 9, 147, 9, 403, 9, 83, 9, 339, 9, 211, 9, 467, 9,
+			51, 9, 307, 9, 179, 9, 435, 9, 115, 9, 371, 9, 243, 9, 499, 9, 11, 9, 267, 9, 139, 9, 395, 9, 75, 9, 331, 9, 203, 9, 459, 9, 43, 9, 299, 9, 171, 9,
+			427, 9, 107, 9, 363, 9, 235, 9, 491, 9, 27, 9, 283, 9, 155, 9, 411, 9, 91, 9, 347, 9, 219, 9, 475, 9, 59, 9, 315, 9, 187, 9, 443, 9, 123, 9, 379,
+			9, 251, 9, 507, 9, 7, 9, 263, 9, 135, 9, 391, 9, 71, 9, 327, 9, 199, 9, 455, 9, 39, 9, 295, 9, 167, 9, 423, 9, 103, 9, 359, 9, 231, 9, 487, 9, 23,
+			9, 279, 9, 151, 9, 407, 9, 87, 9, 343, 9, 215, 9, 471, 9, 55, 9, 311, 9, 183, 9, 439, 9, 119, 9, 375, 9, 247, 9, 503, 9, 15, 9, 271, 9, 143, 9,
+			399, 9, 79, 9, 335, 9, 207, 9, 463, 9, 47, 9, 303, 9, 175, 9, 431, 9, 111, 9, 367, 9, 239, 9, 495, 9, 31, 9, 287, 9, 159, 9, 415, 9, 95, 9, 351, 9,
+			223, 9, 479, 9, 63, 9, 319, 9, 191, 9, 447, 9, 127, 9, 383, 9, 255, 9, 511, 9, 0, 7, 64, 7, 32, 7, 96, 7, 16, 7, 80, 7, 48, 7, 112, 7, 8, 7, 72, 7,
+			40, 7, 104, 7, 24, 7, 88, 7, 56, 7, 120, 7, 4, 7, 68, 7, 36, 7, 100, 7, 20, 7, 84, 7, 52, 7, 116, 7, 3, 8, 131, 8, 67, 8, 195, 8, 35, 8, 163, 8,
+			99, 8, 227, 8 ];
+
+	StaticTree.static_dtree = [ 0, 5, 16, 5, 8, 5, 24, 5, 4, 5, 20, 5, 12, 5, 28, 5, 2, 5, 18, 5, 10, 5, 26, 5, 6, 5, 22, 5, 14, 5, 30, 5, 1, 5, 17, 5, 9, 5,
+			25, 5, 5, 5, 21, 5, 13, 5, 29, 5, 3, 5, 19, 5, 11, 5, 27, 5, 7, 5, 23, 5 ];
+
+	StaticTree.static_l_desc = new StaticTree(StaticTree.static_ltree, Tree.extra_lbits, LITERALS + 1, L_CODES, MAX_BITS);
+
+	StaticTree.static_d_desc = new StaticTree(StaticTree.static_dtree, Tree.extra_dbits, 0, D_CODES, MAX_BITS);
+
+	StaticTree.static_bl_desc = new StaticTree(null, Tree.extra_blbits, 0, BL_CODES, MAX_BL_BITS);
+
+	// Deflate
+
+	var MAX_MEM_LEVEL = 9;
+	var DEF_MEM_LEVEL = 8;
+
+	function Config(good_length, max_lazy, nice_length, max_chain, func) {
+		var that = this;
+		that.good_length = good_length;
+		that.max_lazy = max_lazy;
+		that.nice_length = nice_length;
+		that.max_chain = max_chain;
+		that.func = func;
+	}
+
+	var STORED = 0;
+	var FAST = 1;
+	var SLOW = 2;
+	var config_table = [ new Config(0, 0, 0, 0, STORED), new Config(4, 4, 8, 4, FAST), new Config(4, 5, 16, 8, FAST), new Config(4, 6, 32, 32, FAST),
+			new Config(4, 4, 16, 16, SLOW), new Config(8, 16, 32, 32, SLOW), new Config(8, 16, 128, 128, SLOW), new Config(8, 32, 128, 256, SLOW),
+			new Config(32, 128, 258, 1024, SLOW), new Config(32, 258, 258, 4096, SLOW) ];
+
+	var z_errmsg = [ "need dictionary", // Z_NEED_DICT
+	// 2
+	"stream end", // Z_STREAM_END 1
+	"", // Z_OK 0
+	"", // Z_ERRNO (-1)
+	"stream error", // Z_STREAM_ERROR (-2)
+	"data error", // Z_DATA_ERROR (-3)
+	"", // Z_MEM_ERROR (-4)
+	"buffer error", // Z_BUF_ERROR (-5)
+	"",// Z_VERSION_ERROR (-6)
+	"" ];
+
+	// block not completed, need more input or more output
+	var NeedMore = 0;
+
+	// block flush performed
+	var BlockDone = 1;
+
+	// finish started, need only more output at next deflate
+	var FinishStarted = 2;
+
+	// finish done, accept no more input or output
+	var FinishDone = 3;
+
+	// preset dictionary flag in zlib header
+	var PRESET_DICT = 0x20;
+
+	var INIT_STATE = 42;
+	var BUSY_STATE = 113;
+	var FINISH_STATE = 666;
+
+	// The deflate compression method
+	var Z_DEFLATED = 8;
+
+	var STORED_BLOCK = 0;
+	var STATIC_TREES = 1;
+	var DYN_TREES = 2;
+
+	var MIN_MATCH = 3;
+	var MAX_MATCH = 258;
+	var MIN_LOOKAHEAD = (MAX_MATCH + MIN_MATCH + 1);
+
+	function smaller(tree, n, m, depth) {
+		var tn2 = tree[n * 2];
+		var tm2 = tree[m * 2];
+		return (tn2 < tm2 || (tn2 == tm2 && depth[n] <= depth[m]));
+	}
+
+	function Deflate() {
+
+		var that = this;
+		var strm; // pointer back to this zlib stream
+		var status; // as the name implies
+		// pending_buf; // output still pending
+		var pending_buf_size; // size of pending_buf
+		// pending_out; // next pending byte to output to the stream
+		// pending; // nb of bytes in the pending buffer
+		var method; // STORED (for zip only) or DEFLATED
+		var last_flush; // value of flush param for previous deflate call
+
+		var w_size; // LZ77 window size (32K by default)
+		var w_bits; // log2(w_size) (8..16)
+		var w_mask; // w_size - 1
+
+		var window;
+		// Sliding window. Input bytes are read into the second half of the window,
+		// and move to the first half later to keep a dictionary of at least wSize
+		// bytes. With this organization, matches are limited to a distance of
+		// wSize-MAX_MATCH bytes, but this ensures that IO is always
+		// performed with a length multiple of the block size. Also, it limits
+		// the window size to 64K, which is quite useful on MSDOS.
+		// To do: use the user input buffer as sliding window.
+
+		var window_size;
+		// Actual size of window: 2*wSize, except when the user input buffer
+		// is directly used as sliding window.
+
+		var prev;
+		// Link to older string with same hash index. To limit the size of this
+		// array to 64K, this link is maintained only for the last 32K strings.
+		// An index in this array is thus a window index modulo 32K.
+
+		var head; // Heads of the hash chains or NIL.
+
+		var ins_h; // hash index of string to be inserted
+		var hash_size; // number of elements in hash table
+		var hash_bits; // log2(hash_size)
+		var hash_mask; // hash_size-1
+
+		// Number of bits by which ins_h must be shifted at each input
+		// step. It must be such that after MIN_MATCH steps, the oldest
+		// byte no longer takes part in the hash key, that is:
+		// hash_shift * MIN_MATCH >= hash_bits
+		var hash_shift;
+
+		// Window position at the beginning of the current output block. Gets
+		// negative when the window is moved backwards.
+
+		var block_start;
+
+		var match_length; // length of best match
+		var prev_match; // previous match
+		var match_available; // set if previous match exists
+		var strstart; // start of string to insert
+		var match_start; // start of matching string
+		var lookahead; // number of valid bytes ahead in window
+
+		// Length of the best match at previous step. Matches not greater than this
+		// are discarded. This is used in the lazy match evaluation.
+		var prev_length;
+
+		// To speed up deflation, hash chains are never searched beyond this
+		// length. A higher limit improves compression ratio but degrades the speed.
+		var max_chain_length;
+
+		// Attempt to find a better match only when the current match is strictly
+		// smaller than this value. This mechanism is used only for compression
+		// levels >= 4.
+		var max_lazy_match;
+
+		// Insert new strings in the hash table only if the match length is not
+		// greater than this length. This saves time but degrades compression.
+		// max_insert_length is used only for compression levels <= 3.
+
+		var level; // compression level (1..9)
+		var strategy; // favor or force Huffman coding
+
+		// Use a faster search when the previous match is longer than this
+		var good_match;
+
+		// Stop searching when current match exceeds this
+		var nice_match;
+
+		var dyn_ltree; // literal and length tree
+		var dyn_dtree; // distance tree
+		var bl_tree; // Huffman tree for bit lengths
+
+		var l_desc = new Tree(); // desc for literal tree
+		var d_desc = new Tree(); // desc for distance tree
+		var bl_desc = new Tree(); // desc for bit length tree
+
+		// that.heap_len; // number of elements in the heap
+		// that.heap_max; // element of largest frequency
+		// The sons of heap[n] are heap[2*n] and heap[2*n+1]. heap[0] is not used.
+		// The same heap array is used to build all trees.
+
+		// Depth of each subtree used as tie breaker for trees of equal frequency
+		that.depth = [];
+
+		var l_buf; // index for literals or lengths */
+
+		// Size of match buffer for literals/lengths. There are 4 reasons for
+		// limiting lit_bufsize to 64K:
+		// - frequencies can be kept in 16 bit counters
+		// - if compression is not successful for the first block, all input
+		// data is still in the window so we can still emit a stored block even
+		// when input comes from standard input. (This can also be done for
+		// all blocks if lit_bufsize is not greater than 32K.)
+		// - if compression is not successful for a file smaller than 64K, we can
+		// even emit a stored file instead of a stored block (saving 5 bytes).
+		// This is applicable only for zip (not gzip or zlib).
+		// - creating new Huffman trees less frequently may not provide fast
+		// adaptation to changes in the input data statistics. (Take for
+		// example a binary file with poorly compressible code followed by
+		// a highly compressible string table.) Smaller buffer sizes give
+		// fast adaptation but have of course the overhead of transmitting
+		// trees more frequently.
+		// - I can't count above 4
+		var lit_bufsize;
+
+		var last_lit; // running index in l_buf
+
+		// Buffer for distances. To simplify the code, d_buf and l_buf have
+		// the same number of elements. To use different lengths, an extra flag
+		// array would be necessary.
+
+		var d_buf; // index of pendig_buf
+
+		// that.opt_len; // bit length of current block with optimal trees
+		// that.static_len; // bit length of current block with static trees
+		var matches; // number of string matches in current block
+		var last_eob_len; // bit length of EOB code for last block
+
+		// Output buffer. bits are inserted starting at the bottom (least
+		// significant bits).
+		var bi_buf;
+
+		// Number of valid bits in bi_buf. All bits above the last valid bit
+		// are always zero.
+		var bi_valid;
+
+		// number of codes at each bit length for an optimal tree
+		that.bl_count = [];
+
+		// heap used to build the Huffman trees
+		that.heap = [];
+
+		dyn_ltree = [];
+		dyn_dtree = [];
+		bl_tree = [];
+
+		function lm_init() {
+			var i;
+			window_size = 2 * w_size;
+
+			head[hash_size - 1] = 0;
+			for (i = 0; i < hash_size - 1; i++) {
+				head[i] = 0;
+			}
+
+			// Set the default configuration parameters:
+			max_lazy_match = config_table[level].max_lazy;
+			good_match = config_table[level].good_length;
+			nice_match = config_table[level].nice_length;
+			max_chain_length = config_table[level].max_chain;
+
+			strstart = 0;
+			block_start = 0;
+			lookahead = 0;
+			match_length = prev_length = MIN_MATCH - 1;
+			match_available = 0;
+			ins_h = 0;
+		}
+
+		function init_block() {
+			var i;
+			// Initialize the trees.
+			for (i = 0; i < L_CODES; i++)
+				dyn_ltree[i * 2] = 0;
+			for (i = 0; i < D_CODES; i++)
+				dyn_dtree[i * 2] = 0;
+			for (i = 0; i < BL_CODES; i++)
+				bl_tree[i * 2] = 0;
+
+			dyn_ltree[END_BLOCK * 2] = 1;
+			that.opt_len = that.static_len = 0;
+			last_lit = matches = 0;
+		}
+
+		// Initialize the tree data structures for a new zlib stream.
+		function tr_init() {
+
+			l_desc.dyn_tree = dyn_ltree;
+			l_desc.stat_desc = StaticTree.static_l_desc;
+
+			d_desc.dyn_tree = dyn_dtree;
+			d_desc.stat_desc = StaticTree.static_d_desc;
+
+			bl_desc.dyn_tree = bl_tree;
+			bl_desc.stat_desc = StaticTree.static_bl_desc;
+
+			bi_buf = 0;
+			bi_valid = 0;
+			last_eob_len = 8; // enough lookahead for inflate
+
+			// Initialize the first block of the first file:
+			init_block();
+		}
+
+		// Restore the heap property by moving down the tree starting at node k,
+		// exchanging a node with the smallest of its two sons if necessary,
+		// stopping
+		// when the heap property is re-established (each father smaller than its
+		// two sons).
+		that.pqdownheap = function(tree, // the tree to restore
+		k // node to move down
+		) {
+			var heap = that.heap;
+			var v = heap[k];
+			var j = k << 1; // left son of k
+			while (j <= that.heap_len) {
+				// Set j to the smallest of the two sons:
+				if (j < that.heap_len && smaller(tree, heap[j + 1], heap[j], that.depth)) {
+					j++;
+				}
+				// Exit if v is smaller than both sons
+				if (smaller(tree, v, heap[j], that.depth))
+					break;
+
+				// Exchange v with the smallest son
+				heap[k] = heap[j];
+				k = j;
+				// And continue down the tree, setting j to the left son of k
+				j <<= 1;
+			}
+			heap[k] = v;
+		};
+
+		// Scan a literal or distance tree to determine the frequencies of the codes
+		// in the bit length tree.
+		function scan_tree(tree,// the tree to be scanned
+		max_code // and its largest code of non zero frequency
+		) {
+			var n; // iterates over all tree elements
+			var prevlen = -1; // last emitted length
+			var curlen; // length of current code
+			var nextlen = tree[0 * 2 + 1]; // length of next code
+			var count = 0; // repeat count of the current code
+			var max_count = 7; // max repeat count
+			var min_count = 4; // min repeat count
+
+			if (nextlen === 0) {
+				max_count = 138;
+				min_count = 3;
+			}
+			tree[(max_code + 1) * 2 + 1] = 0xffff; // guard
+
+			for (n = 0; n <= max_code; n++) {
+				curlen = nextlen;
+				nextlen = tree[(n + 1) * 2 + 1];
+				if (++count < max_count && curlen == nextlen) {
+					continue;
+				} else if (count < min_count) {
+					bl_tree[curlen * 2] += count;
+				} else if (curlen !== 0) {
+					if (curlen != prevlen)
+						bl_tree[curlen * 2]++;
+					bl_tree[REP_3_6 * 2]++;
+				} else if (count <= 10) {
+					bl_tree[REPZ_3_10 * 2]++;
+				} else {
+					bl_tree[REPZ_11_138 * 2]++;
+				}
+				count = 0;
+				prevlen = curlen;
+				if (nextlen === 0) {
+					max_count = 138;
+					min_count = 3;
+				} else if (curlen == nextlen) {
+					max_count = 6;
+					min_count = 3;
+				} else {
+					max_count = 7;
+					min_count = 4;
+				}
+			}
+		}
+
+		// Construct the Huffman tree for the bit lengths and return the index in
+		// bl_order of the last bit length code to send.
+		function build_bl_tree() {
+			var max_blindex; // index of last bit length code of non zero freq
+
+			// Determine the bit length frequencies for literal and distance trees
+			scan_tree(dyn_ltree, l_desc.max_code);
+			scan_tree(dyn_dtree, d_desc.max_code);
+
+			// Build the bit length tree:
+			bl_desc.build_tree(that);
+			// opt_len now includes the length of the tree representations, except
+			// the lengths of the bit lengths codes and the 5+5+4 bits for the
+			// counts.
+
+			// Determine the number of bit length codes to send. The pkzip format
+			// requires that at least 4 bit length codes be sent. (appnote.txt says
+			// 3 but the actual value used is 4.)
+			for (max_blindex = BL_CODES - 1; max_blindex >= 3; max_blindex--) {
+				if (bl_tree[Tree.bl_order[max_blindex] * 2 + 1] !== 0)
+					break;
+			}
+			// Update opt_len to include the bit length tree and counts
+			that.opt_len += 3 * (max_blindex + 1) + 5 + 5 + 4;
+
+			return max_blindex;
+		}
+
+		// Output a byte on the stream.
+		// IN assertion: there is enough room in pending_buf.
+		function put_byte(p) {
+			that.pending_buf[that.pending++] = p;
+		}
+
+		function put_short(w) {
+			put_byte(w & 0xff);
+			put_byte((w >>> 8) & 0xff);
+		}
+
+		function putShortMSB(b) {
+			put_byte((b >> 8) & 0xff);
+			put_byte((b & 0xff) & 0xff);
+		}
+
+		function send_bits(value, length) {
+			var val, len = length;
+			if (bi_valid > Buf_size - len) {
+				val = value;
+				// bi_buf |= (val << bi_valid);
+				bi_buf |= ((val << bi_valid) & 0xffff);
+				put_short(bi_buf);
+				bi_buf = val >>> (Buf_size - bi_valid);
+				bi_valid += len - Buf_size;
+			} else {
+				// bi_buf |= (value) << bi_valid;
+				bi_buf |= (((value) << bi_valid) & 0xffff);
+				bi_valid += len;
+			}
+		}
+
+		function send_code(c, tree) {
+			var c2 = c * 2;
+			send_bits(tree[c2] & 0xffff, tree[c2 + 1] & 0xffff);
+		}
+
+		// Send a literal or distance tree in compressed form, using the codes in
+		// bl_tree.
+		function send_tree(tree,// the tree to be sent
+		max_code // and its largest code of non zero frequency
+		) {
+			var n; // iterates over all tree elements
+			var prevlen = -1; // last emitted length
+			var curlen; // length of current code
+			var nextlen = tree[0 * 2 + 1]; // length of next code
+			var count = 0; // repeat count of the current code
+			var max_count = 7; // max repeat count
+			var min_count = 4; // min repeat count
+
+			if (nextlen === 0) {
+				max_count = 138;
+				min_count = 3;
+			}
+
+			for (n = 0; n <= max_code; n++) {
+				curlen = nextlen;
+				nextlen = tree[(n + 1) * 2 + 1];
+				if (++count < max_count && curlen == nextlen) {
+					continue;
+				} else if (count < min_count) {
+					do {
+						send_code(curlen, bl_tree);
+					} while (--count !== 0);
+				} else if (curlen !== 0) {
+					if (curlen != prevlen) {
+						send_code(curlen, bl_tree);
+						count--;
+					}
+					send_code(REP_3_6, bl_tree);
+					send_bits(count - 3, 2);
+				} else if (count <= 10) {
+					send_code(REPZ_3_10, bl_tree);
+					send_bits(count - 3, 3);
+				} else {
+					send_code(REPZ_11_138, bl_tree);
+					send_bits(count - 11, 7);
+				}
+				count = 0;
+				prevlen = curlen;
+				if (nextlen === 0) {
+					max_count = 138;
+					min_count = 3;
+				} else if (curlen == nextlen) {
+					max_count = 6;
+					min_count = 3;
+				} else {
+					max_count = 7;
+					min_count = 4;
+				}
+			}
+		}
+
+		// Send the header for a block using dynamic Huffman trees: the counts, the
+		// lengths of the bit length codes, the literal tree and the distance tree.
+		// IN assertion: lcodes >= 257, dcodes >= 1, blcodes >= 4.
+		function send_all_trees(lcodes, dcodes, blcodes) {
+			var rank; // index in bl_order
+
+			send_bits(lcodes - 257, 5); // not +255 as stated in appnote.txt
+			send_bits(dcodes - 1, 5);
+			send_bits(blcodes - 4, 4); // not -3 as stated in appnote.txt
+			for (rank = 0; rank < blcodes; rank++) {
+				send_bits(bl_tree[Tree.bl_order[rank] * 2 + 1], 3);
+			}
+			send_tree(dyn_ltree, lcodes - 1); // literal tree
+			send_tree(dyn_dtree, dcodes - 1); // distance tree
+		}
+
+		// Flush the bit buffer, keeping at most 7 bits in it.
+		function bi_flush() {
+			if (bi_valid == 16) {
+				put_short(bi_buf);
+				bi_buf = 0;
+				bi_valid = 0;
+			} else if (bi_valid >= 8) {
+				put_byte(bi_buf & 0xff);
+				bi_buf >>>= 8;
+				bi_valid -= 8;
+			}
+		}
+
+		// Send one empty static block to give enough lookahead for inflate.
+		// This takes 10 bits, of which 7 may remain in the bit buffer.
+		// The current inflate code requires 9 bits of lookahead. If the
+		// last two codes for the previous block (real code plus EOB) were coded
+		// on 5 bits or less, inflate may have only 5+3 bits of lookahead to decode
+		// the last real code. In this case we send two empty static blocks instead
+		// of one. (There are no problems if the previous block is stored or fixed.)
+		// To simplify the code, we assume the worst case of last real code encoded
+		// on one bit only.
+		function _tr_align() {
+			send_bits(STATIC_TREES << 1, 3);
+			send_code(END_BLOCK, StaticTree.static_ltree);
+
+			bi_flush();
+
+			// Of the 10 bits for the empty block, we have already sent
+			// (10 - bi_valid) bits. The lookahead for the last real code (before
+			// the EOB of the previous block) was thus at least one plus the length
+			// of the EOB plus what we have just sent of the empty static block.
+			if (1 + last_eob_len + 10 - bi_valid < 9) {
+				send_bits(STATIC_TREES << 1, 3);
+				send_code(END_BLOCK, StaticTree.static_ltree);
+				bi_flush();
+			}
+			last_eob_len = 7;
+		}
+
+		// Save the match info and tally the frequency counts. Return true if
+		// the current block must be flushed.
+		function _tr_tally(dist, // distance of matched string
+		lc // match length-MIN_MATCH or unmatched char (if dist==0)
+		) {
+			var out_length, in_length, dcode;
+			that.pending_buf[d_buf + last_lit * 2] = (dist >>> 8) & 0xff;
+			that.pending_buf[d_buf + last_lit * 2 + 1] = dist & 0xff;
+
+			that.pending_buf[l_buf + last_lit] = lc & 0xff;
+			last_lit++;
+
+			if (dist === 0) {
+				// lc is the unmatched char
+				dyn_ltree[lc * 2]++;
+			} else {
+				matches++;
+				// Here, lc is the match length - MIN_MATCH
+				dist--; // dist = match distance - 1
+				dyn_ltree[(Tree._length_code[lc] + LITERALS + 1) * 2]++;
+				dyn_dtree[Tree.d_code(dist) * 2]++;
+			}
+
+			if ((last_lit & 0x1fff) === 0 && level > 2) {
+				// Compute an upper bound for the compressed length
+				out_length = last_lit * 8;
+				in_length = strstart - block_start;
+				for (dcode = 0; dcode < D_CODES; dcode++) {
+					out_length += dyn_dtree[dcode * 2] * (5 + Tree.extra_dbits[dcode]);
+				}
+				out_length >>>= 3;
+				if ((matches < Math.floor(last_lit / 2)) && out_length < Math.floor(in_length / 2))
+					return true;
+			}
+
+			return (last_lit == lit_bufsize - 1);
+			// We avoid equality with lit_bufsize because of wraparound at 64K
+			// on 16 bit machines and because stored blocks are restricted to
+			// 64K-1 bytes.
+		}
+
+		// Send the block data compressed using the given Huffman trees
+		function compress_block(ltree, dtree) {
+			var dist; // distance of matched string
+			var lc; // match length or unmatched char (if dist === 0)
+			var lx = 0; // running index in l_buf
+			var code; // the code to send
+			var extra; // number of extra bits to send
+
+			if (last_lit !== 0) {
+				do {
+					dist = ((that.pending_buf[d_buf + lx * 2] << 8) & 0xff00) | (that.pending_buf[d_buf + lx * 2 + 1] & 0xff);
+					lc = (that.pending_buf[l_buf + lx]) & 0xff;
+					lx++;
+
+					if (dist === 0) {
+						send_code(lc, ltree); // send a literal byte
+					} else {
+						// Here, lc is the match length - MIN_MATCH
+						code = Tree._length_code[lc];
+
+						send_code(code + LITERALS + 1, ltree); // send the length
+						// code
+						extra = Tree.extra_lbits[code];
+						if (extra !== 0) {
+							lc -= Tree.base_length[code];
+							send_bits(lc, extra); // send the extra length bits
+						}
+						dist--; // dist is now the match distance - 1
+						code = Tree.d_code(dist);
+
+						send_code(code, dtree); // send the distance code
+						extra = Tree.extra_dbits[code];
+						if (extra !== 0) {
+							dist -= Tree.base_dist[code];
+							send_bits(dist, extra); // send the extra distance bits
+						}
+					} // literal or match pair ?
+
+					// Check that the overlay between pending_buf and d_buf+l_buf is
+					// ok:
+				} while (lx < last_lit);
+			}
+
+			send_code(END_BLOCK, ltree);
+			last_eob_len = ltree[END_BLOCK * 2 + 1];
+		}
+
+		// Flush the bit buffer and align the output on a byte boundary
+		function bi_windup() {
+			if (bi_valid > 8) {
+				put_short(bi_buf);
+			} else if (bi_valid > 0) {
+				put_byte(bi_buf & 0xff);
+			}
+			bi_buf = 0;
+			bi_valid = 0;
+		}
+
+		// Copy a stored block, storing first the length and its
+		// one's complement if requested.
+		function copy_block(buf, // the input data
+		len, // its length
+		header // true if block header must be written
+		) {
+			bi_windup(); // align on byte boundary
+			last_eob_len = 8; // enough lookahead for inflate
+
+			if (header) {
+				put_short(len);
+				put_short(~len);
+			}
+
+			that.pending_buf.set(window.subarray(buf, buf + len), that.pending);
+			that.pending += len;
+		}
+
+		// Send a stored block
+		function _tr_stored_block(buf, // input block
+		stored_len, // length of input block
+		eof // true if this is the last block for a file
+		) {
+			send_bits((STORED_BLOCK << 1) + (eof ? 1 : 0), 3); // send block type
+			copy_block(buf, stored_len, true); // with header
+		}
+
+		// Determine the best encoding for the current block: dynamic trees, static
+		// trees or store, and output the encoded block to the zip file.
+		function _tr_flush_block(buf, // input block, or NULL if too old
+		stored_len, // length of input block
+		eof // true if this is the last block for a file
+		) {
+			var opt_lenb, static_lenb;// opt_len and static_len in bytes
+			var max_blindex = 0; // index of last bit length code of non zero freq
+
+			// Build the Huffman trees unless a stored block is forced
+			if (level > 0) {
+				// Construct the literal and distance trees
+				l_desc.build_tree(that);
+
+				d_desc.build_tree(that);
+
+				// At this point, opt_len and static_len are the total bit lengths
+				// of
+				// the compressed block data, excluding the tree representations.
+
+				// Build the bit length tree for the above two trees, and get the
+				// index
+				// in bl_order of the last bit length code to send.
+				max_blindex = build_bl_tree();
+
+				// Determine the best encoding. Compute first the block length in
+				// bytes
+				opt_lenb = (that.opt_len + 3 + 7) >>> 3;
+				static_lenb = (that.static_len + 3 + 7) >>> 3;
+
+				if (static_lenb <= opt_lenb)
+					opt_lenb = static_lenb;
+			} else {
+				opt_lenb = static_lenb = stored_len + 5; // force a stored block
+			}
+
+			if ((stored_len + 4 <= opt_lenb) && buf != -1) {
+				// 4: two words for the lengths
+				// The test buf != NULL is only necessary if LIT_BUFSIZE > WSIZE.
+				// Otherwise we can't have processed more than WSIZE input bytes
+				// since
+				// the last block flush, because compression would have been
+				// successful. If LIT_BUFSIZE <= WSIZE, it is never too late to
+				// transform a block into a stored block.
+				_tr_stored_block(buf, stored_len, eof);
+			} else if (static_lenb == opt_lenb) {
+				send_bits((STATIC_TREES << 1) + (eof ? 1 : 0), 3);
+				compress_block(StaticTree.static_ltree, StaticTree.static_dtree);
+			} else {
+				send_bits((DYN_TREES << 1) + (eof ? 1 : 0), 3);
+				send_all_trees(l_desc.max_code + 1, d_desc.max_code + 1, max_blindex + 1);
+				compress_block(dyn_ltree, dyn_dtree);
+			}
+
+			// The above check is made mod 2^32, for files larger than 512 MB
+			// and uLong implemented on 32 bits.
+
+			init_block();
+
+			if (eof) {
+				bi_windup();
+			}
+		}
+
+		function flush_block_only(eof) {
+			_tr_flush_block(block_start >= 0 ? block_start : -1, strstart - block_start, eof);
+			block_start = strstart;
+			strm.flush_pending();
+		}
+
+		// Fill the window when the lookahead becomes insufficient.
+		// Updates strstart and lookahead.
+		//
+		// IN assertion: lookahead < MIN_LOOKAHEAD
+		// OUT assertions: strstart <= window_size-MIN_LOOKAHEAD
+		// At least one byte has been read, or avail_in === 0; reads are
+		// performed for at least two bytes (required for the zip translate_eol
+		// option -- not supported here).
+		function fill_window() {
+			var n, m;
+			var p;
+			var more; // Amount of free space at the end of the window.
+
+			do {
+				more = (window_size - lookahead - strstart);
+
+				// Deal with !@#$% 64K limit:
+				if (more === 0 && strstart === 0 && lookahead === 0) {
+					more = w_size;
+				} else if (more == -1) {
+					// Very unlikely, but possible on 16 bit machine if strstart ==
+					// 0
+					// and lookahead == 1 (input done one byte at time)
+					more--;
+
+					// If the window is almost full and there is insufficient
+					// lookahead,
+					// move the upper half to the lower one to make room in the
+					// upper half.
+				} else if (strstart >= w_size + w_size - MIN_LOOKAHEAD) {
+					window.set(window.subarray(w_size, w_size + w_size), 0);
+
+					match_start -= w_size;
+					strstart -= w_size; // we now have strstart >= MAX_DIST
+					block_start -= w_size;
+
+					// Slide the hash table (could be avoided with 32 bit values
+					// at the expense of memory usage). We slide even when level ==
+					// 0
+					// to keep the hash table consistent if we switch back to level
+					// > 0
+					// later. (Using level 0 permanently is not an optimal usage of
+					// zlib, so we don't care about this pathological case.)
+
+					n = hash_size;
+					p = n;
+					do {
+						m = (head[--p] & 0xffff);
+						head[p] = (m >= w_size ? m - w_size : 0);
+					} while (--n !== 0);
+
+					n = w_size;
+					p = n;
+					do {
+						m = (prev[--p] & 0xffff);
+						prev[p] = (m >= w_size ? m - w_size : 0);
+						// If n is not on any hash chain, prev[n] is garbage but
+						// its value will never be used.
+					} while (--n !== 0);
+					more += w_size;
+				}
+
+				if (strm.avail_in === 0)
+					return;
+
+				// If there was no sliding:
+				// strstart <= WSIZE+MAX_DIST-1 && lookahead <= MIN_LOOKAHEAD - 1 &&
+				// more == window_size - lookahead - strstart
+				// => more >= window_size - (MIN_LOOKAHEAD-1 + WSIZE + MAX_DIST-1)
+				// => more >= window_size - 2*WSIZE + 2
+				// In the BIG_MEM or MMAP case (not yet supported),
+				// window_size == input_size + MIN_LOOKAHEAD &&
+				// strstart + s->lookahead <= input_size => more >= MIN_LOOKAHEAD.
+				// Otherwise, window_size == 2*WSIZE so more >= 2.
+				// If there was sliding, more >= WSIZE. So in all cases, more >= 2.
+
+				n = strm.read_buf(window, strstart + lookahead, more);
+				lookahead += n;
+
+				// Initialize the hash value now that we have some input:
+				if (lookahead >= MIN_MATCH) {
+					ins_h = window[strstart] & 0xff;
+					ins_h = (((ins_h) << hash_shift) ^ (window[strstart + 1] & 0xff)) & hash_mask;
+				}
+				// If the whole input has less than MIN_MATCH bytes, ins_h is
+				// garbage,
+				// but this is not important since only literal bytes will be
+				// emitted.
+			} while (lookahead < MIN_LOOKAHEAD && strm.avail_in !== 0);
+		}
+
+		// Copy without compression as much as possible from the input stream,
+		// return
+		// the current block state.
+		// This function does not insert new strings in the dictionary since
+		// uncompressible data is probably not useful. This function is used
+		// only for the level=0 compression option.
+		// NOTE: this function should be optimized to avoid extra copying from
+		// window to pending_buf.
+		function deflate_stored(flush) {
+			// Stored blocks are limited to 0xffff bytes, pending_buf is limited
+			// to pending_buf_size, and each stored block has a 5 byte header:
+
+			var max_block_size = 0xffff;
+			var max_start;
+
+			if (max_block_size > pending_buf_size - 5) {
+				max_block_size = pending_buf_size - 5;
+			}
+
+			// Copy as much as possible from input to output:
+			while (true) {
+				// Fill the window as much as possible:
+				if (lookahead <= 1) {
+					fill_window();
+					if (lookahead === 0 && flush == Z_NO_FLUSH)
+						return NeedMore;
+					if (lookahead === 0)
+						break; // flush the current block
+				}
+
+				strstart += lookahead;
+				lookahead = 0;
+
+				// Emit a stored block if pending_buf will be full:
+				max_start = block_start + max_block_size;
+				if (strstart === 0 || strstart >= max_start) {
+					// strstart === 0 is possible when wraparound on 16-bit machine
+					lookahead = (strstart - max_start);
+					strstart = max_start;
+
+					flush_block_only(false);
+					if (strm.avail_out === 0)
+						return NeedMore;
+
+				}
+
+				// Flush if we may have to slide, otherwise block_start may become
+				// negative and the data will be gone:
+				if (strstart - block_start >= w_size - MIN_LOOKAHEAD) {
+					flush_block_only(false);
+					if (strm.avail_out === 0)
+						return NeedMore;
+				}
+			}
+
+			flush_block_only(flush == Z_FINISH);
+			if (strm.avail_out === 0)
+				return (flush == Z_FINISH) ? FinishStarted : NeedMore;
+
+			return flush == Z_FINISH ? FinishDone : BlockDone;
+		}
+
+		function longest_match(cur_match) {
+			var chain_length = max_chain_length; // max hash chain length
+			var scan = strstart; // current string
+			var match; // matched string
+			var len; // length of current match
+			var best_len = prev_length; // best match length so far
+			var limit = strstart > (w_size - MIN_LOOKAHEAD) ? strstart - (w_size - MIN_LOOKAHEAD) : 0;
+			var _nice_match = nice_match;
+
+			// Stop when cur_match becomes <= limit. To simplify the code,
+			// we prevent matches with the string of window index 0.
+
+			var wmask = w_mask;
+
+			var strend = strstart + MAX_MATCH;
+			var scan_end1 = window[scan + best_len - 1];
+			var scan_end = window[scan + best_len];
+
+			// The code is optimized for HASH_BITS >= 8 and MAX_MATCH-2 multiple of
+			// 16.
+			// It is easy to get rid of this optimization if necessary.
+
+			// Do not waste too much time if we already have a good match:
+			if (prev_length >= good_match) {
+				chain_length >>= 2;
+			}
+
+			// Do not look for matches beyond the end of the input. This is
+			// necessary
+			// to make deflate deterministic.
+			if (_nice_match > lookahead)
+				_nice_match = lookahead;
+
+			do {
+				match = cur_match;
+
+				// Skip to next match if the match length cannot increase
+				// or if the match length is less than 2:
+				if (window[match + best_len] != scan_end || window[match + best_len - 1] != scan_end1 || window[match] != window[scan]
+						|| window[++match] != window[scan + 1])
+					continue;
+
+				// The check at best_len-1 can be removed because it will be made
+				// again later. (This heuristic is not always a win.)
+				// It is not necessary to compare scan[2] and match[2] since they
+				// are always equal when the other bytes match, given that
+				// the hash keys are equal and that HASH_BITS >= 8.
+				scan += 2;
+				match++;
+
+				// We check for insufficient lookahead only every 8th comparison;
+				// the 256th check will be made at strstart+258.
+				do {
+				} while (window[++scan] == window[++match] && window[++scan] == window[++match] && window[++scan] == window[++match]
+						&& window[++scan] == window[++match] && window[++scan] == window[++match] && window[++scan] == window[++match]
+						&& window[++scan] == window[++match] && window[++scan] == window[++match] && scan < strend);
+
+				len = MAX_MATCH - (strend - scan);
+				scan = strend - MAX_MATCH;
+
+				if (len > best_len) {
+					match_start = cur_match;
+					best_len = len;
+					if (len >= _nice_match)
+						break;
+					scan_end1 = window[scan + best_len - 1];
+					scan_end = window[scan + best_len];
+				}
+
+			} while ((cur_match = (prev[cur_match & wmask] & 0xffff)) > limit && --chain_length !== 0);
+
+			if (best_len <= lookahead)
+				return best_len;
+			return lookahead;
+		}
+
+		// Compress as much as possible from the input stream, return the current
+		// block state.
+		// This function does not perform lazy evaluation of matches and inserts
+		// new strings in the dictionary only for unmatched strings or for short
+		// matches. It is used only for the fast compression options.
+		function deflate_fast(flush) {
+			// short hash_head = 0; // head of the hash chain
+			var hash_head = 0; // head of the hash chain
+			var bflush; // set if current block must be flushed
+
+			while (true) {
+				// Make sure that we always have enough lookahead, except
+				// at the end of the input file. We need MAX_MATCH bytes
+				// for the next match, plus MIN_MATCH bytes to insert the
+				// string following the next match.
+				if (lookahead < MIN_LOOKAHEAD) {
+					fill_window();
+					if (lookahead < MIN_LOOKAHEAD && flush == Z_NO_FLUSH) {
+						return NeedMore;
+					}
+					if (lookahead === 0)
+						break; // flush the current block
+				}
+
+				// Insert the string window[strstart .. strstart+2] in the
+				// dictionary, and set hash_head to the head of the hash chain:
+				if (lookahead >= MIN_MATCH) {
+					ins_h = (((ins_h) << hash_shift) ^ (window[(strstart) + (MIN_MATCH - 1)] & 0xff)) & hash_mask;
+
+					// prev[strstart&w_mask]=hash_head=head[ins_h];
+					hash_head = (head[ins_h] & 0xffff);
+					prev[strstart & w_mask] = head[ins_h];
+					head[ins_h] = strstart;
+				}
+
+				// Find the longest match, discarding those <= prev_length.
+				// At this point we have always match_length < MIN_MATCH
+
+				if (hash_head !== 0 && ((strstart - hash_head) & 0xffff) <= w_size - MIN_LOOKAHEAD) {
+					// To simplify the code, we prevent matches with the string
+					// of window index 0 (in particular we have to avoid a match
+					// of the string with itself at the start of the input file).
+					if (strategy != Z_HUFFMAN_ONLY) {
+						match_length = longest_match(hash_head);
+					}
+					// longest_match() sets match_start
+				}
+				if (match_length >= MIN_MATCH) {
+					// check_match(strstart, match_start, match_length);
+
+					bflush = _tr_tally(strstart - match_start, match_length - MIN_MATCH);
+
+					lookahead -= match_length;
+
+					// Insert new strings in the hash table only if the match length
+					// is not too large. This saves time but degrades compression.
+					if (match_length <= max_lazy_match && lookahead >= MIN_MATCH) {
+						match_length--; // string at strstart already in hash table
+						do {
+							strstart++;
+
+							ins_h = ((ins_h << hash_shift) ^ (window[(strstart) + (MIN_MATCH - 1)] & 0xff)) & hash_mask;
+							// prev[strstart&w_mask]=hash_head=head[ins_h];
+							hash_head = (head[ins_h] & 0xffff);
+							prev[strstart & w_mask] = head[ins_h];
+							head[ins_h] = strstart;
+
+							// strstart never exceeds WSIZE-MAX_MATCH, so there are
+							// always MIN_MATCH bytes ahead.
+						} while (--match_length !== 0);
+						strstart++;
+					} else {
+						strstart += match_length;
+						match_length = 0;
+						ins_h = window[strstart] & 0xff;
+
+						ins_h = (((ins_h) << hash_shift) ^ (window[strstart + 1] & 0xff)) & hash_mask;
+						// If lookahead < MIN_MATCH, ins_h is garbage, but it does
+						// not
+						// matter since it will be recomputed at next deflate call.
+					}
+				} else {
+					// No match, output a literal byte
+
+					bflush = _tr_tally(0, window[strstart] & 0xff);
+					lookahead--;
+					strstart++;
+				}
+				if (bflush) {
+
+					flush_block_only(false);
+					if (strm.avail_out === 0)
+						return NeedMore;
+				}
+			}
+
+			flush_block_only(flush == Z_FINISH);
+			if (strm.avail_out === 0) {
+				if (flush == Z_FINISH)
+					return FinishStarted;
+				else
+					return NeedMore;
+			}
+			return flush == Z_FINISH ? FinishDone : BlockDone;
+		}
+
+		// Same as above, but achieves better compression. We use a lazy
+		// evaluation for matches: a match is finally adopted only if there is
+		// no better match at the next window position.
+		function deflate_slow(flush) {
+			// short hash_head = 0; // head of hash chain
+			var hash_head = 0; // head of hash chain
+			var bflush; // set if current block must be flushed
+			var max_insert;
+
+			// Process the input block.
+			while (true) {
+				// Make sure that we always have enough lookahead, except
+				// at the end of the input file. We need MAX_MATCH bytes
+				// for the next match, plus MIN_MATCH bytes to insert the
+				// string following the next match.
+
+				if (lookahead < MIN_LOOKAHEAD) {
+					fill_window();
+					if (lookahead < MIN_LOOKAHEAD && flush == Z_NO_FLUSH) {
+						return NeedMore;
+					}
+					if (lookahead === 0)
+						break; // flush the current block
+				}
+
+				// Insert the string window[strstart .. strstart+2] in the
+				// dictionary, and set hash_head to the head of the hash chain:
+
+				if (lookahead >= MIN_MATCH) {
+					ins_h = (((ins_h) << hash_shift) ^ (window[(strstart) + (MIN_MATCH - 1)] & 0xff)) & hash_mask;
+					// prev[strstart&w_mask]=hash_head=head[ins_h];
+					hash_head = (head[ins_h] & 0xffff);
+					prev[strstart & w_mask] = head[ins_h];
+					head[ins_h] = strstart;
+				}
+
+				// Find the longest match, discarding those <= prev_length.
+				prev_length = match_length;
+				prev_match = match_start;
+				match_length = MIN_MATCH - 1;
+
+				if (hash_head !== 0 && prev_length < max_lazy_match && ((strstart - hash_head) & 0xffff) <= w_size - MIN_LOOKAHEAD) {
+					// To simplify the code, we prevent matches with the string
+					// of window index 0 (in particular we have to avoid a match
+					// of the string with itself at the start of the input file).
+
+					if (strategy != Z_HUFFMAN_ONLY) {
+						match_length = longest_match(hash_head);
+					}
+					// longest_match() sets match_start
+
+					if (match_length <= 5 && (strategy == Z_FILTERED || (match_length == MIN_MATCH && strstart - match_start > 4096))) {
+
+						// If prev_match is also MIN_MATCH, match_start is garbage
+						// but we will ignore the current match anyway.
+						match_length = MIN_MATCH - 1;
+					}
+				}
+
+				// If there was a match at the previous step and the current
+				// match is not better, output the previous match:
+				if (prev_length >= MIN_MATCH && match_length <= prev_length) {
+					max_insert = strstart + lookahead - MIN_MATCH;
+					// Do not insert strings in hash table beyond this.
+
+					// check_match(strstart-1, prev_match, prev_length);
+
+					bflush = _tr_tally(strstart - 1 - prev_match, prev_length - MIN_MATCH);
+
+					// Insert in hash table all strings up to the end of the match.
+					// strstart-1 and strstart are already inserted. If there is not
+					// enough lookahead, the last two strings are not inserted in
+					// the hash table.
+					lookahead -= prev_length - 1;
+					prev_length -= 2;
+					do {
+						if (++strstart <= max_insert) {
+							ins_h = (((ins_h) << hash_shift) ^ (window[(strstart) + (MIN_MATCH - 1)] & 0xff)) & hash_mask;
+							// prev[strstart&w_mask]=hash_head=head[ins_h];
+							hash_head = (head[ins_h] & 0xffff);
+							prev[strstart & w_mask] = head[ins_h];
+							head[ins_h] = strstart;
+						}
+					} while (--prev_length !== 0);
+					match_available = 0;
+					match_length = MIN_MATCH - 1;
+					strstart++;
+
+					if (bflush) {
+						flush_block_only(false);
+						if (strm.avail_out === 0)
+							return NeedMore;
+					}
+				} else if (match_available !== 0) {
+
+					// If there was no match at the previous position, output a
+					// single literal. If there was a match but the current match
+					// is longer, truncate the previous match to a single literal.
+
+					bflush = _tr_tally(0, window[strstart - 1] & 0xff);
+
+					if (bflush) {
+						flush_block_only(false);
+					}
+					strstart++;
+					lookahead--;
+					if (strm.avail_out === 0)
+						return NeedMore;
+				} else {
+					// There is no previous match to compare with, wait for
+					// the next step to decide.
+
+					match_available = 1;
+					strstart++;
+					lookahead--;
+				}
+			}
+
+			if (match_available !== 0) {
+				bflush = _tr_tally(0, window[strstart - 1] & 0xff);
+				match_available = 0;
+			}
+			flush_block_only(flush == Z_FINISH);
+
+			if (strm.avail_out === 0) {
+				if (flush == Z_FINISH)
+					return FinishStarted;
+				else
+					return NeedMore;
+			}
+
+			return flush == Z_FINISH ? FinishDone : BlockDone;
+		}
+
+		function deflateReset(strm) {
+			strm.total_in = strm.total_out = 0;
+			strm.msg = null; //
+			
+			that.pending = 0;
+			that.pending_out = 0;
+
+			status = BUSY_STATE;
+
+			last_flush = Z_NO_FLUSH;
+
+			tr_init();
+			lm_init();
+			return Z_OK;
+		}
+
+		that.deflateInit = function(strm, _level, bits, _method, memLevel, _strategy) {
+			if (!_method)
+				_method = Z_DEFLATED;
+			if (!memLevel)
+				memLevel = DEF_MEM_LEVEL;
+			if (!_strategy)
+				_strategy = Z_DEFAULT_STRATEGY;
+
+			// byte[] my_version=ZLIB_VERSION;
+
+			//
+			// if (!version || version[0] != my_version[0]
+			// || stream_size != sizeof(z_stream)) {
+			// return Z_VERSION_ERROR;
+			// }
+
+			strm.msg = null;
+
+			if (_level == Z_DEFAULT_COMPRESSION)
+				_level = 6;
+
+			if (memLevel < 1 || memLevel > MAX_MEM_LEVEL || _method != Z_DEFLATED || bits < 9 || bits > 15 || _level < 0 || _level > 9 || _strategy < 0
+					|| _strategy > Z_HUFFMAN_ONLY) {
+				return Z_STREAM_ERROR;
+			}
+
+			strm.dstate = that;
+
+			w_bits = bits;
+			w_size = 1 << w_bits;
+			w_mask = w_size - 1;
+
+			hash_bits = memLevel + 7;
+			hash_size = 1 << hash_bits;
+			hash_mask = hash_size - 1;
+			hash_shift = Math.floor((hash_bits + MIN_MATCH - 1) / MIN_MATCH);
+
+			window = new Uint8Array(w_size * 2);
+			prev = [];
+			head = [];
+
+			lit_bufsize = 1 << (memLevel + 6); // 16K elements by default
+
+			// We overlay pending_buf and d_buf+l_buf. This works since the average
+			// output size for (length,distance) codes is <= 24 bits.
+			that.pending_buf = new Uint8Array(lit_bufsize * 4);
+			pending_buf_size = lit_bufsize * 4;
+
+			d_buf = Math.floor(lit_bufsize / 2);
+			l_buf = (1 + 2) * lit_bufsize;
+
+			level = _level;
+
+			strategy = _strategy;
+			method = _method & 0xff;
+
+			return deflateReset(strm);
+		};
+
+		that.deflateEnd = function() {
+			if (status != INIT_STATE && status != BUSY_STATE && status != FINISH_STATE) {
+				return Z_STREAM_ERROR;
+			}
+			// Deallocate in reverse order of allocations:
+			that.pending_buf = null;
+			head = null;
+			prev = null;
+			window = null;
+			// free
+			that.dstate = null;
+			return status == BUSY_STATE ? Z_DATA_ERROR : Z_OK;
+		};
+
+		that.deflateParams = function(strm, _level, _strategy) {
+			var err = Z_OK;
+
+			if (_level == Z_DEFAULT_COMPRESSION) {
+				_level = 6;
+			}
+			if (_level < 0 || _level > 9 || _strategy < 0 || _strategy > Z_HUFFMAN_ONLY) {
+				return Z_STREAM_ERROR;
+			}
+
+			if (config_table[level].func != config_table[_level].func && strm.total_in !== 0) {
+				// Flush the last buffer:
+				err = strm.deflate(Z_PARTIAL_FLUSH);
+			}
+
+			if (level != _level) {
+				level = _level;
+				max_lazy_match = config_table[level].max_lazy;
+				good_match = config_table[level].good_length;
+				nice_match = config_table[level].nice_length;
+				max_chain_length = config_table[level].max_chain;
+			}
+			strategy = _strategy;
+			return err;
+		};
+
+		that.deflateSetDictionary = function(strm, dictionary, dictLength) {
+			var length = dictLength;
+			var n, index = 0;
+
+			if (!dictionary || status != INIT_STATE)
+				return Z_STREAM_ERROR;
+
+			if (length < MIN_MATCH)
+				return Z_OK;
+			if (length > w_size - MIN_LOOKAHEAD) {
+				length = w_size - MIN_LOOKAHEAD;
+				index = dictLength - length; // use the tail of the dictionary
+			}
+			window.set(dictionary.subarray(index, index + length), 0);
+
+			strstart = length;
+			block_start = length;
+
+			// Insert all strings in the hash table (except for the last two bytes).
+			// s->lookahead stays null, so s->ins_h will be recomputed at the next
+			// call of fill_window.
+
+			ins_h = window[0] & 0xff;
+			ins_h = (((ins_h) << hash_shift) ^ (window[1] & 0xff)) & hash_mask;
+
+			for (n = 0; n <= length - MIN_MATCH; n++) {
+				ins_h = (((ins_h) << hash_shift) ^ (window[(n) + (MIN_MATCH - 1)] & 0xff)) & hash_mask;
+				prev[n & w_mask] = head[ins_h];
+				head[ins_h] = n;
+			}
+			return Z_OK;
+		};
+
+		that.deflate = function(_strm, flush) {
+			var i, header, level_flags, old_flush, bstate;
+
+			if (flush > Z_FINISH || flush < 0) {
+				return Z_STREAM_ERROR;
+			}
+
+			if (!_strm.next_out || (!_strm.next_in && _strm.avail_in !== 0) || (status == FINISH_STATE && flush != Z_FINISH)) {
+				_strm.msg = z_errmsg[Z_NEED_DICT - (Z_STREAM_ERROR)];
+				return Z_STREAM_ERROR;
+			}
+			if (_strm.avail_out === 0) {
+				_strm.msg = z_errmsg[Z_NEED_DICT - (Z_BUF_ERROR)];
+				return Z_BUF_ERROR;
+			}
+
+			strm = _strm; // just in case
+			old_flush = last_flush;
+			last_flush = flush;
+
+			// Write the zlib header
+			if (status == INIT_STATE) {
+				header = (Z_DEFLATED + ((w_bits - 8) << 4)) << 8;
+				level_flags = ((level - 1) & 0xff) >> 1;
+
+				if (level_flags > 3)
+					level_flags = 3;
+				header |= (level_flags << 6);
+				if (strstart !== 0)
+					header |= PRESET_DICT;
+				header += 31 - (header % 31);
+
+				status = BUSY_STATE;
+				putShortMSB(header);
+			}
+
+			// Flush as much pending output as possible
+			if (that.pending !== 0) {
+				strm.flush_pending();
+				if (strm.avail_out === 0) {
+					// console.log(" avail_out==0");
+					// Since avail_out is 0, deflate will be called again with
+					// more output space, but possibly with both pending and
+					// avail_in equal to zero. There won't be anything to do,
+					// but this is not an error situation so make sure we
+					// return OK instead of BUF_ERROR at next call of deflate:
+					last_flush = -1;
+					return Z_OK;
+				}
+
+				// Make sure there is something to do and avoid duplicate
+				// consecutive
+				// flushes. For repeated and useless calls with Z_FINISH, we keep
+				// returning Z_STREAM_END instead of Z_BUFF_ERROR.
+			} else if (strm.avail_in === 0 && flush <= old_flush && flush != Z_FINISH) {
+				strm.msg = z_errmsg[Z_NEED_DICT - (Z_BUF_ERROR)];
+				return Z_BUF_ERROR;
+			}
+
+			// User must not provide more input after the first FINISH:
+			if (status == FINISH_STATE && strm.avail_in !== 0) {
+				_strm.msg = z_errmsg[Z_NEED_DICT - (Z_BUF_ERROR)];
+				return Z_BUF_ERROR;
+			}
+
+			// Start a new block or continue the current one.
+			if (strm.avail_in !== 0 || lookahead !== 0 || (flush != Z_NO_FLUSH && status != FINISH_STATE)) {
+				bstate = -1;
+				switch (config_table[level].func) {
+				case STORED:
+					bstate = deflate_stored(flush);
+					break;
+				case FAST:
+					bstate = deflate_fast(flush);
+					break;
+				case SLOW:
+					bstate = deflate_slow(flush);
+					break;
+				default:
+				}
+
+				if (bstate == FinishStarted || bstate == FinishDone) {
+					status = FINISH_STATE;
+				}
+				if (bstate == NeedMore || bstate == FinishStarted) {
+					if (strm.avail_out === 0) {
+						last_flush = -1; // avoid BUF_ERROR next call, see above
+					}
+					return Z_OK;
+					// If flush != Z_NO_FLUSH && avail_out === 0, the next call
+					// of deflate should use the same flush parameter to make sure
+					// that the flush is complete. So we don't have to output an
+					// empty block here, this will be done at next call. This also
+					// ensures that for a very small output buffer, we emit at most
+					// one empty block.
+				}
+
+				if (bstate == BlockDone) {
+					if (flush == Z_PARTIAL_FLUSH) {
+						_tr_align();
+					} else { // FULL_FLUSH or SYNC_FLUSH
+						_tr_stored_block(0, 0, false);
+						// For a full flush, this empty block will be recognized
+						// as a special marker by inflate_sync().
+						if (flush == Z_FULL_FLUSH) {
+							// state.head[s.hash_size-1]=0;
+							for (i = 0; i < hash_size/*-1*/; i++)
+								// forget history
+								head[i] = 0;
+						}
+					}
+					strm.flush_pending();
+					if (strm.avail_out === 0) {
+						last_flush = -1; // avoid BUF_ERROR at next call, see above
+						return Z_OK;
+					}
+				}
+			}
+
+			if (flush != Z_FINISH)
+				return Z_OK;
+			return Z_STREAM_END;
+		};
+	}
+
+	// ZStream
+
+	function ZStream() {
+		var that = this;
+		that.next_in_index = 0;
+		that.next_out_index = 0;
+		// that.next_in; // next input byte
+		that.avail_in = 0; // number of bytes available at next_in
+		that.total_in = 0; // total nb of input bytes read so far
+		// that.next_out; // next output byte should be put there
+		that.avail_out = 0; // remaining free space at next_out
+		that.total_out = 0; // total nb of bytes output so far
+		// that.msg;
+		// that.dstate;
+	}
+
+	ZStream.prototype = {
+		deflateInit : function(level, bits) {
+			var that = this;
+			that.dstate = new Deflate();
+			if (!bits)
+				bits = MAX_BITS;
+			return that.dstate.deflateInit(that, level, bits);
+		},
+
+		deflate : function(flush) {
+			var that = this;
+			if (!that.dstate) {
+				return Z_STREAM_ERROR;
+			}
+			return that.dstate.deflate(that, flush);
+		},
+
+		deflateEnd : function() {
+			var that = this;
+			if (!that.dstate)
+				return Z_STREAM_ERROR;
+			var ret = that.dstate.deflateEnd();
+			that.dstate = null;
+			return ret;
+		},
+
+		deflateParams : function(level, strategy) {
+			var that = this;
+			if (!that.dstate)
+				return Z_STREAM_ERROR;
+			return that.dstate.deflateParams(that, level, strategy);
+		},
+
+		deflateSetDictionary : function(dictionary, dictLength) {
+			var that = this;
+			if (!that.dstate)
+				return Z_STREAM_ERROR;
+			return that.dstate.deflateSetDictionary(that, dictionary, dictLength);
+		},
+
+		// Read a new buffer from the current input stream, update the
+		// total number of bytes read. All deflate() input goes through
+		// this function so some applications may wish to modify it to avoid
+		// allocating a large strm->next_in buffer and copying from it.
+		// (See also flush_pending()).
+		read_buf : function(buf, start, size) {
+			var that = this;
+			var len = that.avail_in;
+			if (len > size)
+				len = size;
+			if (len === 0)
+				return 0;
+			that.avail_in -= len;
+			buf.set(that.next_in.subarray(that.next_in_index, that.next_in_index + len), start);
+			that.next_in_index += len;
+			that.total_in += len;
+			return len;
+		},
+
+		// Flush as much pending output as possible. All deflate() output goes
+		// through this function so some applications may wish to modify it
+		// to avoid allocating a large strm->next_out buffer and copying into it.
+		// (See also read_buf()).
+		flush_pending : function() {
+			var that = this;
+			var len = that.dstate.pending;
+
+			if (len > that.avail_out)
+				len = that.avail_out;
+			if (len === 0)
+				return;
+
+			// if (that.dstate.pending_buf.length <= that.dstate.pending_out || that.next_out.length <= that.next_out_index
+			// || that.dstate.pending_buf.length < (that.dstate.pending_out + len) || that.next_out.length < (that.next_out_index +
+			// len)) {
+			// console.log(that.dstate.pending_buf.length + ", " + that.dstate.pending_out + ", " + that.next_out.length + ", " +
+			// that.next_out_index + ", " + len);
+			// console.log("avail_out=" + that.avail_out);
+			// }
+
+			that.next_out.set(that.dstate.pending_buf.subarray(that.dstate.pending_out, that.dstate.pending_out + len), that.next_out_index);
+
+			that.next_out_index += len;
+			that.dstate.pending_out += len;
+			that.total_out += len;
+			that.avail_out -= len;
+			that.dstate.pending -= len;
+			if (that.dstate.pending === 0) {
+				that.dstate.pending_out = 0;
+			}
+		}
+	};
+
+	// Deflater
+
+	function Deflater(level) {
+		var that = this;
+		var z = new ZStream();
+		var bufsize = 512;
+		var flush = Z_NO_FLUSH;
+		var buf = new Uint8Array(bufsize);
+
+		if (typeof level == "undefined")
+			level = Z_DEFAULT_COMPRESSION;
+		z.deflateInit(level);
+		z.next_out = buf;
+
+		that.append = function(data, onprogress) {
+			var err, buffers = [], lastIndex = 0, bufferIndex = 0, bufferSize = 0, array;
+			if (!data.length)
+				return;
+			z.next_in_index = 0;
+			z.next_in = data;
+			z.avail_in = data.length;
+			do {
+				z.next_out_index = 0;
+				z.avail_out = bufsize;
+				err = z.deflate(flush);
+				if (err != Z_OK)
+					throw "deflating: " + z.msg;
+				if (z.next_out_index)
+					if (z.next_out_index == bufsize)
+						buffers.push(new Uint8Array(buf));
+					else
+						buffers.push(new Uint8Array(buf.subarray(0, z.next_out_index)));
+				bufferSize += z.next_out_index;
+				if (onprogress && z.next_in_index > 0 && z.next_in_index != lastIndex) {
+					onprogress(z.next_in_index);
+					lastIndex = z.next_in_index;
+				}
+			} while (z.avail_in > 0 || z.avail_out === 0);
+			array = new Uint8Array(bufferSize);
+			buffers.forEach(function(chunk) {
+				array.set(chunk, bufferIndex);
+				bufferIndex += chunk.length;
+			});
+			return array;
+		};
+		that.flush = function() {
+			var err, buffers = [], bufferIndex = 0, bufferSize = 0, array;
+			do {
+				z.next_out_index = 0;
+				z.avail_out = bufsize;
+				err = z.deflate(Z_FINISH);
+				if (err != Z_STREAM_END && err != Z_OK)
+					throw "deflating: " + z.msg;
+				if (bufsize - z.avail_out > 0)
+					buffers.push(new Uint8Array(buf.subarray(0, z.next_out_index)));
+				bufferSize += z.next_out_index;
+			} while (z.avail_in > 0 || z.avail_out === 0);
+			z.deflateEnd();
+			array = new Uint8Array(bufferSize);
+			buffers.forEach(function(chunk) {
+				array.set(chunk, bufferIndex);
+				bufferIndex += chunk.length;
+			});
+			return array;
+		};
+	}
+
+	var deflater;
+
+	if (obj.zip)
+		obj.zip.Deflater = Deflater;
+	else {
+		deflater = new Deflater();
+		obj.addEventListener("message", function(event) {
+			var message = event.data;
+			if (message.init) {
+				deflater = new Deflater(message.level);
+				obj.postMessage({
+					oninit : true
+				});
+			}
+			if (message.append)
+				obj.postMessage({
+					onappend : true,
+					data : deflater.append(message.data, function(current) {
+						obj.postMessage({
+							progress : true,
+							current : current
+						});
+					})
+				});
+			if (message.flush)
+				obj.postMessage({
+					onflush : true,
+					data : deflater.flush()
+				});
+		}, false);
+	}
+
+})(this);

+ 2163 - 0
lib/zip.js/inflate.js

@@ -0,0 +1,2163 @@
+/*
+ Copyright (c) 2013 Gildas Lormeau. All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above copyright 
+ notice, this list of conditions and the following disclaimer in 
+ the documentation and/or other materials provided with the distribution.
+
+ 3. The names of the authors may not be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
+ INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
+ INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
+ INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
+ OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * This program is based on JZlib 1.0.2 ymnk, JCraft,Inc.
+ * JZlib is based on zlib-1.1.3, so all credit should go authors
+ * Jean-loup Gailly([email protected]) and Mark Adler([email protected])
+ * and contributors of zlib.
+ */
+
+(function(obj) {
+
+	// Global
+	var MAX_BITS = 15;
+
+	var Z_OK = 0;
+	var Z_STREAM_END = 1;
+	var Z_NEED_DICT = 2;
+	var Z_STREAM_ERROR = -2;
+	var Z_DATA_ERROR = -3;
+	var Z_MEM_ERROR = -4;
+	var Z_BUF_ERROR = -5;
+
+	var inflate_mask = [ 0x00000000, 0x00000001, 0x00000003, 0x00000007, 0x0000000f, 0x0000001f, 0x0000003f, 0x0000007f, 0x000000ff, 0x000001ff, 0x000003ff,
+			0x000007ff, 0x00000fff, 0x00001fff, 0x00003fff, 0x00007fff, 0x0000ffff ];
+
+	var MANY = 1440;
+
+	// JZlib version : "1.0.2"
+	var Z_NO_FLUSH = 0;
+	var Z_FINISH = 4;
+
+	// InfTree
+	var fixed_bl = 9;
+	var fixed_bd = 5;
+
+	var fixed_tl = [ 96, 7, 256, 0, 8, 80, 0, 8, 16, 84, 8, 115, 82, 7, 31, 0, 8, 112, 0, 8, 48, 0, 9, 192, 80, 7, 10, 0, 8, 96, 0, 8, 32, 0, 9, 160, 0, 8, 0,
+			0, 8, 128, 0, 8, 64, 0, 9, 224, 80, 7, 6, 0, 8, 88, 0, 8, 24, 0, 9, 144, 83, 7, 59, 0, 8, 120, 0, 8, 56, 0, 9, 208, 81, 7, 17, 0, 8, 104, 0, 8, 40,
+			0, 9, 176, 0, 8, 8, 0, 8, 136, 0, 8, 72, 0, 9, 240, 80, 7, 4, 0, 8, 84, 0, 8, 20, 85, 8, 227, 83, 7, 43, 0, 8, 116, 0, 8, 52, 0, 9, 200, 81, 7, 13,
+			0, 8, 100, 0, 8, 36, 0, 9, 168, 0, 8, 4, 0, 8, 132, 0, 8, 68, 0, 9, 232, 80, 7, 8, 0, 8, 92, 0, 8, 28, 0, 9, 152, 84, 7, 83, 0, 8, 124, 0, 8, 60,
+			0, 9, 216, 82, 7, 23, 0, 8, 108, 0, 8, 44, 0, 9, 184, 0, 8, 12, 0, 8, 140, 0, 8, 76, 0, 9, 248, 80, 7, 3, 0, 8, 82, 0, 8, 18, 85, 8, 163, 83, 7,
+			35, 0, 8, 114, 0, 8, 50, 0, 9, 196, 81, 7, 11, 0, 8, 98, 0, 8, 34, 0, 9, 164, 0, 8, 2, 0, 8, 130, 0, 8, 66, 0, 9, 228, 80, 7, 7, 0, 8, 90, 0, 8,
+			26, 0, 9, 148, 84, 7, 67, 0, 8, 122, 0, 8, 58, 0, 9, 212, 82, 7, 19, 0, 8, 106, 0, 8, 42, 0, 9, 180, 0, 8, 10, 0, 8, 138, 0, 8, 74, 0, 9, 244, 80,
+			7, 5, 0, 8, 86, 0, 8, 22, 192, 8, 0, 83, 7, 51, 0, 8, 118, 0, 8, 54, 0, 9, 204, 81, 7, 15, 0, 8, 102, 0, 8, 38, 0, 9, 172, 0, 8, 6, 0, 8, 134, 0,
+			8, 70, 0, 9, 236, 80, 7, 9, 0, 8, 94, 0, 8, 30, 0, 9, 156, 84, 7, 99, 0, 8, 126, 0, 8, 62, 0, 9, 220, 82, 7, 27, 0, 8, 110, 0, 8, 46, 0, 9, 188, 0,
+			8, 14, 0, 8, 142, 0, 8, 78, 0, 9, 252, 96, 7, 256, 0, 8, 81, 0, 8, 17, 85, 8, 131, 82, 7, 31, 0, 8, 113, 0, 8, 49, 0, 9, 194, 80, 7, 10, 0, 8, 97,
+			0, 8, 33, 0, 9, 162, 0, 8, 1, 0, 8, 129, 0, 8, 65, 0, 9, 226, 80, 7, 6, 0, 8, 89, 0, 8, 25, 0, 9, 146, 83, 7, 59, 0, 8, 121, 0, 8, 57, 0, 9, 210,
+			81, 7, 17, 0, 8, 105, 0, 8, 41, 0, 9, 178, 0, 8, 9, 0, 8, 137, 0, 8, 73, 0, 9, 242, 80, 7, 4, 0, 8, 85, 0, 8, 21, 80, 8, 258, 83, 7, 43, 0, 8, 117,
+			0, 8, 53, 0, 9, 202, 81, 7, 13, 0, 8, 101, 0, 8, 37, 0, 9, 170, 0, 8, 5, 0, 8, 133, 0, 8, 69, 0, 9, 234, 80, 7, 8, 0, 8, 93, 0, 8, 29, 0, 9, 154,
+			84, 7, 83, 0, 8, 125, 0, 8, 61, 0, 9, 218, 82, 7, 23, 0, 8, 109, 0, 8, 45, 0, 9, 186, 0, 8, 13, 0, 8, 141, 0, 8, 77, 0, 9, 250, 80, 7, 3, 0, 8, 83,
+			0, 8, 19, 85, 8, 195, 83, 7, 35, 0, 8, 115, 0, 8, 51, 0, 9, 198, 81, 7, 11, 0, 8, 99, 0, 8, 35, 0, 9, 166, 0, 8, 3, 0, 8, 131, 0, 8, 67, 0, 9, 230,
+			80, 7, 7, 0, 8, 91, 0, 8, 27, 0, 9, 150, 84, 7, 67, 0, 8, 123, 0, 8, 59, 0, 9, 214, 82, 7, 19, 0, 8, 107, 0, 8, 43, 0, 9, 182, 0, 8, 11, 0, 8, 139,
+			0, 8, 75, 0, 9, 246, 80, 7, 5, 0, 8, 87, 0, 8, 23, 192, 8, 0, 83, 7, 51, 0, 8, 119, 0, 8, 55, 0, 9, 206, 81, 7, 15, 0, 8, 103, 0, 8, 39, 0, 9, 174,
+			0, 8, 7, 0, 8, 135, 0, 8, 71, 0, 9, 238, 80, 7, 9, 0, 8, 95, 0, 8, 31, 0, 9, 158, 84, 7, 99, 0, 8, 127, 0, 8, 63, 0, 9, 222, 82, 7, 27, 0, 8, 111,
+			0, 8, 47, 0, 9, 190, 0, 8, 15, 0, 8, 143, 0, 8, 79, 0, 9, 254, 96, 7, 256, 0, 8, 80, 0, 8, 16, 84, 8, 115, 82, 7, 31, 0, 8, 112, 0, 8, 48, 0, 9,
+			193, 80, 7, 10, 0, 8, 96, 0, 8, 32, 0, 9, 161, 0, 8, 0, 0, 8, 128, 0, 8, 64, 0, 9, 225, 80, 7, 6, 0, 8, 88, 0, 8, 24, 0, 9, 145, 83, 7, 59, 0, 8,
+			120, 0, 8, 56, 0, 9, 209, 81, 7, 17, 0, 8, 104, 0, 8, 40, 0, 9, 177, 0, 8, 8, 0, 8, 136, 0, 8, 72, 0, 9, 241, 80, 7, 4, 0, 8, 84, 0, 8, 20, 85, 8,
+			227, 83, 7, 43, 0, 8, 116, 0, 8, 52, 0, 9, 201, 81, 7, 13, 0, 8, 100, 0, 8, 36, 0, 9, 169, 0, 8, 4, 0, 8, 132, 0, 8, 68, 0, 9, 233, 80, 7, 8, 0, 8,
+			92, 0, 8, 28, 0, 9, 153, 84, 7, 83, 0, 8, 124, 0, 8, 60, 0, 9, 217, 82, 7, 23, 0, 8, 108, 0, 8, 44, 0, 9, 185, 0, 8, 12, 0, 8, 140, 0, 8, 76, 0, 9,
+			249, 80, 7, 3, 0, 8, 82, 0, 8, 18, 85, 8, 163, 83, 7, 35, 0, 8, 114, 0, 8, 50, 0, 9, 197, 81, 7, 11, 0, 8, 98, 0, 8, 34, 0, 9, 165, 0, 8, 2, 0, 8,
+			130, 0, 8, 66, 0, 9, 229, 80, 7, 7, 0, 8, 90, 0, 8, 26, 0, 9, 149, 84, 7, 67, 0, 8, 122, 0, 8, 58, 0, 9, 213, 82, 7, 19, 0, 8, 106, 0, 8, 42, 0, 9,
+			181, 0, 8, 10, 0, 8, 138, 0, 8, 74, 0, 9, 245, 80, 7, 5, 0, 8, 86, 0, 8, 22, 192, 8, 0, 83, 7, 51, 0, 8, 118, 0, 8, 54, 0, 9, 205, 81, 7, 15, 0, 8,
+			102, 0, 8, 38, 0, 9, 173, 0, 8, 6, 0, 8, 134, 0, 8, 70, 0, 9, 237, 80, 7, 9, 0, 8, 94, 0, 8, 30, 0, 9, 157, 84, 7, 99, 0, 8, 126, 0, 8, 62, 0, 9,
+			221, 82, 7, 27, 0, 8, 110, 0, 8, 46, 0, 9, 189, 0, 8, 14, 0, 8, 142, 0, 8, 78, 0, 9, 253, 96, 7, 256, 0, 8, 81, 0, 8, 17, 85, 8, 131, 82, 7, 31, 0,
+			8, 113, 0, 8, 49, 0, 9, 195, 80, 7, 10, 0, 8, 97, 0, 8, 33, 0, 9, 163, 0, 8, 1, 0, 8, 129, 0, 8, 65, 0, 9, 227, 80, 7, 6, 0, 8, 89, 0, 8, 25, 0, 9,
+			147, 83, 7, 59, 0, 8, 121, 0, 8, 57, 0, 9, 211, 81, 7, 17, 0, 8, 105, 0, 8, 41, 0, 9, 179, 0, 8, 9, 0, 8, 137, 0, 8, 73, 0, 9, 243, 80, 7, 4, 0, 8,
+			85, 0, 8, 21, 80, 8, 258, 83, 7, 43, 0, 8, 117, 0, 8, 53, 0, 9, 203, 81, 7, 13, 0, 8, 101, 0, 8, 37, 0, 9, 171, 0, 8, 5, 0, 8, 133, 0, 8, 69, 0, 9,
+			235, 80, 7, 8, 0, 8, 93, 0, 8, 29, 0, 9, 155, 84, 7, 83, 0, 8, 125, 0, 8, 61, 0, 9, 219, 82, 7, 23, 0, 8, 109, 0, 8, 45, 0, 9, 187, 0, 8, 13, 0, 8,
+			141, 0, 8, 77, 0, 9, 251, 80, 7, 3, 0, 8, 83, 0, 8, 19, 85, 8, 195, 83, 7, 35, 0, 8, 115, 0, 8, 51, 0, 9, 199, 81, 7, 11, 0, 8, 99, 0, 8, 35, 0, 9,
+			167, 0, 8, 3, 0, 8, 131, 0, 8, 67, 0, 9, 231, 80, 7, 7, 0, 8, 91, 0, 8, 27, 0, 9, 151, 84, 7, 67, 0, 8, 123, 0, 8, 59, 0, 9, 215, 82, 7, 19, 0, 8,
+			107, 0, 8, 43, 0, 9, 183, 0, 8, 11, 0, 8, 139, 0, 8, 75, 0, 9, 247, 80, 7, 5, 0, 8, 87, 0, 8, 23, 192, 8, 0, 83, 7, 51, 0, 8, 119, 0, 8, 55, 0, 9,
+			207, 81, 7, 15, 0, 8, 103, 0, 8, 39, 0, 9, 175, 0, 8, 7, 0, 8, 135, 0, 8, 71, 0, 9, 239, 80, 7, 9, 0, 8, 95, 0, 8, 31, 0, 9, 159, 84, 7, 99, 0, 8,
+			127, 0, 8, 63, 0, 9, 223, 82, 7, 27, 0, 8, 111, 0, 8, 47, 0, 9, 191, 0, 8, 15, 0, 8, 143, 0, 8, 79, 0, 9, 255 ];
+	var fixed_td = [ 80, 5, 1, 87, 5, 257, 83, 5, 17, 91, 5, 4097, 81, 5, 5, 89, 5, 1025, 85, 5, 65, 93, 5, 16385, 80, 5, 3, 88, 5, 513, 84, 5, 33, 92, 5,
+			8193, 82, 5, 9, 90, 5, 2049, 86, 5, 129, 192, 5, 24577, 80, 5, 2, 87, 5, 385, 83, 5, 25, 91, 5, 6145, 81, 5, 7, 89, 5, 1537, 85, 5, 97, 93, 5,
+			24577, 80, 5, 4, 88, 5, 769, 84, 5, 49, 92, 5, 12289, 82, 5, 13, 90, 5, 3073, 86, 5, 193, 192, 5, 24577 ];
+
+	// Tables for deflate from PKZIP's appnote.txt.
+	var cplens = [ // Copy lengths for literal codes 257..285
+	3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0 ];
+
+	// see note #13 above about 258
+	var cplext = [ // Extra bits for literal codes 257..285
+	0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 112, 112 // 112==invalid
+	];
+
+	var cpdist = [ // Copy offsets for distance codes 0..29
+	1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577 ];
+
+	var cpdext = [ // Extra bits for distance codes
+	0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13 ];
+
+	// If BMAX needs to be larger than 16, then h and x[] should be uLong.
+	var BMAX = 15; // maximum bit length of any code
+
+	function InfTree() {
+		var that = this;
+
+		var hn; // hufts used in space
+		var v; // work area for huft_build
+		var c; // bit length count table
+		var r; // table entry for structure assignment
+		var u; // table stack
+		var x; // bit offsets, then code stack
+
+		function huft_build(b, // code lengths in bits (all assumed <=
+		// BMAX)
+		bindex, n, // number of codes (assumed <= 288)
+		s, // number of simple-valued codes (0..s-1)
+		d, // list of base values for non-simple codes
+		e, // list of extra bits for non-simple codes
+		t, // result: starting table
+		m, // maximum lookup bits, returns actual
+		hp,// space for trees
+		hn,// hufts used in space
+		v // working area: values in order of bit length
+		) {
+			// Given a list of code lengths and a maximum table size, make a set of
+			// tables to decode that set of codes. Return Z_OK on success,
+			// Z_BUF_ERROR
+			// if the given code set is incomplete (the tables are still built in
+			// this
+			// case), Z_DATA_ERROR if the input is invalid (an over-subscribed set
+			// of
+			// lengths), or Z_MEM_ERROR if not enough memory.
+
+			var a; // counter for codes of length k
+			var f; // i repeats in table every f entries
+			var g; // maximum code length
+			var h; // table level
+			var i; // counter, current code
+			var j; // counter
+			var k; // number of bits in current code
+			var l; // bits per table (returned in m)
+			var mask; // (1 << w) - 1, to avoid cc -O bug on HP
+			var p; // pointer into c[], b[], or v[]
+			var q; // points to current table
+			var w; // bits before this table == (l * h)
+			var xp; // pointer into x
+			var y; // number of dummy codes added
+			var z; // number of entries in current table
+
+			// Generate counts for each bit length
+
+			p = 0;
+			i = n;
+			do {
+				c[b[bindex + p]]++;
+				p++;
+				i--; // assume all entries <= BMAX
+			} while (i !== 0);
+
+			if (c[0] == n) { // null input--all zero length codes
+				t[0] = -1;
+				m[0] = 0;
+				return Z_OK;
+			}
+
+			// Find minimum and maximum length, bound *m by those
+			l = m[0];
+			for (j = 1; j <= BMAX; j++)
+				if (c[j] !== 0)
+					break;
+			k = j; // minimum code length
+			if (l < j) {
+				l = j;
+			}
+			for (i = BMAX; i !== 0; i--) {
+				if (c[i] !== 0)
+					break;
+			}
+			g = i; // maximum code length
+			if (l > i) {
+				l = i;
+			}
+			m[0] = l;
+
+			// Adjust last length count to fill out codes, if needed
+			for (y = 1 << j; j < i; j++, y <<= 1) {
+				if ((y -= c[j]) < 0) {
+					return Z_DATA_ERROR;
+				}
+			}
+			if ((y -= c[i]) < 0) {
+				return Z_DATA_ERROR;
+			}
+			c[i] += y;
+
+			// Generate starting offsets into the value table for each length
+			x[1] = j = 0;
+			p = 1;
+			xp = 2;
+			while (--i !== 0) { // note that i == g from above
+				x[xp] = (j += c[p]);
+				xp++;
+				p++;
+			}
+
+			// Make a table of values in order of bit lengths
+			i = 0;
+			p = 0;
+			do {
+				if ((j = b[bindex + p]) !== 0) {
+					v[x[j]++] = i;
+				}
+				p++;
+			} while (++i < n);
+			n = x[g]; // set n to length of v
+
+			// Generate the Huffman codes and for each, make the table entries
+			x[0] = i = 0; // first Huffman code is zero
+			p = 0; // grab values in bit order
+			h = -1; // no tables yet--level -1
+			w = -l; // bits decoded == (l * h)
+			u[0] = 0; // just to keep compilers happy
+			q = 0; // ditto
+			z = 0; // ditto
+
+			// go through the bit lengths (k already is bits in shortest code)
+			for (; k <= g; k++) {
+				a = c[k];
+				while (a-- !== 0) {
+					// here i is the Huffman code of length k bits for value *p
+					// make tables up to required level
+					while (k > w + l) {
+						h++;
+						w += l; // previous table always l bits
+						// compute minimum size table less than or equal to l bits
+						z = g - w;
+						z = (z > l) ? l : z; // table size upper limit
+						if ((f = 1 << (j = k - w)) > a + 1) { // try a k-w bit table
+							// too few codes for
+							// k-w bit table
+							f -= a + 1; // deduct codes from patterns left
+							xp = k;
+							if (j < z) {
+								while (++j < z) { // try smaller tables up to z bits
+									if ((f <<= 1) <= c[++xp])
+										break; // enough codes to use up j bits
+									f -= c[xp]; // else deduct codes from patterns
+								}
+							}
+						}
+						z = 1 << j; // table entries for j-bit table
+
+						// allocate new table
+						if (hn[0] + z > MANY) { // (note: doesn't matter for fixed)
+							return Z_DATA_ERROR; // overflow of MANY
+						}
+						u[h] = q = /* hp+ */hn[0]; // DEBUG
+						hn[0] += z;
+
+						// connect to last table, if there is one
+						if (h !== 0) {
+							x[h] = i; // save pattern for backing up
+							r[0] = /* (byte) */j; // bits in this table
+							r[1] = /* (byte) */l; // bits to dump before this table
+							j = i >>> (w - l);
+							r[2] = /* (int) */(q - u[h - 1] - j); // offset to this table
+							hp.set(r, (u[h - 1] + j) * 3);
+							// to
+							// last
+							// table
+						} else {
+							t[0] = q; // first table is returned result
+						}
+					}
+
+					// set up table entry in r
+					r[1] = /* (byte) */(k - w);
+					if (p >= n) {
+						r[0] = 128 + 64; // out of values--invalid code
+					} else if (v[p] < s) {
+						r[0] = /* (byte) */(v[p] < 256 ? 0 : 32 + 64); // 256 is
+						// end-of-block
+						r[2] = v[p++]; // simple code is just the value
+					} else {
+						r[0] = /* (byte) */(e[v[p] - s] + 16 + 64); // non-simple--look
+						// up in lists
+						r[2] = d[v[p++] - s];
+					}
+
+					// fill code-like entries with r
+					f = 1 << (k - w);
+					for (j = i >>> w; j < z; j += f) {
+						hp.set(r, (q + j) * 3);
+					}
+
+					// backwards increment the k-bit code i
+					for (j = 1 << (k - 1); (i & j) !== 0; j >>>= 1) {
+						i ^= j;
+					}
+					i ^= j;
+
+					// backup over finished tables
+					mask = (1 << w) - 1; // needed on HP, cc -O bug
+					while ((i & mask) != x[h]) {
+						h--; // don't need to update q
+						w -= l;
+						mask = (1 << w) - 1;
+					}
+				}
+			}
+			// Return Z_BUF_ERROR if we were given an incomplete table
+			return y !== 0 && g != 1 ? Z_BUF_ERROR : Z_OK;
+		}
+
+		function initWorkArea(vsize) {
+			var i;
+			if (!hn) {
+				hn = []; // []; //new Array(1);
+				v = []; // new Array(vsize);
+				c = new Int32Array(BMAX + 1); // new Array(BMAX + 1);
+				r = []; // new Array(3);
+				u = new Int32Array(BMAX); // new Array(BMAX);
+				x = new Int32Array(BMAX + 1); // new Array(BMAX + 1);
+			}
+			if (v.length < vsize) {
+				v = []; // new Array(vsize);
+			}
+			for (i = 0; i < vsize; i++) {
+				v[i] = 0;
+			}
+			for (i = 0; i < BMAX + 1; i++) {
+				c[i] = 0;
+			}
+			for (i = 0; i < 3; i++) {
+				r[i] = 0;
+			}
+			// for(int i=0; i<BMAX; i++){u[i]=0;}
+			u.set(c.subarray(0, BMAX), 0);
+			// for(int i=0; i<BMAX+1; i++){x[i]=0;}
+			x.set(c.subarray(0, BMAX + 1), 0);
+		}
+
+		that.inflate_trees_bits = function(c, // 19 code lengths
+		bb, // bits tree desired/actual depth
+		tb, // bits tree result
+		hp, // space for trees
+		z // for messages
+		) {
+			var result;
+			initWorkArea(19);
+			hn[0] = 0;
+			result = huft_build(c, 0, 19, 19, null, null, tb, bb, hp, hn, v);
+
+			if (result == Z_DATA_ERROR) {
+				z.msg = "oversubscribed dynamic bit lengths tree";
+			} else if (result == Z_BUF_ERROR || bb[0] === 0) {
+				z.msg = "incomplete dynamic bit lengths tree";
+				result = Z_DATA_ERROR;
+			}
+			return result;
+		};
+
+		that.inflate_trees_dynamic = function(nl, // number of literal/length codes
+		nd, // number of distance codes
+		c, // that many (total) code lengths
+		bl, // literal desired/actual bit depth
+		bd, // distance desired/actual bit depth
+		tl, // literal/length tree result
+		td, // distance tree result
+		hp, // space for trees
+		z // for messages
+		) {
+			var result;
+
+			// build literal/length tree
+			initWorkArea(288);
+			hn[0] = 0;
+			result = huft_build(c, 0, nl, 257, cplens, cplext, tl, bl, hp, hn, v);
+			if (result != Z_OK || bl[0] === 0) {
+				if (result == Z_DATA_ERROR) {
+					z.msg = "oversubscribed literal/length tree";
+				} else if (result != Z_MEM_ERROR) {
+					z.msg = "incomplete literal/length tree";
+					result = Z_DATA_ERROR;
+				}
+				return result;
+			}
+
+			// build distance tree
+			initWorkArea(288);
+			result = huft_build(c, nl, nd, 0, cpdist, cpdext, td, bd, hp, hn, v);
+
+			if (result != Z_OK || (bd[0] === 0 && nl > 257)) {
+				if (result == Z_DATA_ERROR) {
+					z.msg = "oversubscribed distance tree";
+				} else if (result == Z_BUF_ERROR) {
+					z.msg = "incomplete distance tree";
+					result = Z_DATA_ERROR;
+				} else if (result != Z_MEM_ERROR) {
+					z.msg = "empty distance tree with lengths";
+					result = Z_DATA_ERROR;
+				}
+				return result;
+			}
+
+			return Z_OK;
+		};
+
+	}
+
+	InfTree.inflate_trees_fixed = function(bl, // literal desired/actual bit depth
+	bd, // distance desired/actual bit depth
+	tl,// literal/length tree result
+	td// distance tree result
+	) {
+		bl[0] = fixed_bl;
+		bd[0] = fixed_bd;
+		tl[0] = fixed_tl;
+		td[0] = fixed_td;
+		return Z_OK;
+	};
+
+	// InfCodes
+
+	// waiting for "i:"=input,
+	// "o:"=output,
+	// "x:"=nothing
+	var START = 0; // x: set up for LEN
+	var LEN = 1; // i: get length/literal/eob next
+	var LENEXT = 2; // i: getting length extra (have base)
+	var DIST = 3; // i: get distance next
+	var DISTEXT = 4;// i: getting distance extra
+	var COPY = 5; // o: copying bytes in window, waiting
+	// for space
+	var LIT = 6; // o: got literal, waiting for output
+	// space
+	var WASH = 7; // o: got eob, possibly still output
+	// waiting
+	var END = 8; // x: got eob and all data flushed
+	var BADCODE = 9;// x: got error
+
+	function InfCodes() {
+		var that = this;
+
+		var mode; // current inflate_codes mode
+
+		// mode dependent information
+		var len = 0;
+
+		var tree; // pointer into tree
+		var tree_index = 0;
+		var need = 0; // bits needed
+
+		var lit = 0;
+
+		// if EXT or COPY, where and how much
+		var get = 0; // bits to get for extra
+		var dist = 0; // distance back to copy from
+
+		var lbits = 0; // ltree bits decoded per branch
+		var dbits = 0; // dtree bits decoder per branch
+		var ltree; // literal/length/eob tree
+		var ltree_index = 0; // literal/length/eob tree
+		var dtree; // distance tree
+		var dtree_index = 0; // distance tree
+
+		// Called with number of bytes left to write in window at least 258
+		// (the maximum string length) and number of input bytes available
+		// at least ten. The ten bytes are six bytes for the longest length/
+		// distance pair plus four bytes for overloading the bit buffer.
+
+		function inflate_fast(bl, bd, tl, tl_index, td, td_index, s, z) {
+			var t; // temporary pointer
+			var tp; // temporary pointer
+			var tp_index; // temporary pointer
+			var e; // extra bits or operation
+			var b; // bit buffer
+			var k; // bits in bit buffer
+			var p; // input data pointer
+			var n; // bytes available there
+			var q; // output window write pointer
+			var m; // bytes to end of window or read pointer
+			var ml; // mask for literal/length tree
+			var md; // mask for distance tree
+			var c; // bytes to copy
+			var d; // distance back to copy from
+			var r; // copy source pointer
+
+			var tp_index_t_3; // (tp_index+t)*3
+
+			// load input, output, bit values
+			p = z.next_in_index;
+			n = z.avail_in;
+			b = s.bitb;
+			k = s.bitk;
+			q = s.write;
+			m = q < s.read ? s.read - q - 1 : s.end - q;
+
+			// initialize masks
+			ml = inflate_mask[bl];
+			md = inflate_mask[bd];
+
+			// do until not enough input or output space for fast loop
+			do { // assume called with m >= 258 && n >= 10
+				// get literal/length code
+				while (k < (20)) { // max bits for literal/length code
+					n--;
+					b |= (z.read_byte(p++) & 0xff) << k;
+					k += 8;
+				}
+
+				t = b & ml;
+				tp = tl;
+				tp_index = tl_index;
+				tp_index_t_3 = (tp_index + t) * 3;
+				if ((e = tp[tp_index_t_3]) === 0) {
+					b >>= (tp[tp_index_t_3 + 1]);
+					k -= (tp[tp_index_t_3 + 1]);
+
+					s.window[q++] = /* (byte) */tp[tp_index_t_3 + 2];
+					m--;
+					continue;
+				}
+				do {
+
+					b >>= (tp[tp_index_t_3 + 1]);
+					k -= (tp[tp_index_t_3 + 1]);
+
+					if ((e & 16) !== 0) {
+						e &= 15;
+						c = tp[tp_index_t_3 + 2] + (/* (int) */b & inflate_mask[e]);
+
+						b >>= e;
+						k -= e;
+
+						// decode distance base of block to copy
+						while (k < (15)) { // max bits for distance code
+							n--;
+							b |= (z.read_byte(p++) & 0xff) << k;
+							k += 8;
+						}
+
+						t = b & md;
+						tp = td;
+						tp_index = td_index;
+						tp_index_t_3 = (tp_index + t) * 3;
+						e = tp[tp_index_t_3];
+
+						do {
+
+							b >>= (tp[tp_index_t_3 + 1]);
+							k -= (tp[tp_index_t_3 + 1]);
+
+							if ((e & 16) !== 0) {
+								// get extra bits to add to distance base
+								e &= 15;
+								while (k < (e)) { // get extra bits (up to 13)
+									n--;
+									b |= (z.read_byte(p++) & 0xff) << k;
+									k += 8;
+								}
+
+								d = tp[tp_index_t_3 + 2] + (b & inflate_mask[e]);
+
+								b >>= (e);
+								k -= (e);
+
+								// do the copy
+								m -= c;
+								if (q >= d) { // offset before dest
+									// just copy
+									r = q - d;
+									if (q - r > 0 && 2 > (q - r)) {
+										s.window[q++] = s.window[r++]; // minimum
+										// count is
+										// three,
+										s.window[q++] = s.window[r++]; // so unroll
+										// loop a
+										// little
+										c -= 2;
+									} else {
+										s.window.set(s.window.subarray(r, r + 2), q);
+										q += 2;
+										r += 2;
+										c -= 2;
+									}
+								} else { // else offset after destination
+									r = q - d;
+									do {
+										r += s.end; // force pointer in window
+									} while (r < 0); // covers invalid distances
+									e = s.end - r;
+									if (c > e) { // if source crosses,
+										c -= e; // wrapped copy
+										if (q - r > 0 && e > (q - r)) {
+											do {
+												s.window[q++] = s.window[r++];
+											} while (--e !== 0);
+										} else {
+											s.window.set(s.window.subarray(r, r + e), q);
+											q += e;
+											r += e;
+											e = 0;
+										}
+										r = 0; // copy rest from start of window
+									}
+
+								}
+
+								// copy all or what's left
+								if (q - r > 0 && c > (q - r)) {
+									do {
+										s.window[q++] = s.window[r++];
+									} while (--c !== 0);
+								} else {
+									s.window.set(s.window.subarray(r, r + c), q);
+									q += c;
+									r += c;
+									c = 0;
+								}
+								break;
+							} else if ((e & 64) === 0) {
+								t += tp[tp_index_t_3 + 2];
+								t += (b & inflate_mask[e]);
+								tp_index_t_3 = (tp_index + t) * 3;
+								e = tp[tp_index_t_3];
+							} else {
+								z.msg = "invalid distance code";
+
+								c = z.avail_in - n;
+								c = (k >> 3) < c ? k >> 3 : c;
+								n += c;
+								p -= c;
+								k -= c << 3;
+
+								s.bitb = b;
+								s.bitk = k;
+								z.avail_in = n;
+								z.total_in += p - z.next_in_index;
+								z.next_in_index = p;
+								s.write = q;
+
+								return Z_DATA_ERROR;
+							}
+						} while (true);
+						break;
+					}
+
+					if ((e & 64) === 0) {
+						t += tp[tp_index_t_3 + 2];
+						t += (b & inflate_mask[e]);
+						tp_index_t_3 = (tp_index + t) * 3;
+						if ((e = tp[tp_index_t_3]) === 0) {
+
+							b >>= (tp[tp_index_t_3 + 1]);
+							k -= (tp[tp_index_t_3 + 1]);
+
+							s.window[q++] = /* (byte) */tp[tp_index_t_3 + 2];
+							m--;
+							break;
+						}
+					} else if ((e & 32) !== 0) {
+
+						c = z.avail_in - n;
+						c = (k >> 3) < c ? k >> 3 : c;
+						n += c;
+						p -= c;
+						k -= c << 3;
+
+						s.bitb = b;
+						s.bitk = k;
+						z.avail_in = n;
+						z.total_in += p - z.next_in_index;
+						z.next_in_index = p;
+						s.write = q;
+
+						return Z_STREAM_END;
+					} else {
+						z.msg = "invalid literal/length code";
+
+						c = z.avail_in - n;
+						c = (k >> 3) < c ? k >> 3 : c;
+						n += c;
+						p -= c;
+						k -= c << 3;
+
+						s.bitb = b;
+						s.bitk = k;
+						z.avail_in = n;
+						z.total_in += p - z.next_in_index;
+						z.next_in_index = p;
+						s.write = q;
+
+						return Z_DATA_ERROR;
+					}
+				} while (true);
+			} while (m >= 258 && n >= 10);
+
+			// not enough input or output--restore pointers and return
+			c = z.avail_in - n;
+			c = (k >> 3) < c ? k >> 3 : c;
+			n += c;
+			p -= c;
+			k -= c << 3;
+
+			s.bitb = b;
+			s.bitk = k;
+			z.avail_in = n;
+			z.total_in += p - z.next_in_index;
+			z.next_in_index = p;
+			s.write = q;
+
+			return Z_OK;
+		}
+
+		that.init = function(bl, bd, tl, tl_index, td, td_index) {
+			mode = START;
+			lbits = /* (byte) */bl;
+			dbits = /* (byte) */bd;
+			ltree = tl;
+			ltree_index = tl_index;
+			dtree = td;
+			dtree_index = td_index;
+			tree = null;
+		};
+
+		that.proc = function(s, z, r) {
+			var j; // temporary storage
+			var tindex; // temporary pointer
+			var e; // extra bits or operation
+			var b = 0; // bit buffer
+			var k = 0; // bits in bit buffer
+			var p = 0; // input data pointer
+			var n; // bytes available there
+			var q; // output window write pointer
+			var m; // bytes to end of window or read pointer
+			var f; // pointer to copy strings from
+
+			// copy input/output information to locals (UPDATE macro restores)
+			p = z.next_in_index;
+			n = z.avail_in;
+			b = s.bitb;
+			k = s.bitk;
+			q = s.write;
+			m = q < s.read ? s.read - q - 1 : s.end - q;
+
+			// process input and output based on current state
+			while (true) {
+				switch (mode) {
+				// waiting for "i:"=input, "o:"=output, "x:"=nothing
+				case START: // x: set up for LEN
+					if (m >= 258 && n >= 10) {
+
+						s.bitb = b;
+						s.bitk = k;
+						z.avail_in = n;
+						z.total_in += p - z.next_in_index;
+						z.next_in_index = p;
+						s.write = q;
+						r = inflate_fast(lbits, dbits, ltree, ltree_index, dtree, dtree_index, s, z);
+
+						p = z.next_in_index;
+						n = z.avail_in;
+						b = s.bitb;
+						k = s.bitk;
+						q = s.write;
+						m = q < s.read ? s.read - q - 1 : s.end - q;
+
+						if (r != Z_OK) {
+							mode = r == Z_STREAM_END ? WASH : BADCODE;
+							break;
+						}
+					}
+					need = lbits;
+					tree = ltree;
+					tree_index = ltree_index;
+
+					mode = LEN;
+				case LEN: // i: get length/literal/eob next
+					j = need;
+
+					while (k < (j)) {
+						if (n !== 0)
+							r = Z_OK;
+						else {
+
+							s.bitb = b;
+							s.bitk = k;
+							z.avail_in = n;
+							z.total_in += p - z.next_in_index;
+							z.next_in_index = p;
+							s.write = q;
+							return s.inflate_flush(z, r);
+						}
+						n--;
+						b |= (z.read_byte(p++) & 0xff) << k;
+						k += 8;
+					}
+
+					tindex = (tree_index + (b & inflate_mask[j])) * 3;
+
+					b >>>= (tree[tindex + 1]);
+					k -= (tree[tindex + 1]);
+
+					e = tree[tindex];
+
+					if (e === 0) { // literal
+						lit = tree[tindex + 2];
+						mode = LIT;
+						break;
+					}
+					if ((e & 16) !== 0) { // length
+						get = e & 15;
+						len = tree[tindex + 2];
+						mode = LENEXT;
+						break;
+					}
+					if ((e & 64) === 0) { // next table
+						need = e;
+						tree_index = tindex / 3 + tree[tindex + 2];
+						break;
+					}
+					if ((e & 32) !== 0) { // end of block
+						mode = WASH;
+						break;
+					}
+					mode = BADCODE; // invalid code
+					z.msg = "invalid literal/length code";
+					r = Z_DATA_ERROR;
+
+					s.bitb = b;
+					s.bitk = k;
+					z.avail_in = n;
+					z.total_in += p - z.next_in_index;
+					z.next_in_index = p;
+					s.write = q;
+					return s.inflate_flush(z, r);
+
+				case LENEXT: // i: getting length extra (have base)
+					j = get;
+
+					while (k < (j)) {
+						if (n !== 0)
+							r = Z_OK;
+						else {
+
+							s.bitb = b;
+							s.bitk = k;
+							z.avail_in = n;
+							z.total_in += p - z.next_in_index;
+							z.next_in_index = p;
+							s.write = q;
+							return s.inflate_flush(z, r);
+						}
+						n--;
+						b |= (z.read_byte(p++) & 0xff) << k;
+						k += 8;
+					}
+
+					len += (b & inflate_mask[j]);
+
+					b >>= j;
+					k -= j;
+
+					need = dbits;
+					tree = dtree;
+					tree_index = dtree_index;
+					mode = DIST;
+				case DIST: // i: get distance next
+					j = need;
+
+					while (k < (j)) {
+						if (n !== 0)
+							r = Z_OK;
+						else {
+
+							s.bitb = b;
+							s.bitk = k;
+							z.avail_in = n;
+							z.total_in += p - z.next_in_index;
+							z.next_in_index = p;
+							s.write = q;
+							return s.inflate_flush(z, r);
+						}
+						n--;
+						b |= (z.read_byte(p++) & 0xff) << k;
+						k += 8;
+					}
+
+					tindex = (tree_index + (b & inflate_mask[j])) * 3;
+
+					b >>= tree[tindex + 1];
+					k -= tree[tindex + 1];
+
+					e = (tree[tindex]);
+					if ((e & 16) !== 0) { // distance
+						get = e & 15;
+						dist = tree[tindex + 2];
+						mode = DISTEXT;
+						break;
+					}
+					if ((e & 64) === 0) { // next table
+						need = e;
+						tree_index = tindex / 3 + tree[tindex + 2];
+						break;
+					}
+					mode = BADCODE; // invalid code
+					z.msg = "invalid distance code";
+					r = Z_DATA_ERROR;
+
+					s.bitb = b;
+					s.bitk = k;
+					z.avail_in = n;
+					z.total_in += p - z.next_in_index;
+					z.next_in_index = p;
+					s.write = q;
+					return s.inflate_flush(z, r);
+
+				case DISTEXT: // i: getting distance extra
+					j = get;
+
+					while (k < (j)) {
+						if (n !== 0)
+							r = Z_OK;
+						else {
+
+							s.bitb = b;
+							s.bitk = k;
+							z.avail_in = n;
+							z.total_in += p - z.next_in_index;
+							z.next_in_index = p;
+							s.write = q;
+							return s.inflate_flush(z, r);
+						}
+						n--;
+						b |= (z.read_byte(p++) & 0xff) << k;
+						k += 8;
+					}
+
+					dist += (b & inflate_mask[j]);
+
+					b >>= j;
+					k -= j;
+
+					mode = COPY;
+				case COPY: // o: copying bytes in window, waiting for space
+					f = q - dist;
+					while (f < 0) { // modulo window size-"while" instead
+						f += s.end; // of "if" handles invalid distances
+					}
+					while (len !== 0) {
+
+						if (m === 0) {
+							if (q == s.end && s.read !== 0) {
+								q = 0;
+								m = q < s.read ? s.read - q - 1 : s.end - q;
+							}
+							if (m === 0) {
+								s.write = q;
+								r = s.inflate_flush(z, r);
+								q = s.write;
+								m = q < s.read ? s.read - q - 1 : s.end - q;
+
+								if (q == s.end && s.read !== 0) {
+									q = 0;
+									m = q < s.read ? s.read - q - 1 : s.end - q;
+								}
+
+								if (m === 0) {
+									s.bitb = b;
+									s.bitk = k;
+									z.avail_in = n;
+									z.total_in += p - z.next_in_index;
+									z.next_in_index = p;
+									s.write = q;
+									return s.inflate_flush(z, r);
+								}
+							}
+						}
+
+						s.window[q++] = s.window[f++];
+						m--;
+
+						if (f == s.end)
+							f = 0;
+						len--;
+					}
+					mode = START;
+					break;
+				case LIT: // o: got literal, waiting for output space
+					if (m === 0) {
+						if (q == s.end && s.read !== 0) {
+							q = 0;
+							m = q < s.read ? s.read - q - 1 : s.end - q;
+						}
+						if (m === 0) {
+							s.write = q;
+							r = s.inflate_flush(z, r);
+							q = s.write;
+							m = q < s.read ? s.read - q - 1 : s.end - q;
+
+							if (q == s.end && s.read !== 0) {
+								q = 0;
+								m = q < s.read ? s.read - q - 1 : s.end - q;
+							}
+							if (m === 0) {
+								s.bitb = b;
+								s.bitk = k;
+								z.avail_in = n;
+								z.total_in += p - z.next_in_index;
+								z.next_in_index = p;
+								s.write = q;
+								return s.inflate_flush(z, r);
+							}
+						}
+					}
+					r = Z_OK;
+
+					s.window[q++] = /* (byte) */lit;
+					m--;
+
+					mode = START;
+					break;
+				case WASH: // o: got eob, possibly more output
+					if (k > 7) { // return unused byte, if any
+						k -= 8;
+						n++;
+						p--; // can always return one
+					}
+
+					s.write = q;
+					r = s.inflate_flush(z, r);
+					q = s.write;
+					m = q < s.read ? s.read - q - 1 : s.end - q;
+
+					if (s.read != s.write) {
+						s.bitb = b;
+						s.bitk = k;
+						z.avail_in = n;
+						z.total_in += p - z.next_in_index;
+						z.next_in_index = p;
+						s.write = q;
+						return s.inflate_flush(z, r);
+					}
+					mode = END;
+				case END:
+					r = Z_STREAM_END;
+					s.bitb = b;
+					s.bitk = k;
+					z.avail_in = n;
+					z.total_in += p - z.next_in_index;
+					z.next_in_index = p;
+					s.write = q;
+					return s.inflate_flush(z, r);
+
+				case BADCODE: // x: got error
+
+					r = Z_DATA_ERROR;
+
+					s.bitb = b;
+					s.bitk = k;
+					z.avail_in = n;
+					z.total_in += p - z.next_in_index;
+					z.next_in_index = p;
+					s.write = q;
+					return s.inflate_flush(z, r);
+
+				default:
+					r = Z_STREAM_ERROR;
+
+					s.bitb = b;
+					s.bitk = k;
+					z.avail_in = n;
+					z.total_in += p - z.next_in_index;
+					z.next_in_index = p;
+					s.write = q;
+					return s.inflate_flush(z, r);
+				}
+			}
+		};
+
+		that.free = function() {
+			// ZFREE(z, c);
+		};
+
+	}
+
+	// InfBlocks
+
+	// Table for deflate from PKZIP's appnote.txt.
+	var border = [ // Order of the bit length code lengths
+	16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 ];
+
+	var TYPE = 0; // get type bits (3, including end bit)
+	var LENS = 1; // get lengths for stored
+	var STORED = 2;// processing stored block
+	var TABLE = 3; // get table lengths
+	var BTREE = 4; // get bit lengths tree for a dynamic
+	// block
+	var DTREE = 5; // get length, distance trees for a
+	// dynamic block
+	var CODES = 6; // processing fixed or dynamic block
+	var DRY = 7; // output remaining window bytes
+	var DONELOCKS = 8; // finished last block, done
+	var BADBLOCKS = 9; // ot a data error--stuck here
+
+	function InfBlocks(z, w) {
+		var that = this;
+
+		var mode = TYPE; // current inflate_block mode
+
+		var left = 0; // if STORED, bytes left to copy
+
+		var table = 0; // table lengths (14 bits)
+		var index = 0; // index into blens (or border)
+		var blens; // bit lengths of codes
+		var bb = [ 0 ]; // bit length tree depth
+		var tb = [ 0 ]; // bit length decoding tree
+
+		var codes = new InfCodes(); // if CODES, current state
+
+		var last = 0; // true if this block is the last block
+
+		var hufts = new Int32Array(MANY * 3); // single malloc for tree space
+		var check = 0; // check on output
+		var inftree = new InfTree();
+
+		that.bitk = 0; // bits in bit buffer
+		that.bitb = 0; // bit buffer
+		that.window = new Uint8Array(w); // sliding window
+		that.end = w; // one byte after sliding window
+		that.read = 0; // window read pointer
+		that.write = 0; // window write pointer
+
+		that.reset = function(z, c) {
+			if (c)
+				c[0] = check;
+			// if (mode == BTREE || mode == DTREE) {
+			// }
+			if (mode == CODES) {
+				codes.free(z);
+			}
+			mode = TYPE;
+			that.bitk = 0;
+			that.bitb = 0;
+			that.read = that.write = 0;
+		};
+
+		that.reset(z, null);
+
+		// copy as much as possible from the sliding window to the output area
+		that.inflate_flush = function(z, r) {
+			var n;
+			var p;
+			var q;
+
+			// local copies of source and destination pointers
+			p = z.next_out_index;
+			q = that.read;
+
+			// compute number of bytes to copy as far as end of window
+			n = /* (int) */((q <= that.write ? that.write : that.end) - q);
+			if (n > z.avail_out)
+				n = z.avail_out;
+			if (n !== 0 && r == Z_BUF_ERROR)
+				r = Z_OK;
+
+			// update counters
+			z.avail_out -= n;
+			z.total_out += n;
+
+			// copy as far as end of window
+			z.next_out.set(that.window.subarray(q, q + n), p);
+			p += n;
+			q += n;
+
+			// see if more to copy at beginning of window
+			if (q == that.end) {
+				// wrap pointers
+				q = 0;
+				if (that.write == that.end)
+					that.write = 0;
+
+				// compute bytes to copy
+				n = that.write - q;
+				if (n > z.avail_out)
+					n = z.avail_out;
+				if (n !== 0 && r == Z_BUF_ERROR)
+					r = Z_OK;
+
+				// update counters
+				z.avail_out -= n;
+				z.total_out += n;
+
+				// copy
+				z.next_out.set(that.window.subarray(q, q + n), p);
+				p += n;
+				q += n;
+			}
+
+			// update pointers
+			z.next_out_index = p;
+			that.read = q;
+
+			// done
+			return r;
+		};
+
+		that.proc = function(z, r) {
+			var t; // temporary storage
+			var b; // bit buffer
+			var k; // bits in bit buffer
+			var p; // input data pointer
+			var n; // bytes available there
+			var q; // output window write pointer
+			var m; // bytes to end of window or read pointer
+
+			var i;
+
+			// copy input/output information to locals (UPDATE macro restores)
+			// {
+			p = z.next_in_index;
+			n = z.avail_in;
+			b = that.bitb;
+			k = that.bitk;
+			// }
+			// {
+			q = that.write;
+			m = /* (int) */(q < that.read ? that.read - q - 1 : that.end - q);
+			// }
+
+			// process input based on current state
+			// DEBUG dtree
+			while (true) {
+				switch (mode) {
+				case TYPE:
+
+					while (k < (3)) {
+						if (n !== 0) {
+							r = Z_OK;
+						} else {
+							that.bitb = b;
+							that.bitk = k;
+							z.avail_in = n;
+							z.total_in += p - z.next_in_index;
+							z.next_in_index = p;
+							that.write = q;
+							return that.inflate_flush(z, r);
+						}
+						n--;
+						b |= (z.read_byte(p++) & 0xff) << k;
+						k += 8;
+					}
+					t = /* (int) */(b & 7);
+					last = t & 1;
+
+					switch (t >>> 1) {
+					case 0: // stored
+						// {
+						b >>>= (3);
+						k -= (3);
+						// }
+						t = k & 7; // go to byte boundary
+
+						// {
+						b >>>= (t);
+						k -= (t);
+						// }
+						mode = LENS; // get length of stored block
+						break;
+					case 1: // fixed
+						// {
+						var bl = []; // new Array(1);
+						var bd = []; // new Array(1);
+						var tl = [ [] ]; // new Array(1);
+						var td = [ [] ]; // new Array(1);
+
+						InfTree.inflate_trees_fixed(bl, bd, tl, td);
+						codes.init(bl[0], bd[0], tl[0], 0, td[0], 0);
+						// }
+
+						// {
+						b >>>= (3);
+						k -= (3);
+						// }
+
+						mode = CODES;
+						break;
+					case 2: // dynamic
+
+						// {
+						b >>>= (3);
+						k -= (3);
+						// }
+
+						mode = TABLE;
+						break;
+					case 3: // illegal
+
+						// {
+						b >>>= (3);
+						k -= (3);
+						// }
+						mode = BADBLOCKS;
+						z.msg = "invalid block type";
+						r = Z_DATA_ERROR;
+
+						that.bitb = b;
+						that.bitk = k;
+						z.avail_in = n;
+						z.total_in += p - z.next_in_index;
+						z.next_in_index = p;
+						that.write = q;
+						return that.inflate_flush(z, r);
+					}
+					break;
+				case LENS:
+
+					while (k < (32)) {
+						if (n !== 0) {
+							r = Z_OK;
+						} else {
+							that.bitb = b;
+							that.bitk = k;
+							z.avail_in = n;
+							z.total_in += p - z.next_in_index;
+							z.next_in_index = p;
+							that.write = q;
+							return that.inflate_flush(z, r);
+						}
+						n--;
+						b |= (z.read_byte(p++) & 0xff) << k;
+						k += 8;
+					}
+
+					if ((((~b) >>> 16) & 0xffff) != (b & 0xffff)) {
+						mode = BADBLOCKS;
+						z.msg = "invalid stored block lengths";
+						r = Z_DATA_ERROR;
+
+						that.bitb = b;
+						that.bitk = k;
+						z.avail_in = n;
+						z.total_in += p - z.next_in_index;
+						z.next_in_index = p;
+						that.write = q;
+						return that.inflate_flush(z, r);
+					}
+					left = (b & 0xffff);
+					b = k = 0; // dump bits
+					mode = left !== 0 ? STORED : (last !== 0 ? DRY : TYPE);
+					break;
+				case STORED:
+					if (n === 0) {
+						that.bitb = b;
+						that.bitk = k;
+						z.avail_in = n;
+						z.total_in += p - z.next_in_index;
+						z.next_in_index = p;
+						that.write = q;
+						return that.inflate_flush(z, r);
+					}
+
+					if (m === 0) {
+						if (q == that.end && that.read !== 0) {
+							q = 0;
+							m = /* (int) */(q < that.read ? that.read - q - 1 : that.end - q);
+						}
+						if (m === 0) {
+							that.write = q;
+							r = that.inflate_flush(z, r);
+							q = that.write;
+							m = /* (int) */(q < that.read ? that.read - q - 1 : that.end - q);
+							if (q == that.end && that.read !== 0) {
+								q = 0;
+								m = /* (int) */(q < that.read ? that.read - q - 1 : that.end - q);
+							}
+							if (m === 0) {
+								that.bitb = b;
+								that.bitk = k;
+								z.avail_in = n;
+								z.total_in += p - z.next_in_index;
+								z.next_in_index = p;
+								that.write = q;
+								return that.inflate_flush(z, r);
+							}
+						}
+					}
+					r = Z_OK;
+
+					t = left;
+					if (t > n)
+						t = n;
+					if (t > m)
+						t = m;
+					that.window.set(z.read_buf(p, t), q);
+					p += t;
+					n -= t;
+					q += t;
+					m -= t;
+					if ((left -= t) !== 0)
+						break;
+					mode = last !== 0 ? DRY : TYPE;
+					break;
+				case TABLE:
+
+					while (k < (14)) {
+						if (n !== 0) {
+							r = Z_OK;
+						} else {
+							that.bitb = b;
+							that.bitk = k;
+							z.avail_in = n;
+							z.total_in += p - z.next_in_index;
+							z.next_in_index = p;
+							that.write = q;
+							return that.inflate_flush(z, r);
+						}
+
+						n--;
+						b |= (z.read_byte(p++) & 0xff) << k;
+						k += 8;
+					}
+
+					table = t = (b & 0x3fff);
+					if ((t & 0x1f) > 29 || ((t >> 5) & 0x1f) > 29) {
+						mode = BADBLOCKS;
+						z.msg = "too many length or distance symbols";
+						r = Z_DATA_ERROR;
+
+						that.bitb = b;
+						that.bitk = k;
+						z.avail_in = n;
+						z.total_in += p - z.next_in_index;
+						z.next_in_index = p;
+						that.write = q;
+						return that.inflate_flush(z, r);
+					}
+					t = 258 + (t & 0x1f) + ((t >> 5) & 0x1f);
+					if (!blens || blens.length < t) {
+						blens = []; // new Array(t);
+					} else {
+						for (i = 0; i < t; i++) {
+							blens[i] = 0;
+						}
+					}
+
+					// {
+					b >>>= (14);
+					k -= (14);
+					// }
+
+					index = 0;
+					mode = BTREE;
+				case BTREE:
+					while (index < 4 + (table >>> 10)) {
+						while (k < (3)) {
+							if (n !== 0) {
+								r = Z_OK;
+							} else {
+								that.bitb = b;
+								that.bitk = k;
+								z.avail_in = n;
+								z.total_in += p - z.next_in_index;
+								z.next_in_index = p;
+								that.write = q;
+								return that.inflate_flush(z, r);
+							}
+							n--;
+							b |= (z.read_byte(p++) & 0xff) << k;
+							k += 8;
+						}
+
+						blens[border[index++]] = b & 7;
+
+						// {
+						b >>>= (3);
+						k -= (3);
+						// }
+					}
+
+					while (index < 19) {
+						blens[border[index++]] = 0;
+					}
+
+					bb[0] = 7;
+					t = inftree.inflate_trees_bits(blens, bb, tb, hufts, z);
+					if (t != Z_OK) {
+						r = t;
+						if (r == Z_DATA_ERROR) {
+							blens = null;
+							mode = BADBLOCKS;
+						}
+
+						that.bitb = b;
+						that.bitk = k;
+						z.avail_in = n;
+						z.total_in += p - z.next_in_index;
+						z.next_in_index = p;
+						that.write = q;
+						return that.inflate_flush(z, r);
+					}
+
+					index = 0;
+					mode = DTREE;
+				case DTREE:
+					while (true) {
+						t = table;
+						if (!(index < 258 + (t & 0x1f) + ((t >> 5) & 0x1f))) {
+							break;
+						}
+
+						var j, c;
+
+						t = bb[0];
+
+						while (k < (t)) {
+							if (n !== 0) {
+								r = Z_OK;
+							} else {
+								that.bitb = b;
+								that.bitk = k;
+								z.avail_in = n;
+								z.total_in += p - z.next_in_index;
+								z.next_in_index = p;
+								that.write = q;
+								return that.inflate_flush(z, r);
+							}
+							n--;
+							b |= (z.read_byte(p++) & 0xff) << k;
+							k += 8;
+						}
+
+						// if (tb[0] == -1) {
+						// System.err.println("null...");
+						// }
+
+						t = hufts[(tb[0] + (b & inflate_mask[t])) * 3 + 1];
+						c = hufts[(tb[0] + (b & inflate_mask[t])) * 3 + 2];
+
+						if (c < 16) {
+							b >>>= (t);
+							k -= (t);
+							blens[index++] = c;
+						} else { // c == 16..18
+							i = c == 18 ? 7 : c - 14;
+							j = c == 18 ? 11 : 3;
+
+							while (k < (t + i)) {
+								if (n !== 0) {
+									r = Z_OK;
+								} else {
+									that.bitb = b;
+									that.bitk = k;
+									z.avail_in = n;
+									z.total_in += p - z.next_in_index;
+									z.next_in_index = p;
+									that.write = q;
+									return that.inflate_flush(z, r);
+								}
+								n--;
+								b |= (z.read_byte(p++) & 0xff) << k;
+								k += 8;
+							}
+
+							b >>>= (t);
+							k -= (t);
+
+							j += (b & inflate_mask[i]);
+
+							b >>>= (i);
+							k -= (i);
+
+							i = index;
+							t = table;
+							if (i + j > 258 + (t & 0x1f) + ((t >> 5) & 0x1f) || (c == 16 && i < 1)) {
+								blens = null;
+								mode = BADBLOCKS;
+								z.msg = "invalid bit length repeat";
+								r = Z_DATA_ERROR;
+
+								that.bitb = b;
+								that.bitk = k;
+								z.avail_in = n;
+								z.total_in += p - z.next_in_index;
+								z.next_in_index = p;
+								that.write = q;
+								return that.inflate_flush(z, r);
+							}
+
+							c = c == 16 ? blens[i - 1] : 0;
+							do {
+								blens[i++] = c;
+							} while (--j !== 0);
+							index = i;
+						}
+					}
+
+					tb[0] = -1;
+					// {
+					var bl_ = []; // new Array(1);
+					var bd_ = []; // new Array(1);
+					var tl_ = []; // new Array(1);
+					var td_ = []; // new Array(1);
+					bl_[0] = 9; // must be <= 9 for lookahead assumptions
+					bd_[0] = 6; // must be <= 9 for lookahead assumptions
+
+					t = table;
+					t = inftree.inflate_trees_dynamic(257 + (t & 0x1f), 1 + ((t >> 5) & 0x1f), blens, bl_, bd_, tl_, td_, hufts, z);
+
+					if (t != Z_OK) {
+						if (t == Z_DATA_ERROR) {
+							blens = null;
+							mode = BADBLOCKS;
+						}
+						r = t;
+
+						that.bitb = b;
+						that.bitk = k;
+						z.avail_in = n;
+						z.total_in += p - z.next_in_index;
+						z.next_in_index = p;
+						that.write = q;
+						return that.inflate_flush(z, r);
+					}
+					codes.init(bl_[0], bd_[0], hufts, tl_[0], hufts, td_[0]);
+					// }
+					mode = CODES;
+				case CODES:
+					that.bitb = b;
+					that.bitk = k;
+					z.avail_in = n;
+					z.total_in += p - z.next_in_index;
+					z.next_in_index = p;
+					that.write = q;
+
+					if ((r = codes.proc(that, z, r)) != Z_STREAM_END) {
+						return that.inflate_flush(z, r);
+					}
+					r = Z_OK;
+					codes.free(z);
+
+					p = z.next_in_index;
+					n = z.avail_in;
+					b = that.bitb;
+					k = that.bitk;
+					q = that.write;
+					m = /* (int) */(q < that.read ? that.read - q - 1 : that.end - q);
+
+					if (last === 0) {
+						mode = TYPE;
+						break;
+					}
+					mode = DRY;
+				case DRY:
+					that.write = q;
+					r = that.inflate_flush(z, r);
+					q = that.write;
+					m = /* (int) */(q < that.read ? that.read - q - 1 : that.end - q);
+					if (that.read != that.write) {
+						that.bitb = b;
+						that.bitk = k;
+						z.avail_in = n;
+						z.total_in += p - z.next_in_index;
+						z.next_in_index = p;
+						that.write = q;
+						return that.inflate_flush(z, r);
+					}
+					mode = DONELOCKS;
+				case DONELOCKS:
+					r = Z_STREAM_END;
+
+					that.bitb = b;
+					that.bitk = k;
+					z.avail_in = n;
+					z.total_in += p - z.next_in_index;
+					z.next_in_index = p;
+					that.write = q;
+					return that.inflate_flush(z, r);
+				case BADBLOCKS:
+					r = Z_DATA_ERROR;
+
+					that.bitb = b;
+					that.bitk = k;
+					z.avail_in = n;
+					z.total_in += p - z.next_in_index;
+					z.next_in_index = p;
+					that.write = q;
+					return that.inflate_flush(z, r);
+
+				default:
+					r = Z_STREAM_ERROR;
+
+					that.bitb = b;
+					that.bitk = k;
+					z.avail_in = n;
+					z.total_in += p - z.next_in_index;
+					z.next_in_index = p;
+					that.write = q;
+					return that.inflate_flush(z, r);
+				}
+			}
+		};
+
+		that.free = function(z) {
+			that.reset(z, null);
+			that.window = null;
+			hufts = null;
+			// ZFREE(z, s);
+		};
+
+		that.set_dictionary = function(d, start, n) {
+			that.window.set(d.subarray(start, start + n), 0);
+			that.read = that.write = n;
+		};
+
+		// Returns true if inflate is currently at the end of a block generated
+		// by Z_SYNC_FLUSH or Z_FULL_FLUSH.
+		that.sync_point = function() {
+			return mode == LENS ? 1 : 0;
+		};
+
+	}
+
+	// Inflate
+
+	// preset dictionary flag in zlib header
+	var PRESET_DICT = 0x20;
+
+	var Z_DEFLATED = 8;
+
+	var METHOD = 0; // waiting for method byte
+	var FLAG = 1; // waiting for flag byte
+	var DICT4 = 2; // four dictionary check bytes to go
+	var DICT3 = 3; // three dictionary check bytes to go
+	var DICT2 = 4; // two dictionary check bytes to go
+	var DICT1 = 5; // one dictionary check byte to go
+	var DICT0 = 6; // waiting for inflateSetDictionary
+	var BLOCKS = 7; // decompressing blocks
+	var DONE = 12; // finished check, done
+	var BAD = 13; // got an error--stay here
+
+	var mark = [ 0, 0, 0xff, 0xff ];
+
+	function Inflate() {
+		var that = this;
+
+		that.mode = 0; // current inflate mode
+
+		// mode dependent information
+		that.method = 0; // if FLAGS, method byte
+
+		// if CHECK, check values to compare
+		that.was = [ 0 ]; // new Array(1); // computed check value
+		that.need = 0; // stream check value
+
+		// if BAD, inflateSync's marker bytes count
+		that.marker = 0;
+
+		// mode independent information
+		that.wbits = 0; // log2(window size) (8..15, defaults to 15)
+
+		// this.blocks; // current inflate_blocks state
+
+		function inflateReset(z) {
+			if (!z || !z.istate)
+				return Z_STREAM_ERROR;
+
+			z.total_in = z.total_out = 0;
+			z.msg = null;
+			z.istate.mode = BLOCKS;
+			z.istate.blocks.reset(z, null);
+			return Z_OK;
+		}
+
+		that.inflateEnd = function(z) {
+			if (that.blocks)
+				that.blocks.free(z);
+			that.blocks = null;
+			// ZFREE(z, z->state);
+			return Z_OK;
+		};
+
+		that.inflateInit = function(z, w) {
+			z.msg = null;
+			that.blocks = null;
+
+			// set window size
+			if (w < 8 || w > 15) {
+				that.inflateEnd(z);
+				return Z_STREAM_ERROR;
+			}
+			that.wbits = w;
+
+			z.istate.blocks = new InfBlocks(z, 1 << w);
+
+			// reset state
+			inflateReset(z);
+			return Z_OK;
+		};
+
+		that.inflate = function(z, f) {
+			var r;
+			var b;
+
+			if (!z || !z.istate || !z.next_in)
+				return Z_STREAM_ERROR;
+			f = f == Z_FINISH ? Z_BUF_ERROR : Z_OK;
+			r = Z_BUF_ERROR;
+			while (true) {
+				// System.out.println("mode: "+z.istate.mode);
+				switch (z.istate.mode) {
+				case METHOD:
+
+					if (z.avail_in === 0)
+						return r;
+					r = f;
+
+					z.avail_in--;
+					z.total_in++;
+					if (((z.istate.method = z.read_byte(z.next_in_index++)) & 0xf) != Z_DEFLATED) {
+						z.istate.mode = BAD;
+						z.msg = "unknown compression method";
+						z.istate.marker = 5; // can't try inflateSync
+						break;
+					}
+					if ((z.istate.method >> 4) + 8 > z.istate.wbits) {
+						z.istate.mode = BAD;
+						z.msg = "invalid window size";
+						z.istate.marker = 5; // can't try inflateSync
+						break;
+					}
+					z.istate.mode = FLAG;
+				case FLAG:
+
+					if (z.avail_in === 0)
+						return r;
+					r = f;
+
+					z.avail_in--;
+					z.total_in++;
+					b = (z.read_byte(z.next_in_index++)) & 0xff;
+
+					if ((((z.istate.method << 8) + b) % 31) !== 0) {
+						z.istate.mode = BAD;
+						z.msg = "incorrect header check";
+						z.istate.marker = 5; // can't try inflateSync
+						break;
+					}
+
+					if ((b & PRESET_DICT) === 0) {
+						z.istate.mode = BLOCKS;
+						break;
+					}
+					z.istate.mode = DICT4;
+				case DICT4:
+
+					if (z.avail_in === 0)
+						return r;
+					r = f;
+
+					z.avail_in--;
+					z.total_in++;
+					z.istate.need = ((z.read_byte(z.next_in_index++) & 0xff) << 24) & 0xff000000;
+					z.istate.mode = DICT3;
+				case DICT3:
+
+					if (z.avail_in === 0)
+						return r;
+					r = f;
+
+					z.avail_in--;
+					z.total_in++;
+					z.istate.need += ((z.read_byte(z.next_in_index++) & 0xff) << 16) & 0xff0000;
+					z.istate.mode = DICT2;
+				case DICT2:
+
+					if (z.avail_in === 0)
+						return r;
+					r = f;
+
+					z.avail_in--;
+					z.total_in++;
+					z.istate.need += ((z.read_byte(z.next_in_index++) & 0xff) << 8) & 0xff00;
+					z.istate.mode = DICT1;
+				case DICT1:
+
+					if (z.avail_in === 0)
+						return r;
+					r = f;
+
+					z.avail_in--;
+					z.total_in++;
+					z.istate.need += (z.read_byte(z.next_in_index++) & 0xff);
+					z.istate.mode = DICT0;
+					return Z_NEED_DICT;
+				case DICT0:
+					z.istate.mode = BAD;
+					z.msg = "need dictionary";
+					z.istate.marker = 0; // can try inflateSync
+					return Z_STREAM_ERROR;
+				case BLOCKS:
+
+					r = z.istate.blocks.proc(z, r);
+					if (r == Z_DATA_ERROR) {
+						z.istate.mode = BAD;
+						z.istate.marker = 0; // can try inflateSync
+						break;
+					}
+					if (r == Z_OK) {
+						r = f;
+					}
+					if (r != Z_STREAM_END) {
+						return r;
+					}
+					r = f;
+					z.istate.blocks.reset(z, z.istate.was);
+					z.istate.mode = DONE;
+				case DONE:
+					return Z_STREAM_END;
+				case BAD:
+					return Z_DATA_ERROR;
+				default:
+					return Z_STREAM_ERROR;
+				}
+			}
+		};
+
+		that.inflateSetDictionary = function(z, dictionary, dictLength) {
+			var index = 0;
+			var length = dictLength;
+			if (!z || !z.istate || z.istate.mode != DICT0)
+				return Z_STREAM_ERROR;
+
+			if (length >= (1 << z.istate.wbits)) {
+				length = (1 << z.istate.wbits) - 1;
+				index = dictLength - length;
+			}
+			z.istate.blocks.set_dictionary(dictionary, index, length);
+			z.istate.mode = BLOCKS;
+			return Z_OK;
+		};
+
+		that.inflateSync = function(z) {
+			var n; // number of bytes to look at
+			var p; // pointer to bytes
+			var m; // number of marker bytes found in a row
+			var r, w; // temporaries to save total_in and total_out
+
+			// set up
+			if (!z || !z.istate)
+				return Z_STREAM_ERROR;
+			if (z.istate.mode != BAD) {
+				z.istate.mode = BAD;
+				z.istate.marker = 0;
+			}
+			if ((n = z.avail_in) === 0)
+				return Z_BUF_ERROR;
+			p = z.next_in_index;
+			m = z.istate.marker;
+
+			// search
+			while (n !== 0 && m < 4) {
+				if (z.read_byte(p) == mark[m]) {
+					m++;
+				} else if (z.read_byte(p) !== 0) {
+					m = 0;
+				} else {
+					m = 4 - m;
+				}
+				p++;
+				n--;
+			}
+
+			// restore
+			z.total_in += p - z.next_in_index;
+			z.next_in_index = p;
+			z.avail_in = n;
+			z.istate.marker = m;
+
+			// return no joy or set up to restart on a new block
+			if (m != 4) {
+				return Z_DATA_ERROR;
+			}
+			r = z.total_in;
+			w = z.total_out;
+			inflateReset(z);
+			z.total_in = r;
+			z.total_out = w;
+			z.istate.mode = BLOCKS;
+			return Z_OK;
+		};
+
+		// Returns true if inflate is currently at the end of a block generated
+		// by Z_SYNC_FLUSH or Z_FULL_FLUSH. This function is used by one PPP
+		// implementation to provide an additional safety check. PPP uses
+		// Z_SYNC_FLUSH
+		// but removes the length bytes of the resulting empty stored block. When
+		// decompressing, PPP checks that at the end of input packet, inflate is
+		// waiting for these length bytes.
+		that.inflateSyncPoint = function(z) {
+			if (!z || !z.istate || !z.istate.blocks)
+				return Z_STREAM_ERROR;
+			return z.istate.blocks.sync_point();
+		};
+	}
+
+	// ZStream
+
+	function ZStream() {
+	}
+
+	ZStream.prototype = {
+		inflateInit : function(bits) {
+			var that = this;
+			that.istate = new Inflate();
+			if (!bits)
+				bits = MAX_BITS;
+			return that.istate.inflateInit(that, bits);
+		},
+
+		inflate : function(f) {
+			var that = this;
+			if (!that.istate)
+				return Z_STREAM_ERROR;
+			return that.istate.inflate(that, f);
+		},
+
+		inflateEnd : function() {
+			var that = this;
+			if (!that.istate)
+				return Z_STREAM_ERROR;
+			var ret = that.istate.inflateEnd(that);
+			that.istate = null;
+			return ret;
+		},
+
+		inflateSync : function() {
+			var that = this;
+			if (!that.istate)
+				return Z_STREAM_ERROR;
+			return that.istate.inflateSync(that);
+		},
+		inflateSetDictionary : function(dictionary, dictLength) {
+			var that = this;
+			if (!that.istate)
+				return Z_STREAM_ERROR;
+			return that.istate.inflateSetDictionary(that, dictionary, dictLength);
+		},
+		read_byte : function(start) {
+			var that = this;
+			return that.next_in.subarray(start, start + 1)[0];
+		},
+		read_buf : function(start, size) {
+			var that = this;
+			return that.next_in.subarray(start, start + size);
+		}
+	};
+
+	// Inflater
+
+	function Inflater() {
+		var that = this;
+		var z = new ZStream();
+		var bufsize = 512;
+		var flush = Z_NO_FLUSH;
+		var buf = new Uint8Array(bufsize);
+		var nomoreinput = false;
+
+		z.inflateInit();
+		z.next_out = buf;
+
+		that.append = function(data, onprogress) {
+			var err, buffers = [], lastIndex = 0, bufferIndex = 0, bufferSize = 0, array;
+			if (data.length === 0)
+				return;
+			z.next_in_index = 0;
+			z.next_in = data;
+			z.avail_in = data.length;
+			do {
+				z.next_out_index = 0;
+				z.avail_out = bufsize;
+				if ((z.avail_in === 0) && (!nomoreinput)) { // if buffer is empty and more input is available, refill it
+					z.next_in_index = 0;
+					nomoreinput = true;
+				}
+				err = z.inflate(flush);
+				if (nomoreinput && (err == Z_BUF_ERROR))
+					return -1;
+				if (err != Z_OK && err != Z_STREAM_END)
+					throw "inflating: " + z.msg;
+				if ((nomoreinput || err == Z_STREAM_END) && (z.avail_in == data.length))
+					return -1;
+				if (z.next_out_index)
+					if (z.next_out_index == bufsize)
+						buffers.push(new Uint8Array(buf));
+					else
+						buffers.push(new Uint8Array(buf.subarray(0, z.next_out_index)));
+				bufferSize += z.next_out_index;
+				if (onprogress && z.next_in_index > 0 && z.next_in_index != lastIndex) {
+					onprogress(z.next_in_index);
+					lastIndex = z.next_in_index;
+				}
+			} while (z.avail_in > 0 || z.avail_out === 0);
+			array = new Uint8Array(bufferSize);
+			buffers.forEach(function(chunk) {
+				array.set(chunk, bufferIndex);
+				bufferIndex += chunk.length;
+			});
+			return array;
+		};
+		that.flush = function() {
+			z.inflateEnd();
+		};
+	}
+
+	var inflater;
+
+	if (obj.zip)
+		obj.zip.Inflater = Inflater;
+	else {
+		inflater = new Inflater();
+		obj.addEventListener("message", function(event) {
+			var message = event.data;
+
+			if (message.append)
+				obj.postMessage({
+					onappend : true,
+					data : inflater.append(message.data, function(current) {
+						obj.postMessage({
+							progress : true,
+							current : current
+						});
+					})
+				});
+			if (message.flush) {
+				inflater.flush();
+				obj.postMessage({
+					onflush : true
+				});
+			}
+		}, false);
+	}
+
+})(this);

+ 801 - 0
lib/zip.js/zip.js

@@ -0,0 +1,801 @@
+/*
+ Copyright (c) 2013 Gildas Lormeau. All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the distribution.
+
+ 3. The names of the authors may not be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
+ INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
+ INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
+ INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
+ OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+(function(obj) {
+
+	var ERR_BAD_FORMAT = "File format is not recognized.";
+	var ERR_ENCRYPTED = "File contains encrypted entry.";
+	var ERR_ZIP64 = "File is using Zip64 (4gb+ file size).";
+	var ERR_READ = "Error while reading zip file.";
+	var ERR_WRITE = "Error while writing zip file.";
+	var ERR_WRITE_DATA = "Error while writing file data.";
+	var ERR_READ_DATA = "Error while reading file data.";
+	var ERR_DUPLICATED_NAME = "File already exists.";
+	var CHUNK_SIZE = 512 * 1024;
+
+	var INFLATE_JS = "inflate.js";
+	var DEFLATE_JS = "deflate.js";
+	
+	var TEXT_PLAIN = "text/plain";
+	
+	var MESSAGE_EVENT = "message";
+
+	var appendABViewSupported;
+	try {
+		appendABViewSupported = new Blob([ new DataView(new ArrayBuffer(0)) ]).size === 0;
+	} catch (e) {
+	}
+
+	function Crc32() {
+		var crc = -1, that = this;
+		that.append = function(data) {
+			var offset, table = that.table;
+			for (offset = 0; offset < data.length; offset++)
+				crc = (crc >>> 8) ^ table[(crc ^ data[offset]) & 0xFF];
+		};
+		that.get = function() {
+			return ~crc;
+		};
+	}
+	Crc32.prototype.table = (function() {
+		var i, j, t, table = [];
+		for (i = 0; i < 256; i++) {
+			t = i;
+			for (j = 0; j < 8; j++)
+				if (t & 1)
+					t = (t >>> 1) ^ 0xEDB88320;
+				else
+					t = t >>> 1;
+			table[i] = t;
+		}
+		return table;
+	})();
+
+	function blobSlice(blob, index, length) {
+		if (blob.slice)
+			return blob.slice(index, index + length);
+		else if (blob.webkitSlice)
+			return blob.webkitSlice(index, index + length);
+		else if (blob.mozSlice)
+			return blob.mozSlice(index, index + length);
+		else if (blob.msSlice)
+			return blob.msSlice(index, index + length);
+	}
+
+	function getDataHelper(byteLength, bytes) {
+		var dataBuffer, dataArray;
+		dataBuffer = new ArrayBuffer(byteLength);
+		dataArray = new Uint8Array(dataBuffer);
+		if (bytes)
+			dataArray.set(bytes, 0);
+		return {
+			buffer : dataBuffer,
+			array : dataArray,
+			view : new DataView(dataBuffer)
+		};
+	}
+
+	// Readers
+	function Reader() {
+	}
+
+	function TextReader(text) {
+		var that = this, blobReader;
+
+		function init(callback, onerror) {
+			var blob = new Blob([ text ], {
+				type : TEXT_PLAIN
+			});
+			blobReader = new BlobReader(blob);
+			blobReader.init(function() {
+				that.size = blobReader.size;
+				callback();
+			}, onerror);
+		}
+
+		function readUint8Array(index, length, callback, onerror) {
+			blobReader.readUint8Array(index, length, callback, onerror);
+		}
+
+		that.size = 0;
+		that.init = init;
+		that.readUint8Array = readUint8Array;
+	}
+	TextReader.prototype = new Reader();
+	TextReader.prototype.constructor = TextReader;
+
+	function Data64URIReader(dataURI) {
+		var that = this, dataStart;
+
+		function init(callback) {
+			var dataEnd = dataURI.length;
+			while (dataURI.charAt(dataEnd - 1) == "=")
+				dataEnd--;
+			dataStart = dataURI.indexOf(",") + 1;
+			that.size = Math.floor((dataEnd - dataStart) * 0.75);
+			callback();
+		}
+
+		function readUint8Array(index, length, callback) {
+			var i, data = getDataHelper(length);
+			var start = Math.floor(index / 3) * 4;
+			var end = Math.ceil((index + length) / 3) * 4;
+			var bytes = obj.atob(dataURI.substring(start + dataStart, end + dataStart));
+			var delta = index - Math.floor(start / 4) * 3;
+			for (i = delta; i < delta + length; i++)
+				data.array[i - delta] = bytes.charCodeAt(i);
+			callback(data.array);
+		}
+
+		that.size = 0;
+		that.init = init;
+		that.readUint8Array = readUint8Array;
+	}
+	Data64URIReader.prototype = new Reader();
+	Data64URIReader.prototype.constructor = Data64URIReader;
+
+	function BlobReader(blob) {
+		var that = this;
+
+		function init(callback) {
+			this.size = blob.size;
+			callback();
+		}
+
+		function readUint8Array(index, length, callback, onerror) {
+			var reader = new FileReader();
+			reader.onload = function(e) {
+				callback(new Uint8Array(e.target.result));
+			};
+			reader.onerror = onerror;
+			reader.readAsArrayBuffer(blobSlice(blob, index, length));
+		}
+
+		that.size = 0;
+		that.init = init;
+		that.readUint8Array = readUint8Array;
+	}
+	BlobReader.prototype = new Reader();
+	BlobReader.prototype.constructor = BlobReader;
+
+	// Writers
+
+	function Writer() {
+	}
+	Writer.prototype.getData = function(callback) {
+		callback(this.data);
+	};
+
+	function TextWriter(encoding) {
+		var that = this, blob;
+
+		function init(callback) {
+			blob = new Blob([], {
+				type : TEXT_PLAIN
+			});
+			callback();
+		}
+
+		function writeUint8Array(array, callback) {
+			blob = new Blob([ blob, appendABViewSupported ? array : array.buffer ], {
+				type : TEXT_PLAIN
+			});
+			callback();
+		}
+
+		function getData(callback, onerror) {
+			var reader = new FileReader();
+			reader.onload = function(e) {
+				callback(e.target.result);
+			};
+			reader.onerror = onerror;
+			reader.readAsText(blob, encoding);
+		}
+
+		that.init = init;
+		that.writeUint8Array = writeUint8Array;
+		that.getData = getData;
+	}
+	TextWriter.prototype = new Writer();
+	TextWriter.prototype.constructor = TextWriter;
+
+	function Data64URIWriter(contentType) {
+		var that = this, data = "", pending = "";
+
+		function init(callback) {
+			data += "data:" + (contentType || "") + ";base64,";
+			callback();
+		}
+
+		function writeUint8Array(array, callback) {
+			var i, delta = pending.length, dataString = pending;
+			pending = "";
+			for (i = 0; i < (Math.floor((delta + array.length) / 3) * 3) - delta; i++)
+				dataString += String.fromCharCode(array[i]);
+			for (; i < array.length; i++)
+				pending += String.fromCharCode(array[i]);
+			if (dataString.length > 2)
+				data += obj.btoa(dataString);
+			else
+				pending = dataString;
+			callback();
+		}
+
+		function getData(callback) {
+			callback(data + obj.btoa(pending));
+		}
+
+		that.init = init;
+		that.writeUint8Array = writeUint8Array;
+		that.getData = getData;
+	}
+	Data64URIWriter.prototype = new Writer();
+	Data64URIWriter.prototype.constructor = Data64URIWriter;
+
+	function BlobWriter(contentType) {
+		var blob, that = this;
+
+		function init(callback) {
+			blob = new Blob([], {
+				type : contentType
+			});
+			callback();
+		}
+
+		function writeUint8Array(array, callback) {
+			blob = new Blob([ blob, appendABViewSupported ? array : array.buffer ], {
+				type : contentType
+			});
+			callback();
+		}
+
+		function getData(callback) {
+			callback(blob);
+		}
+
+		that.init = init;
+		that.writeUint8Array = writeUint8Array;
+		that.getData = getData;
+	}
+	BlobWriter.prototype = new Writer();
+	BlobWriter.prototype.constructor = BlobWriter;
+
+	// inflate/deflate core functions
+
+	function launchWorkerProcess(worker, reader, writer, offset, size, onappend, onprogress, onend, onreaderror, onwriteerror) {
+		var chunkIndex = 0, index, outputSize;
+
+		function onflush() {
+			worker.removeEventListener(MESSAGE_EVENT, onmessage, false);
+			onend(outputSize);
+		}
+
+		function onmessage(event) {
+			var message = event.data, data = message.data;
+
+			if (message.onappend) {
+				outputSize += data.length;
+				writer.writeUint8Array(data, function() {
+					onappend(false, data);
+					step();
+				}, onwriteerror);
+			}
+			if (message.onflush)
+				if (data) {
+					outputSize += data.length;
+					writer.writeUint8Array(data, function() {
+						onappend(false, data);
+						onflush();
+					}, onwriteerror);
+				} else
+					onflush();
+			if (message.progress && onprogress)
+				onprogress(index + message.current, size);
+		}
+
+		function step() {
+			index = chunkIndex * CHUNK_SIZE;
+			if (index < size)
+				reader.readUint8Array(offset + index, Math.min(CHUNK_SIZE, size - index), function(array) {
+					worker.postMessage({
+						append : true,
+						data : array
+					});
+					chunkIndex++;
+					if (onprogress)
+						onprogress(index, size);
+					onappend(true, array);
+				}, onreaderror);
+			else
+				worker.postMessage({
+					flush : true
+				});
+		}
+
+		outputSize = 0;
+		worker.addEventListener(MESSAGE_EVENT, onmessage, false);
+		step();
+	}
+
+	function launchProcess(process, reader, writer, offset, size, onappend, onprogress, onend, onreaderror, onwriteerror) {
+		var chunkIndex = 0, index, outputSize = 0;
+
+		function step() {
+			var outputData;
+			index = chunkIndex * CHUNK_SIZE;
+			if (index < size)
+				reader.readUint8Array(offset + index, Math.min(CHUNK_SIZE, size - index), function(inputData) {
+					var outputData = process.append(inputData, function() {
+						if (onprogress)
+							onprogress(offset + index, size);
+					});
+					outputSize += outputData.length;
+					onappend(true, inputData);
+					writer.writeUint8Array(outputData, function() {
+						onappend(false, outputData);
+						chunkIndex++;
+						setTimeout(step, 1);
+					}, onwriteerror);
+					if (onprogress)
+						onprogress(index, size);
+				}, onreaderror);
+			else {
+				outputData = process.flush();
+				if (outputData) {
+					outputSize += outputData.length;
+					writer.writeUint8Array(outputData, function() {
+						onappend(false, outputData);
+						onend(outputSize);
+					}, onwriteerror);
+				} else
+					onend(outputSize);
+			}
+		}
+
+		step();
+	}
+
+	function inflate(reader, writer, offset, size, computeCrc32, onend, onprogress, onreaderror, onwriteerror) {
+		var worker, crc32 = new Crc32();
+
+		function oninflateappend(sending, array) {
+			if (computeCrc32 && !sending)
+				crc32.append(array);
+		}
+
+		function oninflateend(outputSize) {
+			onend(outputSize, crc32.get());
+		}
+
+		if (obj.zip.useWebWorkers) {
+			worker = new Worker(obj.zip.workerScriptsPath + INFLATE_JS);
+			launchWorkerProcess(worker, reader, writer, offset, size, oninflateappend, onprogress, oninflateend, onreaderror, onwriteerror);
+		} else
+			launchProcess(new obj.zip.Inflater(), reader, writer, offset, size, oninflateappend, onprogress, oninflateend, onreaderror, onwriteerror);
+		return worker;
+	}
+
+	function deflate(reader, writer, level, onend, onprogress, onreaderror, onwriteerror) {
+		var worker, crc32 = new Crc32();
+
+		function ondeflateappend(sending, array) {
+			if (sending)
+				crc32.append(array);
+		}
+
+		function ondeflateend(outputSize) {
+			onend(outputSize, crc32.get());
+		}
+
+		function onmessage() {
+			worker.removeEventListener(MESSAGE_EVENT, onmessage, false);
+			launchWorkerProcess(worker, reader, writer, 0, reader.size, ondeflateappend, onprogress, ondeflateend, onreaderror, onwriteerror);
+		}
+
+		if (obj.zip.useWebWorkers) {
+			worker = new Worker(obj.zip.workerScriptsPath + DEFLATE_JS);
+			worker.addEventListener(MESSAGE_EVENT, onmessage, false);
+			worker.postMessage({
+				init : true,
+				level : level
+			});
+		} else
+			launchProcess(new obj.zip.Deflater(), reader, writer, 0, reader.size, ondeflateappend, onprogress, ondeflateend, onreaderror, onwriteerror);
+		return worker;
+	}
+
+	function copy(reader, writer, offset, size, computeCrc32, onend, onprogress, onreaderror, onwriteerror) {
+		var chunkIndex = 0, crc32 = new Crc32();
+
+		function step() {
+			var index = chunkIndex * CHUNK_SIZE;
+			if (index < size)
+				reader.readUint8Array(offset + index, Math.min(CHUNK_SIZE, size - index), function(array) {
+					if (computeCrc32)
+						crc32.append(array);
+					if (onprogress)
+						onprogress(index, size, array);
+					writer.writeUint8Array(array, function() {
+						chunkIndex++;
+						step();
+					}, onwriteerror);
+				}, onreaderror);
+			else
+				onend(size, crc32.get());
+		}
+
+		step();
+	}
+
+	// ZipReader
+
+	function decodeASCII(str) {
+		var i, out = "", charCode, extendedASCII = [ '\u00C7', '\u00FC', '\u00E9', '\u00E2', '\u00E4', '\u00E0', '\u00E5', '\u00E7', '\u00EA', '\u00EB',
+				'\u00E8', '\u00EF', '\u00EE', '\u00EC', '\u00C4', '\u00C5', '\u00C9', '\u00E6', '\u00C6', '\u00F4', '\u00F6', '\u00F2', '\u00FB', '\u00F9',
+				'\u00FF', '\u00D6', '\u00DC', '\u00F8', '\u00A3', '\u00D8', '\u00D7', '\u0192', '\u00E1', '\u00ED', '\u00F3', '\u00FA', '\u00F1', '\u00D1',
+				'\u00AA', '\u00BA', '\u00BF', '\u00AE', '\u00AC', '\u00BD', '\u00BC', '\u00A1', '\u00AB', '\u00BB', '_', '_', '_', '\u00A6', '\u00A6',
+				'\u00C1', '\u00C2', '\u00C0', '\u00A9', '\u00A6', '\u00A6', '+', '+', '\u00A2', '\u00A5', '+', '+', '-', '-', '+', '-', '+', '\u00E3',
+				'\u00C3', '+', '+', '-', '-', '\u00A6', '-', '+', '\u00A4', '\u00F0', '\u00D0', '\u00CA', '\u00CB', '\u00C8', 'i', '\u00CD', '\u00CE',
+				'\u00CF', '+', '+', '_', '_', '\u00A6', '\u00CC', '_', '\u00D3', '\u00DF', '\u00D4', '\u00D2', '\u00F5', '\u00D5', '\u00B5', '\u00FE',
+				'\u00DE', '\u00DA', '\u00DB', '\u00D9', '\u00FD', '\u00DD', '\u00AF', '\u00B4', '\u00AD', '\u00B1', '_', '\u00BE', '\u00B6', '\u00A7',
+				'\u00F7', '\u00B8', '\u00B0', '\u00A8', '\u00B7', '\u00B9', '\u00B3', '\u00B2', '_', ' ' ];
+		for (i = 0; i < str.length; i++) {
+			charCode = str.charCodeAt(i) & 0xFF;
+			if (charCode > 127)
+				out += extendedASCII[charCode - 128];
+			else
+				out += String.fromCharCode(charCode);
+		}
+		return out;
+	}
+
+	function decodeUTF8(string) {
+		return decodeURIComponent(escape(string));
+	}
+
+	function getString(bytes) {
+		var i, str = "";
+		for (i = 0; i < bytes.length; i++)
+			str += String.fromCharCode(bytes[i]);
+		return str;
+	}
+
+	function getDate(timeRaw) {
+		var date = (timeRaw & 0xffff0000) >> 16, time = timeRaw & 0x0000ffff;
+		try {
+			return new Date(1980 + ((date & 0xFE00) >> 9), ((date & 0x01E0) >> 5) - 1, date & 0x001F, (time & 0xF800) >> 11, (time & 0x07E0) >> 5,
+					(time & 0x001F) * 2, 0);
+		} catch (e) {
+		}
+	}
+
+	function readCommonHeader(entry, data, index, centralDirectory, onerror) {
+		entry.version = data.view.getUint16(index, true);
+		entry.bitFlag = data.view.getUint16(index + 2, true);
+		entry.compressionMethod = data.view.getUint16(index + 4, true);
+		entry.lastModDateRaw = data.view.getUint32(index + 6, true);
+		entry.lastModDate = getDate(entry.lastModDateRaw);
+		if ((entry.bitFlag & 0x01) === 0x01) {
+			onerror(ERR_ENCRYPTED);
+			return;
+		}
+		if (centralDirectory || (entry.bitFlag & 0x0008) != 0x0008) {
+			entry.crc32 = data.view.getUint32(index + 10, true);
+			entry.compressedSize = data.view.getUint32(index + 14, true);
+			entry.uncompressedSize = data.view.getUint32(index + 18, true);
+		}
+		if (entry.compressedSize === 0xFFFFFFFF || entry.uncompressedSize === 0xFFFFFFFF) {
+			onerror(ERR_ZIP64);
+			return;
+		}
+		entry.filenameLength = data.view.getUint16(index + 22, true);
+		entry.extraFieldLength = data.view.getUint16(index + 24, true);
+	}
+
+	function createZipReader(reader, onerror) {
+		function Entry() {
+		}
+
+		Entry.prototype.getData = function(writer, onend, onprogress, checkCrc32) {
+			var that = this, worker;
+
+			function terminate(callback, param) {
+				if (worker)
+					worker.terminate();
+				worker = null;
+				if (callback)
+					callback(param);
+			}
+
+			function testCrc32(crc32) {
+				var dataCrc32 = getDataHelper(4);
+				dataCrc32.view.setUint32(0, crc32);
+				return that.crc32 == dataCrc32.view.getUint32(0);
+			}
+
+			function getWriterData(uncompressedSize, crc32) {
+				if (checkCrc32 && !testCrc32(crc32))
+					onreaderror();
+				else
+					writer.getData(function(data) {
+						terminate(onend, data);
+					});
+			}
+
+			function onreaderror() {
+				terminate(onerror, ERR_READ_DATA);
+			}
+
+			function onwriteerror() {
+				terminate(onerror, ERR_WRITE_DATA);
+			}
+
+			reader.readUint8Array(that.offset, 30, function(bytes) {
+				var data = getDataHelper(bytes.length, bytes), dataOffset;
+				if (data.view.getUint32(0) != 0x504b0304) {
+					onerror(ERR_BAD_FORMAT);
+					return;
+				}
+				readCommonHeader(that, data, 4, false, onerror);
+				dataOffset = that.offset + 30 + that.filenameLength + that.extraFieldLength;
+				writer.init(function() {
+					if (that.compressionMethod === 0)
+						copy(reader, writer, dataOffset, that.compressedSize, checkCrc32, getWriterData, onprogress, onreaderror, onwriteerror);
+					else
+						worker = inflate(reader, writer, dataOffset, that.compressedSize, checkCrc32, getWriterData, onprogress, onreaderror, onwriteerror);
+				}, onwriteerror);
+			}, onreaderror);
+		};
+
+		function seekEOCDR(offset, entriesCallback) {
+			reader.readUint8Array(reader.size - offset, offset, function(bytes) {
+				var dataView = getDataHelper(bytes.length, bytes).view;
+				if (dataView.getUint32(0) != 0x504b0506) {
+					seekEOCDR(offset + 1, entriesCallback);
+				} else {
+					entriesCallback(dataView);
+				}
+			}, function() {
+				onerror(ERR_READ);
+			});
+		}
+
+		return {
+			getEntries : function(callback) {
+				if (reader.size < 22) {
+					onerror(ERR_BAD_FORMAT);
+					return;
+				}
+				// look for End of central directory record
+				seekEOCDR(22, function(dataView) {
+					var datalength, fileslength;
+					datalength = dataView.getUint32(16, true);
+					fileslength = dataView.getUint16(8, true);
+					reader.readUint8Array(datalength, reader.size - datalength, function(bytes) {
+						var i, index = 0, entries = [], entry, filename, comment, data = getDataHelper(bytes.length, bytes);
+						for (i = 0; i < fileslength; i++) {
+							entry = new Entry();
+							if (data.view.getUint32(index) != 0x504b0102) {
+								onerror(ERR_BAD_FORMAT);
+								return;
+							}
+							readCommonHeader(entry, data, index + 6, true, onerror);
+							entry.commentLength = data.view.getUint16(index + 32, true);
+							entry.directory = ((data.view.getUint8(index + 38) & 0x10) == 0x10);
+							entry.offset = data.view.getUint32(index + 42, true);
+							filename = getString(data.array.subarray(index + 46, index + 46 + entry.filenameLength));
+							entry.filename = ((entry.bitFlag & 0x0800) === 0x0800) ? decodeUTF8(filename) : decodeASCII(filename);
+							if (!entry.directory && entry.filename.charAt(entry.filename.length - 1) == "/")
+								entry.directory = true;
+							comment = getString(data.array.subarray(index + 46 + entry.filenameLength + entry.extraFieldLength, index + 46
+									+ entry.filenameLength + entry.extraFieldLength + entry.commentLength));
+							entry.comment = ((entry.bitFlag & 0x0800) === 0x0800) ? decodeUTF8(comment) : decodeASCII(comment);
+							entries.push(entry);
+							index += 46 + entry.filenameLength + entry.extraFieldLength + entry.commentLength;
+						}
+						callback(entries);
+					}, function() {
+						onerror(ERR_READ);
+					});
+				});
+			},
+			close : function(callback) {
+				if (callback)
+					callback();
+			}
+		};
+	}
+
+	// ZipWriter
+
+	function encodeUTF8(string) {
+		return unescape(encodeURIComponent(string));
+	}
+
+	function getBytes(str) {
+		var i, array = [];
+		for (i = 0; i < str.length; i++)
+			array.push(str.charCodeAt(i));
+		return array;
+	}
+
+	function createZipWriter(writer, onerror, dontDeflate) {
+		var worker, files = {}, filenames = [], datalength = 0;
+
+		function terminate(callback, message) {
+			if (worker)
+				worker.terminate();
+			worker = null;
+			if (callback)
+				callback(message);
+		}
+
+		function onwriteerror() {
+			terminate(onerror, ERR_WRITE);
+		}
+
+		function onreaderror() {
+			terminate(onerror, ERR_READ_DATA);
+		}
+
+		return {
+			add : function(name, reader, onend, onprogress, options) {
+				var header, filename, date;
+
+				function writeHeader(callback) {
+					var data;
+					date = options.lastModDate || new Date();
+					header = getDataHelper(26);
+					files[name] = {
+						headerArray : header.array,
+						directory : options.directory,
+						filename : filename,
+						offset : datalength,
+						comment : getBytes(encodeUTF8(options.comment || ""))
+					};
+					header.view.setUint32(0, 0x14000808);
+					if (options.version)
+						header.view.setUint8(0, options.version);
+					if (!dontDeflate && options.level !== 0 && !options.directory)
+						header.view.setUint16(4, 0x0800);
+					header.view.setUint16(6, (((date.getHours() << 6) | date.getMinutes()) << 5) | date.getSeconds() / 2, true);
+					header.view.setUint16(8, ((((date.getFullYear() - 1980) << 4) | (date.getMonth() + 1)) << 5) | date.getDate(), true);
+					header.view.setUint16(22, filename.length, true);
+					data = getDataHelper(30 + filename.length);
+					data.view.setUint32(0, 0x504b0304);
+					data.array.set(header.array, 4);
+					data.array.set(filename, 30);
+					datalength += data.array.length;
+					writer.writeUint8Array(data.array, callback, onwriteerror);
+				}
+
+				function writeFooter(compressedLength, crc32) {
+					var footer = getDataHelper(16);
+					datalength += compressedLength || 0;
+					footer.view.setUint32(0, 0x504b0708);
+					if (typeof crc32 != "undefined") {
+						header.view.setUint32(10, crc32, true);
+						footer.view.setUint32(4, crc32, true);
+					}
+					if (reader) {
+						footer.view.setUint32(8, compressedLength, true);
+						header.view.setUint32(14, compressedLength, true);
+						footer.view.setUint32(12, reader.size, true);
+						header.view.setUint32(18, reader.size, true);
+					}
+					writer.writeUint8Array(footer.array, function() {
+						datalength += 16;
+						terminate(onend);
+					}, onwriteerror);
+				}
+
+				function writeFile() {
+					options = options || {};
+					name = name.trim();
+					if (options.directory && name.charAt(name.length - 1) != "/")
+						name += "/";
+					if (files.hasOwnProperty(name)) {
+						onerror(ERR_DUPLICATED_NAME);
+						return;
+					}
+					filename = getBytes(encodeUTF8(name));
+					filenames.push(name);
+					writeHeader(function() {
+						if (reader)
+							if (dontDeflate || options.level === 0)
+								copy(reader, writer, 0, reader.size, true, writeFooter, onprogress, onreaderror, onwriteerror);
+							else
+								worker = deflate(reader, writer, options.level, writeFooter, onprogress, onreaderror, onwriteerror);
+						else
+							writeFooter();
+					}, onwriteerror);
+				}
+
+				if (reader)
+					reader.init(writeFile, onreaderror);
+				else
+					writeFile();
+			},
+			close : function(callback) {
+				var data, length = 0, index = 0, indexFilename, file;
+				for (indexFilename = 0; indexFilename < filenames.length; indexFilename++) {
+					file = files[filenames[indexFilename]];
+					length += 46 + file.filename.length + file.comment.length;
+				}
+				data = getDataHelper(length + 22);
+				for (indexFilename = 0; indexFilename < filenames.length; indexFilename++) {
+					file = files[filenames[indexFilename]];
+					data.view.setUint32(index, 0x504b0102);
+					data.view.setUint16(index + 4, 0x1400);
+					data.array.set(file.headerArray, index + 6);
+					data.view.setUint16(index + 32, file.comment.length, true);
+					if (file.directory)
+						data.view.setUint8(index + 38, 0x10);
+					data.view.setUint32(index + 42, file.offset, true);
+					data.array.set(file.filename, index + 46);
+					data.array.set(file.comment, index + 46 + file.filename.length);
+					index += 46 + file.filename.length + file.comment.length;
+				}
+				data.view.setUint32(index, 0x504b0506);
+				data.view.setUint16(index + 8, filenames.length, true);
+				data.view.setUint16(index + 10, filenames.length, true);
+				data.view.setUint32(index + 12, length, true);
+				data.view.setUint32(index + 16, datalength, true);
+				writer.writeUint8Array(data.array, function() {
+					terminate(function() {
+						writer.getData(callback);
+					});
+				}, onwriteerror);
+			}
+		};
+	}
+
+	obj.zip = {
+		Reader : Reader,
+		Writer : Writer,
+		BlobReader : BlobReader,
+		Data64URIReader : Data64URIReader,
+		TextReader : TextReader,
+		BlobWriter : BlobWriter,
+		Data64URIWriter : Data64URIWriter,
+		TextWriter : TextWriter,
+		createReader : function(reader, callback, onerror) {
+			reader.init(function() {
+				callback(createZipReader(reader, onerror));
+			}, onerror);
+		},
+		createWriter : function(writer, callback, onerror, dontDeflate) {
+			writer.init(function() {
+				callback(createZipWriter(writer, onerror, dontDeflate));
+			}, onerror);
+		},
+		workerScriptsPath : "",
+		useWebWorkers : true
+	};
+
+})(this);

+ 32 - 0
manifest.json

@@ -0,0 +1,32 @@
+{
+	"name": "Violent monkey",
+	"version" : "2.0",
+	"manifest_version" : 2,
+	"description" : "__MSG_extDescription__",
+	"developer": {"name" : "Gerald", "url" : "http://gleo.tk"},
+	"homepage_url":"http://github.com/gera2ld/Violentmonkey-nex",
+	"icons" : {
+		"16":"images/icon16.png",
+		"48":"images/icon48.png",
+		"128":"images/icon128.png"
+	},
+	"default_locale":"en",
+	"browser_action" : {
+		"default_icon":"images/icon19.png",
+		"default_title":"__MSG_extName__",
+		"default_popup":"popup.html"
+	},
+	"background" : {"page" : "background.html"},
+	"options_page" : "options.html",
+	"content_scripts": [{
+		"js": ["injected.js"],
+		"matches": ["<all_urls>"],
+		"include_globs": ["*"],
+		"exclude_globs": [],
+		"run_at": "document_start",
+		"all_frames" : true
+	}],
+	"web_accessible_resources" : [],
+	"permissions" : ["tabs", "<all_urls>", "storage", "webRequest", "webRequestBlocking"],
+	"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'unsafe-eval';"
+}

+ 140 - 0
options.html

@@ -0,0 +1,140 @@
+<!doctype html>
+<html class=fill>
+	<head>
+		<meta http-equiv="content-type" content="text/html;charset=utf-8">
+		<link rel="stylesheet" type="text/css" href="style.css" />
+		<link rel="stylesheet" type="text/css" href="lib/codemirror/codemirror.css" />
+		<link rel="shortcut icon" type="image/png" href="icons/icon_16.png" />
+		<script type="text/javascript" src="lib/codemirror/codemirror.js"></script>
+		<script type="text/javascript" src="lib/codemirror/javascript.js"></script>
+		<script type="text/javascript" src="lib/codemirror/continuecomment.js"></script>
+		<script type="text/javascript" src="lib/codemirror/matchbrackets.js"></script>
+		<script type="text/javascript" src="lib/codemirror/match-highlighter.js"></script>
+		<script type="text/javascript" src="lib/codemirror/search.js"></script>
+		<script type="text/javascript" src="lib/codemirror/searchcursor.js"></script>
+		<script type="text/javascript" src="lib/zip.js/zip.js"></script>
+		<title class=i18n>extName</title>
+	</head>
+	<body class=fill>
+		<table id=main class=frame>
+			<tr><td><h1 class=center><span class=i18n>labelOptions</span> - <span class=i18n>extName</span></h1></td></tr>
+			<tr>
+				<td>
+					<div class=left>
+						<button id=bNew class=i18n>buttonNew</button>
+						<span class=i18n>anchorGetMoreScripts</span>
+					</div>
+					<div class=right>
+						<a id=bUpdate class=i18n href=#>anchorUpdateAll</a>
+						<button id=bAdvanced class=i18n>buttonAdvanced</button>
+					</div>
+					<div class=x></div>
+				</td>
+			</tr>
+			<tr class=expand>
+				<td class=expandr>
+					<div class=expanda>
+						<fieldset>
+							<legend>
+								<span class=i18n>labelInstalledScripts</span>
+								<label><input type=checkbox id=cDetail><span class=i18n>labelShowDetails</span></label>
+							</legend>
+							<div id=sList></div>
+						</fieldset>
+					</div>
+				</td>
+			</tr>
+			<tr><td class=center><span class=i18n>anchorSupportPage</span> - <span class=i18n>extAuthor</span> - 2013</td></tr>
+		</table>
+		<div id=overlay class=hide></div>
+		<table id=editor class="frame hide">
+			<tr>
+				<td><h2 class=i18n>labelScriptEditor</h2></td>
+				<td class=buttons><button id=bcustom class=i18n>buttonCustomMeta</button></td>
+			</tr>
+			<tr class=expand><td colspan=2 class=expandr><textarea id=eCode class=expanda></textarea></td></tr>
+			<tr>
+				<td colspan=2>
+					<label class=left><input type=checkbox id=eUpdate><span class=i18n>labelAllowUpdate</span></label>
+					<div class=right>
+						<button id=eSave class=i18n>buttonSave</button>
+						<button id=eSaveClose class=i18n>buttonSaveClose</button>
+						<button id=eClose class=i18n>buttonClose</button>
+					</div>
+				</td>
+			</tr>
+		</table>
+		<div id=meta class="float hide">
+			<table>
+				<tr>
+					<td title="@name" class=i18n>labelName</td><td class=expand><input type=text id=mName></td>
+					<td title="@run-at" class=i18n>labelRunAt</td><td>
+						<select id=mRunAt>
+							<option class=i18n value=default>labelRunAtDefault</option>
+							<option value=start>document-start</option>
+							<option value=body>document-body</option>
+							<option value=end>document-end</option>
+						</select>
+					</td>
+				</tr>
+				<tr title="@homepage"><td class=i18n>labelHomepage</td><td colspan=3 class=expand><input type=text id=mHomepage></td></tr>
+			</table>
+			<table>
+				<tr title="@updateURL"><td class=i18n>labelUpdateURL</td><td class=expand><input type=text id=mUpdateURL></td></tr>
+				<tr title="@downloadURL"><td class=i18n>labelDownloadURL</td><td class=expand><input type=text id=mDownloadURL></td></tr>
+			</table>
+			<fieldset title="@include">
+				<legend class=i18n>labelInclude</legend>
+				<label><input type=checkbox id=cInclude><span class=i18n>labelKeepInclude</span></label>
+				<span class=i18n>labelCustomInclude</span>
+				<textarea id=mInclude></textarea>
+			</fieldset>
+			<fieldset title="@match">
+				<legend class=i18n>labelMatch</legend>
+				<label><input type=checkbox id=cMatch><span class=i18n>labelKeepMatch</span></label>
+				<span class=i18n>labelCustomMatch</span>
+				<textarea id=mMatch></textarea>
+			</fieldset>
+			<fieldset title="@exclude">
+				<legend class=i18n>labelExclude</legend>
+				<label><input type=checkbox id=cExclude><span class=i18n>labelKeepExclude</span></label>
+				<span class=i18n>labelCustomExclude</span>
+				<textarea id=mExclude></textarea>
+			</fieldset>
+			<div class=right><button id=mOK class=i18n>buttonOK</button> <button id=mCancel class=i18n>buttonCancel</button></div>
+		</div>
+		<div id=export class="float hide">
+			<h2 class=i18n>labelExport</h2>
+			<span class=i18n>labelInstalledScripts</span>
+			<div id=xOptions class=right>
+				<a class=i18n>anchorExportOptions</a>
+				<div>
+					<label><input type=checkbox id=cWithData><span class=i18n>labelExportScriptData</span></label>
+				</div>
+			</div>
+			<div id=xList class=list></div>
+			<div class=left><button id=bSelect class=i18n>buttonAllNone</button></div>
+			<div class=right>
+				<a id=xHelper></a><button id=bExport></button>
+				<button id=bClose class=i18n>buttonClose</button>
+			</div>
+		</div>
+		<div id=advanced class="float hide">
+			<h2 class=i18n>labelAdvanced</h2>
+			<label><input type=checkbox id=cUpdate><span class=i18n>labelAutoUpdate</span></label>
+			<div>
+				<span class=i18n>labelSearchLink</span><span class=i18n>labelSearchHint</span> <button id=bDefSearch class=i18n>buttonDefault</button><br>
+				<textarea id=tSearch></textarea><br>
+			</div>
+			<fieldset>
+				<legend class=i18n>labelData</legend>
+				<button id=aExport class=i18n>buttonExport</button>
+				<button id=aImport class=i18n>buttonImport</button>
+				<button id=aVacuum class=i18n>buttonVacuum</button><span class=i18n>labelVacuumHint</span>
+			</fieldset>
+			<div class=right><button id=aClose class=i18n>buttonClose</button></div>
+		</div>
+	</body>
+</html>
+<script src="common.js"></script>
+<script src="options.js"></script>

+ 421 - 0
options.js

@@ -0,0 +1,421 @@
+var $=document.getElementById.bind(document),
+		N=$('main'),L=$('sList'),O=$('overlay');
+zip.workerScriptsPath='lib/zip.js/';
+function split(t){return t.replace(/^\s+|\s+$/g,'').split(/\s*\n\s*/).filter(function(e){return e;});}
+
+// Main options
+function allowUpdate(n){return n.update&&(n.custom.updateURL||n.meta.updateURL);}
+function setIcon(n,d){
+	d.src=cache[n.meta.icon]||'images/icon48.png';
+}
+function modifyItem(d,r){
+	if(r) {
+		if(r.message) d.querySelector('.message').innerHTML=r.message;
+		with(d.querySelector('.update'))
+			if(r.hideUpdate) classList.add('hide');
+			else classList.remove('hide');
+	}
+}
+function loadItem(o,r){
+	var d=o.div,n=o.obj;
+	d.innerHTML='<img class=icon>'
+	+'<a class="name ellipsis" target=_blank></a>'
+	+'<span class=author></span>'
+	+'<span class=version>'+(n.meta.version?'v'+n.meta.version:'')+'</span>'
+	+'<div class=panelT>'
+		+(allowUpdate(n)?'<a data=update class=update href=#>'+_('anchorUpdate')+'</a> ':'')
+		+'<span class=move data=move>&equiv;</span>'
+	+'</div>'
+	+'<div class="descrip ellipsis"></div>'
+	+'<span class=message></span>'
+	+'<div class=panelB>'
+		+'<button data=edit>'+_('buttonEdit')+'</button> '
+		+'<button data=enable>'+_(n.enabled?'buttonDisable':'buttonEnable')+'</button> '
+		+'<button data=remove>'+_('buttonRemove')+'</button>'
+	+'</div>';
+	d.className=n.enabled?'':'disabled';
+	setIcon(n,d.querySelector('.icon'));
+	var a=d.querySelector('.name'),b=n.custom.name||n.meta.name;
+	a.title=b||'';
+	a.innerHTML=b?b.replace(/&/g,'&amp;').replace(/</g,'&lt;'):'<em>'+_('labelNoName')+'</em>';
+	if(b=n.custom.homepage||n.meta.homepage) a.href=b;
+	if(n.meta.author) d.querySelector('.author').innerText=_('labelAuthor')+n.meta.author;
+	a=d.querySelector('.descrip');
+	a.innerText=a.title=n.meta.description||'';
+	modifyItem(d,r);
+}
+function addItem(o){
+	o.div=document.createElement('div');
+	loadItem(o);
+	L.appendChild(o.div);
+}
+function moveUp(i,p){
+	var x=ids[i];
+	ids[i]=ids[i-1];
+	ids[i-1]=x;
+	L.insertBefore(p,p.previousSibling);
+}
+function getSource(e){
+	var o=e.target,p,i;
+	for(p=o;p&&p.parentNode!=L;p=p.parentNode);
+	i=Array.prototype.indexOf.call(L.childNodes,p);
+	return [i,p,o];
+}
+L.onmousedown=function(e){	// for ordering scripts
+	function moveItem(e){
+		var m=getSource(e);if(m[0]<0) return;
+		if(m[0]>=0&&m[0]!=t) {
+			c=m;m=c[1];if(c[0]>t) m=m.nextSibling;
+			L.insertBefore(o[1],m);
+			t=c[0];
+		}
+	}
+	function movedItem(e){
+		o[1].classList.remove('moving');
+		L.onmousemove=L.onmouseup=null;
+		if(o[0]!=t) {
+			chrome.runtime.sendMessage({cmd:'Move',data:{id:ids[o[0]],offset:t-o[0]}});
+			var s=t>o[0]?1:-1,i=o[0],x=ids[i];
+			for(;i!=t;i+=s) ids[i]=ids[i+s];
+			ids[t]=x;
+		}
+	}
+	var o=getSource(e),d=o[2].getAttribute('data'),c=null,t=o[0];
+	if(d=='move') {
+		e.preventDefault();
+		o[1].classList.add('moving');
+		L.onmousemove=moveItem;
+		L.onmouseup=movedItem;
+	}
+};
+L.onclick=function(e){
+	var o=getSource(e),maps={
+		edit:function(i){
+			E.cur=map[ids[i]];
+			chrome.runtime.sendMessage({cmd:'GetScript',data:E.cur.obj.id},gotScript);
+		},
+		enable:function(i,p,o){
+			var e=map[ids[i]].obj;
+			if(e.enabled=!e.enabled) {
+				p.classList.remove('disabled');
+				o.innerText=_('buttonDisable');
+			} else {
+				p.classList.add('disabled');
+				o.innerText=_('buttonEnable');
+			}
+			chrome.runtime.sendMessage({cmd:'EnableScript',data:{id:e.id,data:e.enabled}});
+		},
+		remove:function(i,p){
+			chrome.runtime.sendMessage({cmd:'RemoveScript',data:ids[i]});
+			delete map[ids.splice(i,1)[0]];
+			L.removeChild(p);
+			if(i==L.childNodes.length) i--;
+		},
+		update:function(i){
+			chrome.runtime.sendMessage({cmd:'CheckUpdate',data:ids[i]});
+		}
+	},d=o[2].getAttribute('data'),f=maps[d];
+	if(f) {
+		e.preventDefault();
+		f.apply(this,o);
+	}
+};
+$('bNew').onclick=function(){chrome.runtime.sendMessage({cmd:'NewScript'});};
+$('bUpdate').onclick=function(){chrome.runtime.sendMessage({cmd:'CheckUpdateAll'});};
+$('cDetail').onchange=function(){L.classList.toggle('simple');chrome.runtime.sendMessage({cmd:'SetOption',data:{key:'showDetails',value:this.checked}});};
+var panel=N;
+function switchTo(D){
+	panel.classList.add('hide');D.classList.remove('hide');panel=D;
+}
+var dialogs=[];
+function showDialog(D,z){
+	if(!dialogs.length) {
+		O.classList.remove('hide');
+		setTimeout(function(){O.classList.add('overlay');},1);
+	}
+	if(!z) z=dialogs.length?dialogs[dialogs.length-1].zIndex+1:1;
+	dialogs.push(D);
+	O.style.zIndex=D.style.zIndex=D.zIndex=z;
+	D.classList.remove('hide');
+	D.style.top=(window.innerHeight-D.offsetHeight)/2+'px';
+	D.style.left=(window.innerWidth-D.offsetWidth)/2+'px';
+}
+function closeDialog(){
+	dialogs.pop().classList.add('hide');
+	if(dialogs.length) O.style.zIndex=dialogs.length>1?dialogs[dialogs.length-1]:1;
+	else {
+		O.classList.remove('overlay');
+		setTimeout(function(){O.classList.add('hide');},500);
+	}
+}
+O.onclick=function(){
+	if(dialogs.length) (dialogs[dialogs.length-1].close||closeDialog)();
+};
+function confirmCancel(dirty){
+	return !dirty||confirm(_('confirmNotSaved'));
+}
+initCSS();initI18n();
+
+// Advanced
+var A=$('advanced');
+$('bAdvanced').onclick=function(){showDialog(A);};
+$('cUpdate').onchange=function(){chrome.runtime.sendMessage({cmd:'AutoUpdate',data:this.checked});};
+$('bDefSearch').onclick=function(){$('tSearch').value=_('defaultSearch');};
+$('aExport').onclick=function(){showDialog(X);xLoad();};
+function importFile(e){
+	zip.createReader(new zip.BlobReader(e.target.files[0]),function(r){
+		r.getEntries(function(e){
+			function getFiles(){
+				var i=e.shift();
+				if(i) i.getData(writer,function(t){
+					var c={code:t};
+					if(vm.scripts&&(v=vm.scripts[i.filename.slice(0,-8)])) {
+						c.id=v.id;c.more=v;
+					}
+					chrome.runtime.sendMessage({cmd:'ParseScript',data:c});
+					count++;
+					getFiles();
+				}); else {
+					alert(_('msgImported',[count]));
+					location.reload();
+				}
+			}
+			var i,vm={},writer=new zip.TextWriter(),count=0;
+			for(i=0;i<e.length;i++) if(e[i].filename=='ViolentMonkey') break;
+			if(i<e.length) e.splice(i,1)[0].getData(writer,function(t){
+				try{
+					vm=JSON.parse(t);
+				}catch(e){
+					vm={};
+					console.log('Error parsing ViolentMonkey configuration.');
+				}
+				getFiles();
+			}); else getFiles();
+		});
+	},function(e){console.log(e);});
+}
+$('aImport').onclick=function(){
+	var e=document.createEvent('MouseEvent'),iH=document.createElement('input');
+	iH.setAttribute('type','file');iH.onchange=importFile;
+	e.initMouseEvent('click',true,true,window,0,0,0,0,0,false,false,false,false,0,null);
+	iH.dispatchEvent(e);
+};
+$('aVacuum').onclick=function(){
+	this.disabled=true;
+	this.innerHTML=_('buttonVacuuming');
+	chrome.runtime.sendMessage({cmd:'Vacuum'});
+};
+A.close=$('aClose').onclick=function(){
+	chrome.runtime.sendMessage({cmd:'SetOption',data:{key:'search',value:$('tSearch').value}});
+	closeDialog();
+};
+
+// Export
+var X=$('export'),xL=$('xList'),xE=$('bExport'),xC=$('cCompress'),xD=$('cWithData');
+function xLoad() {
+	xL.innerHTML='';xE.disabled=false;xE.innerHTML=_('buttonExport');
+	ids.forEach(function(i){
+		var d=document.createElement('div');
+		d.className='ellipsis';
+		d.innerText=d.title=map[i].obj.meta.name;
+		xL.appendChild(d);
+	});
+}
+xD.onchange=function(){chrome.runtime.sendMessage({cmd:'SetOption',data:{key:'withData',value:this.checked}});};
+xL.onclick=function(e){
+	var t=e.target;
+	if(t.parentNode!=this) return;
+	t.classList.toggle('selected');
+};
+$('bSelect').onclick=function(){
+	var c=xL.childNodes,v,i;
+	for(i=0;i<c.length;i++) if(!c[i].classList.contains('selected')) break;
+	v=i<c.length;
+	for(i=0;i<c.length;i++) if(v) c[i].classList.add('selected'); else c[i].classList.remove('selected');
+};
+X.close=$('bClose').onclick=closeDialog;
+function exported(o){
+	function addFiles(){
+		adding=true;
+		if(!writer) {	// create writer
+			zip.createWriter(new zip.BlobWriter(),function(w){writer=w;addFiles();});
+			return;
+		}
+		var i=files.shift();
+		if(i) {
+			if(i.name) {	// add file
+				writer.add(i.name,new zip.TextReader(i.content),addFiles);
+				return;
+			} else	// finished
+				writer.close(function(b){
+					var u=URL.createObjectURL(b),e=document.createEvent('MouseEvent'),xH=document.createElement('a');
+					e.initMouseEvent('click',true,true,window,0,0,0,0,0,false,false,false,false,0,null);
+					xH.href=u;
+					xH.download='scripts.zip';
+					xH.dispatchEvent(e);
+					writer=null;
+					X.close();
+					URL.revokeObjectURL(u);
+				});
+		}
+		adding=false;
+	}
+	function addFile(o){
+		files.push(o);
+		if(!adding) addFiles();
+	}
+	var writer=null,files=[],adding=false,
+			n,_n,names={},vm={scripts:{},settings:o.settings};
+	if(xD.checked) vm.values={};
+	o.data.forEach(function(c){
+		var j=0;
+		n=_n=c.custom.name||c.meta.name||'Noname';
+		while(names[n]) n=_n+'_'+(++j);names[n]=1;
+		addFile({name:n+'.user.js',content:c.code});
+		vm.scripts[n]={id:c.id,custom:c.custom,enabled:c.enabled,update:c.update};
+		if(xD.checked&&(n=o.values[c.uri])) vm.values[c.uri]=n;
+	});
+	addFile({name:'ViolentMonkey',content:JSON.stringify(vm)});
+	addFile({});	// finish adding files
+}
+xE.onclick=function(e){
+	e.preventDefault();
+	this.disabled=true;this.innerHTML=_('labelExporting');
+	var i,c=[];
+	for(i=0;i<ids.length;i++)
+		if(xL.childNodes[i].classList.contains('selected')) c.push(ids[i]);
+	chrome.runtime.sendMessage({cmd:'ExportZip',data:{values:xD.checked,data:c}},exported);
+};
+
+// Script Editor
+var E=$('editor'),U=$('eUpdate'),M=$('meta'),
+		mN=$('mName'),mH=$('mHomepage'),mR=$('mRunAt'),
+		mU=$('mUpdateURL'),mD=$('mDownloadURL'),
+    mI=$('mInclude'),mE=$('mExclude'),mM=$('mMatch'),
+    cI=$('cInclude'),cE=$('cExclude'),cM=$('cMatch'),
+		eS=$('eSave'),eSC=$('eSaveClose'),T;
+CodeMirror.keyMap.vm={
+	'Esc':'close',
+	'Ctrl-S':'save',
+	'fallthrough':'default'
+};
+T=CodeMirror.fromTextArea($('eCode'),{
+	lineNumbers:true,
+	matchBrackets:true,
+	mode:'text/typescript',
+	lineWrapping:true,
+	indentUnit:4,
+	indentWithTabs:true,
+	extraKeys:{"Enter":"newlineAndIndentContinueComment"},
+	keyMap:'vm'
+});
+T.on('change',function(){eS.disabled=eSC.disabled=T.isClean();});
+function gotScript(o){
+	switchTo(E);E.scr=o;U.checked=o.update;
+	T.setValue(o.code);T.markClean();T.getDoc().clearHistory();
+	eS.disabled=eSC.disabled=true;T.focus();
+}
+function eSave(){
+	chrome.runtime.sendMessage({
+		cmd:'ParseScript',
+		data:{
+			id:E.scr.id,
+			code:T.getValue(),
+			message:'',
+			more:{
+				update:E.scr.update=U.checked
+			}
+		}
+	});
+	T.markClean();eS.disabled=eSC.disabled=true;
+}
+function eClose(){T.setValue('');switchTo(N);}
+U.onchange=E.markDirty=function(){eS.disabled=eSC.disabled=false;};
+function metaChange(){M.dirty=true;}
+[mN,mH,mR,mU,mD,mI,mM,mE,cI,cM,cE].forEach(function(i){i.onchange=metaChange;});
+$('bcustom').onclick=function(){
+	var e=[],c=E.scr.custom;M.dirty=false;
+	showDialog(M,10);
+	mN.value=c.name||'';
+	mH.value=c.homepage||'';
+	mU.value=c.updateURL||'';
+	mD.value=c.downloadURL||'';
+	switch(c['run-at']){
+		case 'document-start':mR.value='start';break;
+		case 'document-body':mR.value='body';break;
+		case 'document-end':mR.value='end';break;
+		default:mR.value='default';
+	}
+	cI.checked=c._include!=false;
+	mI.value=(c.include||e).join('\n');
+	cM.checked=c._match!=false;
+	mM.value=(c.match||e).join('\n');
+	cE.checked=c._exclude!=false;
+	mE.value=(c.exclude||e).join('\n');
+};
+M.close=function(){if(confirmCancel(M.dirty)) closeDialog();};
+$('mCancel').onclick=closeDialog;
+$('mOK').onclick=function(){
+	if(M.dirty) {
+		var c=E.scr.custom;
+		c.name=mN.value;
+		c.homepage=mH.value;
+		c.updateURL=mU.value;
+		c.downloadURL=mD.value;
+		switch(mR.value){
+			case 'start':c['run-at']='document-start';break;
+			case 'body':c['run-at']='document-body';break;
+			case 'end':c['run-at']='document-end';break;
+			default:delete c['run-at'];
+		}
+		c._include=cI.checked;
+		c.include=split(mI.value);
+		c._match=cM.checked;
+		c.match=split(mM.value);
+		c._exclude=cE.checked;
+		c.exclude=split(mE.value);
+		loadItem(E.cur,E.scr);
+		chrome.runtime.sendMessage({cmd:'SaveScript',data:E.scr});
+	}
+	closeDialog();
+};
+eS.onclick=eSave;
+eSC.onclick=function(){eSave();eClose();};
+CodeMirror.commands.save=function(){if(!eS.disabled) setTimeout(eSave,0);};
+CodeMirror.commands.close=E.close=$('eClose').onclick=function(){if(confirmCancel(!eS.disabled)) eClose();};
+
+// Load at last
+var ids=[],map={},cache;
+function loadOptions(o){
+	cache=o.cache;
+	o.scripts.forEach(function(i){
+		ids.push(i.id);addItem(map[i.id]={obj:i});
+	});
+	$('cUpdate').checked=o.autoUpdate;
+	$('tSearch').value=o.search;
+	if(!($('cDetail').checked=o.showDetails)) L.classList.add('simple');
+	xD.checked=o.withData;
+}
+function updateItem(r){
+	if(!('id' in r)) return;
+	var m=map[r.id];
+	if(!m) map[r.id]=m={};
+	if(r.obj) m.obj=r.obj;
+	switch(r.status){
+		case 0:loadItem(m,r);break;
+		case 1:ids.push(r.id);addItem(m);break;
+		default:modifyItem(m.div,r);
+	}
+}
+chrome.runtime.sendMessage({cmd:'GetData'},loadOptions);
+chrome.runtime.onMessage.addListener(function(req,src){
+	var maps={
+		NewScript: function(o){
+			ids.push(o.id);addItem(map[o.id]={obj:o});o=map[o.id].div;
+		},
+		Vacuumed: function(){$('aVacuum').innerHTML=_('buttonVacuumed');},
+	},f=maps[req.cmd];
+	if(f) f(req.data,src);
+});
+var port=chrome.runtime.connect({name:'Options'});
+port.onMessage.addListener(updateItem);

+ 20 - 0
popup.html

@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+	<head>
+		<meta content="text/html;charset=utf-8" http-equiv="content-type" />
+		<link href="style.css" rel="stylesheet" type="text/css" />
+		<title>Popup Menu - Violentmonkey</title>
+	</head>
+	<body>
+		<div id="popup">
+			<div class="top menu"></div>
+			<div class="bot menu"></div>
+		</div>
+		<div class="hide" id="commands">
+			<div class="top menu"></div>
+			<div class="bot menu"></div>
+		</div>
+	</body>
+</html>
+<script src="common.js"></script>
+<script src="popup.js"></script>

+ 136 - 0
popup.js

@@ -0,0 +1,136 @@
+var $=document.getElementById.bind(document),P=$('popup'),C=$('commands'),
+		pT=P.querySelector('.top'),pB=P.querySelector('.bot'),
+		cT=C.querySelector('.top'),cB=C.querySelector('.bot'),
+		tab=null,ia=null,scripts={},hr=null;
+function loadItem(d,c) {
+  if(d.data=c){
+    d.firstChild.innerText=d.symbol;
+    d.classList.remove('disabled');
+  } else {
+    d.firstChild.innerText = '';
+    d.classList.add('disabled');
+  }
+	return c;
+}
+function addItem(h,c,b) {
+  var d=document.createElement('div');
+  d.innerHTML='<span></span>'+h;
+  if('title' in c) {
+    d.title=typeof c.title=='string'?c.title:h;
+    delete c.title;
+  }
+  d.className='ellipsis';
+  c.holder.insertBefore(d,b);
+  if('symbol' in c) d.firstChild.innerText = c.symbol;
+  for(h in c) d[h]=c[h];
+	return d;
+}
+function menuCommand(e) {
+	chrome.tabs.sendMessage(tab.id,{cmd:'Command',data:e.target.cmd});
+}
+function menuScript(s) {
+	if(scripts[s.id]) return;scripts[s.id]=s;
+  var n=s.meta.name?s.meta.name.replace(/&/g,'&amp;').replace(/</g,'&lt;'):'<em>'+_('labelNoName')+'</em>';
+  loadItem(addItem(n,{
+    holder: pB,
+		symbol: '✓',
+    title: s.meta.name,
+    onclick: function(e){
+			chrome.runtime.sendMessage({cmd:'EnableScript',data:{id:s.id,data:loadItem(this,!this.data)}});
+    }
+  }),s.enabled);
+}
+function initMenu(){
+  addItem(_('menuManageScripts'),{
+    holder: pT,
+    symbol: '➤',
+    //title: true,
+    onclick: function(){
+			var u=chrome.extension.getURL('/options.html');
+			chrome.tabs.query({currentWindow:true,url:u},function(t) {
+				if(t[0]) chrome.tabs.update(t[0].id,{active:true});
+				else chrome.tabs.create({url:u});
+			});
+		}
+  });
+  if(/^https?:\/\//i.test(tab.url)) {
+		var d=addItem(_('menuFindScripts'), {
+			holder: pT,
+			symbol: '➤',
+			//title: true,
+		});
+		loadItem(d,false);
+		chrome.runtime.sendMessage({cmd:'GetOption',data:'search'},function(o){
+			d.onclick=function(){
+				var q='site:userscripts.org+inurl:show+'+tab.url.replace(/^.*?:\/\/([^\/]*?)\.\w+\/.*$/, function(v,g){
+					return g.replace(/\.(com|..)$/, '').replace(/\./g, '+');
+				});
+				chrome.tabs.create({url:o.replace('*',q)});
+			};
+			loadItem(d,true);
+		});
+	}
+  ia=addItem(_('menuScriptEnabled'), {
+    holder: pT,
+		symbol: '✓',
+    //title: true,
+    onclick: function(e) {
+      chrome.runtime.sendMessage({cmd:'SetOption',data:{key:'isApplied',value:loadItem(this,!this.data)}});
+			chrome.browserAction.setIcon({path:'images/icon19'+(this.data?'':'w')+'.png'});
+    }
+  });
+	chrome.runtime.sendMessage({cmd:'GetOption',data:'isApplied'},function(o){loadItem(ia,o);});
+}
+function load(data) {
+  if(data&&data[0]&&data[0].length) {
+    addItem(_('menuBack'), {
+      holder: cT,
+      symbol: '◄',
+      //title: true,
+      onclick: function() {
+        C.classList.add('hide');
+        P.classList.remove('hide');
+      }
+    });
+    cT.appendChild(document.createElement('hr'));
+    data[0].forEach(function(i) {
+      addItem(i[0], {
+        holder: cB,
+        symbol: '➤',
+        //title: true,
+        onclick: menuCommand,
+        cmd: i[0]
+      });
+    });
+    addItem(_('menuCommands'),{
+      holder: pT,
+      symbol: '➤',
+      //title: true,
+      onclick: function() {
+        P.classList.add('hide');
+        C.classList.remove('hide');
+      }
+    },ia);
+  }
+  if(data&&data[1]&&data[1].length) {
+		var ids=[];
+		data[1].forEach(function(i){
+			if(!scripts[i]) ids.push(i);
+		});
+		if(ids.length) chrome.runtime.sendMessage({cmd:'GetMetas',data:ids},function(o){
+			if(!hr) pT.appendChild(hr=document.createElement('hr'));
+			o.forEach(menuScript);
+		});
+	}
+}
+chrome.runtime.onMessage.addListener(function(req,src,callback) {
+	var maps={
+		SetPopup: load,
+	},f=maps[req.cmd];
+	if(f) f(req.data,src,callback);
+	return true;
+});
+chrome.tabs.query({currentWindow:true,active:true},function(t) {
+	tab=t[0];initMenu();chrome.tabs.sendMessage(tab.id,{cmd:'GetPopup'});
+});
+initCSS();

+ 70 - 0
style.css

@@ -0,0 +1,70 @@
+h1,h2{text-shadow:#999 3px 3px 5px;}
+a{color:dodgerblue;cursor:pointer;}
+.center{text-align:center;}
+.float,.frame{position:fixed;background:lightgray;border-spacing:10px;}
+.frame{left:0;right:0;top:0;bottom:0;table-layout:fixed;width:100%;height:100%;}
+.float{margin:0 auto;padding:10px;border-radius:10px;}
+.fill{background:#eee;height:100%;margin:0;padding:0;}
+.em{font-style:italic;}
+.new{color:red;}
+.menu,.ellipsis{white-space:nowrap;text-overflow:ellipsis;overflow:hidden;}
+.menu{overflow-y:auto;}
+div.left{float:left;}
+div.right{float:right;}
+.buttons{text-align:right;vertical-align:bottom;}
+.menu,.x{clear:both;margin:0;padding:0;}
+.hide{display:none;}
+tr{vertical-align:top;padding:0;height:1px;}
+tr.expand{height:auto;}
+.expandr{position:relative;}
+.expanda,.frame .CodeMirror{position:absolute;width:100%;height:100%;}
+.CodeMirror{background:white;}
+textarea{width:100%;border:none;margin:0;padding:0;}
+fieldset{border-radius:5px;}
+#main{background:transparent;width:800px;margin:0 auto;border-spacing:0;}
+#main fieldset{position:relative;height:100%;padding:0;}
+#main legend{margin-left:10px;}
+#meta label,#advanced label{display:block;}
+#meta table{width:100%;}
+#meta td{width:1px;white-space:nowrap;}
+#meta td.expand{width:auto;padding-right:10px;}
+#meta td.expand>*{width:100%;}
+#sList{overflow-x:hidden;overflow-y:auto;position:absolute;top:22px;left:0;right:0;bottom:10px;}
+#sList>div{display:block;padding:10px;border-top:1px dashed gray;overflow:hidden;height:80px;position:relative;}
+#sList>div:last-child{border-bottom:1px dashed gray;}
+#sList>div.moving{background:#bb6;border:1px outset gray;}
+#sList.simple>div{height:50px;}
+#sList .version,#sList .author{margin-left:20px;color:gray;font-style:italic;}
+#sList.simple .author{display:none;}
+#sList.simple .descrip{display:none;}
+#sList .message{color:gray;position:absolute;bottom:10px;left:50px;}
+#sList .icon{width:32px;height:32px;margin:2px;position:absolute;}
+#sList .name{font-weight:bold;line-height:150%;display:inline-block;max-width:400px;margin-left:40px;}
+#sList .descrip{margin-left:40px;}
+#sList .move{cursor:move;margin-left:20px;color:gray;}
+#sList .panelB,#sList .panelT{position:absolute;right:10px;}
+#sList .panelB{bottom:10px;}
+#sList .panelT{top:10px;}
+#sList .panelB>button{width:80px;}
+#sList .disabled{color:gray;background:silver;}
+#sList .disabled a{color:gray;}
+#sList .disabled img{opacity:0.3;}
+#overlay{background:#000;position:fixed;top:0;bottom:0;left:0;right:0;transition:all .5s linear;opacity:0;}
+#overlay.overlay{opacity:0.6;}
+#advanced{padding:20px;}
+#advanced fieldset>*:not(legend){display:inline-block;}
+#advanced legend{margin:0;}
+.list{background:white;overflow-x:hidden;overflow-y:auto;}
+.list>div{display:block;padding:2px 10px;cursor:pointer;border-bottom:1px dashed lightgray;}
+.list .selected{background:gray;color:white;}
+#export .list{width:400px;height:420px;}
+#xOptions{display:inline-block;position:relative;}
+#xOptions>div{display:none;position:absolute;padding:10px;background:#eee;box-shadow:5px 10px 15px rgba(0,0,0,0.4);border:1px solid gray;}
+#xOptions>div>label{display:block;white-space:nowrap;}
+#xOptions:hover>div{display:block;}
+.menu>div{cursor:pointer;padding:5px;display:block;}
+.menu>div:hover{background:gray;color:gold;}
+.menu>div.disabled{color:gray;}
+.menu>div.disabled:hover{color:silver;}
+.menu>div span{display:inline-block;width:20px;}
+.menu>hr{border:none;border-top:1px dashed gray;}