From b5701da64f01a2c1a293491260fb5570f14c3974 Mon Sep 17 00:00:00 2001 From: siemargl Date: Mon, 24 Sep 2018 12:55:07 +0000 Subject: [PATCH] tiny text editor, original git-svn-id: svn://kolibrios.org@7413 a494cfbc-eb01-0410-851d-a64ba20cac60 --- programs/other/tte/LICENSE | 674 +++++++++++ programs/other/tte/Makefile | 6 + programs/other/tte/README.md | 60 + programs/other/tte/images/scr_1.png | Bin 0 -> 59635 bytes programs/other/tte/tte.c | 1713 +++++++++++++++++++++++++++ 5 files changed, 2453 insertions(+) create mode 100644 programs/other/tte/LICENSE create mode 100644 programs/other/tte/Makefile create mode 100644 programs/other/tte/README.md create mode 100644 programs/other/tte/images/scr_1.png create mode 100644 programs/other/tte/tte.c diff --git a/programs/other/tte/LICENSE b/programs/other/tte/LICENSE new file mode 100644 index 0000000000..9cecc1d466 --- /dev/null +++ b/programs/other/tte/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. + + {one line to give the program's name and a brief idea of what it does.} + Copyright (C) {year} {name of author} + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +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: + + {project} Copyright (C) {year} {fullname} + 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/programs/other/tte/Makefile b/programs/other/tte/Makefile new file mode 100644 index 0000000000..a9d5122e2f --- /dev/null +++ b/programs/other/tte/Makefile @@ -0,0 +1,6 @@ +tte: tte.c + $(CC) tte.c -o tte -Wall -Wextra -pedantic -std=c99 + +install: tte + sudo cp tte /usr/local/bin/ + sudo chmod +x /usr/local/bin/ diff --git a/programs/other/tte/README.md b/programs/other/tte/README.md new file mode 100644 index 0000000000..7f9677766e --- /dev/null +++ b/programs/other/tte/README.md @@ -0,0 +1,60 @@ +# tte + +tte (tiny text editor) is a terminal based text editor written in C from scratch, trying to be very minimalistic and dependency independent (it's not even using **curses**). + +This project was mainly created for educational purposes, so is very commented! + +Thanks to [antirez](http://antirez.com) for inspiring me with his project `kilo` and [Jeremy Ruten](https://twitter.com/yjerem) for his tutorials. + +## Installation + +### Compiling +``` +git clone https://github.com/GrenderG/tte.git +cd tte/ +make install +``` +### Downloading executable +Download it from [here](https://github.com/GrenderG/tte/releases/latest), then +``` +sudo mv tte /usr/local/bin/ +sudo chmod +x /usr/local/bin/tte +``` + +## Usage +``` +tte [file_name] +tte -h | --help +tte -v | --version +``` +If you are planning to use special characters like (á, é, í, ó, ú, ¡, ¿, ...) you must use `ISO 8859-1` encoding in your terminal. See [this issue](https://github.com/GrenderG/tte/issues/2) for more info. + +## Keybindings +The key combinations chosen here are the ones that fit the best for me. +``` +Ctrl-Q : Exit +Ctrl-F : Search text (ESC, arrows and enter to interact once searching) +Ctrl-S : Save +Ctrl-E : Flip line upwards +Ctrl-D : Flip line downwards +Ctrl-C : Copy line +Ctrl-X : Cut line +Ctrl-V : Paste line +Ctrl-P : Pause tte (type "fg" to resume) +``` + +## Current supported languages +* C (`*.c`, `*.h`) +* C++ (`*.cpp`, `*.hpp`, `*.cc`) +* Java (`*.java`) +* Bash (`*.sh`) +* Python (`*.py`) +* PHP (`*.php`) +* JavaScript (`*.js`, `*.jsx`) +* JSON (`*.json`, `*.jsonp`) +* XML (partially) (`*.xml`) +* SQL (`*.sql`) +* Ruby (`*.rb`) + +## Images +![First screenshot](https://raw.githubusercontent.com/GrenderG/tte/master/images/scr_1.png) diff --git a/programs/other/tte/images/scr_1.png b/programs/other/tte/images/scr_1.png new file mode 100644 index 0000000000000000000000000000000000000000..20640c0725047dcfdf92eff4f29fb2e60910245e GIT binary patch literal 59635 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 s^KHe_$$|cUG!l|@(X*ZZ+sn54. +*/ + +/*** Include section ***/ + +// We add them above our includes, because the header +// files we are including use the macros to decide what +// features to expose. These macros remove some compilation +// warnings. See +// https://www.gnu.org/software/libc/manual/html_node/Feature-Test-Macros.html +// for more info. +#define _DEFAULT_SOURCE +#define _BSD_SOURCE +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/*** Define section ***/ + +// This mimics the Ctrl + whatever behavior, setting the +// 3 upper bits of the character pressed to 0. +#define CTRL_KEY(k) ((k) & 0x1f) +// Empty buffer +#define ABUF_INIT {NULL, 0} +// Version code +#define TTE_VERSION "0.0.5" +// Length of a tab stop +#define TTE_TAB_STOP 4 +// Times to press Ctrl-Q before exiting +#define TTE_QUIT_TIMES 2 +// Highlight flags +#define HL_HIGHLIGHT_NUMBERS (1 << 0) +#define HL_HIGHLIGHT_STRINGS (1 << 1) + +/*** Data section ***/ + +typedef struct editor_row { + int idx; // Row own index within the file. + int size; // Size of the content (excluding NULL term) + int render_size; // Size of the rendered content + char* chars; // Row content + char* render; // Row content "rendered" for screen (for TABs). + unsigned char* highlight; // This will tell you if a character is part of a string, comment, number... + int hl_open_comment; // True if the line is part of a ML comment. +} editor_row; + +struct editor_syntax { + // file_type field is the name of the filetype that will be displayed + // to the user in the status bar. + char* file_type; + // file_match is an array of strings, where each string contains a + // pattern to match a filename against. If the filename matches, + // then the file will be recognized as having that filetype. + char** file_match; + // This will be a NULL-terminated array of strings, each string containing + // a keyword. To differentiate between the two types of keywords, + // we’ll terminate the second type of keywords with a pipe (|) + // character (also known as a vertical bar). + char** keywords; + // We let each language specify its own single-line comment pattern. + char* singleline_comment_start; + // flags is a bit field that will contain flags for whether to + // highlight numbers and whether to highlight strings for that + // filetype. + char* multiline_comment_start; + char* multiline_comment_end; + int flags; +}; + +struct editor_config { + int cursor_x; + int cursor_y; + int render_x; + int row_offset; // Offset of row displayed. + int col_offset; // Offset of col displayed. + int screen_rows; // Number of rows that we can show + int screen_cols; // Number of cols that we can show + int num_rows; // Number of rows + editor_row* row; + int dirty; // To know if a file has been modified since opening. + char* file_name; + char status_msg[80]; + time_t status_msg_time; + char* copied_char_buffer; + struct editor_syntax* syntax; + struct termios orig_termios; +} ec; + +// Having a dynamic buffer will allow us to write only one +// time once the screen is refreshing, instead of doing +// a lot of write's. +struct a_buf { + char* buf; + int len; +}; + +enum editor_key { + BACKSPACE = 0x7f, // 127 + ARROW_LEFT = 0x3e8, // 1000, large value out of the range of a char. + ARROW_RIGHT, + ARROW_UP, + ARROW_DOWN, + PAGE_UP, + PAGE_DOWN, + HOME_KEY, + END_KEY, + DEL_KEY +}; + +enum editor_highlight { + HL_NORMAL = 0, + HL_SL_COMMENT, + HL_ML_COMMENT, + HL_KEYWORD_1, + HL_KEYWORD_2, + HL_STRING, + HL_NUMBER, + HL_MATCH +}; + +/*** Filetypes ***/ + +char* C_HL_extensions[] = {".c", ".h", ".cpp", ".hpp", ".cc", NULL}; // Array must be terminated with NULL. +char* JAVA_HL_extensions[] = {".java", NULL}; +char* PYTHON_HL_extensions[] = {".py", NULL}; +char* BASH_HL_extensions[] = {".sh", NULL}; +char* JS_HL_extensions[] = {".js", ".jsx", NULL}; +char* PHP_HL_extensions[] = {".php", NULL}; +char* JSON_HL_extensions[] = {".json", ".jsonp", NULL}; +char* XML_HL_extensions[] = {".xml", NULL}; +char* SQL_HL_extensions[] = {".sql", NULL}; +char* RUBY_HL_extensions[] = {".rb", NULL}; + +char* C_HL_keywords[] = { + "switch", "if", "while", "for", "break", "continue", "return", "else", + "struct", "union", "typedef", "static", "enum", "class", "case", "#include", + "volatile", "register", "sizeof", "typedef", "union", "goto", "const", "auto", + "#define", "#if", "#endif", "#error", "#ifdef", "#ifndef", "#undef", + + "int|", "long|", "double|", "float|", "char|", "unsigned|", "signed|", + "void|", "bool|", NULL +}; + +char* JAVA_HL_keywords[] = { + "switch", "if", "while", "for", "break", "continue", "return", "else", + "in", "public", "private", "protected", "static", "final", "abstract", + "enum", "class", "case", "try", "catch", "do", "extends", "implements", + "finally", "import", "instanceof", "interface", "new", "package", "super", + "native", "strictfp", + "synchronized", "this", "throw", "throws", "transient", "volatile", + + "byte|", "char|", "double|", "float|", "int|", "long|", "short|", + "boolean|", NULL +}; + +char* PYTHON_HL_keywords[] = { + "and", "as", "assert", "break", "class", "continue", "def", "del", "elif", + "else", "except", "exec", "finally", "for", "from", "global", "if", "import", + "in", "is", "lambda", "not", "or", "pass", "print", "raise", "return", "try", + "while", "with", "yield", + + "buffer|", "bytearray|", "complex|", "False|", "float|", "frozenset|", "int|", + "list|", "long|", "None|", "set|", "str|", "tuple|", "True|", "type|", + "unicode|", "xrange|", NULL +}; + +char* BASH_HL_keywords[] = { + "case", "do", "done", "elif", "else", "esac", "fi", "for", "function", "if", + "in", "select", "then", "time", "until", "while", "alias", "bg", "bind", "break", + "builtin", "cd", "command", "continue", "declare", "dirs", "disown", "echo", + "enable", "eval", "exec", "exit", "export", "fc", "fg", "getopts", "hash", "help", + "history", "jobs", "kill", "let", "local", "logout", "popd", "pushd", "pwd", "read", + "readonly", "return", "set", "shift", "suspend", "test", "times", "trap", "type", + "typeset", "ulimit", "umask", "unalias", "unset", "wait", "printf", NULL +}; + +char* JS_HL_keywords[] = { + "break", "case", "catch", "class", "const", "continue", "debugger", "default", + "delete", "do", "else", "enum", "export", "extends", "finally", "for", "function", + "if", "implements", "import", "in", "instanceof", "interface", "let", "new", + "package", "private", "protected", "public", "return", "static", "super", "switch", + "this", "throw", "try", "typeof", "var", "void", "while", "with", "yield", "true", + "false", "null", "NaN", "global", "window", "prototype", "constructor", "document", + "isNaN", "arguments", "undefined", + + "Infinity|", "Array|", "Object|", "Number|", "String|", "Boolean|", "Function|", + "ArrayBuffer|", "DataView|", "Float32Array|", "Float64Array|", "Int8Array|", + "Int16Array|", "Int32Array|", "Uint8Array|", "Uint8ClampedArray|", "Uint32Array|", + "Date|", "Error|", "Map|", "RegExp|", "Symbol|", "WeakMap|", "WeakSet|", "Set|", NULL +}; + +char* PHP_HL_keywords[] = { + "__halt_compiler", "break", "clone", "die", "empty", "endswitch", "final", "global", + "include_once", "list", "private", "return", "try", "xor", "abstract", "callable", + "const", "do", "enddeclare", "endwhile", "finally", "goto", "instanceof", "namespace", + "protected", "static", "unset", "yield", "and", "case", "continue", "echo", "endfor", + "eval", "for", "if", "insteadof", "new", "public", "switch", "use", "array", "catch", + "declare", "else", "endforeach", "exit", "foreach", "implements", "interface", "or", + "require", "throw", "var", "as", "class", "default", "elseif", "endif", "extends", + "function", "include", "isset", "print", "require_once", "trait", "while", NULL +}; + +char* JSON_HL_keywords[] = { + NULL +}; + +char* XML_HL_keywords[] = { + NULL +}; + +char* SQL_HL_keywords[] = { + "SELECT", "FROM", "DROP", "CREATE", "TABLE", "DEFAULT", "FOREIGN", "UPDATE", "LOCK", + "INSERT", "INTO", "VALUES", "LOCK", "UNLOCK", "WHERE", "DINSTINCT", "BETWEEN", "NOT", + "NULL", "TO", "ON", "ORDER", "GROUP", "IF", "BY", "HAVING", "USING", "UNION", "UNIQUE", + "AUTO_INCREMENT", "LIKE", "WITH", "INNER", "OUTER", "JOIN", "COLUMN", "DATABASE", "EXISTS", + "NATURAL", "LIMIT", "UNSIGNED", "MAX", "MIN", "PRECISION", "ALTER", "DELETE", "CASCADE", + "PRIMARY", "KEY", "CONSTRAINT", "ENGINE", "CHARSET", "REFERENCES", "WRITE", + + "BIT|", "TINYINT|", "BOOL|", "BOOLEAN|", "SMALLINT|", "MEDIUMINT|", "INT|", "INTEGER|", + "BIGINT|", "DOUBLE|", "DECIMAL|", "DEC|" "FLOAT|", "DATE|", "DATETIME|", "TIMESTAMP|", + "TIME|", "YEAR|", "CHAR|", "VARCHAR|", "TEXT|", "ENUM|", "SET|", "BLOB|", "VARBINARY|", + "TINYBLOB|", "TINYTEXT|", "MEDIUMBLOB|", "MEDIUMTEXT|", "LONGTEXT|", + + "select", "from", "drop", "create", "table", "default", "foreign", "update", "lock", + "insert", "into", "values", "lock", "unlock", "where", "dinstinct", "between", "not", + "null", "to", "on", "order", "group", "if", "by", "having", "using", "union", "unique", + "auto_increment", "like", "with", "inner", "outer", "join", "column", "database", "exists", + "natural", "limit", "unsigned", "max", "min", "precision", "alter", "delete", "cascade", + "primary", "key", "constraint", "engine", "charset", "references", "write", + + "bit|", "tinyint|", "bool|", "boolean|", "smallint|", "mediumint|", "int|", "integer|", + "bigint|", "double|", "decimal|", "dec|" "float|", "date|", "datetime|", "timestamp|", + "time|", "year|", "char|", "varchar|", "text|", "enum|", "set|", "blob|", "varbinary|", + "tinyblob|", "tinytext|", "mediumblob|", "mediumtext|", "longtext|", NULL +}; + +char* RUBY_HL_keywords[] = { + "__ENCODING__", "__LINE__", "__FILE__", "BEGIN", "END", "alias", "and", "begin", "break", + "case", "class", "def", "defined?", "do", "else", "elsif", "end", "ensure", "for", "if", + "in", "module", "next", "not", "or", "redo", "rescue", "retry", "return", "self", "super", + "then", "undef", "unless", "until", "when", "while", "yield", NULL +}; + +struct editor_syntax HL_DB[] = { + { + "c", + C_HL_extensions, + C_HL_keywords, + "//", + "/*", + "*/", + HL_HIGHLIGHT_NUMBERS | HL_HIGHLIGHT_STRINGS + }, + { + "java", + JAVA_HL_extensions, + JAVA_HL_keywords, + "//", + "/*", + "*/", + HL_HIGHLIGHT_NUMBERS | HL_HIGHLIGHT_STRINGS + }, + { + "python", + PYTHON_HL_extensions, + PYTHON_HL_keywords, + "#", + "'''", + "'''", + HL_HIGHLIGHT_NUMBERS | HL_HIGHLIGHT_STRINGS + }, + { + "bash", + BASH_HL_extensions, + BASH_HL_keywords, + "#", + NULL, + NULL, + HL_HIGHLIGHT_NUMBERS | HL_HIGHLIGHT_STRINGS + }, + { + "js", + JS_HL_extensions, + JS_HL_keywords, + "//", + "/*", + "*/", + HL_HIGHLIGHT_NUMBERS | HL_HIGHLIGHT_STRINGS + }, + { + "php", + PHP_HL_extensions, + PHP_HL_keywords, + "//", + "/*", + "*/", + HL_HIGHLIGHT_NUMBERS | HL_HIGHLIGHT_STRINGS + }, + { + "json", + JSON_HL_extensions, + JSON_HL_keywords, + NULL, + NULL, + NULL, + HL_HIGHLIGHT_NUMBERS | HL_HIGHLIGHT_STRINGS + }, + { + "xml", + XML_HL_extensions, + XML_HL_keywords, + NULL, + NULL, + NULL, + HL_HIGHLIGHT_NUMBERS | HL_HIGHLIGHT_STRINGS + }, + { + "sql", + SQL_HL_extensions, + SQL_HL_keywords, + "--", + "/*", + "*/", + HL_HIGHLIGHT_NUMBERS | HL_HIGHLIGHT_STRINGS + }, + { + "ruby", + RUBY_HL_extensions, + RUBY_HL_keywords, + "#", + "=begin", + "=end", + HL_HIGHLIGHT_NUMBERS | HL_HIGHLIGHT_STRINGS + } +}; + +// Size of the "Hightlight Database" (HL_DB). +#define HL_DB_ENTRIES (sizeof(HL_DB) / sizeof(HL_DB[0])) + +/*** Declarations section ***/ + +void editorClearScreen(); + +void editorRefreshScreen(); + +void editorSetStatusMessage(const char* msg, ...); + +void consoleBufferOpen(); + +void abufFree(); + +void abufAppend(); + +char *editorPrompt(char* prompt, void (*callback)(char*, int)); + +void editorRowAppendString(editor_row* row, char* s, size_t len); + +void editorInsertNewline(); + +/*** Terminal section ***/ + +void die(const char* s) { + editorClearScreen(); + // perror looks for global errno variable and then prints + // a descriptive error mesage for it. + perror(s); + printf("\r\n"); + exit(1); +} + +void disableRawMode() { + if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &ec.orig_termios) == -1) + die("Failed to disable raw mode"); +} + +void enableRawMode() { + // Save original terminal state into orig_termios. + if (tcgetattr(STDIN_FILENO, &ec.orig_termios) == -1) + die("Failed to get current terminal state"); + // At exit, restore the original state. + atexit(disableRawMode); + + // Modify the original state to enter in raw mode. + struct termios raw = ec.orig_termios; + // This disables Ctrl-M, Ctrl-S and Ctrl-Q commands. + // (BRKINT, INPCK and ISTRIP are not estrictly mandatory, + // but it is recommended to turn them off in case any + // system needs it). + raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); + // Turning off all output processing (\r\n). + raw.c_oflag &= ~(OPOST); + // Setting character size to 8 bits per byte (it should be + // like that on most systems, but whatever). + raw.c_cflag |= (CS8); + // Using NOT operator on ECHO | ICANON | IEXTEN | ISIG and + // then bitwise-AND them with flags field in order to + // force c_lflag 4th bit to become 0. This disables + // chars being printed (ECHO) and let us turn off + // canonical mode in order to read input byte-by-byte + // instead of line-by-line (ICANON), ISIG disables + // Ctrl-C command and IEXTEN the Ctrl-V one. + raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); + // read() function now returns as soon as there is any + // input to be read. + raw.c_cc[VMIN] = 0; + // Forcing read() function to return every 1/10 of a + // second if there is nothing to read. + raw.c_cc[VTIME] = 1; + + consoleBufferOpen(); + + if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1) + die("Failed to set raw mode"); +} + +int editorReadKey() { + int nread; + char c; + while ((nread = read(STDIN_FILENO, &c, 1)) != 1) { + // Ignoring EAGAIN to make it work on Cygwin. + if (nread == -1 && errno != EAGAIN) + die("Error reading input"); + } + + // Check escape sequences, if first byte + // is an escape character then... + if (c == '\x1b') { + char seq[3]; + + if (read(STDIN_FILENO, &seq[0], 1) != 1 || + read(STDIN_FILENO, &seq[1], 1) != 1) + return '\x1b'; + + if (seq[0] == '[') { + if (seq[1] >= '0' && seq[1] <= '9') { + if (read(STDIN_FILENO, &seq[2], 1) != 1) + return '\x1b'; + if (seq[2] == '~') { + switch (seq[1]) { + // Home and End keys may be sent in many ways depending on the OS + // \x1b[1~, \x1b[7~, \x1b[4~, \x1b[8~ + case '1': + case '7': + return HOME_KEY; + case '4': + case '8': + return END_KEY; + // Del key is sent as \x1b[3~ + case '3': + return DEL_KEY; + // Page Up and Page Down send '\x1b', '[', '5' or '6' and '~'. + case '5': return PAGE_UP; + case '6': return PAGE_DOWN; + } + } + } else { + switch (seq[1]) { + // Arrow keys send multiple bytes starting with '\x1b', '['' + // and followed by an 'A', 'B', 'C' or 'D' depending on which + // arrow is pressed. + case 'A': return ARROW_UP; + case 'B': return ARROW_DOWN; + case 'C': return ARROW_RIGHT; + case 'D': return ARROW_LEFT; + // Home key can also be sent as \x1b[H + case 'H': return HOME_KEY; + // End key can also be sent as \x1b[F + case 'F': return END_KEY; + } + } + } else if (seq[0] == 'O') { + switch (seq[1]) { + // Yes, Home key can ALSO be sent as \x1bOH + case 'H': return HOME_KEY; + // And... End key as \x1bOF + case 'F': return END_KEY; + } + } + return '\x1b'; + } else { + return c; + } +} + +int getWindowSize(int* screen_rows, int* screen_cols) { + struct winsize ws; + + // Getting window size thanks to ioctl into the given + // winsize struct. + if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) { + return -1; + } else { + *screen_cols = ws.ws_col; + *screen_rows = ws.ws_row; + return 0; + } +} + +void editorUpdateWindowSize() { + if (getWindowSize(&ec.screen_rows, &ec.screen_cols) == -1) + die("Failed to get window size"); + ec.screen_rows -= 2; // Room for the status bar. +} + +void editorHandleSigwinch() { + editorUpdateWindowSize(); + if (ec.cursor_y > ec.screen_rows) + ec.cursor_y = ec.screen_rows - 1; + if (ec.cursor_x > ec.screen_cols) + ec.cursor_x = ec.screen_cols - 1; + editorRefreshScreen(); +} + +void editorHandleSigcont() { + disableRawMode(); + consoleBufferOpen(); + enableRawMode(); + editorRefreshScreen(); +} + +void consoleBufferOpen() { + // Switch to another terminal buffer in order to be able to restore state at exit + // by calling consoleBufferClose(). + if (write(STDOUT_FILENO, "\x1b[?47h", 6) == -1) + die("Error changing terminal buffer"); +} + +void consoleBufferClose() { + // Restore console to the state tte opened. + if (write(STDOUT_FILENO, "\x1b[?9l", 5) == -1 || + write(STDOUT_FILENO, "\x1b[?47l", 6) == -1) + die("Error restoring buffer state"); + + /*struct a_buf ab = {.buf = NULL, .len = 0}; + char* buf = NULL; + if (asprintf(&buf, "\x1b[%d;%dH\r\n", ec.screen_rows + 1, 1) == -1) + die("Error restoring buffer state"); + abufAppend(&ab, buf, strlen(buf)); + free(buf); + + if (write(STDOUT_FILENO, ab.buf, ab.len) == -1) + die("Error restoring buffer state"); + abufFree(&ab);*/ + + editorClearScreen(); +} + +/*** Syntax highlighting ***/ + +int isSeparator(int c) { + // strchr() looks to see if any one of the characters in the first string + // appear in the second string. If so, it returns a pointer to the + // character in the second string that matched. Otherwise, it + // returns NULL. + return isspace(c) || c == '\0' || strchr(",.()+-/*=~%<>[]:;", c) != NULL; +} + +int isAlsoNumber(int c) { + return c == '.' || c == 'x' || c == 'a' || c == 'b' || c == 'c' || c == 'd' || c == 'e' || c == 'f'; +} + +void editorUpdateSyntax(editor_row* row) { + row -> highlight = realloc(row -> highlight, row -> render_size); + // void * memset ( void * ptr, int value, size_t num ); + // Sets the first num bytes of the block of memory pointed by ptr to + // the specified value. With this we set all characters to HL_NORMAL. + memset(row -> highlight, HL_NORMAL, row -> render_size); + + if (ec.syntax == NULL) + return; + + char** keywords = ec.syntax -> keywords; + + char* scs = ec.syntax -> singleline_comment_start; + char* mcs = ec.syntax -> multiline_comment_start; + char* mce = ec.syntax -> multiline_comment_end; + + int scs_len = scs ? strlen(scs) : 0; + int mcs_len = mcs ? strlen(mcs) : 0; + int mce_len = mce ? strlen(mce) : 0; + + int prev_sep = 1; // True (1) if the previous char is a separator, false otherwise. + int in_string = 0; // If != 0, inside a string. We also keep track if it's ' or " + int in_comment = (row -> idx > 0 && ec.row[row -> idx - 1].hl_open_comment); // This is ONLY used on ML comments. + + int i = 0; + while (i < row -> render_size) { + char c = row -> render[i]; + // Highlight type of the previous character. + unsigned char prev_highlight = (i > 0) ? row -> highlight[i - 1] : HL_NORMAL; + + if (scs_len && !in_string && !in_comment) { + // int strncmp ( const char * str1, const char * str2, size_t num ); + // Compares up to num characters of the C string str1 to those of the C string str2. + // This function starts comparing the first character of each string. If they are + // equal to each other, it continues with the following pairs until the characters + // differ, until a terminating null-character is reached, or until num characters + // match in both strings, whichever happens first. + if (!strncmp(&row -> render[i], scs, scs_len)) { + memset(&row -> highlight[i], HL_SL_COMMENT, row -> render_size - i); + break; + } + } + + if (mcs_len && mce_len && !in_string) { + if (in_comment) { + row -> highlight[i] = HL_ML_COMMENT; + if (!strncmp(&row -> render[i], mce, mce_len)) { + memset(&row -> highlight[i], HL_ML_COMMENT, mce_len); + i += mce_len; + in_comment = 0; + prev_sep = 1; + continue; + } else { + i++; + continue; + } + } else if (!strncmp(&row -> render[i], mcs, mcs_len)) { + memset(&row -> highlight[i], HL_ML_COMMENT, mcs_len); + i += mcs_len; + in_comment = 1; + continue; + } + + } + + if (ec.syntax -> flags & HL_HIGHLIGHT_STRINGS) { + if (in_string) { + row -> highlight[i] = HL_STRING; + // If we’re in a string and the current character is a backslash (\), + // and there’s at least one more character in that line that comes + // after the backslash, then we highlight the character that comes + // after the backslash with HL_STRING and consume it. We increment + // i by 2 to consume both characters at once. + if (c == '\\' && i + 1 < row -> render_size) { + row -> highlight[i + 1] = HL_STRING; + i += 2; + continue; + } + + if (c == in_string) + in_string = 0; + i++; + prev_sep = 1; + continue; + } else { + if (c == '"' || c == '\'') { + in_string = c; + row -> highlight[i] = HL_STRING; + i++; + continue; + } + } + } + + if (ec.syntax -> flags & HL_HIGHLIGHT_NUMBERS) { + if ((isdigit(c) && (prev_sep || prev_highlight == HL_NUMBER)) || + (isAlsoNumber(c) && prev_highlight == HL_NUMBER)) { + row -> highlight[i] = HL_NUMBER; + i++; + prev_sep = 0; + continue; + } + } + + if (prev_sep) { + int j; + for (j = 0; keywords[j]; j++) { + int kw_len = strlen(keywords[j]); + int kw_2 = keywords[j][kw_len - 1] == '|'; + if (kw_2) + kw_len--; + + // Keywords require a separator both before and after the keyword. + if (!strncmp(&row -> render[i], keywords[j], kw_len) && + isSeparator(row -> render[i + kw_len])) { + memset(&row -> highlight[i], kw_2 ? HL_KEYWORD_2 : HL_KEYWORD_1, kw_len); + i += kw_len; + break; + } + } + if (keywords[j] != NULL) { + prev_sep = 0; + continue; + } + } + + prev_sep = isSeparator(c); + i++; + } + + int changed = (row -> hl_open_comment != in_comment); + // This tells us whether the row ended as an unclosed multi-line + // comment or not. + row -> hl_open_comment = in_comment; + // A user could comment out an entire file just by changing one line. + // So it seems like we need to update the syntax of all the lines + // following the current line. However, we know the highlighting + // of the next line will not change if the value of this line’s + // // // hl_open_comment did not change. So we check if it changed, and + // // only call editorUpdateSyntax() on the next line if + // hl_open_comment changed (and if there is a next line in the file). + // Because editorUpdateSyntax() keeps calling itself with the next + // line, the change will continue to propagate to more and more lines + // until one of them is unchanged, at which point we know that all + // the lines after that one must be unchanged as well. + if (changed && row -> idx + 1 < ec.num_rows) + editorUpdateSyntax(&ec.row[row -> idx + 1]); +} + +int editorSyntaxToColor(int highlight) { + // We return ANSI codes for colors. + // See https://en.wikipedia.org/wiki/ANSI_escape_code#Colors + // for a list of them. + switch (highlight) { + case HL_SL_COMMENT: + case HL_ML_COMMENT: return 36; + case HL_KEYWORD_1: return 31; + case HL_KEYWORD_2: return 32; + case HL_STRING: return 33; + case HL_NUMBER: return 35; + case HL_MATCH: return 34; + default: return 37; + } +} + +void editorSelectSyntaxHighlight() { + ec.syntax = NULL; + if (ec.file_name == NULL) + return; + + for (unsigned int j = 0; j < HL_DB_ENTRIES; j++) { + struct editor_syntax* es = &HL_DB[j]; + unsigned int i = 0; + + while (es -> file_match[i]) { + char* p = strstr(ec.file_name, es -> file_match[i]); + if (p != NULL) { + // Returns a pointer to the first occurrence of str2 in str1, + // or a null pointer if str2 is not part of str1. + int pat_len = strlen(es -> file_match[i]); + if (es -> file_match[i][0] != '.' || p[pat_len] == '\0') { + ec.syntax = es; + + int file_row; + for (file_row = 0; file_row < ec.num_rows; file_row++) { + editorUpdateSyntax(&ec.row[file_row]); + } + + return; + } + } + i++; + } + } +} + +/*** Row operations ***/ + +int editorRowCursorXToRenderX(editor_row* row, int cursor_x) { + int render_x = 0; + int j; + // For each character, if its a tab we use rx % TTE_TAB_STOP + // to find out how many columns we are to the right of the last + // tab stop, and then subtract that from TTE_TAB_STOP - 1 to + // find out how many columns we are to the left of the next tab + // stop. We add that amount to rx to get just to the left of the + // next tab stop, and then the unconditional rx++ statement gets + // us right on the next tab stop. Notice how this works even if + // we are currently on a tab stop. + for (j = 0; j < cursor_x; j++) { + if (row -> chars[j] == '\t') + render_x += (TTE_TAB_STOP - 1) - (render_x % TTE_TAB_STOP); + render_x++; + } + return render_x; +} + +int editorRowRenderXToCursorX(editor_row* row, int render_x) { + int cur_render_x = 0; + int cursor_x; + for (cursor_x = 0; cursor_x < row -> size; cursor_x++) { + if (row -> chars[cursor_x] == '\t') + cur_render_x += (TTE_TAB_STOP - 1) - (cur_render_x % TTE_TAB_STOP); + cur_render_x++; + + if (cur_render_x > render_x) + return cursor_x; + } + return cursor_x; +} + +void editorUpdateRow(editor_row* row) { + // First, we have to loop through the chars of the row + // and count the tabs in order to know how much memory + // to allocate for render. The maximum number of characters + // needed for each tab is 8. row->size already counts 1 for + // each tab, so we multiply the number of tabs by 7 and add + // that to row->size to get the maximum amount of memory we'll + // need for the rendered row. + int tabs = 0; + int j; + for (j = 0; j < row -> size; j++) { + if (row -> chars[j] == '\t') + tabs++; + } + free(row -> render); + row -> render = malloc(row -> size + tabs * (TTE_TAB_STOP - 1) + 1); + + // After allocating the memory, we check whether the current character + // is a tab. If it is, we append one space (because each tab must + // advance the cursor forward at least one column), and then append + // spaces until we get to a tab stop, which is a column that is + // divisible by 8 + int idx = 0; + for (j = 0; j < row -> size; j++) { + if (row -> chars[j] == '\t') { + row -> render[idx++] = ' '; + while (idx % TTE_TAB_STOP != 0) + row -> render[idx++] = ' '; + } else + row -> render[idx++] = row -> chars[j]; + } + row -> render[idx] = '\0'; + row -> render_size = idx; + + editorUpdateSyntax(row); +} + +void editorInsertRow(int at, char* s, size_t line_len) { + if (at < 0 || at > ec.num_rows) + return; + + ec.row = realloc(ec.row, sizeof(editor_row) * (ec.num_rows + 1)); + memmove(&ec.row[at + 1], &ec.row[at], sizeof(editor_row) * (ec.num_rows - at)); + + for (int j = at + 1; j <= ec.num_rows; j++) { + ec.row[j].idx++; + } + + ec.row[at].idx = at; + + ec.row[at].size = line_len; + ec.row[at].chars = malloc(line_len + 1); // We want to add terminator char '\0' at the end + memcpy(ec.row[at].chars, s, line_len); + ec.row[at].chars[line_len] = '\0'; + + ec.row[at].render_size = 0; + ec.row[at].render = NULL; + ec.row[at].highlight = NULL; + ec.row[at].hl_open_comment = 0; + editorUpdateRow(&ec.row[at]); + + ec.num_rows++; + ec.dirty++; +} + +void editorFreeRow(editor_row* row) { + free(row -> render); + free(row -> chars); + free(row -> highlight); +} + +void editorDelRow(int at) { + if (at < 0 || at >= ec.num_rows) + return; + editorFreeRow(&ec.row[at]); + memmove(&ec.row[at], &ec.row[at + 1], sizeof(editor_row) * (ec.num_rows - at - 1)); + + for (int j = at; j < ec.num_rows - 1; j++) { + ec.row[j].idx--; + } + + ec.num_rows--; + ec.dirty++; +} + +// -1 down, 1 up +void editorFlipRow(int dir) { + editor_row c_row = ec.row[ec.cursor_y]; + ec.row[ec.cursor_y] = ec.row[ec.cursor_y - dir]; + ec.row[ec.cursor_y - dir] = c_row; + + ec.row[ec.cursor_y].idx += dir; + ec.row[ec.cursor_y - dir].idx -= dir; + + int first = (dir == 1) ? ec.cursor_y - 1 : ec.cursor_y; + editorUpdateSyntax(&ec.row[first]); + editorUpdateSyntax(&ec.row[first] + 1); + if (ec.num_rows - ec.cursor_y > 2) + editorUpdateSyntax(&ec.row[first] + 2); + + ec.cursor_y -= dir; + ec.dirty++; +} + +void editorCopy(int cut) { + ec.copied_char_buffer = realloc(ec.copied_char_buffer, strlen(ec.row[ec.cursor_y].chars) + 1); + strcpy(ec.copied_char_buffer, ec.row[ec.cursor_y].chars); + editorSetStatusMessage(cut ? "Content cut" : "Content copied"); +} + +void editorCut() { + editorCopy(-1); + editorDelRow(ec.cursor_y); + if (ec.num_rows - ec.cursor_y > 0) + editorUpdateSyntax(&ec.row[ec.cursor_y]); + if (ec.num_rows - ec.cursor_y > 1) + editorUpdateSyntax(&ec.row[ec.cursor_y + 1]); + ec.cursor_x = ec.cursor_y == ec.num_rows ? 0 : ec.row[ec.cursor_y].size; +} + +void editorPaste() { + if (ec.copied_char_buffer == NULL) + return; + + if (ec.cursor_y == ec.num_rows) + editorInsertRow(ec.cursor_y, ec.copied_char_buffer, strlen(ec.copied_char_buffer)); + else + editorRowAppendString(&ec.row[ec.cursor_y], ec.copied_char_buffer, strlen(ec.copied_char_buffer)); + ec.cursor_x += strlen(ec.copied_char_buffer); +} + +void editorRowInsertChar(editor_row* row, int at, int c) { + if (at < 0 || at > row -> size) + at = row -> size; + // We need to allocate 2 bytes because we also have to make room for + // the null byte. + row -> chars = realloc(row -> chars, row -> size + 2); + // memmove it's like memcpy(), but is safe to use when the source and + // destination arrays overlap + memmove(&row -> chars[at + 1], &row -> chars[at], row -> size - at + 1); + row -> size++; + row -> chars[at] = c; + editorUpdateRow(row); + ec.dirty++; // This way we can see "how dirty" a file is. +} + +void editorInsertNewline() { + // If we're at the beginning of a line, all we have to do is insert + // a new blank row before the line we're on. + if (ec.cursor_x == 0) { + editorInsertRow(ec.cursor_y, "", 0); + // Otherwise, we have to split the line we're on into two rows. + } else { + editor_row* row = &ec.row[ec.cursor_y]; + editorInsertRow(ec.cursor_y + 1, &row -> chars[ec.cursor_x], row -> size - ec.cursor_x); + row = &ec.row[ec.cursor_y]; + row -> size = ec.cursor_x; + row -> chars[row -> size] = '\0'; + editorUpdateRow(row); + } + ec.cursor_y++; + ec.cursor_x = 0; +} + +void editorRowAppendString(editor_row* row, char* s, size_t len) { + row -> chars = realloc(row -> chars, row -> size + len + 1); + memcpy(&row -> chars[row -> size], s, len); + row -> size += len; + row -> chars[row -> size] = '\0'; + editorUpdateRow(row); + ec.dirty++; +} + +void editorRowDelChar(editor_row* row, int at) { + if (at < 0 || at >= row -> size) + return; + // Overwriting the deleted character with the characters that come + // after it. + memmove(&row -> chars[at], &row -> chars[at + 1], row -> size - at); + row -> size--; + editorUpdateRow(row); + ec.dirty++; +} + +/*** Editor operations ***/ + +void editorInsertChar(int c) { + // If this is true, the cursor is on the tilde line after the end of + // the file, so we need to append a new row to the file before inserting + // a character there. + if (ec.cursor_y == ec.num_rows) + editorInsertRow(ec.num_rows, "", 0); + editorRowInsertChar(&ec.row[ec.cursor_y], ec.cursor_x, c); + ec.cursor_x++; // This way we can see "how dirty" a file is. +} + +void editorDelChar() { + // If the cursor is past the end of the file, there's nothing to delete. + if (ec.cursor_y == ec.num_rows) + return; + // Cursor is at the beginning of a file, there's nothing to delete. + if (ec.cursor_x == 0 && ec.cursor_y == 0) + return; + + editor_row* row = &ec.row[ec.cursor_y]; + if (ec.cursor_x > 0) { + editorRowDelChar(row, ec.cursor_x - 1); + ec.cursor_x--; + // Deleting a line and moving up all the content. + } else { + ec.cursor_x = ec.row[ec.cursor_y - 1].size; + editorRowAppendString(&ec.row[ec.cursor_y -1], row -> chars, row -> size); + editorDelRow(ec.cursor_y); + ec.cursor_y--; + } +} + +/*** File I/O ***/ + +char* editorRowsToString(int* buf_len) { + int total_len = 0; + int j; + // Adding up the lengths of each row of text, adding 1 + // to each one for the newline character we'll add to + // the end of each line. + for (j = 0; j < ec.num_rows; j++) { + total_len += ec.row[j].size + 1; + } + *buf_len = total_len; + + char* buf = malloc(total_len); + char* p = buf; + // Copying the contents of each row to the end of the + // buffer, appending a newline character after each + // row. + for (j = 0; j < ec.num_rows; j++) { + memcpy(p, ec.row[j].chars, ec.row[j].size); + p += ec.row[j].size; + *p = '\n'; + p++; + } + + return buf; +} + +void editorOpen(char* file_name) { + free(ec.file_name); + ec.file_name = strdup(file_name); + + editorSelectSyntaxHighlight(); + + FILE* file = fopen(file_name, "r+"); + if (!file) + die("Failed to open the file"); + + char* line = NULL; + // Unsigned int of at least 16 bit. + size_t line_cap = 0; + // Bigger than int + ssize_t line_len; + while ((line_len = getline(&line, &line_cap, file)) != -1) { + // We already know each row represents one line of text, there's no need + // to keep carriage return and newline characters. + if (line_len > 0 && (line[line_len - 1] == '\n' || line[line_len - 1] == '\r')) + line_len--; + editorInsertRow(ec.num_rows, line, line_len); + } + free(line); + fclose(file); + ec.dirty = 0; +} + +void editorSave() { + if (ec.file_name == NULL) { + ec.file_name = editorPrompt("Save as: %s (ESC to cancel)", NULL); + if (ec.file_name == NULL) { + editorSetStatusMessage("Save aborted"); + return; + } + editorSelectSyntaxHighlight(); + } + + int len; + char* buf = editorRowsToString(&len); + + // We want to create if it doesn't already exist (O_CREAT flag), giving + // 0644 permissions (the standard ones). O_RDWR stands for reading and + // writing. + int fd = open(ec.file_name, O_RDWR | O_CREAT, 0644); + if (fd != -1) { + // ftruncate sets the file's size to the specified length. + if (ftruncate(fd, len) != -1) { + // Writing the file. + if (write(fd, buf, len) == len) { + close(fd); + free(buf); + ec.dirty = 0; + editorSetStatusMessage("%d bytes written to disk", len); + return; + } + } + close(fd); + } + + free(buf); + editorSetStatusMessage("Cant's save file. Error occurred: %s", strerror(errno)); +} + +/*** Search section ***/ + +void editorSearchCallback(char* query, int key) { + // Index of the row that the last match was on, -1 if there was + // no last match. + static int last_match = -1; + // 1 for searching forward and -1 for searching backwards. + static int direction = 1; + + static int saved_highlight_line; + static char* saved_hightlight = NULL; + + if (saved_hightlight) { + memcpy(ec.row[saved_highlight_line].highlight, saved_hightlight, ec.row[saved_highlight_line].render_size); + free(saved_hightlight); + saved_hightlight = NULL; + } + + // Checking if the user pressed Enter or Escape, in which case + // they are leaving search mode so we return immediately. + if (key == '\r' || key == '\x1b') { + last_match = -1; + direction = 1; + return; + } else if (key == ARROW_RIGHT || key == ARROW_DOWN) { + direction = 1; + } else if (key == ARROW_LEFT || key == ARROW_UP) { + if (last_match == -1) { + // If nothing matched and the left or up arrow key was pressed + return; + } + direction = -1; + } else { + last_match = -1; + direction = 1; + } + + int current = last_match; + int i; + for (i = 0; i < ec.num_rows; i++) { + current += direction; + if (current == -1) + current = ec.num_rows - 1; + else if (current == ec.num_rows) + current = 0; + + editor_row* row = &ec.row[current]; + // We use strstr to check if query is a substring of the + // current row. It returns NULL if there is no match, + // oterwhise it returns a pointer to the matching substring. + char* match = strstr(row -> render, query); + if (match) { + last_match = current; + ec.cursor_y = current; + ec.cursor_x = editorRowRenderXToCursorX(row, match - row -> render); + // We set this like so to scroll to the bottom of the file so + // that the next screen refresh will cause the matching line to + // be at the very top of the screen. + ec.row_offset = ec.num_rows; + + saved_highlight_line = current; + saved_hightlight = malloc(row -> render_size); + memcpy(saved_hightlight, row -> highlight, row -> render_size); + memset(&row -> highlight[match - row -> render], HL_MATCH, strlen(query)); + break; + } + } +} + +void editorSearch() { + int saved_cursor_x = ec.cursor_x; + int saved_cursor_y = ec.cursor_y; + int saved_col_offset = ec.col_offset; + int saved_row_offset = ec.row_offset; + + char* query = editorPrompt("Search: %s (Use ESC / Enter / Arrows)", editorSearchCallback); + + if (query) { + free(query); + // If query is NULL, that means they pressed Escape, so in that case we + // restore the cursor previous position. + } else { + ec.cursor_x = saved_cursor_x; + ec.cursor_y = saved_cursor_y; + ec.col_offset = saved_col_offset; + ec.row_offset = saved_row_offset; + } +} + +/*** Append buffer section **/ + +void abufAppend(struct a_buf* ab, const char* s, int len) { + // Using realloc to get a block of free memory that is + // the size of the current string + the size of the string + // to be appended. + char* new = realloc(ab -> buf, ab -> len + len); + + if (new == NULL) + return; + + // Copying the string s at the end of the current data in + // the buffer. + memcpy(&new[ab -> len], s, len); + ab -> buf = new; + ab -> len += len; +} + +void abufFree(struct a_buf* ab) { + // Deallocating buffer. + free(ab -> buf); +} + +/*** Output section ***/ + +void editorScroll() { + ec.render_x = 0; + if (ec.cursor_y < ec.num_rows) + ec.render_x = editorRowCursorXToRenderX(&ec.row[ec.cursor_y], ec.cursor_x); + // The first if statement checks if the cursor is above the visible window, + // and if so, scrolls up to where the cursor is. The second if statement checks + // if the cursor is past the bottom of the visible window, and contains slightly + // more complicated arithmetic because ec.row_offset refers to what's at the top + // of the screen, and we have to get ec.screen_rows involved to talk about what's + // at the bottom of the screen. + if (ec.cursor_y < ec.row_offset) + ec.row_offset = ec.cursor_y; + if (ec.cursor_y >= ec.row_offset + ec.screen_rows) + ec.row_offset = ec.cursor_y - ec.screen_rows + 1; + + if (ec.render_x < ec.col_offset) + ec.col_offset = ec.render_x; + if (ec.render_x >= ec.col_offset + ec.screen_cols) + ec.col_offset = ec.render_x - ec.screen_cols + 1; +} + +void editorDrawStatusBar(struct a_buf* ab) { + // This switches to inverted colors. + // NOTE: + // The m command (Select Graphic Rendition) causes the text printed + // after it to be printed with various possible attributes including + // bold (1), underscore (4), blink (5), and inverted colors (7). An + // argument of 0 clears all attributes (the default one). See + // http://vt100.net/docs/vt100-ug/chapter3.html#SGR for more info. + abufAppend(ab, "\x1b[7m", 4); + + char status[80], r_status[80]; + // Showing up to 20 characters of the filename, followed by the number of lines. + int len = snprintf(status, sizeof(status), " Editing: %.20s %s", ec.file_name ? ec.file_name : "New file", ec.dirty ? "(modified)" : ""); + int col_size = ec.row && ec.cursor_y <= ec.num_rows - 1 ? col_size = ec.row[ec.cursor_y].size : 0; + int r_len = snprintf(r_status, sizeof(r_status), "%d/%d lines %d/%d cols ", ec.cursor_y + 1 > ec.num_rows ? ec.num_rows : ec.cursor_y + 1, ec.num_rows, + ec.cursor_x + 1 > col_size ? col_size : ec.cursor_x + 1, col_size); + if (len > ec.screen_cols) + len = ec.screen_cols; + abufAppend(ab, status, len); + while (len < ec.screen_cols) { + if (ec.screen_cols - len == r_len) { + abufAppend(ab, r_status, r_len); + break; + } else { + abufAppend(ab, " ", 1); + len++; + } + } + // This switches back to normal colors. + abufAppend(ab, "\x1b[m", 3); + + abufAppend(ab, "\r\n", 2); +} + +void editorDrawMessageBar(struct a_buf *ab) { + // Clearing the message bar. + abufAppend(ab, "\x1b[K", 3); + int msg_len = strlen(ec.status_msg); + if (msg_len > ec.screen_cols) + msg_len = ec.screen_cols; + // We only show the message if its less than 5 secons old, but + // remember the screen is only being refreshed after each keypress. + if (msg_len && time(NULL) - ec.status_msg_time < 5) + abufAppend(ab, ec.status_msg, msg_len); +} + +void editorDrawWelcomeMessage(struct a_buf* ab) { + char welcome[80]; + // Using snprintf to truncate message in case the terminal + // is too tiny to handle the entire string. + int welcome_len = snprintf(welcome, sizeof(welcome), + "tte %s ", TTE_VERSION); + if (welcome_len > ec.screen_cols) + welcome_len = ec.screen_cols; + // Centering the message. + int padding = (ec.screen_cols - welcome_len) / 2; + // Remember that everything != 0 is true. + if (padding) { + abufAppend(ab, "~", 1); + padding--; + } + while (padding--) + abufAppend(ab, " ", 1); + abufAppend(ab, welcome, welcome_len); +} + +// The ... argument makes editorSetStatusMessage() a variadic function, +// meaning it can take any number of arguments. C's way of dealing with +// these arguments is by having you call va_start() and va_end() on a +// // value of type va_list. The last argument before the ... (in this +// case, msg) must be passed to va_start(), so that the address of +// the next arguments is known. Then, between the va_start() and +// va_end() calls, you would call va_arg() and pass it the type of +// the next argument (which you usually get from the given format +// string) and it would return the value of that argument. In +// this case, we pass msg and args to vsnprintf() and it takes care +// of reading the format string and calling va_arg() to get each +// argument. +void editorSetStatusMessage(const char* msg, ...) { + va_list args; + va_start(args, msg); + vsnprintf(ec.status_msg, sizeof(ec.status_msg), msg, args); + va_end(args); + ec.status_msg_time = time(NULL); +} + +void editorDrawRows(struct a_buf* ab) { + int y; + for (y = 0; y < ec.screen_rows; y++) { + int file_row = y + ec.row_offset; + if(file_row >= ec.num_rows) { + if (ec.num_rows == 0 && y == ec.screen_rows / 3) + editorDrawWelcomeMessage(ab); + else + abufAppend(ab, "~", 1); + } else { + int len = ec.row[file_row].render_size - ec.col_offset; + // len can be a negative number, meaning the user scrolled + // horizontally past the end of the line. In that case, we set + // len to 0 so that nothing is displayed on that line. + if (len < 0) + len = 0; + if (len > ec.screen_cols) + len = ec.screen_cols; + + char* c = &ec.row[file_row].render[ec.col_offset]; + unsigned char* highlight = &ec.row[file_row].highlight[ec.col_offset]; + int current_color = -1; + int j; + for (j = 0; j < len; j++) { + // Displaying nonprintable characters as (A-Z, @, and ?). + if (iscntrl(c[j])) { + char sym = (c[j] <= 26) ? '@' + c[j] : '?'; + abufAppend(ab, "\x1b[7m", 4); + abufAppend(ab, &sym, 1); + abufAppend(ab, "\x1b[m", 3); + if (current_color != -1) { + char buf[16]; + int c_len = snprintf(buf, sizeof(buf), "\x1b[%dm", current_color); + abufAppend(ab, buf, c_len); + } + } else if (highlight[j] == HL_NORMAL) { + if (current_color != -1) { + abufAppend(ab, "\x1b[39m", 5); + current_color = -1; + } + abufAppend(ab, &c[j], 1); + } else { + int color = editorSyntaxToColor(highlight[j]); + // We only use escape sequence if the new color is different + // from the last character's color. + if (color != current_color) { + current_color = color; + char buf[16]; + int c_len = snprintf(buf, sizeof(buf), "\x1b[%dm", color); + abufAppend(ab, buf, c_len); + } + + abufAppend(ab, &c[j], 1); + } + } + abufAppend(ab, "\x1b[39m", 5); + } + + // Redrawing each line instead of the whole screen. + abufAppend(ab, "\x1b[K", 3); + // Addind a new line + abufAppend(ab, "\r\n", 2); + } +} + +void editorRefreshScreen() { + editorScroll(); + + struct a_buf ab = ABUF_INIT; + + // Hiding the cursor while the screen is refreshing. + // See http://vt100.net/docs/vt100-ug/chapter3.html#S3.3.4 + // for more info. + abufAppend(&ab, "\x1b[?25l", 6); + abufAppend(&ab, "\x1b[H", 3); + + editorDrawRows(&ab); + editorDrawStatusBar(&ab); + editorDrawMessageBar(&ab); + + // Moving the cursor where it should be. + char buf[32]; + snprintf(buf, sizeof(buf), "\x1b[%d;%dH", (ec.cursor_y - ec.row_offset) + 1, (ec.render_x - ec.col_offset) + 1); + abufAppend(&ab, buf, strlen(buf)); + + // Showing again the cursor. + abufAppend(&ab, "\x1b[?25h", 6); + + // Writing all content at once + write(STDOUT_FILENO, ab.buf, ab.len); + abufFree(&ab); +} + +void editorClearScreen() { + // Writing 4 bytes out to the terminal: + // - (1 byte) \x1b : escape character + // - (3 bytes) [2J : Clears the entire screen, see + // http://vt100.net/docs/vt100-ug/chapter3.html#ED + // for more info. + write(STDOUT_FILENO, "\x1b[2J", 4); + // Writing 3 bytes to reposition the cursor back at + // the top-left corner, see + // http://vt100.net/docs/vt100-ug/chapter3.html#CUP + // for more info. + write(STDOUT_FILENO, "\x1b[H", 3); +} + +/*** Input section ***/ + +char* editorPrompt(char* prompt, void (*callback)(char*, int)) { + size_t buf_size = 128; + char* buf = malloc(buf_size); + + size_t buf_len = 0; + buf[0] = '\0'; + + while (1) { + editorSetStatusMessage(prompt, buf); + editorRefreshScreen(); + + int c = editorReadKey(); + if (c == DEL_KEY || c == CTRL_KEY('h') || c == BACKSPACE) { + if (buf_len != 0) + buf[--buf_len] = '\0'; + } else if (c == '\x1b') { + editorSetStatusMessage(""); + if (callback) + callback(buf, c); + free(buf); + return NULL; + } else if (c == '\r') { + if (buf_len != 0) { + editorSetStatusMessage(""); + if (callback) + callback(buf, c); + return buf; + } + } else if (!iscntrl(c) && isprint(c)) { + if (buf_len == buf_size - 1) { + buf_size *= 2; + buf = realloc(buf, buf_size); + } + buf[buf_len++] = c; + buf[buf_len] = '\0'; + } + + if (callback) + callback(buf, c); + } +} + +void editorMoveCursor(int key) { + editor_row* row = (ec.cursor_y >= ec.num_rows) ? NULL : &ec.row[ec.cursor_y]; + + switch (key) { + case ARROW_LEFT: + if (ec.cursor_x != 0) + ec.cursor_x--; + // If <- is pressed, move to the end of the previous line + else if (ec.cursor_y > 0) { + ec.cursor_y--; + ec.cursor_x = ec.row[ec.cursor_y].size; + } + break; + case ARROW_RIGHT: + if (row && ec.cursor_x < row -> size) + ec.cursor_x++; + // If -> is pressed, move to the start of the next line + else if (row && ec.cursor_x == row -> size) { + ec.cursor_y++; + ec.cursor_x = 0; + } + break; + case ARROW_UP: + if (ec.cursor_y != 0) + ec.cursor_y--; + break; + case ARROW_DOWN: + if (ec.cursor_y < ec.num_rows) + ec.cursor_y++; + break; + } + + // Move cursor_x if it ends up past the end of the line it's on + row = (ec.cursor_y >= ec.num_rows) ? NULL : &ec.row[ec.cursor_y]; + int row_len = row ? row -> size : 0; + if (ec.cursor_x > row_len) + ec.cursor_x = row_len; +} + +void editorProcessKeypress() { + static int quit_times = TTE_QUIT_TIMES; + + int c = editorReadKey(); + + switch (c) { + case '\r': // Enter key + editorInsertNewline(); + break; + case CTRL_KEY('q'): + if (ec.dirty && quit_times > 0) { + editorSetStatusMessage("Warning! File has unsaved changes. Press Ctrl-Q %d more time%s to quit", quit_times, quit_times > 1 ? "s" : ""); + quit_times--; + return; + } + editorClearScreen(); + consoleBufferClose(); + exit(0); + break; + case CTRL_KEY('s'): + editorSave(); + break; + case CTRL_KEY('e'): + if (ec.cursor_y > 0 && ec.cursor_y <= ec.num_rows - 1) + editorFlipRow(1); + break; + case CTRL_KEY('d'): + if (ec.cursor_y < ec.num_rows - 1) + editorFlipRow(-1); + break; + case CTRL_KEY('x'): + if (ec.cursor_y < ec.num_rows) + editorCut(); + break; + case CTRL_KEY('c'): + if (ec.cursor_y < ec.num_rows) + editorCopy(0); + break; + case CTRL_KEY('v'): + editorPaste(); + break; + case CTRL_KEY('p'): + consoleBufferClose(); + kill(0, SIGTSTP); + case ARROW_UP: + case ARROW_DOWN: + case ARROW_LEFT: + case ARROW_RIGHT: + editorMoveCursor(c); + break; + case PAGE_UP: + case PAGE_DOWN: + { // You can't declare variables directly inside a switch statement. + if (c == PAGE_UP) + ec.cursor_y = ec.row_offset; + else if (c == PAGE_DOWN) + ec.cursor_y = ec.row_offset + ec.screen_rows - 1; + + int times = ec.screen_rows; + while (times--) + editorMoveCursor(c == PAGE_UP ? ARROW_UP : ARROW_DOWN); + } + break; + case HOME_KEY: + ec.cursor_x = 0; + break; + case END_KEY: + if (ec.cursor_y < ec.num_rows) + ec.cursor_x = ec.row[ec.cursor_y].size; + break; + case CTRL_KEY('f'): + editorSearch(); + break; + case BACKSPACE: + case CTRL_KEY('h'): + case DEL_KEY: + if (c == DEL_KEY) + editorMoveCursor(ARROW_RIGHT); + editorDelChar(); + break; + case CTRL_KEY('l'): + case '\x1b': // Escape key + break; + default: + editorInsertChar(c); + break; + } + + quit_times = TTE_QUIT_TIMES; +} + +/*** Init section ***/ + +void initEditor() { + ec.cursor_x = 0; + ec.cursor_y = 0; + ec.render_x = 0; + ec.row_offset = 0; + ec.col_offset = 0; + ec.num_rows = 0; + ec.row = NULL; + ec.dirty = 0; + ec.file_name = NULL; + ec.status_msg[0] = '\0'; + ec.status_msg_time = 0; + ec.copied_char_buffer = NULL; + ec.syntax = NULL; + + editorUpdateWindowSize(); + // The SIGWINCH signal is sent to a process when its controlling + // terminal changes its size (a window change). + signal(SIGWINCH, editorHandleSigwinch); + // The SIGCONT signal instructs the operating system to continue + // (restart) a process previously paused by the SIGSTOP or SIGTSTP + // signal. + signal(SIGCONT, editorHandleSigcont); +} + +void printHelp() { + printf("Usage: tte [OPTIONS] [FILE]\n\n"); + printf("\nKEYBINDINGS\n-----------\n\n"); + printf("Keybinding\t\tAction\n\n"); + printf("Ctrl-Q \t\tExit\n"); + printf("Ctrl-S \t\tSave\n"); + printf("Ctrl-F \t\tSearch. Esc, enter and arrows to interact once searching\n"); + printf("Ctrl-E \t\tFlip line upwards\n"); + printf("Ctrl-D \t\tFlip line downwards\n"); + printf("Ctrl-C \t\tCopy line\n"); + printf("Ctrl-X \t\tCut line\n"); + printf("Ctrl-V \t\tPaste line\n"); + printf("Ctrl-P \t\tPause tte (type \"fg\" to resume)\n"); + + printf("\n\nOPTIONS\n-------\n\n"); + printf("Option \t\tAction\n\n"); + printf("-h | --help \t\tPrints the help\n"); + printf("-v | --version\t\tPrints the version of tte\n"); + + printf("\n\nFor now, usage of ISO 8859-1 is recommended.\n"); +} + +// 1 if editor should open, 0 otherwise, -1 if the program should exit +int handleArgs(int argc, char* argv[]) { + if (argc == 1) + return 0; + + if (argc >= 2) { + if (strncmp("-h", argv[1], 2) == 0 || strncmp("--help", argv[1], 6) == 0) { + printHelp(); + return -1; + } else if(strncmp("-v", argv[1], 2) == 0 || strncmp("--version", argv[1], 9) == 0) { + printf("tte - version %s\n", TTE_VERSION); + return -1; + } + } + + return 1; +} + +int main(int argc, char* argv[]) { + initEditor(); + int arg_response = handleArgs(argc, argv); + if (arg_response == 1) + editorOpen(argv[1]); + else if (arg_response == -1) + return 0; + enableRawMode(); + + editorSetStatusMessage(" Ctrl-Q to quit | Ctrl-S to save | (tte -h | --help for more info)"); + + while (1) { + editorRefreshScreen(); + editorProcessKeypress(); + } + + return 0; +}