diff --git a/doc/LICENSE b/doc/LICENSE new file mode 100644 index 0000000..94a9ed0 --- /dev/null +++ b/doc/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + 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. + + + Copyright (C) + + 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 . + +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: + + Copyright (C) + 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 +. + + 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 +. diff --git a/doc/index.html b/doc/index.html new file mode 100644 index 0000000..1b7cfe7 --- /dev/null +++ b/doc/index.html @@ -0,0 +1,234 @@ + + + + Mergely Manual + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+
+

Mergely Manual

+ +

Overview

+

+ The core of mergely is a javascript-based diff and customizable markup engine. + Mergely provides a rich API that enables integration into your own application. It + can be used as a diff tool (read-only) or as both a diff and merge + tool for plain text, CSS, HTML, XML, javascript, PHP, C, C++, etc. +

+

+ +

+ +

Basic Usage

+

+ Mergely requires jQuery and CodeMirror. + A supported implementation of CodeMirror is provided in the Mergely download. +

+

+ To use Mergely, you need to first load the required javascript and css files: +

+
+<script type="text/javascript"
+ src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js"></script>
+
+<script type="text/javascript" src="../lib/codemirror.min.js"></script>
+<link type="text/css" rel="stylesheet" href="../lib/codemirror.css" />
+
+<script type="text/javascript" src="../lib/mergely.js"></script>
+<link type="text/css" rel="stylesheet" href="../lib/mergely.css" />
+				
+

+ Then, create a div for the editor: +

+
+<div id="compare"><div>
+				
+

+ Then, initialize the 'compare' div with the mergely jquery plugin, setting + options as required: +

+
+$(document).ready(function () {
+	$('#compare').mergely({
+		cmsettings: { readOnly: false, lineNumbers: true },
+		lhs: function(setValue) {
+			setValue('the quick red fox\njumped over the hairy dog');
+		},
+		rhs: function(setValue) {
+			setValue('the quick brown fox\njumped over the lazy dog');
+		}
+	});
+});
+				
+ +

Options

+

+ The following options are available on initialization: +

+
+
autoresize
+
Enables/disables the auto-resizing of the editor. Defaults to true.
+
cmsettings
+
CodeMirror settings (see CodeMirror). Defaults to {mode: 'application/xml', readOnly: false, lineWrapping: false, lineNumbers: true}.
+
editor_width
+
Starting width. Defaults to '400px'.
+
editor_height
+
Starting height. Defaults to '400px'.
+
resize_timeout
+
The timeout, after a resize, before Mergely auto-resizes. Only used when autoresize enabled. Defaults to 500.
+
change_timeout
+
The timeout, after a text change, before Mergely calcualtes a diff. Only used when readonly enabled. Defaults to 500.
+
fgcolor
+
The foreground color that mergely marks changes with on the canvas. Defaults to '#4ba3fa'
+
bgcolor
+
The background color that mergely fills the margin canvas with. Defaults to '#eeeeee'
+
fadein
+
A jQuery fade-in value to enable the editor to fade in. Set to empty string to disable. Defaults to 'fast'
+
+ +

Callbacks

+

+ The following callbacks are available on initialization: +

+

+
lhs(setValue)
+
Allows the opportunity to set the value of the left-hand editor on initialization. A handle to a setValue function is passed as an argument.
+
rhs(setValue)
+
Allows the opportunity to set the value of the right-hand editor on initialization. A handle to a setValue function is passed as an argument.
+
height(h)
+
Allows the opportunity to adjust the height when then the editor is resized. Return the adjusted height.
+
width(w)
+
Allows the opportunity to adjust the width when the editor is resized. Return the adjusted width.
+
loaded()
+
A callback to indicate that Mergely has finished initializing and is loaded.
+
resized()
+
A callback to indicate that Mergely has been resized.
+
+ +

Methods

+

+ The following methods are available after initialization: +

+

+
$(selector).mergely('lhs', value)
+
Set the value of the left-hand editor. Best used with ajax.
+
$(selector).mergely('rhs', value)
+
Set the value of the right-hand editor. Best used with ajax.
+
$(selector).mergely('swap')
+
Swap the content of the left and right editors.
+
$(selector).mergely('merge', side)
+
Merge from side to the opposite side.
+
$(selector).mergely('get', side)
+
Gets the editor contents.
+
$(selector).mergely('clear', side)
+
Clears the editor contents.
+
$(selector).mergely('search', side, text)
+
Search the editor for text. Repeating the call will find the next available token.
+
$(selector).resize()
+
Resize the editor.
+
+ +

Styles

+

+ The following styles will allow you to brand your own editor: +

+
+
.mergely-column
+
The editors.
+
.mergely-active
+
The active editor.
+
mergely-c-start
+
Styles the starting line of a change.
+
mergely-c-end
+
Styles the ending line of a change.
+
mergely-a-start
+
Styles the starting line of an addition.
+
mergely-a-mid
+
Styles the middle text region of an addition.
+
mergely-a-end
+
Styles the ending line of an addition.
+
mergely-d-start
+
Styles the starting line of a deletion.
+
mergely-d-mid
+
Styles the middle text region of a deletion.
+
mergely-d-end
+
Styles the ending line of a deletion.
+
mergely-c-rem
+
Styles the middle text region of a deletion.
+
mergely-c-add
+
Styles the middle text region of an addition.
+
mergely-a-start-lhs
+
Styles the start of an addition on the left-hand side.
+
mergely-a-end-lhs
+
Styles the end of an addition on the left-hand side.
+
mergely-d-start-rhs
+
Styles the start of an deletion on the right-hand side.
+
mergely-d-end-rhs
+
Styles the start of an deletion on the right-hand side.
+
+
+
+
+ + + diff --git a/examples/example1.html b/examples/example1.html new file mode 100644 index 0000000..e587aac --- /dev/null +++ b/examples/example1.html @@ -0,0 +1,49 @@ + + + + + Mergely - Simple Example + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + + diff --git a/examples/example2.html b/examples/example2.html new file mode 100644 index 0000000..4711863 --- /dev/null +++ b/examples/example2.html @@ -0,0 +1,57 @@ + + + + + Mergely - Simple Example + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + + diff --git a/examples/example3.html b/examples/example3.html new file mode 100644 index 0000000..539bd9a --- /dev/null +++ b/examples/example3.html @@ -0,0 +1,57 @@ + + + + + Mergely - Simple Example + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + + diff --git a/examples/jquery.corner.js b/examples/jquery.corner.js new file mode 100644 index 0000000..c416613 --- /dev/null +++ b/examples/jquery.corner.js @@ -0,0 +1,249 @@ +/*! + * jQuery corner plugin: simple corner rounding + * Examples and documentation at: http://jquery.malsup.com/corner/ + * version 2.12 (23-MAY-2011) + * Requires jQuery v1.3.2 or later + * Dual licensed under the MIT and GPL licenses: + * http://www.opensource.org/licenses/mit-license.php + * http://www.gnu.org/licenses/gpl.html + * Authors: Dave Methvin and Mike Alsup + */ + +/** + * corner() takes a single string argument: $('#myDiv').corner("effect corners width") + * + * effect: name of the effect to apply, such as round, bevel, notch, bite, etc (default is round). + * corners: one or more of: top, bottom, tr, tl, br, or bl. (default is all corners) + * width: width of the effect; in the case of rounded corners this is the radius. + * specify this value using the px suffix such as 10px (yes, it must be pixels). + */ +;(function($) { + +var style = document.createElement('div').style, + moz = style['MozBorderRadius'] !== undefined, + webkit = style['WebkitBorderRadius'] !== undefined, + radius = style['borderRadius'] !== undefined || style['BorderRadius'] !== undefined, + mode = document.documentMode || 0, + noBottomFold = $.browser.msie && (($.browser.version < 8 && !mode) || mode < 8), + + expr = $.browser.msie && (function() { + var div = document.createElement('div'); + try { div.style.setExpression('width','0+0'); div.style.removeExpression('width'); } + catch(e) { return false; } + return true; + })(); + +$.support = $.support || {}; +$.support.borderRadius = moz || webkit || radius; // so you can do: if (!$.support.borderRadius) $('#myDiv').corner(); + +function sz(el, p) { + return parseInt($.css(el,p))||0; +}; +function hex2(s) { + s = parseInt(s).toString(16); + return ( s.length < 2 ) ? '0'+s : s; +}; +function gpc(node) { + while(node) { + var v = $.css(node,'backgroundColor'), rgb; + if (v && v != 'transparent' && v != 'rgba(0, 0, 0, 0)') { + if (v.indexOf('rgb') >= 0) { + rgb = v.match(/\d+/g); + return '#'+ hex2(rgb[0]) + hex2(rgb[1]) + hex2(rgb[2]); + } + return v; + } + if (node.nodeName.toLowerCase() == 'html') + break; + node = node.parentNode; // keep walking if transparent + } + return '#ffffff'; +}; + +function getWidth(fx, i, width) { + switch(fx) { + case 'round': return Math.round(width*(1-Math.cos(Math.asin(i/width)))); + case 'cool': return Math.round(width*(1+Math.cos(Math.asin(i/width)))); + case 'sharp': return width-i; + case 'bite': return Math.round(width*(Math.cos(Math.asin((width-i-1)/width)))); + case 'slide': return Math.round(width*(Math.atan2(i,width/i))); + case 'jut': return Math.round(width*(Math.atan2(width,(width-i-1)))); + case 'curl': return Math.round(width*(Math.atan(i))); + case 'tear': return Math.round(width*(Math.cos(i))); + case 'wicked': return Math.round(width*(Math.tan(i))); + case 'long': return Math.round(width*(Math.sqrt(i))); + case 'sculpt': return Math.round(width*(Math.log((width-i-1),width))); + case 'dogfold': + case 'dog': return (i&1) ? (i+1) : width; + case 'dog2': return (i&2) ? (i+1) : width; + case 'dog3': return (i&3) ? (i+1) : width; + case 'fray': return (i%2)*width; + case 'notch': return width; + case 'bevelfold': + case 'bevel': return i+1; + case 'steep': return i/2 + 1; + case 'invsteep':return (width-i)/2+1; + } +}; + +$.fn.corner = function(options) { + // in 1.3+ we can fix mistakes with the ready state + if (this.length == 0) { + if (!$.isReady && this.selector) { + var s = this.selector, c = this.context; + $(function() { + $(s,c).corner(options); + }); + } + return this; + } + + return this.each(function(index){ + var $this = $(this), + // meta values override options + o = [$this.attr($.fn.corner.defaults.metaAttr) || '', options || ''].join(' ').toLowerCase(), + keep = /keep/.test(o), // keep borders? + cc = ((o.match(/cc:(#[0-9a-f]+)/)||[])[1]), // corner color + sc = ((o.match(/sc:(#[0-9a-f]+)/)||[])[1]), // strip color + width = parseInt((o.match(/(\d+)px/)||[])[1]) || 10, // corner width + re = /round|bevelfold|bevel|notch|bite|cool|sharp|slide|jut|curl|tear|fray|wicked|sculpt|long|dog3|dog2|dogfold|dog|invsteep|steep/, + fx = ((o.match(re)||['round'])[0]), + fold = /dogfold|bevelfold/.test(o), + edges = { T:0, B:1 }, + opts = { + TL: /top|tl|left/.test(o), TR: /top|tr|right/.test(o), + BL: /bottom|bl|left/.test(o), BR: /bottom|br|right/.test(o) + }, + // vars used in func later + strip, pad, cssHeight, j, bot, d, ds, bw, i, w, e, c, common, $horz; + + if ( !opts.TL && !opts.TR && !opts.BL && !opts.BR ) + opts = { TL:1, TR:1, BL:1, BR:1 }; + + // support native rounding + if ($.fn.corner.defaults.useNative && fx == 'round' && (radius || moz || webkit) && !cc && !sc) { + if (opts.TL) + $this.css(radius ? 'border-top-left-radius' : moz ? '-moz-border-radius-topleft' : '-webkit-border-top-left-radius', width + 'px'); + if (opts.TR) + $this.css(radius ? 'border-top-right-radius' : moz ? '-moz-border-radius-topright' : '-webkit-border-top-right-radius', width + 'px'); + if (opts.BL) + $this.css(radius ? 'border-bottom-left-radius' : moz ? '-moz-border-radius-bottomleft' : '-webkit-border-bottom-left-radius', width + 'px'); + if (opts.BR) + $this.css(radius ? 'border-bottom-right-radius' : moz ? '-moz-border-radius-bottomright' : '-webkit-border-bottom-right-radius', width + 'px'); + return; + } + + strip = document.createElement('div'); + $(strip).css({ + overflow: 'hidden', + height: '1px', + minHeight: '1px', + fontSize: '1px', + backgroundColor: sc || 'transparent', + borderStyle: 'solid' + }); + + pad = { + T: parseInt($.css(this,'paddingTop'))||0, R: parseInt($.css(this,'paddingRight'))||0, + B: parseInt($.css(this,'paddingBottom'))||0, L: parseInt($.css(this,'paddingLeft'))||0 + }; + + if (typeof this.style.zoom != undefined) this.style.zoom = 1; // force 'hasLayout' in IE + if (!keep) this.style.border = 'none'; + strip.style.borderColor = cc || gpc(this.parentNode); + cssHeight = $(this).outerHeight(); + + for (j in edges) { + bot = edges[j]; + // only add stips if needed + if ((bot && (opts.BL || opts.BR)) || (!bot && (opts.TL || opts.TR))) { + strip.style.borderStyle = 'none '+(opts[j+'R']?'solid':'none')+' none '+(opts[j+'L']?'solid':'none'); + d = document.createElement('div'); + $(d).addClass('jquery-corner'); + ds = d.style; + + bot ? this.appendChild(d) : this.insertBefore(d, this.firstChild); + + if (bot && cssHeight != 'auto') { + if ($.css(this,'position') == 'static') + this.style.position = 'relative'; + ds.position = 'absolute'; + ds.bottom = ds.left = ds.padding = ds.margin = '0'; + if (expr) + ds.setExpression('width', 'this.parentNode.offsetWidth'); + else + ds.width = '100%'; + } + else if (!bot && $.browser.msie) { + if ($.css(this,'position') == 'static') + this.style.position = 'relative'; + ds.position = 'absolute'; + ds.top = ds.left = ds.right = ds.padding = ds.margin = '0'; + + // fix ie6 problem when blocked element has a border width + if (expr) { + bw = sz(this,'borderLeftWidth') + sz(this,'borderRightWidth'); + ds.setExpression('width', 'this.parentNode.offsetWidth - '+bw+'+ "px"'); + } + else + ds.width = '100%'; + } + else { + ds.position = 'relative'; + ds.margin = !bot ? '-'+pad.T+'px -'+pad.R+'px '+(pad.T-width)+'px -'+pad.L+'px' : + (pad.B-width)+'px -'+pad.R+'px -'+pad.B+'px -'+pad.L+'px'; + } + + for (i=0; i < width; i++) { + w = Math.max(0,getWidth(fx,i, width)); + e = strip.cloneNode(false); + e.style.borderWidth = '0 '+(opts[j+'R']?w:0)+'px 0 '+(opts[j+'L']?w:0)+'px'; + bot ? d.appendChild(e) : d.insertBefore(e, d.firstChild); + } + + if (fold && $.support.boxModel) { + if (bot && noBottomFold) continue; + for (c in opts) { + if (!opts[c]) continue; + if (bot && (c == 'TL' || c == 'TR')) continue; + if (!bot && (c == 'BL' || c == 'BR')) continue; + + common = { position: 'absolute', border: 'none', margin: 0, padding: 0, overflow: 'hidden', backgroundColor: strip.style.borderColor }; + $horz = $('
').css(common).css({ width: width + 'px', height: '1px' }); + switch(c) { + case 'TL': $horz.css({ bottom: 0, left: 0 }); break; + case 'TR': $horz.css({ bottom: 0, right: 0 }); break; + case 'BL': $horz.css({ top: 0, left: 0 }); break; + case 'BR': $horz.css({ top: 0, right: 0 }); break; + } + d.appendChild($horz[0]); + + var $vert = $('
').css(common).css({ top: 0, bottom: 0, width: '1px', height: width + 'px' }); + switch(c) { + case 'TL': $vert.css({ left: width }); break; + case 'TR': $vert.css({ right: width }); break; + case 'BL': $vert.css({ left: width }); break; + case 'BR': $vert.css({ right: width }); break; + } + d.appendChild($vert[0]); + } + } + } + } + }); +}; + +$.fn.uncorner = function() { + if (radius || moz || webkit) + this.css(radius ? 'border-radius' : moz ? '-moz-border-radius' : '-webkit-border-radius', 0); + $('div.jquery-corner', this).remove(); + return this; +}; + +// expose options +$.fn.corner.defaults = { + useNative: true, // true if plugin should attempt to use native browser support for border radius rounding + metaAttr: 'data-corner' // name of meta attribute to use for options +}; + +})(jQuery); diff --git a/examples/lhs.txt b/examples/lhs.txt new file mode 100644 index 0000000..14b478c --- /dev/null +++ b/examples/lhs.txt @@ -0,0 +1,2 @@ +the quick red fox jumped +over the hairy dog diff --git a/examples/lhs_long.txt b/examples/lhs_long.txt new file mode 100644 index 0000000..d155599 --- /dev/null +++ b/examples/lhs_long.txt @@ -0,0 +1 @@ +the quick red fox jumped over the hairy dog on a cold, wet, and windy day in January diff --git a/examples/rhs.txt b/examples/rhs.txt new file mode 100644 index 0000000..13fde44 --- /dev/null +++ b/examples/rhs.txt @@ -0,0 +1,2 @@ +the quick brown fox jumped +over the lazy dog diff --git a/examples/rhs_long.txt b/examples/rhs_long.txt new file mode 100644 index 0000000..2c3f250 --- /dev/null +++ b/examples/rhs_long.txt @@ -0,0 +1 @@ +the quick brown fox jumped over the lazy dog on a cold, wet, and windy day in December diff --git a/lib/codemirror.css b/lib/codemirror.css new file mode 100644 index 0000000..89d08ff --- /dev/null +++ b/lib/codemirror.css @@ -0,0 +1,107 @@ +.CodeMirror { + line-height: 1em; + font-family: monospace; +} + +.CodeMirror-scroll { + overflow: auto; + height: 300px; + /* This is needed to prevent an IE[67] bug where the scrolled content + is visible outside of the scrolling box. */ + position: relative; +} + +.CodeMirror-gutter { + position: absolute; left: 0; top: 0; + z-index: 10; + background-color: #f7f7f7; + border-right: 1px solid #eee; + min-width: 2em; + height: 100%; +} +.CodeMirror-gutter-text { + color: #aaa; + text-align: right; + padding: .4em .2em .4em .4em; + white-space: pre !important; +} +.CodeMirror-lines { + padding: .4em; +} + +.CodeMirror pre { + -moz-border-radius: 0; + -webkit-border-radius: 0; + -o-border-radius: 0; + border-radius: 0; + border-width: 0; margin: 0; padding: 0; background: transparent; + font-family: inherit; + font-size: inherit; + padding: 0; margin: 0; + white-space: pre; + word-wrap: normal; +} + +.CodeMirror-wrap pre { + word-wrap: break-word; + white-space: pre-wrap; +} +.CodeMirror-wrap .CodeMirror-scroll { + overflow-x: hidden; +} + +.CodeMirror textarea { + outline: none !important; +} + +.CodeMirror pre.CodeMirror-cursor { + z-index: 10; + position: absolute; + visibility: hidden; + border-left: 1px solid black; +} +.CodeMirror-focused pre.CodeMirror-cursor { + visibility: visible; +} + +div.CodeMirror-selected { background: #d9d9d9; } +.CodeMirror-focused div.CodeMirror-selected { background: #d7d4f0; } + +.CodeMirror-searching { + background: #ffa; + background: rgba(255, 255, 0, .4); +} + +/* Default theme */ + +.cm-s-default span.cm-keyword {color: #708;} +.cm-s-default span.cm-atom {color: #219;} +.cm-s-default span.cm-number {color: #164;} +.cm-s-default span.cm-def {color: #00f;} +.cm-s-default span.cm-variable {color: black;} +.cm-s-default span.cm-variable-2 {color: #05a;} +.cm-s-default span.cm-variable-3 {color: #085;} +.cm-s-default span.cm-property {color: black;} +.cm-s-default span.cm-operator {color: black;} +.cm-s-default span.cm-comment {color: #a50;} +.cm-s-default span.cm-string {color: #a11;} +.cm-s-default span.cm-string-2 {color: #f50;} +.cm-s-default span.cm-meta {color: #555;} +.cm-s-default span.cm-error {color: #f00;} +.cm-s-default span.cm-qualifier {color: #555;} +.cm-s-default span.cm-builtin {color: #30a;} +.cm-s-default span.cm-bracket {color: #cc7;} +.cm-s-default span.cm-tag {color: #170;} +.cm-s-default span.cm-attribute {color: #00c;} +.cm-s-default span.cm-header {color: #a0a;} +.cm-s-default span.cm-quote {color: #090;} +.cm-s-default span.cm-hr {color: #999;} +.cm-s-default span.cm-link {color: #00c;} + +span.cm-header, span.cm-strong {font-weight: bold;} +span.cm-em {font-style: italic;} +span.cm-emstrong {font-style: italic; font-weight: bold;} +span.cm-link {text-decoration: underline;} + +div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;} +div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} diff --git a/lib/codemirror.js b/lib/codemirror.js new file mode 100644 index 0000000..3635eef --- /dev/null +++ b/lib/codemirror.js @@ -0,0 +1,2817 @@ +// CodeMirror version 2.21 +// +// All functions that need access to the editor's state live inside +// the CodeMirror function. Below that, at the bottom of the file, +// some utilities are defined. + +// CodeMirror is the only global var we claim +var CodeMirror = (function() { + // This is the function that produces an editor instance. It's + // closure is used to store the editor state. + function CodeMirror(place, givenOptions) { + // Determine effective options based on given values and defaults. + var options = {}, defaults = CodeMirror.defaults; + for (var opt in defaults) + if (defaults.hasOwnProperty(opt)) + options[opt] = (givenOptions && givenOptions.hasOwnProperty(opt) ? givenOptions : defaults)[opt]; + + var targetDocument = options["document"]; + // The element in which the editor lives. + var wrapper = targetDocument.createElement("div"); + wrapper.className = "CodeMirror" + (options.lineWrapping ? " CodeMirror-wrap" : ""); + // This mess creates the base DOM structure for the editor. + wrapper.innerHTML = + '
' + // Wraps and hides input textarea + '
' + + '
' + + '
' + // Set to the height of the text, causes scrolling + '
' + // Moved around its parent to cover visible view + '
' + + // Provides positioning relative to (visible) text origin + '
' + + '
' + + '
 
' + // Absolutely positioned blinky cursor + '
' + // DIVs containing the selection and the actual code + '
'; + if (place.appendChild) place.appendChild(wrapper); else place(wrapper); + // I've never seen more elegant code in my life. + var inputDiv = wrapper.firstChild, input = inputDiv.firstChild, + scroller = wrapper.lastChild, code = scroller.firstChild, + mover = code.firstChild, gutter = mover.firstChild, gutterText = gutter.firstChild, + lineSpace = gutter.nextSibling.firstChild, measure = lineSpace.firstChild, + cursor = measure.nextSibling, selectionDiv = cursor.nextSibling, + lineDiv = selectionDiv.nextSibling; + themeChanged(); + // Needed to hide big blue blinking cursor on Mobile Safari + if (ios) input.style.width = "0px"; + if (!webkit) lineSpace.draggable = true; + lineSpace.style.outline = "none"; + if (options.tabindex != null) input.tabIndex = options.tabindex; + if (!options.gutter && !options.lineNumbers) gutter.style.display = "none"; + + // Check for problem with IE innerHTML not working when we have a + // P (or similar) parent node. + try { stringWidth("x"); } + catch (e) { + if (e.message.match(/runtime/i)) + e = new Error("A CodeMirror inside a P-style element does not work in Internet Explorer. (innerHTML bug)"); + throw e; + } + + // Delayed object wrap timeouts, making sure only one is active. blinker holds an interval. + var poll = new Delayed(), highlight = new Delayed(), blinker; + + // mode holds a mode API object. doc is the tree of Line objects, + // work an array of lines that should be parsed, and history the + // undo history (instance of History constructor). + var mode, doc = new BranchChunk([new LeafChunk([new Line("")])]), work, focused; + loadMode(); + // The selection. These are always maintained to point at valid + // positions. Inverted is used to remember that the user is + // selecting bottom-to-top. + var sel = {from: {line: 0, ch: 0}, to: {line: 0, ch: 0}, inverted: false}; + // Selection-related flags. shiftSelecting obviously tracks + // whether the user is holding shift. + var shiftSelecting, lastClick, lastDoubleClick, lastScrollPos = 0, draggingText, + overwrite = false, suppressEdits = false; + // Variables used by startOperation/endOperation to track what + // happened during the operation. + var updateInput, userSelChange, changes, textChanged, selectionChanged, leaveInputAlone, + gutterDirty, callbacks; + // Current visible range (may be bigger than the view window). + var displayOffset = 0, showingFrom = 0, showingTo = 0, lastSizeC = 0; + // bracketHighlighted is used to remember that a backet has been + // marked. + var bracketHighlighted; + // Tracks the maximum line length so that the horizontal scrollbar + // can be kept static when scrolling. + var maxLine = "", maxWidth, tabText = computeTabText(); + + // Initialize the content. + operation(function(){setValue(options.value || ""); updateInput = false;})(); + var history = new History(); + + // Register our event handlers. + connect(scroller, "mousedown", operation(onMouseDown)); + connect(scroller, "dblclick", operation(onDoubleClick)); + connect(lineSpace, "dragstart", onDragStart); + connect(lineSpace, "selectstart", e_preventDefault); + // 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 (!gecko) connect(scroller, "contextmenu", onContextMenu); + connect(scroller, "scroll", function() { + lastScrollPos = scroller.scrollTop; + updateDisplay([]); + if (options.fixedGutter) gutter.style.left = scroller.scrollLeft + "px"; + if (options.onScroll) options.onScroll(instance); + }); + connect(window, "resize", function() {updateDisplay(true);}); + connect(input, "keyup", operation(onKeyUp)); + connect(input, "input", fastPoll); + connect(input, "keydown", operation(onKeyDown)); + connect(input, "keypress", operation(onKeyPress)); + connect(input, "focus", onFocus); + connect(input, "blur", onBlur); + + connect(scroller, "dragenter", e_stop); + connect(scroller, "dragover", e_stop); + connect(scroller, "drop", operation(onDrop)); + connect(scroller, "paste", function(){focusInput(); fastPoll();}); + connect(input, "paste", fastPoll); + connect(input, "cut", operation(function(){ + if (!options.readOnly) replaceSelection(""); + })); + + // IE throws unspecified error in certain cases, when + // trying to access activeElement before onload + var hasFocus; try { hasFocus = (targetDocument.activeElement == input); } catch(e) { } + if (hasFocus) setTimeout(onFocus, 20); + else onBlur(); + + function isLine(l) {return l >= 0 && l < doc.size;} + // The instance object that we'll return. Mostly calls out to + // local functions in the CodeMirror function. Some do some extra + // range checking and/or clipping. operation is used to wrap the + // call so that changes it makes are tracked, and the display is + // updated afterwards. + var instance = wrapper.CodeMirror = { + getValue: getValue, + setValue: operation(setValue), + getSelection: getSelection, + replaceSelection: operation(replaceSelection), + focus: function(){focusInput(); onFocus(); fastPoll();}, + setOption: function(option, value) { + var oldVal = options[option]; + options[option] = value; + if (option == "mode" || option == "indentUnit") loadMode(); + else if (option == "readOnly" && value == "nocursor") {onBlur(); input.blur();} + else if (option == "readOnly" && !value) {resetInput(true);} + else if (option == "theme") themeChanged(); + else if (option == "lineWrapping" && oldVal != value) operation(wrappingChanged)(); + else if (option == "tabSize") operation(tabsChanged)(); + if (option == "lineNumbers" || option == "gutter" || option == "firstLineNumber" || option == "theme") + updateDisplay(true); + }, + getOption: function(option) {return options[option];}, + undo: operation(undo), + redo: operation(redo), + indentLine: operation(function(n, dir) { + if (typeof dir != "string") { + if (dir == null) dir = options.smartIndent ? "smart" : "prev"; + else dir = dir ? "add" : "subtract"; + } + if (isLine(n)) indentLine(n, dir); + }), + indentSelection: operation(indentSelected), + historySize: function() {return {undo: history.done.length, redo: history.undone.length};}, + clearHistory: function() {history = new History();}, + matchBrackets: operation(function(){matchBrackets(true);}), + getTokenAt: operation(function(pos) { + pos = clipPos(pos); + return getLine(pos.line).getTokenAt(mode, getStateBefore(pos.line), pos.ch); + }), + getStateAfter: function(line) { + line = clipLine(line == null ? doc.size - 1: line); + return getStateBefore(line + 1); + }, + cursorCoords: function(start){ + if (start == null) start = sel.inverted; + return pageCoords(start ? sel.from : sel.to); + }, + charCoords: function(pos){return pageCoords(clipPos(pos));}, + coordsChar: function(coords) { + var off = eltOffset(lineSpace); + return coordsChar(coords.x - off.left, coords.y - off.top); + }, + markText: operation(markText), + setBookmark: setBookmark, + setMarker: operation(addGutterMarker), + clearMarker: operation(removeGutterMarker), + setLineClass: operation(setLineClass), + hideLine: operation(function(h) {return setLineHidden(h, true);}), + showLine: operation(function(h) {return setLineHidden(h, false);}), + onDeleteLine: function(line, f) { + if (typeof line == "number") { + if (!isLine(line)) return null; + line = getLine(line); + } + (line.handlers || (line.handlers = [])).push(f); + return line; + }, + lineInfo: lineInfo, + addWidget: function(pos, node, scroll, vert, horiz) { + pos = localCoords(clipPos(pos)); + var top = pos.yBot, left = pos.x; + node.style.position = "absolute"; + code.appendChild(node); + if (vert == "over") top = pos.y; + else if (vert == "near") { + var vspace = Math.max(scroller.offsetHeight, doc.height * textHeight()), + hspace = Math.max(code.clientWidth, lineSpace.clientWidth) - paddingLeft(); + if (pos.yBot + node.offsetHeight > vspace && pos.y > node.offsetHeight) + top = pos.y - node.offsetHeight; + if (left + node.offsetWidth > hspace) + left = hspace - node.offsetWidth; + } + node.style.top = (top + paddingTop()) + "px"; + node.style.left = node.style.right = ""; + if (horiz == "right") { + left = code.clientWidth - node.offsetWidth; + node.style.right = "0px"; + } else { + if (horiz == "left") left = 0; + else if (horiz == "middle") left = (code.clientWidth - node.offsetWidth) / 2; + node.style.left = (left + paddingLeft()) + "px"; + } + if (scroll) + scrollIntoView(left, top, left + node.offsetWidth, top + node.offsetHeight); + }, + + lineCount: function() {return doc.size;}, + clipPos: clipPos, + getCursor: function(start) { + if (start == null) start = sel.inverted; + return copyPos(start ? sel.from : sel.to); + }, + somethingSelected: function() {return !posEq(sel.from, sel.to);}, + setCursor: operation(function(line, ch, user) { + if (ch == null && typeof line.line == "number") setCursor(line.line, line.ch, user); + else setCursor(line, ch, user); + }), + setSelection: operation(function(from, to, user) { + (user ? setSelectionUser : setSelection)(clipPos(from), clipPos(to || from)); + }), + getLine: function(line) {if (isLine(line)) return getLine(line).text;}, + getLineHandle: function(line) {if (isLine(line)) return getLine(line);}, + setLine: operation(function(line, text) { + if (isLine(line)) replaceRange(text, {line: line, ch: 0}, {line: line, ch: getLine(line).text.length}); + }), + removeLine: operation(function(line) { + if (isLine(line)) replaceRange("", {line: line, ch: 0}, clipPos({line: line+1, ch: 0})); + }), + replaceRange: operation(replaceRange), + getRange: function(from, to) {return getRange(clipPos(from), clipPos(to));}, + + execCommand: function(cmd) {return commands[cmd](instance);}, + // Stuff used by commands, probably not much use to outside code. + moveH: operation(moveH), + deleteH: operation(deleteH), + moveV: operation(moveV), + toggleOverwrite: function() {overwrite = !overwrite;}, + + posFromIndex: function(off) { + var lineNo = 0, ch; + doc.iter(0, doc.size, function(line) { + var sz = line.text.length + 1; + if (sz > off) { ch = off; return true; } + off -= sz; + ++lineNo; + }); + return clipPos({line: lineNo, ch: ch}); + }, + indexFromPos: function (coords) { + if (coords.line < 0 || coords.ch < 0) return 0; + var index = coords.ch; + doc.iter(0, coords.line, function (line) { + index += line.text.length + 1; + }); + return index; + }, + scrollTo: function(x, y) { + if (x != null) scroller.scrollTop = x; + if (y != null) scroller.scrollLeft = y; + updateDisplay([]); + }, + + operation: function(f){return operation(f)();}, + refresh: function(){ + updateDisplay(true); + if (scroller.scrollHeight > lastScrollPos) + scroller.scrollTop = lastScrollPos; + }, + getInputField: function(){return input;}, + getWrapperElement: function(){return wrapper;}, + getScrollerElement: function(){return scroller;}, + getGutterElement: function(){return gutter;} + }; + + function getLine(n) { return getLineAt(doc, n); } + function updateLineHeight(line, height) { + gutterDirty = true; + var diff = height - line.height; + for (var n = line; n; n = n.parent) n.height += diff; + } + + function setValue(code) { + var top = {line: 0, ch: 0}; + updateLines(top, {line: doc.size - 1, ch: getLine(doc.size-1).text.length}, + splitLines(code), top, top); + updateInput = true; + } + function getValue(code) { + var text = []; + doc.iter(0, doc.size, function(line) { text.push(line.text); }); + return text.join("\n"); + } + + function onMouseDown(e) { + setShift(e_prop(e, "shiftKey")); + // Check whether this is a click in a widget + for (var n = e_target(e); n != wrapper; n = n.parentNode) + if (n.parentNode == code && n != mover) return; + + // See if this is a click in the gutter + for (var n = e_target(e); n != wrapper; n = n.parentNode) + if (n.parentNode == gutterText) { + if (options.onGutterClick) + options.onGutterClick(instance, indexOf(gutterText.childNodes, n) + showingFrom, e); + return e_preventDefault(e); + } + + var start = posFromMouse(e); + + switch (e_button(e)) { + case 3: + if (gecko && !mac) onContextMenu(e); + return; + case 2: + if (start) setCursor(start.line, start.ch, true); + 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) == scroller) e_preventDefault(e); return;} + + if (!focused) onFocus(); + + var now = +new Date; + if (lastDoubleClick && lastDoubleClick.time > now - 400 && posEq(lastDoubleClick.pos, start)) { + e_preventDefault(e); + setTimeout(focusInput, 20); + return selectLine(start.line); + } else if (lastClick && lastClick.time > now - 400 && posEq(lastClick.pos, start)) { + lastDoubleClick = {time: now, pos: start}; + e_preventDefault(e); + return selectWordAt(start); + } else { lastClick = {time: now, pos: start}; } + + var last = start, going; + if (dragAndDrop && !options.readOnly && !posEq(sel.from, sel.to) && + !posLess(start, sel.from) && !posLess(sel.to, start)) { + // Let the drag handler handle this. + if (webkit) lineSpace.draggable = true; + var up = connect(targetDocument, "mouseup", operation(function(e2) { + if (webkit) lineSpace.draggable = false; + draggingText = false; + up(); + if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) < 10) { + e_preventDefault(e2); + setCursor(start.line, start.ch, true); + focusInput(); + } + }), true); + draggingText = true; + return; + } + e_preventDefault(e); + setCursor(start.line, start.ch, true); + + function extend(e) { + var cur = posFromMouse(e, true); + if (cur && !posEq(cur, last)) { + if (!focused) onFocus(); + last = cur; + setSelectionUser(start, cur); + updateInput = false; + var visible = visibleLines(); + if (cur.line >= visible.to || cur.line < visible.from) + going = setTimeout(operation(function(){extend(e);}), 150); + } + } + + var move = connect(targetDocument, "mousemove", operation(function(e) { + clearTimeout(going); + e_preventDefault(e); + extend(e); + }), true); + var up = connect(targetDocument, "mouseup", operation(function(e) { + clearTimeout(going); + var cur = posFromMouse(e); + if (cur) setSelectionUser(start, cur); + e_preventDefault(e); + focusInput(); + updateInput = true; + move(); up(); + }), true); + } + function onDoubleClick(e) { + for (var n = e_target(e); n != wrapper; n = n.parentNode) + if (n.parentNode == gutterText) return e_preventDefault(e); + var start = posFromMouse(e); + if (!start) return; + lastDoubleClick = {time: +new Date, pos: start}; + e_preventDefault(e); + selectWordAt(start); + } + function onDrop(e) { + e.preventDefault(); + var pos = posFromMouse(e, true), files = e.dataTransfer.files; + if (!pos || options.readOnly) return; + if (files && files.length && window.FileReader && window.File) { + function loadFile(file, i) { + var reader = new FileReader; + reader.onload = function() { + text[i] = reader.result; + if (++read == n) { + pos = clipPos(pos); + operation(function() { + var end = replaceRange(text.join(""), pos, pos); + setSelectionUser(pos, end); + })(); + } + }; + reader.readAsText(file); + } + var n = files.length, text = Array(n), read = 0; + for (var i = 0; i < n; ++i) loadFile(files[i], i); + } + else { + try { + var text = e.dataTransfer.getData("Text"); + if (text) { + var curFrom = sel.from, curTo = sel.to; + setSelectionUser(pos, pos); + if (draggingText) replaceRange("", curFrom, curTo); + replaceSelection(text); + focusInput(); + } + } + catch(e){} + } + } + function onDragStart(e) { + var txt = getSelection(); + // This will reset escapeElement + htmlEscape(txt); + e.dataTransfer.setDragImage(escapeElement, 0, 0); + e.dataTransfer.setData("Text", txt); + } + function handleKeyBinding(e) { + var name = keyNames[e_prop(e, "keyCode")], next = keyMap[options.keyMap].auto, bound, dropShift; + function handleNext() { + return next.call ? next.call(null, instance) : next; + } + if (name == null || e.altGraphKey) { + if (next) options.keyMap = handleNext(); + return null; + } + if (e_prop(e, "altKey")) name = "Alt-" + name; + if (e_prop(e, "ctrlKey")) name = "Ctrl-" + name; + if (e_prop(e, "metaKey")) name = "Cmd-" + name; + if (e_prop(e, "shiftKey") && + (bound = lookupKey("Shift-" + name, options.extraKeys, options.keyMap))) { + dropShift = true; + } else { + bound = lookupKey(name, options.extraKeys, options.keyMap); + } + if (typeof bound == "string") { + if (commands.propertyIsEnumerable(bound)) bound = commands[bound]; + else bound = null; + } + if (next && (bound || !isModifierKey(e))) options.keyMap = handleNext(); + if (!bound) return false; + var prevShift = shiftSelecting; + try { + if (options.readOnly) suppressEdits = true; + if (dropShift) shiftSelecting = null; + bound(instance); + } finally { + shiftSelecting = prevShift; + suppressEdits = false; + } + e_preventDefault(e); + return true; + } + var lastStoppedKey = null; + function onKeyDown(e) { + if (!focused) onFocus(); + if (ie && e.keyCode == 27) { e.returnValue = false; } + if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return; + var code = e_prop(e, "keyCode"); + // IE does strange things with escape. + setShift(code == 16 || e_prop(e, "shiftKey")); + // First give onKeyEvent option a chance to handle this. + var handled = handleKeyBinding(e); + if (window.opera) { + lastStoppedKey = handled ? code : null; + // Opera has no cut event... we try to at least catch the key combo + if (!handled && code == 88 && e_prop(e, mac ? "metaKey" : "ctrlKey")) + replaceSelection(""); + } + } + function onKeyPress(e) { + var keyCode = e_prop(e, "keyCode"), charCode = e_prop(e, "charCode"); + if (window.opera && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return;} + if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return; + if (window.opera && !e.which && handleKeyBinding(e)) return; + if (options.electricChars && mode.electricChars && options.smartIndent && !options.readOnly) { + var ch = String.fromCharCode(charCode == null ? keyCode : charCode); + if (mode.electricChars.indexOf(ch) > -1) + setTimeout(operation(function() {indentLine(sel.to.line, "smart");}), 75); + } + fastPoll(); + } + function onKeyUp(e) { + if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return; + if (e_prop(e, "keyCode") == 16) shiftSelecting = null; + } + + function onFocus() { + if (options.readOnly == "nocursor") return; + if (!focused) { + if (options.onFocus) options.onFocus(instance); + focused = true; + if (wrapper.className.search(/\bCodeMirror-focused\b/) == -1) + wrapper.className += " CodeMirror-focused"; + if (!leaveInputAlone) resetInput(true); + } + slowPoll(); + restartBlink(); + } + function onBlur() { + if (focused) { + if (options.onBlur) options.onBlur(instance); + focused = false; + if (bracketHighlighted) + operation(function(){ + if (bracketHighlighted) { bracketHighlighted(); bracketHighlighted = null; } + })(); + wrapper.className = wrapper.className.replace(" CodeMirror-focused", ""); + } + clearInterval(blinker); + setTimeout(function() {if (!focused) shiftSelecting = null;}, 150); + } + + // Replace the range from from to to by the strings in newText. + // Afterwards, set the selection to selFrom, selTo. + function updateLines(from, to, newText, selFrom, selTo) { + if (suppressEdits) return; + if (history) { + var old = []; + doc.iter(from.line, to.line + 1, function(line) { old.push(line.text); }); + history.addChange(from.line, newText.length, old); + while (history.done.length > options.undoDepth) history.done.shift(); + } + updateLinesNoUndo(from, to, newText, selFrom, selTo); + } + function unredoHelper(from, to, dir) { + var set = from.pop(), len = set ? set.length : 0, out = []; + for (var i = dir > 0 ? 0 : len - 1, e = dir > 0 ? len : -1; i != e; i += dir) { + var change = set[i]; + var replaced = [], end = change.start + change.added; + doc.iter(change.start, end, function(line) { replaced.push(line.text); }); + out.push({start: change.start, added: change.old.length, old: replaced}); + var pos = clipPos({line: change.start + change.old.length - 1, + ch: editEnd(replaced[replaced.length-1], change.old[change.old.length-1])}); + updateLinesNoUndo({line: change.start, ch: 0}, {line: end - 1, ch: getLine(end-1).text.length}, change.old, pos, pos); + } + updateInput = true; + to.push(out); + } + function undo() {unredoHelper(history.done, history.undone, -1);} + function redo() {unredoHelper(history.undone, history.done, 1);} + + function updateLinesNoUndo(from, to, newText, selFrom, selTo) { + if (suppressEdits) return; + var recomputeMaxLength = false, maxLineLength = maxLine.length; + if (!options.lineWrapping) + doc.iter(from.line, to.line, function(line) { + if (line.text.length == maxLineLength) {recomputeMaxLength = true; return true;} + }); + if (from.line != to.line || newText.length > 1) gutterDirty = true; + + var nlines = to.line - from.line, firstLine = getLine(from.line), lastLine = getLine(to.line); + // First adjust the line structure, taking some care to leave highlighting intact. + if (from.ch == 0 && to.ch == 0 && newText[newText.length - 1] == "") { + // This is a whole-line replace. Treated specially to make + // sure line objects move the way they are supposed to. + var added = [], prevLine = null; + if (from.line) { + prevLine = getLine(from.line - 1); + prevLine.fixMarkEnds(lastLine); + } else lastLine.fixMarkStarts(); + for (var i = 0, e = newText.length - 1; i < e; ++i) + added.push(Line.inheritMarks(newText[i], prevLine)); + if (nlines) doc.remove(from.line, nlines, callbacks); + if (added.length) doc.insert(from.line, added); + } else if (firstLine == lastLine) { + if (newText.length == 1) + firstLine.replace(from.ch, to.ch, newText[0]); + else { + lastLine = firstLine.split(to.ch, newText[newText.length-1]); + firstLine.replace(from.ch, null, newText[0]); + firstLine.fixMarkEnds(lastLine); + var added = []; + for (var i = 1, e = newText.length - 1; i < e; ++i) + added.push(Line.inheritMarks(newText[i], firstLine)); + added.push(lastLine); + doc.insert(from.line + 1, added); + } + } else if (newText.length == 1) { + firstLine.replace(from.ch, null, newText[0]); + lastLine.replace(null, to.ch, ""); + firstLine.append(lastLine); + doc.remove(from.line + 1, nlines, callbacks); + } else { + var added = []; + firstLine.replace(from.ch, null, newText[0]); + lastLine.replace(null, to.ch, newText[newText.length-1]); + firstLine.fixMarkEnds(lastLine); + for (var i = 1, e = newText.length - 1; i < e; ++i) + added.push(Line.inheritMarks(newText[i], firstLine)); + if (nlines > 1) doc.remove(from.line + 1, nlines - 1, callbacks); + doc.insert(from.line + 1, added); + } + if (options.lineWrapping) { + var perLine = scroller.clientWidth / charWidth() - 3; + doc.iter(from.line, from.line + newText.length, function(line) { + if (line.hidden) return; + var guess = Math.ceil(line.text.length / perLine) || 1; + if (guess != line.height) updateLineHeight(line, guess); + }); + } else { + doc.iter(from.line, i + newText.length, function(line) { + var l = line.text; + if (l.length > maxLineLength) { + maxLine = l; maxLineLength = l.length; maxWidth = null; + recomputeMaxLength = false; + } + }); + if (recomputeMaxLength) { + maxLineLength = 0; maxLine = ""; maxWidth = null; + doc.iter(0, doc.size, function(line) { + var l = line.text; + if (l.length > maxLineLength) { + maxLineLength = l.length; maxLine = l; + } + }); + } + } + + // Add these lines to the work array, so that they will be + // highlighted. Adjust work lines if lines were added/removed. + var newWork = [], lendiff = newText.length - nlines - 1; + for (var i = 0, l = work.length; i < l; ++i) { + var task = work[i]; + if (task < from.line) newWork.push(task); + else if (task > to.line) newWork.push(task + lendiff); + } + var hlEnd = from.line + Math.min(newText.length, 500); + highlightLines(from.line, hlEnd); + newWork.push(hlEnd); + work = newWork; + startWorker(100); + // Remember that these lines changed, for updating the display + changes.push({from: from.line, to: to.line + 1, diff: lendiff}); + var changeObj = {from: from, to: to, text: newText}; + if (textChanged) { + for (var cur = textChanged; cur.next; cur = cur.next) {} + cur.next = changeObj; + } else textChanged = changeObj; + + // Update the selection + function updateLine(n) {return n <= Math.min(to.line, to.line + lendiff) ? n : n + lendiff;} + setSelection(selFrom, selTo, updateLine(sel.from.line), updateLine(sel.to.line)); + + // Make sure the scroll-size div has the correct height. + if (scroller.clientHeight) + code.style.height = (doc.height * textHeight() + 2 * paddingTop()) + "px"; + } + + function replaceRange(code, from, to) { + from = clipPos(from); + if (!to) to = from; else to = clipPos(to); + code = splitLines(code); + function adjustPos(pos) { + if (posLess(pos, from)) return pos; + if (!posLess(to, pos)) return end; + var line = pos.line + code.length - (to.line - from.line) - 1; + var ch = pos.ch; + if (pos.line == to.line) + ch += code[code.length-1].length - (to.ch - (to.line == from.line ? from.ch : 0)); + return {line: line, ch: ch}; + } + var end; + replaceRange1(code, from, to, function(end1) { + end = end1; + return {from: adjustPos(sel.from), to: adjustPos(sel.to)}; + }); + return end; + } + function replaceSelection(code, collapse) { + replaceRange1(splitLines(code), sel.from, sel.to, function(end) { + if (collapse == "end") return {from: end, to: end}; + else if (collapse == "start") return {from: sel.from, to: sel.from}; + else return {from: sel.from, to: end}; + }); + } + function replaceRange1(code, from, to, computeSel) { + var endch = code.length == 1 ? code[0].length + from.ch : code[code.length-1].length; + var newSel = computeSel({line: from.line + code.length - 1, ch: endch}); + updateLines(from, to, code, newSel.from, newSel.to); + } + + function getRange(from, to) { + var l1 = from.line, l2 = to.line; + if (l1 == l2) return getLine(l1).text.slice(from.ch, to.ch); + var code = [getLine(l1).text.slice(from.ch)]; + doc.iter(l1 + 1, l2, function(line) { code.push(line.text); }); + code.push(getLine(l2).text.slice(0, to.ch)); + return code.join("\n"); + } + function getSelection() { + return getRange(sel.from, sel.to); + } + + var pollingFast = false; // Ensures slowPoll doesn't cancel fastPoll + function slowPoll() { + if (pollingFast) return; + poll.set(options.pollInterval, function() { + startOperation(); + readInput(); + if (focused) slowPoll(); + endOperation(); + }); + } + function fastPoll() { + var missed = false; + pollingFast = true; + function p() { + startOperation(); + var changed = readInput(); + if (!changed && !missed) {missed = true; poll.set(60, p);} + else {pollingFast = false; slowPoll();} + endOperation(); + } + 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.) + var prevInput = ""; + function readInput() { + if (leaveInputAlone || !focused || hasSelection(input) || options.readOnly) return false; + var text = input.value; + if (text == prevInput) return false; + shiftSelecting = null; + var same = 0, l = Math.min(prevInput.length, text.length); + while (same < l && prevInput[same] == text[same]) ++same; + if (same < prevInput.length) + sel.from = {line: sel.from.line, ch: sel.from.ch - (prevInput.length - same)}; + else if (overwrite && posEq(sel.from, sel.to)) + sel.to = {line: sel.to.line, ch: Math.min(getLine(sel.to.line).text.length, sel.to.ch + (text.length - same))}; + replaceSelection(text.slice(same), "end"); + prevInput = text; + return true; + } + function resetInput(user) { + if (!posEq(sel.from, sel.to)) { + prevInput = ""; + input.value = getSelection(); + input.select(); + } else if (user) prevInput = input.value = ""; + } + + function focusInput() { + if (options.readOnly != "nocursor") input.focus(); + } + + function scrollEditorIntoView() { + if (!cursor.getBoundingClientRect) return; + var rect = cursor.getBoundingClientRect(); + // IE returns bogus coordinates when the instance sits inside of an iframe and the cursor is hidden + if (ie && rect.top == rect.bottom) return; + var winH = window.innerHeight || Math.max(document.body.offsetHeight, document.documentElement.offsetHeight); + if (rect.top < 0 || rect.bottom > winH) cursor.scrollIntoView(); + } + function scrollCursorIntoView() { + var cursor = localCoords(sel.inverted ? sel.from : sel.to); + var x = options.lineWrapping ? Math.min(cursor.x, lineSpace.offsetWidth) : cursor.x; + return scrollIntoView(x, cursor.y, x, cursor.yBot); + } + function scrollIntoView(x1, y1, x2, y2) { + var pl = paddingLeft(), pt = paddingTop(), lh = textHeight(); + y1 += pt; y2 += pt; x1 += pl; x2 += pl; + var screen = scroller.clientHeight, screentop = scroller.scrollTop, scrolled = false, result = true; + if (y1 < screentop) {scroller.scrollTop = Math.max(0, y1 - 2*lh); scrolled = true;} + else if (y2 > screentop + screen) {scroller.scrollTop = y2 + lh - screen; scrolled = true;} + + var screenw = scroller.clientWidth, screenleft = scroller.scrollLeft; + var gutterw = options.fixedGutter ? gutter.clientWidth : 0; + if (x1 < screenleft + gutterw) { + if (x1 < 50) x1 = 0; + scroller.scrollLeft = Math.max(0, x1 - 10 - gutterw); + scrolled = true; + } + else if (x2 > screenw + screenleft - 3) { + scroller.scrollLeft = x2 + 10 - screenw; + scrolled = true; + if (x2 > code.clientWidth) result = false; + } + if (scrolled && options.onScroll) options.onScroll(instance); + return result; + } + + function visibleLines() { + var lh = textHeight(), top = scroller.scrollTop - paddingTop(); + var from_height = Math.max(0, Math.floor(top / lh)); + var to_height = Math.ceil((top + scroller.clientHeight) / lh); + return {from: lineAtHeight(doc, from_height), + to: lineAtHeight(doc, to_height)}; + } + // Uses a set of changes plus the current scroll position to + // determine which DOM updates have to be made, and makes the + // updates. + function updateDisplay(changes, suppressCallback) { + if (!scroller.clientWidth) { + showingFrom = showingTo = displayOffset = 0; + return; + } + // Compute the new visible window + var visible = visibleLines(); + // Bail out if the visible area is already rendered and nothing changed. + if (changes !== true && changes.length == 0 && visible.from > showingFrom && visible.to < showingTo) return; + var from = Math.max(visible.from - 100, 0), to = Math.min(doc.size, visible.to + 100); + if (showingFrom < from && from - showingFrom < 20) from = showingFrom; + if (showingTo > to && showingTo - to < 20) to = Math.min(doc.size, showingTo); + + // Create a range of theoretically intact lines, and punch holes + // in that using the change info. + var intact = changes === true ? [] : + computeIntact([{from: showingFrom, to: showingTo, domStart: 0}], changes); + // 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.domStart += (from - range.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 (intactLines == to - from) return; + intact.sort(function(a, b) {return a.domStart - b.domStart;}); + + var th = textHeight(), gutterDisplay = gutter.style.display; + lineDiv.style.display = "none"; + patchDisplay(from, to, intact); + lineDiv.style.display = gutter.style.display = ""; + + // Position the mover div to align with the lines it's supposed + // to be showing (which will cover the visible display) + var different = from != showingFrom || to != showingTo || lastSizeC != scroller.clientHeight + th; + // This is just a bogus formula that detects when the editor is + // resized or the font size changes. + if (different) lastSizeC = scroller.clientHeight + th; + showingFrom = from; showingTo = to; + displayOffset = heightAtLine(doc, from); + mover.style.top = (displayOffset * th) + "px"; + if (scroller.clientHeight) + code.style.height = (doc.height * th + 2 * paddingTop()) + "px"; + + // Since this is all rather error prone, it is honoured with the + // only assertion in the whole file. + if (lineDiv.childNodes.length != showingTo - showingFrom) + throw new Error("BAD PATCH! " + JSON.stringify(intact) + " size=" + (showingTo - showingFrom) + + " nodes=" + lineDiv.childNodes.length); + + if (options.lineWrapping) { + maxWidth = scroller.clientWidth; + var curNode = lineDiv.firstChild, heightChanged = false; + doc.iter(showingFrom, showingTo, function(line) { + if (!line.hidden) { + var height = Math.round(curNode.offsetHeight / th) || 1; + if (line.height != height) { + updateLineHeight(line, height); + gutterDirty = heightChanged = true; + } + } + curNode = curNode.nextSibling; + }); + if (heightChanged) + code.style.height = (doc.height * th + 2 * paddingTop()) + "px"; + } else { + if (maxWidth == null) maxWidth = stringWidth(maxLine); + if (maxWidth > scroller.clientWidth) { + lineSpace.style.width = maxWidth + "px"; + // Needed to prevent odd wrapping/hiding of widgets placed in here. + code.style.width = ""; + code.style.width = scroller.scrollWidth + "px"; + } else { + lineSpace.style.width = code.style.width = ""; + } + } + gutter.style.display = gutterDisplay; + if (different || gutterDirty) updateGutter(); + updateSelection(); + if (!suppressCallback && options.onUpdate) options.onUpdate(instance); + return true; + } + + 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, + domStart: range.domStart}); + 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, domStart: range.domStart}); + if (change.to < range.to) + intact2.push({from: change.to + diff, to: range.to + diff, + domStart: range.domStart + (change.to - range.from)}); + } + } + intact = intact2; + } + return intact; + } + + function patchDisplay(from, to, intact) { + // The first pass removes the DOM nodes that aren't intact. + if (!intact.length) lineDiv.innerHTML = ""; + else { + function killNode(node) { + var tmp = node.nextSibling; + node.parentNode.removeChild(node); + return tmp; + } + var domPos = 0, curNode = lineDiv.firstChild, n; + for (var i = 0; i < intact.length; ++i) { + var cur = intact[i]; + while (cur.domStart > domPos) {curNode = killNode(curNode); domPos++;} + for (var j = 0, e = cur.to - cur.from; j < e; ++j) {curNode = curNode.nextSibling; domPos++;} + } + while (curNode) curNode = killNode(curNode); + } + // This pass fills in the lines that actually changed. + var nextIntact = intact.shift(), curNode = lineDiv.firstChild, j = from; + var scratch = targetDocument.createElement("div"), newElt; + doc.iter(from, to, function(line) { + if (nextIntact && nextIntact.to == j) nextIntact = intact.shift(); + if (!nextIntact || nextIntact.from > j) { + if (line.hidden) var html = scratch.innerHTML = "
";
+          else {
+            var html = '
' + line.getHTML(tabText) + '
'; + // Kludge to make sure the styled element lies behind the selection (by z-index) + if (line.className) + html = '
 
' + html + "
"; + } + scratch.innerHTML = html; + lineDiv.insertBefore(scratch.firstChild, curNode); + } else { + curNode = curNode.nextSibling; + } + ++j; + }); + } + + function updateGutter() { + if (!options.gutter && !options.lineNumbers) return; + var hText = mover.offsetHeight, hEditor = scroller.clientHeight; + gutter.style.height = (hText - hEditor < 2 ? hEditor : hText) + "px"; + var html = [], i = showingFrom; + doc.iter(showingFrom, Math.max(showingTo, showingFrom + 1), function(line) { + if (line.hidden) { + html.push("
");
+        } else {
+          var marker = line.gutterMarker;
+          var text = options.lineNumbers ? i + options.firstLineNumber : null;
+          if (marker && marker.text)
+            text = marker.text.replace("%N%", text != null ? text : "");
+          else if (text == null)
+            text = "\u00a0";
+          html.push((marker && marker.style ? '
' : "
"), text);
+          for (var j = 1; j < line.height; ++j) html.push("
 "); + html.push("
"); + } + ++i; + }); + gutter.style.display = "none"; + gutterText.innerHTML = html.join(""); + var minwidth = String(doc.size).length, firstNode = gutterText.firstChild, val = eltText(firstNode), pad = ""; + while (val.length + pad.length < minwidth) pad += "\u00a0"; + if (pad) firstNode.insertBefore(targetDocument.createTextNode(pad), firstNode.firstChild); + gutter.style.display = ""; + lineSpace.style.marginLeft = gutter.offsetWidth + "px"; + gutterDirty = false; + } + function updateSelection() { + var collapsed = posEq(sel.from, sel.to); + var fromPos = localCoords(sel.from, true); + var toPos = collapsed ? fromPos : localCoords(sel.to, true); + var headPos = sel.inverted ? fromPos : toPos, th = textHeight(); + var wrapOff = eltOffset(wrapper), lineOff = eltOffset(lineDiv); + inputDiv.style.top = Math.max(0, Math.min(scroller.offsetHeight, headPos.y + lineOff.top - wrapOff.top)) + "px"; + inputDiv.style.left = Math.max(0, Math.min(scroller.offsetWidth, headPos.x + lineOff.left - wrapOff.left)) + "px"; + if (collapsed) { + cursor.style.top = headPos.y + "px"; + cursor.style.left = (options.lineWrapping ? Math.min(headPos.x, lineSpace.offsetWidth) : headPos.x) + "px"; + cursor.style.display = ""; + selectionDiv.style.display = "none"; + } else { + var sameLine = fromPos.y == toPos.y, html = ""; + function add(left, top, right, height) { + html += '
'; + } + if (sel.from.ch && fromPos.y >= 0) { + var right = sameLine ? lineSpace.clientWidth - toPos.x : 0; + add(fromPos.x, fromPos.y, right, th); + } + var middleStart = Math.max(0, fromPos.y + (sel.from.ch ? th : 0)); + var middleHeight = Math.min(toPos.y, lineSpace.clientHeight) - middleStart; + if (middleHeight > 0.2 * th) + add(0, middleStart, 0, middleHeight); + if ((!sameLine || !sel.from.ch) && toPos.y < lineSpace.clientHeight - .5 * th) + add(0, toPos.y, lineSpace.clientWidth - toPos.x, th); + selectionDiv.innerHTML = html; + cursor.style.display = "none"; + selectionDiv.style.display = ""; + } + } + + function setShift(val) { + if (val) shiftSelecting = shiftSelecting || (sel.inverted ? sel.to : sel.from); + else shiftSelecting = null; + } + function setSelectionUser(from, to) { + var sh = shiftSelecting && clipPos(shiftSelecting); + if (sh) { + if (posLess(sh, from)) from = sh; + else if (posLess(to, sh)) to = sh; + } + setSelection(from, to); + userSelChange = true; + } + // Update the selection. Last two args are only used by + // updateLines, since they have to be expressed in the line + // numbers before the update. + function setSelection(from, to, oldFrom, oldTo) { + goalColumn = null; + if (oldFrom == null) {oldFrom = sel.from.line; oldTo = sel.to.line;} + if (posEq(sel.from, from) && posEq(sel.to, to)) return; + if (posLess(to, from)) {var tmp = to; to = from; from = tmp;} + + // Skip over hidden lines. + if (from.line != oldFrom) from = skipHidden(from, oldFrom, sel.from.ch); + if (to.line != oldTo) to = skipHidden(to, oldTo, sel.to.ch); + + if (posEq(from, to)) sel.inverted = false; + else if (posEq(from, sel.to)) sel.inverted = false; + else if (posEq(to, sel.from)) sel.inverted = true; + + sel.from = from; sel.to = to; + selectionChanged = true; + } + function skipHidden(pos, oldLine, oldCh) { + function getNonHidden(dir) { + var lNo = pos.line + dir, end = dir == 1 ? doc.size : -1; + while (lNo != end) { + var line = getLine(lNo); + if (!line.hidden) { + var ch = pos.ch; + if (ch > oldCh || ch > line.text.length) ch = line.text.length; + return {line: lNo, ch: ch}; + } + lNo += dir; + } + } + var line = getLine(pos.line); + if (!line.hidden) return pos; + if (pos.line >= oldLine) return getNonHidden(1) || getNonHidden(-1); + else return getNonHidden(-1) || getNonHidden(1); + } + function setCursor(line, ch, user) { + var pos = clipPos({line: line, ch: ch || 0}); + (user ? setSelectionUser : setSelection)(pos, pos); + } + + function clipLine(n) {return Math.max(0, Math.min(n, doc.size-1));} + function clipPos(pos) { + if (pos.line < 0) return {line: 0, ch: 0}; + if (pos.line >= doc.size) return {line: doc.size-1, ch: getLine(doc.size-1).text.length}; + var ch = pos.ch, linelen = getLine(pos.line).text.length; + if (ch == null || ch > linelen) return {line: pos.line, ch: linelen}; + else if (ch < 0) return {line: pos.line, ch: 0}; + else return pos; + } + + function findPosH(dir, unit) { + var end = sel.inverted ? sel.from : sel.to, line = end.line, ch = end.ch; + var lineObj = getLine(line); + function findNextLine() { + for (var l = line + dir, e = dir < 0 ? -1 : doc.size; l != e; l += dir) { + var lo = getLine(l); + if (!lo.hidden) { line = l; lineObj = lo; return true; } + } + } + function moveOnce(boundToLine) { + if (ch == (dir < 0 ? 0 : lineObj.text.length)) { + if (!boundToLine && findNextLine()) ch = dir < 0 ? lineObj.text.length : 0; + else return false; + } else ch += dir; + return true; + } + if (unit == "char") moveOnce(); + else if (unit == "column") moveOnce(true); + else if (unit == "word") { + var sawWord = false; + for (;;) { + if (dir < 0) if (!moveOnce()) break; + if (isWordChar(lineObj.text.charAt(ch))) sawWord = true; + else if (sawWord) {if (dir < 0) {dir = 1; moveOnce();} break;} + if (dir > 0) if (!moveOnce()) break; + } + } + return {line: line, ch: ch}; + } + function moveH(dir, unit) { + var pos = dir < 0 ? sel.from : sel.to; + if (shiftSelecting || posEq(sel.from, sel.to)) pos = findPosH(dir, unit); + setCursor(pos.line, pos.ch, true); + } + function deleteH(dir, unit) { + if (!posEq(sel.from, sel.to)) replaceRange("", sel.from, sel.to); + else if (dir < 0) replaceRange("", findPosH(dir, unit), sel.to); + else replaceRange("", sel.from, findPosH(dir, unit)); + userSelChange = true; + } + var goalColumn = null; + function moveV(dir, unit) { + var dist = 0, pos = localCoords(sel.inverted ? sel.from : sel.to, true); + if (goalColumn != null) pos.x = goalColumn; + if (unit == "page") dist = Math.min(scroller.clientHeight, window.innerHeight || document.documentElement.clientHeight); + else if (unit == "line") dist = textHeight(); + var target = coordsChar(pos.x, pos.y + dist * dir + 2); + setCursor(target.line, target.ch, true); + goalColumn = pos.x; + } + + function selectWordAt(pos) { + var line = getLine(pos.line).text; + var start = pos.ch, end = pos.ch; + while (start > 0 && isWordChar(line.charAt(start - 1))) --start; + while (end < line.length && isWordChar(line.charAt(end))) ++end; + setSelectionUser({line: pos.line, ch: start}, {line: pos.line, ch: end}); + } + function selectLine(line) { + setSelectionUser({line: line, ch: 0}, {line: line, ch: getLine(line).text.length}); + } + function indentSelected(mode) { + if (posEq(sel.from, sel.to)) return indentLine(sel.from.line, mode); + var e = sel.to.line - (sel.to.ch ? 0 : 1); + for (var i = sel.from.line; i <= e; ++i) indentLine(i, mode); + } + + function indentLine(n, how) { + if (!how) how = "add"; + if (how == "smart") { + if (!mode.indent) how = "prev"; + else var state = getStateBefore(n); + } + + var line = getLine(n), curSpace = line.indentation(options.tabSize), + curSpaceString = line.text.match(/^\s*/)[0], indentation; + if (how == "prev") { + if (n) indentation = getLine(n-1).indentation(options.tabSize); + else indentation = 0; + } + else if (how == "smart") indentation = mode.indent(state, line.text.slice(curSpaceString.length), line.text); + else if (how == "add") indentation = curSpace + options.indentUnit; + else if (how == "subtract") indentation = curSpace - options.indentUnit; + indentation = Math.max(0, indentation); + var diff = indentation - curSpace; + + if (!diff) { + if (sel.from.line != n && sel.to.line != n) return; + var indentString = curSpaceString; + } + else { + var indentString = "", pos = 0; + if (options.indentWithTabs) + for (var i = Math.floor(indentation / options.tabSize); i; --i) {pos += options.tabSize; indentString += "\t";} + while (pos < indentation) {++pos; indentString += " ";} + } + + replaceRange(indentString, {line: n, ch: 0}, {line: n, ch: curSpaceString.length}); + } + + function loadMode() { + mode = CodeMirror.getMode(options, options.mode); + doc.iter(0, doc.size, function(line) { line.stateAfter = null; }); + work = [0]; + startWorker(); + } + function gutterChanged() { + var visible = options.gutter || options.lineNumbers; + gutter.style.display = visible ? "" : "none"; + if (visible) gutterDirty = true; + else lineDiv.parentNode.style.marginLeft = 0; + } + function wrappingChanged(from, to) { + if (options.lineWrapping) { + wrapper.className += " CodeMirror-wrap"; + var perLine = scroller.clientWidth / charWidth() - 3; + doc.iter(0, doc.size, function(line) { + if (line.hidden) return; + var guess = Math.ceil(line.text.length / perLine) || 1; + if (guess != 1) updateLineHeight(line, guess); + }); + lineSpace.style.width = code.style.width = ""; + } else { + wrapper.className = wrapper.className.replace(" CodeMirror-wrap", ""); + maxWidth = null; maxLine = ""; + doc.iter(0, doc.size, function(line) { + if (line.height != 1 && !line.hidden) updateLineHeight(line, 1); + if (line.text.length > maxLine.length) maxLine = line.text; + }); + } + changes.push({from: 0, to: doc.size}); + } + function computeTabText() { + for (var str = '', i = 0; i < options.tabSize; ++i) str += " "; + return str + ""; + } + function tabsChanged() { + tabText = computeTabText(); + updateDisplay(true); + } + function themeChanged() { + scroller.className = scroller.className.replace(/\s*cm-s-\w+/g, "") + + options.theme.replace(/(^|\s)\s*/g, " cm-s-"); + } + + function TextMarker() { this.set = []; } + TextMarker.prototype.clear = operation(function() { + var min = Infinity, max = -Infinity; + for (var i = 0, e = this.set.length; i < e; ++i) { + var line = this.set[i], mk = line.marked; + if (!mk || !line.parent) continue; + var lineN = lineNo(line); + min = Math.min(min, lineN); max = Math.max(max, lineN); + for (var j = 0; j < mk.length; ++j) + if (mk[j].set == this.set) mk.splice(j--, 1); + } + if (min != Infinity) + changes.push({from: min, to: max + 1}); + }); + TextMarker.prototype.find = function() { + var from, to; + for (var i = 0, e = this.set.length; i < e; ++i) { + var line = this.set[i], mk = line.marked; + for (var j = 0; j < mk.length; ++j) { + var mark = mk[j]; + if (mark.set == this.set) { + if (mark.from != null || mark.to != null) { + var found = lineNo(line); + if (found != null) { + if (mark.from != null) from = {line: found, ch: mark.from}; + if (mark.to != null) to = {line: found, ch: mark.to}; + } + } + } + } + } + return {from: from, to: to}; + }; + + function markText(from, to, className) { + from = clipPos(from); to = clipPos(to); + var tm = new TextMarker(); + function add(line, from, to, className) { + getLine(line).addMark(new MarkedText(from, to, className, tm.set)); + } + if (from.line == to.line) add(from.line, from.ch, to.ch, className); + else { + add(from.line, from.ch, null, className); + for (var i = from.line + 1, e = to.line; i < e; ++i) + add(i, null, null, className); + add(to.line, null, to.ch, className); + } + changes.push({from: from.line, to: to.line + 1}); + return tm; + } + + function setBookmark(pos) { + pos = clipPos(pos); + var bm = new Bookmark(pos.ch); + getLine(pos.line).addMark(bm); + return bm; + } + + function addGutterMarker(line, text, className) { + if (typeof line == "number") line = getLine(clipLine(line)); + line.gutterMarker = {text: text, style: className}; + gutterDirty = true; + return line; + } + function removeGutterMarker(line) { + if (typeof line == "number") line = getLine(clipLine(line)); + line.gutterMarker = null; + gutterDirty = true; + } + + function changeLine(handle, op) { + var no = handle, line = handle; + if (typeof handle == "number") line = getLine(clipLine(handle)); + else no = lineNo(handle); + if (no == null) return null; + if (op(line, no)) changes.push({from: no, to: no + 1}); + else return null; + return line; + } + function setLineClass(handle, className) { + return changeLine(handle, function(line) { + if (line.className != className) { + line.className = className; + return true; + } + }); + } + function setLineHidden(handle, hidden) { + return changeLine(handle, function(line, no) { + if (line.hidden != hidden) { + line.hidden = hidden; + updateLineHeight(line, hidden ? 0 : 1); + var fline = sel.from.line, tline = sel.to.line; + if (hidden && (fline == no || tline == no)) { + var from = fline == no ? skipHidden({line: fline, ch: 0}, fline, 0) : sel.from; + var to = tline == no ? skipHidden({line: tline, ch: 0}, tline, 0) : sel.to; + setSelection(from, to); + } + return (gutterDirty = true); + } + }); + } + + function lineInfo(line) { + if (typeof line == "number") { + if (!isLine(line)) return null; + var n = line; + line = getLine(line); + if (!line) return null; + } + else { + var n = lineNo(line); + if (n == null) return null; + } + var marker = line.gutterMarker; + return {line: n, handle: line, text: line.text, markerText: marker && marker.text, + markerClass: marker && marker.style, lineClass: line.className}; + } + + function stringWidth(str) { + measure.innerHTML = "
x
"; + measure.firstChild.firstChild.firstChild.nodeValue = str; + return measure.firstChild.firstChild.offsetWidth || 10; + } + // These are used to go from pixel positions to character + // positions, taking varying character widths into account. + function charFromX(line, x) { + if (x <= 0) return 0; + var lineObj = getLine(line), text = lineObj.text; + function getX(len) { + measure.innerHTML = "
" + lineObj.getHTML(tabText, len) + "
"; + return measure.firstChild.firstChild.offsetWidth; + } + var from = 0, fromX = 0, to = text.length, toX; + // Guess a suitable upper bound for our search. + var estimated = Math.min(to, Math.ceil(x / charWidth())); + for (;;) { + var estX = getX(estimated); + if (estX <= x && estimated < to) estimated = Math.min(to, Math.ceil(estimated * 1.2)); + else {toX = estX; to = estimated; break;} + } + if (x > toX) return to; + // Try to guess a suitable lower bound as well. + estimated = Math.floor(to * 0.8); estX = getX(estimated); + if (estX < x) {from = estimated; fromX = estX;} + // Do a binary search between these bounds. + for (;;) { + if (to - from <= 1) return (toX - x > x - fromX) ? from : to; + var middle = Math.ceil((from + to) / 2), middleX = getX(middle); + if (middleX > x) {to = middle; toX = middleX;} + else {from = middle; fromX = middleX;} + } + } + + var tempId = Math.floor(Math.random() * 0xffffff).toString(16); + function measureLine(line, ch) { + if (ch == 0) return {top: 0, left: 0}; + var extra = ""; + // Include extra text at the end to make sure the measured line is wrapped in the right way. + if (options.lineWrapping) { + var end = line.text.indexOf(" ", ch + 2); + extra = htmlEscape(line.text.slice(ch + 1, end < 0 ? line.text.length : end + (ie ? 5 : 0))); + } + measure.innerHTML = "
" + line.getHTML(tabText, ch) +
+        '' + htmlEscape(line.text.charAt(ch) || " ") + "" +
+        extra + "
"; + var elt = document.getElementById("CodeMirror-temp-" + tempId); + var top = elt.offsetTop, left = elt.offsetLeft; + // Older IEs report zero offsets for spans directly after a wrap + if (ie && top == 0 && left == 0) { + var backup = document.createElement("span"); + backup.innerHTML = "x"; + elt.parentNode.insertBefore(backup, elt.nextSibling); + top = backup.offsetTop; + } + return {top: top, left: left}; + } + function localCoords(pos, inLineWrap) { + var x, lh = textHeight(), y = lh * (heightAtLine(doc, pos.line) - (inLineWrap ? displayOffset : 0)); + if (pos.ch == 0) x = 0; + else { + var sp = measureLine(getLine(pos.line), pos.ch); + x = sp.left; + if (options.lineWrapping) y += Math.max(0, sp.top); + } + return {x: x, y: y, yBot: y + lh}; + } + // Coords must be lineSpace-local + function coordsChar(x, y) { + if (y < 0) y = 0; + var th = textHeight(), cw = charWidth(), heightPos = displayOffset + Math.floor(y / th); + var lineNo = lineAtHeight(doc, heightPos); + if (lineNo >= doc.size) return {line: doc.size - 1, ch: getLine(doc.size - 1).text.length}; + var lineObj = getLine(lineNo), text = lineObj.text; + var tw = options.lineWrapping, innerOff = tw ? heightPos - heightAtLine(doc, lineNo) : 0; + if (x <= 0 && innerOff == 0) return {line: lineNo, ch: 0}; + function getX(len) { + var sp = measureLine(lineObj, len); + if (tw) { + var off = Math.round(sp.top / th); + return Math.max(0, sp.left + (off - innerOff) * scroller.clientWidth); + } + return sp.left; + } + var from = 0, fromX = 0, to = text.length, toX; + // Guess a suitable upper bound for our search. + var estimated = Math.min(to, Math.ceil((x + innerOff * scroller.clientWidth * .9) / cw)); + for (;;) { + var estX = getX(estimated); + if (estX <= x && estimated < to) estimated = Math.min(to, Math.ceil(estimated * 1.2)); + else {toX = estX; to = estimated; break;} + } + if (x > toX) return {line: lineNo, ch: to}; + // Try to guess a suitable lower bound as well. + estimated = Math.floor(to * 0.8); estX = getX(estimated); + if (estX < x) {from = estimated; fromX = estX;} + // Do a binary search between these bounds. + for (;;) { + if (to - from <= 1) return {line: lineNo, ch: (toX - x > x - fromX) ? from : to}; + var middle = Math.ceil((from + to) / 2), middleX = getX(middle); + if (middleX > x) {to = middle; toX = middleX;} + else {from = middle; fromX = middleX;} + } + } + function pageCoords(pos) { + var local = localCoords(pos, true), off = eltOffset(lineSpace); + return {x: off.left + local.x, y: off.top + local.y, yBot: off.top + local.yBot}; + } + + var cachedHeight, cachedHeightFor, measureText; + function textHeight() { + if (measureText == null) { + measureText = "
";
+        for (var i = 0; i < 49; ++i) measureText += "x
"; + measureText += "x
"; + } + var offsetHeight = lineDiv.clientHeight; + if (offsetHeight == cachedHeightFor) return cachedHeight; + cachedHeightFor = offsetHeight; + measure.innerHTML = measureText; + cachedHeight = measure.firstChild.offsetHeight / 50 || 1; + measure.innerHTML = ""; + return cachedHeight; + } + var cachedWidth, cachedWidthFor = 0; + function charWidth() { + if (scroller.clientWidth == cachedWidthFor) return cachedWidth; + cachedWidthFor = scroller.clientWidth; + return (cachedWidth = stringWidth("x")); + } + function paddingTop() {return lineSpace.offsetTop;} + function paddingLeft() {return lineSpace.offsetLeft;} + + function posFromMouse(e, liberal) { + var offW = eltOffset(scroller, true), x, y; + // Fails unpredictably on IE[67] when mouse is dragged around quickly. + try { x = e.clientX; y = e.clientY; } catch (e) { return null; } + // This is a mess of a heuristic to try and determine whether a + // scroll-bar was clicked or not, and to return null if one was + // (and !liberal). + if (!liberal && (x - offW.left > scroller.clientWidth || y - offW.top > scroller.clientHeight)) + return null; + var offL = eltOffset(lineSpace, true); + return coordsChar(x - offL.left, y - offL.top); + } + function onContextMenu(e) { + var pos = posFromMouse(e); + if (!pos || window.opera) return; // Opera is difficult. + if (posEq(sel.from, sel.to) || posLess(pos, sel.from) || !posLess(pos, sel.to)) + operation(setCursor)(pos.line, pos.ch); + + var oldCSS = input.style.cssText; + inputDiv.style.position = "absolute"; + input.style.cssText = "position: fixed; width: 30px; height: 30px; top: " + (e.clientY - 5) + + "px; left: " + (e.clientX - 5) + "px; z-index: 1000; background: white; " + + "border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);"; + leaveInputAlone = true; + var val = input.value = getSelection(); + focusInput(); + input.select(); + function rehide() { + var newVal = splitLines(input.value).join("\n"); + if (newVal != val) operation(replaceSelection)(newVal, "end"); + inputDiv.style.position = "relative"; + input.style.cssText = oldCSS; + leaveInputAlone = false; + resetInput(true); + slowPoll(); + } + + if (gecko) { + e_stop(e); + var mouseup = connect(window, "mouseup", function() { + mouseup(); + setTimeout(rehide, 20); + }, true); + } + else { + setTimeout(rehide, 50); + } + } + + // Cursor-blinking + function restartBlink() { + clearInterval(blinker); + var on = true; + cursor.style.visibility = ""; + blinker = setInterval(function() { + cursor.style.visibility = (on = !on) ? "" : "hidden"; + }, 650); + } + + var matching = {"(": ")>", ")": "(<", "[": "]>", "]": "[<", "{": "}>", "}": "{<"}; + function matchBrackets(autoclear) { + var head = sel.inverted ? sel.from : sel.to, line = getLine(head.line), pos = head.ch - 1; + var match = (pos >= 0 && matching[line.text.charAt(pos)]) || matching[line.text.charAt(++pos)]; + if (!match) return; + var ch = match.charAt(0), forward = match.charAt(1) == ">", d = forward ? 1 : -1, st = line.styles; + for (var off = pos + 1, i = 0, e = st.length; i < e; i+=2) + if ((off -= st[i].length) <= 0) {var style = st[i+1]; break;} + + var stack = [line.text.charAt(pos)], re = /[(){}[\]]/; + function scan(line, from, to) { + if (!line.text) return; + var st = line.styles, pos = forward ? 0 : line.text.length - 1, cur; + for (var i = forward ? 0 : st.length - 2, e = forward ? st.length : -2; i != e; i += 2*d) { + var text = st[i]; + if (st[i+1] != null && st[i+1] != style) {pos += d * text.length; continue;} + for (var j = forward ? 0 : text.length - 1, te = forward ? text.length : -1; j != te; j += d, pos+=d) { + if (pos >= from && pos < to && re.test(cur = text.charAt(j))) { + var match = matching[cur]; + if (match.charAt(1) == ">" == forward) stack.push(cur); + else if (stack.pop() != match.charAt(0)) return {pos: pos, match: false}; + else if (!stack.length) return {pos: pos, match: true}; + } + } + } + } + for (var i = head.line, e = forward ? Math.min(i + 100, doc.size) : Math.max(-1, i - 100); i != e; i+=d) { + var line = getLine(i), first = i == head.line; + var found = scan(line, first && forward ? pos + 1 : 0, first && !forward ? pos : line.text.length); + if (found) break; + } + if (!found) found = {pos: null, match: false}; + var style = found.match ? "CodeMirror-matchingbracket" : "CodeMirror-nonmatchingbracket"; + var one = markText({line: head.line, ch: pos}, {line: head.line, ch: pos+1}, style), + two = found.pos != null && markText({line: i, ch: found.pos}, {line: i, ch: found.pos + 1}, style); + var clear = operation(function(){one.clear(); two && two.clear();}); + if (autoclear) setTimeout(clear, 800); + else bracketHighlighted = clear; + } + + // 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(n) { + var minindent, minline; + for (var search = n, lim = n - 40; search > lim; --search) { + if (search == 0) return 0; + var line = getLine(search-1); + if (line.stateAfter) return search; + var indented = line.indentation(options.tabSize); + if (minline == null || minindent > indented) { + minline = search - 1; + minindent = indented; + } + } + return minline; + } + function getStateBefore(n) { + var start = findStartLine(n), state = start && getLine(start-1).stateAfter; + if (!state) state = startState(mode); + else state = copyState(mode, state); + doc.iter(start, n, function(line) { + line.highlight(mode, state, options.tabSize); + line.stateAfter = copyState(mode, state); + }); + if (start < n) changes.push({from: start, to: n}); + if (n < doc.size && !getLine(n).stateAfter) work.push(n); + return state; + } + function highlightLines(start, end) { + var state = getStateBefore(start); + doc.iter(start, end, function(line) { + line.highlight(mode, state, options.tabSize); + line.stateAfter = copyState(mode, state); + }); + } + function highlightWorker() { + var end = +new Date + options.workTime; + var foundWork = work.length; + while (work.length) { + if (!getLine(showingFrom).stateAfter) var task = showingFrom; + else var task = work.pop(); + if (task >= doc.size) continue; + var start = findStartLine(task), state = start && getLine(start-1).stateAfter; + if (state) state = copyState(mode, state); + else state = startState(mode); + + var unchanged = 0, compare = mode.compareStates, realChange = false, + i = start, bail = false; + doc.iter(i, doc.size, function(line) { + var hadState = line.stateAfter; + if (+new Date > end) { + work.push(i); + startWorker(options.workDelay); + if (realChange) changes.push({from: task, to: i + 1}); + return (bail = true); + } + var changed = line.highlight(mode, state, options.tabSize); + if (changed) realChange = true; + line.stateAfter = copyState(mode, state); + if (compare) { + if (hadState && compare(hadState, state)) return true; + } else { + if (changed !== false || !hadState) unchanged = 0; + else if (++unchanged > 3 && (!mode.indent || mode.indent(hadState, "") == mode.indent(state, ""))) + return true; + } + ++i; + }); + if (bail) return; + if (realChange) changes.push({from: task, to: i + 1}); + } + if (foundWork && options.onHighlightComplete) + options.onHighlightComplete(instance); + } + function startWorker(time) { + if (!work.length) return; + highlight.set(time, operation(highlightWorker)); + } + + // 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. + function startOperation() { + updateInput = userSelChange = textChanged = null; + changes = []; selectionChanged = false; callbacks = []; + } + function endOperation() { + var reScroll = false, updated; + if (selectionChanged) reScroll = !scrollCursorIntoView(); + if (changes.length) updated = updateDisplay(changes, true); + else { + if (selectionChanged) updateSelection(); + if (gutterDirty) updateGutter(); + } + if (reScroll) scrollCursorIntoView(); + if (selectionChanged) {scrollEditorIntoView(); restartBlink();} + + if (focused && !leaveInputAlone && + (updateInput === true || (updateInput !== false && selectionChanged))) + resetInput(userSelChange); + + if (selectionChanged && options.matchBrackets) + setTimeout(operation(function() { + if (bracketHighlighted) {bracketHighlighted(); bracketHighlighted = null;} + if (posEq(sel.from, sel.to)) matchBrackets(false); + }), 20); + var tc = textChanged, cbs = callbacks; // these can be reset by callbacks + if (selectionChanged && options.onCursorActivity) + options.onCursorActivity(instance); + if (tc && options.onChange && instance) + options.onChange(instance, tc); + for (var i = 0; i < cbs.length; ++i) cbs[i](instance); + if (updated && options.onUpdate) options.onUpdate(instance); + } + var nestedOperation = 0; + function operation(f) { + return function() { + if (!nestedOperation++) startOperation(); + try {var result = f.apply(this, arguments);} + finally {if (!--nestedOperation) endOperation();} + return result; + }; + } + + for (var ext in extensions) + if (extensions.propertyIsEnumerable(ext) && + !instance.propertyIsEnumerable(ext)) + instance[ext] = extensions[ext]; + return instance; + } // (end of function CodeMirror) + + // The default configuration options. + CodeMirror.defaults = { + value: "", + mode: null, + theme: "default", + indentUnit: 2, + indentWithTabs: false, + smartIndent: true, + tabSize: 4, + keyMap: "default", + extraKeys: null, + electricChars: true, + onKeyEvent: null, + lineWrapping: false, + lineNumbers: false, + gutter: false, + fixedGutter: false, + firstLineNumber: 1, + readOnly: false, + onChange: null, + onCursorActivity: null, + onGutterClick: null, + onHighlightComplete: null, + onUpdate: null, + onFocus: null, onBlur: null, onScroll: null, + matchBrackets: false, + workTime: 100, + workDelay: 200, + pollInterval: 100, + undoDepth: 40, + tabindex: null, + document: window.document + }; + + var ios = /AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent); + var mac = ios || /Mac/.test(navigator.platform); + var win = /Win/.test(navigator.platform); + + // Known modes, by name and by MIME + var modes = {}, mimeModes = {}; + CodeMirror.defineMode = function(name, mode) { + if (!CodeMirror.defaults.mode && name != "null") CodeMirror.defaults.mode = name; + modes[name] = mode; + }; + CodeMirror.defineMIME = function(mime, spec) { + mimeModes[mime] = spec; + }; + CodeMirror.getMode = function(options, spec) { + if (typeof spec == "string" && mimeModes.hasOwnProperty(spec)) + spec = mimeModes[spec]; + if (typeof spec == "string") + var mname = spec, config = {}; + else if (spec != null) + var mname = spec.name, config = spec; + var mfactory = modes[mname]; + if (!mfactory) { + if (window.console) console.warn("No mode " + mname + " found, falling back to plain text."); + return CodeMirror.getMode(options, "text/plain"); + } + return mfactory(options, config || {}); + }; + CodeMirror.listModes = function() { + var list = []; + for (var m in modes) + if (modes.propertyIsEnumerable(m)) list.push(m); + return list; + }; + CodeMirror.listMIMEs = function() { + var list = []; + for (var m in mimeModes) + if (mimeModes.propertyIsEnumerable(m)) list.push({mime: m, mode: mimeModes[m]}); + return list; + }; + + var extensions = CodeMirror.extensions = {}; + CodeMirror.defineExtension = function(name, func) { + extensions[name] = func; + }; + + var commands = CodeMirror.commands = { + selectAll: function(cm) {cm.setSelection({line: 0, ch: 0}, {line: cm.lineCount() - 1});}, + 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, {line: from.line + 1, ch: 0}); + else cm.replaceRange("", from, sel ? to : {line: from.line}); + }, + deleteLine: function(cm) {var l = cm.getCursor().line; cm.replaceRange("", {line: l, ch: 0}, {line: l});}, + undo: function(cm) {cm.undo();}, + redo: function(cm) {cm.redo();}, + goDocStart: function(cm) {cm.setCursor(0, 0, true);}, + goDocEnd: function(cm) {cm.setSelection({line: cm.lineCount() - 1}, null, true);}, + goLineStart: function(cm) {cm.setCursor(cm.getCursor().line, 0, true);}, + goLineStartSmart: function(cm) { + var cur = cm.getCursor(); + var text = cm.getLine(cur.line), firstNonWS = Math.max(0, text.search(/\S/)); + cm.setCursor(cur.line, cur.ch <= firstNonWS && cur.ch ? 0 : firstNonWS, true); + }, + goLineEnd: function(cm) {cm.setSelection({line: cm.getCursor().line}, null, true);}, + 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");}, + goWordRight: function(cm) {cm.moveH(1, "word");}, + delCharLeft: function(cm) {cm.deleteH(-1, "char");}, + delCharRight: function(cm) {cm.deleteH(1, "char");}, + delWordLeft: function(cm) {cm.deleteH(-1, "word");}, + delWordRight: function(cm) {cm.deleteH(1, "word");}, + 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");}, + 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), + {line: cur.line, ch: cur.ch - 1}, {line: cur.line, ch: cur.ch + 1}); + }, + newlineAndIndent: function(cm) { + cm.replaceSelection("\n", "end"); + cm.indentLine(cm.getCursor().line); + }, + toggleOverwrite: function(cm) {cm.toggleOverwrite();} + }; + + var keyMap = CodeMirror.keyMap = {}; + keyMap.basic = { + "Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goLineDown", + "End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageDown": "goPageDown", + "Delete": "delCharRight", "Backspace": "delCharLeft", "Tab": "indentMore", "Shift-Tab": "indentLess", + "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": "goWordLeft", "Ctrl-Right": "goWordRight", "Alt-Left": "goLineStart", "Alt-Right": "goLineEnd", + "Ctrl-Backspace": "delWordLeft", "Ctrl-Delete": "delWordRight", "Ctrl-S": "save", "Ctrl-F": "find", + "Ctrl-G": "findNext", "Shift-Ctrl-G": "findPrev", "Shift-Ctrl-F": "replace", "Shift-Ctrl-R": "replaceAll", + 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": "goWordLeft", + "Alt-Right": "goWordRight", "Cmd-Left": "goLineStart", "Cmd-Right": "goLineEnd", "Alt-Backspace": "delWordLeft", + "Ctrl-Alt-Backspace": "delWordRight", "Alt-Delete": "delWordRight", "Cmd-S": "save", "Cmd-F": "find", + "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift-Cmd-Alt-F": "replaceAll", + 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": "goPageUp", "Shift-Ctrl-V": "goPageDown", "Ctrl-D": "delCharRight", "Ctrl-H": "delCharLeft", + "Alt-D": "delWordRight", "Alt-Backspace": "delWordLeft", "Ctrl-K": "killLine", "Ctrl-T": "transposeChars" + }; + + function lookupKey(name, extraMap, map) { + function lookup(name, map, ft) { + var found = map[name]; + if (found != null) return found; + if (ft == null) ft = map.fallthrough; + if (ft == null) return map.catchall; + if (typeof ft == "string") return lookup(name, keyMap[ft]); + for (var i = 0, e = ft.length; i < e; ++i) { + found = lookup(name, keyMap[ft[i]]); + if (found != null) return found; + } + return null; + } + return extraMap ? lookup(name, extraMap, map) : lookup(name, keyMap[map]); + } + function isModifierKey(event) { + var name = keyNames[e_prop(event, "keyCode")]; + return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod"; + } + + CodeMirror.fromTextArea = function(textarea, options) { + if (!options) options = {}; + options.value = textarea.value; + if (!options.tabindex && textarea.tabindex) + options.tabindex = textarea.tabindex; + + function save() {textarea.value = instance.getValue();} + if (textarea.form) { + // Deplorable hack to make the submit method do the right thing. + var rmSubmit = connect(textarea.form, "submit", save, true); + if (typeof textarea.form.submit == "function") { + var realSubmit = textarea.form.submit; + function wrappedSubmit() { + save(); + textarea.form.submit = realSubmit; + textarea.form.submit(); + textarea.form.submit = wrappedSubmit; + } + textarea.form.submit = wrappedSubmit; + } + } + + textarea.style.display = "none"; + var instance = CodeMirror(function(node) { + textarea.parentNode.insertBefore(node, textarea.nextSibling); + }, options); + instance.save = save; + instance.getTextArea = function() { return textarea; }; + instance.toTextArea = function() { + save(); + textarea.parentNode.removeChild(instance.getWrapperElement()); + textarea.style.display = ""; + if (textarea.form) { + rmSubmit(); + if (typeof textarea.form.submit == "function") + textarea.form.submit = realSubmit; + } + }; + return instance; + }; + + // 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; + + // 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; + } + StringStream.prototype = { + eol: function() {return this.pos >= this.string.length;}, + sol: function() {return this.pos == 0;}, + peek: function() {return this.string.charAt(this.pos);}, + 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() {return countColumn(this.string, this.start, this.tabSize);}, + indentation: function() {return countColumn(this.string, null, this.tabSize);}, + match: function(pattern, consume, caseInsensitive) { + if (typeof pattern == "string") { + function cased(str) {return caseInsensitive ? str.toLowerCase() : str;} + if (cased(this.string).indexOf(cased(pattern), this.pos) == this.pos) { + if (consume !== false) this.pos += pattern.length; + return true; + } + } + else { + var match = this.string.slice(this.pos).match(pattern); + if (match && consume !== false) this.pos += match[0].length; + return match; + } + }, + current: function(){return this.string.slice(this.start, this.pos);} + }; + CodeMirror.StringStream = StringStream; + + function MarkedText(from, to, className, set) { + this.from = from; this.to = to; this.style = className; this.set = set; + } + MarkedText.prototype = { + attach: function(line) { this.set.push(line); }, + detach: function(line) { + var ix = indexOf(this.set, line); + if (ix > -1) this.set.splice(ix, 1); + }, + split: function(pos, lenBefore) { + if (this.to <= pos && this.to != null) return null; + var from = this.from < pos || this.from == null ? null : this.from - pos + lenBefore; + var to = this.to == null ? null : this.to - pos + lenBefore; + return new MarkedText(from, to, this.style, this.set); + }, + dup: function() { return new MarkedText(null, null, this.style, this.set); }, + clipTo: function(fromOpen, from, toOpen, to, diff) { + if (this.from != null && this.from >= from) + this.from = Math.max(to, this.from) + diff; + if (this.to != null && this.to > from) + this.to = to < this.to ? this.to + diff : from; + if (fromOpen && to > this.from && (to < this.to || this.to == null)) + this.from = null; + if (toOpen && (from < this.to || this.to == null) && (from > this.from || this.from == null)) + this.to = null; + }, + isDead: function() { return this.from != null && this.to != null && this.from >= this.to; }, + sameSet: function(x) { return this.set == x.set; } + }; + + function Bookmark(pos) { + this.from = pos; this.to = pos; this.line = null; + } + Bookmark.prototype = { + attach: function(line) { this.line = line; }, + detach: function(line) { if (this.line == line) this.line = null; }, + split: function(pos, lenBefore) { + if (pos < this.from) { + this.from = this.to = (this.from - pos) + lenBefore; + return this; + } + }, + isDead: function() { return this.from > this.to; }, + clipTo: function(fromOpen, from, toOpen, to, diff) { + if ((fromOpen || from < this.from) && (toOpen || to > this.to)) { + this.from = 0; this.to = -1; + } else if (this.from > from) { + this.from = this.to = Math.max(to, this.from) + diff; + } + }, + sameSet: function(x) { return false; }, + find: function() { + if (!this.line || !this.line.parent) return null; + return {line: lineNo(this.line), ch: this.from}; + }, + clear: function() { + if (this.line) { + var found = indexOf(this.line.marked, this); + if (found != -1) this.line.marked.splice(found, 1); + this.line = null; + } + } + }; + + // Line objects. These hold state related to a line, including + // highlighting info (the styles array). + function Line(text, styles) { + this.styles = styles || [text, null]; + this.text = text; + this.height = 1; + this.marked = this.gutterMarker = this.className = this.handlers = null; + this.stateAfter = this.parent = this.hidden = null; + } + Line.inheritMarks = function(text, orig) { + var ln = new Line(text), mk = orig && orig.marked; + if (mk) { + for (var i = 0; i < mk.length; ++i) { + if (mk[i].to == null && mk[i].style) { + var newmk = ln.marked || (ln.marked = []), mark = mk[i]; + var nmark = mark.dup(); newmk.push(nmark); nmark.attach(ln); + } + } + } + return ln; + } + Line.prototype = { + // Replace a piece of a line, keeping the styles around it intact. + replace: function(from, to_, text) { + var st = [], mk = this.marked, to = to_ == null ? this.text.length : to_; + copyStyles(0, from, this.styles, st); + if (text) st.push(text, null); + copyStyles(to, this.text.length, this.styles, st); + this.styles = st; + this.text = this.text.slice(0, from) + text + this.text.slice(to); + this.stateAfter = null; + if (mk) { + var diff = text.length - (to - from); + for (var i = 0; i < mk.length; ++i) { + var mark = mk[i]; + mark.clipTo(from == null, from || 0, to_ == null, to, diff); + if (mark.isDead()) {mark.detach(this); mk.splice(i--, 1);} + } + } + }, + // Split a part off a line, keeping styles and markers intact. + split: function(pos, textBefore) { + var st = [textBefore, null], mk = this.marked; + copyStyles(pos, this.text.length, this.styles, st); + var taken = new Line(textBefore + this.text.slice(pos), st); + if (mk) { + for (var i = 0; i < mk.length; ++i) { + var mark = mk[i]; + var newmark = mark.split(pos, textBefore.length); + if (newmark) { + if (!taken.marked) taken.marked = []; + taken.marked.push(newmark); newmark.attach(taken); + } + } + } + return taken; + }, + append: function(line) { + var mylen = this.text.length, mk = line.marked, mymk = this.marked; + this.text += line.text; + copyStyles(0, line.text.length, line.styles, this.styles); + if (mymk) { + for (var i = 0; i < mymk.length; ++i) + if (mymk[i].to == null) mymk[i].to = mylen; + } + if (mk && mk.length) { + if (!mymk) this.marked = mymk = []; + outer: for (var i = 0; i < mk.length; ++i) { + var mark = mk[i]; + if (!mark.from) { + for (var j = 0; j < mymk.length; ++j) { + var mymark = mymk[j]; + if (mymark.to == mylen && mymark.sameSet(mark)) { + mymark.to = mark.to == null ? null : mark.to + mylen; + if (mymark.isDead()) { + mymark.detach(this); + mk.splice(i--, 1); + } + continue outer; + } + } + } + mymk.push(mark); + mark.attach(this); + mark.from += mylen; + if (mark.to != null) mark.to += mylen; + } + } + }, + fixMarkEnds: function(other) { + var mk = this.marked, omk = other.marked; + if (!mk) return; + for (var i = 0; i < mk.length; ++i) { + var mark = mk[i], close = mark.to == null; + if (close && omk) { + for (var j = 0; j < omk.length; ++j) + if (omk[j].sameSet(mark)) {close = false; break;} + } + if (close) mark.to = this.text.length; + } + }, + fixMarkStarts: function() { + var mk = this.marked; + if (!mk) return; + for (var i = 0; i < mk.length; ++i) + if (mk[i].from == null) mk[i].from = 0; + }, + addMark: function(mark) { + mark.attach(this); + if (this.marked == null) this.marked = []; + this.marked.push(mark); + this.marked.sort(function(a, b){return (a.from || 0) - (b.from || 0);}); + }, + // Run the given mode's parser over a line, update the styles + // array, which contains alternating fragments of text and CSS + // classes. + highlight: function(mode, state, tabSize) { + var stream = new StringStream(this.text, tabSize), st = this.styles, pos = 0; + var changed = false, curWord = st[0], prevWord; + if (this.text == "" && mode.blankLine) mode.blankLine(state); + while (!stream.eol()) { + var style = mode.token(stream, state); + var substr = this.text.slice(stream.start, stream.pos); + stream.start = stream.pos; + if (pos && st[pos-1] == style) + st[pos-2] += substr; + else if (substr) { + if (!changed && (st[pos+1] != style || (pos && st[pos-2] != prevWord))) changed = true; + st[pos++] = substr; st[pos++] = style; + prevWord = curWord; curWord = st[pos]; + } + // Give up when line is ridiculously long + if (stream.pos > 5000) { + st[pos++] = this.text.slice(stream.pos); st[pos++] = null; + break; + } + } + if (st.length != pos) {st.length = pos; changed = true;} + if (pos && st[pos-2] != prevWord) changed = true; + // Short lines with simple highlights return null, and are + // counted as changed by the driver because they are likely to + // highlight the same way in various contexts. + return changed || (st.length < 5 && this.text.length < 10 ? null : false); + }, + // Fetch the parser token for a given character. Useful for hacks + // that want to inspect the mode state (say, for completion). + getTokenAt: function(mode, state, ch) { + var txt = this.text, stream = new StringStream(txt); + while (stream.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, + state: state}; + }, + indentation: function(tabSize) {return countColumn(this.text, null, tabSize);}, + // Produces an HTML fragment for the line, taking selection, + // marking, and highlighting into account. + getHTML: function(tabText, endAt) { + var html = [], first = true; + function span(text, style) { + if (!text) return; + // Work around a bug where, in some compat modes, IE ignores leading spaces + if (first && ie && text.charAt(0) == " ") text = "\u00a0" + text.slice(1); + first = false; + if (style) html.push('', htmlEscape(text).replace(/\t/g, tabText), ""); + else html.push(htmlEscape(text).replace(/\t/g, tabText)); + } + var st = this.styles, allText = this.text, marked = this.marked; + var len = allText.length; + if (endAt != null) len = Math.min(endAt, len); + function styleToClass(style) { + if (!style) return null; + return "cm-" + style.replace(/ +/g, " cm-"); + } + + if (!allText && endAt == null) + span(" "); + else if (!marked || !marked.length) + for (var i = 0, ch = 0; ch < len; i+=2) { + var str = st[i], style = st[i+1], l = str.length; + if (ch + l > len) str = str.slice(0, len - ch); + ch += l; + span(str, styleToClass(style)); + } + else { + var pos = 0, i = 0, text = "", style, sg = 0; + var nextChange = marked[0].from || 0, marks = [], markpos = 0; + function advanceMarks() { + var m; + while (markpos < marked.length && + ((m = marked[markpos]).from == pos || m.from == null)) { + if (m.style != null) marks.push(m); + ++markpos; + } + nextChange = markpos < marked.length ? marked[markpos].from : Infinity; + for (var i = 0; i < marks.length; ++i) { + var to = marks[i].to || Infinity; + if (to == pos) marks.splice(i--, 1); + else nextChange = Math.min(to, nextChange); + } + } + var m = 0; + while (pos < len) { + if (nextChange == pos) advanceMarks(); + var upto = Math.min(len, nextChange); + while (true) { + if (text) { + var end = pos + text.length; + var appliedStyle = style; + for (var j = 0; j < marks.length; ++j) + appliedStyle = (appliedStyle ? appliedStyle + " " : "") + marks[j].style; + span(end > upto ? text.slice(0, upto - pos) : text, appliedStyle); + if (end >= upto) {text = text.slice(upto - pos); pos = upto; break;} + pos = end; + } + text = st[i++]; style = styleToClass(st[i++]); + } + } + } + return html.join(""); + }, + cleanUp: function() { + this.parent = null; + if (this.marked) + for (var i = 0, e = this.marked.length; i < e; ++i) this.marked[i].detach(this); + } + }; + // Utility used by replace and split above + function copyStyles(from, to, source, dest) { + for (var i = 0, pos = 0, state = 0; pos < to; i+=2) { + var part = source[i], end = pos + part.length; + if (state == 0) { + if (end > from) dest.push(part.slice(from - pos, Math.min(part.length, to - pos)), source[i+1]); + if (end >= from) state = 1; + } + else if (state == 1) { + if (end > to) dest.push(part.slice(0, to - pos), source[i+1]); + else dest.push(part, source[i+1]); + } + pos = end; + } + } + + // Data structure that holds the sequence of lines. + 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; }, + remove: function(at, n, callbacks) { + for (var i = at, e = at + n; i < e; ++i) { + var line = this.lines[i]; + this.height -= line.height; + line.cleanUp(); + if (line.handlers) + for (var j = 0; j < line.handlers.length; ++j) callbacks.push(line.handlers[j]); + } + this.lines.splice(at, n); + }, + collapse: function(lines) { + lines.splice.apply(lines, [lines.length, 0].concat(this.lines)); + }, + insertHeight: function(at, lines, height) { + this.height += height; + this.lines.splice.apply(this.lines, [at, 0].concat(lines)); + 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; }, + remove: function(at, n, callbacks) { + 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.remove(at, rm, callbacks); + 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); + }, + insert: function(at, lines) { + var height = 0; + for (var i = 0, e = lines.length; i < e; ++i) height += lines[i].height; + this.insertHeight(at, lines, height); + }, + insertHeight: 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.insertHeight(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(); + }, + iter: function(from, to, op) { this.iterN(from, to - from, op); }, + 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; + } + } + }; + + function getLineAt(chunk, n) { + 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 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, e = chunk.children.length; ; ++i) { + if (chunk.children[i] == cur) break; + no += chunk.children[i].chunkSize(); + } + } + return no; + } + function lineAtHeight(chunk, h) { + var n = 0; + 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(chunk, n) { + var h = 0; + outer: do { + for (var i = 0, e = chunk.children.length; i < e; ++i) { + var child = chunk.children[i], sz = child.chunkSize(); + if (n < sz) { chunk = child; continue outer; } + n -= sz; + h += child.height; + } + return h; + } while (!chunk.lines); + for (var i = 0; i < n; ++i) h += chunk.lines[i].height; + return h; + } + + // The history object 'chunks' changes that are made close together + // and at almost the same time into bigger undoable units. + function History() { + this.time = 0; + this.done = []; this.undone = []; + } + History.prototype = { + addChange: function(start, added, old) { + this.undone.length = 0; + var time = +new Date, cur = this.done[this.done.length - 1], last = cur && cur[cur.length - 1]; + var dtime = time - this.time; + if (dtime > 400 || !last) { + this.done.push([{start: start, added: added, old: old}]); + } else if (last.start > start + added || last.start + last.added < start - last.added + last.old.length) { + cur.push({start: start, added: added, old: old}); + } else { + var oldoff = 0; + if (start < last.start) { + for (var i = last.start - start - 1; i >= 0; --i) + last.old.unshift(old[i]); + last.added += last.start - start; + last.start = start; + } + else if (last.start < start) { + oldoff = start - last.start; + added += oldoff; + } + for (var i = last.added - oldoff, e = old.length; i < e; ++i) + last.old.push(old[i]); + if (last.added < added) last.added = added; + } + this.time = time; + } + }; + + 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_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) { + if (e.which) return e.which; + else if (e.button & 1) return 1; + else if (e.button & 2) return 3; + else if (e.button & 4) return 2; + } + + // Allow 3rd-party code to override event properties by adding an override + // object to an event object. + function e_prop(e, prop) { + var overridden = e.override && e.override.hasOwnProperty(prop); + return overridden ? e.override[prop] : e[prop]; + } + + // Event handler registration. If disconnect is true, it'll return a + // function that unregisters the handler. + function connect(node, type, handler, disconnect) { + if (typeof node.addEventListener == "function") { + node.addEventListener(type, handler, false); + if (disconnect) return function() {node.removeEventListener(type, handler, false);}; + } + else { + var wrapHandler = function(event) {handler(event || window.event);}; + node.attachEvent("on" + type, wrapHandler); + if (disconnect) return function() {node.detachEvent("on" + type, wrapHandler);}; + } + } + CodeMirror.connect = connect; + + function Delayed() {this.id = null;} + Delayed.prototype = {set: function(ms, f) {clearTimeout(this.id); this.id = setTimeout(f, ms);}}; + + // Detect drag-and-drop + var dragAndDrop = function() { + // IE8 has ondragstart and ondrop properties, but doesn't seem to + // actually support ondragstart the way it's supposed to work. + if (/MSIE [1-8]\b/.test(navigator.userAgent)) return false; + var div = document.createElement('div'); + return "draggable" in div; + }(); + + var gecko = /gecko\/\d{7}/i.test(navigator.userAgent); + var ie = /MSIE \d/.test(navigator.userAgent); + var webkit = /WebKit\//.test(navigator.userAgent); + + var lineSep = "\n"; + // Feature-detect whether newlines in textareas are converted to \r\n + (function () { + var te = document.createElement("textarea"); + te.value = "foo\nbar"; + if (te.value.indexOf("\r") > -1) lineSep = "\r\n"; + }()); + + // Counts the column offset in a string, taking tabs into account. + // Used mostly to find indentation. + function countColumn(string, end, tabSize) { + if (end == null) { + end = string.search(/[^\s\u00a0]/); + if (end == -1) end = string.length; + } + for (var i = 0, n = 0; i < end; ++i) { + if (string.charAt(i) == "\t") n += tabSize - (n % tabSize); + else ++n; + } + return n; + } + + function computedStyle(elt) { + if (elt.currentStyle) return elt.currentStyle; + return window.getComputedStyle(elt, null); + } + + // Find the position of an element by following the offsetParent chain. + // If screen==true, it returns screen (rather than page) coordinates. + function eltOffset(node, screen) { + var bod = node.ownerDocument.body; + var x = 0, y = 0, skipBody = false; + for (var n = node; n; n = n.offsetParent) { + var ol = n.offsetLeft, ot = n.offsetTop; + // Firefox reports weird inverted offsets when the body has a border. + if (n == bod) { x += Math.abs(ol); y += Math.abs(ot); } + else { x += ol, y += ot; } + if (screen && computedStyle(n).position == "fixed") + skipBody = true; + } + var e = screen && !skipBody ? null : bod; + for (var n = node.parentNode; n != e; n = n.parentNode) + if (n.scrollLeft != null) { x -= n.scrollLeft; y -= n.scrollTop;} + return {left: x, top: y}; + } + // Use the faster and saner getBoundingClientRect method when possible. + if (document.documentElement.getBoundingClientRect != null) eltOffset = function(node, screen) { + // Take the parts of bounding client rect that we are interested in so we are able to edit if need be, + // since the returned value cannot be changed externally (they are kept in sync as the element moves within the page) + try { var box = node.getBoundingClientRect(); box = { top: box.top, left: box.left }; } + catch(e) { box = {top: 0, left: 0}; } + if (!screen) { + // Get the toplevel scroll, working around browser differences. + if (window.pageYOffset == null) { + var t = document.documentElement || document.body.parentNode; + if (t.scrollTop == null) t = document.body; + box.top += t.scrollTop; box.left += t.scrollLeft; + } else { + box.top += window.pageYOffset; box.left += window.pageXOffset; + } + } + return box; + }; + + // Get a node's text content. + function eltText(node) { + return node.textContent || node.innerText || node.nodeValue || ""; + } + function selectInput(node) { + if (ios) { // Mobile Safari apparently has a bug where select() is broken. + node.selectionStart = 0; + node.selectionEnd = node.value.length; + } else node.select(); + } + + // Operations on {line, ch} objects. + 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 {line: x.line, ch: x.ch};} + + var escapeElement = document.createElement("pre"); + function htmlEscape(str) { + escapeElement.textContent = str; + return escapeElement.innerHTML; + } + // Recent (late 2011) Opera betas insert bogus newlines at the start + // of the textContent, so we strip those. + if (htmlEscape("a") == "\na") + htmlEscape = function(str) { + escapeElement.textContent = str; + return escapeElement.innerHTML.slice(1); + }; + // Some IEs don't preserve tabs through innerHTML + else if (htmlEscape("\t") != "\t") + htmlEscape = function(str) { + escapeElement.innerHTML = ""; + escapeElement.appendChild(document.createTextNode(str)); + return escapeElement.innerHTML; + }; + CodeMirror.htmlEscape = htmlEscape; + + // Used to position the cursor after an undo/redo by finding the + // last edited character. + function editEnd(from, to) { + if (!to) return 0; + if (!from) return to.length; + for (var i = from.length, j = to.length; i >= 0 && j >= 0; --i, --j) + if (from.charAt(i) != to.charAt(j)) break; + return j + 1; + } + + 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 isWordChar(ch) { + return /\w/.test(ch) || ch.toUpperCase() != ch.toLowerCase(); + } + + // 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, nl, result = []; + while ((nl = string.indexOf("\n", pos)) > -1) { + result.push(string.slice(pos, string.charAt(nl-1) == "\r" ? nl - 1 : nl)); + pos = nl + 1; + } + result.push(string.slice(pos)); + return result; + } : function(string){return string.split(/\r?\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; + }; + + CodeMirror.defineMode("null", function() { + return {token: function(stream) {stream.skipToEnd();}}; + }); + CodeMirror.defineMIME("text/plain", "null"); + + 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", 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; + })(); + + return CodeMirror; +})(); diff --git a/lib/codemirror.min.js b/lib/codemirror.min.js new file mode 100644 index 0000000..37c06a2 --- /dev/null +++ b/lib/codemirror.min.js @@ -0,0 +1 @@ +var CodeMirror=function(){function a(d,e){function bS(a){return a>=0&&a=c.to||b.linee-400&&T(bv.pos,d))return B(a),setTimeout(cx,20),cU(d.line);if(bu&&bu.time>e-400&&T(bu.pos,d))return bv={time:e,pos:d},B(a),cT(d);bu={time:e,pos:d};var g=d,h;if(J&&!f.readOnly&&!T(bs.from,bs.to)&&!U(d,bs.from)&&!U(bs.to,d)){M&&(bf.draggable=!0);var i=H(z,"mouseup",dO(function(b){M&&(bf.draggable=!1),bx=!1,i(),Math.abs(a.clientX-b.clientX)+Math.abs(a.clientY-b.clientY)<10&&(B(b),cL(d.line,d.ch,!0),cx())}),!0);bx=!0;return}B(a),cL(d.line,d.ch,!0);var k=H(z,"mousemove",dO(function(a){clearTimeout(h),B(a),j(a)}),!0),i=H(z,"mouseup",dO(function(a){clearTimeout(h);var b=dB(a);b&&cI(d,b),B(a),cx(),bA=!0,k(),i()}),!0)}function bZ(a){for(var b=E(a);b!=C;b=b.parentNode)if(b.parentNode==be)return B(a);var c=dB(a);if(!c)return;bv={time:+(new Date),pos:c},B(a),cT(c)}function b$(a){a.preventDefault();var b=dB(a,!0),c=a.dataTransfer.files;if(!b||f.readOnly)return;if(c&&c.length&&window.FileReader&&window.File){function d(a,c){var d=new FileReader;d.onload=function(){g[c]=d.result,++h==e&&(b=cN(b),dO(function(){var a=cm(g.join(""),b,b);cI(b,a)})())},d.readAsText(a)}var e=c.length,g=Array(e),h=0;for(var i=0;i-1&&setTimeout(dO(function(){cW(bs.to.line,"smart")}),75)}ct()}function ce(a){if(f.onKeyEvent&&f.onKeyEvent(bT,A(a)))return;G(a,"keyCode")==16&&(bt=null)}function cf(){if(f.readOnly=="nocursor")return;br||(f.onFocus&&f.onFocus(bT),br=!0,C.className.search(/\bCodeMirror-focused\b/)==-1&&(C.className+=" CodeMirror-focused"),bF||cw(!0)),cs(),dD()}function cg(){br&&(f.onBlur&&f.onBlur(bT),br=!1,bM&&dO(function(){bM&&(bM(),bM=null)})(),C.className=C.className.replace(" CodeMirror-focused","")),clearInterval(bn),setTimeout(function(){br||(bt=null)},150)}function ch(a,b,c,d,e){if(bz)return;if(bQ){var g=[];bp.iter(a.line,b.line+1,function(a){g.push(a.text)}),bQ.addChange(a.line,c.length,g);while(bQ.done.length>f.undoDepth)bQ.done.shift()}cl(a,b,c,d,e)}function ci(a,b,c){var d=a.pop(),e=d?d.length:0,f=[];for(var g=c>0?0:e-1,h=c>0?e:-1;g!=h;g+=c){var i=d[g],j=[],k=i.start+i.added;bp.iter(i.start,k,function(a){j.push(a.text)}),f.push({start:i.start,added:i.old.length,old:j});var l=cN({line:i.start+i.old.length-1,ch:Y(j[j.length-1],i.old[i.old.length-1])});cl({line:i.start,ch:0},{line:k-1,ch:bU(k-1).text.length},i.old,l,l)}bA=!0,b.push(f)}function cj(){ci(bQ.done,bQ.undone,-1)}function ck(){ci(bQ.undone,bQ.done,1)}function cl(a,b,c,d,e){function y(a){return a<=Math.min(b.line,b.line+s)?a:a+s}if(bz)return;var g=!1,h=bN.length;f.lineWrapping||bp.iter(a.line,b.line,function(a){if(a.text.length==h)return g=!0,!0});if(a.line!=b.line||c.length>1)bG=!0;var i=b.line-a.line,j=bU(a.line),k=bU(b.line);if(a.ch==0&&b.ch==0&&c[c.length-1]==""){var l=[],m=null;a.line?(m=bU(a.line-1),m.fixMarkEnds(k)):k.fixMarkStarts();for(var n=0,o=c.length-1;n1&&bp.remove(a.line+1,i-1,bH),bp.insert(a.line+1,l)}if(f.lineWrapping){var p=P.clientWidth/dy()-3;bp.iter(a.line,a.line+c.length,function(a){if(a.hidden)return;var b=Math.ceil(a.text.length/p)||1;b!=a.height&&bV(a,b)})}else bp.iter(a.line,n+c.length,function(a){var b=a.text;b.length>h&&(bN=b,h=b.length,bO=null,g=!1)}),g&&(h=0,bN="",bO=null,bp.iter(0,bp.size,function(a){var b=a.text;b.length>h&&(h=b.length,bN=b)}));var r=[],s=c.length-i-1;for(var n=0,t=bq.length;nb.line&&r.push(u+s)}var v=a.line+Math.min(c.length,500);dI(a.line,v),r.push(v),bq=r,dK(100),bC.push({from:a.line,to:b.line+1,diff:s});var w={from:a,to:b,text:c};if(bD){for(var x=bD;x.next;x=x.next);x.next=w}else bD=w;cJ(d,e,y(bs.from.line),y(bs.to.line)),P.clientHeight&&(S.style.height=bp.height*dv()+2*dz()+"px")}function cm(a,b,c){function d(d){if(U(d,b))return d;if(!U(c,d))return e;var f=d.line+a.length-(c.line-b.line)-1,g=d.ch;return d.line==c.line&&(g+=a[a.length-1].length-(c.ch-(c.line==b.line?b.ch:0))),{line:f,ch:g}}b=cN(b),c?c=cN(c):c=b,a=_(a);var e;return co(a,b,c,function(a){return e=a,{from:d(bs.from),to:d(bs.to)}}),e}function cn(a,b){co(_(a),bs.from,bs.to,function(a){return b=="end"?{from:a,to:a}:b=="start"?{from:bs.from,to:bs.from}:{from:bs.from,to:a}})}function co(a,b,c,d){var e=a.length==1?a[0].length+b.ch:a[a.length-1].length,f=d({line:b.line+a.length-1,ch:e});ch(b,c,a,f.from,f.to)}function cp(a,b){var c=a.line,d=b.line;if(c==d)return bU(c).text.slice(a.ch,b.ch);var e=[bU(c).text.slice(a.ch)];return bp.iter(c+1,d,function(a){e.push(a.text)}),e.push(bU(d).text.slice(0,b.ch)),e.join("\n")}function cq(){return cp(bs.from,bs.to)}function cs(){if(cr)return;bl.set(f.pollInterval,function(){dL(),cv(),br&&cs(),dM()})}function ct(){function b(){dL();var c=cv();!c&&!a?(a=!0,bl.set(60,b)):(cr=!1,cs()),dM()}var a=!1;cr=!0,bl.set(20,b)}function cv(){if(bF||!br||ba(O)||f.readOnly)return!1;var a=O.value;if(a==cu)return!1;bt=null;var b=0,c=Math.min(cu.length,a.length);while(bb)&&bh.scrollIntoView()}function cz(){var a=dp(bs.inverted?bs.from:bs.to),b=f.lineWrapping?Math.min(a.x,bf.offsetWidth):a.x;return cA(b,a.y,b,a.yBot)}function cA(a,b,c,d){var e=dA(),g=dz(),h=dv();b+=g,d+=g,a+=e,c+=e;var i=P.clientHeight,j=P.scrollTop,k=!1,l=!0;bj+i&&(P.scrollTop=d+h-i,k=!0);var m=P.clientWidth,n=P.scrollLeft,o=f.fixedGutter?bd.clientWidth:0;return am+n-3&&(P.scrollLeft=c+10-m,k=!0,c>S.clientWidth&&(l=!1)),k&&f.onScroll&&f.onScroll(bT),l}function cB(){var a=dv(),b=P.scrollTop-dz(),c=Math.max(0,Math.floor(b/a)),d=Math.ceil((b+P.clientHeight)/a);return{from:w(bp,c),to:w(bp,d)}}function cC(a,b){if(!P.clientWidth){bJ=bK=bI=0;return}var c=cB();if(a!==!0&&a.length==0&&c.from>bJ&&c.toe&&bK-e<20&&(e=Math.min(bp.size,bK));var g=a===!0?[]:cD([{from:bJ,to:bK,domStart:0}],a),h=0;for(var i=0;ie&&(j.to=e),j.from>=j.to?g.splice(i--,1):h+=j.to-j.from}if(h==e-d)return;g.sort(function(a,b){return a.domStart-b.domStart});var k=dv(),l=bd.style.display;bj.style.display="none",cE(d,e,g),bj.style.display=bd.style.display="";var m=d!=bJ||e!=bK||bL!=P.clientHeight+k;m&&(bL=P.clientHeight+k),bJ=d,bK=e,bI=x(bp,d),bc.style.top=bI*k+"px",P.clientHeight&&(S.style.height=bp.height*k+2*dz()+"px");if(bj.childNodes.length!=bK-bJ)throw new Error("BAD PATCH! "+JSON.stringify(g)+" size="+(bK-bJ)+" nodes="+bj.childNodes.length);if(f.lineWrapping){bO=P.clientWidth;var n=bj.firstChild,o=!1;bp.iter(bJ,bK,function(a){if(!a.hidden){var b=Math.round(n.offsetHeight/k)||1;a.height!=b&&(bV(a,b),bG=o=!0)}n=n.nextSibling}),o&&(S.style.height=bp.height*k+2*dz()+"px")}else bO==null&&(bO=dk(bN)),bO>P.clientWidth?(bf.style.width=bO+"px",S.style.width="",S.style.width=P.scrollWidth+"px"):bf.style.width=S.style.width="";return bd.style.display=l,(m||bG)&&cF(),cG(),!b&&f.onUpdate&&f.onUpdate(bT),!0}function cD(a,b){for(var c=0,d=b.length||0;c=j.to?f.push(j):(e.from>j.from&&f.push({from:j.from,to:e.from,domStart:j.domStart}),e.toe)f=d(f),e++;for(var j=0,k=i.to-i.from;jj){if(a.hidden)var b=m.innerHTML="
";else{var b="
"+a.getHTML(bP)+"
";a.className&&(b='
 
'+b+"
")}m.innerHTML=b,bj.insertBefore(m.firstChild,f)}else f=f.nextSibling;++j})}function cF(){if(!f.gutter&&!f.lineNumbers)return;var a=bc.offsetHeight,b=P.clientHeight;bd.style.height=(a-b<2?b:a)+"px";var c=[],d=bJ;bp.iter(bJ,Math.max(bK,bJ+1),function(a){if(a.hidden)c.push("
");else{var b=a.gutterMarker,e=f.lineNumbers?d+f.firstLineNumber:null;b&&b.text?e=b.text.replace("%N%",e!=null?e:""):e==null&&(e="\u00a0"),c.push(b&&b.style?'
':"
",e);for(var g=1;g ");c.push("
")}++d}),bd.style.display="none",be.innerHTML=c.join("");var e=String(bp.size).length,g=be.firstChild,h=R(g),i="";while(h.length+i.length
'}if(bs.from.ch&&b.y>=0){var l=i?bf.clientWidth-c.x:0;k(b.x,b.y,l,e)}var m=Math.max(0,b.y+(bs.from.ch?e:0)),n=Math.min(c.y,bf.clientHeight)-m;n>.2*e&&k(0,m,0,n),(!i||!bs.from.ch)&&c.yc||g>f.text.length)g=f.text.length;return{line:d,ch:g}}d+=b}}var e=bU(a.line);return e.hidden?a.line>=b?d(1)||d(-1):d(-1)||d(1):a}function cL(a,b,c){var d=cN({line:a,ch:b||0});(c?cI:cJ)(d,d)}function cM(a){return Math.max(0,Math.min(a,bp.size-1))}function cN(a){if(a.line<0)return{line:0,ch:0};if(a.line>=bp.size)return{line:bp.size-1,ch:bU(bp.size-1).text.length};var b=a.ch,c=bU(a.line).text.length;return b==null||b>c?{line:a.line,ch:c}:b<0?{line:a.line,ch:0}:a}function cO(a,b){function g(){for(var b=d+a,c=a<0?-1:bp.size;b!=c;b+=a){var e=bU(b);if(!e.hidden)return d=b,f=e,!0}}function h(b){if(e==(a<0?0:f.text.length))if(!b&&g())e=a<0?f.text.length:0;else return!1;else e+=a;return!0}var c=bs.inverted?bs.from:bs.to,d=c.line,e=c.ch,f=bU(d);if(b=="char")h();else if(b=="column")h(!0);else if(b=="word"){var i=!1;for(;;){if(a<0&&!h())break;if($(f.text.charAt(e)))i=!0;else if(i){a<0&&(a=1,h());break}if(a>0&&!h())break}}return{line:d,ch:e}}function cP(a,b){var c=a<0?bs.from:bs.to;if(bt||T(bs.from,bs.to))c=cO(a,b);cL(c.line,c.ch,!0)}function cQ(a,b){T(bs.from,bs.to)?a<0?cm("",cO(a,b),bs.to):cm("",bs.from,cO(a,b)):cm("",bs.from,bs.to),bB=!0}function cS(a,b){var c=0,d=dp(bs.inverted?bs.from:bs.to,!0);cR!=null&&(d.x=cR),b=="page"?c=Math.min(P.clientHeight,window.innerHeight||document.documentElement.clientHeight):b=="line"&&(c=dv());var e=dq(d.x,d.y+c*a+2);cL(e.line,e.ch,!0),cR=d.x}function cT(a){var b=bU(a.line).text,c=a.ch,d=a.ch;while(c>0&&$(b.charAt(c-1)))--c;while(dbN.length&&(bN=a.text)});bC.push({from:0,to:bp.size})}function c$(){for(var a='',b=0;b"}function c_(){bP=c$(),cC(!0)}function da(){P.className=P.className.replace(/\s*cm-s-\w+/g,"")+f.theme.replace(/(^|\s)\s*/g," cm-s-")}function db(){this.set=[]}function dc(a,b,c){function e(a,b,c,e){bU(a).addMark(new o(b,c,e,d.set))}a=cN(a),b=cN(b);var d=new db;if(a.line==b.line)e(a.line,a.ch,b.ch,c);else{e(a.line,a.ch,null,c);for(var f=a.line+1,g=b.line;f",bg.firstChild.firstChild.offsetWidth}if(b<=0)return 0;var c=bU(a),d=c.text,f=0,g=0,h=d.length,i,j=Math.min(h,Math.ceil(b/dy()));for(;;){var k=e(j);if(k<=b&&ji)return h;j=Math.floor(h*.8),k=e(j),kb-g?f:h;var l=Math.ceil((f+h)/2),m=e(l);m>b?(h=l,i=m):(f=l,g=m)}}function dn(a,b){if(b==0)return{top:0,left:0};var c="";if(f.lineWrapping){var d=a.text.indexOf(" ",b+2);c=X(a.text.slice(b+1,d<0?a.text.length:d+(L?5:0)))}bg.innerHTML="
"+a.getHTML(bP,b)+''+X(a.text.charAt(b)||" ")+""+c+"
";var e=document.getElementById("CodeMirror-temp-"+dm),g=e.offsetTop,h=e.offsetLeft;if(L&&g==0&&h==0){var i=document.createElement("span");i.innerHTML="x",e.parentNode.insertBefore(i,e.nextSibling),g=i.offsetTop}return{top:g,left:h}}function dp(a,b){var c,d=dv(),e=d*(x(bp,a.line)-(b?bI:0));if(a.ch==0)c=0;else{var g=dn(bU(a.line),a.ch);c=g.left,f.lineWrapping&&(e+=Math.max(0,g.top))}return{x:c,y:e,yBot:e+d}}function dq(a,b){function l(a){var b=dn(h,a);if(j){var d=Math.round(b.top/c);return Math.max(0,b.left+(d-k)*P.clientWidth)}return b.left}b<0&&(b=0);var c=dv(),d=dy(),e=bI+Math.floor(b/c),g=w(bp,e);if(g>=bp.size)return{line:bp.size-1,ch:bU(bp.size-1).text.length};var h=bU(g),i=h.text,j=f.lineWrapping,k=j?e-x(bp,g):0;if(a<=0&&k==0)return{line:g,ch:0};var m=0,n=0,o=i.length,p,q=Math.min(o,Math.ceil((a+k*P.clientWidth*.9)/d));for(;;){var r=l(q);if(r<=a&&qp)return{line:g,ch:o};q=Math.floor(o*.8),r=l(q),ra-n?m:o};var s=Math.ceil((m+o)/2),t=l(s);t>a?(o=s,p=t):(m=s,n=t)}}function dr(a){var b=dp(a,!0),c=Q(bf);return{x:c.left+b.x,y:c.top+b.y,yBot:c.top+b.yBot}}function dv(){if(du==null){du="
";for(var a=0;a<49;++a)du+="x
";du+="x
"}var b=bj.clientHeight;return b==dt?ds:(dt=b,bg.innerHTML=du,ds=bg.firstChild.offsetHeight/50||1,bg.innerHTML="",ds)}function dy(){return P.clientWidth==dx?dw:(dx=P.clientWidth,dw=dk("x"))}function dz(){return bf.offsetTop}function dA(){return bf.offsetLeft}function dB(a,b){var c=Q(P,!0),d,e;try{d=a.clientX,e=a.clientY}catch(a){return null}if(!b&&(d-c.left>P.clientWidth||e-c.top>P.clientHeight))return null;var f=Q(bf,!0);return dq(d-f.left,e-f.top)}function dC(a){function e(){var a=_(O.value).join("\n");a!=d&&dO(cn)(a,"end"),N.style.position="relative",O.style.cssText=c,bF=!1,cw(!0),cs()}var b=dB(a);if(!b||window.opera)return;(T(bs.from,bs.to)||U(b,bs.from)||!U(b,bs.to))&&dO(cL)(b.line,b.ch);var c=O.style.cssText;N.style.position="absolute",O.style.cssText="position: fixed; width: 30px; height: 30px; top: "+(a.clientY-5)+"px; left: "+(a.clientX-5)+"px; z-index: 1000; background: white; "+"border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);",bF=!0;var d=O.value=cq();cx(),O.select();if(K){D(a);var f=H(window,"mouseup",function(){f(),setTimeout(e,20)},!0)}else setTimeout(e,50)}function dD(){clearInterval(bn);var a=!0;bh.style.visibility="",bn=setInterval(function(){bh.style.visibility=(a=!a)?"":"hidden"},650)}function dF(a){function p(a,b,c){if(!a.text)return;var d=a.styles,e=g?0:a.text.length-1,f;for(var i=g?0:d.length-2,j=g?d.length:-2;i!=j;i+=2*h){var k=d[i];if(d[i+1]!=null&&d[i+1]!=m){e+=h*k.length;continue}for(var l=g?0:k.length-1,p=g?k.length:-1;l!=p;l+=h,e+=h)if(e>=b&&e"==g)n.push(f);else{if(n.pop()!=q.charAt(0))return{pos:e,match:!1};if(!n.length)return{pos:e,match:!0}}}}}var b=bs.inverted?bs.from:bs.to,c=bU(b.line),d=b.ch-1,e=d>=0&&dE[c.text.charAt(d)]||dE[c.text.charAt(++d)];if(!e)return;var f=e.charAt(0),g=e.charAt(1)==">",h=g?1:-1,i=c.styles;for(var j=d+1,k=0,l=i.length;ke;--d){if(d==0)return 0;var g=bU(d-1);if(g.stateAfter)return d;var h=g.indentation(f.tabSize);if(c==null||b>h)c=d-1,b=h}return c}function dH(a){var b=dG(a),c=b&&bU(b-1).stateAfter;return c?c=l(bo,c):c=m(bo),bp.iter(b,a,function(a){a.highlight(bo,c,f.tabSize),a.stateAfter=l(bo,c)}),b=bp.size)continue;var d=dG(c),e=d&&bU(d-1).stateAfter;e?e=l(bo,e):e=m(bo);var g=0,h=bo.compareStates,i=!1,j=d,k=!1;bp.iter(j,bp.size,function(b){var d=b.stateAfter;if(+(new Date)>a)return bq.push(j),dK(f.workDelay),i&&bC.push({from:c,to:j+1}),k=!0;var m=b.highlight(bo,e,f.tabSize);m&&(i=!0),b.stateAfter=l(bo,e);if(h){if(d&&h(d,e))return!0}else if(m!==!1||!d)g=0;else if(++g>3&&(!bo.indent||bo.indent(d,"")==bo.indent(e,"")))return!0;++j});if(k)return;i&&bC.push({from:c,to:j+1})}b&&f.onHighlightComplete&&f.onHighlightComplete(bT)}function dK(a){if(!bq.length)return;bm.set(a,dO(dJ))}function dL(){bA=bB=bD=null,bC=[],bE=!1,bH=[]}function dM(){var a=!1,b;bE&&(a=!cz()),bC.length?b=cC(bC,!0):(bE&&cG(),bG&&cF()),a&&cz(),bE&&(cy(),dD()),br&&!bF&&(bA===!0||bA!==!1&&bE)&&cw(bB),bE&&f.matchBrackets&&setTimeout(dO(function(){bM&&(bM(),bM=null),T(bs.from,bs.to)&&dF(!1)}),20);var c=bD,d=bH;bE&&f.onCursorActivity&&f.onCursorActivity(bT),c&&f.onChange&&bT&&f.onChange(bT,c);for(var e=0;eh&&a.y>b.offsetHeight&&(f=a.y-b.offsetHeight),g+b.offsetWidth>i&&(g=i-b.offsetWidth)}b.style.top=f+dz()+"px",b.style.left=b.style.right="",e=="right"?(g=S.clientWidth-b.offsetWidth,b.style.right="0px"):(e=="left"?g=0:e=="middle"&&(g=(S.clientWidth-b.offsetWidth)/2),b.style.left=g+dA()+"px"),c&&cA(g,f,g+b.offsetWidth,f+b.offsetHeight)},lineCount:function(){return bp.size},clipPos:cN,getCursor:function(a){return a==null&&(a=bs.inverted),V(a?bs.from:bs.to)},somethingSelected:function(){return!T(bs.from,bs.to)},setCursor:dO(function(a,b,c){b==null&&typeof a.line=="number"?cL(a.line,a.ch,c):cL(a,b,c)}),setSelection:dO(function(a,b,c){(c?cI:cJ)(cN(a),cN(b||a))}),getLine:function(a){if(bS(a))return bU(a).text},getLineHandle:function(a){if(bS(a))return bU(a)},setLine:dO(function(a,b){bS(a)&&cm(b,{line:a,ch:0},{line:a,ch:bU(a).text.length})}),removeLine:dO(function(a){bS(a)&&cm("",{line:a,ch:0},cN({line:a+1,ch:0}))}),replaceRange:dO(cm),getRange:function(a,b){return cp(cN(a),cN(b))},execCommand:function(a){return h[a](bT)},moveH:dO(cP),deleteH:dO(cQ),moveV:dO(cS),toggleOverwrite:function(){by=!by},posFromIndex:function(a){var b=0,c;return bp.iter(0,bp.size,function(d){var e=d.text.length+1;if(e>a)return c=a,!0;a-=e,++b}),cN({line:b,ch:c})},indexFromPos:function(a){if(a.line<0||a.ch<0)return 0;var b=a.ch;return bp.iter(0,a.line,function(a){b+=a.text.length+1}),b},scrollTo:function(a,b){a!=null&&(P.scrollTop=a),b!=null&&(P.scrollLeft=b),cC([])},operation:function(a){return dO(a)()},refresh:function(){cC(!0),P.scrollHeight>bw&&(P.scrollTop=bw)},getInputField:function(){return O},getWrapperElement:function(){return C},getScrollerElement:function(){return P},getGutterElement:function(){return bd}},cb=null,cr=!1,cu="",cR=null;db.prototype.clear=dO(function(){var a=Infinity,b=-Infinity;for(var c=0,d=this.set.length;c",")":"(<","[":"]>","]":"[<","{":"}>","}":"{<"},dN=0;for(var dP in g)g.propertyIsEnumerable(dP)&&!bT.propertyIsEnumerable(dP)&&(bT[dP]=g[dP]);return bT}function j(a,b,c){function d(a,b,c){var e=b[a];if(e!=null)return e;c==null&&(c=b.fallthrough);if(c==null)return b.catchall;if(typeof c=="string")return d(a,i[c]);for(var f=0,g=c.length;fa&&d.push(h.slice(a-f,Math.min(h.length,b-f)),c[e+1]),i>=a&&(g=1)):g==1&&(i>b?d.push(h.slice(0,b-f),c[e+1]):d.push(h,c[e+1])),f=i}}function s(a){this.lines=a,this.parent=null;for(var b=0,c=a.length,d=0;b=0&&d>=0;--c,--d)if(a.charAt(c)!=b.charAt(d))break;return d+1}function Z(a,b){if(a.indexOf)return a.indexOf(b);for(var c=0,d=a.length;c0&&b.ch=this.string.length},sol:function(){return this.pos==0},peek:function(){return this.string.charAt(this.pos)},next:function(){if(this.posb},eatSpace:function(){var a=this.pos;while(/[\s\u00a0]/.test(this.string.charAt(this.pos)))++this.pos;return this.pos>a},skipToEnd:function(){this.pos=this.string.length},skipTo:function(a){var b=this.string.indexOf(a,this.pos);if(b>-1)return this.pos=b,!0},backUp:function(a){this.pos-=a},column:function(){return O(this.string,this.start,this.tabSize)},indentation:function(){return O(this.string,null,this.tabSize)},match:function(a,b,c){if(typeof a!="string"){var e=this.string.slice(this.pos).match(a);return e&&b!==!1&&(this.pos+=e[0].length),e}function d(a){return c?a.toLowerCase():a}if(d(this.string).indexOf(d(a),this.pos)==this.pos)return b!==!1&&(this.pos+=a.length),!0},current:function(){return this.string.slice(this.start,this.pos)}},a.StringStream=n,o.prototype={attach:function(a){this.set.push(a)},detach:function(a){var b=Z(this.set,a);b>-1&&this.set.splice(b,1)},split:function(a,b){if(this.to<=a&&this.to!=null)return null;var c=this.from=b&&(this.from=Math.max(d,this.from)+e),this.to!=null&&this.to>b&&(this.to=dthis.from&&(dthis.from||this.from==null)&&(this.to=null)},isDead:function(){return this.from!=null&&this.to!=null&&this.from>=this.to},sameSet:function(a){return this.set==a.set}},p.prototype={attach:function(a){this.line=a},detach:function(a){this.line==a&&(this.line=null)},split:function(a,b){if(athis.to},clipTo:function(a,b,c,d,e){(a||bthis.to)?(this.from=0,this.to=-1):this.from>b&&(this.from=this.to=Math.max(d,this.from)+e)},sameSet:function(a){return!1},find:function(){return!this.line||!this.line.parent?null:{line:v(this.line),ch:this.from}},clear:function(){if(this.line){var a=Z(this.line.marked,this);a!=-1&&this.line.marked.splice(a,1),this.line=null}}},q.inheritMarks=function(a,b){var c=new q(a),d=b&&b.marked;if(d)for(var e=0;e5e3){e[f++]=this.text.slice(d.pos),e[f++]=null;break}}return e.length!=f&&(e.length=f,g=!0),f&&e[f-2]!=i&&(g=!0),g||(e.length<5&&this.text.length<10?null:!1)},getTokenAt:function(a,b,c){var d=this.text,e=new n(d);while(e.pos',X(b).replace(/\t/g,a),"
"):c.push(X(b).replace(/\t/g,a))}function j(a){return a?"cm-"+a.replace(/ +/g," cm-"):null}var c=[],d=!0,f=this.styles,g=this.text,h=this.marked,i=g.length;b!=null&&(i=Math.min(b,i));if(!g&&b==null)e(" ");else if(!h||!h.length)for(var k=0,l=0;li&&(m=m.slice(0,i-l)),l+=o,e(m,j(n))}else{var p=0,k=0,q="",n,r=0,s=h[0].from||0,t=[],u=0;function v(){var a;while(ux?q.slice(0,x-p):q,z);if(y>=x){q=q.slice(x-p),p=x;break}p=y}q=f[k++],n=j(f[k++])}}}return c.join("")},cleanUp:function(){this.parent=null;if(this.marked)for(var a=0,b=this.marked.length;a50){while(f.lines.length>50){var h=f.lines.splice(f.lines.length-25,25),i=new s(h);f.height-=i.height,this.children.splice(d+1,0,i),i.parent=this}this.maybeSpill()}break}a-=g}},maybeSpill:function(){if(this.children.length<=10)return;var a=this;do{var b=a.children.splice(a.children.length-5,5),c=new t(b);if(!a.parent){var d=new t(a.children);d.parent=a,a.children=[d,c],a=d}else{a.size-=c.size,a.height-=c.height;var e=Z(a.parent.children,a);a.parent.children.splice(e+1,0,c)}c.parent=a.parent}while(a.children.length>10);a.parent.maybeSpill()},iter:function(a,b,c){this.iterN(a,b-a,c)},iterN:function(a,b,c){for(var d=0,e=this.children.length;d400||!f)this.done.push([{start:a,added:b,old:c}]);else if(f.start>a+b||f.start+f.added=0;--i)f.old.unshift(c[i]);f.added+=f.start-a,f.start=a}else f.start-1&&(N="\r\n")})(),document.documentElement.getBoundingClientRect!=null&&(Q=function(a,b){try{var c=a.getBoundingClientRect();c={top:c.top,left:c.left}}catch(d){c={top:0,left:0}}if(!b)if(window.pageYOffset==null){var e=document.documentElement||document.body.parentNode;e.scrollTop==null&&(e=document.body),c.top+=e.scrollTop,c.left+=e.scrollLeft}else c.top+=window.pageYOffset,c.left+=window.pageXOffset;return c});var W=document.createElement("pre");X("a")=="\na"?X=function(a){return W.textContent=a,W.innerHTML.slice(1)}:X("\t")!="\t"&&(X=function(a){return W.innerHTML="",W.appendChild(document.createTextNode(a)),W.innerHTML}),a.htmlEscape=X;var _="\n\nb".split(/\n/).length!=3?function(a){var b=0,c,d=[];while((c=a.indexOf("\n",b))>-1)d.push(a.slice(b,a.charAt(c-1)=="\r"?c-1:c)),b=c+1;return d.push(a.slice(b)),d}:function(a){return a.split(/\r?\n/)};a.splitLines=_;var ba=window.getSelection?function(a){try{return a.selectionStart!=a.selectionEnd}catch(b){return!1}}:function(a){try{var b=a.ownerDocument.selection.createRange()}catch(c){}return!b||b.parentElement()!=a?!1:b.compareEndPoints("StartToEnd",b)!=0};a.defineMode("null",function(){return{token:function(a){a.skipToEnd()}}}),a.defineMIME("text/plain","null");var bb={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",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"};return a.keyNames=bb,function(){for(var a=0;a<10;a++)bb[a+48]=String(a);for(var a=65;a<=90;a++)bb[a]=String.fromCharCode(a);for(var a=1;a<=12;a++)bb[a+111]=bb[a+63235]="F"+a}(),a}() diff --git a/lib/mergely.css b/lib/mergely.css new file mode 100644 index 0000000..1b9dda0 --- /dev/null +++ b/lib/mergely.css @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2012 by Jamie Peabody, http://www.mergely.com/ + * All rights reserved. + * Version: 2.3 2012-02-07 + */ +/* required */ +.mergely-column textarea { width: 80px; height: 200px; } +.mergely-column { float: left; } +.mergely-margin { float: left; } +.mergely-canvas { float: left; width: 28px; } + +/* resizeable */ +.mergely-resizer { width: 100%; height: 100%; } + +/* style configuration */ +.mergely-column { border: 1px solid #ccc; } +.mergely-active { border: 1px solid #4ba3fa; } + +/* markup */ +.mergely-c-start { border-top: 1px solid #ccc !important; } +.mergely-c-end { border-bottom: 1px solid #ccc !important; } +.mergely-a-start { border-top: 1px solid #4ba3fa !important; background-color: #ddeeff !important; } +.mergely-a-mid { background-color: #ddeeff !important; } +.mergely-a-end { border-bottom: 1px solid #4ba3fa !important; background-color: #ddeeff !important; } +.mergely-d-start { border-top: 1px solid #ff7f7f !important; background-color: #f9e9e9 !important; } +.mergely-d-mid { background-color: #f9e9e9 !important; } +.mergely-d-end { border-bottom: 1px solid #ff7f7f !important; background-color: #f9e9e9 !important; } +.mergely-c-rem { text-decoration: line-through; color: #888; background-color: #f9e9e9; } +.mergely-c-add { background-color: #ddeeff; } +.mergely-d-start-rhs { border-top: 1px solid #ff7f7f !important; } +.mergely-d-end-rhs { border-bottom: 1px solid #ff7f7f !important; } +.mergely-a-start-lhs { border-top: 1px solid #4ba3fa !important; } +.mergely-a-end-lhs { border-bottom: 1px solid #4ba3fa !important; } diff --git a/lib/mergely.js b/lib/mergely.js new file mode 100644 index 0000000..0673047 --- /dev/null +++ b/lib/mergely.js @@ -0,0 +1,1147 @@ +/** + * Copyright (c) 2012 by Jamie Peabody, http://www.mergely.com/ + * All rights reserved. + * Version: 2.3 2012-02-07 + */ +Mgly = {}; + +Object.size = function(obj) { + var size = 0, key; + for (key in obj) { + if (obj.hasOwnProperty(key)) size++; + } + return size; +}; + +Mgly.LCS = function(x, y) { + //http://en.wikipedia.org/wiki/Longest_common_subsequence_problem + this.length = this._lcs(x, y); + var C = []; + x = x.split(''); + y = y.split(''); + x.unshift('');//add an empty element to the start [1..m] + y.unshift('');//add an empty element to the start [1..n] + this.C = C; + this.x = x; + this.y = y; + var i = 0; + var j = 0; + for (i = 0; i < x.length + 1; ++i) { + C[i] = []; + for (j = 0; j < y.length + 1; ++j) C[i][j] = 0; + } + for (i = 1; i < x.length + 1; ++i) { + for (j = 1; j < y.length + 1; ++j) { + if (x[i - 1] == y[j - 1]) C[i][j] = C[i - 1][j - 1] + 1; + else C[i][j] = Math.max( C[i][j - 1], C[i - 1][j] ); + } + } + this.ready = 1; +} + +Mgly.LCS.prototype.clear = function() { + this.ready = 0; +} + +Mgly.LCS.prototype._diff = function(i, j, added, removed) { + var x = this.x; + var y = this.y; + var C = this.C; + if (this.ready && i > 0 && j > 0 && (x[i] == y[j])) { + this._diff(i - 1, j - 1, added, removed); + } + else { + if (j > 0 && (i == 0 || (C[i][j - 1] >= C[i-1][j]))) { + this._diff(i, j - 1, added, removed); + if (added) added(j - 1, y[j]); + } + else if (i > 0 && (j == 0 || (C[i][j - 1] < C[i - 1][j]))) { + this._diff(i - 1, j, added, removed); + if (removed) removed(i - 1, x[i]); + } + } +} + +Mgly.LCS.prototype.diff = function(added, removed) { + this._diff(this.x.length - 1, this.y.length - 1, added, removed); +} + +Mgly.LCS.prototype._lcs = function(string1, string2) { + // init max value + var longest = 0; + // init 2D array with 0 + var table = Array(string1.length); + for(a = 0; a <= string1.length; a++){ + table[a] = Array(string2.length); + for(b = 0; b <= string2.length; b++){ + table[a][b] = 0; + } + } + // fill table + for(var i = 0; i < string1.length; i++) { + for(var j = 0; j < string2.length; j++) { + if(string1[i]==string2[j]) { + if(table[i][j] == 0){ + table[i+1][j+1] = 1; + } + else { + table[i+1][j+1] = table[i][j] + 1; + } + if(table[i+1][j+1] > longest){ + longest = table[i+1][j+1]; + } + } + else { + table[i+1][j+1] = 0; + } + } + } + return longest; +} + +Mgly.diff = function(lhs, rhs) { + this.diff_codes = {}; + this.max_code = 0; + var lhs_lines = lhs.split('\n'); + var rhs_lines = rhs.split('\n'); + if (lhs.length == 0) lhs_lines = []; + if (rhs.length == 0) rhs_lines = []; + + var lhs_data = new Object(); + lhs_data.data = this._diff_codes(lhs_lines); + lhs_data.modified = {}; + lhs_data.length = Object.size(lhs_data.data); + + var rhs_data = new Object(); + rhs_data.data = this._diff_codes(rhs_lines); + rhs_data.modified = {}; + rhs_data.length = Object.size(rhs_data.data); + + var max = (lhs_data.length + rhs_data.length + 1); + var vector_d = Array( 2 * max + 2 ); + var vector_u = Array( 2 * max + 2 ); + + this._lcs(lhs_data, 0, lhs_data.length, rhs_data, 0, rhs_data.length, vector_u, vector_d); + this._optimize(lhs_data); + this._optimize(rhs_data); + this.items = this._create_diffs(lhs_data, rhs_data); +}; + +Mgly.diff.prototype.changes = function() { + return this.items; +} + +Mgly.diff.prototype.normal_form = function() { + var nf = ''; + for (var index in this.items) { + var item = this.items[index]; + var lhs_str = ''; + var rhs_str = ''; + var change = 'c'; + if (item.lhs_deleted_count == 0 && item.rhs_inserted_count > 0) change = 'a'; + else if (item.lhs_deleted_count > 0 && item.rhs_inserted_count == 0) change = 'd'; + + if (item.lhs_deleted_count == 1) lhs_str = item.lhs_start + 1; + else if (item.lhs_deleted_count == 0) lhs_str = item.lhs_start; + else lhs_str = (item.lhs_start + 1) + ',' + (item.lhs_start + item.lhs_deleted_count); + + if (item.rhs_inserted_count == 1) rhs_str = item.rhs_start + 1; + else if (item.rhs_inserted_count == 0) rhs_str = item.rhs_start; + else rhs_str = (item.rhs_start + 1) + ',' + (item.rhs_start + item.rhs_inserted_count); + nf += lhs_str + change + rhs_str + '\n'; + } + return nf; +} + +Mgly.diff.prototype._diff_codes = function(lines) { + var code = this.max_code; + var codes = {}; + for (var i = 0; i < lines.length; ++i) { + var line = lines[i]; + var aCode = this.diff_codes[line]; + if (aCode != undefined) { + codes[i] = aCode; + } + else { + this.max_code++; + this.diff_codes[line] = this.max_code; + codes[i] = this.max_code; + } + } + return codes; +} + +Mgly.diff.prototype._lcs = function(lhs, lhs_lower, lhs_upper, rhs, rhs_lower, rhs_upper, vector_u, vector_d) { + while ( (lhs_lower < lhs_upper) && (rhs_lower < rhs_upper) && (lhs.data[lhs_lower] == rhs.data[rhs_lower]) ) { + ++lhs_lower; + ++rhs_lower; + } + while ( (lhs_lower < lhs_upper) && (rhs_lower < rhs_upper) && (lhs.data[lhs_upper - 1] == rhs.data[rhs_upper - 1]) ) { + --lhs_upper; + --rhs_upper; + } + if (lhs_lower == lhs_upper) { + while (rhs_lower < rhs_upper) { + rhs.modified[ rhs_lower++ ] = true; + } + } + else if (rhs_lower == rhs_upper) { + while (lhs_lower < lhs_upper) { + lhs.modified[ lhs_lower++ ] = true; + } + } + else { + var sms = this._sms(lhs, lhs_lower, lhs_upper, rhs, rhs_lower, rhs_upper, vector_u, vector_d); + this._lcs(lhs, lhs_lower, sms.x, rhs, rhs_lower, sms.y, vector_u, vector_d); + this._lcs(lhs, sms.x, lhs_upper, rhs, sms.y, rhs_upper, vector_u, vector_d); + } +} + +Mgly.diff.prototype._sms = function(lhs, lhs_lower, lhs_upper, rhs, rhs_lower, rhs_upper, vector_u, vector_d) { + var max = lhs.length + rhs.length + 1; + var kdown = lhs_lower - rhs_lower; + var kup = lhs_upper - rhs_upper; + var delta = (lhs_upper - lhs_lower) - (rhs_upper - rhs_lower); + var odd = (delta & 1) != 0; + var offset_down = max - kdown; + var offset_up = max - kup; + var maxd = ((lhs_upper - lhs_lower + rhs_upper - rhs_lower) / 2) + 1; + vector_d[ offset_down + kdown + 1 ] = lhs_lower; + vector_u[ offset_up + kup - 1 ] = lhs_upper; + var ret = new Object(); + for (var d = 0; d <= maxd; ++d) { + for (var k = kdown - d; k <= kdown + d; k += 2) { + var x, y; + if (k == kdown - d) { + x = vector_d[ offset_down + k + 1 ];//down + } + else { + x = vector_d[ offset_down + k - 1 ] + 1;//right + if ((k < (kdown + d)) && (vector_d[ offset_down + k + 1 ] >= x)) { + x = vector_d[ offset_down + k + 1 ];//down + } + } + y = x - k; + // find the end of the furthest reaching forward D-path in diagonal k. + while ((x < lhs_upper) && (y < rhs_upper) && (lhs.data[x] == rhs.data[y])) { + x++; y++; + } + vector_d[ offset_down + k ] = x; + // overlap ? + if (odd && (kup - d < k) && (k < kup + d)) { + if (vector_u[offset_up + k] <= vector_d[offset_down + k]) { + ret.x = vector_d[offset_down + k]; + ret.y = vector_d[offset_down + k] - k; + return (ret); + } + } + } + // Extend the reverse path. + for (var k = kup - d; k <= kup + d; k += 2) { + // find the only or better starting point + var x, y; + if (k == kup + d) { + x = vector_u[offset_up + k - 1]; // up + } else { + x = vector_u[offset_up + k + 1] - 1; // left + if ((k > kup - d) && (vector_u[offset_up + k - 1] < x)) + x = vector_u[offset_up + k - 1]; // up + } + y = x - k; + while ((x > lhs_lower) && (y > rhs_lower) && (lhs.data[x - 1] == rhs.data[y - 1])) { + // diagonal + x--; + y--; + } + vector_u[offset_up + k] = x; + // overlap ? + if (!odd && (kdown - d <= k) && (k <= kdown + d)) { + if (vector_u[offset_up + k] <= vector_d[offset_down + k]) { + ret.x = vector_d[offset_down + k]; + ret.y = vector_d[offset_down + k] - k; + return (ret); + } + } + } + } + throw "the algorithm should never come here."; +} + +Mgly.diff.prototype._optimize = function(data) { + var start = 0, end = 0; + while (start < data.length) { + while ((start < data.length) && (data.modified[start] == undefined || data.modified[start] == false)) { + start++; + } + end = start; + while ((end < data.length) && (data.modified[end] == true)) { + end++; + } + if ((end < data.length) && (data.data[start] == data.data[end])) { + data.modified[start] = false; + data.modified[end] = true; + } + else { + start = end; + } + } +} + +Mgly.diff.prototype._create_diffs = function(lhs_data, rhs_data) { + var items = []; + var lhs_start = 0, rhs_start = 0; + var lhs_line = 0, rhs_line = 0; + + while (lhs_line < lhs_data.length || rhs_line < rhs_data.length) { + if ((lhs_line < lhs_data.length) && (!lhs_data.modified[lhs_line]) + && (rhs_line < rhs_data.length) && (!rhs_data.modified[rhs_line])) { + // equal lines + lhs_line++; + rhs_line++; + } + else { + // maybe deleted and/or inserted lines + lhs_start = lhs_line; + rhs_start = rhs_line; + + while (lhs_line < lhs_data.length && (rhs_line >= rhs_data.length || lhs_data.modified[lhs_line])) + lhs_line++; + + while (rhs_line < rhs_data.length && (lhs_line >= lhs_data.length || rhs_data.modified[rhs_line])) + rhs_line++; + + if ((lhs_start < lhs_line) || (rhs_start < rhs_line)) { + // store a new difference-item + var aItem = new Object(); + aItem.lhs_start = lhs_start; + aItem.rhs_start = rhs_start; + aItem.lhs_deleted_count = lhs_line - lhs_start; + aItem.rhs_inserted_count = rhs_line - rhs_start; + items.push(aItem); + } + } + } + return items; +} + +Mgly.mergely = function(el, options) { + if (el) { + this.init(el, options); + } +}; + +$.extend(Mgly.mergely.prototype, +{ + name: "mergely", + //http://jupiterjs.com/news/writing-the-perfect-jquery-plugin + init: function(el, options) { + this.settings = { + autoresize: true, + fadein: 'fast', + editor_width: '400px', editor_height: '400px', + resize_timeout: 500, + change_timeout: 150, + fgcolor: '#4ba3fa', + bgcolor: '#eee', + lhs: function(setValue) { }, + rhs: function(setValue) { }, + loaded: function() { }, + height: function(h) { return h - 20; }, + width: function(w) { return w; }, + resize: function() { + var w = $(el).parent().width(); + var h = $(window).height(); + if (this.width) w = this.width(w); + if (this.height) h = this.height(h); + var content_width = w / 2.0 - 2 * 8 - 8; + var content_height = h; + var tthis = $(el); + tthis.find('.mergely-column, .CodeMirror-scroll').css({ 'width': content_width + 'px' }); + tthis.find('.mergely-column, .mergely-canvas, .mergely-margin, .mergely-column textarea, .CodeMirror-scroll').css({ 'height': content_height + 'px' }); + tthis.find('.mergely-canvas').css({ 'height': content_height + 'px' }); + tthis.find('.mergely-column textarea').css({ 'width': content_width + 'px' }); + tthis.css({ 'width': w + 'px', 'height': h + 'px' }); + if (tthis.css('display') == 'none') { + if (this.fadein != false) tthis.fadeIn(this.fadein); + else tthis.show(); + if (this.loaded) this.loaded(); + } + if (this.resized) this.resized(); + }, + _debug: '', //scroll,draw,calc,diff,markup + resized: function() { } + }; + this.cmsettings = { + mode: 'application/xml', + readOnly: false, + lineWrapping: false, + lineNumbers: true + } + + // save this element for faster queries + this.element = $(el); + + // save options if there are any + if (options.cmsettings) $.extend(this.cmsettings, options.cmsettings); + if (options) $.extend(this.settings, options); + + // bind if the element is destroyed + this.element.bind("destroyed", $.proxy(this.teardown, this)); + + // save this instance in jQuery data + $.data(el, this.name, this); + + this._setup(el); + }, + // bind events to this instance's methods + bind: function() { + var tthis = this; + this.editor = []; + var lhs_cmsettings = jQuery.extend({ + onChange: function () { tthis._changing(tthis.id + '-lhs', tthis.id + '-rhs'); }, + onScroll: function () { tthis._scrolling(tthis.id + '-lhs'); } + }, this.cmsettings); + this.editor[this.id + '-lhs'] = CodeMirror.fromTextArea( + $('#' + this.id + '-lhs').get(0), lhs_cmsettings + ); + var rhs_cmsettings = jQuery.extend({ + onChange: function () { tthis._changing(tthis.id + '-lhs', tthis.id + '-rhs'); }, + onScroll: function () { tthis._scrolling(tthis.id + '-rhs'); } + }, this.cmsettings); + this.editor[this.id + '-rhs'] = CodeMirror.fromTextArea( + $('#' + this.id + '-rhs').get(0), rhs_cmsettings + ); + // resize + var sz_timeout1 = null; + $(window).resize( + function () { + if (sz_timeout1) clearTimeout(sz_timeout1); + sz_timeout1 = setTimeout(function () { + tthis.em_height = null; //recalculate + if (tthis.settings.resize) tthis.settings.resize(); + tthis.editor[tthis.id + '-lhs'].refresh(); + tthis.editor[tthis.id + '-rhs'].refresh(); + tthis._changing(tthis.id + '-lhs', tthis.id + '-rhs'); + }, tthis.settings.resize_timeout); + } + ); + }, + unbind: function() { + this.editor[this.id + '-lhs'].toTextArea(); + this.editor[this.id + '-rhs'].toTextArea(); + }, + destroy: function() { + this.element.unbind("destroyed", this.teardown); + this.teardown(); + }, + teardown: function() { + this.unbind(); + }, + lhs: function(text) { + this.editor[this.id + '-lhs'].setValue(text); + }, + rhs: function(text) { + this.editor[this.id + '-rhs'].setValue(text); + }, + swap: function() { + var le = this.editor[this.id + '-lhs']; + var re = this.editor[this.id + '-rhs']; + var tmp = re.getValue(); + re.setValue(le.getValue()); + le.setValue(tmp); + }, + merge: function(side) { + var le = this.editor[this.id + '-lhs']; + var re = this.editor[this.id + '-rhs']; + if (side == 'lhs') le.setValue(re.getValue()); + else re.setValue(le.getValue()); + }, + get: function(side) { + var ed = this.editor[this.id + '-' + side]; + var t = ed.getValue(); + if (t == undefined) return ''; + return t; + }, + clear: function(side) { + var ed = this.editor[this.id + '-' + side]; + ed.setValue(''); + }, + search: function(side, query) { + var le = this.editor[this.id + '-lhs']; + var re = this.editor[this.id + '-rhs']; + var editor; + if (side == 'lhs') editor = le; + else editor = re; + if ((editor.getSelection().length == 0) || (this.prev_query[side] != query)) { + this.cursor[this.id] = editor.getSearchCursor(query, { line: 0, ch: 0 }, false); + this.prev_query[side] = query; + } + if (this.cursor[this.id].findNext()) { + editor.setSelection(this.cursor[this.id].from(), this.cursor[this.id].to()); + } + else { + this.cursor[this.id] = editor.getSearchCursor(query, { line: 0, ch: 0 }, false); + } + }, + resize: function() { + this.settings.resize(); + this._changing(this.id + '-lhs', this.id + '-rhs'); + }, + _setup: function(el) { + $(this.element).hide();//hide + this.id = $(el).attr('id'); + var height = this.settings.editor_height; + var width = this.settings.editor_width; + this.changed_timeout = null; + this.change_funcs = []; + this.prev_query = []; + this.cursor = []; + this.change_exp = new RegExp(/(\d+(?:,\d+)?)([acd])(\d+(?:,\d+)?)/); + var merge_left_button; + var merge_right_button; + if ($.button != undefined) { + //jquery ui + merge_left_button = ''; + merge_right_button = ''; + } + else { + // homebrew + var style = 'width:1em;height:1em;background-color:#888;cursor:pointer;text-align:center;color:#eee;border:1px solid: #222;margin-right:5px;'; + merge_left_button = '
<
'; + merge_right_button = '
>
'; + } + this.merge_right_button = $(merge_right_button); + this.merge_left_button = $(merge_left_button); + if (this.merge_right_button.corner) this.merge_right_button.corner('3px'); + if (this.merge_left_button.corner) this.merge_left_button.corner('3px'); + + // create the textarea and canvas elements + $(this.element).append($('
')); + $(this.element).append($('
')); + $(this.element).append($('
')); + $(this.element).append($('
')); + $(this.element).append($('
')); + this.bind(); + if (this.settings.lhs) this.settings.lhs( this.editor[this.id + '-lhs'].setValue ); + if (this.settings.rhs) this.settings.rhs( this.editor[this.id + '-rhs'].setValue ); + + // resize only after bind + $(window).resize(); + + //codemirror + var cmstyle = '#' + this.id + ' .CodeMirror-gutter-text { padding: 5px 0 0 0; }' + + '#' + this.id + ' .CodeMirror-lines pre, ' + '#' + this.id + ' .CodeMirror-gutter-text pre { line-height: 18px; }'; + if (this.settings.autoresize) { + cmstyle += this.id + ' .CodeMirror-scroll { height: 100%; overflow: auto; }'; + } + $('').appendTo('head'); + }, + _scrolling: function(editor_name) { + var scroller = $(this.editor[editor_name].getScrollerElement()); + if (this.midway == undefined) { + this.midway = (scroller.height() / 2.0 + scroller.offset().top).toFixed(2); + } + // balance-line + var midline = this.editor[editor_name].coordsChar({x:0, y:this.midway}); + var top_to = scroller.scrollTop(); + var left_to = scroller.scrollLeft(); + + this.trace('scroll', 'midway', this.midway); + this.trace('scroll', 'midline', midline); + this.trace('scroll', 'top_to', top_to); + this.trace('scroll', 'left_to', left_to); + + for (var name in this.editor) { + if (editor_name == name) continue; //same editor + var this_side = editor_name.replace(this.id + '-', ''); + var other_side = name.replace(this.id + '-', ''); + var top_adjust = 0; + + // find the last change that is less than or within the midway point + // do not move the rhs until the lhs end point is >= the rhs end point. + var last_change = null; + for (var i in this.changes) { + var change = this.changes[i]; + if ((midline.line >= change[this_side+'-line-from'])) { + last_change = change; + if (midline.line >= last_change[this_side+'-line-to']) { + top_adjust += + (change[this_side+'-y-end'] - change[this_side+'-y-start']) - (change[other_side+'-y-end'] - change[other_side+'-y-start']); + } + } + } + + var scroll = true; + if (last_change) { + this.trace('scroll', 'last visible change', last_change); + if ((last_change[this_side+'-line-from'] < midline.line) && + (last_change[this_side+'-line-to'] > midline.line)) { + scroll = false; + } + } + if (scroll) { + // scroll the other side + this.trace('scroll', 'scrolling other side', top_to - top_adjust); + var scroller = $(this.editor[name].getScrollerElement()); + scroller.scrollTop(top_to - top_adjust).scrollLeft(left_to); + } + else this.trace('scroll', 'not scrolling other side'); + this._calculate_offsets(this.id + '-lhs', this.id + '-rhs', this.changes); + this._draw_diff(this.id + '-lhs', this.id + '-rhs', this.changes); + this.trace('scroll', 'scrolled'); + } + }, + _changing: function(editor_name1, editor_name2) { + var tthis = this; + if (this.changed_timeout != null) clearTimeout(this.changed_timeout); + this.changed_timeout = setTimeout(function(){ + tthis._changed(editor_name1, editor_name2); + }, this.settings.change_timeout); + }, + _changed: function(editor_name1, editor_name2) { + for (var name in this.editor) { + var editor = this.editor[name]; + editor.operation(function() { + for (var i = 0, l = editor.lineCount(); i < l; ++i) { + editor.clearMarker(i); + editor.setLineClass(i, null); + } + }); + } + //remove previous markup changes + for (var i in this.change_funcs) { + var change = this.change_funcs[i]; + if (change.clear != undefined) change.clear(); + else change();//prev codemirror + } + this._diff(editor_name1, editor_name2); + }, + _diff: function(editor_name1, editor_name2) { + var lhs = this.editor[editor_name1].getValue(); + var rhs = this.editor[editor_name2].getValue(); + var d = new Mgly.diff(lhs, rhs); + this.changes = this._parse_diff(editor_name1, editor_name2, d.normal_form()); + this._calculate_offsets(editor_name1, editor_name2, this.changes); + this._markup_changes(editor_name1, editor_name2, this.changes); + this._draw_diff(editor_name1, editor_name2, this.changes); + }, + _parse_diff: function (editor_name1, editor_name2, diff) { + this.trace('diff', 'diff results:\n', diff); + var changes = []; + var change_id = 0; + // parse diff + var diff_lines = diff.split(/\n/); + for (var i = 0; i < diff_lines.length; ++i) { + if (diff_lines[i].length == 0) continue; + var change = {}; + var test = this.change_exp.exec(diff_lines[i]); + if (test == null) continue; + // lines are zero-based + var fr = test[1].split(','); + change['lhs-line-from'] = fr[0] - 1; + if (fr.length == 1) change['lhs-line-to'] = fr[0] - 1; + else change['lhs-line-to'] = fr[1] - 1; + var to = test[3].split(','); + change['rhs-line-from'] = to[0] - 1; + if (to.length == 1) change['rhs-line-to'] = to[0] - 1; + else change['rhs-line-to'] = to[1] - 1; + // TODO: optimize for changes that are adds/removes + change['op'] = test[2]; + changes[change_id++] = change; + this.trace('diff', 'change', change); + } + return changes; + }, + _calculate_offsets: function (editor_name1, editor_name2, changes) { + if (this.draw_top_offset == null) { + var topnode = this.element.find('.CodeMirror-gutter-text pre').first(); + var top_offset = topnode.offset().top; + this.em_height = topnode.get(0).offsetHeight; + // this is the distance from the top of the screen + this.draw_top_offset = 6.5 - top_offset; + if (this.em_height > 0) { + this.draw_lhs_min = 0.5; + this.draw_rhs_max = $('#' + editor_name1 + '-' + editor_name2 + '-canvas').width() - 0.5; //24.5; + this.draw_lhs_width = 5; + this.draw_rhs_width = 5; + } + this.trace('calc', 'change offsets calculated', 'top_offset', top_offset); + } + for (var i in changes) { + var change = changes[i]; + change['lhs-y-start'] = this.draw_top_offset + this.editor[editor_name1].charCoords({line: change['lhs-line-from'], ch:0}).y; + change['lhs-y-end'] = this.draw_top_offset + this.editor[editor_name1].charCoords({line: change['lhs-line-to']+1, ch:0}).y - 1; + change['rhs-y-start'] = this.draw_top_offset + this.editor[editor_name2].charCoords({line: change['rhs-line-from'], ch:0}).y; + change['rhs-y-end'] = this.draw_top_offset + this.editor[editor_name2].charCoords({line: change['rhs-line-to']+1, ch:0}).y - 1; + if (change['op'] == 'd') { + change['rhs-y-start'] = change['rhs-y-end']; + } + else if (change['op'] == 'a') { + change['lhs-y-start'] = change['lhs-y-end']; + } + this.trace('calc', 'change offsets calculated', i, change); + } + }, + _markup_changes: function (editor_name1, editor_name2, changes) { + $('.merge-button').remove(); // clear + var editor = this.editor[editor_name1]; + editor.operation(function() { + for (var i in changes) { + var change = changes[i]; + var class_start = 'mergely-' + change['op'] + '-start'; + var class_end = 'mergely-' + change['op'] + '-end'; + var class_start_end = class_start + ' ' + class_end; + if (change['lhs-line-from'] == change['lhs-line-to']) { + if (change['op'] == 'c') { + editor.setLineClass(change['lhs-line-from'], class_start_end); + //editor.setMarker(change['lhs-line-from'], '%N%', class_start_end); + } + else if (change['op'] == 'a') { + editor.setLineClass(change['lhs-line-from'], class_end + '-lhs'); + //editor.setMarker(change['lhs-line-from'], '%N%', class_end + '-lhs'); + } + else if (change['op'] == 'd') { + editor.setLineClass(change['lhs-line-from'], class_start_end + ' mergely-c-rem'); + //editor.setMarker(change['lhs-line-from'], '%N%', class_start_end + ' mergely-c-rem'); + } + } + else { + editor.setLineClass(change['lhs-line-from'], class_start + ((change['op'] == 'd')? ' mergely-c-rem' : '') ); + editor.setLineClass(change['lhs-line-to'], class_end + ((change['op'] == 'd')? ' mergely-c-rem' : '') ); + //editor.setMarker(change['lhs-line-from'], '%N%', class_start + ((change['op'] == 'd')? ' mergely-c-rem' : '') ); + //editor.setMarker(change['lhs-line-to'], '%N%', class_end + ((change['op'] == 'd')? ' mergely-c-rem' : '') ); + + if (change['op'] == 'd') {// fill in deletes between from/to + for (var line = change['lhs-line-from'] + 1; line < change['lhs-line-to']; ++line) { + editor.setLineClass(line, 'mergely-c-rem mergely-d-mid'); + //editor.setMarker(line, '%N%', 'mergely-c-rem mergely-d-mid'); + } + } + } + } + }); + + var editor = this.editor[editor_name2]; + editor.operation(function() { + for (var i in changes) { + var change = changes[i]; + var class_start = 'mergely-' + change['op'] + '-start'; + var class_end = 'mergely-' + change['op'] + '-end'; + var class_start_end = class_start + ' ' + class_end; + if (change['rhs-line-from'] == change['rhs-line-to']) { + if (change['op'] == 'c') { + editor.setLineClass(change['rhs-line-from'], class_start_end); + //editor.setMarker(change['rhs-line-from'], '%N%', class_start_end); + } + else if (change['op'] == 'a') { + editor.setLineClass(change['rhs-line-from'], class_start_end); + //editor.setMarker(change['rhs-line-from'], '%N%', class_start_end); + } + else if (change['op'] == 'd') { + editor.setLineClass(change['rhs-line-from'], class_end + '-rhs'); + //editor.setMarker(change['rhs-line-from'], '%N%', class_end + '-rhs'); + } + } + else { + editor.setLineClass(change['rhs-line-from'], class_start); + editor.setLineClass(change['rhs-line-to'], class_end); + //editor.setMarker(change['rhs-line-from'], '%N%', class_start); + //editor.setMarker(change['rhs-line-to'], '%N%', class_end); + + if (change['op'] == 'a') {// fill in deletes between from/to + for (var line = change['rhs-line-from'] + 1; line < change['rhs-line-to']; ++line) { + editor.setLineClass(line, 'mergely-c-add mergely-a-mid'); + //editor.setMarker(line, '%N%', 'mergely-c-add mergely-a-mid'); + } + } + } + } + }); + + for (var i in changes) { + var change = changes[i]; + var class_start = 'mergely-' + change['op'] + '-start'; + var class_end = 'mergely-' + change['op'] + '-end'; + var class_start_end = class_start + ' ' + class_end; + + if (!this.cmsettings.readOnly) { + var button = this.merge_right_button.clone(); + if (button.button) button.button({icons: {primary: 'ui-icon-triangle-1-e'}, text: false}); + button.addClass('merge-button'); + button.attr('id', 'merge-right-' + i); + // the strangeness here is to get around creating closures in a loop, which do + // not work. to get around, need to wrap and call the function. + var tthis = this; + $(button).get(0).onclick = (function (change) { + return function() { + var lhs_line = tthis.editor[editor_name1].lineInfo(change['lhs-line-to']); + var rhs_line = tthis.editor[editor_name2].lineInfo(change['rhs-line-to']); + var text = tthis.editor[editor_name1].getRange( + { 'line': change['lhs-line-from'], 'ch': 0 }, + { 'line': change['lhs-line-to'], 'ch': lhs_line.text.length }); + if (change['op'] == 'c') { + tthis.editor[editor_name2].replaceRange( + text, { 'line': change['rhs-line-from'], 'ch': 0 }, + { 'line': change['rhs-line-to'], 'ch': rhs_line.text.length }); + } + else if (change['op'] == 'a' ) { + var from = parseInt(change['rhs-line-from']); + var to = parseInt(change['rhs-line-to']); + for (var i = to; i >= from; --i) { + tthis.editor[editor_name2].removeLine(i); + } + } + else { + //must force a newline + text = text + '\n'; + tthis.editor[editor_name2].replaceRange( + text, { 'line': change['rhs-line-from'] + 1, 'ch': 0 }); + } + + //reset + tthis.editor[editor_name1].setValue(tthis.editor[editor_name1].getValue()); + tthis.editor[editor_name2].setValue(tthis.editor[editor_name2].getValue()); + return false; + } + })(change); + + this.trace('markup', 'lhs adding button', change['lhs-line-from']); + this.editor[editor_name1].addWidget( + { 'line': change['lhs-line-from'], 'ch': 0 }, button.get(0), false, 'over', 'right') + + button = this.merge_left_button.clone(); + if (button.button) button.button({icons: {primary: 'ui-icon-triangle-1-w'}, text: false}); + button.addClass('merge-button'); + button.attr('id', 'merge-left-' + i); + // the strangeness here is to get around creating closures in a loop, which do + // not work. to get around, need to wrap and call the function. + var tthis = this; + $(button).get(0).onclick = (function (change) { + return function () { + var lhs_line = tthis.editor[editor_name1].lineInfo(change['lhs-line-to']); + var rhs_line = tthis.editor[editor_name2].lineInfo(change['rhs-line-to']); + var text = tthis.editor[editor_name2].getRange( + { 'line': change['rhs-line-from'], 'ch': 0 }, + { 'line': change['rhs-line-to'], 'ch': rhs_line.text.length }); + if (change['op'] == 'c') { + tthis.editor[editor_name1].replaceRange( + text, { 'line': change['lhs-line-from'], 'ch': 0 }, + { 'line': change['lhs-line-to'], 'ch': lhs_line.text.length }); + } + else if (change['op'] == 'd' ) { + var from = parseInt(change['lhs-line-from']); + var to = parseInt(change['lhs-line-to']); + for (var i = to; i >= from; --i) { + tthis.editor[editor_name1].removeLine(i); + } + } + else { + //must force a newline + text = text + '\n'; + tthis.editor[editor_name1].replaceRange( + text, { 'line': change['lhs-line-from'] + 1, 'ch': 0 }); + } + //reset + tthis.editor[editor_name1].setValue(tthis.editor[editor_name1].getValue()); + tthis.editor[editor_name2].setValue(tthis.editor[editor_name2].getValue()); + return false; + } + })(change); + + this.trace('markup', 'rhs adding button', change['rhs-line-from']); + this.editor[editor_name2].addWidget( + { 'line': change['rhs-line-from'], 'ch': 0 }, button.get(0), false, 'over', 'right') + } + // + // LCS or line changes + // + if (change['op'] == 'a') { + var from = change['rhs-line-from']; + var to = change['rhs-line-to']; + var to_ln = this.editor[editor_name2].lineInfo(to); + if (to_ln) { + var func = this.editor[editor_name2].markText({line:from, ch:0}, {line:to, ch:to_ln.text.length}, 'mergely-c-add'); + this.change_funcs.push(func); + } + continue; + } + else if (change['op'] == 'd') { + var from = change['lhs-line-from']; + var to = change['lhs-line-to']; + var to_ln = this.editor[editor_name1].lineInfo(to); + if (to_ln) { + var func = this.editor[editor_name1].markText({line:from, ch:0}, {line:to, ch:to_ln.text.length}, 'mergely-c-rem'); + this.change_funcs.push(func); + } + continue; + } + + var tthis = this; + + for (var j = change['lhs-line-from'], k = change['rhs-line-from'], i = 0; + ((j >= 0) && (j <= change['lhs-line-to'])) || ((k >= 0) && (k <= change['rhs-line-to'])); + ++j, ++k) { + if (k + i > change['rhs-line-to']) { + // lhs continues past rhs, mark lhs as deleted + var lhs_line = this.editor[editor_name1].getLine( j ); + var func = this.editor[editor_name1].markText({line:j, ch:0}, {line:j, ch:lhs_line.length}, 'mergely-c-rem'); + this.change_funcs.push(func); + continue; + } + if (j + i > change['lhs-line-to']) { + // rhs continues past lhs, mark rhs as added + var rhs_line = this.editor[editor_name2].getLine( k ); + var func = this.editor[editor_name1].markText({line:k, ch:0}, {line:k, ch:lhs_line.length}, 'mergely-c-add'); + this.change_funcs.push(func); + continue; + } + + var lhs_line = this.editor[editor_name1].getLine( j ); + var rhs_line = this.editor[editor_name2].getLine( k ); + var lhs_start = { 'line': -1, 'ch': -1 }; + var lhs_stop = { 'line': -1, 'ch': -1 }; + var rhs_start = { 'line': -1, 'ch': -1 }; + var rhs_stop = { 'line': -1, 'ch': -1 }; + + var lcs = new Mgly.LCS(lhs_line, rhs_line); + var max = Math.max(lhs_line.length, rhs_line.length); + if (max == 0) max = 1; + var percent = ((1.0)*lcs.length / max) * 100; + if (percent < 10) lcs.clear(); + lcs.diff( + added = function (index, c) { + if (rhs_start.ch < 0) { + rhs_start.line = k; + rhs_start.ch = index; + rhs_stop.line = k; + rhs_stop.ch = index; + } + else if (index == rhs_stop.ch + 1) { + rhs_stop.ch = index; + } + else { + if ((rhs_start.ch >= 0) && (rhs_stop.ch >= rhs_start.ch)) { + rhs_stop.ch += 1; + var func = tthis.editor[editor_name2].markText(rhs_start, rhs_stop, 'mergely-c-add'); + tthis.change_funcs.push(func); + } + //reset + rhs_start.ch = -1; + rhs_stop.ch = -1; + if (c != '\n') this.added(index, c);//call again + } + }, + removed = function (index, c) { + if (lhs_start.ch < 0) { + lhs_start.line = j; + lhs_start.ch = index; + lhs_stop.line = j; + lhs_stop.ch = index; + } + else if (index == lhs_stop.ch + 1) { + lhs_stop.ch = index; + } + else { + if ((lhs_start.ch >= 0) && (lhs_stop.ch >= lhs_start.ch)) { + lhs_stop.ch += 1; + var func = tthis.editor[editor_name1].markText(lhs_start, lhs_stop, 'mergely-c-rem'); + tthis.change_funcs.push(func); + } + //reset + lhs_start.ch = -1; + lhs_stop.ch = -1; + if (c != '\n') this.removed(index, c);//call again + } + } + ); + if ((rhs_start.ch >= 0) && (rhs_stop.ch >= rhs_start.ch)) { + rhs_stop.ch += 1; + var func = this.editor[editor_name2].markText(rhs_start, rhs_stop, 'mergely-c-add'); + this.change_funcs.push(func); + } + if ((lhs_start.ch >= 0) && (lhs_stop.ch >= lhs_start.ch)) { + lhs_stop.ch += 1; + var func = this.editor[editor_name1].markText(lhs_start, lhs_stop, 'mergely-c-rem'); + this.change_funcs.push(func); + } + } + + + } + }, + _draw_diff: function(editor_name1, editor_name2, changes) { + var visible_page_height = $(this.editor[editor_name1].getScrollerElement()).height(); + var gutter_height = $(this.editor[editor_name1].getScrollerElement()).children(':first-child').height(); + var visible_page_ratio = (visible_page_height / gutter_height); + var margin_ratio = (visible_page_height / gutter_height); + var lhs_scroller = $(this.editor[editor_name1].getScrollerElement()); + var rhs_scroller = $(this.editor[editor_name2].getScrollerElement()); + var lhs_lines = this.editor[editor_name1].lineCount(); + var rhs_lines = this.editor[editor_name2].lineCount(); + + this.trace('draw', 'visible_page_height', visible_page_height); + this.trace('draw', 'gutter_height', gutter_height); + this.trace('draw', 'visible_page_ratio', visible_page_ratio); + this.trace('draw', 'lhs-scroller-top', lhs_scroller.scrollTop()); + this.trace('draw', 'rhs-scroller-top', rhs_scroller.scrollTop()); + + var dcanvas = document.getElementById(editor_name1 + '-' + editor_name2 + '-canvas'); + if (dcanvas == undefined) throw 'Failed to find: ' + editor_name1 + '-' + editor_name2 + '-canvas'; + $.each($('canvas'), function () { + $(this).get(0).height = visible_page_height; + }); + var clhs = $('#' + this.id + '-lhs-margin'); + var crhs = $('#' + this.id + '-rhs-margin'); + clhs.unbind('click'); + crhs.unbind('click'); + var mcanvas_lhs = clhs.get(0); + var mcanvas_rhs = crhs.get(0); + var lhs_xyoffset = $(clhs).offset(); + var rhs_xyoffset = $(crhs).offset(); + + var ctx = dcanvas.getContext('2d'); + var ctx_lhs = mcanvas_lhs.getContext('2d'); + var ctx_rhs = mcanvas_rhs.getContext('2d'); + + ctx_lhs.beginPath(); + ctx_lhs.fillStyle = this.settings.bgcolor; + ctx_lhs.strokeStyle = '#888'; + ctx_lhs.fillRect(0, 0, 6.5, visible_page_height); + ctx_lhs.strokeRect(0, 0, 6.5, visible_page_height); + + ctx_rhs.beginPath(); + ctx_rhs.fillStyle = this.settings.bgcolor; + ctx_rhs.strokeStyle = '#888'; + ctx_rhs.fillRect(0, 0, 6.5, visible_page_height); + ctx_rhs.strokeRect(0, 0, 6.5, visible_page_height); + + for (var i in changes) { + var change = changes[i]; + + var lhs_y_start = change['lhs-y-start']; + var lhs_y_end = change['lhs-y-end']; + var rhs_y_start = change['rhs-y-start']; + var rhs_y_end = change['rhs-y-end']; + + // draw left box + ctx.beginPath(); + ctx.strokeStyle = this.settings.fgcolor; + ctx.lineWidth = 1; + ctx.moveTo(this.draw_lhs_min, lhs_y_start); + ctx.lineTo(this.draw_lhs_min + this.draw_lhs_width, lhs_y_start); + ctx.lineTo(this.draw_lhs_min + this.draw_lhs_width, lhs_y_end + 1); + ctx.lineTo(this.draw_lhs_min, lhs_y_end + 1); + ctx.stroke(); + + // draw right box + ctx.moveTo(this.draw_rhs_max, rhs_y_start); + ctx.lineTo(this.draw_rhs_max - this.draw_rhs_width, rhs_y_start); + ctx.lineTo(this.draw_rhs_max - this.draw_rhs_width, rhs_y_end + 1); + ctx.lineTo(this.draw_rhs_max, rhs_y_end + 1); + ctx.stroke(); + + // connect boxes + ctx.moveTo(this.draw_lhs_min + this.draw_lhs_width, lhs_y_start + (lhs_y_end + 1 - lhs_y_start) / 2.0); + ctx.lineTo(this.draw_rhs_max - this.draw_rhs_width, rhs_y_start + (rhs_y_end + 1 - rhs_y_start) / 2.0); + ctx.stroke(); + + // margin indicators + /* + if (this.em_height * lhs_lines > visible_page_height) { + // margin indicators need to be re-calculated per-ratio + lhs_y_start = change['lhs-line-from'] * (visible_page_height / lhs_lines); + lhs_y_end = (change['lhs-line-to'] + 1) * (visible_page_height / lhs_lines); + } + if (this.em_height * rhs_lines > visible_page_height) { + // margin indicators need to be re-calculated per-ratio + rhs_y_start = change['rhs-line-from'] * (visible_page_height / rhs_lines); + rhs_y_end = (change['rhs-line-to'] + 1) * (visible_page_height / rhs_lines); + } + */ + this.trace('draw', change); + // margin indicators + lhs_y_start = ((change['lhs-y-start'] + lhs_scroller.scrollTop()) * visible_page_ratio); + lhs_y_end = ((change['lhs-y-end'] + lhs_scroller.scrollTop()) * visible_page_ratio) + 1; + rhs_y_start = ((change['rhs-y-start'] + rhs_scroller.scrollTop()) * visible_page_ratio); + rhs_y_end = ((change['rhs-y-end'] + rhs_scroller.scrollTop()) * visible_page_ratio) + 1; + this.trace('draw', 'marker calculated', lhs_y_start, lhs_y_end, rhs_y_start, rhs_y_end); + + ctx_lhs.beginPath(); + ctx_lhs.fillStyle = this.settings.fgcolor; + ctx_lhs.strokeStyle = '#000'; + ctx_lhs.lineWidth = 0.5; + ctx_lhs.fillRect(1.5, lhs_y_start, 4.5, Math.max(lhs_y_end - lhs_y_start, 5)); + ctx_lhs.strokeRect(1.5, lhs_y_start, 4.5, Math.max(lhs_y_end - lhs_y_start, 5)); + + ctx_rhs.beginPath(); + ctx_rhs.fillStyle = this.settings.fgcolor; + ctx_rhs.strokeStyle = '#000'; + ctx_rhs.lineWidth = 0.5; + ctx_rhs.fillRect(1.5, rhs_y_start, 4.5, Math.max(rhs_y_end - rhs_y_start, 5)); + ctx_rhs.strokeRect(1.5, rhs_y_start, 4.5, Math.max(rhs_y_end - rhs_y_start, 5)); + } + + // visible window feedback + ctx_lhs.fillStyle = 'rgba(0, 0, 200, 0.5)'; + ctx_rhs.fillStyle = 'rgba(0, 0, 200, 0.5)'; + + var to = clhs.height() * visible_page_ratio; + var from = (lhs_scroller.scrollTop() / gutter_height) * clhs.height(); + this.trace('draw', 'cls.height', clhs.height()); + this.trace('draw', 'lhs_scroller.scrollTop()', lhs_scroller.scrollTop()); + this.trace('draw', 'gutter_height', gutter_height); + this.trace('draw', 'visible_page_ratio', visible_page_ratio); + this.trace('draw', 'from', from, 'to', to); + + ctx_lhs.fillRect(1.5, from, 4.5, to); + ctx_rhs.fillRect(1.5, from, 4.5, to); + + clhs.click(function (ev) { + var y = ev.pageY - lhs_xyoffset.top - (to / 2); + var sto = Math.max(0, (y / mcanvas_lhs.height) * lhs_scroller.get(0).scrollHeight); + lhs_scroller.scrollTop(sto); + }); + crhs.click(function (ev) { + var y = ev.pageY - rhs_xyoffset.top - (to / 2); + var sto = Math.max(0, (y / mcanvas_rhs.height) * rhs_scroller.get(0).scrollHeight); + rhs_scroller.scrollTop(sto); + }); + }, + trace: function(name) { + if(this.settings._debug.indexOf(name) >= 0) { + arguments[0] = name+':'; + console.log(this, [].slice.apply(arguments)); + } + } +}); + +$.pluginMaker = function(plugin) { + // add the plugin function as a jQuery plugin + $.fn[plugin.prototype.name] = function(options) { + // get the arguments + var args = $.makeArray(arguments), + after = args.slice(1); + var rc = undefined; + this.each(function() { + // see if we have an instance + var instance = $.data(this, plugin.prototype.name); + if (instance) { + // call a method on the instance + if (typeof options == "string") { + rc = instance[options].apply(instance, after); + } else if (instance.update) { + // call update on the instance + alert('here'); + return instance.update.apply(instance, args); + } + } else { + // create the plugin + new plugin(this, options); + } + }); + if (rc != undefined) return rc; + }; +}; + +// make the mergely widget +$.pluginMaker(Mgly.mergely); diff --git a/lib/mergely.min.js b/lib/mergely.min.js new file mode 100644 index 0000000..21d7b6c --- /dev/null +++ b/lib/mergely.min.js @@ -0,0 +1,6 @@ +/** + * Copyright (c) 2012 by Jamie Peabody, http://www.mergely.com/ + * All rights reserved. + * Version: 2.3 2012-02-07 + */ +Mgly={},Object.size=function(a){var b=0,c;for(c in a)a.hasOwnProperty(c)&&b++;return b},Mgly.LCS=function(a,b){this.length=this._lcs(a,b);var c=[];a=a.split(""),b=b.split(""),a.unshift(""),b.unshift(""),this.C=c,this.x=a,this.y=b;var d=0,e=0;for(d=0;d0&&b>0&&e[a]==f[b]?this._diff(a-1,b-1,c,d):b>0&&(a==0||g[a][b-1]>=g[a-1][b])?(this._diff(a,b-1,c,d),c&&c(b-1,f[b])):a>0&&(b==0||g[a][b-1]e&&(e=f[g+1][h+1])):f[g+1][h+1]=0;return e},Mgly.diff=function(a,b){this.diff_codes={},this.max_code=0;var c=a.split("\n"),d=b.split("\n");a.length==0&&(c=[]),b.length==0&&(d=[]);var e=new Object;e.data=this._diff_codes(c),e.modified={},e.length=Object.size(e.data);var f=new Object;f.data=this._diff_codes(d),f.modified={},f.length=Object.size(f.data);var g=e.length+f.length+1,h=Array(2*g+2),i=Array(2*g+2);this._lcs(e,0,e.length,f,0,f.length,i,h),this._optimize(e),this._optimize(f),this.items=this._create_diffs(e,f)},Mgly.diff.prototype.changes=function(){return this.items},Mgly.diff.prototype.normal_form=function(){var a="";for(var b in this.items){var c=this.items[b],d="",e="",f="c";c.lhs_deleted_count==0&&c.rhs_inserted_count>0?f="a":c.lhs_deleted_count>0&&c.rhs_inserted_count==0&&(f="d"),c.lhs_deleted_count==1?d=c.lhs_start+1:c.lhs_deleted_count==0?d=c.lhs_start:d=c.lhs_start+1+","+(c.lhs_start+c.lhs_deleted_count),c.rhs_inserted_count==1?e=c.rhs_start+1:c.rhs_inserted_count==0?e=c.rhs_start:e=c.rhs_start+1+","+(c.rhs_start+c.rhs_inserted_count),a+=d+f+e+"\n"}return a},Mgly.diff.prototype._diff_codes=function(a){var b=this.max_code,c={};for(var d=0;d=t&&(t=h[n+s+1])),u=t-s;while(tk-r&&g[o+s-1]b&&u>e&&a.data[t-1]==d.data[u-1])t--,u--;g[o+s]=t;if(!m&&j-r<=s&&s<=j+r&&g[o+s]<=h[n+s])return q.x=h[n+s],q.y=h[n+s]-s,q}}throw"the algorithm should never come here."},Mgly.diff.prototype._optimize=function(a){var b=0,c=0;while(b=b.length||a.modified[f]))f++;while(g=a.length||b.modified[g]))g++;if(d',e='';else{var f="width:1em;height:1em;background-color:#888;cursor:pointer;text-align:center;color:#eee;border:1px solid: #222;margin-right:5px;";d='
<
',e='
>
'}this.merge_right_button=$(e),this.merge_left_button=$(d),this.merge_right_button.corner&&this.merge_right_button.corner("3px"),this.merge_left_button.corner&&this.merge_left_button.corner("3px"),$(this.element).append($('
')),$(this.element).append($('
')),$(this.element).append($('
')),$(this.element).append($('
')),$(this.element).append($('
')),this.bind(),this.settings.lhs&&this.settings.lhs(this.editor[this.id+"-lhs"].setValue),this.settings.rhs&&this.settings.rhs(this.editor[this.id+"-rhs"].setValue),$(window).resize();var g="#"+this.id+" .CodeMirror-gutter-text { padding: 5px 0 0 0; }"+"#"+this.id+" .CodeMirror-lines pre, "+"#"+this.id+" .CodeMirror-gutter-text pre { line-height: 18px; }";this.settings.autoresize&&(g+=this.id+" .CodeMirror-scroll { height: 100%; overflow: auto; }"),$('").appendTo("head")},_scrolling:function(a){var b=$(this.editor[a].getScrollerElement());this.midway==undefined&&(this.midway=(b.height()/2+b.offset().top).toFixed(2));var c=this.editor[a].coordsChar({x:0,y:this.midway}),d=b.scrollTop(),e=b.scrollLeft();this.trace("scroll","midway",this.midway),this.trace("scroll","midline",c),this.trace("scroll","top_to",d),this.trace("scroll","left_to",e);for(var f in this.editor){if(a==f)continue;var g=a.replace(this.id+"-",""),h=f.replace(this.id+"-",""),i=0,j=null;for(var k in this.changes){var l=this.changes[k];c.line>=l[g+"-line-from"]&&(j=l,c.line>=j[g+"-line-to"]&&(i+=l[g+"-y-end"]-l[g+"-y-start"]-(l[h+"-y-end"]-l[h+"-y-start"])))}var m=!0;j&&(this.trace("scroll","last visible change",j),j[g+"-line-from"]c.line&&(m=!1));if(m){this.trace("scroll","scrolling other side",d-i);var b=$(this.editor[f].getScrollerElement());b.scrollTop(d-i).scrollLeft(e)}else this.trace("scroll","not scrolling other side");this._calculate_offsets(this.id+"-lhs",this.id+"-rhs",this.changes),this._draw_diff(this.id+"-lhs",this.id+"-rhs",this.changes),this.trace("scroll","scrolled")}},_changing:function(a,b){var c=this;this.changed_timeout!=null&&clearTimeout(this.changed_timeout),this.changed_timeout=setTimeout(function(){c._changed(a,b)},this.settings.change_timeout)},_changed:function(a,b){for(var c in this.editor){var d=this.editor[c];d.operation(function(){for(var a=0,b=d.lineCount();a0&&(this.draw_lhs_min=.5,this.draw_rhs_max=$("#"+a+"-"+b+"-canvas").width()-.5,this.draw_lhs_width=5,this.draw_rhs_width=5),this.trace("calc","change offsets calculated","top_offset",e)}for(var f in c){var g=c[f];g["lhs-y-start"]=this.draw_top_offset+this.editor[a].charCoords({line:g["lhs-line-from"],ch:0}).y,g["lhs-y-end"]=this.draw_top_offset+this.editor[a].charCoords({line:g["lhs-line-to"]+1,ch:0}).y-1,g["rhs-y-start"]=this.draw_top_offset+this.editor[b].charCoords({line:g["rhs-line-from"],ch:0}).y,g["rhs-y-end"]=this.draw_top_offset+this.editor[b].charCoords({line:g["rhs-line-to"]+1,ch:0}).y-1,g["op"]=="d"?g["rhs-y-start"]=g["rhs-y-end"]:g["op"]=="a"&&(g["lhs-y-start"]=g["lhs-y-end"]),this.trace("calc","change offsets calculated",f,g)}},_markup_changes:function(a,b,c){$(".merge-button").remove();var d=this.editor[a];d.operation(function(){for(var a in c){var b=c[a],e="mergely-"+b.op+"-start",f="mergely-"+b.op+"-end",g=e+" "+f;if(b["lhs-line-from"]==b["lhs-line-to"])b["op"]=="c"?d.setLineClass(b["lhs-line-from"],g):b["op"]=="a"?d.setLineClass(b["lhs-line-from"],f+"-lhs"):b["op"]=="d"&&d.setLineClass(b["lhs-line-from"],g+" mergely-c-rem");else{d.setLineClass(b["lhs-line-from"],e+(b["op"]=="d"?" mergely-c-rem":"")),d.setLineClass(b["lhs-line-to"],f+(b["op"]=="d"?" mergely-c-rem":""));if(b["op"]=="d")for(var h=b["lhs-line-from"]+1;h=g;--i)k.editor[b].removeLine(i)}else f+="\n",k.editor[b].replaceRange(f,{line:c["rhs-line-from"]+1,ch:0});return k.editor[a].setValue(k.editor[a].getValue()),k.editor[b].setValue(k.editor[b].getValue()),!1}}(f),this.trace("markup","lhs adding button",f["lhs-line-from"]),this.editor[a].addWidget({line:f["lhs-line-from"],ch:0},j.get(0),!1,"over","right"),j=this.merge_left_button.clone(),j.button&&j.button({icons:{primary:"ui-icon-triangle-1-w"},text:!1}),j.addClass("merge-button"),j.attr("id","merge-left-"+e);var k=this;$(j).get(0).onclick=function(c){return function(){var d=k.editor[a].lineInfo(c["lhs-line-to"]),e=k.editor[b].lineInfo(c["rhs-line-to"]),f=k.editor[b].getRange({line:c["rhs-line-from"],ch:0},{line:c["rhs-line-to"],ch:e.text.length});if(c["op"]=="c")k.editor[a].replaceRange(f,{line:c["lhs-line-from"],ch:0},{line:c["lhs-line-to"],ch:d.text.length});else if(c["op"]=="d"){var g=parseInt(c["lhs-line-from"]),h=parseInt(c["lhs-line-to"]);for(var i=h;i>=g;--i)k.editor[a].removeLine(i)}else f+="\n",k.editor[a].replaceRange(f,{line:c["lhs-line-from"]+1,ch:0});return k.editor[a].setValue(k.editor[a].getValue()),k.editor[b].setValue(k.editor[b].getValue()),!1}}(f),this.trace("markup","rhs adding button",f["rhs-line-from"]),this.editor[b].addWidget({line:f["rhs-line-from"],ch:0},j.get(0),!1,"over","right")}if(f["op"]=="a"){var l=f["rhs-line-from"],m=f["rhs-line-to"],n=this.editor[b].lineInfo(m);if(n){var o=this.editor[b].markText({line:l,ch:0},{line:m,ch:n.text.length},"mergely-c-add");this.change_funcs.push(o)}continue}if(f["op"]=="d"){var l=f["lhs-line-from"],m=f["lhs-line-to"],n=this.editor[a].lineInfo(m);if(n){var o=this.editor[a].markText({line:l,ch:0},{line:m,ch:n.text.length},"mergely-c-rem");this.change_funcs.push(o)}continue}var k=this;for(var p=f["lhs-line-from"],q=f["rhs-line-from"],e=0;p>=0&&p<=f["lhs-line-to"]||q>=0&&q<=f["rhs-line-to"];++p,++q){if(q+e>f["rhs-line-to"]){var r=this.editor[a].getLine(p),o=this.editor[a].markText({line:p,ch:0},{line:p,ch:r.length},"mergely-c-rem");this.change_funcs.push(o);continue}if(p+e>f["lhs-line-to"]){var s=this.editor[b].getLine(q),o=this.editor[a].markText({line:q,ch:0},{line:q,ch:r.length},"mergely-c-add");this.change_funcs.push(o);continue}var r=this.editor[a].getLine(p),s=this.editor[b].getLine(q),t={line:-1,ch:-1},u={line:-1,ch:-1},v={line:-1,ch:-1},w={line:-1,ch:-1},x=new Mgly.LCS(r,s),y=Math.max(r.length,s.length);y==0&&(y=1);var z=1*x.length/y*100;z<10&&x.clear(),x.diff(added=function(a,c){if(v.ch<0)v.line=q,v.ch=a,w.line=q,w.ch=a;else if(a==w.ch+1)w.ch=a;else{if(v.ch>=0&&w.ch>=v.ch){w.ch+=1;var d=k.editor[b].markText(v,w,"mergely-c-add");k.change_funcs.push(d)}v.ch=-1,w.ch=-1,c!="\n"&&this.added(a,c)}},removed=function(b,c){if(t.ch<0)t.line=p,t.ch=b,u.line=p,u.ch=b;else if(b==u.ch+1)u.ch=b;else{if(t.ch>=0&&u.ch>=t.ch){u.ch+=1;var d=k.editor[a].markText(t,u,"mergely-c-rem");k.change_funcs.push(d)}t.ch=-1,u.ch=-1,c!="\n"&&this.removed(b,c)}});if(v.ch>=0&&w.ch>=v.ch){w.ch+=1;var o=this.editor[b].markText(v,w,"mergely-c-add");this.change_funcs.push(o)}if(t.ch>=0&&u.ch>=t.ch){u.ch+=1;var o=this.editor[a].markText(t,u,"mergely-c-rem");this.change_funcs.push(o)}}}},_draw_diff:function(a,b,c){var d=$(this.editor[a].getScrollerElement()).height(),e=$(this.editor[a].getScrollerElement()).children(":first-child").height(),f=d/e,g=d/e,h=$(this.editor[a].getScrollerElement()),i=$(this.editor[b].getScrollerElement()),j=this.editor[a].lineCount(),k=this.editor[b].lineCount();this.trace("draw","visible_page_height",d),this.trace("draw","gutter_height",e),this.trace("draw","visible_page_ratio",f),this.trace("draw","lhs-scroller-top",h.scrollTop()),this.trace("draw","rhs-scroller-top",i.scrollTop());var l=document.getElementById(a+"-"+b+"-canvas");if(l==undefined)throw"Failed to find: "+a+"-"+b+"-canvas";$.each($("canvas"),function(){$(this).get(0).height=d});var m=$("#"+this.id+"-lhs-margin"),n=$("#"+this.id+"-rhs-margin");m.unbind("click"),n.unbind("click");var o=m.get(0),p=n.get(0),q=$(m).offset(),r=$(n).offset(),s=l.getContext("2d"),t=o.getContext("2d"),u=p.getContext("2d");t.beginPath(),t.fillStyle=this.settings.bgcolor,t.strokeStyle="#888",t.fillRect(0,0,6.5,d),t.strokeRect(0,0,6.5,d),u.beginPath(),u.fillStyle=this.settings.bgcolor,u.strokeStyle="#888",u.fillRect(0,0,6.5,d),u.strokeRect(0,0,6.5,d);for(var v in c){var w=c[v],x=w["lhs-y-start"],y=w["lhs-y-end"],z=w["rhs-y-start"],A=w["rhs-y-end"];s.beginPath(),s.strokeStyle=this.settings.fgcolor,s.lineWidth=1,s.moveTo(this.draw_lhs_min,x),s.lineTo(this.draw_lhs_min+this.draw_lhs_width,x),s.lineTo(this.draw_lhs_min+this.draw_lhs_width,y+1),s.lineTo(this.draw_lhs_min,y+1),s.stroke(),s.moveTo(this.draw_rhs_max,z),s.lineTo(this.draw_rhs_max-this.draw_rhs_width,z),s.lineTo(this.draw_rhs_max-this.draw_rhs_width,A+1),s.lineTo(this.draw_rhs_max,A+1),s.stroke(),s.moveTo(this.draw_lhs_min+this.draw_lhs_width,x+(y+1-x)/2),s.lineTo(this.draw_rhs_max-this.draw_rhs_width,z+(A+1-z)/2),s.stroke(),this.trace("draw",w),x=(w["lhs-y-start"]+h.scrollTop())*f,y=(w["lhs-y-end"]+h.scrollTop())*f+1,z=(w["rhs-y-start"]+i.scrollTop())*f,A=(w["rhs-y-end"]+i.scrollTop())*f+1,this.trace("draw","marker calculated",x,y,z,A),t.beginPath(),t.fillStyle=this.settings.fgcolor,t.strokeStyle="#000",t.lineWidth=.5,t.fillRect(1.5,x,4.5,Math.max(y-x,5)),t.strokeRect(1.5,x,4.5,Math.max(y-x,5)),u.beginPath(),u.fillStyle=this.settings.fgcolor,u.strokeStyle="#000",u.lineWidth=.5,u.fillRect(1.5,z,4.5,Math.max(A-z,5)),u.strokeRect(1.5,z,4.5,Math.max(A-z,5))}t.fillStyle="rgba(0, 0, 200, 0.5)",u.fillStyle="rgba(0, 0, 200, 0.5)";var B=m.height()*f,C=h.scrollTop()/e*m.height();this.trace("draw","cls.height",m.height()),this.trace("draw","lhs_scroller.scrollTop()",h.scrollTop()),this.trace("draw","gutter_height",e),this.trace("draw","visible_page_ratio",f),this.trace("draw","from",C,"to",B),t.fillRect(1.5,C,4.5,B),u.fillRect(1.5,C,4.5,B),m.click(function(a){var b=a.pageY-q.top-B/2,c=Math.max(0,b/o.height*h.get(0).scrollHeight);h.scrollTop(c)}),n.click(function(a){var b=a.pageY-r.top-B/2,c=Math.max(0,b/p.height*i.get(0).scrollHeight);i.scrollTop(c)})},trace:function(a){this.settings._debug.indexOf(a)>=0&&(arguments[0]=a+":",console.log(this,[].slice.apply(arguments)))}}),$.pluginMaker=function(a){$.fn[a.prototype.name]=function(b){var c=$.makeArray(arguments),d=c.slice(1),e=undefined;this.each(function(){var f=$.data(this,a.prototype.name);if(f){if(typeof b=="string")e=f[b].apply(f,d);else if(f.update)return alert("here"),f.update.apply(f,c)}else new a(this,b)});if(e!=undefined)return e}},$.pluginMaker(Mgly.mergely)