pax_global_header00006660000000000000000000000064147511643100014514gustar00rootroot0000000000000052 comment=4a7ec2c3ae5e0a341c015d8e96b07247e26d5e14 ne-3.3.4/000077500000000000000000000000001475116431000121255ustar00rootroot00000000000000ne-3.3.4/CHANGES000066400000000000000000000161561475116431000131310ustar00rootroot00000000000000(This file, CHANGES, lists fixes. See NEWS for new features and enhancements.) 3.3.4 2025-02-06 * Improved YAML style file. 3.3.3 2023-10-19 * Fixed document reordering in SelectDoc requester when first or last document wraps around to the other end of the document list. * After closing the current document, the most recently active adjacent document is chosen as the new current document. 3.3.2 2022-09-13 * Fixed issue where autocomplete could fail/crash with UTF-8 characters. * Fixed issue where file requester could return incorrect path to the selected file. * Fixed makefile so that it correctly supports parallel builds. Thanks to Emily Lucy Ishikawa for contributing this fix. 3.3.1 2021-05-16 * ttysize() is now invoked at start, improving user experience in the termcap/ANSI versions. * Fix sh syntax highlighting in double-quoted strings with complex variable expressions. * Fix java syntax highlighting for strings containing percent signs not in a format specifier context. * Fixed possible document name issue with docs from similarly named directories. 3.3.0 2020-02-27 * Fixed possible crash when closing documents from within the SelectDoc requester. * Fixed adjusted position of mark, bookmark on same line as and to the right of multi-line pastes. * Fixed cursor position after reformatting a document's final paragraph. * Paragraph n where n>1 is atomic; i.e. a single Undo reverts changes. 3.2.1 2019-10-02 * Fixed reordering items on multi-page requesters. 3.2.0 2019-09-30 * Fixed typo for "Home" key in default.keys. * Add backtick support for MarkDown syntax (md.jsf) * Fixed potentially infinite backwards search-and-replace. 3.1.2 2018-10-06 * Fixed visual glitch when characters spanning multiple columns are only partially displayed on the left side. * Fixed PARAGRAPH command. * DeleteNextWord now deletes through the end of the next N words. * Undoing DeleteNextWord/DeletePrevWord also restores cursor position. 3.1.1 2017-06-04 * Fixed visual glitch when deleting characters from long lines. * Fixed broken behavior in WordWrap with leading whitespace. * On CloseDoc (^Q), the next document now becomes active. It used to make the previous document active. 3.1.0 2017-04-29 * Comments in .yaml files are highlighted more correctly. * Determine better whether Find and FindRegExp patterns need to be recompiled when switching buffers. Fix possible lockup after cancelled Find. * Fixed missing screen update after ABOUT (regression introduced in 3.0.1). Thanks to Brian Callahan for reporting this bug. * Help command uses its optional parameter again. * PasteVert behaves beyond the right end of lines again. * AutoComplete keeps words containing apostrophes as single words ("can't" instead of "can" and "t"). * Home, End, Tab keys now work in menus. * Backward FindRegExp works again. * Resizing the window in inputs, requesters is more sane. * Restored correct keyboard input for 8-bit character sets. * Update syntax code for feature parity with current Joe. 3.0.1 2015-06-22 * Updated version of GNU regex library provides 64-bit regular expressions. * We no longer assign stdin, fixing a *BSD compatibility problem (thanks to Brian Callahan for reporting this issue). * Fixed missing screen update at startup when no default autoprefs are available. 3.0 2015-06-18 * Allow remapping of character SEQuences in .keys files. * Global macros are now loaded from NE_GLOBAL_DIR/macros rather than NE_GLOBAL_DIR (/usr/local/share/ne/macros vs /usr/local/share/ne). 2.6 2015-04-17 * Ancient bug with vertical clip edges fixed. * AutoComplete occasionally would omit some completions. * Exit attempts to save all modified documents even if one cannot be saved. (It used to give up on the first error.) * Avoid crash when resizing window while command prompt is active. * Avoid SaveMacro optimization before Undo, called macros. 2.5 2012-12-24 * Pathologically long-running PARAGRAPH commands are now stoppable (^-\). * Moved modified flag '*' to right end of status line to be easier to see. * COPY, CUT, ERASE copy the correct text to the clip and do not crash any more when in free form mode and cursor or mark is beyond the end of a line. * ReplaceOnce was returning a generic error code instead of success, thus stopping macros. 2.4 2012-04-10 * CLOSEDOC and QUIT (^Q and Alt-Q) now close string requesters just like Esc. * Macro calling macros are now stopped at an arbitrary depth of 32 calls. * Last command of a loaded macro w/o trailing new-line now works. * Vertical block selections where mark is below cursor select correct text. * Mark right of a tab no longer moves when you change tab size. * MARK and MARKVERT with no parameters always set rather than toggle the mark. * Cursor no longer goes to start of line when you change tab size. * A couple of operations in free form mode (joining the first line with the following one and deleting a block with an extreme beyond the end of file) should not cause crashes anymore. * AUTOCOMPLETE could sometimes insert an inadvertent trailing "*". 2.3 2011-10-28 * Fixed ridiculously old bug when copying a block and the marker is after the cursor. * Tweak to syntax file for java. * Changed build date in "About" to ISO YYYY-MM-DD format. * Tweak suspend to signal process group; reduce chance of apparent hang. * Instantaneous window resize works again. * Now we display an error message (instead of crashing or returning an I/O error) when a file is too large (>=2GiB). * We no longer set the buffer filename in case of I/O error. 2.2 2011-01-23 * Fixed memory allocation macros in regex code from glibc to work on systems that return NULL on zero-sized allocations. * In makefile, made explicit the dependencies for regex.o. * Fixed bug in "AdjustView R" that could push current character off screen. * Ensure tab size remains less than half the window width when the window changes size. * Fixed buggy out-of-memory handling when loading files. * Fixed buggy HTML/CSS syntax highlighting. * Fixed wrong background line colour when clearing to the end of line. * ToUpper and ToLower now stop when they reach the end of a document. 2.1 2010-03-17 * Fixed efficiency bug introduced with syntax highlighting: ne should now be much more responsive along slow connections. * Fixed old, bad, shameful bug: complex assertions were compiled into the code even for the non-debug version. As a result, ne was deadly slow on large files. * Fixed small mistakes in the keyboard sequences displayed in the menus, and small discrepancies between default.keys/default.menus and reality (thanks to John Gabriele for having pointed out this). * In some cases after a keyboard timeout an ESC character was left in the keyboard buffer, causing weird behaviours. * Probably really (this time) fixed problems with regexps matching empty strings. * BackSpace and Delete behave better in FreeForm mode. ne-3.3.4/COPYING000066400000000000000000001045131475116431000131640ustar00rootroot00000000000000 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 . ne-3.3.4/INSTALL.md000066400000000000000000000110701475116431000135540ustar00rootroot00000000000000Build and install into `/usr/local` ----------------------------------- cd ne-x.y.z make build install Note that you must have adequate privileges. Build and install into some alternative location ------------------------------------------------ Set the PREFIX variable, for example: cd ne-x.y.z make PREFIX=/home//opt build install Makefiles --------- There are three makefiles provided with the distribution: a top-level makefile for easy build and installation, a low-level makefile in the src directory that just builds ne's executable and provides some more flexibility, and another low-level makefile in the doc directory. The top-level makefile provides targets "source" (builds the standard source distribution), "cygwin" (builds the cygwin distribution), "build" (builds ne) and "install" (installs ne). The PREFIX make variable (see above) decides where ne will be installed and which will be its global directory. The LIBS variable can be used to change the name of the curses library: by default, the low-level makefile uses "-lcurses", but depending on your system you might prefer to specify "LIBS=-lncurses", "LIBS=-lncursesw" or even "LIBS=-lterminfo". For installation (i.e., "make install"), a POSIX compliant machine with a terminfo database should be sufficient. Note that terminfo might come bundled in a package named "curses", "ncurses" or some variant of it, and you may need to install the ncurses development files. Choose the simplest variant of this package, as ne does not actually use curses (a virtual screen library), but just the underlying terminfo database. You may receive some errors from the install-info command if you do not have write access to the system infodir.bak file; these can be ignored. Note, however, that creating a source distribution with the "source" target requires a complete build, and in particular the presence of a number of tools that manipulate texinfo files, as some of the source files are generated from the documentation. By default the build target does not produce the documentation PDF since that depends on pdftex. If you still want to build the PDF you can do that using `make alldocs build install`. By playing with the low-level src makefile you have more options (as you can first build using the low-level makefile and then use the install target of the top-level makefile). If you have a termcap database, you should specify "NE_TERMCAP=1" (i.e., type "make NE_TERMCAP=1"). It uses the GNU version of termcap, whose sources are included (no library is needed). In general, if a compilation via a simple "make" fails you should try these variations in order until one of them succeeds: make make NE_POSIX=1 make NE_TERMCAP=1 make NE_POSIX=1 NE_TERMCAP=1 They use slightly different #define's to overcome the slight differences among systems. If you have a problem with the local compiler and you have the GNU C compiler installed, try "CC=gcc", and possibly also "OPTS=-ansi". If you are compiling under Cygwin or similar emulations of UN*X running under other operating systems, you can specify "NE_ANSI=1" to build a copy of ne that by default will use built-in ANSI terminal control sequences. By combining "NE_ANSI=1" and "NE_TERMCAP=1" you will get a version of ne that needs no library, and moreover starts by default in ANSI mode. Regardless of how ne was built, you can always override this choice by invoking ne with one of the command line options "--ansi" or "--no-ansi". ne can handle UTF-8, and supports multiple-column characters. The latter requires some support from the system: you can disable wide-character, multiple-column support with "NE_NOWCHAR=1". If you cannot install ne as root, you can change the position of the global preferences directory with "NE_GLOBAL_DIR=directory" (this is done automatically by the top-level makefile on the basis of the PREFIX variable). The global directory should contain automatic preferences files for common extensions, and must contain the syntax directory provided with ne's distribution, which contains joe's syntax definition files. In any case, if the NE_GLOBAL_DIR environment variable is set ne will use its value instead. The value ne ultimately uses, whether compiled in or from the environment, is displayed at startup if no file is open. Compatibility problems are also discussed in the documentation. Don't be alarmed if you get a lot of warnings about signed vs. unsigned values. If something does not work, please feel free to email us. * seba () * Todd () * ne-3.3.4/NEWS000066400000000000000000000247611475116431000126360ustar00rootroot00000000000000(This file, NEWS, lists new features and enhancements. See CHANGES for fixes.) 3.3.4 2025-02-06 * Bracketed paste now consults the AutoIndent flag to determine whether to preserve the cursor's initial horizontal offset. It's like a block level AutoIndent rather than a line-by-line (and therefore cumulative) AutoIndent. Being created internally by the Shift command, the introduced leading space also honors the Tabs and ShiftTabs flags. Like leading space introduced by AutoIndent, a single Undo removes the bracketed paste's auto indented space; a second Undo removes the pasted text itself. Bracketed pastes now act like "normal" pastes, setting the '<' and '>' bookmarks, and leaving the cursor at the head of the paste. 3.3.3 2023-10-19 * BracketedPaste support has been added; turns off AutoIndent when pasting text from the system clipboard. 3.3.2 2022-09-13 * Paste and PasteVert now set two bookmarks designated '<' and '>' which are set to the start and end of the most recently pasted text. Accessed by the "GotoBookmark <" and "GotoBookmark >" commands. Two associated menu items have been added to the "Search" menu. * Added toml.jsf syntax. * Support '_' in numeric constants, binary and hex constants, imaginary numbers in go.jsf syntax. * Now there is an uninstall target that uninstalls ne. * Additionally load key bindings from ~/.ne/.keys-${TERM} 3.3.1 2021-05-16 * The makefile has been made more distro friendly by making it possible to override all compiler and linker options. * A workaround has been implemented to make --ansi (mostly) work when compiling with ncurses. A change to ncurses in 2012 made tgoto() not working unless tgetent()/setupterm() are called first. 3.3.0 2020-02-27 * You can now Save (^A) named documents from within the SelectDoc (F4) requester. * The mark, if set, is visible. * Warning about opening a document with the same name as an existing document now considers the fully qualified canonical path and name of each rather than just their basenames. * Status bar Modified flag (*) may also be underlined, indicating the corresponding file's modification time has changed since the document was loaded or saved. 3.2.0 2019-09-30 * Macros: They now work across multiple documents. You can cancel macro recording, and append new recorded commands to your current macro. * New documents inherit their predecessor's current macro, search, and replace strings. * Saving of read-only documents requires confirmation. * Word completion now does not consider the word the cursor is currently in, which helps in disambiguating. * The option -h is now an alias for --help. Thanks to Lonnie Abelbeck for implementing this feature. * Add asterisk.jsf syntax. * FIND in string inputs brings up a requester with your input history. Use Enter to replace, TAB to insert into your input line; Escape to exit. * Enhanced commands: MoveEOW, NextWord, and PrevWord can now move to either end of the indicated word via optional '<' and '>' parameter modifiers. * Requesters make better use of white space. 3.1.2 2018-10-06 * RepeatLast now accepts "Find" or "Replace" after its optional number so you can explicitly repeat either operation even if the other was most recently performed. * We now use the xenl property to detect whether we can write on the last column of the last line, thus making the status bar complete. 3.1.1 2017-06-04 * You can now CloseDoc (^Q) unmodified documents from within the SelectDoc (F4) requester. 3.1.0 2017-04-29 * Large files that do not fit into memory are memory-mapped transparently if enough disk space is available. * Determine a "virtual extension" based on document contents and patterns from ~/.ne/.extensions. Allows overrides of specified extensions only. If no match, use .default#ap prefs. * Status flag "!" indicates the last line of document is not empty, thus the last line of the resulting saved file will not be terminated. * Requesters by default no longer remove non-matching entries as you enter characters. Use Insert or Delete keys within a requester to toggle between these behaviors. * Documents loaded through file requesters now have relative instead of absolute paths. * New NameConvert command (NC for short) changes current document's file name from relative to absolute path or absolute to relative path. * "SetBookmark ?" and "GotoBookmark ?" now prompt for the bookmark you want to set/goto. The prompt also indicates which bookmarks are set. * Command line option "--read-only" opens next document in read-only mode. * Options "--read-only" and "+N,M" can be applied to piped input: ls -l | ne --read-only +3,5 - * Warn if SaveAs would overwrite an existing file. * Suspend (Crtl-Z) works in prompts, requesters. * "KeyCode" now takes optional parameter. * Immediately/only after "Not Found", RepeatLast wraps around to the other end of the document. 3.0 2015-06-18 * ne is now fully 64-bit, and needs to be compiled by a C99-compliant compiler. Files can be of any size, provided that enough core memory is available. * ne is able to read from named pipes. You can even pipe content into ne: it will be opened as the first document. * It is now possible to interrupt searches (it used to be possible just to interrupt global replace actions). * Word wrap has been reverted to pre-2.5: it preserves just whitespace, and it doesn't reparagraph at each line split or join. Paragraph keeps the usual smart features preserving comments, etc. 2.6 2015-04-17 * Requesters (filenames, help, autocompletions, etc.) do progressive "fuzzy match" by typed characters, backspace. * Document requester (F4) opens with cursor on the current document. Docs with unsaved changes are bold, flagged with "*". F2/F3 reorder documents. * Tab in Syntax command displays requester of extant syntax recognizers. * You can now use AutoComplete in command line prompts. * New SaveAll command saves all modified documents. * Warns before saving over a file that was modified since the buffer was last loaded or saved. * `' joins (), {}, [], and <> as character pairs known to MatchBracket. * New example macros: aspell, DeleteSOL. * Add % to special leading characters for re-wrapping Paragraphs. * WordWrap no longer waits for the cursor to hit the right margin; it wraps on insertions and deletions now. * Updates to ruby, erb, python, conf, many other syntax recognizers. Added these recognizers from the Joe project: ant batch classic_pascal comment_todo csharp debian differences elixir erlang git-commit go haml htmlerb ini iptables js json md powershell pp prolog properties sieve typescript whitespace yaml 2.5 2012-12-24 * New DelTabs flag, function separated from Tabs flag. * Reformatting with the Paragraph command is now aware of common leading characters used in comments: > # / * and spaces. * WordWrap preserves leading characters identically to Paragraph. 2.4 2012-04-10 * New Shift command indents/outdents selected lines. * Recorded macros preserve comments; indicate other included macros. * New AtomicUndo command groups changes to be undone/redone as a group instead of individually. * For commands that have key bindings, Help displays them. * New syntax highlighting for texinfo files. 2.3 2011-10-28 * Takes 'SEQ "sequence" KEYCODE' in ~/.ne/.keys to bind character sequences to key codes. 2.2 2011-01-23 * Now ne is distributed under the GPLv3. * Added AutoMatchBracket mode to indicate visible matching of {}, (), [], <> pairs. Mode is 1 (brightness) by default. * Bookmarks now remember/restore their vertical offsets in the window. * Bookmark commands take -1/+1 to cycle through your bookmarks; Use "UnsetBookmark *" to unset all bookmarks. * New --binary command line option loads next listed file in binary mode. May appear multiple times on the command line. * +[N[,M]] command line option moves to N-th line, M-th column of next file loaded. May appear multiple times on the command line. * Mention http://groups.google.com/group/niceeditor in splash screen. * About now displays splash screen in addition to its status bar message. * In FastGUI mode, when highlighting menu items the cursor is now positioned on the border of the menu rather than on the first letter of menu items. * New "tabs" syntax definition makes tabs visibly distinct from spaces. * Recognize C99 integer types from and highlight accordingly. 2.1 2010-03-17 * New commands: KeyCode, DeleteNextWord, DeletePrevWord, AutoComplete, InsertTab, Tabs, RequestOrder. * Now we set the syntax when a file is saved with a (different) name. * Now we correctly highlight control characters in the command line. * Display request lists by columns ("RequestOrder 1") or rows ("RequestOrder 0"). * Allow window resizing during requests (file selection, help, AutoComplete). * Sort filenames in dictionary order ("aa", "Ab", "ac", ...). * Enter in Help places you on the right command in the command list. * Only prompt once about identical filenames on startup. * Consider "_" as a word character for word-oriented commands. * Built-in filename extension to syntax mapping updates: dtx -> tex, latex -> tex, sage -> python. * Include new txt2tags syntax file from http://txt2tags.sourceforge.net/ * Replace reports the number of replacements (again). * Display the global directory on startup even if it is not found. * Default global directory changed from /usr/local/lib/ne to /usr/local/share/ne. * Aborting an OpenNew doesn't leave you in a new blank document. * AdjustView now takes optional number of lines or columns to adjust by; swapped meaning of AdjustView 'C' and 'M' parameters. * Read .keys and possibly .menus files from NE_GLOBAL_DIR on startup. * More defensive reading of lines/columns from terminfo to avoid crashes with XTerm on Mac OS X. * More portable and robust window-size change detection. * More parsimonious status-bar updates. * StatusBar, FastGUI, VerboseMacros, and RequestOrder are not buffer specific, are only saved in ~/.ne/.default#ap. * Current syntax name is buffer specific; only saved in autoprefs, not ~/.ne/.default#ap. * The Amiga is officially no longer supported (well, not exactly an improvement...). ne-3.3.4/README.md000066400000000000000000000021401475116431000134010ustar00rootroot00000000000000Welcome to ne, the nice editor. ------------------------------- ne is a free (GPL'd) text editor based on the POSIX standard that runs (we hope) on almost any UN*X machine. ne is easy to use for the beginner, but powerful and fully configurable for the wizard, and most sparing in its resource usage. See the [manual](https://ne.di.unimi.it/docs/index.html) for some highlights of ne's features. ne is distributed under the GNU Public License (see COPYING). The INSTALL.md file contains detailed installation instructions. The version of this distribution of ne can be found in src/version.h. Documentation (in the "doc" directory) is provided in the form of a texinfo file. It can be printed as a manual using TeX and GNU's texinfo.tex macro package, or turned into a hypertext document using GNU's makeinfo. The directory contains several pre-compiled printable and hypertext versions of the documentation. If something does not work, please feel free to email us, or write to the mailing list. * seba () * Todd () * ne-3.3.4/doc/000077500000000000000000000000001475116431000126725ustar00rootroot00000000000000ne-3.3.4/doc/.gitignore000066400000000000000000000002051475116431000146570ustar00rootroot00000000000000version.texinfo html/ *.gz ne.txt ne.txt-e ne.aux ne.cm ne.cp ne.cms ne.cps ne.dvi ne.fn ne.ky ne.log ne.pg ne.toc ne.tp ne.vr *.pdf ne-3.3.4/doc/default.keys000066400000000000000000000132251475116431000152160ustar00rootroot00000000000000# # This is a ready-made keyboard binding configuration file that replicates # ne's built-in bindings. You can use it as a start for modifications. # # Note: the keycode numbers in this file are case-insensitive hexadecimal # values as reported by the KeyCode command. The "0x" prefixes are # optional and omitted. Valid range is from 0 to 1FF. # # Cursor/editing keys # # Cursor up KEY 101 LineUp # Cursor down KEY 102 LineDown # Cursor left KEY 103 MoveLeft # Cursor right KEY 104 MoveRight # Cursor home KEY 105 MoveIncUp # Cursor end KEY 106 MoveIncDown # Cursor next page KEY 107 NextPage # Cursor prev page KEY 108 PrevPage # Scroll forward KEY 109 LineDown # Scroll backward KEY 10A LineUp # Clear to end of line KEY 110 DeleteEOL # Clear to end of screen KEY 111 NOP # Backspace KEY 112 BackSpace # Delete line KEY 113 DeleteLine # Insert line KEY 114 InsertLine # Delete char KEY 115 DeleteChar # Insert char KEY 116 Insert # Exit insert mode KEY 117 NOP # Clear KEY 118 Clear # Keypad A1 KEY 120 MoveSOF # Keypad A3 KEY 121 PrevPage # Keypad B2 KEY 122 ToggleSEOL # Keypad C1 KEY 123 MoveEOF # Keypad C3 KEY 124 NextPage # KEY 125 Exec # Clear all tabs KEY 128 NOP # Clear tab KEY 129 NOP # Set tab KEY 12A NOP # # Function keys # # F0 KEY 140 Escape # F1 KEY 141 Escape # F2 KEY 142 NextDoc # F3 KEY 143 PrevDoc # F4 KEY 144 SelectDoc # F5 KEY 145 Undo # F6 KEY 146 Redo # F7 KEY 147 PrevWord # F8 KEY 148 NextWord # F9 KEY 149 PlayOnce # F10 KEY 14A Help KEY 15F DeletePrevWord KEY 160 DeleteNextWord # # CONTROL-letter shortcuts # # CONTROL-@ KEY 0 MarkVert # CONTROL-a KEY 1 MoveSOL # CONTROL-b KEY 2 Mark # CONTROL-c KEY 3 Copy # CONTROL-d KEY 4 NewDoc # CONTROL-e KEY 5 MoveEOL # CONTROL-f KEY 6 Find # CONTROL-g KEY 7 RepeatLast # CONTROL-h KEY 8 BackSpace # CONTROL-i cannot be redefined (it's TAB) # CONTROL-j KEY A GotoLine # CONTROL-k KEY B Exec # CONTROL-l KEY C Refresh # CONTROL-m cannot be redefined (it's RETURN) # CONTROL-n KEY E NextPage # CONTROL-o KEY F Open # CONTROL-p KEY 10 PrevPage # CONTROL-q KEY 11 CloseDoc # CONTROL-r KEY 12 Replace # CONTROL-s KEY 13 Save # CONTROL-t KEY 14 Record # CONTROL-u KEY 15 UndelLine # CONTROL-v KEY 16 Paste # CONTROL-w KEY 17 PasteVert # CONTROL-x KEY 18 Cut # CONTROL-y KEY 19 DeleteLine # CONTROL-z KEY 1A Suspend # CONTROL-[ cannot be redefined (it's Escape) # CONTROL-\ cannot be redefined (it's the interrupt character) # CONTROL-] KEY 1D MatchBracket # CONTROL-^ KEY 1E AdjustView # CONTROL-_ KEY 1F FindRegExp # Delete KEY 7F DeleteChar KEY 1cf OpenClip KEY 1d0 Paragraph KEY 1d1 Quit KEY 1d2 Redo KEY 1d3 SaveClip KEY 1d4 Through KEY 1d5 Undo KEY 1d6 ToUpper KEY 1d7 WordWrap KEY 1d8 Exit KEY 1d9 DeleteEOL KEY 1da CRLF # # The following bindings set up the same actions for a number of correlated #codes. This guarantees that the user is able to use the '~' bindings with # CTRL-META, META or just a prefixed ESC. # # CONTROL-META-a, META-a, ESC A and ESC a KEY 81 MoveSOF KEY 181 MoveSOF KEY 1C1 MoveSOF KEY 1E1 MoveSOF # CONTROL-META-b, META-b, ESC B and ESC b KEY 82 PrevWord KEY 182 PrevWord KEY 1C2 PrevWord KEY 1E2 PrevWord # CONTROL-META-c, META-c, ESC C and ESC c KEY 83 AdjustView C KEY 183 AdjustView C KEY 1C3 AdjustView C KEY 1E3 AdjustView C # CONTROL-META-d, META-d, ESC D and ESC d KEY 84 NextDoc KEY 184 NextDoc KEY 1C4 NextDoc KEY 1E4 NextDoc # CONTROL-META-e, META-e, ESC E and ESC e KEY 85 MoveEOF KEY 185 MoveEOF KEY 1C5 MoveEOF KEY 1E5 MoveEOF # CONTROL-META-f, META-f, ESC F and ESC f KEY 86 NextWord KEY 186 NextWord KEY 1C6 NextWord KEY 1E6 NextWord # CONTROL-META-g, META-g, ESC G and ESC g KEY 87 GotoBookMark KEY 187 GotoBookMark KEY 1C7 GotoBookMark KEY 1E7 GotoBookMark # CONTROL-META-i, META-i, ESC I and ESC i KEY 89 AutoComplete KEY 189 AutoComplete KEY 1C9 AutoComplete KEY 1E9 AutoComplete # CONTROL-META-j, META-j, ESC J and ESC j KEY 8A GotoColumn KEY 18A GotoColumn KEY 1CA GotoColumn KEY 1EA GotoColumn # CONTROL-META-k, META-k, ESC K and ESC k KEY 8B SetBookmark KEY 18B SetBookmark KEY 1CB SetBookmark KEY 1EB SetBookmark # CONTROL-META-l, META-l, ESC L and ESC l KEY 8C ToLower KEY 18C ToLower KEY 1CC ToLower KEY 1EC ToLower # CONTROL-META-m, META-m, ESC M and ESC m KEY 8D Play 1 KEY 18D Play 1 KEY 1CD Play 1 KEY 1ED Play 1 # CONTROL-META-n, META-n, ESC N and ESC n KEY 8E OpenNew KEY 18E OpenNew KEY 1CE OpenNew KEY 1EE OpenNew # CONTROL-META-o, META-o, ESC O and ESC o KEY 8F OpenClip KEY 18F OpenClip KEY 1CF OpenClip KEY 1EF OpenClip # CONTROL-META-p, META-p, ESC P and ESC p KEY 90 Paragraph KEY 190 Paragraph KEY 1D0 Paragraph KEY 1F0 Paragraph # CONTROL-META-q, META-q, ESC Q and ESC q KEY 91 Quit KEY 191 Quit KEY 1D1 Quit KEY 1F1 Quit # CONTROL-META-r, META-r, ESC R and ESC r KEY 92 Redo KEY 192 Redo KEY 1D2 Redo KEY 1F2 Redo # CONTROL-META-s, META-s, ESC S and ESC s KEY 93 SaveClip KEY 193 SaveClip KEY 1D3 SaveClip KEY 1F3 SaveClip # CONTROL-META-t, META-t, ESC T and ESC t KEY 94 Through KEY 194 Through KEY 1D4 Through KEY 1F4 Through # CONTROL-META-u, META-u, ESC U and ESC u KEY 95 Undo KEY 195 Undo KEY 1D5 Undo KEY 1F5 Undo # CONTROL-META-v, META-v, ESC V and ESC v KEY 96 ToUpper KEY 196 ToUpper KEY 1D6 ToUpper KEY 1F6 ToUpper # CONTROL-META-w, META-w, ESC W and ESC w KEY 97 WordWrap KEY 197 WordWrap KEY 1D7 WordWrap KEY 1F7 WordWrap # CONTROL-META-x, META-x, ESC X and ESC x KEY 98 Exit KEY 198 Exit KEY 1D8 Exit KEY 1F8 Exit # CONTROL-META-y, META-y, ESC Y and ESC y KEY 99 DeleteEOL KEY 199 DeleteEOL KEY 1D9 DeleteEOL KEY 1F9 DeleteEOL # CONTROL-META-z, META-z, ESC Z and ESC z KEY 9A CRLF KEY 19A CRLF KEY 1DA CRLF KEY 1FA CRLF ne-3.3.4/doc/default.menus000066400000000000000000000066451475116431000154020ustar00rootroot00000000000000# # This is a ready-made menu configuration file that replicates ne's built-in # menus. # MENU "File" ITEM "Open... ^O" Open ITEM "Open New... [N" OpenNew ITEM "Save ^S" Save ITEM "Save As... " SaveAs ITEM "Quit Now [Q" Quit ITEM "Save&Exit [X" Exit ITEM "About " About MENU "Documents" ITEM "New ^D" NewDoc ITEM "Clear " Clear ITEM "Close ^Q" CloseDoc ITEM "Next f2/[D" NextDoc ITEM "Prev f3" PrevDoc ITEM "Select... f4" SelectDoc MENU "Edit" ITEM "Mark Block ^B" Mark ITEM "Cut ^X" Cut ITEM "Copy ^C" Copy ITEM "Paste ^V" Paste ITEM "Mark Vert ^@" MarkVert ITEM "Paste Vert ^W" PasteVert ITEM "Through [T" Through ITEM "Erase " Erase ITEM "Delete EOL [Y" DeleteEOL ITEM "Delete Line ^Y" DeleteLine ITEM "Undel Line ^U" UndelLine ITEM "Del Prev Word " DeletePrevWord ITEM "Del Next Word " DeleteNextWord ITEM "Open Clip [O" OpenClip ITEM "Save Clip [S" SaveClip MENU "Search" ITEM "Find... ^F" Find ITEM "Find RegExp... ^_" FindRegExp ITEM "Replace... ^R" Replace ITEM "Replace Once... " ReplaceOnce ITEM "Replace All... " ReplaceAll ITEM "Repeat Last ^G" RepeatLast ITEM "Goto Line... ^J" GotoLine ITEM "Goto Col... [J" GotoColumn ITEM "Goto Mark " GotoMark ITEM "Goto Start Of Paste" GotoBookmark < ITEM "Goto End Of Paste " GotoBookmark > ITEM "Match Bracket ^]" MatchBracket ITEM "Set Bookmark [K" SetBookmark ITEM "Goto Bookmark [G" GotoBookmark MENU "Macros" ITEM "Start/Stop Rec ^T" Record ITEM "Play Once f9/[M" Play 1 ITEM "Play Many... " Play ITEM "Play Macro... " Macro ITEM "Open Macro... " OpenMacro ITEM "Save Macro... " SaveMacro MENU "Extras" ITEM "Exec... ^K" Exec ITEM "Suspend ^Z" Suspend ITEM "Help... f10" Help ITEM "Refresh ^L" Refresh ITEM "Undo f5/[U" Undo ITEM "Redo f6/[R" Redo ITEM "Center " Center ITEM "Shift Right " Shift ITEM "Shift Left " Shift < ITEM "Paragraph [P" Paragraph ITEM "ToUpper [V" ToUpper ITEM "ToLower [L" ToLower ITEM "Capitalize " Capitalize ITEM "AutoComplete [I" AutoComplete ITEM "UTF-8 " UTF8 MENU "Navigation" ITEM "Move Left " MoveLeft ITEM "Move Right " MoveRight ITEM "Line Up " LineUp ITEM "Line Down " LineDown ITEM "Prev Page ^P" PrevPage ITEM "Next Page ^N" NextPage ITEM "Page Up " PageUp ITEM "Page Down " PageDown ITEM "Start Of File [A" MoveSOF ITEM "End Of File [E" MoveEOF ITEM "Start Of Line ^A" MoveSOL ITEM "End Of Line ^E" MoveEOL ITEM "Top Of Screen " MoveTOS ITEM "Bottom Of Screen" MoveBOS ITEM "Adjust View ^^" AdjustView ITEM "Middle View [C" AdjustView M ITEM "Incr Up Home" MoveIncUp ITEM "Incr Down End" MoveIncDown ITEM "Prev Word f7/[B" PrevWord ITEM "Next Word f8/[F" NextWord MENU "Prefs" ITEM "Tab Size... " TabSize ITEM "Tabs/Spaces " Tabs ITEM "Insert/Over Ins" Insert ITEM "Free Form " FreeForm ITEM "Status Bar " StatusBar ITEM "Hex Code " HexCode ITEM "Fast GUI " FastGUI ITEM "Word Wrap [W" WordWrap ITEM "Right Margin " RightMargin ITEM "Auto Indent " AutoIndent ITEM "Request Order " RequestOrder ITEM "Preserve CR " PreserveCR ITEM "Save CR/LF [Z" CRLF ITEM "Load Prefs... " LoadPrefs ITEM "Save Prefs... " SavePrefs ITEM "Load Auto Prefs " LoadAutoPrefs ITEM "Save Auto Prefs " SaveAutoPrefs ITEM "Save Def Prefs " SaveDefPrefs ne-3.3.4/doc/makefile000066400000000000000000000025541475116431000144000ustar00rootroot00000000000000# # This Makefile has the basic commands for converting # ne.texinfo into a plain text file (ne.txt), an # info document set (ne.info*), a set of HTML files (html/) and a PDF # document (ne.pdf). # DOCS=ne.info.gz ne.txt html/index.html docs: $(DOCS) pdf: ne.pdf install: docs cp * ../../../doc version.texinfo: ( cd .. ; $(MAKE) version ) ne.txt: ne.texinfo version.texinfo (makeinfo --plaintext --no-headers ne.texinfo -o ne.txt || makeinfo --no-headers ne.texinfo -o ne.txt) sed -i -e "s/\`/'/g" ne.txt sed -i -e "s/''''/'\`''/g" ne.txt sed -i -e "s/'ne'/ne/g" ne.txt ne.info.gz: ne.texinfo version.texinfo makeinfo ne.texinfo sed -i -e "s/\`/'/g" ne.info sed -i -e "s/''''/'\`''/g" ne.info sed -i -e "s/'ne'/ne/g" ne.info rm -f ne.info*gz gzip -9 ne.info* texinfo.cnf: texinfo.cnf.in cp texinfo.cnf.in texinfo.cnf if locale -c height | grep 279 ; then \ echo "@c -- US Letter detected by makefile." >> texinfo.cnf ;\ else \ echo "@c -- US Letter not detected by makefile; selecting A4." >> texinfo.cnf ;\ echo "@afourpaper" >> texinfo.cnf ;\ fi ne.pdf: ne.texinfo version.texinfo texinfo.cnf pdftex ne.texinfo html/index.html: ne.texinfo version.texinfo -rm -fr html makeinfo --html -o html ne.texinfo clean: rm -f ne.txt ne.info* ne.ps ne.pdf ne.aux ne.cms ne.cps ne.fn ne.log ne.tp ne.cm ne.cp ne.dvi ne.ky ne.pg ne.toc ne.vr rm -rf html/ ne-3.3.4/doc/ne.1000066400000000000000000000042201475116431000133540ustar00rootroot00000000000000.TH NE 1 "by Sebastiano Vigna and Todd M. Lewis" "ne" \" -*- nroff -*- .SH NAME ne \- A nice editor .SH SYNOPSIS .B ne [options] files .TP Options: [\-\-help] [\-\-] [+[N[,M]]] [\-\-binary] [\-\-read-only] [\-\-utf8] [\-\-no\-utf8] [\-\-ansi] [\-\-no\-ansi] [\-\-no\-config] [\-\-no\-syntax] [\-\-prefs ext] [\-\-keys key\-configuration\-file] [\-\-menus menu\-configuration\-file] [\-\-macro macro\-file] .SH DESCRIPTION \fBne\fR is a free text editor that runs on (hopefully almost) any UN*X machine. \fBne\fR is easy to use for the beginner, but powerful and fully configurable for the wizard, and most sparing in its resource usage. This documentation is incomplete. The Texinfo/info/HTML/PDF documentation is the authoritative source (try \fBinfo ne\fR or \fBinfo \-f ne\fR). .SS OPTIONS .TP .I "--help" Prints a help message. .TP .I "--" Next token is a filename. May appear more than once. .TP .I "+[N[,M]]" Moves to the last or N-th line, first or M-th column of the next named file. May appear more than once. .TP .TP .I "--binary" Load the next named file in binary mode. May appear more than once. .TP .I "--read-only" Load the next file in read-only mode. May appear more than once. .TP .I "--utf8" Use UTF-8 I/O. .TP .I "--no-utf8" Do not use UTF-8 I/O. .TP .I "--ansi" Use the built-in ANSI sequences. .TP .I "--no-ansi" Never use the built-in ANSI sequences. .TP .I "--no-config" Skip reading the menu and keyboard configuration files. .TP .I "--no-syntax" Disable syntax-highlighting support. .TP .I "--prefs ext" Set autoprefs for the provided extension before loading the first file. .TP .I "--keys key-configuration-file" Use the specified keyboard configuration file. .TP .I "--menus menu-configuration-file" Use the specified menu configuration file. .TP .I "--macro macro-file" Execute the given macro after startup. .SS USAGE Start \fBne\fR, then use escape, escape-escape or F1 to access the menus. .SS BUGS Please send bug reports to Sebastiano Vigna or Todd Lewis . .SS AUTHORS \fBne\fR was originally written by Sebastiano Vigna. Todd M. Lewis added several new features. Daniele Filaretti helped with syntax highlighting. ne-3.3.4/doc/ne.texinfo000066400000000000000000007230051475116431000147010ustar00rootroot00000000000000\input texinfo @c -*-texinfo-*- @setfilename ne.info @settitle @code{ne}'s manual @dircategory Text creation and manipulation @direntry * ne: (ne). The nice editor @end direntry @defindex cm @include version.texinfo @ignore This file, besides being a normal Texinfo file, allows generation of on-line help and other crucial parts of the C source code for ne. The program info2src.pl (a *very* dirty hack) does this. See the Makefile to see how this is done. Conventions: @file for files, directories, etc. (but extensions, etc. use @samp); @code for command line stuff, internal commands, environment variables; @samp for menu/menu item names, and anything left that's literal. @end ignore @ifinfo This file documents ne @value{VERSION}, a free text editor for @sc{un*x}. Copyright (C) 1993-1998 Sebastiano Vigna. Copyright (C) 1999-@value{RELEASE_YEAR} Todd M. Lewis and Sebastiano Vigna. Permission is granted to make and distribute verbatim copies of this manual provided the copyright notice and this permission notice are preserved on all copies. @ignore Permission is granted to process this file through TeX and print the results, provided the printed document carries copying permission notice identical to this one except for the removal of this paragraph (this paragraph not being relevant to the printed manual). @end ignore Permission is granted to copy and distribute modified versions of this manual under the conditions for verbatim copying, provided that the entire resulting derived work is distributed under the terms of a permission notice identical to this one. Permission is granted to copy and distribute translations of this manual into another language, under the above conditions for modified versions, except that this permission notice may be stated in a translation approved by the Free Software Foundation. @end ifinfo @setchapternewpage odd @finalout @titlepage @title ne @subtitle A nice editor @subtitle Version @value{VERSION} @author by Sebastiano Vigna and Todd M. Lewis @page @vskip 0pt plus 1filll Copyright @copyright{} 1993-1998 Sebastiano Vigna@* Copyright @copyright{} 1999-@value{RELEASE_YEAR} Todd M. Lewis and Sebastiano Vigna Permission is granted to make and distribute verbatim copies of this manual provided the copyright notice and this permission notice are preserved on all copies. Permission is granted to copy and distribute modified versions of this manual under the conditions for verbatim copying, provided that the entire resulting derived work is distributed under the terms of a permission notice identical to this one. Permission is granted to copy and distribute translations of this manual into another language, under the above conditions for modified versions, except that this permission notice may be stated in a translation approved by the Free Software Foundation. @end titlepage @contents @ifnottex @node Top @top This file describes @code{ne} version @value{VERSION}, a free text editor that runs (we hope) on almost any @sc{un*x} machine. @code{ne} is easy to use for the beginner, but powerful and fully configurable for the wizard, and most sparing in its resource use. @noindent Copyright (C) 1993-1998 Sebastiano Vigna@* @noindent Copyright (C) 1999-@value{RELEASE_YEAR} Todd M. Lewis and Sebastiano Vigna @end ifnottex @menu * Introduction:: * Basics:: * Reference:: * Commands:: * Configuration:: * Hints and Tricks:: * Motivations and Design:: * The Encoding Mess:: * History:: * Portability Problems:: * Acknowledgments:: * Concept Index:: * Command Index:: @end menu @node Introduction @chapter Introduction @cindex LITHP @cindex Features @cindex vi @cindex POSIX @cindex terminfo @cindex termcap @code{ne} is a full screen text editor for @sc{un*x} (or, more precisely, for @sc{posix}: @pxref{Motivations and Design}). I came to the decision to write such an editor after getting completely sick of @code{vi}, both from a feature and user interface point of view. I needed an editor that I could use through a @code{telnet} connection or a phone line and that wouldn't fire off a full-blown @sc{lithp}@footnote{This otherwise unremarkable language is distinguished by the absence of an @samp{s} in its character set; users must substitute @samp{th}. @sc{lithp} is said to be useful in protheththing lithtth.} operating system just to do some editing. A concise overview of the main features follows: @itemize @bullet{} @item three user interfaces: control keystrokes, command line, and menus; keystrokes and menus are completely configurable; @item syntax highlighting; @item full support for UTF-8 files, including multiple-column characters; @item 64-bit file/line length; @item simple scripting language where scripts can be generated @i{via} an idiotproof record/play method; @item unlimited undo/redo capability (can be disabled with a command); @item automatic preferences system based on the extension of the file name being edited or regex content matching; @item automatic completion of prefixes using words in your documents as dictionary; @item a file requester with completion features for easy file retrieval; @item extended regular expression search and replace @`a la @code{emacs} and @code{vi}; @item a very compact memory model---you can easily load and modify very large files, even if they do not fit your core memory; @item editing of binary files. @end itemize @node Basics @chapter Basics @noindent @i{Simple things should be simple. Complex things should be possible.} (Alan Kay) @code{ne}'s user interface is essentially a compromise between the limits of character driven terminals and the power of GUIs. While @emph{real} editing is done without ever touching a mouse, it is also true that editing should be doable without ever touching a manual. These two conflicting goals can be accommodated easily in a single program if we can offer a series of interfaces that allow for differentiated use. In other words, it is unlikely that an @code{ne} wizard will ever have to activate a menu, but to become an expert user you just have to use the menus enough to learn by heart the most important keystrokes. A good manual is always invaluable when one comes to configuration and esoteric features, but few users will ever need to change @code{ne}'s menus or key bindings. Another important thing is that powerful features should always be accessible, at least in part, to every user. The average user should be able to record his actions, replay them, and save them in a humanly readable format for further use and editing. In the following sections we shall take a quick tour of @code{ne}'s features. @menu * Terminology:: * Starting:: * Loading and Saving:: * Editing:: * Basic Preferences:: * Basic Macros:: * More Advanced Features:: @end menu @node Terminology @section Terminology @cindex File @cindex Document @cindex Buffer In this section we explain and contrast some of the terms @code{ne} uses. Understanding these distinctions will go a long way towards making the rest of this manual make sense. A @dfn{file} is a group of bytes stored on disk. This may seem rather obvious, but the important distinction here is that @code{ne} does not edit files; it edits @dfn{documents}. A @dfn{document} is what @code{ne} calls one of the ``text thingies'' that you can edit. It is a sequence of lines of text in the computer's memory---not on disk.@footnote{Actually, it can be in a region of the disk used to simulate a larger memory. @code{ne} will switch to such a simulation whenever the computer's memory is not sufficient for editing a file. This means, in particular, that out-of-memory errors can be caused by insufficient disk space, too.} Documents can be created, edited, saved in files, loaded from files, discarded, @i{et cetera}. When a document is loaded from or saved to a file, it remains associated with that file by name until the document is either closed or saved to a different file. Interactions between documents and files are handled by the commands under the @samp{File} menu. The @samp{Documents} menu commands only deal with documents. @xref{Menus}. Internally, @code{ne} holds its documents' text in @dfn{buffers}. A @dfn{buffer} is a chunk of memory in which @code{ne} holds text. In addition to each document's text, buffers hold any loaded or recorded macros, undo records, a copy of your last deleted line, a copy of all your previous responses to long input, and several other things. @node Starting @section Starting @cindex Keyboard usage @cindex Menu usage @cindex Shortcuts @cindex Status bar @cindex Menu bar @cindex Control key @cindex Meta key @cindex Command line To start @code{ne}, just type @samp{ne} and press @key{Return}. If you want to edit some specific file(s), you can put their name(s) on the command line just after the command name, as for any @sc{un*x} command. The screen of your terminal will be cleared (or filled with text loaded from the first file you specified). You can also pipe the result of a command directly into @code{ne}: it will be loaded and opened as the first document. See @ref{Arguments} for other command line options. Writing text is pretty straightforward: if your terminal is properly configured, every key will (should) do what you expect. Alphabetic characters insert text, cursor keys move the cursor, and so on. You can use the @key{Delete} and @key{Backspace} key to perform corrections. If your keyboard has an @key{Insert} key, you can use it to @dfn{toggle} (switch from on to off, or vice versa) insert mode. In general, @code{ne} tries to squeeze everything it can from your keyboard. Function keys and special movement keys should work flawlessly if your terminal is properly configured. If not, complain to your system administrator. If that doesn't help, see @ref{Key Bindings}. At the bottom of the screen, you will see a line containing some numbers and letters. This is called the @dfn{status bar} because it reports to you part of the internal state of the editor. At startup, the status bar has the following form: @example L: 1 C: 1 12% ia----pvu-t------@@A* @end example @noindent (the numbers could be different, and a file name could be shown as last item instead of @samp{}). You probably already guessed that the numbers after @samp{L:} and @samp{C:} are your cursor's line and column numbers, respectively, whereas the percentage indicates approximately your position in the file. The small letters represent user flags that you can turn on and off. In particular, @samp{i} tells you that insert mode is on, while @samp{p} tells that the automatic preferences system is activated. The @samp{*} means this document has not been saved. For a thorough explanation of the meaning of the flags on the status bar, see @ref{The Status Bar}. Once you are accustomed to cursor movement and line editing, it is time to press @key{f1} (the first function key), or in case your keyboard does not have such a key, @key{Escape}. Immediately, the @dfn{menu bar} will appear, and the first menu will be drawn. (If you find yourself waiting for the menu to appear, you can press @key{Escape} twice in a row.) You can now move around menus and menu items by pressing the cursor keys. Moreover, a lower case alphabetic key will move to the next item in the current menu whose name starts with that letter, and an upper case alphabetic key will move to the next menu whose name starts with that letter. Moving around the menus should give you an idea of the capabilities of @code{ne}. If you want to save your work, you should use the @samp{Save As...} item from the @samp{File} menu. Menus are fully discussed in @ref{Menus}. When you want to exit from the menu system, press @key{f1} (or @key{Escape}) again. If instead you prefer to choose a command and execute it, move to the respective menu item and press @key{Return}. At the end of several menu items you will find strange symbols like @kbd{^A} or @key{f1}. They represent @dfn{shortcuts} for the respective menu items. In other words, instead of activating, selecting and executing a menu item, which can take seconds, you can simply press a couple of keys. The symbol @samp{^} in front of a character denotes the shortcut produced by the @key{Control} key plus that character (we assume here that you are perfectly aware of the usage of the @key{Control} key: it is just as if you had to type a capital letter with @key{Shift}). The descriptions of the form @key{f}@var{n} represent instead function keys. Finally, the symbol @samp{[} in front of a character denotes the shortcut produced by @key{Control} plus @key{Meta} (a.k.a. @key{Alt}) plus that character, @emph{or} @key{Meta} plus that character, depending on your terminal emulator---you must check for yourself. Moreover, these last bindings may not work with some terminals, in which case you can replace them with a sequence: just press the @key{Escape} key followed by the letter. A few menu items are bound to two control sequences (just in case one does not work, or it is impractical). Note that under certain conditions (for instance, while using @code{ne} through a @code{telnet} connection) some of the shortcuts might not work because they are trapped by the operating system for other purposes (@pxref{Hints and Tricks}). Finally, we have the third and last interface to @code{ne}'s features: the @dfn{command line}. If you press @kbd{@key{Control}-K}, or @key{Escape} followed by @samp{:} (a la @code{vi}), you will be requested to enter a command to execute. Just press @key{Return} for the time being (or, if you are really interested in this topic, @pxref{The Command Line}). In the sections that follow, when explaining how to use a command we shall usually describe the corresponding menu item. The related shortcut and command can be found on the menu item itself, and in @ref{Menus}. @node Loading and Saving @section Loading and Saving @cindex Loading a file @cindex Quitting @cindex Exiting @cindex Closing a document @cindex Opening a file @cindex Saving a file @cindex Writing a file @cindex File requester The first thing to learn about an editor is how to exit. @code{ne} has a @code{CloseDoc} command that can be activated by pressing @kbd{@key{Control}-Q}, by choosing the @samp{Close} item of the @samp{Document} menu, or by activating the command line with @kbd{@key{Control}-K}, writing @samp{cd} and pressing @key{Return}. Its effect is to close the current document without saving any modifications. (You will be requested to confirm your choice in case the current document has been modified since the last save.) There is also a @code{Quit} command, which closes all the documents without saving any modifications, and a @code{Save&Exit} (@kbd{@key{Meta}-X}) command, which saves the modified documents before quitting. This choice of shortcuts could surprise you. Wouldn't @samp{Quit} be a much better candidate for @kbd{@key{Control}-Q}? Well, experience shows that the most common operation is closing a document rather than quitting the editor. If there is just one document, the two operations coincide (this is typical, for instance, when you use @code{ne} for writing electronic mail), and if there are many documents, it is far more common to close a single document than all the existing documents. If you want to load a file, you may use the @code{Open} command, which can be activated by pressing @kbd{@key{Control}-O}, by choosing the @samp{Open...} item of the @samp{File} menu, or by typing it on the command line (as in the previous case). You will be prompted with a list of files and directories in the current working directory. (You can tell the directory names because they end with a slash; they will also appear in a bold face if your terminal allows it.) You can select any of the file names by using the cursor keys, or any other movement key. Pressing an alphabetic key will move the cursor to the first entry after the cursor that starts with the given letter. When the cursor is positioned over the file you want to open, press @key{Return}, and the file will be opened. If instead you move to a directory name, pressing @key{Return} will display the contents of that directory. You can also escape with @key{f1}, @key{Escape} or @key{Escape}-@key{Escape} and manually type the file name on the command line (or escape again, and abort the @code{Open} operation). If you escape with @key{Tab} instead, the file or directory under the cursor will be copied to the input line, where you can modify it manually. @code{ne} also has file name completion features activated by @key{Tab} (@pxref{The Input Line}). When you want to save a file, just use the command @code{Save} (@kbd{@key{Control}-S}). It will use the current document name or will ask you for one if the current document has no name. @code{SaveAs}, on the other hand, will always ask for a new name before saving the file. @code{SaveAll} will save all modified documents. If the file you are saving a document to has changed since you last loaded or saved it, perhaps because another user updated it while you were editing, @code{ne} will warn you before overwriting the file. If @code{ne} is interrupted by an external signal (for instance, if your terminal crashes), it will try to save your work in some emergency files. These files will have names similar to your current files, but they will have a pound sign @samp{#} prefixed to their names. @xref{Emergency Save}. @node Editing @section Editing @cindex Deleting characters @cindex Deleting lines @cindex Undeleting lines @cindex Block operations @cindex Clip usage @cindex Multiple documents An editor is presumably used for editing text. If you decide not to edit text, you probably don't want to use @code{ne}, because that's all it does---it edits text. It does not play @code{Tetris}. It does not evaluate recursive functions. It does not solve your love problems. It just allows you to edit text. The design of @code{ne} makes editing extremely natural and straightforward. There is nothing special you have to do to start editing once you've started @code{ne}. Just start typing, and the text you type shows up in your document. @code{ne} provides two ways of deleting characters: the @key{Backspace} key (or @kbd{@key{Control}-H}, if you have no such key) and the @key{Delete} key. In the former case you delete the character to the left of the cursor, while in the latter case you delete the character just under the cursor. This is in contrast with many @sc{un*x} editors, which for unknown reasons decide to limit your ways of destroying things---something notoriously much funnier than creating. (See @ref{DeleteChar} and @ref{Backspace}.) If you want to delete a line, you can use the @code{DeleteLine} command, or @kbd{@key{Control}-Y}. A very nice feature of @code{ne} is that each time a nonempty line is deleted, it is stored in a temporary buffer from which it can be undeleted via the @code{UndelLine} command or @kbd{@key{Control}-U}. (See @ref{DeleteLine} and @ref{UndelLine}.) If you want to copy, cut, paste, shift or erase a block of text, you have to set a mark. This is done via the @code{Mark} command, activated by choosing the @samp{Mark Block} item of the @samp{Edit} menu, or by pressing @kbd{@key{Control}-B} (think ``block''). This command sets the mark at the current cursor position. Whenever the mark is set, the text between the mark and the cursor can be cut, copied or erased. Note that by using @kbd{@key{Control}-@@} you can set a @emph{vertical} mark instead, which allows you to mark rectangles of text. Whenever a mark has been set, either an @samp{M} appears on the command line or a @samp{V} appears if the mark is vertical. If you forget where the mark is currently, you can use the @samp{Goto Mark} menu item of the @samp{Search} menu to move the cursor to it. The block of text you cut or copy is saved in a @dfn{clip}, which you can @samp{Paste} somewhere else in your document, or save it to a file with the @samp{Save Clip...} menu item of the @samp{Edit} menu. You can also load a file directly into a clip with @samp{Open Clip...}, and @samp{Paste} it anywhere. All such operations act on the @dfn{current clip}, which is by default the clip 0. You can change the current clip number with the @code{ClipNumber} command. @xref{ClipNumber}. One of the most noteworthy features of @code{ne} is its @dfn{unlimited undo/redo} capability. Each editing action is recorded, and can be played back and forth as much as you like. Undo and redo are bound to the function keys @key{f5} and @key{f6}. Another interesting feature of @code{ne} is its ability to load an unlimited number of documents. If you activate the @code{NewDoc} command (using the @samp{Document} menu or the command line), a new, empty document will be created. You can switch between your documents with @key{f2} and @key{f3}, which are bound to the @code{PrevDoc} and @code{NextDoc} commands. If you have a lot of documents, the @samp{Select...} menu item (@key{f4}) prompts you with the list of names of currently loaded documents and allows you to choose directly which to edit. In that list, names of documents with unsaved changes will be bold. You can also change their relative order in that list with the @key{f2} and @key{f3} keys. @node Basic Preferences @section Basic Preferences @cindex Preferences @cindex Flags @cindex Insert mode @cindex Automatic preferences @cindex Virtual Extensions @code{ne} has a number of @dfn{flags} that specify alternative behaviors, the most prototypical example being the @dfn{insert} flag, which specifies whether the text you type is inserted into the existing text or replaces it. You can toggle this flag with the @samp{Insert} menu item of the @samp{Prefs} menu, or with the @key{Insert} key of your keyboard. (@dfn{Toggle} means to change the value of a flag from true to false, or from false to true; @pxref{Insert}.) Another important flag is the @dfn{free form} flag, which specifies whether the cursor can be moved beyond the right end of each line of text or only to existing text (a la @code{vi}). Programmers usually prefer non free form editing; text writers seem to prefer free form. See @ref{FreeForm} for some elaboration. The free form flag can be set with the @samp{Free Form} menu item of the @samp{Prefs} menu. At this point, we suggest you explore by trial and error the other flags of the @samp{Prefs} menu, or try the @code{Flags} command (@pxref{Flags}), which explains all the flags and the commands that operate on them. We prefer spending a few words discussing @dfn{automatic preferences} or @dfn{autoprefs}, and @dfn{default preferences} or @dfn{defprefs}. Having many flags ensures a high degree of flexibility, but it can turn editing into a nightmare if you have to turn on and off dozens of flags for each different kind of file you edit. @code{ne}'s solution is to load your default preferences whenever @code{ne} is run before loading any file, then additionally set your stated preferences automatically for each @dfn{file type} as files are loaded. A file's type is determined by the @dfn{extension} of its file name, that is, the last group of letters after the last dot. For instance, the extension of @file{ne.texinfo} is @samp{texinfo}, the extension of @file{source.c} is @samp{c}, and the extension of @file{my.txt} is @samp{txt}. Thus, when you select the @samp{Save Def Prefs} menu item or use the @code{SaveDefPrefs} command, a special preferences file named @file{.default#ap} is saved. In addition to other preferences, this file also includes a small set of preferences which are global to @code{ne} rather than specific to particular document types. These preferences are: @code{FastGUI}, @code{RequestOrder}, @code{StatusBar} and @code{VerboseMacros}; @pxref{FastGUI}, @xref{RequestOrder}, @xref{StatusBar}, and @xref{VerboseMacros}. These extra preferences are not saved by the @code{SaveAutoPrefs} command. By contrast, whenever you select the @samp{Save AutoPrefs} menu item, @code{ne} saves the flags of your current document to be used when you load other files with the same extension. These @dfn{autoprefs} are saved in a file in your @file{~/.ne} directory. This file has the same name as the extension of the current document with @samp{#ap} appended to it. It contains all the commands necessary to recreate your current document's flag settings. Whenever you open a file with this file name extension, @code{ne} will automagically recreate your preferred flag settings for that file type. (There is a flag that inhibits the process; @pxref{AutoPrefs}.) Similar to preference flags, the current syntax definition is specific to the current document type, so it also is saved in autoprefs files by the @code{SaveAutoPrefs} command or @samp{Save AutoPrefs} menu; it is not saved in the @file{.default#ap} file. Note that a preferences file---whether @file{.default#ap} or an AutoPrefs file--- is just a macro (as described in the following section). Thus, it can be edited manually if necessary. Some files have no extension, but the file type can be discerned by simple examination. Consider for example a file named @file{example} which contains XML. You may reasonably expect it to be treated as an @file{.xml} file rather than a generic file. For the purposes of applying automatic preferences and syntax definitions, @code{ne} provides a mechanism for overriding a wrong or missing extension with a @dfn{virtual extension} based on a document's contents. You do this by creating a @file{~/.ne/.extensions} file which is fully described in the @ref{Virtual Extensions} section. @node Basic Macros @section Basic Macros @cindex Macro definition @cindex Recording a macro @cindex Saving a macro @cindex Interrupting a macro @cindex Interrupt character @cindex Caching a macro @cindex Unloading macros @cindex Executing a macro @cindex Comments in a macro Very often, the programmer or the text writer has to repeat some complex editing action over a series of similar blocks of text. This is where @dfn{macros} come in. A @dfn{macro} is a stored sequence of commands. Any sequence of commands you find yourself repeating is an excellent candidate for being made into a macro. You could create a macro by editing a document that only contains valid @code{ne} commands and saving it, but by far the easiest way to create a macro is to have @code{ne} record your actions. @code{ne} allows you to record macros and then play them (execute the commands they contain) many times. You can save recorded macros as files for future use, edit them, or bind them to any key. You could even reconfigure each key of your keyboard to play a complex macro if you wanted to. @code{ne} can have any number of named macros loaded at the same time. In addition, each document has one unnamed macro in its @dfn{current macro} buffer. Named macros are loaded from---and get the name of---files, while each document's unnamed current macro buffer is where your recorded macro is held before you save it, play it, or record over it. Recording a macro is very simple. The keystroke @kbd{@key{Control}-T} starts and stops recording a macro. When you start recording a macro, @code{ne} starts recording all your actions (with a few exceptions). You can see that you are recording a macro if an @samp{R} appears on the status bar. When you stop recording (again using @kbd{@key{Control}-T}), @code{ne} throws away the current document's current macro and replaces it with the one you just finished recording. You can then play the macro with the @samp{Play Once} item of the @samp{Macros} menu or with the @key{f9} key. If you want to repeat the action many times, the @code{Play} command allows you to specify a number of times to repeat the macro. You can always interrupt the macro's execution with @kbd{@key{Control}-\}. These commands are omitted from recorded macros: @example Escape Help Play Exec KeyCode Quit Flags Macro Record @end example A recorded macro has no name. It's just an anonymous sequence of commands associated with your current document. It will go away when you record another macro, close the document, or exit @code{ne}. If you want to save your recorded macro for future use, you can give it a name by saving it with the @samp{Save Macro...} menu item or the @code{SaveMacro} command. The macro is saved as a regular text file in your current directory by default or whatever directory you specify when prompted for the macro's name. If you save it in your @file{~/.ne} directory then it will be easy to access it later from any other directory. The @samp{Open Macro...} menu item and the @code{OpenMacro} command load a macro from a file into the current document's macro buffer just as if you had just @code{Record}ed it. The current setting of your @code{VerboseMacros} flag determines whether long or abbreviated command names are used when saving a macro. For your convenience, @code{SaveMacro} will also convert consecutive @code{InsertChar} commands into single---usually much more readable--- @code{InsertString} commands, but only if all the inserted characters are simple printable characters, and only if the macro contains no @code{Undo} commands or macro invocations later in the macro. Any macro can be loaded from a file and played with the @samp{Play Macro...} menu item or the @code{Macro} command. (This won't modify the recorded anonymous macro that may be in the @dfn{current macro} buffer; @code{OpenMacro} does that.) Useful macros can be permanently bound to a keystroke as explained in @ref{Key Bindings}. Moreover, whenever a command line's first non-blank character is an ASCII letter but does not specify one of @code{ne}'s built in commands, it is assumed to specify the name of a macro to execute. Thus, you can execute macros just by typing their file names at the command line. If the file containing your macro is somewhere besides your current directory or your @file{~/.ne} directory, and you have not already loaded it, then use the @code{Macro} command on the command line followed by the path to the macro. That's only necessary the first time you use such a macro. From then on, you can use the macro's base name on the command line without the preceding @code{Macro} command. If the first attempt to open a macro fails, @code{ne} checks for a macro with the given name in your @file{~/.ne} directory. This allows you to program simple extensions to @code{ne}'s command set. For instance, all automatic preferences macros---which are just specially named macros that contain only commands to set preferences flags---can be executed just by typing their names. For example, if you have an automatic preference for the @samp{txt} extension, you can set @code{ne}'s flags exactly as if you had loaded a file ending with @samp{.txt} by typing the command @code{txt#ap}. In general, it is a good idea to save frequently used macros in @file{~/.ne} so that you can invoke them by name without specifying a path regardless of your current directory. On the other hand, if you have a macro that is customized for one document or a set of documents that you store in one directory, then you might want to save the macro in that directory instead. If you do, then you would want to @code{cd} to that directory before you start @code{ne} so that you can access that macro without specifying a path. If your macro has the same name as one of @code{ne}'s built-in commands, you can only access it with the @code{Macro @var{name}} command. Built-in command names are always searched before the @code{ne} command interpreter looks for macros. The system administrator may make some macros available from the @file{macros} subdirectory of @code{ne}'s global directory. @xref{Arguments}. Since loading a macro each time it is invoked would be a rather slow and expensive process, once a named macro has been executed it is cached internally. Subsequent invocations of the named macro will use the cached version. @noindent @strong{Warning:} while path and file names are case sensitive when initially loading macros, loaded macro names are @emph{not} case sensitive or path sensitive. @code{ne} only caches the file name of an already loaded macro, not the path, and it uses a case insensitive comparison when resolving command and macro names. As such, if you invoke @file{~/foobar/MyMacro}, @code{ne} remembers it with the case-insensitive name @file{mymacro}; a subsequent call for @file{/usr/MYMACRO} will instead find and use the cached version of @file{~/foobar/MyMacro}. You can clear all macros from the cache by using the @code{UnloadMacros} command. @xref{UnloadMacros}. The behaviour of macros may vary with different preferences. If the user changes the @code{AutoIndent} and @code{WordWrap} flags, for example, new lines and new text may not appear the way they would have when a macro was recorded. A good general purpose macro avoids such problems by using the @code{PushPrefs} command first to preserve the current preferences. The macro then sets any preferences that could affect its behaviour. Once that is taken care of it can get on with the actual work for which it were intended. Finally, it will use the @code{PopPrefs} command to restore the original preferences. Note that if a macro stops before it restores the preferences (either by the user pressing @kbd{@key{Control}-\} or by a command failing) then dealing with the changed preferences falls to the user. Any changes made to a document by a macro are recorded just as if you had entered the text and commands yourself. Therefore you can use the @code{Undo} command to roll back those changes one at a time. This can be useful especially when developing macros, but you may want to be able to undo all the changes made by a macro with a single @code{Undo} command. The @code{AtomicUndo} command makes this possible. If you add @code{AtomicUndo +} at the start of your macro and @code{AtomicUndo -} at the end, then the @code{Undo} and @code{Redo} commands will handle all changes made by your macro atomically, i.e., as if they had been made by a single command, even if your macro calls other macros which could themselves contain matching @code{AtomicUndo +} and @code{AtomicUndo -} commands. @xref{AtomicUndo}. Leading spaces in macros are ignored, so you can indent your macros to make them more readable. Any line in a macro that starts with a non-blank, non-alphabetical character is considered a comment, so you can add comments to a macro by starting a line with @samp{#}. Recorded macros sometimes have comments added to them indicating calls to other macros. Macros can operate across multiple documents, by using the @code{NextDoc} and @code{PrevDoc} commands for example. When you stop recording, the unnamed macro is associated with the current document, replacing that document's prior unnamed macro. You can cancel in-progress macro recording---and thus avoid wiping out a document's existing unnamed macro---with the @code{Record 0} command, or by selecting @code{Record Cancel} from the @code{Macro} menu. You can append additional recorded commands to your document's current macro with the @code{Record 1} command, or by selecting @code{Record Append} from the @code{Macro} menu. Finally, you can insert the current document's unnamed macro into your in-progress macro recording as many times as you like either by pressing the @key{f9} key or entering the @code{Play 1} command. Although the unnamed macro is distroyed and replaced when you stop recording your new macro, until then it's available for use like any other. @node More Advanced Features @section More Advanced Features @cindex UTF-8 support @cindex Bookmarks @cindex Automatic Completion @cindex Automatic Bracket Matching @cindex MS-DOS files @cindex File requester @cindex Binary files @cindex Executing @sc{uni*x} commands @subsection UTF-8 support @code{ne} can load and manipulate UTF-8 files transparently, in particular on systems that provide UTF-8 I/O. @xref{UTF-8 Support}. @subsection Bookmarks It often happens that you have to browse through a file, switching frequently between a small number of positions. In this case, you can use @dfn{bookmarks}. There are up to ten bookmarks per document, each designated by a single digit, with the default being @samp{0}. You can set them with the @code{SetBookmark} command, and you can return to any set bookmark with the @code{GotoBookmark} command. Another pair of bookmarks designated by @samp{<} and @samp{>} mark the start and end points of the last block of text you pasted into your current document. So for example @code{GotoBookmark >} will take you to the end of the most recently pasted text. Also, @code{ne} sets an automatic bookmark (designated by @samp{-}) at your current position in a document whenever you use the @code{GotoBookmark} command. You can use a @code{GotoBookmark -} command to return to the location of the previous @code{GotoBookmark} command. Doing so will reset the automatic bookmark, so that subsequent @code{GotoBookmark -} commands will switch between those two locations. The special parameters @samp{+1} and @samp{-1} indicate the next or previous set bookmark in conjunction with @code{GotoBookmark} and @code{UnsetBookmark}, but reference the next or previous unset bookmark when used with @code{SetBookmark}. A sequence of @code{GotoBookmark +1} commands lets you easily cycle through all your set bookmarks. Finally, the special parameter @samp{?} causes @code{SetBookmark} and @code{GotoBookmark} to prompt you for a bookmark designation. This prompt includes an indication of which bookmarks are already set for the current document. @xref{SetBookmark}, @ref{GotoBookmark}, and @ref{UnsetBookmark}. Note that in the default configuration no key binding is assigned to these commands. If you use them frequently, you may want to change the key bindings. @xref{Key Bindings}. @subsection Automatic Completion The @code{AutoComplete} command helps you extend a given prefix with matching words from your open documents. You can specify the @code{AutoComplete} command and prefix on the command line, or you can enter the prefix directly into your document and activate the @code{AutoComplete} command. With the cursor at the right end of your prefix, activate the @code{AutoComplete} command by entering either the @key{Escape}-@key{Tab} or the @key{Escape}-@key{I} key sequence, or the @kbd{@key{Control}-@key{META}-@key{I}} key combination, or by selecting @code{AutoComplete} from the @code{Extras} menu. If the prefix can be extended unambiguously, the extension will be immediately inserted into your document (this is the case, for instance, if only one word matches the prefix), and a message will tell you whether the extension is an actual word or just the longest possible extension (for instance, if you expand @samp{fo} and your document contains @samp{foobar} and @samp{foofoo} then the partial match will be @samp{foo}). Otherwise, @code{ne} presents you with a list of all matching words: choose the one you want and press @key{Return}, to select it; otherwise, press @key{f1}, @key{Escape} or @key{Escape}-@key{Escape} to cancel the completion operation. The current state of the @code{CaseSearch} flag determines whether the prefix match is case sensitive. Any matching words which only exist in other open documents but not the current one are displayed in bold with an asterisk; think of that as a warning that if you select one of these bold words you will introduce a new word into your current document. Plain words already exist somewhere in your current document. @xref{AutoComplete}, and @ref{CaseSearch}. @subsection Automatic Bracket Matching Unless you tell it not to (with the @code{AutoMatchBracket} command), @code{ne} will highlight any recognized bracket that matches the bracket your cursor is on if that matching bracket is currently visible on your screen. Recognized bracket pairs are @samp{@{@}}, @samp{()}, @samp{[]}, @samp{<>}, and @samp{`'}. @xref{AutoMatchBracket}. @subsection MS-DOS files @code{ne} will detect automagically the presence of MS-DOS line terminators (CR/LFs) and set the CR/LF flag. When the file will be saved, the terminators will be restored correctly. You can change this behaviour using the @code{PreserveCR} and @code{CRLF} commands. @xref{PreserveCR}, and @ref{CRLF}. @subsection Binary files @code{ne} allows a simplified form of @dfn{binary editing}. If the binary flag is set, only NULLs are considered newlines when loading or saving. Thus, binary files can be safely loaded, modified and saved. Inserting a new line or joining two lines has the effect of inserting or deleting a NULL. Be careful not to mismatch the state of the binary flag when loading and saving the same file. @subsection File requester The @code{NoFileReq} command deactivates the file requester. It is intended for ``tough guys'' who always remember the names of their files and can type them at the speed of light (maybe with the help of the completer, which is activated by the @key{Tab} key; @pxref{The Input Line}). @subsection Executing @sc{un*x} commands There are three ways to execute @sc{un*x} commands from within @code{ne}. The @code{System} command can run any @sc{un*x} command; you will get back into @code{ne} as soon as the command execution terminates. @xref{System}. The @code{Through} (@kbd{@key{Meta}-T}) command (which can be found in the @samp{Edit} menu), however, is much more powerful; it cuts the current block, passes it as standard input to any @sc{un*x} command, and pastes the command's output at the current cursor position. This provides a neat way to pass a part of your document through one of @sc{un*x}'s many @dfn{filter commands} (commands that read from standard input and write to standard output, e.g., @code{sort}). @xref{Through}. Finally, you can use the @code{Suspend} (@kbd{@key{Control}-Z}) command to temporarily stop @code{ne} and return to your command shell. @xref{Suspend}. @subsection Advanced key bindings @code{ne} allows you to associate any keystroke with any command, both built-in commands (with or without parameters) and macros. These associations are referred to as @dfn{key bindings}, which you define in your @code{~/.ne/.keys} file. The @code{KeyCode} command allows you to see the key code @code{ne} sees in response to any key or key combination on your keyboard. It also shows the command string currently bound to that key code. This is described in @ref{Key Bindings}. The following chapters provide an exhaustive list of the remaining features of @code{ne}. @xref{Reference}. @node Reference @chapter Reference In this chapter we shall methodically overview each part of @code{ne}. It is required reading for becoming an expert user because some commands and features are not available through menus. @menu * Arguments:: * The Status Bar:: * The Input Line:: * The Command Line:: * The Requester:: * Syntax Highlighting:: * Menus:: * Regular Expressions:: * Automatic Preferences:: * Emergency Save:: * UTF-8 Support:: @end menu @node Arguments @section Arguments @cindex Arguments @cindex Global Directory @cindex Startup macro @cindex Skipping configuration files @cindex Setting configuration file names The main arguments you can give to @code{ne} are the names of files you want to edit. They will be loaded into separate documents. If you specify @code{--help} or @code{-h} anywhere on the command line, a simple help text describing @code{ne}'s arguments will be printed. The @code{+@var{N}} option causes @code{ne} to advance to the @var{N}th line of the next document loaded. This option is fairly common among editors and text display programs like @code{vi} and @code{less}. The @var{N} itself is optional. Without it, a bare @code{+} on the command line causes @code{ne} to advance to the last line of the first document. You can specify a line and column as @code{+N,M}. Any non-digit can be used to separate the @var{N} from the @var{M}. As it only affects the next document loaded, it can appear multiple times on the command line. The @code{--binary} option causes @code{ne} to load the next document in binary mode. Binary mode treats the normal line termination characters as any other character and only breaks lines on NULL characters. Like @code{+N,M}, @code{--binary} only affects the next document loaded, and it can appear multiple times on the command line. See @ref{Binary}. The @code{--read-only}/@code{--readonly}/@code{--ro} option causes @code{ne} to load the next named file into a read-only document. You can't modify a read-only document without first taking special action such as turning off the read-only flag. You can still @code{Save} (@ref{Save}) a read-only document to a file if the file's permissions allow it, but @code{ne} will prompt you before attempting to save a document marked read-only. The @code{--read-only} option only affects the next document loaded, so it can appear multiple times on the command line. A document's read-only flag is automatically set when a file is loaded if the corresponding file is not writable (as determined by the @code{access()} system call) regardless of whether the @code{--read-only} option is used. See @ref{ReadOnly}. The @code{--no-config}/@code{--noconfig} option skips the reading of the key bindings and menu configuration files (@pxref{Configuration}). This is essential if you are experimenting with a new configuration and you make mistakes in it. The @code{--prefs @var{extension}} option makes @code{ne} load a specified set of automatic preferences, that is, those associated with the provided extension, instead of the default ones, before loading the first file. It can be useful, for instance, when piping a file into @code{ne} or when reading from named pipes, as in those cases there is no file extension from which @code{ne} can guess the correct preferences. Note that preferences are cloned from the current document when a new document is created, so if you open a number of files without extension this option will propagate to all of them. The @code{--macro @var{filename}} option specifies the name of a macro that will be started just after all documents have been loaded. A typical macro would move the cursor to a certain line. The @code{--keys @var{filename}} option and the @code{--menus @var{filename}} option specify a name different from the default one (@file{.keys} and @file{.menus}, respectively) for the key bindings and the menu configuration files. For key bindings, @code{ne} loads system wide files first, then those in your @file{~/.ne} directory, and finally those in the current directory. Key bindings loaded later may override those loaded previously. In each location, @file{~/.ne} will first look for a file named @file{.keys} (or whatever base name you specify with the @code{--keys} option), then a file with the same name followed by a dash and the value of your @code{TERM} environment variable. This is how you can create key bindings specifically for different terminals on a single system. Menu configuration, unlike key bindings, is not cumulative; @code{ne} looks first in your @file{~/.ne} directory possibly followed by the system wide global directory for a menu configuration file and loads only the first one it finds, if any. The @code{--ansi} and the @code{--no-ansi}/@code{--noansi} options manage @code{ne}'s built-in ANSI sequences. Usually @code{ne} tries to retrieve from your system some information that is necessary to handle your terminal. If for some reason this is impossible, you can ask @code{ne} to use a built-in set of sequences that will work on many terminals using the @code{--ansi} option (to be true, @code{ne} can be even compiled so that it uses directly the built-in set, but you need not know this). If you want to be sure (usually for debugging purposes) that @code{ne} is not using the built-in set, you can specify @code{--no-ansi}. The @code{--no-syntax} option disables @code{ne}'s normal syntax highlighting capability. For most editing situations, this would be unnecessary, but for extremely large files it may be helpful. Syntax highlighting incurs small memory usage and processor overhead penalties for each line of text. The @code{--no-syntax} option eliminates that overhead. Note that files longer than ten million bytes will have syntax highlighting disabled by default, but it is possible to re-enable it. @xref{Syntax Highlighting}. The @code{--utf8} and @code{--no-utf8} options can be used to force or inhibit UTF-8 I/O, overriding the choice imposed by the system locale. Note, however, that in general it is more advisable to set the @code{LANG} environment variable to a locale supporting UTF-8 (you can usually see the locale list with @code{locale -a}). @xref{UTF-8 Support}. If you need to open a file whose name starts with @samp{--}, you can put @samp{--} before the filename, which will skip command recognition for the next word. You can use I/O redirection to pipe the output of other commands into your first document. For example, @example ls -l | ne file1.txt --read-only file2.txt @end example will open three documents: an unnamed document containing the output of the @code{ls -l} command, the contents of @file{file1.txt}, and the contents of @file{file2.txt} with the read-only flag set. It's possible to apply the @code{--binary}, @code{--read-only}, and @code{+N,M} options to the piped unnamed document by referencing it as a single @code{-}. Only the first such file name will reference the piped document (even if it isn't the first file name on the command line). Subsequent dashes will be considered normal file names. If you want the first dash to be treated like a normal file instead of a reference to the piped document, prefix the dash with @samp{--}. Consider these two command lines: @example ls -l | ne --read-only +3,8 - file1.txt - ls -l | ne file1.txt -- - --read-only +3,8 - ls -l | ne --binary file1.txt --read-only -- - @end example All three of these commands open @code{ne} with three documents: the output of the @code{ls -l} command will be in the first unnamed document, the contents of @file{file1.txt} will be in the second document, while the third document will contain the contents of the file @file{-} (or an empty document with that name if there is no such file). The first and second commands do exactly the same thing: the unnamed first document is marked read-only and the cursor is positioned on line 3 column 8, while the other two document are opened normally. In the case of the third command, @file{file1.txt} is opened in binary mode, the document named @file{-} is marked read-only, while the first, unnamed, document---which is not referenced on the command line---with the output from @code{ls -l} is opened normally. Finally, @code{ne} has a @dfn{global directory} where the system administrator can store macros, default preferences, and syntax definitions for all users of the system. The location of this directory is defined when @code{ne} is built, but you can override it by creating and exporting the @code{NE_GLOBAL_DIR} environment variable prior to invoking @code{ne}. If you load no files when you start @code{ne}, or if you invoke the @code{About} command, it will display a splash screen. The last line on that screen shows the global directory @code{ne} is using, if it exists, or an error message otherwise. @node The Status Bar @section The Status Bar @cindex Status bar @cindex Line and column numbers @cindex Fast GUI The last line of the screen, the @dfn{status bar}, is reserved by @code{ne} for displaying some information about its internal state. Note that on most terminals it is physically impossible to write a character on the last column of the last line, so we are not stealing precious editing space. The status bar looks more or less like this: @example L: 31 C: 25 12% iabcwfpvurt!MRPC@@8* 20 /foo/bar @end example The numbers after @samp{L:} and @samp{C:} are the line and column of the cursor position. The first line and the first column are both number 1. Then, @code{ne} shows the percentage of lines before the current line (it will be 0% on the first line, and 100% on the last line). Following that are a sequence of letters or dashes. These indicate the status of a series of flags which we shall look at later. The hexadecimal digits following the flags give the code for the character at the cursor, and are displayed optionally (@pxref{HexCode}). If your cursor is at or beyond the right end of the current line, the code disappears. The file name appearing after the character code is the file name of the current document. The left end of very long file names may be truncated to keep the right end visible. Of course, @code{ne} is keeping track internally of the complete file name. It is used by the @code{Save} command and as the default input for the @code{SaveAs} command. @xref{Save}, and @ref{SaveAs}. The displayed line and column numbers, the percentage indicator and the character code change when the cursor moves. This fact can really slow down cursor movement if you are using @code{ne} through a slow connection. If you find this to be a problem, it is a good idea to turn off the status bar using either the @samp{Status Bar} menu item of the @samp{Prefs} menu or the @code{StatusBar} command. @xref{StatusBar}. Alternatively you can turn on the fast GUI mode using either the @samp{Fast GUI} menu item of the @samp{Prefs} menu or the @code{FastGUI} command (@pxref{FastGUI}). In fast GUI mode the location of the mark is not highlighted, and status bar is not draw in reverse, so some additional optimization can be done when refreshing it. The letters after the line and column number represent the status of the flags associated with the current document. Flags that are off display a @samp{-} instead of a letter. Each flag also has an associated command. The @code{Flags} command describes them all when you don't have this manual handy. Here's the list in detail: @table @samp @item i appears if the insert flag is true. @xref{Insert}. @item a appears if the auto indent flag is true. @xref{AutoIndent}. @item b appears if the back search flag is true. @xref{SearchBack}. @item c appears if the case sensitive search flag is true. @xref{CaseSearch}. @item w appears if the word wrap flag is true. @xref{WordWrap}. @item f appears if the free form flag is true. @xref{FreeForm}. @item p appears if the automatic preferences flag is true. @xref{AutoPrefs}. @item v appears if the verbose macros flag is true. @xref{VerboseMacros}. @item u appears if the undo flag is true. @xref{DoUndo}. @item r appears if the read only flag is true. @xref{ReadOnly}. @item t or T appears as @samp{t} if the tabs flag is true, @samp{T} if the shifttabs flag is also true. @xref{Tabs}, @ref{ShiftTabs}. @item d appears if the deltabs flag is true. @xref{DelTabs}. @item B or ! appears if the binary flag is true. @xref{Binary}. @item ! appears in place of @samp{B} when not in binary mode and the last line of the document is not empty (i.e. the last line of the saved file would not be terminated). @item M or V appears if you are currently marking a block. @xref{Mark}. @item V can appear in place of @samp{M} if you are currently marking a vertical block. @xref{MarkVert}. @item R appears if you are currently recording a macro. @xref{Record}. @item P appears if the PreserveCR flag is true. @xref{PreserveCR}. @item C appears if the CRLF flag is true. @xref{CRLF}. @item @@ appears if UTF-8 I/O is enabled. @xref{UTF8IO}. @item A/8/U denotes the current document encoding---US-ASCII, 8-bit or UTF-8. @xref{UTF8}. @item * appears if the document has been modified since the last save, or if the @code{Modified} command was issued to set this flag. @xref{Modified}. This @samp{-} or @samp{*} may be underlined, which indicates the corresponding file's modification time has changed since the current document was loaded from or saved to that file. @end table Note that sometimes @code{ne} needs to communicate some message to you. The message is usually written over the status bar, where it stays until you do something. Any action such as moving the cursor or inserting a character will restore the normal status bar. @node The Input Line @section The Input Line @cindex Input line @cindex Escaping an input @cindex Immediate input @cindex Long input @cindex File name completion The bottom line of the screen is usually occupied by the status bar (@pxref{The Status Bar}). However, whenever @code{ne} prompts you for a command or file name or asks you to confirm some action, the bottom line becomes the @dfn{input line}. You can see this because a @dfn{prompt} is displayed at the start of the line, suggesting what kind of input is required. (Prompts always ends with a colon, so it is easy to distinguish them from @dfn{error messages}, which overwrite the status bar from time to time.) @code{ne} uses the input line in two essentially different ways: @dfn{immediate} input and @dfn{long} input. You can easily distinguish between these two modes because in immediate input mode the cursor is not on the input line, while for long input mode it is. Immediate input is used whenever @code{ne} needs you to specify a simple choice that can be expressed by one character (for example, @samp{y} or @samp{n}). When you type the character, @code{ne} will immediately accept and use your input. Most immediate inputs display a character just after the prompt. This character is the default response, which is used if you just press the @key{Return} key. Note that immediate input is not case sensitive. Moreover, if a yes/no choice is requested, @emph{anything} other than @samp{y} will be considered a negative response. Long input is used when a whole string is required. You can enter and edit your response to long inputs like a line of text in a document. Most key bindings related to line editing work on the command line exactly as they do in a document. This is true even of custom key bindings. Just edit as you are used to. Moreover, the you can paste the first line of the current clip using the keystroke that is bound to the @code{Paste} command, usually @kbd{@key{Control}-V}. If your long input is longer than the screen width, the input line scrolls to accommodate your text so you can input very long lines even on small monitors. (There is a limit of 2048 characters.) The default response to a long input is the response you gave to the previous long input. Your @emph{first action} when presented with a long input will either erase the default response or allow you to edit it. If the first thing you type is a printing character, the default response will be erased. Anything else (cursor movement for example) will allow you to edit it further. Long input also lets you access your previous long input responses with the up and down cursor commands (or with wider movement commands, such as start/end of file, page up/down, etc.). Once you find a previous input you like, you can edit it further. Long input history is not document specific, so you can recall any of your inputs regardless of which document was active when you entered it. Furthermore, @code{ne} saves the most recent long inputs in @file{~/.ne/.history} when you end your @code{ne} session and loads them again when you begin another @code{ne} session. Invoking the @code{Find} command, usually bound to @kbd{@key{Control}-F}, brings up a requester showing your prior inputs. You can close the requester with the @key{Escape} key, replace your input line with a highlighted prior entry with the @key{Enter} key, or insert that prior entry into your input line with the @key{Tab} key. When asked to input a number, you can choose between decimal, octal and hexadecimal notation in the standard way: a number starting with @samp{0} is considered in octal, a number starting with @samp{0x} is considered in hexadecimal, and in all other cases decimal base is assumed. Whenever a file name is requested, you can type a partial file name and @dfn{complete} it with the @key{Tab} key. @code{ne} will scan the current directory (or the directory that you partially specified) and search for the files matching your partial suggestion. The longest prefix common to all such files will be copied to the input line (@code{ne} will beep if no completion exists). It's easier done than said---just try. If you press @key{Tab} again, you will be brought into the file requester: only the files and directories matching your partial specification will appear, and as usual you will be able to navigate and select a file or escape. @xref{The Requester}. Note that @code{ne} considers the @emph{last word} on the input line the partial file name to complete, no matter where the cursor is currently (you must use quotes if the name contains spaces, even if it is the only item on the input line). Complete long input with the @key{Return} key. You can cancel a long input using @key{f1}, @key{Escape}, @key{Escape}-@key{Escape} or any key that is bound to the @code{Escape} command. The effect will vary depending on what your were requested to input, but the execution of the command requiring the input will stop. @node The Command Line @section The Command Line @cindex Command line The command line is a typical (topical) way of controlling an editor on character driven systems. It has some advantages over menus in terms of access speed, but it is not desirable from a user interface point of view. @code{ne} has a command line that should be used whenever strange features have to be accessed, or whenever you want to use a command that you are familiar with and that is not bound to any key. You have two ways to access the command line: by activating the menu and typing a colon (@samp{:}) or by typing @kbd{@key{Control}-K} (or any key that is bound to the @code{Exec} command; @pxref{Exec}). The first method will work regardless of any key binding configuration if you activate the menus with the @key{Escape} key since that key cannot be reconfigured. Of course, there is also a menu entry that does the same job. Once you activate the command line, the status bar will turn into an input line (@pxref{The Input Line}) with a @samp{Command:} prompt waiting for you to do a long input. In other words, you can now type any command (possibly with arguments), and when you press @key{Return}, the command will be executed. If the command you specify does not appear in @code{ne}'s internal tables, it is considered to be the name of a macro. @xref{Basic Macros}, for details. @node The Requester @section The Requester @cindex Requester @cindex File requester @cindex Interrupting directory scanning @cindex Help requester In various situations, @code{ne} needs to ask you to choose one string from several (where ``several'' can mean a lot). For this kind of event, the @dfn{requester} is issued. The requester displays the strings in as many columns as possible and lets you move with the cursor from one string to another. The strings can fill many screens, which are handled as consecutive pages. Most navigation keys work exactly as in normal editing. This is true even of custom key bindings. Thus, for instance, you can page up and down through the list with @kbd{@key{Control}-P} and @kbd{@key{Control}-N} (in the standard keyboard configuration). A special feature is bound to printing characters: the requester progressively advances to entries that match the characters you type without regard to case. You can use @key{Backspace} to incrementally undo your matched characters. This progressive matching works in two modes which you can switch between on the fly with either the @key{Insert} or @key{Delete} key. In the default mode, the cursor indicating your current selection simply advances to the next matching entry (if there is one). In the other mode, all entries which don't match the characters you've entered are removed from the list so you only see the matching entries. The @key{Backspace} key incrementally returns them to your list as your match becomes less specific. You can switch between the two modes as often as you wish while searching for your desired entry. This lets you quickly navigate large lists to get to the entries you really want. One example of a requester is the list of commands appearing when you use the @code{Help} command. Another is the list of document words matching a prefix given to the @code{AutoComplete} command. A third example is the file requester that @code{ne} issues whenever a file operation is going to take place. In this case, pressing @key{Return} while on a directory name will enter that directory and refresh the requester with that directory's entries. Note also that, should the requester take too long to appear, you can interrupt the directory scanning with @kbd{@key{Control}-\}. However, the listing will likely be incomplete. Yet another example of a requester is the list of documents you currently have open. This requester is displayed when you use the @code{Select...} entry from the @code{Documents} menu, or invoke the @code{SelectDoc} command with the @key{f4} key. Documents with unsaved changes will be bold (if your terminal supports bold) and marked with an asterisk. These documents are generally listed in the order they were opened. However, in this requester you can reorder these documents by using the keys bound to the @code{NextDoc} and @code{PrevDoc} commands, usually @key{f2} and @key{f3}. Any document reordering and selection will only take effect if you exit the requester with the @key{Return} key. You can also save named documents and close unmodified documents without leaving the @code{SelectDoc} requester by using the key bound to the @code{Save} and @code{CloseDoc} commands respectively, usually @kbd{@key{Control}-S} and @kbd{@key{Control}-Q}. You can't close the last document this way because it would cause @code{ne} to exit. Regardless of the type of requester, you can confirm your selection with @key{Return} just as with the input line (@pxref{The Input Line}), or you can escape the requester without making a selection with @key{f1} or the @key{Escape} key (or whatever has been bound to the @code{Escape} command). Moreover, if you are selecting a file name through the requester there is a third possibility: by escaping with the @key{Tab} key, the file or directory name that the cursor is currently on will be copied to the input line. This allows you to choose an existing name with @key{Tab} and modify the name on the input line before hitting @key{Return}. Note that there are two items that always appear at the top of a file requester: @file{./} and @file{../}. The first one represents the current directory and can be used to force a reread of the directory. The second one represents the parent directory and can be used to move up by one directory level. The path to file names and directories selected through the requester will be relative to the current directory, i.e. the directory you were in when you invoked @code{ne}. The exception is when you've entered a path on the command line that starts with a @key{/}, then hit @key{Tab} to invoke the requester. In that case the path eventually returned by the requester will be an absolute path. (Note that you can change the current document's name from relative to absolute or absolute to relative with the @code{NameConvert} command either on the command line or from the @code{Extras} menu.) All requesters present their selections by default in ``row major order,'' which means the second string is on the same row as the first but to its right, at the top of the second column, and so on across each row before filling in the next row down. If you prefer your lists displayed in ``column major order''---the first, second, and third strings are in the same column and each column is filled before starting on the next column to the right---then use the @code{RequestOrder} command to switch that preference. The setting can be stored in your default preferences the next time you save them. See @ref{Preferences Commands}. @node Syntax Highlighting @section Syntax Highlighting @cindex Syntax Highlighting Syntax highlighting is particularly useful for programming language text or other types of documents which have a strictly defined syntax. Colors indicate different syntactic categories of text according to the syntax definition in use. Syntax definitions are stored in separate files. @code{ne} comes with a suite of syntax definitions for many popular programming languages and other common text file types. When you load a file, @code{ne} selects the appropriate syntax definition as determined by the filename extension in much the same way autoprefs are loaded. (See @ref{Virtual Extensions} for ways to override a file's extension based on file contents.) It also contains a built-in table of common filename extensions that share the same syntax definitions. For example, both @samp{cbl}, and @samp{cob} files use the @samp{cobol} definition. See the @ref{Syntax} command for the complete list of built-in extension mappings. If there is no matching syntax definition for the filename extension, or if the document you are editing has no filename yet, or you just want to try a different syntax definition, you can load and use the syntax definition of your choice with the @code{Syntax} command. It takes the syntax name as a parameter. For example, the name ``@code{c}'' works for C syntax files with extensions @samp{.c}, @samp{.h}, @samp{c++}, etc. @code{ne} searches for the specified syntax definition file in the @samp{syntax} subdirectory of your @file{~/.ne} directory first. If not found there, @code{ne} then looks in the @samp{syntax} subdirectory of @code{ne}'s global directory for the syntax definition file. @xref{Arguments}. With no parameter, the @code{Syntax} command prompts you for a syntax to load, the offered default being the currently loaded syntax if there is one. Use the @key{Tab} key at that prompt to get a list of available syntax recognizers. One syntax definition you may find useful for any type of text file is called simply @samp{tabs}. It highlights the @sc{tab}s in your text so you can distinguish them from regular spaces. You can create your own syntax definitions and store them in your @file{~/.ne/syntax} directory (actually, modifying the colors of an existing definition is much easier; @pxref{Hints and Tricks}). A complete explanation of syntax specifications is beyond the scope of this document, but the existing definition files should prove to be useful examples. In particular, the @file{syntax/c.jsf} file contains some particularly helpful comments. Syntax definition files have a @samp{.jsf} extension. Do not include that extension when using the @code{Syntax} command. Your own syntax recognizers will be preferred over the global recognizers. If you use the @key{Tab} key at the syntax prompt to display the requester of extant recognizers, yours will be marked with an asterisk and bold if your terminal supports that. Syntax highlighting does incur a slight penalty in memory used per line of text, and it also consumes some CPU resources. For small to medium sized files you'll probably never notice. But for extremely large files---on the order of the size of your system's RAM---the difference could be significant. If you invoke @code{ne} with the @code{--no-syntax} parameter, @code{ne} will disable the syntax highlighting mechanism entirely, freeing up the memory and CPU otherwise consumed. (Note that if you are that tight on memory, you may need to disable the undo buffer as well. @xref{DoUndo}.) On the other hand, @code{ne} will silently disable syntax highlighting on files longer than ten million bytes, but you can force it using the @code{Syntax} command. Note that there is a basic difference between these two cases: when you use the @code{--no-syntax} parameter, the additional memory is not allocated at all, and syntax highlighting cannot be enabled without restarting @code{ne}. On the contrary, the automatic disabling for long files keeps only @code{ne} from computing the actual highlighting, and it can be overridden as explained above. @code{ne} uses code from another editor---the GPL-licensed @code{joe}---for its syntax highlighting capabilities. Because of this fact, the syntax definition files are identical, even to the @samp{.jsf} extension, which is an acronym for ``Joe's Syntax File''. It's possible that if both @code{joe} and @code{ne} are installed on your system that they share the same syntax file directory. @node Menus @section Menus @cindex Menus @code{ne}'s menus are extremely straightforward. The suggested way of learning their use is by trial and error, with a peek here and there at this manual when some doubts arise. You activate menus with the @key{f1} key, or in case your keyboard does not have such a key, @key{Escape}, @key{Escape}-@key{Escape} or any key that is bound to the @code{Escape} command. Move around the menus pressing with the cursor keys, the @key{Page Up} and @key{Page Down} keys (which move to the first or last menu item in a menu), and the @key{Home} and @key{End} keys (which move to the first or last menus). You can also move around menus and menu items by pressing the alphabetic keys; a lower case letter will move to the first item in the current menu whose name starts with the given letter; an upper case letter will move to the first menu whose name starts with the given letter. If you've activated the menus and you want to switch immediately to the command line, press the @key{:} key. The menus will clear and you'll find yourself on the command line. @xref{The Command Line}. Each menu item of @code{ne}'s standard menu corresponds to a single command. In explaining what each menu item allows you to do, we shall simply refer you to the section that explains the command relative to the menu item. If you plan to change @code{ne}'s menu (@pxref{Changing Menus}), you should take a look at the file @file{default.menus} that comes with @code{ne}'s distribution. It contains a complete menu configuration that clones the standard one. @menu * File:: * Documents:: * Edit:: * Search:: * Macros:: * Extras:: * Navigation:: * Prefs:: @end menu @node File @subsection File The File menu contains standard items that allow loading and saving files. Quitting @code{ne} (which doesn't save changes) or exiting @code{ne} (which does save changes) is also possible. @table @samp @item Open@dots{} @xref{Open}. @item Open New@dots{} @xref{OpenNew}. @item Save @xref{Save}. @item Save As@dots{} @xref{SaveAs}. @item Save All @xref{SaveAll}. @item Quit Now @xref{Quit}. @item Save&Exit @xref{Exit}. @item About @xref{About}. @end table @node Documents @subsection Documents The Documents menu contains commands that create new documents, destroy them, and browse through them. @table @samp @item New @xref{NewDoc}. @item Clear @xref{Clear}. @item Close @xref{CloseDoc}. @item Next @xref{NextDoc}. @item Prev @xref{PrevDoc}. @item Select@dots{} @xref{SelectDoc}. @end table @node Edit @subsection Edit The Edit menu contains commands related to cutting and pasting text. @table @samp @item Mark Block @xref{Mark}. @item Cut @xref{Cut}. @item Copy @xref{Copy}. @item Paste @xref{Paste}. @item Erase @xref{Erase}. @item Through @xref{Through}. @item Delete Line @xref{DeleteLine}. @item Delete EOL @xref{DeleteEOL}. @item Mark Vert @xref{MarkVert}. @item Paste Vert @xref{PasteVert}. @item Open Clip@dots{} @xref{OpenClip}. @item Save Clip@dots{} @xref{SaveClip}. @end table @node Search @subsection Search The Search menu contains commands related to searching for specific contents or locations within a document. @table @samp @item Find@dots{} @xref{Find}. @item Find RegExp@dots{} @xref{FindRegExp}. @item Replace@dots{} @xref{Replace}. @item Replace Once@dots{} @xref{ReplaceOnce}. @item Replace All@dots{} @xref{ReplaceAll}. @item Repeat Last @xref{RepeatLast}. @item Goto Line@dots{} @xref{GotoLine}. @item Goto Col@dots{} @xref{GotoColumn}. @item Goto Mark@dots{} @xref{GotoMark}. @item Match Bracket @xref{MatchBracket}. @item Set Bookmark @xref{SetBookmark}. @item Unset Bookmark @xref{UnsetBookmark}. @item Goto Bookmark @xref{GotoBookmark}. @end table @node Macros @subsection Macros The Macros menu contains commands related to creating and using macros. @table @samp @item Record @xref{Record}. @item Stop @xref{Record}. @item Replace@dots{} @xref{Replace}. @item Play Once @itemx Play Many@dots{} @xref{Play}. @item Play Macro@dots{} @xref{Macro}. @item Open Macro@dots{} @xref{OpenMacro}. @item Save Macro@dots{} @xref{SaveMacro}. @end table @node Extras @subsection Extras This menu contains a few special items that don't fit in obvious ways into other menus. @table @samp @item Exec@dots{} @xref{Exec}. @item Suspend @xref{Suspend}. @item Help@dots{} @xref{Help}. @item Refresh @xref{Refresh}. @item Undo @xref{Undo}. @item Redo @xref{Redo}. @item Undel Line @xref{UndelLine}. @item Center @xref{Center}. @item Shift Right @itemx Shift Left @xref{Shift}. @item Paragraph @xref{Paragraph}. @item Adjust View @itemx Center View @xref{AdjustView}. @item ToUpper @xref{ToUpper}. @item ToLower @xref{ToLower}. @item Capitalize @xref{Capitalize}. @end table @node Navigation @subsection Navigation The Navigation menu contains commands related moving around in a document. @table @samp @item Move Left @xref{MoveLeft}. @item Move Right @xref{MoveRight}. @item Line Up @xref{LineUp}. @item Line Down @xref{LineDown}. @item Prev Page @xref{PrevPage}. @item Next Page @xref{NextPage}. @item Page Up @xref{PageUp}. @item Page Down @xref{PageDown}. @item Start Of File @xref{MoveSOF}. @item End Of File @xref{MoveEOF}. @item Start Of Line @xref{MoveSOL}. @item End Of Line @xref{MoveEOL}. @item Top Of Screen @xref{MoveTOS}. @item Bottom Of Screen @xref{MoveBOS}. @item Incr Up @xref{MoveIncUp}. @item Incr Down @xref{MoveIncDown}. @item Prev Word @xref{PrevWord}. @item Next Word @xref{NextWord}. @end table @node Prefs @subsection Prefs The Prefs menu contains commands related to setting, storing, and using your preferred document flags. @table @samp @item Tab Size@dots{} @xref{TabSize}. @item Tabs as Spaces @xref{Tabs}. @item Insert/Over @xref{Insert}. @item Free Form @xref{FreeForm}. @item Status Bar @xref{StatusBar}. @item Hex Code @xref{HexCode}. @item Fast GUI @xref{FastGUI}. @item Word Wrap @xref{WordWrap}. @item Right Margin @xref{RightMargin}. @item Auto Indent @xref{AutoIndent}. @item Request Order @xref{RequestOrder}. @item Preserve CR @xref{PreserveCR}. @item Save CR/LF @xref{CRLF}. @item Load Prefs@dots{} @xref{LoadPrefs}. @item Save Prefs@dots{} @xref{SavePrefs}. @item Load AutoPrefs @xref{LoadAutoPrefs}. @item Save AutoPrefs @xref{SaveAutoPrefs}. @item Save Def Prefs @xref{SaveDefPrefs}. @end table @node Regular Expressions @section Regular Expressions @cindex Regular Expressions Regular expressions are a powerful way of specifying complex search and replace operations. @code{ne} supports the full regular expression syntax on US-ASCII and 8-bit documents, but has to impose a restriction on character sets when searching in UTF-8 text. @xref{UTF-8 Support}. @subsection Syntax The following section is taken (with minor modifications) from the GNU regular expression library documentation and is Copyright @copyright{} Free Software Foundation. A regular expression describes a set of strings. The simplest case is one that describes a particular string; for example, the string @samp{foo} when regarded as a regular expression matches @samp{foo} and nothing else. Nontrivial regular expressions use certain special constructs so that they can match more than one string. For example, the regular expression @samp{foo|bar} matches either the string @samp{foo} or the string @samp{bar}; the regular expression @samp{c[ad]*r} matches any of the strings @samp{cr}, @samp{car}, @samp{cdr}, @samp{caar}, @samp{cadddar} and all other such strings with any number of @samp{a}'s and @samp{d}'s. Regular expressions have a syntax in which a few characters are special constructs and the rest are @dfn{ordinary}. An ordinary character is a simple regular expression which matches that character and nothing else. The special characters are @samp{$}, @samp{^}, @samp{.}, @samp{*}, @samp{+}, @samp{?}, @samp{[}, @samp{]} , @samp{(}, @samp{)} and @samp{\}. Any other character appearing in a regular expression is ordinary, unless a @samp{\} precedes it. For example, @samp{f} is not a special character, so it is ordinary, and therefore @samp{f} is a regular expression that matches the string @samp{f} and no other string. (It does @emph{not} match the string @samp{ff}.) Likewise, @samp{o} is a regular expression that matches only @samp{o}. Any two regular expressions @var{a} and @var{b} can be concatenated. The result is a regular expression that matches a string if @var{a} matches some amount of the beginning of that string and @var{b} matches the rest of the string. As a simple example, we can concatenate the regular expressions @samp{f} and @samp{o} to get the regular expression @samp{fo}, which matches only the string @samp{fo}. Still trivial. Note: special characters are treated as ordinary ones if they are in contexts where their special meanings make no sense. For example, @samp{*foo} treats @samp{*} as ordinary since there is no preceding expression on which the @samp{*} can act. It is poor practice to depend on this behaviour; better to quote the special character anyway, regardless of where is appears. The following are the characters and character sequences that have special meaning within regular expressions. Any character not mentioned here is not special; it stands for exactly itself for the purposes of searching and matching. @table @samp @item . is a special character that matches anything except a newline. Using concatenation, we can make regular expressions like @samp{a.b}, which matches any three-character string which begins with @samp{a} and ends with @samp{b}. @item * is not a construct by itself; it is a suffix, which means the preceding regular expression is to be repeated as many times as possible. In @samp{fo*}, the @samp{*} applies to the @samp{o}, so @samp{fo*} matches @samp{f} followed by any number of @samp{o}'s. The case of zero @samp{o}'s is allowed: @samp{fo*} does match @samp{f}. @samp{*} always applies to the @emph{smallest} possible preceding expression. Thus, @samp{fo*} has a repeating @samp{o}, not a repeating @samp{fo}. @item + @samp{+} is like @samp{*} except that at least one match for the preceding pattern is required for @samp{+}. Thus, @samp{c[ad]+r} does not match @samp{cr} but does match anything else that @samp{c[ad]*r} would match. @item ? @samp{?} is like @samp{*} except that it allows either zero or one match for the preceding pattern. Thus, @samp{c[ad]?r} matches @samp{cr} or @samp{car} or @samp{cdr}, and nothing else. @item [ @dots{} ] @samp{[} begins a @dfn{character set}, which is terminated by a @samp{]}. In the simplest case, the characters between the two form the set. Thus, @samp{[ad]} matches either @samp{a} or @samp{d}, and @samp{[ad]*} matches any string of @samp{a}'s and @samp{d}'s (including the empty string), from which it follows that @samp{c[ad]*r} matches @samp{car}, @i{et cetera}. Character ranges can also be included in a character set, by writing two characters with a @samp{-} between them. Thus, @samp{[a-z]} matches any lower-case letter. Ranges may be intermixed freely with individual characters, as in @samp{[a-z$%.]}, which matches any lower case letter or @samp{$}, @samp{%} or period. Note that the usual special characters are not special any more inside a character set. A completely different set of special characters exists inside character sets: @samp{]}, @samp{-} and @samp{^}. As @samp{\} is not special inside character sets, you cannot use the shortcuts @samp{\s} or @samp{\w} there. To include a @samp{]} in a character set, you must make it the first character. For example, @samp{[]a]} matches @samp{]} or @samp{a}. To include a @samp{-}, you must use it in a context where it cannot possibly indicate a range: that is, as the first character, or immediately after a range. Note that when searching in UTF-8 text, a character set may contain US-ASCII characters only. @item [^ @dots{} ] @samp{[^} begins a @dfn{complement character set}, which matches any character except the ones specified. Thus, @samp{[^a-z0-9A-Z]} matches all characters @emph{except} letters and digits. Also in this case, when searching in UTF-8 text a complemented character set may contain US-ASCII characters only. @samp{^} is not special in a character set unless it is the first character. The character following the @samp{^} is treated as if it were first (it may be a @samp{-} or a @samp{]}). @item ^ is a special character that matches the empty string -- but only if at the beginning of a line in the text being matched. Otherwise it fails to match anything. Thus, @samp{^foo} matches a @samp{foo} that occurs at the beginning of a line. @item $ is similar to @samp{^} but matches only at the end of a line. Thus, @samp{xx*$} matches a string of one or more @samp{x}'s at the end of a line. @item \ has two functions: it quotes the above special characters (including @samp{\}), and it introduces additional special constructs. Because @samp{\} quotes special characters, @samp{\$} is a regular expression that matches only @samp{$}, and @samp{\[} is a regular expression that matches only @samp{[}, and so on. For the most part, @samp{\} followed by any character matches only that character. However, there are several exceptions: characters which, when preceded by @samp{\}, are special constructs. Such characters are always ordinary when encountered on their own. @item | specifies an alternative. Two regular expressions @var{a} and @var{b} with @samp{|} in between form an expression that matches anything that either @var{a} or @var{b} will match. Thus, @samp{foo|bar} matches either @samp{foo} or @samp{bar} but no other string. @samp{|} applies to the largest possible surrounding expressions. Only a surrounding @samp{( @dots{} )} grouping can limit the grouping power of @samp{|}. @item ( @dots{} ) is a grouping construct that serves three purposes: @enumerate @item To enclose a set of @samp{|} alternatives for other operations. Thus, @samp{(foo|bar)x} matches either @samp{foox} or @samp{barx}. @item To enclose a complicated expression for the postfix @samp{*} to operate on. Thus, @samp{ba(na)*} matches @samp{bananana} @i{et cetera}, with any (zero or more) number of @samp{na}'s. @item To mark a matched substring for future reference. @end enumerate This last application is not a consequence of the idea of a parenthetical grouping; it is a separate feature that happens to be assigned as a second meaning to the same @samp{( @dots{} )} construct because there is no conflict in practice between the two meanings. Here is an explanation of this feature: @item \@var{digit} After the end of a @samp{( @dots{} )} construct, the matcher remembers the beginning and end of the text matched by that construct. Then, later on in the regular expression, you can use @samp{\} followed by @var{digit} to mean ``match the same text matched the @var{digit}'th time by the @samp{( @dots{} )} construct.'' The @samp{( @dots{} )} constructs are numbered in order of commencement in the regexp. The strings matching the first nine @samp{( @dots{} )} constructs appearing in a regular expression are assigned numbers 1 through 9 in order of their beginnings. @samp{\1} through @samp{\9} may be used to refer to the text matched by the corresponding @samp{( @dots{} )} construct. For example, @samp{(.+)\1} matches any non empty string that is composed of two identical halves. The @samp{(.+)} matches the first half, which may be anything non empty, but the @samp{\1} that follows must match the same exact text. @item \b matches the empty string, but only if it is at the beginning or end of a word. Thus, @samp{\bfoo\b} matches any occurrence of @samp{foo} as a separate word. @samp{\bball(s|)\b} matches @samp{ball} or @samp{balls} as a separate word. @item \B matches the empty string, provided it is @emph{not} at the beginning or end of a word. @item \< matches the empty string, but only if it is at the beginning of a word. @item \> matches the empty string, but only if it is at the end of a word. @item \s matches any white-space character in US-ASCII. These are @key{tab}, @kbd{@key{Control}-J} (line feed), @kbd{@key{Control}-k} (vertical tab), @kbd{@key{Control}-L} (form feed), @kbd{@key{Control}-M} (carriage return), and space. @item \w matches any word-constituent character. These are US-ASCII letters, numbers and the underscore, independently of the document encoding. @item \W matches any character that is not a word-constituent. @end table @subsection Replacing regular expressions Also the replacement string has some special feature when doing a regular expression search and replace. Exactly as during the search, @samp{\} followed by @var{digit} stands for ``the text matched the @var{digit}'th time by the @samp{( @dots{} )} construct in the search expression''. Moreover, @samp{\0} represent the whole string matched by the regular expression. Thus, for instance, the replace string @samp{\0\0} has the effect of doubling any string matched. Another example: if you search for @samp{(a+)(b+)}, replacing with @samp{\2x\1}, you will match any string composed by a series of @samp{a}'s followed by a series of @samp{b}'s, and you will replace it with the string obtained by moving the @samp{a} in front of the @samp{b}'s, adding moreover @samp{x} in between. For instance, @samp{aaaab} will be matched and replaced by @samp{bxaaaa}. Note that the backslash character can escape itself. Thus, to put a backslash in the replacement string, you have to use @samp{\\}. @node Automatic Preferences @section Automatic Preferences @cindex Automatic preferences Automatic preferences let you set up a custom configuration that is automatically used whenever you open a file with a given extension. For instance, you may prefer a @sc{tab} size of three when editing C sources, but eight could be more palatable when writing electronic mail. The use of autoprefs is definitely straightforward. You simply use the @samp{Save AutoPrefs} menu item (or the @code{SaveAutoPrefs} command; @pxref{SaveAutoPrefs}) when the current document has the given extension and the current configuration suits your tastes. The internal state of a series of options will be recorded as a macro containing commands that reproduce the current configuration. The macro is then saved in the @file{~/.ne} directory (which is created if necessary) with the name given by the extension, postfixed with @samp{#ap}. Thus, the C sources automatic preferences file will be named @samp{c#ap}, the one for @TeX{} files @samp{tex#ap}, and so on. Macros are generated with short or long command names depending on the status of the verbose macros flag. @xref{VerboseMacros}. Automatic preferences files are loaded and executed whenever a file with a known extension is opened. Note that you can manually edit such files, and even insert commands, but any command that does something other than setting a flag will be rejected, and an error message will be issued. @node Emergency Save @section Emergency Save @cindex Emergency Save When @code{ne} is interrupted by an abnormal event (for instance, the crash of your terminal), it will try to save all unsaved documents in its current directory. Named documents will have their names prefixed with a @samp{#}. Unnamed documents will be given names made up of hexadecimal numbers obtained by some addresses in memory that will make them unique. @node UTF-8 Support @section UTF-8 Support @cindex UTF-8 Support @code{ne} can manipulate UTF-8 files and supports UTF-8 when communicating with the user. At startup, @code{ne} fetches the system locale description, and checks whether it contains the string @samp{utf8} or @samp{utf-8}. In this case, it starts communicating with the user using UTF-8. This behaviour can be modified either using a suitable command line option (see @pxref{Arguments}), or using @ref{UTF8IO}. This makes it possible to display and read from the keyboard a wide range of characters. Independently of the input/output encoding, @code{ne} keeps track of the encoding of each document. @code{ne} does not try to select a particular coding on a document, unless it is forced to do so, for instance because a certain character is inserted. Once a document has a definite encoding, however, it keeps it forever. More precisely, every document may be in one of three @emph{encoding modes}: US-ASCII, when it is entirely composed of US-ASCII characters; 8-bit, if it contains also other characters, but it is not UTF-8 encoded; and finally, UTF-8, if it is UTF-8-encoded. The behaviour of @code{ne} in US-ASCII and 8-bit mode is similar to previous versions: each byte in the document is considered a separate character. There are, however, two important differences: first, if I/O is not UTF-8 encoded, @emph{any} encoding of the ISO-8859 family will work flawlessly, as @code{ne} merely reads bytes from the keyboard and displays bytes on the screen. On the contrary, in the case of UTF-8 input/output @code{ne} must take a decision as to which encoding is used for non-UTF-8 documents, and presently this is hardwired to ISO-8859-1. Second, 8-bit documents use localized casing and character type functions. This means that case-insensitive searches or case foldings will work with, say, Cyrillic characters, provided that your locale is set correctly. In UTF-8 mode, instead, @code{ne} interprets the bytes in the document in a different way---several bytes may encode a single character. The whole process is completely transparent to the user, but if you really want to look at the document content, you can switch to 8-bit mode (see @pxref{UTF8}). For most operations, UTF-8 support should be transparent. However, in some cases, in particular when mixing documents with different encodings, @code{ne} will refuse to perform certain operations because of incompatible encodings. The main limitation of UTF-8 documents is that when searching for a regular expression in a UTF-8 text, character sets may only contain US-ASCII characters (see @pxref{Regular Expressions}). You can, of course, partially emulate a full UTF-8 character set implementation specifying the possible alternatives using @samp{|} (but you have no ranges). @node Commands @chapter Commands @cindex Commands Everything @code{ne} can do is specified through a command. Commands can be manually typed on the command line, bound to a key, to a menu item, or grouped into macros for easier manipulation. If you want to fully exploit the power of @code{ne}, you will be faced sooner or later with using commands directly. @menu * General Guidelines:: * File Commands:: * Document Commands:: * Clip Commands:: * Search Commands:: * Macros Commands:: * Undo Commands:: * Formatting Commands:: * Preferences Commands:: * Navigation Commands:: * Editing Commands:: * Support Commands:: @end menu @node General Guidelines @section General Guidelines @cindex Long names @cindex Short names @cindex Command arguments @cindex Flags @cindex Repeating actions @cindex Escape conventions @cindex Quoting conventions Every command in @code{ne} has a long and a short name. Except in a very few cases, the short name is given by two or three letters that are the initials of the words that form the long name. For instance, @code{SearchBack} has short name @code{SB}, @code{SaveDefPrefs} has the short name @code{SDP}, and @code{AdjustView}'s short name is @code{AV}. There are some exceptions however. The most frequently used commands such as @code{Exit} have one-letter short names (@code{X}). Also some commands use a different short name to avoid clashes with a more common command's short name. For example, @code{StatusBar}'s short name is @code{ST} rather than @code{SB} to avoid clashes with @code{SearchBack}'s short name. A command always has at most one argument. This is a chosen limitation that allows @code{ne}'s parsing of commands and macros to be very fast. Moreover, it nullifies nearly all problems related to delimiters, escape characters, and the like. The unique argument can be a number, a string, or a flag modifier. You can easily distinguish these three cases even without this manual by looking at what the @code{Help} command says about the given command. Note that when a command's argument is enclosed in square brackets, it is optional. Strings are general purpose arguments. Numbers are used to modify internal parameters, such as the size of a @sc{tab}. A flag modifier is an optional number that is interpreted as follows: @itemize @bullet{} @item 0 means clearing the flag; @item 1 (or any positive number) means setting the flag; @item no number means toggling the flag. @end itemize Thus, @code{StatusBar 1} will activate that status bar, while @code{I} will toggle insert/overstrike. This design choice is due to the fact that most of the time during interactive editing you need to @emph{change} a flag. For instance, you may be in insert mode and you want to overstrike, or vice versa. Absolute settings (those with a number) are useful essentially for macros. It is reasonable to use the fastest approach for the most frequent interactive event. When a number or a string is required and the argument is optional, most of the time you will be prompted to type the argument on the command line. As for the input line, for numeric arguments you can choose between decimal, octal and hexadecimal notation in the standard way: a number starting with @samp{0} is considered in octal, a number starting with @samp{0x} is considered in hexadecimal, and in all other cases decimal base is assumed. When a number represents how many times @code{ne} should repeat an action, it is always understood that the command will terminate when the conditions for applying it are no longer true. For instance, the @code{Paragraph} command accepts the number of paragraphs to format. But if not enough paragraphs exists in the text, only the available ones will be formatted. This easily allows performing operations on an entire document by specifying preposterously huge numbers as arguments. @code{ToUpper 200000000} will make all the words in the document upper case. (At least, one would hope so!) Note that this is much faster than recording a macro with the command @code{ToUpper} in it and playing it many times because in the former case the command has to be parsed just one time. In any case, if a macro or a repeated operation takes too long, you can stop it using the interrupt key (@kbd{@key{Control}-\}). To handle situations such as an argument string starting with a space, @code{ne} implements a simple mechanism whereby you can enclose any string argument in double quotes. If the first non-blank character after the command and last character of the command line are double quotes, the quotes will be removed and whatever is left will be used as the string argument. For example, the @code{Find} command to find a space could be entered on the command line or in a macro as @code{Find " "}. The only case needing special treatment is when a string starts and ends with double quotes. The command @code{Find ""quote""} would locate the next occurrence of the string @samp{"quote"} (including the double quotes). However, @code{Find onequote"} wouldn't require special treatment because the command argument doesn't both start and end with a double quote. @ignore N O T E T O D O C U M E N T M A I N T A I N E R S It is essential that, in the following sections, all cross references explaining the text of a command point only to another command, unless they are inside an @ifclear autohelp / @endif pair. This way, the text can be automagically extracted for the on-line help with makeinfo. @end ignore @node File Commands @section File Commands These commands allow opening and saving files. They all act in the context of the current document (i.e., the document displayed when the command is issued). @menu * Open:: * OpenNew:: * Save:: * SaveAs:: * SaveAll:: @end menu @node Open @subsection Open @cmindex Open @noindent Syntax: @code{Open [@var{filename}]}@* @noindent Abbreviation: @code{O} @noindent replaces the contents of the current document with that of the file specified by the @var{filename} string. (To load @var{filename}'s content into a new document without changing the current document, see @ref{OpenNew}.) The current document's macro, search, and replace strings are preserved. If the optional @var{filename} argument is not specified, the file requester is opened, and you are prompted to select a file. (You can inhibit the file requester opening by using the @code{NoFileReq} command; see @ref{NoFileReq}.) If you escape from the file requester, you can input the file name on the command line, the default being the current document name, if available. If the current document is marked as modified at the time the command is issued, you have to confirm the action. @node OpenNew @subsection OpenNew @cmindex OpenNew @noindent Syntax: @code{OpenNew [@var{filename}]}@* @noindent Abbreviation: @code{ON} @noindent creates a new document and loads into it the contents of the file specified by the optional @var{filename} string. This new document will inherit the macro, search, and replace strings from the current document. If @var{filename} is unspecified, the file requester behaves the same as for the @code{Open} command; see @ref{Open}. @node Save @subsection Save @cmindex Save @noindent Syntax: @code{Save}@* @noindent Abbreviation: @code{S} @noindent saves the current document using its default file name. If the current document is unnamed, the file requester will open and you will be prompted to select a file. (You can inhibit the file requester opening by using the @code{NoFileReq} command; see @ref{NoFileReq}.) If you escape from the file requester, you can input the file name on the command line. If the file has been modified since the current document was loaded or last saved (perhaps by another user), @code{ne} will warn you before overwriting the updated file. If the current document's read only flag is set, @code{ne} will prompt you before attempting to save it. @node SaveAs @subsection SaveAs @cmindex SaveAs @noindent Syntax: @code{SaveAs [@var{filename}]}@* @noindent Abbreviation: @code{SA} @noindent saves the current document using the specified string as the file name. If the optional @var{filename} argument is not specified, the file requester will open and you will be prompted to select a file. (You can inhibit the file requester opening by using the @code{NoFileReq} command; see @ref{NoFileReq}.) If you escape from the file requester, you can enter the file name on the input line, the default being the current document name, if available. If the file has been modified since the current document was loaded or last saved (perhaps by another user), @code{ne} will warn you before overwriting the updated file. If the current document's read only flag is set, @code{ne} will prompt you before attempting to save it. @node SaveAll @subsection SaveAll @cmindex SaveAll @noindent Syntax: @code{SaveAll}@* @noindent Abbreviation: @code{SL} @noindent saves all modified documents. If any modified documents cannot be saved, the action is suspended and an error message is issued. Note that only named documents can be saved, so @code{SaveAll} will report an error if you have any modified unnamed documents. Other reasons @code{SaveAll} may fail include: if any of the modified documents' corresponding files have been updated since they were loaded or last saved, or if any modified documents' read only flags are set. @node Document Commands @section Document Commands These commands allow manipulation of the circular list of documents in @code{ne}. @menu * Quit:: * Exit:: * NewDoc:: * Clear:: * CloseDoc:: * NextDoc:: * PrevDoc:: * SelectDoc:: @end menu @node Quit @subsection Quit @cmindex Quit @noindent Syntax: @code{Quit}@* @noindent Abbreviation: @code{Q} @noindent closes all documents and exits. If any documents are modified, you have to confirm the action. @node Exit @subsection Exit @cmindex Exit @noindent Syntax: @code{Exit}@* @noindent Abbreviation: @code{X} @noindent saves all modified documents, closes them and exits. If any documents cannot be saved, the action is suspended and an error message is issued and no documents are closed. Note that only named documents can be saved, so @code{Exit} will report an error if you have any modified unnamed documents. Like @code{SaveAll}, @code{Exit} will not save a document if its corresponding file has been modified since the document was loaded or last saved, or if its read only flag is set, in which cases an error is reported and no documents are closed. @node NewDoc @subsection NewDoc @cmindex NewDoc @noindent Syntax: @code{NewDoc}@* @noindent Abbreviation: @code{N} @noindent creates a new, empty, unnamed document that becomes the current document. The position of the document in the document list is just after the current document. The preferences of the new document are a copy of the preferences of the current document. @node Clear @subsection Clear @cmindex Clear @noindent Syntax: @code{Clear}@* @noindent Abbreviation: @code{CL} @noindent destroys the contents of the current document and of its undo buffer. Moreover, the document becomes unnamed. If your current document is marked as modified, you have to confirm the action. @node CloseDoc @subsection CloseDoc @cmindex CloseDoc @noindent Syntax: @code{CloseDoc}@* @noindent Abbreviation: @code{CD} @noindent closes the current document. If the document was modified since it was last saved, you have to confirm the action. If it is the only document, @code{ne} exits. Otherwise @code{ne} will select the most recently active next or previous document adjacent to the one you just closed as the new current document. See @ref{NextDoc}, and @ref{PrevDoc}. @node NextDoc @subsection NextDoc @cmindex NextDoc @noindent Syntax: @code{NextDoc}@* @noindent Abbreviation: @code{ND} @noindent sets as current document the next document in the document list. See @ref{PrevDoc} and @ref{SelectDoc}. @node PrevDoc @subsection PrevDoc @cmindex PrevDoc @noindent Syntax: @code{PrevDoc}@* @noindent Abbreviation: @code{PD} @noindent sets as current document the previous document in the document list. See @ref{NextDoc} and @ref{SelectDoc}. @node SelectDoc @subsection SelectDoc @cmindex SelectDoc @noindent Syntax: @code{SelectDoc}@* @noindent Abbreviation: @code{SD} @noindent displays a requester containing the names of all the documents in memory. Your cursor will be on your current document's name, and documents with unsaved changes will be indicated with asterisks (and bold if your terminal supports that). You select whichever document you want to become the current document by moving your cursor to its name and hitting @key{Return}. While the list of documents is displayed, you can close unmodified documents with your @code{CloseDoc} key (@kbd{@key{Control}-Q}), save named documents with your @code{Save} key (@kbd{@key{Control}-S}), and you can alter their relative order using your @code{NextDoc} and @code{PrevDoc} keys (@key{f2} and @key{f3}), which have the effect of moving the currently selected document forward or backward in the list, respectively. Using plain characters and the @key{BackSpace} navigates your displayed document names based on their common prefixes. The text between the beginning and the cursor within the currently selected document's name is the current common prefix. The @key{Ins} key toggles between hiding or displaying names not matching the current common prefix. This can be especially useful if you have a large number of open documents. If you escape from the requester, the requester goes away, you are returned to your original current document (unless you closed it!), and no reordering of documents takes place. @code{SelectDoc} is especially useful if you have a large number of documents open or if you want to quickly see which documents contain unsaved changes. See @ref{NextDoc}, and @ref{PrevDoc}. @node Clip Commands @section Clip Commands These commands control the clipping system. A @dfn{clip} is a snippet of text separate from any document, which you can save to a file or insert into a document. You can select text in a document and copy it to a clip, optionally deleting it from your text. You can also load text directly from a file into a clip. @code{ne} can have any number of clips, which are distinguished by an integer. Most clip commands act on the current clip, which can be selected with @code{ClipNumber}. Clips can be copied and pasted in two ways---normally (as lines of text) or vertically (as a rectangular block of characters). Note that by using the @code{Through} command you can automatically pass a (possibly vertical) block of text through any filter (such as @code{sort} under @sc{un*x}). @menu * Mark:: * MarkVert:: * Copy:: * Cut:: * Paste:: * PasteVert:: * Erase:: * Shift:: * OpenClip:: * SaveClip:: * ClipNumber:: * Through:: @end menu @node Mark @subsection Mark @cmindex Mark @noindent Syntax: @code{Mark [0|1]}@* @noindent Abbreviation: @code{M} @noindent sets the mark at the current position or cancels the previous mark. The mark and cursor together define the range of text over which clips (@code{Cut}, @code{Copy}, @code{Erase}) and left and right shifts operate. If you invoke @code{Mark} with no arguments, it will set the mark. If you specify 0 or 1, the mark will be cancelled or set to the current position, respectively. A capital @samp{M} appears on the status bar, if the mark is active. @node MarkVert @subsection MarkVert @cmindex MarkVert @noindent Syntax: @code{MarkVert [0|1]}@* @noindent Abbreviation: @code{MV} @noindent is the same as @code{Mark}, but the region manipulated by the cut/paste commands is the rectangle having as vertices the cursor and the mark. If you invoke @code{MarkVert} with no arguments, it will set the mark. If you specify 0 or 1, the mark will be cancelled or set to the current position, respectively. Moreover, a capital @samp{V}, rather than a capital @samp{M}, will appear on the status bar. For example, if you have the following text: @example aaaBbbccc aaabbbccc aaabbbCcc @end example @noindent and you set a vertical mark at @samp{B} then move the cursor to @samp{C}, you can cut or copy all of the @samp{B}s. If you have made a vertical cut or copy, it's very likely you will want to use @code{PasteVert} rather than the usual @code{Paste} to reinsert the text in a rectangle. See @ref{PasteVert}. @node Copy @subsection Copy @cmindex Copy @noindent Syntax: @code{Copy [@var{n}]}@* @noindent Abbreviation: @code{C} @noindent copies the contents of the characters lying between the cursor and the mark into the clip specified by the optional numeric argument, the default clip being the current clip, which can be set with the @code{ClipNumber} command; see @ref{ClipNumber}. If the current mark was vertical, the rectangle of characters defined by the cursor and the mark is copied instead. @node Cut @subsection Cut @cmindex Cut @noindent Syntax: @code{Cut [@var{n}]}@* @noindent Abbreviation: @code{CU} @noindent acts just like @code{Copy}, but also deletes the block being copied. @node Paste @subsection Paste @cmindex Paste @noindent Syntax: @code{Paste [@var{n}]}@* @noindent Abbreviation: @code{P} @noindent pastes the contents of specified clip into the current document at the cursor position. If you don't specify the clip number, the current clip is used; Specify which clip is current with @ref{ClipNumber}. Every time you paste into a document, a pair of bookmarks designated by @samp{<} and @samp{>} mark the start and end points of the text you pasted. Subsequently you can move to either end of a pasted block with the @code{GotoBookmark <} or @code{GotoBookmark >} commands. @node PasteVert @subsection PasteVert @cmindex PasteVert @noindent Syntax: @code{PasteVert [@var{n}]}@* @noindent Abbreviation: @code{PV} @noindent vertically pastes the contents of the specified clip, the default being the current clip. Each line of the clip is inserted on consecutive lines at the horizontal cursor position. Every time you paste into a document, a pair of bookmarks designated by @samp{<} and @samp{>} mark the start and end points of the text you pasted. Subsequently you can move to either end of a pasted block with the @code{GotoBookmark <} or @code{GotoBookmark >} commands. @node Erase @subsection Erase @cmindex Erase @noindent Syntax: @code{Erase}@* @noindent Abbreviation: @code{E} @noindent acts like @code{Cut}, but the block is just deleted and not copied into any clip. @node Shift @subsection Shift @cmindex Shift @noindent Syntax: @code{Shift [<|>][n][t|s]}@* @noindent Abbreviation: @code{SH} @noindent shifts the text on lines between the mark and the cursor either right (@samp{>}) or left (@samp{<}) by adding or removing white space on each line. The adjustment size, specified as an unsigned integer @samp{n}, is in units of the current tab size (@samp{t}) or spaces (@samp{s}). The defaults are @samp{>}, @samp{1}, and @samp{t}. Adjustments start at the left edge of a vertical mark, or column 1 otherwise. If the mark is not currently set, only the current line is affected. @code{Shift} will insert tab characters only if @samp{s} is not used, and both of the document's @code{Tabs} and @code{ShiftTabs} flags are set---in which case an upper case @samp{T} will appear in the status bar. If either of the @code{Tabs} or @code{ShiftTabs} flags is unset (i.e there is no upper case @samp{T} in the status bar) @code{Shift} will only insert spaces. In the case of left shifts, if any indicated line has insufficient leading white space for the requested adjustment to be made, then @code{Shift} reports an error and makes no changes. @node OpenClip @subsection OpenClip @cmindex OpenClip @noindent Syntax: @code{OpenClip [@var{filename}]}@* @noindent Abbreviation: @code{OC} @noindent loads the given file name as the current clip, just as if you cut or copied it from the current document; see @ref{Copy}. If the optional @var{filename} argument is not specified, the file requester will open and you will be prompted to select a file. (You can inhibit the file requester opening by using the @code{NoFileReq} command; see @ref{NoFileReq}.) If you escape from the file requester, you can enter the file name on the input line. @node SaveClip @subsection SaveClip @cmindex SaveClip @noindent Syntax: @code{SaveClip [@var{filename}]}@* @noindent Abbreviation: @code{SC} @noindent saves the current clip to the given file name. If the optional @var{filename} argument is not specified, the file requester will open and you will be prompted to select a file. (You can inhibit the file requester opening by using the @code{NoFileReq} command; see @ref{NoFileReq}.) If you escape from the file requester, you can enter the file name on the input line. @node ClipNumber @subsection ClipNumber @cmindex ClipNumber @noindent Syntax: @code{ClipNumber [@var{n}]}@* @noindent Abbreviation: @code{CN} @noindent sets the current clip number. This number is used by @code{OpenClip} and @code{SaveClip}, and by @code{Copy}, @code{Cut} and @code{Paste} if they are called without any argument. Its default value is zero. @var{n} is limited only by the integer size of the machine @code{ne} is running on. If the optional argument @var{n} is not specified, you can enter it on the input line, the default being the current clip number. @node Through @subsection Through @cmindex Through @noindent Syntax: @code{Through [@var{command}]}@* @noindent Abbreviation: @code{T} @noindent asks the shell to execute @var{command}, piping the current block in the standard input, and replacing it with the output of the command. This command is most useful with filters, such as @code{sort}. Its practical effect is to pass the block through the specified filter. Note that by selecting an empty block (or equivalently by having the mark unset) you can use @code{Through} to insert the output of any @sc{un*x} command in your file. If the optional argument @var{command} is not specified, you can enter it on the input line. @node Search Commands @section Search Commands These commands control the search system. @code{ne} offers two complementary searching techniques: a simple, fast exact matching search (optionally ignoring case), and a very flexible and powerful, but slower, regular expression search based on the GNU @code{regex} library (again, optionally case insensitive). @menu * Find:: * FindRegExp:: * Replace:: * ReplaceOnce:: * ReplaceAll:: * RepeatLast:: * MatchBracket:: * AutoMatchBracket:: * SearchBack:: * CaseSearch:: * AutoComplete:: @end menu @node Find @subsection Find @cmindex Find @noindent Syntax: @code{Find [@var{pattern}]}@* @noindent Abbreviation: @code{F} @noindent searches for the given pattern. The cursor is positioned on the first occurrence of the pattern, or an error message is given. The direction and the case sensitivity of the search are established by the value of the back search and case sensitive search flags. See @ref{SearchBack}, and @ref{CaseSearch}. If the optional argument @var{pattern} is not specified, you can enter it on the input line, the default being the last pattern used. @node FindRegExp @subsection FindRegExp @cmindex FindRegExp @noindent Syntax: @code{FindRegExp [@var{pattern}]}@* @noindent Abbreviation: @code{FX} @noindent searches the current document for the given extended regular expression @ifclear autohelp (@pxref{Regular Expressions}) @end ifclear . The cursor is positioned on the first string matching the expression. The direction and the kind of search are established by the value of the back search and case sensitive search flags. See @ref{SearchBack}, and @ref{CaseSearch}. If the optional argument @var{pattern} is not specified, you can enter it on the input line, the default being the last pattern used. @node Replace @subsection Replace @cmindex Replace @noindent Syntax: @code{Replace [@var{string}]}@* @noindent Abbreviation: @code{R} @noindent moves to the first match of the most recent find string or regular expression and prompts you for which action to perform. You can choose among: @itemize @bullet @item replacing the string found with the given string and moving to the next match (@samp{Yes}); @item moving to the next match (@samp{No}); @item replacing the string found with the given string, and stopping the search (@samp{Last}); @item stopping the search immediately (@samp{Quit}); @item replacing @emph{all} occurrences of the find string with the given string (@samp{All}); @item reversing the search direction (@samp{Backward} or @samp{Forward}); this choice will also modify the value of the back search flag. See @ref{SearchBack}. @end itemize @code{Replace} is mainly useful for interactive editing. @code{ReplaceOnce}, @code{ReplaceAll} and @code{RepeatLast} are more suited to macros. If no find string was ever specified, you can enter it on the input line. If the optional argument @var{string} is not specified, you can enter it on the input line, the default being the last string used. When the last search was a regular expression search, there are some special features you can use in the replace string @ifclear autohelp (@pxref{Regular Expressions}) @end ifclear . See @ref{FindRegExp}. Note that normally a search starts just one character after the cursor. However, when @code{Replace} is invoked, the search starts at the character just @emph{under} the cursor, so that you can safely @code{Find} a pattern and @code{Replace} it without having to move back. @noindent @strong{Warning:} when recording a macro with @ref{Record}, there is no trace in the macro of your interaction with @code{ne} during the replacement process. When the macro is played, you will again have to choose which actions to perform. If you want to apply automatic replacement of strings for a certain number of times, you should look at @ref{ReplaceOnce}, @ref{ReplaceAll}, and @ref{RepeatLast}. @node ReplaceOnce @subsection ReplaceOnce @cmindex ReplaceOnce @noindent Syntax: @code{ReplaceOnce [@var{string}]}@* @noindent Abbreviation: @code{R1} @noindent acts just like @code{Replace}, but without any interaction with you (unless there is no find string). The first string matched by the last search pattern, if it exists, is replaced by the given replacement string. If the optional argument @var{string} is not specified, you can enter it on the input line, the default being the last string used. @node ReplaceAll @subsection ReplaceAll @cmindex ReplaceAll @noindent Syntax: @code{ReplaceAll [@var{string}]}@* @noindent Abbreviation: @code{RA} @noindent is similar to @code{ReplaceOnce}, but replaces @emph{all} occurrences of the last search pattern between the cursor position and the end of the document (in the direction indicated by the @code{SearchBack} flag) with the given replacement string. If the optional argument @var{string} is not specified, you can enter it on the input line, the default being the last string used. Note that a single @code{Undo} will restore @emph{all} the occurrences of the search pattern replaced by @code{ReplaceAll}. See @ref{Undo}. @node RepeatLast @subsection RepeatLast @cmindex RepeatLast @noindent Syntax: @code{RepeatLast [@var{times}] [F|Find|R|Replace]}@* @noindent Abbreviation: @code{RL} @noindent repeats for the given number of times the last find or replace operation (with replace we mean here a single replace, even if the last @code{Replace} operation ended with a global substitution). If you don't specify either @var{Find} or @var{Replace}, it will repeat whichever one was last performed. If any find or replace operation runs into the end of the document (in the direction indicated by the @code{SearchBack} flag) and stops, then and only then will a @code{RepeatLast} command ``wrap around'' to the other end of the document to continue the find or replace operation. @code{RepeatLast} is especially useful for researching a given number of times, or replacing something a given number of times. The standard technique for accomplishing this is: @enumerate @item @code{Find} (or @code{FindRegExp}) the string you are interested in; @item if you want to repeat a replace operation, @code{ReplaceOnce} with the replacement string you are interested in; @item now issue a @code{RepeatLast @var{n}-1} command, where @var{n} is the number of occurrences you wanted to skip over, or replace. @end enumerate The important thing about this sequence of actions is that it will work this way even in a macro. The @code{Replace} command cannot be used in a macro unless you really want to interact with @code{ne} during the macro execution. Avoiding interaction during macros is the primary reason the commands @code{ReplaceAll} and @code{ReplaceOnce} are provided. @node MatchBracket @subsection MatchBracket @cmindex MatchBracket @noindent Syntax: @code{MatchBracket}@* @noindent Abbreviation: @code{MB} @noindent moves the cursor to the bracket associated with the bracket the cursor is on. If the cursor is not on a bracket, or there is no bracket associated with the current one, an error message is issued. Recognized brackets are @samp{@{@}}, @samp{()}, @samp{[]} @samp{<>}, and @samp{`'}. See @ref{AutoMatchBracket}. @node AutoMatchBracket @subsection AutoMatchBracket @cmindex AutoMatchBracket @noindent Syntax: @code{AutoMatchBracket [0..15]}@* @noindent Abbreviation: @code{AMB} @noindent sets the auto match bracket mode. When the cursor is on a recognized bracket (@samp{@{@}}, @samp{()}, @samp{[]}, @samp{<>}, or @samp{`'}) and the associated matching bracket is on the screen, that matching bracket will be indicated according to the mode. The mode is either zero for no bracket matching, or the sum of 1 (altered foreground and background brightness), 2 (inverse), 4 (bold), and 8 (underline). If no mode is specified, @code{ne} prompts you for one. The default mode is 1. See @ref{MatchBracket}. @node SearchBack @subsection SearchBack @cmindex SearchBack @noindent Syntax: @code{SearchBack [0|1]}@* @noindent Abbreviation: @code{SB} @noindent sets the back search flag. When this flag is true, every search or replacement command is performed backwards. If you invoke @code{SearchBack} with no arguments, it will toggle the flag. If you specify 0 or 1, the flag will be set to false or true, respectively. A lower case @samp{b} will appear on the status bar if the flag is true. Note that this flag also can be set through interactions with the @code{Replace} command. See @ref{Replace}. @node CaseSearch @subsection CaseSearch @cmindex CaseSearch @noindent Syntax: @code{CaseSearch [0|1]}@* @noindent Abbreviation: @code{CS} @noindent sets the case sensitivity flag. When this flag is true, the search commands distinguish between the upper and lower case letters. By default the flag is false. If you invoke @code{CaseSearch} with no arguments, it will toggle the flag. If you specify 0 or 1, the flag will be set to false or true, respectively. A lower case @samp{c} will appear on the status bar if the flag is true. @node AutoComplete @subsection AutoComplete @cmindex AutoComplete @noindent Syntax: @code{AutoComplete [@var{prefix}]}@* @noindent Abbreviation: @code{AC} @noindent attempts to extend the @var{prefix} using matching words from your open documents, and inserts the extended text into your document. If the @var{prefix} can be extended unambiguously, the matching text is immediately inserted into your document. Otherwise, @code{ne} displays a selection of all words in open documents that match @var{prefix}, and inserts the word you select into the current document. Matching words from the current document display normally; those which only exist in other open documents are bold and with a trailing asterisk. If no @var{prefix} is given on the command line, or if @code{AutoComplete} is selected from the @code{Extras} menu or using a keyboard shortcut, the word characters to the immediate left of the cursor in the current document are used as the @var{prefix}. Note that if no word characters are to the left of the cursor, or the @var{prefix} given on the command line is an empty string (@code{""}), then all words in all your open documents are displayed. Prefix matches may be case sensitive or not depending on the current document's @code{CaseSearch} flag state. See @ref{CaseSearch}. @node Macros Commands @section Macros Commands Macros are lists of commands. Any series of operations that has to be performed frequently is a good candidate for being a macro. Macros can be written manually: they are just ASCII files, each command occupying a line (lines starting with @samp{#} are considered comments; lines starting with other nonalphabetical characters are presently ignored). But the real power of macros is that they be recorded during the normal usage of @code{ne}. When the recording terminates, the operations that have been recorded can be saved for later use. Note that each document has its own current macro (the last macro that has been opened or recorded). @menu * Record:: * Play:: * Macro:: * OpenMacro:: * SaveMacro:: * UnloadMacros:: @end menu @node Record @subsection Record @cmindex Record @noindent Syntax: @code{Record [0|1]}@* @noindent Abbreviation: @code{REC} @noindent starts, stops, cancels, or resumes macro recording. With no arguments, @code{Record} starts recording your commands as a new macro unless recording is already underway. In that case, macro recording is stopped, and the newly recorded macro replaces the current document's unnamed macro. The new macro can be played or saved via @ref{Play}, or @ref{SaveMacro}. The default key binding for @kbd{@key{Control}-T} is @code{Record} with no arguments, and is by far the most common way to use the @code{Record} command. If you've started recording a macro and wish to cancel rather than wipe out your existing macro, you can cancel the recording by using @code{Record 0}. An error will be displayed if you aren't recording already, but it's harmless. Sometimes you've got a macro either recorded or loaded from a file (see @ref{OpenMacro}), but you'd like to record additional commands onto the end of it. @code{Record 1} will do that. It will start recording onto the end of a copy of your current document's macro. An error will be displayed if you are already recording, but it's otherwise harmless. @node Play @subsection Play @cmindex Play @noindent Syntax: @code{Play [@var{times}]}@* @noindent Abbreviation: @code{PL} @noindent plays the current document's macro the given number of times. If the optional argument @var{times} is not specified, you can enter it on the input line. A (possibly iterated) macro execution terminates as soon as its stream of instructions is exhausted, or one of its commands returns an error. This means that, for instance, you can perform some complex operation on all the lines containing a certain pattern by recording a macro that searches for the pattern and performs the operation, and then playing it a preposterously huge number of times. Execution of a macro can be interrupted by @kbd{@key{Control}-\}. @node Macro @subsection Macro @cmindex Macro @noindent Syntax: @code{Macro [@var{filename}]}@* @noindent Abbreviation: @code{MA} @noindent executes the given file name as a macro. If the optional @var{filename} argument is not specified, the file requester is opened, and you are prompted to select a file. (You can inhibit the file requester opening by using the @code{NoFileReq} command; see @ref{NoFileReq}.) If you escape from the file requester, you can input the file name on the command line. Note that macros whose names do not conflict with a command can be called without using @code{Macro}. Whenever @code{ne} is required to perform a command it cannot find in its internal tables, it will look for a macro by that name in the current directory. If this search also fails, @code{ne} looks in @file{~/.ne} and finally in the @file{macros} subdirectory of @code{ne}'s global directory (defined when @code{ne} was built, or in a place specified by your @code{NE_GLOBAL_DIR} environment variable) for a macro file by that name. @noindent @strong{Warning:} the first time a macro is executed it is cached into a hash table and is kept @emph{forever} in memory unless the @code{UnloadMacros} command is issued; see @ref{UnloadMacros}. The next time a macro with the same file name is invoked, the cached list is searched for it before accessing the file using a case insensitive string comparison. That is, if you call @code{~/foobar/macro}, a subsequent call for @code{/usr/MACRO} or even just @code{MaCrO} will use the cached version of @code{~/foobar/macro}. Note that the cache table is global to @code{ne} and not specific to any single document. This greatly improves efficiency when macros are used repeatedly. @node OpenMacro @subsection OpenMacro @cmindex OpenMacro @noindent Syntax: @code{OpenMacro [@var{filename}]}@* @noindent Abbreviation: @code{OM} @noindent loads the given file name as the current document's macro just as if you @code{Record}ed it; see @ref{Record}. If the optional @var{filename} argument is not specified, the file requester is opened, and you are prompted to select a file. (You can inhibit the file requester opening by using the @code{NoFileReq} command; see @ref{NoFileReq}.) If you escape from the file requester, you can input the file name on the command line. @node SaveMacro @subsection SaveMacro @cmindex SaveMacro @noindent Syntax: @code{SaveMacro [@var{filename}]}@* @noindent Abbreviation: @code{SM} @noindent saves the current document's macro in a file with the given name. If the optional @var{filename} argument is not specified, the file requester is opened, and you are prompted to select a file. (You can inhibit the file requester opening by using the @code{NoFileReq} command; see @ref{NoFileReq}.) If you escape from the file requester, you can input the file name on the command line. @code{SaveMacro} is of course most useful for saving macros you just recorded. The macros can then be loaded as normal text files for further editing, if necessary. Note that @code{SaveMacro} converts @code{InsertChar} commands into a possibly smaller number of @code{InsertString} commands. This makes macros easier to read and edit. See @ref{InsertChar}, and @ref{InsertString}. @node UnloadMacros @subsection UnloadMacros @cmindex UnloadMacros @noindent Syntax: @code{UnloadMacros}@* @noindent Abbreviation: @code{UM} @noindent frees the macro cache list. After this command, the @code{Macro} command will be forced to search for the file containing the macros it has to play. @code{UnloadMacros} is especially useful if you are experimenting with a macro bound to some keystroke, and you are interactively modifying it and playing it. @code{UnloadMacros} forces @code{ne} to look for the newer version available. @node Undo Commands @section Undo Commands The following commands control the undo system. @menu * Undo:: * Redo:: * UndelLine:: * DoUndo:: * AtomicUndo:: @end menu @node Undo @subsection Undo @cmindex Undo @noindent Syntax: @code{Undo [@var{n}]}@* @noindent Abbreviation: @code{U} @noindent undoes the last @var{n} actions. If @var{n} is not specified, it is assumed to be one. After you undo a number of actions, you can @code{Redo} all or some of them; see @ref{Redo}. However, if you take any new actions after having @code{Undo}ne some, you can no longer @code{Redo} those @code{Undo}ne actions. See @ref{Redo}. @node Redo @subsection Redo @cmindex Redo @noindent Syntax: @code{Redo [@var{n}]}@* @noindent Abbreviation: @code{RE} @noindent redoes the last @var{n} actions undone by @code{Undo} (as long as you don't take any actions that change the text between the @code{Undo} and @code{Redo} commands). If @var{n} is not specified, it is assumed to be one. You can only @code{Redo} actions that have been @code{Undo}ne. See @ref{Undo}. @node UndelLine @subsection UndelLine @cmindex UndelLine @noindent Syntax: @code{UndelLine [@var{n}]}@* @noindent Abbreviation: @code{UL} @noindent inserts at the cursor position for @var{n} times the last non-empty line that was deleted with the @code{DeleteLine} command. If @var{n} is not specified, it is assumed to be one. @code{UndelLine} is most useful in that it allows a very fast way of moving one line around. Just delete it, and undelete it somewhere else. It is also an easy way to replicate a line without getting involved with clips. Note that @code{UndelLine} works independently of the status of the undo flag. See @ref{DoUndo}. @node DoUndo @subsection DoUndo @cmindex DoUndo @noindent Syntax: @code{DoUndo [0|1]}@* @noindent Abbreviation: @code{DU} @noindent sets the flag that enables or disables the undo system. When you turn the undo system off, all the recorded actions are discarded, and the undo buffers are reset. If you invoke @code{DoUndo} with no arguments, it will toggle the flag. If you specify 0 or 1, the flag will be set to false or true, respectively. A lower case @samp{u} will appear on the status bar if the flag is true. (The @samp{U} will be upper case if the flag is true and the @code{AtomicUndo} level is non-zero.) The usefulness of this option relies in the fact that the undo system is a major memory eater. If you plan to do massive editing (say, cutting and pasting megabytes of text) it is a good idea to disable the undo system, both for improving (doubling) performance and for using less (half) memory. Except for this, on a virtual memory system we see no reason to not keep the undo flag always true, and this is indeed the default. @node AtomicUndo @subsection AtomicUndo @cmindex AtomicUndo @noindent Syntax: @code{AtomicUndo [0|+|-]}@* @noindent Abbreviation: @code{AU} @noindent increases, decreases, sets or clears the @code{AtomicUndo} level. The normal level is zero. All current document changes made while the @code{AtomicUndo} level is above zero are treated as a single change by the @code{Undo} and @code{Redo} commands. If no parameter is given, a level of 0 is set to 1; otherwise the current non-zero level is decremented. If 0 is given, the level is reset to zero. Parameters of @samp{+} and @samp{-} respectively increment and decrement the level, which in no case can be negative. If the level is above zero, the @code{DoUndo} flag in the status bar, which is normally a lower-case @samp{u}, becomes upper case @samp{U}. Two other actions will reset the @code{AtomicUndo} level to zero: invoking the @code{Undo} command, and disabling the undo system with the @code{DoUndo} command. You cannot set a non-zero @code{AtomicUndo} level unless the undo system is enabled. Note: macros that you wish to undo and redo atomically---i.e., as if they were single commands---should begin with @code{AtomicUndo +} and end with @code{AtomicUndo -} so that they can call and/or be called by other macros. @node Formatting Commands @section Formatting Commands The following commands allow simple formatting operations on the text. Note that for @code{ne} a paragraph is delimited by an empty line or a line with leading white space incongruous with that of preceding lines. @menu * Center:: * Paragraph:: * ToUpper:: * ToLower:: * Capitalize:: * RightMargin:: * WordWrap:: * AutoIndent:: @end menu @node Center @subsection Center @cmindex Center @noindent Syntax: @code{Center [@var{n}]}@* @noindent Abbreviation: @code{CE} @noindent centers @var{n} lines from the cursor position onwards. If @var{n} is not specified, it is assumed to be one. The lines are centered with spaces, relatively to the value of the right margin as set by the @code{RightMargin} command. See @ref{RightMargin}. @node Paragraph @subsection Paragraph @cmindex Paragraph @noindent Syntax: @code{Paragraph [@var{n}]}@* @noindent Abbreviation: @code{PA} @noindent reformats @var{n} paragraphs from the cursor position onwards. If @var{n} is not specified, it is assumed to be one. The paragraphs are formatted relatively to the value of the right margin as set by the @code{RightMargin} command. See @ref{RightMargin}. @code{ne}'s notion of a paragraph includes the current non-blank line (regardless of its leading white space) and all subsequent non-blank lines that have identical (to each other's---not to the first line's) leading white space. Therefore your paragraphs can have various first line indentations and left margins. @ignore Note: the following was true, briefly, but was removed because it sometimes broke "normal" text that contained certain characters. I'm leaving this here in case someday we reimplement this feature. @code{Paragraph} also takes into account characters commonly used at the left edge of block comments (@samp{/}, @samp{*}, @samp{#}, spaces, and tabs) or quoted email (@samp{>}), and attempts to preserve those on the left edge when possible. @end ignore After the @code{Paragraph} command completes, your cursor will be positioned on the first non-blank character after the last reformatted paragraph (or, if there is no such character, at the end of the document). @code{Paragraph} does not insert ``smart'' spaces after full stops and colons, nor does it do other ``smart'' things such as justification. If you need such facilities, you should consider using a text formatter. @TeX{} for example is usually an excellent choice. @node ToUpper @subsection ToUpper @cmindex ToUpper @noindent Syntax: @code{ToUpper [@var{n}]}@* @noindent Abbreviation: @code{TU} @noindent shifts to upper case the letters from the cursor position up to the end of a word, and moves to the first letter of next word for @var{n} times. The description of the command may seem a little bit cryptic. What is really happening is that there are situations where you only want to upper case the last part of a word. In this case, you just have to position the cursor in the first character you want to upper case, and use @code{ToUpper} with no argument. If you apply @code{ToUpper} on the first character of a word, it will just upper case @var{n} words. @node ToLower @subsection ToLower @cmindex ToLower @noindent Syntax: @code{ToLower [@var{n}]}@* @noindent Abbreviation: @code{TL} @noindent acts exactly like @code{ToUpper}, but lowers the case. See @ref{ToUpper}. @node Capitalize @subsection Capitalize @cmindex Capitalize @noindent Syntax: @code{Capitalize [@var{n}]}@* @noindent Abbreviation: @code{CA} @noindent acts exactly like @code{ToUpper}, but capitalizes, that is, makes the first letter upper case and the other ones lower case. See @ref{ToUpper}. @node RightMargin @subsection RightMargin @cmindex RightMargin @noindent Syntax: @code{RightMargin [@var{n}]}@* @noindent Abbreviation: @code{RM} @noindent sets the right margin for all formatting operations, and for @code{WordWrap}. See @ref{WordWrap}. If the optional argument @var{n} is not specified, you can enter it on the input line, the default being the current value of the right margin. A value of zero for @var{n} will force @code{ne} to use (what it thinks it is) the current screen width as right margin. @node WordWrap @subsection WordWrap @cmindex WordWrap @noindent Syntax: @code{WordWrap [0|1]}@* @noindent Abbreviation: @code{WW} @noindent sets the word wrap flag. When this flag is true, @code{ne} will automatically break lines of text when you attempt to insert characters beyond the right margin. See @ref{RightMargin}. @ignore See ignored section in "paragraph" discussion. @code{ne} will attempt to preserve certain invariant characters at the left edge of paragraphs typically used to indicate comments in source code or quoted passages in email text. See @ref{Paragraph}. @end ignore If you invoke @code{WordWrap} with no arguments, it will toggle the flag. If you specify 0 or 1, the flag will be set to false or true, respectively. A lower case @samp{w} will appear on the status bar if the flag is true. @node AutoIndent @subsection AutoIndent @cmindex AutoIndent @noindent Syntax: @code{AutoIndent [0|1]}@* @noindent Abbreviation: @code{AI} @noindent sets the auto indent flag. When this flag is true, @code{ne} will automatically insert @sc{tab}s and spaces on a new line (created by an @code{InsertLine} command, or by automatic word wrapping) in such a way to replicate the initial spaces of the previous line. Most useful for indenting programs. If you invoke @code{AutoIndent} with no arguments, it will toggle the flag. If you specify 0 or 1, the flag will be set to false or true, respectively. A lower case @samp{a} will appear on the status bar if the flag is true. @code{AutoIndent} features a nice interaction with @code{Undo}. Whenever a new line is created, the insertion of spaces is recorded as a separate action in the undo buffer (with respect to the line creation). If you are not satisfied with the indentation, just give the @code{Undo} command and the indentation will disappear (but the new line will remain in place, since its creation has been recorded as a separate action). See @ref{Undo}. @node Preferences Commands @section Preferences Commands These commands allow you to set your preferences, that is, the value of a series of flags that modify the behaviour of @code{ne}. (Some of the flag commands, like the command for the indent flag, appear in other sections.) The status of the flags can be saved and restored later either by writing them out to a file (saved as a macro that suitably sets the flags) or by pushing them onto a ``preferences stack''. The back search and the read only flags are not saved, because they do not represent a preference, but rather a temporary state. The escape time and the turbo parameter are global to @code{ne}, and are not saved. However, you can add manually to a preferences file any preferences command (such as @code{EscapeTime} or @code{Turbo}); usually, this will be done to the default preferences file @file{~/.ne/.default#ap}. Note that there is an automatic preferences system, which automagically loads a preferences file related to the extension of the file name. Automatic preferences files are kept in your @file{~/.ne} directory. They are named as an extension postfixed with @samp{#ap}. Each time you open a file whose name has an extension for which there is an automatic preferences file, the latter is executed. Opening a file which has no extension causes the prefs from @file{~/.ne/.default#ap} to be loaded. If you want to inhibit this process, you can clear the automatic preferences flag. See @ref{AutoPrefs}. @menu * Flags:: * AutoPrefs:: * Binary:: * Insert:: * FastGUI:: * FreeForm:: * NoFileReq:: * RequestOrder:: * StatusBar:: * HexCode:: * ReadOnly:: * EscapeTime:: * TabSize:: * Tabs:: * DelTabs:: * ShiftTabs:: * Turbo:: * VerboseMacros:: * PreserveCR:: * CRLF:: * BracketedPaste:: * VisualBell:: * PushPrefs:: * PopPrefs:: * LoadPrefs:: * SavePrefs:: * LoadAutoPrefs:: * SaveAutoPrefs:: * SaveDefPrefs:: * Modified:: * Syntax:: * UTF8:: * UTF8Auto:: * UTF8IO:: @end menu @node Flags @subsection Flags @cmindex Flags @noindent Syntax: @code{Flags}@* @noindent Abbreviation: @code{FLAG} @noindent displays a list of all the status flags for ne and their associated commands. It is not recorded when recording a macro. @example FLAG COMMAND ABBR DESCRIPTION i Insert I inserts new characters (vs. replacing) a AutoIndent AI aligns cursor under previous line after b SearchBack SB searches search backward rather than forward c CaseSearch CS searches are case sensitive w WordWrap WW breaks long lines as you type f FreeForm FF allows cursor to move beyond the end of lines p AutoPrefs AP use automatic preferences based on file extension v VerboseMacros VM record macros using use long command names u DoUndo DU record edits for later undoing r ReadOnly RO changes are not allowed/saves are prompted t/T Tabs TAB TAB key inserts TABs instead of spaces T ShiftTabs SHT Shift may insert TABs (only if 't' is also set) d DelTabs DT BS and DEL may remove tabs worth of space B/! Binary B affects file loading/saving M Mark M mark set for line-oriented block operations V MarkVert MV like mark, but block is rectangle R Record REC actions are being recorded in a macro P PreserveCR PCR affects how chars are loaded from files C CRLF CRLF use CR/LF as line terminator @@ UTF8IO U8IO I/O (keyboard and terminal) are UTF-8 encoded A/8/U UTF8 U8 the document encoding (ASCII, 8-bit or UTF-8) */_ Modified MOD document has been modified since last saved _ (none) file's modtime changed since doc was loaded/saved @end example The @code{RequestOrder} and @code{AutoMatchBracket} flags' states are not indicated on the status bar. See @ref{RequestOrder} and @ref{AutoMatchBracket} respectively. A @samp{!} indicates the last line is not terminated. @node AutoPrefs @subsection AutoPrefs @cmindex AutoPrefs @noindent Syntax: @code{AutoPrefs [0|1]}@* @noindent Abbreviation: @code{AP} @noindent sets the automatic preferences flag. If this flag is true, each time an @code{Open} command is executed and a file is loaded, @code{ne} will look for an automatic preferences file in your @file{~/.ne} directory. The preferences file name is given by the extension of the file loaded, postfixed with @samp{#ap}. Thus, for instance, C sources have an associated @file{c#ap} file. @ifclear autohelp @xref{Automatic Preferences}. @end ifclear If you invoke @code{AutoPrefs} with no arguments, it will toggle the flag. If you specify 0 or 1, the flag will be set to false or true, respectively. A lower case @samp{p} will appear on the status bar if the flag is true. @node Binary @subsection Binary @cmindex Binary @cindex Binary files @noindent Syntax: @code{Binary [0|1]}@* @noindent Abbreviation: @code{B} @noindent sets the binary flag. When this flag is true, loading and saving a document is performed in a different way. On loading, only nulls are considered newlines; on saving, nulls are saved instead of newlines. This allows you to edit a binary file, fix some text in it, and save it without modifying anything else. Normally, line feeds, carriage returns and nulls are considered newlines, so that what you load will have all nulls and carriage returns substituted by newlines when saved. Note that since usually binary files contain a great number of nulls, and every null will be considered a line terminator, the memory necessary for loading a binary file can be several times bigger than the length of the file itself. Thus, binary editing within @code{ne} should be considered not a normal activity, but rather an exceptional one. If you invoke @code{Binary} with no arguments, it will toggle the flag. If you specify 0 or 1, the flag will be set to false or true, respectively. An upper case @samp{B} will appear on the status bar if the flag is true. If false, a @samp{-} or @samp{!} will indicate whether the last line of the document is empty, which will determine whether the resulting file will have a normal line termination when the document is saved. @node Insert @subsection Insert @cmindex Insert @noindent Syntax: @code{Insert [0|1]}@* @noindent Abbreviation: @code{I} @noindent sets the insert flag. If this flag is true, the text you type is inserted, otherwise it overwrites the existing characters. This also governs the behaviour of the @code{InsertChar} and @code{InsertString} commands. If you invoke @code{Insert} with no arguments, it will toggle the flag. If you specify 0 or 1, the flag will be set to false or true, respectively. A lower case @samp{i} will appear on the status bar if the flag is true. @node FastGUI @subsection FastGUI @cmindex FastGUI @noindent Syntax: @code{FastGUI [0|1]}@* @noindent Abbreviation: @code{FG} @noindent sets the fast graphical user interface flag. When this flag is true, @code{ne} tries to print as little as possible while displaying menus and the status bar. In particular, menu items are highlighted by the cursor only, the status bar is not highlighted (which allows printing it with fewer characters), the current position of the mark is not highlighted, and the hexadecimal code for the character under the cursor is not displayed. This option is only (but very) useful if you are using @code{ne} through a slow connection. If you invoke @code{FastGUI} with no arguments, it will toggle the flag. If you specify 0 or 1, the flag will be set to false or true, respectively. The @code{FastGUI} setting is saved in your @file{~/.ne/.default#ap} file when you use the @code{SaveDefPrefs} command or the @samp{Save Def Prefs} menu. It is not saved by the @code{SaveAutoPrefs} command. @node FreeForm @subsection FreeForm @cmindex FreeForm @noindent Syntax: @code{FreeForm [0|1]}@* @noindent Abbreviation: @code{FF} @noindent sets the free form flag. When this flag is true, you can move with the cursor anywhere on the screen, even where there is no text present (however, you cannot move inside the space expansion of a @sc{tab} character). If you invoke @code{FreeForm} with no arguments, it will toggle the flag. If you specify 0 or 1, the flag will be set to false or true, respectively. A lower case @samp{f} will appear on the status bar if the flag is true. The issue free-form-versus-non-free-form is a major religious war that has engaged users from day one. The due of the implementor is to allow both choices, and to set as default the correct one (in his humble opinion). In this case, non-free-form. @node NoFileReq @subsection NoFileReq @cmindex NoFileReq @noindent Syntax: @code{NoFileReq [0|1]}@* @noindent Abbreviation: @code{NFR} @noindent sets the file requester flag. When this flag is true, the file requester is never opened, under any circumstances. If you invoke @code{NoFileReq} with no arguments, it will toggle the flag. If you specify 0 or 1, the flag will be set to false or true, respectively. @node RequestOrder @subsection RequestOrder @cmindex RequestOrder @noindent Syntax: @code{RequestOrder [0|1]}@* @noindent Abbreviation: @code{RQO} @noindent sets the request order flag. When this flag is true, the requester displays entries in column order. Otherwise entries are displayed by rows. If you invoke @code{RequestOrder} with no arguments, it will toggle the flag. If you specify 0 or 1, the flag will be set to false or true, respectively. The @code{RequestOrder} setting is saved in your @file{~/.ne/.default#ap} file when you use the @code{SaveDefPrefs} command or the @samp{Save Def Prefs} menu. It is not saved by the @code{SaveAutoPrefs} command. @node StatusBar @subsection StatusBar @cmindex StatusBar @noindent Syntax: @code{StatusBar [0|1]}@* @noindent Abbreviation: @code{ST} @noindent sets the status bar flag. When this flag is true, the status bar is displayed at the bottom of the screen. There are only two reasons to turn off the status bar we are aware of: @itemize @bullet{} @item if you are using @code{ne} through a slow connection, updating the line/column indicator can really slow down editing; @item scrolling caused by cursor movement on terminals that do not allow to set a scrolling region can produce annoying flashes at the bottom of the screen. @end itemize If you invoke @code{StatusBar} with no arguments, it will toggle the flag. If you specify 0 or 1, the flag will be set to false or true, respectively. The @code{StatusBar} setting is saved in your @file{~/.ne/.default#ap} file when you use the @code{SaveDefPrefs} command or the @samp{Save Def Prefs} menu. It is not saved by the @code{SaveAutoPrefs} command. @node HexCode @subsection HexCode @cmindex HexCode @noindent Syntax: @code{HexCode [0|1]}@* @noindent Abbreviation: @code{HC} @noindent sets the hex code flag. When this flag is true, the hexadecimal code of the character currently under the cursor is displayed on the status bar. @node ReadOnly @subsection ReadOnly @cmindex ReadOnly @noindent Syntax: @code{ReadOnly [0|1]}@* @noindent Abbreviation: @code{RO} @noindent sets the read only flag. When this flag is true, no editing can be performed on the document (any such attempt produces an error message). Saving read only documents is inhibited as well; you must affirmatively answer a prompt to save a document with the read only flag set. This flag is automatically set whenever you open a file that you cannot write to. See @ref{Open}. If you invoke @code{ReadOnly} with no arguments, it will toggle the flag. If you specify 0 or 1, the flag will be set to false or true, respectively. A lower case @samp{r} will appear on the status bar if the flag is true. @node EscapeTime @subsection EscapeTime @cmindex EscapeTime @noindent Syntax: @code{EscapeTime [@var{n}]}@* @noindent Abbreviation: @code{ET} @noindent sets the escape time. The @key{Escape} key is recognized as such after @var{n} tenths of second. @ifclear autohelp (@pxref{Motivations and Design}.) @end ifclear Along slow connections, it can happen that the default value of 10 is too low: in this case, escape sequences (e.g., those of the arrow keys) could be erroneously broken into an escape and some spurious characters. Rising the escape time usually solves this problem. Allowed values range from 0 to 255, with 0 disabling escape time, which means that a single @key{Escape} will not be recognized as a distinct command. Note that you can accelerate the recognition of the @key{Escape} key by hitting it twice in a row. Note that the escape time is global to @code{ne}, and it is not saved. However, you can add an @code{EscapeTime} command manually to a preferences file. @node TabSize @subsection TabSize @cmindex TabSize @noindent Syntax: @code{TabSize [@var{size}]}@* @noindent Abbreviation: @code{TS} @noindent sets the number of spaces @code{ne} will use when expanding a @sc{tab} character. If the optional argument @var{size} is not specified, you can enter it on the input line, the default being the current @sc{tab} size. Allowed values are strictly between 0 and half the width of the screen. @node Tabs @subsection Tabs @cmindex Tabs @noindent Syntax: @code{Tabs [0|1]}@* @noindent Abbreviation: @code{TAB} @noindent sets the @code{Tabs} flag. When this flag is set, @key{Tab} key and the @code{InsertTab} command will insert literal @var{TAB} characters. Otherwise it will insert enough spaces to have the same visual effect. In normal editing, the @key{Tab} key invokes the command "@code{InsertTab} 1". Unlike most others, the @key{Tab} key cannot be mapped to other commands. Thus the @code{Tabs} flag provides the only customization @code{ne} offers for the @key{Tab} key. If set, either a lower case @samp{t} or upper case @samp{T} will appear in the status bar depending on the state of the @code{ShiftTabs} flag. (The @code{ShiftTabs} flag is irrelevant if the @code{Tabs} flag is off.) See @ref{ShiftTabs}. @node DelTabs @subsection DelTabs @cmindex DelTabs @noindent Syntax: @code{DelTabs [0|1]}@* @noindent Abbreviation: @code{DT} @noindent sets the @code{DelTabs} flag. When this flag is set, a @samp{d} will appear on the status bar, and the @key{BackSpace} and @key{Del} keys will remove a tab's worth of @var{space} characters if a @var{TAB} character could have occupied the same whitespace in the current line as the removed spaces. This is the deletion counterpart to the @code{Tabs} flag. See @ref{Tabs}. @node ShiftTabs @subsection ShiftTabs @cmindex ShiftTabs @noindent Syntax: @code{ShiftTabs [0|1]}@* @noindent Abbreviation: @code{SHT} @noindent sets the @code{ShiftTabs} flag. @code{ShiftTabs} has an effect only when the @code{Tabs} flag is set, in which case an upper case @samp{T} appears in the status bar. When this flag and the @code{Tabs} flag are both set, left and right @code{Shift} commands may use tab characters to adjust leading white space. Otherwise only spaces are used. See @ref{Shift}. @node Turbo @subsection Turbo @cmindex Turbo @noindent Syntax: @code{Turbo [@var{steps}]}@* @noindent Abbreviation: @code{TUR} @noindent sets the turbo parameter. Iterated actions and global replaces will update at most @var{steps} lines of the screen (or at most twice the number of visible rows if @var{steps} is zero); then, update will be delayed to the end of the action. This feature is most useful when massive operations (such as replacing thousands of occurrences of a pattern) have to be performed. After having updated @var{steps} lines, @code{ne} can proceed at maximum speed, because no visual update has to be performed. The value of the turbo parameter has to be adapted to the kind of terminal you are using. Very high values can be good on high-speed terminals, since the time required for the visual updates is very small, and it is always safer to look at what the editor is really doing. On slow terminals, however, small values ensure that operations such as paragraph formatting will not take too long. You have to be careful about setting the turbo parameter too low. @code{ne} keeps track internally of the part of the screen that needs refresh in a very rough way. This means that a value of less than, say, 8 will force it to do a lot of unnecessary refresh. The default value of this parameter is zero, which means twice the number of lines of the screen; for several reasons this does seem to be a good value. @node VerboseMacros @subsection VerboseMacros @cmindex VerboseMacros @noindent Syntax: @code{VerboseMacros [0|1]}@* @noindent Abbreviation: @code{VM} @noindent sets the verbose macros flag. When this flag is true, all macros generated by recording or by automatic preferences saving will contain full names, instead of short names. This is highly desirable if you are going to edit the macro manually, but it can slow down command parsing. If you invoke @code{VerboseMacros} with no arguments, it will toggle the flag. If you specify 0 or 1, the flag will be set to false or true, respectively. A lower case @samp{v} will appear on the status bar if the flag is true. The only reason to use this flag is when recording a macro that will be played a great number of times. Automatic preferences files are too short to be an issue with respect to execution timing. The @code{VerboseMacros} setting is saved in your @file{~/.ne/.default#ap} file when you use the @code{SaveDefPrefs} command or the @samp{Save Def Prefs} menu. It is not saved by the @code{SaveAutoPrefs} command. @node PreserveCR @subsection PreserveCR @cmindex PreserveCR @noindent Syntax: @code{PreserveCR [0|1]}@* @noindent Abbreviation: @code{PCR} @noindent sets the preserve carriage returns flag. When a file is loaded into a document for which this flag is false, both CR (carriage return) and NL (new line) characters are treated as line terminators. If the flag is true, CR characters do not act as line terminators but are instead preserved in the document. This flag has no effect except when loading a file into a document. If you invoke @code{PreserveCR} with no arguments, it will toggle the flag. If you specify 0 or 1, the flag will be set to false or true, respectively. An upper case @samp{P} will appear on the status bar if the flag is true. @node CRLF @subsection CRLF @cmindex CRLF @noindent Syntax: @code{CRLF [0|1]}@* @noindent Abbreviation: @code{CRLF} @noindent sets the CR/LF flag. When a file is saved from a document for which this flag is true, both a CR (carriage return) and a NL (new line) character are output as line terminators. This flag has no effect except when saving a file. This flag is automatically set if you load a file that has at least one CR/LF sequence in it. If you invoke @code{CRLF} with no arguments, it will toggle the flag. If you specify 0 or 1, the flag will be set to false or true, respectively. An upper case @samp{C} will appear on the status bar if the flag is true. @node BracketedPaste @subsection BracketedPaste @cmindex BracketedPaste @noindent Syntax: @code{BracketedPaste [0|1|@var{macro_name_before} @var{macro_name_after}]}@* @noindent Abbreviation: @code{BP} @noindent ``Bracketed paste'' is an informal though widely adopted protocol between terminal mode programs (like @code{ne}) and the terminal emulators through which they present their user interfaces. When @code{BracketedPaste} is enabled, blocks of text pasted from a system clipboard are ``bracketed'' -- that is, they are immediately preceded and followed -- by special character sequences which distinguish such blocks from text you actually typed. Bracketed paste text is usually already indented, tabbed, and line wrapped. The @code{BracketedPaste} command lets you manage factors that change input handling while inserting bracketed paste text into your document. @code{BracketedPaste 0} disables bracketed paste support entirely; @code{BracketedPaste 1} enables bracketed paste support, temporarily disables the @code{AutoIndent} flag, and increases the @code{AtomicUndo} level during bracketed pastes; when given a pair of macro names, bracketed paste support is enabled and the indicated macros will be run immediately before and after processing bracketed paste text. The macros need not already exist. (See the @code{Macro} help page, especially the warning at the bottom.) @code{BracketedPaste} with no argument will prompt you to enter a new value with the prompt's default being the current value. The default value is @samp{1} -- enabled with default behaviour. This is an automatic preference (see @ref{AutoPrefs}) and so will be saved by both @code{SaveAutoPrefs} and @code{SaveDefPrefs}. The flags @code{AutoIndent}, @code{Tabs}, @code{ShiftTabs}, and @code{WordWrap} all affect handling of pasted text. The examples below show contents of before and after macros which would have effects similar to @samp{BracketedPaste 1}. @example # Invoked Before Bracketed Paste # Invoked After Bracketed Paste PushPrefs PopPrefs AtomicUndo + AtomicUndo - AutoIndent 0 Refresh Refresh @end example @node VisualBell @subsection VisualBell @cmindex VisualBell @noindent Syntax: @code{VisualBell [0|1]}@* @noindent Abbreviation: @code{VB} @noindent sets the visual bell flag. When this flag is true, the terminal will flash (if possible) instead of beeping. If you invoke @code{VisualBell} with no arguments, it will toggle the flag. If you specify 0 or 1, the flag will be set to false or true, respectively. @node PushPrefs @subsection PushPrefs @cmindex PushPrefs @noindent Syntax: @code{PushPrefs [@var{n}]}@* @noindent Abbreviation: @code{PUSHP} @noindent pushes @var{n} copies of the user preferences onto a stack. If not specified, @var{n} defaults to one. Use the @code{PopPrefs} command to pop preferences off the stack and restore the values. See @ref{PopPrefs}. Note that the preferences stack is global, not document-specific, so you could @code{PushPrefs} one document's preferences, switch documents, then @code{PopPrefs} those preferences, thereby altering the preferences for the second document. The maximum preferences stack depth is 32. @code{PushPrefs} and @code{PopPrefs} are useful in macros that require certain preferences to work properly. A macro can @code{PushPrefs}, change any preferences necessary, do its work, then @code{PopPrefs} to restore the users previous preferences settings. @noindent @code{PushPrefs} saves the following values on the preferences stack: @example AutoIndent DelTabs NoFileReq StatusBar VisualBell AutoPrefs DoUndo PreserveCR ShiftTabs WordWrap Binary FreeForm ReadOnly Tabs CaseSearch HexCode RightMargin TabSize ClipNumber Insert SearchBack UTF8Auto @end example @node PopPrefs @subsection PopPrefs @cmindex PopPrefs @noindent Syntax: @code{PopPrefs [@var{n}]}@* @noindent Abbreviation: @code{POPP} @noindent pops @var{n} sets of preferences from the preferences stack (where they were placed previously by @code{PushPrefs}) and applies those preferences to the current document. See @ref{PushPrefs}. If not specified, @var{n} defaults to one. Note that the preferences stack is global, not document specific. Therefore you could @code{PushPrefs} one document's preferences, switch documents, then @code{PopPrefs} those settings altering the preferences for the second document. The maximum preferences stack depth is 32. @code{PushPrefs} and @code{PopPrefs} are useful in macros that require certain preferences to work properly. A macro can @code{PushPrefs}, change any preferences necessary, do its work, then @code{PopPrefs} to restore the users previous preferences settings. @noindent PopPrefs restores the following values from the preferences stack: @example AutoIndent DelTabs NoFileReq StatusBar VisualBell AutoPrefs DoUndo PreserveCR ShiftTabs WordWrap Binary FreeForm ReadOnly Tabs CaseSearch HexCode RightMargin TabSize ClipNumber Insert SearchBack UTF8Auto @end example @node LoadPrefs @subsection LoadPrefs @cmindex LoadPrefs @noindent Syntax: @code{LoadPrefs [@var{filename}]}@* @noindent Abbreviation: @code{LP} @noindent loads the given preference file, and sets the current preferences accordingly. If the optional @var{filename} argument is not specified, the file requester is opened, and you are prompted to select a file. (You can inhibit the file requester opening by using the @code{NoFileReq} command; see @ref{NoFileReq}.) If you escape from the file requester, you can input the file name on the command line. Note that a preferences file is just a macro containing only option modifiers. You can manually edit a preferences file for special purposes, such as filtering out specific settings. @ifclear autohelp @xref{Hints and Tricks}. @end ifclear @node SavePrefs @subsection SavePrefs @cmindex SavePrefs @noindent Syntax: @code{SavePrefs [@var{filename}]}@* @noindent Abbreviation: @code{SP} @noindent saves the current preferences to the given file. If the optional @var{filename} argument is not specified, the file requester is opened, and you are prompted to select a file. (You can inhibit the file requester opening by using the @code{NoFileReq} command; see @ref{NoFileReq}.) If you escape from the file requester, you can input the file name on the command line. @node LoadAutoPrefs @subsection LoadAutoPrefs @cmindex LoadAutoPrefs @noindent Syntax: @code{LoadAutoPrefs}@* @noindent Abbreviation: @code{LAP} @noindent loads the preferences file in @file{~/.ne} associated with the current document's file name extension. If the current file name has no extension, the default preferences are loaded. See @ref{AutoPrefs}. @node SaveAutoPrefs @subsection SaveAutoPrefs @cmindex SaveAutoPrefs @noindent Syntax: @code{SaveAutoPrefs}@* @noindent Abbreviation: @code{SAP} @noindent saves the current preferences to the file in @file{~/.ne} associated with the current document's file name extension. If the current file name has no extension, an error message is issued. See @ref{AutoPrefs}. @node SaveDefPrefs @subsection SaveDefPrefs @cmindex SaveDefPrefs @noindent Syntax: @code{SaveDefPrefs}@* @noindent Abbreviation: @code{SDP} @noindent saves the current preferences to the @file{~/.ne/.default#ap} file. This file is always loaded by @code{ne} at startup. @node Modified @subsection Modified @cmindex Modified @noindent Syntax: @code{Modified [0|1]}@* @noindent Abbreviation: @code{MOD} @noindent sets the modified flag. This flag is set automatically whenever a document is modified, and is used to determine which documents need to be saved by @code{SaveAll} or when @code{ne} exits. Normally you would not alter this flag, but when a document is inadvertently modified and you don't want the changes saved, @code{Modified} provides a way to make @code{ne} consider the document unchanged. If you invoke @code{Modified} with no arguments, it will toggle the flag. If you specify 0 or 1, the flag will be set to false or true, respectively. An asterisk (@samp{*}) will appear on the status bar when this flag is set. If your terminal supports underlining, it will be underlined if the corresponding file's modification time has changed since the document was loaded or saved (perhaps by another user). @node Syntax @subsection Syntax @cmindex Syntax @noindent Syntax: @code{Syntax [@var{name}|*]}@* @noindent Abbreviation: @code{SY} @noindent loads the syntax with the given name, and colors the current document accordingly. If the optional @var{name} argument is not specified, you are prompted for one. The current one, if set, is suggested as the default. Use the @key{Tab} key for a requester of the extant syntax recognizers. The special @var{name} * turns off syntax highlighting for the current document. Otherwise, @var{name} must match a syntax definition either in your @file{~/.ne/syntax} directory or in a directory named @samp{syntax} inside @code{ne}'s global directory. Additionally, @code{ne} has a table mapping common suffixes to syntax names. If there is no syntax with a given name, @code{ne} will try to remap the name using the following table (the string before the colon is the name of the syntax file): @ignore N O T E T O D O C U M E N T M A I N T A I N E R S The following "example" section is read by info2src.pl to build ne's extension map. You can break a line if you end it with a "," but otherwise stick to the .jsf file base name followed by ":" followed by a list of common extensions. Don't get fancy with this format. @end ignore @example ada: adb, ads asm: s c: c++, cc, cpp, cxx, h, h++, hpp, l, lex, y, yacc cobol: cbl, cob csh: tcsh diff: patch fortran: f, F, for, f90, F90 html: htm java: js lisp: el, lsp mason: mas ocaml: ml, mli pascal: p, pas perl: pl, pm ps: eps puppet: pp python: py, sage rexx: rex ruby: rb sh: bash, bash_login, bash_logout, bash_profile, bashrc, ksh, profile, rc skill: il tex: latex, dtx, sty texinfo: texi, txi troff: 1 verilog: v, vh, vhd xml: xsd yaml: yml @end example @node UTF8 @subsection UTF8 @cmindex UTF8 @noindent Syntax: @code{UTF8 [0|1]}@* @noindent Abbreviation: @code{U8} @noindent sets the UTF-8 flag. When this flag is true, @code{ne} considers the current document as UTF-8 coded. Note that this flag is set automatically upon file loading (if possible) if you required automatic detection. See @ref{UTF8Auto}. If you invoke @code{UTF8} with no arguments, it will toggle the flag. If you specify 0 or 1, the flag will be set to false or true, respectively. When you try to set this flag, the document will be checked for UTF-8 compliance, and you will get an error message in case of failure. When you try to reset it, the document is set to ASCII or 8-bit, depending on its content. A @samp{U} will appear on the status bar if the flag is true. Alternatively, an @samp{A} or an @samp{8} will be displayed to denote whether the document is composed exclusively by US-ASCII characters, or also by other 8-bit characters (whose encoding is likely to be part of the ISO-8859 family). Note that each time this command modifies the document encoding, it also resets the undo buffer. @node UTF8Auto @subsection UTF8Auto @cmindex UTF8Auto @noindent Syntax: @code{UTF8Auto [0|1]}@* @noindent Abbreviation: @code{U8A} @noindent sets the UTF-8 automatic-detection flag. When this flag is true, @code{ne} will try to guess whether a file just loaded is UTF-8 encoded. Moreover, when a non US-ASCII character is inserted in a pure US-ASCII document, ne will automatically switch to UTF-8. See @ref{UTF8}. The flag is true by default if @code{ne} detects UTF-8 I/O at startup. See @ref{UTF8IO}. If you invoke @code{UTF8Auto} with no arguments, it will toggle the flag. If you specify 0 or 1, the flag will be set to false or true, respectively. @node UTF8IO @subsection UTF8IO @cmindex UTF8IO @noindent Syntax: @code{UTF8IO [0|1]}@* @noindent Abbreviation: @code{U8IO} @noindent sets the UTF-8 input/output flag. This flag is set automatically depending on your locale setting, and is used to determine whether communication with the user (keyboard and terminal) should be UTF-8 encoded. Normally you would not alter this flag, but sometimes @code{ne} may make the wrong guess (e.g., when you are remotely connected). If you invoke @code{UTF8IO} with no arguments, it will toggle the flag. If you specify 0 or 1, the flag will be set to false or true, respectively. An @samp{@@} will appear on the status bar if the flag is true. @node Navigation Commands @section Navigation Commands These commands allow you to move through a document. Besides the standard commands that allow you to move by lines, pages, @i{et cetera}, @code{ne} has bookmarks that let you mark a position in a file so to move to the same position later. @menu * MoveLeft:: * MoveRight:: * LineUp:: * LineDown:: * GotoLine:: * GotoColumn:: * GotoMark:: * PrevPage:: * NextPage:: * PageUp:: * PageDown:: * PrevWord:: * NextWord:: * MoveEOL:: * MoveSOL:: * MoveTOS:: * MoveBOS:: * MoveEOF:: * MoveSOF:: * MoveEOW:: * MoveIncUp:: * MoveIncDown:: * AdjustView:: * ToggleSEOF:: * ToggleSEOL:: * SetBookmark:: * GotoBookmark:: * UnsetBookmark:: @end menu @node MoveLeft @subsection MoveLeft @cmindex MoveLeft @noindent Syntax: @code{MoveLeft [@var{n}]}@* @noindent Abbreviation: @code{ML} @noindent moves the cursor to the left by one character @var{n} times. If the optional @var{n} argument is not specified, it is assumed to be one. @node MoveRight @subsection MoveRight @cmindex MoveRight @noindent Syntax: @code{MoveRight [@var{n}]}@* @noindent Abbreviation: @code{MR} @noindent moves the cursor to the right by one character @var{n} times. If the optional @var{n} argument is not specified, it is assumed to be one. @node LineUp @subsection LineUp @cmindex LineUp @noindent Syntax: @code{LineUp [@var{n}]}@* @noindent Abbreviation: @code{LU} @noindent moves the cursor up by one line @var{n} times. If the optional @var{n} argument is not specified, it is assumed to be one. @node LineDown @subsection LineDown @cmindex LineDown @noindent Syntax: @code{LineDown [@var{n}]}@* @noindent Abbreviation: @code{LD} @noindent moves the cursor down by one line @var{n} times. If the optional @var{n} argument is not specified, it is assumed to be one. @node GotoLine @subsection GotoLine @cmindex GotoLine @noindent Syntax: @code{GotoLine [@var{line}]}@* @noindent Abbreviation: @code{GL} @noindent moves the cursor to the @var{line}th line of the file. If @var{line} is zero or greater than the number of lines in the file, the cursor is moved to the last line. If the optional argument @var{line} is not specified, you can enter it on the input line; the default input response is the current line number. @node GotoColumn @subsection GotoColumn @cmindex GotoColumn @noindent Syntax: @code{GotoColumn [@var{column}]}@* @noindent Abbreviation: @code{GC} @noindent moves the cursor to the @var{column}th column of the file. If the optional argument @var{line} is not specified, you can enter it on the input line; the default input response is the current column number. @node GotoMark @subsection GotoMark @cmindex GotoMark @noindent Syntax: @code{GotoMark}@* @noindent Abbreviation: @code{GM} @noindent moves the cursor to the current mark, if it exists. See @ref{Mark}. @code{GotoMark} is mainly useful if you forgot where you started marking. If you want to record positions in a file and jump to them later, you may want to use bookmarks instead. See @ref{SetBookmark}. @node PrevPage @subsection PrevPage @cmindex PrevPage @noindent Syntax: @code{PrevPage [@var{n}]}@* @noindent Abbreviation: @code{PP} @noindent moves the cursor @var{n} pages backward, if the cursor is on the first line of the screen; otherwise moves the cursor to the first line of the screen, and moves by @var{n}-1 pages. If the optional @var{n} argument is not specified, it is assumed to be one. @node NextPage @subsection NextPage @cmindex NextPage @noindent Syntax: @code{NextPage [@var{n}]}@* @noindent Abbreviation: @code{NP} @noindent moves the cursor @var{n} pages forward, if the cursor is on the last line of the screen; otherwise moves the cursor to the last line of the screen, and moves by @var{n}-1 pages. If the optional @var{n} argument is not specified, it is assumed to be one. @node PageUp @subsection PageUp @cmindex PageUp @noindent Syntax: @code{PageUp [@var{n}]}@* @noindent Abbreviation: @code{PUP} @noindent pages the screen backward by @var{n} screens. If @var{n} is not specified, it is assumed to be one. @node PageDown @subsection PageDown @cmindex PageDown @noindent Syntax: @code{PageDown [@var{n}]}@* @noindent Abbreviation: @code{PDN} @noindent pages the screen forward by @var{n} screens. If @var{n} is not specified, it is assumed to be one. @node PrevWord @subsection PrevWord @cmindex PrevWord @noindent Syntax: @code{PrevWord [@var{n}][<|>]}@* @noindent Abbreviation: @code{PW} @noindent moves the cursor to the left to the @var{n}th word beginning or ending. If the optional @var{n} argument is not specified, one is used. The optional @samp{<} or @samp{>} determines whether to move to the word beginning or ending, respectively. If unspecified, the left end (@samp{<}) is used. @node NextWord @subsection NextWord @cmindex NextWord @noindent Syntax: @code{NextWord [@var{n}][<|>]}@* @noindent Abbreviation: @code{NW} @noindent moves the cursor to the right to the @var{n}th word beginning or ending. If the optional @var{n} argument is not specified, one is used. The optional @samp{<} or @samp{>} determines whether to move to the word beginning or ending, respectively. If unspecified, the left end (@samp{<}) is used. @node MoveEOL @subsection MoveEOL @cmindex MoveEOL @noindent Syntax: @code{MoveEOL}@* @noindent Abbreviation: @code{EOL} @noindent moves the cursor to the end of the current line (@code{EOL} = end of line). @node MoveSOL @subsection MoveSOL @cmindex MoveSOL @noindent Syntax: @code{MoveSOL}@* @noindent Abbreviation: @code{SOL} @noindent moves the cursor to the start of the current line (@code{SOL} = start of line). @node MoveTOS @subsection MoveTOS @cmindex MoveTOS @noindent Syntax: @code{MoveTOS}@* @noindent Abbreviation: @code{TOS} @noindent moves the cursor to the top line of the screen (@code{TOS} = top of screen). @node MoveBOS @subsection MoveBOS @cmindex MoveBOS @noindent Syntax: @code{MoveBOS}@* @noindent Abbreviation: @code{BOS} @noindent moves the cursor to the lowest line currently visible (@code{BOS} = bottom of screen). @node MoveEOF @subsection MoveEOF @cmindex MoveEOF @noindent Syntax: @code{MoveEOF}@* @noindent Abbreviation: @code{EOF} @noindent moves the cursor to the end of the document (@code{EOF} = end of file). @node MoveSOF @subsection MoveSOF @cmindex MoveSOF @noindent Syntax: @code{MoveSOF}@* @noindent Abbreviation: @code{SOF} @noindent moves the cursor to the start of the document (@code{SOF} = start of file). @node MoveEOW @subsection MoveEOW @cmindex MoveEOW @noindent Syntax: @code{MoveEOW [<|>]}@* @noindent Abbreviation: @code{EOW} @noindent moves the cursor to the end of the current word, that end being the right end unless you include the optional parameter @samp{<}. If the cursor is not currently in a word, or if it's already at the indicated end, it retains its current location. @code{MoveEOW} is extremely useful in macros, because it allows you to copy precisely the word the cursor is on. @ifclear autohelp @xref{Hints and Tricks}. @end ifclear @node MoveIncUp @subsection MoveIncUp @cmindex MoveIncUp @noindent Syntax: @code{MoveIncUp}@* @noindent Abbreviation: @code{MIU} @noindent moves the cursor incrementally towards the beginning of the document. More precisely, if the cursor is not on the start of the line it lies on, then it is moved to the start of that line. Otherwise, if it is on the first line of the screen, then it is moved to the start of the document; otherwise, it is moved to the first line of the screen. @node MoveIncDown @subsection MoveIncDown @cmindex MoveIncDown @noindent Syntax: @code{MoveIncDown}@* @noindent Abbreviation: @code{MID} @noindent moves the cursor incrementally towards the end of the document. More precisely, if the cursor is not on the end of the line it lies on, then it is moved to the end of that line. Otherwise, if it is on the last line of the screen, then it is moved to the end of the document; otherwise, it is moved to the last line of the screen. @node AdjustView @subsection AdjustView @cmindex AdjustView @noindent Syntax: @code{AdjustView [T|M|B|L|C|R] [@var{n}]}@* @noindent Abbreviation: @code{AV} @noindent shifts the view (text visible in the terminal window) horizontally or vertically without changing the cursor's position in the document. View adjustments are constrained by the current @sc{tab} size and the length and width of the current document. If called with no arguments @samp{T} is assumed. @samp{T}, @samp{M}, and @samp{B} cause vertical shifts so that the current line becomes the top, middle, or bottom-most visible line respectively. @samp{L}, @samp{C}, and @samp{R} cause horizontal shifts, making the current column the left-most, center, or right-most visible positions. A optional number @var{n} immediately after @samp{T}, @samp{B}, @samp{L}, or @samp{R} indicate the number or rows or columns to shift the view toward the top, bottom, left, or right of the window. Horizontal and vertical adjustment specifications may be combined, so that for example @samp{AdjustView TL} shifts the view so that the current position becomes the top left-most character on screen (within the limits of the current @sc{tab} size). Likewise, @samp{AdjustView B3R5} shifts the view three lines toward the bottom and five columns (excepting @sc{tab} size) toward the right. @node ToggleSEOF @subsection ToggleSEOF @cmindex ToggleSEOF @noindent Syntax: @code{ToggleSEOF}@* @noindent Abbreviation: @code{TSEOF} @noindent moves the cursor to the start of document, if it is not already there; otherwise, moves it to the end of the document. This kind of toggling command is very useful in order to gain some keystrokes on systems with very few keys. See also @ref{ToggleSEOL}, @ref{MoveSOF}, and @ref{MoveEOF}. @node ToggleSEOL @subsection ToggleSEOL @cmindex ToggleSEOL @noindent Syntax: @code{ToggleSEOL}@* @noindent Abbreviation: @code{TSEOL} @noindent moves the cursor to the start of the current line, if it is not already there; otherwise, moves it to the end of the current line. This kind of toggling command is very useful in order to gain some keystrokes on systems with very few keys. See also @ref{ToggleSEOF}, @ref{MoveSOL}, and @ref{MoveEOL}. @node SetBookmark @subsection SetBookmark @cmindex SetBookmark @noindent Syntax: @code{SetBookmark [@var{n}|@var{+1}|@var{-1}|@var{<}|@var{>}|@var{-}|@var{?}]}@* @noindent Abbreviation: @code{SBM} @noindent sets a document bookmark to the current cursor position. Each document has 10 available bookmarks designated @samp{0} to @samp{9}, the most recently pasted block's start and end designated @samp{<} and @samp{>}, plus the automatic bookmark designated by @samp{-}. If no option is given, @samp{0} is assumed. Values of @var{n} from @samp{0} to @samp{9} set the @var{n}th bookmark, while @samp{+1} and @samp{-1} indicate respectively the next and previous available unset bookmarks. You can also set the pasted block's start and end which will be reset on the next @code{Paste} or @code{PasteVert} command, or the @samp{-} automatic bookmark, but it will be reset automatically to the current position whenever a @code{GotoBookmark} command is issued. The @samp{?} option will cause @code{SetBookmark} to prompt you for a bookmark designation. The promp will include an indication of which bookmarks are currently set. You may find this useful in macros, or to bind a key to @samp{SetBookmark ?}. @node GotoBookmark @subsection GotoBookmark @cmindex GotoBookmark @noindent Syntax: @code{GotoBookmark [@var{n}|@var{+1}|@var{-1}|@var{<}|@var{>}|@var{-}]|@var{?}]}@* @noindent Abbreviation: @code{GBM} @noindent moves the cursor to the designated bookmark if that bookmark is set; see @ref{SetBookmark}. Each document has 10 available bookmarks designated @samp{0} to @samp{9}, either end of the most recently pasted text block designated @samp{<} and @samp{>}, plus the automatic bookmark designated by @samp{-}. If no option is given, @samp{0} is assumed. The options @samp{+1} and @samp{-1} indicate respectively the next and previous set bookmarks, so that repeated @code{GotoBookmark +1} commands will cycle through all currently set numbered bookmarks. When successful, the automatic bookmark @samp{-} is set to the position in the document from which the command was issued, so that @code{GotoBookmark -} returns you to the location from which you last issued a successful @code{GotoBookmark} command. Subsequent repeated @code{GotoBookmark -} commands will toggle you between the two locations. The @samp{?} option will cause @code{GotoBookmark} to prompt you for a bookmark designation. The promp will include an indication of which bookmarks are currently set. You may find this useful in macros, or to bind a key to @samp{GotoBookmark ?}. @node UnsetBookmark @subsection UnsetBookmark @cmindex UnsetBookmark @noindent Syntax: @code{UnsetBookmark [@var{n}|@var{+1}|@var{-1}|@var{<}|@var{>}|@var{-}|@var{*}]}@* @noindent Abbreviation: @code{UBM} @noindent unsets either the @var{n}th bookmark (0 through 9), the next (@var{+1}) or previous (@var{-1}) set bookmarks, the bookmarks designating the start (@var{<}) or end (@var{>}) of the most recently pasted text block, the automatic (@var{-}) bookmark, or all (@var{*}) bookmarks, making it as if they had never been set; see @ref{SetBookmark}. If no option is specified, @var{n} is assumed to be zero. While you can unset the automatic bookmark @samp{-}, it will be reset automatically to the current position whenever a @code{GotoBookmark} command is issued. @node Editing Commands @section Editing Commands These commands allow modifying a document directly. @menu * InsertChar:: * InsertString:: * InsertTab:: * DeleteChar:: * DeletePrevWord:: * DeleteNextWord:: * Backspace:: * InsertLine:: * DeleteLine:: * DeleteEOL:: @end menu @node InsertChar @subsection InsertChar @cmindex InsertChar @noindent Syntax: @code{InsertChar [@var{code}]}@* @noindent Abbreviation: @code{IC} @noindent inserts a character whose @sc{ascii} code is @var{code} at the current cursor position. @var{code} can be either decimal, hexadecimal if preceded by @samp{0x}, or octal if preceded by @samp{0}. In any case, @var{code} must be different from 0. All the currently active preferences options (insert, word wrapping, auto indent, @i{et cetera}) are applied. If the optional argument @var{code} is not specified, you can enter it on the input line, the default being the last inserted character. Note that inserting a line feed (10) is completely different from inserting a line with @code{InsertLine}. @code{InsertChar 10} puts the control char @kbd{@key{Control}-J} in the text at the current cursor position. See @ref{InsertLine}. Note also that @code{SaveMacro} converts @code{InsertChar} commands into a possibly smaller number of @code{InsertString} commands. This makes macros easier to read and edit. See @ref{SaveMacro}. @node InsertString @subsection InsertString @cmindex InsertString @noindent Syntax: @code{InsertString [@var{text}]}@* @noindent Abbreviation: @code{IS} @noindent inserts @var{text} at the current cursor position. If the optional argument @var{text} is omitted, you will be prompted for it on the command line. All the currently active preferences options (insert, word wrapping, auto indent, @i{et cetera}) are applied. Note that @code{SaveMacro} converts @code{InsertChar} commands into a possibly smaller number of @code{InsertString} commands. This makes macros easier to read and edit. See @ref{SaveMacro}. @node InsertTab @subsection InsertTab @cmindex InsertTab @noindent Syntax: @code{InsertTab [@var{n}]}@* @noindent Abbreviation: @code{IT} @noindent inserts either @var{n} literal @var{TAB} characters or one or more spaces sufficient to advance the current cursor position @var{n} tab stops depending on the @code{Tabs} flag. See @ref{Tabs}, @ref{TabSize}. @node DeleteChar @subsection DeleteChar @cmindex DeleteChar @noindent Syntax: @code{DeleteChar [@var{n}]}@* @noindent Abbreviation: @code{DC} @noindent deletes @var{n} characters from the text. If the optional @var{n} argument is not specified, it is assumed to be one. Deleting a character when the cursor is just after the last char on a line will join a line with the following one; in other words, the carriage return between the two lines will be deleted. Note that if the cursor is past the end of the current line, no action will be performed. @node DeletePrevWord @subsection DeletePrevWord @cmindex DeletePrevWord @noindent Syntax: @code{DeletePrevWord [@var{n}]}@* @noindent Abbreviation: @code{DPW} @noindent deletes text from the current position through the first character of the @var{n}'th previous start-of-word. If the optional @var{n} argument is not specified, it is assumed to be one (in which case, if the cursor is in the middle of a word the effect is just to delete to the start of that word). @node DeleteNextWord @subsection DeleteNextWord @cmindex DeleteNextWord @noindent Syntax: @code{DeleteNextWord [@var{n}]}@* @noindent Abbreviation: @code{DNW} @noindent deletes text from the current position to the @var{n}'th next end-of-word If the optional @var{n} argument is not specified, it is assumed to be one (in which case, if the cursor is in the middle of a word the effect is just to delete to the end of that word). @node Backspace @subsection Backspace @cmindex Backspace @noindent Syntax: @code{Backspace [@var{n}]}@* @noindent Abbreviation: @code{BS} @noindent acts like @code{DeleteChar}, but moves the cursor to the left before deleting each character. @node InsertLine @subsection InsertLine @cmindex InsertLine @noindent Syntax: @code{InsertLine [@var{n}]}@* @noindent Abbreviation: @code{IL} @noindent inserts @var{n} lines at the current cursor position, breaking the current line. If the optional @var{n} argument is not specified, it is assumed to be one. @node DeleteLine @subsection DeleteLine @cmindex DeleteLine @noindent Syntax: @code{DeleteLine [@var{n}]}@* @noindent Abbreviation: @code{DL} @noindent deletes @var{n} lines starting from the current cursor position, putting the last one in the temporary buffer, from which it can be undeleted. See @ref{UndelLine}. If the optional @var{n} argument is not specified, it is assumed to be one. Note that this action is in no way inverse with respect to @code{InsertLine}. @node DeleteEOL @subsection DeleteEOL @cmindex DeleteEOL @noindent Syntax: @code{DeleteEOL}@* @noindent Abbreviation: @code{DE} @noindent deletes all characters from the current cursor position to the end of the line. @code{DeleteEOL} could be easily implemented with a macro, but it is such a common, basic editing feature that it seemed worth a separate implementation. @node Support Commands @section Support Commands These commands perform miscellaneous useful actions. In particular, they provide access to the shell and a way to assign the functionality of @key{Escape} to another key. @menu * About:: * Alert:: * Beep:: * Exec:: * Flash:: * Help:: * NOP:: * Refresh:: * Suspend:: * System:: * Escape:: * KeyCode:: * NameConvert:: @end menu @node About @subsection About @cmindex About @noindent Syntax: @code{About}@* @noindent Abbreviation: @code{About} @noindent displays the copyright splash screen and places a simple information line containing the version and build date of @code{ne} on the status bar. Press any key to dismiss this screen. @node Alert @subsection Alert @cmindex Alert @noindent Syntax: @code{Alert}@* @noindent Abbreviation: @code{AL} @noindent beeps or flashes, depending on the value of the visual bell flag. @node Beep @subsection Beep @cmindex Beep @noindent Syntax: @code{Beep}@* @noindent Abbreviation: @code{BE} @noindent beeps. If your terminal cannot beep, it flashes. If it cannot flash, nothing happens (but you have a very bad terminal). @node Exec @subsection Exec @cmindex Exec @noindent Syntax: @code{Exec}@* @noindent Abbreviation: @code{EX} @noindent prompts the user on the input line, asking for a command, and executes it. It is never registered while recording a macro (though the command you type is). @code{Exec} is mainly useful for key bindings, menu configurations, and in manually programmed macros. Note that if the command you specify does not appear in @code{ne}'s internal tables, it is considered to be a macro name. See @ref{Macro}. @node Flash @subsection Flash @cmindex Flash @noindent Syntax: @code{Flash}@* @noindent Abbreviation: @code{FL} @noindent acts as @code{Beep}, but interchanging the words ``beep'' and ``flash''. Same comments apply. See @ref{Beep}. @node Help @subsection Help @cmindex Help @noindent Syntax: @code{Help [@var{name}]}@* @noindent Abbreviation: @code{H} @noindent displays some help about the command @var{name} (both the short and the long versions of the command names are accepted). If no argument is given, a list of all existing commands in long form is displayed, allowing you to choose one. You can browse the help text with the standard navigation keys. If you press @key{Return}, the command list will be displayed again. If you press @key{f1} or @key{Escape}, you will return to normal editing. Invocations of the @code{Help} command are never registered while recording macros so that you can safely access the help system while recording. See @ref{Record}. @node NOP @subsection NOP @cmindex NOP @noindent Syntax: @code{NOP}@* @noindent Abbreviation: @code{NOP} @noindent does nothing. Mainly useful for inhibiting standard key bindings. @node Refresh @subsection Refresh @cmindex Refresh @noindent Syntax: @code{Refresh}@* @noindent Abbreviation: @code{REF} @noindent refreshes the display. @code{Refresh} is very important, and should preferably be bound to the @kbd{@key{Control}-L} sequence, for historical reasons. It can always happen that a noisy phone line or a quirk in the terminal corrupts the display. This command restores it from scratch. @code{Refresh} has the side effect of checking to see if your window size has changed, and will modify the display to take that into account. @node Suspend @subsection Suspend @cmindex Suspend @noindent Syntax: @code{Suspend}@* @noindent Abbreviation: @code{SU} @noindent suspends @code{ne} and returns you to a shell prompt; usually, the shell command @code{fg} is used to resume @code{ne}. @node System @subsection System @cmindex System @noindent Syntax: @code{System [@var{command}]}@* @noindent Abbreviation: @code{SYS} @noindent asks the shell to execute @var{command}. The terminal is temporarily reset to the state it was in before @code{ne}'s activation, and @var{command} is started. When the execution is finished, control returns to @code{ne}. If the optional argument @var{command} is not specified, you can enter it on the input line. @ignore Before starting the execution, @code{ne} defines a series of environment variables which can thus be used on the command line. The variables allow to refer to parts of the internal state of @code{ne}. @end ignore @node Escape @subsection Escape @cmindex Escape @noindent Syntax: @code{Escape}@* @noindent Abbreviation: @code{ESC} @noindent toggles the menus on and off, or escapes from the input line. This command is mainly useful for reprogramming the menu activator, and it is never registered while recording a macro. See @ref{Record}. @node KeyCode @subsection KeyCode @cmindex KeyCode @noindent Syntax: @code{KeyCode [@var{k}]}@* @noindent Abbreviation: @code{KC} @noindent prompts you to press a key, and reports on the status bar the key code @code{ne} associates with that key, the command currently associated with that key code, as well as the input class for that key code. Input class codes are: ALPHA, COMMAND, RETURN, TAB, IGNORE, and INVALID. This can be useful while configuring your @file{~/.ne/.keys} file. If the optional integer @var{k} (between 0 and 511) is given, @code{KeyCode} uses @var{k} as the key code and displays the information described above. @var{k} can be either decimal, hexadecimal if preceded by '0x', or octal if preceded by '0'. @node NameConvert @subsection NameConvert @cmindex NameConvert @noindent Syntax: @code{NameConvert [0|1]}@* @noindent Abbreviation: @code{NC} @noindent converts the current document's name between relative and absolute path names. With no parameter it switches the current name from relative to absolute, or absolute to relative. With @code{1} it converts the relative path to absolute or leaves the absolute path unchanged. With @code{0} it converts the absolute path to relative or leaves the relative path unchanged. @node Configuration @chapter Configuration @cindex Meta key In this chapter we shall see how the menus and the key bindings of @code{ne} can be completely configured. Note that menu and key configuration is parsed at startup time, and cannot be changed during the execution of the program. This is a chosen limitation. We will also see how to override incorrect or missing file name extensions by comparing the contents of documents to patterns to determine @emph{virtual extensions}. @ignore It should also be remarked that the standard configuration of @code{ne} does not contain key bindings relative to the @key{Meta} key. This choice was forced by the fact that the behaviour of this key is unpredictable on most systems. If your @key{Meta} key does what it should (i.e., it rises the high bit of any character), you can configure about thirty new shortcuts---the @kbd{@key{Control}-@key{Meta}-@var{letter}} combinations---that will produce ASCII characters between 128 and 159, and will be parsed as shortcuts by @code{ne}. @end ignore @menu * Key Bindings:: * Changing Menus:: * Virtual Extensions:: @end menu @node Key Bindings @section Key Bindings @cindex Key bindings @cindex Configuring the keyboard @code{ne} allows you to associate any keystroke with any command. These associations are referred to as key bindings. You define your key bindings in a (possibly UTF-8) file named @file{.keys} in your @file{~/.ne} directory. You could additionally create a @file{.keys} file in your current directory, any your system administrator might also place a @file{.keys} file in your system's global directory. These will always be loaded each time you start @code{ne}, but you can also create key binding files with the same base name (@file{.keys}) followed by a dash and some string. Such a file will only be loaded when the string matches the value of your @code{TERM} environment variable. For example, you might have files named @file{~/.ne/.keys}, @file{~/.ne/.keys-xterm}, and @file{~/.ne/.keys-vt102}. The first file's key bindings will always be loaded, while at most one of the latter two files will be loaded, depending on your @code{TERM} environment variable. You can change your key binding files' default base name from @file{.keys} by passing an argument to the @code{--keys} option when you start @code{ne}, and you can prevent any configuration from being loaded with the @code{--no-config} option (@pxref{Arguments}). The format of key binding files is simple: each line starting with the @samp{KEY} sequence of capital characters is considered the description of a key binding. Each line starting with @samp{SEQ} binds a character sequence to a key. All other lines are considered comments. The format of a key binding description is @example KEY @var{hexcode} @var{command} @end example The @var{hexcode} value is the @sc{ascii} code of the keystroke. (For special keys such as @key{Insert} or function keys, you should take a look at the file @file{default.keys} that comes with @code{ne}'s distribution: it contains a complete, commented definition of @code{ne}'s standard bindings that you can modify with a trial-and-error approach.) The easiest way to see the code @code{ne} uses for a given key is by using the @ref{KeyCode} command. It prompts you to press a key, then reports the code for that key on the status bar. It also displays the command bound to that key if there is one. You can write just the hexadecimal digits, nothing else is necessary (but a prefixing @samp{0x} is tolerated). For instance, @example KEY 1 MoveSOL @end example @noindent binds to @kbd{@key{Control}-A} the action of moving to the start of a line, while @example KEY 101 LineUp @end example @noindent binds to the ``cursor-up'' key the action of moving the cursor one line up. @var{command} can be any @code{ne} command, including @code{Escape} (which allows reconfiguring the menu activator) and @code{Macro}, which allows binding complex sequences of actions to a single keystroke. The binding of a macro is very fast because on the first call the macro is cached in memory. @xref{Macro}. Note that you cannot @emph{ever} redefine @key{Return} or @key{Escape}. This is a basic issue---however brain damaged is the current configuration, you will always be able to exploit fully the menus and the command line. Besides the ``standard'' combinations (e.g., @kbd{@key{Control}-@var{letter}}), it possible to program combinations based on the @key{Meta} key (a.k.a. @key{Alt}). The situation in this case is a bit more involved, because depending on the terminal emulator you are using, the effect of the @key{Meta} key can be widely different. For instance, @code{xterm} raises the eighth bit of a character, so, for instance, @example KEY 81 MoveSOF @end example @noindent binds @kbd{@key{Control}-@key{Meta}-a} to the action of moving to the start of the document. However, @code{gnome-terminal} will emit the character of ASCII code 1 prefixed with ESC instead (``@code{\x1b\x01}''). To handle this case, @code{ne} provides codes from 180 on for @emph{simulated @key{Meta} sequences}: for instance, @example KEY 181 MoveSOF @end example @noindent binds the abovementioned sequence to the same action as before. In general, the code 180+@var{x} corresponds to the sequence ESC followed by the ASCII character of code @var{x}. Note that some of these sequences may be disabled, if they conflict with existing sequences of your terminal (for instance, ESC followed by @samp{O} is always disabled because it prefixes several built-in keyboard sequences). As a final note, we remark that typing @kbd{@key{Meta}-a} on @code{gnome-terminal} will produce an ESC followed by @samp{a} (``@code{\x1ba}''). Since it is obviously easier to press just @key{Meta} rather than @key{Meta} and @key{Control} at the same time, it is a good idea to associate the same sequence also to this combination, using @example KEY 1E1 MoveSOF @end example @noindent Moreover, this setting provides the user with a second choice: one can press @key{Escape} followed by a letter instead of using modifiers. This is the approach used by default in @code{ne}: this way, @key{Control} with @key{Meta} plus a letter should always work, and @key{Meta} should work sometimes (of course, if you're sure to use always the same kind of emulator you can bind more features). Again, the best place to look is the @file{default.keys} file. As stated above, each line starting with @samp{SEQ} binds a character sequence to a key code. The format for a @samp{SEQ} binding is @example SEQ "@var{sequence}" @var{hexcode} @end example @noindent where "@var{sequence}" is a double-quoted string of characters (which can include escaped hexadecimals) followed by a hexadecimal key code as described above for @samp{KEY} definitions. You should rarely need this, as properly configured systems already do this for most keys. However, some key combinations (@key{Control} in conjunction with cursor keys for example) are usually not defined. If you know the character sequence your system generates for such a combination, you may use @samp{SEQ} to bind that sequence to a particular key code if that sequence isn't already defined on your system. For example, @key{Control}-``cursor-left'' may generate the sequence @code{\x1b[1;5D}. The following lines bind that sequence to the @key{f10} key code @samp{14A}, then bind that key code to the @samp{HELP} command. @example SEQ "\x1b[1;5D" 14A KEY 14A HELP @end example Sequences are inherently terminal- or terminal emulator-specific, so their utility will vary depending on how many emulators you use. At least they give you the possibility to use keys or key combinations that aren't covered by @code{curses}. The key binding file is parsed at startup. If something does not work, @code{ne} exits displaying an error message. If you want @code{ne} to skip parsing the key binding file (for instance, to correct the broken file), just give @code{ne} the @code{--no-config} argument. @xref{Arguments}. @node Changing Menus @section Changing Menus @cindex Configuring the menus @code{ne} allows you to change the contents of its menus. To accomplish this task, you have to create a file named @file{.menus} in your home directory, or in @file{~/.ne}. You can change the default name (possibly specifying a complete path) using the @code{--menus} argument (@pxref{Arguments}). Each line of a menu configuration file not starting with the @samp{MENU} or @samp{ITEM} keywords is considered a comment. You should describe the menus as in the following example: @example MENU "File" ITEM "Open... ^O" Open ITEM "Close " Close ITEM "DoIt " Macro DoIt @end example In other words: a line of this form @example MENU "@var{title}" @end example will start the definition of a new menu, having the given title. Each line of the form @example ITEM "@var{text}" @var{command} @end example will then define a menu item, and associate the given command to it. Any number of menus can be accommodated, but you should consider that many terminals are 80 columns wide. There is also a minor restriction on the items---their width has to be constant throughout each menu (but different menus can have different widths). Note that the text of an item, as the name of a menu, is between quotes. Whatever follows the last quote is considered the command associated to the menu. @noindent @strong{Warning:} the description of key bindings in menus (@samp{^O} in the previous example) is very important for the beginner; there is no relation inside @code{ne} about what you say in the menu and how you configure the key bindings (@pxref{Key Bindings}). Please do not say things in the menus that are not true in the key binding file. The menu configuration file is parsed at startup. If something does not work, @code{ne} exits displaying an error message. If you want @code{ne} to skip the menu configuration phase (for instance, to correct the broken file), just give @code{ne} the @code{--no-config} argument. @xref{Arguments}. @node Virtual Extensions @section Virtual Extensions @cindex Virtual Extensions @cindex Extension by Content When a document is loaded or saved under a different name, @code{ne} may examine the contents of the document to determine whether to ignore the corresponding file's actual or missing extension and use instead a @dfn{virtual extension}. This affects which AutoPrefs and Syntax settings @code{ne} applies to the document. The document's contents are matched against the regular expressions (see @pxref{Regular Expressions}) you include in your @file{~/.ne/.extensions} file. (There may also be an @file{extensions} file (no leading @samp{.}) in the global directory.) If @code{ne} finds a match it will act as if the document's name had the corresponding extension. Note that by default @code{ne} does not override a file's given extension. However, you can specify any number of extensions that you would like to allow to be overridden by including lines in your @file{~/.ne/extensions} containing only a dot followed by a single extension or shell ``glob pattern''. In particular, ``@code{.*}'' would allow overriding all extensions. @noindent Here's an example @file{~/.ne/.extensions} file: @example # The following patterns match some common command interpreters. # They must match on the first line. sh 1 ^#!\s*/.*\b(bash|sh|ksh|zsh)\s* csh 1 ^#!\s*/.*\b(csh|tcsh)\s* pl 1 ^#!\s*/.*\bperl\b py 1 ^#!\s*/.*\bpython[0-9]*\s* rb 1 ^#!\s*/.*\bruby\s* xml 1 ^<\?xml # These must match in the first 30 and 1000 lines, respectively. yaml 30 ^---$ ini 1000i ^\[\s*(\w|[.-])+\s*\]\s*$ # You must list the existing extensions you wish to override, one # per line. Shell glob patterns are allowed. Note that ".*" would # allow overriding any extension. (Think before you do that!) .conf .tx[0-9] @end example @noindent The only lines which matter consist of a space-delimited set of @verbatim extension number regular_expression @end verbatim @noindent or a single @samp{.} followed by a glob pattern. Anything else is treated as a comment. The number must be a positive integer indicating the maximum line number of the document in which the corresponding regular expression must match. The exception is zero, which allows a match on any line in the document. (Actually, ne restricts the examined portion of the document to the first 1,000,000 bytes.) If the number has a lower-case @samp{i} suffix (see the @file{ini} example above), the corresponding regular expression is not case sensitive. Trailing spaces are not included as part of the regular_expression. Only the last instance of any extension specification is considered. This allows you to override any specifications from the global @file{extensions} file. If you really need two different patterns, join them into one by concatenating them with a @samp{|} like so: @verbatim foo 1000i (pattern_A)|(pattern_B) @end verbatim @node Hints and Tricks @chapter Hints and Tricks @cindex Escape usage @cindex Turbo adjustment @cindex Shortcuts not working @cindex Meta key @cindex Changing colors @cindex Large files @table @emph @item Use @key{f1} or @key{Escape}-@key{Escape}, not @key{Escape}. Due to the limitations of the techniques used when communicating with a terminal, it is not possible to ``decide'' that the user pressed the @key{Escape} key for about a second after the actual key press (@pxref{EscapeTime}). This means that you will experience annoying delays when using menus. If you have no @key{f1} key, use @key{Escape}-@key{Escape}, or redefine a keystroke assigning the command @code{Escape}, and you will be able to use that keystroke instead of @key{Escape}. Unfortunately, some GUI-based terminals (most notably, @code{gnome-terminal}) use @key{f1} for their own purposes; in that case, you can assign the @code{Escape} command to another key (@pxref{Configuration}). @item Check for the presence of a @key{Meta} key. If your system has a standard @key{Meta} or @key{Alt} key, there is a good chance that you have several other shortcuts. If the built-in @key{Meta} bindings do not work, you must discover which is the effect of the @key{Meta} in your terminal emulator. Indeed, it is possible in theory to configure about 150 shortcuts. @xref{Configuration}. In any case, prefixing a key with @key{Escape} has the same effect as holding down @key{Meta}, so with the standard key bindings you can, for instance, advance by word with @kbd{@key{Escape}} followed by @kbd{F}. @item When editing very large files, please use the @code{--no-syntax} option. Even if @code{ne} will switch transparently to memory-mapped disk files, syntax highlighting requires a great deal of additional memory. @item Mac users should turn on ``Delete sends CTRL-H'' in the @command{Terminal} settings. If you are a Mac user, you need to check the ``Delete sends CTRL-H'' option in the @samp{Advanced} tab of the @command{Terminal} application settings. @item @code{ne} does tilde expansion. When you have to specify a file name, you can always start with @file{~/} in order to specify your home directory, or @file{~@var{user}/} to specify the home directory of another user. @item It is easy to correct bad colors. Sometimes, due to different opinions about the best default foreground and background colors, some of the color choices in a syntax file might be unreadable (for instance, @samp{dim white} on a terminal with a white background). Just copy the guilty syntax specification file to the @file{~/.ne/syntax} directory, and change the color names at the start of the file. @item Use the @samp{tabs} syntax to distinguish @sc{tab}s from @sc{space}s. When you're struggling to clean up a mix of @sc{tab}s and @sc{space}s, temporarily switching to the @samp{tabs} syntax may help. The command @command{Syntax tabs} makes @sc{tab} characters show up in a different background color from @sc{space}s. Once you've gotten your white space issues straightened out, you can switch back to the syntax appropriate for your current file type. @item @code{ne} does interactive filename completion. When you have to specify a file name as last element of a long input, you can invoke the completer using @key{Tab}. If you hit it twice in a row, you will enter the file requester, where you can navigate and escape back to the command line, either with @key{f1}, which will let you edit again your previous input, or with @key{Tab}, which will copy your current selection over your previous file name. In other words, you can freely alternate completion, editing and browsing. @item Disable the status bar for slow connections. @code{ne} tries to emit as few characters as possible when updating the screen. However, for each key you type it is likely that the status bar has to be updated. If your connection is very slow, you can disable the status bar to get a quicker response (@pxref{StatusBar}). @item The @key{Escape} delay when activating menus can be avoided. If you press after @key{Escape} any key that does not produce the second character of an escape sequence, @code{ne} will immediately recognize the @key{Escape} key code as such. Since non-alphabetical keys have no effect while browsing through the menus, if you're forced to use @key{Escape} as menu activator you can press, for instance, @samp{,} just after it to speed up the menu activation (note that @samp{:} would not work, because it would activate the command line). Alternatively, you can just type @key{Escape} twice in a row. @item Use turbo mode for lengthy operations. Turbo mode (@pxref{Turbo}) allows performing very complex operations without updating the screen until the operations are complete. This can be a major plus if you are editing very long files, or if your terminal is slow. If the default value (0, which means twice the number of visible rows) does not give you the best results, experiment other values. @item Regular expressions are powerful, and slow. Regular expressions must be studied very carefully. If you spend a lot of time doing editing, it is definitely reasonable to study even their most esoteric features. Very complex editing actions can be performed by a single find/replace using the @code{\@var{n}} convention. But remember always that regular expressions are much slower than a normal search: in particular, if you use them on a UTF-8 text, @code{ne} has to transform them into an equivalent (but more complex) expression that cannot match partially a UTF-8 sequence, and this expansion makes the search even slower. @item Use the correct movement commands in a macro. Many boring, repetitive editing actions can be performed in a breeze by recording them the first time. Remember, however, that while recording a complex macro you should always use a cursor movement that will apply in a different context. For instance, if you are copying a word, you cannot move with cursor keys, because that word at another application of the macro could be of a different length. Rather, use the next/previous word keys and the @code{MoveEOW} command, which guarantee a correct behaviour in all situations. @item Some preferences can be preserved even with automatic preferences. When you save an autoprefs file, the file simply contains a macro that, when executed, produces the current configuration. However, you could want, for instance, to never change the insert/overwrite state. In this case, just edit the autoprefs files with @code{ne} and delete the line containing the command setting the insert flag. When the autoprefs are loaded later, the insert flag will be left untouched. This trick is particularly useful with the @code{StatusBar} and @code{FastGUI} commands. @item If some keystrokes do not work, check for system-specific features. Sometimes it can happen that a keystroke does not work---for instance, @kbd{@key{Control}-O} does not open a file. This usually is due to the kernel tracking that key for its purposes. For instance, along a @code{telnet} connection with xon/xoff flow control, @kbd{@key{Control}-S} and @kbd{@key{Control}-Q} would block and release the output instead of saving and quitting. In these cases, if you do not need the system feature you should check how to disable it: for instance, some @sc{bsd}-like systems feature a delayed suspend signal that is not in the @sc{posix} standard, and thus cannot be disabled by @code{ne}. On @sc{hp-ux}, the command @code{stty dsusp ^-} would disable the signal, and would let the control sequence previously assigned to it to run up to @code{ne}. @end table @node Motivations and Design @chapter Motivations and Design @cindex Mode @cindex curses @cindex POSIX @cindex terminfo @cindex termcap @cindex Magic cookie terminals @cindex Resource usage @cindex Interrupt character In this chapter I will try to outline the rationale behind @code{ne}'s design choices. Moreover, some present, voluntary limitations of the current implementation will be described. The intended audience of such a description is the programmer wanting to hack up @code{ne}'s sources, or the informed user wanting to deepen his knowledge of the limitations. The design goal of @code{ne} was to write an editor that is easy to use at first sight, powerful, and completely configurable. Making @code{ne} run on any terminal that @code{vi} could handle was also a basic issue, because there is no use getting accustomed to a new tool if you cannot use it when you really need it. Finally, using resources sparingly was considered essential. @code{ne} has no concept of @emph{mode}. All shortcuts are defined by a single key, possibly with a modifier (such as @key{Control} or @key{Meta}). Modality is in my opinion a Bad Thing unless it has a very clear visual feedback. As an example, menus are a form of modality. After entering the menus, the alphabetic keys and the navigation keys have a different meaning. But the modality is clearly reflected by a change in the user interface. The same can be said about the input line, because it is always preceded by a (possibly highlighted) prompt ending with a colon. @code{ne} has no sophisticated visual updating system similar to, for instance, the one of @code{curses}. All updating is done while manipulating the text, and only if the turbo flag is set can some iterated operations delay the update. (In this case, @code{ne} keeps track in a very rough way of the part of the screen that changed.) Moreover, the output is not preempted by additional input coming in, so that along a slow connection the output could not keep up with the input. However, along reasonably fast connections, the responsiveness of the editor is greatly enhanced by the direct update. And since we update the screen in parallel with the internal representation, we can exploit our knowledge to output a very small number of characters per modification. As it is typical in @code{ne}, when such design tradeoffs arise, preference is given to the solution that is effective on a good part of the existing hardware and will be very effective on most future hardware. @code{ne} uses a particular scheme for handling text. There is a doubly linked list of line descriptors that contain pointers to each line of text. The lines themselves are kept in a list of pools, which is expanded and reduced dynamically. The interesting thing is that for each pool @code{ne} keeps track just of the first and of the last character used. A character is free iff it contains a null, so there is no need for a list of free chunks. The point is that the free characters lying between that first and the last used characters (the @dfn{lost} characters) can only be allocated @emph{locally}: whenever a line has to grow in length, @code{ne} first checks if there are enough free characters around it. Otherwise, it remaps the line elsewhere. Since editing is essentially a local activity, the number of such lost characters remains very low. And the manipulation of a line is extremely fast and independent of the size of the file, which can be very huge. A mathematical analysis of the space/time tradeoff is rather difficult, but empirical evidence suggests that the idea works. @code{ne} takes the @sc{posix} standard as the basis for @sc{un*x} compatibility. The fact that this standard has been designed by a worldwide recognized and impartial organization such as @sc{ieee} makes it in my opinion the most interesting effort in its league. No attempt is made to support ten thousand different versions and releases by using conditional compilation. Very few assumptions are made about the behaviour of the system calls. This has obvious advantages in terms of code testing, maintenance, and reliability. For the same reasons, the availability of an @sc{ansi} C (C99) compiler is assumed. If the system has a @code{terminfo} database and the related functions (which are usually contained in the @code{curses} library), @code{ne} will use them. The need for a terminal capability database is clear, and the choice of @code{terminfo} (with respect to @code{termcap}) is compulsory if you want to support a series of features (such as more than ten function keys) that @code{termcap} lacks. If @code{terminfo} is not available, @code{ne} can use a @code{termcap} database, or, as a last resort, a built-in set of ANSI control sequences. Some details about this can be found in @ref{Portability Problems}. @code{ne} does not allow redefinition of the @key{Escape}, @key{Tab} or @key{Return} keys, nor of the interrupt character @kbd{@key{Control}-\}. This decision has been made mainly for two reasons. First of all, it is necessary to keep a user from transforming @code{ne}'s bindings to such a point that another unaware user cannot work with it. These two keys and the alphabetic keys allow activating any command without any further knowledge of the key bindings, so it seems to me this is a good choice. As a second point, the @key{Escape} key usage should generally be avoided. The reason is that most escape sequences that are produced by special keys start with the escape character. When @key{Escape} is pressed, @code{ne} has to wait for one second (this timing can be changed with the @code{EscapeTime} command), just to be sure that it did not receive the first character of an escape sequence. This makes the response of the key very slow, unless it is immediately followed by another key such as @samp{:}, or by @key{Escape}, again. @xref{Hints and Tricks}. Note that, as has been stated several times, the custom key bindings also work when doing a long input, navigating through the menus or browsing the requester. However, this is only partially true. To keep the code size and complexity down, in these cases @code{ne} recognizes only direct bindings to commands, and discards the arguments. Thus, for instance, if a key is bound to the command line @code{LineUp 2}, it will act like @code{LineUp}, while a binding to @code{Macro MoveItUp} would produce no result. Of course full binding capability is available while writing text. (This limitation will probably be lifted in a future version: presently it does not seem to limit seriously the configurability of @code{ne}.) @code{ne} has some restrictions in its terminal handling. It does not support highlighting on terminals that use a magic cookie. Supporting such terminals correctly is a royal pain, and I did not have any means of testing the code anyway. Moreover, they are rather obsolete. Another lack of support is for the capability strings that specify a file to print or a program to launch in order to initialize the terminal. The macro capabilities of @code{ne} are rather limited. For instance, you cannot give an argument to a macro: macros are simply sequences of commands that can be played back automatically. This makes them very useful for everyday use in a learn/play context, but rather inflexible for extending the capabilities of the editor. @code{ne} has been written with sparing resource use as a basic goal. Every possible effort has been made to reduce the use of @sc{cpu} time and memory, the number of system calls, and the number of characters output to the terminal. For instance, command parsing is done through hash techniques, and the escape sequence analysis uses the order structure of strings for minimizing the number of comparisons. The optimal cursor motion functions were directly copied from @code{emacs}. The update of files using syntax highlighting is as lazy as possible: modifications cause just the update of the current line, and the rest of the screen is updated only when you move away. The search algorithm is a simplified version of the Boyer-Moore algorithm that provides high performance with a minimal setup time. An effort has been taken to move to the text segment all data that do not change during the program execution. When the status bar is switched off, additional optimizations reduce the cursor movement to a minimum. A word should be said about lists. Clearly, handling the text as a single block with an insertion gap (a la @code{emacs}) allows you to gain some memory. However, the management of the text as a linked list requires much less @sc{cpu} time, and the tradeoff seems to be particularly favorable on virtual memory systems, where moving the insertion gap can require a lot of accesses to different pages. @node The Encoding Mess @chapter The Encoding Mess @cindex UTF-8 @cindex ISO-8859 family @cindex ISO-8859-1 @code{ne} supports UTF-8. It can use UTF-8 for its input/output, and it can also interpret one or more documents as containing UTF-8 encoded text, acting accordingly. Note that the document content is actual UTF-8 text---@code{ne} does not use wide characters. As a positive side-effect, @code{ne} can support fully the ISO-10646 standard, but nonetheless non-UTF-8 texts occupy exactly one byte per character. More precisely, @emph{any} piece of text in @code{ne} is classified as US-ASCII, 8-bit or UTF-8. A US-ASCII text contains only US-ASCII characters. An 8-bit text sports a one-to-one correspondence between characters and bytes, whereas an UTF-8 text is interpreted in UTF-8. Of course, this raises a difficult question: @emph{when} should a document be classified as UTF-8? Character encodings are a mess. There is nothing we can do to change this fact, as character encodings are @emph{metadata that modify data semantics}. The same file may represent different texts of different lengths when interpreted with different encodings. Thus, there is no safe way of guessing the encoding of a file. @code{ne} stays on the safe side: it will never try to convert a file from an encoding to another one. It can, however, interpret data contained in a document depending on an encoding: in other words, encodings are truly treated as metadata. You can switch off UTF-8 at any time, and see the same document as a standard 8-bit file. Moreover, @code{ne} uses a @emph{lazy} approach to the problem: first of all, unless the UTF-8 automatic detection flag is set (@pxref{UTF8Auto}), no attempt is ever made to consider a file as UTF-8 encoded. Every file, clip, command line, etc., is firstly scanned for non-US-ASCII characters: if it is entirely made of US-ASCII characters, it is classified as US-ASCII. An US-ASCII piece of text is compatible with anything else---it may be pasted in any document, or, if it is a document, it may accept any form of text. Documents classified as US-ASCII are distinguished by an @samp{A} on the status bar. As soon as a user action forces a choice of encoding (e.g., an accented character is typed, or an UTF-8-encoded clip is pasted), @code{ne} fixes the mode to 8-bit or UTF-8 (when there is a choice, this depends on the value of the @ref{UTF8Auto} flag). Of course, in some cases this may be impossible, and in that case an error will be reported. All this happens behind the scenes, and it is designed so that in 99% of the cases there is no need to think of encodings. In any case, should @code{ne}'s behaviour not match your needs, you can always change at run time the level of UTF-8 support. @node History @chapter History @cindex TurboText @cindex Amiga The main inspiration for this work came from Martin Taillefer's @code{TurboText} for the Amiga, which is the best editor I ever saw on any computer. The first versions of @code{ne} were created on an Amiga 3000T, using the port of the @code{curses} library by Simon John Raybould. After switching to the lower-level @code{terminfo} library, the development continued under @sc{un*x}. Finally, I ported @code{terminfo} to the Amiga, thus making it possible to develop on that platform again. For @code{ne} 1.0, an effort has been made to provide a @code{terminfo} emulation using GNU's @code{termcap}. The development eventually moved to Linux. Todd Lewis got involved with @code{ne} when the University of North Carolina's Chapel Hill campus migrated its central research computers from @sc{mvs} to @sc{unix} in 1995. The readily available @sc{unix} editors had serious weaknesses in their user interfaces, especially from the standpoint of @sc{mvs} users who were not too excited about having to move their projects to another platform while learning an entirely new suite of tools. @code{ne} offered an easily understood interface with enough capabilities to keep these new @sc{unix} users productive. Todd installed and has maintained @code{ne} at UNC since then, making several improvements to the code to meet his users' needs. In early 1999 his code base and mine were merged to become version 1.17. Support for syntax highlighting was added in 2009 with code and techniques heavily borrowed from the GNU-licensed editor @code{joe}, which was written by Joseph H. Allen. Much of the work to incorporate this code into @code{ne} was undertaken by Daniele Filaretti, an undergraduate student working under the direction of Sebastiano at the Universit@`a degli Studi di Milano. @node Portability Problems @chapter Portability Problems @cindex Portability @cindex POSIX @cindex terminfo @cindex termcap @cindex Printable characters This chapter is devoted to the description of the (hopefully very few) problems that could arise when porting @code{ne} to other flavors of @sc{un*x}. The fact that only @sc{posix} calls have been used (@pxref{Motivations and Design}) should guarantee that on @sc{posix}-compliant systems a recompilation should suffice. Unfortunately, @code{terminfo} has not been standardized by @sc{ieee}, so that different calls could be available. The necessary calls are @code{setupterm()}, @code{tparm()} and @code{tputs()}. The other @code{terminfo} functions are never used. If @code{terminfo} is not available, the source files @file{info2cap.c} and @file{info2cap.h} map @code{terminfo} calls on @code{termcap} calls. The complete GNU @code{termcap} sources are distributed with @code{ne}, so no library at all is needed to use them. You just have to compile using one of the options explained in the @file{makefile} and in the @file{README}. Should you need comprehensive information on GNU @code{termcap}, you can find the distribution files on any @code{ftp} site that distributes the GNU archives. I should note that the GNU @code{termcap} manual is definitely the best manual ever written about terminal databases. There are, however, some details that are not specified by @sc{posix}, or are specified with insufficient precision. The places of the source where such details come to the light are evidenced by the @samp{PORTABILITY PROBLEM} string, which is followed by a complete explanation of the problem. For instance, there is no standard way of printing extended @sc{ascii} characters (i.e., characters whose code is smaller than 32 or greater than 126). On many system, these characters have to be filtered and replaced with something printable: the default behaviour is to add 64 to all characters under 32 (so that control characters will translate to the respective letter) and to print them in reverse video; moreover, all characters between 127 and 160 are visualized as a reversed question mark (this works particularly well with ISO Latin 1, but Windows users might not like it). This behavior can be easily changed by modifying the @code{out()} function in @file{term.c}. Note that it is certainly possible that some system features not standardized by @sc{posix} interfere with @code{ne}'s use of the I/O stream. Such problems should be dealt with locally by using the system facilities rather than by horribly @code{#ifdef}'ing the source code. An example is given in @ref{Hints and Tricks}. @node Acknowledgments @chapter Acknowledgments A lot of people contributed to this project. Part of the code comes from @code{emacs} and @code{joe}. Many people, in particular at the silab (the Milan University Computer Science Department Laboratory), helped in beta testing the first versions. Daniele Filaretti worked at the integration of syntax-highlighting code from @code{joe}. John Gabriele suggested several new features and relentlessly tested them. Comments, complaints, desiderata are welcome. @example Sebastiano Vigna Via California 22 I-20144 Milano MI Italia sebastiano.vigna@@unimi.it Todd M. Lewis 418 Arlington Cir. Sanford, NC 27330-7600 USA utoddl@@gmail.com ne home page: @uref{http://ne.di.unimi.it/} Discuss ne at @uref{http://groups.google.com/group/niceeditor/} Github repo: @uref{https://github.com/vigna/ne/} @end example @page @page @node Concept Index @unnumbered Concept Index @printindex cp @page @node Command Index @unnumbered Command Index @printindex cm @bye ne-3.3.4/doc/texinfo.cnf.in000066400000000000000000000003511475116431000154420ustar00rootroot00000000000000@c Most of the world uses A4 paper, but texinfo defaults to @c US Letter (8.5in x 11in) paper. If we can determine from @c the locale that we should use 11in (279mm) paper, we'll take @c the default. Otherwise we'll assume A4 paper. ne-3.3.4/doc/texinfo.tex000077500000000000000000010055541475116431000151050ustar00rootroot00000000000000% texinfo.tex -- TeX macros to handle Texinfo files. % % Load plain if necessary, i.e., if running under initex. \expandafter\ifx\csname fmtname\endcsname\relax\input plain\fi % \def\texinfoversion{2007-01-02.19} % % Copyright (C) 1985, 1986, 1988, 1990, 1991, 1992, 1993, 1994, 1995, % 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, % 2007 Free Software Foundation, Inc. % % This texinfo.tex file 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 2, or (at % your option) any later version. % % This texinfo.tex file 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 texinfo.tex file; see the file COPYING. If not, write % to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, % Boston, MA 02110-1301, USA. % % As a special exception, when this file is read by TeX when processing % a Texinfo source document, you may use the result without % restriction. (This has been our intent since Texinfo was invented.) % % Please try the latest version of texinfo.tex before submitting bug % reports; you can get the latest version from: % http://www.gnu.org/software/texinfo/ (the Texinfo home page), or % ftp://tug.org/tex/texinfo.tex % (and all CTAN mirrors, see http://www.ctan.org). % The texinfo.tex in any given distribution could well be out % of date, so if that's what you're using, please check. % % Send bug reports to bug-texinfo@gnu.org. Please include including a % complete document in each bug report with which we can reproduce the % problem. Patches are, of course, greatly appreciated. % % To process a Texinfo manual with TeX, it's most reliable to use the % texi2dvi shell script that comes with the distribution. For a simple % manual foo.texi, however, you can get away with this: % tex foo.texi % texindex foo.?? % tex foo.texi % tex foo.texi % dvips foo.dvi -o # or whatever; this makes foo.ps. % The extra TeX runs get the cross-reference information correct. % Sometimes one run after texindex suffices, and sometimes you need more % than two; texi2dvi does it as many times as necessary. % % It is possible to adapt texinfo.tex for other languages, to some % extent. You can get the existing language-specific files from the % full Texinfo distribution. % % The GNU Texinfo home page is http://www.gnu.org/software/texinfo. \message{Loading texinfo [version \texinfoversion]:} % If in a .fmt file, print the version number % and turn on active characters that we couldn't do earlier because % they might have appeared in the input file name. \everyjob{\message{[Texinfo version \texinfoversion]}% \catcode`+=\active \catcode`\_=\active} \chardef\other=12 % We never want plain's \outer definition of \+ in Texinfo. % For @tex, we can use \tabalign. \let\+ = \relax % Save some plain tex macros whose names we will redefine. \let\ptexb=\b \let\ptexbullet=\bullet \let\ptexc=\c \let\ptexcomma=\, \let\ptexdot=\. \let\ptexdots=\dots \let\ptexend=\end \let\ptexequiv=\equiv \let\ptexexclam=\! \let\ptexfootnote=\footnote \let\ptexgtr=> \let\ptexhat=^ \let\ptexi=\i \let\ptexindent=\indent \let\ptexinsert=\insert \let\ptexlbrace=\{ \let\ptexless=< \let\ptexnewwrite\newwrite \let\ptexnoindent=\noindent \let\ptexplus=+ \let\ptexrbrace=\} \let\ptexslash=\/ \let\ptexstar=\* \let\ptext=\t % If this character appears in an error message or help string, it % starts a new line in the output. \newlinechar = `^^J % Use TeX 3.0's \inputlineno to get the line number, for better error % messages, but if we're using an old version of TeX, don't do anything. % \ifx\inputlineno\thisisundefined \let\linenumber = \empty % Pre-3.0. \else \def\linenumber{l.\the\inputlineno:\space} \fi % Set up fixed words for English if not already set. \ifx\putwordAppendix\undefined \gdef\putwordAppendix{Appendix}\fi \ifx\putwordChapter\undefined \gdef\putwordChapter{Chapter}\fi \ifx\putwordfile\undefined \gdef\putwordfile{file}\fi \ifx\putwordin\undefined \gdef\putwordin{in}\fi \ifx\putwordIndexIsEmpty\undefined \gdef\putwordIndexIsEmpty{(Index is empty)}\fi \ifx\putwordIndexNonexistent\undefined \gdef\putwordIndexNonexistent{(Index is nonexistent)}\fi \ifx\putwordInfo\undefined \gdef\putwordInfo{Info}\fi \ifx\putwordInstanceVariableof\undefined \gdef\putwordInstanceVariableof{Instance Variable of}\fi \ifx\putwordMethodon\undefined \gdef\putwordMethodon{Method on}\fi \ifx\putwordNoTitle\undefined \gdef\putwordNoTitle{No Title}\fi \ifx\putwordof\undefined \gdef\putwordof{of}\fi \ifx\putwordon\undefined \gdef\putwordon{on}\fi \ifx\putwordpage\undefined \gdef\putwordpage{page}\fi \ifx\putwordsection\undefined \gdef\putwordsection{section}\fi \ifx\putwordSection\undefined \gdef\putwordSection{Section}\fi \ifx\putwordsee\undefined \gdef\putwordsee{see}\fi \ifx\putwordSee\undefined \gdef\putwordSee{See}\fi \ifx\putwordShortTOC\undefined \gdef\putwordShortTOC{Short Contents}\fi \ifx\putwordTOC\undefined \gdef\putwordTOC{Table of Contents}\fi % \ifx\putwordMJan\undefined \gdef\putwordMJan{January}\fi \ifx\putwordMFeb\undefined \gdef\putwordMFeb{February}\fi \ifx\putwordMMar\undefined \gdef\putwordMMar{March}\fi \ifx\putwordMApr\undefined \gdef\putwordMApr{April}\fi \ifx\putwordMMay\undefined \gdef\putwordMMay{May}\fi \ifx\putwordMJun\undefined \gdef\putwordMJun{June}\fi \ifx\putwordMJul\undefined \gdef\putwordMJul{July}\fi \ifx\putwordMAug\undefined \gdef\putwordMAug{August}\fi \ifx\putwordMSep\undefined \gdef\putwordMSep{September}\fi \ifx\putwordMOct\undefined \gdef\putwordMOct{October}\fi \ifx\putwordMNov\undefined \gdef\putwordMNov{November}\fi \ifx\putwordMDec\undefined \gdef\putwordMDec{December}\fi % \ifx\putwordDefmac\undefined \gdef\putwordDefmac{Macro}\fi \ifx\putwordDefspec\undefined \gdef\putwordDefspec{Special Form}\fi \ifx\putwordDefvar\undefined \gdef\putwordDefvar{Variable}\fi \ifx\putwordDefopt\undefined \gdef\putwordDefopt{User Option}\fi \ifx\putwordDeffunc\undefined \gdef\putwordDeffunc{Function}\fi % Since the category of space is not known, we have to be careful. \chardef\spacecat = 10 \def\spaceisspace{\catcode`\ =\spacecat} % sometimes characters are active, so we need control sequences. \chardef\colonChar = `\: \chardef\commaChar = `\, \chardef\dashChar = `\- \chardef\dotChar = `\. \chardef\exclamChar= `\! \chardef\lquoteChar= `\` \chardef\questChar = `\? \chardef\rquoteChar= `\' \chardef\semiChar = `\; \chardef\underChar = `\_ % Ignore a token. % \def\gobble#1{} % The following is used inside several \edef's. \def\makecsname#1{\expandafter\noexpand\csname#1\endcsname} % Hyphenation fixes. \hyphenation{ Flor-i-da Ghost-script Ghost-view Mac-OS Post-Script ap-pen-dix bit-map bit-maps data-base data-bases eshell fall-ing half-way long-est man-u-script man-u-scripts mini-buf-fer mini-buf-fers over-view par-a-digm par-a-digms rath-er rec-tan-gu-lar ro-bot-ics se-vere-ly set-up spa-ces spell-ing spell-ings stand-alone strong-est time-stamp time-stamps which-ever white-space wide-spread wrap-around } % Margin to add to right of even pages, to left of odd pages. \newdimen\bindingoffset \newdimen\normaloffset \newdimen\pagewidth \newdimen\pageheight % For a final copy, take out the rectangles % that mark overfull boxes (in case you have decided % that the text looks ok even though it passes the margin). % \def\finalout{\overfullrule=0pt} % @| inserts a changebar to the left of the current line. It should % surround any changed text. This approach does *not* work if the % change spans more than two lines of output. To handle that, we would % have adopt a much more difficult approach (putting marks into the main % vertical list for the beginning and end of each change). % \def\|{% % \vadjust can only be used in horizontal mode. \leavevmode % % Append this vertical mode material after the current line in the output. \vadjust{% % We want to insert a rule with the height and depth of the current % leading; that is exactly what \strutbox is supposed to record. \vskip-\baselineskip % % \vadjust-items are inserted at the left edge of the type. So % the \llap here moves out into the left-hand margin. \llap{% % % For a thicker or thinner bar, change the `1pt'. \vrule height\baselineskip width1pt % % This is the space between the bar and the text. \hskip 12pt }% }% } % Sometimes it is convenient to have everything in the transcript file % and nothing on the terminal. We don't just call \tracingall here, % since that produces some useless output on the terminal. We also make % some effort to order the tracing commands to reduce output in the log % file; cf. trace.sty in LaTeX. % \def\gloggingall{\begingroup \globaldefs = 1 \loggingall \endgroup}% \def\loggingall{% \tracingstats2 \tracingpages1 \tracinglostchars2 % 2 gives us more in etex \tracingparagraphs1 \tracingoutput1 \tracingmacros2 \tracingrestores1 \showboxbreadth\maxdimen \showboxdepth\maxdimen \ifx\eTeXversion\undefined\else % etex gives us more logging \tracingscantokens1 \tracingifs1 \tracinggroups1 \tracingnesting2 \tracingassigns1 \fi \tracingcommands3 % 3 gives us more in etex \errorcontextlines16 }% % add check for \lastpenalty to plain's definitions. If the last thing % we did was a \nobreak, we don't want to insert more space. % \def\smallbreak{\ifnum\lastpenalty<10000\par\ifdim\lastskip<\smallskipamount \removelastskip\penalty-50\smallskip\fi\fi} \def\medbreak{\ifnum\lastpenalty<10000\par\ifdim\lastskip<\medskipamount \removelastskip\penalty-100\medskip\fi\fi} \def\bigbreak{\ifnum\lastpenalty<10000\par\ifdim\lastskip<\bigskipamount \removelastskip\penalty-200\bigskip\fi\fi} % For @cropmarks command. % Do @cropmarks to get crop marks. % \newif\ifcropmarks \let\cropmarks = \cropmarkstrue % % Dimensions to add cropmarks at corners. % Added by P. A. MacKay, 12 Nov. 1986 % \newdimen\outerhsize \newdimen\outervsize % set by the paper size routines \newdimen\cornerlong \cornerlong=1pc \newdimen\cornerthick \cornerthick=.3pt \newdimen\topandbottommargin \topandbottommargin=.75in % Main output routine. \chardef\PAGE = 255 \output = {\onepageout{\pagecontents\PAGE}} \newbox\headlinebox \newbox\footlinebox % \onepageout takes a vbox as an argument. Note that \pagecontents % does insertions, but you have to call it yourself. \def\onepageout#1{% \ifcropmarks \hoffset=0pt \else \hoffset=\normaloffset \fi % \ifodd\pageno \advance\hoffset by \bindingoffset \else \advance\hoffset by -\bindingoffset\fi % % Do this outside of the \shipout so @code etc. will be expanded in % the headline as they should be, not taken literally (outputting ''code). \setbox\headlinebox = \vbox{\let\hsize=\pagewidth \makeheadline}% \setbox\footlinebox = \vbox{\let\hsize=\pagewidth \makefootline}% % {% % Have to do this stuff outside the \shipout because we want it to % take effect in \write's, yet the group defined by the \vbox ends % before the \shipout runs. % \indexdummies % don't expand commands in the output. \normalturnoffactive % \ in index entries must not stay \, e.g., if % the page break happens to be in the middle of an example. % We don't want .vr (or whatever) entries like this: % \entry{{\tt \indexbackslash }acronym}{32}{\code {\acronym}} % "\acronym" won't work when it's read back in; % it needs to be % {\code {{\tt \backslashcurfont }acronym} \shipout\vbox{% % Do this early so pdf references go to the beginning of the page. \ifpdfmakepagedest \pdfdest name{\the\pageno} xyz\fi % \ifcropmarks \vbox to \outervsize\bgroup \hsize = \outerhsize \vskip-\topandbottommargin \vtop to0pt{% \line{\ewtop\hfil\ewtop}% \nointerlineskip \line{% \vbox{\moveleft\cornerthick\nstop}% \hfill \vbox{\moveright\cornerthick\nstop}% }% \vss}% \vskip\topandbottommargin \line\bgroup \hfil % center the page within the outer (page) hsize. \ifodd\pageno\hskip\bindingoffset\fi \vbox\bgroup \fi % \unvbox\headlinebox \pagebody{#1}% \ifdim\ht\footlinebox > 0pt % Only leave this space if the footline is nonempty. % (We lessened \vsize for it in \oddfootingyyy.) % The \baselineskip=24pt in plain's \makefootline has no effect. \vskip 24pt \unvbox\footlinebox \fi % \ifcropmarks \egroup % end of \vbox\bgroup \hfil\egroup % end of (centering) \line\bgroup \vskip\topandbottommargin plus1fill minus1fill \boxmaxdepth = \cornerthick \vbox to0pt{\vss \line{% \vbox{\moveleft\cornerthick\nsbot}% \hfill \vbox{\moveright\cornerthick\nsbot}% }% \nointerlineskip \line{\ewbot\hfil\ewbot}% }% \egroup % \vbox from first cropmarks clause \fi }% end of \shipout\vbox }% end of group with \indexdummies \advancepageno \ifnum\outputpenalty>-20000 \else\dosupereject\fi } \newinsert\margin \dimen\margin=\maxdimen \def\pagebody#1{\vbox to\pageheight{\boxmaxdepth=\maxdepth #1}} {\catcode`\@ =11 \gdef\pagecontents#1{\ifvoid\topins\else\unvbox\topins\fi % marginal hacks, juha@viisa.uucp (Juha Takala) \ifvoid\margin\else % marginal info is present \rlap{\kern\hsize\vbox to\z@{\kern1pt\box\margin \vss}}\fi \dimen@=\dp#1 \unvbox#1 \ifvoid\footins\else\vskip\skip\footins\footnoterule \unvbox\footins\fi \ifr@ggedbottom \kern-\dimen@ \vfil \fi} } % Here are the rules for the cropmarks. Note that they are % offset so that the space between them is truly \outerhsize or \outervsize % (P. A. MacKay, 12 November, 1986) % \def\ewtop{\vrule height\cornerthick depth0pt width\cornerlong} \def\nstop{\vbox {\hrule height\cornerthick depth\cornerlong width\cornerthick}} \def\ewbot{\vrule height0pt depth\cornerthick width\cornerlong} \def\nsbot{\vbox {\hrule height\cornerlong depth\cornerthick width\cornerthick}} % Parse an argument, then pass it to #1. The argument is the rest of % the input line (except we remove a trailing comment). #1 should be a % macro which expects an ordinary undelimited TeX argument. % \def\parsearg{\parseargusing{}} \def\parseargusing#1#2{% \def\argtorun{#2}% \begingroup \obeylines \spaceisspace #1% \parseargline\empty% Insert the \empty token, see \finishparsearg below. } {\obeylines % \gdef\parseargline#1^^M{% \endgroup % End of the group started in \parsearg. \argremovecomment #1\comment\ArgTerm% }% } % First remove any @comment, then any @c comment. \def\argremovecomment#1\comment#2\ArgTerm{\argremovec #1\c\ArgTerm} \def\argremovec#1\c#2\ArgTerm{\argcheckspaces#1\^^M\ArgTerm} % Each occurence of `\^^M' or `\^^M' is replaced by a single space. % % \argremovec might leave us with trailing space, e.g., % @end itemize @c foo % This space token undergoes the same procedure and is eventually removed % by \finishparsearg. % \def\argcheckspaces#1\^^M{\argcheckspacesX#1\^^M \^^M} \def\argcheckspacesX#1 \^^M{\argcheckspacesY#1\^^M} \def\argcheckspacesY#1\^^M#2\^^M#3\ArgTerm{% \def\temp{#3}% \ifx\temp\empty % Do not use \next, perhaps the caller of \parsearg uses it; reuse \temp: \let\temp\finishparsearg \else \let\temp\argcheckspaces \fi % Put the space token in: \temp#1 #3\ArgTerm } % If a _delimited_ argument is enclosed in braces, they get stripped; so % to get _exactly_ the rest of the line, we had to prevent such situation. % We prepended an \empty token at the very beginning and we expand it now, % just before passing the control to \argtorun. % (Similarily, we have to think about #3 of \argcheckspacesY above: it is % either the null string, or it ends with \^^M---thus there is no danger % that a pair of braces would be stripped. % % But first, we have to remove the trailing space token. % \def\finishparsearg#1 \ArgTerm{\expandafter\argtorun\expandafter{#1}} % \parseargdef\foo{...} % is roughly equivalent to % \def\foo{\parsearg\Xfoo} % \def\Xfoo#1{...} % % Actually, I use \csname\string\foo\endcsname, ie. \\foo, as it is my % favourite TeX trick. --kasal, 16nov03 \def\parseargdef#1{% \expandafter \doparseargdef \csname\string#1\endcsname #1% } \def\doparseargdef#1#2{% \def#2{\parsearg#1}% \def#1##1% } % Several utility definitions with active space: { \obeyspaces \gdef\obeyedspace{ } % Make each space character in the input produce a normal interword % space in the output. Don't allow a line break at this space, as this % is used only in environments like @example, where each line of input % should produce a line of output anyway. % \gdef\sepspaces{\obeyspaces\let =\tie} % If an index command is used in an @example environment, any spaces % therein should become regular spaces in the raw index file, not the % expansion of \tie (\leavevmode \penalty \@M \ ). \gdef\unsepspaces{\let =\space} } \def\flushcr{\ifx\par\lisppar \def\next##1{}\else \let\next=\relax \fi \next} % Define the framework for environments in texinfo.tex. It's used like this: % % \envdef\foo{...} % \def\Efoo{...} % % It's the responsibility of \envdef to insert \begingroup before the % actual body; @end closes the group after calling \Efoo. \envdef also % defines \thisenv, so the current environment is known; @end checks % whether the environment name matches. The \checkenv macro can also be % used to check whether the current environment is the one expected. % % Non-false conditionals (@iftex, @ifset) don't fit into this, so they % are not treated as enviroments; they don't open a group. (The % implementation of @end takes care not to call \endgroup in this % special case.) % At runtime, environments start with this: \def\startenvironment#1{\begingroup\def\thisenv{#1}} % initialize \let\thisenv\empty % ... but they get defined via ``\envdef\foo{...}'': \long\def\envdef#1#2{\def#1{\startenvironment#1#2}} \def\envparseargdef#1#2{\parseargdef#1{\startenvironment#1#2}} % Check whether we're in the right environment: \def\checkenv#1{% \def\temp{#1}% \ifx\thisenv\temp \else \badenverr \fi } % Evironment mismatch, #1 expected: \def\badenverr{% \errhelp = \EMsimple \errmessage{This command can appear only \inenvironment\temp, not \inenvironment\thisenv}% } \def\inenvironment#1{% \ifx#1\empty out of any environment% \else in environment \expandafter\string#1% \fi } % @end foo executes the definition of \Efoo. % But first, it executes a specialized version of \checkenv % \parseargdef\end{% \if 1\csname iscond.#1\endcsname \else % The general wording of \badenverr may not be ideal, but... --kasal, 06nov03 \expandafter\checkenv\csname#1\endcsname \csname E#1\endcsname \endgroup \fi } \newhelp\EMsimple{Press RETURN to continue.} %% Simple single-character @ commands % @@ prints an @ % Kludge this until the fonts are right (grr). \def\@{{\tt\char64}} % This is turned off because it was never documented % and you can use @w{...} around a quote to suppress ligatures. %% Define @` and @' to be the same as ` and ' %% but suppressing ligatures. %\def\`{{`}} %\def\'{{'}} % Used to generate quoted braces. \def\mylbrace {{\tt\char123}} \def\myrbrace {{\tt\char125}} \let\{=\mylbrace \let\}=\myrbrace \begingroup % Definitions to produce \{ and \} commands for indices, % and @{ and @} for the aux/toc files. \catcode`\{ = \other \catcode`\} = \other \catcode`\[ = 1 \catcode`\] = 2 \catcode`\! = 0 \catcode`\\ = \other !gdef!lbracecmd[\{]% !gdef!rbracecmd[\}]% !gdef!lbraceatcmd[@{]% !gdef!rbraceatcmd[@}]% !endgroup % @comma{} to avoid , parsing problems. \let\comma = , % Accents: @, @dotaccent @ringaccent @ubaraccent @udotaccent % Others are defined by plain TeX: @` @' @" @^ @~ @= @u @v @H. \let\, = \c \let\dotaccent = \. \def\ringaccent#1{{\accent23 #1}} \let\tieaccent = \t \let\ubaraccent = \b \let\udotaccent = \d % Other special characters: @questiondown @exclamdown @ordf @ordm % Plain TeX defines: @AA @AE @O @OE @L (plus lowercase versions) @ss. \def\questiondown{?`} \def\exclamdown{!`} \def\ordf{\leavevmode\raise1ex\hbox{\selectfonts\lllsize \underbar{a}}} \def\ordm{\leavevmode\raise1ex\hbox{\selectfonts\lllsize \underbar{o}}} % Dotless i and dotless j, used for accents. \def\imacro{i} \def\jmacro{j} \def\dotless#1{% \def\temp{#1}% \ifx\temp\imacro \ptexi \else\ifx\temp\jmacro \j \else \errmessage{@dotless can be used only with i or j}% \fi\fi } % The \TeX{} logo, as in plain, but resetting the spacing so that a % period following counts as ending a sentence. (Idea found in latex.) % \edef\TeX{\TeX \spacefactor=1000 } % @LaTeX{} logo. Not quite the same results as the definition in % latex.ltx, since we use a different font for the raised A; it's most % convenient for us to use an explicitly smaller font, rather than using % the \scriptstyle font (since we don't reset \scriptstyle and % \scriptscriptstyle). % \def\LaTeX{% L\kern-.36em {\setbox0=\hbox{T}% \vbox to \ht0{\hbox{\selectfonts\lllsize A}\vss}}% \kern-.15em \TeX } % Be sure we're in horizontal mode when doing a tie, since we make space % equivalent to this in @example-like environments. Otherwise, a space % at the beginning of a line will start with \penalty -- and % since \penalty is valid in vertical mode, we'd end up putting the % penalty on the vertical list instead of in the new paragraph. {\catcode`@ = 11 % Avoid using \@M directly, because that causes trouble % if the definition is written into an index file. \global\let\tiepenalty = \@M \gdef\tie{\leavevmode\penalty\tiepenalty\ } } % @: forces normal size whitespace following. \def\:{\spacefactor=1000 } % @* forces a line break. \def\*{\hfil\break\hbox{}\ignorespaces} % @/ allows a line break. \let\/=\allowbreak % @. is an end-of-sentence period. \def\.{.\spacefactor=\endofsentencespacefactor\space} % @! is an end-of-sentence bang. \def\!{!\spacefactor=\endofsentencespacefactor\space} % @? is an end-of-sentence query. \def\?{?\spacefactor=\endofsentencespacefactor\space} % @frenchspacing on|off says whether to put extra space after punctuation. % \def\onword{on} \def\offword{off} % \parseargdef\frenchspacing{% \def\temp{#1}% \ifx\temp\onword \plainfrenchspacing \else\ifx\temp\offword \plainnonfrenchspacing \else \errhelp = \EMsimple \errmessage{Unknown @frenchspacing option `\temp', must be on/off}% \fi\fi } % @w prevents a word break. Without the \leavevmode, @w at the % beginning of a paragraph, when TeX is still in vertical mode, would % produce a whole line of output instead of starting the paragraph. \def\w#1{\leavevmode\hbox{#1}} % @group ... @end group forces ... to be all on one page, by enclosing % it in a TeX vbox. We use \vtop instead of \vbox to construct the box % to keep its height that of a normal line. According to the rules for % \topskip (p.114 of the TeXbook), the glue inserted is % max (\topskip - \ht (first item), 0). If that height is large, % therefore, no glue is inserted, and the space between the headline and % the text is small, which looks bad. % % Another complication is that the group might be very large. This can % cause the glue on the previous page to be unduly stretched, because it % does not have much material. In this case, it's better to add an % explicit \vfill so that the extra space is at the bottom. The % threshold for doing this is if the group is more than \vfilllimit % percent of a page (\vfilllimit can be changed inside of @tex). % \newbox\groupbox \def\vfilllimit{0.7} % \envdef\group{% \ifnum\catcode`\^^M=\active \else \errhelp = \groupinvalidhelp \errmessage{@group invalid in context where filling is enabled}% \fi \startsavinginserts % \setbox\groupbox = \vtop\bgroup % Do @comment since we are called inside an environment such as % @example, where each end-of-line in the input causes an % end-of-line in the output. We don't want the end-of-line after % the `@group' to put extra space in the output. Since @group % should appear on a line by itself (according to the Texinfo % manual), we don't worry about eating any user text. \comment } % % The \vtop produces a box with normal height and large depth; thus, TeX puts % \baselineskip glue before it, and (when the next line of text is done) % \lineskip glue after it. Thus, space below is not quite equal to space % above. But it's pretty close. \def\Egroup{% % To get correct interline space between the last line of the group % and the first line afterwards, we have to propagate \prevdepth. \endgraf % Not \par, as it may have been set to \lisppar. \global\dimen1 = \prevdepth \egroup % End the \vtop. % \dimen0 is the vertical size of the group's box. \dimen0 = \ht\groupbox \advance\dimen0 by \dp\groupbox % \dimen2 is how much space is left on the page (more or less). \dimen2 = \pageheight \advance\dimen2 by -\pagetotal % if the group doesn't fit on the current page, and it's a big big % group, force a page break. \ifdim \dimen0 > \dimen2 \ifdim \pagetotal < \vfilllimit\pageheight \page \fi \fi \box\groupbox \prevdepth = \dimen1 \checkinserts } % % TeX puts in an \escapechar (i.e., `@') at the beginning of the help % message, so this ends up printing `@group can only ...'. % \newhelp\groupinvalidhelp{% group can only be used in environments such as @example,^^J% where each line of input produces a line of output.} % @need space-in-mils % forces a page break if there is not space-in-mils remaining. \newdimen\mil \mil=0.001in % Old definition--didn't work. %\parseargdef\need{\par % %% This method tries to make TeX break the page naturally %% if the depth of the box does not fit. %{\baselineskip=0pt% %\vtop to #1\mil{\vfil}\kern -#1\mil\nobreak %\prevdepth=-1000pt %}} \parseargdef\need{% % Ensure vertical mode, so we don't make a big box in the middle of a % paragraph. \par % % If the @need value is less than one line space, it's useless. \dimen0 = #1\mil \dimen2 = \ht\strutbox \advance\dimen2 by \dp\strutbox \ifdim\dimen0 > \dimen2 % % Do a \strut just to make the height of this box be normal, so the % normal leading is inserted relative to the preceding line. % And a page break here is fine. \vtop to #1\mil{\strut\vfil}% % % TeX does not even consider page breaks if a penalty added to the % main vertical list is 10000 or more. But in order to see if the % empty box we just added fits on the page, we must make it consider % page breaks. On the other hand, we don't want to actually break the % page after the empty box. So we use a penalty of 9999. % % There is an extremely small chance that TeX will actually break the % page at this \penalty, if there are no other feasible breakpoints in % sight. (If the user is using lots of big @group commands, which % almost-but-not-quite fill up a page, TeX will have a hard time doing % good page breaking, for example.) However, I could not construct an % example where a page broke at this \penalty; if it happens in a real % document, then we can reconsider our strategy. \penalty9999 % % Back up by the size of the box, whether we did a page break or not. \kern -#1\mil % % Do not allow a page break right after this kern. \nobreak \fi } % @br forces paragraph break (and is undocumented). \let\br = \par % @page forces the start of a new page. % \def\page{\par\vfill\supereject} % @exdent text.... % outputs text on separate line in roman font, starting at standard page margin % This records the amount of indent in the innermost environment. % That's how much \exdent should take out. \newskip\exdentamount % This defn is used inside fill environments such as @defun. \parseargdef\exdent{\hfil\break\hbox{\kern -\exdentamount{\rm#1}}\hfil\break} % This defn is used inside nofill environments such as @example. \parseargdef\nofillexdent{{\advance \leftskip by -\exdentamount \leftline{\hskip\leftskip{\rm#1}}}} % @inmargin{WHICH}{TEXT} puts TEXT in the WHICH margin next to the current % paragraph. For more general purposes, use the \margin insertion % class. WHICH is `l' or `r'. % \newskip\inmarginspacing \inmarginspacing=1cm \def\strutdepth{\dp\strutbox} % \def\doinmargin#1#2{\strut\vadjust{% \nobreak \kern-\strutdepth \vtop to \strutdepth{% \baselineskip=\strutdepth \vss % if you have multiple lines of stuff to put here, you'll need to % make the vbox yourself of the appropriate size. \ifx#1l% \llap{\ignorespaces #2\hskip\inmarginspacing}% \else \rlap{\hskip\hsize \hskip\inmarginspacing \ignorespaces #2}% \fi \null }% }} \def\inleftmargin{\doinmargin l} \def\inrightmargin{\doinmargin r} % % @inmargin{TEXT [, RIGHT-TEXT]} % (if RIGHT-TEXT is given, use TEXT for left page, RIGHT-TEXT for right; % else use TEXT for both). % \def\inmargin#1{\parseinmargin #1,,\finish} \def\parseinmargin#1,#2,#3\finish{% not perfect, but better than nothing. \setbox0 = \hbox{\ignorespaces #2}% \ifdim\wd0 > 0pt \def\lefttext{#1}% have both texts \def\righttext{#2}% \else \def\lefttext{#1}% have only one text \def\righttext{#1}% \fi % \ifodd\pageno \def\temp{\inrightmargin\righttext}% odd page -> outside is right margin \else \def\temp{\inleftmargin\lefttext}% \fi \temp } % @include file insert text of that file as input. % \def\include{\parseargusing\filenamecatcodes\includezzz} \def\includezzz#1{% \pushthisfilestack \def\thisfile{#1}% {% \makevalueexpandable \def\temp{\input #1 }% \expandafter }\temp \popthisfilestack } \def\filenamecatcodes{% \catcode`\\=\other \catcode`~=\other \catcode`^=\other \catcode`_=\other \catcode`|=\other \catcode`<=\other \catcode`>=\other \catcode`+=\other \catcode`-=\other } \def\pushthisfilestack{% \expandafter\pushthisfilestackX\popthisfilestack\StackTerm } \def\pushthisfilestackX{% \expandafter\pushthisfilestackY\thisfile\StackTerm } \def\pushthisfilestackY #1\StackTerm #2\StackTerm {% \gdef\popthisfilestack{\gdef\thisfile{#1}\gdef\popthisfilestack{#2}}% } \def\popthisfilestack{\errthisfilestackempty} \def\errthisfilestackempty{\errmessage{Internal error: the stack of filenames is empty.}} \def\thisfile{} % @center line % outputs that line, centered. % \parseargdef\center{% \ifhmode \let\next\centerH \else \let\next\centerV \fi \next{\hfil \ignorespaces#1\unskip \hfil}% } \def\centerH#1{% {% \hfil\break \advance\hsize by -\leftskip \advance\hsize by -\rightskip \line{#1}% \break }% } \def\centerV#1{\line{\kern\leftskip #1\kern\rightskip}} % @sp n outputs n lines of vertical space \parseargdef\sp{\vskip #1\baselineskip} % @comment ...line which is ignored... % @c is the same as @comment % @ignore ... @end ignore is another way to write a comment \def\comment{\begingroup \catcode`\^^M=\other% \catcode`\@=\other \catcode`\{=\other \catcode`\}=\other% \commentxxx} {\catcode`\^^M=\other \gdef\commentxxx#1^^M{\endgroup}} \let\c=\comment % @paragraphindent NCHARS % We'll use ems for NCHARS, close enough. % NCHARS can also be the word `asis' or `none'. % We cannot feasibly implement @paragraphindent asis, though. % \def\asisword{asis} % no translation, these are keywords \def\noneword{none} % \parseargdef\paragraphindent{% \def\temp{#1}% \ifx\temp\asisword \else \ifx\temp\noneword \defaultparindent = 0pt \else \defaultparindent = #1em \fi \fi \parindent = \defaultparindent } % @exampleindent NCHARS % We'll use ems for NCHARS like @paragraphindent. % It seems @exampleindent asis isn't necessary, but % I preserve it to make it similar to @paragraphindent. \parseargdef\exampleindent{% \def\temp{#1}% \ifx\temp\asisword \else \ifx\temp\noneword \lispnarrowing = 0pt \else \lispnarrowing = #1em \fi \fi } % @firstparagraphindent WORD % If WORD is `none', then suppress indentation of the first paragraph % after a section heading. If WORD is `insert', then do indent at such % paragraphs. % % The paragraph indentation is suppressed or not by calling % \suppressfirstparagraphindent, which the sectioning commands do. % We switch the definition of this back and forth according to WORD. % By default, we suppress indentation. % \def\suppressfirstparagraphindent{\dosuppressfirstparagraphindent} \def\insertword{insert} % \parseargdef\firstparagraphindent{% \def\temp{#1}% \ifx\temp\noneword \let\suppressfirstparagraphindent = \dosuppressfirstparagraphindent \else\ifx\temp\insertword \let\suppressfirstparagraphindent = \relax \else \errhelp = \EMsimple \errmessage{Unknown @firstparagraphindent option `\temp'}% \fi\fi } % Here is how we actually suppress indentation. Redefine \everypar to % \kern backwards by \parindent, and then reset itself to empty. % % We also make \indent itself not actually do anything until the next % paragraph. % \gdef\dosuppressfirstparagraphindent{% \gdef\indent{% \restorefirstparagraphindent \indent }% \gdef\noindent{% \restorefirstparagraphindent \noindent }% \global\everypar = {% \kern -\parindent \restorefirstparagraphindent }% } \gdef\restorefirstparagraphindent{% \global \let \indent = \ptexindent \global \let \noindent = \ptexnoindent \global \everypar = {}% } % @asis just yields its argument. Used with @table, for example. % \def\asis#1{#1} % @math outputs its argument in math mode. % % One complication: _ usually means subscripts, but it could also mean % an actual _ character, as in @math{@var{some_variable} + 1}. So make % _ active, and distinguish by seeing if the current family is \slfam, % which is what @var uses. { \catcode`\_ = \active \gdef\mathunderscore{% \catcode`\_=\active \def_{\ifnum\fam=\slfam \_\else\sb\fi}% } } % Another complication: we want \\ (and @\) to output a \ character. % FYI, plain.tex uses \\ as a temporary control sequence (why?), but % this is not advertised and we don't care. Texinfo does not % otherwise define @\. % % The \mathchar is class=0=ordinary, family=7=ttfam, position=5C=\. \def\mathbackslash{\ifnum\fam=\ttfam \mathchar"075C \else\backslash \fi} % \def\math{% \tex \mathunderscore \let\\ = \mathbackslash \mathactive $\finishmath } \def\finishmath#1{#1$\endgroup} % Close the group opened by \tex. % Some active characters (such as <) are spaced differently in math. % We have to reset their definitions in case the @math was an argument % to a command which sets the catcodes (such as @item or @section). % { \catcode`^ = \active \catcode`< = \active \catcode`> = \active \catcode`+ = \active \gdef\mathactive{% \let^ = \ptexhat \let< = \ptexless \let> = \ptexgtr \let+ = \ptexplus } } % @bullet and @minus need the same treatment as @math, just above. \def\bullet{$\ptexbullet$} \def\minus{$-$} % @dots{} outputs an ellipsis using the current font. % We do .5em per period so that it has the same spacing in the cm % typewriter fonts as three actual period characters; on the other hand, % in other typewriter fonts three periods are wider than 1.5em. So do % whichever is larger. % \def\dots{% \leavevmode \setbox0=\hbox{...}% get width of three periods \ifdim\wd0 > 1.5em \dimen0 = \wd0 \else \dimen0 = 1.5em \fi \hbox to \dimen0{% \hskip 0pt plus.25fil .\hskip 0pt plus1fil .\hskip 0pt plus1fil .\hskip 0pt plus.5fil }% } % @enddots{} is an end-of-sentence ellipsis. % \def\enddots{% \dots \spacefactor=\endofsentencespacefactor } % @comma{} is so commas can be inserted into text without messing up % Texinfo's parsing. % \let\comma = , % @refill is a no-op. \let\refill=\relax % If working on a large document in chapters, it is convenient to % be able to disable indexing, cross-referencing, and contents, for test runs. % This is done with @novalidate (before @setfilename). % \newif\iflinks \linkstrue % by default we want the aux files. \let\novalidate = \linksfalse % @setfilename is done at the beginning of every texinfo file. % So open here the files we need to have open while reading the input. % This makes it possible to make a .fmt file for texinfo. \def\setfilename{% \fixbackslash % Turn off hack to swallow `\input texinfo'. \iflinks \tryauxfile % Open the new aux file. TeX will close it automatically at exit. \immediate\openout\auxfile=\jobname.aux \fi % \openindices needs to do some work in any case. \openindices \let\setfilename=\comment % Ignore extra @setfilename cmds. % % If texinfo.cnf is present on the system, read it. % Useful for site-wide @afourpaper, etc. \openin 1 texinfo.cnf \ifeof 1 \else \input texinfo.cnf \fi \closein 1 % \comment % Ignore the actual filename. } % Called from \setfilename. % \def\openindices{% \newindex{cp}% \newcodeindex{fn}% \newcodeindex{vr}% \newcodeindex{tp}% \newcodeindex{ky}% \newcodeindex{pg}% } % @bye. \outer\def\bye{\pagealignmacro\tracingstats=1\ptexend} \message{pdf,} % adobe `portable' document format \newcount\tempnum \newcount\lnkcount \newtoks\filename \newcount\filenamelength \newcount\pgn \newtoks\toksA \newtoks\toksB \newtoks\toksC \newtoks\toksD \newbox\boxA \newcount\countA \newif\ifpdf \newif\ifpdfmakepagedest % when pdftex is run in dvi mode, \pdfoutput is defined (so \pdfoutput=1 % can be set). So we test for \relax and 0 as well as \undefined, % borrowed from ifpdf.sty. \ifx\pdfoutput\undefined \else \ifx\pdfoutput\relax \else \ifcase\pdfoutput \else \pdftrue \fi \fi \fi % PDF uses PostScript string constants for the names of xref targets, % for display in the outlines, and in other places. Thus, we have to % double any backslashes. Otherwise, a name like "\node" will be % interpreted as a newline (\n), followed by o, d, e. Not good. % http://www.ntg.nl/pipermail/ntg-pdftex/2004-July/000654.html % (and related messages, the final outcome is that it is up to the TeX % user to double the backslashes and otherwise make the string valid, so % that's what we do). % double active backslashes. % {\catcode`\@=0 \catcode`\\=\active @gdef@activebackslashdouble{% @catcode`@\=@active @let\=@doublebackslash} } % To handle parens, we must adopt a different approach, since parens are % not active characters. hyperref.dtx (which has the same problem as % us) handles it with this amazing macro to replace tokens, with minor % changes for Texinfo. It is included here under the GPL by permission % from the author, Heiko Oberdiek. % % #1 is the tokens to replace. % #2 is the replacement. % #3 is the control sequence with the string. % \def\HyPsdSubst#1#2#3{% \def\HyPsdReplace##1#1##2\END{% ##1% \ifx\\##2\\% \else #2% \HyReturnAfterFi{% \HyPsdReplace##2\END }% \fi }% \xdef#3{\expandafter\HyPsdReplace#3#1\END}% } \long\def\HyReturnAfterFi#1\fi{\fi#1} % #1 is a control sequence in which to do the replacements. \def\backslashparens#1{% \xdef#1{#1}% redefine it as its expansion; the definition is simply % \lastnode when called from \setref -> \pdfmkdest. \HyPsdSubst{(}{\realbackslash(}{#1}% \HyPsdSubst{)}{\realbackslash)}{#1}% } \newhelp\nopdfimagehelp{Texinfo supports .png, .jpg, .jpeg, and .pdf images with PDF output, and none of those formats could be found. (.eps cannot be supported due to the design of the PDF format; use regular TeX (DVI output) for that.)} \ifpdf \input pdfcolor \pdfcatalog{/PageMode /UseOutlines} % % #1 is image name, #2 width (might be empty/whitespace), #3 height (ditto). \def\dopdfimage#1#2#3{% \def\imagewidth{#2}\setbox0 = \hbox{\ignorespaces #2}% \def\imageheight{#3}\setbox2 = \hbox{\ignorespaces #3}% % % pdftex (and the PDF format) support .png, .jpg, .pdf (among % others). Let's try in that order. \let\pdfimgext=\empty \begingroup \openin 1 #1.png \ifeof 1 \openin 1 #1.jpg \ifeof 1 \openin 1 #1.jpeg \ifeof 1 \openin 1 #1.JPG \ifeof 1 \openin 1 #1.pdf \ifeof 1 \errhelp = \nopdfimagehelp \errmessage{Could not find image file #1 for pdf}% \else \gdef\pdfimgext{pdf}% \fi \else \gdef\pdfimgext{JPG}% \fi \else \gdef\pdfimgext{jpeg}% \fi \else \gdef\pdfimgext{jpg}% \fi \else \gdef\pdfimgext{png}% \fi \closein 1 \endgroup % % without \immediate, pdftex seg faults when the same image is % included twice. (Version 3.14159-pre-1.0-unofficial-20010704.) \ifnum\pdftexversion < 14 \immediate\pdfimage \else \immediate\pdfximage \fi \ifdim \wd0 >0pt width \imagewidth \fi \ifdim \wd2 >0pt height \imageheight \fi \ifnum\pdftexversion<13 #1.\pdfimgext \else {#1.\pdfimgext}% \fi \ifnum\pdftexversion < 14 \else \pdfrefximage \pdflastximage \fi} % \def\pdfmkdest#1{{% % We have to set dummies so commands such as @code, and characters % such as \, aren't expanded when present in a section title. \indexnofonts \turnoffactive \activebackslashdouble \def\pdfdestname{#1}% \backslashparens\pdfdestname \pdfdest name{\pdfdestname} xyz% }} % % used to mark target names; must be expandable. \def\pdfmkpgn#1{#1} % \let\linkcolor = \Blue % was Cyan, but that seems light? \def\endlink{\Black\pdfendlink} % % Adding outlines to PDF; macros for calculating structure of outlines % come from Petr Olsak \def\expnumber#1{\expandafter\ifx\csname#1\endcsname\relax 0% \else \csname#1\endcsname \fi} \def\advancenumber#1{\tempnum=\expnumber{#1}\relax \advance\tempnum by 1 \expandafter\xdef\csname#1\endcsname{\the\tempnum}} % % #1 is the section text, which is what will be displayed in the % outline by the pdf viewer. #2 is the pdf expression for the number % of subentries (or empty, for subsubsections). #3 is the node text, % which might be empty if this toc entry had no corresponding node. % #4 is the page number % \def\dopdfoutline#1#2#3#4{% % Generate a link to the node text if that exists; else, use the % page number. We could generate a destination for the section % text in the case where a section has no node, but it doesn't % seem worth the trouble, since most documents are normally structured. \def\pdfoutlinedest{#3}% \ifx\pdfoutlinedest\empty \def\pdfoutlinedest{#4}% \else % Doubled backslashes in the name. {\activebackslashdouble \xdef\pdfoutlinedest{#3}% \backslashparens\pdfoutlinedest}% \fi % % Also double the backslashes in the display string. {\activebackslashdouble \xdef\pdfoutlinetext{#1}% \backslashparens\pdfoutlinetext}% % \pdfoutline goto name{\pdfmkpgn{\pdfoutlinedest}}#2{\pdfoutlinetext}% } % \def\pdfmakeoutlines{% \begingroup % Thanh's hack / proper braces in bookmarks \edef\mylbrace{\iftrue \string{\else}\fi}\let\{=\mylbrace \edef\myrbrace{\iffalse{\else\string}\fi}\let\}=\myrbrace % % Read toc silently, to get counts of subentries for \pdfoutline. \def\numchapentry##1##2##3##4{% \def\thischapnum{##2}% \def\thissecnum{0}% \def\thissubsecnum{0}% }% \def\numsecentry##1##2##3##4{% \advancenumber{chap\thischapnum}% \def\thissecnum{##2}% \def\thissubsecnum{0}% }% \def\numsubsecentry##1##2##3##4{% \advancenumber{sec\thissecnum}% \def\thissubsecnum{##2}% }% \def\numsubsubsecentry##1##2##3##4{% \advancenumber{subsec\thissubsecnum}% }% \def\thischapnum{0}% \def\thissecnum{0}% \def\thissubsecnum{0}% % % use \def rather than \let here because we redefine \chapentry et % al. a second time, below. \def\appentry{\numchapentry}% \def\appsecentry{\numsecentry}% \def\appsubsecentry{\numsubsecentry}% \def\appsubsubsecentry{\numsubsubsecentry}% \def\unnchapentry{\numchapentry}% \def\unnsecentry{\numsecentry}% \def\unnsubsecentry{\numsubsecentry}% \def\unnsubsubsecentry{\numsubsubsecentry}% \readdatafile{toc}% % % Read toc second time, this time actually producing the outlines. % The `-' means take the \expnumber as the absolute number of % subentries, which we calculated on our first read of the .toc above. % % We use the node names as the destinations. \def\numchapentry##1##2##3##4{% \dopdfoutline{##1}{count-\expnumber{chap##2}}{##3}{##4}}% \def\numsecentry##1##2##3##4{% \dopdfoutline{##1}{count-\expnumber{sec##2}}{##3}{##4}}% \def\numsubsecentry##1##2##3##4{% \dopdfoutline{##1}{count-\expnumber{subsec##2}}{##3}{##4}}% \def\numsubsubsecentry##1##2##3##4{% count is always zero \dopdfoutline{##1}{}{##3}{##4}}% % % PDF outlines are displayed using system fonts, instead of % document fonts. Therefore we cannot use special characters, % since the encoding is unknown. For example, the eogonek from % Latin 2 (0xea) gets translated to a | character. Info from % Staszek Wawrykiewicz, 19 Jan 2004 04:09:24 +0100. % % xx to do this right, we have to translate 8-bit characters to % their "best" equivalent, based on the @documentencoding. Right % now, I guess we'll just let the pdf reader have its way. \indexnofonts \setupdatafile \catcode`\\=\active \otherbackslash \input \jobname.toc \endgroup } % \def\skipspaces#1{\def\PP{#1}\def\D{|}% \ifx\PP\D\let\nextsp\relax \else\let\nextsp\skipspaces \ifx\p\space\else\addtokens{\filename}{\PP}% \advance\filenamelength by 1 \fi \fi \nextsp} \def\getfilename#1{\filenamelength=0\expandafter\skipspaces#1|\relax} \ifnum\pdftexversion < 14 \let \startlink \pdfannotlink \else \let \startlink \pdfstartlink \fi % make a live url in pdf output. \def\pdfurl#1{% \begingroup % it seems we really need yet another set of dummies; have not % tried to figure out what each command should do in the context % of @url. for now, just make @/ a no-op, that's the only one % people have actually reported a problem with. % \normalturnoffactive \def\@{@}% \let\/=\empty \makevalueexpandable \leavevmode\Red \startlink attr{/Border [0 0 0]}% user{/Subtype /Link /A << /S /URI /URI (#1) >>}% \endgroup} \def\pdfgettoks#1.{\setbox\boxA=\hbox{\toksA={#1.}\toksB={}\maketoks}} \def\addtokens#1#2{\edef\addtoks{\noexpand#1={\the#1#2}}\addtoks} \def\adn#1{\addtokens{\toksC}{#1}\global\countA=1\let\next=\maketoks} \def\poptoks#1#2|ENDTOKS|{\let\first=#1\toksD={#1}\toksA={#2}} \def\maketoks{% \expandafter\poptoks\the\toksA|ENDTOKS|\relax \ifx\first0\adn0 \else\ifx\first1\adn1 \else\ifx\first2\adn2 \else\ifx\first3\adn3 \else\ifx\first4\adn4 \else\ifx\first5\adn5 \else\ifx\first6\adn6 \else\ifx\first7\adn7 \else\ifx\first8\adn8 \else\ifx\first9\adn9 \else \ifnum0=\countA\else\makelink\fi \ifx\first.\let\next=\done\else \let\next=\maketoks \addtokens{\toksB}{\the\toksD} \ifx\first,\addtokens{\toksB}{\space}\fi \fi \fi\fi\fi\fi\fi\fi\fi\fi\fi\fi \next} \def\makelink{\addtokens{\toksB}% {\noexpand\pdflink{\the\toksC}}\toksC={}\global\countA=0} \def\pdflink#1{% \startlink attr{/Border [0 0 0]} goto name{\pdfmkpgn{#1}} \linkcolor #1\endlink} \def\done{\edef\st{\global\noexpand\toksA={\the\toksB}}\st} \else \let\pdfmkdest = \gobble \let\pdfurl = \gobble \let\endlink = \relax \let\linkcolor = \relax \let\pdfmakeoutlines = \relax \fi % \ifx\pdfoutput \message{fonts,} % Change the current font style to #1, remembering it in \curfontstyle. % For now, we do not accumulate font styles: @b{@i{foo}} prints foo in % italics, not bold italics. % \def\setfontstyle#1{% \def\curfontstyle{#1}% not as a control sequence, because we are \edef'd. \csname ten#1\endcsname % change the current font } % Select #1 fonts with the current style. % \def\selectfonts#1{\csname #1fonts\endcsname \csname\curfontstyle\endcsname} \def\rm{\fam=0 \setfontstyle{rm}} \def\it{\fam=\itfam \setfontstyle{it}} \def\sl{\fam=\slfam \setfontstyle{sl}} \def\bf{\fam=\bffam \setfontstyle{bf}}\def\bfstylename{bf} \def\tt{\fam=\ttfam \setfontstyle{tt}} % Texinfo sort of supports the sans serif font style, which plain TeX does not. % So we set up a \sf. \newfam\sffam \def\sf{\fam=\sffam \setfontstyle{sf}} \let\li = \sf % Sometimes we call it \li, not \sf. % We don't need math for this font style. \def\ttsl{\setfontstyle{ttsl}} % Default leading. \newdimen\textleading \textleading = 13.2pt % Set the baselineskip to #1, and the lineskip and strut size % correspondingly. There is no deep meaning behind these magic numbers % used as factors; they just match (closely enough) what Knuth defined. % \def\lineskipfactor{.08333} \def\strutheightpercent{.70833} \def\strutdepthpercent {.29167} % \def\setleading#1{% \normalbaselineskip = #1\relax \normallineskip = \lineskipfactor\normalbaselineskip \normalbaselines \setbox\strutbox =\hbox{% \vrule width0pt height\strutheightpercent\baselineskip depth \strutdepthpercent \baselineskip }% } % Set the font macro #1 to the font named #2, adding on the % specified font prefix (normally `cm'). % #3 is the font's design size, #4 is a scale factor \def\setfont#1#2#3#4{\dimen255=#3pt\divide\dimen255by1000 \multiply\dimen255by#4% \global\font#1=\fontprefix#27t at \dimen255} % Use cm as the default font prefix. % To specify the font prefix, you must define \fontprefix % before you read in texinfo.tex. \ifx\fontprefix\undefined \def\fontprefix{p} \fi % Support font families that don't use the same naming scheme as CM. \def\rmshape{tmr} \def\rmbshape{tmb} %where the normal face is bold \def\bfshape{tmb} \def\bxshape{tmb} \def\ttshape{crr} \def\ttbshape{crb} \def\ttslshape{crro} \def\itshape{tmri} \def\itbshape{tmbi} \def\slshape{tmro} \def\slbshape{tmbo} \def\sfshape{hvr} \def\sfbshape{hvb} \def\scshape{tmrc} \def\scbshape{tmbc} % Definitions for a main text size of 11pt. This is the default in % Texinfo. % \def\definetextfontsizexi{ % Text fonts (11.2pt, magstep1). \def\textnominalsize{11pt} \edef\mainmagstep{\magstephalf} \setfont\textrm\rmshape{10}{\mainmagstep} \setfont\texttt\ttshape{9}{\mainmagstep} \setfont\textbf\bfshape{10}{\mainmagstep} \setfont\textit\itshape{10}{\mainmagstep} \setfont\textsl\slshape{10}{\mainmagstep} \setfont\textsf\sfshape{10}{\mainmagstep} \setfont\textsc\scshape{10}{\mainmagstep} \setfont\textttsl\ttslshape{9}{\mainmagstep} \font\texti=cmmi10 scaled \mainmagstep \font\textsy=cmsy10 scaled \mainmagstep % A few fonts for @defun names and args. \setfont\defbf\bfshape{10}{\magstep1} \setfont\deftt\ttshape{9}{\magstep1} \setfont\defttsl\ttslshape{9}{\magstep1} \def\df{\let\tentt=\deftt \let\tenbf = \defbf \let\tenttsl=\defttsl \bf} % Fonts for indices, footnotes, small examples (9pt). \def\smallnominalsize{9pt} \setfont\smallrm\rmshape{9}{1000} \setfont\smalltt\ttshape{8.1}{1000} \setfont\smallbf\bfshape{10}{900} \setfont\smallit\itshape{9}{1000} \setfont\smallsl\slshape{9}{1000} \setfont\smallsf\sfshape{9}{1000} \setfont\smallsc\scshape{10}{900} \setfont\smallttsl\ttslshape{8.1}{1000} \font\smalli=cmmi9 \font\smallsy=cmsy9 % Fonts for small examples (8pt). \def\smallernominalsize{8pt} \setfont\smallerrm\rmshape{8}{1000} \setfont\smallertt\ttshape{7.2}{1000} \setfont\smallerbf\bfshape{10}{800} \setfont\smallerit\itshape{8}{1000} \setfont\smallersl\slshape{8}{1000} \setfont\smallersf\sfshape{8}{1000} \setfont\smallersc\scshape{10}{800} \setfont\smallerttsl\ttslshape{7.2}{1000} \font\smalleri=cmmi8 \font\smallersy=cmsy8 % Fonts for title page (20.4pt): \def\titlenominalsize{20pt} \setfont\titlerm\rmbshape{12}{\magstep3} \setfont\titleit\itbshape{10}{\magstep4} \setfont\titlesl\slbshape{10}{\magstep4} \setfont\titlett\ttbshape{9}{\magstep4} \setfont\titlettsl\ttslshape{9}{\magstep4} \setfont\titlesf\sfbshape{17}{\magstep1} \let\titlebf=\titlerm \setfont\titlesc\scbshape{10}{\magstep4} \font\titlei=cmmi12 scaled \magstep3 \font\titlesy=cmsy10 scaled \magstep4 \def\authorrm{\secrm} \def\authortt{\sectt} % Chapter (and unnumbered) fonts (17.28pt). \def\chapnominalsize{17pt} \setfont\chaprm\rmbshape{12}{\magstep2} \setfont\chapit\itbshape{10}{\magstep3} \setfont\chapsl\slbshape{10}{\magstep3} \setfont\chaptt\ttbshape{9}{\magstep3} \setfont\chapttsl\ttslshape{9}{\magstep3} \setfont\chapsf\sfbshape{17}{1000} \let\chapbf=\chaprm \setfont\chapsc\scbshape{10}{\magstep3} \font\chapi=cmmi12 scaled \magstep2 \font\chapsy=cmsy10 scaled \magstep3 % Section fonts (14.4pt). \def\secnominalsize{14pt} \setfont\secrm\rmbshape{12}{\magstep1} \setfont\secit\itbshape{10}{\magstep2} \setfont\secsl\slbshape{10}{\magstep2} \setfont\sectt\ttbshape{9}{\magstep2} \setfont\secttsl\ttslshape{9}{\magstep2} \setfont\secsf\sfbshape{12}{\magstep1} \let\secbf\secrm \setfont\secsc\scbshape{10}{\magstep2} \font\seci=cmmi12 scaled \magstep1 \font\secsy=cmsy10 scaled \magstep2 % Subsection fonts (13.15pt). \def\ssecnominalsize{13pt} \setfont\ssecrm\rmbshape{12}{\magstephalf} \setfont\ssecit\itbshape{10}{1315} \setfont\ssecsl\slbshape{10}{1315} \setfont\ssectt\ttbshape{9}{1315} \setfont\ssecttsl\ttslshape{9}{1315} \setfont\ssecsf\sfbshape{12}{\magstephalf} \let\ssecbf\ssecrm \setfont\ssecsc\scbshape{10}{1315} \font\sseci=cmmi12 scaled \magstephalf \font\ssecsy=cmsy10 scaled 1315 % Reduced fonts for @acro in text (10pt). \def\reducednominalsize{10pt} \setfont\reducedrm\rmshape{10}{1000} \setfont\reducedtt\ttshape{9}{1000} \setfont\reducedbf\bfshape{10}{1000} \setfont\reducedit\itshape{10}{1000} \setfont\reducedsl\slshape{10}{1000} \setfont\reducedsf\sfshape{10}{1000} \setfont\reducedsc\scshape{10}{1000} \setfont\reducedttsl\ttslshape{9}{1000} \font\reducedi=cmmi10 \font\reducedsy=cmsy10 % reset the current fonts \textfonts \rm } % end of 11pt text font size definitions % Definitions to make the main text be 10pt Computer Modern, with % section, chapter, etc., sizes following suit. This is for the GNU % Press printing of the Emacs 22 manual. Maybe other manuals in the % future. Used with @smallbook, which sets the leading to 12pt. % \def\definetextfontsizex{% % Text fonts (10pt). \def\textnominalsize{10pt} \edef\mainmagstep{1000} \setfont\textrm\rmshape{10}{\mainmagstep} \setfont\texttt\ttshape{9}{\mainmagstep} \setfont\textbf\bfshape{10}{\mainmagstep} \setfont\textit\itshape{10}{\mainmagstep} \setfont\textsl\slshape{10}{\mainmagstep} \setfont\textsf\sfshape{10}{\mainmagstep} \setfont\textsc\scshape{10}{\mainmagstep} \setfont\textttsl\ttslshape{9}{\mainmagstep} \font\texti=cmmi10 scaled \mainmagstep \font\textsy=cmsy10 scaled \mainmagstep % A few fonts for @defun names and args. \setfont\defbf\bfshape{10}{\magstephalf} \setfont\deftt\ttshape{9}{\magstephalf} \setfont\defttsl\ttslshape{9}{\magstephalf} \def\df{\let\tentt=\deftt \let\tenbf = \defbf \let\tenttsl=\defttsl \bf} % Fonts for indices, footnotes, small examples (9pt). \def\smallnominalsize{9pt} \setfont\smallrm\rmshape{9}{1000} \setfont\smalltt\ttshape{9}{900} \setfont\smallbf\bfshape{10}{900} \setfont\smallit\itshape{9}{1000} \setfont\smallsl\slshape{9}{1000} \setfont\smallsf\sfshape{9}{1000} \setfont\smallsc\scshape{10}{900} \setfont\smallttsl\ttslshape{9}{900} \font\smalli=cmmi9 \font\smallsy=cmsy9 % Fonts for small examples (8pt). \def\smallernominalsize{8pt} \setfont\smallerrm\rmshape{8}{1000} \setfont\smallertt\ttshape{9}{800} \setfont\smallerbf\bfshape{10}{800} \setfont\smallerit\itshape{8}{1000} \setfont\smallersl\slshape{8}{1000} \setfont\smallersf\sfshape{8}{1000} \setfont\smallersc\scshape{10}{800} \setfont\smallerttsl\ttslshape{9}{800} \font\smalleri=cmmi8 \font\smallersy=cmsy8 % Fonts for title page (20.4pt): \def\titlenominalsize{20pt} \setfont\titlerm\rmbshape{12}{\magstep3} \setfont\titleit\itbshape{10}{\magstep4} \setfont\titlesl\slbshape{10}{\magstep4} \setfont\titlett\ttbshape{9}{\magstep4} \setfont\titlettsl\ttslshape{9}{\magstep4} \setfont\titlesf\sfbshape{17}{\magstep1} \let\titlebf=\titlerm \setfont\titlesc\scbshape{10}{\magstep4} \font\titlei=cmmi12 scaled \magstep3 \font\titlesy=cmsy10 scaled \magstep4 \def\authorrm{\secrm} \def\authortt{\sectt} % Chapter fonts (14.4pt). \def\chapnominalsize{14pt} \setfont\chaprm\rmbshape{12}{\magstep1} \setfont\chapit\itbshape{10}{\magstep2} \setfont\chapsl\slbshape{10}{\magstep2} \setfont\chaptt\ttbshape{9}{\magstep2} \setfont\chapttsl\ttslshape{9}{\magstep2} \setfont\chapsf\sfbshape{12}{\magstep1} \let\chapbf\chaprm \setfont\chapsc\scbshape{10}{\magstep2} \font\chapi=cmmi12 scaled \magstep1 \font\chapsy=cmsy10 scaled \magstep2 % Section fonts (12pt). \def\secnominalsize{12pt} \setfont\secrm\rmbshape{12}{1000} \setfont\secit\itbshape{10}{\magstep1} \setfont\secsl\slbshape{10}{\magstep1} \setfont\sectt\ttbshape{9}{\magstep1} \setfont\secttsl\ttslshape{9}{\magstep1} \setfont\secsf\sfbshape{12}{1000} \let\secbf\secrm \setfont\secsc\scbshape{10}{\magstep1} \font\seci=cmmi12 \font\secsy=cmsy10 scaled \magstep1 % Subsection fonts (10pt). \def\ssecnominalsize{10pt} \setfont\ssecrm\rmbshape{10}{1000} \setfont\ssecit\itbshape{10}{1000} \setfont\ssecsl\slbshape{10}{1000} \setfont\ssectt\ttbshape{9}{1000} \setfont\ssecttsl\ttslshape{9}{1000} \setfont\ssecsf\sfbshape{10}{1000} \let\ssecbf\ssecrm \setfont\ssecsc\scbshape{10}{1000} \font\sseci=cmmi10 \font\ssecsy=cmsy10 % Reduced fonts for @acro in text (9pt). \def\reducednominalsize{9pt} \setfont\reducedrm\rmshape{9}{1000} \setfont\reducedtt\ttshape{9}{900} \setfont\reducedbf\bfshape{10}{900} \setfont\reducedit\itshape{9}{1000} \setfont\reducedsl\slshape{9}{1000} \setfont\reducedsf\sfshape{9}{1000} \setfont\reducedsc\scshape{10}{900} \setfont\reducedttsl\ttslshape{9}{900} \font\reducedi=cmmi9 \font\reducedsy=cmsy9 % reduce space between paragraphs \divide\parskip by 2 % reset the current fonts \textfonts \rm } % end of 10pt text font size definitions % We provide the user-level command % @fonttextsize 10 % (or 11) to redefine the text font size. pt is assumed. % \def\xword{10} \def\xiword{11} % \parseargdef\fonttextsize{% \def\textsizearg{#1}% \wlog{doing @fonttextsize \textsizearg}% % % Set \globaldefs so that documents can use this inside @tex, since % makeinfo 4.8 does not support it, but we need it nonetheless. % \begingroup \globaldefs=1 \ifx\textsizearg\xword \definetextfontsizex \else \ifx\textsizearg\xiword \definetextfontsizexi \else \errhelp=\EMsimple \errmessage{@fonttextsize only supports `10' or `11', not `\textsizearg'} \fi\fi \endgroup } % In order for the font changes to affect most math symbols and letters, % we have to define the \textfont of the standard families. Since % texinfo doesn't allow for producing subscripts and superscripts except % in the main text, we don't bother to reset \scriptfont and % \scriptscriptfont (which would also require loading a lot more fonts). % \def\resetmathfonts{% \textfont0=\tenrm \textfont1=\teni \textfont2=\tensy \textfont\itfam=\tenit \textfont\slfam=\tensl \textfont\bffam=\tenbf \textfont\ttfam=\tentt \textfont\sffam=\tensf } % The font-changing commands redefine the meanings of \tenSTYLE, instead % of just \STYLE. We do this because \STYLE needs to also set the % current \fam for math mode. Our \STYLE (e.g., \rm) commands hardwire % \tenSTYLE to set the current font. % % Each font-changing command also sets the names \lsize (one size lower) % and \lllsize (three sizes lower). These relative commands are used in % the LaTeX logo and acronyms. % % This all needs generalizing, badly. % \def\textfonts{% \let\tenrm=\textrm \let\tenit=\textit \let\tensl=\textsl \let\tenbf=\textbf \let\tentt=\texttt \let\smallcaps=\textsc \let\tensf=\textsf \let\teni=\texti \let\tensy=\textsy \let\tenttsl=\textttsl \def\curfontsize{text}% \def\lsize{reduced}\def\lllsize{smaller}% \resetmathfonts \setleading{\textleading}} \def\titlefonts{% \let\tenrm=\titlerm \let\tenit=\titleit \let\tensl=\titlesl \let\tenbf=\titlebf \let\tentt=\titlett \let\smallcaps=\titlesc \let\tensf=\titlesf \let\teni=\titlei \let\tensy=\titlesy \let\tenttsl=\titlettsl \def\curfontsize{title}% \def\lsize{chap}\def\lllsize{subsec}% \resetmathfonts \setleading{25pt}} \def\titlefont#1{{\titlefonts\rm #1}} \def\chapfonts{% \let\tenrm=\chaprm \let\tenit=\chapit \let\tensl=\chapsl \let\tenbf=\chapbf \let\tentt=\chaptt \let\smallcaps=\chapsc \let\tensf=\chapsf \let\teni=\chapi \let\tensy=\chapsy \let\tenttsl=\chapttsl \def\curfontsize{chap}% \def\lsize{sec}\def\lllsize{text}% \resetmathfonts \setleading{19pt}} \def\secfonts{% \let\tenrm=\secrm \let\tenit=\secit \let\tensl=\secsl \let\tenbf=\secbf \let\tentt=\sectt \let\smallcaps=\secsc \let\tensf=\secsf \let\teni=\seci \let\tensy=\secsy \let\tenttsl=\secttsl \def\curfontsize{sec}% \def\lsize{subsec}\def\lllsize{reduced}% \resetmathfonts \setleading{16pt}} \def\subsecfonts{% \let\tenrm=\ssecrm \let\tenit=\ssecit \let\tensl=\ssecsl \let\tenbf=\ssecbf \let\tentt=\ssectt \let\smallcaps=\ssecsc \let\tensf=\ssecsf \let\teni=\sseci \let\tensy=\ssecsy \let\tenttsl=\ssecttsl \def\curfontsize{ssec}% \def\lsize{text}\def\lllsize{small}% \resetmathfonts \setleading{15pt}} \let\subsubsecfonts = \subsecfonts \def\reducedfonts{% \let\tenrm=\reducedrm \let\tenit=\reducedit \let\tensl=\reducedsl \let\tenbf=\reducedbf \let\tentt=\reducedtt \let\reducedcaps=\reducedsc \let\tensf=\reducedsf \let\teni=\reducedi \let\tensy=\reducedsy \let\tenttsl=\reducedttsl \def\curfontsize{reduced}% \def\lsize{small}\def\lllsize{smaller}% \resetmathfonts \setleading{10.5pt}} \def\smallfonts{% \let\tenrm=\smallrm \let\tenit=\smallit \let\tensl=\smallsl \let\tenbf=\smallbf \let\tentt=\smalltt \let\smallcaps=\smallsc \let\tensf=\smallsf \let\teni=\smalli \let\tensy=\smallsy \let\tenttsl=\smallttsl \def\curfontsize{small}% \def\lsize{smaller}\def\lllsize{smaller}% \resetmathfonts \setleading{10.5pt}} \def\smallerfonts{% \let\tenrm=\smallerrm \let\tenit=\smallerit \let\tensl=\smallersl \let\tenbf=\smallerbf \let\tentt=\smallertt \let\smallcaps=\smallersc \let\tensf=\smallersf \let\teni=\smalleri \let\tensy=\smallersy \let\tenttsl=\smallerttsl \def\curfontsize{smaller}% \def\lsize{smaller}\def\lllsize{smaller}% \resetmathfonts \setleading{9.5pt}} % Set the fonts to use with the @small... environments. \let\smallexamplefonts = \smallfonts % About \smallexamplefonts. If we use \smallfonts (9pt), @smallexample % can fit this many characters: % 8.5x11=86 smallbook=72 a4=90 a5=69 % If we use \scriptfonts (8pt), then we can fit this many characters: % 8.5x11=90+ smallbook=80 a4=90+ a5=77 % For me, subjectively, the few extra characters that fit aren't worth % the additional smallness of 8pt. So I'm making the default 9pt. % % By the way, for comparison, here's what fits with @example (10pt): % 8.5x11=71 smallbook=60 a4=75 a5=58 % % I wish the USA used A4 paper. % --karl, 24jan03. % Set up the default fonts, so we can use them for creating boxes. % \definetextfontsizexi % Define these so they can be easily changed for other fonts. \def\angleleft{$\langle$} \def\angleright{$\rangle$} % Count depth in font-changes, for error checks \newcount\fontdepth \fontdepth=0 % Fonts for short table of contents. \setfont\shortcontrm\rmshape{12}{1000} \setfont\shortcontbf\bfshape{10}{\magstep1} % no cmb12 \setfont\shortcontsl\slshape{12}{1000} \setfont\shortconttt\ttshape{12}{900} %% Add scribe-like font environments, plus @l for inline lisp (usually sans %% serif) and @ii for TeX italic % \smartitalic{ARG} outputs arg in italics, followed by an italic correction % unless the following character is such as not to need one. \def\smartitalicx{\ifx\next,\else\ifx\next-\else\ifx\next.\else \ptexslash\fi\fi\fi} \def\smartslanted#1{{\ifusingtt\ttsl\sl #1}\futurelet\next\smartitalicx} \def\smartitalic#1{{\ifusingtt\ttsl\it #1}\futurelet\next\smartitalicx} % like \smartslanted except unconditionally uses \ttsl. % @var is set to this for defun arguments. \def\ttslanted#1{{\ttsl #1}\futurelet\next\smartitalicx} % like \smartslanted except unconditionally use \sl. We never want % ttsl for book titles, do we? \def\cite#1{{\sl #1}\futurelet\next\smartitalicx} \let\i=\smartitalic \let\slanted=\smartslanted \let\var=\smartslanted \let\dfn=\smartslanted \let\emph=\smartitalic % @b, explicit bold. \def\b#1{{\bf #1}} \let\strong=\b % @sansserif, explicit sans. \def\sansserif#1{{\sf #1}} % We can't just use \exhyphenpenalty, because that only has effect at % the end of a paragraph. Restore normal hyphenation at the end of the % group within which \nohyphenation is presumably called. % \def\nohyphenation{\hyphenchar\font = -1 \aftergroup\restorehyphenation} \def\restorehyphenation{\hyphenchar\font = `- } % Set sfcode to normal for the chars that usually have another value. % Can't use plain's \frenchspacing because it uses the `\x notation, and % sometimes \x has an active definition that messes things up. % \catcode`@=11 \def\plainfrenchspacing{% \sfcode\dotChar =\@m \sfcode\questChar=\@m \sfcode\exclamChar=\@m \sfcode\colonChar=\@m \sfcode\semiChar =\@m \sfcode\commaChar =\@m \def\endofsentencespacefactor{1000}% for @. and friends } \def\plainnonfrenchspacing{% \sfcode`\.3000\sfcode`\?3000\sfcode`\!3000 \sfcode`\:2000\sfcode`\;1500\sfcode`\,1250 \def\endofsentencespacefactor{3000}% for @. and friends } \catcode`@=\other \def\endofsentencespacefactor{3000}% default \def\t#1{% {\tt \rawbackslash \plainfrenchspacing #1}% \null } \def\samp#1{`\tclose{#1}'\null} \setfont\keyrm\rmshape{8}{1000} \font\keysy=cmsy9 \def\key#1{{\keyrm\textfont2=\keysy \leavevmode\hbox{% \raise0.4pt\hbox{\angleleft}\kern-.08em\vtop{% \vbox{\hrule\kern-0.4pt \hbox{\raise0.4pt\hbox{\vphantom{\angleleft}}#1}}% \kern-0.4pt\hrule}% \kern-.06em\raise0.4pt\hbox{\angleright}}}} \def\key #1{{\nohyphenation \uppercase{#1}}\null} % The old definition, with no lozenge: %\def\key #1{{\ttsl \nohyphenation \uppercase{#1}}\null} \def\ctrl #1{{\tt \rawbackslash \hat}#1} % @file, @option are the same as @samp. \let\file=\samp \let\option=\samp % @code is a modification of @t, % which makes spaces the same size as normal in the surrounding text. \def\tclose#1{% {% % Change normal interword space to be same as for the current font. \spaceskip = \fontdimen2\font % % Switch to typewriter. \tt % % But `\ ' produces the large typewriter interword space. \def\ {{\spaceskip = 0pt{} }}% % % Turn off hyphenation. \nohyphenation % \rawbackslash \plainfrenchspacing #1% }% \null } % We *must* turn on hyphenation at `-' and `_' in @code. % Otherwise, it is too hard to avoid overfull hboxes % in the Emacs manual, the Library manual, etc. % Unfortunately, TeX uses one parameter (\hyphenchar) to control % both hyphenation at - and hyphenation within words. % We must therefore turn them both off (\tclose does that) % and arrange explicitly to hyphenate at a dash. % -- rms. { \catcode`\-=\active \catcode`\_=\active \catcode`\'=\active \catcode`\`=\active % \global\def\code{\begingroup \catcode\rquoteChar=\active \catcode\lquoteChar=\active \let'\codequoteright \let`\codequoteleft % \catcode\dashChar=\active \catcode\underChar=\active \ifallowcodebreaks \let-\codedash \let_\codeunder \else \let-\realdash \let_\realunder \fi \codex } } \def\realdash{-} \def\codedash{-\discretionary{}{}{}} \def\codeunder{% % this is all so @math{@code{var_name}+1} can work. In math mode, _ % is "active" (mathcode"8000) and \normalunderscore (or \char95, etc.) % will therefore expand the active definition of _, which is us % (inside @code that is), therefore an endless loop. \ifusingtt{\ifmmode \mathchar"075F % class 0=ordinary, family 7=ttfam, pos 0x5F=_. \else\normalunderscore \fi \discretionary{}{}{}}% {\_}% } \def\codex #1{\tclose{#1}\endgroup} % An additional complication: the above will allow breaks after, e.g., % each of the four underscores in __typeof__. This is undesirable in % some manuals, especially if they don't have long identifiers in % general. @allowcodebreaks provides a way to control this. % \newif\ifallowcodebreaks \allowcodebreakstrue \def\keywordtrue{true} \def\keywordfalse{false} \parseargdef\allowcodebreaks{% \def\txiarg{#1}% \ifx\txiarg\keywordtrue \allowcodebreakstrue \else\ifx\txiarg\keywordfalse \allowcodebreaksfalse \else \errhelp = \EMsimple \errmessage{Unknown @allowcodebreaks option `\txiarg'}% \fi\fi } % @kbd is like @code, except that if the argument is just one @key command, % then @kbd has no effect. % @kbdinputstyle -- arg is `distinct' (@kbd uses slanted tty font always), % `example' (@kbd uses ttsl only inside of @example and friends), % or `code' (@kbd uses normal tty font always). \parseargdef\kbdinputstyle{% \def\txiarg{#1}% \ifx\txiarg\worddistinct \gdef\kbdexamplefont{\ttsl}\gdef\kbdfont{\ttsl}% \else\ifx\txiarg\wordexample \gdef\kbdexamplefont{\ttsl}\gdef\kbdfont{\tt}% \else\ifx\txiarg\wordcode \gdef\kbdexamplefont{\tt}\gdef\kbdfont{\tt}% \else \errhelp = \EMsimple \errmessage{Unknown @kbdinputstyle option `\txiarg'}% \fi\fi\fi } \def\worddistinct{distinct} \def\wordexample{example} \def\wordcode{code} % Default is `distinct.' \kbdinputstyle distinct \def\xkey{\key} \def\kbdfoo#1#2#3\par{\def\one{#1}\def\three{#3}\def\threex{??}% \ifx\one\xkey\ifx\threex\three \key{#2}% \else{\tclose{\kbdfont\look}}\fi \else{\tclose{\kbdfont\look}}\fi} % For @indicateurl, @env, @command quotes seem unnecessary, so use \code. \let\indicateurl=\code \let\env=\code \let\command=\code % @uref (abbreviation for `urlref') takes an optional (comma-separated) % second argument specifying the text to display and an optional third % arg as text to display instead of (rather than in addition to) the url % itself. First (mandatory) arg is the url. Perhaps eventually put in % a hypertex \special here. % \def\uref#1{\douref #1,,,\finish} \def\douref#1,#2,#3,#4\finish{\begingroup \unsepspaces \pdfurl{#1}% \setbox0 = \hbox{\ignorespaces #3}% \ifdim\wd0 > 0pt \unhbox0 % third arg given, show only that \else \setbox0 = \hbox{\ignorespaces #2}% \ifdim\wd0 > 0pt \ifpdf \unhbox0 % PDF: 2nd arg given, show only it \else \unhbox0\ (\code{#1})% DVI: 2nd arg given, show both it and url \fi \else \code{#1}% only url given, so show it \fi \fi \endlink \endgroup} % @url synonym for @uref, since that's how everyone uses it. % \let\url=\uref % rms does not like angle brackets --karl, 17may97. % So now @email is just like @uref, unless we are pdf. % %\def\email#1{\angleleft{\tt #1}\angleright} \ifpdf \def\email#1{\doemail#1,,\finish} \def\doemail#1,#2,#3\finish{\begingroup \unsepspaces \pdfurl{mailto:#1}% \setbox0 = \hbox{\ignorespaces #2}% \ifdim\wd0>0pt\unhbox0\else\code{#1}\fi \endlink \endgroup} \else \let\email=\uref \fi % Check if we are currently using a typewriter font. Since all the % Computer Modern typewriter fonts have zero interword stretch (and % shrink), and it is reasonable to expect all typewriter fonts to have % this property, we can check that font parameter. % \def\ifmonospace{\ifdim\fontdimen3\font=0pt } % Typeset a dimension, e.g., `in' or `pt'. The only reason for the % argument is to make the input look right: @dmn{pt} instead of @dmn{}pt. % \def\dmn#1{\thinspace #1} \def\kbd#1{\def\look{#1}\expandafter\kbdfoo\look??\par} % @l was never documented to mean ``switch to the Lisp font'', % and it is not used as such in any manual I can find. We need it for % Polish suppressed-l. --karl, 22sep96. %\def\l#1{{\li #1}\null} % Explicit font changes: @r, @sc, undocumented @ii. \def\r#1{{\rm #1}} % roman font \def\sc#1{{\smallcaps#1}} % smallcaps font \def\ii#1{{\it #1}} % italic font % @acronym for "FBI", "NATO", and the like. % We print this one point size smaller, since it's intended for % all-uppercase. % \def\acronym#1{\doacronym #1,,\finish} \def\doacronym#1,#2,#3\finish{% {\selectfonts\lsize #1}% \def\temp{#2}% \ifx\temp\empty \else \space ({\unsepspaces \ignorespaces \temp \unskip})% \fi } % @abbr for "Comput. J." and the like. % No font change, but don't do end-of-sentence spacing. % \def\abbr#1{\doabbr #1,,\finish} \def\doabbr#1,#2,#3\finish{% {\plainfrenchspacing #1}% \def\temp{#2}% \ifx\temp\empty \else \space ({\unsepspaces \ignorespaces \temp \unskip})% \fi } % @pounds{} is a sterling sign, which Knuth put in the CM italic font. % \def\pounds{{\it\$}} % @euro{} comes from a separate font, depending on the current style. % We use the free feym* fonts from the eurosym package by Henrik % Theiling, which support regular, slanted, bold and bold slanted (and % "outlined" (blackboard board, sort of) versions, which we don't need). % It is available from http://www.ctan.org/tex-archive/fonts/eurosym. % % Although only regular is the truly official Euro symbol, we ignore % that. The Euro is designed to be slightly taller than the regular % font height. % % feymr - regular % feymo - slanted % feybr - bold % feybo - bold slanted % % There is no good (free) typewriter version, to my knowledge. % A feymr10 euro is ~7.3pt wide, while a normal cmtt10 char is ~5.25pt wide. % Hmm. % % Also doesn't work in math. Do we need to do math with euro symbols? % Hope not. % % \def\euro{{\eurofont e}} \def\eurofont{% % We set the font at each command, rather than predefining it in % \textfonts and the other font-switching commands, so that % installations which never need the symbol don't have to have the % font installed. % % There is only one designed size (nominal 10pt), so we always scale % that to the current nominal size. % % By the way, simply using "at 1em" works for cmr10 and the like, but % does not work for cmbx10 and other extended/shrunken fonts. % \def\eurosize{\csname\curfontsize nominalsize\endcsname}% % \ifx\curfontstyle\bfstylename % bold: \font\thiseurofont = \ifusingit{feybo10}{feybr10} at \eurosize \else % regular: \font\thiseurofont = \ifusingit{feymo10}{feymr10} at \eurosize \fi \thiseurofont } % @registeredsymbol - R in a circle. The font for the R should really % be smaller yet, but lllsize is the best we can do for now. % Adapted from the plain.tex definition of \copyright. % \def\registeredsymbol{% $^{{\ooalign{\hfil\raise.07ex\hbox{\selectfonts\lllsize R}% \hfil\crcr\Orb}}% }$% } % @textdegree - the normal degrees sign. % \def\textdegree{$^\circ$} % Laurent Siebenmann reports \Orb undefined with: % Textures 1.7.7 (preloaded format=plain 93.10.14) (68K) 16 APR 2004 02:38 % so we'll define it if necessary. % \ifx\Orb\undefined \def\Orb{\mathhexbox20D} \fi \message{page headings,} \newskip\titlepagetopglue \titlepagetopglue = 1.5in \newskip\titlepagebottomglue \titlepagebottomglue = 2pc % First the title page. Must do @settitle before @titlepage. \newif\ifseenauthor \newif\iffinishedtitlepage % Do an implicit @contents or @shortcontents after @end titlepage if the % user says @setcontentsaftertitlepage or @setshortcontentsaftertitlepage. % \newif\ifsetcontentsaftertitlepage \let\setcontentsaftertitlepage = \setcontentsaftertitlepagetrue \newif\ifsetshortcontentsaftertitlepage \let\setshortcontentsaftertitlepage = \setshortcontentsaftertitlepagetrue \parseargdef\shorttitlepage{\begingroup\hbox{}\vskip 1.5in \chaprm \centerline{#1}% \endgroup\page\hbox{}\page} \envdef\titlepage{% % Open one extra group, as we want to close it in the middle of \Etitlepage. \begingroup \parindent=0pt \textfonts % Leave some space at the very top of the page. \vglue\titlepagetopglue % No rule at page bottom unless we print one at the top with @title. \finishedtitlepagetrue % % Most title ``pages'' are actually two pages long, with space % at the top of the second. We don't want the ragged left on the second. \let\oldpage = \page \def\page{% \iffinishedtitlepage\else \finishtitlepage \fi \let\page = \oldpage \page \null }% } \def\Etitlepage{% \iffinishedtitlepage\else \finishtitlepage \fi % It is important to do the page break before ending the group, % because the headline and footline are only empty inside the group. % If we use the new definition of \page, we always get a blank page % after the title page, which we certainly don't want. \oldpage \endgroup % % Need this before the \...aftertitlepage checks so that if they are % in effect the toc pages will come out with page numbers. \HEADINGSon % % If they want short, they certainly want long too. \ifsetshortcontentsaftertitlepage \shortcontents \contents \global\let\shortcontents = \relax \global\let\contents = \relax \fi % \ifsetcontentsaftertitlepage \contents \global\let\contents = \relax \global\let\shortcontents = \relax \fi } \def\finishtitlepage{% \vskip4pt \hrule height 2pt width \hsize \vskip\titlepagebottomglue \finishedtitlepagetrue } %%% Macros to be used within @titlepage: \let\subtitlerm=\tenrm \def\subtitlefont{\subtitlerm \normalbaselineskip = 13pt \normalbaselines} \def\authorfont{\authorrm \normalbaselineskip = 16pt \normalbaselines \let\tt=\authortt} \parseargdef\title{% \checkenv\titlepage \leftline{\titlefonts\rm #1} % print a rule at the page bottom also. \finishedtitlepagefalse \vskip4pt \hrule height 4pt width \hsize \vskip4pt } \parseargdef\subtitle{% \checkenv\titlepage {\subtitlefont \rightline{#1}}% } % @author should come last, but may come many times. % It can also be used inside @quotation. % \parseargdef\author{% \def\temp{\quotation}% \ifx\thisenv\temp \def\quotationauthor{#1}% printed in \Equotation. \else \checkenv\titlepage \ifseenauthor\else \vskip 0pt plus 1filll \seenauthortrue \fi {\authorfont \leftline{#1}}% \fi } %%% Set up page headings and footings. \let\thispage=\folio \newtoks\evenheadline % headline on even pages \newtoks\oddheadline % headline on odd pages \newtoks\evenfootline % footline on even pages \newtoks\oddfootline % footline on odd pages % Now make TeX use those variables \headline={{\textfonts\rm \ifodd\pageno \the\oddheadline \else \the\evenheadline \fi}} \footline={{\textfonts\rm \ifodd\pageno \the\oddfootline \else \the\evenfootline \fi}\HEADINGShook} \let\HEADINGShook=\relax % Commands to set those variables. % For example, this is what @headings on does % @evenheading @thistitle|@thispage|@thischapter % @oddheading @thischapter|@thispage|@thistitle % @evenfooting @thisfile|| % @oddfooting ||@thisfile \def\evenheading{\parsearg\evenheadingxxx} \def\evenheadingxxx #1{\evenheadingyyy #1\|\|\|\|\finish} \def\evenheadingyyy #1\|#2\|#3\|#4\finish{% \global\evenheadline={\rlap{\centerline{#2}}\line{#1\hfil#3}}} \def\oddheading{\parsearg\oddheadingxxx} \def\oddheadingxxx #1{\oddheadingyyy #1\|\|\|\|\finish} \def\oddheadingyyy #1\|#2\|#3\|#4\finish{% \global\oddheadline={\rlap{\centerline{#2}}\line{#1\hfil#3}}} \parseargdef\everyheading{\oddheadingxxx{#1}\evenheadingxxx{#1}}% \def\evenfooting{\parsearg\evenfootingxxx} \def\evenfootingxxx #1{\evenfootingyyy #1\|\|\|\|\finish} \def\evenfootingyyy #1\|#2\|#3\|#4\finish{% \global\evenfootline={\rlap{\centerline{#2}}\line{#1\hfil#3}}} \def\oddfooting{\parsearg\oddfootingxxx} \def\oddfootingxxx #1{\oddfootingyyy #1\|\|\|\|\finish} \def\oddfootingyyy #1\|#2\|#3\|#4\finish{% \global\oddfootline = {\rlap{\centerline{#2}}\line{#1\hfil#3}}% % % Leave some space for the footline. Hopefully ok to assume % @evenfooting will not be used by itself. \global\advance\pageheight by -12pt \global\advance\vsize by -12pt } \parseargdef\everyfooting{\oddfootingxxx{#1}\evenfootingxxx{#1}} % @headings double turns headings on for double-sided printing. % @headings single turns headings on for single-sided printing. % @headings off turns them off. % @headings on same as @headings double, retained for compatibility. % @headings after turns on double-sided headings after this page. % @headings doubleafter turns on double-sided headings after this page. % @headings singleafter turns on single-sided headings after this page. % By default, they are off at the start of a document, % and turned `on' after @end titlepage. \def\headings #1 {\csname HEADINGS#1\endcsname} \def\HEADINGSoff{% \global\evenheadline={\hfil} \global\evenfootline={\hfil} \global\oddheadline={\hfil} \global\oddfootline={\hfil}} \HEADINGSoff % When we turn headings on, set the page number to 1. % For double-sided printing, put current file name in lower left corner, % chapter name on inside top of right hand pages, document % title on inside top of left hand pages, and page numbers on outside top % edge of all pages. \def\HEADINGSdouble{% \global\pageno=1 \global\evenfootline={\hfil} \global\oddfootline={\hfil} \global\evenheadline={\line{\folio\hfil\thistitle}} \global\oddheadline={\line{\thischapter\hfil\folio}} \global\let\contentsalignmacro = \chapoddpage } \let\contentsalignmacro = \chappager % For single-sided printing, chapter title goes across top left of page, % page number on top right. \def\HEADINGSsingle{% \global\pageno=1 \global\evenfootline={\hfil} \global\oddfootline={\hfil} \global\evenheadline={\line{\thischapter\hfil\folio}} \global\oddheadline={\line{\thischapter\hfil\folio}} \global\let\contentsalignmacro = \chappager } \def\HEADINGSon{\HEADINGSdouble} \def\HEADINGSafter{\let\HEADINGShook=\HEADINGSdoublex} \let\HEADINGSdoubleafter=\HEADINGSafter \def\HEADINGSdoublex{% \global\evenfootline={\hfil} \global\oddfootline={\hfil} \global\evenheadline={\line{\folio\hfil\thistitle}} \global\oddheadline={\line{\thischapter\hfil\folio}} \global\let\contentsalignmacro = \chapoddpage } \def\HEADINGSsingleafter{\let\HEADINGShook=\HEADINGSsinglex} \def\HEADINGSsinglex{% \global\evenfootline={\hfil} \global\oddfootline={\hfil} \global\evenheadline={\line{\thischapter\hfil\folio}} \global\oddheadline={\line{\thischapter\hfil\folio}} \global\let\contentsalignmacro = \chappager } % Subroutines used in generating headings % This produces Day Month Year style of output. % Only define if not already defined, in case a txi-??.tex file has set % up a different format (e.g., txi-cs.tex does this). \ifx\today\undefined \def\today{% \number\day\space \ifcase\month \or\putwordMJan\or\putwordMFeb\or\putwordMMar\or\putwordMApr \or\putwordMMay\or\putwordMJun\or\putwordMJul\or\putwordMAug \or\putwordMSep\or\putwordMOct\or\putwordMNov\or\putwordMDec \fi \space\number\year} \fi % @settitle line... specifies the title of the document, for headings. % It generates no output of its own. \def\thistitle{\putwordNoTitle} \def\settitle{\parsearg{\gdef\thistitle}} \message{tables,} % Tables -- @table, @ftable, @vtable, @item(x). % default indentation of table text \newdimen\tableindent \tableindent=.8in % default indentation of @itemize and @enumerate text \newdimen\itemindent \itemindent=.3in % margin between end of table item and start of table text. \newdimen\itemmargin \itemmargin=.1in % used internally for \itemindent minus \itemmargin \newdimen\itemmax % Note @table, @ftable, and @vtable define @item, @itemx, etc., with % these defs. % They also define \itemindex % to index the item name in whatever manner is desired (perhaps none). \newif\ifitemxneedsnegativevskip \def\itemxpar{\par\ifitemxneedsnegativevskip\nobreak\vskip-\parskip\nobreak\fi} \def\internalBitem{\smallbreak \parsearg\itemzzz} \def\internalBitemx{\itemxpar \parsearg\itemzzz} \def\itemzzz #1{\begingroup % \advance\hsize by -\rightskip \advance\hsize by -\tableindent \setbox0=\hbox{\itemindicate{#1}}% \itemindex{#1}% \nobreak % This prevents a break before @itemx. % % If the item text does not fit in the space we have, put it on a line % by itself, and do not allow a page break either before or after that % line. We do not start a paragraph here because then if the next % command is, e.g., @kindex, the whatsit would get put into the % horizontal list on a line by itself, resulting in extra blank space. \ifdim \wd0>\itemmax % % Make this a paragraph so we get the \parskip glue and wrapping, % but leave it ragged-right. \begingroup \advance\leftskip by-\tableindent \advance\hsize by\tableindent \advance\rightskip by0pt plus1fil \leavevmode\unhbox0\par \endgroup % % We're going to be starting a paragraph, but we don't want the % \parskip glue -- logically it's part of the @item we just started. \nobreak \vskip-\parskip % % Stop a page break at the \parskip glue coming up. However, if % what follows is an environment such as @example, there will be no % \parskip glue; then the negative vskip we just inserted would % cause the example and the item to crash together. So we use this % bizarre value of 10001 as a signal to \aboveenvbreak to insert % \parskip glue after all. Section titles are handled this way also. % \penalty 10001 \endgroup \itemxneedsnegativevskipfalse \else % The item text fits into the space. Start a paragraph, so that the % following text (if any) will end up on the same line. \noindent % Do this with kerns and \unhbox so that if there is a footnote in % the item text, it can migrate to the main vertical list and % eventually be printed. \nobreak\kern-\tableindent \dimen0 = \itemmax \advance\dimen0 by \itemmargin \advance\dimen0 by -\wd0 \unhbox0 \nobreak\kern\dimen0 \endgroup \itemxneedsnegativevskiptrue \fi } \def\item{\errmessage{@item while not in a list environment}} \def\itemx{\errmessage{@itemx while not in a list environment}} % @table, @ftable, @vtable. \envdef\table{% \let\itemindex\gobble \tablecheck{table}% } \envdef\ftable{% \def\itemindex ##1{\doind {fn}{\code{##1}}}% \tablecheck{ftable}% } \envdef\vtable{% \def\itemindex ##1{\doind {vr}{\code{##1}}}% \tablecheck{vtable}% } \def\tablecheck#1{% \ifnum \the\catcode`\^^M=\active \endgroup \errmessage{This command won't work in this context; perhaps the problem is that we are \inenvironment\thisenv}% \def\next{\doignore{#1}}% \else \let\next\tablex \fi \next } \def\tablex#1{% \def\itemindicate{#1}% \parsearg\tabley } \def\tabley#1{% {% \makevalueexpandable \edef\temp{\noexpand\tablez #1\space\space\space}% \expandafter }\temp \endtablez } \def\tablez #1 #2 #3 #4\endtablez{% \aboveenvbreak \ifnum 0#1>0 \advance \leftskip by #1\mil \fi \ifnum 0#2>0 \tableindent=#2\mil \fi \ifnum 0#3>0 \advance \rightskip by #3\mil \fi \itemmax=\tableindent \advance \itemmax by -\itemmargin \advance \leftskip by \tableindent \exdentamount=\tableindent \parindent = 0pt \parskip = \smallskipamount \ifdim \parskip=0pt \parskip=2pt \fi \let\item = \internalBitem \let\itemx = \internalBitemx } \def\Etable{\endgraf\afterenvbreak} \let\Eftable\Etable \let\Evtable\Etable \let\Eitemize\Etable \let\Eenumerate\Etable % This is the counter used by @enumerate, which is really @itemize \newcount \itemno \envdef\itemize{\parsearg\doitemize} \def\doitemize#1{% \aboveenvbreak \itemmax=\itemindent \advance\itemmax by -\itemmargin \advance\leftskip by \itemindent \exdentamount=\itemindent \parindent=0pt \parskip=\smallskipamount \ifdim\parskip=0pt \parskip=2pt \fi \def\itemcontents{#1}% % @itemize with no arg is equivalent to @itemize @bullet. \ifx\itemcontents\empty\def\itemcontents{\bullet}\fi \let\item=\itemizeitem } % Definition of @item while inside @itemize and @enumerate. % \def\itemizeitem{% \advance\itemno by 1 % for enumerations {\let\par=\endgraf \smallbreak}% reasonable place to break {% % If the document has an @itemize directly after a section title, a % \nobreak will be last on the list, and \sectionheading will have % done a \vskip-\parskip. In that case, we don't want to zero % parskip, or the item text will crash with the heading. On the % other hand, when there is normal text preceding the item (as there % usually is), we do want to zero parskip, or there would be too much % space. In that case, we won't have a \nobreak before. At least % that's the theory. \ifnum\lastpenalty<10000 \parskip=0in \fi \noindent \hbox to 0pt{\hss \itemcontents \kern\itemmargin}% \vadjust{\penalty 1200}}% not good to break after first line of item. \flushcr } % \splitoff TOKENS\endmark defines \first to be the first token in % TOKENS, and \rest to be the remainder. % \def\splitoff#1#2\endmark{\def\first{#1}\def\rest{#2}}% % Allow an optional argument of an uppercase letter, lowercase letter, % or number, to specify the first label in the enumerated list. No % argument is the same as `1'. % \envparseargdef\enumerate{\enumeratey #1 \endenumeratey} \def\enumeratey #1 #2\endenumeratey{% % If we were given no argument, pretend we were given `1'. \def\thearg{#1}% \ifx\thearg\empty \def\thearg{1}\fi % % Detect if the argument is a single token. If so, it might be a % letter. Otherwise, the only valid thing it can be is a number. % (We will always have one token, because of the test we just made. % This is a good thing, since \splitoff doesn't work given nothing at % all -- the first parameter is undelimited.) \expandafter\splitoff\thearg\endmark \ifx\rest\empty % Only one token in the argument. It could still be anything. % A ``lowercase letter'' is one whose \lccode is nonzero. % An ``uppercase letter'' is one whose \lccode is both nonzero, and % not equal to itself. % Otherwise, we assume it's a number. % % We need the \relax at the end of the \ifnum lines to stop TeX from % continuing to look for a . % \ifnum\lccode\expandafter`\thearg=0\relax \numericenumerate % a number (we hope) \else % It's a letter. \ifnum\lccode\expandafter`\thearg=\expandafter`\thearg\relax \lowercaseenumerate % lowercase letter \else \uppercaseenumerate % uppercase letter \fi \fi \else % Multiple tokens in the argument. We hope it's a number. \numericenumerate \fi } % An @enumerate whose labels are integers. The starting integer is % given in \thearg. % \def\numericenumerate{% \itemno = \thearg \startenumeration{\the\itemno}% } % The starting (lowercase) letter is in \thearg. \def\lowercaseenumerate{% \itemno = \expandafter`\thearg \startenumeration{% % Be sure we're not beyond the end of the alphabet. \ifnum\itemno=0 \errmessage{No more lowercase letters in @enumerate; get a bigger alphabet}% \fi \char\lccode\itemno }% } % The starting (uppercase) letter is in \thearg. \def\uppercaseenumerate{% \itemno = \expandafter`\thearg \startenumeration{% % Be sure we're not beyond the end of the alphabet. \ifnum\itemno=0 \errmessage{No more uppercase letters in @enumerate; get a bigger alphabet} \fi \char\uccode\itemno }% } % Call \doitemize, adding a period to the first argument and supplying the % common last two arguments. Also subtract one from the initial value in % \itemno, since @item increments \itemno. % \def\startenumeration#1{% \advance\itemno by -1 \doitemize{#1.}\flushcr } % @alphaenumerate and @capsenumerate are abbreviations for giving an arg % to @enumerate. % \def\alphaenumerate{\enumerate{a}} \def\capsenumerate{\enumerate{A}} \def\Ealphaenumerate{\Eenumerate} \def\Ecapsenumerate{\Eenumerate} % @multitable macros % Amy Hendrickson, 8/18/94, 3/6/96 % % @multitable ... @end multitable will make as many columns as desired. % Contents of each column will wrap at width given in preamble. Width % can be specified either with sample text given in a template line, % or in percent of \hsize, the current width of text on page. % Table can continue over pages but will only break between lines. % To make preamble: % % Either define widths of columns in terms of percent of \hsize: % @multitable @columnfractions .25 .3 .45 % @item ... % % Numbers following @columnfractions are the percent of the total % current hsize to be used for each column. You may use as many % columns as desired. % Or use a template: % @multitable {Column 1 template} {Column 2 template} {Column 3 template} % @item ... % using the widest term desired in each column. % Each new table line starts with @item, each subsequent new column % starts with @tab. Empty columns may be produced by supplying @tab's % with nothing between them for as many times as empty columns are needed, % ie, @tab@tab@tab will produce two empty columns. % @item, @tab do not need to be on their own lines, but it will not hurt % if they are. % Sample multitable: % @multitable {Column 1 template} {Column 2 template} {Column 3 template} % @item first col stuff @tab second col stuff @tab third col % @item % first col stuff % @tab % second col stuff % @tab % third col % @item first col stuff @tab second col stuff % @tab Many paragraphs of text may be used in any column. % % They will wrap at the width determined by the template. % @item@tab@tab This will be in third column. % @end multitable % Default dimensions may be reset by user. % @multitableparskip is vertical space between paragraphs in table. % @multitableparindent is paragraph indent in table. % @multitablecolmargin is horizontal space to be left between columns. % @multitablelinespace is space to leave between table items, baseline % to baseline. % 0pt means it depends on current normal line spacing. % \newskip\multitableparskip \newskip\multitableparindent \newdimen\multitablecolspace \newskip\multitablelinespace \multitableparskip=0pt \multitableparindent=6pt \multitablecolspace=12pt \multitablelinespace=0pt % Macros used to set up halign preamble: % \let\endsetuptable\relax \def\xendsetuptable{\endsetuptable} \let\columnfractions\relax \def\xcolumnfractions{\columnfractions} \newif\ifsetpercent % #1 is the @columnfraction, usually a decimal number like .5, but might % be just 1. We just use it, whatever it is. % \def\pickupwholefraction#1 {% \global\advance\colcount by 1 \expandafter\xdef\csname col\the\colcount\endcsname{#1\hsize}% \setuptable } \newcount\colcount \def\setuptable#1{% \def\firstarg{#1}% \ifx\firstarg\xendsetuptable \let\go = \relax \else \ifx\firstarg\xcolumnfractions \global\setpercenttrue \else \ifsetpercent \let\go\pickupwholefraction \else \global\advance\colcount by 1 \setbox0=\hbox{#1\unskip\space}% Add a normal word space as a % separator; typically that is always in the input, anyway. \expandafter\xdef\csname col\the\colcount\endcsname{\the\wd0}% \fi \fi \ifx\go\pickupwholefraction % Put the argument back for the \pickupwholefraction call, so % we'll always have a period there to be parsed. \def\go{\pickupwholefraction#1}% \else \let\go = \setuptable \fi% \fi \go } % multitable-only commands. % % @headitem starts a heading row, which we typeset in bold. % Assignments have to be global since we are inside the implicit group % of an alignment entry. Note that \everycr resets \everytab. \def\headitem{\checkenv\multitable \crcr \global\everytab={\bf}\the\everytab}% % % A \tab used to include \hskip1sp. But then the space in a template % line is not enough. That is bad. So let's go back to just `&' until % we encounter the problem it was intended to solve again. % --karl, nathan@acm.org, 20apr99. \def\tab{\checkenv\multitable &\the\everytab}% % @multitable ... @end multitable definitions: % \newtoks\everytab % insert after every tab. % \envdef\multitable{% \vskip\parskip \startsavinginserts % % @item within a multitable starts a normal row. % We use \def instead of \let so that if one of the multitable entries % contains an @itemize, we don't choke on the \item (seen as \crcr aka % \endtemplate) expanding \doitemize. \def\item{\crcr}% % \tolerance=9500 \hbadness=9500 \setmultitablespacing \parskip=\multitableparskip \parindent=\multitableparindent \overfullrule=0pt \global\colcount=0 % \everycr = {% \noalign{% \global\everytab={}% \global\colcount=0 % Reset the column counter. % Check for saved footnotes, etc. \checkinserts % Keeps underfull box messages off when table breaks over pages. %\filbreak % Maybe so, but it also creates really weird page breaks when the % table breaks over pages. Wouldn't \vfil be better? Wait until the % problem manifests itself, so it can be fixed for real --karl. }% }% % \parsearg\domultitable } \def\domultitable#1{% % To parse everything between @multitable and @item: \setuptable#1 \endsetuptable % % This preamble sets up a generic column definition, which will % be used as many times as user calls for columns. % \vtop will set a single line and will also let text wrap and % continue for many paragraphs if desired. \halign\bgroup &% \global\advance\colcount by 1 \multistrut \vtop{% % Use the current \colcount to find the correct column width: \hsize=\expandafter\csname col\the\colcount\endcsname % % In order to keep entries from bumping into each other % we will add a \leftskip of \multitablecolspace to all columns after % the first one. % % If a template has been used, we will add \multitablecolspace % to the width of each template entry. % % If the user has set preamble in terms of percent of \hsize we will % use that dimension as the width of the column, and the \leftskip % will keep entries from bumping into each other. Table will start at % left margin and final column will justify at right margin. % % Make sure we don't inherit \rightskip from the outer environment. \rightskip=0pt \ifnum\colcount=1 % The first column will be indented with the surrounding text. \advance\hsize by\leftskip \else \ifsetpercent \else % If user has not set preamble in terms of percent of \hsize % we will advance \hsize by \multitablecolspace. \advance\hsize by \multitablecolspace \fi % In either case we will make \leftskip=\multitablecolspace: \leftskip=\multitablecolspace \fi % Ignoring space at the beginning and end avoids an occasional spurious % blank line, when TeX decides to break the line at the space before the % box from the multistrut, so the strut ends up on a line by itself. % For example: % @multitable @columnfractions .11 .89 % @item @code{#} % @tab Legal holiday which is valid in major parts of the whole country. % Is automatically provided with highlighting sequences respectively % marking characters. \noindent\ignorespaces##\unskip\multistrut }\cr } \def\Emultitable{% \crcr \egroup % end the \halign \global\setpercentfalse } \def\setmultitablespacing{% \def\multistrut{\strut}% just use the standard line spacing % % Compute \multitablelinespace (if not defined by user) for use in % \multitableparskip calculation. We used define \multistrut based on % this, but (ironically) that caused the spacing to be off. % See bug-texinfo report from Werner Lemberg, 31 Oct 2004 12:52:20 +0100. \ifdim\multitablelinespace=0pt \setbox0=\vbox{X}\global\multitablelinespace=\the\baselineskip \global\advance\multitablelinespace by-\ht0 \fi %% Test to see if parskip is larger than space between lines of %% table. If not, do nothing. %% If so, set to same dimension as multitablelinespace. \ifdim\multitableparskip>\multitablelinespace \global\multitableparskip=\multitablelinespace \global\advance\multitableparskip-7pt %% to keep parskip somewhat smaller %% than skip between lines in the table. \fi% \ifdim\multitableparskip=0pt \global\multitableparskip=\multitablelinespace \global\advance\multitableparskip-7pt %% to keep parskip somewhat smaller %% than skip between lines in the table. \fi} \message{conditionals,} % @iftex, @ifnotdocbook, @ifnothtml, @ifnotinfo, @ifnotplaintext, % @ifnotxml always succeed. They currently do nothing; we don't % attempt to check whether the conditionals are properly nested. But we % have to remember that they are conditionals, so that @end doesn't % attempt to close an environment group. % \def\makecond#1{% \expandafter\let\csname #1\endcsname = \relax \expandafter\let\csname iscond.#1\endcsname = 1 } \makecond{iftex} \makecond{ifnotdocbook} \makecond{ifnothtml} \makecond{ifnotinfo} \makecond{ifnotplaintext} \makecond{ifnotxml} % Ignore @ignore, @ifhtml, @ifinfo, and the like. % \def\direntry{\doignore{direntry}} \def\documentdescription{\doignore{documentdescription}} \def\docbook{\doignore{docbook}} \def\html{\doignore{html}} \def\ifdocbook{\doignore{ifdocbook}} \def\ifhtml{\doignore{ifhtml}} \def\ifinfo{\doignore{ifinfo}} \def\ifnottex{\doignore{ifnottex}} \def\ifplaintext{\doignore{ifplaintext}} \def\ifxml{\doignore{ifxml}} \def\ignore{\doignore{ignore}} \def\menu{\doignore{menu}} \def\xml{\doignore{xml}} % Ignore text until a line `@end #1', keeping track of nested conditionals. % % A count to remember the depth of nesting. \newcount\doignorecount \def\doignore#1{\begingroup % Scan in ``verbatim'' mode: \obeylines \catcode`\@ = \other \catcode`\{ = \other \catcode`\} = \other % % Make sure that spaces turn into tokens that match what \doignoretext wants. \spaceisspace % % Count number of #1's that we've seen. \doignorecount = 0 % % Swallow text until we reach the matching `@end #1'. \dodoignore{#1}% } { \catcode`_=11 % We want to use \_STOP_ which cannot appear in texinfo source. \obeylines % % \gdef\dodoignore#1{% % #1 contains the command name as a string, e.g., `ifinfo'. % % Define a command to find the next `@end #1'. \long\def\doignoretext##1^^M@end #1{% \doignoretextyyy##1^^M@#1\_STOP_}% % % And this command to find another #1 command, at the beginning of a % line. (Otherwise, we would consider a line `@c @ifset', for % example, to count as an @ifset for nesting.) \long\def\doignoretextyyy##1^^M@#1##2\_STOP_{\doignoreyyy{##2}\_STOP_}% % % And now expand that command. \doignoretext ^^M% }% } \def\doignoreyyy#1{% \def\temp{#1}% \ifx\temp\empty % Nothing found. \let\next\doignoretextzzz \else % Found a nested condition, ... \advance\doignorecount by 1 \let\next\doignoretextyyy % ..., look for another. % If we're here, #1 ends with ^^M\ifinfo (for example). \fi \next #1% the token \_STOP_ is present just after this macro. } % We have to swallow the remaining "\_STOP_". % \def\doignoretextzzz#1{% \ifnum\doignorecount = 0 % We have just found the outermost @end. \let\next\enddoignore \else % Still inside a nested condition. \advance\doignorecount by -1 \let\next\doignoretext % Look for the next @end. \fi \next } % Finish off ignored text. { \obeylines% % Ignore anything after the last `@end #1'; this matters in verbatim % environments, where otherwise the newline after an ignored conditional % would result in a blank line in the output. \gdef\enddoignore#1^^M{\endgroup\ignorespaces}% } % @set VAR sets the variable VAR to an empty value. % @set VAR REST-OF-LINE sets VAR to the value REST-OF-LINE. % % Since we want to separate VAR from REST-OF-LINE (which might be % empty), we can't just use \parsearg; we have to insert a space of our % own to delimit the rest of the line, and then take it out again if we % didn't need it. % We rely on the fact that \parsearg sets \catcode`\ =10. % \parseargdef\set{\setyyy#1 \endsetyyy} \def\setyyy#1 #2\endsetyyy{% {% \makevalueexpandable \def\temp{#2}% \edef\next{\gdef\makecsname{SET#1}}% \ifx\temp\empty \next{}% \else \setzzz#2\endsetzzz \fi }% } % Remove the trailing space \setxxx inserted. \def\setzzz#1 \endsetzzz{\next{#1}} % @clear VAR clears (i.e., unsets) the variable VAR. % \parseargdef\clear{% {% \makevalueexpandable \global\expandafter\let\csname SET#1\endcsname=\relax }% } % @value{foo} gets the text saved in variable foo. \def\value{\begingroup\makevalueexpandable\valuexxx} \def\valuexxx#1{\expandablevalue{#1}\endgroup} { \catcode`\- = \active \catcode`\_ = \active % \gdef\makevalueexpandable{% \let\value = \expandablevalue % We don't want these characters active, ... \catcode`\-=\other \catcode`\_=\other % ..., but we might end up with active ones in the argument if % we're called from @code, as @code{@value{foo-bar_}}, though. % So \let them to their normal equivalents. \let-\realdash \let_\normalunderscore } } % We have this subroutine so that we can handle at least some @value's % properly in indexes (we call \makevalueexpandable in \indexdummies). % The command has to be fully expandable (if the variable is set), since % the result winds up in the index file. This means that if the % variable's value contains other Texinfo commands, it's almost certain % it will fail (although perhaps we could fix that with sufficient work % to do a one-level expansion on the result, instead of complete). % \def\expandablevalue#1{% \expandafter\ifx\csname SET#1\endcsname\relax {[No value for ``#1'']}% \message{Variable `#1', used in @value, is not set.}% \else \csname SET#1\endcsname \fi } % @ifset VAR ... @end ifset reads the `...' iff VAR has been defined % with @set. % % To get special treatment of `@end ifset,' call \makeond and the redefine. % \makecond{ifset} \def\ifset{\parsearg{\doifset{\let\next=\ifsetfail}}} \def\doifset#1#2{% {% \makevalueexpandable \let\next=\empty \expandafter\ifx\csname SET#2\endcsname\relax #1% If not set, redefine \next. \fi \expandafter }\next } \def\ifsetfail{\doignore{ifset}} % @ifclear VAR ... @end ifclear reads the `...' iff VAR has never been % defined with @set, or has been undefined with @clear. % % The `\else' inside the `\doifset' parameter is a trick to reuse the % above code: if the variable is not set, do nothing, if it is set, % then redefine \next to \ifclearfail. % \makecond{ifclear} \def\ifclear{\parsearg{\doifset{\else \let\next=\ifclearfail}}} \def\ifclearfail{\doignore{ifclear}} % @dircategory CATEGORY -- specify a category of the dir file % which this file should belong to. Ignore this in TeX. \let\dircategory=\comment % @defininfoenclose. \let\definfoenclose=\comment \message{indexing,} % Index generation facilities % Define \newwrite to be identical to plain tex's \newwrite % except not \outer, so it can be used within macros and \if's. \edef\newwrite{\makecsname{ptexnewwrite}} % \newindex {foo} defines an index named foo. % It automatically defines \fooindex such that % \fooindex ...rest of line... puts an entry in the index foo. % It also defines \fooindfile to be the number of the output channel for % the file that accumulates this index. The file's extension is foo. % The name of an index should be no more than 2 characters long % for the sake of vms. % \def\newindex#1{% \iflinks \expandafter\newwrite \csname#1indfile\endcsname \openout \csname#1indfile\endcsname \jobname.#1 % Open the file \fi \expandafter\xdef\csname#1index\endcsname{% % Define @#1index \noexpand\doindex{#1}} } % @defindex foo == \newindex{foo} % \def\defindex{\parsearg\newindex} % Define @defcodeindex, like @defindex except put all entries in @code. % \def\defcodeindex{\parsearg\newcodeindex} % \def\newcodeindex#1{% \iflinks \expandafter\newwrite \csname#1indfile\endcsname \openout \csname#1indfile\endcsname \jobname.#1 \fi \expandafter\xdef\csname#1index\endcsname{% \noexpand\docodeindex{#1}}% } % @synindex foo bar makes index foo feed into index bar. % Do this instead of @defindex foo if you don't want it as a separate index. % % @syncodeindex foo bar similar, but put all entries made for index foo % inside @code. % \def\synindex#1 #2 {\dosynindex\doindex{#1}{#2}} \def\syncodeindex#1 #2 {\dosynindex\docodeindex{#1}{#2}} % #1 is \doindex or \docodeindex, #2 the index getting redefined (foo), % #3 the target index (bar). \def\dosynindex#1#2#3{% % Only do \closeout if we haven't already done it, else we'll end up % closing the target index. \expandafter \ifx\csname donesynindex#2\endcsname \undefined % The \closeout helps reduce unnecessary open files; the limit on the % Acorn RISC OS is a mere 16 files. \expandafter\closeout\csname#2indfile\endcsname \expandafter\let\csname\donesynindex#2\endcsname = 1 \fi % redefine \fooindfile: \expandafter\let\expandafter\temp\expandafter=\csname#3indfile\endcsname \expandafter\let\csname#2indfile\endcsname=\temp % redefine \fooindex: \expandafter\xdef\csname#2index\endcsname{\noexpand#1{#3}}% } % Define \doindex, the driver for all \fooindex macros. % Argument #1 is generated by the calling \fooindex macro, % and it is "foo", the name of the index. % \doindex just uses \parsearg; it calls \doind for the actual work. % This is because \doind is more useful to call from other macros. % There is also \dosubind {index}{topic}{subtopic} % which makes an entry in a two-level index such as the operation index. \def\doindex#1{\edef\indexname{#1}\parsearg\singleindexer} \def\singleindexer #1{\doind{\indexname}{#1}} % like the previous two, but they put @code around the argument. \def\docodeindex#1{\edef\indexname{#1}\parsearg\singlecodeindexer} \def\singlecodeindexer #1{\doind{\indexname}{\code{#1}}} % Take care of Texinfo commands that can appear in an index entry. % Since there are some commands we want to expand, and others we don't, % we have to laboriously prevent expansion for those that we don't. % \def\indexdummies{% \escapechar = `\\ % use backslash in output files. \def\@{@}% change to @@ when we switch to @ as escape char in index files. \def\ {\realbackslash\space }% % % Need these in case \tex is in effect and \{ is a \delimiter again. % But can't use \lbracecmd and \rbracecmd because texindex assumes % braces and backslashes are used only as delimiters. \let\{ = \mylbrace \let\} = \myrbrace % % I don't entirely understand this, but when an index entry is % generated from a macro call, the \endinput which \scanmacro inserts % causes processing to be prematurely terminated. This is, % apparently, because \indexsorttmp is fully expanded, and \endinput % is an expandable command. The redefinition below makes \endinput % disappear altogether for that purpose -- although logging shows that % processing continues to some further point. On the other hand, it % seems \endinput does not hurt in the printed index arg, since that % is still getting written without apparent harm. % % Sample source (mac-idx3.tex, reported by Graham Percival to % help-texinfo, 22may06): % @macro funindex {WORD} % @findex xyz % @end macro % ... % @funindex commtest % % The above is not enough to reproduce the bug, but it gives the flavor. % % Sample whatsit resulting: % .@write3{\entry{xyz}{@folio }{@code {xyz@endinput }}} % % So: \let\endinput = \empty % % Do the redefinitions. \commondummies } % For the aux and toc files, @ is the escape character. So we want to % redefine everything using @ as the escape character (instead of % \realbackslash, still used for index files). When everything uses @, % this will be simpler. % \def\atdummies{% \def\@{@@}% \def\ {@ }% \let\{ = \lbraceatcmd \let\} = \rbraceatcmd % % Do the redefinitions. \commondummies \otherbackslash } % Called from \indexdummies and \atdummies. % \def\commondummies{% % % \definedummyword defines \#1 as \string\#1\space, thus effectively % preventing its expansion. This is used only for control% words, % not control letters, because the \space would be incorrect for % control characters, but is needed to separate the control word % from whatever follows. % % For control letters, we have \definedummyletter, which omits the % space. % % These can be used both for control words that take an argument and % those that do not. If it is followed by {arg} in the input, then % that will dutifully get written to the index (or wherever). % \def\definedummyword ##1{\def##1{\string##1\space}}% \def\definedummyletter##1{\def##1{\string##1}}% \let\definedummyaccent\definedummyletter % \commondummiesnofonts % \definedummyletter\_% % % Non-English letters. \definedummyword\AA \definedummyword\AE \definedummyword\L \definedummyword\OE \definedummyword\O \definedummyword\aa \definedummyword\ae \definedummyword\l \definedummyword\oe \definedummyword\o \definedummyword\ss \definedummyword\exclamdown \definedummyword\questiondown \definedummyword\ordf \definedummyword\ordm % % Although these internal commands shouldn't show up, sometimes they do. \definedummyword\bf \definedummyword\gtr \definedummyword\hat \definedummyword\less \definedummyword\sf \definedummyword\sl \definedummyword\tclose \definedummyword\tt % \definedummyword\LaTeX \definedummyword\TeX % % Assorted special characters. \definedummyword\bullet \definedummyword\comma \definedummyword\copyright \definedummyword\registeredsymbol \definedummyword\dots \definedummyword\enddots \definedummyword\equiv \definedummyword\error \definedummyword\euro \definedummyword\expansion \definedummyword\minus \definedummyword\pounds \definedummyword\point \definedummyword\print \definedummyword\result \definedummyword\textdegree % % We want to disable all macros so that they are not expanded by \write. \macrolist % \normalturnoffactive % % Handle some cases of @value -- where it does not contain any % (non-fully-expandable) commands. \makevalueexpandable } % \commondummiesnofonts: common to \commondummies and \indexnofonts. % \def\commondummiesnofonts{% % Control letters and accents. \definedummyletter\!% \definedummyaccent\"% \definedummyaccent\'% \definedummyletter\*% \definedummyaccent\,% \definedummyletter\.% \definedummyletter\/% \definedummyletter\:% \definedummyaccent\=% \definedummyletter\?% \definedummyaccent\^% \definedummyaccent\`% \definedummyaccent\~% \definedummyword\u \definedummyword\v \definedummyword\H \definedummyword\dotaccent \definedummyword\ringaccent \definedummyword\tieaccent \definedummyword\ubaraccent \definedummyword\udotaccent \definedummyword\dotless % % Texinfo font commands. \definedummyword\b \definedummyword\i \definedummyword\r \definedummyword\sc \definedummyword\t % % Commands that take arguments. \definedummyword\acronym \definedummyword\cite \definedummyword\code \definedummyword\command \definedummyword\dfn \definedummyword\emph \definedummyword\env \definedummyword\file \definedummyword\kbd \definedummyword\key \definedummyword\math \definedummyword\option \definedummyword\pxref \definedummyword\ref \definedummyword\samp \definedummyword\strong \definedummyword\tie \definedummyword\uref \definedummyword\url \definedummyword\var \definedummyword\verb \definedummyword\w \definedummyword\xref } % \indexnofonts is used when outputting the strings to sort the index % by, and when constructing control sequence names. It eliminates all % control sequences and just writes whatever the best ASCII sort string % would be for a given command (usually its argument). % \def\indexnofonts{% % Accent commands should become @asis. \def\definedummyaccent##1{\let##1\asis}% % We can just ignore other control letters. \def\definedummyletter##1{\let##1\empty}% % Hopefully, all control words can become @asis. \let\definedummyword\definedummyaccent % \commondummiesnofonts % % Don't no-op \tt, since it isn't a user-level command % and is used in the definitions of the active chars like <, >, |, etc. % Likewise with the other plain tex font commands. %\let\tt=\asis % \def\ { }% \def\@{@}% % how to handle braces? \def\_{\normalunderscore}% % % Non-English letters. \def\AA{AA}% \def\AE{AE}% \def\L{L}% \def\OE{OE}% \def\O{O}% \def\aa{aa}% \def\ae{ae}% \def\l{l}% \def\oe{oe}% \def\o{o}% \def\ss{ss}% \def\exclamdown{!}% \def\questiondown{?}% \def\ordf{a}% \def\ordm{o}% % \def\LaTeX{LaTeX}% \def\TeX{TeX}% % % Assorted special characters. % (The following {} will end up in the sort string, but that's ok.) \def\bullet{bullet}% \def\comma{,}% \def\copyright{copyright}% \def\registeredsymbol{R}% \def\dots{...}% \def\enddots{...}% \def\equiv{==}% \def\error{error}% \def\euro{euro}% \def\expansion{==>}% \def\minus{-}% \def\pounds{pounds}% \def\point{.}% \def\print{-|}% \def\result{=>}% \def\textdegree{degrees}% % % We need to get rid of all macros, leaving only the arguments (if present). % Of course this is not nearly correct, but it is the best we can do for now. % makeinfo does not expand macros in the argument to @deffn, which ends up % writing an index entry, and texindex isn't prepared for an index sort entry % that starts with \. % % Since macro invocations are followed by braces, we can just redefine them % to take a single TeX argument. The case of a macro invocation that % goes to end-of-line is not handled. % \macrolist } \let\indexbackslash=0 %overridden during \printindex. \let\SETmarginindex=\relax % put index entries in margin (undocumented)? % Most index entries go through here, but \dosubind is the general case. % #1 is the index name, #2 is the entry text. \def\doind#1#2{\dosubind{#1}{#2}{}} % Workhorse for all \fooindexes. % #1 is name of index, #2 is stuff to put there, #3 is subentry -- % empty if called from \doind, as we usually are (the main exception % is with most defuns, which call us directly). % \def\dosubind#1#2#3{% \iflinks {% % Store the main index entry text (including the third arg). \toks0 = {#2}% % If third arg is present, precede it with a space. \def\thirdarg{#3}% \ifx\thirdarg\empty \else \toks0 = \expandafter{\the\toks0 \space #3}% \fi % \edef\writeto{\csname#1indfile\endcsname}% % \ifvmode \dosubindsanitize \else \dosubindwrite \fi }% \fi } % Write the entry in \toks0 to the index file: % \def\dosubindwrite{% % Put the index entry in the margin if desired. \ifx\SETmarginindex\relax\else \insert\margin{\hbox{\vrule height8pt depth3pt width0pt \the\toks0}}% \fi % % Remember, we are within a group. \indexdummies % Must do this here, since \bf, etc expand at this stage \def\backslashcurfont{\indexbackslash}% \indexbackslash isn't defined now % so it will be output as is; and it will print as backslash. % % Process the index entry with all font commands turned off, to % get the string to sort by. {\indexnofonts \edef\temp{\the\toks0}% need full expansion \xdef\indexsorttmp{\temp}% }% % % Set up the complete index entry, with both the sort key and % the original text, including any font commands. We write % three arguments to \entry to the .?? file (four in the % subentry case), texindex reduces to two when writing the .??s % sorted result. \edef\temp{% \write\writeto{% \string\entry{\indexsorttmp}{\noexpand\folio}{\the\toks0}}% }% \temp } % Take care of unwanted page breaks: % % If a skip is the last thing on the list now, preserve it % by backing up by \lastskip, doing the \write, then inserting % the skip again. Otherwise, the whatsit generated by the % \write will make \lastskip zero. The result is that sequences % like this: % @end defun % @tindex whatever % @defun ... % will have extra space inserted, because the \medbreak in the % start of the @defun won't see the skip inserted by the @end of % the previous defun. % % But don't do any of this if we're not in vertical mode. We % don't want to do a \vskip and prematurely end a paragraph. % % Avoid page breaks due to these extra skips, too. % % But wait, there is a catch there: % We'll have to check whether \lastskip is zero skip. \ifdim is not % sufficient for this purpose, as it ignores stretch and shrink parts % of the skip. The only way seems to be to check the textual % representation of the skip. % % The following is almost like \def\zeroskipmacro{0.0pt} except that % the ``p'' and ``t'' characters have catcode \other, not 11 (letter). % \edef\zeroskipmacro{\expandafter\the\csname z@skip\endcsname} % % ..., ready, GO: % \def\dosubindsanitize{% % \lastskip and \lastpenalty cannot both be nonzero simultaneously. \skip0 = \lastskip \edef\lastskipmacro{\the\lastskip}% \count255 = \lastpenalty % % If \lastskip is nonzero, that means the last item was a % skip. And since a skip is discardable, that means this % -\skip0 glue we're inserting is preceded by a % non-discardable item, therefore it is not a potential % breakpoint, therefore no \nobreak needed. \ifx\lastskipmacro\zeroskipmacro \else \vskip-\skip0 \fi % \dosubindwrite % \ifx\lastskipmacro\zeroskipmacro % If \lastskip was zero, perhaps the last item was a penalty, and % perhaps it was >=10000, e.g., a \nobreak. In that case, we want % to re-insert the same penalty (values >10000 are used for various % signals); since we just inserted a non-discardable item, any % following glue (such as a \parskip) would be a breakpoint. For example: % % @deffn deffn-whatever % @vindex index-whatever % Description. % would allow a break between the index-whatever whatsit % and the "Description." paragraph. \ifnum\count255>9999 \penalty\count255 \fi \else % On the other hand, if we had a nonzero \lastskip, % this make-up glue would be preceded by a non-discardable item % (the whatsit from the \write), so we must insert a \nobreak. \nobreak\vskip\skip0 \fi } % The index entry written in the file actually looks like % \entry {sortstring}{page}{topic} % or % \entry {sortstring}{page}{topic}{subtopic} % The texindex program reads in these files and writes files % containing these kinds of lines: % \initial {c} % before the first topic whose initial is c % \entry {topic}{pagelist} % for a topic that is used without subtopics % \primary {topic} % for the beginning of a topic that is used with subtopics % \secondary {subtopic}{pagelist} % for each subtopic. % Define the user-accessible indexing commands % @findex, @vindex, @kindex, @cindex. \def\findex {\fnindex} \def\kindex {\kyindex} \def\cindex {\cpindex} \def\vindex {\vrindex} \def\tindex {\tpindex} \def\pindex {\pgindex} \def\cindexsub {\begingroup\obeylines\cindexsub} {\obeylines % \gdef\cindexsub "#1" #2^^M{\endgroup % \dosubind{cp}{#2}{#1}}} % Define the macros used in formatting output of the sorted index material. % @printindex causes a particular index (the ??s file) to get printed. % It does not print any chapter heading (usually an @unnumbered). % \parseargdef\printindex{\begingroup \dobreak \chapheadingskip{10000}% % \smallfonts \rm \tolerance = 9500 \everypar = {}% don't want the \kern\-parindent from indentation suppression. % % See if the index file exists and is nonempty. % Change catcode of @ here so that if the index file contains % \initial {@} % as its first line, TeX doesn't complain about mismatched braces % (because it thinks @} is a control sequence). \catcode`\@ = 11 \openin 1 \jobname.#1s \ifeof 1 % \enddoublecolumns gets confused if there is no text in the index, % and it loses the chapter title and the aux file entries for the % index. The easiest way to prevent this problem is to make sure % there is some text. \putwordIndexNonexistent \else % % If the index file exists but is empty, then \openin leaves \ifeof % false. We have to make TeX try to read something from the file, so % it can discover if there is anything in it. \read 1 to \temp \ifeof 1 \putwordIndexIsEmpty \else % Index files are almost Texinfo source, but we use \ as the escape % character. It would be better to use @, but that's too big a change % to make right now. \def\indexbackslash{\backslashcurfont}% \catcode`\\ = 0 \escapechar = `\\ \begindoublecolumns \input \jobname.#1s \enddoublecolumns \fi \fi \closein 1 \endgroup} % These macros are used by the sorted index file itself. % Change them to control the appearance of the index. \def\initial#1{{% % Some minor font changes for the special characters. \let\tentt=\sectt \let\tt=\sectt \let\sf=\sectt % % Remove any glue we may have, we'll be inserting our own. \removelastskip % % We like breaks before the index initials, so insert a bonus. \nobreak \vskip 0pt plus 3\baselineskip \penalty 0 \vskip 0pt plus -3\baselineskip % % Typeset the initial. Making this add up to a whole number of % baselineskips increases the chance of the dots lining up from column % to column. It still won't often be perfect, because of the stretch % we need before each entry, but it's better. % % No shrink because it confuses \balancecolumns. \vskip 1.67\baselineskip plus .5\baselineskip \leftline{\secbf #1}% % Do our best not to break after the initial. \nobreak \vskip .33\baselineskip plus .1\baselineskip }} % \entry typesets a paragraph consisting of the text (#1), dot leaders, and % then page number (#2) flushed to the right margin. It is used for index % and table of contents entries. The paragraph is indented by \leftskip. % % A straightforward implementation would start like this: % \def\entry#1#2{... % But this frozes the catcodes in the argument, and can cause problems to % @code, which sets - active. This problem was fixed by a kludge--- % ``-'' was active throughout whole index, but this isn't really right. % % The right solution is to prevent \entry from swallowing the whole text. % --kasal, 21nov03 \def\entry{% \begingroup % % Start a new paragraph if necessary, so our assignments below can't % affect previous text. \par % % Do not fill out the last line with white space. \parfillskip = 0in % % No extra space above this paragraph. \parskip = 0in % % Do not prefer a separate line ending with a hyphen to fewer lines. \finalhyphendemerits = 0 % % \hangindent is only relevant when the entry text and page number % don't both fit on one line. In that case, bob suggests starting the % dots pretty far over on the line. Unfortunately, a large % indentation looks wrong when the entry text itself is broken across % lines. So we use a small indentation and put up with long leaders. % % \hangafter is reset to 1 (which is the value we want) at the start % of each paragraph, so we need not do anything with that. \hangindent = 2em % % When the entry text needs to be broken, just fill out the first line % with blank space. \rightskip = 0pt plus1fil % % A bit of stretch before each entry for the benefit of balancing % columns. \vskip 0pt plus1pt % % Swallow the left brace of the text (first parameter): \afterassignment\doentry \let\temp = } \def\doentry{% \bgroup % Instead of the swallowed brace. \noindent \aftergroup\finishentry % And now comes the text of the entry. } \def\finishentry#1{% % #1 is the page number. % % The following is kludged to not output a line of dots in the index if % there are no page numbers. The next person who breaks this will be % cursed by a Unix daemon. \def\tempa{{\rm }}% \def\tempb{#1}% \edef\tempc{\tempa}% \edef\tempd{\tempb}% \ifx\tempc\tempd \ % \else % % If we must, put the page number on a line of its own, and fill out % this line with blank space. (The \hfil is overwhelmed with the % fill leaders glue in \indexdotfill if the page number does fit.) \hfil\penalty50 \null\nobreak\indexdotfill % Have leaders before the page number. % % The `\ ' here is removed by the implicit \unskip that TeX does as % part of (the primitive) \par. Without it, a spurious underfull % \hbox ensues. \ifpdf \pdfgettoks#1.% \ \the\toksA \else \ #1% \fi \fi \par \endgroup } % Like plain.tex's \dotfill, except uses up at least 1 em. \def\indexdotfill{\cleaders \hbox{$\mathsurround=0pt \mkern1.5mu.\mkern1.5mu$}\hskip 1em plus 1fill} \def\primary #1{\line{#1\hfil}} \newskip\secondaryindent \secondaryindent=0.5cm \def\secondary#1#2{{% \parfillskip=0in \parskip=0in \hangindent=1in \hangafter=1 \noindent\hskip\secondaryindent\hbox{#1}\indexdotfill \ifpdf \pdfgettoks#2.\ \the\toksA % The page number ends the paragraph. \else #2 \fi \par }} % Define two-column mode, which we use to typeset indexes. % Adapted from the TeXbook, page 416, which is to say, % the manmac.tex format used to print the TeXbook itself. \catcode`\@=11 \newbox\partialpage \newdimen\doublecolumnhsize \def\begindoublecolumns{\begingroup % ended by \enddoublecolumns % Grab any single-column material above us. \output = {% % % Here is a possibility not foreseen in manmac: if we accumulate a % whole lot of material, we might end up calling this \output % routine twice in a row (see the doublecol-lose test, which is % essentially a couple of indexes with @setchapternewpage off). In % that case we just ship out what is in \partialpage with the normal % output routine. Generally, \partialpage will be empty when this % runs and this will be a no-op. See the indexspread.tex test case. \ifvoid\partialpage \else \onepageout{\pagecontents\partialpage}% \fi % \global\setbox\partialpage = \vbox{% % Unvbox the main output page. \unvbox\PAGE \kern-\topskip \kern\baselineskip }% }% \eject % run that output routine to set \partialpage % % Use the double-column output routine for subsequent pages. \output = {\doublecolumnout}% % % Change the page size parameters. We could do this once outside this % routine, in each of @smallbook, @afourpaper, and the default 8.5x11 % format, but then we repeat the same computation. Repeating a couple % of assignments once per index is clearly meaningless for the % execution time, so we may as well do it in one place. % % First we halve the line length, less a little for the gutter between % the columns. We compute the gutter based on the line length, so it % changes automatically with the paper format. The magic constant % below is chosen so that the gutter has the same value (well, +-<1pt) % as it did when we hard-coded it. % % We put the result in a separate register, \doublecolumhsize, so we % can restore it in \pagesofar, after \hsize itself has (potentially) % been clobbered. % \doublecolumnhsize = \hsize \advance\doublecolumnhsize by -.04154\hsize \divide\doublecolumnhsize by 2 \hsize = \doublecolumnhsize % % Double the \vsize as well. (We don't need a separate register here, % since nobody clobbers \vsize.) \vsize = 2\vsize } % The double-column output routine for all double-column pages except % the last. % \def\doublecolumnout{% \splittopskip=\topskip \splitmaxdepth=\maxdepth % Get the available space for the double columns -- the normal % (undoubled) page height minus any material left over from the % previous page. \dimen@ = \vsize \divide\dimen@ by 2 \advance\dimen@ by -\ht\partialpage % % box0 will be the left-hand column, box2 the right. \setbox0=\vsplit255 to\dimen@ \setbox2=\vsplit255 to\dimen@ \onepageout\pagesofar \unvbox255 \penalty\outputpenalty } % % Re-output the contents of the output page -- any previous material, % followed by the two boxes we just split, in box0 and box2. \def\pagesofar{% \unvbox\partialpage % \hsize = \doublecolumnhsize \wd0=\hsize \wd2=\hsize \hbox to\pagewidth{\box0\hfil\box2}% } % % All done with double columns. \def\enddoublecolumns{% \output = {% % Split the last of the double-column material. Leave it on the % current page, no automatic page break. \balancecolumns % % If we end up splitting too much material for the current page, % though, there will be another page break right after this \output % invocation ends. Having called \balancecolumns once, we do not % want to call it again. Therefore, reset \output to its normal % definition right away. (We hope \balancecolumns will never be % called on to balance too much material, but if it is, this makes % the output somewhat more palatable.) \global\output = {\onepageout{\pagecontents\PAGE}}% }% \eject \endgroup % started in \begindoublecolumns % % \pagegoal was set to the doubled \vsize above, since we restarted % the current page. We're now back to normal single-column % typesetting, so reset \pagegoal to the normal \vsize (after the % \endgroup where \vsize got restored). \pagegoal = \vsize } % % Called at the end of the double column material. \def\balancecolumns{% \setbox0 = \vbox{\unvbox255}% like \box255 but more efficient, see p.120. \dimen@ = \ht0 \advance\dimen@ by \topskip \advance\dimen@ by-\baselineskip \divide\dimen@ by 2 % target to split to %debug\message{final 2-column material height=\the\ht0, target=\the\dimen@.}% \splittopskip = \topskip % Loop until we get a decent breakpoint. {% \vbadness = 10000 \loop \global\setbox3 = \copy0 \global\setbox1 = \vsplit3 to \dimen@ \ifdim\ht3>\dimen@ \global\advance\dimen@ by 1pt \repeat }% %debug\message{split to \the\dimen@, column heights: \the\ht1, \the\ht3.}% \setbox0=\vbox to\dimen@{\unvbox1}% \setbox2=\vbox to\dimen@{\unvbox3}% % \pagesofar } \catcode`\@ = \other \message{sectioning,} % Chapters, sections, etc. % \unnumberedno is an oxymoron, of course. But we count the unnumbered % sections so that we can refer to them unambiguously in the pdf % outlines by their "section number". We avoid collisions with chapter % numbers by starting them at 10000. (If a document ever has 10000 % chapters, we're in trouble anyway, I'm sure.) \newcount\unnumberedno \unnumberedno = 10000 \newcount\chapno \newcount\secno \secno=0 \newcount\subsecno \subsecno=0 \newcount\subsubsecno \subsubsecno=0 % This counter is funny since it counts through charcodes of letters A, B, ... \newcount\appendixno \appendixno = `\@ % % \def\appendixletter{\char\the\appendixno} % We do the following ugly conditional instead of the above simple % construct for the sake of pdftex, which needs the actual % letter in the expansion, not just typeset. % \def\appendixletter{% \ifnum\appendixno=`A A% \else\ifnum\appendixno=`B B% \else\ifnum\appendixno=`C C% \else\ifnum\appendixno=`D D% \else\ifnum\appendixno=`E E% \else\ifnum\appendixno=`F F% \else\ifnum\appendixno=`G G% \else\ifnum\appendixno=`H H% \else\ifnum\appendixno=`I I% \else\ifnum\appendixno=`J J% \else\ifnum\appendixno=`K K% \else\ifnum\appendixno=`L L% \else\ifnum\appendixno=`M M% \else\ifnum\appendixno=`N N% \else\ifnum\appendixno=`O O% \else\ifnum\appendixno=`P P% \else\ifnum\appendixno=`Q Q% \else\ifnum\appendixno=`R R% \else\ifnum\appendixno=`S S% \else\ifnum\appendixno=`T T% \else\ifnum\appendixno=`U U% \else\ifnum\appendixno=`V V% \else\ifnum\appendixno=`W W% \else\ifnum\appendixno=`X X% \else\ifnum\appendixno=`Y Y% \else\ifnum\appendixno=`Z Z% % The \the is necessary, despite appearances, because \appendixletter is % expanded while writing the .toc file. \char\appendixno is not % expandable, thus it is written literally, thus all appendixes come out % with the same letter (or @) in the toc without it. \else\char\the\appendixno \fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi \fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi} % Each @chapter defines this as the name of the chapter. % page headings and footings can use it. @section does likewise. % However, they are not reliable, because we don't use marks. \def\thischapter{} \def\thissection{} \newcount\absseclevel % used to calculate proper heading level \newcount\secbase\secbase=0 % @raisesections/@lowersections modify this count % @raisesections: treat @section as chapter, @subsection as section, etc. \def\raisesections{\global\advance\secbase by -1} \let\up=\raisesections % original BFox name % @lowersections: treat @chapter as section, @section as subsection, etc. \def\lowersections{\global\advance\secbase by 1} \let\down=\lowersections % original BFox name % we only have subsub. \chardef\maxseclevel = 3 % % A numbered section within an unnumbered changes to unnumbered too. % To achive this, remember the "biggest" unnum. sec. we are currently in: \chardef\unmlevel = \maxseclevel % % Trace whether the current chapter is an appendix or not: % \chapheadtype is "N" or "A", unnumbered chapters are ignored. \def\chapheadtype{N} % Choose a heading macro % #1 is heading type % #2 is heading level % #3 is text for heading \def\genhead#1#2#3{% % Compute the abs. sec. level: \absseclevel=#2 \advance\absseclevel by \secbase % Make sure \absseclevel doesn't fall outside the range: \ifnum \absseclevel < 0 \absseclevel = 0 \else \ifnum \absseclevel > 3 \absseclevel = 3 \fi \fi % The heading type: \def\headtype{#1}% \if \headtype U% \ifnum \absseclevel < \unmlevel \chardef\unmlevel = \absseclevel \fi \else % Check for appendix sections: \ifnum \absseclevel = 0 \edef\chapheadtype{\headtype}% \else \if \headtype A\if \chapheadtype N% \errmessage{@appendix... within a non-appendix chapter}% \fi\fi \fi % Check for numbered within unnumbered: \ifnum \absseclevel > \unmlevel \def\headtype{U}% \else \chardef\unmlevel = 3 \fi \fi % Now print the heading: \if \headtype U% \ifcase\absseclevel \unnumberedzzz{#3}% \or \unnumberedseczzz{#3}% \or \unnumberedsubseczzz{#3}% \or \unnumberedsubsubseczzz{#3}% \fi \else \if \headtype A% \ifcase\absseclevel \appendixzzz{#3}% \or \appendixsectionzzz{#3}% \or \appendixsubseczzz{#3}% \or \appendixsubsubseczzz{#3}% \fi \else \ifcase\absseclevel \chapterzzz{#3}% \or \seczzz{#3}% \or \numberedsubseczzz{#3}% \or \numberedsubsubseczzz{#3}% \fi \fi \fi \suppressfirstparagraphindent } % an interface: \def\numhead{\genhead N} \def\apphead{\genhead A} \def\unnmhead{\genhead U} % @chapter, @appendix, @unnumbered. Increment top-level counter, reset % all lower-level sectioning counters to zero. % % Also set \chaplevelprefix, which we prepend to @float sequence numbers % (e.g., figures), q.v. By default (before any chapter), that is empty. \let\chaplevelprefix = \empty % \outer\parseargdef\chapter{\numhead0{#1}} % normally numhead0 calls chapterzzz \def\chapterzzz#1{% % section resetting is \global in case the chapter is in a group, such % as an @include file. \global\secno=0 \global\subsecno=0 \global\subsubsecno=0 \global\advance\chapno by 1 % % Used for \float. \gdef\chaplevelprefix{\the\chapno.}% \resetallfloatnos % \message{\putwordChapter\space \the\chapno}% % % Write the actual heading. \chapmacro{#1}{Ynumbered}{\the\chapno}% % % So @section and the like are numbered underneath this chapter. \global\let\section = \numberedsec \global\let\subsection = \numberedsubsec \global\let\subsubsection = \numberedsubsubsec } \outer\parseargdef\appendix{\apphead0{#1}} % normally apphead0 calls appendixzzz \def\appendixzzz#1{% \global\secno=0 \global\subsecno=0 \global\subsubsecno=0 \global\advance\appendixno by 1 \gdef\chaplevelprefix{\appendixletter.}% \resetallfloatnos % \def\appendixnum{\putwordAppendix\space \appendixletter}% \message{\appendixnum}% % \chapmacro{#1}{Yappendix}{\appendixletter}% % \global\let\section = \appendixsec \global\let\subsection = \appendixsubsec \global\let\subsubsection = \appendixsubsubsec } \outer\parseargdef\unnumbered{\unnmhead0{#1}} % normally unnmhead0 calls unnumberedzzz \def\unnumberedzzz#1{% \global\secno=0 \global\subsecno=0 \global\subsubsecno=0 \global\advance\unnumberedno by 1 % % Since an unnumbered has no number, no prefix for figures. \global\let\chaplevelprefix = \empty \resetallfloatnos % % This used to be simply \message{#1}, but TeX fully expands the % argument to \message. Therefore, if #1 contained @-commands, TeX % expanded them. For example, in `@unnumbered The @cite{Book}', TeX % expanded @cite (which turns out to cause errors because \cite is meant % to be executed, not expanded). % % Anyway, we don't want the fully-expanded definition of @cite to appear % as a result of the \message, we just want `@cite' itself. We use % \the to achieve this: TeX expands \the only once, % simply yielding the contents of . (We also do this for % the toc entries.) \toks0 = {#1}% \message{(\the\toks0)}% % \chapmacro{#1}{Ynothing}{\the\unnumberedno}% % \global\let\section = \unnumberedsec \global\let\subsection = \unnumberedsubsec \global\let\subsubsection = \unnumberedsubsubsec } % @centerchap is like @unnumbered, but the heading is centered. \outer\parseargdef\centerchap{% % Well, we could do the following in a group, but that would break % an assumption that \chapmacro is called at the outermost level. % Thus we are safer this way: --kasal, 24feb04 \let\centerparametersmaybe = \centerparameters \unnmhead0{#1}% \let\centerparametersmaybe = \relax } % @top is like @unnumbered. \let\top\unnumbered % Sections. \outer\parseargdef\numberedsec{\numhead1{#1}} % normally calls seczzz \def\seczzz#1{% \global\subsecno=0 \global\subsubsecno=0 \global\advance\secno by 1 \sectionheading{#1}{sec}{Ynumbered}{\the\chapno.\the\secno}% } \outer\parseargdef\appendixsection{\apphead1{#1}} % normally calls appendixsectionzzz \def\appendixsectionzzz#1{% \global\subsecno=0 \global\subsubsecno=0 \global\advance\secno by 1 \sectionheading{#1}{sec}{Yappendix}{\appendixletter.\the\secno}% } \let\appendixsec\appendixsection \outer\parseargdef\unnumberedsec{\unnmhead1{#1}} % normally calls unnumberedseczzz \def\unnumberedseczzz#1{% \global\subsecno=0 \global\subsubsecno=0 \global\advance\secno by 1 \sectionheading{#1}{sec}{Ynothing}{\the\unnumberedno.\the\secno}% } % Subsections. \outer\parseargdef\numberedsubsec{\numhead2{#1}} % normally calls numberedsubseczzz \def\numberedsubseczzz#1{% \global\subsubsecno=0 \global\advance\subsecno by 1 \sectionheading{#1}{subsec}{Ynumbered}{\the\chapno.\the\secno.\the\subsecno}% } \outer\parseargdef\appendixsubsec{\apphead2{#1}} % normally calls appendixsubseczzz \def\appendixsubseczzz#1{% \global\subsubsecno=0 \global\advance\subsecno by 1 \sectionheading{#1}{subsec}{Yappendix}% {\appendixletter.\the\secno.\the\subsecno}% } \outer\parseargdef\unnumberedsubsec{\unnmhead2{#1}} %normally calls unnumberedsubseczzz \def\unnumberedsubseczzz#1{% \global\subsubsecno=0 \global\advance\subsecno by 1 \sectionheading{#1}{subsec}{Ynothing}% {\the\unnumberedno.\the\secno.\the\subsecno}% } % Subsubsections. \outer\parseargdef\numberedsubsubsec{\numhead3{#1}} % normally numberedsubsubseczzz \def\numberedsubsubseczzz#1{% \global\advance\subsubsecno by 1 \sectionheading{#1}{subsubsec}{Ynumbered}% {\the\chapno.\the\secno.\the\subsecno.\the\subsubsecno}% } \outer\parseargdef\appendixsubsubsec{\apphead3{#1}} % normally appendixsubsubseczzz \def\appendixsubsubseczzz#1{% \global\advance\subsubsecno by 1 \sectionheading{#1}{subsubsec}{Yappendix}% {\appendixletter.\the\secno.\the\subsecno.\the\subsubsecno}% } \outer\parseargdef\unnumberedsubsubsec{\unnmhead3{#1}} %normally unnumberedsubsubseczzz \def\unnumberedsubsubseczzz#1{% \global\advance\subsubsecno by 1 \sectionheading{#1}{subsubsec}{Ynothing}% {\the\unnumberedno.\the\secno.\the\subsecno.\the\subsubsecno}% } % These macros control what the section commands do, according % to what kind of chapter we are in (ordinary, appendix, or unnumbered). % Define them by default for a numbered chapter. \let\section = \numberedsec \let\subsection = \numberedsubsec \let\subsubsection = \numberedsubsubsec % Define @majorheading, @heading and @subheading % NOTE on use of \vbox for chapter headings, section headings, and such: % 1) We use \vbox rather than the earlier \line to permit % overlong headings to fold. % 2) \hyphenpenalty is set to 10000 because hyphenation in a % heading is obnoxious; this forbids it. % 3) Likewise, headings look best if no \parindent is used, and % if justification is not attempted. Hence \raggedright. \def\majorheading{% {\advance\chapheadingskip by 10pt \chapbreak }% \parsearg\chapheadingzzz } \def\chapheading{\chapbreak \parsearg\chapheadingzzz} \def\chapheadingzzz#1{% {\chapfonts \vbox{\hyphenpenalty=10000\tolerance=5000 \parindent=0pt\raggedright \rm #1\hfill}}% \bigskip \par\penalty 200\relax \suppressfirstparagraphindent } % @heading, @subheading, @subsubheading. \parseargdef\heading{\sectionheading{#1}{sec}{Yomitfromtoc}{} \suppressfirstparagraphindent} \parseargdef\subheading{\sectionheading{#1}{subsec}{Yomitfromtoc}{} \suppressfirstparagraphindent} \parseargdef\subsubheading{\sectionheading{#1}{subsubsec}{Yomitfromtoc}{} \suppressfirstparagraphindent} % These macros generate a chapter, section, etc. heading only % (including whitespace, linebreaking, etc. around it), % given all the information in convenient, parsed form. %%% Args are the skip and penalty (usually negative) \def\dobreak#1#2{\par\ifdim\lastskip<#1\removelastskip\penalty#2\vskip#1\fi} %%% Define plain chapter starts, and page on/off switching for it % Parameter controlling skip before chapter headings (if needed) \newskip\chapheadingskip \def\chapbreak{\dobreak \chapheadingskip {-4000}} \def\chappager{\par\vfill\supereject} \def\chapoddpage{\chappager \ifodd\pageno \else \hbox to 0pt{} \chappager\fi} \def\setchapternewpage #1 {\csname CHAPPAG#1\endcsname} \def\CHAPPAGoff{% \global\let\contentsalignmacro = \chappager \global\let\pchapsepmacro=\chapbreak \global\let\pagealignmacro=\chappager} \def\CHAPPAGon{% \global\let\contentsalignmacro = \chappager \global\let\pchapsepmacro=\chappager \global\let\pagealignmacro=\chappager \global\def\HEADINGSon{\HEADINGSsingle}} \def\CHAPPAGodd{% \global\let\contentsalignmacro = \chapoddpage \global\let\pchapsepmacro=\chapoddpage \global\let\pagealignmacro=\chapoddpage \global\def\HEADINGSon{\HEADINGSdouble}} \CHAPPAGon % Chapter opening. % % #1 is the text, #2 is the section type (Ynumbered, Ynothing, % Yappendix, Yomitfromtoc), #3 the chapter number. % % To test against our argument. \def\Ynothingkeyword{Ynothing} \def\Yomitfromtockeyword{Yomitfromtoc} \def\Yappendixkeyword{Yappendix} % \def\chapmacro#1#2#3{% \pchapsepmacro {% \chapfonts \rm % % Have to define \thissection before calling \donoderef, because the % xref code eventually uses it. On the other hand, it has to be called % after \pchapsepmacro, or the headline will change too soon. \gdef\thissection{#1}% \gdef\thischaptername{#1}% % % Only insert the separating space if we have a chapter/appendix % number, and don't print the unnumbered ``number''. \def\temptype{#2}% \ifx\temptype\Ynothingkeyword \setbox0 = \hbox{}% \def\toctype{unnchap}% \gdef\thischapternum{}% \gdef\thischapter{#1}% \else\ifx\temptype\Yomitfromtockeyword \setbox0 = \hbox{}% contents like unnumbered, but no toc entry \def\toctype{omit}% \gdef\thischapternum{}% \gdef\thischapter{}% \else\ifx\temptype\Yappendixkeyword \setbox0 = \hbox{\putwordAppendix{} #3\enspace}% \def\toctype{app}% \xdef\thischapternum{\appendixletter}% % We don't substitute the actual chapter name into \thischapter % because we don't want its macros evaluated now. And we don't % use \thissection because that changes with each section. % \xdef\thischapter{\putwordAppendix{} \appendixletter: \noexpand\thischaptername}% \else \setbox0 = \hbox{#3\enspace}% \def\toctype{numchap}% \xdef\thischapternum{\the\chapno}% \xdef\thischapter{\putwordChapter{} \the\chapno: \noexpand\thischaptername}% \fi\fi\fi % % Write the toc entry for this chapter. Must come before the % \donoderef, because we include the current node name in the toc % entry, and \donoderef resets it to empty. \writetocentry{\toctype}{#1}{#3}% % % For pdftex, we have to write out the node definition (aka, make % the pdfdest) after any page break, but before the actual text has % been typeset. If the destination for the pdf outline is after the % text, then jumping from the outline may wind up with the text not % being visible, for instance under high magnification. \donoderef{#2}% % % Typeset the actual heading. \vbox{\hyphenpenalty=10000 \tolerance=5000 \parindent=0pt \raggedright \hangindent=\wd0 \centerparametersmaybe \unhbox0 #1\par}% }% \nobreak\bigskip % no page break after a chapter title \nobreak } % @centerchap -- centered and unnumbered. \let\centerparametersmaybe = \relax \def\centerparameters{% \advance\rightskip by 3\rightskip \leftskip = \rightskip \parfillskip = 0pt } % I don't think this chapter style is supported any more, so I'm not % updating it with the new noderef stuff. We'll see. --karl, 11aug03. % \def\setchapterstyle #1 {\csname CHAPF#1\endcsname} % \def\unnchfopen #1{% \chapoddpage {\chapfonts \vbox{\hyphenpenalty=10000\tolerance=5000 \parindent=0pt\raggedright \rm #1\hfill}}\bigskip \par\nobreak } \def\chfopen #1#2{\chapoddpage {\chapfonts \vbox to 3in{\vfil \hbox to\hsize{\hfil #2} \hbox to\hsize{\hfil #1} \vfil}}% \par\penalty 5000 % } \def\centerchfopen #1{% \chapoddpage {\chapfonts \vbox{\hyphenpenalty=10000\tolerance=5000 \parindent=0pt \hfill {\rm #1}\hfill}}\bigskip \par\nobreak } \def\CHAPFopen{% \global\let\chapmacro=\chfopen \global\let\centerchapmacro=\centerchfopen} % Section titles. These macros combine the section number parts and % call the generic \sectionheading to do the printing. % \newskip\secheadingskip \def\secheadingbreak{\dobreak \secheadingskip{-1000}} % Subsection titles. \newskip\subsecheadingskip \def\subsecheadingbreak{\dobreak \subsecheadingskip{-500}} % Subsubsection titles. \def\subsubsecheadingskip{\subsecheadingskip} \def\subsubsecheadingbreak{\subsecheadingbreak} % Print any size, any type, section title. % % #1 is the text, #2 is the section level (sec/subsec/subsubsec), #3 is % the section type for xrefs (Ynumbered, Ynothing, Yappendix), #4 is the % section number. % \def\sectionheading#1#2#3#4{% {% % Switch to the right set of fonts. \csname #2fonts\endcsname \rm % % Insert space above the heading. \csname #2headingbreak\endcsname % % Only insert the space after the number if we have a section number. \def\sectionlevel{#2}% \def\temptype{#3}% % \ifx\temptype\Ynothingkeyword \setbox0 = \hbox{}% \def\toctype{unn}% \gdef\thissection{#1}% \else\ifx\temptype\Yomitfromtockeyword % for @headings -- no section number, don't include in toc, % and don't redefine \thissection. \setbox0 = \hbox{}% \def\toctype{omit}% \let\sectionlevel=\empty \else\ifx\temptype\Yappendixkeyword \setbox0 = \hbox{#4\enspace}% \def\toctype{app}% \gdef\thissection{#1}% \else \setbox0 = \hbox{#4\enspace}% \def\toctype{num}% \gdef\thissection{#1}% \fi\fi\fi % % Write the toc entry (before \donoderef). See comments in \chapmacro. \writetocentry{\toctype\sectionlevel}{#1}{#4}% % % Write the node reference (= pdf destination for pdftex). % Again, see comments in \chapmacro. \donoderef{#3}% % % Interline glue will be inserted when the vbox is completed. % That glue will be a valid breakpoint for the page, since it'll be % preceded by a whatsit (usually from the \donoderef, or from the % \writetocentry if there was no node). We don't want to allow that % break, since then the whatsits could end up on page n while the % section is on page n+1, thus toc/etc. are wrong. Debian bug 276000. \nobreak % % Output the actual section heading. \vbox{\hyphenpenalty=10000 \tolerance=5000 \parindent=0pt \raggedright \hangindent=\wd0 % zero if no section number \unhbox0 #1}% }% % Add extra space after the heading -- half of whatever came above it. % Don't allow stretch, though. \kern .5 \csname #2headingskip\endcsname % % Do not let the kern be a potential breakpoint, as it would be if it % was followed by glue. \nobreak % % We'll almost certainly start a paragraph next, so don't let that % glue accumulate. (Not a breakpoint because it's preceded by a % discardable item.) \vskip-\parskip % % This is purely so the last item on the list is a known \penalty > % 10000. This is so \startdefun can avoid allowing breakpoints after % section headings. Otherwise, it would insert a valid breakpoint between: % % @section sec-whatever % @deffn def-whatever \penalty 10001 } \message{toc,} % Table of contents. \newwrite\tocfile % Write an entry to the toc file, opening it if necessary. % Called from @chapter, etc. % % Example usage: \writetocentry{sec}{Section Name}{\the\chapno.\the\secno} % We append the current node name (if any) and page number as additional % arguments for the \{chap,sec,...}entry macros which will eventually % read this. The node name is used in the pdf outlines as the % destination to jump to. % % We open the .toc file for writing here instead of at @setfilename (or % any other fixed time) so that @contents can be anywhere in the document. % But if #1 is `omit', then we don't do anything. This is used for the % table of contents chapter openings themselves. % \newif\iftocfileopened \def\omitkeyword{omit}% % \def\writetocentry#1#2#3{% \edef\writetoctype{#1}% \ifx\writetoctype\omitkeyword \else \iftocfileopened\else \immediate\openout\tocfile = \jobname.toc \global\tocfileopenedtrue \fi % \iflinks {\atdummies \edef\temp{% \write\tocfile{@#1entry{#2}{#3}{\lastnode}{\noexpand\folio}}}% \temp }% \fi \fi % % Tell \shipout to create a pdf destination on each page, if we're % writing pdf. These are used in the table of contents. We can't % just write one on every page because the title pages are numbered % 1 and 2 (the page numbers aren't printed), and so are the first % two pages of the document. Thus, we'd have two destinations named % `1', and two named `2'. \ifpdf \global\pdfmakepagedesttrue \fi } % These characters do not print properly in the Computer Modern roman % fonts, so we must take special care. This is more or less redundant % with the Texinfo input format setup at the end of this file. % \def\activecatcodes{% \catcode`\"=\active \catcode`\$=\active \catcode`\<=\active \catcode`\>=\active \catcode`\\=\active \catcode`\^=\active \catcode`\_=\active \catcode`\|=\active \catcode`\~=\active } % Read the toc file, which is essentially Texinfo input. \def\readtocfile{% \setupdatafile \activecatcodes \input \jobname.toc } \newskip\contentsrightmargin \contentsrightmargin=1in \newcount\savepageno \newcount\lastnegativepageno \lastnegativepageno = -1 % Prepare to read what we've written to \tocfile. % \def\startcontents#1{% % If @setchapternewpage on, and @headings double, the contents should % start on an odd page, unlike chapters. Thus, we maintain % \contentsalignmacro in parallel with \pagealignmacro. % From: Torbjorn Granlund \contentsalignmacro \immediate\closeout\tocfile % % Don't need to put `Contents' or `Short Contents' in the headline. % It is abundantly clear what they are. \def\thischapter{}% \chapmacro{#1}{Yomitfromtoc}{}% % \savepageno = \pageno \begingroup % Set up to handle contents files properly. \raggedbottom % Worry more about breakpoints than the bottom. \advance\hsize by -\contentsrightmargin % Don't use the full line length. % % Roman numerals for page numbers. \ifnum \pageno>0 \global\pageno = \lastnegativepageno \fi } % Normal (long) toc. \def\contents{% \startcontents{\putwordTOC}% \openin 1 \jobname.toc \ifeof 1 \else \readtocfile \fi \vfill \eject \contentsalignmacro % in case @setchapternewpage odd is in effect \ifeof 1 \else \pdfmakeoutlines \fi \closein 1 \endgroup \lastnegativepageno = \pageno \global\pageno = \savepageno } % And just the chapters. \def\summarycontents{% \startcontents{\putwordShortTOC}% % \let\numchapentry = \shortchapentry \let\appentry = \shortchapentry \let\unnchapentry = \shortunnchapentry % We want a true roman here for the page numbers. \secfonts \let\rm=\shortcontrm \let\bf=\shortcontbf \let\sl=\shortcontsl \let\tt=\shortconttt \rm \hyphenpenalty = 10000 \advance\baselineskip by 1pt % Open it up a little. \def\numsecentry##1##2##3##4{} \let\appsecentry = \numsecentry \let\unnsecentry = \numsecentry \let\numsubsecentry = \numsecentry \let\appsubsecentry = \numsecentry \let\unnsubsecentry = \numsecentry \let\numsubsubsecentry = \numsecentry \let\appsubsubsecentry = \numsecentry \let\unnsubsubsecentry = \numsecentry \openin 1 \jobname.toc \ifeof 1 \else \readtocfile \fi \closein 1 \vfill \eject \contentsalignmacro % in case @setchapternewpage odd is in effect \endgroup \lastnegativepageno = \pageno \global\pageno = \savepageno } \let\shortcontents = \summarycontents % Typeset the label for a chapter or appendix for the short contents. % The arg is, e.g., `A' for an appendix, or `3' for a chapter. % \def\shortchaplabel#1{% % This space should be enough, since a single number is .5em, and the % widest letter (M) is 1em, at least in the Computer Modern fonts. % But use \hss just in case. % (This space doesn't include the extra space that gets added after % the label; that gets put in by \shortchapentry above.) % % We'd like to right-justify chapter numbers, but that looks strange % with appendix letters. And right-justifying numbers and % left-justifying letters looks strange when there is less than 10 % chapters. Have to read the whole toc once to know how many chapters % there are before deciding ... \hbox to 1em{#1\hss}% } % These macros generate individual entries in the table of contents. % The first argument is the chapter or section name. % The last argument is the page number. % The arguments in between are the chapter number, section number, ... % Chapters, in the main contents. \def\numchapentry#1#2#3#4{\dochapentry{#2\labelspace#1}{#4}} % % Chapters, in the short toc. % See comments in \dochapentry re vbox and related settings. \def\shortchapentry#1#2#3#4{% \tocentry{\shortchaplabel{#2}\labelspace #1}{\doshortpageno\bgroup#4\egroup}% } % Appendices, in the main contents. % Need the word Appendix, and a fixed-size box. % \def\appendixbox#1{% % We use M since it's probably the widest letter. \setbox0 = \hbox{\putwordAppendix{} M}% \hbox to \wd0{\putwordAppendix{} #1\hss}} % \def\appentry#1#2#3#4{\dochapentry{\appendixbox{#2}\labelspace#1}{#4}} % Unnumbered chapters. \def\unnchapentry#1#2#3#4{\dochapentry{#1}{#4}} \def\shortunnchapentry#1#2#3#4{\tocentry{#1}{\doshortpageno\bgroup#4\egroup}} % Sections. \def\numsecentry#1#2#3#4{\dosecentry{#2\labelspace#1}{#4}} \let\appsecentry=\numsecentry \def\unnsecentry#1#2#3#4{\dosecentry{#1}{#4}} % Subsections. \def\numsubsecentry#1#2#3#4{\dosubsecentry{#2\labelspace#1}{#4}} \let\appsubsecentry=\numsubsecentry \def\unnsubsecentry#1#2#3#4{\dosubsecentry{#1}{#4}} % And subsubsections. \def\numsubsubsecentry#1#2#3#4{\dosubsubsecentry{#2\labelspace#1}{#4}} \let\appsubsubsecentry=\numsubsubsecentry \def\unnsubsubsecentry#1#2#3#4{\dosubsubsecentry{#1}{#4}} % This parameter controls the indentation of the various levels. % Same as \defaultparindent. \newdimen\tocindent \tocindent = 15pt % Now for the actual typesetting. In all these, #1 is the text and #2 is the % page number. % % If the toc has to be broken over pages, we want it to be at chapters % if at all possible; hence the \penalty. \def\dochapentry#1#2{% \penalty-300 \vskip1\baselineskip plus.33\baselineskip minus.25\baselineskip \begingroup \chapentryfonts \tocentry{#1}{\dopageno\bgroup#2\egroup}% \endgroup \nobreak\vskip .25\baselineskip plus.1\baselineskip } \def\dosecentry#1#2{\begingroup \secentryfonts \leftskip=\tocindent \tocentry{#1}{\dopageno\bgroup#2\egroup}% \endgroup} \def\dosubsecentry#1#2{\begingroup \subsecentryfonts \leftskip=2\tocindent \tocentry{#1}{\dopageno\bgroup#2\egroup}% \endgroup} \def\dosubsubsecentry#1#2{\begingroup \subsubsecentryfonts \leftskip=3\tocindent \tocentry{#1}{\dopageno\bgroup#2\egroup}% \endgroup} % We use the same \entry macro as for the index entries. \let\tocentry = \entry % Space between chapter (or whatever) number and the title. \def\labelspace{\hskip1em \relax} \def\dopageno#1{{\rm #1}} \def\doshortpageno#1{{\rm #1}} \def\chapentryfonts{\secfonts \rm} \def\secentryfonts{\textfonts} \def\subsecentryfonts{\textfonts} \def\subsubsecentryfonts{\textfonts} \message{environments,} % @foo ... @end foo. % @point{}, @result{}, @expansion{}, @print{}, @equiv{}. % % Since these characters are used in examples, it should be an even number of % \tt widths. Each \tt character is 1en, so two makes it 1em. % \def\point{$\star$} \def\result{\leavevmode\raise.15ex\hbox to 1em{\hfil$\Rightarrow$\hfil}} \def\expansion{\leavevmode\raise.1ex\hbox to 1em{\hfil$\mapsto$\hfil}} \def\print{\leavevmode\lower.1ex\hbox to 1em{\hfil$\dashv$\hfil}} \def\equiv{\leavevmode\lower.1ex\hbox to 1em{\hfil$\ptexequiv$\hfil}} % The @error{} command. % Adapted from the TeXbook's \boxit. % \newbox\errorbox % {\tentt \global\dimen0 = 3em}% Width of the box. \dimen2 = .55pt % Thickness of rules % The text. (`r' is open on the right, `e' somewhat less so on the left.) \setbox0 = \hbox{\kern-.75pt \reducedsf error\kern-1.5pt} % \setbox\errorbox=\hbox to \dimen0{\hfil \hsize = \dimen0 \advance\hsize by -5.8pt % Space to left+right. \advance\hsize by -2\dimen2 % Rules. \vbox{% \hrule height\dimen2 \hbox{\vrule width\dimen2 \kern3pt % Space to left of text. \vtop{\kern2.4pt \box0 \kern2.4pt}% Space above/below. \kern3pt\vrule width\dimen2}% Space to right. \hrule height\dimen2} \hfil} % \def\error{\leavevmode\lower.7ex\copy\errorbox} % @tex ... @end tex escapes into raw Tex temporarily. % One exception: @ is still an escape character, so that @end tex works. % But \@ or @@ will get a plain tex @ character. \envdef\tex{% \catcode `\\=0 \catcode `\{=1 \catcode `\}=2 \catcode `\$=3 \catcode `\&=4 \catcode `\#=6 \catcode `\^=7 \catcode `\_=8 \catcode `\~=\active \let~=\tie \catcode `\%=14 \catcode `\+=\other \catcode `\"=\other \catcode `\|=\other \catcode `\<=\other \catcode `\>=\other \escapechar=`\\ % \let\b=\ptexb \let\bullet=\ptexbullet \let\c=\ptexc \let\,=\ptexcomma \let\.=\ptexdot \let\dots=\ptexdots \let\equiv=\ptexequiv \let\!=\ptexexclam \let\i=\ptexi \let\indent=\ptexindent \let\noindent=\ptexnoindent \let\{=\ptexlbrace \let\+=\tabalign \let\}=\ptexrbrace \let\/=\ptexslash \let\*=\ptexstar \let\t=\ptext \let\frenchspacing=\plainfrenchspacing % \def\endldots{\mathinner{\ldots\ldots\ldots\ldots}}% \def\enddots{\relax\ifmmode\endldots\else$\mathsurround=0pt \endldots\,$\fi}% \def\@{@}% } % There is no need to define \Etex. % Define @lisp ... @end lisp. % @lisp environment forms a group so it can rebind things, % including the definition of @end lisp (which normally is erroneous). % Amount to narrow the margins by for @lisp. \newskip\lispnarrowing \lispnarrowing=0.4in % This is the definition that ^^M gets inside @lisp, @example, and other % such environments. \null is better than a space, since it doesn't % have any width. \def\lisppar{\null\endgraf} % This space is always present above and below environments. \newskip\envskipamount \envskipamount = 0pt % Make spacing and below environment symmetrical. We use \parskip here % to help in doing that, since in @example-like environments \parskip % is reset to zero; thus the \afterenvbreak inserts no space -- but the % start of the next paragraph will insert \parskip. % \def\aboveenvbreak{{% % =10000 instead of <10000 because of a special case in \itemzzz and % \sectionheading, q.v. \ifnum \lastpenalty=10000 \else \advance\envskipamount by \parskip \endgraf \ifdim\lastskip<\envskipamount \removelastskip % it's not a good place to break if the last penalty was \nobreak % or better ... \ifnum\lastpenalty<10000 \penalty-50 \fi \vskip\envskipamount \fi \fi }} \let\afterenvbreak = \aboveenvbreak % \nonarrowing is a flag. If "set", @lisp etc don't narrow margins; it will % also clear it, so that its embedded environments do the narrowing again. \let\nonarrowing=\relax % @cartouche ... @end cartouche: draw rectangle w/rounded corners around % environment contents. \font\circle=lcircle10 \newdimen\circthick \newdimen\cartouter\newdimen\cartinner \newskip\normbskip\newskip\normpskip\newskip\normlskip \circthick=\fontdimen8\circle % \def\ctl{{\circle\char'013\hskip -6pt}}% 6pt from pl file: 1/2charwidth \def\ctr{{\hskip 6pt\circle\char'010}} \def\cbl{{\circle\char'012\hskip -6pt}} \def\cbr{{\hskip 6pt\circle\char'011}} \def\carttop{\hbox to \cartouter{\hskip\lskip \ctl\leaders\hrule height\circthick\hfil\ctr \hskip\rskip}} \def\cartbot{\hbox to \cartouter{\hskip\lskip \cbl\leaders\hrule height\circthick\hfil\cbr \hskip\rskip}} % \newskip\lskip\newskip\rskip \envdef\cartouche{% \ifhmode\par\fi % can't be in the midst of a paragraph. \startsavinginserts \lskip=\leftskip \rskip=\rightskip \leftskip=0pt\rightskip=0pt % we want these *outside*. \cartinner=\hsize \advance\cartinner by-\lskip \advance\cartinner by-\rskip \cartouter=\hsize \advance\cartouter by 18.4pt % allow for 3pt kerns on either % side, and for 6pt waste from % each corner char, and rule thickness \normbskip=\baselineskip \normpskip=\parskip \normlskip=\lineskip % Flag to tell @lisp, etc., not to narrow margin. \let\nonarrowing = t% \vbox\bgroup \baselineskip=0pt\parskip=0pt\lineskip=0pt \carttop \hbox\bgroup \hskip\lskip \vrule\kern3pt \vbox\bgroup \kern3pt \hsize=\cartinner \baselineskip=\normbskip \lineskip=\normlskip \parskip=\normpskip \vskip -\parskip \comment % For explanation, see the end of \def\group. } \def\Ecartouche{% \ifhmode\par\fi \kern3pt \egroup \kern3pt\vrule \hskip\rskip \egroup \cartbot \egroup \checkinserts } % This macro is called at the beginning of all the @example variants, % inside a group. \def\nonfillstart{% \aboveenvbreak \hfuzz = 12pt % Don't be fussy \sepspaces % Make spaces be word-separators rather than space tokens. \let\par = \lisppar % don't ignore blank lines \obeylines % each line of input is a line of output \parskip = 0pt \parindent = 0pt \emergencystretch = 0pt % don't try to avoid overfull boxes \ifx\nonarrowing\relax \advance \leftskip by \lispnarrowing \exdentamount=\lispnarrowing \else \let\nonarrowing = \relax \fi \let\exdent=\nofillexdent } % If you want all examples etc. small: @set dispenvsize small. % If you want even small examples the full size: @set dispenvsize nosmall. % This affects the following displayed environments: % @example, @display, @format, @lisp % \def\smallword{small} \def\nosmallword{nosmall} \let\SETdispenvsize\relax \def\setnormaldispenv{% \ifx\SETdispenvsize\smallword % end paragraph for sake of leading, in case document has no blank % line. This is redundant with what happens in \aboveenvbreak, but % we need to do it before changing the fonts, and it's inconvenient % to change the fonts afterward. \ifnum \lastpenalty=10000 \else \endgraf \fi \smallexamplefonts \rm \fi } \def\setsmalldispenv{% \ifx\SETdispenvsize\nosmallword \else \ifnum \lastpenalty=10000 \else \endgraf \fi \smallexamplefonts \rm \fi } % We often define two environments, @foo and @smallfoo. % Let's do it by one command: \def\makedispenv #1#2{ \expandafter\envdef\csname#1\endcsname {\setnormaldispenv #2} \expandafter\envdef\csname small#1\endcsname {\setsmalldispenv #2} \expandafter\let\csname E#1\endcsname \afterenvbreak \expandafter\let\csname Esmall#1\endcsname \afterenvbreak } % Define two synonyms: \def\maketwodispenvs #1#2#3{ \makedispenv{#1}{#3} \makedispenv{#2}{#3} } % @lisp: indented, narrowed, typewriter font; @example: same as @lisp. % % @smallexample and @smalllisp: use smaller fonts. % Originally contributed by Pavel@xerox. % \maketwodispenvs {lisp}{example}{% \nonfillstart \tt\quoteexpand \let\kbdfont = \kbdexamplefont % Allow @kbd to do something special. \gobble % eat return } % @display/@smalldisplay: same as @lisp except keep current font. % \makedispenv {display}{% \nonfillstart \gobble } % @format/@smallformat: same as @display except don't narrow margins. % \makedispenv{format}{% \let\nonarrowing = t% \nonfillstart \gobble } % @flushleft: same as @format, but doesn't obey \SETdispenvsize. \envdef\flushleft{% \let\nonarrowing = t% \nonfillstart \gobble } \let\Eflushleft = \afterenvbreak % @flushright. % \envdef\flushright{% \let\nonarrowing = t% \nonfillstart \advance\leftskip by 0pt plus 1fill \gobble } \let\Eflushright = \afterenvbreak % @quotation does normal linebreaking (hence we can't use \nonfillstart) % and narrows the margins. We keep \parskip nonzero in general, since % we're doing normal filling. So, when using \aboveenvbreak and % \afterenvbreak, temporarily make \parskip 0. % \envdef\quotation{% {\parskip=0pt \aboveenvbreak}% because \aboveenvbreak inserts \parskip \parindent=0pt % % @cartouche defines \nonarrowing to inhibit narrowing at next level down. \ifx\nonarrowing\relax \advance\leftskip by \lispnarrowing \advance\rightskip by \lispnarrowing \exdentamount = \lispnarrowing \else \let\nonarrowing = \relax \fi \parsearg\quotationlabel } % We have retained a nonzero parskip for the environment, since we're % doing normal filling. % \def\Equotation{% \par \ifx\quotationauthor\undefined\else % indent a bit. \leftline{\kern 2\leftskip \sl ---\quotationauthor}% \fi {\parskip=0pt \afterenvbreak}% } % If we're given an argument, typeset it in bold with a colon after. \def\quotationlabel#1{% \def\temp{#1}% \ifx\temp\empty \else {\bf #1: }% \fi } % LaTeX-like @verbatim...@end verbatim and @verb{...} % If we want to allow any as delimiter, % we need the curly braces so that makeinfo sees the @verb command, eg: % `@verbx...x' would look like the '@verbx' command. --janneke@gnu.org % % [Knuth]: Donald Ervin Knuth, 1996. The TeXbook. % % [Knuth] p.344; only we need to do the other characters Texinfo sets % active too. Otherwise, they get lost as the first character on a % verbatim line. \def\dospecials{% \do\ \do\\\do\{\do\}\do\$\do\&% \do\#\do\^\do\^^K\do\_\do\^^A\do\%\do\~% \do\<\do\>\do\|\do\@\do+\do\"% } % % [Knuth] p. 380 \def\uncatcodespecials{% \def\do##1{\catcode`##1=\other}\dospecials} % % [Knuth] pp. 380,381,391 % Disable Spanish ligatures ?` and !` of \tt font \begingroup \catcode`\`=\active\gdef`{\relax\lq} \endgroup % % Setup for the @verb command. % % Eight spaces for a tab \begingroup \catcode`\^^I=\active \gdef\tabeightspaces{\catcode`\^^I=\active\def^^I{\ \ \ \ \ \ \ \ }} \endgroup % \def\setupverb{% \tt % easiest (and conventionally used) font for verbatim \def\par{\leavevmode\endgraf}% \catcode`\`=\active \tabeightspaces % Respect line breaks, % print special symbols as themselves, and % make each space count % must do in this order: \obeylines \uncatcodespecials \sepspaces } % Setup for the @verbatim environment % % Real tab expansion \newdimen\tabw \setbox0=\hbox{\tt\space} \tabw=8\wd0 % tab amount % \def\starttabbox{\setbox0=\hbox\bgroup} % Allow an option to not replace quotes with a regular directed right % quote/apostrophe (char 0x27), but instead use the undirected quote % from cmtt (char 0x0d). The undirected quote is ugly, so don't make it % the default, but it works for pasting with more pdf viewers (at least % evince), the lilypond developers report. xpdf does work with the % regular 0x27. % \def\codequoteright{% \expandafter\ifx\csname SETcodequoteundirected\endcsname\relax '% \else \char'15 \fi } % % and a similar option for the left quote char vs. a grave accent. % Modern fonts display ASCII 0x60 as a grave accent, so some people like % the code environments to do likewise. % \def\codequoteleft{% \expandafter\ifx\csname SETcodequotebacktick\endcsname\relax `% \else \char'22 \fi } % \begingroup \catcode`\^^I=\active \gdef\tabexpand{% \catcode`\^^I=\active \def^^I{\leavevmode\egroup \dimen0=\wd0 % the width so far, or since the previous tab \divide\dimen0 by\tabw \multiply\dimen0 by\tabw % compute previous multiple of \tabw \advance\dimen0 by\tabw % advance to next multiple of \tabw \wd0=\dimen0 \box0 \starttabbox }% } \catcode`\'=\active \gdef\rquoteexpand{\catcode\rquoteChar=\active \def'{\codequoteright}}% % \catcode`\`=\active \gdef\lquoteexpand{\catcode\lquoteChar=\active \def`{\codequoteleft}}% % \gdef\quoteexpand{\rquoteexpand \lquoteexpand}% \endgroup % start the verbatim environment. \def\setupverbatim{% \let\nonarrowing = t% \nonfillstart % Easiest (and conventionally used) font for verbatim \tt \def\par{\leavevmode\egroup\box0\endgraf}% \catcode`\`=\active \tabexpand \quoteexpand % Respect line breaks, % print special symbols as themselves, and % make each space count % must do in this order: \obeylines \uncatcodespecials \sepspaces \everypar{\starttabbox}% } % Do the @verb magic: verbatim text is quoted by unique % delimiter characters. Before first delimiter expect a % right brace, after last delimiter expect closing brace: % % \def\doverb'{'#1'}'{#1} % % [Knuth] p. 382; only eat outer {} \begingroup \catcode`[=1\catcode`]=2\catcode`\{=\other\catcode`\}=\other \gdef\doverb{#1[\def\next##1#1}[##1\endgroup]\next] \endgroup % \def\verb{\begingroup\setupverb\doverb} % % % Do the @verbatim magic: define the macro \doverbatim so that % the (first) argument ends when '@end verbatim' is reached, ie: % % \def\doverbatim#1@end verbatim{#1} % % For Texinfo it's a lot easier than for LaTeX, % because texinfo's \verbatim doesn't stop at '\end{verbatim}': % we need not redefine '\', '{' and '}'. % % Inspired by LaTeX's verbatim command set [latex.ltx] % \begingroup \catcode`\ =\active \obeylines % % ignore everything up to the first ^^M, that's the newline at the end % of the @verbatim input line itself. Otherwise we get an extra blank % line in the output. \xdef\doverbatim#1^^M#2@end verbatim{#2\noexpand\end\gobble verbatim}% % We really want {...\end verbatim} in the body of the macro, but % without the active space; thus we have to use \xdef and \gobble. \endgroup % \envdef\verbatim{% \setupverbatim\doverbatim } \let\Everbatim = \afterenvbreak % @verbatiminclude FILE - insert text of file in verbatim environment. % \def\verbatiminclude{\parseargusing\filenamecatcodes\doverbatiminclude} % \def\doverbatiminclude#1{% {% \makevalueexpandable \setupverbatim \input #1 \afterenvbreak }% } % @copying ... @end copying. % Save the text away for @insertcopying later. % % We save the uninterpreted tokens, rather than creating a box. % Saving the text in a box would be much easier, but then all the % typesetting commands (@smallbook, font changes, etc.) have to be done % beforehand -- and a) we want @copying to be done first in the source % file; b) letting users define the frontmatter in as flexible order as % possible is very desirable. % \def\copying{\checkenv{}\begingroup\scanargctxt\docopying} \def\docopying#1@end copying{\endgroup\def\copyingtext{#1}} % \def\insertcopying{% \begingroup \parindent = 0pt % paragraph indentation looks wrong on title page \scanexp\copyingtext \endgroup } \message{defuns,} % @defun etc. \newskip\defbodyindent \defbodyindent=.4in \newskip\defargsindent \defargsindent=50pt \newskip\deflastargmargin \deflastargmargin=18pt % Start the processing of @deffn: \def\startdefun{% \ifnum\lastpenalty<10000 \medbreak \else % If there are two @def commands in a row, we'll have a \nobreak, % which is there to keep the function description together with its % header. But if there's nothing but headers, we need to allow a % break somewhere. Check specifically for penalty 10002, inserted % by \defargscommonending, instead of 10000, since the sectioning % commands also insert a nobreak penalty, and we don't want to allow % a break between a section heading and a defun. % \ifnum\lastpenalty=10002 \penalty2000 \fi % % Similarly, after a section heading, do not allow a break. % But do insert the glue. \medskip % preceded by discardable penalty, so not a breakpoint \fi % \parindent=0in \advance\leftskip by \defbodyindent \exdentamount=\defbodyindent } \def\dodefunx#1{% % First, check whether we are in the right environment: \checkenv#1% % % As above, allow line break if we have multiple x headers in a row. % It's not a great place, though. \ifnum\lastpenalty=10002 \penalty3000 \fi % % And now, it's time to reuse the body of the original defun: \expandafter\gobbledefun#1% } \def\gobbledefun#1\startdefun{} % \printdefunline \deffnheader{text} % \def\printdefunline#1#2{% \begingroup % call \deffnheader: #1#2 \endheader % common ending: \interlinepenalty = 10000 \advance\rightskip by 0pt plus 1fil \endgraf \nobreak\vskip -\parskip \penalty 10002 % signal to \startdefun and \dodefunx % Some of the @defun-type tags do not enable magic parentheses, % rendering the following check redundant. But we don't optimize. \checkparencounts \endgroup } \def\Edefun{\endgraf\medbreak} % \makedefun{deffn} creates \deffn, \deffnx and \Edeffn; % the only thing remainnig is to define \deffnheader. % \def\makedefun#1{% \expandafter\let\csname E#1\endcsname = \Edefun \edef\temp{\noexpand\domakedefun \makecsname{#1}\makecsname{#1x}\makecsname{#1header}}% \temp } % \domakedefun \deffn \deffnx \deffnheader % % Define \deffn and \deffnx, without parameters. % \deffnheader has to be defined explicitly. % \def\domakedefun#1#2#3{% \envdef#1{% \startdefun \parseargusing\activeparens{\printdefunline#3}% }% \def#2{\dodefunx#1}% \def#3% } %%% Untyped functions: % @deffn category name args \makedefun{deffn}{\deffngeneral{}} % @deffn category class name args \makedefun{defop}#1 {\defopon{#1\ \putwordon}} % \defopon {category on}class name args \def\defopon#1#2 {\deffngeneral{\putwordon\ \code{#2}}{#1\ \code{#2}} } % \deffngeneral {subind}category name args % \def\deffngeneral#1#2 #3 #4\endheader{% % Remember that \dosubind{fn}{foo}{} is equivalent to \doind{fn}{foo}. \dosubind{fn}{\code{#3}}{#1}% \defname{#2}{}{#3}\magicamp\defunargs{#4\unskip}% } %%% Typed functions: % @deftypefn category type name args \makedefun{deftypefn}{\deftypefngeneral{}} % @deftypeop category class type name args \makedefun{deftypeop}#1 {\deftypeopon{#1\ \putwordon}} % \deftypeopon {category on}class type name args \def\deftypeopon#1#2 {\deftypefngeneral{\putwordon\ \code{#2}}{#1\ \code{#2}} } % \deftypefngeneral {subind}category type name args % \def\deftypefngeneral#1#2 #3 #4 #5\endheader{% \dosubind{fn}{\code{#4}}{#1}% \defname{#2}{#3}{#4}\defunargs{#5\unskip}% } %%% Typed variables: % @deftypevr category type var args \makedefun{deftypevr}{\deftypecvgeneral{}} % @deftypecv category class type var args \makedefun{deftypecv}#1 {\deftypecvof{#1\ \putwordof}} % \deftypecvof {category of}class type var args \def\deftypecvof#1#2 {\deftypecvgeneral{\putwordof\ \code{#2}}{#1\ \code{#2}} } % \deftypecvgeneral {subind}category type var args % \def\deftypecvgeneral#1#2 #3 #4 #5\endheader{% \dosubind{vr}{\code{#4}}{#1}% \defname{#2}{#3}{#4}\defunargs{#5\unskip}% } %%% Untyped variables: % @defvr category var args \makedefun{defvr}#1 {\deftypevrheader{#1} {} } % @defcv category class var args \makedefun{defcv}#1 {\defcvof{#1\ \putwordof}} % \defcvof {category of}class var args \def\defcvof#1#2 {\deftypecvof{#1}#2 {} } %%% Type: % @deftp category name args \makedefun{deftp}#1 #2 #3\endheader{% \doind{tp}{\code{#2}}% \defname{#1}{}{#2}\defunargs{#3\unskip}% } % Remaining @defun-like shortcuts: \makedefun{defun}{\deffnheader{\putwordDeffunc} } \makedefun{defmac}{\deffnheader{\putwordDefmac} } \makedefun{defspec}{\deffnheader{\putwordDefspec} } \makedefun{deftypefun}{\deftypefnheader{\putwordDeffunc} } \makedefun{defvar}{\defvrheader{\putwordDefvar} } \makedefun{defopt}{\defvrheader{\putwordDefopt} } \makedefun{deftypevar}{\deftypevrheader{\putwordDefvar} } \makedefun{defmethod}{\defopon\putwordMethodon} \makedefun{deftypemethod}{\deftypeopon\putwordMethodon} \makedefun{defivar}{\defcvof\putwordInstanceVariableof} \makedefun{deftypeivar}{\deftypecvof\putwordInstanceVariableof} % \defname, which formats the name of the @def (not the args). % #1 is the category, such as "Function". % #2 is the return type, if any. % #3 is the function name. % % We are followed by (but not passed) the arguments, if any. % \def\defname#1#2#3{% % Get the values of \leftskip and \rightskip as they were outside the @def... \advance\leftskip by -\defbodyindent % % How we'll format the type name. Putting it in brackets helps % distinguish it from the body text that may end up on the next line % just below it. \def\temp{#1}% \setbox0=\hbox{\kern\deflastargmargin \ifx\temp\empty\else [\rm\temp]\fi} % % Figure out line sizes for the paragraph shape. % The first line needs space for \box0; but if \rightskip is nonzero, % we need only space for the part of \box0 which exceeds it: \dimen0=\hsize \advance\dimen0 by -\wd0 \advance\dimen0 by \rightskip % The continuations: \dimen2=\hsize \advance\dimen2 by -\defargsindent % (plain.tex says that \dimen1 should be used only as global.) \parshape 2 0in \dimen0 \defargsindent \dimen2 % % Put the type name to the right margin. \noindent \hbox to 0pt{% \hfil\box0 \kern-\hsize % \hsize has to be shortened this way: \kern\leftskip % Intentionally do not respect \rightskip, since we need the space. }% % % Allow all lines to be underfull without complaint: \tolerance=10000 \hbadness=10000 \exdentamount=\defbodyindent {% % defun fonts. We use typewriter by default (used to be bold) because: % . we're printing identifiers, they should be in tt in principle. % . in languages with many accents, such as Czech or French, it's % common to leave accents off identifiers. The result looks ok in % tt, but exceedingly strange in rm. % . we don't want -- and --- to be treated as ligatures. % . this still does not fix the ?` and !` ligatures, but so far no % one has made identifiers using them :). \df \tt \def\temp{#2}% return value type \ifx\temp\empty\else \tclose{\temp} \fi #3% output function name }% {\rm\enskip}% hskip 0.5 em of \tenrm % \boldbrax % arguments will be output next, if any. } % Print arguments in slanted roman (not ttsl), inconsistently with using % tt for the name. This is because literal text is sometimes needed in % the argument list (groff manual), and ttsl and tt are not very % distinguishable. Prevent hyphenation at `-' chars. % \def\defunargs#1{% % use sl by default (not ttsl), % tt for the names. \df \sl \hyphenchar\font=0 % % On the other hand, if an argument has two dashes (for instance), we % want a way to get ttsl. Let's try @var for that. \let\var=\ttslanted #1% \sl\hyphenchar\font=45 } % We want ()&[] to print specially on the defun line. % \def\activeparens{% \catcode`\(=\active \catcode`\)=\active \catcode`\[=\active \catcode`\]=\active \catcode`\&=\active } % Make control sequences which act like normal parenthesis chars. \let\lparen = ( \let\rparen = ) % Be sure that we always have a definition for `(', etc. For example, % if the fn name has parens in it, \boldbrax will not be in effect yet, % so TeX would otherwise complain about undefined control sequence. { \activeparens \global\let(=\lparen \global\let)=\rparen \global\let[=\lbrack \global\let]=\rbrack \global\let& = \& \gdef\boldbrax{\let(=\opnr\let)=\clnr\let[=\lbrb\let]=\rbrb} \gdef\magicamp{\let&=\amprm} } \newcount\parencount % If we encounter &foo, then turn on ()-hacking afterwards \newif\ifampseen \def\amprm#1 {\ampseentrue{\bf\ }} \def\parenfont{% \ifampseen % At the first level, print parens in roman, % otherwise use the default font. \ifnum \parencount=1 \rm \fi \else % The \sf parens (in \boldbrax) actually are a little bolder than % the contained text. This is especially needed for [ and ] . \sf \fi } \def\infirstlevel#1{% \ifampseen \ifnum\parencount=1 #1% \fi \fi } \def\bfafterword#1 {#1 \bf} \def\opnr{% \global\advance\parencount by 1 {\parenfont(}% \infirstlevel \bfafterword } \def\clnr{% {\parenfont)}% \infirstlevel \sl \global\advance\parencount by -1 } \newcount\brackcount \def\lbrb{% \global\advance\brackcount by 1 {\bf[}% } \def\rbrb{% {\bf]}% \global\advance\brackcount by -1 } \def\checkparencounts{% \ifnum\parencount=0 \else \badparencount \fi \ifnum\brackcount=0 \else \badbrackcount \fi } \def\badparencount{% \errmessage{Unbalanced parentheses in @def}% \global\parencount=0 } \def\badbrackcount{% \errmessage{Unbalanced square braces in @def}% \global\brackcount=0 } \message{macros,} % @macro. % To do this right we need a feature of e-TeX, \scantokens, % which we arrange to emulate with a temporary file in ordinary TeX. \ifx\eTeXversion\undefined \newwrite\macscribble \def\scantokens#1{% \toks0={#1}% \immediate\openout\macscribble=\jobname.tmp \immediate\write\macscribble{\the\toks0}% \immediate\closeout\macscribble \input \jobname.tmp } \fi \def\scanmacro#1{% \begingroup \newlinechar`\^^M \let\xeatspaces\eatspaces % Undo catcode changes of \startcontents and \doprintindex % When called from @insertcopying or (short)caption, we need active % backslash to get it printed correctly. Previously, we had % \catcode`\\=\other instead. We'll see whether a problem appears % with macro expansion. --kasal, 19aug04 \catcode`\@=0 \catcode`\\=\active \escapechar=`\@ % ... and \example \spaceisspace % % Append \endinput to make sure that TeX does not see the ending newline. % I've verified that it is necessary both for e-TeX and for ordinary TeX % --kasal, 29nov03 \scantokens{#1\endinput}% \endgroup } \def\scanexp#1{% \edef\temp{\noexpand\scanmacro{#1}}% \temp } \newcount\paramno % Count of parameters \newtoks\macname % Macro name \newif\ifrecursive % Is it recursive? % List of all defined macros in the form % \definedummyword\macro1\definedummyword\macro2... % Currently is also contains all @aliases; the list can be split % if there is a need. \def\macrolist{} % Add the macro to \macrolist \def\addtomacrolist#1{\expandafter \addtomacrolistxxx \csname#1\endcsname} \def\addtomacrolistxxx#1{% \toks0 = \expandafter{\macrolist\definedummyword#1}% \xdef\macrolist{\the\toks0}% } % Utility routines. % This does \let #1 = #2, with \csnames; that is, % \let \csname#1\endcsname = \csname#2\endcsname % (except of course we have to play expansion games). % \def\cslet#1#2{% \expandafter\let \csname#1\expandafter\endcsname \csname#2\endcsname } % Trim leading and trailing spaces off a string. % Concepts from aro-bend problem 15 (see CTAN). {\catcode`\@=11 \gdef\eatspaces #1{\expandafter\trim@\expandafter{#1 }} \gdef\trim@ #1{\trim@@ @#1 @ #1 @ @@} \gdef\trim@@ #1@ #2@ #3@@{\trim@@@\empty #2 @} \def\unbrace#1{#1} \unbrace{\gdef\trim@@@ #1 } #2@{#1} } % Trim a single trailing ^^M off a string. {\catcode`\^^M=\other \catcode`\Q=3% \gdef\eatcr #1{\eatcra #1Q^^MQ}% \gdef\eatcra#1^^MQ{\eatcrb#1Q}% \gdef\eatcrb#1Q#2Q{#1}% } % Macro bodies are absorbed as an argument in a context where % all characters are catcode 10, 11 or 12, except \ which is active % (as in normal texinfo). It is necessary to change the definition of \. % It's necessary to have hard CRs when the macro is executed. This is % done by making ^^M (\endlinechar) catcode 12 when reading the macro % body, and then making it the \newlinechar in \scanmacro. \def\scanctxt{% \catcode`\"=\other \catcode`\+=\other \catcode`\<=\other \catcode`\>=\other \catcode`\@=\other \catcode`\^=\other \catcode`\_=\other \catcode`\|=\other \catcode`\~=\other } \def\scanargctxt{% \scanctxt \catcode`\\=\other \catcode`\^^M=\other } \def\macrobodyctxt{% \scanctxt \catcode`\{=\other \catcode`\}=\other \catcode`\^^M=\other \usembodybackslash } \def\macroargctxt{% \scanctxt \catcode`\\=\other } % \mbodybackslash is the definition of \ in @macro bodies. % It maps \foo\ => \csname macarg.foo\endcsname => #N % where N is the macro parameter number. % We define \csname macarg.\endcsname to be \realbackslash, so % \\ in macro replacement text gets you a backslash. {\catcode`@=0 @catcode`@\=@active @gdef@usembodybackslash{@let\=@mbodybackslash} @gdef@mbodybackslash#1\{@csname macarg.#1@endcsname} } \expandafter\def\csname macarg.\endcsname{\realbackslash} \def\macro{\recursivefalse\parsearg\macroxxx} \def\rmacro{\recursivetrue\parsearg\macroxxx} \def\macroxxx#1{% \getargs{#1}% now \macname is the macname and \argl the arglist \ifx\argl\empty % no arguments \paramno=0% \else \expandafter\parsemargdef \argl;% \fi \if1\csname ismacro.\the\macname\endcsname \message{Warning: redefining \the\macname}% \else \expandafter\ifx\csname \the\macname\endcsname \relax \else \errmessage{Macro name \the\macname\space already defined}\fi \global\cslet{macsave.\the\macname}{\the\macname}% \global\expandafter\let\csname ismacro.\the\macname\endcsname=1% \addtomacrolist{\the\macname}% \fi \begingroup \macrobodyctxt \ifrecursive \expandafter\parsermacbody \else \expandafter\parsemacbody \fi} \parseargdef\unmacro{% \if1\csname ismacro.#1\endcsname \global\cslet{#1}{macsave.#1}% \global\expandafter\let \csname ismacro.#1\endcsname=0% % Remove the macro name from \macrolist: \begingroup \expandafter\let\csname#1\endcsname \relax \let\definedummyword\unmacrodo \xdef\macrolist{\macrolist}% \endgroup \else \errmessage{Macro #1 not defined}% \fi } % Called by \do from \dounmacro on each macro. The idea is to omit any % macro definitions that have been changed to \relax. % \def\unmacrodo#1{% \ifx #1\relax % remove this \else \noexpand\definedummyword \noexpand#1% \fi } % This makes use of the obscure feature that if the last token of a % is #, then the preceding argument is delimited by % an opening brace, and that opening brace is not consumed. \def\getargs#1{\getargsxxx#1{}} \def\getargsxxx#1#{\getmacname #1 \relax\getmacargs} \def\getmacname #1 #2\relax{\macname={#1}} \def\getmacargs#1{\def\argl{#1}} % Parse the optional {params} list. Set up \paramno and \paramlist % so \defmacro knows what to do. Define \macarg.blah for each blah % in the params list, to be ##N where N is the position in that list. % That gets used by \mbodybackslash (above). % We need to get `macro parameter char #' into several definitions. % The technique used is stolen from LaTeX: let \hash be something % unexpandable, insert that wherever you need a #, and then redefine % it to # just before using the token list produced. % % The same technique is used to protect \eatspaces till just before % the macro is used. \def\parsemargdef#1;{\paramno=0\def\paramlist{}% \let\hash\relax\let\xeatspaces\relax\parsemargdefxxx#1,;,} \def\parsemargdefxxx#1,{% \if#1;\let\next=\relax \else \let\next=\parsemargdefxxx \advance\paramno by 1% \expandafter\edef\csname macarg.\eatspaces{#1}\endcsname {\xeatspaces{\hash\the\paramno}}% \edef\paramlist{\paramlist\hash\the\paramno,}% \fi\next} % These two commands read recursive and nonrecursive macro bodies. % (They're different since rec and nonrec macros end differently.) \long\def\parsemacbody#1@end macro% {\xdef\temp{\eatcr{#1}}\endgroup\defmacro}% \long\def\parsermacbody#1@end rmacro% {\xdef\temp{\eatcr{#1}}\endgroup\defmacro}% % This defines the macro itself. There are six cases: recursive and % nonrecursive macros of zero, one, and many arguments. % Much magic with \expandafter here. % \xdef is used so that macro definitions will survive the file % they're defined in; @include reads the file inside a group. \def\defmacro{% \let\hash=##% convert placeholders to macro parameter chars \ifrecursive \ifcase\paramno % 0 \expandafter\xdef\csname\the\macname\endcsname{% \noexpand\scanmacro{\temp}}% \or % 1 \expandafter\xdef\csname\the\macname\endcsname{% \bgroup\noexpand\macroargctxt \noexpand\braceorline \expandafter\noexpand\csname\the\macname xxx\endcsname}% \expandafter\xdef\csname\the\macname xxx\endcsname##1{% \egroup\noexpand\scanmacro{\temp}}% \else % many \expandafter\xdef\csname\the\macname\endcsname{% \bgroup\noexpand\macroargctxt \noexpand\csname\the\macname xx\endcsname}% \expandafter\xdef\csname\the\macname xx\endcsname##1{% \expandafter\noexpand\csname\the\macname xxx\endcsname ##1,}% \expandafter\expandafter \expandafter\xdef \expandafter\expandafter \csname\the\macname xxx\endcsname \paramlist{\egroup\noexpand\scanmacro{\temp}}% \fi \else \ifcase\paramno % 0 \expandafter\xdef\csname\the\macname\endcsname{% \noexpand\norecurse{\the\macname}% \noexpand\scanmacro{\temp}\egroup}% \or % 1 \expandafter\xdef\csname\the\macname\endcsname{% \bgroup\noexpand\macroargctxt \noexpand\braceorline \expandafter\noexpand\csname\the\macname xxx\endcsname}% \expandafter\xdef\csname\the\macname xxx\endcsname##1{% \egroup \noexpand\norecurse{\the\macname}% \noexpand\scanmacro{\temp}\egroup}% \else % many \expandafter\xdef\csname\the\macname\endcsname{% \bgroup\noexpand\macroargctxt \expandafter\noexpand\csname\the\macname xx\endcsname}% \expandafter\xdef\csname\the\macname xx\endcsname##1{% \expandafter\noexpand\csname\the\macname xxx\endcsname ##1,}% \expandafter\expandafter \expandafter\xdef \expandafter\expandafter \csname\the\macname xxx\endcsname \paramlist{% \egroup \noexpand\norecurse{\the\macname}% \noexpand\scanmacro{\temp}\egroup}% \fi \fi} \def\norecurse#1{\bgroup\cslet{#1}{macsave.#1}} % \braceorline decides whether the next nonwhitespace character is a % {. If so it reads up to the closing }, if not, it reads the whole % line. Whatever was read is then fed to the next control sequence % as an argument (by \parsebrace or \parsearg) \def\braceorline#1{\let\macnamexxx=#1\futurelet\nchar\braceorlinexxx} \def\braceorlinexxx{% \ifx\nchar\bgroup\else \expandafter\parsearg \fi \macnamexxx} % @alias. % We need some trickery to remove the optional spaces around the equal % sign. Just make them active and then expand them all to nothing. \def\alias{\parseargusing\obeyspaces\aliasxxx} \def\aliasxxx #1{\aliasyyy#1\relax} \def\aliasyyy #1=#2\relax{% {% \expandafter\let\obeyedspace=\empty \addtomacrolist{#1}% \xdef\next{\global\let\makecsname{#1}=\makecsname{#2}}% }% \next } \message{cross references,} \newwrite\auxfile \newif\ifhavexrefs % True if xref values are known. \newif\ifwarnedxrefs % True if we warned once that they aren't known. % @inforef is relatively simple. \def\inforef #1{\inforefzzz #1,,,,**} \def\inforefzzz #1,#2,#3,#4**{\putwordSee{} \putwordInfo{} \putwordfile{} \file{\ignorespaces #3{}}, node \samp{\ignorespaces#1{}}} % @node's only job in TeX is to define \lastnode, which is used in % cross-references. The @node line might or might not have commas, and % might or might not have spaces before the first comma, like: % @node foo , bar , ... % We don't want such trailing spaces in the node name. % \parseargdef\node{\checkenv{}\donode #1 ,\finishnodeparse} % % also remove a trailing comma, in case of something like this: % @node Help-Cross, , , Cross-refs \def\donode#1 ,#2\finishnodeparse{\dodonode #1,\finishnodeparse} \def\dodonode#1,#2\finishnodeparse{\gdef\lastnode{#1}} \let\nwnode=\node \let\lastnode=\empty % Write a cross-reference definition for the current node. #1 is the % type (Ynumbered, Yappendix, Ynothing). % \def\donoderef#1{% \ifx\lastnode\empty\else \setref{\lastnode}{#1}% \global\let\lastnode=\empty \fi } % @anchor{NAME} -- define xref target at arbitrary point. % \newcount\savesfregister % \def\savesf{\relax \ifhmode \savesfregister=\spacefactor \fi} \def\restoresf{\relax \ifhmode \spacefactor=\savesfregister \fi} \def\anchor#1{\savesf \setref{#1}{Ynothing}\restoresf \ignorespaces} % \setref{NAME}{SNT} defines a cross-reference point NAME (a node or an % anchor), which consists of three parts: % 1) NAME-title - the current sectioning name taken from \thissection, % or the anchor name. % 2) NAME-snt - section number and type, passed as the SNT arg, or % empty for anchors. % 3) NAME-pg - the page number. % % This is called from \donoderef, \anchor, and \dofloat. In the case of % floats, there is an additional part, which is not written here: % 4) NAME-lof - the text as it should appear in a @listoffloats. % \def\setref#1#2{% \pdfmkdest{#1}% \iflinks {% \atdummies % preserve commands, but don't expand them \edef\writexrdef##1##2{% \write\auxfile{@xrdef{#1-% #1 of \setref, expanded by the \edef ##1}{##2}}% these are parameters of \writexrdef }% \toks0 = \expandafter{\thissection}% \immediate \writexrdef{title}{\the\toks0 }% \immediate \writexrdef{snt}{\csname #2\endcsname}% \Ynumbered etc. \writexrdef{pg}{\folio}% will be written later, during \shipout }% \fi } % @xref, @pxref, and @ref generate cross-references. For \xrefX, #1 is % the node name, #2 the name of the Info cross-reference, #3 the printed % node name, #4 the name of the Info file, #5 the name of the printed % manual. All but the node name can be omitted. % \def\pxref#1{\putwordsee{} \xrefX[#1,,,,,,,]} \def\xref#1{\putwordSee{} \xrefX[#1,,,,,,,]} \def\ref#1{\xrefX[#1,,,,,,,]} \def\xrefX[#1,#2,#3,#4,#5,#6]{\begingroup \unsepspaces \def\printedmanual{\ignorespaces #5}% \def\printedrefname{\ignorespaces #3}% \setbox1=\hbox{\printedmanual\unskip}% \setbox0=\hbox{\printedrefname\unskip}% \ifdim \wd0 = 0pt % No printed node name was explicitly given. \expandafter\ifx\csname SETxref-automatic-section-title\endcsname\relax % Use the node name inside the square brackets. \def\printedrefname{\ignorespaces #1}% \else % Use the actual chapter/section title appear inside % the square brackets. Use the real section title if we have it. \ifdim \wd1 > 0pt % It is in another manual, so we don't have it. \def\printedrefname{\ignorespaces #1}% \else \ifhavexrefs % We know the real title if we have the xref values. \def\printedrefname{\refx{#1-title}{}}% \else % Otherwise just copy the Info node name. \def\printedrefname{\ignorespaces #1}% \fi% \fi \fi \fi % % Make link in pdf output. \ifpdf \leavevmode \getfilename{#4}% {\indexnofonts \turnoffactive % See comments at \activebackslashdouble. {\activebackslashdouble \xdef\pdfxrefdest{#1}% \backslashparens\pdfxrefdest}% % \ifnum\filenamelength>0 \startlink attr{/Border [0 0 0]}% goto file{\the\filename.pdf} name{\pdfxrefdest}% \else \startlink attr{/Border [0 0 0]}% goto name{\pdfmkpgn{\pdfxrefdest}}% \fi }% \linkcolor \fi % % Float references are printed completely differently: "Figure 1.2" % instead of "[somenode], p.3". We distinguish them by the % LABEL-title being set to a magic string. {% % Have to otherify everything special to allow the \csname to % include an _ in the xref name, etc. \indexnofonts \turnoffactive \expandafter\global\expandafter\let\expandafter\Xthisreftitle \csname XR#1-title\endcsname }% \iffloat\Xthisreftitle % If the user specified the print name (third arg) to the ref, % print it instead of our usual "Figure 1.2". \ifdim\wd0 = 0pt \refx{#1-snt}{}% \else \printedrefname \fi % % if the user also gave the printed manual name (fifth arg), append % "in MANUALNAME". \ifdim \wd1 > 0pt \space \putwordin{} \cite{\printedmanual}% \fi \else % node/anchor (non-float) references. % % If we use \unhbox0 and \unhbox1 to print the node names, TeX does not % insert empty discretionaries after hyphens, which means that it will % not find a line break at a hyphen in a node names. Since some manuals % are best written with fairly long node names, containing hyphens, this % is a loss. Therefore, we give the text of the node name again, so it % is as if TeX is seeing it for the first time. \ifdim \wd1 > 0pt \putwordsection{} ``\printedrefname'' \putwordin{} \cite{\printedmanual}% \else % _ (for example) has to be the character _ for the purposes of the % control sequence corresponding to the node, but it has to expand % into the usual \leavevmode...\vrule stuff for purposes of % printing. So we \turnoffactive for the \refx-snt, back on for the % printing, back off for the \refx-pg. {\turnoffactive % Only output a following space if the -snt ref is nonempty; for % @unnumbered and @anchor, it won't be. \setbox2 = \hbox{\ignorespaces \refx{#1-snt}{}}% \ifdim \wd2 > 0pt \refx{#1-snt}\space\fi }% % output the `[mynode]' via a macro so it can be overridden. \xrefprintnodename\printedrefname % % But we always want a comma and a space: ,\space % % output the `page 3'. \turnoffactive \putwordpage\tie\refx{#1-pg}{}% \fi \fi \endlink \endgroup} % This macro is called from \xrefX for the `[nodename]' part of xref % output. It's a separate macro only so it can be changed more easily, % since square brackets don't work well in some documents. Particularly % one that Bob is working on :). % \def\xrefprintnodename#1{[#1]} % Things referred to by \setref. % \def\Ynothing{} \def\Yomitfromtoc{} \def\Ynumbered{% \ifnum\secno=0 \putwordChapter@tie \the\chapno \else \ifnum\subsecno=0 \putwordSection@tie \the\chapno.\the\secno \else \ifnum\subsubsecno=0 \putwordSection@tie \the\chapno.\the\secno.\the\subsecno \else \putwordSection@tie \the\chapno.\the\secno.\the\subsecno.\the\subsubsecno \fi\fi\fi } \def\Yappendix{% \ifnum\secno=0 \putwordAppendix@tie @char\the\appendixno{}% \else \ifnum\subsecno=0 \putwordSection@tie @char\the\appendixno.\the\secno \else \ifnum\subsubsecno=0 \putwordSection@tie @char\the\appendixno.\the\secno.\the\subsecno \else \putwordSection@tie @char\the\appendixno.\the\secno.\the\subsecno.\the\subsubsecno \fi\fi\fi } % Define \refx{NAME}{SUFFIX} to reference a cross-reference string named NAME. % If its value is nonempty, SUFFIX is output afterward. % \def\refx#1#2{% {% \indexnofonts \otherbackslash \expandafter\global\expandafter\let\expandafter\thisrefX \csname XR#1\endcsname }% \ifx\thisrefX\relax % If not defined, say something at least. \angleleft un\-de\-fined\angleright \iflinks \ifhavexrefs \message{\linenumber Undefined cross reference `#1'.}% \else \ifwarnedxrefs\else \global\warnedxrefstrue \message{Cross reference values unknown; you must run TeX again.}% \fi \fi \fi \else % It's defined, so just use it. \thisrefX \fi #2% Output the suffix in any case. } % This is the macro invoked by entries in the aux file. Usually it's % just a \def (we prepend XR to the control sequence name to avoid % collisions). But if this is a float type, we have more work to do. % \def\xrdef#1#2{% {% The node name might contain 8-bit characters, which in our current % implementation are changed to commands like @'e. Don't let these % mess up the control sequence name. \indexnofonts \turnoffactive \xdef\safexrefname{#1}% }% % \expandafter\gdef\csname XR\safexrefname\endcsname{#2}% remember this xref % % Was that xref control sequence that we just defined for a float? \expandafter\iffloat\csname XR\safexrefname\endcsname % it was a float, and we have the (safe) float type in \iffloattype. \expandafter\let\expandafter\floatlist \csname floatlist\iffloattype\endcsname % % Is this the first time we've seen this float type? \expandafter\ifx\floatlist\relax \toks0 = {\do}% yes, so just \do \else % had it before, so preserve previous elements in list. \toks0 = \expandafter{\floatlist\do}% \fi % % Remember this xref in the control sequence \floatlistFLOATTYPE, % for later use in \listoffloats. \expandafter\xdef\csname floatlist\iffloattype\endcsname{\the\toks0 {\safexrefname}}% \fi } % Read the last existing aux file, if any. No error if none exists. % \def\tryauxfile{% \openin 1 \jobname.aux \ifeof 1 \else \readdatafile{aux}% \global\havexrefstrue \fi \closein 1 } \def\setupdatafile{% \catcode`\^^@=\other \catcode`\^^A=\other \catcode`\^^B=\other \catcode`\^^C=\other \catcode`\^^D=\other \catcode`\^^E=\other \catcode`\^^F=\other \catcode`\^^G=\other \catcode`\^^H=\other \catcode`\^^K=\other \catcode`\^^L=\other \catcode`\^^N=\other \catcode`\^^P=\other \catcode`\^^Q=\other \catcode`\^^R=\other \catcode`\^^S=\other \catcode`\^^T=\other \catcode`\^^U=\other \catcode`\^^V=\other \catcode`\^^W=\other \catcode`\^^X=\other \catcode`\^^Z=\other \catcode`\^^[=\other \catcode`\^^\=\other \catcode`\^^]=\other \catcode`\^^^=\other \catcode`\^^_=\other % It was suggested to set the catcode of ^ to 7, which would allow ^^e4 etc. % in xref tags, i.e., node names. But since ^^e4 notation isn't % supported in the main text, it doesn't seem desirable. Furthermore, % that is not enough: for node names that actually contain a ^ % character, we would end up writing a line like this: 'xrdef {'hat % b-title}{'hat b} and \xrdef does a \csname...\endcsname on the first % argument, and \hat is not an expandable control sequence. It could % all be worked out, but why? Either we support ^^ or we don't. % % The other change necessary for this was to define \auxhat: % \def\auxhat{\def^{'hat }}% extra space so ok if followed by letter % and then to call \auxhat in \setq. % \catcode`\^=\other % % Special characters. Should be turned off anyway, but... \catcode`\~=\other \catcode`\[=\other \catcode`\]=\other \catcode`\"=\other \catcode`\_=\other \catcode`\|=\other \catcode`\<=\other \catcode`\>=\other \catcode`\$=\other \catcode`\#=\other \catcode`\&=\other \catcode`\%=\other \catcode`+=\other % avoid \+ for paranoia even though we've turned it off % % This is to support \ in node names and titles, since the \ % characters end up in a \csname. It's easier than % leaving it active and making its active definition an actual \ % character. What I don't understand is why it works in the *value* % of the xrdef. Seems like it should be a catcode12 \, and that % should not typeset properly. But it works, so I'm moving on for % now. --karl, 15jan04. \catcode`\\=\other % % Make the characters 128-255 be printing characters. {% \count1=128 \def\loop{% \catcode\count1=\other \advance\count1 by 1 \ifnum \count1<256 \loop \fi }% }% % % @ is our escape character in .aux files, and we need braces. \catcode`\{=1 \catcode`\}=2 \catcode`\@=0 } \def\readdatafile#1{% \begingroup \setupdatafile \input\jobname.#1 \endgroup} \message{insertions,} % including footnotes. \newcount \footnoteno % The trailing space in the following definition for supereject is % vital for proper filling; pages come out unaligned when you do a % pagealignmacro call if that space before the closing brace is % removed. (Generally, numeric constants should always be followed by a % space to prevent strange expansion errors.) \def\supereject{\par\penalty -20000\footnoteno =0 } % @footnotestyle is meaningful for info output only. \let\footnotestyle=\comment {\catcode `\@=11 % % Auto-number footnotes. Otherwise like plain. \gdef\footnote{% \let\indent=\ptexindent \let\noindent=\ptexnoindent \global\advance\footnoteno by \@ne \edef\thisfootno{$^{\the\footnoteno}$}% % % In case the footnote comes at the end of a sentence, preserve the % extra spacing after we do the footnote number. \let\@sf\empty \ifhmode\edef\@sf{\spacefactor\the\spacefactor}\ptexslash\fi % % Remove inadvertent blank space before typesetting the footnote number. \unskip \thisfootno\@sf \dofootnote }% % Don't bother with the trickery in plain.tex to not require the % footnote text as a parameter. Our footnotes don't need to be so general. % % Oh yes, they do; otherwise, @ifset (and anything else that uses % \parseargline) fails inside footnotes because the tokens are fixed when % the footnote is read. --karl, 16nov96. % \gdef\dofootnote{% \insert\footins\bgroup % We want to typeset this text as a normal paragraph, even if the % footnote reference occurs in (for example) a display environment. % So reset some parameters. \hsize=\pagewidth \interlinepenalty\interfootnotelinepenalty \splittopskip\ht\strutbox % top baseline for broken footnotes \splitmaxdepth\dp\strutbox \floatingpenalty\@MM \leftskip\z@skip \rightskip\z@skip \spaceskip\z@skip \xspaceskip\z@skip \parindent\defaultparindent % \smallfonts \rm % % Because we use hanging indentation in footnotes, a @noindent appears % to exdent this text, so make it be a no-op. makeinfo does not use % hanging indentation so @noindent can still be needed within footnote % text after an @example or the like (not that this is good style). \let\noindent = \relax % % Hang the footnote text off the number. Use \everypar in case the % footnote extends for more than one paragraph. \everypar = {\hang}% \textindent{\thisfootno}% % % Don't crash into the line above the footnote text. Since this % expands into a box, it must come within the paragraph, lest it % provide a place where TeX can split the footnote. \footstrut \futurelet\next\fo@t } }%end \catcode `\@=11 % In case a @footnote appears in a vbox, save the footnote text and create % the real \insert just after the vbox finished. Otherwise, the insertion % would be lost. % Similarily, if a @footnote appears inside an alignment, save the footnote % text to a box and make the \insert when a row of the table is finished. % And the same can be done for other insert classes. --kasal, 16nov03. % Replace the \insert primitive by a cheating macro. % Deeper inside, just make sure that the saved insertions are not spilled % out prematurely. % \def\startsavinginserts{% \ifx \insert\ptexinsert \let\insert\saveinsert \else \let\checkinserts\relax \fi } % This \insert replacement works for both \insert\footins{foo} and % \insert\footins\bgroup foo\egroup, but it doesn't work for \insert27{foo}. % \def\saveinsert#1{% \edef\next{\noexpand\savetobox \makeSAVEname#1}% \afterassignment\next % swallow the left brace \let\temp = } \def\makeSAVEname#1{\makecsname{SAVE\expandafter\gobble\string#1}} \def\savetobox#1{\global\setbox#1 = \vbox\bgroup \unvbox#1} \def\checksaveins#1{\ifvoid#1\else \placesaveins#1\fi} \def\placesaveins#1{% \ptexinsert \csname\expandafter\gobblesave\string#1\endcsname {\box#1}% } % eat @SAVE -- beware, all of them have catcode \other: { \def\dospecials{\do S\do A\do V\do E} \uncatcodespecials % ;-) \gdef\gobblesave @SAVE{} } % initialization: \def\newsaveins #1{% \edef\next{\noexpand\newsaveinsX \makeSAVEname#1}% \next } \def\newsaveinsX #1{% \csname newbox\endcsname #1% \expandafter\def\expandafter\checkinserts\expandafter{\checkinserts \checksaveins #1}% } % initialize: \let\checkinserts\empty \newsaveins\footins \newsaveins\margin % @image. We use the macros from epsf.tex to support this. % If epsf.tex is not installed and @image is used, we complain. % % Check for and read epsf.tex up front. If we read it only at @image % time, we might be inside a group, and then its definitions would get % undone and the next image would fail. \openin 1 = epsf.tex \ifeof 1 \else % Do not bother showing banner with epsf.tex v2.7k (available in % doc/epsf.tex and on ctan). \def\epsfannounce{\toks0 = }% \input epsf.tex \fi \closein 1 % % We will only complain once about lack of epsf.tex. \newif\ifwarnednoepsf \newhelp\noepsfhelp{epsf.tex must be installed for images to work. It is also included in the Texinfo distribution, or you can get it from ftp://tug.org/tex/epsf.tex.} % \def\image#1{% \ifx\epsfbox\undefined \ifwarnednoepsf \else \errhelp = \noepsfhelp \errmessage{epsf.tex not found, images will be ignored}% \global\warnednoepsftrue \fi \else \imagexxx #1,,,,,\finish \fi } % % Arguments to @image: % #1 is (mandatory) image filename; we tack on .eps extension. % #2 is (optional) width, #3 is (optional) height. % #4 is (ignored optional) html alt text. % #5 is (ignored optional) extension. % #6 is just the usual extra ignored arg for parsing this stuff. \newif\ifimagevmode \def\imagexxx#1,#2,#3,#4,#5,#6\finish{\begingroup \catcode`\^^M = 5 % in case we're inside an example \normalturnoffactive % allow _ et al. in names % If the image is by itself, center it. \ifvmode \imagevmodetrue \nobreak\bigskip % Usually we'll have text after the image which will insert % \parskip glue, so insert it here too to equalize the space % above and below. \nobreak\vskip\parskip \nobreak \line\bgroup \fi % % Output the image. \ifpdf \dopdfimage{#1}{#2}{#3}% \else % \epsfbox itself resets \epsf?size at each figure. \setbox0 = \hbox{\ignorespaces #2}\ifdim\wd0 > 0pt \epsfxsize=#2\relax \fi \setbox0 = \hbox{\ignorespaces #3}\ifdim\wd0 > 0pt \epsfysize=#3\relax \fi \epsfbox{#1.eps}% \fi % \ifimagevmode \egroup \bigbreak \fi % space after the image \endgroup} % @float FLOATTYPE,LABEL,LOC ... @end float for displayed figures, tables, % etc. We don't actually implement floating yet, we always include the % float "here". But it seemed the best name for the future. % \envparseargdef\float{\eatcommaspace\eatcommaspace\dofloat#1, , ,\finish} % There may be a space before second and/or third parameter; delete it. \def\eatcommaspace#1, {#1,} % #1 is the optional FLOATTYPE, the text label for this float, typically % "Figure", "Table", "Example", etc. Can't contain commas. If omitted, % this float will not be numbered and cannot be referred to. % % #2 is the optional xref label. Also must be present for the float to % be referable. % % #3 is the optional positioning argument; for now, it is ignored. It % will somehow specify the positions allowed to float to (here, top, bottom). % % We keep a separate counter for each FLOATTYPE, which we reset at each % chapter-level command. \let\resetallfloatnos=\empty % \def\dofloat#1,#2,#3,#4\finish{% \let\thiscaption=\empty \let\thisshortcaption=\empty % % don't lose footnotes inside @float. % % BEWARE: when the floats start float, we have to issue warning whenever an % insert appears inside a float which could possibly float. --kasal, 26may04 % \startsavinginserts % % We can't be used inside a paragraph. \par % \vtop\bgroup \def\floattype{#1}% \def\floatlabel{#2}% \def\floatloc{#3}% we do nothing with this yet. % \ifx\floattype\empty \let\safefloattype=\empty \else {% % the floattype might have accents or other special characters, % but we need to use it in a control sequence name. \indexnofonts \turnoffactive \xdef\safefloattype{\floattype}% }% \fi % % If label is given but no type, we handle that as the empty type. \ifx\floatlabel\empty \else % We want each FLOATTYPE to be numbered separately (Figure 1, % Table 1, Figure 2, ...). (And if no label, no number.) % \expandafter\getfloatno\csname\safefloattype floatno\endcsname \global\advance\floatno by 1 % {% % This magic value for \thissection is output by \setref as the % XREFLABEL-title value. \xrefX uses it to distinguish float % labels (which have a completely different output format) from % node and anchor labels. And \xrdef uses it to construct the % lists of floats. % \edef\thissection{\floatmagic=\safefloattype}% \setref{\floatlabel}{Yfloat}% }% \fi % % start with \parskip glue, I guess. \vskip\parskip % % Don't suppress indentation if a float happens to start a section. \restorefirstparagraphindent } % we have these possibilities: % @float Foo,lbl & @caption{Cap}: Foo 1.1: Cap % @float Foo,lbl & no caption: Foo 1.1 % @float Foo & @caption{Cap}: Foo: Cap % @float Foo & no caption: Foo % @float ,lbl & Caption{Cap}: 1.1: Cap % @float ,lbl & no caption: 1.1 % @float & @caption{Cap}: Cap % @float & no caption: % \def\Efloat{% \let\floatident = \empty % % In all cases, if we have a float type, it comes first. \ifx\floattype\empty \else \def\floatident{\floattype}\fi % % If we have an xref label, the number comes next. \ifx\floatlabel\empty \else \ifx\floattype\empty \else % if also had float type, need tie first. \appendtomacro\floatident{\tie}% \fi % the number. \appendtomacro\floatident{\chaplevelprefix\the\floatno}% \fi % % Start the printed caption with what we've constructed in % \floatident, but keep it separate; we need \floatident again. \let\captionline = \floatident % \ifx\thiscaption\empty \else \ifx\floatident\empty \else \appendtomacro\captionline{: }% had ident, so need a colon between \fi % % caption text. \appendtomacro\captionline{\scanexp\thiscaption}% \fi % % If we have anything to print, print it, with space before. % Eventually this needs to become an \insert. \ifx\captionline\empty \else \vskip.5\parskip \captionline % % Space below caption. \vskip\parskip \fi % % If have an xref label, write the list of floats info. Do this % after the caption, to avoid chance of it being a breakpoint. \ifx\floatlabel\empty \else % Write the text that goes in the lof to the aux file as % \floatlabel-lof. Besides \floatident, we include the short % caption if specified, else the full caption if specified, else nothing. {% \atdummies % % since we read the caption text in the macro world, where ^^M % is turned into a normal character, we have to scan it back, so % we don't write the literal three characters "^^M" into the aux file. \scanexp{% \xdef\noexpand\gtemp{% \ifx\thisshortcaption\empty \thiscaption \else \thisshortcaption \fi }% }% \immediate\write\auxfile{@xrdef{\floatlabel-lof}{\floatident \ifx\gtemp\empty \else : \gtemp \fi}}% }% \fi \egroup % end of \vtop % % place the captured inserts % % BEWARE: when the floats start floating, we have to issue warning % whenever an insert appears inside a float which could possibly % float. --kasal, 26may04 % \checkinserts } % Append the tokens #2 to the definition of macro #1, not expanding either. % \def\appendtomacro#1#2{% \expandafter\def\expandafter#1\expandafter{#1#2}% } % @caption, @shortcaption % \def\caption{\docaption\thiscaption} \def\shortcaption{\docaption\thisshortcaption} \def\docaption{\checkenv\float \bgroup\scanargctxt\defcaption} \def\defcaption#1#2{\egroup \def#1{#2}} % The parameter is the control sequence identifying the counter we are % going to use. Create it if it doesn't exist and assign it to \floatno. \def\getfloatno#1{% \ifx#1\relax % Haven't seen this figure type before. \csname newcount\endcsname #1% % % Remember to reset this floatno at the next chap. \expandafter\gdef\expandafter\resetallfloatnos \expandafter{\resetallfloatnos #1=0 }% \fi \let\floatno#1% } % \setref calls this to get the XREFLABEL-snt value. We want an @xref % to the FLOATLABEL to expand to "Figure 3.1". We call \setref when we % first read the @float command. % \def\Yfloat{\floattype@tie \chaplevelprefix\the\floatno}% % Magic string used for the XREFLABEL-title value, so \xrefX can % distinguish floats from other xref types. \def\floatmagic{!!float!!} % #1 is the control sequence we are passed; we expand into a conditional % which is true if #1 represents a float ref. That is, the magic % \thissection value which we \setref above. % \def\iffloat#1{\expandafter\doiffloat#1==\finish} % % #1 is (maybe) the \floatmagic string. If so, #2 will be the % (safe) float type for this float. We set \iffloattype to #2. % \def\doiffloat#1=#2=#3\finish{% \def\temp{#1}% \def\iffloattype{#2}% \ifx\temp\floatmagic } % @listoffloats FLOATTYPE - print a list of floats like a table of contents. % \parseargdef\listoffloats{% \def\floattype{#1}% floattype {% % the floattype might have accents or other special characters, % but we need to use it in a control sequence name. \indexnofonts \turnoffactive \xdef\safefloattype{\floattype}% }% % % \xrdef saves the floats as a \do-list in \floatlistSAFEFLOATTYPE. \expandafter\ifx\csname floatlist\safefloattype\endcsname \relax \ifhavexrefs % if the user said @listoffloats foo but never @float foo. \message{\linenumber No `\safefloattype' floats to list.}% \fi \else \begingroup \leftskip=\tocindent % indent these entries like a toc \let\do=\listoffloatsdo \csname floatlist\safefloattype\endcsname \endgroup \fi } % This is called on each entry in a list of floats. We're passed the % xref label, in the form LABEL-title, which is how we save it in the % aux file. We strip off the -title and look up \XRLABEL-lof, which % has the text we're supposed to typeset here. % % Figures without xref labels will not be included in the list (since % they won't appear in the aux file). % \def\listoffloatsdo#1{\listoffloatsdoentry#1\finish} \def\listoffloatsdoentry#1-title\finish{{% % Can't fully expand XR#1-lof because it can contain anything. Just % pass the control sequence. On the other hand, XR#1-pg is just the % page number, and we want to fully expand that so we can get a link % in pdf output. \toksA = \expandafter{\csname XR#1-lof\endcsname}% % % use the same \entry macro we use to generate the TOC and index. \edef\writeentry{\noexpand\entry{\the\toksA}{\csname XR#1-pg\endcsname}}% \writeentry }} \message{localization,} % @documentlanguage is usually given very early, just after % @setfilename. If done too late, it may not override everything % properly. Single argument is the language abbreviation. % It would be nice if we could set up a hyphenation file here. % \parseargdef\documentlanguage{% \tex % read txi-??.tex file in plain TeX. % Read the file if it exists. \openin 1 txi-#1.tex \ifeof 1 \errhelp = \nolanghelp \errmessage{Cannot read language file txi-#1.tex}% \else \input txi-#1.tex \fi \closein 1 \endgroup } \newhelp\nolanghelp{The given language definition file cannot be found or is empty. Maybe you need to install it? In the current directory should work if nowhere else does.} % Set the catcode of characters 128 through 255 to the specified number. % \def\setnonasciicharscatcode#1{% \count255=128 \loop\ifnum\count255<256 \global\catcode\count255=#1 \advance\count255 by 1 \repeat } % @documentencoding sets the definition of non-ASCII characters % according to the specified encoding. % \parseargdef\documentencoding{% % Encoding being declared for the document. \def\declaredencoding{\csname #1.enc\endcsname}% % % Supported encodings: names converted to tokens in order to be able % to compare them with \ifx. \def\ascii{\csname US-ASCII.enc\endcsname}% \def\latnine{\csname ISO-8859-15.enc\endcsname}% \def\latone{\csname ISO-8859-1.enc\endcsname}% \def\lattwo{\csname ISO-8859-2.enc\endcsname}% \def\utfeight{\csname UTF-8.enc\endcsname}% % \ifx \declaredencoding \ascii \asciichardefs % \else \ifx \declaredencoding \lattwo \setnonasciicharscatcode\active \lattwochardefs % \else \ifx \declaredencoding \latone \setnonasciicharscatcode\active \latonechardefs % \else \ifx \declaredencoding \latnine \setnonasciicharscatcode\active \latninechardefs % \else \ifx \declaredencoding \utfeight \setnonasciicharscatcode\active \utfeightchardefs % \else \message{Unknown document encoding #1, ignoring.}% % \fi % utfeight \fi % latnine \fi % latone \fi % lattwo \fi % ascii } % A message to be logged when using a character that isn't available % the default font encoding (OT1). % \def\missingcharmsg#1{\message{Character missing in OT1 encoding: #1.}} % Take account of \c (plain) vs. \, (Texinfo) difference. \def\cedilla#1{\ifx\c\ptexc\c{#1}\else\,{#1}\fi} % First, make active non-ASCII characters in order for them to be % correctly categorized when TeX reads the replacement text of % macros containing the character definitions. \setnonasciicharscatcode\active % % Latin1 (ISO-8859-1) character definitions. \def\latonechardefs{% \gdef^^a0{~} \gdef^^a1{\exclamdown} \gdef^^a2{\missingcharmsg{CENT SIGN}} \gdef^^a3{{\pounds}} \gdef^^a4{\missingcharmsg{CURRENCY SIGN}} \gdef^^a5{\missingcharmsg{YEN SIGN}} \gdef^^a6{\missingcharmsg{BROKEN BAR}} \gdef^^a7{\S} \gdef^^a8{\"{}} \gdef^^a9{\copyright} \gdef^^aa{\ordf} \gdef^^ab{\missingcharmsg{LEFT-POINTING DOUBLE ANGLE QUOTATION MARK}} \gdef^^ac{$\lnot$} \gdef^^ad{\-} \gdef^^ae{\registeredsymbol} \gdef^^af{\={}} % \gdef^^b0{\textdegree} \gdef^^b1{$\pm$} \gdef^^b2{$^2$} \gdef^^b3{$^3$} \gdef^^b4{\'{}} \gdef^^b5{$\mu$} \gdef^^b6{\P} % \gdef^^b7{$^.$} \gdef^^b8{\cedilla\ } \gdef^^b9{$^1$} \gdef^^ba{\ordm} % \gdef^^bb{\missingcharmsg{RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK}} \gdef^^bc{$1\over4$} \gdef^^bd{$1\over2$} \gdef^^be{$3\over4$} \gdef^^bf{\questiondown} % \gdef^^c0{\`A} \gdef^^c1{\'A} \gdef^^c2{\^A} \gdef^^c3{\~A} \gdef^^c4{\"A} \gdef^^c5{\ringaccent A} \gdef^^c6{\AE} \gdef^^c7{\cedilla C} \gdef^^c8{\`E} \gdef^^c9{\'E} \gdef^^ca{\^E} \gdef^^cb{\"E} \gdef^^cc{\`I} \gdef^^cd{\'I} \gdef^^ce{\^I} \gdef^^cf{\"I} % \gdef^^d0{\missingcharmsg{LATIN CAPITAL LETTER ETH}} \gdef^^d1{\~N} \gdef^^d2{\`O} \gdef^^d3{\'O} \gdef^^d4{\^O} \gdef^^d5{\~O} \gdef^^d6{\"O} \gdef^^d7{$\times$} \gdef^^d8{\O} \gdef^^d9{\`U} \gdef^^da{\'U} \gdef^^db{\^U} \gdef^^dc{\"U} \gdef^^dd{\'Y} \gdef^^de{\missingcharmsg{LATIN CAPITAL LETTER THORN}} \gdef^^df{\ss} % \gdef^^e0{\`a} \gdef^^e1{\'a} \gdef^^e2{\^a} \gdef^^e3{\~a} \gdef^^e4{\"a} \gdef^^e5{\ringaccent a} \gdef^^e6{\ae} \gdef^^e7{\cedilla c} \gdef^^e8{\`e} \gdef^^e9{\'e} \gdef^^ea{\^e} \gdef^^eb{\"e} \gdef^^ec{\`{\dotless i}} \gdef^^ed{\'{\dotless i}} \gdef^^ee{\^{\dotless i}} \gdef^^ef{\"{\dotless i}} % \gdef^^f0{\missingcharmsg{LATIN SMALL LETTER ETH}} \gdef^^f1{\~n} \gdef^^f2{\`o} \gdef^^f3{\'o} \gdef^^f4{\^o} \gdef^^f5{\~o} \gdef^^f6{\"o} \gdef^^f7{$\div$} \gdef^^f8{\o} \gdef^^f9{\`u} \gdef^^fa{\'u} \gdef^^fb{\^u} \gdef^^fc{\"u} \gdef^^fd{\'y} \gdef^^fe{\missingcharmsg{LATIN SMALL LETTER THORN}} \gdef^^ff{\"y} } % Latin9 (ISO-8859-15) encoding character definitions. \def\latninechardefs{% % Encoding is almost identical to Latin1. \latonechardefs % \gdef^^a4{\euro} \gdef^^a6{\v S} \gdef^^a8{\v s} \gdef^^b4{\v Z} \gdef^^b8{\v z} \gdef^^bc{\OE} \gdef^^bd{\oe} \gdef^^be{\"Y} } % Latin2 (ISO-8859-2) character definitions. \def\lattwochardefs{% \gdef^^a0{~} \gdef^^a1{\missingcharmsg{LATIN CAPITAL LETTER A WITH OGONEK}} \gdef^^a2{\u{}} \gdef^^a3{\L} \gdef^^a4{\missingcharmsg{CURRENCY SIGN}} \gdef^^a5{\v L} \gdef^^a6{\'S} \gdef^^a7{\S} \gdef^^a8{\"{}} \gdef^^a9{\v S} \gdef^^aa{\cedilla S} \gdef^^ab{\v T} \gdef^^ac{\'Z} \gdef^^ad{\-} \gdef^^ae{\v Z} \gdef^^af{\dotaccent Z} % \gdef^^b0{\textdegree} \gdef^^b1{\missingcharmsg{LATIN SMALL LETTER A WITH OGONEK}} \gdef^^b2{\missingcharmsg{OGONEK}} \gdef^^b3{\l} \gdef^^b4{\'{}} \gdef^^b5{\v l} \gdef^^b6{\'s} \gdef^^b7{\v{}} \gdef^^b8{\cedilla\ } \gdef^^b9{\v s} \gdef^^ba{\cedilla s} \gdef^^bb{\v t} \gdef^^bc{\'z} \gdef^^bd{\H{}} \gdef^^be{\v z} \gdef^^bf{\dotaccent z} % \gdef^^c0{\'R} \gdef^^c1{\'A} \gdef^^c2{\^A} \gdef^^c3{\u A} \gdef^^c4{\"A} \gdef^^c5{\'L} \gdef^^c6{\'C} \gdef^^c7{\cedilla C} \gdef^^c8{\v C} \gdef^^c9{\'E} \gdef^^ca{\missingcharmsg{LATIN CAPITAL LETTER E WITH OGONEK}} \gdef^^cb{\"E} \gdef^^cc{\v E} \gdef^^cd{\'I} \gdef^^ce{\^I} \gdef^^cf{\v D} % \gdef^^d0{\missingcharmsg{LATIN CAPITAL LETTER D WITH STROKE}} \gdef^^d1{\'N} \gdef^^d2{\v N} \gdef^^d3{\'O} \gdef^^d4{\^O} \gdef^^d5{\H O} \gdef^^d6{\"O} \gdef^^d7{$\times$} \gdef^^d8{\v R} \gdef^^d9{\ringaccent U} \gdef^^da{\'U} \gdef^^db{\H U} \gdef^^dc{\"U} \gdef^^dd{\'Y} \gdef^^de{\cedilla T} \gdef^^df{\ss} % \gdef^^e0{\'r} \gdef^^e1{\'a} \gdef^^e2{\^a} \gdef^^e3{\u a} \gdef^^e4{\"a} \gdef^^e5{\'l} \gdef^^e6{\'c} \gdef^^e7{\cedilla c} \gdef^^e8{\v c} \gdef^^e9{\'e} \gdef^^ea{\missingcharmsg{LATIN SMALL LETTER E WITH OGONEK}} \gdef^^eb{\"e} \gdef^^ec{\v e} \gdef^^ed{\'\i} \gdef^^ee{\^\i} \gdef^^ef{\v d} % \gdef^^f0{\missingcharmsg{LATIN SMALL LETTER D WITH STROKE}} \gdef^^f1{\'n} \gdef^^f2{\v n} \gdef^^f3{\'o} \gdef^^f4{\^o} \gdef^^f5{\H o} \gdef^^f6{\"o} \gdef^^f7{$\div$} \gdef^^f8{\v r} \gdef^^f9{\ringaccent u} \gdef^^fa{\'u} \gdef^^fb{\H u} \gdef^^fc{\"u} \gdef^^fd{\'y} \gdef^^fe{\cedilla t} \gdef^^ff{\dotaccent{}} } % UTF-8 character definitions. % % This code to support UTF-8 is based on LaTeX's utf8.def, with some % changes for Texinfo conventions. It is included here under the GPL by % permission from Frank Mittelbach and the LaTeX team. % \newcount\countUTFx \newcount\countUTFy \newcount\countUTFz \gdef\UTFviiiTwoOctets#1#2{\expandafter \UTFviiiDefined\csname u8:#1\string #2\endcsname} % \gdef\UTFviiiThreeOctets#1#2#3{\expandafter \UTFviiiDefined\csname u8:#1\string #2\string #3\endcsname} % \gdef\UTFviiiFourOctets#1#2#3#4{\expandafter \UTFviiiDefined\csname u8:#1\string #2\string #3\string #4\endcsname} \gdef\UTFviiiDefined#1{% \ifx #1\relax \message{\linenumber Unicode char \string #1 not defined for Texinfo}% \else \expandafter #1% \fi } \begingroup \catcode`\~13 \catcode`\"12 \def\UTFviiiLoop{% \global\catcode\countUTFx\active \uccode`\~\countUTFx \uppercase\expandafter{\UTFviiiTmp}% \advance\countUTFx by 1 \ifnum\countUTFx < \countUTFy \expandafter\UTFviiiLoop \fi} \countUTFx = "C2 \countUTFy = "E0 \def\UTFviiiTmp{% \xdef~{\noexpand\UTFviiiTwoOctets\string~}} \UTFviiiLoop \countUTFx = "E0 \countUTFy = "F0 \def\UTFviiiTmp{% \xdef~{\noexpand\UTFviiiThreeOctets\string~}} \UTFviiiLoop \countUTFx = "F0 \countUTFy = "F4 \def\UTFviiiTmp{% \xdef~{\noexpand\UTFviiiFourOctets\string~}} \UTFviiiLoop \endgroup \begingroup \catcode`\"=12 \catcode`\<=12 \catcode`\.=12 \catcode`\,=12 \catcode`\;=12 \catcode`\!=12 \catcode`\~=13 \gdef\DeclareUnicodeCharacter#1#2{% \countUTFz = "#1\relax \wlog{\space\space defining Unicode char U+#1 (decimal \the\countUTFz)}% \begingroup \parseXMLCharref \def\UTFviiiTwoOctets##1##2{% \csname u8:##1\string ##2\endcsname}% \def\UTFviiiThreeOctets##1##2##3{% \csname u8:##1\string ##2\string ##3\endcsname}% \def\UTFviiiFourOctets##1##2##3##4{% \csname u8:##1\string ##2\string ##3\string ##4\endcsname}% \expandafter\expandafter\expandafter\expandafter \expandafter\expandafter\expandafter \gdef\UTFviiiTmp{#2}% \endgroup} \gdef\parseXMLCharref{% \ifnum\countUTFz < "A0\relax \errhelp = \EMsimple \errmessage{Cannot define Unicode char value < 00A0}% \else\ifnum\countUTFz < "800\relax \parseUTFviiiA,% \parseUTFviiiB C\UTFviiiTwoOctets.,% \else\ifnum\countUTFz < "10000\relax \parseUTFviiiA;% \parseUTFviiiA,% \parseUTFviiiB E\UTFviiiThreeOctets.{,;}% \else \parseUTFviiiA;% \parseUTFviiiA,% \parseUTFviiiA!% \parseUTFviiiB F\UTFviiiFourOctets.{!,;}% \fi\fi\fi } \gdef\parseUTFviiiA#1{% \countUTFx = \countUTFz \divide\countUTFz by 64 \countUTFy = \countUTFz \multiply\countUTFz by 64 \advance\countUTFx by -\countUTFz \advance\countUTFx by 128 \uccode `#1\countUTFx \countUTFz = \countUTFy} \gdef\parseUTFviiiB#1#2#3#4{% \advance\countUTFz by "#10\relax \uccode `#3\countUTFz \uppercase{\gdef\UTFviiiTmp{#2#3#4}}} \endgroup \def\utfeightchardefs{% \DeclareUnicodeCharacter{00A0}{\tie} \DeclareUnicodeCharacter{00A1}{\exclamdown} \DeclareUnicodeCharacter{00A3}{\pounds} \DeclareUnicodeCharacter{00A8}{\"{ }} \DeclareUnicodeCharacter{00A9}{\copyright} \DeclareUnicodeCharacter{00AA}{\ordf} \DeclareUnicodeCharacter{00AD}{\-} \DeclareUnicodeCharacter{00AE}{\registeredsymbol} \DeclareUnicodeCharacter{00AF}{\={ }} \DeclareUnicodeCharacter{00B0}{\ringaccent{ }} \DeclareUnicodeCharacter{00B4}{\'{ }} \DeclareUnicodeCharacter{00B8}{\cedilla{ }} \DeclareUnicodeCharacter{00BA}{\ordm} \DeclareUnicodeCharacter{00BF}{\questiondown} \DeclareUnicodeCharacter{00C0}{\`A} \DeclareUnicodeCharacter{00C1}{\'A} \DeclareUnicodeCharacter{00C2}{\^A} \DeclareUnicodeCharacter{00C3}{\~A} \DeclareUnicodeCharacter{00C4}{\"A} \DeclareUnicodeCharacter{00C5}{\AA} \DeclareUnicodeCharacter{00C6}{\AE} \DeclareUnicodeCharacter{00C7}{\cedilla{C}} \DeclareUnicodeCharacter{00C8}{\`E} \DeclareUnicodeCharacter{00C9}{\'E} \DeclareUnicodeCharacter{00CA}{\^E} \DeclareUnicodeCharacter{00CB}{\"E} \DeclareUnicodeCharacter{00CC}{\`I} \DeclareUnicodeCharacter{00CD}{\'I} \DeclareUnicodeCharacter{00CE}{\^I} \DeclareUnicodeCharacter{00CF}{\"I} \DeclareUnicodeCharacter{00D1}{\~N} \DeclareUnicodeCharacter{00D2}{\`O} \DeclareUnicodeCharacter{00D3}{\'O} \DeclareUnicodeCharacter{00D4}{\^O} \DeclareUnicodeCharacter{00D5}{\~O} \DeclareUnicodeCharacter{00D6}{\"O} \DeclareUnicodeCharacter{00D8}{\O} \DeclareUnicodeCharacter{00D9}{\`U} \DeclareUnicodeCharacter{00DA}{\'U} \DeclareUnicodeCharacter{00DB}{\^U} \DeclareUnicodeCharacter{00DC}{\"U} \DeclareUnicodeCharacter{00DD}{\'Y} \DeclareUnicodeCharacter{00DF}{\ss} \DeclareUnicodeCharacter{00E0}{\`a} \DeclareUnicodeCharacter{00E1}{\'a} \DeclareUnicodeCharacter{00E2}{\^a} \DeclareUnicodeCharacter{00E3}{\~a} \DeclareUnicodeCharacter{00E4}{\"a} \DeclareUnicodeCharacter{00E5}{\aa} \DeclareUnicodeCharacter{00E6}{\ae} \DeclareUnicodeCharacter{00E7}{\cedilla{c}} \DeclareUnicodeCharacter{00E8}{\`e} \DeclareUnicodeCharacter{00E9}{\'e} \DeclareUnicodeCharacter{00EA}{\^e} \DeclareUnicodeCharacter{00EB}{\"e} \DeclareUnicodeCharacter{00EC}{\`{\dotless{i}}} \DeclareUnicodeCharacter{00ED}{\'{\dotless{i}}} \DeclareUnicodeCharacter{00EE}{\^{\dotless{i}}} \DeclareUnicodeCharacter{00EF}{\"{\dotless{i}}} \DeclareUnicodeCharacter{00F1}{\~n} \DeclareUnicodeCharacter{00F2}{\`o} \DeclareUnicodeCharacter{00F3}{\'o} \DeclareUnicodeCharacter{00F4}{\^o} \DeclareUnicodeCharacter{00F5}{\~o} \DeclareUnicodeCharacter{00F6}{\"o} \DeclareUnicodeCharacter{00F8}{\o} \DeclareUnicodeCharacter{00F9}{\`u} \DeclareUnicodeCharacter{00FA}{\'u} \DeclareUnicodeCharacter{00FB}{\^u} \DeclareUnicodeCharacter{00FC}{\"u} \DeclareUnicodeCharacter{00FD}{\'y} \DeclareUnicodeCharacter{00FF}{\"y} \DeclareUnicodeCharacter{0100}{\=A} \DeclareUnicodeCharacter{0101}{\=a} \DeclareUnicodeCharacter{0102}{\u{A}} \DeclareUnicodeCharacter{0103}{\u{a}} \DeclareUnicodeCharacter{0106}{\'C} \DeclareUnicodeCharacter{0107}{\'c} \DeclareUnicodeCharacter{0108}{\^C} \DeclareUnicodeCharacter{0109}{\^c} \DeclareUnicodeCharacter{010A}{\dotaccent{C}} \DeclareUnicodeCharacter{010B}{\dotaccent{c}} \DeclareUnicodeCharacter{010C}{\v{C}} \DeclareUnicodeCharacter{010D}{\v{c}} \DeclareUnicodeCharacter{010E}{\v{D}} \DeclareUnicodeCharacter{0112}{\=E} \DeclareUnicodeCharacter{0113}{\=e} \DeclareUnicodeCharacter{0114}{\u{E}} \DeclareUnicodeCharacter{0115}{\u{e}} \DeclareUnicodeCharacter{0116}{\dotaccent{E}} \DeclareUnicodeCharacter{0117}{\dotaccent{e}} \DeclareUnicodeCharacter{011A}{\v{E}} \DeclareUnicodeCharacter{011B}{\v{e}} \DeclareUnicodeCharacter{011C}{\^G} \DeclareUnicodeCharacter{011D}{\^g} \DeclareUnicodeCharacter{011E}{\u{G}} \DeclareUnicodeCharacter{011F}{\u{g}} \DeclareUnicodeCharacter{0120}{\dotaccent{G}} \DeclareUnicodeCharacter{0121}{\dotaccent{g}} \DeclareUnicodeCharacter{0124}{\^H} \DeclareUnicodeCharacter{0125}{\^h} \DeclareUnicodeCharacter{0128}{\~I} \DeclareUnicodeCharacter{0129}{\~{\dotless{i}}} \DeclareUnicodeCharacter{012A}{\=I} \DeclareUnicodeCharacter{012B}{\={\dotless{i}}} \DeclareUnicodeCharacter{012C}{\u{I}} \DeclareUnicodeCharacter{012D}{\u{\dotless{i}}} \DeclareUnicodeCharacter{0130}{\dotaccent{I}} \DeclareUnicodeCharacter{0131}{\dotless{i}} \DeclareUnicodeCharacter{0132}{IJ} \DeclareUnicodeCharacter{0133}{ij} \DeclareUnicodeCharacter{0134}{\^J} \DeclareUnicodeCharacter{0135}{\^{\dotless{j}}} \DeclareUnicodeCharacter{0139}{\'L} \DeclareUnicodeCharacter{013A}{\'l} \DeclareUnicodeCharacter{0141}{\L} \DeclareUnicodeCharacter{0142}{\l} \DeclareUnicodeCharacter{0143}{\'N} \DeclareUnicodeCharacter{0144}{\'n} \DeclareUnicodeCharacter{0147}{\v{N}} \DeclareUnicodeCharacter{0148}{\v{n}} \DeclareUnicodeCharacter{014C}{\=O} \DeclareUnicodeCharacter{014D}{\=o} \DeclareUnicodeCharacter{014E}{\u{O}} \DeclareUnicodeCharacter{014F}{\u{o}} \DeclareUnicodeCharacter{0150}{\H{O}} \DeclareUnicodeCharacter{0151}{\H{o}} \DeclareUnicodeCharacter{0152}{\OE} \DeclareUnicodeCharacter{0153}{\oe} \DeclareUnicodeCharacter{0154}{\'R} \DeclareUnicodeCharacter{0155}{\'r} \DeclareUnicodeCharacter{0158}{\v{R}} \DeclareUnicodeCharacter{0159}{\v{r}} \DeclareUnicodeCharacter{015A}{\'S} \DeclareUnicodeCharacter{015B}{\'s} \DeclareUnicodeCharacter{015C}{\^S} \DeclareUnicodeCharacter{015D}{\^s} \DeclareUnicodeCharacter{015E}{\cedilla{S}} \DeclareUnicodeCharacter{015F}{\cedilla{s}} \DeclareUnicodeCharacter{0160}{\v{S}} \DeclareUnicodeCharacter{0161}{\v{s}} \DeclareUnicodeCharacter{0162}{\cedilla{t}} \DeclareUnicodeCharacter{0163}{\cedilla{T}} \DeclareUnicodeCharacter{0164}{\v{T}} \DeclareUnicodeCharacter{0168}{\~U} \DeclareUnicodeCharacter{0169}{\~u} \DeclareUnicodeCharacter{016A}{\=U} \DeclareUnicodeCharacter{016B}{\=u} \DeclareUnicodeCharacter{016C}{\u{U}} \DeclareUnicodeCharacter{016D}{\u{u}} \DeclareUnicodeCharacter{016E}{\ringaccent{U}} \DeclareUnicodeCharacter{016F}{\ringaccent{u}} \DeclareUnicodeCharacter{0170}{\H{U}} \DeclareUnicodeCharacter{0171}{\H{u}} \DeclareUnicodeCharacter{0174}{\^W} \DeclareUnicodeCharacter{0175}{\^w} \DeclareUnicodeCharacter{0176}{\^Y} \DeclareUnicodeCharacter{0177}{\^y} \DeclareUnicodeCharacter{0178}{\"Y} \DeclareUnicodeCharacter{0179}{\'Z} \DeclareUnicodeCharacter{017A}{\'z} \DeclareUnicodeCharacter{017B}{\dotaccent{Z}} \DeclareUnicodeCharacter{017C}{\dotaccent{z}} \DeclareUnicodeCharacter{017D}{\v{Z}} \DeclareUnicodeCharacter{017E}{\v{z}} \DeclareUnicodeCharacter{01C4}{D\v{Z}} \DeclareUnicodeCharacter{01C5}{D\v{z}} \DeclareUnicodeCharacter{01C6}{d\v{z}} \DeclareUnicodeCharacter{01C7}{LJ} \DeclareUnicodeCharacter{01C8}{Lj} \DeclareUnicodeCharacter{01C9}{lj} \DeclareUnicodeCharacter{01CA}{NJ} \DeclareUnicodeCharacter{01CB}{Nj} \DeclareUnicodeCharacter{01CC}{nj} \DeclareUnicodeCharacter{01CD}{\v{A}} \DeclareUnicodeCharacter{01CE}{\v{a}} \DeclareUnicodeCharacter{01CF}{\v{I}} \DeclareUnicodeCharacter{01D0}{\v{\dotless{i}}} \DeclareUnicodeCharacter{01D1}{\v{O}} \DeclareUnicodeCharacter{01D2}{\v{o}} \DeclareUnicodeCharacter{01D3}{\v{U}} \DeclareUnicodeCharacter{01D4}{\v{u}} \DeclareUnicodeCharacter{01E2}{\={\AE}} \DeclareUnicodeCharacter{01E3}{\={\ae}} \DeclareUnicodeCharacter{01E6}{\v{G}} \DeclareUnicodeCharacter{01E7}{\v{g}} \DeclareUnicodeCharacter{01E8}{\v{K}} \DeclareUnicodeCharacter{01E9}{\v{k}} \DeclareUnicodeCharacter{01F0}{\v{\dotless{j}}} \DeclareUnicodeCharacter{01F1}{DZ} \DeclareUnicodeCharacter{01F2}{Dz} \DeclareUnicodeCharacter{01F3}{dz} \DeclareUnicodeCharacter{01F4}{\'G} \DeclareUnicodeCharacter{01F5}{\'g} \DeclareUnicodeCharacter{01F8}{\`N} \DeclareUnicodeCharacter{01F9}{\`n} \DeclareUnicodeCharacter{01FC}{\'{\AE}} \DeclareUnicodeCharacter{01FD}{\'{\ae}} \DeclareUnicodeCharacter{01FE}{\'{\O}} \DeclareUnicodeCharacter{01FF}{\'{\o}} \DeclareUnicodeCharacter{021E}{\v{H}} \DeclareUnicodeCharacter{021F}{\v{h}} \DeclareUnicodeCharacter{0226}{\dotaccent{A}} \DeclareUnicodeCharacter{0227}{\dotaccent{a}} \DeclareUnicodeCharacter{0228}{\cedilla{E}} \DeclareUnicodeCharacter{0229}{\cedilla{e}} \DeclareUnicodeCharacter{022E}{\dotaccent{O}} \DeclareUnicodeCharacter{022F}{\dotaccent{o}} \DeclareUnicodeCharacter{0232}{\=Y} \DeclareUnicodeCharacter{0233}{\=y} \DeclareUnicodeCharacter{0237}{\dotless{j}} \DeclareUnicodeCharacter{1E02}{\dotaccent{B}} \DeclareUnicodeCharacter{1E03}{\dotaccent{b}} \DeclareUnicodeCharacter{1E04}{\udotaccent{B}} \DeclareUnicodeCharacter{1E05}{\udotaccent{b}} \DeclareUnicodeCharacter{1E06}{\ubaraccent{B}} \DeclareUnicodeCharacter{1E07}{\ubaraccent{b}} \DeclareUnicodeCharacter{1E0A}{\dotaccent{D}} \DeclareUnicodeCharacter{1E0B}{\dotaccent{d}} \DeclareUnicodeCharacter{1E0C}{\udotaccent{D}} \DeclareUnicodeCharacter{1E0D}{\udotaccent{d}} \DeclareUnicodeCharacter{1E0E}{\ubaraccent{D}} \DeclareUnicodeCharacter{1E0F}{\ubaraccent{d}} \DeclareUnicodeCharacter{1E1E}{\dotaccent{F}} \DeclareUnicodeCharacter{1E1F}{\dotaccent{f}} \DeclareUnicodeCharacter{1E20}{\=G} \DeclareUnicodeCharacter{1E21}{\=g} \DeclareUnicodeCharacter{1E22}{\dotaccent{H}} \DeclareUnicodeCharacter{1E23}{\dotaccent{h}} \DeclareUnicodeCharacter{1E24}{\udotaccent{H}} \DeclareUnicodeCharacter{1E25}{\udotaccent{h}} \DeclareUnicodeCharacter{1E26}{\"H} \DeclareUnicodeCharacter{1E27}{\"h} \DeclareUnicodeCharacter{1E30}{\'K} \DeclareUnicodeCharacter{1E31}{\'k} \DeclareUnicodeCharacter{1E32}{\udotaccent{K}} \DeclareUnicodeCharacter{1E33}{\udotaccent{k}} \DeclareUnicodeCharacter{1E34}{\ubaraccent{K}} \DeclareUnicodeCharacter{1E35}{\ubaraccent{k}} \DeclareUnicodeCharacter{1E36}{\udotaccent{L}} \DeclareUnicodeCharacter{1E37}{\udotaccent{l}} \DeclareUnicodeCharacter{1E3A}{\ubaraccent{L}} \DeclareUnicodeCharacter{1E3B}{\ubaraccent{l}} \DeclareUnicodeCharacter{1E3E}{\'M} \DeclareUnicodeCharacter{1E3F}{\'m} \DeclareUnicodeCharacter{1E40}{\dotaccent{M}} \DeclareUnicodeCharacter{1E41}{\dotaccent{m}} \DeclareUnicodeCharacter{1E42}{\udotaccent{M}} \DeclareUnicodeCharacter{1E43}{\udotaccent{m}} \DeclareUnicodeCharacter{1E44}{\dotaccent{N}} \DeclareUnicodeCharacter{1E45}{\dotaccent{n}} \DeclareUnicodeCharacter{1E46}{\udotaccent{N}} \DeclareUnicodeCharacter{1E47}{\udotaccent{n}} \DeclareUnicodeCharacter{1E48}{\ubaraccent{N}} \DeclareUnicodeCharacter{1E49}{\ubaraccent{n}} \DeclareUnicodeCharacter{1E54}{\'P} \DeclareUnicodeCharacter{1E55}{\'p} \DeclareUnicodeCharacter{1E56}{\dotaccent{P}} \DeclareUnicodeCharacter{1E57}{\dotaccent{p}} \DeclareUnicodeCharacter{1E58}{\dotaccent{R}} \DeclareUnicodeCharacter{1E59}{\dotaccent{r}} \DeclareUnicodeCharacter{1E5A}{\udotaccent{R}} \DeclareUnicodeCharacter{1E5B}{\udotaccent{r}} \DeclareUnicodeCharacter{1E5E}{\ubaraccent{R}} \DeclareUnicodeCharacter{1E5F}{\ubaraccent{r}} \DeclareUnicodeCharacter{1E60}{\dotaccent{S}} \DeclareUnicodeCharacter{1E61}{\dotaccent{s}} \DeclareUnicodeCharacter{1E62}{\udotaccent{S}} \DeclareUnicodeCharacter{1E63}{\udotaccent{s}} \DeclareUnicodeCharacter{1E6A}{\dotaccent{T}} \DeclareUnicodeCharacter{1E6B}{\dotaccent{t}} \DeclareUnicodeCharacter{1E6C}{\udotaccent{T}} \DeclareUnicodeCharacter{1E6D}{\udotaccent{t}} \DeclareUnicodeCharacter{1E6E}{\ubaraccent{T}} \DeclareUnicodeCharacter{1E6F}{\ubaraccent{t}} \DeclareUnicodeCharacter{1E7C}{\~V} \DeclareUnicodeCharacter{1E7D}{\~v} \DeclareUnicodeCharacter{1E7E}{\udotaccent{V}} \DeclareUnicodeCharacter{1E7F}{\udotaccent{v}} \DeclareUnicodeCharacter{1E80}{\`W} \DeclareUnicodeCharacter{1E81}{\`w} \DeclareUnicodeCharacter{1E82}{\'W} \DeclareUnicodeCharacter{1E83}{\'w} \DeclareUnicodeCharacter{1E84}{\"W} \DeclareUnicodeCharacter{1E85}{\"w} \DeclareUnicodeCharacter{1E86}{\dotaccent{W}} \DeclareUnicodeCharacter{1E87}{\dotaccent{w}} \DeclareUnicodeCharacter{1E88}{\udotaccent{W}} \DeclareUnicodeCharacter{1E89}{\udotaccent{w}} \DeclareUnicodeCharacter{1E8A}{\dotaccent{X}} \DeclareUnicodeCharacter{1E8B}{\dotaccent{x}} \DeclareUnicodeCharacter{1E8C}{\"X} \DeclareUnicodeCharacter{1E8D}{\"x} \DeclareUnicodeCharacter{1E8E}{\dotaccent{Y}} \DeclareUnicodeCharacter{1E8F}{\dotaccent{y}} \DeclareUnicodeCharacter{1E90}{\^Z} \DeclareUnicodeCharacter{1E91}{\^z} \DeclareUnicodeCharacter{1E92}{\udotaccent{Z}} \DeclareUnicodeCharacter{1E93}{\udotaccent{z}} \DeclareUnicodeCharacter{1E94}{\ubaraccent{Z}} \DeclareUnicodeCharacter{1E95}{\ubaraccent{z}} \DeclareUnicodeCharacter{1E96}{\ubaraccent{h}} \DeclareUnicodeCharacter{1E97}{\"t} \DeclareUnicodeCharacter{1E98}{\ringaccent{w}} \DeclareUnicodeCharacter{1E99}{\ringaccent{y}} \DeclareUnicodeCharacter{1EA0}{\udotaccent{A}} \DeclareUnicodeCharacter{1EA1}{\udotaccent{a}} \DeclareUnicodeCharacter{1EB8}{\udotaccent{E}} \DeclareUnicodeCharacter{1EB9}{\udotaccent{e}} \DeclareUnicodeCharacter{1EBC}{\~E} \DeclareUnicodeCharacter{1EBD}{\~e} \DeclareUnicodeCharacter{1ECA}{\udotaccent{I}} \DeclareUnicodeCharacter{1ECB}{\udotaccent{i}} \DeclareUnicodeCharacter{1ECC}{\udotaccent{O}} \DeclareUnicodeCharacter{1ECD}{\udotaccent{o}} \DeclareUnicodeCharacter{1EE4}{\udotaccent{U}} \DeclareUnicodeCharacter{1EE5}{\udotaccent{u}} \DeclareUnicodeCharacter{1EF2}{\`Y} \DeclareUnicodeCharacter{1EF3}{\`y} \DeclareUnicodeCharacter{1EF4}{\udotaccent{Y}} \DeclareUnicodeCharacter{1EF8}{\~Y} \DeclareUnicodeCharacter{1EF9}{\~y} \DeclareUnicodeCharacter{2013}{--} \DeclareUnicodeCharacter{2014}{---} \DeclareUnicodeCharacter{2022}{\bullet} \DeclareUnicodeCharacter{2026}{\dots} \DeclareUnicodeCharacter{20AC}{\euro} \DeclareUnicodeCharacter{2192}{\expansion} \DeclareUnicodeCharacter{21D2}{\result} \DeclareUnicodeCharacter{2212}{\minus} \DeclareUnicodeCharacter{2217}{\point} \DeclareUnicodeCharacter{2261}{\equiv} }% end of \utfeightchardefs % US-ASCII character definitions. \def\asciichardefs{% nothing need be done \relax } % Make non-ASCII characters printable again for compatibility with % existing Texinfo documents that may use them, even without declaring a % document encoding. % \setnonasciicharscatcode \other \message{formatting,} \newdimen\defaultparindent \defaultparindent = 15pt \chapheadingskip = 15pt plus 4pt minus 2pt \secheadingskip = 12pt plus 3pt minus 2pt \subsecheadingskip = 9pt plus 2pt minus 2pt % Prevent underfull vbox error messages. \vbadness = 10000 % Don't be so finicky about underfull hboxes, either. \hbadness = 2000 % Following George Bush, just get rid of widows and orphans. \widowpenalty=10000 \clubpenalty=10000 % Use TeX 3.0's \emergencystretch to help line breaking, but if we're % using an old version of TeX, don't do anything. We want the amount of % stretch added to depend on the line length, hence the dependence on % \hsize. We call this whenever the paper size is set. % \def\setemergencystretch{% \ifx\emergencystretch\thisisundefined % Allow us to assign to \emergencystretch anyway. \def\emergencystretch{\dimen0}% \else \emergencystretch = .15\hsize \fi } % Parameters in order: 1) textheight; 2) textwidth; % 3) voffset; 4) hoffset; 5) binding offset; 6) topskip; % 7) physical page height; 8) physical page width. % % We also call \setleading{\textleading}, so the caller should define % \textleading. The caller should also set \parskip. % \def\internalpagesizes#1#2#3#4#5#6#7#8{% \voffset = #3\relax \topskip = #6\relax \splittopskip = \topskip % \vsize = #1\relax \advance\vsize by \topskip \outervsize = \vsize \advance\outervsize by 2\topandbottommargin \pageheight = \vsize % \hsize = #2\relax \outerhsize = \hsize \advance\outerhsize by 0.5in \pagewidth = \hsize % \normaloffset = #4\relax \bindingoffset = #5\relax % \ifpdf \pdfpageheight #7\relax \pdfpagewidth #8\relax \fi % \setleading{\textleading} % \parindent = \defaultparindent \setemergencystretch } % @letterpaper (the default). \def\letterpaper{{\globaldefs = 1 \parskip = 3pt plus 2pt minus 1pt \textleading = 13.2pt % % If page is nothing but text, make it come out even. \internalpagesizes{46\baselineskip}{6in}% {\voffset}{.25in}% {\bindingoffset}{36pt}% {11in}{8.5in}% }} % Use @smallbook to reset parameters for 7x9.25 trim size. \def\smallbook{{\globaldefs = 1 \parskip = 2pt plus 1pt \textleading = 12pt % \internalpagesizes{7.5in}{5in}% {\voffset}{.25in}% {\bindingoffset}{16pt}% {9.25in}{7in}% % \lispnarrowing = 0.3in \tolerance = 700 \hfuzz = 1pt \contentsrightmargin = 0pt \defbodyindent = .5cm }} % Use @smallerbook to reset parameters for 6x9 trim size. % (Just testing, parameters still in flux.) \def\smallerbook{{\globaldefs = 1 \parskip = 1.5pt plus 1pt \textleading = 12pt % \internalpagesizes{7.4in}{4.8in}% {-.2in}{-.4in}% {0pt}{14pt}% {9in}{6in}% % \lispnarrowing = 0.25in \tolerance = 700 \hfuzz = 1pt \contentsrightmargin = 0pt \defbodyindent = .4cm }} % Use @afourpaper to print on European A4 paper. \def\afourpaper{{\globaldefs = 1 \parskip = 3pt plus 2pt minus 1pt \textleading = 13.2pt % % Double-side printing via postscript on Laserjet 4050 % prints double-sided nicely when \bindingoffset=10mm and \hoffset=-6mm. % To change the settings for a different printer or situation, adjust % \normaloffset until the front-side and back-side texts align. Then % do the same for \bindingoffset. You can set these for testing in % your texinfo source file like this: % @tex % \global\normaloffset = -6mm % \global\bindingoffset = 10mm % @end tex \internalpagesizes{51\baselineskip}{160mm} {\voffset}{\hoffset}% {\bindingoffset}{44pt}% {297mm}{210mm}% % \tolerance = 700 \hfuzz = 1pt \contentsrightmargin = 0pt \defbodyindent = 5mm }} % Use @afivepaper to print on European A5 paper. % From romildo@urano.iceb.ufop.br, 2 July 2000. % He also recommends making @example and @lisp be small. \def\afivepaper{{\globaldefs = 1 \parskip = 2pt plus 1pt minus 0.1pt \textleading = 12.5pt % \internalpagesizes{160mm}{120mm}% {\voffset}{\hoffset}% {\bindingoffset}{8pt}% {210mm}{148mm}% % \lispnarrowing = 0.2in \tolerance = 800 \hfuzz = 1.2pt \contentsrightmargin = 0pt \defbodyindent = 2mm \tableindent = 12mm }} % A specific text layout, 24x15cm overall, intended for A4 paper. \def\afourlatex{{\globaldefs = 1 \afourpaper \internalpagesizes{237mm}{150mm}% {\voffset}{4.6mm}% {\bindingoffset}{7mm}% {297mm}{210mm}% % % Must explicitly reset to 0 because we call \afourpaper. \globaldefs = 0 }} % Use @afourwide to print on A4 paper in landscape format. \def\afourwide{{\globaldefs = 1 \afourpaper \internalpagesizes{241mm}{165mm}% {\voffset}{-2.95mm}% {\bindingoffset}{7mm}% {297mm}{210mm}% \globaldefs = 0 }} % @pagesizes TEXTHEIGHT[,TEXTWIDTH] % Perhaps we should allow setting the margins, \topskip, \parskip, % and/or leading, also. Or perhaps we should compute them somehow. % \parseargdef\pagesizes{\pagesizesyyy #1,,\finish} \def\pagesizesyyy#1,#2,#3\finish{{% \setbox0 = \hbox{\ignorespaces #2}\ifdim\wd0 > 0pt \hsize=#2\relax \fi \globaldefs = 1 % \parskip = 3pt plus 2pt minus 1pt \setleading{\textleading}% % \dimen0 = #1 \advance\dimen0 by \voffset % \dimen2 = \hsize \advance\dimen2 by \normaloffset % \internalpagesizes{#1}{\hsize}% {\voffset}{\normaloffset}% {\bindingoffset}{44pt}% {\dimen0}{\dimen2}% }} % Set default to letter. % \letterpaper \message{and turning on texinfo input format.} % Define macros to output various characters with catcode for normal text. \catcode`\"=\other \catcode`\~=\other \catcode`\^=\other \catcode`\_=\other \catcode`\|=\other \catcode`\<=\other \catcode`\>=\other \catcode`\+=\other \catcode`\$=\other \def\normaldoublequote{"} \def\normaltilde{~} \def\normalcaret{^} \def\normalunderscore{_} \def\normalverticalbar{|} \def\normalless{<} \def\normalgreater{>} \def\normalplus{+} \def\normaldollar{$}%$ font-lock fix % This macro is used to make a character print one way in \tt % (where it can probably be output as-is), and another way in other fonts, % where something hairier probably needs to be done. % % #1 is what to print if we are indeed using \tt; #2 is what to print % otherwise. Since all the Computer Modern typewriter fonts have zero % interword stretch (and shrink), and it is reasonable to expect all % typewriter fonts to have this, we can check that font parameter. % \def\ifusingtt#1#2{\ifdim \fontdimen3\font=0pt #1\else #2\fi} % Same as above, but check for italic font. Actually this also catches % non-italic slanted fonts since it is impossible to distinguish them from % italic fonts. But since this is only used by $ and it uses \sl anyway % this is not a problem. \def\ifusingit#1#2{\ifdim \fontdimen1\font>0pt #1\else #2\fi} % Turn off all special characters except @ % (and those which the user can use as if they were ordinary). % Most of these we simply print from the \tt font, but for some, we can % use math or other variants that look better in normal text. \catcode`\"=\active \def\activedoublequote{{\tt\char34}} \let"=\activedoublequote \catcode`\~=\active \def~{{\tt\char126}} \chardef\hat=`\^ \catcode`\^=\active \def^{{\tt \hat}} \catcode`\_=\active \def_{\ifusingtt\normalunderscore\_} \let\realunder=_ % Subroutine for the previous macro. \def\_{\leavevmode \kern.07em \vbox{\hrule width.3em height.1ex}\kern .07em } \catcode`\|=\active \def|{{\tt\char124}} \chardef \less=`\< \catcode`\<=\active \def<{{\tt \less}} \chardef \gtr=`\> \catcode`\>=\active \def>{{\tt \gtr}} \catcode`\+=\active \def+{{\tt \char 43}} \catcode`\$=\active \def${\ifusingit{{\sl\$}}\normaldollar}%$ font-lock fix % If a .fmt file is being used, characters that might appear in a file % name cannot be active until we have parsed the command line. % So turn them off again, and have \everyjob (or @setfilename) turn them on. % \otherifyactive is called near the end of this file. \def\otherifyactive{\catcode`+=\other \catcode`\_=\other} % Used sometimes to turn off (effectively) the active characters even after % parsing them. \def\turnoffactive{% \normalturnoffactive \otherbackslash } \catcode`\@=0 % \backslashcurfont outputs one backslash character in current font, % as in \char`\\. \global\chardef\backslashcurfont=`\\ \global\let\rawbackslashxx=\backslashcurfont % let existing .??s files work % \realbackslash is an actual character `\' with catcode other, and % \doublebackslash is two of them (for the pdf outlines). {\catcode`\\=\other @gdef@realbackslash{\} @gdef@doublebackslash{\\}} % In texinfo, backslash is an active character; it prints the backslash % in fixed width font. \catcode`\\=\active @def@normalbackslash{{@tt@backslashcurfont}} % On startup, @fixbackslash assigns: % @let \ = @normalbackslash % \rawbackslash defines an active \ to do \backslashcurfont. % \otherbackslash defines an active \ to be a literal `\' character with % catcode other. @gdef@rawbackslash{@let\=@backslashcurfont} @gdef@otherbackslash{@let\=@realbackslash} % Same as @turnoffactive except outputs \ as {\tt\char`\\} instead of % the literal character `\'. % @def@normalturnoffactive{% @let\=@normalbackslash @let"=@normaldoublequote @let~=@normaltilde @let^=@normalcaret @let_=@normalunderscore @let|=@normalverticalbar @let<=@normalless @let>=@normalgreater @let+=@normalplus @let$=@normaldollar %$ font-lock fix @unsepspaces } % Make _ and + \other characters, temporarily. % This is canceled by @fixbackslash. @otherifyactive % If a .fmt file is being used, we don't want the `\input texinfo' to show up. % That is what \eatinput is for; after that, the `\' should revert to printing % a backslash. % @gdef@eatinput input texinfo{@fixbackslash} @global@let\ = @eatinput % On the other hand, perhaps the file did not have a `\input texinfo'. Then % the first `\' in the file would cause an error. This macro tries to fix % that, assuming it is called before the first `\' could plausibly occur. % Also turn back on active characters that might appear in the input % file name, in case not using a pre-dumped format. % @gdef@fixbackslash{% @ifx\@eatinput @let\ = @normalbackslash @fi @catcode`+=@active @catcode`@_=@active } % Say @foo, not \foo, in error messages. @escapechar = `@@ % These look ok in all fonts, so just make them not special. @catcode`@& = @other @catcode`@# = @other @catcode`@% = @other @c Local variables: @c eval: (add-hook 'write-file-hooks 'time-stamp) @c page-delimiter: "^\\\\message" @c time-stamp-start: "def\\\\texinfoversion{" @c time-stamp-format: "%:y-%02m-%02d.%02H" @c time-stamp-end: "}" @c End: @c vim:sw=2: @ignore arch-tag: e1b36e32-c96e-4135-a41a-0b2efa2ea115 @end ignore ne-3.3.4/extensions000066400000000000000000000023641475116431000142540ustar00rootroot00000000000000# Comments and blank lines are allowed, or any line that doesn't match: # ^\s*(\w+)\s*([0-9]*)\s*(.+) # ^\s*(\w+)\s+([0-9]+i?)\s+(.+[^ \t])\s*$ # Canonical format: [i] # Space delimited; when matches the first lines, .jsf applies. # "i" suffix on makes search case-insensitive. # These patterns match some common command interpreters in the first line: csh 1 ^#!\s*/.*(csh|tcsh)\b\s* sh 1 ^#!\s*/.*sh\b\s* pl 1 ^#!\s*/.*perl\b\s* py 1 ^#!\s*/.*python[0-9]*\b\s* rb 1 ^#!\s*/.*ruby\b\s* xml 1 ^\s*<\?xml\b # These must match in the first 30 and 20 lines respectively to work. yaml 30 ^---$ ini 20 ^\[\s*\w+\s*\]$ # By default ne does not override a file's given extension. However, # you can specify any number of extensions that you would like to # allow to be overridden by including lines in your ~/.ne/extensions # containing only a dot followed by a single extension or shell "glob # pattern". (Basically if "ls *.pat" matches, then put ".pat" on a # line by itself in your ~/.ne/.extensions file. Note that ".*" would # allow overriding all extensions. # # Don't put such extensions in this global file, as then individual # users cannot choose whether to allow overriding such files. ne-3.3.4/macros/000077500000000000000000000000001475116431000134115ustar00rootroot00000000000000ne-3.3.4/macros/DeleteSOL000066400000000000000000000016241475116431000151170ustar00rootroot00000000000000# This example macro implements a "Delete to Start-of-line" # command which you can map to a key of your choice. If I # chose to use the Alt-U key, for example, then first I use # the KeyCode command to find the ne key number for that key # combination. On my system, it's 1f5, which is currently # mapped to the "U" command shortcut for "UNDO". To map it to # this DeleteSOL macro instead, I would put these lines: # # # Let Alt-U map to the DeleteSOL macro # KEY 1f5 DeleteSOL # # in my ~/.ne/.keys file. The first line is just a comment. # # Note that while the case of the "KEY" keyword and keycode # itself don't matter (you could spell it "Key 1F5" or "key # 1f5" or even "kEY 1F5"), and the case of built-in ne # commands don't matter either, the case of macro names does # matter, as these must match file names. AtomicUndo + PushPrefs AutoIndent 0 InsertLine LineUp DeleteLine PopPrefs AtomicUndo - ne-3.3.4/macros/aspell000066400000000000000000000011161475116431000146130ustar00rootroot00000000000000# This example macro works with your system's aspell # command to spell-check the selected block of text. # The AtomicUndo commands at the beginning and end make # it possible to undo/redo the entire operation in one # go. Otherwise the Cut and Paste would each require # their own undo/redo. Indentation is just for clarity; # it doesn't affect operation. AtomicUndo + Cut SaveClip ~/.ne/ne-aspell.txt System aspell --check ~/.ne/ne-aspell.txt OpenClip ~/.ne/ne-aspell.txt System rm -f ~/.ne/ne-aspell.txt ~/.ne/ne-aspell.txt.bak Paste GotoBookmark > AtomicUndo - ne-3.3.4/makefile000066400000000000000000000111351475116431000136260ustar00rootroot00000000000000# Makefile for ne's distribution archive. .PHONY: install VERSION=3.3.4 # If you change this prefix, you can invoke "make build install" and ne will # be compiled and installed under the $(PREFIX) hierarchy. You can even use # "make install PREFIX=$HOME/" to install ne locally into the directory # . # # Note that the build target uses ne's default compilation options. More # options can be passed to the build process by running directly make in # the "src" directory. PREFIX?=/usr/local PROGRAM = ne STRIP?=strip ifeq ($(OS), Windows_NT) OS := Windows else OS := $(shell uname -s) endif build: docs ( cd src; $(MAKE) clean; $(MAKE) NE_GLOBAL_DIR=$(PREFIX)/share/ne; $(STRIP) ne ) docs: ( cd doc; $(MAKE) ) alldocs: docs ( cd doc; $(MAKE) pdf ) version: ./version.pl VERSION=$(VERSION) source: version alldocs ( cd src; $(MAKE) clean; $(MAKE) ) -rm -f ne-$(VERSION) ln -s . ne-$(VERSION) tar cvf ne-$(VERSION).tar ne-$(VERSION)/version.pl ne-$(VERSION)/makefile ne-$(VERSION)/COPYING ne-$(VERSION)/INSTALL.md ne-$(VERSION)/README.md ne-$(VERSION)/NEWS ne-$(VERSION)/CHANGES \ ne-$(VERSION)/src/*.[hc] ne-$(VERSION)/src/*.c.in ne-$(VERSION)/src/*.pl \ ne-$(VERSION)/extensions \ ne-$(VERSION)/macros/* \ ne-$(VERSION)/syntax/*.jsf \ ne-$(VERSION)/src/makefile ne-$(VERSION)/src/ne.texinfo ne-$(VERSION)/doc/ne.1 \ ne-$(VERSION)/doc/makefile ne-$(VERSION)/doc/ne.texinfo ne-$(VERSION)/doc/texinfo.cnf.in ne-$(VERSION)/doc/ne.info* ne-$(VERSION)/doc/version.* \ ne-$(VERSION)/doc/html/*.html \ ne-$(VERSION)/doc/ne.pdf ne-$(VERSION)/doc/ne.txt ne-$(VERSION)/doc/default* -rm -f ne-*.tar.gz gzip ne-$(VERSION).tar -rm -f ne-$(VERSION) install: mkdir -p $(DESTDIR)$(PREFIX)/bin mkdir -p $(DESTDIR)$(PREFIX)/share/ne/syntax mkdir -p $(DESTDIR)$(PREFIX)/share/ne/macros mkdir -p $(DESTDIR)$(PREFIX)/share/man/man1 mkdir -p $(DESTDIR)$(PREFIX)/share/doc/ne mkdir -p $(DESTDIR)$(PREFIX)/share/info cp -pf src/ne$(CMDSUFFIX) $(DESTDIR)$(PREFIX)/bin cp -p extensions $(DESTDIR)$(PREFIX)/share/ne cp -p syntax/*.jsf $(DESTDIR)$(PREFIX)/share/ne/syntax cp -p macros/* $(DESTDIR)$(PREFIX)/share/ne/macros cp -p doc/ne.1 $(DESTDIR)$(PREFIX)/share/man/man1 cp -pR doc/html doc/ne.txt doc/default.* README.md COPYING NEWS CHANGES $(DESTDIR)$(PREFIX)/share/doc/ne if [ -f doc/ne.pdf ]; then cp -p doc/ne.pdf $(DESTDIR)$(PREFIX)/share/doc/ne ; fi cp -p doc/ne.info.gz $(DESTDIR)$(PREFIX)/share/info -install-info --dir-file=$(DESTDIR)$(PREFIX)/share/info/dir $(DESTDIR)$(PREFIX)/share/info/ne.info.gz directory = $(DESTDIR)$(PREFIX)/share/ne dir_target = $(directory)-$(wildcard $(directory)) dir_present = $(directory)-$(directory) dir_absent = $(directory)- uninstall: | $(dir_target) $(dir_present): -rm -fr $(DESTDIR)$(PREFIX)/share/ne -rm -fr $(DESTDIR)$(PREFIX)/doc/ne -rm -fr $(DESTDIR)$(PREFIX)/bin/ne -rm -fr $(DESTDIR)$(PREFIX)/bin/ne.exe -rm -f $(DESTDIR)$(PREFIX)/share/man/man1/ne.1 -install-info --delete --dir-file=$(DESTDIR)$(PREFIX)/share/info/dir $(DESTDIR)$(PREFIX)/share/info/ne.info.gz -rm -f $(DESTDIR)$(PREFIX)/share/info/ne.info.gz @echo "ne uninstalled." $(dir_absent): @echo "Cannot uninstall: folder $(directory) does not exist; please check the DESTDIR (\"$(DESTDIR)\") and PREFIX (\"$(PREFIX)\") make variables." # Creates cygwin package on Windows cygwin: version ifneq ($(OS), Windows) $(error This target can only be run under Windows) endif ( cd src; $(MAKE) clean; $(MAKE) NE_GLOBAL_DIR=/usr/share/ne NE_TERMCAP=1 NE_ANSI=1 OPTS=-U__STRICT_ANSI__ ) $(MAKE) install PREFIX=/usr CMDSUFFIX=.exe tar zcvf ne-cygwin-ansi-$(VERSION)-$(shell uname -m).tar.gz /usr/share/ne /usr/bin/ne.exe /usr/share/doc/ne /usr/share/info/ne.info.gz /usr/share/man/man1/ne.1 ( cd src; $(MAKE) clean; $(MAKE) NE_GLOBAL_DIR=/usr/share/ne OPTS=-U__STRICT_ANSI__ ) $(MAKE) install PREFIX=/usr CMDSUFFIX=.exe tar zcvf ne-cygwin-$(VERSION)-$(shell uname -m).tar.gz /usr/share/ne /usr/bin/ne.exe /usr/share/doc/ne /usr/share/info/ne.info.gz /usr/share/man/man1/ne.1 # Creates Mac OS X .dmg macosx: version alldocs ifneq ($(OS), Darwin) $(error This target can only be run under Mac OS X) endif ( cd src; $(MAKE) clean; $(MAKE) NE_GLOBAL_DIR=/usr/local/share/ne; strip ne ) -rm -fr /tmp/package-ne-$(VERSION) $(MAKE) install DESTDIR=/tmp/package-ne-$(VERSION) pkgbuild --root /tmp/package-ne-$(VERSION) --install-location "/" --version $(VERSION) --identifier ne-$(VERSION) ne-$(VERSION).pkg -rm -f ne-$(VERSION).dmg hdiutil create -fs HFS+ -srcfolder ne-$(VERSION).pkg -volname ne-$(VERSION) ne-$(VERSION).dmg clean: -rm -f ne-*.tar* really-clean: clean (cd src; $(MAKE) clean) (cd doc; $(MAKE) clean) ne-3.3.4/ne.spec000066400000000000000000000062561475116431000134140ustar00rootroot00000000000000%define _hardened_build 1 Summary: ne, the nice editor Name: ne Version: 3.3.4 Release: 1%{?dist} License: GPL-3.0-or-later Source0: https://ne.di.unimi.it/ne-%{version}.tar.gz URL: https://ne.di.unimi.it/ Requires: ncurses BuildRequires: gcc BuildRequires: ncurses-devel BuildRequires: make BuildRequires: bash BuildRequires: perl BuildRequires: texinfo BuildRequires: sed %description ne is a free (GPL'd) text editor based on the POSIX standard that runs (we hope) on almost every UN*X machine. ne is easy to use for the beginner, but powerful and fully configurable for the wizard, and most sparing in its resource usage. %prep %setup -q %build cd src %make_build NE_GLOBAL_DIR=%{_datadir}/ne LIBS=-lncurses OPTS="%{optflags} -fno-strict-aliasing -Wno-parentheses" %install mkdir -p $RPM_BUILD_ROOT%{_bindir} mkdir -p $RPM_BUILD_ROOT%{_datadir}/ne/syntax mkdir -p $RPM_BUILD_ROOT%{_datadir}/ne/macros mkdir -p $RPM_BUILD_ROOT%{_infodir} mkdir -p $RPM_BUILD_ROOT%{_mandir}/man1 install -p -m 755 ./src/ne $RPM_BUILD_ROOT%{_bindir}/ne install -p -m 644 ./extensions $RPM_BUILD_ROOT%{_datadir}/ne/extensions install -p -m 644 ./syntax/*.jsf $RPM_BUILD_ROOT%{_datadir}/ne/syntax install -p -m 644 ./macros/* $RPM_BUILD_ROOT%{_datadir}/ne/macros install -p -m 644 ./doc/ne.1 $RPM_BUILD_ROOT%{_mandir}/man1 install -p -m 644 ./doc/ne.info* $RPM_BUILD_ROOT%{_infodir} rm INSTALL.md mv doc/html . %files %{_bindir}/ne %{_datadir}/ne/ %{_mandir}/man1/ne.1* %doc %{_infodir}/ne.info* %doc ./README.md %doc ./NEWS %doc ./CHANGES %package doc Summary: Documentation for ne, the nice editor BuildArch: noarch %description doc Documentation for ne, the nice editor. %files doc %license ./COPYING %doc html %doc ./doc/ne.texinfo %doc ./doc/ne.pdf %doc ./doc/ne.txt %doc ./doc/default.* %changelog * Thu Feb 6 2025 Sebastiano Vigna - 3.3.4-1 - First release * Fri Jan 17 2025 Fedora Release Engineering - 3.3.3-6 - Rebuilt for https://fedoraproject.org/wiki/Fedora_42_Mass_Rebuild * Thu Jul 25 2024 Miroslav Suchý - 3.3.3-5 - convert license to SPDX * Thu Jul 18 2024 Fedora Release Engineering - 3.3.3-4 - Rebuilt for https://fedoraproject.org/wiki/Fedora_41_Mass_Rebuild * Thu Jan 25 2024 Fedora Release Engineering - 3.3.3-3 - Rebuilt for https://fedoraproject.org/wiki/Fedora_40_Mass_Rebuild * Sun Jan 21 2024 Fedora Release Engineering - 3.3.3-2 - Rebuilt for https://fedoraproject.org/wiki/Fedora_40_Mass_Rebuild * Thu Oct 19 2023 Sebastiano Vigna - 3.3.3-1 - First release * Thu Jul 20 2023 Fedora Release Engineering - 3.3.2-4 - Rebuilt for https://fedoraproject.org/wiki/Fedora_39_Mass_Rebuild * Thu Jan 19 2023 Fedora Release Engineering - 3.3.2-3 - Rebuilt for https://fedoraproject.org/wiki/Fedora_38_Mass_Rebuild * Tue Oct 25 2022 Sebastiano Vigna - 3.3.2-2 - Hardened build * Tue Sep 13 2022 Sebastiano Vigna - 3.3.2-1 - First release * Tue May 18 2021 Sebastiano Vigna - 3.3.1-1 - First release ne-3.3.4/src/000077500000000000000000000000001475116431000127145ustar00rootroot00000000000000ne-3.3.4/src/.gitignore000066400000000000000000000001311475116431000146770ustar00rootroot00000000000000enums.h ext.c hash.c hash.h help.c help.h names.c names.h ne version.h *.gcda *.gcno *.o ne-3.3.4/src/TODO000066400000000000000000000010361475116431000134040ustar00rootroot000000000000000) Garbage collection. Under some extreme circumstances in which the number of lost characters is intolerably high, ne should allocate a new block of memory and move contiguously the text into it. 1) Fast large copying. When one pastes a *very large* clip, we should create a pool entry just for that copy and copy contiguously in it, instead of allocating line by line. 2) Check that update_partial_line() is always given the best start column. 3) Rewrite SHIFT to use direct buffer access rather than cursor movements. ne-3.3.4/src/actions.c000066400000000000000000001723561475116431000145360ustar00rootroot00000000000000/* Main command processing loop. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2025 Todd M. Lewis and Sebastiano Vigna This file is part of ne, the nice editor. This library 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 library 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 . */ #include "ne.h" #include "support.h" #include "version.h" #include /* ne's temporary file name template for the THROUGH command. */ #define NE_TMP ".ne-tmp.XXXXXX" /* Turns an unspecified integer argument (-1) to 1. This is what most commands require. */ #define NORMALIZE(x) { x = (x)<0 ? 1 : (x); } /* The length of the static message buffer. It must be larger by a factor of about three of the maximum screen width as UTF-8 encoded characters might take several characters per screen position. */ #define MAX_MESSAGE_SIZE (1024) /* Here, given a mask represent a user flag and an integer i, we do as follows: i < 0 : toggle flag; i = 0 : clear flag; i > 0 : set flag; */ #define SET_USER_FLAG(b,i,x) {\ if ((i)<0) (b)->x = !(b)->x;\ else (b)->x = ((i) != 0);\ } #define SET_GLOBAL_FLAG(c,f) {\ if ((c)<0) (f) = !(f);\ else (f) = ((c) != 0);\ } /* Converts a non-positive result from request_number() to OK if the function was aborted or not-a-number error if an invalid number was read. */ #define NUMERIC_ERROR(c) ((c) == ABORT ? OK : NOT_A_NUMBER) /* Do we want RepealLast to wrap on the next invocation? Upon NOT_FOUND from search/replace functions, set this to 2. do_action() reduces toward 0.*/ static int perform_wrap; #ifdef LOGACTIONS /* Wrap do_action() and various request* functions to facilitate action logging. */ int do_action_wrapped(buffer *b, action a, int64_t c, char *p); static int da_depth = 0; static FILE *da_log; static buffer *da_prev_b; bool request_response_wrapper(const buffer *b, const char *prompt, bool default_value) { bool resp = request_response(b, prompt, default_value); if (da_log) { fprintf(da_log," %p request_response: %s\n", b, resp ? "true" : "false"); } return resp; } #define request_response request_response_wrapper char request_char_wrapper(const buffer *b, const char *prompt, const char default_value) { char resp = request_char(b, prompt, default_value); if (da_log) { fprintf(da_log," %p request_char: '%c'\n", b, resp); } return resp; } #define request_char request_char_wrapper int64_t request_number_wrapper(const buffer *b, const char *prompt, int64_t default_value) { int64_t resp = request_number(b, prompt, default_value); if (da_log) { fprintf(da_log," %p request_number: %ld\n", b, resp); } return resp; } #define request_number request_number_wrapper char *request_string_wrapper(const buffer *b, const char *prompt, const char *default_string, bool accept_null_string, int completion_type, bool prefer_utf8) { char *resp = request_string(b, prompt, default_string, accept_null_string, completion_type, prefer_utf8); if (da_log) { fprintf(da_log," %p request_string: '%s'\n", b, resp ? resp : ""); } return resp; } #define request_string request_string_wrapper char *request_wrapper(const buffer *b, const char *prompt, const char *default_string, bool alpha_allowed, int completion_type, bool prefer_utf8) { char * resp = request(b, prompt, default_string, alpha_allowed, completion_type, prefer_utf8); if (da_log) { fprintf(da_log," %p request: '%s'\n", b, resp ? resp : ""); } return resp; } #define request request_wrapper char *request_file_wrapper(const buffer *b, const char *prompt, const char *default_name) { char * resp = request_file(b, prompt, default_name); if (da_log) { fprintf(da_log," %p request: '%s'\n", b, resp ? resp : ""); } return resp; } #define request_file request_file_wrapper int do_action(buffer *b, action a, int64_t c, char *p) { if (!da_log) da_log = fopen("/tmp/ne-actions.log","a"); if (da_log) { if (b != da_prev_b && b->filename) { fprintf(da_log, "%p: %s\n", b, b->filename); da_prev_b = b; } fprintf(da_log,"%p%2d %ld,%ld(%ld) %s %ld '%s'\n", b, da_depth++, b->cur_line, b->cur_pos, b->cur_char, command_names[a], c, p ? p : ""); fflush(da_log); } int rc = do_action_wrapped(b, a, c, p); da_depth--; return rc; } /* End of the insertions to achieve logging of do_action(). */ #endif /* This is the dispatcher of all actions that have some effect on the text. The arguments are: b: the buffer to act on, a: an action to be executed, c: a possible integer parameter, and p: a possible string parameter. -1 and NULL are, respectively, reserved values meaning "no argument". For most operations, the integer argument is the number of repetitions. When an on/off choice is required, nonzero means on, zero means off, no argument means toggle. If there is a string argument (i.e. p != NULL), it is assumed that the action will consume p -- it ends up being free()d or stored somewhere. Though efficient, this has lead to some memory leaks (can you find them?). */ #ifdef LOGACTIONS int do_action_wrapped(buffer *b, action a, int64_t c, char *p) { #else int do_action(buffer *b, action a, int64_t c, char *p) { #endif static char msg[MAX_MESSAGE_SIZE]; line_desc *next_ld; HIGHLIGHT_STATE next_line_state; int error = OK; char_stream *recording; int64_t col; char *q; assert(b->cur_pos >= 0); assert_buffer(b); assert_buffer_content(b); assert(b->encoding != ENC_UTF8 || b->cur_pos >= b->cur_line_desc->line_len || utf8len(b->cur_line_desc->line[b->cur_pos]) > 0); assert(b != cur_buffer || b->cur_x < ne_columns); assert(b != cur_buffer || b->cur_y < ne_lines - 1); #ifndef NDEBUG if (b->syn && b->attr_len != -1) { HIGHLIGHT_STATE next_state = parse(b->syn, b->cur_line_desc, b->cur_line_desc->highlight_state, b->encoding == ENC_UTF8); assert(attr_len == b->attr_len); assert(attr_len == 0 || memcmp(attr_buf, b->attr_buf, attr_len) == 0); assert(memcmp(&next_state, &b->next_state, sizeof next_state) == 0); } #endif stop = false; if (recording_macro) record_action(recording_macro, a, c, p, verbose_macros); if (perform_wrap > 0) perform_wrap--; if (cur_buffer->visible_mark.shown) highlight_mark(cur_buffer, false); switch(a) { case EXIT_A: if (save_all_modified_buffers()) { print_error(CANT_SAVE_EXIT_SUSPENDED); return ERROR; } else { close_history(); unset_interactive_mode(); exit(0); } return OK; case SAVEALL_A: if (save_all_modified_buffers()) { print_error(CANT_SAVE_ALL); return ERROR; } else print_message(info_msg[MODIFIED_SAVED]); return OK; case PUSHPREFS_A: NORMALIZE(c); for (int64_t i = 0; i < c && !(error = push_prefs(b)) && !stop; i++); return stop ? STOPPED : error ; case POPPREFS_A: NORMALIZE(c); for (int64_t i = 0; i < c && !(error = pop_prefs(b)) && !stop; i++); return stop ? STOPPED : error ; case QUIT_A: if (modified_buffers() && !request_response(b, info_msg[SOME_DOCUMENTS_ARE_NOT_SAVED], false)) return ERROR; close_history(); unset_interactive_mode(); exit(0); case LINEUP_A: NORMALIZE(c); for(int64_t i = 0; i < c && !(error = line_up(b)) && !stop; i++); return stop ? STOPPED : error; case LINEDOWN_A: NORMALIZE(c); for(int64_t i = 0; i < c && !(error = line_down(b)) && !stop; i++); return stop ? STOPPED : error; case PREVPAGE_A: NORMALIZE(c); for(int64_t i = 0; i < c && !(error = prev_page(b)) && !stop; i++); return stop ? STOPPED : error; case NEXTPAGE_A: NORMALIZE(c); for(int64_t i = 0; i < c && !(error = next_page(b)) && !stop; i++); return stop ? STOPPED : error; case MOVELEFT_A: NORMALIZE(c); for(int64_t i = 0; i < c && !(error = char_left(b)) && !stop; i++); return stop ? STOPPED : error; case MOVERIGHT_A: NORMALIZE(c); for(int64_t i = 0; i < c && !(error = char_right(b)) && !stop; i++); return stop ? STOPPED : error; case MOVESOL_A: move_to_sol(b); return OK; case MOVEEOL_A: move_to_eol(b); return OK; case MOVESOF_A: move_to_sof(b); return OK; case MOVEEOF_A: delay_update(); move_to_bof(b); move_to_eol(b); return OK; case PAGEUP_A: NORMALIZE(c); for(int64_t i = 0; i < c && !(error = page_up(b)) && !stop; i++); return stop ? STOPPED : error; case PAGEDOWN_A: NORMALIZE(c); for(int64_t i = 0; i < c && !(error = page_down(b)) && !stop; i++); return stop ? STOPPED : error; case MOVETOS_A: error = move_tos(b); return error; case MOVEBOS_A: error = move_bos(b); return error; case ADJUSTVIEW_A: NORMALIZE(c); error = adjust_view(b, p); if (p) free(p); return error; case TOGGLESEOF_A: toggle_sof_eof(b); return OK; case TOGGLESEOL_A: toggle_sol_eol(b); return OK; case NEXTWORD_A: case PREVWORD_A: { /* parse_word_parm(char *p, char *pat_in, int64_t *match) */ struct { int64_t c; int64_t left; int64_t right; } params = {-1, 0, 0}; error = parse_word_parm(p, "#<>", (int64_t *)¶ms) || (params.left && params.right); if (!error) { if (!(params.left || params.right)) params.left = 1; NORMALIZE(params.c); for(int64_t i = 0; i < params.c && !(error = search_word(b, a==NEXTWORD_A ? 1 : -1, params.left!=0)) && !stop; i++); } if (p) free(p); return stop ? STOPPED : error; } case DELETENEXTWORD_A: case DELETEPREVWORD_A: if (b->opt.read_only) return DOCUMENT_IS_READ_ONLY; recording = recording_macro; recording_macro = NULL; NORMALIZE(c); int marking_t = b->marking; int mark_is_vertical_t = b->mark_is_vertical; b->bookmark[WORDWRAP_BOOKMARK].pos = b->block_start_pos; b->bookmark[WORDWRAP_BOOKMARK].line = b->block_start_line; b->bookmark_mask |= (1 << WORDWRAP_BOOKMARK); start_undo_chain(b); b->marking = 1; b->mark_is_vertical = 0; b->block_start_line = b->cur_line; b->block_start_pos = b->cur_pos; if (a == DELETEPREVWORD_A) { /* This ugly insertion and deletion of a single character ensures that the cursor ends up here after an undo. */ insert_one_char(b, b->cur_line_desc, b->cur_line, b->cur_pos, ' '); delete_stream(b, b->cur_line_desc, b->cur_line, b->cur_pos, 1); for(int64_t i = 0; i < c && !(error = search_word(b, -1, true)) && !stop; i++); } else if (c > 0) { move_to_eow(b); if (b->block_start_line != b->cur_line || b->block_start_pos != b->cur_pos) c--; for(int64_t i = 0; i < c && !(error = search_word(b, 1, true)) && !stop; i++); if (c) move_to_eow(b); } const bool line_changed = b->block_start_line != b->cur_line; if (line_changed || b->block_start_pos != b->cur_pos) { /* we moved */ b->attr_len = -1; error |= erase_block(b, false); end_undo_chain(b); if (line_changed) { if (b->syn) update_syntax_states_delay(b, b->cur_line_desc, NULL); update_window_lines(b, b->cur_line_desc, b->cur_y, ne_lines - 2, false); } else { update_line(b, b->cur_line_desc, b->cur_y, 0, false); if (b->syn) need_attr_update = true; } } else { end_undo_chain(b); if (a == DELETEPREVWORD_A) error |= undo(b); } b->bookmark_mask &= ~(1 << WORDWRAP_BOOKMARK); b->block_start_pos = b->bookmark[WORDWRAP_BOOKMARK].pos; b->block_start_line = b->bookmark[WORDWRAP_BOOKMARK].line; b->marking = marking_t; b->mark_is_vertical = mark_is_vertical_t; recording_macro = recording; return stop ? STOPPED : error; case MOVEEOW_A: if (!p || (p && !strcmp(p,">"))) move_to_eow(b); else if (p && !strcmp(p,"<")) move_to_sow(b); else if (p) { free(p); return SYNTAX_ERROR; } if (p) free(p); return OK; case MOVEINCUP_A: move_inc_up(b); return OK; case MOVEINCDOWN_A: move_inc_down(b); return OK; case BRACKETEDPASTE_A: if (!bracketed_paste) { print_message("Bracketed Paste support is globally disabled."); free(p); return OK; } if (!p || (p && p[0] == '?')) { free(p); snprintf(msg, MAX_MESSAGE_SIZE, "BracketedPaste: 0|1|macro_before macro_after"); p = request_string(b, msg, cur_bracketed_paste_value(b), true, COMPLETE_NONE, b->encoding == ENC_UTF8 || b->encoding == ENC_ASCII && b->opt.utf8auto); } if (p) { if ((*p == '0' || *p == '1') && *(p+1) == '\0') { c = *p - '0'; if (b->bpaste_support == c) { free(p); return OK; } b->bpaste_support = c; if (c == 0) turn_off_bracketed_paste(); if (c == 1) turn_on_bracketed_paste(); free(b->bpaste_macro_before); free(b->bpaste_macro_after); b->bpaste_macro_before = NULL; b->bpaste_macro_after = NULL; } else { char *q = p; char *m1, *m2; while (*q && *q == ' ') q++; /* skip leading spaces */ m1 = q; /* start of 1st macro name */ while (*q && *q != ' ') q++; /* keep non-spaces */ if (*q == ' ') { *q = '\0'; /* mark end of 1st macro name */ q++; } while (*q && *q == ' ') q++; /* skip middle spaces */ m2 = q; /* start of 2nd macro name */ while (*q && *q != ' ') q++; /* keep non-spaces */ *q = '\0'; /* mark end of 2nd macro name */ if (strlen(m1) && strlen(m2)) { free(b->bpaste_macro_before); b->bpaste_macro_before = str_dup(m1); free(b->bpaste_macro_after); b->bpaste_macro_after = str_dup(m2); b->bpaste_support = 2; } else { free(p); return INVALID_BRACKETED_PASTE_DESIGNATION; } } free(p); } return OK; case UNSETBOOKMARK_A: if (p && p[0] == '*' && !p[1]) { /* Special parm "*" for UNSETBOOKMARK_A */ b->bookmark_mask = b->cur_bookmark = 0; print_message(info_msg[ALL_BOOKMARKS_CLEARED]); free(p); return OK; } /* Intentionally fall through to regular BOOKMARK parm parsing. */ case SETBOOKMARK_A: case GOTOBOOKMARK_A: { bool relative = false; /* Value for *p c becomes ------------ -------------------------- "" 0 "?" promp "-" AUTO_BOOKMARK "0".."9" 0..9 "+1" prev "-1" next "<" PASTE_START_BOOKMARK ">" PASTE_END_BOOKMARK Anything else is out of range. */ if (p) { if (p[0] == '?') { free(p); snprintf(msg, MAX_MESSAGE_SIZE, "Cur Bookmarks: [%s] %s (0-9, -1, +1, '<', '>', or '-')", cur_bookmarks_string(b), a == SETBOOKMARK_A ? "SetBookmark" : "GotoBookmark"); p = request_string(b, msg, NULL, true, COMPLETE_NONE, b->encoding == ENC_UTF8 || b->encoding == ENC_ASCII && b->opt.utf8auto); if (!p) { return INVALID_BOOKMARK_DESIGNATION; } } } if (p) { if ((p[0]=='+' || p[0]=='-') && p[1]=='1') { if (b->cur_bookmark < 0 || b->cur_bookmark > MAX_USER_BOOKMARK) b->cur_bookmark = 0; int i; for(i = 0; i <= MAX_USER_BOOKMARK; i++) { b->cur_bookmark = (b->cur_bookmark + MAX_USER_BOOKMARK + 1 + (p[0] == '+' ? 1 : -1)) % (MAX_USER_BOOKMARK + 1); if ((a == SETBOOKMARK_A ? ~b->bookmark_mask : b->bookmark_mask) & (1 << b->cur_bookmark)) { c = b->cur_bookmark; relative = true; break; } } if (i==MAX_USER_BOOKMARK + 1) { free(p); switch (a) { case SETBOOKMARK_A: return NO_UNSET_BOOKMARKS_TO_SET; case GOTOBOOKMARK_A: return NO_SET_BOOKMARKS_TO_GOTO; default: return NO_SET_BOOKMARKS_TO_UNSET; } } } else if (p[0]) { if (!p[1]) { if (*p == '-') c = AUTO_BOOKMARK; else if (*p == '<') c = PASTE_START_BOOKMARK; else if (*p == '>') c = PASTE_END_BOOKMARK; else c = *p - '0'; } else c = -1; } else c = 0; free(p); if (c < 0 || c >= NUM_BOOKMARKS) return INVALID_BOOKMARK_DESIGNATION; } else c = 0; switch(a) { case SETBOOKMARK_A: b->bookmark[c].pos = b->cur_pos; b->bookmark[c].line = b->cur_line; b->bookmark[c].cur_y = b->cur_y; b->bookmark_mask |= (1 << c); b->cur_bookmark = c; snprintf(msg, MAX_MESSAGE_SIZE, "Bookmark %c set", "0123456789-<>w"[c]); print_message(msg); break; case UNSETBOOKMARK_A: if (! (b->bookmark_mask & (1 << c))) return BOOKMARK_NOT_SET; b->bookmark_mask &= ~(1 << c); snprintf(msg, MAX_MESSAGE_SIZE, "Bookmark %c unset", "0123456789-<>w"[c]); print_message(msg); break; case GOTOBOOKMARK_A: if (! (b->bookmark_mask & (1 << c))) return BOOKMARK_NOT_SET; else { const int64_t prev_line = b->cur_line; const int64_t prev_pos = b->cur_pos; const int cur_y = b->cur_y; b->cur_bookmark = c; int avshift; delay_update(); goto_line_pos(b, b->bookmark[c].line, b->bookmark[c].pos); if (avshift = b->cur_y - b->bookmark[c].cur_y) { snprintf(msg, MAX_MESSAGE_SIZE, "%c%d", avshift > 0 ? 'T' :'B', avshift > 0 ? avshift : -avshift); adjust_view(b, msg); } b->bookmark[AUTO_BOOKMARK].line = prev_line; b->bookmark[AUTO_BOOKMARK].pos = prev_pos; b->bookmark[AUTO_BOOKMARK].cur_y = cur_y; b->bookmark_mask |= 1<w"[c]); print_message(msg); } } default: ; /* Can't happen */ } return OK; } case GOTOLINE_A: if (c < 0 && (c = request_number(b, "Line", b->cur_line + 1)) < 0) return NUMERIC_ERROR(c); if (c == 0 || c > b->num_lines) c = b->num_lines; goto_line(b, --c); return OK; case GOTOCOLUMN_A: if (c < 0 && (c = request_number(b, "Column", b->cur_x + b->win_x + 1)) < 0) return NUMERIC_ERROR(c); goto_column(b, c ? --c : 0); return OK; case INSERTSTRING_A: /* Since we are going to call another action, we do not want to record this insertion twice. */ recording = recording_macro; recording_macro = NULL; error = ERROR; if (p || (p = request_string(b, "String", NULL, false, COMPLETE_NONE, b->encoding == ENC_UTF8 || b->encoding == ENC_ASCII && b->opt.utf8auto))) { encoding_type encoding = detect_encoding(p, strlen(p)); error = OK; start_undo_chain(b); /* We cannot rely on encoding promotion done by INSERTCHAR_A, because it could work just for part of the string if UTF-8 auto-detection is not enabled. */ if (b->encoding == ENC_ASCII || encoding == ENC_ASCII || (b->encoding == encoding)) { if (b->encoding == ENC_ASCII) b->encoding = encoding; for(int64_t pos = 0; p[pos] && error == OK; pos = next_pos(p, pos, encoding)) error = do_action(b, INSERTCHAR_A, get_char(&p[pos], encoding), NULL); } else error = INVALID_STRING; end_undo_chain(b); free(p); } recording_macro = recording; return error; case TABS_A: SET_USER_FLAG(b, c, opt.tabs); return OK; case DELTABS_A: SET_USER_FLAG(b, c, opt.del_tabs); return OK; case SHIFTTABS_A: SET_USER_FLAG(b, c, opt.shift_tabs); return OK; case AUTOMATCHBRACKET_A: if (c < 0 && (c = request_number(b, "Match mode (sum of 0:none, 1:brightness, 2:inverse, 4:bold, 8:underline)", b->opt.automatch)) < 0 || c > 15) return ((c) == ABORT ? OK : INVALID_MATCH_MODE); b->opt.automatch = c; return OK; case INSERTTAB_A: recording = recording_macro; recording_macro = NULL; NORMALIZE(c); start_undo_chain(b); if (b->opt.tabs) { while (c-- > 0) { error = do_action(b, INSERTCHAR_A, '\t', NULL); } } else { while (c-- > 0) { do { error = do_action(b, INSERTCHAR_A, ' ', NULL); } while (b->opt.tab_size && (b->win_x + b->cur_x) % b->opt.tab_size); } } end_undo_chain(b); recording_macro = recording; return error; case INSERTCHAR_A: { static int last_inserted_char = ' '; int deleted_char, old_char; if (b->opt.read_only) return DOCUMENT_IS_READ_ONLY; if ((c < 0 || c > MAX_UTF_8) && ((c = request_number(b, "Char Code", last_inserted_char)) < 0 || c > MAX_UTF_8)) return NUMERIC_ERROR(c); if (c == 0) return CANT_INSERT_0; if (b->encoding == ENC_ASCII) { if (c > 0xFF) b->encoding = ENC_UTF8; else if (c > 0x7F) b->encoding = b->opt.utf8auto ? ENC_UTF8 : ENC_8_BIT; } if (c > 0xFF && b->encoding == ENC_8_BIT) return INVALID_CHARACTER; last_inserted_char = c; old_char = b->cur_pos < b->cur_line_desc->line_len ? get_char(&b->cur_line_desc->line[b->cur_pos], b->encoding) : 0; ensure_attributes(b); start_undo_chain(b); if (deleted_char = !b->opt.insert && b->cur_pos < b->cur_line_desc->line_len) delete_one_char(b, b->cur_line_desc, b->cur_line, b->cur_pos); if (b->cur_pos > b->cur_line_desc->line_len) { /* We insert spaces to reach the insertion position. */ insert_spaces(b, b->cur_line_desc, b->cur_line, b->cur_line_desc->line_len, b->cur_pos - b->cur_line_desc->line_len); if (b->syn) update_line(b, b->cur_line_desc, b->cur_y, 0, true); } insert_one_char(b, b->cur_line_desc, b->cur_line, b->cur_pos, c); need_attr_update = true; /* At this point the line has been modified: note that if we are in overwrite mode and write a character at or beyond the length of the current line, we are actually doing an insertion. */ if (!deleted_char) update_inserted_char(b, c, b->cur_line_desc, b->cur_pos, b->cur_char, b->cur_y, b->cur_x); else update_overwritten_char(b, old_char, c, b->cur_line_desc, b->cur_pos, b->cur_char, b->cur_y, b->cur_x); char_right(b); /* Note the use of ne_columns-1. This avoids a double horizontal scrolling each time a word wrap happens with b->opt.right_margin = 0. */ error = b->opt.word_wrap && b->win_x + b->cur_x >= (b->opt.right_margin ? b->opt.right_margin : ne_columns - 1) ? word_wrap(b) : ERROR; if (error == ERROR) { assert_buffer_content(b); /* No word wrap. */ if (b->syn) update_line(b, b->cur_line_desc, b->cur_y, 0, true); assert_buffer_content(b); } else { /* Fixes in case of word wrapping. */ const bool wont_scroll = b->win_x == 0; int64_t a = 0; update_line(b, b->cur_line_desc, b->cur_y, calc_width(b->cur_line_desc, b->cur_line_desc->line_len, b->opt.tab_size, b->encoding) - b->win_x, false); need_attr_update = false; /* Poke the correct state into the next line. */ if (b->syn) ((line_desc *)b->cur_line_desc->ld_node.next)->highlight_state = b->next_state; if (b->opt.auto_indent) a = auto_indent_line(b, b->cur_line + 1, (line_desc *)b->cur_line_desc->ld_node.next, INT_MAX); move_to_sol(b); line_down(b); goto_pos(b, error + a); if (wont_scroll) { if (b->cur_line == b->num_lines - 1) update_line(b, b->cur_line_desc, b->cur_y, 0, false); else scroll_window(b, b->cur_line_desc, b->cur_y, 1); } need_attr_update = true; assert_buffer_content(b); } end_undo_chain(b); return OK; } case BACKSPACE_A: case DELETECHAR_A: if (b->opt.read_only) return DOCUMENT_IS_READ_ONLY; NORMALIZE(c); start_undo_chain(b); for(int64_t i = 0; i < c && !stop; i++) { if (a == BACKSPACE_A) { if (b->cur_pos == 0) { if (b->cur_line == 0) { /* Start of buffer. We just return an error. */ end_undo_chain(b); return ERROR; } /* We turn a backspace at the start of a line into a delete at the end of the previous line. */ char_left(b); } else { if (b->opt.del_tabs && (b->win_x + b->cur_x) % b->opt.tab_size == 0 && (b->cur_pos > b->cur_line_desc->line_len || b->cur_line_desc->line[b->cur_pos - 1] == ' ')) { /* We are deleting one or more spaces from a tabbing position. We go left until the previous tabbing, or when spaces end. */ int64_t back = 1; while((b->win_x + b->cur_x - back) % b->opt.tab_size != 0 && (b->cur_pos - back > b->cur_line_desc->line_len || b->cur_line_desc->line[b->cur_pos - back - 1] == ' ')) back++; goto_pos(b, b->cur_pos - back); } else char_left(b); /* If we are not over text, we are in free form mode; the backspace is turned into moving to the left. */ if (b->cur_pos >= b->cur_line_desc->line_len) continue; } } /* From here, we just implement a delete. */ if (b->opt.del_tabs && b->cur_pos < b->cur_line_desc->line_len && b->cur_line_desc->line[b->cur_pos] == ' ' && ((b->win_x + b->cur_x) % b->opt.tab_size == 0 || b->cur_line_desc->line[b->cur_pos - 1] != ' ')) { col = 0; do col++; while((b->win_x + b->cur_x + col) % b->opt.tab_size != 0 && b->cur_pos + col < b->cur_line_desc->line_len && b->cur_line_desc->line[b->cur_pos + col] == ' '); /* We are positioned at the start of the block of col spaces. If there is at most one character to delete, we can just go on. Otherwise, we replace the block with a TAB, doing some magick to keep everything in sync. */ if (col > 1 && (b->win_x + b->cur_x + col) % b->opt.tab_size == 0) { if (b->syn) { ensure_attributes(b); memmove(b->attr_buf + b->cur_char + 1, b->attr_buf + b->cur_char + col, b->attr_len - (b->cur_char + col)); b->attr_buf[b->cur_char] = -1; b->attr_len -= (col - 1); } delete_stream(b, b->cur_line_desc, b->cur_line, b->cur_pos, col); insert_one_char(b, b->cur_line_desc, b->cur_line, b->cur_pos, '\t'); } } if (b->cur_pos > b->cur_line_desc->line_len) { col = b->win_x + b->cur_x; /* We are not over text; we must be in FreeForm mode. We're deleting past the end of the line, so if we aren't on the last line we need to pad this line with space up to col, then fall through to the delete_one_char() below. */ if (b->cur_line_desc->ld_node.next->next == NULL) continue; if (b->cur_line_desc->line_len == 0) { auto_indent_line(b, b->cur_line, b->cur_line_desc, col); resync_pos(b); } /* We need spaces if the line was not empty, or if we were sitting in the middle of a TAB. */ insert_spaces(b, b->cur_line_desc, b->cur_line, b->cur_line_desc->line_len, col - calc_width(b->cur_line_desc, b->cur_line_desc->line_len, b->opt.tab_size, b->encoding)); if (b->syn) store_attributes(b, b->cur_line_desc); } ensure_attributes(b); if (b->cur_pos < b->cur_line_desc->line_len) { /* Deletion inside a line. */ const int old_char = b->encoding == ENC_UTF8 ? utf8char(&b->cur_line_desc->line[b->cur_pos]) : b->cur_line_desc->line[b->cur_pos]; const uint32_t old_attr = b->syn ? b->attr_buf[b->cur_char] : 0; if (b->syn) { /* Invalidate attrs beyond the right window edge. */ const int64_t right_char = calc_char_len(b->cur_line_desc, calc_pos(b->cur_line_desc, b->win_x + ne_columns, b->opt.tab_size, b->encoding), b->encoding); if (right_char < b->attr_len) b->attr_len = right_char; } delete_one_char(b, b->cur_line_desc, b->cur_line, b->cur_pos); update_deleted_char(b, old_char, old_attr, b->cur_line_desc, b->cur_pos, b->cur_char, b->cur_y, b->cur_x); if (b->syn) update_line(b, b->cur_line_desc, b->cur_y, 0, true); } else { /* Here we handle the case in which two lines are joined. Note that if the first line is empty, it is just deleted by delete_one_char(), so we must store its initial state and restore it after the deletion. */ if (b->syn && b->cur_pos == 0) next_line_state = b->cur_line_desc->highlight_state; delete_one_char(b, b->cur_line_desc, b->cur_line, b->cur_pos); if (b->syn && b->cur_pos == 0) b->cur_line_desc->highlight_state = next_line_state; update_line(b, b->cur_line_desc, b->cur_y, b->cur_x, true); if (b->cur_y < ne_lines - 2) scroll_window(b, (line_desc *)b->cur_line_desc->ld_node.next, b->cur_y + 1, -1); } } need_attr_update = true; end_undo_chain(b); return error ? error : stop ? STOPPED : 0; case INSERTLINE_A: if (b->opt.read_only) return DOCUMENT_IS_READ_ONLY; NORMALIZE(c); /* There SHOULD be a start_undo_chain(b) here, but in practice we've become used to using a single Undo after an InsertLine to remove the autoindent space. Therefore, this sillyness. If we had a real DeleteSOL command, we wouldn't need the Undo to undo InsertLine in two steps. There could also be extant macros out there that expect Undo to revert autoindent and InsertLine in two steps. So, again, this sillyness with excessive start_ and end_undo_chain. */ for(int64_t i = 0; i < c && !stop; i++) { start_undo_chain(b); if (b->win_x == 0) ensure_attributes(b); if (insert_one_line(b, b->cur_line_desc, b->cur_line, b->cur_pos > b->cur_line_desc->line_len ? b->cur_line_desc->line_len : b->cur_pos) == OK) { end_undo_chain(b); if (b->win_x) { int64_t a = -1; /* If b->win_x is nonzero, the move_to_sol() call will refresh the entire video, so we shouldn't do anything. However, we must poke into the next line initial state the correct state. */ if (b->syn) { b->attr_len = -1; ensure_attributes(b); ((line_desc *)b->cur_line_desc->ld_node.next)->highlight_state = b->next_state; } if (b->opt.auto_indent) { start_undo_chain(b); a = auto_indent_line(b, b->cur_line + 1, (line_desc *)b->cur_line_desc->ld_node.next, INT_MAX); end_undo_chain(b); } move_to_sol(b); line_down(b); if (a != -1) goto_pos(b, a); } else { int64_t a = -1; update_line(b, b->cur_line_desc, b->cur_y, b->cur_x, false); /* We need to avoid updates until we fix the next line. */ need_attr_update = false; /* We poke into the next line initial state the correct state. */ if (b->syn) { b->attr_len = -1; ensure_attributes(b); ((line_desc *)b->cur_line_desc->ld_node.next)->highlight_state = b->next_state; } if (b->opt.auto_indent) { start_undo_chain(b); a = auto_indent_line(b, b->cur_line + 1, (line_desc *)b->cur_line_desc->ld_node.next, INT_MAX); end_undo_chain(b); } move_to_sol(b); line_down(b); if (a != -1) goto_pos(b, a); if (b->cur_line == b->num_lines - 1) update_line(b, b->cur_line_desc, b->cur_y, 0, false); else scroll_window(b, b->cur_line_desc, b->cur_y, 1); } need_attr_update = true; } else end_undo_chain(b); } return stop ? STOPPED : 0; case DELETELINE_A: if (b->opt.read_only) return DOCUMENT_IS_READ_ONLY; NORMALIZE(c); col = b->win_x + b->cur_x; b->cur_pos = -1; start_undo_chain(b); for(int64_t i = 0; i < c && !stop; i++) { if (error = delete_one_line(b, b->cur_line_desc, b->cur_line)) break; scroll_window(b, b->cur_line_desc, b->cur_y, -1); } end_undo_chain(b); if (b->syn) { b->attr_len = -1; update_line(b, b->cur_line_desc, b->cur_y, 0, false); need_attr_update = true; } goto_column(b, col); return stop ? STOPPED : error; case UNDELLINE_A: if (b->opt.read_only) return DOCUMENT_IS_READ_ONLY; NORMALIZE(c); next_ld = (line_desc *)b->cur_line_desc->ld_node.next; start_undo_chain(b); for(int64_t i = 0; i < c && !stop; i++) { /* This is a bit tricky. First of all, if we are undeleting for the first time and the local attribute buffer is not valid we fill it. */ if (i == 0) ensure_attributes(b); if (error = undelete_line(b)) break; if (i == 0) { if (b->syn) { /* Now the only valid part of the local attribute buffer is before b->cur_char. */ if (b->cur_char < b->attr_len) b->attr_len = b->cur_char; update_line(b, b->cur_line_desc, b->cur_y, b->cur_x, false); next_line_state = b->next_state; } else update_line(b, b->cur_line_desc, b->cur_y, b->cur_x, false); } /* For each undeletion, we must poke into the next line its correct initial state. */ if (b->syn) ((line_desc *)b->cur_line_desc->ld_node.next)->highlight_state = next_line_state; /* We actually scroll down the remaining lines, if necessary. */ if (b->cur_y < ne_lines - 2) scroll_window(b, (line_desc *)b->cur_line_desc->ld_node.next, b->cur_y + 1, 1); } if (b->syn) { /* Finally, we force the update of the initial states of all following lines up to next_ld. */ need_attr_update = true; update_syntax_states(b, b->cur_y, b->cur_line_desc, next_ld); } end_undo_chain(b); return stop ? STOPPED : error; case DELETEEOL_A: if (b->opt.read_only) return DOCUMENT_IS_READ_ONLY; ensure_attributes(b); delete_to_eol(b, b->cur_line_desc, b->cur_line, b->cur_pos); update_line(b, b->cur_line_desc, b->cur_y, b->cur_x, false); need_attr_update = true; return OK; case SAVE_A: p = str_dup(b->filename); case SAVEAS_A: if (b->opt.read_only && !request_response(b, info_msg[SAVE_READ_ONLY_DOCUMENT],false)) { free(p); return DOCUMENT_NOT_SAVED; } if (p || (q = p = request_file(b, "Filename", b->filename))) { print_info(SAVING); if (buffer_file_modified(b, p) && !request_response(b, info_msg[a == SAVE_A ? FILE_HAS_BEEN_MODIFIED : FILE_ALREADY_EXISTS], false)) { free(p); return DOCUMENT_NOT_SAVED; } c = b->opt.read_only; SET_USER_FLAG(b, 0, opt.read_only); error = save_buffer_to_file(b, p); SET_USER_FLAG(b, c, opt.read_only); if (!print_error(error)) { const bool load_syntax = b->filename == NULL || ! same_str(extension(p), extension(b->filename)); change_filename(b, p); if (load_syntax) { b->syn = NULL; /* So that autoprefs will load the right syntax. */ load_auto_prefs(b, NULL); /* Will get extension from the name, or virtual extension. */ reset_syntax_states(b); reset_window(); } print_info(SAVED); } else { free(p); return ERROR; } } b->undo.last_save_step = b->undo.cur_step; return OK; case KEYCODE_A: if (c >= NUM_KEYS) c = -1; if (c < 0) { print_message(info_msg[PRESS_A_KEY]); do c = get_key_code(); while(c == INVALID_CHAR || c > 0xFF || CHAR_CLASS(c) == IGNORE); } col = (c < 0) ? -c-1 : c; snprintf(msg, MAX_MESSAGE_SIZE, "Key Code: 0x%02x, Input Class: %s, Assigned Command: %s", (int)col, input_class_names[CHAR_CLASS(c)], (key_binding[col] && key_binding[col][0]) ? key_binding[col] : "(none)" ); print_message(msg); return OK; case CLEAR_A: if ((b->is_modified) && !request_response(b, info_msg[THIS_DOCUMENT_NOT_SAVED], false)) return ERROR; clear_buffer(b); reset_window(); return OK; case OPEN_A: if ((b->is_modified) && !request_response(b, info_msg[THIS_DOCUMENT_NOT_SAVED], false)) return ERROR; case OPENNEW_A: if (p || (p = request_file(b, "Filename", b->filename))) { static bool dprompt = false; /* Set to true if we ever respond 'yes' to the prompt. */ bool empty = is_buffer_empty(b); if (b = new_buffer()) reset_window(); else { if (p) free(p); return OUT_OF_MEMORY; } buffer *dup = get_buffer_named(p); /* 'c' -- flag meaning "Don't prompt if we've ever responded 'yes'." */ if (!dup || dup == b || (dprompt && !c) || (dprompt = request_response(b, info_msg[SAME_NAME], false))) { error = load_file_in_buffer(b, p); if (error == FILE_DOES_NOT_EXIST && a == OPEN_A && (empty || request_response(b, info_msg[NO_SUCH_FILE_EXISTS], false))) error = OK; if (! error || (a == OPENNEW_A && error == FILE_DOES_NOT_EXIST)) { /* Keep the new buffer, or delete it? */ change_filename(b, p); p = NULL; b->syn = NULL; /* So that autoprefs will load the right syntax. */ if (b->opt.auto_prefs) { if (b->allocated_chars - b->free_chars <= MAX_SYNTAX_SIZE) { if (load_auto_prefs(b, NULL) == HAS_NO_EXTENSION) load_auto_prefs(b, DEF_PREFS_NAME); reset_syntax_states(b); } else if (error == OK) error = FILE_TOO_LARGE_SYNTAX_HIGHLIGHTING_DISABLED; } buffer * old_buffer = (buffer *)cur_buffer->b_node.prev; /* preserve cur_macro, find_string, and replace_string */ free_char_stream(cur_buffer->cur_macro); cur_buffer->cur_macro = dup_stream(old_buffer->cur_macro); cur_buffer->find_string = str_dup(old_buffer->find_string); cur_buffer->replace_string = str_dup(old_buffer->replace_string); cur_buffer->last_was_replace = old_buffer->last_was_replace; cur_buffer->last_was_regexp = old_buffer->last_was_regexp; if (a == OPEN_A) { do_action(cur_buffer, PREVDOC_A, 1, NULL); delete_buffer(); } } else { delete_buffer(); do_action(cur_buffer, PREVDOC_A, 1, NULL); } } else { delete_buffer(); do_action(cur_buffer, PREVDOC_A, 1, NULL); } print_error(error); reset_window(); if (p) free(p); return error; } return ERROR; case ABOUT_A: about(); return OK; case REFRESH_A: clear_entire_screen(); ttysize(); keep_cursor_on_screen(cur_buffer); reset_window(); if (executing_macro) { refresh_window(b); draw_status_bar(); } cur_buffer->act = ++buffer_actuations; return OK; case FIND_A: case FINDREGEXP_A: if (p || (p = request_string(b, a == FIND_A ? "Find" : "Find RegExp", b->find_string, false, COMPLETE_NONE, b->encoding == ENC_UTF8 || b->encoding == ENC_ASCII && b->opt.utf8auto))) { const encoding_type encoding = detect_encoding(p, strlen(p)); if (encoding != ENC_ASCII && b->encoding != ENC_ASCII && encoding != b->encoding) { free(p); return INCOMPATIBLE_SEARCH_STRING_ENCODING; } free(b->find_string); b->find_string = p; b->find_string_changed = 1; print_error(error = (a == FIND_A ? find : find_regexp)(b, NULL, false, false)); if (error == NOT_FOUND) perform_wrap = 2; b->last_was_replace = 0; b->last_was_regexp = (a == FINDREGEXP_A); } return error ? ERROR : 0; case REPLACE_A: case REPLACEONCE_A: case REPLACEALL_A: if (b->opt.read_only) { free(p); return DOCUMENT_IS_READ_ONLY; } if ((q = b->find_string) || (q = request_string(b, b->last_was_regexp ? "Find RegExp" : "Find", NULL, false, COMPLETE_NONE, b->encoding == ENC_UTF8 || b->encoding == ENC_ASCII && b->opt.utf8auto))) { const encoding_type search_encoding = detect_encoding(q, strlen(q)); if (search_encoding != ENC_ASCII && b->encoding != ENC_ASCII && search_encoding != b->encoding) { free(p); free(q); return INCOMPATIBLE_SEARCH_STRING_ENCODING; } if (q != b->find_string) { free(b->find_string); b->find_string = q; b->find_string_changed = 1; } if (p || (p = request_string(b, b->last_was_regexp ? "Replace RegExp" : "Replace", b->replace_string, true, COMPLETE_NONE, b->encoding == ENC_UTF8 || b->encoding == ENC_ASCII && b->opt.utf8auto))) { const encoding_type replace_encoding = detect_encoding(p, strlen(p)); bool first_search = true; int64_t num_replace = 0; if (replace_encoding != ENC_ASCII && b->encoding != ENC_ASCII && replace_encoding != b->encoding || search_encoding != ENC_ASCII && replace_encoding != ENC_ASCII && search_encoding != replace_encoding) { free(p); return INCOMPATIBLE_REPLACE_STRING_ENCODING; } c = 0; b->last_was_replace = 1; free(b->replace_string); b->replace_string = p; if (a == REPLACEALL_A) start_undo_chain(b); while(!stop && !(error = (b->last_was_regexp ? find_regexp : find)(b, NULL, !first_search && a != REPLACEALL_A && c != 'A' && c != 'Y', false))) { if (c != 'A' && a != REPLACEALL_A && a != REPLACEONCE_A) { refresh_window(b); c = request_char(b, b->opt.search_back ? "Replace (Yes/No/Last/All/Quit/Forward)" : "Replace (Yes/No/Last/All/Quit/Backward)", 'n'); if (c == 'Q') break; if (c == 'A') start_undo_chain(b); } if (c == 'A' || c == 'Y' || c == 'L' || a == REPLACEONCE_A || a == REPLACEALL_A) { /* We delay buffer encoding promotion until it is really necessary. */ if (b->encoding == ENC_ASCII) b->encoding = replace_encoding; const int64_t cur_char = b->cur_char; const int cur_x = b->cur_x; if (b->last_was_regexp) error = replace_regexp(b, p); else error = replace(b, strlen(b->find_string), p); if (!error) { if (cur_char < b->attr_len) b->attr_len = cur_char; update_line(b, b->cur_line_desc, b->cur_y, cur_x, false); if (b->syn) { need_attr_update = true; update_syntax_states(b, b->cur_y, b->cur_line_desc, NULL); } num_replace++; if (b->opt.search_back) error = char_left(b) ? NOT_FOUND : OK; else if (last_replace_empty_match) error = char_right(cur_buffer) ? NOT_FOUND : OK; if (error) break; } if (print_error(error)) { if (a == REPLACEALL_A || c == 'A') end_undo_chain(b); return ERROR; } } if (c == 'B' && !(b->opt.search_back) || c == 'F' && (b->opt.search_back)) { b->opt.search_back = !b->opt.search_back; b->find_string_changed = 1; } if (a == REPLACEONCE_A || c == 'L') break; first_search = false; } if (a == REPLACEALL_A || c == 'A') end_undo_chain(b); if (num_replace) { snprintf(msg, MAX_MESSAGE_SIZE, "%" PRId64 " replacement%s made.%s", num_replace, num_replace > 1 ? "s" : "", error == NOT_FOUND ? strchr(error_msg[NOT_FOUND], '(')-1 :""); print_message(msg); } if (stop) error = STOPPED; if (error == STOPPED) reset_window(); if (error == NOT_FOUND) perform_wrap = 2; if (error && ((c != 'A' && a != REPLACEALL_A || first_search) || error != NOT_FOUND)) { print_error(error); return ERROR; } return OK; } } return ERROR; case REPEATLAST_A: if (p) { char *q; c = strtoll(p, &q, 0); if (p == q) c = -1; while (*q == ' ') q++; if (*q) { if ( !strcasecmp("f",q) || !strcasecmp("find",q)) b->last_was_replace = false; else if (!strcasecmp("r",q) || !strcasecmp("replace",q)) b->last_was_replace = true; else { free(p); return SYNTAX_ERROR; } } free(p); } if (b->opt.read_only && b->last_was_replace) return DOCUMENT_IS_READ_ONLY; if (!b->find_string) return NO_SEARCH_STRING; if ((b->last_was_replace) && !b->replace_string) return NO_REPLACE_STRING; const encoding_type search_encoding = detect_encoding(b->find_string, strlen(b->find_string)); if (search_encoding != ENC_ASCII && b->encoding != ENC_ASCII && search_encoding != b->encoding) return INCOMPATIBLE_SEARCH_STRING_ENCODING; if (b->last_was_replace) { const encoding_type replace_encoding = detect_encoding(b->replace_string, strlen(b->replace_string)); if (replace_encoding != ENC_ASCII && b->encoding != ENC_ASCII && replace_encoding != b->encoding || search_encoding != ENC_ASCII && replace_encoding != ENC_ASCII && search_encoding != replace_encoding) return INCOMPATIBLE_REPLACE_STRING_ENCODING; } NORMALIZE(c); error = OK; int64_t num_replace = 0; start_undo_chain(b); for (int64_t i = 0; i < c && ! stop && ! (error = (b->last_was_regexp ? find_regexp : find)(b, NULL, !b->last_was_replace||((b->cur_pos + b->cur_line == 0)&&b->opt.search_back), perform_wrap > 0)); i++) if (b->last_was_replace) { const int64_t cur_char = b->cur_char; const int cur_x = b->cur_x; if (b->last_was_regexp) error = replace_regexp(b, b->replace_string); else error = replace(b, strlen(b->find_string), b->replace_string); if (! error) { if (cur_char < b->attr_len) b->attr_len = cur_char; update_line(b, b->cur_line_desc, b->cur_y, cur_x, false); if (b->syn) { need_attr_update = true; update_syntax_states(b, b->cur_y, b->cur_line_desc, NULL); } num_replace++; if (b->opt.search_back) error = char_left(b) ? NOT_FOUND : OK; else if (last_replace_empty_match) error = char_right(cur_buffer) ? NOT_FOUND : OK; } if (error) break; } end_undo_chain(b); if (num_replace) { snprintf(msg, MAX_MESSAGE_SIZE, "%" PRId64 " replacement%s made.%s", num_replace, num_replace > 1 ? "s" : "", error == NOT_FOUND ? strchr(error_msg[NOT_FOUND], '(')-1 :""); print_message(msg); } if (stop) error = STOPPED; if (error == STOPPED) reset_window(); if (error == NOT_FOUND) perform_wrap = 2; return num_replace && error ? ERROR : error; case MATCHBRACKET_A: return print_error(match_bracket(b)) ? ERROR : 0; case ALERT_A: alert(); return OK; case BEEP_A: ring_bell(); return OK; case FLASH_A: do_flash(); return OK; case ESCAPETIME_A: if (c < 0 && (c = request_number(b, "Timeout (1/10s)", -1))<0) return NUMERIC_ERROR(c); if (c < 256) { set_escape_time(c); return OK; } else return ESCAPE_TIME_OUT_OF_RANGE; case TABSIZE_A: if (c < 0 && (c = request_number(b, "TAB Size", b->opt.tab_size))<=0) return NUMERIC_ERROR(c); if (c < ne_columns / 2) { const int64_t pos = b->cur_pos; move_to_sol(b); b->opt.tab_size = c; goto_pos(b, pos); reset_window(); return OK; } return TAB_SIZE_OUT_OF_RANGE; case TURBO_A: if ((int)c < 0 && (int)(c = request_number(b, "Turbo Threshold", turbo)) < 0) return NUMERIC_ERROR(c); turbo = c; return OK; case CLIPNUMBER_A: if ((int)c < 0 && (int)(c = request_number(b, "Clip Number", b->opt.cur_clip)) < 0) return NUMERIC_ERROR(c); b->opt.cur_clip = c; return OK; case RIGHTMARGIN_A: if ((int)c < 0 && (int)(c = request_number(b, "Right Margin", b->opt.right_margin)) < 0) return NUMERIC_ERROR(c); b->opt.right_margin = c; return OK; case FREEFORM_A: SET_USER_FLAG(b, c, opt.free_form); return OK; case PRESERVECR_A: SET_USER_FLAG(b, c, opt.preserve_cr); return OK; case CRLF_A: SET_USER_FLAG(b, c, is_CRLF); return OK; case VISUALBELL_A: SET_USER_FLAG(b, c, opt.visual_bell); return OK; case STATUSBAR_A: SET_GLOBAL_FLAG(c, status_bar); reset_status_bar(); return OK; case HEXCODE_A: SET_USER_FLAG(b, c, opt.hex_code); reset_status_bar(); return OK; case FASTGUI_A: SET_GLOBAL_FLAG(c, fast_gui); reset_status_bar(); return OK; case INSERT_A: SET_USER_FLAG(b, c, opt.insert); return OK; case WORDWRAP_A: SET_USER_FLAG(b, c, opt.word_wrap); return OK; case AUTOINDENT_A: SET_USER_FLAG(b, c, opt.auto_indent); return OK; case VERBOSEMACROS_A: SET_GLOBAL_FLAG(c, verbose_macros); return OK; case AUTOPREFS_A: SET_USER_FLAG(b, c, opt.auto_prefs); return OK; case BINARY_A: SET_USER_FLAG(b, c, opt.binary); return OK; case NOFILEREQ_A: SET_USER_FLAG(b, c, opt.no_file_req); return OK; case REQUESTORDER_A: SET_GLOBAL_FLAG(c, req_order); return OK; case UTF8AUTO_A: SET_USER_FLAG(b, c, opt.utf8auto); return OK; case UTF8_A: { const encoding_type old_encoding = b->encoding, encoding = detect_buffer_encoding(b); if (c < 0 && b->encoding != ENC_UTF8 || c > 0) { if (encoding == ENC_ASCII || encoding == ENC_UTF8) b->encoding = ENC_UTF8; else return BUFFER_IS_NOT_UTF8; } else b->encoding = encoding == ENC_ASCII ? ENC_ASCII : ENC_8_BIT; if (old_encoding != b->encoding) { reset_syntax_states(b); reset_undo_buffer(&b->undo); } b->attr_len = -1; need_attr_update = false; move_to_sol(b); reset_window(); return OK; } case MODIFIED_A: SET_USER_FLAG(b, c, is_modified); return OK; case UTF8IO_A: if (c < 0) io_utf8 = ! io_utf8; else io_utf8 = c != 0; reset_window(); return OK; case DOUNDO_A: SET_USER_FLAG(b, c, opt.do_undo); if (!(b->opt.do_undo)) { reset_undo_buffer(&b->undo); b->atomic_undo = 0; } return OK; case READONLY_A: SET_USER_FLAG(b, c, opt.read_only); return OK; case CASESEARCH_A: SET_USER_FLAG(b, c, opt.case_search); b->find_string_changed = 1; return OK; case SEARCHBACK_A: SET_USER_FLAG(b, c, opt.search_back); b->find_string_changed = 1; return OK; case ATOMICUNDO_A: if (b->opt.do_undo) { /* set c to the desired b->link_undos */ if (!p) { c = b->link_undos ? b->link_undos - 1 : 1; } else if (p[0]=='0') { c = 0; } else if (p[0]=='-') { c = b->link_undos ? b->link_undos - 1 : 0; } else if (p[0]=='+' || p[0]=='1') { /* Kindly allow undocumented "AtomicUndo 1" also. */ c = b->link_undos + 1; } else { free(p); return INVALID_LEVEL; } while(c > b->link_undos) start_undo_chain(b); while(c < b->link_undos) end_undo_chain(b); b->atomic_undo = (c > 0) ? 1 : 0; snprintf(msg, MAX_MESSAGE_SIZE, "AtomicUndo level: %" PRId64, c); print_message(msg); if (p) free(p); return OK; } else { if (p) free(p); return UNDO_NOT_ENABLED; } case RECORD_A: if (recording_macro) { if (c < 0) { /* normal completion */ free_char_stream(b->cur_macro); b->cur_macro = recording_macro; recording_macro = NULL; print_message(info_msg[MACRO_RECORDING_COMPLETED]); } else if (c == 0) { /* cancel recording */ free_char_stream(recording_macro); recording_macro = NULL; print_message(info_msg[MACRO_RECORDING_CANCELLED]); } else { print_message(info_msg[INVALID_ARGUMENT_WHILE_RECORDING]); return ERROR; } } else if (c == 1) { /* resume recording */ if (recording_macro = dup_stream(b->cur_macro)) print_message(info_msg[MACRO_RECORD_APPENDING_STARTED]); } else if (c < 0) { /* start recording */ recording_macro = alloc_char_stream(0); print_message(info_msg[STARTING_MACRO_RECORDING]); } else { print_message(info_msg[INVALID_ARGUMENT_WHILE_RECORDING]); return ERROR; } return OK; case PLAY_A: if (c < 0 && (c = request_number(b, "Times", 1))<=0) return NUMERIC_ERROR(c); if (recording_macro) add_to_stream(recording_macro, "# include macro ", 26); error = play_macro(b->cur_macro, c); if (recording_macro) add_to_stream(recording_macro, "# conclude macro ", 27); return print_error(error) ? ERROR : 0; case SAVEMACRO_A: if (p || (p = request_file(b, "Macro Name", NULL))) { print_info(SAVING); optimize_macro(b->cur_macro, verbose_macros); if ((error = print_error(save_stream(b->cur_macro, p, b->is_CRLF, false))) == OK) print_info(SAVED); free(p); return error ? ERROR : 0; } return ERROR; case OPENMACRO_A: if (p || (p = request_file(b, "Macro Name", NULL))) { char_stream *cs; cs = load_stream(b->cur_macro, p, false, false); if (cs) b->cur_macro = cs; free(p); return cs ? 0 : ERROR; } return ERROR; case MACRO_A: if (p || (p = request_file(b, "Macro Name", NULL))) { error = print_error(execute_macro(b, p)); free(p); return error ? ERROR : 0; } return ERROR; case UNLOADMACROS_A: unload_macros(); return OK; case NEWDOC_A: new_buffer(); reset_window(); return OK; case CLOSEDOC_A: if ((b->is_modified) && !request_response(b, info_msg[THIS_DOCUMENT_NOT_SAVED], false)) return ERROR; if (!delete_buffer()) { close_history(); unset_interactive_mode(); exit(0); } cur_buffer->act = ++buffer_actuations; keep_cursor_on_screen(cur_buffer); reset_window(); /* We used to return ERROR after a buffer has been deleted to prevent the calling routines (and macros) attempting to work on an nonexistent buffer, but that should no longer be an issue. */ return OK; case NEXTDOC_A: /* Was NEXT_BUFFER: */ if (b->b_node.next->next) cur_buffer = (buffer *)b->b_node.next; else cur_buffer = (buffer *)buffers.head; cur_buffer->act = ++buffer_actuations; keep_cursor_on_screen(cur_buffer); reset_window(); need_attr_update = false; b->attr_len = -1; return OK; case PREVDOC_A: if (b->b_node.prev->prev) cur_buffer = (buffer *)b->b_node.prev; else cur_buffer = (buffer *)buffers.tail_pred; cur_buffer->act = ++buffer_actuations; keep_cursor_on_screen(cur_buffer); reset_window(); need_attr_update = false; b->attr_len = -1; return OK; case SELECTDOC_A: ; const int n = request_document(); if (n < 0 || !(b = get_nth_buffer(n))) return ERROR; cur_buffer = b; cur_buffer->act = ++buffer_actuations; keep_cursor_on_screen(cur_buffer); reset_window(); need_attr_update = false; b->attr_len = -1; return OK; case MARK_A: case MARKVERT_A: if (c < 0) c = 1; SET_USER_FLAG(b, c, marking); if (!b->marking) return(OK); print_message(info_msg[a == MARK_A ? BLOCK_START_MARKED : VERTICAL_BLOCK_START_MARKED]); b->mark_is_vertical = (a == MARKVERT_A); b->block_start_line = b->cur_line; b->block_start_pos = b->cur_pos; return OK; case CUT_A: if (b->opt.read_only) return DOCUMENT_IS_READ_ONLY; case COPY_A: if (!(error = print_error((b->mark_is_vertical ? copy_vert_to_clip : copy_to_clip)(b, (int)c < 0 ? b->opt.cur_clip : c, a == CUT_A)))) { b->marking = 0; update_window_lines(b, b->cur_line_desc, b->cur_y, ne_lines - 2, false); } return error ? ERROR : 0; case ERASE_A: if (b->opt.read_only) return DOCUMENT_IS_READ_ONLY; if (!(error = print_error((b->mark_is_vertical ? erase_vert_block : erase_block)(b, true)))) { b->marking = 0; update_window_lines(b, b->cur_line_desc, b->cur_y, ne_lines - 2, false); } return OK; case PASTE_A: case PASTEVERT_A: if (b->opt.read_only) return DOCUMENT_IS_READ_ONLY; if (!(error = print_error((a == PASTE_A ? paste_to_buffer : paste_vert_to_buffer)(b, (int)c < 0 ? b->opt.cur_clip : c)))) update_window_lines(b, b->cur_line_desc, b->cur_y, ne_lines - 2, false); assert_buffer_content(b); return error ? ERROR : 0; case GOTOMARK_A: if (b->marking) { delay_update(); goto_line_pos(b, b->block_start_line, b->block_start_pos); return OK; } print_error(MARK_BLOCK_FIRST); return ERROR; case OPENCLIP_A: if (p || (p = request_file(b, "Clip Name", NULL))) { error = print_error(load_clip(b->opt.cur_clip, p, b->opt.preserve_cr, b->opt.binary)); free(p); return error ? ERROR : 0; } return ERROR; case SAVECLIP_A: if (p || (p = request_file(b, "Clip Name", NULL))) { print_info(SAVING); if ((error = print_error(save_clip(b->opt.cur_clip, p, b->is_CRLF, b->opt.binary))) == OK) print_info(SAVED); free(p); return error ? ERROR : 0; } return ERROR; case EXEC_A: if (p || (p = request_string(b, "Command", b->command_line, false, COMPLETE_FILE, b->encoding == ENC_UTF8 || b->encoding == ENC_ASCII && b->opt.utf8auto))) { free(b->command_line); b->command_line = p; return print_error(execute_command_line(b, p)) ? ERROR : 0; } return ERROR; case NAMECONVERT_A: q = NULL; if (b->filename && (p = ne_getcwd(CUR_DIR_MAX_SIZE))) { if (b->filename[0] == '/' && (c < 1)) q = relative_file_path(b->filename, p); else if (b->filename[0] != '/' && (c != 0)) q = absolute_file_path(b->filename, p); if (q) { change_filename(b, q); reset_status_bar(); } free(p); } return OK; case SYSTEM_A: if (p || (p = request_string(b, "Shell command", NULL, false, COMPLETE_FILE, b->encoding == ENC_UTF8 || b->encoding == ENC_ASCII && b->opt.utf8auto))) { unset_interactive_mode(); if (system(p)) error = EXTERNAL_COMMAND_ERROR; set_interactive_mode(); free(p); ttysize(); keep_cursor_on_screen(cur_buffer); reset_window(); return print_error(error) ? ERROR : OK; } return ERROR; case THROUGH_A: if (b->opt.read_only) return DOCUMENT_IS_READ_ONLY; if (!b->marking) b->mark_is_vertical = 0; if (p || (p = request_string(b, "Filter", NULL, false, COMPLETE_FILE, b->encoding == ENC_UTF8 || b->encoding == ENC_ASCII && b->opt.utf8auto))) { int fin = -1, fout = -1; char tmpnam1[strlen(P_tmpdir)+strlen(NE_TMP)+2], tmpnam2[strlen(P_tmpdir)+strlen(NE_TMP)+2], *command; strcat(strcat(strcpy(tmpnam1, P_tmpdir), "/"), NE_TMP); strcat(strcat(strcpy(tmpnam2, P_tmpdir), "/"), NE_TMP); if ((fin = mkstemp(tmpnam1)) != -1) close(fin); if ((fout = mkstemp(tmpnam2)) != -1) close(fout); if (fin != -1 && fout != -1) { realloc_clip_desc(get_nth_clip(INT_MAX), INT_MAX, 0); if (!b->marking || !(error = (b->mark_is_vertical ? copy_vert_to_clip : copy_to_clip)(b, INT_MAX, false))) { if (!(error = save_clip(INT_MAX, tmpnam1, b->is_CRLF, b->opt.binary))) { if (command = malloc(strlen(p) + strlen(tmpnam1) + strlen(tmpnam2) + 16)) { strcat(strcat(strcat(strcat(strcat(strcpy(command, "( "), p), " ) <"), tmpnam1), " >"), tmpnam2); unset_interactive_mode(); if (system(command)) error = EXTERNAL_COMMAND_ERROR; set_interactive_mode(); if (!error) { if (!(error = load_clip(INT_MAX, tmpnam2, b->opt.preserve_cr, b->opt.binary))) { start_undo_chain(b); if (b->marking) (b->mark_is_vertical ? erase_vert_block : erase_block)(b, true); error = (b->mark_is_vertical ? paste_vert_to_buffer : paste_to_buffer)(b, INT_MAX); end_undo_chain(b); b->marking = 0; realloc_clip_desc(get_nth_clip(INT_MAX), INT_MAX, 0); } } free(command); } else error = OUT_OF_MEMORY; } } } else error = CANT_OPEN_TEMPORARY_FILE; remove(tmpnam1); remove(tmpnam2); ttysize(); keep_cursor_on_screen(cur_buffer); reset_window(); free(p); return print_error(error) ? ERROR : OK; } return ERROR; case TOUPPER_A: if (b->opt.read_only) return DOCUMENT_IS_READ_ONLY; NORMALIZE(c); start_undo_chain(b); for(int64_t i = 0; i < c && !(error = to_upper(b)) && !stop; i++); end_undo_chain(b); if (stop) error = STOPPED; return print_error(error) ? ERROR : 0; case TOLOWER_A: if (b->opt.read_only) return DOCUMENT_IS_READ_ONLY; NORMALIZE(c); start_undo_chain(b); for(int64_t i = 0; i < c && !(error = to_lower(b)) && !stop; i++); end_undo_chain(b); if (stop) error = STOPPED; return print_error(error) ? ERROR : 0; case CAPITALIZE_A: if (b->opt.read_only) return DOCUMENT_IS_READ_ONLY; NORMALIZE(c); start_undo_chain(b); for(int64_t i = 0; i < c && !(error = capitalize(b)) && !stop; i++); end_undo_chain(b); if (stop) error = STOPPED; return print_error(error) ? ERROR : 0; case CENTER_A: if (b->opt.read_only) return DOCUMENT_IS_READ_ONLY; NORMALIZE(c); start_undo_chain(b); for(int64_t i = 0; i < c && !(error = center(b)) && !stop; i++) { need_attr_update = true; b->attr_len = -1; update_line(b, b->cur_line_desc, b->cur_y, 0, false); move_to_sol(b); if (line_down(b) != OK) break; } end_undo_chain(b); if (stop) error = STOPPED; return print_error(error) ? ERROR : 0; case PARAGRAPH_A: if (b->opt.read_only) return DOCUMENT_IS_READ_ONLY; NORMALIZE(c); start_undo_chain(b); for(int64_t i = 0; i < c && !(error = paragraph(b, i == 0)) && !stop; i++); end_undo_chain(b); if (stop) error = STOPPED; if (error == STOPPED) reset_window(); assert(b->cur_pos >= 0); return print_error(error) ? ERROR : 0; case SHIFT_A: if (b->opt.read_only) return DOCUMENT_IS_READ_ONLY; error = shift(b, p, &msg[0], MAX_MESSAGE_SIZE); if (stop) error = STOPPED; if (p) free(p); return print_error(error) ? ERROR : 0; case LOADPREFS_A: if (p || (p = request_file(b, "Prefs Name", NULL))) { error = print_error(load_prefs(b, p)); free(p); return error ? ERROR : OK; } return ERROR; case SAVEPREFS_A: if (p || (p = request_file(b, "Prefs Name", NULL))) { error = print_error(save_prefs(b, p)); free(p); return error ? ERROR : OK; } return ERROR; case LOADAUTOPREFS_A: return print_error(load_auto_prefs(b, NULL)) ? ERROR : OK; case SAVEAUTOPREFS_A: return print_error(save_auto_prefs(b, NULL)) ? ERROR : OK; case SAVEDEFPREFS_A: return print_error(save_auto_prefs(b, DEF_PREFS_NAME)) ? ERROR : OK; case SYNTAX_A: if (!do_syntax) return SYNTAX_NOT_ENABLED; if (p || (p = request_string(b, "Syntax", b->syn ? (const char *)b->syn->name : NULL, true, COMPLETE_SYNTAX, b->encoding == ENC_UTF8 || b->encoding == ENC_ASCII && b->opt.utf8auto))) { if (!strcmp(p, "*")) b->syn = NULL; else error = print_error(load_syntax_by_name(b, p)); reset_window(); free(p); return error ? ERROR : OK; } return ERROR; case ESCAPE_A: handle_menus(); return OK; case UNDO_A: if (b->opt.read_only) return DOCUMENT_IS_READ_ONLY; if (!b->opt.do_undo) return UNDO_NOT_ENABLED; NORMALIZE(c); delay_update(); if (b->atomic_undo) { b->atomic_undo = 0; while (b->link_undos) end_undo_chain(b); print_message(info_msg[ATOMIC_UNDO_LEVEL_0]); } for(int64_t i = 0; i < c && !(error = undo(b)) && !stop; i++); if (stop) error = STOPPED; b->is_modified = b->undo.cur_step != b->undo.last_save_step; update_window(b); return print_error(error) ? ERROR : 0; case REDO_A: if (b->opt.read_only) return DOCUMENT_IS_READ_ONLY; if (!b->opt.do_undo) return UNDO_NOT_ENABLED; NORMALIZE(c); delay_update(); for(int64_t i = 0; i < c && !(error = redo(b)) && !stop; i++); if (stop) error = STOPPED; b->is_modified = b->undo.cur_step != b->undo.last_save_step; update_window(b); return print_error(error) ? ERROR : 0; case FLAGS_A: help("FLAGS"); reset_window(); return OK; case HELP_A: help(p); if (p) free(p); reset_window(); return OK; case SUSPEND_A: stop_ne(); reset_window(); keep_cursor_on_screen(cur_buffer); return OK; case AUTOCOMPLETE_A: /* Since we are going to call other actions (INSERTSTRING_A and DELETEPREVWORD_A), we do not want to record this insertion twice. Also, we are counting on INSERTSTRING_A to handle character encoding issues. */ recording = recording_macro; int64_t pos = b->cur_pos; if (!p) { /* no prefix given; find one left of the cursor. */ if (context_prefix(b, &p, &pos)) return OUT_OF_MEMORY; } snprintf(msg, MAX_MESSAGE_SIZE, "AutoComplete: prefix \"%s\"", p); int e; if (p = autocomplete(p, msg, true, &e)) { recording_macro = NULL; start_undo_chain(b); if (pos >= b->cur_pos || (error = do_action(b, DELETEPREVWORD_A, 1, NULL)) == OK) error = do_action(b, INSERTSTRING_A, 0, p); end_undo_chain(b); recording_macro = recording; print_message(info_msg[e]); } else if (stop) error = STOPPED; else if (e == AUTOCOMPLETE_NO_MATCH) print_message(info_msg[AUTOCOMPLETE_NO_MATCH]); return print_error(error) ? ERROR : 0; default: if (p) free(p); return OK; } } ne-3.3.4/src/ansi.c000066400000000000000000000051261475116431000140160ustar00rootroot00000000000000/* Hardwired ANSI terminal control sequences. Copyright (C) 2001-2025 Todd M. Lewis and Sebastiano Vigna This file is part of ne, the nice editor. This library 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 library 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 . */ #include "ne.h" #ifdef NE_TERMCAP #include "info2cap.h" #endif /* Pokes into the terminfo capability strings the ANSI definitions. This is good for situation in which no database is available. The keyboard is already fixed in keys.c, because key_may_set() will poke into the keyboard capabilities all ANSI keyboard sequences. */ void setup_ansi_term(void) { #ifdef NE_TERMCAP ne_cursor_address = "\x1b[%i%d;%dH"; ne_set_background = "\x1b[4%dm"; ne_set_foreground = "\x1b[3%dm"; #else /* This call to tgetent() is necessary due to changes to ncurses that made tgoto() stop working if setupterm()/tgetent() are not invoked first. Unfortunately, this means that if this call does not succeed ANSI mode will not work. */ tgetent(NULL, "vt100"); ne_cursor_address = "\x1b[%i%p1%d;%p2%dH"; ne_set_background = "\x1b[4%p1%dm"; ne_set_foreground = "\x1b[3%p1%dm"; #endif ne_enter_bold_mode = "\x1b[1m"; ne_enter_underline_mode = "\x1b[4m"; ne_enter_blink_mode = "\x1b[5m"; ne_lines = 25; ne_columns = 80; ne_carriage_return = "\xd"; ne_cursor_home = "\x1b[H"; ne_cursor_right = "\x1b[C"; ne_cursor_down = "\x1b[B"; ne_cursor_left = "\x1b[D"; ne_cursor_up = "\x1b[A"; ne_auto_right_margin = 1; ne_eat_newline_glitch = 0; ne_clr_eos = "\x1b[J"; ne_clear_screen = "\x1b[H\x1b[J"; ne_bell = "\x7"; ne_scroll_forward = "\xa"; ne_enter_standout_mode = "\x1b[7m"; ne_exit_standout_mode = ne_exit_attribute_mode = "\x1b[m"; ne_magic_cookie_glitch = -1; ne_move_standout_mode = 0; ne_insert_line = "\x1b[L"; ne_delete_line = "\x1b[M"; ne_delete_character = "\x1b[P"; ne_move_insert_mode = 1; ne_exit_alt_charset_mode = "\x1b[10m"; ne_tilde_glitch = 0; ne_memory_below = 0; ne_has_meta_key = 0; ne_clr_eol = "\x1b[K"; ne_transparent_underline = 0; ne_no_color_video = 3; ansi_color_ok = true; } ne-3.3.4/src/ansi.h000066400000000000000000000014641475116431000140240ustar00rootroot00000000000000/* Hardwired ANSI terminal control sequences (prototypes). Copyright (C) 2001-2025 Todd M. Lewis and Sebastiano Vigna This file is part of ne, the nice editor. This library 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 library 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 . */ void setup_ansi_term(void); ne-3.3.4/src/autocomp.c000066400000000000000000000143421475116431000147130ustar00rootroot00000000000000/* AutoComplete Copyright (C) 2010-2025 Todd M. Lewis and Sebastiano Vigna This file is part of ne, the nice editor. This library 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 library 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 . */ #include "ne.h" #include "support.h" #define EXTERNAL_FLAG_CHAR '*' #define MAX_AUTOCOMPLETE_SCAN (1000000) static req_list rl; /* Keeps track of how many strings we have scanned. At MAX_AUTOCOMPLETE_SCAN we return. */ static int count_scanned; static void add_string(const char * const s, const int len, const int ext) { char *buf = strntmp(s, len); if (len < 1) return; req_list_add(&rl, buf, ext); } static void search_buff(const buffer *b, char * p, const int encoding, const bool case_search, const int ext) { assert(p); const int p_len = strlen(p); const int (*cmp)(const char *, const char *, size_t) = (const int (*)(const char *, const char *, size_t))(case_search ? strncmp : strncasecmp); for(line_desc *ld = (line_desc *)b->line_desc_list.head, *next; next = (line_desc *)ld->ld_node.next; ld = next) { int64_t l = 0, r = 0; do { /* find left edge of word */ while (l < ld->line_len - p_len && !ne_isword(get_char(&ld->line[l], b->encoding), b->encoding)) l = next_pos(ld->line, l, b->encoding); if (l < ld->line_len - p_len ) { int ch; /* find right edge of word */ r = next_pos(ld->line, l, b->encoding); /* accept "'" as a word character if it is followed by another word character, so that words like "don't" are not broken into "don" and "t". */ while (r < ld->line_len && ( ne_isword(ch=get_char(&ld->line[r], b->encoding), b->encoding) || ( r+1 < ld->line_len && ch == '\'' && ne_isword(get_char(&ld->line[r+1], b->encoding), b->encoding)) ) ) r = next_pos(ld->line, r, b->encoding); if ((b != cur_buffer || ld != b->cur_line_desc || b->cur_pos < l || r < b->cur_pos) && r - l > p_len && (b->encoding == encoding || is_ascii(&ld->line[l], r - l)) && !cmp(p, &ld->line[l], p_len)) add_string(&ld->line[l], r - l, ext); l = r; count_scanned++; } assert(l <= ld->line_len); if (stop || count_scanned >= MAX_AUTOCOMPLETE_SCAN) { add_string(NULL, -1, 0); return; } } while (l < ld->line_len - p_len); } add_string(NULL, -1, 0); } /* Returns a completion for the (non-NULL) prefix p, showing suffixes from all buffers if ext is true. Note that p is free()'d by this function, and that, in turn, the returned string must be free()'d by the caller if it is non-NULL (a returned NULL means that no completion is available). If there is more than one completion, this function will invoke request_strings() (and subsequently reset_window()) after displaying req_msg. In any case, error will contain a value out of those in the enum info that start with AUTOCOMPLETE_. */ char *autocomplete(char *p, char *req_msg, const int ext, int * const error) { assert(p); int max_len = 0, min_len = INT_MAX, prefix_len = strlen(p); static int ac_prune = true; req_list_init(&rl, (cur_buffer->opt.case_search ? strcmp : strdictcmp), false, false, EXTERNAL_FLAG_CHAR); rl.prune = ac_prune; count_scanned = 0; search_buff(cur_buffer, p, cur_buffer->encoding, cur_buffer->opt.case_search, false); if (stop) { req_list_free(&rl); free(p); return NULL; } if (ext) { buffer *b = (buffer *)buffers.head; while (b->b_node.next) { if (b != cur_buffer) { search_buff(b, p, cur_buffer->encoding, cur_buffer->opt.case_search, true); if (stop) { req_list_free(&rl); free(p); return NULL; } } b = (buffer *)b->b_node.next; } } for(int i = 0; i < rl.cur_entries; i++) { const int l = strlen(rl.entries[i]); if (max_len < l) max_len = l; if (min_len > l) min_len = l; } /* We compact the table into a vector of char pointers. */ req_list_finalize(&rl); free(p); p = NULL; #ifdef NE_TEST /* During tests, we always output the middle entry. */ if (rl.cur_entries) { if (rl.entries[rl.cur_entries/2][strlen(rl.entries[rl.cur_entries/2]) - 1] == EXTERNAL_FLAG_CHAR) rl.entries[rl.cur_entries/2][strlen(rl.entries[rl.cur_entries/2]) - 1] = 0; p = str_dup(rl.entries[rl.cur_entries/2]); } *error = AUTOCOMPLETE_COMPLETED; req_list_free(&rl); return p; #endif if (rl.cur_entries > 0) { qsort(rl.entries, rl.cur_entries, sizeof(char *), strdictcmpp); /* Find maximum common prefix. */ int m = strlen(rl.entries[0]); if (m && rl.entries[0][m-1] == EXTERNAL_FLAG_CHAR) m--; encoding_type enc_0 = detect_encoding(rl.entries[0], m); for (int i = 1; m && i < rl.cur_entries; i++) { encoding_type enc_i = detect_encoding(rl.entries[i], strlen(rl.entries[i])); int mi = max_prefix(rl.entries[0], enc_0, rl.entries[i], enc_i); if (mi < m) m = mi; } /* If we can output more characters than the prefix len, we do so without starting the requester. */ if (m > prefix_len) { p = malloc(m + 1); strncpy(p, rl.entries[0], m); p[m] = 0; *error = min_len == m ? AUTOCOMPLETE_COMPLETED : AUTOCOMPLETE_PARTIAL; } else { if (req_msg) print_message(req_msg); int result = request_strings(&rl, 0); ac_prune = rl.prune; if (result != ERROR) { result = result >= 0 ? result : -result - 2; /* Delete EXTERNAL_FLAG_CHAR at the end of the strings if necessary. */ if (rl.entries[result][strlen(rl.entries[result]) - 1] == EXTERNAL_FLAG_CHAR) rl.entries[result][strlen(rl.entries[result]) - 1] = 0; p = str_dup(rl.entries[result]); *error = AUTOCOMPLETE_COMPLETED; } else *error = AUTOCOMPLETE_CANCELLED; reset_window(); } } else *error = AUTOCOMPLETE_NO_MATCH; req_list_free(&rl); D(fprintf(stderr, "autocomp returning '%s', entries: %d\n", p, rl.cur_entries);) return p; } ne-3.3.4/src/buffer.c000066400000000000000000001423621475116431000143410ustar00rootroot00000000000000/* Buffer handling functions, including allocation, deallocation, and I/O. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2025 Todd M. Lewis and Sebastiano Vigna This file is part of ne, the nice editor. This library 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 library 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 . */ #include "ne.h" #include "support.h" #include /* The standard pool allocation dimension. */ #define STD_POOL_SIZE (16 * 1024) /* The standard line descriptor pool allocation dimension (in lines). */ #define STD_LINE_DESC_POOL_SIZE (512) /* The starting size when reading a non-seekable file. */ #define START_SIZE (8 * 1024) /* The number of lines by which we increment the first line descriptor pool dimension, with respect to the number of lines of the given file. */ #define STANDARD_LINE_INCREMENT (256) /* The size of the space array. Batch printing of spaces happens in blocks of this size. */ #define MAX_STACK_SPACES (256) /* The length of the block used in order to optimize saves. */ #define SAVE_BLOCK_LEN (16 * 1024 - 1) /* The length of a half of the circular buffer used for memory mapping. */ #define CIRC_BUFFER_SIZE (8 * 1024) /* The number of line descriptors in the buffer used for memory mapping. */ #define LD_BUFFER_COUNT (256) /* Detects (heuristically) the encoding of a buffer. */ encoding_type detect_buffer_encoding(const buffer * const b) { line_desc *ld = (line_desc *)b->line_desc_list.head, *next; encoding_type encoding = ENC_ASCII, e; while(next = (line_desc *)ld->ld_node.next) { e = detect_encoding(ld->line, ld->line_len); if (e != ENC_ASCII) { if (encoding == ENC_ASCII) encoding = e; if (e == ENC_8_BIT) encoding = ENC_8_BIT; } ld = next; } return encoding; } /* These functions allocate and deallocate character pools. The size of the pool is the number of characters, and it is forced to be at least STD_POOL_SIZE. force is passed to alloc_or_mmap(). */ char_pool *alloc_char_pool(int64_t size, const int fd_or_zero, int force) { if (size < STD_POOL_SIZE && ! fd_or_zero) size = STD_POOL_SIZE; char_pool * const cp = calloc(1, sizeof(char_pool)); if (cp) { if (cp->pool = alloc_or_mmap(size, fd_or_zero, &force)) { cp->mapped = force; cp->size = size; return cp; } free(cp); } return NULL; } char_pool *alloc_char_pool_from_memory(char * const pool, const int64_t size) { char_pool * const cp = calloc(1, sizeof(char_pool)); if (cp) { cp->pool = pool; cp->size = size; return cp; } return NULL; } void free_char_pool(char_pool * const cp) { if (cp == NULL) return; if (cp->mapped) munmap(cp->pool, cp->size); else free(cp->pool); free(cp); } /* Given a pointer in a character pool and a buffer, this function returns the respective pool. It can return NULL if the pointer wasn't in any pool, but this condition denotes a severe malfunctioning. */ char_pool *get_char_pool(buffer * const b, char * const p) { for(char_pool *cp = (char_pool *)b->char_pool_list.head; cp->cp_node.next;) { assert_char_pool(cp); if (p >= cp->pool && p < cp->pool + cp->size) return cp; cp = (char_pool *)cp->cp_node.next; } assert(false); return NULL; } /* These functions allocate and deallocate line-descriptor pools. The size of the pool is the number of lines, and is forced to be at least STD_LINE_DESC_POOL_SIZE. force is passed to alloc_or_mmap(). */ line_desc_pool *alloc_line_desc_pool(int64_t pool_size, int force) { if (pool_size < STD_LINE_DESC_POOL_SIZE) pool_size = STD_LINE_DESC_POOL_SIZE; line_desc_pool * const ldp = calloc(1, sizeof(line_desc_pool)); if (ldp) { if (ldp->pool = alloc_or_mmap(pool_size * (do_syntax ? sizeof(line_desc) : sizeof(no_syntax_line_desc)), 0, &force)) { ldp->mapped = force; ldp->size = pool_size; new_list(&ldp->free_list); for(int64_t i = 0; i < pool_size; i++) if (do_syntax) add_tail(&ldp->free_list, &((line_desc *)ldp->pool)[i].ld_node); else add_tail(&ldp->free_list, &((no_syntax_line_desc *)ldp->pool)[i].ld_node); return ldp; } free(ldp); } return NULL; } /* This function creates a line-descriptor pool using a given region of memory. which must be able to hold pool_size element of the right type (depending on do_syntax). All items in the pool are considered to be allocated. */ line_desc_pool *alloc_line_desc_pool_from_memory(void *pool, int64_t pool_size) { line_desc_pool * const ldp = calloc(1, sizeof(line_desc_pool)); if (ldp) { new_list(&ldp->free_list); ldp->pool = pool; ldp->size = ldp->allocated_items = pool_size; return ldp; } return NULL; } void free_line_desc_pool(line_desc_pool * const ldp) { if (ldp == NULL) return; assert_line_desc_pool(ldp); if (ldp->mapped) munmap(ldp->pool, ldp->size * (do_syntax ? sizeof(line_desc) : sizeof(no_syntax_line_desc))); else free(ldp->pool); free(ldp); } /* These functions allocate and deallocate a buffer. Note that on allocation we have to initialize the list pointers, and on dellocation we have to free all the lists. Moreover, on allocation a buffer pointer can be passed so that the new buffer can inherit various user flags. */ buffer *alloc_buffer(const buffer * const cur_b) { buffer *b; if (b = calloc(1, sizeof(buffer))) { new_list(&b->line_desc_pool_list); new_list(&b->line_desc_list); new_list(&b->char_pool_list); b->act = ++buffer_actuations; b->cur_macro = alloc_char_stream(0); b->bpaste_support = bracketed_paste ? 1 : 0; b->opt.tab_size = 8; b->opt.insert = b->opt.tabs = b->opt.shift_tabs = b->opt.automatch = b->opt.do_undo = b->opt.auto_prefs = 1; b->opt.utf8auto = io_utf8; b->attr_len = -1; if (cur_b) { b->opt.cur_clip = cur_b->opt.cur_clip; b->opt.tab_size = cur_b->opt.tab_size; b->opt.tabs = cur_b->opt.tabs; b->opt.del_tabs = cur_b->opt.del_tabs; b->opt.shift_tabs = cur_b->opt.shift_tabs; b->opt.automatch = cur_b->opt.automatch; b->opt.right_margin = cur_b->opt.right_margin; b->opt.free_form = cur_b->opt.free_form; b->opt.hex_code = cur_b->opt.hex_code; b->opt.word_wrap = cur_b->opt.word_wrap; b->opt.auto_indent = cur_b->opt.auto_indent; b->opt.preserve_cr = cur_b->opt.preserve_cr; b->opt.do_undo = cur_b->opt.do_undo; b->opt.auto_prefs = cur_b->opt.auto_prefs; b->opt.no_file_req = cur_b->opt.no_file_req; b->opt.case_search = cur_b->opt.case_search; b->opt.binary = cur_b->opt.binary; b->opt.utf8auto = cur_b->opt.utf8auto; b->opt.visual_bell = cur_b->opt.visual_bell; b->bpaste_support = cur_b->bpaste_support; if (b->bpaste_support == 2) { b->bpaste_macro_before = str_dup(cur_b->bpaste_macro_before); b->bpaste_macro_after = str_dup(cur_b->bpaste_macro_after); } } /* This leaves out only opt.read_only and opt.search_back, which are implicitly set to 0 by the calloc(). */ return b; } return NULL; } /* This function is useful when resetting a buffer, but not really destroying it. Since it modifies some lists, it cannot be interrupted from a signal. Note that the search, replace and command_line strings are not cleared. */ void free_buffer_contents(buffer * const b) { if (!b) return; block_signals(); free_list(&b->line_desc_pool_list, free_line_desc_pool); free_list(&b->char_pool_list, free_char_pool); new_list(&b->line_desc_list); b->cur_line_desc = b->top_line_desc = NULL; b->allocated_chars = b->free_chars = 0; b->num_lines = 0; b->is_CRLF = false; b->encoding = ENC_ASCII; b->bookmark_mask = 0; b->mtime = 0; free_char_stream(b->last_deleted); b->last_deleted = NULL; free(b->filename); b->filename = NULL; reset_undo_buffer(&b->undo); b->is_modified = b->marking = b->x_wanted = 0; release_signals(); } /* Removes all data in a buffer, but leaves in the current macro, the search, replace and command_line strings, and an empty line. */ void clear_buffer(buffer * const b) { if (!b) return; block_signals(); free_buffer_contents(b); line_desc * const ld = alloc_line_desc(b); add_head(&b->line_desc_list, &ld->ld_node); if (do_syntax) { ld->highlight_state.state = 0; ld->highlight_state.stack = NULL; ld->highlight_state.saved_s[0] = 0; } b->num_lines = 1; reset_position_to_sof(b); assert_buffer(b); release_signals(); } /* Frees all the data associated to a buffer. */ void free_buffer(buffer * const b) { if (b == NULL) return; assert_buffer(b); free_buffer_contents(b); free_char_stream(b->cur_macro); free(b->find_string); free(b->replace_string); free(b->command_line); free(b->bpaste_macro_before); free(b->bpaste_macro_after); if (b->attr_buf) free(b->attr_buf); free(b); } /* Computes how many characters have been "lost" in a buffer, that is, how many free characters lie inside the first and last used characters of the character pools. This characters can only be allocated by alloc_chars_around(). */ int64_t calc_lost_chars(const buffer * const b) { int64_t n = 0; for(char_pool *cp = (char_pool *)b->char_pool_list.head; cp->cp_node.next; cp = (char_pool *)cp->cp_node.next) n += cp->size - (cp->last_used - cp->first_used + 1); return b->free_chars - n; } /* Returns the nth buffer in the global buffer list, or NULL if less than n buffers are available. */ buffer *get_nth_buffer(int n) { for(buffer *b = (buffer *)buffers.head; b->b_node.next; b = (buffer *)b->b_node.next) if (!n--) return b; return NULL; } /* Returns the first buffer with a name matching *p. The buffers' names and *p are converted to their fully qualified form for the comparisons. This is a departure from earlier behavior, which only considered the file_part() of either name. */ buffer *get_buffer_named(const char *p) { char *bname=NULL, *pname=NULL, *cwd=NULL; buffer *b; int rc = 1; if (!p) return NULL; cwd = ne_getcwd(CUR_DIR_MAX_SIZE); if (!cwd) return NULL; if ((pname = absolute_file_path(p, cwd))) { for(b = (buffer *)buffers.head; b->b_node.next; b = (buffer *)b->b_node.next) { if (b->filename && (bname = absolute_file_path(b->filename, cwd))) { if (!(rc = strcmp(bname, pname))) break; } free(bname); bname = NULL; } } free(pname); free(bname); free(cwd); if (!rc) return b; return NULL; } /* Returns true if the given pointer references an extant buffer. */ bool is_buffer(const buffer * const maybe_buf) { for(buffer *b = (buffer *)buffers.head; b->b_node.next; b = (buffer *)b->b_node.next) if (maybe_buf == b) return true; return false; } /* Returns true if the given buffer is empty. */ bool is_buffer_empty(const buffer * const b) { if (b) { if (b->line_desc_list.head->next == NULL || b->line_desc_list.head->next->next == NULL && ((line_desc *)b->line_desc_list.head)->line_len == 0) return true; } return false; } /* Returns true if any of the buffers has been modified since the last save. */ int modified_buffers(void) { for(buffer *b = (buffer *)buffers.head; b->b_node.next; b = (buffer *)b->b_node.next) if (b->is_modified) return true; return false; } /* Saves all buffers which have been modified since the last save. Returns an error if a save is unsuccessful, a file on-disk was modified since last loaded or saved, or if a buffer has no name. */ int save_all_modified_buffers(void) { int rc = 0; for(buffer *b = (buffer *)buffers.head; b->b_node.next; b = (buffer *)b->b_node.next) if (b->is_modified) { if (buffer_file_modified(b, NULL)) rc = ERROR; else if (save_buffer_to_file(b, NULL)) rc = ERROR; } return rc; } /* Now we have the much more sophisticated allocation functions which create small elements such as lines and line descriptors. All the operations are in the context of a given buffer. Most of these functions are protected internally against being interrupted by signals, since auto_save could die miserably because of the inconsistent state of a list. */ /* Allocates a line descriptor from the pools available in the given buffer. A new pool is allocated and linked if necessary. New line descriptors are created with an invalid syntax state, so they will always force an update. */ line_desc *alloc_line_desc(buffer * const b) { block_signals(); line_desc_pool *ldp; for(ldp = (line_desc_pool *)b->line_desc_pool_list.head; ldp->ldp_node.next; ldp = (line_desc_pool *)ldp->ldp_node.next) { assert_line_desc_pool(ldp); if (ldp->free_list.head->next) { line_desc * const ld = (line_desc *)ldp->free_list.head; rem(&ld->ld_node); if (!ldp->free_list.head->next) { rem(&ldp->ldp_node); add_tail(&b->line_desc_pool_list, &ldp->ldp_node); } ldp->allocated_items++; ld->line = NULL; ld->line_len = 0; if (do_syntax) ld->highlight_state.state = -1; release_signals(); return ld; } } /* No chances, all pools are full. Let's allocate a new one, using the standard pool size, and let's put it at the start of the list, so that it is always scanned first. */ if (ldp = alloc_line_desc_pool(0, -1)) { add_head(&b->line_desc_pool_list, &ldp->ldp_node); line_desc * const ld = (line_desc *)ldp->free_list.head; rem(&ld->ld_node); ldp->allocated_items = 1; if (do_syntax) ld->highlight_state.state = -1; release_signals(); return ld; } release_signals(); return NULL; } /* Frees a line descriptor, (and the line descriptor pool containing it, should it become empty). */ void free_line_desc(buffer * const b, line_desc * const ld) { /* We scan the pool list in order to find where the given line descriptor lives. */ line_desc_pool *ldp; for(ldp = (line_desc_pool *)b->line_desc_pool_list.head; ldp->ldp_node.next; ldp = (line_desc_pool *)ldp->ldp_node.next) { assert_line_desc_pool(ldp); if (do_syntax && ld >= (line_desc *)ldp->pool && ld < (line_desc *)ldp->pool + ldp->size || !do_syntax && (no_syntax_line_desc *)ld >= (no_syntax_line_desc *)ldp->pool && (no_syntax_line_desc *)ld < (no_syntax_line_desc *)ldp->pool + ldp->size) break; } assert(ldp->ldp_node.next != NULL); block_signals(); add_head(&ldp->free_list, &ld->ld_node); if (--ldp->allocated_items == 0) { rem(&ldp->ldp_node); free_line_desc_pool(ldp); } release_signals(); } /* Allocates len characters from the character pools of the given buffer. If necessary, a new pool is allocated. */ char *alloc_chars(buffer * const b, const int64_t len) { if (!len || !b) return NULL; assert_buffer(b); block_signals(); char_pool *cp; for(cp = (char_pool *)b->char_pool_list.head; cp->cp_node.next; cp = (char_pool *)cp->cp_node.next) { assert_char_pool(cp); /* We try to allocate before the first used character, or after the last used character. If we succeed with a pool which is not the head of the list, we move it to the head in order to optimize the next try. */ if (cp->first_used >= len) { cp->first_used -= len; b->free_chars -= len; if (cp != (char_pool *)b->char_pool_list.head) { rem(&cp->cp_node); add_head(&b->char_pool_list, &cp->cp_node); } release_signals(); return cp->pool + cp->first_used; } else if (cp->size - cp->last_used > len) { cp->last_used += len; b->free_chars -= len; if (cp != (char_pool *)b->char_pool_list.head) { rem(&cp->cp_node); add_head(&b->char_pool_list, &cp->cp_node); } release_signals(); return cp->pool + cp->last_used - len + 1; } } /* If no free space has been found, we allocate a new pool which is guaranteed to contain at least len characters. The pool is added to the head of the list. */ if (cp = alloc_char_pool(len, 0, -1)) { add_head(&b->char_pool_list, &cp->cp_node); cp->last_used = len - 1; b->allocated_chars += cp->size; b->free_chars += cp->size - len; release_signals(); return cp->pool; } release_signals(); return NULL; } /* This function is very important, since it embeds all the philosophy behind ne's character pool management. It performs an allocation *locally*, that is, it tries to see if there are enough free characters around the line pointed to by a line descriptor by looking at non-nullness of surrounding characters (if a character is set to 0, it is free). First the characters after the line are checked, then the characters before (this can be reversed via the check_first_before flag). The number of characters available *after* the line is returned, or ERROR if the allocation failed. The caller can recover the characters available before the line since he knows the length of the allocation. Note that it is *only* through this function that the "lost" characters can be allocated, but being editing a local activity, this is what happens usually. */ int64_t alloc_chars_around(buffer * const b, line_desc * const ld, const int64_t n, const bool check_first_before) { assert(ld->line != NULL); char_pool *cp = get_char_pool(b, ld->line); assert_char_pool(cp); block_signals(); char *before = ld->line - 1; char *after = ld->line + ld->line_len; if (check_first_before) { while(before >= cp->pool && !*before && (ld->line - 1) - before < n) before--; while(after < cp->pool + cp->size && !*after && (after - (ld->line + ld->line_len)) + ((ld->line - 1) - before)pool + cp->size && !*after && after - (ld->line + ld->line_len)= cp->pool && !*before && (after - (ld->line + ld->line_len)) + ((ld->line - 1) - before)line - 1) - before) + (after - (ld->line + ld->line_len)) <= n); assert(((ld->line - 1) - before) + (after - (ld->line + ld->line_len)) >= 0); if (((ld->line - 1) - before) + (after - (ld->line + ld->line_len)) == n) { if (cp->pool + cp->first_used == ld->line) cp->first_used = (before + 1) - cp->pool; if (cp->pool + cp->last_used == ld->line + ld->line_len - 1) cp->last_used = (after - 1) - cp->pool; b->free_chars -= n; release_signals(); return after - (ld->line + ld->line_len); } release_signals(); return ERROR; } /* Frees a block of len characters pointed to by p. If the char pool containing the block becomes completely free, it is removed from the list. */ void free_chars(buffer *const b, char *const p, const int64_t len) { if (!b || !p || !len) return; char_pool *cp = get_char_pool(b, p); assert_char_pool(cp); assert(*p); assert(p[len - 1]); block_signals(); memset(p, 0, len); b->free_chars += len; if (p == &cp->pool[cp->first_used]) while(cp->first_used <= cp->last_used && !cp->pool[cp->first_used]) cp->first_used++; if (p + len - 1 == &cp->pool[cp->last_used]) while(!cp->pool[cp->last_used] && cp->first_used <= cp->last_used) cp->last_used--; if (cp->last_used < cp->first_used) { rem(&cp->cp_node); b->allocated_chars -= cp->size; b->free_chars -= cp->size; free_char_pool(cp); release_signals(); return; } assert_char_pool(cp); release_signals(); } /* The following functions represent the only legal way of modifying a buffer. They are all based on insert_stream and delete_stream (except for the I/O functions). A stream is a sequence of NULL-terminated strings. The semantics associated is that each string is a separate line terminated by a line feed, *except for the last one*. Thus, a NULL-terminated string is a line with no linefeed. All the functions accept a position specified via a line descriptor and a position (which is the offset to be applied to the line pointer of the line descriptor). Also the line number is usually supplied, since it is necessary for recording the operation in the undo buffer. */ /* Inserts a line at the current position. The effect is obtained by inserting a stream containing one NULL. */ int insert_one_line(buffer * const b, line_desc * const ld, const int64_t line, const int64_t pos) { return insert_stream(b, ld, line, pos, "", 1); } /* Deletes a whole line, putting it in the temporary line buffer used by the UndelLine command. */ int delete_one_line(buffer * const b, line_desc * const ld, const int64_t line) { assert_line_desc(ld, b->encoding); assert_buffer(b); block_signals(); if (ld->line_len && (b->last_deleted = reset_stream(b->last_deleted))) add_to_stream(b->last_deleted, ld->line, ld->line_len); /* We delete a line by delete_stream()ing its length plus one. However, if we are on the last line of text, there is no terminating line feed. */ const int error = delete_stream(b, ld, line, 0, ld->line_len + (ld->ld_node.next->next ? 1 : 0)); release_signals(); return error; } /* Undeletes the last deleted line, using the last_deleted stream. */ int undelete_line(buffer * const b) { line_desc * const ld = b->cur_line_desc; if (!b->last_deleted) return ERROR; start_undo_chain(b); if (b->cur_pos > ld->line_len) insert_spaces(b, ld, b->cur_line, ld->line_len, b->win_x + b->cur_x - calc_width(ld, ld->line_len, b->opt.tab_size, b->encoding)); insert_one_line(b, ld, b->cur_line, b->cur_pos); insert_stream(b, ld, b->cur_line, b->cur_pos, b->last_deleted->stream, b->last_deleted->len); end_undo_chain(b); return OK; } /* Deletes a line up to its end. */ void delete_to_eol(buffer * const b, line_desc * const ld, const int64_t line, const int64_t pos) { if (!ld || pos >= ld->line_len) return; delete_stream(b, ld, line, pos, ld->line_len - pos); } /* Inserts a stream in a line at a given position. The position has to be smaller or equal to the line length. Since the stream can contain many lines, this function can be used for manipulating all insertions. It also records the inverse operation in the undo buffer if b->opt.do_undo is true. */ int insert_stream(buffer * const b, line_desc * ld, int64_t line, int64_t pos, const char * const stream, const int64_t stream_len) { assert(pos >= 0); assert(stream_len >= 0); if (!b || !ld || !stream || stream_len < 1 || pos > ld->line_len) return ERROR; assert_line_desc(ld, b->encoding); assert_buffer(b); block_signals(); if (b->opt.do_undo && !(b->undoing || b->redoing)) { const int error = add_undo_step(b, line, pos, -stream_len); if (error) { release_signals(); return error; } } const char *s = stream; while(s - stream < stream_len) { int64_t const len = strnlen_ne(s, stream_len - (s - stream)); if (len) { /* First case; there is no character allocated on this line. We have to freshly allocate the line. */ if (!ld->line) { if (ld->line = alloc_chars(b, len)) { memcpy(ld->line, s, len); ld->line_len = len; } else { release_signals(); return OUT_OF_MEMORY_DISK_FULL; } } /* Second case. There are not enough characters around ld->line. Note that the value of the check_first_before parameter depends on the position at which the insertion will be done, and it is chosen in such a way to minimize the number of characters to move. */ else { const int64_t result = alloc_chars_around(b, ld, len, pos < ld->line_len / 2); if (result < 0) { char * const p = alloc_chars(b, ld->line_len + len); if (p) { memcpy(p, ld->line, pos); memcpy(&p[pos], s, len); memcpy(&p[pos + len], ld->line + pos, ld->line_len - pos); free_chars(b, ld->line, ld->line_len); ld->line = p; ld->line_len += len; } else { release_signals(); return OUT_OF_MEMORY_DISK_FULL; } } else { /* Third case. There are enough free characters around ld->line. */ if (len - result) memmove(ld->line - (len - result), ld->line, pos); if (result) memmove(ld->line + pos + result, ld->line + pos, ld->line_len - pos); memcpy(ld->line - (len - result) + pos, s, len); ld->line -= (len - result); ld->line_len += len; } } b->is_modified = 1; /* We just inserted len chars at (line,pos); adjust bookmarks and mark accordingly. */ if (b->marking && b->block_start_line == line && b->block_start_pos > pos) b->block_start_pos += len; for (int i = 0, mask = b->bookmark_mask; mask; i++, mask >>= 1) if ((mask & 1) && b->bookmark[i].line == line && b->bookmark[i].pos > pos) b->bookmark[i].pos += len; } /* If the string we have inserted has a NULL at the end, we create a new line under the current one and set ld to point to it. */ if (len + (s - stream) < stream_len) { line_desc *new_ld; if (new_ld = alloc_line_desc(b)) { add(&new_ld->ld_node, &ld->ld_node); b->num_lines++; if (pos + len < ld->line_len) { new_ld->line_len = ld->line_len - pos - len; new_ld->line = &ld->line[pos + len]; ld->line_len = pos + len; if (pos + len == 0) ld->line = NULL; } b->is_modified = 1; ld = new_ld; /* We just inserted a line break at (line,pos); adjust the buffer bookmarks and mark accordingly. */ if (b->marking) { if (b->block_start_line == line && b->block_start_pos > pos) { b->block_start_pos -= pos + len; b->block_start_line++; } else if (b->block_start_line > line) b->block_start_line++; } for (int i = 0, mask=b->bookmark_mask; mask; i++, mask >>= 1) { if (mask & 1) { if (b->bookmark[i].line == line && b->bookmark[i].pos > pos) { b->bookmark[i].pos -= pos + len; b->bookmark[i].line++; } else if (b->bookmark[i].line > line) b->bookmark[i].line++; } } pos = 0; line++; } else { release_signals(); return OUT_OF_MEMORY_DISK_FULL; } } s += len + 1; } release_signals(); return OK; } /* Inserts a single ISO 10646 character (it creates, if necessary, a suitable temporary stream). The character must be compatible with the current buffer encoding. */ int insert_one_char(buffer * const b, line_desc * const ld, const int64_t line, const int64_t pos, const int c) { static char t[8]; assert(b->encoding == ENC_8_BIT || b->encoding == ENC_UTF8 || c <= 127); assert(b->encoding == ENC_UTF8 || c <= 255); assert(c != 0); if (b->encoding == ENC_UTF8) t[utf8str(c, t)] = 0; else t[0] = c, t[1] = 0; return insert_stream(b, ld, line, pos, t, strlen(t)); } /* Inserts a number of spaces. */ int insert_spaces(buffer * const b, line_desc * const ld, const int64_t line, const int64_t pos, int64_t n) { static char spaces[MAX_STACK_SPACES]; int result = OK, i; if (!spaces[0]) memset(spaces, ' ', sizeof spaces); while(result == OK && n > 0) { i = min(n, MAX_STACK_SPACES); result = insert_stream(b, ld, line, pos, spaces, i); n -= i; } assert(result != OK || n == 0); return result; } /* Deletes a stream of len bytes, that is, deletes len bytes from the given position, counting line feeds as a byte. The operation is recorded in the undo buffer. */ int delete_stream(buffer * const b, line_desc * const ld, const int64_t line, const int64_t pos, int64_t len) { assert_buffer(b); assert_line_desc(ld, b->encoding); /* If we are in no man's land, we return. */ if (!b || !ld || !len || pos > ld->line_len || pos == ld->line_len && !ld->ld_node.next->next) return ERROR; block_signals(); if (b->opt.do_undo && !(b->undoing || b->redoing)) { const int error = add_undo_step(b, line, pos, len); if (error) { release_signals(); return error; } } while(len) { /* First case: we are just on the end of a line. We join the current line with the following one (if it's there of course). If, however, the current line is empty, we rather remove it. The only difference is in the resulting syntax state. */ if (pos == ld->line_len) { line_desc *next_ld = (line_desc *)ld->ld_node.next; /* There's nothing more to do--we are at the end of the file. */ if (next_ld->ld_node.next == NULL) break; /* We're about to join line+1 to line; adjust mark and bookmarks accordingly. */ if (b->marking) { if (b->block_start_line == line+1) { b->block_start_line--; b->block_start_pos += ld->line_len; } else if (b->block_start_line > line) b->block_start_line--; } for (int i = 0, mask = b->bookmark_mask; mask; i++, mask >>= 1) { if (mask & 1) { if (b->bookmark[i].line == line+1) { b->bookmark[i].line--; b->bookmark[i].pos += ld->line_len; } else if (b->bookmark[i].line > line) b->bookmark[i].line--; } } /* If one of the lines is empty, or their contents are adjacent, we either do nothing or simply set a pointer. */ if (!ld->line || !next_ld->line || ld->line + ld->line_len == next_ld->line) { if (!ld->line) ld->line = next_ld->line; } else { int64_t n, m; if ((n = alloc_chars_around(b, ld, next_ld->line_len, false))<0 && (m = alloc_chars_around(b, next_ld, ld->line_len, true))<0) { /* We try to allocate characters around one line or the other one; if we fail, we allocate enough space for both lines elsewhere. */ char * const p = alloc_chars(b, ld->line_len + next_ld->line_len); if (p) { memcpy(p, ld->line, ld->line_len); memcpy(p + ld->line_len, next_ld->line, next_ld->line_len); free_chars(b, ld->line, ld->line_len); free_chars(b, next_ld->line, next_ld->line_len); ld->line = p; } else { release_signals(); if (b->opt.do_undo && !(b->undoing || b->redoing)) fix_last_undo_step(b, -len); return OUT_OF_MEMORY_DISK_FULL; } } /* In case one of the alloc_chars_around succeeds, we have just to move the lines in the right place. */ else if (n >= 0) { if (n < next_ld->line_len) memmove(ld->line + (n - next_ld->line_len), ld->line, ld->line_len); ld->line += (n - next_ld->line_len); memcpy(ld->line + ld->line_len, next_ld->line, next_ld->line_len); free_chars(b, next_ld->line, next_ld->line_len); } else { if (m) memmove(next_ld->line + m, next_ld->line, next_ld->line_len); next_ld->line += m; memcpy(next_ld->line - ld->line_len, ld->line, ld->line_len); free_chars(b, ld->line, ld->line_len); ld->line = next_ld->line - ld->line_len; } } ld->line_len += next_ld->line_len; b->num_lines--; rem(&next_ld->ld_node); free_line_desc(b, next_ld); len--; if (!b->redoing) { if (b->undoing) add_to_stream(&b->undo.redo, "", 1); else if (b->opt.do_undo) add_to_undo_stream(&b->undo, "", 1); } } /* Second case: we are inside a line. We delete len bytes or, if there are less then len bytes to delete, we delete up to the end of the line. In the latter case, we simply set the line length and free the corresponding bytes. Otherwise, the number of bytes to move is minimized. */ else { int64_t n = len > ld->line_len - pos ? ld->line_len - pos : len; /* We're about to erase n chars at (line,pos); adjust mark and bookmarks accordingly. */ if (b->marking) if (b->block_start_line == line) if (b->block_start_pos >= pos) if (b->block_start_pos < pos + n) b->block_start_pos = pos; else b->block_start_pos -= n; for (int i = 0, mask = b->bookmark_mask; mask; i++, mask>>=1) { if (mask & 1) { if (b->bookmark[i].line == line) if (b->bookmark[i].pos >= pos) if (b->bookmark[i].pos < pos + n) b->bookmark[i].pos = pos; else b->bookmark[i].pos -= n; } } if (!b->redoing) { if (b->undoing) add_to_stream(&b->undo.redo, &ld->line[pos], n); else if (b->opt.do_undo) add_to_undo_stream(&b->undo, &ld->line[pos], n); } if (n == ld->line_len - pos) free_chars(b, &ld->line[pos], n); else { if (pos < ld->line_len / 2) { memmove(ld->line + n, ld->line, pos); free_chars(b, ld->line, n); ld->line += n; } else { memmove(ld->line + pos, ld->line + pos + n, ld->line_len - pos - n); free_chars(b, &ld->line[ld->line_len - n], n); } } if (!(ld->line_len -= n)) ld->line = NULL; len -= n; assert_line_desc(ld, b->encoding); } b->is_modified = 1; } if (b->opt.do_undo && !(b->undoing || b->redoing)) fix_last_undo_step(b, -len); release_signals(); return OK; } /* Deletes a single character. */ int delete_one_char(buffer * const b, line_desc * const ld, const int64_t line, const int64_t pos) { return delete_stream(b, ld, line, pos, b->encoding == ENC_UTF8 && pos < ld->line_len ? utf8len(ld->line[pos]) : 1); } /* Returns the line descriptor for line n of buffer b, or NULL if n is out of range. We assume that cur_line and cur_line_desc are coherent, and try to use the faster way (i.e., relative or absolute). */ line_desc *nth_line_desc(const buffer *b, const int64_t n) { if (n < 0 || n >= b->num_lines) return NULL; line_desc *ld; const int64_t best_absolute_cost = min(n, b->num_lines - 1 - n); const int64_t relative_cost = b->cur_line < n ? n - b->cur_line : b->cur_line - n; if (best_absolute_cost < relative_cost) { if (n < b->num_lines / 2) { ld = (line_desc *)b->line_desc_list.head; for(int64_t i = 0; i < n; i++) ld = (line_desc *)ld->ld_node.next; } else { ld = (line_desc *)b->line_desc_list.tail_pred; for(int64_t i = 0; i < b->num_lines - 1 - n; i++) ld = (line_desc *)ld->ld_node.prev; } } else { ld = (line_desc *)b->cur_line_desc; if (n < b->cur_line) for(int64_t i = 0; i < b->cur_line - n; i++) ld = (line_desc *)ld->ld_node.prev; else for(int64_t i = 0; i < n - b->cur_line; i++) ld = (line_desc *)ld->ld_node.next; } return ld; } /* Changes the buffer file name to the given string, which must have been obtained through malloc(). */ void change_filename(buffer * const b, char * const name) { assert(name != NULL); if (b->filename) free(b->filename); b->filename = name; } /* Here we load a file into a given buffer. The buffer lists are deallocated first. If there is not write access to the file, the read-only flag is set. Note that we consider line feeds 0x0A's, 0x0D's and 0x00's (the last being made necessary by the way the pools are handled), unless the binary flag is set, in which case we consider only the 0x00's. */ int load_file_in_buffer(buffer * const b, const char *name) { if (!b) return ERROR; assert_buffer(b); name = tilde_expand(name); if (is_directory(name)) return FILE_IS_DIRECTORY; if (is_migrated(name)) return FILE_IS_MIGRATED; const int fd = open(name, READ_FLAGS); if (fd >= 0) { const int result = load_fd_in_buffer(b, fd); close(fd); b->mtime = file_mod_time(name); if (!result) b->opt.read_only = (access(name, W_OK) != 0); return result; } return errno == ENOENT ? FILE_DOES_NOT_EXIST : CANT_OPEN_FILE; } /* Support function for load_fd_in_buffer(). */ static int create_mmap_files(buffer * const b, const int fd, const int char_fd, const int ld_fd, const size_t len, const int line_desc_size, char * const terminators) { /* A circular buffer whose first and second half are alternately written and reloaded. */ char buffer[CIRC_BUFFER_SIZE * 2]; /* A buffer for line descriptors. */ char ld_buffer[LD_BUFFER_COUNT * line_desc_size]; line_desc *ld_buffer_syn = (line_desc *)ld_buffer; no_syntax_line_desc *ld_buffer_no_syn = (no_syntax_line_desc *)ld_buffer; int ld_count = 0; size_t remaining = len; size_t to_do = min(remaining, sizeof buffer); ssize_t result = read(fd, buffer, to_do); if (result < to_do) return IO_ERROR; remaining -= to_do; int i = 0; int64_t curr_pos = 0, start_of_line = 0, end_of_line = 0; while(curr_pos < len) { /* Here we replicate the logic of load_fd_in_buffer(). The circularity of the buffer makes it possible to check the current character and the following one. */ if (!b->opt.binary && (buffer[i] == terminators[0] || buffer[i] == terminators[1]) || !buffer[i]) { end_of_line = curr_pos; if (curr_pos < len - 1 && buffer[i] == '\r' && buffer[i + 1 & sizeof buffer - 1] == '\n') { b->is_CRLF = true; buffer[i] = 0; curr_pos++; b->free_chars++; i = i + 1 & sizeof buffer - 1; if ((i & sizeof buffer / 2 - 1) == 0) { // Buffer flip-flop char * const p = buffer + (i ^ sizeof buffer / 2); if (write(char_fd, p, sizeof buffer / 2) < sizeof buffer / 2) return OUT_OF_MEMORY_DISK_FULL; to_do = min(remaining, sizeof buffer / 2); ssize_t result = read(fd, p, to_do); if (result < to_do) return IO_ERROR; remaining -= to_do; } } b->num_lines++; /* Line descriptors are created with an offset from the start of fd, rather than an absolute pointer in memory. They will be fixed afterwards. */ if (do_syntax) { ld_buffer_syn[ld_count].line = (char *)start_of_line; ld_buffer_syn[ld_count].line_len = end_of_line - start_of_line; } else { ld_buffer_no_syn[ld_count].line = (char *)start_of_line; ld_buffer_no_syn[ld_count].line_len = end_of_line - start_of_line; } if (++ld_count == LD_BUFFER_COUNT) { if (write(ld_fd, ld_buffer, LD_BUFFER_COUNT * line_desc_size) < LD_BUFFER_COUNT * line_desc_size) return OUT_OF_MEMORY_DISK_FULL; ld_count = 0; } b->free_chars++; buffer[i] = 0; start_of_line = curr_pos + 1; } curr_pos++; i = i + 1 & sizeof buffer - 1; if ((i & sizeof buffer / 2 - 1) == 0) { // Buffer flip-flop char * const p = buffer + (i ^ sizeof buffer / 2); if (write(char_fd, p, sizeof buffer / 2) < sizeof buffer / 2) return OUT_OF_MEMORY_DISK_FULL; to_do = min(remaining, sizeof buffer / 2); ssize_t result = read(fd, p, to_do); if (result < to_do) return IO_ERROR; remaining -= to_do; } } if ((i & sizeof buffer / 2 - 1) != 0 && write(char_fd, buffer + (i & sizeof buffer / 2), i & sizeof buffer / 2 - 1) < (i & sizeof buffer / 2 - 1)) return OUT_OF_MEMORY_DISK_FULL; b->num_lines++; if (do_syntax) { ld_buffer_syn[ld_count].line = (char *)start_of_line; ld_buffer_syn[ld_count].line_len = curr_pos - start_of_line; } else { ld_buffer_no_syn[ld_count].line = (char *)start_of_line; ld_buffer_no_syn[ld_count].line_len = curr_pos - start_of_line; } ld_count++; if (write(ld_fd, ld_buffer, ld_count * line_desc_size) < ld_count * line_desc_size) return OUT_OF_MEMORY_DISK_FULL; return OK; } int load_fd_mmap(buffer * const b, const int fd, const size_t len, char * const terminators, char_pool **cp, line_desc_pool **ldp) { char template[16]; const int char_fd = mkstemp(strcpy(template, ".ne-mmap-XXXXXX")); unlink(template); const int ld_fd = mkstemp(strcpy(template, ".ne-mmap-XXXXXX")); unlink(template); const int line_desc_size = do_syntax ? sizeof(line_desc) : sizeof(no_syntax_line_desc); char * char_p = MAP_FAILED, * ld_p = MAP_FAILED; if (char_fd != -1 && ld_fd != -1) { const int error = create_mmap_files(b, fd, char_fd, ld_fd, len, line_desc_size, terminators); if (error) { close(char_fd); close(ld_fd); return error; } char_p = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, char_fd, 0); ld_p = mmap(NULL, b->num_lines * line_desc_size, PROT_READ | PROT_WRITE, MAP_SHARED, ld_fd, 0); if (char_p != MAP_FAILED && ld_p != MAP_FAILED && (*cp = alloc_char_pool_from_memory(char_p, len)) && (*ldp = alloc_line_desc_pool_from_memory(ld_p, b->num_lines))) { (*cp)->mapped = true; (*ldp)->mapped = true; b->allocated_chars = len; /* We replace the offsets from the start of the file with actual memory pointers, while adding the line descriptors to the buffer list. */ if (do_syntax) { for(line_desc *ld = (line_desc *)ld_p, *ld_end = ld + b->num_lines; ld < ld_end; ld++) { ld->line = ld->line_len ? char_p + (int64_t)ld->line : NULL; add_tail(&b->line_desc_list, &ld->ld_node); } } else { for(no_syntax_line_desc *ld = (no_syntax_line_desc *)ld_p, *ld_end = ld + b->num_lines; ld < ld_end; ld++) { ld->line = ld->line_len ? char_p + (int64_t)ld->line : NULL; add_tail(&b->line_desc_list, &ld->ld_node); } } return OK; } else { close(char_fd); close(ld_fd); int error = IO_ERROR; if (char_p != MAP_FAILED) munmap(char_p, len); if (ld_p != MAP_FAILED) munmap(ld_p, b->num_lines * line_desc_size); if (char_p != MAP_FAILED && ld_p != MAP_FAILED) error = OUT_OF_MEMORY; if (*cp) free(*cp); if (*ldp) free(*ldp); return error; } } else { if (char_fd != -1) close(char_fd); if (ld_fd != -1) close(ld_fd); return IO_ERROR; } } /* This function, together with insert_stream and delete_stream, is the only way of modifying the contents of a buffer. While loading a file could have passed through insert_stream, it would have been intolerably slow for large files. The flexible pool structure of ne makes it possible loading the file with a single read in a big pool. */ int load_fd_in_buffer(buffer *b, int fd) { char terminators[] = { 0x0d, 0x0a }; if (b->opt.preserve_cr) terminators[0] = 0; off_t len = lseek(fd, 0, SEEK_END); if (len == 0) { clear_buffer(b); b->encoding = ENC_ASCII; if (b->opt.do_undo) b->undo.last_save_step = 0; return OK; } /* In the following code, we create a character pool and a line descriptor pool either by loading into memory, or by memory mapping. We are expected to compute the number of lines. */ char_pool *cp = NULL; line_desc_pool *ldp = NULL; if (len > 0) { /* Seekable */ if (lseek(fd, 0, SEEK_SET) < 0) return IO_ERROR; block_signals(); free_buffer_contents(b); cp = alloc_char_pool(len, fd, 0); if (! cp) { // mmap() const int error = load_fd_mmap(b, fd, len, terminators, &cp, &ldp); if (error) { clear_buffer(b); release_signals(); return error; } } } else { /* Not seekable */ block_signals(); free_buffer_contents(b); int64_t curr_size = START_SIZE; len = 0; char *pool = calloc(curr_size, 1); for(;;) { const int64_t res = read_safely(fd, pool + len, curr_size - len); if (res < 0) { free(pool); clear_buffer(b); release_signals(); return IO_ERROR; } len += res; if (len < curr_size) break; char * const new_pool = realloc(pool, curr_size *= 2); if (new_pool == NULL) { free(pool); clear_buffer(b); release_signals(); return OUT_OF_MEMORY; } pool = new_pool; memset(pool + len, 0, curr_size - len); } cp = alloc_char_pool_from_memory(pool, curr_size); if (!cp) { free(pool); clear_buffer(b); release_signals(); return OUT_OF_MEMORY; } } if (! ldp) { // Not mmap()'s b->allocated_chars = cp->size; b->free_chars = cp->size - len; char *p = cp->pool; /* This is the first pass on the data we just read. We count the number of lines. If we meet a CR/LF sequence and we did not ask for binary files, we decide the file is of CR/LF type. Note that this cannot happen if preserve_cr is set. */ for(int64_t i = b->num_lines = 0; i < len; i++, p++) if (!b->opt.binary && (*p == terminators[0] || *p == terminators[1]) || !*p) { if (i < len - 1 && p[0] == '\r' && p[1] == '\n') { b->is_CRLF = true; p++, i++; b->free_chars++; } b->num_lines++; b->free_chars++; } b->num_lines++; ldp = alloc_line_desc_pool(b->num_lines + STANDARD_LINE_INCREMENT, -1); if (ldp) { char *p = cp->pool, * const end = p + len; /* This is the second pass. Here we find the actual lines, and set to NUL the line terminators if necessary, following the same rationale of the first pass (this is important, as b->free_chars has been computed on the first pass). */ for(int64_t i = 0; i < b->num_lines; i++) { line_desc *ld = do_syntax ? &((line_desc *)ldp->pool)[i] : (line_desc *)&((no_syntax_line_desc *)ldp->pool)[i]; rem(&ld->ld_node); add_tail(&b->line_desc_list, &ld->ld_node); char *q = p; while(q < end && (b->opt.binary || *q != terminators[0] && *q != terminators[1]) && *q) q++; ld->line_len = q - p; ld->line = q - p ? p : NULL; if (q < end) { if (q - cp->pool < len - 1 && q[0] == '\r' && q[1] == '\n') *q++ = 0; *q++ = 0; } p = q; } ldp->allocated_items = b->num_lines; } else { free_char_pool(cp); clear_buffer(b); release_signals(); return OUT_OF_MEMORY_DISK_FULL; } } /* Now, if UTF-8 auto-detection is enabled, we try to guess whether this buffer is in UTF-8. */ const encoding_type encoding = detect_encoding(cp->pool, len); if (encoding == ENC_ASCII) b->encoding = ENC_ASCII; else { if (b->opt.utf8auto && encoding == ENC_UTF8) b->encoding = ENC_UTF8; else b->encoding = ENC_8_BIT; } /* We set correctly the offsets of the first and last character used. If no character is used (i.e., we have a file of line feeds), the char pool is freed. */ if (b->free_chars < b->allocated_chars) { cp->last_used = len; while(!cp->pool[cp->first_used]) cp->first_used++; while(!cp->pool[--cp->last_used]); add_head(&b->char_pool_list, &cp->cp_node); assert_char_pool(cp); } else free_char_pool(cp); add_head(&b->line_desc_pool_list, &ldp->ldp_node); reset_position_to_sof(b); if (b->opt.do_undo) b->undo.last_save_step = 0; release_signals(); return OK; } /* Recomputes initial states for all lines in a buffer. */ void reset_syntax_states(buffer *b) { if (b->syn) { HIGHLIGHT_STATE next_line_state = { 0, 0, "" }; for(line_desc *ld = (line_desc *)b->line_desc_list.head; ld->ld_node.next; ld = (line_desc *)ld->ld_node.next) { ld->highlight_state = next_line_state; next_line_state = parse(b->syn, ld, next_line_state, b->encoding == ENC_UTF8); } b->attr_len = -1; } } /* Ensures that the attribute buffer of this buffer is large enough. */ void ensure_attr_buf(buffer * const b, const int64_t capacity) { if (capacity == 0) return; /* attr_buf already exists? */ if (!b->attr_buf) { b->attr_size = capacity; b->attr_buf = malloc(b->attr_size * sizeof *b->attr_buf); } else if (capacity > b->attr_size) { b->attr_size = capacity; b->attr_buf = realloc(b->attr_buf, b->attr_size * sizeof *b->attr_buf); } } /* Here we save a buffer to a given file. If no file is specified, the buffer filename field is used. The is_modified flag is set to 0, and the mtime is updated. */ int save_buffer_to_file(buffer *b, const char *name) { line_desc *ld = (line_desc *)b->line_desc_list.head; if (!b) return ERROR; assert_buffer(b); if (b->opt.read_only) return DOCUMENT_IS_READ_ONLY; if (name == NULL) name = b->filename; if (!name) return ERROR; name = tilde_expand(name); if (is_directory(name)) return FILE_IS_DIRECTORY; if (is_migrated(name)) return FILE_IS_MIGRATED; block_signals(); int error = OK; const int fd = open(name, WRITE_FLAGS, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); if (fd >= 0) { /* If we can allocate SAVE_BLOCK_LEN bytes, we will use them as a buffer for our saves. */ char * const p = malloc(SAVE_BLOCK_LEN + 1); if (p) { /* used keeps track of the number of bytes used in the buffer. l, len specify the pointer to the block of characters to save, and its length. In case of very long lines, or buffer border crossing, they could point in the middle of a line descriptor. */ int64_t used = 0, len; char *l; while(ld->ld_node.next) { l = ld->line; len = ld->line_len; while(len > 0) { if (SAVE_BLOCK_LEN - used > len) { memcpy(p + used, l, len); used += len; len = 0; } else { memcpy(p + used, l, SAVE_BLOCK_LEN - used); len -= SAVE_BLOCK_LEN - used; l += SAVE_BLOCK_LEN - used; used = 0; if (write(fd, p, SAVE_BLOCK_LEN) < SAVE_BLOCK_LEN) { error = CANNOT_SAVE_DISK_FULL; break; } } } if (error) break; ld = (line_desc *)ld->ld_node.next; /* Note that the two previous blocks never leave used == SAVE_BLOCK_LEN. Thus, we can always assume there are two free bytes at p+used. */ if (ld->ld_node.next) { if (b->opt.binary) p[used++] = 0; else { if (b->is_CRLF) p[used++] = '\r'; p[used++] = '\n'; } } if (used >= SAVE_BLOCK_LEN) { if (write(fd, p, used) < used) { error = IO_ERROR; break; } else used = 0; } } if (!error && used && write(fd, p, used) < used) error = IO_ERROR; free(p); } else { /* If the buffer is not available, just save line by line. */ while(ld->ld_node.next) { if (ld->line) { if (write(fd, ld->line, ld->line_len) < ld->line_len) { error = IO_ERROR; break; } } ld = (line_desc *)ld->ld_node.next; if (ld->ld_node.next) { if (!b->opt.binary && b->is_CRLF && write(fd, "\r", 1) < 1) { error = IO_ERROR; break; } if (write(fd, b->opt.binary ? "\0" : "\n", 1) < 1) { error = IO_ERROR; break; } } } } if (close(fd)) error = IO_ERROR; if (error == OK) b->is_modified = 0; b->mtime = file_mod_time(name); } else error = CANT_OPEN_FILE; release_signals(); return error; } /* Autosaves a given buffer. If the buffer has a name, a '#' is prefixed to it. If the buffer has no name, a fake name is generated using the PID of ne and the pointer to the buffer structure. This ensures uniqueness. Autosave never writes on the original file, also because it can be called during an emergency exit caused by a signal. */ void auto_save(buffer *b) { if (b->is_modified) { char *p; if (b->filename) { if (p = malloc(strlen(file_part(b->filename)) + 2)) { strcpy(p, "#"); strcat(p, file_part(b->filename)); } } else if (p = malloc(MAX_INT_LEN * 2)) sprintf(p, "%p.%x", b, getpid()); save_buffer_to_file(b, p); free(p); } } ne-3.3.4/src/clips.c000066400000000000000000000464571475116431000142120ustar00rootroot00000000000000/* Clip handling functions. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2025 Todd M. Lewis and Sebastiano Vigna This file is part of ne, the nice editor. This library 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 library 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 . */ #include "ne.h" #include "support.h" /* A clip is a numbered node in the global clip list. The contents of the clip are handled through the stream functions contained in streams.c. At creation time, a clip is marked with an encoding. Clips, of course, may be pasted only in buffers with a compatible encoding. Note that pasting a clip in an ASCII buffer may change its encoding. */ /* Allocates a clip descriptor. */ clip_desc *alloc_clip_desc(int n, int64_t size) { assert(n >= 0); assert(size >= 0); clip_desc * const cd = calloc(1, sizeof(clip_desc)); if (cd) { cd->n = n; if (cd->cs = alloc_char_stream(size)) return cd; free(cd); } return NULL; } /* Reallocates a clip descriptor of the given size. If cd is NULL, this is equivalent to calling alloc_clip_desc. */ clip_desc *realloc_clip_desc(clip_desc *cd, int n, int64_t size) { assert(n >= 0); assert(size >= 0); if (!cd) return alloc_clip_desc(n, size); assert_clip_desc(cd); if (cd->n != n) return NULL; char_stream * const cs = realloc_char_stream(cd->cs, size); if (cs) { cd->cs = cs; return cd; } return NULL; } /* Frees a clip descriptor. */ void free_clip_desc(clip_desc *cd) { if (!cd) return; assert_clip_desc(cd); free_char_stream(cd->cs); free(cd); } /* Scans the global clip list, searching for a specific numbered clip. Returns NULL on failure. */ clip_desc *get_nth_clip(int n) { for(clip_desc *cd = (clip_desc *)clips.head; cd->cd_node.next; cd = (clip_desc *)cd->cd_node.next) { assert_clip_desc(cd); if (cd->n == n) return cd; } return NULL; } /* Copies the characters between the cursor and the block marker of the given buffer to the nth clip. If the cut flag is true, the characters are also removed from the text. The code scans the text two times: the first time in order to determine the exact length of the text, the second time in order to actually copy it. */ int copy_to_clip(buffer *b, int n, bool cut) { if (!b->marking) return MARK_BLOCK_FIRST; if (b->block_start_line >= b->num_lines) return MARK_OUT_OF_BUFFER; /* If the mark and the cursor are on the same line and on the same position (or both beyond the line length), we can't copy anything. */ line_desc *ld = b->cur_line_desc; clip_desc *cd = get_nth_clip(n); int64_t y = b->cur_line; if (y == b->block_start_line && (b->cur_pos == b->block_start_pos || b->cur_pos >= ld->line_len && b->block_start_pos >= ld->line_len)) { clip_desc * const new_cd = realloc_clip_desc(cd, n, 0); if (!new_cd) return OUT_OF_MEMORY; if (!cd) add_head(&clips, &new_cd->cd_node); return OK; } /* We have two different loops for direct or inverse copying. Making this conditional code would be cumbersome, awkward, and definitely inefficient. */ bool chaining; char *p = NULL; if (y > b->block_start_line || y == b->block_start_line && b->cur_pos > b->block_start_pos) { /* mark before/above cursor */ int64_t clip_len = 0; chaining = false; for(int pass = 0; pass < 2; pass++) { ld = b->cur_line_desc; for(int64_t i = y; i >= b->block_start_line; i--) { int64_t start_pos = 0; if (i == b->block_start_line) { if (!pass && cut && ld->line_len < b->block_start_pos) { if (!chaining) { chaining = true; start_undo_chain(b); } const int64_t bsp = b->block_start_pos; /* because the mark will move when we insert_spaces()! */ insert_spaces(b, ld, i, ld->line_len, b->block_start_pos - ld->line_len); b->block_start_pos = bsp; } start_pos = min(ld->line_len, b->block_start_pos); } const int64_t end_pos = i == y ? min(ld->line_len, b->cur_pos) : ld->line_len; const int64_t len = end_pos - start_pos; if (pass) { assert(!(len != 0 && ld->line == NULL)); if (i != y) *--p = 0; p -= len; if (ld->line) memcpy(p, ld->line + start_pos, len); } else clip_len += len + (i != y); ld = (line_desc *)ld->ld_node.prev; } if (pass) { cd->cs->len = clip_len; set_stream_encoding(cd->cs, b->encoding); assert_clip_desc(cd); if (cut) { goto_line_pos(b, b->block_start_line, b->block_start_pos); delete_stream(b, b->cur_line_desc, b->cur_line, b->cur_pos, clip_len); update_syntax_states_delay(b, b->cur_line_desc, NULL); } if (chaining) end_undo_chain(b); return OK; } clip_desc * const new_cd = realloc_clip_desc(cd, n, clip_len); if (!new_cd) { if (chaining) end_undo_chain(b); return OUT_OF_MEMORY; } if (!cd) add_head(&clips, &new_cd->cd_node); cd = new_cd; p = cd->cs->stream + clip_len; } } else { /* mark after cursor */ int64_t clip_len = 0; chaining = false; for(int pass = 0; pass < 2; pass++) { ld = b->cur_line_desc; for(int64_t i = y; i <= b->block_start_line; i++) { int64_t start_pos = 0; if (i == y) { if (!pass && cut && b->cur_pos > ld->line_len) { if (!chaining) { chaining = true; start_undo_chain(b); } insert_spaces(b, ld, i, ld->line_len, b->cur_pos - ld->line_len); } start_pos = b->cur_pos > ld->line_len ? ld->line_len : b->cur_pos; } const int64_t end_pos = i == b->block_start_line ? min(b->block_start_pos, ld->line_len) : ld->line_len; const int64_t len = end_pos - start_pos; if (pass) { assert(!(len != 0 && ld->line == NULL)); if (ld->line) memcpy(p, ld->line + start_pos, len); p += len; if (i != b->block_start_line) *(p++) = 0; } else clip_len += len + (i != y); ld = (line_desc *)ld->ld_node.next; } if (pass) { cd->cs->len = clip_len; set_stream_encoding(cd->cs, b->encoding); assert_clip_desc(cd); if (cut) delete_stream(b, b->cur_line_desc, b->cur_line, b->cur_pos, clip_len); update_syntax_states_delay(b, b->cur_line_desc, NULL); if (chaining) end_undo_chain(b); return OK; } clip_desc * const new_cd = realloc_clip_desc(cd, n, clip_len); if (!new_cd) { if (chaining) end_undo_chain(b); return OUT_OF_MEMORY; } if (!cd) add_head(&clips, &new_cd->cd_node); cd = new_cd; p = cd->cs->stream; } } if (chaining) end_undo_chain(b); return OK; } /* Simply erases a block, without putting it in a clip. Calls update_syntax_states_delay() if update is true. */ int erase_block(buffer *b, const bool update) { if (!b->marking) return MARK_BLOCK_FIRST; if (b->block_start_line >= b->num_lines) return MARK_OUT_OF_BUFFER; int64_t y = b->cur_line, erase_len = 0; line_desc *ld = b->cur_line_desc; if (y == b->block_start_line && (b->cur_pos == b->block_start_pos || b->cur_pos >= ld->line_len && b->block_start_pos >= ld->line_len)) return OK; bool chaining = false; if (y > b->block_start_line || y == b->block_start_line && b->cur_pos > b->block_start_pos) { for(int64_t i = y; i >= b->block_start_line; i--) { int64_t start_pos = 0; if (i == b->block_start_line) { if (ld->line_len < b->block_start_pos) { if (!chaining) { chaining = true; start_undo_chain(b); } const int64_t bsp = b->block_start_pos; /* because the mark will move when we insert_spaces()! */ insert_spaces(b, ld, i, ld->line_len, b->block_start_pos - ld->line_len); b->block_start_pos = bsp; } start_pos = min(ld->line_len, b->block_start_pos); } const int64_t end_pos = i == y ? min(ld->line_len, b->cur_pos) : ld->line_len; const int64_t len = end_pos - start_pos; erase_len += len + 1; ld = (line_desc *)ld->ld_node.prev; } goto_line_pos(b, b->block_start_line, b->block_start_pos); } else { for(int64_t i = y; i <= b->block_start_line; i++) { int64_t start_pos = 0; if (i == y) { if (b->cur_pos > ld->line_len) { if (!chaining) { chaining = true; start_undo_chain(b); } insert_spaces(b, ld, i, ld->line_len, b->cur_pos - ld->line_len); } start_pos = b->cur_pos > ld->line_len ? ld->line_len : b->cur_pos; } const int64_t end_pos = i == b->block_start_line ? min(b->block_start_pos, ld->line_len) : ld->line_len; const int64_t len = end_pos - start_pos; erase_len += len + 1; ld = (line_desc *)ld->ld_node.next; } } delete_stream(b, b->cur_line_desc, b->cur_line, b->cur_pos, erase_len - 1); if (chaining) end_undo_chain(b); if (update) update_syntax_states_delay(b, b->cur_line_desc, NULL); return OK; } /* Pastes a clip into a buffer. Since clips are streams, the operation is definitely straightforward. */ int paste_to_buffer(buffer *b, int n) { clip_desc * const cd = get_nth_clip(n); if (!cd) return CLIP_DOESNT_EXIST; if (!cd->cs->len) return OK; if (cd->cs->encoding == ENC_ASCII || b->encoding == ENC_ASCII || cd->cs->encoding == b->encoding) { line_desc * const ld = b->cur_line_desc, * const end_ld = (line_desc *)b->cur_line_desc->ld_node.next; if (b->encoding == ENC_ASCII) b->encoding = cd->cs->encoding; b->bookmark[PASTE_START_BOOKMARK].pos = b->cur_pos; b->bookmark[PASTE_START_BOOKMARK].line = b->cur_line; b->bookmark[PASTE_START_BOOKMARK].cur_y = b->cur_y; b->bookmark_mask |= (1 << PASTE_START_BOOKMARK); b->bookmark[PASTE_END_BOOKMARK].pos = next_pos(b->cur_line_desc->line, b->cur_pos, b->encoding); b->bookmark[PASTE_END_BOOKMARK].line = b->cur_line; b->bookmark_mask |= (1 << PASTE_END_BOOKMARK); start_undo_chain(b); if (b->cur_pos > ld->line_len) insert_spaces(b, ld, b->cur_line, ld->line_len, b->win_x + b->cur_x - calc_width(ld, ld->line_len, b->opt.tab_size, b->encoding)); insert_stream(b, ld, b->cur_line, b->cur_pos, cd->cs->stream, cd->cs->len); end_undo_chain(b); b->bookmark[PASTE_END_BOOKMARK].pos = prev_pos(nth_line_desc(b, b->bookmark[PASTE_END_BOOKMARK].line)->line, b->bookmark[PASTE_END_BOOKMARK].pos, b->encoding); b->bookmark[PASTE_END_BOOKMARK].cur_y = min(ne_lines - 2, b->bookmark[PASTE_START_BOOKMARK].cur_y + b->bookmark[PASTE_END_BOOKMARK].line - b->bookmark[PASTE_START_BOOKMARK].line); assert(ld == b->cur_line_desc); update_syntax_states_delay(b, ld, end_ld); return OK; } return INCOMPATIBLE_CLIP_ENCODING; } /* Works like copy_to_clip(), but the region to copy is the rectangle defined by the cursor and the marker. Same comments apply. Note that in case of a cut we use start_undo_chain() in order to make the various deletions a single undo operation. */ int copy_vert_to_clip(buffer *b, int n, bool cut) { if (!b->marking) return MARK_BLOCK_FIRST; if (b->block_start_line >= b->num_lines) return MARK_OUT_OF_BUFFER; int64_t y = b->cur_line; line_desc *ld = b->cur_line_desc; clip_desc *cd = get_nth_clip(n); if (b->cur_pos == b->block_start_pos || y == b->block_start_line && b->cur_pos >= ld->line_len && b->block_start_pos >= ld->line_len) { clip_desc * const new_cd = realloc_clip_desc(cd, n, 0); if (!new_cd) return OUT_OF_MEMORY; set_stream_encoding(new_cd->cs, ENC_ASCII); if (!cd) add_head(&clips, &new_cd->cd_node); return OK; } int64_t start_x = calc_width(nth_line_desc(b, b->block_start_line), b->block_start_pos, b->opt.tab_size, b->encoding); int64_t end_x = b->win_x + b->cur_x; if (end_x < start_x) { const uint64_t t = start_x; start_x = end_x; end_x = t; } const int64_t cur_pos = b->cur_pos; if (cut) { b->cur_pos = -1; start_undo_chain(b); } char *p = NULL; if (y > b->block_start_line) { int64_t clip_len = 0; for(int pass = 0; pass < 2; pass++) { ld = b->cur_line_desc; for(int64_t i = y; i >= b->block_start_line; i--) { const int64_t start_pos = calc_pos(ld, start_x, b->opt.tab_size, b->encoding); const int64_t len = calc_pos(ld, end_x, b->opt.tab_size, b->encoding) - start_pos; if (pass) { *--p = 0; p -= len; if (len) memcpy(p, ld->line + start_pos, len); if (cut) delete_stream(b, ld, i, start_pos, len); } else clip_len += len + 1; ld = (line_desc *)ld->ld_node.prev; } if (pass) { cd->cs->len = clip_len; set_stream_encoding(cd->cs, b->encoding); assert_clip_desc(cd); if (cut) { update_syntax_states_delay(b, (line_desc *)ld->ld_node.next, b->cur_line_desc); goto_line_pos(b, min(b->block_start_line, b->cur_line), min(b->block_start_pos, cur_pos)); end_undo_chain(b); } return OK; } clip_desc * const new_cd = realloc_clip_desc(cd, n, clip_len); if (!new_cd) return OUT_OF_MEMORY; if (!cd) add_head(&clips, &new_cd->cd_node); cd = new_cd; p = cd->cs->stream + clip_len; } } else { int64_t clip_len = 0; for(int pass = 0; pass < 2; pass++) { ld = b->cur_line_desc; for(int64_t i = y; i <= b->block_start_line; i++) { const int64_t start_pos = calc_pos(ld, start_x, b->opt.tab_size, b->encoding); const int64_t len = calc_pos(ld, end_x, b->opt.tab_size, b->encoding) - start_pos; if (pass) { if (len) memcpy(p, ld->line + start_pos, len); p += len; *(p++) = 0; if (cut) delete_stream(b, ld, i, start_pos, len); } else clip_len += len + 1; ld = (line_desc *)ld->ld_node.next; } if (pass) { cd->cs->len = clip_len; set_stream_encoding(cd->cs, b->encoding); assert_clip_desc(cd); if (cut) { update_syntax_states_delay(b, b->cur_line_desc, (line_desc *)ld->ld_node.prev); goto_line_pos(b, min(b->block_start_line, b->cur_line), min(b->block_start_pos, cur_pos)); end_undo_chain(b); } return OK; } clip_desc * const new_cd = realloc_clip_desc(cd, n, clip_len); if (!new_cd) return OUT_OF_MEMORY; if (!cd) add_head(&clips, &new_cd->cd_node); cd = new_cd; p = cd->cs->stream; } } if (cut) end_undo_chain(b); return OK; } /* Simply erases a vertical block, without putting it in a clip. Calls update_syntax_states_delay() if update is true. */ int erase_vert_block(buffer *b, const bool update) { if (!b->marking) return MARK_BLOCK_FIRST; if (b->block_start_line >= b->num_lines) return MARK_OUT_OF_BUFFER; int64_t y = b->cur_line; line_desc *ld = b->cur_line_desc; if (b->cur_pos == b->block_start_pos || y == b->block_start_line && b->cur_pos >= ld->line_len && b->block_start_pos >= ld->line_len) return OK; const int64_t cur_pos = b->cur_pos; b->cur_pos = -1; int64_t start_x = calc_width(nth_line_desc(b, b->block_start_line), b->block_start_pos, b->opt.tab_size, b->encoding); int64_t end_x = b->win_x + b->cur_x; if (end_x < start_x) { const uint64_t t = start_x; start_x = end_x; end_x = t; } start_undo_chain(b); if (y > b->block_start_line) { for(int64_t i = y; i >= b->block_start_line; i--) { const int64_t start_pos = calc_pos(ld, start_x, b->opt.tab_size, b->encoding); const int64_t len = calc_pos(ld, end_x, b->opt.tab_size, b->encoding) - start_pos; delete_stream(b, ld, i, start_pos, len); ld = (line_desc *)ld->ld_node.prev; } if (update) update_syntax_states_delay(b, (line_desc *)ld->ld_node.next, b->cur_line_desc); } else { for(int64_t i = y; i <= b->block_start_line; i++) { const int64_t start_pos = calc_pos(ld, start_x, b->opt.tab_size, b->encoding); const int64_t len = calc_pos(ld, end_x, b->opt.tab_size, b->encoding)-start_pos; delete_stream(b, ld, i, start_pos, len); ld = (line_desc *)ld->ld_node.next; } if (update) update_syntax_states_delay(b, b->cur_line_desc, (line_desc *)ld->ld_node.prev); } end_undo_chain(b); goto_line_pos(b, min(b->block_start_line, b->cur_line), min(b->block_start_pos, cur_pos)); return OK; } /* Performs a vertical paste. It has to be done via an insert_stream() for each string of the clip. Again, the undo linking feature makes all these operations a single undo step. */ int paste_vert_to_buffer(buffer *b, int n) { line_desc * ld = b->cur_line_desc; clip_desc * const cd = get_nth_clip(n); if (!cd) return CLIP_DOESNT_EXIST; if (!cd->cs->len) return OK; if (cd->cs->encoding != ENC_ASCII && b->encoding != ENC_ASCII && cd->cs->encoding != b->encoding) return INCOMPATIBLE_CLIP_ENCODING; if (b->encoding == ENC_ASCII) b->encoding = cd->cs->encoding; char * const stream = cd->cs->stream; char *p = stream; const int64_t stream_len = cd->cs->len; const uint64_t x = b->cur_x + b->win_x; int64_t line = b->cur_line; start_undo_chain(b); b->bookmark[PASTE_START_BOOKMARK].pos = b->cur_pos; b->bookmark[PASTE_START_BOOKMARK].line = b->cur_line; b->bookmark[PASTE_START_BOOKMARK].cur_y = b->cur_y; b->bookmark_mask |= (1 << PASTE_START_BOOKMARK); while(p - stream < stream_len) { if (!ld->ld_node.next) { insert_one_line(b, (line_desc *)ld->ld_node.prev, line - 1, ((line_desc *)ld->ld_node.prev)->line_len); ld = (line_desc *)ld->ld_node.prev; } const int64_t len = strnlen_ne(p, stream_len - (p - stream)); if (len) { uint64_t pos, n; for(n = pos = 0; pos < ld->line_len && n < x; pos = next_pos(ld->line, pos, b->encoding)) { if (ld->line[pos] == '\t') n += b->opt.tab_size - n % b->opt.tab_size; else n += get_char_width(&ld->line[pos], b->encoding); } b->bookmark[PASTE_END_BOOKMARK].line = line; b->bookmark_mask |= (1 << PASTE_END_BOOKMARK); if (pos == ld->line_len && n < x) { /* We miss x - n characters after the end of the line. */ insert_spaces(b, ld, line, ld->line_len, x - n); b->bookmark[PASTE_END_BOOKMARK].pos = next_pos(ld->line, ld->line_len, b->encoding); insert_stream(b, ld, line, ld->line_len, p, len); } else { b->bookmark[PASTE_END_BOOKMARK].pos = next_pos(ld->line, pos, b->encoding); insert_stream(b, ld, line, pos, p, len); } } p += len + 1; ld = (line_desc *)ld->ld_node.next; line++; } b->bookmark[PASTE_END_BOOKMARK].pos = prev_pos(((line_desc *)ld->ld_node.prev)->line, b->bookmark[PASTE_END_BOOKMARK].pos, b->encoding); b->bookmark[PASTE_END_BOOKMARK].cur_y = min(ne_lines - 2, b->bookmark[PASTE_START_BOOKMARK].cur_y + b->bookmark[PASTE_END_BOOKMARK].line - b->bookmark[PASTE_START_BOOKMARK].line); end_undo_chain(b); update_syntax_states_delay(b, b->cur_line_desc, ld); return OK; } /* Loads a clip. It is just a load_stream, plus an insertion in the clip list. If preserve_cr is true, CRs are preserved. */ int load_clip(int n, const char *name, const bool preserve_cr, const bool binary) { clip_desc *cd = get_nth_clip(n); if (!cd) { if (!(cd = alloc_clip_desc(n, 0))) return OUT_OF_MEMORY; add_head(&clips, &cd->cd_node); } const int error = load_stream(cd->cs, name, preserve_cr, binary) ? OK : CANT_OPEN_FILE; if (error == OK) set_stream_encoding(cd->cs, ENC_ASCII); return error; } /* Saves a clip to a file. If CRLF is true, the clip is saved with CR/LF pairs as line terminators. */ int save_clip(int n, const char *name, const bool CRLF, const bool binary) { clip_desc * const cd = get_nth_clip(n); if (!cd) return CLIP_DOESNT_EXIST; return save_stream(cd->cs, name, CRLF, binary); } ne-3.3.4/src/cm.c000066400000000000000000000222231475116431000134600ustar00rootroot00000000000000/* Optimal cursor motion functions. Based primarily on public domain code written by Chris Torek. Originally part of GNU Emacs. Vastly edited and modified for use within ne. Copyright (C) 1985, 1995 Free Software Foundation, Inc. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2025 Todd M. Lewis and Sebastiano Vigna This file is part of ne, the nice editor. This library 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 library 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 . */ #include #include #ifndef NE_TERMCAP #include #include #else #include "info2cap.h" #endif #include "cm.h" #define BIG 9999 int cost; /* sums up costs */ /* This function is used in place of putchar() in tputs() so can we can computed the padded length of a capability string. Note that they should be putchar()-like, so we have to care about the returned value. */ int evalcost (int c) { cost++; return c; } /* This function is used in tputs(). */ int cmputc (int c) { return putchar(c & 0x7f); } /* Terminals with magicwrap (xn) don't all behave identically. The VT100 leaves the cursor in the last column but will wrap before printing the next character. I hear that the Concept terminal does the wrap immediately but ignores the next newline it sees. And some terminals just have buggy firmware, and think that the cursor is still in limbo if we use direct cursor addressing from the phantom column. The only guaranteed safe thing to do is to emit a CRLF immediately after we reach the last column; this takes us to a known state. */ void cmcheckmagic () { if (curX == ScreenCols) { assert(MagicWrap && curY < ScreenRows - 1); putchar ('\r'); putchar ('\n'); curX = 0; curY++; } } /* (Re)Initialises the cost factors, given the output speed of the terminal in the variable ospeed. (Note: this holds B300, B9600, etc -- ie stuff out of .) */ void cmcostinit () { char *p; #define COST(x,e) (x ? (cost = 0, tputs (x, 1, e), cost) : BIG) #define CMCOST(x,e) ((x == 0) ? BIG : (p = tgoto(x, 0, 0), COST(p, e))) Wcm.cc_up = COST (Wcm.cm_up, evalcost); Wcm.cc_down = COST (Wcm.cm_down, evalcost); Wcm.cc_left = COST (Wcm.cm_left, evalcost); Wcm.cc_right = COST (Wcm.cm_right, evalcost); Wcm.cc_home = COST (Wcm.cm_home, evalcost); Wcm.cc_cr = COST (Wcm.cm_cr, evalcost); Wcm.cc_ll = COST (Wcm.cm_ll, evalcost); Wcm.cc_tab = Wcm.cm_tabwidth ? COST (Wcm.cm_tab, evalcost) : BIG; /* These last three are actually minimum costs. When (if) they are candidates for the least-cost motion, the real cost is computed. (Note that "0" is the assumed to generate the minimum cost. While this is not necessarily true, I have yet to see a terminal for which is not; all the terminals that have variable-cost cursor motion seem to take straight numeric values. --ACT) */ Wcm.cc_abs = CMCOST (Wcm.cm_abs, evalcost); Wcm.cc_habs = CMCOST (Wcm.cm_habs, evalcost); Wcm.cc_vabs = CMCOST (Wcm.cm_vabs, evalcost); #undef CMCOST #undef COST } /* Calculates the cost to move from (srcy, srcx) to (dsty, dstx) using up and down, and left and right, motions, and tabs. If doit is set actually perform the motion. */ static int calccost (int srcy, int srcx, int dsty, int dstx, int doit) { register int deltay, deltax, c, totalcost; int ntabs, n2tabs, tabx, tab2x, tabcost; register char *p; /* If have just wrapped on a terminal with xn, don't believe the cursor position: give up here and force use of absolute positioning. */ if (curX == Wcm.cm_cols) goto fail; totalcost = 0; if ((deltay = dsty - srcy) == 0) goto x; if (deltay < 0) p = Wcm.cm_up, c = Wcm.cc_up, deltay = -deltay; else p = Wcm.cm_down, c = Wcm.cc_down; if (c == BIG) { /* caint get thar from here */ if (doit) printf ("OOPS"); return c; } totalcost = c * deltay; if (doit) while (deltay-- != 0) tputs (p, 1, cmputc); x: if ((deltax = dstx - srcx) == 0) goto done; if (deltax < 0) { p = Wcm.cm_left, c = Wcm.cc_left, deltax = -deltax; goto dodelta; /* skip all the tab junk */ } /* Tabs (the toughie) */ if (Wcm.cc_tab >= BIG || !Wcm.cm_usetabs) goto olddelta; /* forget it! */ /* ntabs is # tabs towards but not past dstx; n2tabs is one more (ie past dstx), but this is only valid if that is not past the right edge of the screen. We can check that at the same time as we figure out where we would be if we use the tabs (which we will put into tabx (for ntabs) and tab2x (for n2tabs)). */ ntabs = (deltax + srcx % Wcm.cm_tabwidth) / Wcm.cm_tabwidth; n2tabs = ntabs + 1; tabx = (srcx / Wcm.cm_tabwidth + ntabs) * Wcm.cm_tabwidth; tab2x = tabx + Wcm.cm_tabwidth; if (tab2x >= Wcm.cm_cols) n2tabs = 0; /* too far (past edge) */ /* Now set tabcost to the cost for using ntabs, and c to the cost for using n2tabs, then pick the minimum. */ /* cost for ntabs - cost for right motion */ tabcost = ntabs ? ntabs * Wcm.cc_tab + (dstx - tabx) * Wcm.cc_right : BIG; /* cost for n2tabs - cost for left motion */ c = n2tabs ? n2tabs * Wcm.cc_tab + (tab2x - dstx) * Wcm.cc_left : BIG; if (c < tabcost) /* then cheaper to overshoot & back up */ ntabs = n2tabs, tabcost = c, tabx = tab2x; if (tabcost >= BIG) /* caint use tabs */ goto newdelta; /* See if tabcost is less than just moving right */ if (tabcost < (deltax * Wcm.cc_right)) { totalcost += tabcost; /* use the tabs */ if (doit) while (ntabs-- != 0) tputs (Wcm.cm_tab, 1, cmputc); srcx = tabx; } /* Now might as well just recompute the delta. */ newdelta: if ((deltax = dstx - srcx) == 0) goto done; olddelta: if (deltax > 0) p = Wcm.cm_right, c = Wcm.cc_right; else p = Wcm.cm_left, c = Wcm.cc_left, deltax = -deltax; dodelta: if (c == BIG) { /* caint get thar from here */ fail: if (doit) printf ("OOPS"); return BIG; } totalcost += c * deltax; if (doit) while (deltax-- != 0) tputs (p, 1, cmputc); done: return totalcost; } #define USEREL 0 #define USEHOME 1 #define USELL 2 #define USECR 3 void cmgoto (int row, int col) { int homecost, crcost, llcost, relcost, directcost; int use = USEREL; char *p, *dcm; /* First the degenerate case */ if (row == curY && col == curX) return; /* already there */ if (curY >= 0 && curX >= 0) { /* We may have quick ways to go to the upper-left, bottom-left, start-of-line, or start-of-next-line. Or it might be best to start where we are. Examine the options, and pick the cheapest. */ relcost = calccost (curY, curX, row, col, 0); use = USEREL; if ((homecost = Wcm.cc_home) < BIG) homecost += calccost (0, 0, row, col, 0); if (homecost < relcost) relcost = homecost, use = USEHOME; if ((llcost = Wcm.cc_ll) < BIG) llcost += calccost (Wcm.cm_rows - 1, 0, row, col, 0); if (llcost < relcost) relcost = llcost, use = USELL; if ((crcost = Wcm.cc_cr) < BIG) { if (Wcm.cm_autolf) if (curY + 1 >= Wcm.cm_rows) crcost = BIG; else crcost += calccost (curY + 1, 0, row, col, 0); else crcost += calccost (curY, 0, row, col, 0); } if (crcost < relcost) relcost = crcost, use = USECR; directcost = Wcm.cc_abs, dcm = Wcm.cm_abs; if (row == curY && Wcm.cc_habs < BIG) directcost = Wcm.cc_habs, dcm = Wcm.cm_habs; else if (col == curX && Wcm.cc_vabs < BIG) directcost = Wcm.cc_vabs, dcm = Wcm.cm_vabs; } else { directcost = 0, relcost = 100000; dcm = Wcm.cm_abs; } /* In the following comparison, the = in <= is because when the costs are the same, it looks nicer (I think) to move directly there. */ if (directcost <= relcost) { /* compute REAL direct cost */ cost = 0; p = dcm == Wcm.cm_habs ? tgoto (dcm, row, col) : tgoto (dcm, col, row); tputs (p, 1, evalcost); if (cost <= relcost) { /* really is cheaper */ tputs (p, 1, cmputc); curY = row, curX = col; return; } } switch (use) { case USEHOME: tputs (Wcm.cm_home, 1, cmputc); curY = 0, curX = 0; break; case USELL: tputs (Wcm.cm_ll, 1, cmputc); curY = Wcm.cm_rows - 1, curX = 0; break; case USECR: tputs (Wcm.cm_cr, 1, cmputc); if (Wcm.cm_autolf) curY++; curX = 0; break; } (void) calccost (curY, curX, row, col, 1); curY = row, curX = col; } /* Clears out all terminal info. Used before copying into it the info on the actual terminal. */ void Wcm_clear () { memset(&Wcm, 0, sizeof Wcm); } /* Initialises stuff. Returns 0 if can do CM, -1 if cannot, -2 if size not specified. */ int Wcm_init () { if (Wcm.cm_abs) return 0; /* Require up and left, and, if no absolute, down and right */ if (!Wcm.cm_up || !Wcm.cm_left) return - 1; if (!Wcm.cm_abs && (!Wcm.cm_down || !Wcm.cm_right)) return - 1; /* Check that we know the size of the screen.... */ if (Wcm.cm_rows <= 0 || Wcm.cm_cols <= 0) return - 2; return 0; } ne-3.3.4/src/cm.h000066400000000000000000000103221475116431000134620ustar00rootroot00000000000000/* Optimal cursor motion definitions. Based primarily on public domain code written by Chris Torek. Originally part of GNU Emacs. Vastly edited and modified for use within ne. Copyright (C) 1985, 1989 Free Software Foundation, Inc. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2025 Todd M. Lewis and Sebastiano Vigna This file is part of ne, the nice editor. This library 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 library 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 . */ /* This structure holds everything needed to do cursor motion. */ extern struct cm { /* Cursor position. -1 in *both* variables means the cursor position is unknown, in order to force absolute cursor motion. */ int cm_curY, /* current row */ cm_curX; /* current column */ /* Capabilities from terminfo */ char *cm_up, /* up (up) */ *cm_down, /* down (do) */ *cm_left, /* left (bs) */ *cm_right, /* right (nd) */ *cm_home, /* home (ho) */ *cm_cr, /* carriage return (cr) */ *cm_ll, /* last line (ll) */ *cm_tab, /* tab (ta) */ *cm_backtab, /* backtab (bt) */ *cm_abs, /* absolute (cm) */ *cm_habs, /* horizontal absolute (ch) */ *cm_vabs, /* vertical absolute (cv) */ *cm_multiup, /* multiple up (UP) */ *cm_multidown, /* multiple down (DO) */ *cm_multileft, /* multiple left (LE) */ *cm_multiright; /* multiple right (RI) */ int cm_cols, /* Number of cols on screen (co) */ cm_rows, /* Number of rows on screen (li) */ cm_tabwidth; /* tab width (it) */ unsigned int cm_autowrap:1, /* autowrap flag (am) */ cm_magicwrap:1, /* vt100s: cursor stays in last col but will wrap if next char is printing (xn) */ cm_usetabs:1, /* if set, use tabs */ cm_autolf:1, /* \r performs a \r\n (rn) */ cm_losewrap:1; /* if reach right margin, forget cursor location */ /* Costs */ int cc_up, /* cost for up */ cc_down, /* etc */ cc_left, cc_right, cc_home, cc_cr, cc_ll, cc_tab, cc_backtab, cc_abs, /* abs costs are actually min costs */ cc_habs, cc_vabs; } Wcm; /* Shorthands */ #define curY Wcm.cm_curY #define curX Wcm.cm_curX #define Up Wcm.cm_up #define Down Wcm.cm_down #define Left Wcm.cm_left #define Right Wcm.cm_right #define Tab Wcm.cm_tab #define BackTab Wcm.cm_backtab #define TabWidth Wcm.cm_tabwidth #define CR Wcm.cm_cr #define Home Wcm.cm_home #define LastLine Wcm.cm_ll #define AbsPosition Wcm.cm_abs #define ColPosition Wcm.cm_habs #define RowPosition Wcm.cm_vabs #define MultiUp Wcm.cm_multiup #define MultiDown Wcm.cm_multidown #define MultiLeft Wcm.cm_multileft #define MultiRight Wcm.cm_multiright #define AutoWrap Wcm.cm_autowrap #define MagicWrap Wcm.cm_magicwrap #define UseTabs Wcm.cm_usetabs #define ScreenRows Wcm.cm_rows #define ScreenCols Wcm.cm_cols #define UpCost Wcm.cc_up #define DownCost Wcm.cc_down #define LeftCost Wcm.cc_left #define RightCost Wcm.cc_right #define HomeCost Wcm.cc_home #define CRCost Wcm.cc_cr #define LastLineCost Wcm.cc_ll #define TabCost Wcm.cc_tab #define BackTabCost Wcm.cc_backtab #define AbsPositionCost Wcm.cc_abs #define ColPositionCost Wcm.cc_habs #define RowPositionCost Wcm.cc_vabs #define MultiUpCost Wcm.cc_multiup #define MultiDownCost Wcm.cc_multidown #define MultiLeftCost Wcm.cc_multileft #define MultiRightCost Wcm.cc_multiright #define cmat(row,col) (curY = (row), curX = (col)) #define cmplus(n) \ { \ if ((curX += (n)) >= ScreenCols && !MagicWrap) \ { \ if (Wcm.cm_losewrap) losecursor (); \ else if (AutoWrap) curX = 0, curY++; \ else curX--; \ } \ } #define losecursor() (curX = -1, curY = -1) int cmputc(int); int Wcm_init (void); void cmcostinit (void); void cmgoto (int row, int col); #include "debug.h" ne-3.3.4/src/command.c000066400000000000000000001061121475116431000144770ustar00rootroot00000000000000/* Command table manipulation functions and vectors. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2025 Todd M. Lewis and Sebastiano Vigna This file is part of ne, the nice editor. This library 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 library 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 . */ #include "ne.h" #include "support.h" #include "help.h" #include "hash.h" #undef TABSIZE /* The standard macro descriptor allocation dimension. */ #define STD_MACRO_DESC_SIZE 1024 /* This structure represents a command. It includes a long and a short name, a NULL-terminated vector of help strings (of specified length) and some flags which are related to the syntax and the semantics of the arguments. */ typedef struct { const char *name, *short_name; const char * const *help; int help_len; int flags; } command; #define NO_ARGS (1<<1) /* This command must be called without argument. */ #define ARG_IS_STRING (1<<2) /* The argument is a string (default is a number). */ #define IS_OPTION (1<<3) /* The command controls an option, and can be played while exec_only_options is true. */ #define DO_NOT_RECORD (1<<4) /* Never record this command. */ #define EMPTY_STRING_OK (1<<5) /* This command can accept an empty string ("") as an argument. */ /* These macros makes the following vector more readable. */ #define HELP_LEN(x) (sizeof(x ## _HELP) / sizeof(char *) - 1) #define NAHL(x) x ## _NAME, x ##_ABBREV, x ## _HELP, HELP_LEN(x) /* This is the command vector. Note that the command names come from names.h, and the help names come from help.h. This must be kept sorted. */ static const command commands[ACTION_COUNT] = { { NAHL(ABOUT ), NO_ARGS }, { NAHL(ADJUSTVIEW ), ARG_IS_STRING }, { NAHL(ALERT ), NO_ARGS }, { NAHL(ATOMICUNDO ), ARG_IS_STRING | EMPTY_STRING_OK }, { NAHL(AUTOCOMPLETE ), ARG_IS_STRING | EMPTY_STRING_OK }, { NAHL(AUTOINDENT ), IS_OPTION }, { NAHL(AUTOMATCHBRACKET), IS_OPTION }, { NAHL(AUTOPREFS ), IS_OPTION }, { NAHL(BACKSPACE ),0 }, { NAHL(BEEP ), NO_ARGS }, { NAHL(BINARY ), IS_OPTION }, { NAHL(BRACKETEDPASTE), ARG_IS_STRING | IS_OPTION | EMPTY_STRING_OK }, { NAHL(CAPITALIZE ),0 }, { NAHL(CASESEARCH ), IS_OPTION }, { NAHL(CENTER ),0 }, { NAHL(CLEAR ), NO_ARGS }, { NAHL(CLIPNUMBER ), IS_OPTION }, { NAHL(CLOSEDOC ), NO_ARGS }, { NAHL(COPY ),0 }, { NAHL(CRLF ), IS_OPTION }, { NAHL(CUT ),0 }, { NAHL(DELETECHAR ),0 }, { NAHL(DELETEEOL ), NO_ARGS }, { NAHL(DELETELINE ),0 }, { NAHL(DELETENEXTWORD),0 }, { NAHL(DELETEPREVWORD),0 }, { NAHL(DELTABS ), IS_OPTION }, { NAHL(DOUNDO ), IS_OPTION }, { NAHL(ERASE ),0 }, { NAHL(ESCAPE ), DO_NOT_RECORD }, { NAHL(ESCAPETIME ), IS_OPTION }, { NAHL(EXEC ), ARG_IS_STRING | DO_NOT_RECORD }, { NAHL(EXIT ), NO_ARGS }, { NAHL(FASTGUI ), IS_OPTION }, { NAHL(FIND ), ARG_IS_STRING }, { NAHL(FINDREGEXP ), ARG_IS_STRING }, { NAHL(FLAGS ), NO_ARGS | DO_NOT_RECORD }, { NAHL(FLASH ), NO_ARGS }, { NAHL(FREEFORM ), IS_OPTION }, { NAHL(GOTOBOOKMARK ), ARG_IS_STRING | EMPTY_STRING_OK }, { NAHL(GOTOCOLUMN ),0 }, { NAHL(GOTOLINE ),0 }, { NAHL(GOTOMARK ), NO_ARGS }, { NAHL(HELP ), ARG_IS_STRING | DO_NOT_RECORD }, { NAHL(HEXCODE ), IS_OPTION }, { NAHL(INSERT ), IS_OPTION }, { NAHL(INSERTCHAR ),0 }, { NAHL(INSERTLINE ),0 }, { NAHL(INSERTSTRING ), ARG_IS_STRING }, { NAHL(INSERTTAB ),0 }, { NAHL(KEYCODE ), DO_NOT_RECORD }, { NAHL(LINEDOWN ),0 }, { NAHL(LINEUP ),0 }, { NAHL(LOADAUTOPREFS ), NO_ARGS }, { NAHL(LOADPREFS ), ARG_IS_STRING }, { NAHL(MACRO ), ARG_IS_STRING | DO_NOT_RECORD }, { NAHL(MARK ), IS_OPTION }, { NAHL(MARKVERT ), IS_OPTION }, { NAHL(MATCHBRACKET ), NO_ARGS }, { NAHL(MODIFIED ), IS_OPTION }, { NAHL(MOVEBOS ), NO_ARGS }, { NAHL(MOVEEOF ), NO_ARGS }, { NAHL(MOVEEOL ), NO_ARGS }, { NAHL(MOVEEOW ), ARG_IS_STRING | EMPTY_STRING_OK }, { NAHL(MOVEINCDOWN ), NO_ARGS }, { NAHL(MOVEINCUP ), NO_ARGS }, { NAHL(MOVELEFT ),0 }, { NAHL(MOVERIGHT ),0 }, { NAHL(MOVESOF ), NO_ARGS }, { NAHL(MOVESOL ), NO_ARGS }, { NAHL(MOVETOS ), NO_ARGS }, { NAHL(NAMECONVERT ), IS_OPTION }, { NAHL(NEWDOC ), NO_ARGS }, { NAHL(NEXTDOC ), NO_ARGS }, { NAHL(NEXTPAGE ),0 }, { NAHL(NEXTWORD ), ARG_IS_STRING | EMPTY_STRING_OK }, { NAHL(NOFILEREQ ), IS_OPTION }, { NAHL(NOP ), NO_ARGS }, { NAHL(OPEN ), ARG_IS_STRING }, { NAHL(OPENCLIP ), ARG_IS_STRING }, { NAHL(OPENMACRO ), ARG_IS_STRING }, { NAHL(OPENNEW ), ARG_IS_STRING }, { NAHL(PAGEDOWN ),0 }, { NAHL(PAGEUP ),0 }, { NAHL(PARAGRAPH ),0 }, { NAHL(PASTE ),0 }, { NAHL(PASTEVERT ),0 }, { NAHL(PLAY ), DO_NOT_RECORD }, { NAHL(POPPREFS ),0 }, { NAHL(PRESERVECR ), IS_OPTION }, { NAHL(PREVDOC ), NO_ARGS }, { NAHL(PREVPAGE ),0 }, { NAHL(PREVWORD ), ARG_IS_STRING | EMPTY_STRING_OK }, { NAHL(PUSHPREFS ), IS_OPTION }, { NAHL(QUIT ), DO_NOT_RECORD }, { NAHL(READONLY ), IS_OPTION }, { NAHL(RECORD ), IS_OPTION | DO_NOT_RECORD }, { NAHL(REDO ),0 }, { NAHL(REFRESH ), NO_ARGS }, { NAHL(REPEATLAST ), ARG_IS_STRING | EMPTY_STRING_OK }, { NAHL(REPLACE ), ARG_IS_STRING | EMPTY_STRING_OK }, { NAHL(REPLACEALL ), ARG_IS_STRING | EMPTY_STRING_OK }, { NAHL(REPLACEONCE ), ARG_IS_STRING | EMPTY_STRING_OK }, { NAHL(REQUESTORDER ), IS_OPTION }, { NAHL(RIGHTMARGIN ), IS_OPTION }, { NAHL(SAVE ), NO_ARGS }, { NAHL(SAVEALL ), NO_ARGS }, { NAHL(SAVEAS ), ARG_IS_STRING }, { NAHL(SAVEAUTOPREFS ), NO_ARGS }, { NAHL(SAVECLIP ), ARG_IS_STRING }, { NAHL(SAVEDEFPREFS ), NO_ARGS }, { NAHL(SAVEMACRO ), ARG_IS_STRING }, { NAHL(SAVEPREFS ), ARG_IS_STRING }, { NAHL(SEARCHBACK ), IS_OPTION }, { NAHL(SELECTDOC ),0 }, { NAHL(SETBOOKMARK ), ARG_IS_STRING | EMPTY_STRING_OK }, { NAHL(SHIFT ), ARG_IS_STRING | EMPTY_STRING_OK }, { NAHL(SHIFTTABS ), IS_OPTION }, { NAHL(STATUSBAR ), IS_OPTION }, { NAHL(SUSPEND ), NO_ARGS }, { NAHL(SYNTAX ), ARG_IS_STRING | IS_OPTION }, { NAHL(SYSTEM ), ARG_IS_STRING }, { NAHL(TABS ), IS_OPTION }, { NAHL(TABSIZE ), IS_OPTION }, { NAHL(THROUGH ), ARG_IS_STRING }, { NAHL(TOGGLESEOF ), NO_ARGS }, { NAHL(TOGGLESEOL ), NO_ARGS }, { NAHL(TOLOWER ),0 }, { NAHL(TOUPPER ),0 }, { NAHL(TURBO ), IS_OPTION }, { NAHL(UNDELLINE ),0 }, { NAHL(UNDO ),0 }, { NAHL(UNLOADMACROS ), NO_ARGS }, { NAHL(UNSETBOOKMARK ), ARG_IS_STRING | EMPTY_STRING_OK }, { NAHL(UTF8 ), IS_OPTION }, { NAHL(UTF8AUTO ), IS_OPTION }, { NAHL(UTF8IO ), IS_OPTION }, { NAHL(VERBOSEMACROS ), IS_OPTION }, { NAHL(VISUALBELL ), IS_OPTION }, { NAHL(WORDWRAP ), IS_OPTION }, }; /* Checks whether the command line m starts with the command c. Return 0 on success, non-zero on failure. */ int cmdcmp(const char *c, const char *m) { assert(c != NULL); assert(m != NULL); while (*c && ascii_up_case[*(unsigned char *)c] == ascii_up_case[*(unsigned char *)m]) { c++; m++; } return *c || *m && !isasciispace(*m) ; } /* This table *can* have conflicts, so that we can keep its size much smaller. */ static macro_desc *macro_hash_table[MACRO_HASH_TABLE_SIZE]; /* This is the command name hashing function. We consider only the 5 least significant bits because they are the bits which distinguish characters, independently of their case. We are not interested in strings which contain non-alphabetical characters, because they will certainly generate an error (the only exception notably being "R1"). We should subtract 1 to s[i], but this doesn't seem to produce any improvement. hash_macro() act as hash(), but uses MACRO_HASH_TABLE_SIZE for its modulo. */ static int hash_cmd(const char * const s, int len) { int h = -1; while(len-- != 0) h = (h * 31 + ascii_up_case[(unsigned char)s[len]]) % HASH_TABLE_SIZE; return (h + HASH_TABLE_SIZE) % HASH_TABLE_SIZE; } static int hash_macro(const char * const s, int len) { int h = -1; while(len-- != 0) h = (h * 31 + ascii_up_case[(unsigned char)s[len]]) % MACRO_HASH_TABLE_SIZE; return (h + MACRO_HASH_TABLE_SIZE) % MACRO_HASH_TABLE_SIZE; } /* Parses a command line. This function has an interface which is slightly varied with respect to the other functions of ne. In case of a parsing error, an error index *with sign inverted* is passed back. In case parsing succeeds, an (greater or equal to zero) action is returned, and the numerical or string argument is passed in the variables pointed to by num_arg or string_arg, respectively, if they are non-NULL. Otherwise, the argument is not passed back. The string argument is free()able. -1 and NULL denote the lack of an optional numerical or string argument, respectively. NOP is returned on a NOP command, or on a comment line (any line whose first non-space character is a non alphabetic character). Note that the various syntax flags are used here. */ int parse_command_line(const char * command_line, int64_t * const num_arg, char ** const string_arg, const bool exec_only_options) { if (num_arg) *num_arg = -1; if (string_arg) *string_arg = NULL; if (!command_line || !*command_line) return NOP_A; while(isasciispace(*command_line)) command_line++; const char *p = command_line; D(fprintf(stderr,"parse_command_line[%d]: command_line=\"%s\"\n", __LINE__, p);) if (!isalpha((unsigned char)*p)) { /* Comment, treated as NOP. */ const int len = strlen(p); if (!(*string_arg = malloc(len + 1))) return -OUT_OF_MEMORY; memcpy(*string_arg, p, len); (*string_arg)[len] = 0; D(fprintf(stderr,"parse_command_line[%d]: returning NOP_A\n", __LINE__);) return NOP_A; } while(*p && !isasciispace(*p)) p++; const int h = hash_cmd(command_line, p - command_line); action a; if ((a = hash_table[h]) && !cmdcmp(commands[--a].name, command_line) || (a = short_hash_table[h]) && !cmdcmp(commands[--a].short_name, command_line)) { while(isasciispace(*p)) p++; if (!(*p && (commands[a].flags & NO_ARGS))) { if (!*p || (commands[a].flags & ARG_IS_STRING) || isxdigit((unsigned char)*p) || *p == 'x' || *p =='X') { if ((commands[a].flags & IS_OPTION) || !exec_only_options) { if (*p) { if ((commands[a].flags & ARG_IS_STRING) && string_arg) { int len = strlen(p); if (len > 1 && *p == '"' && p[len - 1] == '"') { p++; len -= 2; } if (len == 0 && !(commands[a].flags & EMPTY_STRING_OK)) return -STRING_IS_EMPTY; if (!(*string_arg = malloc(len + 1))) return -OUT_OF_MEMORY; memcpy(*string_arg, p, len); (*string_arg)[len] = 0; } else if (num_arg) { char *q; *num_arg = strtoll(p, &q, 0); if (*q && !isasciispace(*q)) return -NOT_A_NUMBER; } } return a; } D(fprintf(stderr, "parse_command[%d] error: Can execute only options \"%s\"\n", __LINE__, command_line);) return -CAN_EXECUTE_ONLY_OPTIONS; } D(fprintf(stderr, "parse_command[%d] error: Has numeric argument \"%s\"\n", __LINE__, command_line);) return -HAS_NUMERIC_ARGUMENT; } D(fprintf(stderr, "parse_command[%d] error: Has no argument \"%s\"\n", __LINE__, command_line);) return -HAS_NO_ARGUMENT; } D(fprintf(stderr, "parse_command[%d] error: No such command \"%s\"\n", __LINE__, command_line);) return -NO_SUCH_COMMAND; } /* Parses and executes a command line. Standard error codes are returned. If the search for a standard command fails, we try to execute a macro in ~/.ne with the same name. */ int execute_command_line(buffer *b, const char *command_line) { encoding_type encoding = detect_encoding(command_line, strlen(command_line)); if (b->encoding != ENC_ASCII && encoding != ENC_ASCII && b->encoding != encoding) return INCOMPATIBLE_COMMAND_ENCODING; int64_t n; int a; char *p; if ((a = parse_command_line(command_line, &n, &p, b->exec_only_options)) >= 0) return do_action(b, a, n, p); a = -a; if ((a == NO_SUCH_COMMAND) && (a = execute_macro(b, command_line)) == CANT_OPEN_MACRO) a = NO_SUCH_COMMAND; return a; } /* Allocates a macro descriptor. It does not allocate the internal character stream, which has to be allocated and stuffed in separately. */ macro_desc *alloc_macro_desc(void) { return calloc(1, sizeof(macro_desc)); } /* Frees a macro descriptor. */ void free_macro_desc(macro_desc *md) { if (!md) return; assert_macro_desc(md); free(md->name); free_char_stream(md->cs); free(md); } /* Here we record an action in a character stream. The action name is expanded in a short or long name, depending on the value of the verbose parameter. A numerical or string argument are expanded and copied, too. If the command should not be recorded (for instance, ESCAPE_A) we return. */ void record_action(char_stream *cs, action a, int64_t c, const char *p, bool verbose) { if (commands[a].flags & DO_NOT_RECORD) return; char t[MAX_INT_LEN + 2]; /* NOP_A is special; it may actually be a comment. Blank lines and real NOPs are recorded as blank lines. */ if (a == NOP_A) { if (p && *p) add_to_stream(cs, p, strlen(p) + 1); else add_to_stream(cs, "", 1); return; } if (verbose) add_to_stream(cs, commands[a].name, strlen(commands[a].name)); else add_to_stream(cs, commands[a].short_name, strlen(commands[a].short_name)); if (c >= 0) { sprintf(t, " %" PRId64, c); add_to_stream(cs, t, strlen(t)); } else if (p) { add_to_stream(cs, " ", 1); if (!*p || isasciispace(*p)) add_to_stream(cs, "\"", 1); add_to_stream(cs, p, strlen(p)); if (!*p || isasciispace(*p)) add_to_stream(cs, "\"", 1); } add_to_stream(cs, "", 1); } /* A support function for optimize_macro(). It examines a string to see if it is a valid "InsertChar ##" command. If it is, then insertchar_val() returns the character code, otherwise it returns 0. */ static int insertchar_val(const char *p) { if ( !p || !*p) return 0; while(isasciispace(*p)) p++; const char * const cmd = p; if (!isalpha((unsigned char)*p)) return 0; while(*p && !isasciispace(*p)) p++; int h = hash_cmd(cmd, p - cmd); action a; if (((a = hash_table[h]) && !cmdcmp(commands[--a].name, cmd) || (a = short_hash_table[h]) && !cmdcmp(commands[--a].short_name, cmd)) && a == INSERTCHAR_A) { while(isasciispace(*p)) p++; h = strtol(p, (char **)&cmd, 0); return *cmd || h < 0 ? 0 : h; } return 0; } /* Optimizing macros is not safe if there are any subsequent undo commands or calls to other macros (which may themselves contain undo commands). This function looks through a stream for undo or non-built in commands, and returns false if any are found; returns true otherwise. */ bool vet_optimize_macro_stream(char_stream * const cs, int64_t pos) { int64_t n; int a; char *p; while (pos < cs->len ) { if ((a = parse_command_line(&cs->stream[pos], &n, &p, 0)) >= 0) { if (p) free(p); if (a == UNDO_A) return false; /* optimization is not safe */ } else { a = -a; if (a == NO_SUCH_COMMAND) return false; /* possibly a macro invocation */ } pos += strlen(&cs->stream[pos]) + 1; } return true; } /* Looks through the macro stream for consecutive runs of InsertChar commands and replaces them with appropriate InsertString commands. This makes macros much easier to read if and when they have to be edited. Note that if the character inserted by InsertChar is not an ASCII character, then we should leave it as an InsertChar command to maximize portability of the macros. */ void optimize_macro(char_stream *cs, bool verbose) { if (!cs || !cs->len) return; int building = 0; bool safe_to_optimize = false; for (int64_t pos = 0; pos < cs->len; pos += strlen(&cs->stream[pos]) + 1) { char * const cmd = &cs->stream[pos]; const int chr = insertchar_val(cmd); if (chr < 0x80 && isprint(chr) && (safe_to_optimize = vet_optimize_macro_stream(cs, pos))) { delete_from_stream(cs, pos, strlen(cmd) + 1); const char two[2] = { chr }; if (building) { building++; insert_in_stream(cs, two, building, 1); } else { const char * const insert = verbose ? INSERTSTRING_NAME : INSERTSTRING_ABBREV; const int64_t len = strlen(insert); insert_in_stream(cs, "\"", pos, 2); /* Closing quote */ insert_in_stream(cs, two, pos, 1); /* The character itself */ insert_in_stream(cs, " \"", pos, 2); /* space and opening quote */ insert_in_stream(cs, insert, pos, len); /* The command itself */ building = pos + len + 2; /* This is where the char is now */ } } else building = 0; } } /* Plays a character stream c times, considering each line as a command line. It polls the global stop variable in order to check for the user interrupting. Note that the macro is duplicated before the first execution: this is absolutely necessary, for otherwise a call to CloseDoc, Record or UnloadMacros could free() the block of memory which we are executing. */ int play_macro(char_stream *cs, int64_t c) { if (!cs) return ERROR; /* If len is 0 or 1, the character stream does not contain anything. */ const int64_t len = cs->len; if (len < 2 || c < 1) return OK; char * const stream = malloc(len); if (!stream) return OUT_OF_MEMORY; static int call_depth = 0; if (++call_depth > 32) { --call_depth; return MAX_MACRO_DEPTH_EXCEEDED; } executing_macro = true; memcpy(stream, cs->stream, len); stop = false; int error = OK; while(!stop && !error && c--) { char * p = stream; while(!stop && p - stream < len) { #ifdef NE_TEST fprintf(stderr, "%s\n", p); /* During tests, we output to stderr the current command. */ #endif if (error = execute_command_line(cur_buffer, p)) #ifndef NE_TEST break /* During tests, we never interrupt a macro. */ #endif ; #ifdef NE_TEST refresh_window(cur_buffer); draw_status_bar(); #endif p += strlen(p) + 1; } } free(stream); if (--call_depth == 0) executing_macro = false; return stop ? STOPPED : error; } /* Loads a macro, and puts it in the global macro hash table. file_part is applied to the name argument before storing it and hashing it. Note that if the macro can't be opened, we retry prefixing its name with the preferences directory name (~/.ne/). Thus, for instance, all autopreferences file whose name does not conflict with internal commands can be executed transparently just by typing their name. */ macro_desc *load_macro(const char *name) { assert(name != NULL); macro_desc * const md = alloc_macro_desc(); if (!md) return NULL; char_stream * cs = load_stream(md->cs, name, false, false); D(fprintf(stderr,"load_macro[%d]: name=%s, cs=%lx\n", __LINE__, name, cs);) char *macro_dir, *prefs_dir; if (!cs && (prefs_dir = exists_prefs_dir()) && (macro_dir = malloc(strlen(prefs_dir) + 2 + strlen(name)))) { strcat(strcpy(macro_dir, prefs_dir), name); cs = load_stream(md->cs, macro_dir, false, false); D(fprintf(stderr,"load_macro[%d]: name=%s, cs=%lx\n", __LINE__, macro_dir, cs);) free(macro_dir); } if (!cs && (prefs_dir = exists_gprefs_dir()) && (macro_dir = malloc(strlen(prefs_dir) + 2 + strlen(name) + 7))) { strcat(strcat(strcpy(macro_dir, prefs_dir), "macros/"), name); cs = load_stream(md->cs, macro_dir, false, false); D(fprintf(stderr,"load_macro[%d]: name=%s, cs=%lx\n", __LINE__, macro_dir, cs);) free(macro_dir); } if (cs) { /* the last line may not be null-terminated, so... */ add_to_stream(cs, "", 1); md->cs = cs; md->name = str_dup(file_part(name)); const int h = hash_macro(md->name, strlen(md->name)); macro_desc **m = ¯o_hash_table[h]; while(*m) m = &((*m)->next); *m = md; return md; } free_macro_desc(md); return NULL; } /* Executes a named macro. If the macro is not in the global macro list, it is loaded. A standard error code is returned. */ int execute_macro(buffer *b, const char *name) { const char * const p = file_part(name); int h = hash_macro(p, strlen(p)); D(fprintf(stderr,"execute_macro[%d]: searching macro_hash_table for file_part of name=%s\n", __LINE__, name);) macro_desc *md; for(md = macro_hash_table[h]; md && cmdcmp(md->name, p); md = md->next ); if (!md) { md = load_macro(name); D(fprintf(stderr,"execute_macro[%d]: load_macro(\"%s\") returned md=%lx\n", __LINE__, name, md);) } assert_macro_desc(md); int rc = CANT_OPEN_MACRO; if (md) { if (recording_macro) { add_to_stream(recording_macro, "# include macro ", 16); add_to_stream(recording_macro, md->name, strlen(md->name)+1); } rc = play_macro(md->cs, 1); if (recording_macro) { add_to_stream(recording_macro, "# conclude macro ", 17); add_to_stream(recording_macro, md->name, strlen(md->name)+1); } } return rc; } /* Clears up the macro table. */ void unload_macros(void) { for(int i = 0; i < MACRO_HASH_TABLE_SIZE; i++) { macro_desc *m = macro_hash_table[i]; macro_hash_table[i] = NULL; while(m) { macro_desc * const n = m->next; free_macro_desc(m); m = n; } } } /* Find first n key strokes that currently map to commands[i].name or commands[i].short_name. Returns either NULL or a char string that must be freed by the caller. */ char *find_key_strokes(int c, int n) { char *str=NULL, *p; for(int i = 0; i < NUM_KEYS && n; i++) { if (key_binding[i]) { if (((!strncasecmp(commands[c].short_name, key_binding[i], strlen(commands[c].short_name))) && ((!key_binding[i][strlen(commands[c].short_name)] ) || (key_binding[i][strlen(commands[c].short_name)] == ' ') ) ) || ((!strncasecmp(commands[c].name, key_binding[i], strlen(commands[c].name))) && ((!key_binding[i][strlen(commands[c].name)] ) || (key_binding[i][strlen(commands[c].name)] == ' ') ) ) ) { n--; if (!str) { if (!(str = malloc(1))) return NULL; str[0] = '\0'; } if (p = realloc(str, strlen(str) + strlen(key_stroke[i]) + 2)) { str = strcat(strcat(p, *p ? " " : ""), key_stroke[i]); } else { free(str); return NULL; } } } } return str; } char *bound_keys_string(int c) { char *key_strokes = find_key_strokes(c, 9); char *str=NULL; if (key_strokes) { if ((str = malloc(strlen(key_strokes) + 16))) strcat(strcpy(str, "Bound keys(s): "), key_strokes); free(key_strokes); } return str; } /* This function helps. The help text relative to the command name pointed to by p is displayed (p can also contain arguments). The string *p is not free'd by help(). If p is NULL, the alphabetically ordered list of commands is displayed with the string requester. The help finishes when the user escapes. WARNING: help() assumes a lot about how request_strings() and 'req_list's work rather than using the support functions to build its req_list. Any changes here or in request.c need to be thoroughly checked in both places. */ void help(char *p) { bool request_order_orig = req_order; req_list rl = { .ignore_tab=true, .help_quits=true }; D(fprintf(stderr, "Help Called with parm %p.\n", p);) int r = 0, width; do { print_message(info_msg[HELP_KEYS]); rl.cur_entries = ACTION_COUNT; rl.alloc_entries = 0; rl.entries = (char **)command_names; rl.lengths = realloc(rl.lengths, sizeof(int) * rl.cur_entries); width = 0; for (int i=0,w; i width) width = w; for (int i=0; i= 0) { D(fprintf(stderr, "Help check #2: p=%p, r=%d\n", p, r);) if (p) { for(r = 0; r < strlen(p); r++) if (isasciispace((unsigned char)p[r])) break; r = hash_cmd(p, r); D(fprintf(stderr, "Help check #3: p=%p, *p=%s, r=%d\n", p, p, r);) action a; if ((a = hash_table[r]) && !cmdcmp(commands[--a].name, p) || (a = short_hash_table[r]) && !cmdcmp(commands[--a].short_name, p)) r = a; else r = -1; D(fprintf(stderr, "Help check #4: r=%d\n", r);) p = NULL; } else { D(fprintf(stderr, "Gonna parse_command_line(\"%s\",NULL,NULL,false);\n", command_names[r]);) r = parse_command_line(command_names[r], NULL, NULL, false); D(fprintf(stderr, "...and got r=%d\n", r);) } if (r < 0) { r = 0; continue; } assert(r >= 0 && r < ACTION_COUNT); print_message(info_msg[HELP_COMMAND_KEYS]); char *key_strokes, **tmphelp; if ((key_strokes = bound_keys_string(r)) && (tmphelp = calloc(commands[r].help_len+1, sizeof(char *)))) { tmphelp[0] = (char *)commands[r].help[0]; tmphelp[1] = (char *)commands[r].help[1]; tmphelp[2] = key_strokes; memcpy(&tmphelp[3], &commands[r].help[2], sizeof(char *) * (commands[r].help_len-2)); rl.cur_entries = commands[r].help_len+1; rl.alloc_entries = 0; rl.entries = tmphelp; rl.lengths = realloc(rl.lengths, sizeof(int) * rl.cur_entries); width = 0; for (int i=0,w; i width) width = w; for (int i=0; i width) width = w; for (int i=0; i= 0); free(rl.lengths); draw_status_bar(); } /* Parse string parameters for NextWord, PrevWord, AdjustView, etc. */ int parse_word_parm(char *p, char *pat_in, int64_t *match) { int i, len = strlen(pat_in); char *pat = strntmp(pat_in, len); if (p) { while (*p) { if (isasciispace(*p)) p++; else if (isdigit((unsigned char)*p)) { for (i=0; i where from_year and to_year are 4-digit years. This program updates any .c, .h, .pl, .in, and .jsf files in the current directory that have strings matching these patterns: Copyright (C) ####- Copyright (C) to these: Copyright (C) ####- Copyright (C) -\n]; exit 1; } my ($pass,$fail,$changes) = (0,0,0); my @files = grep { -f $_ } glob('*.c *.h *.pl *.in *.jsf'); for my $file ( @files ) { if (! open FILE, "<", $file ) { print "Note: couldn't read '$file'; $!\n"; next; } my $text = join('',); close FILE; my $count = $text =~ s/(Copyright\s+\(C\)\s+$from_year)/$1-${to_year}/ig; $count += $text =~ s/(Copyright\s+\(C\)\s+\d\d\d\d-)$from_year/$1${to_year}/ig; # printf "%8d %s\n", $count, $file; next unless $count > 0; if ( ! open FILE, ">", $file ) { print "Error: couldn't update '$file'; $!\n"; $fail++; next; } print FILE $text; if (close FILE) { printf "Lines updated: %4d %s\n", $count, $file; $pass++; $changes += $count; } else { print "Error updating '$file'; $!\n"; $fail++; } } print "Files updated: $pass\nLines updated: $changes\nErrors: $fail\n"; exit ($fail ? 2 : 0); ne-3.3.4/src/debug.h000066400000000000000000000016421475116431000141560ustar00rootroot00000000000000/* Definition for debug statements and assertions. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2025 Todd M. Lewis and Sebastiano Vigna This file is part of ne, the nice editor. This library 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 library 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 . */ #include #include #ifdef DEBUGPRINTF #define D(x) x #else #define D(x) ; #endif ne-3.3.4/src/display.c000066400000000000000000001054161475116431000145340ustar00rootroot00000000000000/* Display handling functions with optional update delay Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2025 Todd M. Lewis and Sebastiano Vigna This file is part of ne, the nice editor. This library 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 library 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 . */ #include "ne.h" #include "support.h" #include "cm.h" #include "termchar.h" /* The functions in this file act as an interface between the main code and the raw screen updating functions of term.c. The basic idea is that one has a series of functions which normally just call the basic functions; however, if more than turbo (or lines*2, if turbo is zero) lines have been updated, the update stops and is delayed to the next call to refresh_window(). This function should be called whenever the screen has to be sync'd with its contents (for instance, whenever the user gets back in control). The mechanism allows for fast, responsive screen updates for short operations, and one-in-all updates for long operations. */ #define TURBO (turbo ? turbo : ne_lines * 2) /* If true, the current line has changed and care must be taken to update the initial state of the following lines. */ bool need_attr_update; /* If window_needs_refresh, the window has to be refreshed from scratch, starting from first_line and ending on last_line. Update calls keeps track of the number of lines updated. If this number becomes greater than turbo, and the turbo flag is set, we enter turbo mode. */ static bool window_needs_refresh; static int first_line, last_line, updated_lines; /* Prevents any other update from being actually done by setting updated_lines to a value greater than TURBO. It is most useful when the we know that a great deal of update is going to happen, most of which is useless (for instance, when cutting clips). */ void delay_update() { /* During tests, we never delay updates. */ #ifndef NE_TEST updated_lines = TURBO + 1; window_needs_refresh = true; #endif } /* Compares two highlight states for equality. */ int highlight_cmp(HIGHLIGHT_STATE *x, HIGHLIGHT_STATE *y) { return x->state == y->state && x->stack == y->stack && ! strcmp((const char *)x->saved_s, (const char *)y->saved_s); } /* Updates the initial syntax state of line descriptors starting from a given line descriptor. If row is nonnegative, we assume that we have also to update differentially the given lines. We assume that the line at the given line descriptor is correctly displayed, and proceed in updating the initial state of the following lines (and possibly their on-screen rendering). If b->syn or need_attr_update are false, this function does nothing. The state update (and the screen update, if requested) continues until we get to a line whose initial state concides with the final state of the previous line; in case you want to force more lines to be updated, you can provide a non-NULL end_ld. Note that, in any case, we update only visible lines (albeit initial states will be updated as necessary). This function uses the local attribute buffer: thus, after a call the local attribute buffer could be invalidated. */ void update_syntax_states(buffer *b, int row, line_desc *ld, line_desc *end_ld) { if (b->syn && need_attr_update) { bool got_end_ld = end_ld == NULL; bool invalidate_attr_buf = false; HIGHLIGHT_STATE next_line_state = b->attr_len < 0 ? parse(b->syn, ld, ld->highlight_state, b->encoding == ENC_UTF8) : b->next_state; assert(b->attr_len < 0 || b->attr_len == calc_char_len(ld, ld->line_len, b->encoding)); for(;;) { /* We move one row down. */ ld = (line_desc *) ld->ld_node.next; /* We update lines until next_line_state is equal to our current highlight_state, but we go until end_ld if it is not NULL. In any case, we bail out at the end of the file. */ if ((highlight_cmp(&ld->highlight_state, &next_line_state) && got_end_ld) || !ld->ld_node.next) break; if (row >= 0) { row++; if (row < ne_lines - 1) { if (++updated_lines > TURBO) window_needs_refresh = true; if (window_needs_refresh) { if (row < first_line) first_line = row; if (row > last_line) last_line = row; } else { /* If row is positive, the row is visible and we are not in TURBO mode, we need to update a line differentially. To do so, we store in b->attr_buf the attributes of the current line *starting with the current highlight_state*, which should reflect what's on screen. */ store_attributes(b, ld); invalidate_attr_buf = true; } } } /* This is where we go on parsing each line, updating highlight_state and next_line_state at each step. */ ld->highlight_state = next_line_state; next_line_state = parse(b->syn, ld, ld->highlight_state, b->encoding == ENC_UTF8); /* If we are in the visible range and window_needs_refresh is false, b->attr_buf contains the current on-screen attributes, whereas attr_buf contains the new attributes, so we can perform a differential update. */ if (row >= 0 && row < ne_lines - 1 && ! window_needs_refresh) output_line_desc(row, 0, ld, b->win_x, ne_columns, b->opt.tab_size, true, b->encoding == ENC_UTF8, attr_buf, b->attr_buf, b->attr_len); if (ld == end_ld) got_end_ld = true; } if (invalidate_attr_buf) b->attr_len = -1; need_attr_update = false; } } /* Outputs part of a line descriptor at the given screen row and column. The output will start at the first character of the line with a column position larger than or equal to from_col, and will continue until num_cols have been filled (partially overflowing characters will *not* be output). The TABs are expanded and considered in the computation of the column position. from_col and num_cols are not constrained by the length of the string (the string is really considered as terminating with an infinite sequence of spaces). cleared_at_end has the same meaning as in update_line(). If utf8 is true, then the line content is considered to be UTF-8 encoded. If attr is not NULL, it contains a the list of attributes for the line descriptor; if diff is not NULL, the update is differential: we assume that the line is already correctly displayed with the attributes specified in diff. If diff_size is shorter than the current line, all characters without differential information will be updated. */ void output_line_desc(const int row, const int col, const line_desc *ld, const int64_t from_col, const int64_t num_cols, const int tab_size, const bool cleared_at_end, const bool utf8, const uint32_t * const attr, const uint32_t * const diff, const int64_t diff_size) { assert(ld != NULL); assert(row < ne_lines - 1 && col < ne_columns); /* We scan the line descriptor, keeping track in curr_col of the current column (considering TABs) and in pos the current position in the line (considering UTF-8 sequences, if necessary). s is always ld->line + pos. The actual output screen column at any time is col + curr_col - from_col. */ const char *s = ld->line; int64_t curr_col = 0, pos = 0, attr_pos = 0; while(curr_col - from_col < num_cols && pos < ld->line_len) { const int64_t output_col = col + curr_col - from_col; const int c = utf8 ? get_char(s, ENC_UTF8) : *s; const int c_len = utf8 ? utf8seqlen(c) : 1; assert(c_len >= 1); if (*s == '\t') { const int tab_width = tab_size - curr_col % tab_size; for(int i = 0; i < tab_width; i++) if (curr_col + i >= from_col && curr_col + i < from_col + num_cols) { move_cursor(row, output_col + i); output_char(' ', attr ? attr[attr_pos] : 0, false); } curr_col += tab_width; } else { const int c_width = output_width(c); if (output_col >= col) { if (output_col + c_width <= ne_columns) { if (attr) { /* In the case of a differential update, we output only characters whose attributes have changed. */ if (!diff || diff && (attr_pos >= diff_size || diff[attr_pos] != attr[attr_pos])) { move_cursor(row, output_col); output_char(c, attr[attr_pos], utf8); } } else { move_cursor(row, output_col); output_char(c, 0, utf8); } } else { /* The current character is too wide: we can only output spaces in place of its visible part. */ move_cursor(row, output_col); output_spaces(ne_columns - output_col, attr ? &attr[attr_pos] : NULL); } } else if (output_col + c_width > col) { /* The character is only partially displayed. We can only output spaces. */ const int output_width = output_col + c_width - col; for(int i = 0; i < output_width; i++) { move_cursor(row, col + i); output_char(' ', attr ? attr[attr_pos] : 0, false); } } curr_col += c_width; } s += c_len; pos += c_len; attr_pos++; } /* If we have exhausted the characters in the line, we haven't still reached the final output column, and the line is not cleared at the end, since we must assume an infinite number of spaces at the end we must clear the line starting from the leftmost visible position. */ if (curr_col < from_col + num_cols && ! cleared_at_end) { move_cursor(row, col + (curr_col - from_col <= 0 ? 0 : curr_col - from_col)); clear_to_eol(); } } /* Updates part of a line given its number, its line descriptor and a starting column. It can handle lines after the end of the buffer (just pass the tail of the line list). It checks for updated_lines bypassing TURBO, in which case it simply updates first_line and last_line. Note that the starting column is ignored in case b->syn is not NULL. If cleared_at_end is true, this function assumes that it doesn't have to clean up the rest of the line if the string is not long enough to fill the line. IF b->attr_len is nonnegative, the line is updated differentially w.r.t. the content of b->attr_buf. The caller is thus responsible to guarantee that b->attr_buf contents reflect the currently displayed attributes. After a call to this function with argument b->cur_line_desc, b->attr_buf will be updated so to contain again the currently displayed attributes, unless no refresh has been performed, in which case b->attr_len will be set to -1. */ void update_line(buffer * const b, line_desc * const ld, const int row, const int64_t from_col, const bool cleared_at_end) { assert(ld); assert(row < ne_lines - 1); if (++updated_lines > TURBO) window_needs_refresh = true; if (window_needs_refresh) { if (row < first_line) first_line = row; if (row > last_line) last_line = row; b->attr_len = -1; return; } /* If the line descriptor is not valid, we just clear the line from the required position. This can happen while updating a region after the last line. */ if (! ld->ld_node.next) { if (! cleared_at_end) { move_cursor(row, from_col); clear_to_eol(); } return; } if (b->syn) { const bool differential = ld == b->cur_line_desc && b->attr_len >= 0; HIGHLIGHT_STATE next_state = parse(b->syn, ld, ld->highlight_state, b->encoding == ENC_UTF8); output_line_desc(row, 0, ld, b->win_x, ne_columns, b->opt.tab_size, cleared_at_end, b->encoding == ENC_UTF8, attr_buf, differential ? b->attr_buf : NULL, differential ? b->attr_len : 0); if (ld == b->cur_line_desc) { /* If we updated current line, we update the local attribute buffer. */ b->next_state = next_state; ensure_attr_buf(b, attr_len); // This test is necessary to avoid warnings from -fsanitize if (b->attr_len = attr_len) memcpy(b->attr_buf, attr_buf, attr_len * sizeof *b->attr_buf); } } else output_line_desc(row, from_col, ld, from_col + b->win_x, ne_columns - from_col, b->opt.tab_size, cleared_at_end, b->encoding == ENC_UTF8, NULL, NULL, 0); } /* Updates the text window between given lines. If doit is not true and the number of lines that have been updated bypasses TURBO, the update is not done. Rather, first_line, last_line and window_needs_refresh record that some refresh is needed, and from where it should be done. Setting doit to true forces a real update. Generally, doit should be false. */ void update_window_lines(buffer * const b, line_desc * ld, const int start_line, const int end_line, const bool doit) { if ((updated_lines += (end_line - start_line + 1)) > TURBO && !doit) window_needs_refresh = true; if (start_line < first_line) first_line = start_line; if (last_line < end_line) last_line = end_line; if (window_needs_refresh && ! doit) return; assert(first_line >= start_line); if (first_line != start_line) for(uint64_t i = first_line - start_line; i-- != 0; ) ld = (line_desc *)ld->ld_node.next; assert_line_desc(ld, b->encoding); int i; for(i = first_line; i <= last_line && i + b->win_y < b->num_lines; i++) { assert(ld->ld_node.next != NULL); if (b->syn) parse(b->syn, ld, ld->highlight_state, b->encoding == ENC_UTF8); output_line_desc(i, 0, ld, b->win_x, ne_columns, b->opt.tab_size, false, b->encoding == ENC_UTF8, b->syn ? attr_buf : NULL, NULL, 0); ld = (line_desc *)ld->ld_node.next; } for(; i <= last_line; i++) { move_cursor(i, 0); clear_to_eol(); } window_needs_refresh = false; first_line = ne_lines; last_line = -1; } /* Like update_window_lines(), but it updates the whole window, and never forces an update. */ void update_window(buffer * const b) { update_window_lines(b, b->top_line_desc, 0, ne_lines - 2, false); } /* Updates the current line, the following syntax states if necessary, and finally updates all following lines. All operations are preceded by a call to delay_update(). This is mainly written to fix the screen state after a block operation. */ void update_syntax_states_delay(buffer *b, line_desc *start_ld, line_desc *end_ld) { delay_update(); if (b->syn) { b->attr_len = -1; need_attr_update = true; update_syntax_states(b, -1, start_ld, end_ld); } } /* The following functions update a character on the screen. Three operations are supported---insertion, deletion, overwriting. The semantics is a bit involved. Essentially, they should be called *immediately* after the modification has been done. They assume that the video is perfectly up to date, and that only the given modification has been performed. Moreover, in case of syntax highlighting, the functions must update the local attribute buffer so that it reflects the current status of the line. In particular, no update of the rest of the line must be performed for syntactic reasons (it will be handled by the caller). Thus, for instance, update_inserted_char() assumes that ld->line[pos] contains the inserted character. The tough part is expanding/contracting the tabs following the modified position in such a way to maintain them consistent. Moreover, a lot of special cases are considered and optimized (such as writing a space at the end of a line). TURBO is taken into consideration. */ void update_deleted_char(buffer * const b, const int c, const int a, line_desc * const ld, int64_t pos, int64_t attr_pos, const int line, const int x) { if (++updated_lines > TURBO) window_needs_refresh = true; if (window_needs_refresh) { if (line < first_line) first_line = line; if (line > last_line) last_line = line; b->attr_len = -1; return; } if (b->syn) { assert(b->attr_len >= 0); assert(b->attr_len > attr_pos); memmove(b->attr_buf + attr_pos, b->attr_buf + attr_pos + 1, (--b->attr_len - attr_pos) * sizeof *b->attr_buf); } if (pos > ld->line_len || (pos == ld->line_len && ((c == '\t' || c == ' ') && !a))) return; move_cursor(line, x); const int c_width = c == '\t' ? b->opt.tab_size - x % b->opt.tab_size : output_width(c); if (!char_ins_del_ok) { update_line(b, ld, line, x, false); return; } /* Now we search for a visible TAB. If none is found, we just delete c_width characters and update the end of the line. Note that since the character has been already deleted, pos is the position of the character *after* the one just deleted. */ bool tab_found = false; for(int64_t i = x + c_width, j = pos, curr_attr_pos = attr_pos; i < ne_columns && j < ld->line_len; i += get_char_width(&ld->line[j], b->encoding), j = next_pos(ld->line, j, b->encoding), curr_attr_pos++) { if (ld->line[j] == '\t') { /* This is the previous width of the TAB we found. */ const int tab_width = b->opt.tab_size - i % b->opt.tab_size; if (c_width + tab_width > b->opt.tab_size) { /* In this case we cannot enlarge the TAB we found so to compensate for the deletion of c_width columns. Thus, we must delete c_width characters, but also reduce by b->opt.tab_size - c_width the TAB (plus update b->opt.tab_size characters at the end of the line). */ delete_chars(c_width); move_cursor(line, i - c_width); delete_chars(b->opt.tab_size - c_width); update_line(b, ld, line, ne_columns - b->opt.tab_size, true); } else { /* In this case, instead, we just shift the piece of text between our current position and the TAB. Note that this is slower than inserting and deleting, but MUCH nicer to see. */ output_chars(&ld->line[pos], b->syn ? &b->attr_buf[attr_pos] : NULL, j - pos, b->encoding == ENC_UTF8); output_spaces(c_width, b->syn ? &b->attr_buf[curr_attr_pos] : NULL); } tab_found = true; break; } } /* No TAB was found. We just delete the character and fill the end of the line. */ if (!tab_found) { delete_chars(c_width); update_line(b, ld, line, ne_columns - c_width, true); } } /* See comments for update_deleted_char(). */ void update_inserted_char(buffer * const b, const int c, line_desc * const ld, const int64_t pos, const int64_t attr_pos, const int line, const int x) { assert(pos < ld->line_len); if (++updated_lines > TURBO) window_needs_refresh = true; if (window_needs_refresh) { if (line < first_line) first_line = line; if (line > last_line) last_line = line; b->attr_len = -1; return; } const uint32_t * const attr = b->syn ? &attr_buf[attr_pos] : NULL; if (b->syn) { assert(b->attr_len >= 0); /*fprintf(stderr, "+b->attr_len: %d calc_char_len: %d pos: %d ld->line_len %d attr_pos: %d\n", b->attr_len, calc_char_len(ld, ld->line_len, b->encoding), pos, ld->line_len, attr_pos);*/ assert(b->attr_len + 1 == calc_char_len(ld, ld->line_len, b->encoding)); /* We update the stored attribute vector. */ ensure_attr_buf(b, b->attr_len + 1); memmove(b->attr_buf + attr_pos + 1, b->attr_buf + attr_pos, (b->attr_len++ - attr_pos) * sizeof *b->attr_buf ); b->attr_buf[attr_pos] = *attr; } move_cursor(line, x); const int c_len = b->encoding == ENC_UTF8 ? utf8seqlen(c) : 1; const int c_width = c == '\t' ? b->opt.tab_size - x % b->opt.tab_size : output_width(c); if (pos + c_len == ld->line_len) { /* We are the last character on the line. We simply output ourselves. */ if (c != '\t') output_char(c, attr ? *attr : -1, b->encoding == ENC_UTF8); else output_spaces(c_width, attr); return; } if (!char_ins_del_ok) { update_line(b, ld, line, x, false); return; } /* We search for the first TAB on the line. If there is none, we have just to insert our characters. */ for(int64_t i = x + c_width, j = pos + c_len; i < ne_columns && j < ld->line_len; i += get_char_width(&ld->line[j], b->encoding), j = next_pos(ld->line, j, b->encoding)) { if (ld->line[j] == '\t') { const int tab_width = b->opt.tab_size - (i - c_width) % b->opt.tab_size; if (tab_width > c_width) { if (c == '\t') output_spaces(c_width, attr); else output_char(c, attr ? *attr : -1, b->encoding == ENC_UTF8); output_chars(&ld->line[pos + c_len], attr, j - (pos + c_len), b->encoding == ENC_UTF8); } else { if (c == '\t') insert_chars(NULL, attr, c_width, false); else insert_char(c, attr ? *attr : -1, b->encoding == ENC_UTF8); move_cursor(line, i); insert_chars(NULL, attr, b->opt.tab_size - c_width, false); } return; } } if (c == '\t') insert_chars(NULL, attr, c_width, false); else insert_char(c, attr ? *attr : -1, b->encoding == ENC_UTF8); } /* See comments for update_deleted_char(). */ void update_overwritten_char(buffer * const b, const int old_char, const int new_char, line_desc * const ld, int64_t pos, const int64_t attr_pos, const int line, const int x) { assert(ld != NULL); assert(pos < ld->line_len); if (++updated_lines > TURBO) window_needs_refresh = true; if (window_needs_refresh) { if (line < first_line) first_line = line; if (line > last_line) last_line = line; b->attr_len = -1; return; } const uint32_t * const attr = b->syn ? &attr_buf[attr_pos] : NULL; if (b->syn) { /* fprintf(stderr, "-b->attr_len: %d calc_char_len: %d pos: %d ld->line_len %d attr_pos: %d\n", b->attr_len, calc_char_len(ld, ld->line_len, b->encoding), pos, ld->line_len, attr_pos);*/ assert(b->attr_len + 1 == calc_char_len(ld, ld->line_len, b->encoding) || b->attr_len == calc_char_len(ld, ld->line_len, b->encoding)); assert(attr_pos <= b->attr_len); if (attr_pos == b->attr_len) ensure_attr_buf(b, ++b->attr_len); b->attr_buf[attr_pos] = *attr; } const int old_width = old_char == '\t' ? b->opt.tab_size - x % b->opt.tab_size : output_width(old_char); const int new_width = new_char == '\t' ? b->opt.tab_size - x % b->opt.tab_size : output_width(new_char); move_cursor(line, x); if (old_width == new_width) { /* The character did not change its width (the easy case). */ if (old_char != new_char) { if (new_char == '\t') output_spaces(old_width, attr); else output_char(new_char, attr ? *attr : -1, b->encoding == ENC_UTF8); } return; } if (!char_ins_del_ok) { update_line(b, ld, line, x, false); return; } if (new_width < old_width) { /* The character has been shrunk by width_delta. */ const int width_delta = old_width - new_width; /* We search for the first TAB on the line. If there is none, we have just to delete width_delta characters, and update the last width_delta characters on the screen. */ pos = next_pos(ld->line, pos, b->encoding); for(int64_t i = x + old_width, j = pos, curr_attr_pos = attr_pos; i < ne_columns && j < ld->line_len; i += get_char_width(&ld->line[j], b->encoding), j = next_pos(ld->line, j, b->encoding), curr_attr_pos++) { if (ld->line[j] == '\t') { const int tab_width = b->opt.tab_size - i % b->opt.tab_size; /* We found a TAB. Previously, this TAB was tab_width character wide. If width_delta + tab_width does not exceed the width of a TAB, we just add width_delta characters to the expansion of the current TAB. Otherwise, we first delete width_delta characters. Then, if width_delta was not a full TAB we delete the remaining characters from the TAB we found. */ if (width_delta + tab_width <= b->opt.tab_size) { if (new_char == '\t') output_spaces(new_width, attr); else output_char(new_char, attr ? *attr : -1, b->encoding == ENC_UTF8); output_chars(&ld->line[pos], attr, j - pos, b->encoding == ENC_UTF8); output_spaces(width_delta, b->syn ? &b->attr_buf[curr_attr_pos] : NULL); } else { if (new_char == '\t') output_spaces(new_width, attr); else output_char(new_char, attr ? *attr : -1, b->encoding == ENC_UTF8); delete_chars(width_delta); if (width_delta != b->opt.tab_size) { move_cursor(line, i - width_delta); delete_chars(b->opt.tab_size - width_delta); } update_line(b, ld, line, ne_columns - b->opt.tab_size, true); } return; } } delete_chars(width_delta); if (new_char == '\t') output_spaces(new_width, attr); else output_char(new_char, attr ? *attr : -1, b->encoding == ENC_UTF8); update_line(b, ld, line, ne_columns - width_delta, true); } else { /* The character has been enlarged by width_delta. */ const int width_delta = new_width - old_width; /* We search for the first TAB on the line. If there is none, we have just to insert width_delta characters. */ pos = next_pos(ld->line, pos, b->encoding); for(int64_t i = x + old_width, j = pos; i < ne_columns && j < ld->line_len; i += get_char_width(&ld->line[j], b->encoding), j = next_pos(ld->line, j, b->encoding)) { if (ld->line[j] == '\t') { const int tab_width = b->opt.tab_size - i % b->opt.tab_size; /* We found a TAB. Previously, this TAB was tab_width character wide. If width_delta is smaller than tab_width, the enlargement can be absorbed by the TAB we found: we just print the new text between the original position and i. Otherwise, we insert width_delta spaces, and then update the width of the TAB we found. */ if (width_delta < tab_width) { if (new_char == '\t') output_spaces(new_width, attr); else output_char(new_char, attr ? *attr : -1, b->encoding == ENC_UTF8); output_chars(&ld->line[pos], attr, j - pos, b->encoding == ENC_UTF8); } else { insert_chars(NULL, attr, width_delta, false); if (new_char == '\t') output_spaces(new_width, attr); else output_char(new_char, attr ? *attr : -1, b->encoding == ENC_UTF8); move_cursor(line, i + width_delta); insert_chars(NULL, attr, b->opt.tab_size - (i + width_delta) % b->opt.tab_size - tab_width, false); } return; } } insert_chars(NULL, attr, width_delta, false); if (new_char == '\t') output_spaces(new_width, attr); else output_char(new_char, attr ? *attr : -1, b->encoding == ENC_UTF8); } } /* Resets the terminal status, updating the whole window and resetting the status bar. It *never* does any real update; it is just used to mark that the window and the status bar have to be completely rebuilt. */ void reset_window(void) { window_needs_refresh = true; first_line = 0; last_line = ne_lines - 2; reset_status_bar(); } /* Forces the screen update. It should be called whenever the user has to interact, so that he is presented with a correctly updated display. */ void refresh_window(buffer * const b) { if (window_needs_refresh) { line_desc *ld = b->top_line_desc; for(int i = first_line; i-- != 0 && (line_desc *)ld->ld_node.next;) ld = (line_desc *)ld->ld_node.next; if (ld->ld_node.next) update_window_lines(b, ld, first_line, last_line, true); highlight_mark(b, true); updated_lines = 0; } } /* Scrolls a region starting at a given line upward (n == -1) or downward (n == 1). TURBO is checked. */ void scroll_window(buffer * const b, line_desc * const ld, const int line, const int n) { assert(n == -1 || n == 1); assert(line >= 0); assert(line < ne_lines); if (line_ins_del_ok) { if (updated_lines++ > TURBO || window_needs_refresh) { window_needs_refresh = true; if (first_line > line) first_line = line; last_line = ne_lines - 2; return; } } else { /* Argh! We can't insert or delete lines. The only chance is rewriting the last lines of the screen. */ update_window_lines(b, ld, line, ne_lines - 2, false); return; } if (n > 0) update_line(b, ld, line, 0, ins_del_lines(line, 1)); else { line_desc * last_line_ld = b->top_line_desc; for(int i = ne_lines - 2; i-- != 0 && last_line_ld->ld_node.next;) last_line_ld = (line_desc *)last_line_ld->ld_node.next; update_line(b, last_line_ld, ne_lines - 2, 0, ins_del_lines(line, -1)); } } /* If syntax is enabled, and b->attr_len < 0, computes the attributes of the current line descriptor, stores them in b->attr_buf, and stores in b->next_state the return value of parse(). */ void ensure_attributes(buffer *b) { if (! b->syn || b->attr_len >= 0) return; store_attributes(b, b->cur_line_desc); } /* Computes the attributes of the given line, stores them into b->attr_buf and stores in b->next_state the return value of parse(). Note that if you call this function on a line different from the current line, you must take care of invalidating the attribute buffer afterwards (b->attr_len = -1). */ void store_attributes(buffer *b, line_desc *ld) { b->next_state = parse(b->syn, ld, ld->highlight_state, b->encoding == ENC_UTF8); assert(calc_char_len(ld, ld->line_len, b->encoding) == attr_len); // This test is necessary to avoid warnings from -fsanitize ensure_attr_buf(b, attr_len); if (b->attr_len = attr_len) memcpy(b->attr_buf, attr_buf, attr_len * sizeof *b->attr_buf); } static uint32_t invert_boldness(uint32_t orig_attr) { uint32_t tmp_attr = orig_attr; switch (orig_attr & BG_MASK) { case BG_BLACK: tmp_attr = (tmp_attr & ~BG_MASK ) | BG_BBLACK; break; case BG_RED: tmp_attr = (tmp_attr & ~BG_MASK ) | BG_BRED; break; case BG_GREEN: tmp_attr = (tmp_attr & ~BG_MASK ) | BG_BGREEN; break; case BG_YELLOW: tmp_attr = (tmp_attr & ~BG_MASK ) | BG_BYELLOW; break; case BG_BLUE: tmp_attr = (tmp_attr & ~BG_MASK ) | BG_BBLUE; break; case BG_MAGENTA: tmp_attr = (tmp_attr & ~BG_MASK ) | BG_BMAGENTA; break; case BG_CYAN: tmp_attr = (tmp_attr & ~BG_MASK ) | BG_BCYAN; break; case BG_WHITE: tmp_attr = (tmp_attr & ~BG_MASK ) | BG_BWHITE; break; case BG_BBLACK: tmp_attr = (tmp_attr & ~BG_MASK ) | BG_BLACK; break; case BG_BRED: tmp_attr = (tmp_attr & ~BG_MASK ) | BG_RED; break; case BG_BGREEN: tmp_attr = (tmp_attr & ~BG_MASK ) | BG_GREEN; break; case BG_BYELLOW: tmp_attr = (tmp_attr & ~BG_MASK ) | BG_YELLOW; break; case BG_BBLUE: tmp_attr = (tmp_attr & ~BG_MASK ) | BG_BLUE; break; case BG_BMAGENTA: tmp_attr = (tmp_attr & ~BG_MASK ) | BG_MAGENTA; break; case BG_BCYAN: tmp_attr = (tmp_attr & ~BG_MASK ) | BG_CYAN; break; case BG_BWHITE: tmp_attr = (tmp_attr & ~BG_MASK ) | BG_WHITE; break; default: tmp_attr = (tmp_attr & ~BG_MASK ) | BG_BWHITE; break; } switch (orig_attr & FG_MASK) { case FG_BLACK: tmp_attr = (tmp_attr & ~FG_MASK) | FG_BBLACK; break; case FG_RED: tmp_attr = (tmp_attr & ~FG_MASK) | FG_BRED; break; case FG_GREEN: tmp_attr = (tmp_attr & ~FG_MASK) | FG_BGREEN; break; case FG_YELLOW: tmp_attr = (tmp_attr & ~FG_MASK) | FG_BYELLOW; break; case FG_BLUE: tmp_attr = (tmp_attr & ~FG_MASK) | FG_BBLUE; break; case FG_MAGENTA: tmp_attr = (tmp_attr & ~FG_MASK) | FG_BMAGENTA; break; case FG_CYAN: tmp_attr = (tmp_attr & ~FG_MASK) | FG_BCYAN; break; case FG_WHITE: tmp_attr = (tmp_attr & ~FG_MASK) | FG_BWHITE; break; case FG_BBLACK: tmp_attr = (tmp_attr & ~FG_MASK) | FG_BLACK; break; case FG_BRED: tmp_attr = (tmp_attr & ~FG_MASK) | FG_RED; break; case FG_BGREEN: tmp_attr = (tmp_attr & ~FG_MASK) | FG_GREEN; break; case FG_BYELLOW: tmp_attr = (tmp_attr & ~FG_MASK) | FG_YELLOW; break; case FG_BBLUE: tmp_attr = (tmp_attr & ~FG_MASK) | FG_BLUE; break; case FG_BMAGENTA: tmp_attr = (tmp_attr & ~FG_MASK) | FG_MAGENTA; break; case FG_BCYAN: tmp_attr = (tmp_attr & ~FG_MASK) | FG_CYAN; break; case FG_BWHITE: tmp_attr = (tmp_attr & ~FG_MASK) | FG_WHITE; break; default: tmp_attr = (tmp_attr & ~FG_MASK) | FG_BBLACK; break; } return tmp_attr; } /* (Un)highlights (depending on the value of show) the bracket matching the one under the cursor (if any). */ void automatch_bracket(buffer * const b, const bool show) { static int c; static uint32_t orig_attr; if (show) { int64_t match_pos, match_line; uint32_t tmp_attr; line_desc *matching_ld; if (find_matching_bracket(b, b->win_y, b->win_y + ne_lines - 2 >= b->num_lines - 1 ? b->num_lines - 1 : b->win_y + ne_lines - 2, &match_line, &match_pos, &c, &matching_ld) == OK) { /* We limited find_matching_bracket()'s search to the visible lines, but not the visible portions of those lines. Now ensure the matching pos is within the visible window. */ b->automatch.y = match_line - b->win_y; b->automatch.x = calc_width(matching_ld, match_pos, b->opt.tab_size, b->encoding) - b->win_x; if (b->automatch.x >= 0 && b->automatch.x < ne_columns ) { if (b->syn) { parse(b->syn, matching_ld, matching_ld->highlight_state, b->encoding == ENC_UTF8); orig_attr = attr_buf[calc_char_len(matching_ld, match_pos, b->encoding == ENC_UTF8)]; } else orig_attr = -1; tmp_attr = orig_attr; if (b->opt.automatch & 1 ) /* invert boldness of FG, BG */ tmp_attr = invert_boldness(tmp_attr); if (b->opt.automatch & 2 ) tmp_attr = tmp_attr ^ INVERSE; if (b->opt.automatch & 4 ) tmp_attr = tmp_attr ^ BOLD; if (b->opt.automatch & 8 ) tmp_attr = tmp_attr ^ UNDERLINE; move_cursor(b->automatch.y, b->automatch.x); output_char(c, tmp_attr, b->encoding == ENC_UTF8); b->automatch.shown = true; } } } else { if (b->automatch.shown) { if (b->automatch.x >= 0 && b->automatch.x < ne_columns && b->automatch.y >= 0 && b->automatch.y < ne_lines - 1) { move_cursor(b->automatch.y, b->automatch.x); output_char(c, orig_attr, b->encoding == ENC_UTF8); } b->automatch.shown = false; } } } void highlight_mark(buffer * const b, const bool show) { static int c; static uint32_t orig_attr; if (show && !fast_gui) { uint32_t tmp_attr; if (b->marking && b->opt.automatch) { b->visible_mark.y = b->block_start_line - b->win_y; if (b->visible_mark.y >= 0 && b->visible_mark.y < ne_lines - 1) { line_desc *ld = nth_line_desc(b, b->block_start_line); b->visible_mark.x = calc_width(ld, b->block_start_pos, b->opt.tab_size, b->encoding) - b->win_x; if (b->visible_mark.x >= 0 && b->visible_mark.x < ne_columns) { move_cursor(b->visible_mark.y, b->visible_mark.x); if (b->syn && b->block_start_pos < ld->line_len) { parse(b->syn, ld, ld->highlight_state, b->encoding == ENC_UTF8); orig_attr = attr_buf[b->block_start_pos]; } else orig_attr = 0; if (b->block_start_pos < ld->line_len && ld->line[b->block_start_pos] != '\t') { c = get_char( &ld->line[b->block_start_pos], b->encoding); } else c = ' '; tmp_attr = orig_attr; unsigned int mark_style = (b->opt.automatch == 0 || b->opt.automatch == 0x0f ) ? 1+2 : b->opt.automatch ^ 0x07; if (mark_style & 1 ) /* invert boldness of FG, BG */ tmp_attr = invert_boldness(tmp_attr); if (mark_style & 2 ) tmp_attr = tmp_attr ^ INVERSE; if (mark_style & 4 ) tmp_attr = tmp_attr ^ BOLD; if (mark_style & 8 ) tmp_attr = tmp_attr ^ UNDERLINE; output_char(c, tmp_attr, b->encoding == ENC_UTF8); b->visible_mark.shown = true; } } } } else { if (b->visible_mark.shown) { if (b->visible_mark.x >= 0 && b->visible_mark.x < ne_columns && b->visible_mark.y >= 0 && b->visible_mark.y < ne_lines - 1) { move_cursor(b->visible_mark.y, b->visible_mark.x); output_char(c, orig_attr, b->encoding == ENC_UTF8); } b->visible_mark.shown = false; } } } ne-3.3.4/src/edit.c000066400000000000000000000617601475116431000140170ustar00rootroot00000000000000/* Various editing functions such as word wrap, to upper, etc. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2025 Todd M. Lewis and Sebastiano Vigna This file is part of ne, the nice editor. This library 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 library 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 . */ #include "ne.h" #include "support.h" /* The number of type of brackets we recognize. */ #define NUM_BRACKETS 5 /* Applies a given to_first() function to the first letter of the text starting at the cursor, and to_rest() to the following alphabetical letters (see the functions below). */ static int to_something(buffer *b, int (to_first)(int), int (to_rest)(int)) { assert_buffer(b); /* If we are after the end of the line, just return ERROR. */ if (b->cur_line == b->num_lines -1 && b->cur_pos >= b->cur_line_desc->line_len) return ERROR; int64_t pos = b->cur_pos; int c; /* First of all, we search for the word start, if we're not over it. */ if (pos >= b->cur_line_desc->line_len || !ne_isword(c = get_char(&b->cur_line_desc->line[pos], b->encoding), b->encoding)) if (search_word(b, 1, true) != OK) return ERROR; bool changed = false; int64_t new_len = 0; pos = b->cur_pos; const int64_t cur_char = b->cur_char; const int cur_x = b->cur_x; /* Then, we compute the word position extremes, length of the result (which may change because of casing). */ while (pos < b->cur_line_desc->line_len && ne_isword(c = get_char(&b->cur_line_desc->line[pos], b->encoding), b->encoding)) { const int new_c = new_len ? to_rest(c) : to_first(c); changed |= (c != new_c); if (b->encoding == ENC_UTF8) new_len += utf8seqlen(new_c); else new_len++; pos = next_pos(b->cur_line_desc->line, pos, b->encoding); } const int64_t len = pos - b->cur_pos; if (!len) { char_right(b); return OK; } if (changed) { /* We actually perform changes only if some character was case folded. */ char * word = malloc(new_len * sizeof *word); if (!word) return OUT_OF_MEMORY; pos = b->cur_pos; new_len = 0; /* Second pass: we actually build the transformed word. */ while (pos < b->cur_line_desc->line_len && ne_isword(c = get_char(&b->cur_line_desc->line[pos], b->encoding), b->encoding)) { if (b->encoding == ENC_UTF8) new_len += utf8str(new_len ? to_rest(c) : to_first(c), word + new_len); else { word[new_len] = new_len ? to_rest(c) : to_first(c); new_len++; } pos = next_pos(b->cur_line_desc->line, pos, b->encoding); } start_undo_chain(b); delete_stream(b, b->cur_line_desc, b->cur_line, b->cur_pos, len); if (new_len) insert_stream(b, b->cur_line_desc, b->cur_line, b->cur_pos, word, new_len); free(word); end_undo_chain(b); if (cur_char < b->attr_len) b->attr_len = cur_char; update_line(b, b->cur_line_desc, b->cur_y, cur_x, false); need_attr_update = true; } return search_word(b, 1, true); } /* These functions upper case, lower case or capitalize the word the cursor is on. They just call to_something(). Note the parentheses around the function names, which inhibit the possible macros. */ int to_upper(buffer *b) { return b->encoding == ENC_UTF8 ? to_something(b, (utf8toupper), (utf8toupper)) : to_something(b, (toupper), (toupper)); } int to_lower(buffer *b) { return b->encoding == ENC_UTF8 ? to_something(b, (utf8tolower), (utf8tolower)) : to_something(b, (tolower), (tolower)); } int capitalize(buffer *b) { return b->encoding == ENC_UTF8 ? to_something(b, (utf8toupper), (utf8tolower)) : to_something(b, (toupper), (tolower)); } /* Finds which bracket matches the bracket under the cursor, and moves it there. Various error codes can be returned. */ int match_bracket(buffer *b) { int64_t match_line, match_pos; const int rc = find_matching_bracket(b, 0, b->num_lines-1, &match_line, &match_pos, NULL, NULL); if (rc == OK) { goto_line_pos(b, match_line, match_pos); return OK; } return rc; } int find_matching_bracket(buffer *b, const int64_t min_line, int64_t max_line, int64_t *match_line, int64_t *match_pos, int *c, line_desc ** match_ld) { static unsigned char bracket_table[NUM_BRACKETS][2] = { { '(', ')' }, { '[', ']' }, { '{', '}' }, { '<', '>' }, { '`', '\'' } }; line_desc *ld = b->cur_line_desc; if (b->cur_pos >= ld->line_len) return NOT_ON_A_BRACKET; int i, j, dir; for(i = 0; i < NUM_BRACKETS; i++) { for(j = 0; j < 2; j++) if (ld->line[b->cur_pos] == bracket_table[i][j]) break; if (j < 2) break; } if (i == NUM_BRACKETS && j == 2) return NOT_ON_A_BRACKET; if (j) dir = -1; else dir = 1; int n = 0; int64_t pos = b->cur_pos, y = b->cur_line; while(ld->ld_node.next && ld->ld_node.prev && y >= min_line && y <= max_line) { if (pos >= 0) { char * const line = ld->line; while(pos >= 0 && pos < ld->line_len) { if (line[pos] == bracket_table[i][j]) n++; else if (line[pos] == bracket_table[i][1 - j]) n--; if (n == 0) { *match_line = y; *match_pos = pos; if (c) *c = line[pos]; if (match_ld) *match_ld = ld; return OK; } if (dir > 0) pos = next_pos(line, pos, b->encoding); else pos = prev_pos(line, pos, b->encoding); } } pos = -1; if (dir == 1) { ld = (line_desc *)ld->ld_node.next; if (ld->ld_node.next && ld->line) pos = 0; y++; } else { ld = (line_desc *)ld->ld_node.prev; if (ld->ld_node.prev && ld->line) pos = ld->line_len - 1; y--; } } return CANT_FIND_BRACKET; } /* Breaks a line at the first possible position before the current cursor position (i.e., at a tab or at a space). The space is deleted, and a new line is inserted. The cursor is repositioned coherently. The number of bytes existing on the new line is returned, or ERROR if no word wrap was possible. */ int64_t word_wrap(buffer * const b) { const int64_t len = b->cur_line_desc->line_len; char * const line = b->cur_line_desc->line; int64_t pos, first_pos; if (!(pos = b->cur_pos)) return ERROR; /* Find the first possible position we could break a line on. */ first_pos = 0; /* Skip leading white space */ while (first_pos < len && ne_isspace(get_char(&line[first_pos], b->encoding), b->encoding)) first_pos = next_pos(line, first_pos, b->encoding); /* Skip non_space after leading white space */ while (first_pos < len && !ne_isspace(get_char(&line[first_pos], b->encoding), b->encoding)) first_pos = next_pos(line, first_pos, b->encoding); /* Now we know that the line shouldn't be broken before &line[first_pos]. */ /* Search left from the current position to find a candidate space to break the line on.*/ while((pos = prev_pos(line, pos, b->encoding)) && !ne_isspace(get_char(&line[pos], b->encoding), b->encoding)); if (! pos || pos < first_pos) return ERROR; start_undo_chain(b); const int64_t result = b->cur_pos - pos - 1; if (pos < b->cur_pos) b->cur_pos = -1; delete_one_char(b, b->cur_line_desc, b->cur_line, pos); insert_one_line(b, b->cur_line_desc, b->cur_line, pos); end_undo_chain(b); return result; } /* This experimental alternative to word wrapping sets a bookmark, calls paragraph(), then returns to the bookmark (which may have moved due to insertions/deletions). The number of characters existing on the new line is returned, or ERROR if no word wrap was possible. */ int word_wrap2(buffer * const b) { static char avcmd[16]; if (b->cur_pos > b->cur_line_desc->line_len) return OK; bool non_blank_added = false; int avshift; char * line = b->cur_line_desc->line; int64_t pos, original_line; /* If the char to our left is a space, we need to insert a non-space to attach our WORDWRAP_BOOKMARK to because spaces at the split point get removed, which effectively leaves our bookmark on the current line. */ delay_update(); pos = prev_pos(line, b->cur_pos, b->encoding); if (pos >= 0 && (non_blank_added = ne_isspace(get_char(&line[pos], b->encoding), b->encoding))) { start_undo_chain(b); insert_one_char(b, b->cur_line_desc, b->cur_line, b->cur_pos, 'X'); line = b->cur_line_desc->line; goto_pos(b, next_pos(line, b->cur_pos, b->encoding)); } b->bookmark[WORDWRAP_BOOKMARK].pos = b->cur_pos; b->bookmark[WORDWRAP_BOOKMARK].line = original_line = b->cur_line; b->bookmark[WORDWRAP_BOOKMARK].cur_y = b->cur_y; b->bookmark_mask |= (1 << WORDWRAP_BOOKMARK); paragraph(b, false); goto_line_pos(b, b->bookmark[WORDWRAP_BOOKMARK].line, b->bookmark[WORDWRAP_BOOKMARK].pos); line = b->cur_line_desc->line; b->bookmark[WORDWRAP_BOOKMARK].cur_y += b->bookmark[WORDWRAP_BOOKMARK].line - original_line; if (avshift = b->cur_y - b->bookmark[WORDWRAP_BOOKMARK].cur_y) { snprintf(avcmd, 16, "%c%d", avshift > 0 ? 'T' :'B', avshift > 0 ? avshift : -avshift); adjust_view(b, avcmd); } b->bookmark_mask &= ~(1 << WORDWRAP_BOOKMARK); if (non_blank_added) { goto_pos(b, prev_pos(b->cur_line_desc->line, b->cur_pos, b->encoding)); delete_one_char(b, b->cur_line_desc, b->cur_line, b->cur_pos); end_undo_chain(b); } return stop ? STOPPED : OK; } /* These functions reformat a paragraph while preserving appropriate leading US-ASCII white space. */ static char *pa_space; /* Where we keep space for paragraph left offsets */ static int64_t pa_space_len; /* How long pa_space is when tabs are expanded */ static int64_t pa_space_pos; /* How long pa_space is without expanding tabs */ /* save_space() sets pa_space, pa_space_len, and pa_space_pos to reflect the space on the left end of the line ld refers to in the context of the given tab size. If the line contains only space then it is treated identically to an empty line, in which case save_space() returns 0 and pa_space, pa_space_len, and pa_space_pos are cleared. Otherwise it returns 1. The string pa_space points to is not null-terminated, so be careful how you use it. */ static int save_space(line_desc * const ld, const int tab_size, const encoding_type encoding) { if (pa_space) free(pa_space); pa_space = NULL; pa_space_len = 0; pa_space_pos = 0; if (!ld->line) return 0; /* No data on this line. */ int64_t pos = 0; while(pos < ld->line_len && isasciispace(ld->line[pos])) pos = next_pos(ld->line, pos, encoding); if (pos == ld->line_len) return 0; /* Blank lines don't count. */ pa_space_pos = pos; pa_space_len = calc_width(ld, pos, tab_size, encoding); if (pos == 0) { return 1; } if ((pa_space = malloc(pos))) { memcpy(pa_space, ld->line, pos); return 1; } return 0; } /* trim_trailing_space() removes spaces from the end of the line referred to by the line_desc ld. The int line is necessary for undo to work. */ static void trim_trailing_space(buffer * const b, line_desc *ld, const int64_t line, const encoding_type encoding) { if (!ld->line) return; int64_t pos = ld->line_len; while (pos > 0 && isasciispace(ld->line[pos - 1])) pos = prev_pos(ld->line, pos, encoding); if (pos >= 0 && pos < ld->line_len) delete_stream(b, ld, line, pos, ld->line_len - pos); } /* is_part_of_paragraph() determines if the line ld refers to could be considered part of a paragraph based on its leading spaces compared to pa_space_len. If they are the same, is_part_of_paragraph() returns 1, and *first_non_blank is set to the position of the first non-blank character on the line. Otherwise, *first_non_blank is -1 and is_part_of_paragraph() returns 0. */ static int is_part_of_paragraph(const line_desc * const ld, const int tab_size, int64_t * const first_non_blank, const encoding_type encoding) { *first_non_blank = -1; if (!ld->line) return 0; int64_t pos = 0; while (pos < ld->line_len && isasciispace(ld->line[pos])) pos = next_pos(ld->line, pos, encoding); if (pos < ld->line_len && calc_width(ld, pos, tab_size, encoding) == pa_space_len) { *first_non_blank = pos; return 1; } return 0; } /* paragraph() reformats a paragraph following the current parameters for right_margin (a value of 0 forces the use of the full screen width). On completion the cursor is positioned either: * on the first non-blank character after the paragraph if there is one, or * on a blank line following the paragraph if there is one, or * on the last line of the paragraph. paragraph() returns OK unless the cursor ends up on the last line of the file, in which case it returns ERROR. It may return STOPPED if the user interrupts a long-running operation. mark_for_undo indicates whether to do an otherwise useless insertion and deletion of a single character to ensure the cursor ends up at its initial position after an undo. If performing a series of paragraph() calls within an undo chain, it's only useful to do this on the first call. */ int paragraph(buffer * const b, const bool mark_for_undo) { line_desc *ld = b->cur_line_desc, *start_line_desc = ld; if (!ld->line) return line_down(b); /* Establish appropriate leading space. This will be taken from the line following the current line if it is non-blank. Otherwise it will be taken from the current line. Save a copy of it for later as space[]. **/ if ( !( (ld->ld_node.next->next && save_space((line_desc *)ld->ld_node.next, b->opt.tab_size, b->encoding)) || save_space(ld, b->opt.tab_size, b->encoding) ) ) return line_down(b); int64_t pos = b->cur_pos; b->cur_pos = -1; int64_t line = b->cur_line; if (mark_for_undo) { /* This insertion and deletion of a single character ensures that the cursor ends up here after an undo. */ insert_one_char(b, ld, line, 0, ' '); delete_stream(b, ld, line, 0, 1); } const int right_margin = b->opt.right_margin ? b->opt.right_margin : ne_columns; bool done; do { done = true; /* set this to false if we do any work in the loop. */ trim_trailing_space(b, ld, line, b->encoding); /* Suck up subsequent lines until this one is long enough to need splitting */ while ((calc_width(ld, ld->line_len, b->opt.tab_size, b->encoding) <= right_margin) && ld->ld_node.next->next && is_part_of_paragraph((line_desc *)ld->ld_node.next, b->opt.tab_size, &pos, b->encoding)) { line_desc *ld_next = (line_desc *)ld->ld_node.next; insert_one_char(b, ld, line, ld->line_len, ' '); if (pos) delete_stream(b, ld_next, line + 1, 0, pos); /* pos was set by is_part_of_paragraph() above. */ delete_stream(b, ld, line, ld->line_len, 1); /* joins next line to this one */ trim_trailing_space(b, ld, line, b->encoding); done = false; } if (calc_width(ld, ld->line_len, b->opt.tab_size, b->encoding) > right_margin) { int64_t spaces; int64_t split_pos; /* Skip past leading spaces... */ pos = 0; while(pos < ld->line_len && isasciispace(ld->line[pos])) pos = next_pos(ld->line, pos, b->encoding); /* Find the split point */ split_pos = spaces = 0; while (pos < ld->line_len && (calc_width(ld, pos, b->opt.tab_size, b->encoding) < right_margin || ! split_pos)) { if (isasciispace(ld->line[pos])) { split_pos = pos; spaces = 0; while (pos < ld->line_len && isasciispace(ld->line[pos])) { pos = next_pos(ld->line, pos, b->encoding); spaces++; } } else pos = next_pos(ld->line, pos, b->encoding); } if (split_pos) { done = false; /* Remove any space at the split point. */ if (spaces) delete_stream(b, ld, line, split_pos, spaces); /* Split the line at the split point. (We are done with this line) */ insert_one_line(b, ld, line, split_pos); /* Make the new next line the current line **/ if (ld->ld_node.next->next) { ld = (line_desc *)ld->ld_node.next; line++; if (pa_space && pa_space_len && pa_space_pos) insert_stream(b, ld, line, 0, pa_space, pa_space_pos); trim_trailing_space(b, ld, line, b->encoding); } } else { /* Line not split; is there a next one in the paragraph? */ if ( ld->ld_node.next->next && is_part_of_paragraph((line_desc *)ld->ld_node.next, b->opt.tab_size, &pos, b->encoding) ) { ld = (line_desc *)ld->ld_node.next; line++; done = false; } } } } while (!stop && !done); if (pa_space) { free(pa_space); pa_space = NULL; } if (b->syn) { b->attr_len = -1; need_attr_update = true; update_syntax_states(b, -1, start_line_desc, (line_desc *)ld->ld_node.next); } update_window_lines(b, b->cur_line_desc, b->cur_y, ne_lines - 2, false); goto_line_pos(b, line, ld->line_len); if (stop || line_down(b) == ERROR) return stop ? STOPPED : ERROR; /* Try to find the first non-blank starting with this line. */ ld = b->cur_line_desc; line = b->cur_line; do { pos = 0; if (ld->line) { for (; pos < ld->line_len; pos = next_pos(ld->line, pos, b->encoding)) { if (!isasciispace(ld->line[pos])) { goto_line_pos(b, line, pos); return ld->ld_node.next ? OK : ERROR; } } } ld = (line_desc *)ld->ld_node.next; line++; } while (ld->ld_node.next); goto_line_pos(b, line - 1, pos); return ERROR; } /* Centers the current line with respect to the right_margin parameter. If the line (without spaces) is longer than the right margin, nothing happens. */ int center(buffer * const b) { line_desc * const ld = b->cur_line_desc; const int right_margin = b->opt.right_margin ? b->opt.right_margin : ne_columns; int64_t len, start_pos = 0, end_pos = ld->line_len; while(start_pos < ld->line_len && isasciispace(ld->line[start_pos])) start_pos = next_pos(ld->line, start_pos, b->encoding); if (start_pos == ld->line_len) return OK; while(isasciispace(ld->line[prev_pos(ld->line, end_pos, b->encoding)])) end_pos = prev_pos(ld->line, end_pos, b->encoding); len = b->encoding == ENC_UTF8 ? utf8strlen(&ld->line[start_pos], end_pos - start_pos) : end_pos - start_pos; if (len >= right_margin) return OK; b->cur_pos = -1; start_undo_chain(b); delete_stream(b, ld, b->cur_line, end_pos, ld->line_len - end_pos); delete_stream(b, ld, b->cur_line, 0, start_pos); insert_spaces(b, ld, b->cur_line, 0, (right_margin - len) / 2); end_undo_chain(b); return OK; } /* Indents a line of the amount of whitespace present on the previous line, stopping at a given column (use INT_MAX for not stopping). The number of inserted bytes is returned. */ int auto_indent_line(buffer * const b, const int64_t line, line_desc * const ld, const int64_t up_to_col) { line_desc * const prev_ld = (line_desc *)ld->ld_node.prev; if (!prev_ld->ld_node.prev || prev_ld->line_len == 0) return 0; assert_line_desc(prev_ld, b->encoding); int c; int64_t pos = 0; for(int64_t col = 0; pos < prev_ld->line_len && ne_isspace(c = get_char(&prev_ld->line[pos], b->encoding), b->encoding); ) { col += (c == '\t' ? b->opt.tab_size - col % b->opt.tab_size : 1); if (col > up_to_col) break; pos = next_pos(prev_ld->line, pos, b->encoding); } if (pos) insert_stream(b, ld, line, 0, prev_ld->line, pos); return pos; } /* Shift a block of lines left or right with whitespace adjustments. */ int shift(buffer * const b, char *p, char *msg, int msg_size) { const bool use_tabs = b->opt.tabs && b->opt.shift_tabs; const int64_t init_line = b->cur_line, init_pos = b->cur_pos, init_y = b->cur_y; line_desc *ld = NULL, *start_line_desc = NULL; int64_t shift_size = 1; char dir = '>'; int shift_mag = b->opt.tab_size, rc = 0; /* Parse parm p; looks like [<|>] ### [s|t], but we allow them in any order, once, with optional white space. */ if (p) { int dir_b = 0, size_b = 0, st_b = 0; while (*p) { if (isasciispace(*p)) p++; else if (!dir_b && (dir_b = (*p == '<' || *p == '>'))) dir = *p++; else if (!size_b && (size_b = isdigit((unsigned char)*p))) { errno = 0; shift_size = strtoll(p, &p, 10); if (errno) return INVALID_SHIFT_SPECIFIED; } else if (!st_b && (st_b = (*p == 's' || *p == 'S'))) { shift_mag = 1; p++; } else if (!st_b && (st_b = (*p == 't' || *p == 'T'))) p++; else return INVALID_SHIFT_SPECIFIED; } } shift_size *= max(1, shift_mag); if (shift_size == 0) return INVALID_SHIFT_SPECIFIED; int64_t first_line = b->cur_line, last_line = b->cur_line, left_col = 0; if (b->marking) { if (b->mark_is_vertical) left_col = min(calc_width(b->cur_line_desc, b->block_start_pos, b->opt.tab_size, b->encoding), calc_width(b->cur_line_desc, b->cur_pos, b->opt.tab_size, b->encoding)); first_line = min(b->block_start_line, b->cur_line); last_line = max(b->block_start_line, b->cur_line); } /* If we're shifting left (dir=='<'), verify that we have sufficient white space to remove on all the relevant lines before making any changes, i. */ if (dir == '<') { shift_size = -shift_size; /* signed shift_size now also indicates direction. */ for (int64_t line = first_line; !rc && line <= last_line; line++) { int64_t pos; goto_line(b, line); pos = calc_pos(b->cur_line_desc, left_col, b->opt.tab_size, b->encoding); while (pos < b->cur_line_desc->line_len && left_col - calc_width(b->cur_line_desc, pos, b->opt.tab_size, b->encoding) > shift_size) { if (isasciispace(b->cur_line_desc->line[pos])) pos = next_pos(b->cur_line_desc->line, pos, b->encoding); else { rc = INSUFFICIENT_WHITESPACE; break; } } } } if (!rc) { start_undo_chain(b); for (int64_t line = first_line; line <= last_line; line++) { int64_t pos, c_pos, c_col_orig, offset; b->attr_len = -1; goto_line(b, line); b->cur_pos = -1; ld = b->cur_line_desc; if (line == first_line) start_line_desc = ld; pos = calc_pos(ld, left_col, b->opt.tab_size, b->encoding); /* If left_col is in the middle of a tab, pos will be on that tab. */ /* whitespace adjustment strategy: 1. Starting from left_col, advance to the right to the first non-blank character C. 2. Note C's col. The desired new column is this value +/- shift_size. 3. Move left looking for the first tab or non-whitespace or the left_col, whichever comes first. Whitespace changes all take place at that transition point. 4. While C's col is wrong if C's col is too far to the right, if we're on a space, delete it; else if there's a tab to our left, delete it; else we should not have started, because it's not possible! if C's col is too far to the left, if its needs to be beyond the next tab stop, insert a tab and move right; else insert a space. */ /* 1. */ while (pos < ld->line_len && isasciispace(ld->line[pos])) pos = next_pos(ld->line, pos, b->encoding); if (pos >= ld->line_len) continue; /* We ran off the end of the line. */ /* line[pos] should be the first non-blank character. */ /* 2. */ c_pos = pos; c_col_orig = calc_width(ld, c_pos, b->opt.tab_size, b->encoding); /* 3. */ while (pos && ld->line[pos-1] == ' ') pos = prev_pos(ld->line, pos, b->encoding); /* If pos is non-zero, it should be on a blank, with only blanks between here and c_pos. */ /* 4. */ /* offset = how_far_we_have_moved - how_far_we_want_to_move. */ while (!stop && (offset = calc_width(ld, c_pos, b->opt.tab_size, b->encoding)-c_col_orig - shift_size)) { if (offset > 0) { /* still too far right; remove whitespace */ if (ld->line[pos] == ' ') { delete_stream(b, ld, b->cur_line, pos, 1); c_pos--; } else if (pos) { /* should be a tab just to our left */ pos = prev_pos(ld->line, pos, b->encoding); /* now we're on the tab */ if (ld->line[pos] == '\t') { delete_stream(b, ld, b->cur_line, pos, 1); c_pos--; } else break; /* Should have been a tab. This should never happen! Give up on this line and go mangle the next one. */ } else break; /* This should never happen; give up on this line and go mangle the next one. */ } else if (offset < 0) { /* too far left; insert whitespace */ char c = ' '; if (use_tabs && (b->opt.tab_size - calc_width(ld, pos, b->opt.tab_size, b->encoding) % b->opt.tab_size) <= -offset ) c = '\t'; if (insert_one_char(b, ld, b->cur_line, pos, c)) { break; } pos++; c_pos++; } } } end_undo_chain(b); if (b->syn) { b->attr_len = -1; need_attr_update = true; update_syntax_states(b, -1, start_line_desc, (line_desc *)ld->ld_node.next); } update_window_lines(b, b->top_line_desc, 0, ne_lines - 2, false); } /* put the screen back where way we found it. */ goto_line_pos(b, init_line, init_pos); delay_update(); const int64_t avshift = b->cur_y - init_y; if (avshift) { snprintf(msg, msg_size, "%c%" PRId64, avshift > 0 ? 'T' :'B', avshift > 0 ? avshift : -avshift); adjust_view(b, msg); } return rc; } ne-3.3.4/src/errors.c000066400000000000000000000127661475116431000144100ustar00rootroot00000000000000/* Error message vector. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2025 Todd M. Lewis and Sebastiano Vigna This file is part of ne, the nice editor. This library 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 library 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 . */ #include "errors.h" /* Whenever this vector is updated, the corresponding enum in errors.h *must* be updated too. */ char *error_msg[ERROR_COUNT] = { /* 0 */ "", /* 1 */ "Syntax error.", /* 2 */ "Not found. (RepeatLast to wrap)", /* 3 */ "Can't save a document. Exit suspended.", /* 4 */ "Can't save all modified documents.", /* 5 */ "You are not positioned over {}, (), [] or <>.", /* 6 */ "Can't find matching bracket.", /* 7 */ "Bookmark not set.", /* 8 */ "Invalid Bookmark designation (use 0 through 9, -1, +1, '<', '>', or '-').", /* 9 */ "No unset Bookmarks to set.", /* 10 */ "No set Bookmarks to goto.", /* 11 */ "No set Bookmarks to unset.", /* 12 */ "Invalid level (use 0, '+', '-', or none).", /* 13 */ "You cannot insert a character whose ASCII code is 0.", /* 14 */ "No search string.", /* 15 */ "No replace string.", /* 16 */ "TAB size out of range.", /* 17 */ "Invalid match mode.", /* 18 */ "Mark a block first.", /* 19 */ "Out of memory. DANGER!", /* 20 */ "Nothing to undo.", /* 21 */ "Nothing to redo", /* 22 */ "Undo is not enabled", /* 23 */ "No such command.", /* 24 */ "Can execute only preference commands.", /* 25 */ "Command needs a numeric argument.", /* 26 */ "Command has no arguments.", /* 27 */ "Command requires an argument.", /* 28 */ "Wrong character after backslash.", /* 29 */ "File doesn't exist.", /* 30 */ "Can't open file.", /* 31 */ "Can't open temporary files.", /* 32 */ "Error while writing.", /* 33 */ "Document name has no extension.", /* 34 */ "Can't find or create $HOME/.ne directory.", /* 35 */ "Clip does not exist.", /* 36 */ "Mark is out of document.", /* 37 */ "Can't open macro.", /* 38 */ "Maximum macro depth exceeded.", /* 39 */ "This document is read-only.", /* 40 */ "Can't open file (file is migrated).", /* 41 */ "Can't open file (file is a directory).", /* 42 */ "Can't open file (file is too large).", /* 43 */ "Stopped.", /* 44 */ "I/O error.", /* 45 */ "The argument string is empty.", /* 46 */ "External command error.", /* 47 */ "Escape time out of range.", /* 48 */ "Prefs stack is full.", /* 49 */ "Prefs stack is empty.", /* 50 */ "The argument is not a number.", /* 51 */ "This character is not supported in this configuration.", /* 52 */ "This string is not supported in this configuration.", /* 53 */ "This buffer is not UTF-8 encoded.", /* 54 */ "This clip cannot be pasted in this buffer (incompatible encoding).", /* 55 */ "This command line cannot be executed in this buffer (incompatible encoding).", /* 56 */ "This string cannot be searched for in this buffer (incompatible encoding).", /* 57 */ "This replacement string cannot be used in this buffer (incompatible encoding).", /* 58 */ "UTF-8 character classes in regular expressions are not supported.", /* 59 */ "Character classes cannot be complemented when matching against UTF-8 text.", /* 60 */ "The specified regex replacement group is not available in UTF-8 mode.", /* 61 */ "Syntax highlighting is not enabled.", /* 62 */ "There is no syntax for that extension.", /* 63 */ "Invalid Shift specified (use [<|>][#][s|t]; default is \">1t\").", /* 64 */ "Insufficient white space for requested left shift.", /* 65 */ "Document not saved.", /* 66 */ "File is too large--syntax highlighting disabled (use SYNTAX to reactivate).", /* 67 */ "Cannot save: disk full.", /* 68 */ "Out of memory (insufficient disk space?). DANGER!", /* 69 */ "Invalid Bracketed Paste designation (use '0', '1', or two macro names)." }; char *info_msg[INFO_COUNT] = { "Saving...", "Saved.", "All modified documents saved.", "Document is marked ReadOnly; save anyway?", "SELECT cursor/Enter FILTER chars/BS MODE Ins/Del EDIT Tab FILENAME F1/Esc-Esc/Esc", "Start of block marked. Move to the end of block and request actions.", "Start of vertical block marked. Move to the end of block and request actions.", "Starting macro recording...", "Macro recording completed.", "Macro record appending started.", "Macro recording cancelled.", "Some documents have not been saved; are you sure?", "Press a key to see ne's corresponding key code:", "This document is not saved; are you sure?", "There is another document with the same name; are you sure?", "No matching words found.", "Completed.", "Partially completed.", "Cancelled.", "SELECT cursor/Enter FILTER chars/BS MODE Ins/Del REORDER F2/F3 QUIT F1/Esc-Esc/Esc", "File has been modified since buffer was loaded or saved; are you sure?", "A file with that name already exists; are you sure?", "No such file exists; blank out current document?", "HELP cursor/Enter QUIT F1/Esc-Esc/Esc", "BACK Enter QUIT F1/Esc-Esc/Esc", " (browse history with ^F)", "Invalid argument for 'Record' while recording.", "All Bookmarks cleared.", "AtomicUndo level: 0" }; ne-3.3.4/src/errors.h000066400000000000000000000105451475116431000144060ustar00rootroot00000000000000/* Error index enum, and extern declaration of the error vector. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2025 Todd M. Lewis and Sebastiano Vigna This file is part of ne, the nice editor. This library 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 library 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 . */ /* Here we define the table of error messages. These defines are used whenever a function wants to return a specific error to the caller, and wants also to specify that an error message has to be printed. The generic error code which just says "something went wrong" is -1. 0 represents success. Any positive value is one of these enum items, and can be used to index the error message table. Whenever this enum is updated, the corresponding vector in errors.c *must* be updated too. */ #ifdef OK #undef OK #endif #ifdef ERROR #undef ERROR #endif #ifdef ABORT #undef ABORT #endif #define ABORT (-2) #define ERROR (-1) #define OK (0) enum error { /* 1 */ SYNTAX_ERROR = 1, /* 2 */ NOT_FOUND, /* 3 */ CANT_SAVE_EXIT_SUSPENDED, /* 4 */ CANT_SAVE_ALL, /* 5 */ NOT_ON_A_BRACKET, /* 6 */ CANT_FIND_BRACKET, /* 7 */ BOOKMARK_NOT_SET, /* 8 */ INVALID_BOOKMARK_DESIGNATION, /* 9 */ NO_UNSET_BOOKMARKS_TO_SET, /* 10 */ NO_SET_BOOKMARKS_TO_GOTO, /* 11 */ NO_SET_BOOKMARKS_TO_UNSET, /* 12 */ INVALID_LEVEL, /* 13 */ CANT_INSERT_0, /* 14 */ NO_SEARCH_STRING, /* 15 */ NO_REPLACE_STRING, /* 16 */ TAB_SIZE_OUT_OF_RANGE, /* 17 */ INVALID_MATCH_MODE, /* 18 */ MARK_BLOCK_FIRST, /* 19 */ OUT_OF_MEMORY, /* 20 */ NOTHING_TO_UNDO, /* 21 */ NOTHING_TO_REDO, /* 22 */ UNDO_NOT_ENABLED, /* 23 */ NO_SUCH_COMMAND, /* 24 */ CAN_EXECUTE_ONLY_OPTIONS, /* 25 */ HAS_NUMERIC_ARGUMENT, /* 26 */ HAS_NO_ARGUMENT, /* 27 */ REQUIRES_ARGUMENT, /* 28 */ WRONG_CHAR_AFTER_BACKSLASH, /* 29 */ FILE_DOES_NOT_EXIST, /* 30 */ CANT_OPEN_FILE, /* 31 */ CANT_OPEN_TEMPORARY_FILE, /* 32 */ ERROR_WHILE_WRITING, /* 33 */ HAS_NO_EXTENSION, /* 34 */ CANT_FIND_PREFS_DIR, /* 35 */ CLIP_DOESNT_EXIST, /* 36 */ MARK_OUT_OF_BUFFER, /* 37 */ CANT_OPEN_MACRO, /* 38 */ MAX_MACRO_DEPTH_EXCEEDED, /* 39 */ DOCUMENT_IS_READ_ONLY, /* 40 */ FILE_IS_MIGRATED, /* 41 */ FILE_IS_DIRECTORY, /* 42 */ FILE_IS_TOO_LARGE, /* 43 */ STOPPED, /* 44 */ IO_ERROR, /* 45 */ STRING_IS_EMPTY, /* 46 */ EXTERNAL_COMMAND_ERROR, /* 47 */ ESCAPE_TIME_OUT_OF_RANGE, /* 48 */ PREFS_STACK_FULL, /* 49 */ PREFS_STACK_EMPTY, /* 50 */ NOT_A_NUMBER, /* 51 */ INVALID_CHARACTER, /* 52 */ INVALID_STRING, /* 53 */ BUFFER_IS_NOT_UTF8, /* 54 */ INCOMPATIBLE_CLIP_ENCODING, /* 55 */ INCOMPATIBLE_COMMAND_ENCODING, /* 56 */ INCOMPATIBLE_SEARCH_STRING_ENCODING, /* 57 */ INCOMPATIBLE_REPLACE_STRING_ENCODING, /* 58 */ UTF8_REGEXP_CHARACTER_CLASS_NOT_SUPPORTED, /* 59 */ UTF8_REGEXP_COMP_CHARACTER_CLASS_NOT_SUPPORTED, /* 60 */ GROUP_NOT_AVAILABLE, /* 61 */ SYNTAX_NOT_ENABLED, /* 62 */ NO_SYNTAX_FOR_EXT, /* 63 */ INVALID_SHIFT_SPECIFIED, /* 64 */ INSUFFICIENT_WHITESPACE, /* 65 */ DOCUMENT_NOT_SAVED, /* 66 */ FILE_TOO_LARGE_SYNTAX_HIGHLIGHTING_DISABLED, /* 67 */ CANNOT_SAVE_DISK_FULL, /* 68 */ OUT_OF_MEMORY_DISK_FULL, /* 69 */ INVALID_BRACKETED_PASTE_DESIGNATION, ERROR_COUNT }; enum info { SAVING, SAVED, MODIFIED_SAVED, SAVE_READ_ONLY_DOCUMENT, PRESSF1, BLOCK_START_MARKED, VERTICAL_BLOCK_START_MARKED, STARTING_MACRO_RECORDING, MACRO_RECORDING_COMPLETED, MACRO_RECORD_APPENDING_STARTED, MACRO_RECORDING_CANCELLED, SOME_DOCUMENTS_ARE_NOT_SAVED, PRESS_A_KEY, THIS_DOCUMENT_NOT_SAVED, SAME_NAME, AUTOCOMPLETE_NO_MATCH, AUTOCOMPLETE_COMPLETED, AUTOCOMPLETE_PARTIAL, AUTOCOMPLETE_CANCELLED, SELECT_DOC, FILE_HAS_BEEN_MODIFIED, FILE_ALREADY_EXISTS, NO_SUCH_FILE_EXISTS, HELP_KEYS, HELP_COMMAND_KEYS, LONG_INPUT_HELP, INVALID_ARGUMENT_WHILE_RECORDING, ALL_BOOKMARKS_CLEARED, ATOMIC_UNDO_LEVEL_0, INFO_COUNT }; extern char *error_msg[ERROR_COUNT]; extern char *info_msg[INFO_COUNT]; ne-3.3.4/src/exec.c000066400000000000000000000051451475116431000140110ustar00rootroot00000000000000/* List handling functions. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2025 Todd M. Lewis and Sebastiano Vigna This file is part of ne, the nice editor. This library 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 library 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 . */ #include "ne.h" /* These functions provide basic management of lists. The basic ideas in this file come from the Amiga Exec list management functions and their C counterparts developed by Marco Zandonadi. Note that because of the way a list is defined, there are never special cases for the empty list. The price to pay is that a list is empty not if it's NULL, but rather is l->head->next is NULL. The first node of a list is l->head, the last one is l->tail_pred. A node is the last in a list if n->next->next == NULL. */ /* Initializes a list before any usage. */ void new_list(list *l) { l->head = (node *)&(l->tail); l->tail_pred = (node *)&(l->head); l->tail = NULL; } /* Inserts a node at the head of a list. */ void add_head(list *l, node *n) { n->next = l->head; n->prev = (node *) l; l->head->prev = n; l->head = n; } /* Inserts a node at the tail of a list. */ void add_tail(list *l, node *n) { n->next = (node *)&l->tail; n->prev = l->tail_pred; l->tail_pred->next = n; l->tail_pred = n; } /* Removes a node. Note that we do *not* need to know the list. */ void rem(node *n) { n->prev->next = n->next; n->next->prev = n->prev; } /* Adds a node to a list after a specified position. list.head and list.tail_pred are valid positions. */ void add(node *n, node *pos) { n->next = pos->next; n->prev = pos; pos->next->prev = n; pos->next = n; } /* Applies a given deallocation function throughout a whole list, emptying the list itself. */ void free_list(list *l, void (func)()) { node *n1 = l->head, *n2; while(n1->next) { n2 = n1->next; rem(n1); func(n1); n1 = n2; } } /* Applies a given function throughout a whole list. */ void apply_to_list(list *l, void (func)()) { node *n1 = l->head; while(n1->next) { func(n1); n1 = n1->next; } } ne-3.3.4/src/ext.c.in000066400000000000000000000030011475116431000142570ustar00rootroot00000000000000/* Extern's for names and abbreviations of all the commands. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2025 Todd M. Lewis and Sebastiano Vigna This file is part of ne, the nice editor. This library 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 library 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 . */ #include "ne.h" #include "strings.h" struct e2s { const char * ext; const char * syn; }; /* A list of mappings extension -> syntax. MUST be sorted on the first field. */ static struct e2s const e2s[] = { <%%EXTMAP%%> }; static int extcmp(struct e2s const *a, struct e2s const *b) { return strcasecmp(a->ext, b->ext); } /* Searches for a mapping matching the given extension in e2s; if found, returns the resulting syntax. Otherwise, returns ext. */ const char *ext2syntax(const char * const ext) { struct e2s key, *t; key.ext = ext; key.syn = NULL; t = bsearch(&key, e2s, sizeof e2s / sizeof *e2s, sizeof *e2s, (int (*)(const void *, const void *))extcmp); return t ? t->syn : ext; } ne-3.3.4/src/info2cap.c000066400000000000000000000203001475116431000145540ustar00rootroot00000000000000/* terminfo emulation through GNU termcap code. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2025 Todd M. Lewis and Sebastiano Vigna This file is part of ne, the nice editor. This library 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 library 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 . */ #include "ne.h" #include "termcap.h" #include /* This is the number of characters reserved to strings obtained through tparam(). They *have* to be enough, because otherwise the capability string will be silently truncated. */ #define TPARAM_BUF_LEN 2048 char *key_up; char *key_down; char *key_left; char *key_right; char *key_home; char *key_end; char *key_npage; char *key_ppage; char *key_sf; char *key_sr; char *key_eol; char *key_eos; char *key_backspace; char *key_dl; char *key_il; char *key_dc; char *key_ic; char *key_eic; char *key_clear; char *key_a1; char *key_a3; char *key_b2; char *key_c1; char *key_c3; char *key_catab; char *key_ctab; char *key_stab; char *key_f0; char *key_f1; char *key_f2; char *key_f3; char *key_f4; char *key_f5; char *key_f6; char *key_f7; char *key_f8; char *key_f9; char *key_f10; char *key_f11; char *key_f12; char *key_f13; char *key_f14; char *key_f15; char *key_f16; char *key_f17; char *key_f18; char *key_f19; char *key_f20; char *key_f21; char *key_f22; char *key_f23; char *key_f24; char *key_f25; char *key_f26; char *key_f27; char *key_f28; char *key_f29; char *key_f30; char *key_f31; char *key_f32; char *key_f33; char *key_f34; char *key_f35; char *key_f36; char *key_f37; char *key_f38; char *key_f39; char *key_f40; char *key_f41; char *key_f42; char *key_f43; char *key_f44; char *key_f45; char *key_f46; char *key_f47; char *key_f48; char *key_f49; char *key_f50; char *key_f51; char *key_f52; char *key_f53; char *key_f54; char *key_f55; char *key_f56; char *key_f57; char *key_f58; char *key_f59; char *key_f60; char *key_f61; char *key_f62; char *key_f63; /* The tparm() emulation. Note that we have to use a static buffer (because tparm() does it). Thus, instantiated strings longer than TPARAM_BUF_LEN will be copied in tparam_buffer and silently truncated. It should never happen with reasonable values for TPARAM_BUF_LEN, though. */ char *tparm(const char * const cap_string,...) { static char tparam_buffer[TPARAM_BUF_LEN]; va_list ap; int arg[4]; va_start(ap, cap_string); for(int i = 0; i < 4; i++) arg[i] = va_arg(ap, int); va_end(ap); char * const p = tparam(cap_string, tparam_buffer, TPARAM_BUF_LEN, arg[0], arg[1], arg[2], arg[3]); if (p != tparam_buffer) { memcpy(tparam_buffer, p, TPARAM_BUF_LEN - 1); free(p); } return tparam_buffer; } /* This is a real fake. We already know all the parameters. */ int setupterm(const char * const dummy1, const int dummy2, const int * const dummy3) { char * const term_name = getenv("TERM"), *s; if (!term_name) return ERR; int c = tgetent(NULL, term_name); if (c != 1) return ERR; /* if (c == -1) { printf("Could not access the termcap database.\n"); exit(1); } else if (c == 0) { printf("There is no terminal named %s.\n", term_name); exit(1); }*/ struct termios termios; tcgetattr(0, &termios); switch(cfgetospeed(&termios)) { case B50: ospeed = 1; break; case B75: ospeed = 2; break; case B110: ospeed = 3; break; case B134: ospeed = 4; break; case B150: ospeed = 5; break; case B200: ospeed = 6; break; case B300: ospeed = 7; break; case B600: ospeed = 8; break; case B1200: ospeed = 9; break; case B1800: ospeed = 10; break; case B2400: ospeed = 11; break; case B4800: ospeed = 12; break; case B9600: ospeed = 13; break; case B19200: ospeed = 14; break; } if (ospeed == 0) ospeed = 15; if (s = tgetstr("pc", NULL)) PC = *s; ne_generic_type = tgetflag("gn"); if ((ne_lines = tgetnum("li")) <= 0) ne_lines = 25; if ((ne_columns = tgetnum("co")) <= 0) ne_columns = 80; int l = c = 0; if (s = getenv("LINES")) l = atoi(s); if (s = getenv("COLUMNS")) c = atoi(s); if (l > 0 && c > 0) { ne_lines = l; ne_columns = c; } ne_column_address = tgetstr("ch", NULL); ne_row_address = tgetstr("cv", NULL); ne_cursor_address = tgetstr("cm", NULL); ne_carriage_return = tgetstr("cr", NULL); ne_cursor_home = tgetstr("ho", NULL); ne_cursor_to_ll = tgetstr("ll", NULL); ne_cursor_right = tgetstr("nd", NULL); ne_cursor_down = tgetstr("do", NULL); ne_cursor_left = tgetstr("le", NULL); ne_cursor_up = tgetstr("up", NULL); ne_auto_right_margin = tgetflag("am"); ne_eat_newline_glitch = tgetflag("xn"); ne_clr_eos = tgetstr("cd", NULL); ne_clear_screen = tgetstr("cl", NULL); ne_bell = tgetstr("bl", NULL); ne_flash_screen = tgetstr("vb", NULL); ne_scroll_forward = tgetstr("sf", NULL); ne_scroll_reverse = tgetstr("sr", NULL); ne_enter_delete_mode = tgetstr("dm", NULL); ne_exit_delete_mode = tgetstr("ed", NULL); ne_enter_insert_mode = tgetstr("im", NULL); ne_exit_insert_mode = tgetstr("ei", NULL); ne_enter_standout_mode = tgetstr("so", NULL); ne_exit_standout_mode = tgetstr("se", NULL); ne_magic_cookie_glitch = tgetnum("sg"); ne_move_standout_mode = tgetflag("ms"); ne_change_scroll_region = tgetstr("cs", NULL); ne_insert_line = tgetstr("al", NULL); ne_parm_insert_line = tgetstr("AL", NULL); ne_delete_line = tgetstr("dl", NULL); ne_parm_delete_line = tgetstr("DL", NULL); ne_insert_character = tgetstr("ic", NULL); ne_parm_ich = tgetstr("IC", NULL); ne_insert_padding = tgetstr("ip", NULL); ne_delete_character = tgetstr("dc", NULL); ne_parm_dch = tgetstr("DC", NULL); ne_move_insert_mode = tgetflag("mi"); ne_cursor_invisible = tgetstr("vi", NULL); ne_cursor_normal = tgetstr("ve", NULL); ne_init_1string = tgetstr("i1", NULL); ne_init_2string = tgetstr("is", NULL); ne_init_3string = tgetstr("i3", NULL); ne_enter_ca_mode = tgetstr("ti", NULL); ne_exit_ca_mode = tgetstr("te", NULL); ne_exit_attribute_mode = tgetstr("me", NULL); ne_exit_alt_charset_mode = tgetstr("ae", NULL); ne_repeat_char = tgetstr("rp", NULL); ne_tilde_glitch = tgetflag("hz"); ne_memory_below = tgetflag("db"); ne_has_meta_key = tgetflag("km"); ne_meta_on = tgetstr("mm", NULL); ne_meta_off = tgetstr("mo", NULL); ne_set_window = tgetstr("wi", NULL); ne_keypad_local = tgetstr("ke", NULL); ne_keypad_xmit = tgetstr("ks", NULL); ne_clr_eol = tgetstr("ce", NULL); ne_transparent_underline = tgetflag("ul"); /* TODO: add strings */ key_up = tgetstr("ku", NULL); key_down = tgetstr("kd", NULL); key_left = tgetstr("kl", NULL); key_right = tgetstr("kr", NULL); key_home = tgetstr("kh", NULL); key_end = tgetstr("@7", NULL); key_npage = tgetstr("kN", NULL); key_ppage = tgetstr("kP", NULL); key_sf = tgetstr("kF", NULL); key_sr = tgetstr("kR", NULL); /* Editing keys */ key_eol = tgetstr("kE", NULL); key_eos = tgetstr("kS", NULL); key_backspace = tgetstr("kb", NULL); key_dl = tgetstr("kL", NULL); key_il = tgetstr("kA", NULL); key_dc = tgetstr("kD", NULL); key_ic = tgetstr("kI", NULL); key_eic = tgetstr("kM", NULL); key_clear = tgetstr("kC", NULL); /* Keypad keys */ key_a1 = tgetstr("K1", NULL); key_a3 = tgetstr("K2", NULL); key_b2 = tgetstr("K3", NULL); key_c1 = tgetstr("K4", NULL); key_c3 = tgetstr("K5", NULL); /* Tab keys (never used in the standard configuration) */ key_catab = tgetstr("ka", NULL); key_ctab = tgetstr("kt", NULL); key_stab = tgetstr("kT", NULL); /* Function keys */ key_f0 = key_f10 = tgetstr("k0", NULL); key_f1 = tgetstr("k1", NULL); key_f2 = tgetstr("k2", NULL); key_f3 = tgetstr("k3", NULL); key_f4 = tgetstr("k4", NULL); key_f5 = tgetstr("k5", NULL); key_f6 = tgetstr("k6", NULL); key_f7 = tgetstr("k7", NULL); key_f8 = tgetstr("k8", NULL); key_f9 = tgetstr("k9", NULL); return 0; } ne-3.3.4/src/info2cap.h000066400000000000000000000062101475116431000145650ustar00rootroot00000000000000/* terminfo emulation through GNU termcap definitions. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2025 Todd M. Lewis and Sebastiano Vigna This file is part of ne, the nice editor. This library 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 library 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 . */ #include "termcap.h" #define ERR -1 extern char *tparm(const char *cap_string,...); extern int setupterm(const char *, int, const int *); extern char *key_up; extern char *key_down; extern char *key_left; extern char *key_right; extern char *key_home; extern char *key_end; extern char *key_npage; extern char *key_ppage; extern char *key_sf; extern char *key_sr; /* Editing keys */ extern char *key_eol; extern char *key_eos; extern char *key_backspace; extern char *key_dl; extern char *key_il; extern char *key_dc; extern char *key_ic; extern char *key_eic; extern char *key_clear; /* Keypad keys */ extern char *key_a1; extern char *key_a3; extern char *key_b2; extern char *key_c1; extern char *key_c3; /* Tab keys (never used in the standard configuration) */ extern char *key_catab; extern char *key_ctab; extern char *key_stab; /* Function keys */ extern char *key_f0; extern char *key_f1; extern char *key_f2; extern char *key_f3; extern char *key_f4; extern char *key_f5; extern char *key_f6; extern char *key_f7; extern char *key_f8; extern char *key_f9; extern char *key_f10; extern char *key_f11; extern char *key_f12; extern char *key_f13; extern char *key_f14; extern char *key_f15; extern char *key_f16; extern char *key_f17; extern char *key_f18; extern char *key_f19; extern char *key_f20; extern char *key_f21; extern char *key_f22; extern char *key_f23; extern char *key_f24; extern char *key_f25; extern char *key_f26; extern char *key_f27; extern char *key_f28; extern char *key_f29; extern char *key_f30; extern char *key_f31; extern char *key_f32; extern char *key_f33; extern char *key_f34; extern char *key_f35; extern char *key_f36; extern char *key_f37; extern char *key_f38; extern char *key_f39; extern char *key_f40; extern char *key_f41; extern char *key_f42; extern char *key_f43; extern char *key_f44; extern char *key_f45; extern char *key_f46; extern char *key_f47; extern char *key_f48; extern char *key_f49; extern char *key_f50; extern char *key_f51; extern char *key_f52; extern char *key_f53; extern char *key_f54; extern char *key_f55; extern char *key_f56; extern char *key_f57; extern char *key_f58; extern char *key_f59; extern char *key_f60; extern char *key_f61; extern char *key_f62; extern char *key_f63; /* These functions are no-ops in our termcap emulation. */ #define resetterm() #define fixterm() ne-3.3.4/src/info2src.pl000077500000000000000000000441401475116431000150040ustar00rootroot00000000000000#!/usr/bin/env perl use strict; use utf8; use open ':std', ':encoding(UTF-8)'; $| = 1; # Todd M. Lewis # # This program reads the ne.info* files and ne's command names # and descriptions from them, and creates relevant parts of # ne's source code from this data. It works with output from # makeinfo (GNU texinfo 3.12) 1.68 # (and probably other versions too). my $copyright = qq[ Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2025 Todd M. Lewis and Sebastiano Vigna This file is part of ne, the nice editor. This library 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 library 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 .]; my $enums_h_fname = "enums.h"; my $hash_h_fname = "hash.h"; my $hash_c_fname = "hash.c"; my $help_h_fname = "help.h"; my $help_c_fname = "help.c"; my $names_h_fname = "names.h"; my $names_c_fname = "names.c"; my @infofiles = glob "ne.info{,-*}"; my ( %commands, %exts, $body_enums, $body_names, $hash_table_size, @long_hash_table, @short_hash_table, ); foreach ( @infofiles ) { # Make texinfo refs and xrefs more readable. # Refs and xrefs can span lines, so we have to # deal single lines like this: # See *Note Key Bindings::. # as well as multiple lines like this: # See *Note Key # Bindings::. open INFO, $_ or die "Can't read command names from '$_'."; my @line = ; close INFO; my $line = join '', @line; # Pick left- and right-quote depending on whether our docs contain u+2018 and u+2019 already. my ($lq, $rq) = ($line =~ m/‘.+?’/) ? ('‘', '’') : ("'", "'"); $line =~ s<\*note *(\s*)(.+?)::> <$1$lq$2$rq>gis; my $state = 'searching'; my $command; foreach ( split /\n/, $line ) { chomp; if ( $state eq 'searching' ) { next unless ( m/^(Syntax: )[‘'`](([^ ]+).*)[’']\s*$/ ); $command = uc $3; $commands{$command}->{"cmd"} = "$3"; $commands{$command}->{"text"}[0] = "$1$2"; $state = 'building'; # print "---1 found $command\n"; } elsif ( $state eq 'building' ) { if ( substr($_,0,1) eq chr(0x1f) ) { pop @{$commands{$command}->{"text"}} while ($commands{$command}->{"text"}[$#{$commands{$command}->{"text"}}] eq ""); $state = 'searching'; next; } # print "---2 read \"$_\"\n"; if ( m/^(Abbreviation: )[‘'`](.+)[’']/ ) { $commands{$command}->{"abbr"} = "$2"; push @{$commands{$command}->{"text"}}, "$1$2"; # print "---3 pushing $1$2\n"; } else { push @{$commands{$command}->{"text"}}, $_; # print "---4 pushing $_\n"; } } } } ############################################################################ # Now the fun part -- We need to generate a hash table big enough to hold # all the hashed commands and command abbreviations, without collisions. # # The tables are precompiled into the file hash.c. The file hash.h # contains instead #defines which specify the dimensions of the hash # tables. # # The hash function we choose is very simple: it considers a string of # characters as a number in base 32, and takes the remainder of the division # of this number by a suitably large prime number. Suitably large means # that two commands must have different hash codes. sub hash { my ($cmd) = @_; my @cvals = reverse( unpack('C*', uc $cmd ) ); my $h = -1; foreach ( @cvals ) { $h = ($h * 31 + $_ ) % $hash_table_size; } return $h; } # To this purpose, two different hash tables are built for command names # and for abbreviations. This helps the distribution of the string to be # as random as possible. # # Using a sieve, the prime numbers up to $max_hash_table_size are generated. # If $n is prime, $primes[$n] == 1. # # This is Eratosthenes's sieve. Instead of marking with a boolean # each entry of composite, we leave undefined the prime numbers, and # we use the (index in array) construct in order to check for primality. my $max_hash_table_size = 6000; gen_hash_tables(); sub gen_primes { my ($i,$j); my @primes = (1) x $max_hash_table_size; # mark them all prime. for ($i=2; $i<$max_hash_table_size; $i++) { if ( $primes[$i] ) { for ( $j = 2 * $i; $j < $max_hash_table_size; $j += $i ) { $primes[ $j ]=0; # This one isn't prime. } } } return @primes; } # Then, starting from 1500 we test in order all prime numbers: we build # the hash tables and we check for conflicts. If there are no suitable # prime numbers smaller than $max_hash_table_size, we exit with an error. sub gen_hash_tables { my($cmd,$long,$short,$cmd_index); my @primes = gen_primes(); HTSIZE: for ($hash_table_size = 1500; $hash_table_size < $max_hash_table_size; $hash_table_size++ ) { next if ( !$primes[$hash_table_size] ); # start with empty (all-zero) hash tables. @long_hash_table = (0) x $hash_table_size; @short_hash_table = (0) x $hash_table_size; $cmd_index = 0; foreach $cmd ( sort keys %commands ) { $long = hash($commands{$cmd}->{"cmd" }); $short = hash($commands{$cmd}->{"abbr"}); next HTSIZE if ($long_hash_table[$long] || $short_hash_table[$short]); $cmd_index++; $long_hash_table[ $long ] = $cmd_index; $short_hash_table[$short] = $cmd_index; } # If we get here, we've got hash tables with no conflicts. # Declare victory and move on... print "$0: HASH_TABLE_SIZE = $hash_table_size\n"; return 1; } # If we get here, we failed to generate hash tables with no conflicts. # Complain bitterly and die. die("Failed to generate non-conflicting hash tables."); } # Now we're done with hash table generation. ############################################################################ ############################################################################ # Syntax filename Extension mapping # # The Syntax command docs contain a map of filename extensions to # .jsf file stems for commonly occurring real life files. We'll use that # to create the ext.c file, which contains that map in C. { my $state = 'searching'; my $base = ''; foreach ( @{$commands{SYNTAX}->{text}} ) { my $line = $_; if ( $state eq 'searching' ) { if ( $line =~ m/^\s+([a-z0-9+_]+):\s+/i ) { $base = $1; $state = 'building'; } } if ( $state eq 'building' ) { my @token = split /\s+/, $line; foreach my $token ( @token ) { if ( $token =~ m/([a-z0-9+_]+)(,|$)/i ) # conveniently skips "base:" { $exts{$1} = $base; } } $state = 'searching' unless $line =~ m/,\s*$/; } } if ( open EXTIN,"ext.c.in" ) { my @lines = ; close EXTIN; my $data = ''; foreach my $k ( sort keys %exts ) { $data .= qq[\t{ "$k", "$exts{$k}" },\n]; } $data =~ s/,\n$/\n/; # It's C, so take that last ',' off. my $lines = join '', @lines; $lines =~ s/<%%EXTMAP%%>/$data/; if ( open EXT,">ext.c" ) { print EXT $lines; close EXT; } } } open_out_files(); foreach my $cmd ( sort keys %commands ) { body_names_h($cmd); body_names_c($cmd); body_help_h($cmd); body_help_c($cmd); body_enums_h( $cmd ); body_hash_h( $cmd ); body_hash_c( $cmd ); } print scalar keys %commands, " commands processed.\n"; close_out_files(); exit 0; sub open_out_files { open NAMES_H, ">$names_h_fname" or die("Couldn't write $names_h_fname"); head_names_h(); open NAMES_C, ">$names_c_fname" or die("Couldn't write $names_c_fname"); head_names_c(); open HELP_C, ">$help_c_fname" or die("Couldn't write $help_c_fname"); head_help_c(); open HELP_H, ">$help_h_fname" or die("Couldn't write $help_h_fname"); head_help_h(); open ENUMS_H, ">$enums_h_fname" or die("Couldn't write $enums_h_fname"); head_enums_h(); open HASH_H, ">$hash_h_fname" or die("Couldn't write $hash_h_fname"); head_hash_h(); open HASH_C, ">$hash_c_fname" or die("Couldn't write $hash_c_fname"); head_hash_c(); } sub close_out_files { tail_names_h(); close NAMES_H; tail_names_c(); close NAMES_C; tail_help_c(); close HELP_C; tail_help_h(); close HELP_H; tail_enums_h(); close ENUMS_H; tail_hash_h(); close HASH_H; tail_hash_c(); close HASH_C; } ################################################################## ## N A M E S _ H ################################################################## sub head_names_c { print NAMES_C <<"_EOT_"; /* Names and abbreviations of all the commands. $copyright */ #include "ne.h" /* This file is generated by info2src.pl from data found in ne.texinfo. Changes made directly to this file will we lost when the documentation is updated. */ /* Here we have the name of all commands. They are extern'd in names.h. Note that whenever you add or remove a command, you should immediately change also the command_names array at the end of this file. */ _EOT_ $body_names = ""; } sub body_names_c { my ($uccmd) = @_; my $cmd = $commands{$uccmd}->{"cmd"}; if ( ! defined $commands{$uccmd} ) { print "$0 [", __LINE__, "]: \$commands{$uccmd} undefined.\n"; exit 2; } if ( ! defined $commands{$uccmd}->{"abbr"} ) { print "$0 [", __LINE__, "]: \$commands{$uccmd}->{\"abbr\"} undefined.\n"; exit 2; } print NAMES_C <<"_EOT_"; const char ${uccmd}_NAME[] = "$cmd"; const char ${uccmd}_ABBREV[] = "$commands{$uccmd}->{"abbr"}"; _EOT_ $body_names .= " ${uccmd}_NAME,"; $body_names .= "\n" if ( ( my @x = ($body_names =~ m/,/g)) % 4 == 0 ); } sub tail_names_c { print NAMES_C <<"_EOT_"; /* These are extras that are very useful in the default menus and key bindings. See info2src.pl; changes here will be lost the next time ne.texinfo changes. */ const char PLAYONCE_ABBREV[] = "PL 1"; const char RECORD_CANCEL_ABBREV[] = "REC 0"; const char RECORD_APPEND_ABBREV[] = "REC 1"; const char MIDDLEVIEW_ABBREV[] = "AV M"; const char SHIFTLEFT_ABBREV[] = "SH <"; #ifdef ALTPAGING const char SHIFTSPACELEFT_ABBREV[] = "SH {"cmd"}; print NAMES_H <<"_EOT_"; extern const char ${uccmd}_NAME[]; extern const char ${uccmd}_ABBREV[]; _EOT_ } sub tail_names_h { print NAMES_H <<"_EOT_"; /* These are extras that are very useful in the default menus and key bindings. See info2src.pl; changes here will be lost the next time ne.texinfo changes. */ extern const char PLAYONCE_ABBREV[]; extern const char RECORD_CANCEL_ABBREV[]; extern const char RECORD_APPEND_ABBREV[]; extern const char MIDDLEVIEW_ABBREV[]; extern const char SHIFTLEFT_ABBREV[]; #ifdef ALTPAGING extern const char SHIFTSPACELEFT_ABBREV[]; extern const char SHIFTSPACERIGHT_ABBREV[]; #endif /* This is the NULL-terminated, ordered list of names, useful for help etc. */ extern const char * const command_names[ACTION_COUNT+1]; /* This file was automatically generated by $0. */ _EOT_ } ################################################################## ## H E L P _ C ################################################################## sub head_help_c { print HELP_C <<"_EOT_"; /* Help strings. $copyright */ _EOT_ } sub body_help_c { my ($uccmd) = @_; my $cmd = $commands{$uccmd}->{"cmd"}; my $len = @{$commands{$uccmd}->{"text"}} + 1; print HELP_C "const char * const ${uccmd}_HELP[$len] = {\n"; for my $txt ( @{$commands{$uccmd}->{"text"}} ) { my $etxt = $txt; $etxt =~ s#\\#\\\\#g; $etxt =~ s#"#\\"#g; print HELP_C " \"$etxt\",\n"; } print HELP_C "};\n\n"; } sub tail_help_c { print HELP_C <<"_EOT_"; /* This file was automatically generated by $0. */ _EOT_ } ################################################################## ## H E L P _ H ################################################################## sub head_help_h { print HELP_H <<"_EOT_"; /* Help string externs. $copyright */ _EOT_ } sub body_help_h { my ($uccmd) = @_; my $cmd = $commands{$uccmd}->{"cmd"}; my $len = @{$commands{$uccmd}->{"text"}} + 1; print HELP_H <<"_EOT_"; extern const char * const ${uccmd}_HELP[ $len ]; _EOT_ } sub tail_help_h { print HELP_H <<"_EOT_"; /* This file was automatically generated by $0. */ _EOT_ } ################################################################## ## E N U M S _ H ################################################################## sub head_enums_h { print ENUMS_H <<"_EOT_"; /* This is the list of all possible actions that do_action() can execute. Note also that menu handling is governed by such a command (ESCAPE). $copyright */ typedef enum { _EOT_ $body_enums = ""; # We'll grow this string to contain all the commands. } sub body_enums_h { my ($uccmd) = @_; my $cmd = $commands{$uccmd}->{"cmd"}; $body_enums .= " ${uccmd}_A,"; # Count the commas, add '\n' if we've got multiple of 4. $body_enums .= "\n" if ( (my @x = ($body_enums =~ m/,/g)) % 4 == 0 ); } sub tail_enums_h { print ENUMS_H <<"_EOT_"; $body_enums ACTION_COUNT } action; /* This file was automatically generated by $0. */ _EOT_ } ################################################################## ## H A S H _ H ################################################################## sub head_hash_h { print HASH_H <<"_EOT_"; /* Header for precompiled hash tables for internal commands. $copyright */ _EOT_ } sub body_hash_h { # Nothing to do here. } sub tail_hash_h { my $max_command_width = 0; foreach my $cmd ( keys %commands ) { $max_command_width = length $cmd if length $cmd > $max_command_width; } print HASH_H <<"_EOT_"; /* These vectors are hash tables with no conflicts. For each command, the element indexed by the hashed name of the command contains the command number plus one. Thus, only one strcmp() is necessary when analyzing the command line. This technique offers a light speed comparison against the command names, with a very small memory usage. The tables are precompiled, so they can be moved to the text segment. */ #define HASH_TABLE_SIZE ($hash_table_size) extern const unsigned char hash_table[HASH_TABLE_SIZE]; extern const unsigned char short_hash_table[HASH_TABLE_SIZE]; /* The maximum width for a command is used when displaying the command names with the string requester. For example, 18 would allow four columns on an 80x25 screen. */ #define MAX_COMMAND_WIDTH $max_command_width /* This file was automatically generated by $0. */ _EOT_ } ################################################################## ## H A S H _ C ################################################################## sub head_hash_c { print HASH_C <<"_EOT_"; /* Precompiled hash tables for internal commands. $copyright */ /* #include "ne.h" */ #include "hash.h" /* These vectors are hash tables with no conflicts. For each command, the element indexed by the hashed name of the command contains the command number plus one. Thus, only one strcmp() is necessary when analyzing the command line. This technique offers a light speed comparison against the command names, with a very small memory usage. The tables are precompiled, so that they can be moved into the text segment. It is *essential* that any modification whatsoever to the command names, number etc. is reflected in this table. */ _EOT_ } sub body_hash_c { # Nothing to do here. } sub tail_hash_c { print HASH_C "const unsigned char hash_table[HASH_TABLE_SIZE] = {\n"; for (my $i=0; $i<$hash_table_size; $i++) { print HASH_C " $long_hash_table[$i],"; print HASH_C "\n" if ( $i % 20 == 0 ); } print HASH_C "};\n\n"; print HASH_C "const unsigned char short_hash_table[HASH_TABLE_SIZE] = {\n"; for (my $i=0; $i<$hash_table_size; $i++) { print HASH_C " $short_hash_table[$i],"; print HASH_C "\n" if ( $i % 20 == 0 ); } print HASH_C "};\n\n"; print HASH_C <<"_EOT_"; /* This file was automatically generated by $0. */ _EOT_ } ne-3.3.4/src/input.c000066400000000000000000000711401475116431000142220ustar00rootroot00000000000000/* Input line handling. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2025 Todd M. Lewis and Sebastiano Vigna This file is part of ne, the nice editor. This library 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 library 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 . */ #include "ne.h" #include "support.h" #include "termchar.h" #include /* This is the maximum number of bytes which can be typed on the input line. The actual number of characters depends on the line encoding. */ #define MAX_INPUT_LINE_LEN 2048 /* request() is the main function that serves request_number() and request_string(). Given a prompt, a default string and a boolean flag which establish the possibility of any alphabetical input (as opposed to digits only), the user can edit a string of at most MAX_INPUT_LINE_LEN characters. Many useful commands can be used here. The string edited by the user is returned, or NULL if the input was escaped, or the empty string was entered. Note that presently this function always returns a pointer to a static buffer, but this could change in the future. completion_type has several possible values: 0 COMPLETE_NONE means no completion, 1 COMPLETE_FILE complete as a filename, 2 COMPLETE_CMD complete as a command followed by a filename, (not implemented?) 3 COMPLETE_SYNTAX complete as a recognized syntax name. If prefer_utf8 is true, editing an ASCII line inserting an ISO-8859-1 character will turn it into an UTF-8 line. request() relies on a number of auxiliary functions and static data. As always, we would really need nested functions, but, alas, C can't cope with them. */ typedef struct { int start_x; /* first usable screen x position for editing */ int len; /* current raw length of input buffer; buf[len] is always a NULL */ int x; /* screen x position of the cursor */ int pos; /* position of the cursor inside the input buffer */ int offset; /* first displayed buffer character */ encoding_type encoding; /* current encoding of the buffer */ char buf[MAX_INPUT_LINE_LEN + 1]; /* input buffer itself */ } input_buf; static input_buf ib; /* our main input buffer */ /* Unlike ne's document buffers, the command line may (and will) move back to ASCII if all non-US-ASCII characters are deleted .*/ /* Prints an input prompt in the input line. The prompt is assumed not to be UTF-8 encoded. A colon is postpended to the prompt. The position of the first character to use for input is returned. Moreover, the status bar is reset, so that it will be updated. */ static unsigned int print_prompt(const char * const prompt, const bool show_help) { static bool help_shown; static const char *prior_prompt; assert(prompt != NULL || prior_prompt); resume_status_bar = (void (*)(const char *message))&input_and_prompt_refresh; if (prompt) prior_prompt = prompt; move_cursor(ne_lines - 1, 0); clear_to_eol(); standout_on(); set_attr(0); output_string(prior_prompt, false); if (show_help && !help_shown) { help_shown = true; output_string(info_msg[LONG_INPUT_HELP], false); ib.start_x = strlen(info_msg[LONG_INPUT_HELP]); } else ib.start_x = 0; output_string(":", false); standout_off(); output_string(" ", false); reset_status_bar(); return ib.start_x += strlen(prior_prompt) + 2; } /* Prompts the user for a yes/no answer. The prompt is assumed not to be UTF-8 encoded. default_value has to be true or false. true is returned if 'y' was typed, false in any other case. Escaping is not allowed. RETURN returns the default value. */ bool request_response(const buffer * const b, const char * const prompt, const bool default_value) { return request_char(b, prompt, default_value ? 'y' : 'n') == 'Y' ? true : false; } /* Prompts the user for a single character answer. The prompt is assumed not to be UTF-8 encoded. default_value has to be an ISO-8859-1 character which is used for the default answer. The character typed by the user (upper cased) is returned. The default character is used if the user types RETURN. Note that the cursor is moved back to its current position. This offers a clear distinction between immediate and long inputs, and allows for interactive search and replace. We can get away with the INVALID_CHAR (window resizing) and SUSPEND_A/resume code only because this is only ever called in regular editing mode, not from requesters or command input. */ char request_char(const buffer * const b, const char * const prompt, const char default_value) { print_prompt(prompt, false); if (default_value) output_char(default_value, 0, false); highlight_mark((buffer * const)b, true); move_cursor(b->cur_y, b->cur_x); while(true) { int c; input_class ic; do c = get_key_code(); while(c > 0xFF || (ic = CHAR_CLASS(c)) == IGNORE); if (window_changed_size) { window_changed_size = false; reset_window(); keep_cursor_on_screen((buffer * const)b); refresh_window((buffer *)b); print_prompt(NULL, false); if (default_value) output_char(default_value, 0, false); move_cursor(b->cur_y, b->cur_x); } if (c == INVALID_CHAR) continue; /* Window resizing. */ switch(ic) { case ALPHA: highlight_mark((buffer * const)b, false); return (char)localised_up_case[(unsigned char)c]; case RETURN: highlight_mark((buffer * const)b, false); return (char)localised_up_case[(unsigned char)default_value]; case COMMAND: if (c < 0) c = -c - 1; const int a = parse_command_line(key_binding[c], NULL, NULL, false); if (a >= 0) { switch(a) { case SUSPEND_A: stop_ne(); case REFRESH_A: reset_window(); keep_cursor_on_screen((buffer * const)b); refresh_window((buffer *)b); print_prompt(NULL, false); if (default_value) output_char(default_value, 0, false); move_cursor(b->cur_y, b->cur_x); break; } } default: break; } } highlight_mark((buffer * const)b, false); } /* Requests a number, with a given prompt and default value. The prompt is assumed not to be UTF-8 encoded. Only nonnegative integers can be entered. The effects of a negative default_value are mysterious, and not worth an investigation. The returned value is nonnegative if something was entered, negative on escaping or on entering the empty string. */ int64_t request_number(const buffer * const b, const char * const prompt, const int64_t default_value) { static char t[MAX_INT_LEN]; if (default_value >= 0) sprintf(t, "%" PRId64, default_value); char * const result = request(b, prompt, default_value >= 0 ? t : NULL, false, 0, io_utf8); if (!result) return ABORT; if (!*result) return ERROR; char *endptr; const int64_t n = strtoll(result, &endptr, 0); return *endptr || n < 0 ? ERROR : n; } /* Requests a string. The prompt is assumed not to be UTF-8 encoded. The returned string is never longer than MAX_INPUT_LINE_LEN, and has been malloc()ed, so you can use it and then free() it. NULL is returned on escaping, or if entering an empty string (unless accept_null_string is true, in which case the empty string is duplicated and returned). completion_type and prefer_utf8 work as in request(). */ char *request_string(const buffer * const b, const char * const prompt, const char * const default_string, const bool accept_null_string, const int completion_type, const bool prefer_utf8) { const char * const result = request(b, prompt, default_string, true, completion_type, prefer_utf8); if (result && (*result || accept_null_string)) return str_dup(result); return NULL; } static buffer *history_buff = NULL; static void init_history(void) { if (!history_buff){ history_buff = alloc_buffer(NULL); if (history_buff) { const char * const history_filename = tilde_expand("~/.ne/.history"); clear_buffer(history_buff); history_buff->opt.do_undo = 0; history_buff->opt.auto_prefs = 0; load_file_in_buffer(history_buff, history_filename); /* The history buffer is agnostic. The actual encoding of each line is detected dynamically. */ history_buff->encoding = ENC_8_BIT; change_filename(history_buff, str_dup(history_filename)); assert_buffer(history_buff); /* This should be never necessary with new histories, as all lines will be terminated by a newline, but it is kept for backward compatibility. */ move_to_bof(history_buff); if (history_buff->cur_line_desc->line && history_buff->cur_line_desc->line_len) { insert_stream(history_buff, history_buff->cur_line_desc, history_buff->cur_line, history_buff->cur_line_desc->line_len, "", 1); } } } if (history_buff) { move_to_bof(history_buff); move_to_sol(history_buff); } } void close_history(void) { if (history_buff) { if (history_buff->is_modified) { while(history_buff->num_lines > 500) { move_to_sof(history_buff); delete_one_line(history_buff, history_buff->cur_line_desc, history_buff->cur_line); assert_buffer(history_buff); } save_buffer_to_file(history_buff, NULL); } free_buffer(history_buff); history_buff = NULL; } } static void add_to_history(const char * const str) { if (!history_buff || !str || !*str) return; move_to_bof(history_buff); /* This insert_stream() takes care of adding a line, including a line-feed at the end. */ insert_stream(history_buff, history_buff->cur_line_desc, history_buff->cur_line, history_buff->cur_line_desc->line_len, str, strlen(str) + 1); assert_buffer(history_buff); } static int input_buffer_is_ascii() { return is_ascii(ib.buf, ib.len); } /* The following functions perform basic editing actions on the input line. */ static void input_refresh(void) { move_cursor(ne_lines - 1, ib.start_x); ib.x = ib.start_x + ib.pos - ib.offset; for(int i = ib.start_x, j = ib.offset; j < ib.len; i += get_char_width(&ib.buf[j], ib.encoding), j = next_pos(ib.buf, j, ib.encoding)) { if (i + get_char_width(&ib.buf[j], ib.encoding) >= ne_columns) break; output_char(get_char(&ib.buf[j], ib.encoding), 0, ib.encoding); } clear_to_eol(); fflush(stdout); } void input_and_prompt_refresh() { print_prompt(NULL, false); input_refresh(); } static void input_autocomplete(void) { int dx = 0, prefix_pos = ib.pos; char *p; /* find a usable prefix */ if (prefix_pos && prefix_pos <= ib.len) { prefix_pos = prev_pos(ib.buf, prefix_pos, ib.encoding); dx--; while (prefix_pos && ne_isword(get_char(&ib.buf[prefix_pos], ib.encoding), ib.encoding)) { dx--; prefix_pos = prev_pos(ib.buf, prefix_pos, ib.encoding); } if (! ne_isword(get_char(&ib.buf[prefix_pos], ib.encoding), ib.encoding)) { dx++; prefix_pos = next_pos(ib.buf, prefix_pos, ib.encoding); } p = malloc(ib.pos - prefix_pos + 1); if (!p) { alert(); /* OUT_OF_MEMORY */ return; } strncpy(p, &ib.buf[prefix_pos], ib.pos - prefix_pos); } else p = malloc(1); /* no prefix left of the cursor; we'll give an empty one. */ p[ib.pos - prefix_pos] = 0; int ac_err; if (p = autocomplete(p, NULL, true, &ac_err)) { encoding_type ac_encoding = detect_encoding(p, strlen(p)); if (ac_encoding != ENC_ASCII && ib.encoding != ENC_ASCII && ac_encoding != ib.encoding) { free(p); alert(); } else { ib.encoding = ac_encoding; if (prefix_pos < ib.pos) { memmove(&ib.buf[prefix_pos], &ib.buf[ib.pos], ib.len - ib.pos + 1); ib.len -= ib.pos - prefix_pos; ib.pos = prefix_pos; } int ac_len = strlen(p); if (ac_len + ib.len >= MAX_INPUT_LINE_LEN) ac_len = MAX_INPUT_LINE_LEN - ib.len; memmove(&ib.buf[ib.pos + ac_len], &ib.buf[ib.pos], ib.len - ib.pos + 1); memmove(&ib.buf[ib.pos], p, ac_len); ib.len += ac_len; while (ac_len > 0) { const int cw = get_char_width(&ib.buf[ib.pos], ib.encoding); ib.pos = next_pos(ib.buf, ib.pos, ib.encoding); ac_len -= cw; dx++; } free(p); ib.x += dx; if (ib.x >= ne_columns) { dx = ib.x - ne_columns + 1; while (dx--) { ib.offset = next_pos(ib.buf, ib.offset, ib.encoding); } ib.x = ne_columns - 1; } } } if (ac_err == AUTOCOMPLETE_COMPLETED || ac_err == AUTOCOMPLETE_CANCELLED) { do_action(cur_buffer, REFRESH_A, 0, NULL); refresh_window(cur_buffer); print_prompt(NULL, false); } input_refresh(); } static void input_move_left(const bool do_refresh) { if (ib.pos > 0) { const int x_delta = get_char_width(&ib.buf[ib.pos = prev_pos(ib.buf, ib.pos, ib.encoding)], ib.encoding); assert(ib.pos >= 0); if (ib.x == ib.start_x) { ib.offset = ib.pos; if (char_ins_del_ok) { int i, j; for(i = ib.start_x, j = ib.offset; j < ib.len && i + get_char_width(&ib.buf[j], ib.encoding) < ne_columns; i += get_char_width(&ib.buf[j], ib.encoding), j = next_pos(ib.buf, j, ib.encoding)); if (j < ib.len) { move_cursor(ne_lines - 1, i); delete_chars(get_char_width(&ib.buf[j], ib.encoding)); } move_cursor(ne_lines - 1, ib.start_x); insert_char(get_char(&ib.buf[ib.pos], ib.encoding), 0, ib.encoding); move_cursor(ne_lines - 1, ib.start_x); } else if (do_refresh) input_refresh(); } else ib.x -= x_delta; } } static void input_move_right(const bool do_refresh) { const int prev_pos = ib.pos; if (ib.pos < ib.len) { const int x_delta = get_char_width(&ib.buf[ib.pos], ib.encoding); ib.pos = next_pos(ib.buf, ib.pos, ib.encoding); assert(ib.pos <= ib.len); if ((ib.x += x_delta) >= ne_columns) { const int shift = ib.x - (ne_columns - 1); int width = 0; do { width += get_char_width(&ib.buf[ib.offset], ib.encoding); ib.offset = next_pos(ib.buf, ib.offset, ib.encoding); } while(width < shift && ib.offset < ib.len); assert(ib.offset < ib.len); ib.x -= width; if (char_ins_del_ok) { int i, j; move_cursor(ne_lines - 1, ib.start_x); delete_chars(width); move_cursor(ne_lines - 1, ib.x - x_delta); output_char(get_char(&ib.buf[prev_pos], ib.encoding), 0, ib.encoding); i = ib.x; j = ib.pos; while(j < ib.len && i + (width = get_char_width(&ib.buf[j], ib.encoding)) < ne_columns) { output_char(get_char(&ib.buf[j], ib.encoding), 0, ib.encoding); i += width; j = next_pos(ib.buf, j, ib.encoding); } } else if (do_refresh) input_refresh(); } } } static void input_move_to_sol(void) { if (ib.offset == 0) { ib.x = ib.start_x; ib.pos = 0; return; } ib.x = ib.start_x; ib.offset = ib.pos = 0; input_refresh(); } static void input_move_to_eol(void) { const int width_to_end = get_string_width(ib.buf + ib.pos, ib.len - ib.pos, ib.encoding); if (ib.x + width_to_end < ne_columns) { ib.x += width_to_end; ib.pos = ib.len; return; } ib.x = ib.start_x; ib.pos = ib.offset = ib.len; int i = ne_columns - 1 - ib.start_x; for(;;) { const int prev = prev_pos(ib.buf, ib.offset, ib.encoding); const int width = get_char_width(&ib.buf[prev], ib.encoding); if (i - width < 0) break; ib.offset = prev; i -= width; ib.x += width; } input_refresh(); } static void input_next_word(void) { bool space_skipped = false; while(ib.pos < ib.len) { const int c = get_char(&ib.buf[ib.pos], ib.encoding); if (!ne_isword(c, ib.encoding)) space_skipped = true; else if (space_skipped) break; input_move_right(false); } if (ib.x == ne_columns - 1) { ib.offset = ib.pos; ib.x = ib.start_x; } input_refresh(); } static void input_prev_word(void) { bool word_skipped = false; while(ib.pos > 0) { input_move_left(false); const int c = get_char(&ib.buf[ib.pos], ib.encoding); if (ne_isword(c, ib.encoding)) word_skipped = true; else if (word_skipped) { input_move_right(false); break; } } input_refresh(); } /* Pastes the passed string into the input buffer, or the nth clip if str is NULL. */ static void input_paste(char *str) { const clip_desc * const cd = get_nth_clip(cur_buffer->opt.cur_clip); if (str || cd) { if (cd && cd->cs->encoding != ENC_ASCII && ib.encoding != ENC_ASCII && cd->cs->encoding != ib.encoding) { alert(); return; } int paste_len = str ? strlen(str) : strnlen_ne(cd->cs->stream, cd->cs->len); if (ib.len + paste_len > MAX_INPUT_LINE_LEN) paste_len = MAX_INPUT_LINE_LEN - ib.len; memmove(&ib.buf[ib.pos + paste_len], &ib.buf[ib.pos], ib.len - ib.pos + 1); strncpy(&ib.buf[ib.pos], str ? str : cd->cs->stream, paste_len); ib.len += paste_len; if (!input_buffer_is_ascii() && cd->cs->encoding != ENC_ASCII) ib.encoding = cd->cs->encoding; input_refresh(); } } static int request_history(void) { req_list rl; int i = -1; char *tmpstr; if (!history_buff) return -1; line_desc *ld = (line_desc *)history_buff->line_desc_list.tail_pred; if (ld->ld_node.prev && req_list_init(&rl, NULL, true, false, '\0')==OK) { while (ld->ld_node.prev) { if (ld->line_len) { tmpstr = strntmp(ld->line, ld->line_len); req_list_add(&rl, tmpstr, false); } ld = (line_desc *)ld->ld_node.prev; } rl.ignore_tab = false; rl.prune = true; rl.find_quits = true; req_list_finalize(&rl); i = request_strings(&rl, 0); if (i != ERROR) { int selection = i >= 0 ? i : -i - 2; if (i >= 0) { strncpy(ib.buf, rl.entries[selection], MAX_INPUT_LINE_LEN); ib.len = strlen(ib.buf); ib.encoding = detect_encoding(ib.buf, ib.len); input_move_to_sol(); } else input_paste(rl.entries[selection]); } req_list_free(&rl); } strntmp(NULL, -1); return i >= 0 && i < history_buff->num_lines; } /* The filename completion function. Returns NULL if no file matches start_prefix, or the longest prefix common to all files extending start_prefix. */ static char *complete_filename(const char *start_prefix) { /* This might be NULL if the current directory has been unlinked, or it is not readable. In that case, we end up moving to the completion directory. */ char * const cur_dir_name = ne_getcwd(CUR_DIR_MAX_SIZE); char * const dir_name = str_dup(start_prefix); if (dir_name) { char * const p = (char *)file_part(dir_name); *p = 0; if (p != dir_name && chdir(tilde_expand(dir_name)) == -1) { free(dir_name); return NULL; } } start_prefix = file_part(start_prefix); bool is_dir, unique = true; char *cur_prefix = NULL; DIR * const d = opendir(CURDIR); if (d) { for(struct dirent * de; !stop && (de = readdir(d)); ) { if (is_prefix(start_prefix, de->d_name)) if (cur_prefix) { cur_prefix[max_prefix(cur_prefix, ENC_8_BIT, de->d_name, ENC_8_BIT)] = 0; unique = false; } else { cur_prefix = str_dup(de->d_name); is_dir = is_directory(de->d_name); } } closedir(d); } char * result = NULL; if (cur_prefix) { result = malloc(strlen(dir_name) + strlen(cur_prefix) + 2); strcat(strcat(strcpy(result, dir_name), cur_prefix), unique && is_dir ? "/" : ""); } if (cur_dir_name != NULL) { chdir(cur_dir_name); free(cur_dir_name); } free(dir_name); free(cur_prefix); return result; } char *request(const buffer * const b, const char *prompt, const char * const default_string, const bool alpha_allowed, const int completion_type, const bool prefer_utf8) { ib.buf[ib.pos = ib.len = ib.offset = 0] = 0; ib.encoding = ENC_ASCII; highlight_mark((buffer * const)b, true); ib.x = ib.start_x = print_prompt(prompt, true); init_history(); if (default_string) { strncpy(ib.buf, default_string, MAX_INPUT_LINE_LEN); ib.len = strlen(ib.buf); ib.encoding = detect_encoding(ib.buf, ib.len); input_refresh(); } bool first_char_typed = true, last_char_completion = false, selection = false; while(true) { assert(ib.buf[ib.len] == 0); move_cursor(ne_lines - 1, ib.x); int c; input_class ic; do c = get_key_code(); while((ic = CHAR_CLASS(c)) == IGNORE); if (window_changed_size) { window_changed_size = false; reset_window(); keep_cursor_on_screen((buffer * const)b); refresh_window((buffer *)b); input_and_prompt_refresh(); } if (c == INVALID_CHAR) continue; /* Window resizing. */ /* ISO 10646 characters *above 256* can be added only to UTF-8 lines, or ASCII lines (making them, of course, UTF-8). */ if (ic == ALPHA && c > 0xFF && ib.encoding != ENC_ASCII && ib.encoding != ENC_UTF8) ic = INVALID; if (ic != TAB) last_char_completion = false; if (ic == TAB && !completion_type) ic = ALPHA; switch(ic) { case INVALID: alert(); break; case ALPHA: if (first_char_typed) { ib.buf[ib.len = 0] = 0; clear_to_eol(); } if (ib.encoding == ENC_ASCII && c > 0x7F) ib.encoding = prefer_utf8 || c > 0xFF ? ENC_UTF8 : ENC_8_BIT; int c_len = ib.encoding == ENC_UTF8 ? utf8seqlen(c) : 1; int c_width = output_width(c); assert(c_len > 0); if (ib.len <= MAX_INPUT_LINE_LEN - c_len && (alpha_allowed || (c < 0x100 && isxdigit(c)) || c=='X' || c=='x')) { memmove(&ib.buf[ib.pos + c_len], &ib.buf[ib.pos], ib.len - ib.pos + 1); if (c_len == 1) ib.buf[ib.pos] = c; else utf8str(c, &ib.buf[ib.pos]); ib.len += c_len; move_cursor(ne_lines - 1, ib.x); if (ib.x < ne_columns - c_width) { if (ib.pos == ib.len - c_len) output_char(c, 0, ib.encoding); else if (char_ins_del_ok) insert_char(c, 0, ib.encoding); else input_refresh(); } input_move_right(true); } break; case RETURN: selection = true; break; case TAB: if (completion_type == COMPLETE_FILE || completion_type == COMPLETE_SYNTAX) { bool quoted = false; char *prefix, *completion, *p; if (ib.len && ib.buf[ib.len - 1] == '"') { ib.buf[ib.len - 1] = 0; if (prefix = strrchr(ib.buf, '"')) { quoted = true; prefix++; } else ib.buf[ib.len - 1] = '"'; } if (!quoted) { prefix = strchr(ib.buf, ' '); if (prefix) { while (*prefix == ' ') prefix++; } else prefix = ib.buf; } if (last_char_completion || completion_type == COMPLETE_SYNTAX) { if (completion_type == COMPLETE_FILE ) completion = p = request_files(prefix, true); else completion = p = request_syntax(prefix, true); reset_window(); if (completion) { if (*completion) selection = true; else completion++; } } else { if (completion_type == COMPLETE_FILE ) completion = p = complete_filename(prefix); else completion = p = request_syntax(prefix, true); last_char_completion = true; if (!completion) alert(); } if (completion && (prefix - ib.buf) + strlen(completion) + 1 < MAX_INPUT_LINE_LEN) { const encoding_type completion_encoding = detect_encoding(completion, strlen(completion)); if (ib.encoding == ENC_ASCII || completion_encoding == ENC_ASCII || ib.encoding == completion_encoding) { strcpy(prefix, completion); if (quoted) strcat(prefix, "\""); ib.len = strlen(ib.buf); ib.pos = ib.offset = 0; ib.x = ib.start_x; if (ib.encoding == ENC_ASCII) ib.encoding = completion_encoding; input_move_to_eol(); if (quoted) input_move_left(false); input_refresh(); } else alert(); } else if (quoted) strcat(prefix, "\""); free(p); } break; case COMMAND: if (c < 0) c = -c - 1; const int a = parse_command_line(key_binding[c], NULL, NULL, false); if (a >= 0) { switch(a) { case SUSPEND_A: stop_ne(); reset_window(); keep_cursor_on_screen((buffer * const)b); refresh_window((buffer *)b); input_and_prompt_refresh(); break; case FIND_A: if (first_char_typed) { ib.buf[ib.len = 0] = 0; clear_to_eol(); } request_history(); reset_window(); keep_cursor_on_screen((buffer * const)b); refresh_window((buffer *)b); input_and_prompt_refresh(); break; case LINEUP_A: case LINEDOWN_A: case MOVESOF_A: case MOVEEOF_A: case PAGEUP_A: case PAGEDOWN_A: case NEXTPAGE_A: case PREVPAGE_A: if (history_buff) { switch(a) { case LINEUP_A: line_up(history_buff); break; case LINEDOWN_A: line_down(history_buff); break; case MOVESOF_A: move_to_sof(history_buff); break; case MOVEEOF_A: move_to_bof(history_buff); break; case PAGEUP_A: case PREVPAGE_A: prev_page(history_buff); break; case PAGEDOWN_A: case NEXTPAGE_A: next_page(history_buff); break; } /* In some cases, the default displayed on the command line will be the same as the first history item. In that case we skip it. */ if (first_char_typed == true && a == LINEUP_A && history_buff->cur_line_desc->line && !strncmp(history_buff->cur_line_desc->line, ib.buf, history_buff->cur_line_desc->line_len)) line_up(history_buff); if (history_buff->cur_line_desc->line) { strncpy(ib.buf, history_buff->cur_line_desc->line, min(history_buff->cur_line_desc->line_len, MAX_INPUT_LINE_LEN)); ib.buf[min(history_buff->cur_line_desc->line_len, MAX_INPUT_LINE_LEN)] = 0; ib.len = strlen(ib.buf); ib.encoding = detect_encoding(ib.buf, ib.len); } else { ib.buf[ib.len = 0] = 0; ib.encoding = ENC_ASCII; } ib.x = ib.start_x; ib.pos = 0; ib.offset = 0; input_refresh(); } break; case MOVELEFT_A: input_move_left(true); break; case MOVERIGHT_A: input_move_right(true); break; case BACKSPACE_A: if (ib.pos == 0) break; input_move_left(true); case DELETECHAR_A: if (ib.len > 0 && ib.pos < ib.len) { int c_len = ib.encoding == ENC_UTF8 ? utf8len(ib.buf[ib.pos]) : 1; int c_width = get_char_width(&ib.buf[ib.pos], ib.encoding); memmove(&ib.buf[ib.pos], &ib.buf[ib.pos + c_len], ib.len - ib.pos - c_len + 1); ib.len -= c_len; if (input_buffer_is_ascii()) ib.encoding = ENC_ASCII; if (char_ins_del_ok) { int i, j; move_cursor(ne_lines - 1, ib.x); delete_chars(c_width); for(i = ib.x, j = ib.pos; j < ib.len && i + get_char_width(&ib.buf[j], ib.encoding) < ne_columns - c_width; i += get_char_width(&ib.buf[j], ib.encoding), j = next_pos(ib.buf, j, ib.encoding)); if (j < ib.len) { move_cursor(ne_lines - 1, i); while(j < ib.len && i + get_char_width(&ib.buf[j], ib.encoding) < ne_columns) { output_char(get_char(&ib.buf[j], ib.encoding), 0, ib.encoding); i += get_char_width(&ib.buf[j], ib.encoding); j = next_pos(ib.buf, j, ib.encoding); } } } else input_refresh(); } break; case DELETELINE_A: move_cursor(ne_lines - 1, ib.start_x); clear_to_eol(); ib.buf[ib.len = ib.pos = ib.offset = 0] = 0; ib.encoding = ENC_ASCII; ib.x = ib.start_x; break; case DELETEEOL_A: ib.buf[ib.len = ib.pos] = 0; clear_to_eol(); if (input_buffer_is_ascii()) ib.encoding = ENC_ASCII; break; case MOVEINCUP_A: if (ib.x != ib.start_x) { ib.pos = ib.offset; ib.x = ib.start_x; break; } case MOVESOL_A: input_move_to_sol(); break; case MOVEINCDOWN_A: { int i, j; for(i = ib.x, j = ib.pos; j < ib.len && i + get_char_width(&ib.buf[j], ib.encoding) < ne_columns; i += get_char_width(&ib.buf[j], ib.encoding), j = next_pos(ib.buf, j, ib.encoding)); if (j != ib.pos && j < ib.len) { ib.pos = j; ib.x = i; break; } } case MOVEEOL_A: input_move_to_eol(); break; case TOGGLESEOL_A: case TOGGLESEOF_A: if (ib.pos != 0) input_move_to_sol(); else input_move_to_eol(); break; case PREVWORD_A: input_prev_word(); break; case NEXTWORD_A: input_next_word(); break; case REFRESH_A: input_refresh(); break; case PASTE_A: input_paste(NULL); break; case AUTOCOMPLETE_A: input_autocomplete(); break; case ESCAPE_A: highlight_mark((buffer * const)b, false); return NULL; default: break; } } break; default: break; } if (selection) { const line_desc * const last = (line_desc *)history_buff->line_desc_list.tail_pred->prev; assert(ib.buf[ib.len] == 0); if (history_buff->num_lines == 0 || ib.len != last->line_len || strncmp(ib.buf, last->line, last->line_len)) add_to_history(ib.buf); highlight_mark((buffer * const)b, false); return ib.buf; } first_char_typed = false; } highlight_mark((buffer * const)b, false); } ne-3.3.4/src/inputclass.c000066400000000000000000000513761475116431000152610ustar00rootroot00000000000000/* Input class, key bindings and upper casing vector definitions. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2025 Todd M. Lewis and Sebastiano Vigna This file is part of ne, the nice editor. This library 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 library 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 . */ #include "ne.h" const char *input_class_names[INPUT_CLASS_COUNT] = { "ALPHA", "COMMAND", "RETURN", "TAB", "IGNORE", "INVALID" }; const char *config_source_names[CONFIG_SOURCE_COUNT] = { "Built In", "Global Prefs", "Global Prefs (terminal)", "User Prefs", "User Prefs (terminal)", "Current Directory", "Current Directory (terminal)" }; /* This vector contains all key bindings. Each entry points to a command line to be executed when the corresponding keystroke is input. The index correspond to the ASCII code, and to the codes defined in keycodes.h for the special keys. Note that it is nonsense to specify a binding for a key whose class is not COMMAND. */ #ifndef ALTPAGING #define PICK(A,B) A #else #define PICK(A,B) B #endif config_source key_binding_source[NUM_KEYS]; const char *key_binding[NUM_KEYS] = { /* Control-letter bindings (Ctrl-X)* 0..31 0..1f */ /* ^@ */ MARKVERT_ABBREV, /* ^a */ MOVESOL_ABBREV, /* ^b */ MARK_ABBREV, /* ^c */ COPY_ABBREV, /* ^d */ NEWDOC_ABBREV, /* ^e */ MOVEEOL_ABBREV, /* ^f */ FIND_ABBREV, /* ^g */ REPEATLAST_ABBREV, /* ^h */ BACKSPACE_ABBREV, /* ^i */ NULL, /* ^j */ GOTOLINE_ABBREV, /* ^k */ EXEC_ABBREV, /* ^l */ REFRESH_ABBREV, /* ^m */ NULL, /* ^n */ PICK(NEXTPAGE_ABBREV, PAGEDOWN_ABBREV), /* ^o */ OPEN_ABBREV, /* ^p */ PICK(PREVPAGE_ABBREV, PAGEUP_ABBREV), /* ^q */ CLOSEDOC_ABBREV, /* ^r */ REPLACE_ABBREV, /* ^s */ SAVE_ABBREV, /* ^t */ RECORD_ABBREV, /* ^u */ UNDELLINE_ABBREV, /* ^v */ PASTE_ABBREV, /* ^w */ PASTEVERT_ABBREV, /* ^x */ CUT_ABBREV, /* ^y */ DELETELINE_ABBREV, /* ^z */ SUSPEND_ABBREV, /* ^[ */ ESCAPE_ABBREV, /* ^\ */ NULL, /* ^] */ MATCHBRACKET_ABBREV, /* ^^ */ ADJUSTVIEW_ABBREV, /* ^_ */ FINDREGEXP_ABBREV, /* These keys map to strings, not commands. 31..126 20..7e */ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, /* space ! " # $ % & ' ( ) * + , - . / */ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, /* 0 1 2 3 4 5 6 7 8 9 : ; < = > ? */ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, /* @ A B C D E F G H I J K L M N O */ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, /* P Q R S T U V W X Y Z [ \ ] ^ _ */ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, /* ` a b c d e f g h i j k l m n o */ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, /* p q r s t u v w x y z { | } ~ */ DELETECHAR_ABBREV, /* 127 7f */ /* Control-meta-letter bindings (Ctrl-Alt-X) 128..159 80..9f */ /* Ctrl-Alt-@ */ NULL, /* Ctrl-Alt-a */ MOVESOF_ABBREV, /* Ctrl-Alt-b */ PREVWORD_ABBREV, /* Ctrl-Alt-c */ MIDDLEVIEW_ABBREV, /* Ctrl-Alt-d */ NEXTDOC_ABBREV, /* Ctrl-Alt-e */ MOVEEOF_ABBREV, /* Ctrl-Alt-f */ NEXTWORD_ABBREV, /* Ctrl-Alt-g */ GOTOBOOKMARK_ABBREV, /* Ctrl-Alt-h */ NULL, /* Ctrl-Alt-i */ AUTOCOMPLETE_ABBREV, /* Ctrl-Alt-j */ GOTOCOLUMN_ABBREV, /* Ctrl-Alt-k */ SETBOOKMARK_ABBREV, /* Ctrl-Alt-l */ TOLOWER_ABBREV, /* Ctrl-Alt-m */ PLAYONCE_ABBREV, /* Ctrl-Alt-n */ OPENNEW_ABBREV, /* Ctrl-Alt-o */ OPENCLIP_ABBREV, /* Ctrl-Alt-p */ PARAGRAPH_ABBREV, /* Ctrl-Alt-q */ QUIT_ABBREV, /* Ctrl-Alt-r */ REDO_ABBREV, /* Ctrl-Alt-s */ SAVECLIP_ABBREV, /* Ctrl-Alt-t */ THROUGH_ABBREV, /* Ctrl-Alt-u */ UNDO_ABBREV, /* Ctrl-Alt-v */ TOUPPER_ABBREV, /* Ctrl-Alt-w */ WORDWRAP_ABBREV, /* Ctrl-Alt-x */ EXIT_ABBREV, /* Ctrl-Alt-y */ DELETEEOL_ABBREV, /* Ctrl-Alt-z */ CRLF_ABBREV, /* Ctrl-Alt-[ */ NULL, /* Ctrl-Alt-\ */ NULL, /* Ctrl-Alt-] */ NULL, /* Ctrl-Alt-^ */ NULL, /* Ctrl-Alt-_ */ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, /* 160..175 A0..AF */ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, /* 176..191 B0..BF */ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, /* 192..207 C0..CF */ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, /* 207..223 D0..DF */ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, /* 224..239 E0..EF */ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, /* 240..255 F0..FF */ /* The following bindings are for the terminfo extended codes (see keycodes.h). */ /* Cursor movement keys 256..271 100..10F */ NULL, LINEUP_ABBREV, LINEDOWN_ABBREV, MOVELEFT_ABBREV, MOVERIGHT_ABBREV, PICK(MOVEINCUP_ABBREV, MOVESOL_ABBREV), PICK(MOVEINCDOWN_ABBREV, MOVEEOL_ABBREV), PICK(NEXTPAGE_ABBREV, PAGEDOWN_ABBREV), PICK(PREVPAGE_ABBREV, PAGEUP_ABBREV), LINEDOWN_ABBREV, LINEUP_ABBREV, NULL, NULL, NULL, NULL, NULL, /* Editing keys 272..287 110..11F */ DELETEEOL_ABBREV, NULL, BACKSPACE_ABBREV, DELETELINE_ABBREV, UNDELLINE_ABBREV, DELETECHAR_ABBREV, INSERT_ABBREV, NULL, CLEAR_ABBREV, NULL, NULL, NULL, NULL, NULL, NULL, NULL, /* Keypad keys and fake (simulated) menu key 288..303 120..12F */ MOVESOF_ABBREV, PREVPAGE_ABBREV, TOGGLESEOL_ABBREV, MOVEEOF_ABBREV, NEXTPAGE_ABBREV, EXEC_ABBREV, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, /* 304..319 130..13F */ /* Function keys 320..335 140..14F */ /* F0 */ ESCAPE_ABBREV, /* F1 */ ESCAPE_ABBREV, /* F2 */ NEXTDOC_ABBREV, /* F3 */ PREVDOC_ABBREV, /* F4 */ SELECTDOC_ABBREV, /* F5 */ UNDO_ABBREV, /* F6 */ REDO_ABBREV, /* F7 */ PREVWORD_ABBREV, /* F8 */ NEXTWORD_ABBREV, /* F9 */ PLAYONCE_ABBREV, /* F10 */ HELP_ABBREV, /* F11 */ NULL, /* F12 */ NULL, /* F13 */ NULL, /* F14 */ NULL, /* F15 */ NULL, /* 336..351 150..15F */ /* F16 */ NULL, /* F17 */ PICK(NULL, SHIFTLEFT_ABBREV), /* Shift-F5 */ /* F18 */ PICK(NULL, SHIFT_ABBREV), /* Shift-F6 */ /* F19 */ NULL, /* F20 */ NULL, /* F21 */ NULL, /* F22 */ NULL, /* F23 */ NULL, /* F24 */ NULL, /* F25 */ NULL, /* F26 */ NULL, /* F27 */ NULL, /* F28 */ NULL, /* F29 */ PICK(NULL, SHIFTSPACELEFT_ABBREV), /* Ctrl-F5 */ /* F30 */ PICK(NULL, SHIFTSPACERIGHT_ABBREV), /* Ctrl-F6 */ /* F31 */ DELETEPREVWORD_ABBREV, /* Ctrl-F7 */ /* 352..367 160..16F */ /* F32 */ DELETENEXTWORD_ABBREV, /* Ctrl-F8 */ /* F33 */ NULL, /* F34 */ NULL, /* F35 */ NULL, /* F36 */ NULL, /* F37 */ NULL, /* F38 */ NULL, /* F39 */ NULL, /* F40 */ NULL, /* F41 */ NULL, /* F42 */ NULL, /* F43 */ NULL, /* F44 */ NULL, /* F45 */ NULL, /* F46 */ NULL, /* F47 */ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, /* 368..383 170..17F */ /* Prefix-simulated META (ESC Ctrl-a) 384..416 180..1A0 */ /* ESC Ctrl-@ */ NULL, /* ESC Ctrl-a */ MOVESOF_ABBREV, /* ESC Ctrl-b */ PREVWORD_ABBREV, /* ESC Ctrl-c */ MIDDLEVIEW_ABBREV, /* ESC Ctrl-d */ NEXTDOC_ABBREV, /* ESC Ctrl-e */ MOVEEOF_ABBREV, /* ESC Ctrl-f */ NEXTWORD_ABBREV, /* ESC Ctrl-g */ GOTOBOOKMARK_ABBREV, /* ESC Ctrl-h */ NULL, /* ESC Ctrl-i */ AUTOCOMPLETE_ABBREV, /* ESC Ctrl-j */ GOTOCOLUMN_ABBREV, /* ESC Ctrl-k */ SETBOOKMARK_ABBREV, /* ESC Ctrl-l */ TOLOWER_ABBREV, /* ESC Ctrl-m */ PLAYONCE_ABBREV, /* ESC Ctrl-n */ OPENNEW_ABBREV, /* ESC Ctrl-o */ OPENCLIP_ABBREV, /* ESC Ctrl-p */ PARAGRAPH_ABBREV, /* ESC Ctrl-q */ QUIT_ABBREV, /* ESC Ctrl-r */ REDO_ABBREV, /* ESC Ctrl-s */ SAVECLIP_ABBREV, /* ESC Ctrl-t */ THROUGH_ABBREV, /* ESC Ctrl-u */ UNDO_ABBREV, /* ESC Ctrl-v */ TOUPPER_ABBREV, /* ESC Ctrl-w */ WORDWRAP_ABBREV, /* ESC Ctrl-x */ EXIT_ABBREV, /* ESC Ctrl-y */ DELETEEOL_ABBREV, /* ESC Ctrl-z */ CRLF_ABBREV, /* ESC Ctrl-[ */ NULL, /* ESC Ctrl-\ */ NULL, /* ESC Ctrl-] */ NULL, /* ESC Ctrl-^ */ NULL, /* ESC Ctrl-_ */ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, /* 416..431 1A0..1AF */ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, /* 432..447 1B0..1BF */ /* Prefix-simulated META (ESC X) 448..479 1C0..1DF */ /* ESC @ */ NULL, /* ESC A */ MOVESOF_ABBREV, /* ESC B */ PREVWORD_ABBREV, /* ESC C */ MIDDLEVIEW_ABBREV, /* ESC D */ NEXTDOC_ABBREV, /* ESC E */ MOVEEOF_ABBREV, /* ESC F */ NEXTWORD_ABBREV, /* ESC G */ GOTOBOOKMARK_ABBREV, /* ESC H */ NULL, /* ESC I */ AUTOCOMPLETE_ABBREV, /* ESC J */ GOTOCOLUMN_ABBREV, /* ESC K */ SETBOOKMARK_ABBREV, /* ESC L */ TOLOWER_ABBREV, /* ESC M */ PLAYONCE_ABBREV, /* ESC N */ OPENNEW_ABBREV, /* ESC O */ OPENCLIP_ABBREV, /* ESC P */ PARAGRAPH_ABBREV, /* ESC Q */ QUIT_ABBREV, /* ESC R */ REDO_ABBREV, /* ESC S */ SAVECLIP_ABBREV, /* ESC T */ THROUGH_ABBREV, /* ESC U */ UNDO_ABBREV, /* ESC V */ TOUPPER_ABBREV, /* ESC W */ WORDWRAP_ABBREV, /* ESC X */ EXIT_ABBREV, /* ESC Y */ DELETEEOL_ABBREV, /* ESC Z */ CRLF_ABBREV, /* ESC [ */ NULL, /* ESC \ */ NULL, /* ESC ] */ NULL, /* ESC ^ */ NULL, /* ESC _ */ NULL, /* Prefix-simulated META (ESC X) 480..511 1E0..1FF */ /* ESC @ */ NULL, /* ESC a */ MOVESOF_ABBREV, /* ESC b */ PREVWORD_ABBREV, /* ESC c */ MIDDLEVIEW_ABBREV, /* ESC d */ NEXTDOC_ABBREV, /* ESC e */ MOVEEOF_ABBREV, /* ESC f */ NEXTWORD_ABBREV, /* ESC g */ GOTOBOOKMARK_ABBREV, /* ESC h */ NULL, /* ESC i */ AUTOCOMPLETE_ABBREV, /* ESC j */ GOTOCOLUMN_ABBREV, /* ESC k */ SETBOOKMARK_ABBREV, /* ESC l */ TOLOWER_ABBREV, /* ESC m */ PLAYONCE_ABBREV, /* ESC n */ OPENNEW_ABBREV, /* ESC o */ OPENCLIP_ABBREV, /* ESC p */ PARAGRAPH_ABBREV, /* ESC q */ QUIT_ABBREV, /* ESC r */ REDO_ABBREV, /* ESC s */ SAVECLIP_ABBREV, /* ESC t */ THROUGH_ABBREV, /* ESC u */ UNDO_ABBREV, /* ESC v */ TOUPPER_ABBREV, /* ESC w */ WORDWRAP_ABBREV, /* ESC x */ EXIT_ABBREV, /* ESC y */ DELETEEOL_ABBREV, /* ESC z */ CRLF_ABBREV, /* ESC [ */ NULL, /* ESC \ */ NULL, /* ESC ] */ NULL, /* ESC ^ */ NULL, /* ESC _ */ PICK(NULL, AUTOINDENT_ABBREV), /* Two fake ones for bracketed paste 200..201 */ /* n/a */ NULL, /* NE_KEY_BPASTE_BEGIN */ /* n/a */ NULL, /* NE_KEY_BPASTE_END */ }; /* This vector holds, for each ISO-8859-1 key code, its input class. */ const input_class char_class[256] = { /* Control-letter classes */ /* @ a b c d e f g h i j k l m n o */ COMMAND, COMMAND, COMMAND, COMMAND, COMMAND, COMMAND, COMMAND, COMMAND, COMMAND, TAB, COMMAND, COMMAND, COMMAND, RETURN, COMMAND, COMMAND, /* p q r s t u v w x y z [ \ ] ^ _ */ COMMAND, COMMAND, COMMAND, COMMAND, COMMAND, COMMAND, COMMAND, COMMAND, COMMAND, COMMAND, COMMAND, COMMAND, COMMAND, COMMAND, COMMAND, COMMAND, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, COMMAND, /* Control-meta-letter classes PORTABILITY PROBLEM: on some systems, these characters are printable. In this case, it is a good idea to define their type as ALPHA, so that you can type them in the text. They are COMMAND by default because this is what happens in the ISO-8859 family. */ /* @ a b c d e f g h i j k l m n o */ COMMAND, COMMAND, COMMAND, COMMAND, COMMAND, COMMAND, COMMAND, COMMAND, COMMAND, COMMAND, COMMAND, COMMAND, COMMAND, COMMAND, COMMAND, COMMAND, /* p q r s t u v w x y z [ \ ] ^ _ */ COMMAND, COMMAND, COMMAND, COMMAND, COMMAND, COMMAND, COMMAND, COMMAND, COMMAND, COMMAND, COMMAND, COMMAND, COMMAND, COMMAND, COMMAND, COMMAND, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA }; const char meta_prefixed[128][3] = { { '\x1b', 0 }, { '\x1b', 1 }, { '\x1b', 2 }, { '\x1b', 3 }, { '\x1b', 4 }, { '\x1b', 5 }, { '\x1b', 6 }, { '\x1b', 7 }, { '\x1b', 8 }, { '\x1b', 9 }, { '\x1b', 10 }, { '\x1b', 11 }, { '\x1b', 12 }, { '\x1b', 13 }, { '\x1b', 14 }, { '\x1b', 15 }, { '\x1b', 16 }, { '\x1b', 17 }, { '\x1b', 18 }, { '\x1b', 19 }, { '\x1b', 20 }, { '\x1b', 21 }, { '\x1b', 22 }, { '\x1b', 23 }, { '\x1b', 24 }, { '\x1b', 25 }, { '\x1b', 26 }, { '\x1b', 27 }, { '\x1b', 28 }, { '\x1b', 29 }, { '\x1b', 30 }, { '\x1b', 31 }, { '\x1b', 32 }, { '\x1b', 33 }, { '\x1b', 34 }, { '\x1b', 35 }, { '\x1b', 36 }, { '\x1b', 37 }, { '\x1b', 38 }, { '\x1b', 39 }, { '\x1b', 40 }, { '\x1b', 41 }, { '\x1b', 42 }, { '\x1b', 43 }, { '\x1b', 44 }, { '\x1b', 45 }, { '\x1b', 46 }, { '\x1b', 47 }, { '\x1b', 48 }, { '\x1b', 49 }, { '\x1b', 50 }, { '\x1b', 51 }, { '\x1b', 52 }, { '\x1b', 53 }, { '\x1b', 54 }, { '\x1b', 55 }, { '\x1b', 56 }, { '\x1b', 57 }, { '\x1b', 58 }, { '\x1b', 59 }, { '\x1b', 60 }, { '\x1b', 61 }, { '\x1b', 62 }, { '\x1b', 63 }, { '\x1b', 64 }, { '\x1b', 65 }, { '\x1b', 66 }, { '\x1b', 67 }, { '\x1b', 68 }, { '\x1b', 69 }, { '\x1b', 70 }, { '\x1b', 71 }, { '\x1b', 72 }, { '\x1b', 73 }, { '\x1b', 74 }, { '\x1b', 75 }, { '\x1b', 76 }, { '\x1b', 77 }, { '\x1b', 78 }, { '\x1b', 79 }, { '\x1b', 80 }, { '\x1b', 81 }, { '\x1b', 82 }, { '\x1b', 83 }, { '\x1b', 84 }, { '\x1b', 85 }, { '\x1b', 86 }, { '\x1b', 87 }, { '\x1b', 88 }, { '\x1b', 89 }, { '\x1b', 90 }, { '\x1b', 91 }, { '\x1b', 92 }, { '\x1b', 93 }, { '\x1b', 94 }, { '\x1b', 95 }, { '\x1b', 96 }, { '\x1b', 97 }, { '\x1b', 98 }, { '\x1b', 99 }, { '\x1b', 100 }, { '\x1b', 101 }, { '\x1b', 102 }, { '\x1b', 103 }, { '\x1b', 104 }, { '\x1b', 105 }, { '\x1b', 106 }, { '\x1b', 107 }, { '\x1b', 108 }, { '\x1b', 109 }, { '\x1b', 110 }, { '\x1b', 111 }, { '\x1b', 112 }, { '\x1b', 113 }, { '\x1b', 114 }, { '\x1b', 115 }, { '\x1b', 116 }, { '\x1b', 117 }, { '\x1b', 118 }, { '\x1b', 119 }, { '\x1b', 120 }, { '\x1b', 121 }, { '\x1b', 122 }, { '\x1b', 123 }, { '\x1b', 124 }, { '\x1b', 125 }, { '\x1b', 126 }, { '\x1b', 127 } }; const char *key_stroke[NUM_KEYS] = { /* Control-letter bindings (Ctrl-X)* [0..32) [0..20) */ "^@", "^A", "^B", "^C", "^D", "^E", "^F", "^G", "^H", "^I", "^J", "^K", "^L", "^M", "^N", "^O", "^P", "^Q", "^R", "^S", "^T", "^U", "^V", "^W", "^X", "^Y", "^Z", "^[", "^\\", "^]", "^^", "^_", /* These keys map to strings, not commands. [32..127) [20..7f) */ " ", "!", "\"","#", "$", "%", "&", "'", "(", ")", "*", "+", ",", "-", ".", "/", /* space ! " # $ % & ' ( ) * + , - . / */ "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ":", ";", "<", "=", ">", "?", /* 0 1 2 3 4 5 6 7 8 9 : ; < = > ? */ "@", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", /* @ A B C D E F G H I J K L M N O */ "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "[", "\\","]", "^", "_", /* P Q R S T U V W X Y Z [ \ ] ^ _ */ "`", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", /* ` a b c d e f g h i j k l m n o */ "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "{", "|", "}", "~", /* p q r s t u v w x y z { | } ~ */ "Del", /* 127 7f */ /* Control-meta-letter bindings (Ctrl-Alt-X) [128..160) [80..A0) */ "^[@", "^[A", "^[B", "^[C", "^[D", "^[E", "^[F", "^[G ", "^[H", "^[I ", "^[J", "^[K", "^[L", "^[M", "^[N", "^[O", "^[P", "^[Q", "^[R", "^[S", "^[T", "^[U", "^[V", "^[W", "^[X", "^[Y", "^[Z", "^[[", "^[\\", "^[]", "^[^", "^[_", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", /* [160..176) [A0..B0) */ "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", /* [176..192) [B0..C0) */ "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", /* [192..208) [C0..D0) */ "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", /* [208..224) [D0..E0) */ "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", /* [224..240) [E0..F0) */ "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", /* [240..256) [F0..100) */ /* The following bindings are for the terminfo extended codes (see keycodes.h). */ /* Cursor movement keys [256..272) [100..110) */ "", "Up", "Down", "Left", "Right", "IncUp", "IncDn", "PgDn", "PgUp", "LnDn", "LnUp", "", "", "", "", "", /* Editing keys [272..288) [110..120) */ "Del", "", "BackSp", "DelLn", "UndelLn", "DelCh", "Ins", "", "Clear", "", "", "", "", "", "", "", /* Keypad keys and fake (simulated) menu key [288..304) [120..130) */ "KPSoF", "KPPrPg", "S/EoL", "EOF", "NxtPg", "Exec", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", /* [304..320) [130..140) */ /* Function keys [320..336) [140..150) */ "F0", "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", "F13", "F14", "F15", "F16", "F17", "F18", "F19", "F20", "F21", "F22", "F23", "F24", "F25", "F26", "F27", "F28", "F29", "F30", "F31", /* [336..352) [150..160) */ "F32", "F33", "F34", "F35", "F36", "F37", "F38", "F39", "F40", "F41", "F42", "F43", "F44", "F45", "F46", "F47", /* [352..368) [160..170) */ "F48", "F49", "F50", "F51", "F52", "F53", "F54", "F55", "F56", "F57", "F58", "F59", "F60", "F61", "F62", "F63", /* [368..384) [170..180) */ /* Prefix-simulated META (ESC Ctrl-a) [384..416) [180..1A0) */ "ESC-^@", "ESC-^A", "ESC-^B", "ESC-^C", "ESC-^D", "ESC-^E", "ESC-^F", "ESC-^G", "ESC-^H", "ESC-^I", "ESC-^J", "ESC-^K", "ESC-^L", "ESC-^M", "ESC-^N", "ESC-^O", "ESC-^P", "ESC-^Q", "ESC-^R", "ESC-^S", "ESC-^T", "ESC-^U", "ESC-^V", "ESC-^W", "ESC-^X", "ESC-^Y", "ESC-^Z", "ESC-^[", "ESC-^\\", "ESC-^]", "ESC-^^", "ESC-^_", /* Prefix-simulated META (ESC Ctrl-a) [416..447) [1A0..1C0) */ "ESC- ", "ESC-!", "ESC-\"", "ESC-#", "ESC-$", "ESC-%", "ESC-&", "ESC-'", "ESC-(", "ESC-)", "ESC-*", "ESC-+", "ESC-,", "ESC--", "ESC-.", "ESC-/", "ESC-0", "ESC-1", "ESC-2", "ESC-3", "ESC-4", "ESC-5", "ESC-6", "ESC-7", "ESC-8", "ESC-9", "ESC-:", "ESC-;", "ESC-<", "ESC-=", "ESC->", "ESC-?", /* Prefix-simulated META (ESC X) [448..480) [1C0..1E0) */ "ESC-@", "ESC-A", "ESC-B", "ESC-C", "ESC-D", "ESC-E", "ESC-F", "ESC-G", "ESC-H", "ESC-I", "ESC-J", "ESC-K", "ESC-L", "ESC-M", "ESC-N", "ESC-O", "ESC-P", "ESC-Q", "ESC-R", "ESC-S", "ESC-T", "ESC-U", "ESC-V", "ESC-W", "ESC-X", "ESC-Y", "ESC-Z", "ESC-[", "ESC-\\", "ESC-]", "ESC-^", "ESC-_", /* Prefix-simulated META (ESC X) [480..512) [1E0..200) */ "ESC-@", "ESC-a", "ESC-b", "ESC-c", "ESC-d", "ESC-e", "ESC-f", "ESC-g", "ESC-h", "ESC-i", "ESC-j", "ESC-k", "ESC-l", "ESC-m", "ESC-n", "ESC-o", "ESC-p", "ESC-q", "ESC-r", "ESC-s", "ESC-t", "ESC-u", "ESC-v", "ESC-w", "ESC-x", "ESC-y", "ESC-z", "ESC-{", "ESC-|", "ESC-}", "ESC-~", "ESC-DEL", /* Bracketed Paste Begin and End [512, 513] [200, 201] */ "*BPB*", "*BPE*", }; ne-3.3.4/src/keycodes.h000066400000000000000000000046521475116431000147020ustar00rootroot00000000000000/* Extended codes for special keys. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2025 Todd M. Lewis and Sebastiano Vigna This file is part of ne, the nice editor. This library 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 library 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 . */ /* These are curses-like key codes. They are defined for all keys available in the terminfo database. The whole 64 function keys are supported. This allows to map, if necessary, any additional key not included in terminfo's capabilities onto an unused function key capability. */ /* Cursor movement keys */ #define NE_KEY_UP 0x101 #define NE_KEY_DOWN 0x102 #define NE_KEY_LEFT 0x103 #define NE_KEY_RIGHT 0x104 #define NE_KEY_HOME 0x105 #define NE_KEY_END 0x106 #define NE_KEY_NPAGE 0x107 #define NE_KEY_PPAGE 0x108 #define NE_KEY_SCROLL_FORWARD 0x109 #define NE_KEY_SCROLL_REVERSE 0x10A /* Editing keys */ #define NE_KEY_CLEAR_TO_EOL 0x110 #define NE_KEY_CLEAR_TO_EOS 0x111 #define NE_KEY_BACKSPACE 0x112 #define NE_KEY_DELETE_LINE 0x113 #define NE_KEY_INSERT_LINE 0x114 #define NE_KEY_DELETE_CHAR 0x115 #define NE_KEY_INSERT_CHAR 0x116 #define NE_KEY_EXIT_INSERT_CHAR 0x117 #define NE_KEY_CLEAR 0x118 /* Keypad keys */ #define NE_KEY_A1 0x120 #define NE_KEY_A3 0x121 #define NE_KEY_B2 0x122 #define NE_KEY_C1 0x123 #define NE_KEY_C3 0x124 /* Fake (simulated) command key. */ #define NE_KEY_COMMAND 0x125 /* The ignorable key. */ #define NE_KEY_IGNORE 0x126 /* Tab keys (never used in the standard configuration) */ #define NE_KEY_CLEAR_ALL_TABS 0x128 #define NE_KEY_CLEAR_TAB 0x129 #define NE_KEY_SET_TAB 0x12A /* Function keys */ #define NE_KEY_F0 0x140 #define NE_KEY_F(n) (NE_KEY_F0+(n)) /* Prefix-simulated META */ #define NE_KEY_META0 0x180 #define NE_KEY_META(n) (NE_KEY_META0+(n)) #define NE_KEY_BPASTE_BEGIN 0x200 #define NE_KEY_BPASTE_END 0x201 ne-3.3.4/src/keys.c000066400000000000000000000602561475116431000140440ustar00rootroot00000000000000/* Terminfo database scanning and keyboard escape sequence matching functions. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2025 Todd M. Lewis and Sebastiano Vigna This file is part of ne, the nice editor. This library 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 library 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 . */ #include "ne.h" #include "support.h" #include #include #include #include /* The keywords used in the configuration files. */ #define KEY_KEYWORD "KEY" #define SEQ_KEYWORD "SEQ" /* The name of the key bindings file. */ #define KEY_BINDINGS_NAME ".keys" /* Maximum number of key definitions from terminfo plus others we may get from keys file -- i.e. key_may_set(). */ #define MAX_TERM_KEY NUM_KEYS /* Size of the keyboard input buffer. */ #define KBD_BUF_SIZE 512 /* This structure describes a key in the terminfo database. These structures are ordered with respect to the string field to optimize their scanning. The order is *inverted* w.r.t. strcmp(). */ typedef struct { const char *string; int code; config_source source; } term_key; static term_key key[MAX_TERM_KEY]; static int num_keys; /* Function to pass to qsort for sorting the key capabilities array. */ static int keycmp(const void *t1, const void *t2) { return -strcmp(((term_key *)t1)->string, ((term_key *)t2)->string); } /* Search the key capability s in the ordered capability vector; if found at position pos return -pos-1 (i.e., always a negative number), otherwise return the correct place for insertion of s */ int binsearch(const char * const s) { if (num_keys) { int l = 0, m = 0, r = num_keys - 1; while(l <= r) { m = (l + r) / 2; if (is_prefix(s, key[m].string)) return -m - 1; if (strcmp(key[m].string, s) > 0) l = m + 1; else if (strcmp(key[m].string, s) < 0) r = m - 1; } return l; } else return 0; } #ifdef NE_TEST void escstrf(FILE *f, const char *p) { fprintf(f, "\""); while (*p) { if (*p == '"') fprintf(f, "\\\""); else if (isprint(*p)) fprintf(f, "%c", *p); else fprintf(f, "\\x%02x", *p); p++; } fprintf(f,"\""); } void dump_key_config(FILE *f) { int keynum; config_source prev_source = CONFIG_SOURCE_COUNT; for (keynum = 0; keynum < NUM_KEYS; keynum++) { if (key_binding[keynum] && key_binding[keynum][0]) { for (int seqnum = 0; seqnum < num_keys; seqnum++) { if (key[seqnum].code == keynum) { if (prev_source != key[seqnum].source) { fprintf(f, "\n# config source: %s\n", config_source_names[key_binding_source[keynum]]); prev_source = key[seqnum].source; } fprintf(f,"%s%s\t", key[seqnum].source == BUILT_IN ? "# " : "", SEQ_KEYWORD, config_source_names[key[seqnum].source]); escstrf(f, key[seqnum].string); fprintf(f,"\t%x\n", keynum); } } if (prev_source != key_binding_source[keynum]) { fprintf(f, "\n# config source: %s\n", config_source_names[key_binding_source[keynum]]); prev_source = key_binding_source[keynum]; } fprintf(f, "%s%s\t%4x\t%s\n", key_binding_source[keynum] == BUILT_IN ? "# " : "", KEY_KEYWORD, keynum, key_binding[keynum] ); } } } #endif #ifdef DEBUGPRINTF void dump_keys(void) { for (int i = 0; i < num_keys; i++) { char *p = key[i].string; fprintf(stderr, "%3d: \"", i); while (*p) { if (isprint(*p)) fprintf(stderr, "%c", *p); else fprintf(stderr, "\\x%02x", *p); p++; } fprintf (stderr,"\"\t-> %d (%s)\n", key[i].code, config_source_names[key[i].source]); } } #endif /* Sets the first free position in the key capabilities array to the cap_string capability, and increment the first free position counter. */ static void key_set(const char * const cap_string, const int code, config_source source) { if (!cap_string) return; key[num_keys].string = cap_string; key[num_keys].code = code; key[num_keys].source = source; num_keys++; } /* key_may_set() maps a key capability string to a key code number. It assumes the array is already sorted, and it keeps it that way. If the code number is positive and the cap_string is already in the key map, no mapping is done. If the code number is negative and the cap_string is already in the key vector, the matching code is replaced with the positive counterpart of the code passed in. This is part of the horrible hack to make cursor and function keys work on numerous terminals which have broken terminfo and termcap entries, or for weak terminal emulators which happen to produce well-known sequences. Returns > 0 on success, ==0 if table is full (or no cap_string supplied) < 0 if string was already defined. */ int key_may_set(const char * const cap_string, int code, config_source source) { int pos = 0; if (num_keys >= MAX_TERM_KEY - 1) return 0; if (!cap_string || (pos = binsearch(cap_string)) < 0) { if (code < 0) { key[-pos-1].code = -code - 1; key[-pos-1].source = source; } return pos; } if (code < 0) code = -code - 1; memmove(key + pos + 1, key + pos, (num_keys - pos) * sizeof *key); key[pos].string = cap_string; key[pos].code = code; key[pos].source = source; num_keys++; assert(num_keys < MAX_TERM_KEY); return pos+1; } /* Here we scan the terminfo database and build a term_key structure for each key available. num_keys records the number of entries. The array is sorted in reverse order with respect to string field (this optimizes the comparisons, assuming that usually almost all control sequences start with a character smaller than ' ', while the characters typed by the user are almost always greater than or equal to ' '). */ extern const char meta_prefixed[128][3]; void read_key_capabilities(void) { if (!ansi) { /* Cursor movement keys */ key_set(key_up, NE_KEY_UP, BUILT_IN); key_set(key_down, NE_KEY_DOWN, BUILT_IN); key_set(key_left, NE_KEY_LEFT, BUILT_IN); key_set(key_right, NE_KEY_RIGHT, BUILT_IN); key_set(key_home, NE_KEY_HOME, BUILT_IN); key_set(key_end, NE_KEY_END, BUILT_IN); key_set(key_npage, NE_KEY_NPAGE, BUILT_IN); key_set(key_ppage, NE_KEY_PPAGE, BUILT_IN); key_set(key_sf, NE_KEY_SCROLL_FORWARD, BUILT_IN); key_set(key_sr, NE_KEY_SCROLL_REVERSE, BUILT_IN); /* Editing keys */ key_set(key_eol, NE_KEY_CLEAR_TO_EOL, BUILT_IN); key_set(key_eos, NE_KEY_CLEAR_TO_EOS, BUILT_IN); key_set(key_backspace, NE_KEY_BACKSPACE, BUILT_IN); key_set(key_dl, NE_KEY_DELETE_LINE, BUILT_IN); key_set(key_il, NE_KEY_INSERT_LINE, BUILT_IN); key_set(key_dc, NE_KEY_DELETE_CHAR, BUILT_IN); key_set(key_ic, NE_KEY_INSERT_CHAR, BUILT_IN); key_set(key_eic, NE_KEY_EXIT_INSERT_CHAR, BUILT_IN); key_set(key_clear, NE_KEY_CLEAR, BUILT_IN); /* Keypad keys */ key_set(key_a1, NE_KEY_A1, BUILT_IN); key_set(key_a3, NE_KEY_A3, BUILT_IN); key_set(key_b2, NE_KEY_B2, BUILT_IN); key_set(key_c1, NE_KEY_C1, BUILT_IN); key_set(key_c3, NE_KEY_C3, BUILT_IN); /* Tab keys (never used in the standard configuration) */ key_set(key_catab, NE_KEY_CLEAR_ALL_TABS, BUILT_IN); key_set(key_ctab, NE_KEY_CLEAR_TAB, BUILT_IN); key_set(key_stab, NE_KEY_SET_TAB, BUILT_IN); /* Function keys */ key_set(key_f0, NE_KEY_F(0), BUILT_IN); key_set(key_f1, NE_KEY_F(1), BUILT_IN); key_set(key_f2, NE_KEY_F(2), BUILT_IN); key_set(key_f3, NE_KEY_F(3), BUILT_IN); key_set(key_f4, NE_KEY_F(4), BUILT_IN); key_set(key_f5, NE_KEY_F(5), BUILT_IN); key_set(key_f6, NE_KEY_F(6), BUILT_IN); key_set(key_f7, NE_KEY_F(7), BUILT_IN); key_set(key_f8, NE_KEY_F(8), BUILT_IN); key_set(key_f9, NE_KEY_F(9), BUILT_IN); key_set(key_f10, NE_KEY_F(10), BUILT_IN); key_set(key_f11, NE_KEY_F(11), BUILT_IN); key_set(key_f12, NE_KEY_F(12), BUILT_IN); key_set(key_f13, NE_KEY_F(13), BUILT_IN); key_set(key_f14, NE_KEY_F(14), BUILT_IN); key_set(key_f15, NE_KEY_F(15), BUILT_IN); key_set(key_f16, NE_KEY_F(16), BUILT_IN); key_set(key_f17, NE_KEY_F(17), BUILT_IN); key_set(key_f18, NE_KEY_F(18), BUILT_IN); key_set(key_f19, NE_KEY_F(19), BUILT_IN); key_set(key_f20, NE_KEY_F(20), BUILT_IN); key_set(key_f21, NE_KEY_F(21), BUILT_IN); key_set(key_f22, NE_KEY_F(22), BUILT_IN); key_set(key_f23, NE_KEY_F(23), BUILT_IN); key_set(key_f24, NE_KEY_F(24), BUILT_IN); key_set(key_f25, NE_KEY_F(25), BUILT_IN); key_set(key_f26, NE_KEY_F(26), BUILT_IN); key_set(key_f27, NE_KEY_F(27), BUILT_IN); key_set(key_f28, NE_KEY_F(28), BUILT_IN); key_set(key_f29, NE_KEY_F(29), BUILT_IN); key_set(key_f30, NE_KEY_F(30), BUILT_IN); key_set(key_f31, NE_KEY_F(31), BUILT_IN); key_set(key_f32, NE_KEY_F(32), BUILT_IN); key_set(key_f33, NE_KEY_F(33), BUILT_IN); key_set(key_f34, NE_KEY_F(34), BUILT_IN); key_set(key_f35, NE_KEY_F(35), BUILT_IN); key_set(key_f36, NE_KEY_F(36), BUILT_IN); key_set(key_f37, NE_KEY_F(37), BUILT_IN); key_set(key_f38, NE_KEY_F(38), BUILT_IN); key_set(key_f39, NE_KEY_F(39), BUILT_IN); key_set(key_f40, NE_KEY_F(40), BUILT_IN); key_set(key_f41, NE_KEY_F(41), BUILT_IN); key_set(key_f42, NE_KEY_F(42), BUILT_IN); key_set(key_f43, NE_KEY_F(43), BUILT_IN); key_set(key_f44, NE_KEY_F(44), BUILT_IN); key_set(key_f45, NE_KEY_F(45), BUILT_IN); key_set(key_f46, NE_KEY_F(46), BUILT_IN); key_set(key_f47, NE_KEY_F(47), BUILT_IN); key_set(key_f48, NE_KEY_F(48), BUILT_IN); key_set(key_f49, NE_KEY_F(49), BUILT_IN); key_set(key_f50, NE_KEY_F(50), BUILT_IN); key_set(key_f51, NE_KEY_F(51), BUILT_IN); key_set(key_f52, NE_KEY_F(52), BUILT_IN); key_set(key_f53, NE_KEY_F(53), BUILT_IN); key_set(key_f54, NE_KEY_F(54), BUILT_IN); key_set(key_f55, NE_KEY_F(55), BUILT_IN); key_set(key_f56, NE_KEY_F(56), BUILT_IN); key_set(key_f57, NE_KEY_F(57), BUILT_IN); key_set(key_f58, NE_KEY_F(58), BUILT_IN); key_set(key_f59, NE_KEY_F(59), BUILT_IN); key_set(key_f60, NE_KEY_F(60), BUILT_IN); key_set(key_f61, NE_KEY_F(61), BUILT_IN); key_set(key_f62, NE_KEY_F(62), BUILT_IN); key_set(key_f63, NE_KEY_F(63), BUILT_IN); } /* Fake (simulated) command key. */ key_set("\x1B:", NE_KEY_COMMAND, BUILT_IN); key_set(BPASTE_BEGIN_SEQ, NE_KEY_BPASTE_BEGIN, BUILT_IN); key_set(BPASTE_END_SEQ, NE_KEY_BPASTE_END, BUILT_IN); assert(num_keys < MAX_TERM_KEY - 1); D(fprintf(stderr, "Got %d keys from terminfo, etc.\n", num_keys);) qsort(key, num_keys, sizeof(term_key), keycmp); /* A nice hack for common cursor movements borrowed from pico. Unfortunately, quite a few terminfo and termcap entries out there have bad values for cursor key capability strings. (The f# values are generally in sad shape too, but that's a much larger problem.) However, certain escape sequences are quite common among large sets of terminals, and so we define the most common ones here. key_may_set() won't assign key cap strings if that sequence is already taken, so we shouldn't be doing too much damage if the terminfo or termcap happens to be correct. */ key_may_set("\x1b[A", NE_KEY_UP, BUILT_IN); key_may_set("\x1b?x", NE_KEY_UP, BUILT_IN); /* key_may_set("\x1b" "A", NE_KEY_UP, BUILT_IN);*/ key_may_set("\x1bOA", NE_KEY_UP, BUILT_IN); key_may_set("\x9b""A", NE_KEY_UP, BUILT_IN); key_may_set("\x1b[B", NE_KEY_DOWN, BUILT_IN); key_may_set("\x1b?r", NE_KEY_DOWN, BUILT_IN); /* key_may_set("\x1b" "B", NE_KEY_DOWN, BUILT_IN);*/ key_may_set("\x1bOB", NE_KEY_DOWN, BUILT_IN); key_may_set("\x9b""B", NE_KEY_DOWN, BUILT_IN); key_may_set("\x1b[D", NE_KEY_LEFT, BUILT_IN); key_may_set("\x1b?t", NE_KEY_LEFT, BUILT_IN); /*key_may_set("\x1b" "D", NE_KEY_LEFT, BUILT_IN);*/ key_may_set("\x1bOD", NE_KEY_LEFT, BUILT_IN); key_may_set("\x9b""D", NE_KEY_LEFT, BUILT_IN); key_may_set("\x1b[C", NE_KEY_RIGHT, BUILT_IN); key_may_set("\x1b?v", NE_KEY_RIGHT, BUILT_IN); /*key_may_set("\x1b" "C", NE_KEY_RIGHT, BUILT_IN);*/ key_may_set("\x1bOC", NE_KEY_RIGHT, BUILT_IN); key_may_set("\x9b""C", NE_KEY_RIGHT, BUILT_IN); key_may_set("\x1b[1~", NE_KEY_HOME, BUILT_IN); key_may_set("\x1b[4~", NE_KEY_END, BUILT_IN); key_may_set("\x1b[6~", NE_KEY_NPAGE, BUILT_IN); key_may_set("\x1b[5~", NE_KEY_PPAGE, BUILT_IN); key_may_set("\x1b[2~", NE_KEY_INSERT_CHAR, BUILT_IN); key_may_set("\x1b[3~", NE_KEY_DELETE_CHAR, BUILT_IN); key_may_set("\x1b[H", NE_KEY_HOME, BUILT_IN); key_may_set("\x1b[L", NE_KEY_INSERT_CHAR, BUILT_IN); /* gnome-terminal bizarre home/end keys */ key_may_set("\x1bOH", NE_KEY_HOME, BUILT_IN); key_may_set("\x1bOF", NE_KEY_END, BUILT_IN); /* The fundamental F1 escape key has been stolen by Gnome. We replace it with a double escape, if possible. */ key_may_set("\x1B\x1B", NE_KEY_F(1), BUILT_IN); /* More hacking. Function keys are routinely defined wrong on bazillions of systems. This sections codes the F1-F10 keys for vt100, xterms and PCs. I can't believe vendors can ship such buggy termcap/terminfo entries. This also handles the case of an otherwise limited terminal emulator which happens to produce these sequences for function keys. */ /* xterm fkeys: kf1=\E[11~ kf2=\E[12~ kf3=\E[13~ kf4=\E[14~ kf5=\E[15~ kf6=\E[17~ kf7=\E[18~ kf8=\E[19~ kf9=\E[20~ kf10=\E[21~ kf11=\E[23~ kf12=\E[24~ */ key_may_set("\x1b[11~", NE_KEY_F(1), BUILT_IN); key_may_set("\x1b[12~", NE_KEY_F(2), BUILT_IN); key_may_set("\x1b[13~", NE_KEY_F(3), BUILT_IN); key_may_set("\x1b[14~", NE_KEY_F(4), BUILT_IN); key_may_set("\x1b[15~", NE_KEY_F(5), BUILT_IN); key_may_set("\x1b[17~", NE_KEY_F(6), BUILT_IN); key_may_set("\x1b[18~", NE_KEY_F(7), BUILT_IN); key_may_set("\x1b[19~", NE_KEY_F(8), BUILT_IN); key_may_set("\x1b[20~", NE_KEY_F(9), BUILT_IN); key_may_set("\x1b[21~", NE_KEY_F(10), BUILT_IN); key_may_set("\x1b[23~", NE_KEY_F(11), BUILT_IN); key_may_set("\x1b[24~", NE_KEY_F(12), BUILT_IN); /* vt100 keys: k1=\EOP k2=\EOQ k3=\EOR k4=\EOS k5=\EOt k6=\EOu k7=\EOv k8=\EOl k9=\EOw k10=\EOy */ key_may_set("\x1bOP", NE_KEY_F(1), BUILT_IN); key_may_set("\x1bOQ", NE_KEY_F(2), BUILT_IN); key_may_set("\x1bOR", NE_KEY_F(3), BUILT_IN); key_may_set("\x1bOS", NE_KEY_F(4), BUILT_IN); key_may_set("\x1bOt", NE_KEY_F(5), BUILT_IN); key_may_set("\x1bOu", NE_KEY_F(6), BUILT_IN); key_may_set("\x1bOv", NE_KEY_F(7), BUILT_IN); key_may_set("\x1bOl", NE_KEY_F(8), BUILT_IN); key_may_set("\x1bOw", NE_KEY_F(9), BUILT_IN); key_may_set("\x1bOy", NE_KEY_F(10), BUILT_IN); /* pc keys: k1=\E[[A k2=\E[[B k3=\E[[C k4=\E[[D k5=\E[[E */ key_may_set("\x1b[[A", NE_KEY_F(1), BUILT_IN); key_may_set("\x1b[[B", NE_KEY_F(2), BUILT_IN); key_may_set("\x1b[[C", NE_KEY_F(3), BUILT_IN); key_may_set("\x1b[[D", NE_KEY_F(4), BUILT_IN); key_may_set("\x1b[[E", NE_KEY_F(5), BUILT_IN); /* If at this point any sequence of the form ESC+ASCII character is free, we bind it to the simulated META key. */ for(int i = 1; i < 128; i++) key_may_set(meta_prefixed[i], NE_KEY_META(i), BUILT_IN); #ifdef DEBUGPRINTF dump_keys(); #endif } /* Sets the escape time, which is an option, but it's global to ne and it's not saved in autopreferences files. However, an EscapeTime command can be attached manually to any preferences file. */ static int escape_time = 10; void set_escape_time(const int new_escape_time) { escape_time = new_escape_time; } /* Sets the current timeout in the termios structure relative to stdin. If the timeout value (in tenth of a second) is positive, VMIN is set to 0, otherwise to 1. */ static void set_termios_timeout(const int timeout) { struct termios termios; tcgetattr(0, &termios); termios.c_cc[VTIME] = timeout; termios.c_cc[VMIN] = timeout ? 0 : 1; tcsetattr(0, TCSANOW, &termios); } /* Reads in characters, and tries to match them with the sequences corresponding to special keys. Returns a positive number, denoting a character (possibly INVALID_CHAR), or a negative number denoting a key code (if x is the key code, -x-1 will be returned). This function tries to be highly optimized and efficient by employing a sorted array of strings for the terminal keys. An index keeps track of the key which has a partial match with the current contents of the keyboard buffer. As each character is input, a match is tried with the rest of the string. If a new character does not match, we can just increment the key counter (because the array is sorted). When we get out of the array, we give back the first char in the keyboard buffer (the next call will retry a match on the following chars). */ int get_key_code(void) { static int cur_len = 0; static char kbd_buffer[KBD_BUF_SIZE]; int c, e, last_match = 0, cur_key = 0; bool partial_match = false, partial_is_utf8 = false; while(true) { if (cur_len) { /* Something is already in the buffer. last_match is the position we have to check. */ while(last_match < cur_len) { if (last_match == 0 && io_utf8 && (unsigned char)kbd_buffer[0] >= 0x80) { partial_is_utf8 = true; last_match++; } else if (partial_is_utf8) { /* Our partial match is an UTF-8 sequence. */ if ((kbd_buffer[last_match] & 0xC0) == 0x80) { if (utf8len(kbd_buffer[0]) == ++last_match) { c = utf8char(kbd_buffer); if (cur_len -= last_match) memmove(kbd_buffer, kbd_buffer + last_match, cur_len); return c == -1 ? INVALID_CHAR : c; } } else { /* A UTF-8 error. We discard the first character and try again. */ if (--cur_len) memmove(kbd_buffer, kbd_buffer + 1, cur_len); partial_is_utf8 = false; last_match = 0; } } else { /* First easy case. We felt off the array. We return the first character in the buffer and restart the match. */ if (!key[cur_key].string) { c = kbd_buffer[0]; if (--cur_len) memmove(kbd_buffer, kbd_buffer + 1, cur_len); return (unsigned char)c; } /* Second case. We have a partial match on the first last_match characters. If another character matches, either the string is terminated, and we return the key code, or we increment the match count. */ else if (key[cur_key].string[last_match] == kbd_buffer[last_match]) { if (key[cur_key].string[last_match + 1] == 0) { if (cur_len -= last_match + 1) memmove(kbd_buffer, kbd_buffer + last_match + 1, cur_len); assert(key[cur_key].code < NUM_KEYS); return -key[cur_key].code - 1; } else last_match++; } /* The tricky part. If there is a failed match, the order guarantees that no match is possible if the code of the keyboard char is greater than the code of the capability char. Otherwise, we check for the first capability starting with the current keyboard characters. */ else { if ((unsigned char)kbd_buffer[last_match] > (unsigned char)key[cur_key].string[last_match]) { c = kbd_buffer[0]; if (--cur_len) memmove(kbd_buffer, kbd_buffer + 1, cur_len); return (unsigned char)c; } else { last_match = 0; cur_key++; } } } } /* If we have a partial match, let's look at stdin for escape_time tenths of second. If nothing arrives, it is probably time to return what we got. Note that this won't work properly if the terminal has a key capability which is a prefix of another key capability. */ partial_match = true; } fflush(stdout); if (partial_match) set_termios_timeout(escape_time); errno = 0; c = getchar(); e = errno; if (partial_match) set_termios_timeout(0); /* This is necessary to circumvent the slightly different behaviour of getc() in Linux and BSD. */ clearerr(stdin); if (c == EOF && (!partial_match || e) && e != EINTR) kill(getpid(), SIGTERM); partial_match = false; if (c != EOF) { if (cur_len < KBD_BUF_SIZE) kbd_buffer[cur_len++] = c; } else { if (cur_len) { /* We ran out of time. If our match was UTF-8, we discard the partially received UTF-8 sequence. Otherwise, we return the first character of the keyboard buffer. */ if (partial_is_utf8) cur_len = last_match = partial_is_utf8 = 0; else { c = kbd_buffer[0]; if (--cur_len) memmove(kbd_buffer, kbd_buffer + 1, cur_len); return (unsigned char)c; } } else return INVALID_CHAR; } } } static void error_in_key_bindings(const int line, const char * const s) { fprintf(stderr, "Error in key bindings file at line %d: %s\n", line, s); exit(0); } static void get_key_bind(const char * key_bindings_name, char * (exists_prefs_func)(), config_source source) { if (!key_bindings_name) key_bindings_name = KEY_BINDINGS_NAME; char * const prefs_dir = exists_prefs_func(); if (prefs_dir) { char * const key_bindings = malloc(strlen(prefs_dir) + strlen(key_bindings_name) + 1); if (key_bindings) { strcat(strcpy(key_bindings, prefs_dir), key_bindings_name); char_stream * const cs = load_stream(NULL, key_bindings, false, false); if (cs) { char * p = cs->stream; int line = 1; while(p - cs->stream < cs->len) { if (*p && !cmdcmp(KEY_KEYWORD, p)) { while(*p && !isasciispace(*p)) p++; int c; if (sscanf(p, "%x %*s", &c) == 1) { if (c >= 0 && c < NUM_KEYS) { if (c != 27 && c != 13) { while(isasciispace(*p)) p++; while(*p && !isasciispace(*p)) p++; while(isasciispace(*p)) p++; if (*p) { key_binding[c] = p; key_binding_source[c] = source; } else error_in_key_bindings(line, "no command specified."); } else error_in_key_bindings(line, "you cannot redefine ESCAPE and RETURN."); } else error_in_key_bindings(line, "key code out of range."); } else error_in_key_bindings(line, "can't read key code."); } else if (*p && !cmdcmp(SEQ_KEYWORD, p)) { char *buf; while(*p && !isasciispace(*p)) p++; /* skip past SEQ */ while(isasciispace(*p)) p++; /* skip to quoted sequence, like "\x1b[A" */ buf = p; /* Risky: we're replacing the double-quoted string with its parsed equivalent in situ. */ if (parse_string((unsigned char **)&p, (unsigned char *)buf, strlen(p)) > 0) { /* parse_string() expects double-quoted string. */ while(*p && isasciispace(*p)) p++; /* skip to key code */ int c; if (*p && sscanf(p, "%x %*s", &c) == 1) { /* convert key code */ if (c >= 0 && c < NUM_KEYS) { if (c != 27 && c != 13) { if (key_may_set(buf, -c - 1, source) == 0) error_in_key_bindings(line, "sequence table full." ); } else error_in_key_bindings(line, "you cannot redefine ESCAPE and RETURN."); } else error_in_key_bindings(line, "key code out of range."); } else error_in_key_bindings(line, "can't read key code."); } else error_in_key_bindings(line, "can't read double quoted character sequence."); } line++; p += strlen(p) + 1; } } free(key_bindings); } } } char *cur_dir(void) { static char *cur_dir = "./"; return cur_dir; } /* Key bindings override easily, so pull in any global bindings first, then override with the users bindings. */ void get_key_bindings(const char * key_bindings_name) { char * const term_name = getenv("TERM"), *key_bindings_term_name = NULL; if (!key_bindings_name) key_bindings_name = KEY_BINDINGS_NAME; key_bindings_term_name = malloc(strlen(key_bindings_name) + strlen(term_name) + 2); if (key_bindings_term_name) strcat(strcat(strcpy(key_bindings_term_name, key_bindings_name), "-"), term_name); for (int i = 0; i < NUM_KEYS; i++) key_binding_source[i] = BUILT_IN; get_key_bind(key_bindings_name, exists_gprefs_dir, GLOBAL_PREFS); get_key_bind(key_bindings_term_name, exists_gprefs_dir, GLOBAL_PREFS_TERM); get_key_bind(key_bindings_name, exists_prefs_dir, USER_PREFS); get_key_bind(key_bindings_term_name, exists_prefs_dir, USER_PREFS_TERM); get_key_bind(key_bindings_name, cur_dir, CUR_DIR); get_key_bind(key_bindings_term_name, cur_dir, CUR_DIR_TERM); free(key_bindings_term_name); } ne-3.3.4/src/makefile000066400000000000000000000135661475116431000144270ustar00rootroot00000000000000# # This is the makefile for ne. # # The default build relies on terminfo, but you can specify NE_TERMCAP=1 to # rely on termcap, or even NE_ANSI=1 to rely on termcap and to use by default # the built-in ANSI terminal description. For example: # # make # make NE_ANSI=1 # make NE_TERMCAP=1 # # If you use NE_ANSI=1 or NE_TERMCAP=1, no external library is needed. # # Specifying NE_DEBUG=1 will enable debugging info and will compile in a # number of assertions. Moreover, specifying NE_NOWCHAR=1 will remove the # calls to wide character versions of toupper(), tolower(), isspace(), etc., # and also calls to wcwidth(). # # The OPTS make parameter can be used to add options to the compilation # and linking phases. The options are added in the last position, so # they will override ne's default options, making the inclusion of # distribution-specific options easy for distro packagers. # # Defining ALTPAGING will create an ne with slightly different configuration: # - PageUp and ^p map to PageUp (rather than PrevPage) # - PageDown and ^n map to PageDown (rather than NextPage) # - Home and End map to MoveSOL and MoveEOL (rather than MoveIncUp and MoveIncDown) # - Key 1FF ("Esc Del", "[Del", or "Alt-Del") maps to AutoIndent # - (Shift-,Ctrl-)(F5,F6) shift left and right by (tabs,spaces) # - RequestOrder is set, so requesters display in column major order. # Some users may prefer this to the historical settings. # N.B.: This will cause ne's operation to differ slightly from the documentation. # # make OPTS=-DALTPAGING # # By changing the value of NE_GLOBAL_DIR, you can change the directory # where ne tries to read system-wide information (configuration # files, etc.). NE_GLOBAL_DIR ?= /usr/local/share/ne PROGRAM = ne OBJS = actions.o \ ansi.o \ autocomp.o \ buffer.o \ clips.o \ cm.o \ command.o \ display.o \ edit.o \ errors.o \ exec.o \ ext.o \ hash.o \ help.o \ input.o \ inputclass.o \ keys.o \ menu.o \ names.o \ navigation.o \ ne.o \ prefs.o \ regex.o \ request.o \ search.o \ signals.o \ streams.o \ support.o \ syn_hash.o \ syn_regex.o \ syn_utf8.o \ syn_utils.o \ syntax.o \ term.o \ undo.o \ utf8.o TERMCAPOBJS = tparam.o \ info2cap.o \ termcap.o NE_TERMCAP= NE_ANSI= NE_NOWCHAR= NE_DEBUG= NE_TEST= ifeq ($(origin CC),default) CC=c99 endif # Test for GCC ifneq (,$(findstring gcc,$(shell $(CC) 2>&1))) GCCFLAGS=-std=c99 -Wall -Wno-parentheses -fno-strict-aliasing -Wp,-D_FORTIFY_SOURCE=2 # link-time optimizer only on GCC 4.6+ GCC_VERSION_GT_46=$(shell expr `$(CC) -dumpversion` ">=" 4.6) ifeq ($(GCC_VERSION_GT_46),1) GCCFLAGS+= -flto=auto -ffat-lto-objects LDFLAGS=-flto=auto endif endif CFLAGS=$(GCCFLAGS) \ -D_REGEX_LARGE_OFFSETS -D_GNU_SOURCE -DSTDC_HEADERS -DHAVE_SNPRINTF \ $(if $(NE_NOWCHAR), -DNOWCHAR,) \ $(if $(NE_TEST), -DNE_TEST -coverage,) \ $(if $(NE_DEBUG), -g -O -fsanitize=address -fsanitize=undefined,-O3 -DNDEBUG) \ $(if $(NE_TERMCAP), -DNE_TERMCAP,) \ $(if $(NE_ANSI), -DNE_TERMCAP -DNE_ANSI,) \ $(OPTS) LIBS=$(if $(NE_TERMCAP)$(NE_ANSI),,-lcurses -lm) ne: $(OBJS) $(if $(NE_TERMCAP)$(NE_ANSI),$(TERMCAPOBJS),) $(CC) $(LDFLAGS) $(if $(NE_TEST), -coverage,) $(if $(NE_DEBUG), -fsanitize=address -fsanitize=undefined,) $^ $(LIBS) $(OPTS) -lm -o $(PROGRAM) clean: rm -f ne *.o *.gcda *.gcda.info *.gcno core really-clean: clean rm -f ne hash.h hash.c help.c help.h names.c names.h enums.h ext.c .PHONY: coverage coverage: geninfo . -rm -fr coverage -rm -f hash.gcda.info help.gcda.info inputclass.gcda.info names.gcda.info errors.gcda.info genhtml -o coverage *.info ### version.h ../doc/version.texinfo: (cd .. ; $(MAKE) version) ne.texinfo: -ln -sf ../doc/ne.texinfo version.texinfo: ../doc/version.texinfo -ln -sf ../doc/version.texinfo SYNH = syn_types.h syn_hash.h syn_regex.h syn_utf8.h syn_utils.h MAINH = ne.h debug.h enums.h errors.h utf8.h syntax.h keycodes.h termchar.h info2cap.h $(SYNH) ext.c: ext.c.in enums.h names.c names.h hash.c hash.h help.c help.h ext.c: ne.texinfo version.texinfo info2src.pl makeinfo -D autohelp ne.texinfo perl info2src.pl rm -f ne.info* actions.o: $(MAINH) support.h keycodes.h names.h names.c errors.h errors.c protos.h version.h autocomp.o: $(MAINH) support.h protos.h buffer.o: $(MAINH) support.h keycodes.h names.h errors.h protos.h clips.o: $(MAINH) support.h keycodes.h names.h errors.h protos.h cm.o: cm.h command.o: $(MAINH) support.h keycodes.h names.h errors.h protos.h help.h hash.h display.o: $(MAINH) support.h keycodes.h names.h errors.h protos.h termchar.h edit.o: $(MAINH) support.h keycodes.h names.h errors.h protos.h errors.o: errors.h exec.o: $(MAINH) keycodes.h names.h errors.h protos.h hash.o: hash.h info2cap.o: info2cap.h input.o: $(MAINH) support.h term.h keycodes.h names.h errors.h protos.h inputclass.o: $(MAINH) keycodes.h names.h errors.h protos.h keys.o: $(MAINH) keycodes.h names.h errors.h protos.h menu.o: $(MAINH) support.h term.h keycodes.h names.h errors.h protos.h navigation.o: $(MAINH) support.h keycodes.h names.h errors.h protos.h ne.o: $(MAINH) keycodes.h names.h errors.h protos.h version.h regex.h prefs.o: $(MAINH) support.h keycodes.h names.h errors.h protos.h regex.o: regex.h regex_internal.h regex_internal.c regexec.c regcomp.c request.o: $(MAINH) keycodes.h names.h errors.h protos.h search.o: $(MAINH) support.h keycodes.h names.h errors.h protos.h regex.h signals.o: $(MAINH) keycodes.h names.h errors.h protos.h streams.o: $(MAINH) keycodes.h names.h errors.h protos.h support.o: $(MAINH) support.h support.c keycodes.h names.h errors.h protos.h $(CC) $(CFLAGS) -DGLOBALDIR=\"$(NE_GLOBAL_DIR)\" -c support.c syntax.o: $(MAINH) syn_hash.o: $(SYNH) syn_regex.o: $(SYNH) syn_utf8.o: $(SYNH) syn_utils.o: $(SYNH) term.o: termchar.h term.h cm.h ansi.h termcap.o: termcap.h tparam.o: termcap.h undo.o: $(MAINH) support.h keycodes.h names.h errors.h protos.h utf8.o: utf8.h ne-3.3.4/src/menu.c000066400000000000000000000716651475116431000140430ustar00rootroot00000000000000/* Menu handling function. Includes also key and menu configuration parsing. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2025 Todd M. Lewis and Sebastiano Vigna This file is part of ne, the nice editor. This library 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 library 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 . */ #include "ne.h" #include "support.h" #include "termchar.h" /* The default number of menus. */ #define DEF_MENU_NUM 8 /* The number of extras spaces around each menu item, with and without standout. */ #define MENU_EXTRA 2 #define MENU_NOSTANDOUT_EXTRA 4 /* The maximum length of the status bar, excluding the file name. */ #define MAX_BAR_BUFFER_SIZE 128 /* The maximum length of the flag string. */ #define MAX_FLAG_STRING_SIZE 32 /* The maximum length of a message. */ #define MAX_MESSAGE_LENGTH 1024 /* The name of the menu configuration file. */ #define MENU_CONF_NAME ".menus" /* The keywords used in the configuration files. */ #define MENU_KEYWORD "MENU" #define ITEM_KEYWORD "ITEM" /* This structure defines a menu item. command_line points to the command line to be executed when the menu item is selected. */ typedef struct { const char *text; const char *command_line; } menu_item; /* This structure defines a menu. It contains number of items, the horizontal position of the menu, its width, the current item, the menu name and a pointer to the item array. Note that xpos has to be greater than zero. */ typedef struct { int item_num; int xpos, width; int cur_item; const char *text; const menu_item *items; } menu; #ifndef ALTPAGING #define PICK(A,B,C,D) {A,B}, #else #define PICK(A,B,C,D) {C,D}, #endif /* The following structures describe ne's standard menus. */ static menu_item file_item[] = { { "Open... ^O", OPEN_ABBREV }, { "Open New... [N", OPENNEW_ABBREV }, { "Save ^S", SAVE_ABBREV }, { "Save As... ", SAVEAS_ABBREV }, { "Save All ", SAVEALL_ABBREV }, { "Quit Now [Q", QUIT_ABBREV }, { "Save&Exit [X", EXIT_ABBREV }, { "About ", ABOUT_ABBREV } }; static const menu_item documents_item[] = { { "New ^D", NEWDOC_ABBREV }, { "Clear ", CLEAR_ABBREV }, { "Close ^Q", CLOSEDOC_ABBREV }, { "Next f2/[D", NEXTDOC_ABBREV }, { "Prev f3", PREVDOC_ABBREV }, { "Select... f4", SELECTDOC_ABBREV } }; static const menu_item edit_item[] = { { "Mark Block ^B", MARK_ABBREV }, { "Cut ^X", CUT_ABBREV }, { "Copy ^C", COPY_ABBREV }, { "Paste ^V", PASTE_ABBREV }, { "Mark Vert ^@", MARKVERT_ABBREV }, { "Paste Vert ^W", PASTEVERT_ABBREV }, { "Through [T", THROUGH_ABBREV }, { "Erase ", ERASE_ABBREV }, { "Delete EOL [Y", DELETEEOL_ABBREV }, { "Delete Line ^Y", DELETELINE_ABBREV }, { "Undel Line ^U", UNDELLINE_ABBREV }, { "Del Prev Word ", DELETEPREVWORD_ABBREV }, { "Del Next Word ", DELETENEXTWORD_ABBREV }, { "Open Clip [O", OPENCLIP_ABBREV }, { "Save Clip [S", SAVECLIP_ABBREV } }; static const menu_item search_item[] = { { "Find... ^F", FIND_ABBREV }, { "Find RegExp... ^_", FINDREGEXP_ABBREV }, { "Replace... ^R", REPLACE_ABBREV }, { "Replace Once... ", REPLACEONCE_ABBREV }, { "Replace All... ", REPLACEALL_ABBREV }, { "Repeat Last ^G", REPEATLAST_ABBREV }, { "Goto Line... ^J", GOTOLINE_ABBREV }, { "Goto Col... [J", GOTOCOLUMN_ABBREV }, { "Goto Mark ", GOTOMARK_ABBREV }, { "Goto Start Of Paste", "GBM <" }, { "Goto End Of Paste ", "GBM >" }, { "Match Bracket ^]", MATCHBRACKET_ABBREV }, { "Set Bookmark [K", SETBOOKMARK_ABBREV }, { "Goto Bookmark [G", GOTOBOOKMARK_ABBREV } }; static const menu_item macros_item[] = { { "Start/Stop Rec ^T", RECORD_ABBREV }, { "Record Cancel ", RECORD_CANCEL_ABBREV }, { "Record Append ", RECORD_APPEND_ABBREV }, { "Play Once f9/[M", PLAYONCE_ABBREV }, { "Play Many... ", PLAY_ABBREV }, { "Play Macro... ", MACRO_ABBREV }, { "Open Macro... ", OPENMACRO_ABBREV }, { "Save Macro... ", SAVEMACRO_ABBREV }, }; static const menu_item extras_item[] = { { "Exec... ^K", EXEC_ABBREV }, { "Suspend ^Z", SUSPEND_ABBREV }, { "Help... f10", HELP_ABBREV }, { "Refresh ^L", REFRESH_ABBREV }, { "Undo f5/[U", UNDO_ABBREV }, { "Redo f6/[R", REDO_ABBREV }, { "Center ", CENTER_ABBREV }, { "Shift Right ", SHIFT_ABBREV }, { "Shift Left ", SHIFTLEFT_ABBREV }, { "Paragraph [P", PARAGRAPH_ABBREV }, { "Name Convert ", NAMECONVERT_ABBREV }, { "ToUpper [V", TOUPPER_ABBREV }, { "ToLower [L", TOLOWER_ABBREV }, { "Capitalize ", CAPITALIZE_ABBREV }, { "AutoComplete [I", AUTOCOMPLETE_ABBREV }, { "UTF-8 ", UTF8_ABBREV } }; static const menu_item navigation_item[] = { { "Move Left ", MOVELEFT_ABBREV }, { "Move Right ", MOVERIGHT_ABBREV }, { "Line Up ", LINEUP_ABBREV }, { "Line Down ", LINEDOWN_ABBREV }, PICK( "Prev Page ^P", PREVPAGE_ABBREV , "Prev Page ", PREVPAGE_ABBREV) PICK( "Next Page ^N", NEXTPAGE_ABBREV , "Next Page ", NEXTPAGE_ABBREV) PICK( "Page Up ", PAGEUP_ABBREV , "Page Up ^P", PAGEUP_ABBREV) PICK( "Page Down ", PAGEDOWN_ABBREV , "Page Down ^N", PAGEDOWN_ABBREV) { "Start Of File [A", MOVESOF_ABBREV }, { "End Of File [E", MOVEEOF_ABBREV }, { "Start Of Line ^A", MOVESOL_ABBREV }, { "End Of Line ^E", MOVEEOL_ABBREV }, { "Top Of Screen ", MOVETOS_ABBREV }, { "Bottom Of Screen", MOVEBOS_ABBREV }, { "Adjust View ^^", ADJUSTVIEW_ABBREV }, { "Middle View [C", MIDDLEVIEW_ABBREV }, PICK( "Incr Up Home", MOVEINCUP_ABBREV , "Incr Up ", MOVEINCUP_ABBREV) PICK( "Incr Down End", MOVEINCDOWN_ABBREV , "Incr Down ", MOVEINCDOWN_ABBREV) { "Prev Word f7/[B", PREVWORD_ABBREV }, { "Next Word f8/[F", NEXTWORD_ABBREV } }; static const menu_item prefs_item[] = { { "Tab Size... ", TABSIZE_ABBREV }, { "Tabs as Spaces ", TABS_ABBREV }, { "Insert/Over Ins", INSERT_ABBREV }, { "Free Form ", FREEFORM_ABBREV }, { "Status Bar ", STATUSBAR_ABBREV }, { "Hex Code ", HEXCODE_ABBREV }, { "Fast GUI ", FASTGUI_ABBREV }, { "Word Wrap [W", WORDWRAP_ABBREV }, { "Right Margin ", RIGHTMARGIN_ABBREV }, PICK( "Auto Indent ", AUTOINDENT_ABBREV , "Auto Indent [Del", AUTOINDENT_ABBREV) { "Request Order ", REQUESTORDER_ABBREV }, { "Preserve CR ", PRESERVECR_ABBREV }, { "Save CR/LF [Z", CRLF_ABBREV }, { "Load Prefs... ", LOADPREFS_ABBREV }, { "Save Prefs... ", SAVEPREFS_ABBREV }, { "Load Auto Prefs ", LOADAUTOPREFS_ABBREV }, { "Save Auto Prefs ", SAVEAUTOPREFS_ABBREV }, { "Save Def Prefs ", SAVEDEFPREFS_ABBREV }, }; static menu def_menus[DEF_MENU_NUM] = { { sizeof(file_item) / sizeof(menu_item), 1, 14, 0, "File", file_item }, { sizeof(documents_item) / sizeof(menu_item), 6, 12, 0, "Documents", documents_item }, { sizeof(edit_item) / sizeof(menu_item), 16, 14, 0, "Edit", edit_item }, { sizeof(search_item) / sizeof(menu_item), 21, 19, 0, "Search", search_item }, { sizeof(macros_item) / sizeof(menu_item), 28, 17, 0, "Macros", macros_item }, { sizeof(extras_item) / sizeof(menu_item), 35, 15, 0, "Extras", extras_item }, { sizeof(navigation_item) / sizeof(menu_item), 42, 16, 0, "Navigation", navigation_item }, { sizeof(prefs_item) / sizeof(menu_item), 53, 16, 0, "Prefs", prefs_item } }; /* current_menu remembers the last menu activated. menu_num is the number of menus. */ static int current_menu, menu_num = DEF_MENU_NUM; /* menus points to an array of menu_num menu structures. */ static menu *menus = def_menus; #ifdef NE_TEST void dump_menu_config(FILE *f) { int menu, item, keynum; for (menu = 0; menu < menu_num; menu++) { fprintf(f, "%s \"%s\"\n", MENU_KEYWORD, menus[menu].text ); for (item = 0; item < menus[menu].item_num; item++) { fprintf(f, "%s \"%s\" \"%s\"\n", ITEM_KEYWORD, menus[menu].items[item].text, menus[menu].items[item].command_line); } fprintf(f, "\n"); } } #endif static void draw_cur_item(const int n) { move_cursor(menus[n].cur_item + 1, menus[n].xpos - (fast_gui || !standout_ok)); if (!fast_gui && standout_ok) output_chars(menus[n].items[menus[n].cur_item].text, NULL, menus[n].width - (cursor_on_off_ok ? 0 : 1), true); } static void undraw_cur_item(const int n) { if (!fast_gui && standout_ok) { set_attr(0); standout_on(); move_cursor(menus[n].cur_item + 1, menus[n].xpos); output_chars(menus[n].items[menus[n].cur_item].text, NULL, menus[n].width - (cursor_on_off_ok ? 0 : 1), true); standout_off(); } } /* Draws a given menu. It also draws the current menu item. */ static void draw_menu(const int n) { assert(menus[n].xpos > 0); if (menus[n].cur_item + 1 + (standout_ok == 0) >= ne_lines - 1) menus[n].cur_item = 0; move_cursor(0, menus[n].xpos); set_attr(0); output_string(menus[n].text, true); int i; for(i = 0; i < menus[n].item_num; i++) { if (i + 1 + (standout_ok == 0) >= ne_lines - 1) break; move_cursor(i + 1, menus[n].xpos - 1); if (!standout_ok) output_string("|", false); standout_on(); output_string(" ", false); output_string(menus[n].items[i].text, true); output_string(" ", false); standout_off(); if (!standout_ok) output_string("|", false); } if (!standout_ok) { move_cursor(i + 1, menus[n].xpos - 1); for(i = 0; i < menus[n].width + (standout_ok ? MENU_EXTRA : MENU_NOSTANDOUT_EXTRA); i++) output_string("-", false); } draw_cur_item(n); } /* Undraws a menu. This is obtained by refreshing part of the screen via output_line_desc(). */ static void undraw_menu(const int n) { set_attr(0); standout_on(); move_cursor(0, menus[n].xpos); output_string(menus[n].text, true); standout_off(); line_desc *ld = cur_buffer->top_line_desc; for(int i = 1; i <= menus[n].item_num + (standout_ok == 0); i++) { if (i >= ne_lines - 1) break; if (ld->ld_node.next->next) { ld = (line_desc *)ld->ld_node.next; if (cur_buffer->syn) parse(cur_buffer->syn, ld, ld->highlight_state, cur_buffer->encoding == ENC_UTF8); output_line_desc(i, menus[n].xpos - 1, ld, cur_buffer->win_x + menus[n].xpos - 1, menus[n].width + (standout_ok ? MENU_EXTRA : MENU_NOSTANDOUT_EXTRA), cur_buffer->opt.tab_size, false, cur_buffer->encoding == ENC_UTF8, cur_buffer->syn ? attr_buf : NULL, NULL, 0); } else { move_cursor(i, menus[n].xpos - 1); clear_to_eol(); } } } static void draw_next_item(void) { undraw_cur_item(current_menu); menus[current_menu].cur_item = (menus[current_menu].cur_item + 1) % menus[current_menu].item_num; if (menus[current_menu].cur_item + 1 + (standout_ok == 0) >= ne_lines - 1) menus[current_menu].cur_item = 0; draw_cur_item(current_menu); } static void draw_prev_item(void) { undraw_cur_item(current_menu); if (--(menus[current_menu].cur_item) < 0) menus[current_menu].cur_item = menus[current_menu].item_num - 1; if (menus[current_menu].cur_item + 1 + (standout_ok == 0) >= ne_lines - 1) menus[current_menu].cur_item = ne_lines - 3 - (standout_ok == 0) ; draw_cur_item(current_menu); } static void draw_item(const int item) { undraw_cur_item(current_menu); menus[current_menu].cur_item = item; draw_cur_item(current_menu); } static void draw_next_menu(void) { undraw_menu(current_menu); current_menu = (current_menu + 1) % menu_num; if (menus[current_menu].xpos >= ne_columns) current_menu = 0; draw_menu(current_menu); } static void draw_prev_menu(void) { undraw_menu(current_menu); if (--current_menu < 0) current_menu = menu_num - 1; while(menus[current_menu].xpos >= ne_columns) current_menu--; draw_menu(current_menu); } int search_menu_title(int n, const int c) { for(int i = 0; i < menu_num - 1; i++) { if (menus[++n % menu_num].xpos >= ne_columns) continue; if (menus[n % menu_num].text[0] == c) return n % menu_num; } return -1; } int search_menu_item(int n, int c) { c = toupper(c); for(int i = 0, j = menus[n].cur_item; i < menus[n].item_num - 1; i++) { if (++j % menus[n].item_num + 1 + (standout_ok == 0) >= ne_lines - 1) continue; if (menus[n].items[j % menus[n].item_num].text[0] == c) return j % menus[n].item_num; } return -1; } static void item_search(const int c) { int new_item; if (c >= 'a' && c <= 'z') { new_item = search_menu_item(current_menu, c); if (new_item >= 0) draw_item(new_item); } else if (c >= 'A' && c <= 'Z') { new_item = search_menu_title(current_menu, c); if (new_item >= 0) { undraw_menu(current_menu); current_menu = new_item; draw_menu(current_menu); } } } static void draw_first_menu(void) { move_cursor(0, 0); set_attr(0); standout_on(); if (!fast_gui && standout_ok) cursor_off(); for(int i = 0, n = 0; i < ne_columns; ) { output_string(" ", false); i++; if (n < menu_num) { output_string(menus[n].text, true); i += strlen(menus[n].text); n++; } } if (standout_ok) standout_off(); if (menus[current_menu].xpos >= ne_columns) current_menu = 0; draw_menu(current_menu); } static void undraw_last_menu(void) { undraw_menu(current_menu); cur_buffer->attr_len = -1; update_line(cur_buffer, cur_buffer->top_line_desc, 0, 0, false); cursor_on(); } static void do_menu_action(void) { undraw_last_menu(); print_error(execute_command_line(cur_buffer, menus[current_menu].items[menus[current_menu].cur_item].command_line)); } /* showing_msg tells draw_status_bar() that a message is currently shown, and should be cancelled only on the next refresh. Bar gone says that the status bar doesn't exists any longer, so we have to rebuild it entirely. */ static bool showing_msg; static bool bar_gone = true; /* Resets the status bar. It does not perform the refresh, just sets bar_gone to true. */ void reset_status_bar(void) { bar_gone = true; } /* This support function returns a copy of the status string which is never longer than MAX_FLAG_STRING_SIZE characters. The string is kept in a static buffer which is overwritten at each call. Note that the string includes a leading space. This way, if both the line numbers and the flags are updated the cursor does not need to be moved after printing the numbers (an operation which usually needs the output of several characters). */ static int mod_flag_index; char *gen_flag_string(const buffer * const b) { static char string[MAX_FLAG_STRING_SIZE]; const int ch = b->cur_pos >= 0 && b->cur_pos < b->cur_line_desc->line_len ? (b->encoding == ENC_UTF8 ? utf8char(&b->cur_line_desc->line[b->cur_pos]) : (unsigned char)b->cur_line_desc->line[b->cur_pos]) : -1; int i = 0; string[i++] = ' '; string[i++] = b->opt.insert ? 'i' : '-'; string[i++] = b->opt.auto_indent ? 'a' : '-'; string[i++] = b->opt.search_back ? 'b' : '-'; string[i++] = b->opt.case_search ? 'c' : '-'; string[i++] = b->opt.word_wrap ? 'w' : '-'; string[i++] = b->opt.free_form ? 'f' : '-'; string[i++] = b->opt.auto_prefs ? 'p' : '-'; string[i++] = verbose_macros ? 'v' : '-'; string[i++] = b->opt.do_undo ? (b->atomic_undo ? 'U' : 'u') : '-'; string[i++] = b->opt.read_only ? 'r' : '-'; string[i++] = b->opt.tabs ? (b->opt.shift_tabs ? 'T' : 't' ) : '-'; string[i++] = b->opt.del_tabs ? 'd' : '-'; string[i++] = b->opt.binary ? 'B' : ((line_desc *)b->line_desc_list.tail_pred)->line_len ? '!' : '-'; string[i++] = b->marking ? (b->mark_is_vertical ? 'V' :'M') : '-'; string[i++] = recording_macro ? 'R' : '-'; string[i++] = b->opt.preserve_cr ? 'P' : '-'; string[i++] = b->is_CRLF ? 'C' : '-'; string[i++] = io_utf8 ? '@' : '-'; string[i++] = b->encoding != ENC_8_BIT? (b->encoding == ENC_UTF8 ? 'U' : 'A') : '8'; mod_flag_index = i; string[i++] = b->is_modified ? '*' : '-'; if (b->opt.hex_code && !fast_gui) { string[i++] = ' '; if (ch > 0xFFFF) { string[i++] = "0123456789abcdef"[(ch >> 28) & 0x0f]; string[i++] = "0123456789abcdef"[(ch >> 24) & 0x0f]; string[i++] = "0123456789abcdef"[(ch >> 20) & 0x0f]; string[i++] = "0123456789abcdef"[(ch >> 16) & 0x0f]; } else for(int j = 0; j < 4; j++) string[i++] = ' '; if (ch > 0xFF) { string[i++] = "0123456789abcdef"[(ch >> 12) & 0x0f]; string[i++] = "0123456789abcdef"[(ch >> 8) & 0x0f]; } else for(int j = 0; j < 2; j++) string[i++] = ' '; if (ch > -1) { string[i++] = "0123456789abcdef"[(ch >> 4) & 0x0f]; string[i++] = "0123456789abcdef"[ch & 0x0f]; } else for(int j = 0; j < 2; j++) string[i++] = ' '; } string[i] = 0; assert(i < MAX_FLAG_STRING_SIZE); return string; } /* Draws the status bar. If showing_msg is true, it is set to false, bar_gone is set to true and the update is deferred to the next call. If the bar is not completely gone, we try to just update the line and column numbers, and the flags. The function keeps track internally of their last values, so that unnecessary printing is avoided. */ void draw_status_bar(void) { static char bar_buffer[MAX_BAR_BUFFER_SIZE]; static char flag_string[MAX_FLAG_STRING_SIZE]; static int64_t x = -1, y = -1; static int percent = -1; if (showing_msg) { showing_msg = false; bar_gone = true; return; } resume_status_bar = (void (*)(const char *message))&draw_status_bar; set_attr(0); int len; if (!bar_gone && status_bar) { const int new_percent = (int)floor(((cur_buffer->cur_line + 1) * 100.0) / cur_buffer->num_lines); /* This is the space occupied up to "L:", included. */ const int offset = fast_gui || !standout_ok ? 5: 3; const bool update_x = x != cur_buffer->win_x + cur_buffer->cur_x; const bool update_y = y != cur_buffer->cur_line; const bool update_percent = percent != new_percent; char *p; const bool update_flags = strcmp(flag_string, p = gen_flag_string(cur_buffer)); const bool update_filename = strlen(flag_string) != strlen(p); const bool update = update_x || update_y || update_percent || update_flags; if (!update) return; if (!fast_gui && standout_ok) standout_on(); x = cur_buffer->win_x + cur_buffer->cur_x; y = cur_buffer->cur_line; percent = new_percent; if (update_y) { move_cursor(ne_lines - 1, offset); len = sprintf(bar_buffer, "%11" PRId64, y + 1); output_chars(bar_buffer, NULL, len, true); } if (update_x) { move_cursor(ne_lines - 1, offset + 14); len = sprintf(bar_buffer, "%11" PRId64, x + 1); output_chars(bar_buffer, NULL, len, true); } if (update_percent) { move_cursor(ne_lines - 1, offset + 26); len = sprintf(bar_buffer, "%3d", percent); output_chars(bar_buffer, NULL, len, true); } if (update_flags) { strcpy(flag_string, p); move_cursor(ne_lines - 1, offset + 31); output_string(flag_string, true); if (buffer_file_modified(cur_buffer, NULL) && !fast_gui && underline_ok) { underline_on(); move_cursor(ne_lines - 1, offset + 31 + mod_flag_index); output_char(p[mod_flag_index], -1, false); underline_off(); } } if (!fast_gui && standout_ok) standout_off(); if (!update_filename) return; } if (status_bar) { percent = (int)floor(((cur_buffer->cur_line + 1) * 100.0) / cur_buffer->num_lines); move_cursor(ne_lines - 1, 0); if (!fast_gui && standout_ok) standout_on(); strcpy(flag_string, gen_flag_string(cur_buffer)); x = cur_buffer->win_x + cur_buffer->cur_x; y = cur_buffer->cur_line; len = sprintf(bar_buffer, fast_gui || !standout_ok ? ">> L:%11" PRId64 " C:%11" PRId64 " %3d%% %s " : " L:%11" PRId64 " C:%11" PRId64 " %3d%% %s ", y + 1, x + 1, percent, flag_string); move_cursor(ne_lines - 1, 0); output_chars(bar_buffer, NULL, len, true); if (len < ne_columns - 1) { if (cur_buffer->filename) { /* This is a bit complicated because we have to compute the width of the filename first, and then discard initial characters until the remaining part will fit. */ const int encoding = detect_encoding(cur_buffer->filename, strlen(cur_buffer->filename)); int pos = 0, width = get_string_width(cur_buffer->filename, strlen(cur_buffer->filename), encoding); while(width > ne_columns - 1 - len) { width -= get_char_width(&cur_buffer->filename[pos], encoding); pos = next_pos(cur_buffer->filename, pos, encoding); } output_string(cur_buffer->filename + pos, encoding == ENC_UTF8); } else output_string(UNNAMED_NAME, false); } if (!fast_gui && standout_ok) { output_spaces(ne_columns, NULL); standout_off(); } else clear_to_eol(); if (ne_columns > 55 && buffer_file_modified(cur_buffer, NULL) && !fast_gui && underline_ok) { move_cursor(ne_lines - 1, 54); standout_on(); underline_on(); output_char(flag_string[mod_flag_index], -1, false); underline_off(); standout_off(); } } else if (bar_gone) { move_cursor(ne_lines - 1, 0); clear_to_eol(); } bar_gone = false; } /* Prints a message over the status bar. It also sets showing_msg and bar_gone. If message is NULL and showing_msg is true, we reprint the last message. That necessitates caching the message when it isn't NULL. */ void print_message(const char * const message) { static char msg_cache[MAX_MESSAGE_LENGTH]; resume_status_bar = (void (*)(const char *message))&print_message; if (message) { strncpy(msg_cache, message, MAX_MESSAGE_LENGTH); msg_cache[MAX_MESSAGE_LENGTH - 1] = '\0'; } if (interactive_mode) { if (message || showing_msg) { move_cursor(ne_lines - 1, 0); set_attr(0); if (fast_gui || !standout_ok || !status_bar) { clear_to_eol(); output_string(msg_cache, true); } else { standout_on(); output_string(msg_cache, true); output_spaces(ne_columns - strlen(msg_cache), NULL); standout_off(); } fflush(stdout); showing_msg = true; } } } /* Prints an error on the status bar. error_num is a global error code. The function returns the error code passed, and does not do anything if the error code is OK or ERROR. */ int print_error(const int error_num) { assert(error_num < ERROR_COUNT); if (error_num > 0) { print_message(error_msg[error_num]); alert(); } return error_num; } /* Prints an information on the status bar. info_num is a global information code. Note that no beep is generated. */ void print_info(const int info_num) { assert(info_num < INFO_COUNT); print_message(info_msg[info_num]); } /* Rings a bell or flashes the screen, depending on the user preference. */ void alert(void) { if (cur_buffer->opt.visual_bell) do_flash(); else ring_bell(); } /* Handles the menu system: it displays the menus, parses the keyboard input, and eventually executes the correct command line. Note that we support ':' for going to the command line, alphabetic search (upper case for menus, lower case for items) and the cursor movement keys (by line, character, page). Note also the all other actions are executed, so that you can use shortcuts while using menus. */ void handle_menus(void) { draw_first_menu(); while(true) { int c; input_class ic; do c = get_key_code(); while((ic = CHAR_CLASS(c)) == IGNORE); if (window_changed_size) { window_changed_size = false; draw_first_menu(); continue; } switch(ic) { case INVALID: alert(); break; case TAB: draw_next_menu(); break; case ALPHA: if (c == ':') { undraw_last_menu(); do_action(cur_buffer, EXEC_A, -1, NULL); return; } item_search(c); break; case RETURN: do_menu_action(); return; case COMMAND: if (c < 0) c = -c - 1; int64_t n; char *p; const int a = parse_command_line(key_binding[c], &n, &p, false); if (a >= 0) { switch(a) { case MOVELEFT_A: draw_prev_menu(); break; case MOVERIGHT_A: draw_next_menu(); break; case LINEUP_A: draw_prev_item(); break; case LINEDOWN_A: draw_next_item(); break; case PREVPAGE_A: case PAGEUP_A: draw_item(0); break; case NEXTPAGE_A: case PAGEDOWN_A: draw_item(menus[current_menu].item_num - 1); break; case MOVESOL_A: if (current_menu != 0) { undraw_menu(current_menu); current_menu = 0; draw_menu(current_menu); } break; case MOVEEOL_A: if (current_menu != menu_num) { undraw_menu(current_menu); current_menu = menu_num - 1; draw_menu(current_menu); } break; case ESCAPE_A: undraw_last_menu(); return; default: undraw_last_menu(); print_error(do_action(cur_buffer, a, n, p)); return; } } break; default: break; } } } static void error_in_menu_configuration(const int line, const char * const s) { fprintf(stderr, "Error in menu configuration file at line %d: %s\n", line, s); exit(0); } static void get_menu_conf(const char * menu_conf_name, char * (exists_prefs_func)(), config_source source) { if (!menu_conf_name) menu_conf_name = MENU_CONF_NAME; menu *new_menus = NULL; menu_item *new_items = NULL; char * const prefs_dir = exists_prefs_func(); if (prefs_dir) { char * const menu_conf = malloc(strlen(prefs_dir) + strlen(menu_conf_name) + 1); if (menu_conf) { strcat(strcpy(menu_conf, prefs_dir), menu_conf_name); char_stream *cs; if ((cs = load_stream(NULL, menu_conf_name, false, false)) || (cs = load_stream(NULL, menu_conf, false, false))) { for(int pass = 0; pass < 2; pass++) { char *p = cs->stream; int line = 1; int cur_menu = -1; int cur_item = 0, num_items_in_menu = 0; while(p - cs->stream < cs->len) { if (*p) { if (!cmdcmp(MENU_KEYWORD, p)) { if (cur_menu < 0 || num_items_in_menu) { cur_menu++; num_items_in_menu = 0; if (pass) { while(*p && *p++ != '"'); if (*p) { new_menus[cur_menu].text = p; while(*p && *++p != '"'); if (*p) { *p++ = 0; if (cur_menu == 0) new_menus[0].xpos = 1; else new_menus[cur_menu].xpos = new_menus[cur_menu - 1].xpos + strlen(new_menus[cur_menu - 1].text) + 1; new_menus[cur_menu].items = &new_items[cur_item]; } else error_in_menu_configuration(line, "menu name has to end with quotes."); } else error_in_menu_configuration(line, "menu name has to start with quotes."); } } else if (cur_menu >= 0) error_in_menu_configuration(line - 1, "no items specified for this menu."); } else if (!cmdcmp(ITEM_KEYWORD, p)) { if (cur_menu < 0) error_in_menu_configuration(line, "no menu specified for this item."); if (pass) { while(*p && *p++ != '"'); if (*p) { new_items[cur_item].text = p; while(*p && *++p != '"'); if (*p) { *p++ = 0; if (num_items_in_menu == 0 || strlen(new_items[cur_item].text) == new_menus[cur_menu].width) { if (num_items_in_menu == 0) { if ((new_menus[cur_menu].width = strlen(new_items[cur_item].text)) == 0) error_in_menu_configuration(line, "menu item name width has to be greater than zero."); } while (isasciispace(*p)) p++; if (*p) { new_items[cur_item].command_line = p; new_menus[cur_menu].item_num = num_items_in_menu + 1; } else error_in_menu_configuration(line, "no command specified."); } else error_in_menu_configuration(line, "menu item name width has to be constant throughout the menu."); } else error_in_menu_configuration(line, "menu item name has to end with quotes."); } else error_in_menu_configuration(line, "menu item name has to start with quotes."); } num_items_in_menu++; cur_item++; } } line++; p += strlen(p) + 1; } if (pass == 0) { if (!num_items_in_menu) error_in_menu_configuration(line - 1, "no items specified for this menu."); if (cur_menu == -1 || cur_item == 0) error_in_menu_configuration(line, "no menus or items specified."); if (!(new_menus = calloc(cur_menu + 1, sizeof(menu))) || !(new_items = calloc(cur_item, sizeof(menu_item)))) error_in_menu_configuration(line, "not enough memory."); } else { menu_num = cur_menu + 1; menus = new_menus; } } } free(menu_conf); } } } /* Menu configs are all or nothing, so if the user has one, skip any global one. */ void get_menu_configuration(const char * menu_conf_name) { get_menu_conf(menu_conf_name, exists_prefs_dir, USER_PREFS); if (menus == def_menus) get_menu_conf(menu_conf_name, exists_gprefs_dir, GLOBAL_PREFS); } ne-3.3.4/src/navigation.c000066400000000000000000000745471475116431000152400ustar00rootroot00000000000000/* Navigation functions. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2025 Todd M. Lewis and Sebastiano Vigna This file is part of ne, the nice editor. This library 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 library 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 . */ #include "ne.h" #include "support.h" /* The functions in this file move the cursor. They also update the screen accordingly. There are some assumptions which are made in order to simplify the code: the TAB size has to be less than half the number of columns; and win_x has to be a multiple of the TAB size. The functions themselves are very simple; unfortunately, they are the kind of code filled up with +1 and -1 whose nature is not always obvious. Most functions do not have a description, because their name suggests their behaviour in an obvious way. (Yeah, right.) */ /********************************************************************************* You stand absolutely no chance of understanding this code if you aren't intimately familiar with this diagram. |<- cur_pos (in bytes)-->| |<- cur_char (in chars)->| |< cur_x >-----| |< win_x >| --- +----------------------------------------------+ --- ----- | | File boundary | | | win_y | | | | | | | | | --- --- | +-------------------------+ | | | ---- | | | Screen boundary | | cur_line | | cur_y | | | | | | | | | | | | | | | | | | | | | | | --- | | @ <-Cursor | | --- | ne_lines | | | | num_lines | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +-------------------------+ | | ---- | | | +----------------------------------------------+ ----- |<---- ne_columns ------->| ************************************************************************************/ /* "Resyncs" cur_pos (the current character the cursor is on) with cur_x and win_x. It has to take into account the TAB expansion, and can cause left/right movement in order to properly land on a real character. x is the offset from the beginning of the line after TAB expansion. resync_pos() assumes that tab_size < columns/2. Note that this function has to be called whenever the cursor is moved to a different line, keeping the x position constant. The only way of avoiding this problem is not supporting TABs, which is of course unacceptable. Note that if x_wanted is true, then the wanted_x position is used rather tham cur_x+win_x. */ void resync_pos(buffer * const b) { int64_t x = b->win_x + b->cur_x; if (b->x_wanted) x = b->wanted_x; assert(b->opt.tab_size < ne_columns / 2); if (x == 0) { b->cur_pos = b->cur_char = 0; return; } line_desc *ld = b->cur_line_desc; int64_t i, pos, width, last_char_width; for(i = pos = width = 0; pos < ld->line_len; pos = next_pos(ld->line, pos, b->encoding), i++) { if (ld->line[pos] != '\t') width += (last_char_width = get_char_width(&ld->line[pos], b->encoding)); else width += (last_char_width = b->opt.tab_size - width % b->opt.tab_size); if (width == x) { b->cur_pos = pos + (b->encoding == ENC_UTF8 ? utf8len(ld->line[pos]) : 1); b->cur_char = i + 1; if (b->x_wanted) { b->x_wanted = 0; if (x - b->win_x < ne_columns) b->cur_x = x - b->win_x; else { b->win_x = x - ne_columns + 1; b->win_x += b->opt.tab_size - b->win_x % b->opt.tab_size; b->cur_x = x - b->win_x; if (b == cur_buffer) update_window(b); } } return; } if (width > x) { b->cur_pos = pos; b->cur_char = i; width -= last_char_width; b->x_wanted = 1; b->wanted_x = x; if (width - b->win_x < 0) { /* We are on a character which is only partially on the screen (more precisely, its right margin is not). We shift the screen to the left. */ assert(b->win_x > 0); b->win_x = max(0, width - ne_columns + 1); b->win_x -= b->win_x % b->opt.tab_size; b->cur_x = width - b->win_x; if (b == cur_buffer) update_window(b); } else if (width - b->win_x < ne_columns) b->cur_x = width - b->win_x; else { b->win_x = width - ne_columns; b->win_x += b->opt.tab_size - b->win_x % b->opt.tab_size; b->cur_x = width - b->win_x; if (b == cur_buffer) update_window(b); } return; } } if (b->opt.free_form) { b->cur_pos = ld->line_len + x - width; b->cur_char = i + x - width; b->cur_x = x - b->win_x; b->x_wanted = 0; } else { b->wanted_x = x; move_to_eol(b); b->x_wanted = 1; } } int line_up(buffer * const b) { b->y_wanted = 0; if (b->cur_y > 0) { update_syntax_states(b, b->cur_y, b->cur_line_desc, NULL); b->cur_y--; b->cur_line--; b->cur_line_desc = (line_desc *)b->cur_line_desc->ld_node.prev; b->attr_len = -1; resync_pos(b); return OK; } else { if (b->win_y > 0) { update_syntax_states(b, b->cur_y, b->cur_line_desc, NULL); b->win_y--; b->cur_line--; b->cur_line_desc = (line_desc *)b->cur_line_desc->ld_node.prev; b->top_line_desc = (line_desc *)b->top_line_desc->ld_node.prev; b->attr_len = -1; if (b == cur_buffer) scroll_window(b, b->top_line_desc, 0, 1); resync_pos(b); return OK; } } return ERROR; } int line_down(buffer * const b) { b->y_wanted = 0; if (b->cur_y < ne_lines - 2 && b->cur_line < b->num_lines - 1) { update_syntax_states(b, b->cur_y, b->cur_line_desc, NULL); b->cur_y++; b->cur_line++; b->attr_len = -1; b->cur_line_desc = (line_desc *)b->cur_line_desc->ld_node.next; resync_pos(b); return OK; } else { if (b->win_y < b->num_lines - ne_lines + 1) { update_syntax_states(b, b->cur_y, b->cur_line_desc, NULL); b->win_y++; b->cur_line++; b->cur_line_desc = (line_desc *)b->cur_line_desc->ld_node.next; b->top_line_desc = (line_desc *)b->top_line_desc->ld_node.next; b->attr_len = -1; if (b == cur_buffer) scroll_window(b, b->top_line_desc, 0, -1); resync_pos(b); return OK; } } return ERROR; } /* This has to be done whenever we switch to a different buffer * because the screen may have been resized since the last time we * were here. */ void keep_cursor_on_screen(buffer * const b) { b->opt.tab_size = min(b->opt.tab_size, max(ne_columns / 2 - 1, 1)); const int shift_right = b->win_x % b->opt.tab_size; if (shift_right) { b->win_x -= shift_right; b->cur_x += shift_right; } if (b->cur_y > ne_lines - 2) { while(b->cur_y > ne_lines - 2) { b->cur_y--; b->win_y++; b->attr_len = -1; b->top_line_desc = (line_desc *)b->top_line_desc->ld_node.next; } assert(b->win_y = b->cur_line - b->cur_y); b->y_wanted = false; } while(b->cur_x >= ne_columns) { b->win_x += b->opt.tab_size; b->cur_x -= b->opt.tab_size; } } /* Moves win_x of n bytes to the left (n *has* to be a multiple of the current TAB size). It is used by char_left(). cur_x is moved, too. */ static void block_left(buffer * const b, const int n) { const int64_t t = b->win_x; assert(n <= ne_columns); assert(!(n % b->opt.tab_size)); if ((b->win_x -= n) < 0) b->win_x = 0; b->cur_x += t - b->win_x; if (b == cur_buffer) update_window(b); } int char_left(buffer * const b) { line_desc *ld = b->cur_line_desc; assert(ld != NULL); assert_line_desc(ld, b->encoding); b->x_wanted = 0; b->y_wanted = 0; if (b->cur_pos > 0) { int disp = ld->line && b->cur_pos <= ld->line_len ? get_char_width(&ld->line[prev_pos(ld->line, b->cur_pos, b->encoding)], b->encoding) : 1; if (b->cur_pos <= ld->line_len && ld->line[b->cur_pos - 1] == '\t') disp = b->opt.tab_size - calc_width(ld, b->cur_pos - 1, b->opt.tab_size, b->encoding) % b->opt.tab_size; if (b->cur_x < disp) block_left(b, b->opt.tab_size * 2); b->cur_x -= disp; /* If the buffer is UTF-8 encoded, we move back until we find a sequence initiator. */ b->cur_pos = b->cur_pos > ld->line_len ? b->cur_pos - 1 : prev_pos(ld->line, b->cur_pos, b->encoding); b->cur_char--; return OK; } else if (b->cur_line > 0) { line_up(b); move_to_eol(b); return OK; } return ERROR; } /* Same as block_left(), but to the right. */ static void block_right(buffer * const b, const int n) { assert(n <= ne_columns); assert(!(n % b->opt.tab_size)); b->win_x += n; b->cur_x -= n; if (b == cur_buffer) update_window(b); } int char_right(buffer * const b) { const line_desc * const ld = b->cur_line_desc; int disp = ld->line && b->cur_pos < ld->line_len ? get_char_width(&ld->line[b->cur_pos], b->encoding) : 1; assert(ld != NULL); assert_line_desc(ld, b->encoding); if (ld->line && b->cur_pos < ld->line_len && ld->line[b->cur_pos] == '\t') disp = b->opt.tab_size - calc_width(ld, b->cur_pos, b->opt.tab_size, b->encoding) % b->opt.tab_size; b->x_wanted = 0; b->y_wanted = 0; if (b->cur_pos == ld->line_len && !b->opt.free_form) { if (!ld->ld_node.next->next) return ERROR; move_to_sol(b); line_down(b); return OK; } b->cur_x += disp; b->cur_pos = b->cur_pos >= ld->line_len ? b->cur_pos + 1 : next_pos(ld->line, b->cur_pos, b->encoding); b->cur_char++; /* If the current x position would be beyond the right screen margin, or if the same happens for the character we are currently over, we shift the screen to the right. */ if (b->cur_x >= ne_columns || ld->line && b->cur_pos < ld->line_len && b->cur_x + get_char_width(&ld->line[b->cur_pos], b->encoding) > ne_columns) block_right(b, b->opt.tab_size * 2); return OK; } int prev_page(buffer * const b) { b->y_wanted = 0; if (b->cur_y > 0) { update_syntax_states(b, b->cur_y, b->cur_line_desc, NULL); b->cur_line -= b->cur_y; b->cur_y = 0; b->cur_line_desc = b->top_line_desc; b->attr_len = -1; resync_pos(b); return OK; } if (b->win_y == 0) return ERROR; update_syntax_states(b, -1, b->cur_line_desc, NULL); b->attr_len = -1; if ((b->win_y -= ne_lines - 2)<0) b->win_y = 0; line_desc *ld_top = b->top_line_desc; line_desc *ld_cur = b->cur_line_desc; for(int i = 0; i < ne_lines - 2 && ld_top->ld_node.prev->prev; i++) { ld_top = (line_desc *)ld_top->ld_node.prev; ld_cur = (line_desc *)ld_cur->ld_node.prev; b->cur_line--; } b->top_line_desc = ld_top; b->cur_line_desc = ld_cur; if (b == cur_buffer) update_window(b); resync_pos(b); return ERROR; } int next_page(buffer * const b) { b->y_wanted = 0; if (b->cur_y < ne_lines - 2) { update_syntax_states(b, b->cur_y, b->cur_line_desc, NULL); line_desc *ld_cur; if (b->win_y >= b->num_lines - (ne_lines - 1)) { ld_cur = b->top_line_desc; int i; for(i = 0; i < ne_lines - 2 && ld_cur->ld_node.next->next; i++) ld_cur = (line_desc *)ld_cur->ld_node.next; b->cur_line += (i - b->cur_y); b->cur_y = i; } else { b->cur_line += (ne_lines - 2 - b->cur_y); b->cur_y = ne_lines - 2; ld_cur = b->top_line_desc; for(int i = 0; i < ne_lines - 2; i++) ld_cur = (line_desc *)ld_cur->ld_node.next; } b->attr_len = -1; b->cur_line_desc = ld_cur; resync_pos(b); return OK; } if (b->win_y >= b->num_lines - (ne_lines - 1)) return ERROR; update_syntax_states(b, -1, b->cur_line_desc, NULL); b->attr_len = -1; int disp = ne_lines - 2; if (b->win_y + disp > b->num_lines - (ne_lines - 1)) disp = b->num_lines - (ne_lines - 1) - b->win_y; b->win_y += disp; b->cur_line += disp; line_desc *ld_top = b->top_line_desc; line_desc *ld_cur = b->cur_line_desc; for(int i = 0; i < disp && ld_top->ld_node.next->next; i++) { ld_top = (line_desc *)ld_top->ld_node.next; ld_cur = (line_desc *)ld_cur->ld_node.next; } b->top_line_desc = ld_top; b->cur_line_desc = ld_cur; if (b == cur_buffer) update_window(b); resync_pos(b); return OK; } int page_up(buffer * const b) { /* Already on the top line? */ if (b->cur_line == 0) return OK; update_syntax_states(b, -1, b->cur_line_desc, NULL); b->attr_len = -1; if (!b->y_wanted) { b->y_wanted = true; b->wanted_y = b->cur_line; b->wanted_cur_y = b->cur_y; } for (int i = 0; i < ne_lines - 2; i++) { b->wanted_y--; /* We want to move up */ /* Can we move up? */ if (b->wanted_y >= 0 /* We aren't yet off the top */ && b->wanted_y < b->num_lines - 1) { /* we aren't still past the end */ b->cur_line_desc = (line_desc *)b->cur_line_desc->ld_node.prev; b->cur_line--; } /* Should we shift the view up? */ if (b->win_y > 0 /* We aren't already at the top */ && b->win_y + b->wanted_cur_y > b->wanted_y) { /* Gap between virtual cursor and TOS is to small */ b->top_line_desc = (line_desc *)b->top_line_desc->ld_node.prev; b->win_y--; } } b->cur_y = b->cur_line - b->win_y; keep_cursor_on_screen(b); if (b == cur_buffer) update_window(b); resync_pos(b); return OK; } int page_down(buffer * const b) { /* Already on the bottom line? */ if (b->cur_line == b->num_lines - 1) return OK; update_syntax_states(b, -1, b->cur_line_desc, NULL); b->attr_len = -1; if (!b->y_wanted) { b->y_wanted = true; b->wanted_y = b->cur_line; b->wanted_cur_y = b->cur_y; } const int disp = ne_lines - 2; const int shift_view = (b->win_y + disp < b->num_lines); /* can't already see the last line */ for (int i = 0; i < disp; i++) { b->wanted_y++; /* We want to move down */ /* Can we move down? */ if (b->wanted_y > 0 /* We aren't still above the top */ && b->wanted_y < b->num_lines) { /* we aren't yet to the end */ b->cur_line_desc = (line_desc *)b->cur_line_desc->ld_node.next; b->cur_line++; } /* Should we shift the view down? */ if (shift_view /* already decided we should */ && b->wanted_y - b->wanted_cur_y > b->win_y) { /* Gap between virtual cursor and TOS is to big */ b->top_line_desc = (line_desc *)b->top_line_desc->ld_node.next; b->win_y++; } } b->cur_y = b->cur_line - b->win_y; keep_cursor_on_screen(b); if (b == cur_buffer) update_window(b); resync_pos(b); return OK; } int move_tos(buffer * const b) { b->y_wanted = 0; if (b->cur_y > 0) { update_syntax_states(b, b->cur_y, b->cur_line_desc, NULL); b->cur_line -= b->cur_y; b->cur_y = 0; b->cur_line_desc = b->top_line_desc; b->attr_len = -1; resync_pos(b); } return OK; } int move_bos(buffer * const b) { b->y_wanted = 0; if (b->cur_y < ne_lines - 2) { update_syntax_states(b, b->cur_y, b->cur_line_desc, NULL); b->attr_len = -1; line_desc *ld_cur; if (b->win_y >= b->num_lines - (ne_lines - 1)) { ld_cur = b->top_line_desc; int i; for(i = 0; i < ne_lines - 2 && ld_cur->ld_node.next->next; i++) ld_cur = (line_desc *)ld_cur->ld_node.next; b->cur_line += (i - b->cur_y); b->cur_y = i; } else { b->cur_line += (ne_lines - 2 - b->cur_y); b->cur_y = ne_lines - 2; ld_cur = b->top_line_desc; for(int i = 0; i < ne_lines - 2; i++) ld_cur = (line_desc *)ld_cur->ld_node.next; } b->cur_line_desc = ld_cur; resync_pos(b); } return OK; } /* adjust_view() never moves the cursor. It is only concerned with shifting win_x, cur_x, win_y and cur_y -- the variables which control which part of the file is visible in the terminal window. */ int adjust_view(buffer * const b, const char *p) { b->y_wanted = 0; if (!p) p = "t"; int rc = OK; while(*p) { int disp = 0; char *q; int mag = max(0, strtol(p+1, &q, 0)); switch (*p) { case 't' : case 'T' : /* Shift the view so that the current line is displayed at the top. */ disp = mag ? -min(mag, b->cur_y) : -b->cur_y; break; case 'm' : case 'M' : /* Shift the view so that the current line is displayed at the center. */ disp = (ne_lines - 2) / 2 - b->cur_y; break; case 'b' : case 'B' : /* Shift the view so that the current line is displayed at the bottom. */ disp = mag ? min(mag, (ne_lines -2) - b->cur_y) : (ne_lines - 2) - b->cur_y; break; case 'l' : case 'L' : /* Shift the view as far left as possible, or mag columns. */ if (mag == 0) mag = b->cur_x; while (b->cur_x >= b->opt.tab_size && mag > 0) { b->win_x += b->opt.tab_size; b->cur_x -= b->opt.tab_size; mag -= b->opt.tab_size; } break; case 'c' : case 'C' : /* Shift the view as far left as possible. This way we don't have to deal with figuring out which side of Middle the view started on. */ while (b->cur_x >= b->opt.tab_size) { b->win_x += b->opt.tab_size; b->cur_x -= b->opt.tab_size; } /* Since we now know that the cursor is left of center, we can start to shift the view right until the cursor is centered or until we run out of text to shift right. */ while (b->cur_x < (ne_columns / 2) - (ne_columns / 2) % b->opt.tab_size && b->win_x >= b->opt.tab_size) { b->win_x -= b->opt.tab_size; b->cur_x += b->opt.tab_size; } break; case 'r' : case 'R' : /* Shift the view as far right as possible, or mag columns. */ if (mag == 0) mag = b->win_x; while (b->cur_x < ne_columns - b->opt.tab_size && b->win_x >= b->opt.tab_size && mag > 0) { mag -= b->opt.tab_size; b->win_x -= b->opt.tab_size; b->cur_x += b->opt.tab_size; } break; default : /* When we hit a character we don't recognize, we set the rc, but we still process other valid view displacements. */ rc = ERROR; break; } if (disp > 0) { for(int i = 0; i < disp && b->top_line_desc->ld_node.prev->prev; i++) { b->win_y--; b->cur_y++; b->top_line_desc = (line_desc *)b->top_line_desc->ld_node.prev; } } else if (disp < 0) { for(int i = 0; i > disp && b->top_line_desc->ld_node.next->next; i--) { b->win_y++; b->cur_y--; b->top_line_desc = (line_desc *)b->top_line_desc->ld_node.next; } } p = q; } if (b == cur_buffer) update_window(b); resync_pos(b); return rc; } /* Moves to a given line. The comments to goto_line_pos() apply. */ void goto_line(buffer * const b, const int64_t n) { goto_line_pos(b, n, -1); } /* Moves to a given line and byte position, unless the byte position is -1, in which case moves to a given line and calls resync_pos(). Note that this function will call update_syntax_states(), but with row parameter -1. */ void goto_line_pos(buffer * const b, const int64_t n, const int64_t pos) { b->y_wanted = 0; if (n >= b->num_lines || n == b->cur_line && pos == b->cur_pos) return; if (n == b->cur_line && pos == -1) { resync_pos(b); return; } line_desc *ld; if (n >= b->win_y && n < b->win_y + ne_lines - 1) { if (n != b->win_y + b->cur_y) update_syntax_states(b, -1, b->cur_line_desc, NULL); b->attr_len = -1; b->cur_y = n - b->win_y; b->cur_line = n; ld = b->top_line_desc; for(int64_t i = 0; i < b->cur_y; i++) ld = (line_desc *)ld->ld_node.next; b->cur_line_desc = ld; if (pos != -1) goto_pos(b, pos); else resync_pos(b); return; } update_syntax_states(b, -1, b->cur_line_desc, NULL); b->attr_len = -1; b->win_y = n - (ne_lines - 1) / 2; if (b->win_y > b->num_lines - (ne_lines - 1)) b->win_y = b->num_lines - (ne_lines - 1); if (b->win_y < 0) b->win_y = 0; b->cur_y = n - b->win_y; ld = nth_line_desc(b, n); b->cur_line = n; b->cur_line_desc = ld; for(int i = 0; i < b->cur_y; i++) ld = (line_desc *)ld->ld_node.prev; b->top_line_desc = ld; if (b == cur_buffer) update_window(b); if (pos != -1) goto_pos(b, pos); else resync_pos(b); } void goto_column(buffer * const b, const int64_t n) { b->x_wanted = 0; b->y_wanted = 0; if (n == b->win_x + b->cur_x) { if (b->cur_pos == -1) resync_pos(b); return; } if (n >= b->win_x && n < b->win_x + ne_columns) { b->cur_x = n - b->win_x; resync_pos(b); return; } if ((b->win_x = n - ne_columns / 2)<0) b->win_x = 0; b->win_x -= b->win_x % b->opt.tab_size; b->cur_x = n - b->win_x; resync_pos(b); if (b == cur_buffer) update_window(b); } /* This is like a goto_column(), but you specify a position (i.e., a byte offset) instead. The specified position is arbitrary, and it will be rounded to the next legal position if it lands in the middle of a UTF-8 character. */ void goto_pos(buffer * const b, const int64_t pos) { const line_desc * const ld = b->cur_line_desc; const int64_t last_pos = min(pos, ld->line_len); int64_t col, real_pos; for(col = real_pos = b->cur_char = 0; real_pos < last_pos; b->cur_char++, real_pos = next_pos(ld->line, real_pos, b->encoding)) { if (ld->line[real_pos] != '\t') col += get_char_width(&ld->line[real_pos], b->encoding); else col += b->opt.tab_size - col % b->opt.tab_size; } b->x_wanted = 0; if (pos > ld->line_len) { if (b->opt.free_form) { col += pos - ld->line_len; b->cur_char += pos - ld->line_len; b->cur_pos = pos; } else { b->cur_pos = ld->line_len; b->x_wanted = 1; b->wanted_x = col + pos - ld->line_len; } } else b->cur_pos = real_pos; if (col == b->win_x + b->cur_x) return; if (col >= b->win_x && col < b->win_x + ne_columns) { b->cur_x = col - b->win_x; return; } if ((b->win_x = col - ne_columns / 2)<0) b->win_x = 0; b->win_x -= b->win_x % b->opt.tab_size; b->cur_x = col - b->win_x; if (b == cur_buffer) update_window(b); } void move_to_sol(buffer * const b) { b->x_wanted = 0; b->y_wanted = 0; const bool update = b->win_x && b == cur_buffer; b->win_x = b->cur_x = b->cur_pos = b->cur_char = 0; if (update) update_window(b); } void move_to_eol(buffer * const b) { line_desc *ld = b->cur_line_desc; assert(ld->ld_node.next != NULL); assert((ld->line != NULL) == (ld->line_len != 0)); b->x_wanted = 0; b->y_wanted = 0; if (!ld->line) { move_to_sol(b); return; } const int64_t total_width = calc_width(ld, ld->line_len, b->opt.tab_size, b->encoding); if (total_width >= b->win_x && total_width < b->win_x + ne_columns) { /* We move to a visible position. */ b->cur_x = total_width - b->win_x; b->cur_pos = ld->line_len; b->cur_char = calc_char_len(ld, ld->line_len, b->encoding); return; } for(int64_t i = 0, pos = 0, width = 0; pos < ld->line_len; pos = next_pos(ld->line, pos, b->encoding), i++) { if (ld->line[pos] != '\t') width += get_char_width(&ld->line[pos], b->encoding); else width += b->opt.tab_size - width % b->opt.tab_size; if (total_width - width < ne_columns - b->opt.tab_size) { int64_t t = b->win_x; b->win_x = width - width % b->opt.tab_size; b->cur_x = total_width - b->win_x; b->cur_pos = ld->line_len; b->cur_char = calc_char_len(ld, ld->line_len, b->encoding); if (t != b->win_x && b == cur_buffer) update_window(b); return; } } assert(false); } /* Sets the variables like a move_to_sof(), but does not perform any update. This is required in several places. */ void reset_position_to_sof(buffer * const b) { b->x_wanted = b->y_wanted = b->win_x = b->win_y = b->cur_x = b->cur_y = b->cur_line = b->cur_pos = b->cur_char = 0; b->attr_len = -1; b->cur_line_desc = b->top_line_desc = (line_desc *)b->line_desc_list.head; } void move_to_sof(buffer * const b) { const bool moving = b->win_x || b->win_y; if (moving) update_syntax_states(b, -1, b->cur_line_desc, NULL); else update_syntax_states(b, b->cur_y, b->cur_line_desc, NULL); reset_position_to_sof(b); if (moving && b == cur_buffer) update_window(b); } void move_to_bof(buffer * const b) { line_desc *ld = (line_desc *)b->line_desc_list.tail_pred; for(int i = 0; i < ne_lines - 2 && ld->ld_node.prev->prev; i++) ld = (line_desc *)ld->ld_node.prev; b->x_wanted = 0; b->y_wanted = 0; const int64_t old_win_x = b->win_x, old_win_y = b->win_y; b->cur_line = b->num_lines - 1; b->win_x = 0; b->win_y = ld->ld_node.prev->prev ? b->num_lines - (ne_lines - 1) : b->num_lines - 1; if (old_win_x != b->win_x || old_win_y != b->win_y) { update_syntax_states(b, -1, b->cur_line_desc, NULL); if (b == cur_buffer) reset_window(); } else update_syntax_states(b, b->cur_y, b->cur_line_desc, NULL); b->attr_len = -1; if (!ld->ld_node.prev->prev) { b->win_y = b->cur_x = b->cur_char = b->cur_pos = 0; b->cur_y = b->num_lines - 1; b->top_line_desc = (line_desc *)b->line_desc_list.head; b->cur_line_desc = (line_desc *)b->line_desc_list.tail_pred; } else { b->win_x = b->cur_x = b->cur_char = b->cur_pos = 0; b->cur_y = ne_lines - 2; b->top_line_desc = ld; b->cur_line_desc = (line_desc *)b->line_desc_list.tail_pred; } } void toggle_sof_eof(buffer * const b) { if (b->cur_line == 0 && b->cur_pos == 0) { delay_update(); move_to_bof(b); move_to_eol(b); } else move_to_sof(b); } void toggle_sol_eol(buffer * const b) { if (b->cur_pos == 0) move_to_eol(b); else move_to_sol(b); } typedef struct { line_desc *ld; int64_t pos; int64_t y; } spot_t; /* next_spot() returns a pointer to a static spot_t which is the next valid position in the direction dir (-1 or 1) from the given pos. y is the line number for ld. It is more forgiving that next_pos() and prev_pos(), as it takes initial free form positions (past the end of the line) into account. If we're already at the beginning of the document and dir==-1, or at or past the end with dir==1, next_spot returns NULL. */ spot_t *next_spot(const int dir, line_desc * ld, int64_t pos, int64_t y, const encoding_type encoding) { static spot_t spot; if (dir > 0) { if (pos < ld->line_len) { pos = next_pos(ld->line, pos, encoding); } else if (ld->ld_node.next->next) { ld = (line_desc *)ld->ld_node.next; y++; pos = 0; } else return NULL; } else { if (pos > 0) { if (pos <= ld->line_len) pos = prev_pos(ld->line, pos, encoding); else pos = ld->line_len; } else { if (--y < 0) return NULL; ld = (line_desc *)ld->ld_node.prev; pos = ld->line_len; } } spot.ld = ld; spot.y = y; spot.pos = pos; return &spot; } /* Searches for the start or end of the next or previous word, depending on the value of dir and start. Start Dir in Word Transitions ----- --- ------- ----------- F -1 F 1 F -1 T 2 F 1 F 2 F 1 T 1 T -1 F 2 T -1 T 1 T 1 F 1 T 1 T 2 */ int search_word(buffer * const b, const int dir, const bool start) { assert(dir == -1 || dir == 1); line_desc *ld = b->cur_line_desc; int64_t pos = b->cur_pos; int64_t y = b->cur_line; spot_t *newspot = next_spot(dir, ld, pos, y, b->encoding); if (!newspot) return ERROR; if (dir == -1) { pos = newspot->pos; y = newspot->y; ld = newspot->ld; } bool in_word = ne_isword(pos < ld->line_len ? get_char(&ld->line[pos], b->encoding) : '\0', b->encoding); int transitions_left; if (start) { if (dir == -1) { if (in_word) transitions_left = 1; else transitions_left = 2; } else { if (in_word) transitions_left = 2; else transitions_left = 1; } } else { if (dir == -1) { if (in_word) transitions_left = 2; else transitions_left = 1; } else { if (in_word) transitions_left = 1; else transitions_left = 2; } } while(y < b->num_lines && y >= 0) { newspot = next_spot(dir, ld, pos, y, b->encoding); if (!newspot && dir == 1) return ERROR; const int c = (newspot && newspot->pos < newspot->ld->line_len) ? get_char(&newspot->ld->line[newspot->pos], b->encoding) : '\0'; if (ne_isword(c, b->encoding) != in_word) { if (--transitions_left < 1) { if (newspot && dir == 1) goto_line_pos(b, newspot->y, newspot->pos); else goto_line_pos(b, y, pos); return OK; } } if (newspot) { pos = newspot->pos; y = newspot->y; ld = newspot->ld; in_word = ne_isword(c, b->encoding); } else return ERROR; } return ERROR; } /* Moves to the first word character of the current word characters. It doesn't move at all if we aren't in a word. */ void move_to_sow(buffer * const b) { line_desc *ld = b->cur_line_desc; int64_t pos = b->cur_pos; int64_t y = b->cur_line; if (pos > ld->line_len || pos == 0) return; spot_t *newspot = next_spot(-1, ld, pos, y, b->encoding); if (!newspot || newspot->y != y) return; while (newspot && newspot->y == y && ne_isword(get_char(&newspot->ld->line[newspot->pos], b->encoding), b->encoding)) { pos = newspot->pos; y = newspot->y; ld = newspot->ld; newspot = next_spot(-1, ld, pos, y, b->encoding); } if (pos < b->cur_pos && y == b->cur_line) goto_pos(b, pos); } /* Moves to the character after the end of the current word. It doesn't move at all on US-ASCII spaces and punctuation. */ void move_to_eow(buffer * const b) { line_desc *ld = b->cur_line_desc; int64_t pos = b->cur_pos; if (pos >= ld->line_len || !ne_isword(get_char(&ld->line[pos], b->encoding), b->encoding)) return; for(pos = b->cur_pos; pos < ld->line_len; pos += b->encoding == ENC_UTF8 ? utf8len(ld->line[pos]) : 1) if (!ne_isword(get_char(&ld->line[pos], b->encoding), b->encoding)) break; goto_pos(b, pos); } /* Implements Brief's "incrementale move to the end": if we are in the middle of a line, we mode to the end of line; otherwise, if we are in the middle of a page, we move to the end of the page; otherwise, if we are in the middle of a file we move to the end of file. */ void move_inc_down(buffer * const b) { if (b->cur_pos == b->cur_line_desc->line_len) { if (b->cur_y == ne_lines - 2) move_to_bof(b); else next_page(b); } move_to_eol(b); } /* Same as above, towards the top. */ void move_inc_up(buffer * const b) { if (b->cur_pos == 0) { if (b->cur_y == 0) move_to_sof(b); else prev_page(b); } else move_to_sol(b); } ne-3.3.4/src/ne.c000066400000000000000000000420421475116431000134640ustar00rootroot00000000000000/* main(), global initialization and global buffer functions. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2025 Todd M. Lewis and Sebastiano Vigna This file is part of ne, the nice editor. This library 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 library 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 . */ #include "regex.h" #include "ne.h" #include "version.h" #include "termchar.h" #include #include #include /* This is the array containing the "NO WARRANTY" message, which is displayed when ne is called without any specific file name or macro to execute. The message disappears as soon as any key is typed. */ char *NO_WARRANTY_msg[] = { PROGRAM_NAME " " VERSION ".", "Copyright (C) 1993-1998 Sebastiano Vigna", "Copyright (C) 1999-2025 Todd M. Lewis and Sebastiano Vigna", "", "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 .", "", "Press F1, Escape-Escape or Escape to see the menus. The shortcuts prefixed by ^", "are activated by the Control key; the shortcuts prefixed by [ are activated by", "Control+Meta or just Meta, depending on your terminal emulator. Alternatively,", "just press Escape followed by a letter.", "", "ne home page: http://ne.di.unimi.it/ GitHub repo: https://github.com/vigna/ne/", "Discuss ne at http://groups.google.com/group/niceeditor/", NULL }; char ARG_HELP[] = ABOUT_MSG "\n" "Usage: ne [options] [files]\n" "--help print this message. [-h]\n" "-- *next token is a filename.\n" "+[N[,M]] *move to last or N-th line, first or M-th column of next named file.\n" "--binary *load the next file in binary mode.\n" "--read-only *load the next file in read-only mode. [--readonly|--ro]\n" "--utf8 use UTF-8 I/O.\n" "--no-utf8 do not use UTF-8 I/O.\n" "--ansi use built-in ANSI control sequences.\n" "--no-ansi do not use built-in ANSI control sequences. [--noansi]\n" "--no-config do not read configuration files. [--noconfig]\n" "--no-syntax disable syntax-highlighting support.\n" "--no-bpaste disable bracketed paste support. [--nobpaste]\n" "--prefs EXT set autoprefs for the provided extension before loading the first file.\n" "--keys FILE use this file for keyboard configuration.\n" "--menus FILE use this file for menu configuration.\n" "--macro FILE exec this macro after start.\n\n" " *These options may appear multiple times.\n"; /* The regular expression used to parse the locale. */ #define LOCALE_REGEX "\\(UTF-?8\\)\\|\\(ISO-?8859-?\\)\\(1?[0-9]\\)" /* These lists contain the existing buffers, clips and macros. cur_buffer denotes the currently displayed buffer. */ list buffers = { (node *)&buffers.tail, NULL, (node *)&buffers.head }; list clips = { (node *)&clips.tail, NULL, (node *)&clips.head }; char_stream *recording_macro; bool executing_macro; /* global prefs, only saved in ~/.ne/.default#ap if their current settings differ from these defaults. Make sure these defaults match the conditionals in prefs.c:save_prefs(). */ #ifndef ALTPAGING bool req_order; #else bool req_order = true; #endif bool fast_gui; bool status_bar = true; bool interactive_mode; bool verbose_macros = true; bool bracketed_paste = true; /* end of global prefs */ buffer *cur_buffer; unsigned long buffer_actuations = 0; int turbo; bool do_syntax = true; /* Whether we are currently displaying an about message. */ static bool displaying_info; /* These functions live here because they access cur_buffer. new_buffer() creates a new buffer, adds it to the buffer list, and assign it to cur_buffer. delete_buffer() destroys cur_buffer, and makes the previous or next buffer the current buffer, if any of the two exists. */ buffer *new_buffer(void) { buffer *b = alloc_buffer(cur_buffer); if (b) { clear_buffer(b); if (cur_buffer) add(&b->b_node, &cur_buffer->b_node); else add_head(&buffers, &b->b_node); cur_buffer = b; } return b; } bool delete_buffer(void) { buffer *b; if (is_first(cur_buffer)) b = (buffer *)cur_buffer->b_node.next; else if (is_last(cur_buffer)) b = (buffer *)cur_buffer->b_node.prev; else if (((buffer *)cur_buffer->b_node.next)->act > ((buffer *)cur_buffer->b_node.prev)->act) b = (buffer *)cur_buffer->b_node.next; else b = (buffer *)cur_buffer->b_node.prev; rem(&cur_buffer->b_node); free_buffer(cur_buffer); if (! b->b_node.next) b = (buffer *)buffers.head; if (b == (buffer *)&buffers.tail) return false; cur_buffer = b; return true; } void about(void) { set_attr(0); clear_entire_screen(); displaying_info = true; int i; for(i = 0; NO_WARRANTY_msg[i]; i++) { if (i == ne_lines - 1) break; move_cursor(i, 0); output_string(NO_WARRANTY_msg[i], false); } reset_window(); char t[256] = ABOUT_MSG " Global directory"; char * const gprefs_dir = exists_gprefs_dir(); if (gprefs_dir) strncat(strncat(t, ": ", sizeof t - 1), gprefs_dir, sizeof t - 1); else strncat(strncat(strncat(t, " ", sizeof t - 1), get_global_dir(), sizeof t - 1), " not found!", sizeof t - 1); print_message(t); } /* The main() function. It is responsible for argument parsing, calling some terminal and signal initialization functions, and entering the event loop. */ int main(int argc, char **argv) { char *locale = setlocale(LC_ALL, ""); for(int i = 0; i < 256; i++) localised_up_case[i] = toupper(i); if (locale) { struct re_pattern_buffer re_pb; struct re_registers re_reg; memset(&re_pb, 0, sizeof re_pb); memset(&re_reg, 0, sizeof re_reg); re_pb.translate = localised_up_case; re_compile_pattern(LOCALE_REGEX, strlen(LOCALE_REGEX), &re_pb); if (re_search(&re_pb, locale, strlen(locale), 0, strlen(locale), &re_reg) >= 0) { if (re_reg.start[1] >= 0) io_utf8 = true; } free(re_reg.start); free(re_reg.end); } bool no_config = false; char *macro_name = NULL, *key_bindings_name = NULL, *menu_conf_name = NULL, *startup_prefs_name = DEF_PREFS_NAME; char * const skiplist = calloc(argc, 1); if (!skiplist) exit(1); /* We need this many flags. */ for(int i = 1; i < argc; i++) { if (argv[i][0] == '-' && (!strcmp(&argv[i][1], "h") || !strcmp(&argv[i][1], "-help" "\0" VERSION_STRING))) { puts(ARG_HELP); exit(0); } /* Special arguments start with two dashes. If we find one, we cancel its entry in argv[], so that it will be skipped when opening the specified files. The only exception is +N for skipping to the N-th line. */ if (argv[i][0] == '-' && argv[i][1] == '-') { if (!argv[i][2]) i++; /* You can use "--" to force the next token to be a filename */ else if (!strcmp(&argv[i][2], "no-config") || !strcmp(&argv[i][2], "noconfig")) { no_config = true; skiplist[i] = 1; /* argv[i] = NULL; */ } else if (!strcmp(&argv[i][2], "no-ansi") || !strcmp(&argv[i][2], "noansi")) { ansi = false; skiplist[i] = 1; /* argv[i] = NULL; */ } else if (!strcmp(&argv[i][2], "no-syntax") || !strcmp(&argv[i][2], "nosyntax")) { do_syntax = false; skiplist[i] = 1; /* argv[i] = NULL; */ } else if (!strcmp(&argv[i][2], "no-bpaste") || !strcmp(&argv[i][2], "nobpaste")) { turn_off_bracketed_paste(); bracketed_paste = false; skiplist[i] = 1; /* argv[i] = NULL; */ } else if (!strcmp(&argv[i][2], "prefs")) { if (i < argc-1) { startup_prefs_name = argv[i+1]; skiplist[i] = skiplist[i+1] = 1; /* argv[i] = argv[i+1] = NULL; */ } } else if (!strcmp(&argv[i][2], "ansi")) { ansi = true; skiplist[i] = 1; /* argv[i] = NULL; */ } else if (!strcmp(&argv[i][2], "utf8")) { io_utf8 = true; skiplist[i] = 1; /* argv[i] = NULL; */ } else if (!strcmp(&argv[i][2], "no-utf8")) { io_utf8 = false; skiplist[i] = 1; /* argv[i] = NULL; */ } else if (!strcmp(&argv[i][2], "macro")) { if (i < argc-1) { macro_name = argv[i+1]; skiplist[i] = skiplist[i+1] = 1; /* argv[i] = argv[i+1] = NULL; */ } } else if (!strcmp(&argv[i][2], "keys")) { if (i < argc-1) { key_bindings_name = argv[i+1]; skiplist[i] = skiplist[i+1] = 1; /* argv[i] = argv[i+1] = NULL; */ } } else if (!strcmp(&argv[i][2], "menus")) { if (i < argc-1) { menu_conf_name = argv[i+1]; skiplist[i] = skiplist[i+1] = 1; /* argv[i] = argv[i+1] = NULL; */ } } } } #ifdef NE_TEST { /* Dump the builtin menu and key bindings to compare to doc/default.menus and doc/default.keys. */ int dump_menu_config(FILE *f); int dump_key_config(FILE *f); FILE *f; if ((f = fopen("ne_test_dump_default_config", "w")) ) { dump_menu_config(f); dump_key_config(f); fclose(f); } } #endif /* Unless --noconfig was specified, we try to configure the menus and the keyboard. Note that these functions can exit() on error. */ if (!no_config) { get_menu_configuration(menu_conf_name); get_key_bindings(key_bindings_name); } #ifdef NE_TEST { /* Dump the builtin menu and key bindings to compare to doc/default.menus and doc/default.keys. */ int dump_menu_config(FILE *f); int dump_key_config(FILE *f); FILE *f; if ((f = fopen("ne_test_dump_loaded_config", "w")) ) { dump_menu_config(f); dump_key_config(f); fclose(f); } } #endif /* If we cannot even create a buffer, better go... */ if (!new_buffer()) exit(1); /* Now that key_bindings are loaded, try to fix up the NOT_FOUND error_msg and the LONG_INPUT_HELP info_msg. */ { char *keystroke_string, *new_msg_text; if ((keystroke_string = find_key_strokes(REPEATLAST_A, 1))) { if ((new_msg_text = malloc(39+strlen(keystroke_string)))) { strcat(strcat(strcpy(new_msg_text, "Not Found. (RepeatLast with "), keystroke_string), " to wrap.)"); error_msg[NOT_FOUND] = new_msg_text; } free(keystroke_string); } if ((keystroke_string = find_key_strokes(FIND_A, 1))) { if ((new_msg_text = malloc(24+strlen(keystroke_string)))) { strcat(strcat(strcpy(new_msg_text, " (browse history with "), keystroke_string), ")"); info_msg[LONG_INPUT_HELP] = new_msg_text; } free(keystroke_string); } } clear_buffer(cur_buffer); /* The INT_MAX clip always exists, and it is used by the Through command. */ clip_desc * const cd = alloc_clip_desc(INT_MAX, 0); if (!cd) exit(1); add_head(&clips, &cd->cd_node); /* General terminfo and cursor motion initialization. From here onwards, we cannot exit() lightly. */ term_init(); /* We will be always using the last line for the status bar. */ set_terminal_window(ne_lines-1); /* We read in all the key capabilities. */ read_key_capabilities(); /* Some initializations of other modules... */ re_set_syntax( RE_CONTEXT_INDEP_ANCHORS | RE_CONTEXT_INDEP_OPS | RE_HAT_LISTS_NOT_NEWLINE | RE_NEWLINE_ALT | RE_NO_BK_PARENS | RE_NO_BK_VBAR | RE_NO_EMPTY_RANGES ); bool first_file = true; load_virtual_extensions(); load_auto_prefs(cur_buffer, startup_prefs_name); buffer *stdin_buffer = NULL; if (!isatty(fileno(stdin))) { first_file = false; const int error = load_fd_in_buffer(cur_buffer, fileno(stdin)); print_error(error); stdin_buffer = cur_buffer; if (!(freopen("/dev/tty", "r", stdin))) { fprintf(stderr, "Cannot reopen input tty\n"); abort(); } } /* The terminal is prepared for interactive I/O. */ set_interactive_mode(); clear_entire_screen(); /* This function sets fatal_code() as signal interrupt handler for all the dangerous signals (SIGILL, SIGSEGV etc.). */ set_fatal_code(); if (argc > 1) { /* The first file opened does not need a NEWDOC_A action. Note that file loading can be interrupted (wildcarding can sometimes produce unwanted results). */ uint64_t first_line = 0, first_col = 0; bool binary = false, skip_plus = false, read_only = false; stop = false; for(int i = 1; i < argc && !stop; i++) { if (argv[i] && !skiplist[i]) { if (argv[i][0] == '+' && !skip_plus) { /* looking for "+", or "+N" or "+N,M" */ uint64_t tmp_l = INT64_MAX, tmp_c = 0; char *d; errno = 0; if (argv[i][1]) { if (isdigit((unsigned char)argv[i][1])) { tmp_l = strtoll(argv[i]+1, &d, 10); if (!errno) { if (*d) { /* separator between N and M */ if (isdigit((unsigned char)d[1])) { tmp_c = strtoll(d+1, &d, 10); if (*d) errno = ERANGE; } else errno = ERANGE; } } } else errno = ERANGE; } if (!errno) { first_line = tmp_l; first_col = tmp_c; } else { skip_plus = true; i--; } } else if (!strcmp(argv[i], "--binary")) { binary = true; } else if (!strcmp(argv[i], "--read-only") || !strcmp(argv[i], "--readonly") || !strcmp(argv[i], "--ro")) { read_only = true; } else { if (!strcmp(argv[i], "-") && stdin_buffer) { stdin_buffer->opt.binary = binary; if (read_only) stdin_buffer->opt.read_only = read_only; if (first_line) do_action(stdin_buffer, GOTOLINE_A, first_line, NULL); if (first_col) do_action(stdin_buffer, GOTOCOLUMN_A, first_col, NULL); stdin_buffer = NULL; } else { if (!strcmp(argv[i], "--")) i++; if (!first_file) do_action(cur_buffer, NEWDOC_A, -1, NULL); else first_file = false; cur_buffer->opt.binary = binary; if (i < argc) do_action(cur_buffer, OPEN_A, 0, str_dup(argv[i])); if (first_line) do_action(cur_buffer, GOTOLINE_A, first_line, NULL); if (first_col) do_action(cur_buffer, GOTOCOLUMN_A, first_col, NULL); if (read_only) cur_buffer->opt.read_only = read_only; } first_line = first_col = 0; skip_plus = binary = read_only = false; } } } free(skiplist); /* This call makes current the first specified file. It is called only if more than one buffer exists. */ if (get_nth_buffer(1)) do_action(cur_buffer, NEXTDOC_A, -1, NULL); } /* We delay updates. In this way the macro activity does not cause display activity. */ #ifndef NE_TERMCAP if (ansi) #endif ttysize(); reset_window(); delay_update(); if (macro_name) do_action(cur_buffer, MACRO_A, -1, str_dup(macro_name)); else if (first_file) { /* If there is no file to load, and no macro to execute, we display the "NO WARRANTY" message. */ about(); } while(true) { /* If we are displaying the "NO WARRANTY" info, we should not refresh the window now */ if (!displaying_info) { refresh_window(cur_buffer); if (!cur_buffer->visible_mark.shown) highlight_mark(cur_buffer, true); if (cur_buffer->opt.automatch) automatch_bracket(cur_buffer, true); } draw_status_bar(); move_cursor(cur_buffer->cur_y, cur_buffer->cur_x); int c = get_key_code(); if (window_changed_size) { print_error(do_action(cur_buffer, REFRESH_A, 0, NULL)); window_changed_size = displaying_info = false; cur_buffer->automatch.shown = false; cur_buffer->visible_mark.shown = false; } if (c == INVALID_CHAR) continue; /* Window resizing. */ const input_class ic = CHAR_CLASS(c); if (displaying_info) { refresh_window(cur_buffer); displaying_info = false; } if (cur_buffer->automatch.shown) automatch_bracket(cur_buffer, false); switch(ic) { case INVALID: print_error(INVALID_CHARACTER); break; case ALPHA: print_error(do_action(cur_buffer, INSERTCHAR_A, c, NULL)); break; case TAB: print_error(do_action(cur_buffer, INSERTTAB_A, 1, NULL)); break; case RETURN: print_error(do_action(cur_buffer, INSERTLINE_A, -1, NULL)); break; case COMMAND: if (c < 0) c = -c - 1; if (key_binding[c]) print_error(execute_command_line(cur_buffer, key_binding[c])); else if (c == NE_KEY_BPASTE_BEGIN && bracketed_paste) bracketed_paste_begin(cur_buffer); else if (c == NE_KEY_BPASTE_END && bracketed_paste) bracketed_paste_end(cur_buffer); break; default: break; } } } ne-3.3.4/src/ne.h000066400000000000000000000563671475116431000135100ustar00rootroot00000000000000/* Main typedefs and defines. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2025 Todd M. Lewis and Sebastiano Vigna This file is part of ne, the nice editor. This library 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 library 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 . */ #include #include #include #include #include #include #include #define PARAMS(protos) protos #include "syn_types.h" #include "syn_hash.h" #include "syn_regex.h" #include "syn_utf8.h" #include "syn_utils.h" #include #include #include #include #include #ifndef NE_TERMCAP #include #include #else #include "info2cap.h" #endif #include #include "termchar.h" #include "utf8.h" #define CURDIR "." #include #ifndef min #define min(a,b) ((a)<(b)?(a):(b)) #endif #ifndef max #define max(a,b) ((a)>(b)?(a):(b)) #endif /* Include the list of all possible actions that do_action() can execute. Note also that menu handling is governed by such a command (ESCAPE). */ #include "enums.h" /* The input_class of a character is a first differentiation between alphabetic, command-activating, and ignorable character. The class[] vector (indexed by the extended character code) gives back the input class. */ typedef enum { ALPHA, COMMAND, RETURN, TAB, IGNORE, INVALID, INPUT_CLASS_COUNT } input_class; extern const char *input_class_names[]; typedef enum { BUILT_IN, GLOBAL_PREFS, GLOBAL_PREFS_TERM, USER_PREFS, USER_PREFS_TERM, CUR_DIR, CUR_DIR_TERM, CONFIG_SOURCE_COUNT } config_source; extern const char *config_source_names[]; /* This is the expected max length of the current directory name. */ #define CUR_DIR_MAX_SIZE (16*1024) /* The name of the default preferences file. */ #define DEF_PREFS_NAME ".default" /* The name of the syntax subdirectory (global and local). */ #define SYNTAX_DIR "syntax" /* This is the extension of syntax definition files. */ #define SYNTAX_EXT ".jsf" /* The name of the file containing the mappings from extensions to syntax names. */ #define EXT_2_SYN "ext2syn" /* Files with more than this number of characters will not have syntax highlighting enabled automatically. */ #define MAX_SYNTAX_SIZE (10000000) /* This is the name taken by unnamed documents. */ #define UNNAMED_NAME "" /* The number of key codes, including ISO-8859-1 plus 256 extra codes, plus two more reserved for bracketed paste begin and end. */ #define NUM_KEYS (256+256+2) /* A key code that has class INVALID. */ #define INVALID_CHAR INT_MIN /* The mask for commands. */ #define COMMAND_MASK 0x80000000 #define CHAR_CLASS(c) ( ((c)<0) ? (((c) == INVALID_CHAR) ? INVALID : COMMAND) : ((c)>0xFF) ? ALPHA : char_class[(c)] ) /* This number is used throughout ne when an integer has to be expanded in characters. It is expected that sprintf("%d")ing an integer will produce a string shorter than the following number. PORTABILITY PROBLEM: improbable, but possible. */ #define MAX_INT_LEN 32 /* A buffer or clip at any given time may be marked as ASCII, UTF-8 or 8-bit. This means, respectively, that it contains just bytes below 0x80, an UTF-8-encoded byte sequence or an arbitrary byte sequence. The attribution is lazy, but stable. This means that a buffer starts as ASCII, and becomes UTF-8 or 8-bit as soon as some insertion makes it necessary. This is useful to delay the encoding choice as much as possible. */ typedef enum { ENC_ASCII = 0, ENC_UTF8, ENC_8_BIT } encoding_type; /* Bookmark designations. Users get 0 through 9 plus the AUTO_BOOKMARK '-'. */ enum { MAX_USER_BOOKMARK = 9, AUTO_BOOKMARK, PASTE_START_BOOKMARK, PASTE_END_BOOKMARK, WORDWRAP_BOOKMARK, NUM_BOOKMARKS }; enum { COMPLETE_NONE = 0, COMPLETE_FILE, COMPLETE_CMD, /* Unimplemented ??? */ COMPLETE_SYNTAX }; /* This provides a mechanism to easily create a list for request(). */ typedef struct { int (*cmpfnc)(const char *, const char *); unsigned int allow_dupes:1, /* Searches are more efficient if we have no duplicate entries. */ allow_reorder:1, /* Allow NextDoc/PrevDoc keys to re-order entries. */ ignore_tab:1, /* Permits Tab to exit requester. */ reordered:1, /* Indicates whether reordering was done during request. */ prune:1, /* Whether to start off pruning by partial input. */ find_quits:1, /* Map FIND_A to QUIT_A (for long input ^F requester). */ help_quits:1, /* Map HELP_A to QUIT_A (for help requester). */ selectdoc_quits:1; /* Map SELECTDOC_A to QUIT_A (for F4 requester). */ char suffix; int cur_entries; /* count of entries */ int alloc_entries; /* allocated slots in **entries */ char **entries; /* The array of alloc_entries string pointers pointing into *chars */ int *lengths; /* When set, the column width needed to display corresponding entry (strlen() + suffix + blank) */ int *reorder; /* maps from original order to new order when allow_reorder is true. */ int cur_chars; /* count of used characters */ int alloc_chars; /* allocated characters in *chars */ char *chars; /* contiguous block of allocated characters */ } req_list; /* These are the list and node structures used throughout ne. See the exec.c source for some elaborations on the subject. */ typedef struct struct_node { struct struct_node *next; struct struct_node *prev; } node; typedef struct { node *head; node *tail; node *tail_pred; } list; #define is_first(n) (((node *)n)->prev->prev == NULL) #define is_last(n) (((node *)n)->next->next == NULL) /* All the structures that follow have an assertion macro which checks for partial internal consistency of the structure. These macros are extensively used in the code. */ /* This structure represent a sequence, or stream, of NULL-terminated strings. It is used for many purposes, such as storage of macros, clips, etc. General functions allow to allocate, free, load, save and append to such streams. Size represent the size of the chunk of memory pointed to by stream, while len gives you the number of bytes currently used. You can append in the last size-len bytes, or realloc() stream, updating size correspondingly. */ typedef struct { int64_t size, len; char *stream; encoding_type encoding; } char_stream; #ifndef NDEBUG #define assert_char_stream(cs) {if ((cs)) {\ assert((cs)->len<=(cs)->size);\ assert((cs)->len >= 0);\ assert(((cs)->size == 0) == ((cs)->stream == NULL));\ }} #else #define assert_char_stream(cs) ; #endif /* This structure defines a line descriptor; it is a node containing a pointer to the line text, and an integer containing the line length in bytes. The line pointed to line is NOT NULL-terminated. line_len is zero iff line is NULL, in which case we are representing an empty line. ld_node->next is NULL iff this node is free for use. */ typedef struct { node ld_node; char *line; int64_t line_len; HIGHLIGHT_STATE highlight_state; /* Initial highlight state for this line */ } line_desc; /* The purpose of this structure is to provide the byte count for allocating line descriptors when no syntax highlighting is required. */ typedef struct { node ld_node; char *line; int64_t line_len; } no_syntax_line_desc; #ifndef NDEBUG #define assert_line_desc(ld, encoding) {if ((ld)) { \ assert((ld)->line_len >= 0);\ assert(((ld)->line == NULL) == ((ld)->line_len == 0));\ assert(((ld)->line_len == 0) || ((ld)->line[0] != 0 && (ld)->line[(ld)->line_len - 1] != 0));\ if (encoding == ENC_UTF8) { int i = 0; while(i < (ld)->line_len) { assert(utf8len((ld)->line[i]) > 0); i = next_pos((ld)->line, i, encoding);} } \ }} #else #define assert_line_desc(ld, encoding) ; #endif /* This structure defines a pool of line descriptors. pool points to an array of size line descriptors, which are kept in free_list. The allocated_items field keeps track of how many items are allocated. */ typedef struct { node ldp_node; list free_list; int64_t size; int64_t allocated_items; void *pool; // The type of line descriptor can vary. bool mapped; } line_desc_pool; #ifndef NDEBUG #define assert_line_desc_pool(ldp) {if ((ldp)) {\ assert((ldp)->allocated_items <= (ldp)->size);\ assert((ldp)->allocated_items == (ldp)->size || (ldp)->free_list.head->next);\ assert((ldp)->pool != NULL);\ assert((ldp)->size != 0);\ }} #else #define assert_line_desc_pool(ldp) ; #endif /* This structure defines a pool of characters. size represents the size of the pool pointed by pool, while first_used and last_used represent the min and max characters which are used. A character is not used if it is zero. It is perfectly possible (and likely) that between first_used and last_used there are many free chars, which are named "lost" chars. See the source buffer.c for some elaboration on the subject. */ typedef struct { node cp_node; int64_t size; int64_t first_used, last_used; char *pool; bool mapped; } char_pool; #ifndef NDEBUG #define assert_char_pool(cp) {if ((cp)) {\ assert((cp)->first_used<=(cp)->first_used);\ assert((cp)->pool[(cp)->first_used] != 0);\ assert((cp)->first_used >= 0);\ assert((cp)->first_used == 0 || (cp)->pool[(cp)->first_used - 1] == 0);\ assert((cp)->pool[(cp)->last_used] != 0);\ assert((cp)->last_used >= 0);\ assert((cp)->last_used == (cp)->size - 1 || (cp)->pool[(cp)->last_used + 1] == 0);\ assert((cp)->pool != NULL);\ assert((cp)->size != 0);\ }} #else #define assert_char_pool(cp) ; #endif /* This structure defines a macro. A macro is just a stream plus a node, a file name and a hash code relative to the filename (it is used to make the search for a given macro quicker). */ typedef struct struct_macro_desc { struct struct_macro_desc *next; char *name; char_stream *cs; } macro_desc; #ifndef NDEBUG #define assert_macro_desc(md) if (md) assert_char_stream((md)->cs); #else #define assert_macro_desc(md) ; #endif /* This structure defines a clip. Clip are numbered from 0 onwards, and contain a stream of characters. The stream may be optionally marked as UTF-8. */ typedef struct { node cd_node; int n; char_stream *cs; } clip_desc; #ifndef NDEBUG #define assert_clip_desc(cd) {if ((cd)) {\ assert((cd)->n >= 0);\ assert_char_stream((cd)->cs);\ }} #else #define assert_clip_desc(cd) ; #endif /* An undo step is given by a position, a transformation which can be INSERT_CHAR or DELETE_CHAR and the length of the stream to which the transformation applies. For compactness reasons, the transformation is really stored in the sign of the length. Plus means insert, minus means delete. Note also that pos can be negative, in which case the real position is -(pos+1), and the undo step is linked to the following one (in the sense that they should be performed indivisibly). */ typedef struct { int64_t line; int64_t pos; int64_t len; } undo_step; /* This structure defines an undo buffer. steps points to an array of steps_size undo_steps, used up to cur_step. last_step represent the undo step which is the next to be redone in case some undo had place. Note that the characters stored in streams, which are used when executing an insertion undo step, are not directly pointed to by the undo step. The correct position is calculated incrementally, and kept in cur_stream and last_stream. Redo contains the stream of characters necessary to perform the redo steps. last_save_step is the step (if any) corresponding to the last successful buffer save operation. */ typedef struct { undo_step *steps; char *streams; char_stream redo; int64_t steps_size; int64_t streams_size; int64_t cur_step; int64_t cur_stream; int64_t last_step; int64_t last_stream; int64_t last_save_step; } undo_buffer; #ifndef NDEBUG #define assert_undo_buffer(ub) {if ((ub)) {\ assert((ub)->cur_step<=(ub)->last_step);\ assert((ub)->cur_stream<=(ub)->last_stream);\ assert((ub)->cur_step<=(ub)->steps_size);\ assert((ub)->cur_stream<=(ub)->streams_size);\ assert((ub)->last_step<=(ub)->steps_size);\ assert((ub)->last_stream<=(ub)->streams_size);\ assert_char_stream(&(ub)->redo);\ }} #else #define assert_undo_buffer(ub) ; #endif /* This structure defines all the per document options which can be used with PushPrefs and PopPrefs. */ typedef struct { int cur_clip; int tab_size; int right_margin; unsigned int auto_indent:1, /* Replicate indentation when creating a new line */ automatch:4, /* Automatically match visible brackets */ auto_prefs:1, /* Use autoprefs */ binary:1, /* Load and save in binary mode */ case_search:1, /* Look at case matching in searches */ del_tabs:1, /* DEL/BS deletes tab's worth of space. */ do_undo:1, /* Record each action and allow undoing it */ free_form:1, /* Editing is free form (cursor can be anywhere) */ hex_code:1, /* Show hexadecimal code under the cursor */ insert:1, /* Insert mode */ no_file_req:1, /* Do not display the file requester */ preserve_cr:1, /* Preserve Carriage Returns, don't treat as line terminators. */ read_only:1, /* Read-only mode */ search_back:1, /* Last search was backwards */ shift_tabs:1, /* Shift may insert tabs, but only if tabs is also true */ tabs:1, /* TAB inserts TABs(1) vs. spaces(0) */ utf8auto:1, /* Try to detect automatically UTF-8 */ visual_bell:1, /* Prefer visible bell to audible */ word_wrap:1; /* Word wrap is on */ } options_t; #ifndef NDEBUG #define assert_options(o) {if ((o)) {\ assert((o)->tab_size > 0);\ assert_undo_buffer(&(b)->undo);\ }} #else #define assert_options(o) ; #endif /* This structure defines a buffer node; a buffer is composed by two lists, the list of line descriptor pools and the list of character pools, plus some data as the current window and cursor position. The line descriptors are kept in a list, too. The other fields are more or less self-documented. wanted_x represents the position the cursor would like to be on, but cannot because a line is too short or because it falls inside a TAB expansion. When a flag is declared as multilevel, this means that the flag is incremented/decremented rather than set/unset, so that activations and deactivations can be nested. */ typedef struct { node b_node; list line_desc_pool_list; list line_desc_list; list char_pool_list; line_desc *cur_line_desc; line_desc *top_line_desc; char_stream *cur_macro; char_stream *last_deleted; char *filename; char *find_string; char *replace_string; char *command_line; char *bpaste_macro_before; /* name of macro to run before processing bracketed pastes */ char *bpaste_macro_after; /* name of macro to run after processing bracketed pastes */ int bpaste_support; /* 0=not supported; 1=supported w/ defaults; 2=supported with before and after macros */ unsigned long mtime; /* mod time of on-disk file when it was last loaded/saved, or 0 */ unsigned long act; /* increasing indicator of recently active relative to other buffers */ int64_t win_x, win_y; /* line and pos of upper left-most visible character. */ int cur_x, cur_y; /* position of cursor within the window */ int64_t wanted_x; /* desired x position modulo short lines, tabs, etc. Valid only if x_wanted is true. */ int64_t wanted_y; /* desired y position modulo top or bottom of buffer. Valid if y_wanted is true. */ int64_t wanted_cur_y; /* desired cur_y, valid if y_wanted is true */ int64_t cur_line; /* the current line */ int64_t cur_pos; /* position of cursor within the document buffer (counts bytes); -1 if not in sync with win_x/cur_x */ int64_t cur_char; /* position of cursor within the attribute buffer (counts characters); invalid if cur_pos == -1 */ int64_t num_lines; int64_t block_start_line, block_start_pos; struct { bool shown; int x; /* screen x coordinate of the highlighted bracket or mark if shown is true. */ int y; /* screen y coordinate of the highlighted bracket or mark if shown is true. */ } automatch, visible_mark; int64_t allocated_chars; int64_t free_chars; encoding_type encoding; undo_buffer undo; struct { int64_t pos; int64_t line; int cur_y; } bookmark[NUM_BOOKMARKS]; int bookmark_mask; /* bit N is set if bookmark[N] is set */ int cur_bookmark; /* For Goto(Next|Prev)Bookmark. */ struct high_syntax *syn; /* Syntax loaded for this buffer. */ uint32_t *attr_buf; /* If attr_len >= 0, a pointer to the list of *current* attributes of the *current* line. */ int64_t attr_size; /* attr_buf size. */ int64_t attr_len; /* attr_buf valid number of characters, or -1 to denote that attr_buf is not valid. */ HIGHLIGHT_STATE next_state; /* If attr_len >= 0, the state after the *current* line. */ int link_undos; /* Link the undo steps. Multilevel. */ unsigned int is_modified:1, /* Buffer has been modified since last save */ marking:1, /* We are marking a block */ x_wanted:1, /* We're not where we would like to be */ y_wanted:1, /* We've been paging up/down */ exec_only_options:1, /* Only option changes can be executed */ last_was_replace:1, /* The last search operation was a replace */ last_was_regexp:1, /* The last search operation was done with regexps */ undoing:1, /* We are currently undoing an action */ redoing:1, /* We are currently redoing an action */ bpasting:1, /* We are currently doing a bracketed paste */ mark_is_vertical:1, /* The current marking is vertical */ atomic_undo:1, /* subsequent commands undo as a block */ is_CRLF:1; /* Buffer should be saved with CR/LF terminators */ unsigned int find_string_changed; /* 0 = unset; 1 = force; else prior search's serial number */ options_t opt; /* These get pushed/popped on the prefs stack */ } buffer; #ifndef NDEBUG #define assert_buffer(b) {if ((b)) {\ assert((b)->line_desc_list.head->next == NULL || (b)->cur_line_desc != NULL);\ assert((b)->line_desc_list.head->next == NULL || (b)->top_line_desc != NULL);\ assert_line_desc((b)->cur_line_desc, (b)->encoding);\ assert_line_desc((b)->top_line_desc, (b)->encoding);\ assert_char_stream((b)->last_deleted);\ assert_char_stream((b)->cur_macro);\ assert((b)->win_y + (b)->cur_y<(b)->num_lines);\ assert((b)->num_lines > 0);\ assert((b)->opt.tab_size > 0);\ assert((b)->free_chars <= (b)->allocated_chars);\ assert((b)->cur_line == (b)->win_y + (b)->cur_y);\ assert((b)->cur_pos == -1 || calc_width((b)->cur_line_desc, (b)->cur_pos, (b)->opt.tab_size, (b)->encoding) == (b)->win_x + (b)->cur_x);\ assert((b)->cur_pos == -1 || calc_virt_pos((b)->cur_line_desc, (b)->win_x + (b)->cur_x, (b)->opt.tab_size, (b)->encoding) == (b)->cur_pos);\ assert((b)->cur_pos == -1 || (b)->cur_pos > (b)->cur_line_desc->line_len || calc_char_len((b)->cur_line_desc, (b)->cur_pos, (b)->encoding) == (b)->cur_char);\ assert((b)->cur_pos < 0 || (b)->cur_pos >= (b)->cur_line_desc->line_len || (b)->encoding != ENC_UTF8 || utf8len((b)->cur_line_desc->line[(b)->cur_pos]) > 0);\ assert_undo_buffer(&(b)->undo);\ }} #define assert_buffer_content(b) {if ((b)) {\ line_desc *ld;\ ld = (line_desc *)(b)->line_desc_list.head;\ while(ld->ld_node.next) {\ assert_line_desc(ld, (b)->encoding);\ if ((b)->syn) assert(ld->highlight_state.state != -1);\ ld = (line_desc *)ld->ld_node.next;\ }\ if ((b)->syn) assert((b)->attr_len < 0 || (b)->attr_len == calc_char_len((b)->cur_line_desc, (b)->cur_line_desc->line_len, (b)->encoding));\ }} #else #define assert_buffer(b) ; #define assert_buffer_content(b); #endif #include "syntax.h" extern const char *key_binding[]; extern config_source key_binding_source[NUM_KEYS]; extern buffer *cur_buffer; extern unsigned long buffer_actuations; /* These are the global lists. */ extern list buffers, clips; /* This integer keeps the global turbo parameter. */ extern int turbo; /* If true, the current line has changed and care must be taken to update the initial state of the following lines. */ extern bool need_attr_update; /* If true, we want the hardwired ANSI terminal, not a real one. */ extern bool ansi; /* If true, we want requests by column, otherwise by row. */ extern bool req_order; /* The status bar is displayed */ extern bool status_bar; /* Track whether we are in interactive mode. */ extern bool interactive_mode; /* If true, we want abbreviated screen updates. */ extern bool fast_gui; /* Recorded macros use long command names */ extern bool verbose_macros; /* If true, we want syntax highlighting. */ extern bool do_syntax; /* This flag can be set anywhere to false, and will become true if the user hits the interrupt key (usually CTRL-'\'). It is handled through SIGQUIT and SIGINT. */ extern bool stop; /* This is set by the signal handler whenever a SIGWINCH happens. */ extern bool window_changed_size; /* This vector associates to an extended key code (as returned by get_key_code()) its input class. */ extern const input_class char_class[]; /* This vector associates key codes to strings indicating the key combinations required to produce those key codes. */ extern const char *key_stroke[]; /* A boolean recording whether the last replace was for an empty string (of course, this can happen only with regular expressions). */ extern bool last_replace_empty_match; /* This number defines the macro hash table size. This table can have conflicts. */ #define MACRO_HASH_TABLE_SIZE (101) /* When recording a macro, this points to the recording stream. When not recording a macro, this is NULL. */ extern char_stream *recording_macro; /* Whether we're currently executing a macro. */ extern bool executing_macro; /* There are several functions that set the status bar: print_message(), print_prompt() and input_refresh(), and draw_status_bar(). If we're suspended while a requester is up, this is how request() knows what to do to properly restore the status bar upon resume. */ extern void (*resume_status_bar)(const char *message); /* Upcasing vectors for the regex library. */ extern unsigned char localised_up_case[]; extern const unsigned char ascii_up_case[]; #include "keycodes.h" #include "names.h" #include "errors.h" #include "protos.h" #include "utf8.h" #include "debug.h" /* In the unfortunate case we are compiling in some known system with a completely different handling of binary and ASCII files, we force the use of binary files. */ #ifdef O_BINARY #define READ_FLAGS O_RDONLY | O_BINARY #define WRITE_FLAGS O_CREAT | O_TRUNC | O_WRONLY | O_BINARY #else #define READ_FLAGS O_RDONLY #define WRITE_FLAGS O_CREAT | O_TRUNC | O_WRONLY #endif ne-3.3.4/src/ne.texinfo000077700000000000000000000000001475116431000176672../doc/ne.texinfoustar00rootroot00000000000000ne-3.3.4/src/prefs.c000066400000000000000000000461001475116431000142000ustar00rootroot00000000000000/* Preferences functions. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2025 Todd M. Lewis and Sebastiano Vigna This file is part of ne, the nice editor. This library 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 library 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 . */ #include "ne.h" #include "support.h" #include /* These are the names of ne's autoprefs directory. */ #define PREFS_DIR ".ne" /* This string is appended to the filename extension. It tries to be enough strange to avoid clashes with macros. */ #define PREF_FILE_SUFFIX "#ap" /* The name of the virtual extensions file (local and global). */ #define VIRTUAL_EXT_NAME ".extensions" #define VIRTUAL_EXT_NAME_G "extensions" /* The maximum number of characters scanned during a regex search for virtual extensions. */ #define REGEX_SCAN_LIMIT (100000) /* We suppose a configuration file won't be bigger than this. Having it bigger just causes a reallocation. */ #define PREF_FILE_SIZE_GUESS (256) /* If we're saving default prefs, we include global prefs that are not buffer specific. Likewise, if we're saving auto prefs, we don't want to include global prefs. */ static bool saving_defaults; /* Returns a pointer to the extension of a filename, or NULL if there is no extension (or no filename!). */ const char *extension(const char * const filename) { if ( filename ) { for(int i = strlen(filename); i-- != 0;) { if (filename[i] == '/') return NULL; if (filename[i] == '.') return &filename[i + 1]; } } return NULL; } /* Returns a pointer to the absolute name of ne's prefs directory with '/' appended. The name is cached internally, so it needs not to be free()ed. If the directory does not exist, it is created. NULL is returned on failure. */ char *exists_prefs_dir(void) { static char *prefs_dir; /* If we have been already called, we already computed the name. */ if (prefs_dir) return prefs_dir; /* In the UN*X case, we first get the home directory. Then we allocate space for the directory name. */ char * home_dir; if (!(home_dir = getenv("HOME"))) home_dir = "."; if (prefs_dir = malloc(strlen(home_dir) + strlen(PREFS_DIR) + 3)) { strcat(strcat(strcpy(prefs_dir, home_dir), "/"), PREFS_DIR); struct stat s; if (stat(prefs_dir, &s)) { if (mkdir(prefs_dir, 0700)) { free(prefs_dir); return prefs_dir = NULL; } } else if (!S_ISDIR(s.st_mode)) { free(prefs_dir); return prefs_dir = NULL; } return strcat(prefs_dir, "/"); } else return NULL; } /* Returns a pointer to the absolute name of ne's global prefs directory with '/' appended. The name is cached internally, so it needs not to be free()ed. If the directory does not exist, it is not created. NULL is returned on failure. */ char *exists_gprefs_dir(void) { static char *gprefs_dir = NULL; /* If we have been already called, we already computed the name. We should free up the name and re-compute it (because the global dir may have changed). */ if (gprefs_dir) { free(gprefs_dir); gprefs_dir = NULL; } const char *global_dir; if ((global_dir = get_global_dir()) && (gprefs_dir = malloc(strlen(global_dir) + 3 ))) { strcpy(gprefs_dir, global_dir); struct stat s; if (stat(gprefs_dir, &s)) { free(gprefs_dir); return gprefs_dir = NULL; } else if (!S_ISDIR(s.st_mode)) { free(gprefs_dir); return gprefs_dir = NULL; } return strcat(gprefs_dir, "/"); } return NULL; } /* Saves the preferences of the given buffer onto the given file name. If b or name are NULL, ERROR is returned. */ int save_prefs(buffer * const b, const char * const name) { if (!b || !name) return ERROR; assert_buffer(b); char_stream *cs = alloc_char_stream(PREF_FILE_SIZE_GUESS); if (cs) { /* We create a macro by recording an action for each kind of flag. */ if (!saving_defaults && b->syn) record_action(cs, SYNTAX_A, -1, (const char *)b->syn->name, verbose_macros); record_action(cs, TABSIZE_A, b->opt.tab_size, NULL, verbose_macros); /* Skip cur_clip */ record_action(cs, RIGHTMARGIN_A, b->opt.right_margin, NULL, verbose_macros); record_action(cs, FREEFORM_A, b->opt.free_form, NULL, verbose_macros); record_action(cs, HEXCODE_A, b->opt.hex_code, NULL, verbose_macros); record_action(cs, WORDWRAP_A, b->opt.word_wrap, NULL, verbose_macros); record_action(cs, AUTOINDENT_A, b->opt.auto_indent, NULL, verbose_macros); record_action(cs, PRESERVECR_A, b->opt.preserve_cr, NULL, verbose_macros); record_action(cs, INSERT_A, b->opt.insert, NULL, verbose_macros); record_action(cs, DOUNDO_A, b->opt.do_undo, NULL, verbose_macros); record_action(cs, AUTOPREFS_A, b->opt.auto_prefs, NULL, verbose_macros); record_action(cs, NOFILEREQ_A, b->opt.no_file_req, NULL, verbose_macros); /* Skip read_only */ /* Skip search_back */ record_action(cs, CASESEARCH_A, b->opt.case_search, NULL, verbose_macros); record_action(cs, TABS_A, b->opt.tabs, NULL, verbose_macros); record_action(cs, DELTABS_A, b->opt.del_tabs, NULL, verbose_macros); record_action(cs, SHIFTTABS_A, b->opt.shift_tabs, NULL, verbose_macros); record_action(cs, AUTOMATCHBRACKET_A, b->opt.automatch, NULL, verbose_macros); record_action(cs, BINARY_A, b->opt.binary, NULL, verbose_macros); record_action(cs, UTF8AUTO_A, b->opt.utf8auto, NULL, verbose_macros); record_action(cs, VISUALBELL_A, b->opt.visual_bell, NULL, verbose_macros); if (bracketed_paste) record_action(cs, BRACKETEDPASTE_A, -1, cur_bracketed_paste_value(b), verbose_macros); if (saving_defaults) { /* We only save the global flags that differ from their defaults. */ /* Make sure these are in sync with the defaults near the top of ne.c. */ #ifndef ALTPAGING if (req_order) record_action(cs, REQUESTORDER_A, req_order, NULL, verbose_macros); #else if (!req_order) record_action(cs, REQUESTORDER_A, req_order, NULL, verbose_macros); #endif if (fast_gui) record_action(cs, FASTGUI_A, fast_gui, NULL, verbose_macros); if (!status_bar) record_action(cs, STATUSBAR_A, status_bar, NULL, verbose_macros); if (!verbose_macros) record_action(cs, VERBOSEMACROS_A, verbose_macros, NULL, verbose_macros); saving_defaults = false; } const int error = save_stream(cs, name, b->is_CRLF, false); free_char_stream(cs); return error; } return OUT_OF_MEMORY; } /* Loads the given preferences file. The file is just executed, but with the exec_only_options flag set. If b or name are NULL, ERROR is returned. */ int load_prefs(buffer * const b, const char * const name) { if (!b || !name) return ERROR; assert_buffer(b); b->exec_only_options = 1; int error = OK; char_stream * const cs = load_stream(NULL, name, false, false); if (cs) { error = play_macro(cs, 1); free_char_stream(cs); } else error = CANT_OPEN_FILE; b->exec_only_options = 0; return error; } /* Loads the given syntax, taking care to preserve the old syntax if the new one cannot be loaded. */ int load_syntax_by_name(buffer * const b, const char * const name) { assert_buffer(b); assert(name != NULL); struct high_syntax *syn = load_syntax((unsigned char *)name); if (!syn) syn = load_syntax((unsigned char *)ext2syntax(name)); if (syn) { b->syn = syn; reset_syntax_states(b); return OK; } return NO_SYNTAX_FOR_EXT; } /* This data structure holds information about virtual extensions. */ static struct { int max_line; char *ext; char *regex; bool case_sensitive; } *virt_ext; static int num_virt_ext; static int64_t max_max_line; static char **extra_ext; static int64_t num_extra_exts; static void load_virt_ext(char *vname) { /* Our find_regexp() is geared to work on buffers rather than streams, so we'll create a stand-alone buffer. This also buys us proper handling of encodings. */ buffer * vb = alloc_buffer(NULL); if (vb == NULL) return; clear_buffer(vb); vb->opt.auto_prefs = 0; vb->opt.do_undo = 0; vb->opt.case_search = 0; if (load_file_in_buffer(vb, vname) != OK) return; bool skip_first = false; vb->find_string = "^\\s*(\\w+)\\s+([0-9]+i?)\\s+(.+[^ \\t])\\s*$|^\\.([^ \\t/]+)\\s*$"; vb->find_string_changed = 1; if ((virt_ext = realloc(virt_ext, (num_virt_ext + vb->num_lines) * sizeof *virt_ext)) && (extra_ext = realloc(extra_ext, (num_extra_exts + vb->num_lines) * sizeof *extra_ext))) { while (find_regexp(vb, NULL, skip_first, false) == OK) { skip_first = true; if (nth_regex_substring_nonempty(vb->cur_line_desc, 1)) { char * const ext = nth_regex_substring(vb->cur_line_desc, 1); char * const max_line_str = nth_regex_substring(vb->cur_line_desc, 2); char * const regex = nth_regex_substring(vb->cur_line_desc, 3); if (!ext || !max_line_str || !regex) break; errno = 0; char *endptr; int64_t max_line = strtoll(max_line_str, &endptr, 0); if (max_line < 1 || errno) max_line = INT64_MAX; int i; for(i = 0; i < num_virt_ext; i++) if (strcmp(virt_ext[i].ext, ext) == 0) { free(virt_ext[i].ext); free(virt_ext[i].regex); break; } virt_ext[i].ext = ext; virt_ext[i].max_line = max_line; virt_ext[i].regex = regex; virt_ext[i].case_sensitive = *endptr != 'i'; free(max_line_str); if (i == num_virt_ext) num_virt_ext++; } else { char * const ext = nth_regex_substring(vb->cur_line_desc, 4); if (! ext) break; int i; for(i = 0; i < num_extra_exts; i++) if (strcmp(extra_ext[i], ext) == 0) break; if (i == num_extra_exts) extra_ext[num_extra_exts++] = ext; else free(ext); } } } vb->find_string = NULL; /* Or free_buffer() would free() it. */ free_buffer(vb); } /* Loads and stores internally the virtual extensions. First we source the global extensions file, and then the local one. Local specifications override global ones. */ void load_virtual_extensions() { assert(virt_ext == NULL); char *prefs_dir; /* Try global directory first. */ if (prefs_dir = exists_gprefs_dir()) { char virt_name[strlen(VIRTUAL_EXT_NAME_G) + strlen(prefs_dir) + 1]; strcat(strcpy(virt_name, prefs_dir), VIRTUAL_EXT_NAME_G); load_virt_ext(virt_name); } /* Then try the user's ~/.ne/.extensions, possibly overriding global settings. */ if (prefs_dir = exists_prefs_dir()) { char virt_name[strlen(VIRTUAL_EXT_NAME) + strlen(prefs_dir) + 1]; strcat(strcpy(virt_name, prefs_dir), VIRTUAL_EXT_NAME); load_virt_ext(virt_name); } for(int i = 0; i < num_virt_ext; i++) max_max_line = max(max_max_line, virt_ext[i].max_line); } /* virtual_extension() returns an extension determined by a buffers contents and the user's VIRTUAL_EXT_NAME file or possibly the global VIRTUAL_EXT_NAME file. The returned string need not be free()'d. */ static char *virtual_extension(buffer * const b) { if (virt_ext == NULL) return NULL; /* If the buffer filename has an extension, check that it's in extra_ext. */ const char * const filename_ext = extension(b->filename); if (filename_ext != NULL) { int i; for(i = 0; i < num_extra_exts; i++) if (fnmatch(extra_ext[i], filename_ext, 0) == 0) break; if (i == num_extra_exts) return NULL; } /* Reduce the maximum number of lines to scan so that no more than REGEX_SCAN_LIMIT characters are regex'd. */ int64_t line_limit = 0, pos_limit = -1, len = 0; for(line_desc *ld = (line_desc *)b->line_desc_list.head; ld->ld_node.next && line_limit < max_max_line; ld = (line_desc *)ld->ld_node.next, line_limit++) if ((len += ld->line_len + 1) > REGEX_SCAN_LIMIT) { line_limit++; pos_limit = REGEX_SCAN_LIMIT - (len - ld->line_len - 1); break; } int64_t earliest_found_line = INT64_MAX; char *ext = NULL; const int64_t b_cur_line = b->cur_line; const int64_t b_cur_pos = b->cur_pos; const int b_search_back = b->opt.search_back; const int b_case_search = b->opt.case_search; const int b_last_was_regexp = b->last_was_regexp; char * const find_string = b->find_string; b->opt.search_back = true; for(int i = 0; earliest_found_line > 0 && i < num_virt_ext && !stop; i++) { int64_t min_line = -1; /* max_line is 1-based, but internal line numbers (min_line) are 0-based. */ /* Search backwards in b from max_line for the first occurrence of regex. */ b->opt.case_search = virt_ext[i].case_sensitive; const int64_t max_line = min(virt_ext[i].max_line, line_limit); goto_line(b, max_line - 1); goto_pos(b, max_line == line_limit && pos_limit != -1 ? pos_limit : b->cur_line_desc->line_len); b->find_string = virt_ext[i].regex; b->find_string_changed = 1; while (find_regexp(b, NULL, true, false) == OK) { min_line = b->cur_line; D(fprintf(stderr, "[%d] --- found match for '%s' on line <%d>\n", __LINE__, ext, min_line);) if (min_line == 0) break; } if (min_line > -1) { if (min_line < earliest_found_line) { earliest_found_line = min_line; ext = virt_ext[i].ext; } } } goto_line_pos(b, b_cur_line, b_cur_pos); b->opt.search_back = b_search_back; b->opt.case_search = b_case_search; b->last_was_regexp = b_last_was_regexp; b->find_string = find_string; b->find_string_changed = 1; return ext; } /* Performs an automatic preferences operation, which can be loading or saving, depending on the function pointed to by prefs_func. The extension given by ext is used in order to locate the appropriate file. If ext is NULL, the extension of the buffer filename is used instead. If b is NULL, ERROR is returned. */ static int do_auto_prefs(buffer *b, const char * ext, int (prefs_func)(buffer *, const char *)) { if (!b) return ERROR; assert_buffer(b); if (!ext && !(ext = virtual_extension(b)) && !(ext = extension(b->filename))) return HAS_NO_EXTENSION; /* Try global autoprefs -- We always load these before ~/.ne autoprefs. That way the user can override whatever he wants, but anything he doesn't override still gets passed through. */ int error = OK; char *auto_name, *prefs_dir; if (*prefs_func == load_prefs && (prefs_dir = exists_gprefs_dir())) { if (auto_name = malloc(strlen(ext) + strlen(prefs_dir) + strlen(PREF_FILE_SUFFIX) + 2)) { strcat(strcat(strcpy(auto_name, prefs_dir), ext), PREF_FILE_SUFFIX); error = prefs_func(b, auto_name); free(auto_name); /* We don't "return error;" here because we still haven't loaded the user's autoprefs. */ } } /* Try ~/.ne autoprefs */ if (prefs_dir = exists_prefs_dir()) { if (auto_name = malloc(strlen(ext) + strlen(prefs_dir) + strlen(PREF_FILE_SUFFIX) + 2)) { strcat(strcat(strcpy(auto_name, prefs_dir), ext), PREF_FILE_SUFFIX); error = prefs_func(b, auto_name); free(auto_name); } else error = OUT_OF_MEMORY; } else error = CANT_FIND_PREFS_DIR; if (do_syntax && !b->syn) load_syntax_by_name(b, ext); return error; } /* These functions just instantiate do_auto_prefs to either load_prefs or save_prefs. */ int load_auto_prefs(buffer * const b, const char *name) { return do_auto_prefs(b, name, load_prefs); } int save_auto_prefs(buffer * const b, const char *name) { /* In practice, the only time we call save_auto_prefs with a name is when we save the default prefs. If that changes, so too must this method of setting this static flag used by save_prefs. */ saving_defaults = name ? true : false; return do_auto_prefs(b, name, save_prefs); } /* This bit has to do with pushing and popping preferences on the prefs stack. */ #define MAX_PREF_STACK_SIZE 32 typedef struct { int pcount; int psize; options_t pref[MAX_PREF_STACK_SIZE]; } pref_stack_t; static pref_stack_t pstack = { 0, MAX_PREF_STACK_SIZE }; int push_prefs(buffer * const b) { char msg[120]; if (pstack.pcount >= MAX_PREF_STACK_SIZE) { sprintf(msg, "PushPrefs failed, stack is full. %d prefs now on stack.", pstack.pcount); print_message(msg); return PREFS_STACK_FULL; } pstack.pref[pstack.pcount++] = b->opt; sprintf(msg, "User Prefs Pushed, %d Prefs now on stack.", pstack.pcount); print_message(msg); return OK; } int pop_prefs(buffer * const b) { char msg[120]; if (pstack.pcount <= 0) { sprintf(msg, "PopPrefs failed, stack is empty."); print_message(msg); return PREFS_STACK_EMPTY; } else { b->opt = pstack.pref[--pstack.pcount]; sprintf(msg, "User Prefs Popped, %d Prefs remain on stack.", pstack.pcount); print_message(msg); return OK; } } /* Bracketed paste support has its own private options cache. */ static options_t bpaste_opt_cache; static struct { int64_t pos; int64_t line; int cur_y; } bpaste_marks[2]; #define BUFSIZE 2048 static char cmdbuf[BUFSIZE+1]; void bracketed_paste_begin(buffer *b) { if (!bracketed_paste || b->bpaste_support < 1 || b->bpasting) return; bpaste_opt_cache = b->opt; b->bpasting = 1; if (b->bpaste_support == 1) { bpaste_marks[0].pos = b->cur_pos; bpaste_marks[0].line = b->cur_line; bpaste_marks[0].cur_y = b->cur_y; b->opt.auto_indent = 0; start_undo_chain(b); } else if (b->bpaste_support == 2) { cmdbuf[0] = '\0'; strncat(strncpy(cmdbuf, "Macro ", BUFSIZE), b->bpaste_macro_before, BUFSIZE); execute_command_line(b, cmdbuf); } else b->bpasting = 0; } void bracketed_paste_end(buffer *b) { b->bpasting = 0; if (!bracketed_paste || b->bpaste_support < 1) return; b->opt = bpaste_opt_cache; if (b->bpaste_support == 1) { b->bookmark[PASTE_START_BOOKMARK].pos = bpaste_marks[0].pos; b->bookmark[PASTE_START_BOOKMARK].line = bpaste_marks[0].line; b->bookmark[PASTE_START_BOOKMARK].cur_y = bpaste_marks[0].cur_y; b->bookmark_mask |= (1 << PASTE_START_BOOKMARK); b->bookmark[PASTE_END_BOOKMARK].pos = b->cur_pos; b->bookmark[PASTE_END_BOOKMARK].line = b->cur_line; b->bookmark[PASTE_END_BOOKMARK].cur_y = b->cur_y; b->bookmark_mask |= (1 << PASTE_END_BOOKMARK); end_undo_chain(b); if (b->opt.auto_indent && bpaste_marks[0].line < b->cur_line && bpaste_marks[0].pos > 0) { int64_t block_start_line_tmp = b->block_start_line, block_start_pos_tmp = b->block_start_pos; int marking_tmp = b->marking, mark_is_vertical_tmp = b->mark_is_vertical; b->block_start_line = bpaste_marks[0].line + 1; b->block_start_pos = 0; b->marking = 1; b->mark_is_vertical = 0; snprintf(cmdbuf, BUFSIZE, "> %ld s", bpaste_marks[0].pos); shift(b, cmdbuf, &cmdbuf[0], BUFSIZE); b->block_start_line = block_start_line_tmp; b->block_start_pos = block_start_pos_tmp; b->marking = marking_tmp; b->mark_is_vertical = mark_is_vertical_tmp; } do_action(b, GOTOBOOKMARK_A, -1, str_dup("<")); } else if (b->bpaste_support == 2) { strncat(strncpy(cmdbuf, "Macro ", BUFSIZE), b->bpaste_macro_after, BUFSIZE); execute_command_line(b, cmdbuf); } } ne-3.3.4/src/protos.h000066400000000000000000000325761475116431000144300ustar00rootroot00000000000000/* Function prototypes Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2025 Todd M. Lewis and Sebastiano Vigna This file is part of ne, the nice editor. This library 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 library 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 . */ #include "term.h" /* actions.c */ int do_action(buffer *b, action a, int64_t c, char *p); /* autocomp.c */ char *autocomplete(char *p, char *req_msg, const int ext, int * const error); /* buffer.c */ encoding_type detect_buffer_encoding(const buffer *b); char_pool *alloc_char_pool(int64_t size, int fd_or_zero, int force); void free_char_pool(char_pool *cp); char_pool *get_char_pool(buffer *b, char * const p); line_desc_pool *alloc_line_desc_pool(int64_t pool_size, int force); void free_line_desc_pool(line_desc_pool *ldp); buffer *alloc_buffer(const buffer *cur_b); void free_buffer_contents(buffer *b); void clear_buffer(buffer *b); void free_buffer(buffer *b); int64_t calc_lost_chars(const buffer *b); buffer *get_nth_buffer(int n); buffer *get_buffer_named(const char *p); bool is_buffer(const buffer *b); bool is_buffer_empty(const buffer * const b); int modified_buffers(void); int save_all_modified_buffers(void); line_desc *alloc_line_desc(buffer *b); void free_line_desc(buffer *b, line_desc *ld); char *alloc_chars(buffer *b, int64_t len); int64_t alloc_chars_around(buffer *b, line_desc *ld, int64_t n, bool check_first_before); void free_chars(buffer *b, char *p, int64_t len); int insert_one_line(buffer *b, line_desc *ld, int64_t line, int64_t pos); int delete_one_line(buffer *b, line_desc *ld, int64_t line); int undelete_line(buffer *b); void delete_to_eol(buffer *b, line_desc *ld, int64_t line, int64_t pos); int insert_stream(buffer *b, line_desc *ld, int64_t line, int64_t pos, const char *stream, int64_t stream_len); int insert_one_char(buffer *b, line_desc *ld, int64_t line, int64_t pos, int c); int insert_spaces(buffer *b, line_desc *ld, int64_t line, int64_t pos, int64_t n); int delete_stream(buffer *b, line_desc *ld, int64_t line, int64_t pos, int64_t len); int delete_one_char(buffer *b, line_desc *ld, int64_t line, int64_t pos); void change_filename(buffer *b, char *name); void ensure_attr_buf(buffer * const b, const int64_t capacity); int load_file_in_buffer(buffer *b, const char *name); int load_fd_in_buffer(buffer *b, int fd); int save_buffer_to_file(buffer *b, const char *name); void auto_save(buffer *b); void reset_syntax_states(buffer *b); /* clips.c */ clip_desc *alloc_clip_desc(int n, int64_t size); clip_desc *realloc_clip_desc(clip_desc *cd, int n, int64_t size); void free_clip_desc(clip_desc *cd); int is_encoding_neutral(clip_desc *cd); clip_desc *get_nth_clip(int n); int copy_to_clip(buffer *b, int n, bool cut); int erase_block(buffer *b, const bool update); int paste_to_buffer(buffer *b, int n); int copy_vert_to_clip(buffer *b, int n, bool cut); int erase_vert_block(buffer *b, const bool update); int paste_vert_to_buffer(buffer *b, int n); int load_clip(int n, const char *name, bool preserve_cr, bool binary); int save_clip(int n, const char *name, bool CRLF, bool binary); /* command.c */ void build_hash_table(void); void build_command_name_table(void); int parse_command_line(const char *command_line, int64_t *num_arg, char **string_arg, bool exec_only_options); int execute_command_line(buffer *b, const char *command_line); macro_desc *alloc_macro_desc(void); void free_macro_desc(macro_desc *md); void record_action(char_stream *cs, action a, int64_t c, const char *p, bool verbose); int play_macro(char_stream *cs, int64_t c); macro_desc *load_macro(const char *name); int execute_macro(buffer *b, const char *name); void help(char *p); int cmdcmp(const char *c, const char *m); void unload_macros(void); char *find_key_strokes(int c, int n); void optimize_macro(char_stream *cs, bool verbose); int parse_word_parm(char *p, char *pat, int64_t *match); /* display.c */ void update_syntax_states(buffer *b, int row, line_desc *ld, line_desc *end_ld); int highlight_cmp(HIGHLIGHT_STATE *x, HIGHLIGHT_STATE *y); void delay_update(); void output_line_desc(int row, int col, const line_desc *ld, int64_t start, int64_t len, int tab_size, bool cleared_at_end, bool utf8, const uint32_t * const attr, const uint32_t * const diff, const int64_t diff_size); void update_line(buffer *b, line_desc *ld, int n, int64_t start_x, bool cleared_at_end); void update_window_lines(buffer *b, line_desc *ld, int start_line, int end_line, bool doit); void update_syntax_states_delay(buffer *b, line_desc *start_ld, line_desc *end_ld); void update_window(buffer *b); void update_deleted_char(buffer *b, int c, int a, line_desc *ld, int64_t pos, int64_t attr_pos, int line, int x); void update_inserted_char(buffer *b, int c, line_desc *ld, int64_t pos, int64_t attr_pos, int line, int x); void update_overwritten_char(buffer *b, int old_char, int new_char, line_desc *ld, int64_t pos, int64_t attr_pos, int line, int x); void reset_window(void); void refresh_window(buffer *b); void scroll_window(buffer *b, line_desc *ld, int line, int n); void ensure_attributes(buffer *b); void store_attributes(buffer *b, line_desc *ld); void automatch_bracket(buffer * const b, const bool show); void highlight_mark(buffer * const b, const bool show); /* edit.c */ int to_upper(buffer *b); int to_lower(buffer *b); int capitalize(buffer *b); int match_bracket(buffer *b); int find_matching_bracket(buffer *b, const int64_t min_line, const int64_t max_line, int64_t *match_line, int64_t *match_pos, int *c, line_desc ** ld); int64_t word_wrap(buffer *b); int paragraph(buffer * const b, const bool mark_for_undo); int center(buffer *b); int auto_indent_line(buffer * const b, const int64_t line, line_desc * const ld, const int64_t up_to_col); int backtab(buffer *b); int shift(buffer *b, char *p, char *msg, int msg_size); /* errors.c */ /* exec.c */ void new_list(list *l); void add_head(list *l, node *n); void add_tail(list *l, node *n); void rem(node *n); void add(node *n, node *pos); void free_list(list *l, void (func)()); void apply_to_list(list *l, void (func)()); /* ext.c */ const char *ext2syntax(const char * const ext); /* help.c */ /* inputclass.c */ /* keys.c */ void read_key_capabilities(void); void set_escape_time(int new_escape_time); int get_key_code(void); int key_may_set(const char * const cap_string, int code, config_source source); void get_key_bindings(const char *); /* menu.c */ void print_message(const char *message); int search_menu_title(int n, int c); int search_menu_item(int n, int c); void reset_status_bar(void); char *gen_flag_string(const buffer *b); void draw_status_bar(void); int print_error(int error_num); void print_info(int info_num); void alert(void); void handle_menus(void); void get_menu_configuration(const char *); /* names.c */ /* navigation.c */ int adjust_view(buffer *b, const char *p); int char_left(buffer *b); int char_right(buffer *b); int line_down(buffer *b); int line_up(buffer *b); int move_bos(buffer *b); int move_tos(buffer *b); int next_page(buffer *b); int page_down(buffer *b); int page_up(buffer *b); int prev_page(buffer *b); void goto_column(buffer *b, int64_t n); void goto_line_pos(buffer *b, int64_t n, int64_t pos); void goto_line(buffer *b, int64_t n); void goto_pos(buffer *b, int64_t pos); void keep_cursor_on_screen(buffer *b); void move_to_bof(buffer *b); void move_to_eol(buffer *b); void move_to_sof(buffer *b); void move_to_sol(buffer *b); void reset_position_to_sof(buffer *b); void resync_pos(buffer * const b); void toggle_sof_eof(buffer *b); void toggle_sol_eol(buffer *b); int search_word(buffer *b, int dir, bool start); void move_to_eow(buffer *b); void move_to_sow(buffer *b); void move_inc_down(buffer *b); void move_inc_up(buffer *b); /* ne.c */ buffer *new_buffer(void); bool delete_buffer(void); void about(void); void automatch_bracket(buffer *b, bool show); /* prefs.c */ const char *extension(const char *filename); char *exists_prefs_dir(void); char *exists_gprefs_dir(void); int save_prefs(buffer *b, const char *name); int load_prefs(buffer *b, const char *name); int load_syntax_by_name(buffer *b, const char *name); int load_auto_prefs(buffer *b, const char *name); int save_auto_prefs(buffer *b, const char *name); void load_virtual_extensions(); int pop_prefs(buffer *b); int push_prefs(buffer *b); void bracketed_paste_begin(buffer *b); void bracketed_paste_end(buffer *b); /* input.c */ void input_and_prompt_refresh(void); void close_history(void); bool request_response(const buffer *b, const char *prompt, bool default_value); char request_char(const buffer *b, const char *prompt, const char default_value); int64_t request_number(const buffer *b, const char *prompt, int64_t default_value); char *request_string(const buffer *b, const char *prompt, const char *default_string, bool accept_null_string, int completion_type, bool prefer_utf8); /* char *complete_filename(const char *start_prefix); */ char *request(const buffer *b, const char *prompt, const char *default_string, bool alpha_allowed, int completion_type, bool prefer_utf8); /* request.c */ int request_strings(req_list * const rl, int default_entry); char *request_syntax(); char *request_files(const char *filename, bool use_prefix); char *request_file(const buffer *b, const char *prompt, const char *default_name); int request_document(void); int req_list_del(req_list * const rl, int nth); void req_list_free(req_list * const rl); int req_list_init(req_list * const rl, int cmpfnc(const char *, const char *), const bool allow_dupes, const bool allow_reorder, const char suffix); char *req_list_add(req_list * const rl, char * const str, const int suffix); void req_list_finalize(req_list * const rl); /* search.c */ int find(buffer *b, const char *pattern, const bool skip_first, bool wrap_once); int replace(buffer *b, int n, const char *string); int find_regexp(buffer *b, const char *regex, const bool skip_first, bool wrap_once); int replace_regexp(buffer *b, const char *string); char *nth_regex_substring(const line_desc *ld, int i); bool nth_regex_substring_nonempty(const line_desc *ld, int i); /* signals.c */ void stop_ne(void); void set_fatal_code(void); void block_signals(void); void release_signals(void); void set_stop(int sig); void handle_int(int sig); void handle_winch(int sig); /* streams.c */ char_stream *alloc_char_stream(int64_t size); void free_char_stream(char_stream *cs); char_stream *realloc_char_stream(char_stream *cs, int64_t size); char_stream *dup_stream(char_stream * const cs); int add_to_stream(char_stream *cs, const char *s, int64_t len); char_stream *reset_stream(char_stream *cs); void set_stream_encoding(char_stream *cs, encoding_type source); char_stream *load_stream(char_stream *cs, const char *name, bool preserve_cr, bool binary); char_stream *load_stream_from_fd(char_stream *cs, int fd, bool preserve_cr, bool binary); int save_stream(const char_stream *cs, const char *name, bool CRLF, bool binary); int save_stream_to_fd(const char_stream *cs, int fd, bool CRLF, bool binary); int delete_from_stream(char_stream *cs, int64_t p, int64_t len); int insert_in_stream(char_stream *cs, const char *s, int64_t p, int64_t len); /* support.c */ bool same_str(const char *p, const char *q); char *ne_getcwd(const int bufsize); char *relative_file_path(const char *a, const char *b); char *absolute_file_path(const char *a, const char *b); const char *get_global_dir(void); const char *tilde_expand(const char *filename); const char *file_part(const char *pathname); unsigned long file_mod_time(const char *filename); ssize_t read_safely(const int fd, void * const buf, const int64_t len); bool buffer_file_modified(const buffer *b, const char *name); char *str_dup(const char *s); char *strntmp(const char * const s, const int len); int64_t strnlen_ne(const char *s, int64_t n); int strcmpp(const void *a, const void *b); int strdictcmpp(const void *a, const void *b); int strdictcmp(const char *a, const char *b); int filenamecmpp(const void *a, const void *b); int filenamecmp(const char *a, const char *b); void set_interactive_mode(void); void unset_interactive_mode(void); void *alloc_or_mmap(size_t size, int fd_or_zero, int *force); int max_prefix(const char *s, encoding_type s_enc, const char *t, encoding_type t_enc); bool is_prefix(const char *p, const char *s); bool is_migrated(const char *name); bool is_directory(const char *name); encoding_type detect_encoding(const char *s, int64_t len); int context_prefix(const buffer *b, char **p, int64_t *prefix_pos); line_desc *nth_line_desc(const buffer *b, const int64_t n); const char *cur_bookmarks_string(const buffer *b); const char *cur_bracketed_paste_value(const buffer *b); const char *cur_bracketed_paste_string(const buffer *b); /* undo.c */ void start_undo_chain(buffer *b); void end_undo_chain(buffer *b); int add_undo_step(buffer *b, int64_t line, int64_t pos, int64_t len); void fix_last_undo_step(buffer *b, int64_t delta); int add_to_undo_stream(undo_buffer *ub, const char *p, int64_t len); void reset_undo_buffer(undo_buffer *ub); int undo(buffer *b); int redo(buffer *b); ne-3.3.4/src/regcomp.c000066400000000000000000003354711475116431000145310ustar00rootroot00000000000000/* Extended regular expression matching and search library. Copyright (C) 2002-2025 Free Software Foundation, Inc. This file is part of the GNU C Library. Contributed by Isamu Hasegawa . The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. The GNU C Library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with the GNU C Library; if not, see . */ #ifdef _LIBC # include #endif static reg_errcode_t re_compile_internal (regex_t *preg, const char * pattern, size_t length, reg_syntax_t syntax); static void re_compile_fastmap_iter (regex_t *bufp, const re_dfastate_t *init_state, char *fastmap); static reg_errcode_t init_dfa (re_dfa_t *dfa, size_t pat_len); #ifdef RE_ENABLE_I18N static void free_charset (re_charset_t *cset); #endif /* RE_ENABLE_I18N */ static void free_workarea_compile (regex_t *preg); static reg_errcode_t create_initial_state (re_dfa_t *dfa); #ifdef RE_ENABLE_I18N static void optimize_utf8 (re_dfa_t *dfa); #endif static reg_errcode_t analyze (regex_t *preg); static reg_errcode_t preorder (bin_tree_t *root, reg_errcode_t (fn (void *, bin_tree_t *)), void *extra); static reg_errcode_t postorder (bin_tree_t *root, reg_errcode_t (fn (void *, bin_tree_t *)), void *extra); static reg_errcode_t optimize_subexps (void *extra, bin_tree_t *node); static reg_errcode_t lower_subexps (void *extra, bin_tree_t *node); static bin_tree_t *lower_subexp (reg_errcode_t *err, regex_t *preg, bin_tree_t *node); static reg_errcode_t calc_first (void *extra, bin_tree_t *node); static reg_errcode_t calc_next (void *extra, bin_tree_t *node); static reg_errcode_t link_nfa_nodes (void *extra, bin_tree_t *node); static Idx duplicate_node (re_dfa_t *dfa, Idx org_idx, unsigned int constraint); static Idx search_duplicated_node (const re_dfa_t *dfa, Idx org_node, unsigned int constraint); static reg_errcode_t calc_eclosure (re_dfa_t *dfa); static reg_errcode_t calc_eclosure_iter (re_node_set *new_set, re_dfa_t *dfa, Idx node, bool root); static reg_errcode_t calc_inveclosure (re_dfa_t *dfa); static Idx fetch_number (re_string_t *input, re_token_t *token, reg_syntax_t syntax); static int peek_token (re_token_t *token, re_string_t *input, reg_syntax_t syntax) internal_function; static bin_tree_t *parse (re_string_t *regexp, regex_t *preg, reg_syntax_t syntax, reg_errcode_t *err); static bin_tree_t *parse_reg_exp (re_string_t *regexp, regex_t *preg, re_token_t *token, reg_syntax_t syntax, Idx nest, reg_errcode_t *err); static bin_tree_t *parse_branch (re_string_t *regexp, regex_t *preg, re_token_t *token, reg_syntax_t syntax, Idx nest, reg_errcode_t *err); static bin_tree_t *parse_expression (re_string_t *regexp, regex_t *preg, re_token_t *token, reg_syntax_t syntax, Idx nest, reg_errcode_t *err); static bin_tree_t *parse_sub_exp (re_string_t *regexp, regex_t *preg, re_token_t *token, reg_syntax_t syntax, Idx nest, reg_errcode_t *err); static bin_tree_t *parse_dup_op (bin_tree_t *dup_elem, re_string_t *regexp, re_dfa_t *dfa, re_token_t *token, reg_syntax_t syntax, reg_errcode_t *err); static bin_tree_t *parse_bracket_exp (re_string_t *regexp, re_dfa_t *dfa, re_token_t *token, reg_syntax_t syntax, reg_errcode_t *err); static reg_errcode_t parse_bracket_element (bracket_elem_t *elem, re_string_t *regexp, re_token_t *token, int token_len, re_dfa_t *dfa, reg_syntax_t syntax, bool accept_hyphen); static reg_errcode_t parse_bracket_symbol (bracket_elem_t *elem, re_string_t *regexp, re_token_t *token); #ifdef RE_ENABLE_I18N static reg_errcode_t build_equiv_class (bitset_t sbcset, re_charset_t *mbcset, Idx *equiv_class_alloc, const unsigned char *name); static reg_errcode_t build_charclass (RE_TRANSLATE_TYPE trans, bitset_t sbcset, re_charset_t *mbcset, Idx *char_class_alloc, const char *class_name, reg_syntax_t syntax); #else /* not RE_ENABLE_I18N */ static reg_errcode_t build_equiv_class (bitset_t sbcset, const unsigned char *name); static reg_errcode_t build_charclass (RE_TRANSLATE_TYPE trans, bitset_t sbcset, const char *class_name, reg_syntax_t syntax); #endif /* not RE_ENABLE_I18N */ static bin_tree_t *build_charclass_op (re_dfa_t *dfa, RE_TRANSLATE_TYPE trans, const char *class_name, const char *extra, bool non_match, reg_errcode_t *err); static bin_tree_t *create_tree (re_dfa_t *dfa, bin_tree_t *left, bin_tree_t *right, re_token_type_t type); static bin_tree_t *create_token_tree (re_dfa_t *dfa, bin_tree_t *left, bin_tree_t *right, const re_token_t *token); static bin_tree_t *duplicate_tree (const bin_tree_t *src, re_dfa_t *dfa); static void free_token (re_token_t *node); static reg_errcode_t free_tree (void *extra, bin_tree_t *node); static reg_errcode_t mark_opt_subexp (void *extra, bin_tree_t *node); /* This table gives an error message for each of the error codes listed in regex.h. Obviously the order here has to be same as there. POSIX doesn't require that we do anything for REG_NOERROR, but why not be nice? */ static const char __re_error_msgid[] = { #define REG_NOERROR_IDX 0 gettext_noop ("Success") /* REG_NOERROR */ "\0" #define REG_NOMATCH_IDX (REG_NOERROR_IDX + sizeof "Success") gettext_noop ("No match") /* REG_NOMATCH */ "\0" #define REG_BADPAT_IDX (REG_NOMATCH_IDX + sizeof "No match") gettext_noop ("Invalid regular expression") /* REG_BADPAT */ "\0" #define REG_ECOLLATE_IDX (REG_BADPAT_IDX + sizeof "Invalid regular expression") gettext_noop ("Invalid collation character") /* REG_ECOLLATE */ "\0" #define REG_ECTYPE_IDX (REG_ECOLLATE_IDX + sizeof "Invalid collation character") gettext_noop ("Invalid character class name") /* REG_ECTYPE */ "\0" #define REG_EESCAPE_IDX (REG_ECTYPE_IDX + sizeof "Invalid character class name") gettext_noop ("Trailing backslash") /* REG_EESCAPE */ "\0" #define REG_ESUBREG_IDX (REG_EESCAPE_IDX + sizeof "Trailing backslash") gettext_noop ("Invalid back reference") /* REG_ESUBREG */ "\0" #define REG_EBRACK_IDX (REG_ESUBREG_IDX + sizeof "Invalid back reference") gettext_noop ("Unmatched [, [^, [:, [., or [=") /* REG_EBRACK */ "\0" #define REG_EPAREN_IDX (REG_EBRACK_IDX + sizeof "Unmatched [, [^, [:, [., or [=") gettext_noop ("Unmatched ( or \\(") /* REG_EPAREN */ "\0" #define REG_EBRACE_IDX (REG_EPAREN_IDX + sizeof "Unmatched ( or \\(") gettext_noop ("Unmatched \\{") /* REG_EBRACE */ "\0" #define REG_BADBR_IDX (REG_EBRACE_IDX + sizeof "Unmatched \\{") gettext_noop ("Invalid content of \\{\\}") /* REG_BADBR */ "\0" #define REG_ERANGE_IDX (REG_BADBR_IDX + sizeof "Invalid content of \\{\\}") gettext_noop ("Invalid range end") /* REG_ERANGE */ "\0" #define REG_ESPACE_IDX (REG_ERANGE_IDX + sizeof "Invalid range end") gettext_noop ("Memory exhausted") /* REG_ESPACE */ "\0" #define REG_BADRPT_IDX (REG_ESPACE_IDX + sizeof "Memory exhausted") gettext_noop ("Invalid preceding regular expression") /* REG_BADRPT */ "\0" #define REG_EEND_IDX (REG_BADRPT_IDX + sizeof "Invalid preceding regular expression") gettext_noop ("Premature end of regular expression") /* REG_EEND */ "\0" #define REG_ESIZE_IDX (REG_EEND_IDX + sizeof "Premature end of regular expression") gettext_noop ("Regular expression too big") /* REG_ESIZE */ "\0" #define REG_ERPAREN_IDX (REG_ESIZE_IDX + sizeof "Regular expression too big") gettext_noop ("Unmatched ) or \\)") /* REG_ERPAREN */ }; static const size_t __re_error_msgid_idx[] = { REG_NOERROR_IDX, REG_NOMATCH_IDX, REG_BADPAT_IDX, REG_ECOLLATE_IDX, REG_ECTYPE_IDX, REG_EESCAPE_IDX, REG_ESUBREG_IDX, REG_EBRACK_IDX, REG_EPAREN_IDX, REG_EBRACE_IDX, REG_BADBR_IDX, REG_ERANGE_IDX, REG_ESPACE_IDX, REG_BADRPT_IDX, REG_EEND_IDX, REG_ESIZE_IDX, REG_ERPAREN_IDX }; /* Entry points for GNU code. */ /* re_compile_pattern is the GNU regular expression compiler: it compiles PATTERN (of length LENGTH) and puts the result in BUFP. Returns 0 if the pattern was valid, otherwise an error string. Assumes the 'allocated' (and perhaps 'buffer') and 'translate' fields are set in BUFP on entry. */ const char * re_compile_pattern (const char *pattern, size_t length, struct re_pattern_buffer *bufp) { reg_errcode_t ret; /* And GNU code determines whether or not to get register information by passing null for the REGS argument to re_match, etc., not by setting no_sub, unless RE_NO_SUB is set. */ bufp->no_sub = !!(re_syntax_options & RE_NO_SUB); /* Match anchors at newline. */ bufp->newline_anchor = 1; ret = re_compile_internal (bufp, pattern, length, re_syntax_options); if (!ret) return NULL; return gettext (__re_error_msgid + __re_error_msgid_idx[(int) ret]); } #ifdef _LIBC weak_alias (__re_compile_pattern, re_compile_pattern) #endif /* Set by 're_set_syntax' to the current regexp syntax to recognize. Can also be assigned to arbitrarily: each pattern buffer stores its own syntax, so it can be changed between regex compilations. */ /* This has no initializer because initialized variables in Emacs become read-only after dumping. */ reg_syntax_t re_syntax_options; /* Specify the precise syntax of regexps for compilation. This provides for compatibility for various utilities which historically have different, incompatible syntaxes. The argument SYNTAX is a bit mask comprised of the various bits defined in regex.h. We return the old syntax. */ reg_syntax_t re_set_syntax (reg_syntax_t syntax) { reg_syntax_t ret = re_syntax_options; re_syntax_options = syntax; return ret; } #ifdef _LIBC weak_alias (__re_set_syntax, re_set_syntax) #endif int re_compile_fastmap (struct re_pattern_buffer *bufp) { re_dfa_t *dfa = bufp->buffer; char *fastmap = bufp->fastmap; memset (fastmap, '\0', sizeof (char) * SBC_MAX); re_compile_fastmap_iter (bufp, dfa->init_state, fastmap); if (dfa->init_state != dfa->init_state_word) re_compile_fastmap_iter (bufp, dfa->init_state_word, fastmap); if (dfa->init_state != dfa->init_state_nl) re_compile_fastmap_iter (bufp, dfa->init_state_nl, fastmap); if (dfa->init_state != dfa->init_state_begbuf) re_compile_fastmap_iter (bufp, dfa->init_state_begbuf, fastmap); bufp->fastmap_accurate = 1; return 0; } #ifdef _LIBC weak_alias (__re_compile_fastmap, re_compile_fastmap) #endif static inline void __attribute__ ((always_inline)) re_set_fastmap (char *fastmap, bool icase, int ch) { fastmap[ch] = 1; if (icase) fastmap[tolower (ch)] = 1; } /* Helper function for re_compile_fastmap. Compile fastmap for the initial_state INIT_STATE. */ static void re_compile_fastmap_iter (regex_t *bufp, const re_dfastate_t *init_state, char *fastmap) { re_dfa_t *dfa = bufp->buffer; Idx node_cnt; bool icase = (dfa->mb_cur_max == 1 && (bufp->syntax & RE_ICASE)); for (node_cnt = 0; node_cnt < init_state->nodes.nelem; ++node_cnt) { Idx node = init_state->nodes.elems[node_cnt]; re_token_type_t type = dfa->nodes[node].type; if (type == CHARACTER) { re_set_fastmap (fastmap, icase, dfa->nodes[node].opr.c); #ifdef RE_ENABLE_I18N if ((bufp->syntax & RE_ICASE) && dfa->mb_cur_max > 1) { unsigned char buf[MB_LEN_MAX]; unsigned char *p; wchar_t wc; mbstate_t state; p = buf; *p++ = dfa->nodes[node].opr.c; while (++node < dfa->nodes_len && dfa->nodes[node].type == CHARACTER && dfa->nodes[node].mb_partial) *p++ = dfa->nodes[node].opr.c; memset (&state, '\0', sizeof (state)); if (__mbrtowc (&wc, (const char *) buf, p - buf, &state) == p - buf && (__wcrtomb ((char *) buf, __towlower (wc), &state) != (size_t) -1)) re_set_fastmap (fastmap, false, buf[0]); } #endif } else if (type == SIMPLE_BRACKET) { int i, ch; for (i = 0, ch = 0; i < BITSET_WORDS; ++i) { int j; bitset_word_t w = dfa->nodes[node].opr.sbcset[i]; for (j = 0; j < BITSET_WORD_BITS; ++j, ++ch) if (w & ((bitset_word_t) 1 << j)) re_set_fastmap (fastmap, icase, ch); } } #ifdef RE_ENABLE_I18N else if (type == COMPLEX_BRACKET) { re_charset_t *cset = dfa->nodes[node].opr.mbcset; Idx i; # ifdef _LIBC /* See if we have to try all bytes which start multiple collation elements. e.g. In da_DK, we want to catch 'a' since "aa" is a valid collation element, and don't catch 'b' since 'b' is the only collation element which starts from 'b' (and it is caught by SIMPLE_BRACKET). */ if (_NL_CURRENT_WORD (LC_COLLATE, _NL_COLLATE_NRULES) != 0 && (cset->ncoll_syms || cset->nranges)) { const int32_t *table = (const int32_t *) _NL_CURRENT (LC_COLLATE, _NL_COLLATE_TABLEMB); for (i = 0; i < SBC_MAX; ++i) if (table[i] < 0) re_set_fastmap (fastmap, icase, i); } # endif /* _LIBC */ /* See if we have to start the match at all multibyte characters, i.e. where we would not find an invalid sequence. This only applies to multibyte character sets; for single byte character sets, the SIMPLE_BRACKET again suffices. */ if (dfa->mb_cur_max > 1 && (cset->nchar_classes || cset->non_match || cset->nranges # ifdef _LIBC || cset->nequiv_classes # endif /* _LIBC */ )) { unsigned char c = 0; do { mbstate_t mbs; memset (&mbs, 0, sizeof (mbs)); if (__mbrtowc (NULL, (char *) &c, 1, &mbs) == (size_t) -2) re_set_fastmap (fastmap, false, (int) c); } while (++c != 0); } else { /* ... Else catch all bytes which can start the mbchars. */ for (i = 0; i < cset->nmbchars; ++i) { char buf[256]; mbstate_t state; memset (&state, '\0', sizeof (state)); if (__wcrtomb (buf, cset->mbchars[i], &state) != (size_t) -1) re_set_fastmap (fastmap, icase, *(unsigned char *) buf); if ((bufp->syntax & RE_ICASE) && dfa->mb_cur_max > 1) { if (__wcrtomb (buf, __towlower (cset->mbchars[i]), &state) != (size_t) -1) re_set_fastmap (fastmap, false, *(unsigned char *) buf); } } } } #endif /* RE_ENABLE_I18N */ else if (type == OP_PERIOD #ifdef RE_ENABLE_I18N || type == OP_UTF8_PERIOD #endif /* RE_ENABLE_I18N */ || type == END_OF_RE) { memset (fastmap, '\1', sizeof (char) * SBC_MAX); if (type == END_OF_RE) bufp->can_be_null = 1; return; } } } /* Entry point for POSIX code. */ /* regcomp takes a regular expression as a string and compiles it. PREG is a regex_t *. We do not expect any fields to be initialized, since POSIX says we shouldn't. Thus, we set 'buffer' to the compiled pattern; 'used' to the length of the compiled pattern; 'syntax' to RE_SYNTAX_POSIX_EXTENDED if the REG_EXTENDED bit in CFLAGS is set; otherwise, to RE_SYNTAX_POSIX_BASIC; 'newline_anchor' to REG_NEWLINE being set in CFLAGS; 'fastmap' to an allocated space for the fastmap; 'fastmap_accurate' to zero; 're_nsub' to the number of subexpressions in PATTERN. PATTERN is the address of the pattern string. CFLAGS is a series of bits which affect compilation. If REG_EXTENDED is set, we use POSIX extended syntax; otherwise, we use POSIX basic syntax. If REG_NEWLINE is set, then . and [^...] don't match newline. Also, regexec will try a match beginning after every newline. If REG_ICASE is set, then we considers upper- and lowercase versions of letters to be equivalent when matching. If REG_NOSUB is set, then when PREG is passed to regexec, that routine will report only success or failure, and nothing about the registers. It returns 0 if it succeeds, nonzero if it doesn't. (See regex.h for the return codes and their meanings.) */ int regcomp (regex_t *_Restrict_ preg, const char *_Restrict_ pattern, int cflags) { reg_errcode_t ret; reg_syntax_t syntax = ((cflags & REG_EXTENDED) ? RE_SYNTAX_POSIX_EXTENDED : RE_SYNTAX_POSIX_BASIC); preg->buffer = NULL; preg->allocated = 0; preg->used = 0; /* Try to allocate space for the fastmap. */ preg->fastmap = re_malloc (char, SBC_MAX); if (BE (preg->fastmap == NULL, 0)) return REG_ESPACE; syntax |= (cflags & REG_ICASE) ? RE_ICASE : 0; /* If REG_NEWLINE is set, newlines are treated differently. */ if (cflags & REG_NEWLINE) { /* REG_NEWLINE implies neither . nor [^...] match newline. */ syntax &= ~RE_DOT_NEWLINE; syntax |= RE_HAT_LISTS_NOT_NEWLINE; /* It also changes the matching behavior. */ preg->newline_anchor = 1; } else preg->newline_anchor = 0; preg->no_sub = !!(cflags & REG_NOSUB); preg->translate = NULL; ret = re_compile_internal (preg, pattern, strlen (pattern), syntax); /* POSIX doesn't distinguish between an unmatched open-group and an unmatched close-group: both are REG_EPAREN. */ if (ret == REG_ERPAREN) ret = REG_EPAREN; /* We have already checked preg->fastmap != NULL. */ if (BE (ret == REG_NOERROR, 1)) /* Compute the fastmap now, since regexec cannot modify the pattern buffer. This function never fails in this implementation. */ (void) re_compile_fastmap (preg); else { /* Some error occurred while compiling the expression. */ re_free (preg->fastmap); preg->fastmap = NULL; } return (int) ret; } #ifdef _LIBC weak_alias (__regcomp, regcomp) #endif /* Returns a message corresponding to an error code, ERRCODE, returned from either regcomp or regexec. We don't use PREG here. */ size_t regerror (int errcode, const regex_t *_Restrict_ preg, char *_Restrict_ errbuf, size_t errbuf_size) { const char *msg; size_t msg_size; if (BE (errcode < 0 || errcode >= (int) (sizeof (__re_error_msgid_idx) / sizeof (__re_error_msgid_idx[0])), 0)) /* Only error codes returned by the rest of the code should be passed to this routine. If we are given anything else, or if other regex code generates an invalid error code, then the program has a bug. Dump core so we can fix it. */ abort (); msg = gettext (__re_error_msgid + __re_error_msgid_idx[errcode]); msg_size = strlen (msg) + 1; /* Includes the null. */ if (BE (errbuf_size != 0, 1)) { size_t cpy_size = msg_size; if (BE (msg_size > errbuf_size, 0)) { cpy_size = errbuf_size - 1; errbuf[cpy_size] = '\0'; } memcpy (errbuf, msg, cpy_size); } return msg_size; } #ifdef _LIBC weak_alias (__regerror, regerror) #endif #ifdef RE_ENABLE_I18N /* This static array is used for the map to single-byte characters when UTF-8 is used. Otherwise we would allocate memory just to initialize it the same all the time. UTF-8 is the preferred encoding so this is a worthwhile optimization. */ static const bitset_t utf8_sb_map = { /* Set the first 128 bits. */ # if defined __GNUC__ && !defined __STRICT_ANSI__ [0 ... 0x80 / BITSET_WORD_BITS - 1] = BITSET_WORD_MAX # else # if 4 * BITSET_WORD_BITS < ASCII_CHARS # error "bitset_word_t is narrower than 32 bits" # elif 3 * BITSET_WORD_BITS < ASCII_CHARS BITSET_WORD_MAX, BITSET_WORD_MAX, BITSET_WORD_MAX, # elif 2 * BITSET_WORD_BITS < ASCII_CHARS BITSET_WORD_MAX, BITSET_WORD_MAX, # elif 1 * BITSET_WORD_BITS < ASCII_CHARS BITSET_WORD_MAX, # endif (BITSET_WORD_MAX >> (SBC_MAX % BITSET_WORD_BITS == 0 ? 0 : BITSET_WORD_BITS - SBC_MAX % BITSET_WORD_BITS)) # endif }; #endif static void free_dfa_content (re_dfa_t *dfa) { Idx i, j; if (dfa->nodes) for (i = 0; i < dfa->nodes_len; ++i) free_token (dfa->nodes + i); re_free (dfa->nexts); for (i = 0; i < dfa->nodes_len; ++i) { if (dfa->eclosures != NULL) re_node_set_free (dfa->eclosures + i); if (dfa->inveclosures != NULL) re_node_set_free (dfa->inveclosures + i); if (dfa->edests != NULL) re_node_set_free (dfa->edests + i); } re_free (dfa->edests); re_free (dfa->eclosures); re_free (dfa->inveclosures); re_free (dfa->nodes); if (dfa->state_table) for (i = 0; i <= dfa->state_hash_mask; ++i) { struct re_state_table_entry *entry = dfa->state_table + i; for (j = 0; j < entry->num; ++j) { re_dfastate_t *state = entry->array[j]; free_state (state); } re_free (entry->array); } re_free (dfa->state_table); #ifdef RE_ENABLE_I18N if (dfa->sb_char != utf8_sb_map) re_free (dfa->sb_char); #endif re_free (dfa->subexp_map); #ifdef DEBUG re_free (dfa->re_str); #endif re_free (dfa); } /* Free dynamically allocated space used by PREG. */ void regfree (regex_t *preg) { re_dfa_t *dfa = preg->buffer; if (BE (dfa != NULL, 1)) { lock_fini (dfa->lock); free_dfa_content (dfa); } preg->buffer = NULL; preg->allocated = 0; re_free (preg->fastmap); preg->fastmap = NULL; re_free (preg->translate); preg->translate = NULL; } #ifdef _LIBC weak_alias (__regfree, regfree) #endif /* Entry points compatible with 4.2 BSD regex library. We don't define them unless specifically requested. */ #if defined _REGEX_RE_COMP || defined _LIBC /* BSD has one and only one pattern buffer. */ static struct re_pattern_buffer re_comp_buf; char * # ifdef _LIBC /* Make these definitions weak in libc, so POSIX programs can redefine these names if they don't use our functions, and still use regcomp/regexec above without link errors. */ weak_function # endif re_comp (const char *s) { reg_errcode_t ret; char *fastmap; if (!s) { if (!re_comp_buf.buffer) return gettext ("No previous regular expression"); return 0; } if (re_comp_buf.buffer) { fastmap = re_comp_buf.fastmap; re_comp_buf.fastmap = NULL; __regfree (&re_comp_buf); memset (&re_comp_buf, '\0', sizeof (re_comp_buf)); re_comp_buf.fastmap = fastmap; } if (re_comp_buf.fastmap == NULL) { re_comp_buf.fastmap = (char *) malloc (SBC_MAX); if (re_comp_buf.fastmap == NULL) return (char *) gettext (__re_error_msgid + __re_error_msgid_idx[(int) REG_ESPACE]); } /* Since 're_exec' always passes NULL for the 'regs' argument, we don't need to initialize the pattern buffer fields which affect it. */ /* Match anchors at newlines. */ re_comp_buf.newline_anchor = 1; ret = re_compile_internal (&re_comp_buf, s, strlen (s), re_syntax_options); if (!ret) return NULL; /* Yes, we're discarding 'const' here if !HAVE_LIBINTL. */ return (char *) gettext (__re_error_msgid + __re_error_msgid_idx[(int) ret]); } #ifdef _LIBC libc_freeres_fn (free_mem) { __regfree (&re_comp_buf); } #endif #endif /* _REGEX_RE_COMP */ /* Internal entry point. Compile the regular expression PATTERN, whose length is LENGTH. SYNTAX indicate regular expression's syntax. */ static reg_errcode_t re_compile_internal (regex_t *preg, const char * pattern, size_t length, reg_syntax_t syntax) { reg_errcode_t err = REG_NOERROR; re_dfa_t *dfa; re_string_t regexp; /* Initialize the pattern buffer. */ preg->fastmap_accurate = 0; preg->syntax = syntax; preg->not_bol = preg->not_eol = 0; preg->used = 0; preg->re_nsub = 0; preg->can_be_null = 0; preg->regs_allocated = REGS_UNALLOCATED; /* Initialize the dfa. */ dfa = preg->buffer; if (BE (preg->allocated < sizeof (re_dfa_t), 0)) { /* If zero allocated, but buffer is non-null, try to realloc enough space. This loses if buffer's address is bogus, but that is the user's responsibility. If ->buffer is NULL this is a simple allocation. */ dfa = re_realloc (preg->buffer, re_dfa_t, 1); if (dfa == NULL) return REG_ESPACE; preg->allocated = sizeof (re_dfa_t); preg->buffer = dfa; } preg->used = sizeof (re_dfa_t); err = init_dfa (dfa, length); if (BE (err == REG_NOERROR && lock_init (dfa->lock) != 0, 0)) err = REG_ESPACE; if (BE (err != REG_NOERROR, 0)) { free_dfa_content (dfa); preg->buffer = NULL; preg->allocated = 0; return err; } #ifdef DEBUG /* Note: length+1 will not overflow since it is checked in init_dfa. */ dfa->re_str = re_malloc (char, length + 1); strncpy (dfa->re_str, pattern, length + 1); #endif err = re_string_construct (®exp, pattern, length, preg->translate, (syntax & RE_ICASE) != 0, dfa); if (BE (err != REG_NOERROR, 0)) { re_compile_internal_free_return: free_workarea_compile (preg); re_string_destruct (®exp); lock_fini (dfa->lock); free_dfa_content (dfa); preg->buffer = NULL; preg->allocated = 0; return err; } /* Parse the regular expression, and build a structure tree. */ preg->re_nsub = 0; dfa->str_tree = parse (®exp, preg, syntax, &err); if (BE (dfa->str_tree == NULL, 0)) goto re_compile_internal_free_return; /* Analyze the tree and create the nfa. */ err = analyze (preg); if (BE (err != REG_NOERROR, 0)) goto re_compile_internal_free_return; #ifdef RE_ENABLE_I18N /* If possible, do searching in single byte encoding to speed things up. */ if (dfa->is_utf8 && !(syntax & RE_ICASE) && preg->translate == NULL) optimize_utf8 (dfa); #endif /* Then create the initial state of the dfa. */ err = create_initial_state (dfa); /* Release work areas. */ free_workarea_compile (preg); re_string_destruct (®exp); if (BE (err != REG_NOERROR, 0)) { lock_fini (dfa->lock); free_dfa_content (dfa); preg->buffer = NULL; preg->allocated = 0; } return err; } /* Initialize DFA. We use the length of the regular expression PAT_LEN as the initial length of some arrays. */ static reg_errcode_t init_dfa (re_dfa_t *dfa, size_t pat_len) { __re_size_t table_size; #ifndef _LIBC const char *codeset_name; #endif #ifdef RE_ENABLE_I18N size_t max_i18n_object_size = MAX (sizeof (wchar_t), sizeof (wctype_t)); #else size_t max_i18n_object_size = 0; #endif size_t max_object_size = MAX (sizeof (struct re_state_table_entry), MAX (sizeof (re_token_t), MAX (sizeof (re_node_set), MAX (sizeof (regmatch_t), max_i18n_object_size)))); memset (dfa, '\0', sizeof (re_dfa_t)); /* Force allocation of str_tree_storage the first time. */ dfa->str_tree_storage_idx = BIN_TREE_STORAGE_SIZE; /* Avoid overflows. The extra "/ 2" is for the table_size doubling calculation below, and for similar doubling calculations elsewhere. And it's <= rather than <, because some of the doubling calculations add 1 afterwards. */ if (BE (MIN (IDX_MAX, SIZE_MAX / max_object_size) / 2 <= pat_len, 0)) return REG_ESPACE; dfa->nodes_alloc = pat_len + 1; dfa->nodes = re_malloc (re_token_t, dfa->nodes_alloc); /* table_size = 2 ^ ceil(log pat_len) */ for (table_size = 1; ; table_size <<= 1) if (table_size > pat_len) break; dfa->state_table = calloc (sizeof (struct re_state_table_entry), table_size); dfa->state_hash_mask = table_size - 1; dfa->mb_cur_max = MB_CUR_MAX; #ifdef _LIBC if (dfa->mb_cur_max == 6 && strcmp (_NL_CURRENT (LC_CTYPE, _NL_CTYPE_CODESET_NAME), "UTF-8") == 0) dfa->is_utf8 = 1; dfa->map_notascii = (_NL_CURRENT_WORD (LC_CTYPE, _NL_CTYPE_MAP_TO_NONASCII) != 0); #else codeset_name = nl_langinfo (CODESET); if ((codeset_name[0] == 'U' || codeset_name[0] == 'u') && (codeset_name[1] == 'T' || codeset_name[1] == 't') && (codeset_name[2] == 'F' || codeset_name[2] == 'f') && strcmp (codeset_name + 3 + (codeset_name[3] == '-'), "8") == 0) dfa->is_utf8 = 1; /* We check exhaustively in the loop below if this charset is a superset of ASCII. */ dfa->map_notascii = 0; #endif #ifdef RE_ENABLE_I18N if (dfa->mb_cur_max > 1) { if (dfa->is_utf8) dfa->sb_char = (re_bitset_ptr_t) utf8_sb_map; else { int i, j, ch; dfa->sb_char = (re_bitset_ptr_t) calloc (sizeof (bitset_t), 1); if (BE (dfa->sb_char == NULL, 0)) return REG_ESPACE; /* Set the bits corresponding to single byte chars. */ for (i = 0, ch = 0; i < BITSET_WORDS; ++i) for (j = 0; j < BITSET_WORD_BITS; ++j, ++ch) { wint_t wch = __btowc (ch); if (wch != WEOF) dfa->sb_char[i] |= (bitset_word_t) 1 << j; # ifndef _LIBC if (isascii (ch) && wch != ch) dfa->map_notascii = 1; # endif } } } #endif if (BE (dfa->nodes == NULL || dfa->state_table == NULL, 0)) return REG_ESPACE; return REG_NOERROR; } /* Initialize WORD_CHAR table, which indicate which character is "word". In this case "word" means that it is the word construction character used by some operators like "\<", "\>", etc. */ static void internal_function init_word_char (re_dfa_t *dfa) { int i = 0; int j; int ch = 0; dfa->word_ops_used = 1; if (BE (dfa->map_notascii == 0, 1)) { bitset_word_t bits0 = 0x00000000; bitset_word_t bits1 = 0x03ff0000; bitset_word_t bits2 = 0x87fffffe; bitset_word_t bits3 = 0x07fffffe; if (BITSET_WORD_BITS == 64) { dfa->word_char[0] = bits1 << 31 << 1 | bits0; dfa->word_char[1] = bits3 << 31 << 1 | bits2; i = 2; } else if (BITSET_WORD_BITS == 32) { dfa->word_char[0] = bits0; dfa->word_char[1] = bits1; dfa->word_char[2] = bits2; dfa->word_char[3] = bits3; i = 4; } else goto general_case; ch = 128; if (BE (dfa->is_utf8, 1)) { memset (&dfa->word_char[i], '\0', (SBC_MAX - ch) / 8); return; } } general_case: for (; i < BITSET_WORDS; ++i) for (j = 0; j < BITSET_WORD_BITS; ++j, ++ch) if (isalnum (ch) || ch == '_') dfa->word_char[i] |= (bitset_word_t) 1 << j; } /* Free the work area which are only used while compiling. */ static void free_workarea_compile (regex_t *preg) { re_dfa_t *dfa = preg->buffer; bin_tree_storage_t *storage, *next; for (storage = dfa->str_tree_storage; storage; storage = next) { next = storage->next; re_free (storage); } dfa->str_tree_storage = NULL; dfa->str_tree_storage_idx = BIN_TREE_STORAGE_SIZE; dfa->str_tree = NULL; re_free (dfa->org_indices); dfa->org_indices = NULL; } /* Create initial states for all contexts. */ static reg_errcode_t create_initial_state (re_dfa_t *dfa) { Idx first, i; reg_errcode_t err; re_node_set init_nodes; /* Initial states have the epsilon closure of the node which is the first node of the regular expression. */ first = dfa->str_tree->first->node_idx; dfa->init_node = first; err = re_node_set_init_copy (&init_nodes, dfa->eclosures + first); if (BE (err != REG_NOERROR, 0)) return err; /* The back-references which are in initial states can epsilon transit, since in this case all of the subexpressions can be null. Then we add epsilon closures of the nodes which are the next nodes of the back-references. */ if (dfa->nbackref > 0) for (i = 0; i < init_nodes.nelem; ++i) { Idx node_idx = init_nodes.elems[i]; re_token_type_t type = dfa->nodes[node_idx].type; Idx clexp_idx; if (type != OP_BACK_REF) continue; for (clexp_idx = 0; clexp_idx < init_nodes.nelem; ++clexp_idx) { re_token_t *clexp_node; clexp_node = dfa->nodes + init_nodes.elems[clexp_idx]; if (clexp_node->type == OP_CLOSE_SUBEXP && clexp_node->opr.idx == dfa->nodes[node_idx].opr.idx) break; } if (clexp_idx == init_nodes.nelem) continue; if (type == OP_BACK_REF) { Idx dest_idx = dfa->edests[node_idx].elems[0]; if (!re_node_set_contains (&init_nodes, dest_idx)) { reg_errcode_t merge_err = re_node_set_merge (&init_nodes, dfa->eclosures + dest_idx); if (merge_err != REG_NOERROR) return merge_err; i = 0; } } } /* It must be the first time to invoke acquire_state. */ dfa->init_state = re_acquire_state_context (&err, dfa, &init_nodes, 0); /* We don't check ERR here, since the initial state must not be NULL. */ if (BE (dfa->init_state == NULL, 0)) return err; if (dfa->init_state->has_constraint) { dfa->init_state_word = re_acquire_state_context (&err, dfa, &init_nodes, CONTEXT_WORD); dfa->init_state_nl = re_acquire_state_context (&err, dfa, &init_nodes, CONTEXT_NEWLINE); dfa->init_state_begbuf = re_acquire_state_context (&err, dfa, &init_nodes, CONTEXT_NEWLINE | CONTEXT_BEGBUF); if (BE (dfa->init_state_word == NULL || dfa->init_state_nl == NULL || dfa->init_state_begbuf == NULL, 0)) return err; } else dfa->init_state_word = dfa->init_state_nl = dfa->init_state_begbuf = dfa->init_state; re_node_set_free (&init_nodes); return REG_NOERROR; } #ifdef RE_ENABLE_I18N /* If it is possible to do searching in single byte encoding instead of UTF-8 to speed things up, set dfa->mb_cur_max to 1, clear is_utf8 and change DFA nodes where needed. */ static void optimize_utf8 (re_dfa_t *dfa) { Idx node; int i; bool mb_chars = false; bool has_period = false; for (node = 0; node < dfa->nodes_len; ++node) switch (dfa->nodes[node].type) { case CHARACTER: if (dfa->nodes[node].opr.c >= ASCII_CHARS) mb_chars = true; break; case ANCHOR: switch (dfa->nodes[node].opr.ctx_type) { case LINE_FIRST: case LINE_LAST: case BUF_FIRST: case BUF_LAST: break; default: /* Word anchors etc. cannot be handled. It's okay to test opr.ctx_type since constraints (for all DFA nodes) are created by ORing one or more opr.ctx_type values. */ return; } break; case OP_PERIOD: has_period = true; break; case OP_BACK_REF: case OP_ALT: case END_OF_RE: case OP_DUP_ASTERISK: case OP_OPEN_SUBEXP: case OP_CLOSE_SUBEXP: break; case COMPLEX_BRACKET: return; case SIMPLE_BRACKET: /* Just double check. */ { int rshift = (ASCII_CHARS % BITSET_WORD_BITS == 0 ? 0 : BITSET_WORD_BITS - ASCII_CHARS % BITSET_WORD_BITS); for (i = ASCII_CHARS / BITSET_WORD_BITS; i < BITSET_WORDS; ++i) { if (dfa->nodes[node].opr.sbcset[i] >> rshift != 0) return; rshift = 0; } } break; default: abort (); } if (mb_chars || has_period) for (node = 0; node < dfa->nodes_len; ++node) { if (dfa->nodes[node].type == CHARACTER && dfa->nodes[node].opr.c >= ASCII_CHARS) dfa->nodes[node].mb_partial = 0; else if (dfa->nodes[node].type == OP_PERIOD) dfa->nodes[node].type = OP_UTF8_PERIOD; } /* The search can be in single byte locale. */ dfa->mb_cur_max = 1; dfa->is_utf8 = 0; dfa->has_mb_node = dfa->nbackref > 0 || has_period; } #endif /* Analyze the structure tree, and calculate "first", "next", "edest", "eclosure", and "inveclosure". */ static reg_errcode_t analyze (regex_t *preg) { re_dfa_t *dfa = preg->buffer; reg_errcode_t ret; /* Allocate arrays. */ dfa->nexts = re_malloc (Idx, dfa->nodes_alloc); dfa->org_indices = re_malloc (Idx, dfa->nodes_alloc); dfa->edests = re_malloc (re_node_set, dfa->nodes_alloc); dfa->eclosures = re_malloc (re_node_set, dfa->nodes_alloc); if (BE (dfa->nexts == NULL || dfa->org_indices == NULL || dfa->edests == NULL || dfa->eclosures == NULL, 0)) return REG_ESPACE; dfa->subexp_map = re_malloc (Idx, preg->re_nsub); if (dfa->subexp_map != NULL) { Idx i; for (i = 0; i < preg->re_nsub; i++) dfa->subexp_map[i] = i; preorder (dfa->str_tree, optimize_subexps, dfa); for (i = 0; i < preg->re_nsub; i++) if (dfa->subexp_map[i] != i) break; if (i == preg->re_nsub) { free (dfa->subexp_map); dfa->subexp_map = NULL; } } ret = postorder (dfa->str_tree, lower_subexps, preg); if (BE (ret != REG_NOERROR, 0)) return ret; ret = postorder (dfa->str_tree, calc_first, dfa); if (BE (ret != REG_NOERROR, 0)) return ret; preorder (dfa->str_tree, calc_next, dfa); ret = preorder (dfa->str_tree, link_nfa_nodes, dfa); if (BE (ret != REG_NOERROR, 0)) return ret; ret = calc_eclosure (dfa); if (BE (ret != REG_NOERROR, 0)) return ret; /* We only need this during the prune_impossible_nodes pass in regexec.c; skip it if p_i_n will not run, as calc_inveclosure can be quadratic. */ if ((!preg->no_sub && preg->re_nsub > 0 && dfa->has_plural_match) || dfa->nbackref) { dfa->inveclosures = re_malloc (re_node_set, dfa->nodes_len); if (BE (dfa->inveclosures == NULL, 0)) return REG_ESPACE; ret = calc_inveclosure (dfa); } return ret; } /* Our parse trees are very unbalanced, so we cannot use a stack to implement parse tree visits. Instead, we use parent pointers and some hairy code in these two functions. */ static reg_errcode_t postorder (bin_tree_t *root, reg_errcode_t (fn (void *, bin_tree_t *)), void *extra) { bin_tree_t *node, *prev; for (node = root; ; ) { /* Descend down the tree, preferably to the left (or to the right if that's the only child). */ while (node->left || node->right) if (node->left) node = node->left; else node = node->right; do { reg_errcode_t err = fn (extra, node); if (BE (err != REG_NOERROR, 0)) return err; if (node->parent == NULL) return REG_NOERROR; prev = node; node = node->parent; } /* Go up while we have a node that is reached from the right. */ while (node->right == prev || node->right == NULL); node = node->right; } } static reg_errcode_t preorder (bin_tree_t *root, reg_errcode_t (fn (void *, bin_tree_t *)), void *extra) { bin_tree_t *node; for (node = root; ; ) { reg_errcode_t err = fn (extra, node); if (BE (err != REG_NOERROR, 0)) return err; /* Go to the left node, or up and to the right. */ if (node->left) node = node->left; else { bin_tree_t *prev = NULL; while (node->right == prev || node->right == NULL) { prev = node; node = node->parent; if (!node) return REG_NOERROR; } node = node->right; } } } /* Optimization pass: if a SUBEXP is entirely contained, strip it and tell re_search_internal to map the inner one's opr.idx to this one's. Adjust backreferences as well. Requires a preorder visit. */ static reg_errcode_t optimize_subexps (void *extra, bin_tree_t *node) { re_dfa_t *dfa = (re_dfa_t *) extra; if (node->token.type == OP_BACK_REF && dfa->subexp_map) { int idx = node->token.opr.idx; node->token.opr.idx = dfa->subexp_map[idx]; dfa->used_bkref_map |= 1 << node->token.opr.idx; } else if (node->token.type == SUBEXP && node->left && node->left->token.type == SUBEXP) { Idx other_idx = node->left->token.opr.idx; node->left = node->left->left; if (node->left) node->left->parent = node; dfa->subexp_map[other_idx] = dfa->subexp_map[node->token.opr.idx]; if (other_idx < BITSET_WORD_BITS) dfa->used_bkref_map &= ~((bitset_word_t) 1 << other_idx); } return REG_NOERROR; } /* Lowering pass: Turn each SUBEXP node into the appropriate concatenation of OP_OPEN_SUBEXP, the body of the SUBEXP (if any) and OP_CLOSE_SUBEXP. */ static reg_errcode_t lower_subexps (void *extra, bin_tree_t *node) { regex_t *preg = (regex_t *) extra; reg_errcode_t err = REG_NOERROR; if (node->left && node->left->token.type == SUBEXP) { node->left = lower_subexp (&err, preg, node->left); if (node->left) node->left->parent = node; } if (node->right && node->right->token.type == SUBEXP) { node->right = lower_subexp (&err, preg, node->right); if (node->right) node->right->parent = node; } return err; } static bin_tree_t * lower_subexp (reg_errcode_t *err, regex_t *preg, bin_tree_t *node) { re_dfa_t *dfa = preg->buffer; bin_tree_t *body = node->left; bin_tree_t *op, *cls, *tree1, *tree; if (preg->no_sub /* We do not optimize empty subexpressions, because otherwise we may have bad CONCAT nodes with NULL children. This is obviously not very common, so we do not lose much. An example that triggers this case is the sed "script" /\(\)/x. */ && node->left != NULL && (node->token.opr.idx >= BITSET_WORD_BITS || !(dfa->used_bkref_map & ((bitset_word_t) 1 << node->token.opr.idx)))) return node->left; /* Convert the SUBEXP node to the concatenation of an OP_OPEN_SUBEXP, the contents, and an OP_CLOSE_SUBEXP. */ op = create_tree (dfa, NULL, NULL, OP_OPEN_SUBEXP); cls = create_tree (dfa, NULL, NULL, OP_CLOSE_SUBEXP); tree1 = body ? create_tree (dfa, body, cls, CONCAT) : cls; tree = create_tree (dfa, op, tree1, CONCAT); if (BE (tree == NULL || tree1 == NULL || op == NULL || cls == NULL, 0)) { *err = REG_ESPACE; return NULL; } op->token.opr.idx = cls->token.opr.idx = node->token.opr.idx; op->token.opt_subexp = cls->token.opt_subexp = node->token.opt_subexp; return tree; } /* Pass 1 in building the NFA: compute FIRST and create unlinked automaton nodes. Requires a postorder visit. */ static reg_errcode_t calc_first (void *extra, bin_tree_t *node) { re_dfa_t *dfa = (re_dfa_t *) extra; if (node->token.type == CONCAT) { node->first = node->left->first; node->node_idx = node->left->node_idx; } else { node->first = node; node->node_idx = re_dfa_add_node (dfa, node->token); if (BE (node->node_idx == -1, 0)) return REG_ESPACE; if (node->token.type == ANCHOR) dfa->nodes[node->node_idx].constraint = node->token.opr.ctx_type; } return REG_NOERROR; } /* Pass 2: compute NEXT on the tree. Preorder visit. */ static reg_errcode_t calc_next (void *extra, bin_tree_t *node) { switch (node->token.type) { case OP_DUP_ASTERISK: node->left->next = node; break; case CONCAT: node->left->next = node->right->first; node->right->next = node->next; break; default: if (node->left) node->left->next = node->next; if (node->right) node->right->next = node->next; break; } return REG_NOERROR; } /* Pass 3: link all DFA nodes to their NEXT node (any order will do). */ static reg_errcode_t link_nfa_nodes (void *extra, bin_tree_t *node) { re_dfa_t *dfa = (re_dfa_t *) extra; Idx idx = node->node_idx; reg_errcode_t err = REG_NOERROR; switch (node->token.type) { case CONCAT: break; case END_OF_RE: assert (node->next == NULL); break; case OP_DUP_ASTERISK: case OP_ALT: { Idx left, right; dfa->has_plural_match = 1; if (node->left != NULL) left = node->left->first->node_idx; else left = node->next->node_idx; if (node->right != NULL) right = node->right->first->node_idx; else right = node->next->node_idx; assert (left > -1); assert (right > -1); err = re_node_set_init_2 (dfa->edests + idx, left, right); } break; case ANCHOR: case OP_OPEN_SUBEXP: case OP_CLOSE_SUBEXP: err = re_node_set_init_1 (dfa->edests + idx, node->next->node_idx); break; case OP_BACK_REF: dfa->nexts[idx] = node->next->node_idx; if (node->token.type == OP_BACK_REF) err = re_node_set_init_1 (dfa->edests + idx, dfa->nexts[idx]); break; default: assert (!IS_EPSILON_NODE (node->token.type)); dfa->nexts[idx] = node->next->node_idx; break; } return err; } /* Duplicate the epsilon closure of the node ROOT_NODE. Note that duplicated nodes have constraint INIT_CONSTRAINT in addition to their own constraint. */ static reg_errcode_t internal_function duplicate_node_closure (re_dfa_t *dfa, Idx top_org_node, Idx top_clone_node, Idx root_node, unsigned int init_constraint) { Idx org_node, clone_node; bool ok; unsigned int constraint = init_constraint; for (org_node = top_org_node, clone_node = top_clone_node;;) { Idx org_dest, clone_dest; if (dfa->nodes[org_node].type == OP_BACK_REF) { /* If the back reference epsilon-transit, its destination must also have the constraint. Then duplicate the epsilon closure of the destination of the back reference, and store it in edests of the back reference. */ org_dest = dfa->nexts[org_node]; re_node_set_empty (dfa->edests + clone_node); clone_dest = duplicate_node (dfa, org_dest, constraint); if (BE (clone_dest == -1, 0)) return REG_ESPACE; dfa->nexts[clone_node] = dfa->nexts[org_node]; ok = re_node_set_insert (dfa->edests + clone_node, clone_dest); if (BE (! ok, 0)) return REG_ESPACE; } else if (dfa->edests[org_node].nelem == 0) { /* In case of the node can't epsilon-transit, don't duplicate the destination and store the original destination as the destination of the node. */ dfa->nexts[clone_node] = dfa->nexts[org_node]; break; } else if (dfa->edests[org_node].nelem == 1) { /* In case of the node can epsilon-transit, and it has only one destination. */ org_dest = dfa->edests[org_node].elems[0]; re_node_set_empty (dfa->edests + clone_node); /* If the node is root_node itself, it means the epsilon closure has a loop. Then tie it to the destination of the root_node. */ if (org_node == root_node && clone_node != org_node) { ok = re_node_set_insert (dfa->edests + clone_node, org_dest); if (BE (! ok, 0)) return REG_ESPACE; break; } /* In case the node has another constraint, append it. */ constraint |= dfa->nodes[org_node].constraint; clone_dest = duplicate_node (dfa, org_dest, constraint); if (BE (clone_dest == -1, 0)) return REG_ESPACE; ok = re_node_set_insert (dfa->edests + clone_node, clone_dest); if (BE (! ok, 0)) return REG_ESPACE; } else /* dfa->edests[org_node].nelem == 2 */ { /* In case of the node can epsilon-transit, and it has two destinations. In the bin_tree_t and DFA, that's '|' and '*'. */ org_dest = dfa->edests[org_node].elems[0]; re_node_set_empty (dfa->edests + clone_node); /* Search for a duplicated node which satisfies the constraint. */ clone_dest = search_duplicated_node (dfa, org_dest, constraint); if (clone_dest == -1) { /* There is no such duplicated node, create a new one. */ reg_errcode_t err; clone_dest = duplicate_node (dfa, org_dest, constraint); if (BE (clone_dest == -1, 0)) return REG_ESPACE; ok = re_node_set_insert (dfa->edests + clone_node, clone_dest); if (BE (! ok, 0)) return REG_ESPACE; err = duplicate_node_closure (dfa, org_dest, clone_dest, root_node, constraint); if (BE (err != REG_NOERROR, 0)) return err; } else { /* There is a duplicated node which satisfies the constraint, use it to avoid infinite loop. */ ok = re_node_set_insert (dfa->edests + clone_node, clone_dest); if (BE (! ok, 0)) return REG_ESPACE; } org_dest = dfa->edests[org_node].elems[1]; clone_dest = duplicate_node (dfa, org_dest, constraint); if (BE (clone_dest == -1, 0)) return REG_ESPACE; ok = re_node_set_insert (dfa->edests + clone_node, clone_dest); if (BE (! ok, 0)) return REG_ESPACE; } org_node = org_dest; clone_node = clone_dest; } return REG_NOERROR; } /* Search for a node which is duplicated from the node ORG_NODE, and satisfies the constraint CONSTRAINT. */ static Idx search_duplicated_node (const re_dfa_t *dfa, Idx org_node, unsigned int constraint) { Idx idx; for (idx = dfa->nodes_len - 1; dfa->nodes[idx].duplicated && idx > 0; --idx) { if (org_node == dfa->org_indices[idx] && constraint == dfa->nodes[idx].constraint) return idx; /* Found. */ } return -1; /* Not found. */ } /* Duplicate the node whose index is ORG_IDX and set the constraint CONSTRAINT. Return the index of the new node, or -1 if insufficient storage is available. */ static Idx duplicate_node (re_dfa_t *dfa, Idx org_idx, unsigned int constraint) { Idx dup_idx = re_dfa_add_node (dfa, dfa->nodes[org_idx]); if (BE (dup_idx != -1, 1)) { dfa->nodes[dup_idx].constraint = constraint; dfa->nodes[dup_idx].constraint |= dfa->nodes[org_idx].constraint; dfa->nodes[dup_idx].duplicated = 1; /* Store the index of the original node. */ dfa->org_indices[dup_idx] = org_idx; } return dup_idx; } static reg_errcode_t calc_inveclosure (re_dfa_t *dfa) { Idx src, idx; bool ok; for (idx = 0; idx < dfa->nodes_len; ++idx) re_node_set_init_empty (dfa->inveclosures + idx); for (src = 0; src < dfa->nodes_len; ++src) { Idx *elems = dfa->eclosures[src].elems; for (idx = 0; idx < dfa->eclosures[src].nelem; ++idx) { ok = re_node_set_insert_last (dfa->inveclosures + elems[idx], src); if (BE (! ok, 0)) return REG_ESPACE; } } return REG_NOERROR; } /* Calculate "eclosure" for all the node in DFA. */ static reg_errcode_t calc_eclosure (re_dfa_t *dfa) { Idx node_idx; bool incomplete; #ifdef DEBUG assert (dfa->nodes_len > 0); #endif incomplete = false; /* For each nodes, calculate epsilon closure. */ for (node_idx = 0; ; ++node_idx) { reg_errcode_t err; re_node_set eclosure_elem; if (node_idx == dfa->nodes_len) { if (!incomplete) break; incomplete = false; node_idx = 0; } #ifdef DEBUG assert (dfa->eclosures[node_idx].nelem != -1); #endif /* If we have already calculated, skip it. */ if (dfa->eclosures[node_idx].nelem != 0) continue; /* Calculate epsilon closure of 'node_idx'. */ err = calc_eclosure_iter (&eclosure_elem, dfa, node_idx, true); if (BE (err != REG_NOERROR, 0)) return err; if (dfa->eclosures[node_idx].nelem == 0) { incomplete = true; re_node_set_free (&eclosure_elem); } } return REG_NOERROR; } /* Calculate epsilon closure of NODE. */ static reg_errcode_t calc_eclosure_iter (re_node_set *new_set, re_dfa_t *dfa, Idx node, bool root) { reg_errcode_t err; Idx i; re_node_set eclosure; bool ok; bool incomplete = false; err = re_node_set_alloc (&eclosure, dfa->edests[node].nelem + 1); if (BE (err != REG_NOERROR, 0)) return err; /* This indicates that we are calculating this node now. We reference this value to avoid infinite loop. */ dfa->eclosures[node].nelem = -1; /* If the current node has constraints, duplicate all nodes since they must inherit the constraints. */ if (dfa->nodes[node].constraint && dfa->edests[node].nelem && !dfa->nodes[dfa->edests[node].elems[0]].duplicated) { err = duplicate_node_closure (dfa, node, node, node, dfa->nodes[node].constraint); if (BE (err != REG_NOERROR, 0)) return err; } /* Expand each epsilon destination nodes. */ if (IS_EPSILON_NODE(dfa->nodes[node].type)) for (i = 0; i < dfa->edests[node].nelem; ++i) { re_node_set eclosure_elem; Idx edest = dfa->edests[node].elems[i]; /* If calculating the epsilon closure of 'edest' is in progress, return intermediate result. */ if (dfa->eclosures[edest].nelem == -1) { incomplete = true; continue; } /* If we haven't calculated the epsilon closure of 'edest' yet, calculate now. Otherwise use calculated epsilon closure. */ if (dfa->eclosures[edest].nelem == 0) { err = calc_eclosure_iter (&eclosure_elem, dfa, edest, false); if (BE (err != REG_NOERROR, 0)) return err; } else eclosure_elem = dfa->eclosures[edest]; /* Merge the epsilon closure of 'edest'. */ err = re_node_set_merge (&eclosure, &eclosure_elem); if (BE (err != REG_NOERROR, 0)) return err; /* If the epsilon closure of 'edest' is incomplete, the epsilon closure of this node is also incomplete. */ if (dfa->eclosures[edest].nelem == 0) { incomplete = true; re_node_set_free (&eclosure_elem); } } /* An epsilon closure includes itself. */ ok = re_node_set_insert (&eclosure, node); if (BE (! ok, 0)) return REG_ESPACE; if (incomplete && !root) dfa->eclosures[node].nelem = 0; else dfa->eclosures[node] = eclosure; *new_set = eclosure; return REG_NOERROR; } /* Functions for token which are used in the parser. */ /* Fetch a token from INPUT. We must not use this function inside bracket expressions. */ static void internal_function fetch_token (re_token_t *result, re_string_t *input, reg_syntax_t syntax) { re_string_skip_bytes (input, peek_token (result, input, syntax)); } /* Peek a token from INPUT, and return the length of the token. We must not use this function inside bracket expressions. */ static int internal_function peek_token (re_token_t *token, re_string_t *input, reg_syntax_t syntax) { unsigned char c; if (re_string_eoi (input)) { token->type = END_OF_RE; return 0; } c = re_string_peek_byte (input, 0); token->opr.c = c; token->word_char = 0; #ifdef RE_ENABLE_I18N token->mb_partial = 0; if (input->mb_cur_max > 1 && !re_string_first_byte (input, re_string_cur_idx (input))) { token->type = CHARACTER; token->mb_partial = 1; return 1; } #endif if (c == '\\') { unsigned char c2; if (re_string_cur_idx (input) + 1 >= re_string_length (input)) { token->type = BACK_SLASH; return 1; } c2 = re_string_peek_byte_case (input, 1); token->opr.c = c2; token->type = CHARACTER; #ifdef RE_ENABLE_I18N if (input->mb_cur_max > 1) { wint_t wc = re_string_wchar_at (input, re_string_cur_idx (input) + 1); token->word_char = IS_WIDE_WORD_CHAR (wc) != 0; } else #endif token->word_char = IS_WORD_CHAR (c2) != 0; switch (c2) { case '|': if (!(syntax & RE_LIMITED_OPS) && !(syntax & RE_NO_BK_VBAR)) token->type = OP_ALT; break; case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': if (!(syntax & RE_NO_BK_REFS)) { token->type = OP_BACK_REF; token->opr.idx = c2 - '1'; } break; case '<': if (!(syntax & RE_NO_GNU_OPS)) { token->type = ANCHOR; token->opr.ctx_type = WORD_FIRST; } break; case '>': if (!(syntax & RE_NO_GNU_OPS)) { token->type = ANCHOR; token->opr.ctx_type = WORD_LAST; } break; case 'b': if (!(syntax & RE_NO_GNU_OPS)) { token->type = ANCHOR; token->opr.ctx_type = WORD_DELIM; } break; case 'B': if (!(syntax & RE_NO_GNU_OPS)) { token->type = ANCHOR; token->opr.ctx_type = NOT_WORD_DELIM; } break; case 'w': if (!(syntax & RE_NO_GNU_OPS)) token->type = OP_WORD; break; case 'W': if (!(syntax & RE_NO_GNU_OPS)) token->type = OP_NOTWORD; break; case 's': if (!(syntax & RE_NO_GNU_OPS)) token->type = OP_SPACE; break; case 'S': if (!(syntax & RE_NO_GNU_OPS)) token->type = OP_NOTSPACE; break; case '`': if (!(syntax & RE_NO_GNU_OPS)) { token->type = ANCHOR; token->opr.ctx_type = BUF_FIRST; } break; case '\'': if (!(syntax & RE_NO_GNU_OPS)) { token->type = ANCHOR; token->opr.ctx_type = BUF_LAST; } break; case '(': if (!(syntax & RE_NO_BK_PARENS)) token->type = OP_OPEN_SUBEXP; break; case ')': if (!(syntax & RE_NO_BK_PARENS)) token->type = OP_CLOSE_SUBEXP; break; case '+': if (!(syntax & RE_LIMITED_OPS) && (syntax & RE_BK_PLUS_QM)) token->type = OP_DUP_PLUS; break; case '?': if (!(syntax & RE_LIMITED_OPS) && (syntax & RE_BK_PLUS_QM)) token->type = OP_DUP_QUESTION; break; case '{': if ((syntax & RE_INTERVALS) && (!(syntax & RE_NO_BK_BRACES))) token->type = OP_OPEN_DUP_NUM; break; case '}': if ((syntax & RE_INTERVALS) && (!(syntax & RE_NO_BK_BRACES))) token->type = OP_CLOSE_DUP_NUM; break; default: break; } return 2; } token->type = CHARACTER; #ifdef RE_ENABLE_I18N if (input->mb_cur_max > 1) { wint_t wc = re_string_wchar_at (input, re_string_cur_idx (input)); token->word_char = IS_WIDE_WORD_CHAR (wc) != 0; } else #endif token->word_char = IS_WORD_CHAR (token->opr.c); switch (c) { case '\n': if (syntax & RE_NEWLINE_ALT) token->type = OP_ALT; break; case '|': if (!(syntax & RE_LIMITED_OPS) && (syntax & RE_NO_BK_VBAR)) token->type = OP_ALT; break; case '*': token->type = OP_DUP_ASTERISK; break; case '+': if (!(syntax & RE_LIMITED_OPS) && !(syntax & RE_BK_PLUS_QM)) token->type = OP_DUP_PLUS; break; case '?': if (!(syntax & RE_LIMITED_OPS) && !(syntax & RE_BK_PLUS_QM)) token->type = OP_DUP_QUESTION; break; case '{': if ((syntax & RE_INTERVALS) && (syntax & RE_NO_BK_BRACES)) token->type = OP_OPEN_DUP_NUM; break; case '}': if ((syntax & RE_INTERVALS) && (syntax & RE_NO_BK_BRACES)) token->type = OP_CLOSE_DUP_NUM; break; case '(': if (syntax & RE_NO_BK_PARENS) token->type = OP_OPEN_SUBEXP; break; case ')': if (syntax & RE_NO_BK_PARENS) token->type = OP_CLOSE_SUBEXP; break; case '[': token->type = OP_OPEN_BRACKET; break; case '.': token->type = OP_PERIOD; break; case '^': if (!(syntax & (RE_CONTEXT_INDEP_ANCHORS | RE_CARET_ANCHORS_HERE)) && re_string_cur_idx (input) != 0) { char prev = re_string_peek_byte (input, -1); if (!(syntax & RE_NEWLINE_ALT) || prev != '\n') break; } token->type = ANCHOR; token->opr.ctx_type = LINE_FIRST; break; case '$': if (!(syntax & RE_CONTEXT_INDEP_ANCHORS) && re_string_cur_idx (input) + 1 != re_string_length (input)) { re_token_t next; re_string_skip_bytes (input, 1); peek_token (&next, input, syntax); re_string_skip_bytes (input, -1); if (next.type != OP_ALT && next.type != OP_CLOSE_SUBEXP) break; } token->type = ANCHOR; token->opr.ctx_type = LINE_LAST; break; default: break; } return 1; } /* Peek a token from INPUT, and return the length of the token. We must not use this function out of bracket expressions. */ static int internal_function peek_token_bracket (re_token_t *token, re_string_t *input, reg_syntax_t syntax) { unsigned char c; if (re_string_eoi (input)) { token->type = END_OF_RE; return 0; } c = re_string_peek_byte (input, 0); token->opr.c = c; #ifdef RE_ENABLE_I18N if (input->mb_cur_max > 1 && !re_string_first_byte (input, re_string_cur_idx (input))) { token->type = CHARACTER; return 1; } #endif /* RE_ENABLE_I18N */ if (c == '\\' && (syntax & RE_BACKSLASH_ESCAPE_IN_LISTS) && re_string_cur_idx (input) + 1 < re_string_length (input)) { /* In this case, '\' escape a character. */ unsigned char c2; re_string_skip_bytes (input, 1); c2 = re_string_peek_byte (input, 0); token->opr.c = c2; token->type = CHARACTER; return 1; } if (c == '[') /* '[' is a special char in a bracket exps. */ { unsigned char c2; int token_len; if (re_string_cur_idx (input) + 1 < re_string_length (input)) c2 = re_string_peek_byte (input, 1); else c2 = 0; token->opr.c = c2; token_len = 2; switch (c2) { case '.': token->type = OP_OPEN_COLL_ELEM; break; case '=': token->type = OP_OPEN_EQUIV_CLASS; break; case ':': if (syntax & RE_CHAR_CLASSES) { token->type = OP_OPEN_CHAR_CLASS; break; } /* else fall through. */ default: token->type = CHARACTER; token->opr.c = c; token_len = 1; break; } return token_len; } switch (c) { case '-': token->type = OP_CHARSET_RANGE; break; case ']': token->type = OP_CLOSE_BRACKET; break; case '^': token->type = OP_NON_MATCH_LIST; break; default: token->type = CHARACTER; } return 1; } /* Functions for parser. */ /* Entry point of the parser. Parse the regular expression REGEXP and return the structure tree. If an error occurs, ERR is set by error code, and return NULL. This function build the following tree, from regular expression : CAT / \ / \ EOR CAT means concatenation. EOR means end of regular expression. */ static bin_tree_t * parse (re_string_t *regexp, regex_t *preg, reg_syntax_t syntax, reg_errcode_t *err) { re_dfa_t *dfa = preg->buffer; bin_tree_t *tree, *eor, *root; re_token_t current_token; dfa->syntax = syntax; fetch_token (¤t_token, regexp, syntax | RE_CARET_ANCHORS_HERE); tree = parse_reg_exp (regexp, preg, ¤t_token, syntax, 0, err); if (BE (*err != REG_NOERROR && tree == NULL, 0)) return NULL; eor = create_tree (dfa, NULL, NULL, END_OF_RE); if (tree != NULL) root = create_tree (dfa, tree, eor, CONCAT); else root = eor; if (BE (eor == NULL || root == NULL, 0)) { *err = REG_ESPACE; return NULL; } return root; } /* This function build the following tree, from regular expression |: ALT / \ / \ ALT means alternative, which represents the operator '|'. */ static bin_tree_t * parse_reg_exp (re_string_t *regexp, regex_t *preg, re_token_t *token, reg_syntax_t syntax, Idx nest, reg_errcode_t *err) { re_dfa_t *dfa = preg->buffer; bin_tree_t *tree, *branch = NULL; bitset_word_t initial_bkref_map = dfa->completed_bkref_map; tree = parse_branch (regexp, preg, token, syntax, nest, err); if (BE (*err != REG_NOERROR && tree == NULL, 0)) return NULL; while (token->type == OP_ALT) { fetch_token (token, regexp, syntax | RE_CARET_ANCHORS_HERE); if (token->type != OP_ALT && token->type != END_OF_RE && (nest == 0 || token->type != OP_CLOSE_SUBEXP)) { bitset_word_t accumulated_bkref_map = dfa->completed_bkref_map; dfa->completed_bkref_map = initial_bkref_map; branch = parse_branch (regexp, preg, token, syntax, nest, err); if (BE (*err != REG_NOERROR && branch == NULL, 0)) { if (tree != NULL) postorder (tree, free_tree, NULL); return NULL; } dfa->completed_bkref_map |= accumulated_bkref_map; } else branch = NULL; tree = create_tree (dfa, tree, branch, OP_ALT); if (BE (tree == NULL, 0)) { *err = REG_ESPACE; return NULL; } } return tree; } /* This function build the following tree, from regular expression : CAT / \ / \ CAT means concatenation. */ static bin_tree_t * parse_branch (re_string_t *regexp, regex_t *preg, re_token_t *token, reg_syntax_t syntax, Idx nest, reg_errcode_t *err) { bin_tree_t *tree, *expr; re_dfa_t *dfa = preg->buffer; tree = parse_expression (regexp, preg, token, syntax, nest, err); if (BE (*err != REG_NOERROR && tree == NULL, 0)) return NULL; while (token->type != OP_ALT && token->type != END_OF_RE && (nest == 0 || token->type != OP_CLOSE_SUBEXP)) { expr = parse_expression (regexp, preg, token, syntax, nest, err); if (BE (*err != REG_NOERROR && expr == NULL, 0)) { if (tree != NULL) postorder (tree, free_tree, NULL); return NULL; } if (tree != NULL && expr != NULL) { bin_tree_t *newtree = create_tree (dfa, tree, expr, CONCAT); if (newtree == NULL) { postorder (expr, free_tree, NULL); postorder (tree, free_tree, NULL); *err = REG_ESPACE; return NULL; } tree = newtree; } else if (tree == NULL) tree = expr; /* Otherwise expr == NULL, we don't need to create new tree. */ } return tree; } /* This function build the following tree, from regular expression a*: * | a */ static bin_tree_t * parse_expression (re_string_t *regexp, regex_t *preg, re_token_t *token, reg_syntax_t syntax, Idx nest, reg_errcode_t *err) { re_dfa_t *dfa = preg->buffer; bin_tree_t *tree; switch (token->type) { case CHARACTER: tree = create_token_tree (dfa, NULL, NULL, token); if (BE (tree == NULL, 0)) { *err = REG_ESPACE; return NULL; } #ifdef RE_ENABLE_I18N if (dfa->mb_cur_max > 1) { while (!re_string_eoi (regexp) && !re_string_first_byte (regexp, re_string_cur_idx (regexp))) { bin_tree_t *mbc_remain; fetch_token (token, regexp, syntax); mbc_remain = create_token_tree (dfa, NULL, NULL, token); tree = create_tree (dfa, tree, mbc_remain, CONCAT); if (BE (mbc_remain == NULL || tree == NULL, 0)) { *err = REG_ESPACE; return NULL; } } } #endif break; case OP_OPEN_SUBEXP: tree = parse_sub_exp (regexp, preg, token, syntax, nest + 1, err); if (BE (*err != REG_NOERROR && tree == NULL, 0)) return NULL; break; case OP_OPEN_BRACKET: tree = parse_bracket_exp (regexp, dfa, token, syntax, err); if (BE (*err != REG_NOERROR && tree == NULL, 0)) return NULL; break; case OP_BACK_REF: if (!BE (dfa->completed_bkref_map & (1 << token->opr.idx), 1)) { *err = REG_ESUBREG; return NULL; } dfa->used_bkref_map |= 1 << token->opr.idx; tree = create_token_tree (dfa, NULL, NULL, token); if (BE (tree == NULL, 0)) { *err = REG_ESPACE; return NULL; } ++dfa->nbackref; dfa->has_mb_node = 1; break; case OP_OPEN_DUP_NUM: if (syntax & RE_CONTEXT_INVALID_DUP) { *err = REG_BADRPT; return NULL; } /* FALLTHROUGH */ case OP_DUP_ASTERISK: case OP_DUP_PLUS: case OP_DUP_QUESTION: if (syntax & RE_CONTEXT_INVALID_OPS) { *err = REG_BADRPT; return NULL; } else if (syntax & RE_CONTEXT_INDEP_OPS) { fetch_token (token, regexp, syntax); return parse_expression (regexp, preg, token, syntax, nest, err); } /* else fall through */ case OP_CLOSE_SUBEXP: if ((token->type == OP_CLOSE_SUBEXP) && !(syntax & RE_UNMATCHED_RIGHT_PAREN_ORD)) { *err = REG_ERPAREN; return NULL; } /* else fall through */ case OP_CLOSE_DUP_NUM: /* We treat it as a normal character. */ /* Then we can these characters as normal characters. */ token->type = CHARACTER; /* mb_partial and word_char bits should be initialized already by peek_token. */ tree = create_token_tree (dfa, NULL, NULL, token); if (BE (tree == NULL, 0)) { *err = REG_ESPACE; return NULL; } break; case ANCHOR: if ((token->opr.ctx_type & (WORD_DELIM | NOT_WORD_DELIM | WORD_FIRST | WORD_LAST)) && dfa->word_ops_used == 0) init_word_char (dfa); if (token->opr.ctx_type == WORD_DELIM || token->opr.ctx_type == NOT_WORD_DELIM) { bin_tree_t *tree_first, *tree_last; if (token->opr.ctx_type == WORD_DELIM) { token->opr.ctx_type = WORD_FIRST; tree_first = create_token_tree (dfa, NULL, NULL, token); token->opr.ctx_type = WORD_LAST; } else { token->opr.ctx_type = INSIDE_WORD; tree_first = create_token_tree (dfa, NULL, NULL, token); token->opr.ctx_type = INSIDE_NOTWORD; } tree_last = create_token_tree (dfa, NULL, NULL, token); tree = create_tree (dfa, tree_first, tree_last, OP_ALT); if (BE (tree_first == NULL || tree_last == NULL || tree == NULL, 0)) { *err = REG_ESPACE; return NULL; } } else { tree = create_token_tree (dfa, NULL, NULL, token); if (BE (tree == NULL, 0)) { *err = REG_ESPACE; return NULL; } } /* We must return here, since ANCHORs can't be followed by repetition operators. eg. RE"^*" is invalid or "", it must not be "". */ fetch_token (token, regexp, syntax); return tree; case OP_PERIOD: tree = create_token_tree (dfa, NULL, NULL, token); if (BE (tree == NULL, 0)) { *err = REG_ESPACE; return NULL; } if (dfa->mb_cur_max > 1) dfa->has_mb_node = 1; break; case OP_WORD: case OP_NOTWORD: tree = build_charclass_op (dfa, regexp->trans, "alnum", "_", token->type == OP_NOTWORD, err); if (BE (*err != REG_NOERROR && tree == NULL, 0)) return NULL; break; case OP_SPACE: case OP_NOTSPACE: tree = build_charclass_op (dfa, regexp->trans, "space", "", token->type == OP_NOTSPACE, err); if (BE (*err != REG_NOERROR && tree == NULL, 0)) return NULL; break; case OP_ALT: case END_OF_RE: return NULL; case BACK_SLASH: *err = REG_EESCAPE; return NULL; default: /* Must not happen? */ #ifdef DEBUG assert (0); #endif return NULL; } fetch_token (token, regexp, syntax); while (token->type == OP_DUP_ASTERISK || token->type == OP_DUP_PLUS || token->type == OP_DUP_QUESTION || token->type == OP_OPEN_DUP_NUM) { bin_tree_t *dup_tree = parse_dup_op (tree, regexp, dfa, token, syntax, err); if (BE (*err != REG_NOERROR && dup_tree == NULL, 0)) { if (tree != NULL) postorder (tree, free_tree, NULL); return NULL; } tree = dup_tree; /* In BRE consecutive duplications are not allowed. */ if ((syntax & RE_CONTEXT_INVALID_DUP) && (token->type == OP_DUP_ASTERISK || token->type == OP_OPEN_DUP_NUM)) { if (tree != NULL) postorder (tree, free_tree, NULL); *err = REG_BADRPT; return NULL; } } return tree; } /* This function build the following tree, from regular expression (): SUBEXP | */ static bin_tree_t * parse_sub_exp (re_string_t *regexp, regex_t *preg, re_token_t *token, reg_syntax_t syntax, Idx nest, reg_errcode_t *err) { re_dfa_t *dfa = preg->buffer; bin_tree_t *tree; size_t cur_nsub; cur_nsub = preg->re_nsub++; fetch_token (token, regexp, syntax | RE_CARET_ANCHORS_HERE); /* The subexpression may be a null string. */ if (token->type == OP_CLOSE_SUBEXP) tree = NULL; else { tree = parse_reg_exp (regexp, preg, token, syntax, nest, err); if (BE (*err == REG_NOERROR && token->type != OP_CLOSE_SUBEXP, 0)) { if (tree != NULL) postorder (tree, free_tree, NULL); *err = REG_EPAREN; } if (BE (*err != REG_NOERROR, 0)) return NULL; } if (cur_nsub <= '9' - '1') dfa->completed_bkref_map |= 1 << cur_nsub; tree = create_tree (dfa, tree, NULL, SUBEXP); if (BE (tree == NULL, 0)) { *err = REG_ESPACE; return NULL; } tree->token.opr.idx = cur_nsub; return tree; } /* This function parse repetition operators like "*", "+", "{1,3}" etc. */ static bin_tree_t * parse_dup_op (bin_tree_t *elem, re_string_t *regexp, re_dfa_t *dfa, re_token_t *token, reg_syntax_t syntax, reg_errcode_t *err) { bin_tree_t *tree = NULL, *old_tree = NULL; Idx i, start, end, start_idx = re_string_cur_idx (regexp); re_token_t start_token = *token; if (token->type == OP_OPEN_DUP_NUM) { end = 0; start = fetch_number (regexp, token, syntax); if (start == -1) { if (token->type == CHARACTER && token->opr.c == ',') start = 0; /* We treat "{,m}" as "{0,m}". */ else { *err = REG_BADBR; /* {} is invalid. */ return NULL; } } if (BE (start != -2, 1)) { /* We treat "{n}" as "{n,n}". */ end = ((token->type == OP_CLOSE_DUP_NUM) ? start : ((token->type == CHARACTER && token->opr.c == ',') ? fetch_number (regexp, token, syntax) : -2)); } if (BE (start == -2 || end == -2, 0)) { /* Invalid sequence. */ if (BE (!(syntax & RE_INVALID_INTERVAL_ORD), 0)) { if (token->type == END_OF_RE) *err = REG_EBRACE; else *err = REG_BADBR; return NULL; } /* If the syntax bit is set, rollback. */ re_string_set_index (regexp, start_idx); *token = start_token; token->type = CHARACTER; /* mb_partial and word_char bits should be already initialized by peek_token. */ return elem; } if (BE ((end != -1 && start > end) || token->type != OP_CLOSE_DUP_NUM, 0)) { /* First number greater than second. */ *err = REG_BADBR; return NULL; } if (BE (RE_DUP_MAX < (end == -1 ? start : end), 0)) { *err = REG_ESIZE; return NULL; } } else { start = (token->type == OP_DUP_PLUS) ? 1 : 0; end = (token->type == OP_DUP_QUESTION) ? 1 : -1; } fetch_token (token, regexp, syntax); if (BE (elem == NULL, 0)) return NULL; if (BE (start == 0 && end == 0, 0)) { postorder (elem, free_tree, NULL); return NULL; } /* Extract "{n,m}" to "...{0,}". */ if (BE (start > 0, 0)) { tree = elem; for (i = 2; i <= start; ++i) { elem = duplicate_tree (elem, dfa); tree = create_tree (dfa, tree, elem, CONCAT); if (BE (elem == NULL || tree == NULL, 0)) goto parse_dup_op_espace; } if (start == end) return tree; /* Duplicate ELEM before it is marked optional. */ elem = duplicate_tree (elem, dfa); if (BE (elem == NULL, 0)) goto parse_dup_op_espace; old_tree = tree; } else old_tree = NULL; if (elem->token.type == SUBEXP) { uintptr_t subidx = elem->token.opr.idx; postorder (elem, mark_opt_subexp, (void *) subidx); } tree = create_tree (dfa, elem, NULL, (end == -1 ? OP_DUP_ASTERISK : OP_ALT)); if (BE (tree == NULL, 0)) goto parse_dup_op_espace; /* From gnulib's "intprops.h": True if the arithmetic type T is signed. */ #define TYPE_SIGNED(t) (! ((t) 0 < (t) -1)) /* This loop is actually executed only when end != -1, to rewrite {0,n} as ((...?)?)?... We have already created the start+1-th copy. */ if (TYPE_SIGNED (Idx) || end != -1) for (i = start + 2; i <= end; ++i) { elem = duplicate_tree (elem, dfa); tree = create_tree (dfa, tree, elem, CONCAT); if (BE (elem == NULL || tree == NULL, 0)) goto parse_dup_op_espace; tree = create_tree (dfa, tree, NULL, OP_ALT); if (BE (tree == NULL, 0)) goto parse_dup_op_espace; } if (old_tree) tree = create_tree (dfa, old_tree, tree, CONCAT); return tree; parse_dup_op_espace: *err = REG_ESPACE; return NULL; } /* Size of the names for collating symbol/equivalence_class/character_class. I'm not sure, but maybe enough. */ #define BRACKET_NAME_BUF_SIZE 32 #ifndef _LIBC # ifdef RE_ENABLE_I18N /* Convert the byte B to the corresponding wide character. In a unibyte locale, treat B as itself if it is an encoding error. In a multibyte locale, return WEOF if B is an encoding error. */ static wint_t parse_byte (unsigned char b, re_charset_t *mbcset) { wint_t wc = __btowc (b); return wc == WEOF && !mbcset ? b : wc; } #endif /* Local function for parse_bracket_exp only used in case of NOT _LIBC. Build the range expression which starts from START_ELEM, and ends at END_ELEM. The result are written to MBCSET and SBCSET. RANGE_ALLOC is the allocated size of mbcset->range_starts, and mbcset->range_ends, is a pointer argument since we may update it. */ static reg_errcode_t internal_function # ifdef RE_ENABLE_I18N build_range_exp (const reg_syntax_t syntax, bitset_t sbcset, re_charset_t *mbcset, Idx *range_alloc, const bracket_elem_t *start_elem, const bracket_elem_t *end_elem) # else /* not RE_ENABLE_I18N */ build_range_exp (const reg_syntax_t syntax, bitset_t sbcset, const bracket_elem_t *start_elem, const bracket_elem_t *end_elem) # endif /* not RE_ENABLE_I18N */ { unsigned int start_ch, end_ch; /* Equivalence Classes and Character Classes can't be a range start/end. */ if (BE (start_elem->type == EQUIV_CLASS || start_elem->type == CHAR_CLASS || end_elem->type == EQUIV_CLASS || end_elem->type == CHAR_CLASS, 0)) return REG_ERANGE; /* We can handle no multi character collating elements without libc support. */ if (BE ((start_elem->type == COLL_SYM && strlen ((char *) start_elem->opr.name) > 1) || (end_elem->type == COLL_SYM && strlen ((char *) end_elem->opr.name) > 1), 0)) return REG_ECOLLATE; # ifdef RE_ENABLE_I18N { wchar_t wc; wint_t start_wc; wint_t end_wc; start_ch = ((start_elem->type == SB_CHAR) ? start_elem->opr.ch : ((start_elem->type == COLL_SYM) ? start_elem->opr.name[0] : 0)); end_ch = ((end_elem->type == SB_CHAR) ? end_elem->opr.ch : ((end_elem->type == COLL_SYM) ? end_elem->opr.name[0] : 0)); start_wc = ((start_elem->type == SB_CHAR || start_elem->type == COLL_SYM) ? parse_byte (start_ch, mbcset) : start_elem->opr.wch); end_wc = ((end_elem->type == SB_CHAR || end_elem->type == COLL_SYM) ? parse_byte (end_ch, mbcset) : end_elem->opr.wch); if (start_wc == WEOF || end_wc == WEOF) return REG_ECOLLATE; else if (BE ((syntax & RE_NO_EMPTY_RANGES) && start_wc > end_wc, 0)) return REG_ERANGE; /* Got valid collation sequence values, add them as a new entry. However, for !_LIBC we have no collation elements: if the character set is single byte, the single byte character set that we build below suffices. parse_bracket_exp passes no MBCSET if dfa->mb_cur_max == 1. */ if (mbcset) { /* Check the space of the arrays. */ if (BE (*range_alloc == mbcset->nranges, 0)) { /* There is not enough space, need realloc. */ wchar_t *new_array_start, *new_array_end; Idx new_nranges; /* +1 in case of mbcset->nranges is 0. */ new_nranges = 2 * mbcset->nranges + 1; /* Use realloc since mbcset->range_starts and mbcset->range_ends are NULL if *range_alloc == 0. */ new_array_start = re_realloc (mbcset->range_starts, wchar_t, new_nranges); new_array_end = re_realloc (mbcset->range_ends, wchar_t, new_nranges); if (BE (new_array_start == NULL || new_array_end == NULL, 0)) { re_free (new_array_start); re_free (new_array_end); return REG_ESPACE; } mbcset->range_starts = new_array_start; mbcset->range_ends = new_array_end; *range_alloc = new_nranges; } mbcset->range_starts[mbcset->nranges] = start_wc; mbcset->range_ends[mbcset->nranges++] = end_wc; } /* Build the table for single byte characters. */ for (wc = 0; wc < SBC_MAX; ++wc) { if (start_wc <= wc && wc <= end_wc) bitset_set (sbcset, wc); } } # else /* not RE_ENABLE_I18N */ { unsigned int ch; start_ch = ((start_elem->type == SB_CHAR ) ? start_elem->opr.ch : ((start_elem->type == COLL_SYM) ? start_elem->opr.name[0] : 0)); end_ch = ((end_elem->type == SB_CHAR ) ? end_elem->opr.ch : ((end_elem->type == COLL_SYM) ? end_elem->opr.name[0] : 0)); if (start_ch > end_ch) return REG_ERANGE; /* Build the table for single byte characters. */ for (ch = 0; ch < SBC_MAX; ++ch) if (start_ch <= ch && ch <= end_ch) bitset_set (sbcset, ch); } # endif /* not RE_ENABLE_I18N */ return REG_NOERROR; } #endif /* not _LIBC */ #ifndef _LIBC /* Helper function for parse_bracket_exp only used in case of NOT _LIBC.. Build the collating element which is represented by NAME. The result are written to MBCSET and SBCSET. COLL_SYM_ALLOC is the allocated size of mbcset->coll_sym, is a pointer argument since we may update it. */ static reg_errcode_t internal_function # ifdef RE_ENABLE_I18N build_collating_symbol (bitset_t sbcset, re_charset_t *mbcset, Idx *coll_sym_alloc, const unsigned char *name) # else /* not RE_ENABLE_I18N */ build_collating_symbol (bitset_t sbcset, const unsigned char *name) # endif /* not RE_ENABLE_I18N */ { size_t name_len = strlen ((const char *) name); if (BE (name_len != 1, 0)) return REG_ECOLLATE; else { bitset_set (sbcset, name[0]); return REG_NOERROR; } } #endif /* not _LIBC */ /* This function parse bracket expression like "[abc]", "[a-c]", "[[.a-a.]]" etc. */ static bin_tree_t * parse_bracket_exp (re_string_t *regexp, re_dfa_t *dfa, re_token_t *token, reg_syntax_t syntax, reg_errcode_t *err) { #ifdef _LIBC const unsigned char *collseqmb; const char *collseqwc; uint32_t nrules; int32_t table_size; const int32_t *symb_table; const unsigned char *extra; /* Local function for parse_bracket_exp used in _LIBC environment. Seek the collating symbol entry corresponding to NAME. Return the index of the symbol in the SYMB_TABLE, or -1 if not found. */ auto inline int32_t __attribute__ ((always_inline)) seek_collating_symbol_entry (const unsigned char *name, size_t name_len) { int32_t elem; for (elem = 0; elem < table_size; elem++) if (symb_table[2 * elem] != 0) { int32_t idx = symb_table[2 * elem + 1]; /* Skip the name of collating element name. */ idx += 1 + extra[idx]; if (/* Compare the length of the name. */ name_len == extra[idx] /* Compare the name. */ && memcmp (name, &extra[idx + 1], name_len) == 0) /* Yep, this is the entry. */ return elem; } return -1; } /* Local function for parse_bracket_exp used in _LIBC environment. Look up the collation sequence value of BR_ELEM. Return the value if succeeded, UINT_MAX otherwise. */ auto inline unsigned int __attribute__ ((always_inline)) lookup_collation_sequence_value (bracket_elem_t *br_elem) { if (br_elem->type == SB_CHAR) { /* if (MB_CUR_MAX == 1) */ if (nrules == 0) return collseqmb[br_elem->opr.ch]; else { wint_t wc = __btowc (br_elem->opr.ch); return __collseq_table_lookup (collseqwc, wc); } } else if (br_elem->type == MB_CHAR) { if (nrules != 0) return __collseq_table_lookup (collseqwc, br_elem->opr.wch); } else if (br_elem->type == COLL_SYM) { size_t sym_name_len = strlen ((char *) br_elem->opr.name); if (nrules != 0) { int32_t elem, idx; elem = seek_collating_symbol_entry (br_elem->opr.name, sym_name_len); if (elem != -1) { /* We found the entry. */ idx = symb_table[2 * elem + 1]; /* Skip the name of collating element name. */ idx += 1 + extra[idx]; /* Skip the byte sequence of the collating element. */ idx += 1 + extra[idx]; /* Adjust for the alignment. */ idx = (idx + 3) & ~3; /* Skip the multibyte collation sequence value. */ idx += sizeof (unsigned int); /* Skip the wide char sequence of the collating element. */ idx += sizeof (unsigned int) * (1 + *(unsigned int *) (extra + idx)); /* Return the collation sequence value. */ return *(unsigned int *) (extra + idx); } else if (sym_name_len == 1) { /* No valid character. Match it as a single byte character. */ return collseqmb[br_elem->opr.name[0]]; } } else if (sym_name_len == 1) return collseqmb[br_elem->opr.name[0]]; } return UINT_MAX; } /* Local function for parse_bracket_exp used in _LIBC environment. Build the range expression which starts from START_ELEM, and ends at END_ELEM. The result are written to MBCSET and SBCSET. RANGE_ALLOC is the allocated size of mbcset->range_starts, and mbcset->range_ends, is a pointer argument since we may update it. */ auto inline reg_errcode_t __attribute__ ((always_inline)) build_range_exp (bitset_t sbcset, re_charset_t *mbcset, int *range_alloc, bracket_elem_t *start_elem, bracket_elem_t *end_elem) { unsigned int ch; uint32_t start_collseq; uint32_t end_collseq; /* Equivalence Classes and Character Classes can't be a range start/end. */ if (BE (start_elem->type == EQUIV_CLASS || start_elem->type == CHAR_CLASS || end_elem->type == EQUIV_CLASS || end_elem->type == CHAR_CLASS, 0)) return REG_ERANGE; /* FIXME: Implement rational ranges here, too. */ start_collseq = lookup_collation_sequence_value (start_elem); end_collseq = lookup_collation_sequence_value (end_elem); /* Check start/end collation sequence values. */ if (BE (start_collseq == UINT_MAX || end_collseq == UINT_MAX, 0)) return REG_ECOLLATE; if (BE ((syntax & RE_NO_EMPTY_RANGES) && start_collseq > end_collseq, 0)) return REG_ERANGE; /* Got valid collation sequence values, add them as a new entry. However, if we have no collation elements, and the character set is single byte, the single byte character set that we build below suffices. */ if (nrules > 0 || dfa->mb_cur_max > 1) { /* Check the space of the arrays. */ if (BE (*range_alloc == mbcset->nranges, 0)) { /* There is not enough space, need realloc. */ uint32_t *new_array_start; uint32_t *new_array_end; Idx new_nranges; /* +1 in case of mbcset->nranges is 0. */ new_nranges = 2 * mbcset->nranges + 1; new_array_start = re_realloc (mbcset->range_starts, uint32_t, new_nranges); new_array_end = re_realloc (mbcset->range_ends, uint32_t, new_nranges); if (BE (new_array_start == NULL || new_array_end == NULL, 0)) return REG_ESPACE; mbcset->range_starts = new_array_start; mbcset->range_ends = new_array_end; *range_alloc = new_nranges; } mbcset->range_starts[mbcset->nranges] = start_collseq; mbcset->range_ends[mbcset->nranges++] = end_collseq; } /* Build the table for single byte characters. */ for (ch = 0; ch < SBC_MAX; ch++) { uint32_t ch_collseq; /* if (MB_CUR_MAX == 1) */ if (nrules == 0) ch_collseq = collseqmb[ch]; else ch_collseq = __collseq_table_lookup (collseqwc, __btowc (ch)); if (start_collseq <= ch_collseq && ch_collseq <= end_collseq) bitset_set (sbcset, ch); } return REG_NOERROR; } /* Local function for parse_bracket_exp used in _LIBC environment. Build the collating element which is represented by NAME. The result are written to MBCSET and SBCSET. COLL_SYM_ALLOC is the allocated size of mbcset->coll_sym, is a pointer argument since we may update it. */ auto inline reg_errcode_t __attribute__ ((always_inline)) build_collating_symbol (bitset_t sbcset, re_charset_t *mbcset, Idx *coll_sym_alloc, const unsigned char *name) { int32_t elem, idx; size_t name_len = strlen ((const char *) name); if (nrules != 0) { elem = seek_collating_symbol_entry (name, name_len); if (elem != -1) { /* We found the entry. */ idx = symb_table[2 * elem + 1]; /* Skip the name of collating element name. */ idx += 1 + extra[idx]; } else if (name_len == 1) { /* No valid character, treat it as a normal character. */ bitset_set (sbcset, name[0]); return REG_NOERROR; } else return REG_ECOLLATE; /* Got valid collation sequence, add it as a new entry. */ /* Check the space of the arrays. */ if (BE (*coll_sym_alloc == mbcset->ncoll_syms, 0)) { /* Not enough, realloc it. */ /* +1 in case of mbcset->ncoll_syms is 0. */ Idx new_coll_sym_alloc = 2 * mbcset->ncoll_syms + 1; /* Use realloc since mbcset->coll_syms is NULL if *alloc == 0. */ int32_t *new_coll_syms = re_realloc (mbcset->coll_syms, int32_t, new_coll_sym_alloc); if (BE (new_coll_syms == NULL, 0)) return REG_ESPACE; mbcset->coll_syms = new_coll_syms; *coll_sym_alloc = new_coll_sym_alloc; } mbcset->coll_syms[mbcset->ncoll_syms++] = idx; return REG_NOERROR; } else { if (BE (name_len != 1, 0)) return REG_ECOLLATE; else { bitset_set (sbcset, name[0]); return REG_NOERROR; } } } #endif re_token_t br_token; re_bitset_ptr_t sbcset; #ifdef RE_ENABLE_I18N re_charset_t *mbcset; Idx coll_sym_alloc = 0, range_alloc = 0, mbchar_alloc = 0; Idx equiv_class_alloc = 0, char_class_alloc = 0; #endif /* not RE_ENABLE_I18N */ bool non_match = false; bin_tree_t *work_tree; int token_len; bool first_round = true; #ifdef _LIBC collseqmb = (const unsigned char *) _NL_CURRENT (LC_COLLATE, _NL_COLLATE_COLLSEQMB); nrules = _NL_CURRENT_WORD (LC_COLLATE, _NL_COLLATE_NRULES); if (nrules) { /* if (MB_CUR_MAX > 1) */ collseqwc = _NL_CURRENT (LC_COLLATE, _NL_COLLATE_COLLSEQWC); table_size = _NL_CURRENT_WORD (LC_COLLATE, _NL_COLLATE_SYMB_HASH_SIZEMB); symb_table = (const int32_t *) _NL_CURRENT (LC_COLLATE, _NL_COLLATE_SYMB_TABLEMB); extra = (const unsigned char *) _NL_CURRENT (LC_COLLATE, _NL_COLLATE_SYMB_EXTRAMB); } #endif sbcset = (re_bitset_ptr_t) calloc (sizeof (bitset_t), 1); #ifdef RE_ENABLE_I18N mbcset = (re_charset_t *) calloc (sizeof (re_charset_t), 1); #endif /* RE_ENABLE_I18N */ #ifdef RE_ENABLE_I18N if (BE (sbcset == NULL || mbcset == NULL, 0)) #else if (BE (sbcset == NULL, 0)) #endif /* RE_ENABLE_I18N */ { re_free (sbcset); #ifdef RE_ENABLE_I18N re_free (mbcset); #endif *err = REG_ESPACE; return NULL; } token_len = peek_token_bracket (token, regexp, syntax); if (BE (token->type == END_OF_RE, 0)) { *err = REG_BADPAT; goto parse_bracket_exp_free_return; } if (token->type == OP_NON_MATCH_LIST) { #ifdef RE_ENABLE_I18N mbcset->non_match = 1; #endif /* not RE_ENABLE_I18N */ non_match = true; if (syntax & RE_HAT_LISTS_NOT_NEWLINE) bitset_set (sbcset, '\n'); re_string_skip_bytes (regexp, token_len); /* Skip a token. */ token_len = peek_token_bracket (token, regexp, syntax); if (BE (token->type == END_OF_RE, 0)) { *err = REG_BADPAT; goto parse_bracket_exp_free_return; } } /* We treat the first ']' as a normal character. */ if (token->type == OP_CLOSE_BRACKET) token->type = CHARACTER; while (1) { bracket_elem_t start_elem, end_elem; unsigned char start_name_buf[BRACKET_NAME_BUF_SIZE]; unsigned char end_name_buf[BRACKET_NAME_BUF_SIZE]; reg_errcode_t ret; int token_len2 = 0; bool is_range_exp = false; re_token_t token2; start_elem.opr.name = start_name_buf; start_elem.type = COLL_SYM; ret = parse_bracket_element (&start_elem, regexp, token, token_len, dfa, syntax, first_round); if (BE (ret != REG_NOERROR, 0)) { *err = ret; goto parse_bracket_exp_free_return; } first_round = false; /* Get information about the next token. We need it in any case. */ token_len = peek_token_bracket (token, regexp, syntax); /* Do not check for ranges if we know they are not allowed. */ if (start_elem.type != CHAR_CLASS && start_elem.type != EQUIV_CLASS) { if (BE (token->type == END_OF_RE, 0)) { *err = REG_EBRACK; goto parse_bracket_exp_free_return; } if (token->type == OP_CHARSET_RANGE) { re_string_skip_bytes (regexp, token_len); /* Skip '-'. */ token_len2 = peek_token_bracket (&token2, regexp, syntax); if (BE (token2.type == END_OF_RE, 0)) { *err = REG_EBRACK; goto parse_bracket_exp_free_return; } if (token2.type == OP_CLOSE_BRACKET) { /* We treat the last '-' as a normal character. */ re_string_skip_bytes (regexp, -token_len); token->type = CHARACTER; } else is_range_exp = true; } } if (is_range_exp == true) { end_elem.opr.name = end_name_buf; end_elem.type = COLL_SYM; ret = parse_bracket_element (&end_elem, regexp, &token2, token_len2, dfa, syntax, true); if (BE (ret != REG_NOERROR, 0)) { *err = ret; goto parse_bracket_exp_free_return; } token_len = peek_token_bracket (token, regexp, syntax); #ifdef _LIBC *err = build_range_exp (sbcset, mbcset, &range_alloc, &start_elem, &end_elem); #else # ifdef RE_ENABLE_I18N *err = build_range_exp (syntax, sbcset, dfa->mb_cur_max > 1 ? mbcset : NULL, &range_alloc, &start_elem, &end_elem); # else *err = build_range_exp (syntax, sbcset, &start_elem, &end_elem); # endif #endif /* RE_ENABLE_I18N */ if (BE (*err != REG_NOERROR, 0)) goto parse_bracket_exp_free_return; } else { switch (start_elem.type) { case SB_CHAR: bitset_set (sbcset, start_elem.opr.ch); break; #ifdef RE_ENABLE_I18N case MB_CHAR: /* Check whether the array has enough space. */ if (BE (mbchar_alloc == mbcset->nmbchars, 0)) { wchar_t *new_mbchars; /* Not enough, realloc it. */ /* +1 in case of mbcset->nmbchars is 0. */ mbchar_alloc = 2 * mbcset->nmbchars + 1; /* Use realloc since array is NULL if *alloc == 0. */ new_mbchars = re_realloc (mbcset->mbchars, wchar_t, mbchar_alloc); if (BE (new_mbchars == NULL, 0)) goto parse_bracket_exp_espace; mbcset->mbchars = new_mbchars; } mbcset->mbchars[mbcset->nmbchars++] = start_elem.opr.wch; break; #endif /* RE_ENABLE_I18N */ case EQUIV_CLASS: *err = build_equiv_class (sbcset, #ifdef RE_ENABLE_I18N mbcset, &equiv_class_alloc, #endif /* RE_ENABLE_I18N */ start_elem.opr.name); if (BE (*err != REG_NOERROR, 0)) goto parse_bracket_exp_free_return; break; case COLL_SYM: *err = build_collating_symbol (sbcset, #ifdef RE_ENABLE_I18N mbcset, &coll_sym_alloc, #endif /* RE_ENABLE_I18N */ start_elem.opr.name); if (BE (*err != REG_NOERROR, 0)) goto parse_bracket_exp_free_return; break; case CHAR_CLASS: *err = build_charclass (regexp->trans, sbcset, #ifdef RE_ENABLE_I18N mbcset, &char_class_alloc, #endif /* RE_ENABLE_I18N */ (const char *) start_elem.opr.name, syntax); if (BE (*err != REG_NOERROR, 0)) goto parse_bracket_exp_free_return; break; default: assert (0); break; } } if (BE (token->type == END_OF_RE, 0)) { *err = REG_EBRACK; goto parse_bracket_exp_free_return; } if (token->type == OP_CLOSE_BRACKET) break; } re_string_skip_bytes (regexp, token_len); /* Skip a token. */ /* If it is non-matching list. */ if (non_match) bitset_not (sbcset); #ifdef RE_ENABLE_I18N /* Ensure only single byte characters are set. */ if (dfa->mb_cur_max > 1) bitset_mask (sbcset, dfa->sb_char); if (mbcset->nmbchars || mbcset->ncoll_syms || mbcset->nequiv_classes || mbcset->nranges || (dfa->mb_cur_max > 1 && (mbcset->nchar_classes || mbcset->non_match))) { bin_tree_t *mbc_tree; int sbc_idx; /* Build a tree for complex bracket. */ dfa->has_mb_node = 1; br_token.type = COMPLEX_BRACKET; br_token.opr.mbcset = mbcset; mbc_tree = create_token_tree (dfa, NULL, NULL, &br_token); if (BE (mbc_tree == NULL, 0)) goto parse_bracket_exp_espace; for (sbc_idx = 0; sbc_idx < BITSET_WORDS; ++sbc_idx) if (sbcset[sbc_idx]) break; /* If there are no bits set in sbcset, there is no point of having both SIMPLE_BRACKET and COMPLEX_BRACKET. */ if (sbc_idx < BITSET_WORDS) { /* Build a tree for simple bracket. */ br_token.type = SIMPLE_BRACKET; br_token.opr.sbcset = sbcset; work_tree = create_token_tree (dfa, NULL, NULL, &br_token); if (BE (work_tree == NULL, 0)) goto parse_bracket_exp_espace; /* Then join them by ALT node. */ work_tree = create_tree (dfa, work_tree, mbc_tree, OP_ALT); if (BE (work_tree == NULL, 0)) goto parse_bracket_exp_espace; } else { re_free (sbcset); work_tree = mbc_tree; } } else #endif /* not RE_ENABLE_I18N */ { #ifdef RE_ENABLE_I18N free_charset (mbcset); #endif /* Build a tree for simple bracket. */ br_token.type = SIMPLE_BRACKET; br_token.opr.sbcset = sbcset; work_tree = create_token_tree (dfa, NULL, NULL, &br_token); if (BE (work_tree == NULL, 0)) goto parse_bracket_exp_espace; } return work_tree; parse_bracket_exp_espace: *err = REG_ESPACE; parse_bracket_exp_free_return: re_free (sbcset); #ifdef RE_ENABLE_I18N free_charset (mbcset); #endif /* RE_ENABLE_I18N */ return NULL; } /* Parse an element in the bracket expression. */ static reg_errcode_t parse_bracket_element (bracket_elem_t *elem, re_string_t *regexp, re_token_t *token, int token_len, re_dfa_t *dfa, reg_syntax_t syntax, bool accept_hyphen) { #ifdef RE_ENABLE_I18N int cur_char_size; cur_char_size = re_string_char_size_at (regexp, re_string_cur_idx (regexp)); if (cur_char_size > 1) { elem->type = MB_CHAR; elem->opr.wch = re_string_wchar_at (regexp, re_string_cur_idx (regexp)); re_string_skip_bytes (regexp, cur_char_size); return REG_NOERROR; } #endif /* RE_ENABLE_I18N */ re_string_skip_bytes (regexp, token_len); /* Skip a token. */ if (token->type == OP_OPEN_COLL_ELEM || token->type == OP_OPEN_CHAR_CLASS || token->type == OP_OPEN_EQUIV_CLASS) return parse_bracket_symbol (elem, regexp, token); if (BE (token->type == OP_CHARSET_RANGE, 0) && !accept_hyphen) { /* A '-' must only appear as anything but a range indicator before the closing bracket. Everything else is an error. */ re_token_t token2; (void) peek_token_bracket (&token2, regexp, syntax); if (token2.type != OP_CLOSE_BRACKET) /* The actual error value is not standardized since this whole case is undefined. But ERANGE makes good sense. */ return REG_ERANGE; } elem->type = SB_CHAR; elem->opr.ch = token->opr.c; return REG_NOERROR; } /* Parse a bracket symbol in the bracket expression. Bracket symbols are such as [::], [..], and [==]. */ static reg_errcode_t parse_bracket_symbol (bracket_elem_t *elem, re_string_t *regexp, re_token_t *token) { unsigned char ch, delim = token->opr.c; int i = 0; if (re_string_eoi(regexp)) return REG_EBRACK; for (;; ++i) { if (i >= BRACKET_NAME_BUF_SIZE) return REG_EBRACK; if (token->type == OP_OPEN_CHAR_CLASS) ch = re_string_fetch_byte_case (regexp); else ch = re_string_fetch_byte (regexp); if (re_string_eoi(regexp)) return REG_EBRACK; if (ch == delim && re_string_peek_byte (regexp, 0) == ']') break; elem->opr.name[i] = ch; } re_string_skip_bytes (regexp, 1); elem->opr.name[i] = '\0'; switch (token->type) { case OP_OPEN_COLL_ELEM: elem->type = COLL_SYM; break; case OP_OPEN_EQUIV_CLASS: elem->type = EQUIV_CLASS; break; case OP_OPEN_CHAR_CLASS: elem->type = CHAR_CLASS; break; default: break; } return REG_NOERROR; } /* Helper function for parse_bracket_exp. Build the equivalence class which is represented by NAME. The result are written to MBCSET and SBCSET. EQUIV_CLASS_ALLOC is the allocated size of mbcset->equiv_classes, is a pointer argument since we may update it. */ static reg_errcode_t #ifdef RE_ENABLE_I18N build_equiv_class (bitset_t sbcset, re_charset_t *mbcset, Idx *equiv_class_alloc, const unsigned char *name) #else /* not RE_ENABLE_I18N */ build_equiv_class (bitset_t sbcset, const unsigned char *name) #endif /* not RE_ENABLE_I18N */ { #ifdef _LIBC uint32_t nrules = _NL_CURRENT_WORD (LC_COLLATE, _NL_COLLATE_NRULES); if (nrules != 0) { const int32_t *table, *indirect; const unsigned char *weights, *extra, *cp; unsigned char char_buf[2]; int32_t idx1, idx2; unsigned int ch; size_t len; /* Calculate the index for equivalence class. */ cp = name; table = (const int32_t *) _NL_CURRENT (LC_COLLATE, _NL_COLLATE_TABLEMB); weights = (const unsigned char *) _NL_CURRENT (LC_COLLATE, _NL_COLLATE_WEIGHTMB); extra = (const unsigned char *) _NL_CURRENT (LC_COLLATE, _NL_COLLATE_EXTRAMB); indirect = (const int32_t *) _NL_CURRENT (LC_COLLATE, _NL_COLLATE_INDIRECTMB); idx1 = findidx (table, indirect, extra, &cp, -1); if (BE (idx1 == 0 || *cp != '\0', 0)) /* This isn't a valid character. */ return REG_ECOLLATE; /* Build single byte matching table for this equivalence class. */ len = weights[idx1 & 0xffffff]; for (ch = 0; ch < SBC_MAX; ++ch) { char_buf[0] = ch; cp = char_buf; idx2 = findidx (table, indirect, extra, &cp, 1); /* idx2 = table[ch]; */ if (idx2 == 0) /* This isn't a valid character. */ continue; /* Compare only if the length matches and the collation rule index is the same. */ if (len == weights[idx2 & 0xffffff] && (idx1 >> 24) == (idx2 >> 24)) { int cnt = 0; while (cnt <= len && weights[(idx1 & 0xffffff) + 1 + cnt] == weights[(idx2 & 0xffffff) + 1 + cnt]) ++cnt; if (cnt > len) bitset_set (sbcset, ch); } } /* Check whether the array has enough space. */ if (BE (*equiv_class_alloc == mbcset->nequiv_classes, 0)) { /* Not enough, realloc it. */ /* +1 in case of mbcset->nequiv_classes is 0. */ Idx new_equiv_class_alloc = 2 * mbcset->nequiv_classes + 1; /* Use realloc since the array is NULL if *alloc == 0. */ int32_t *new_equiv_classes = re_realloc (mbcset->equiv_classes, int32_t, new_equiv_class_alloc); if (BE (new_equiv_classes == NULL, 0)) return REG_ESPACE; mbcset->equiv_classes = new_equiv_classes; *equiv_class_alloc = new_equiv_class_alloc; } mbcset->equiv_classes[mbcset->nequiv_classes++] = idx1; } else #endif /* _LIBC */ { if (BE (strlen ((const char *) name) != 1, 0)) return REG_ECOLLATE; bitset_set (sbcset, *name); } return REG_NOERROR; } /* Helper function for parse_bracket_exp. Build the character class which is represented by NAME. The result are written to MBCSET and SBCSET. CHAR_CLASS_ALLOC is the allocated size of mbcset->char_classes, is a pointer argument since we may update it. */ static reg_errcode_t #ifdef RE_ENABLE_I18N build_charclass (RE_TRANSLATE_TYPE trans, bitset_t sbcset, re_charset_t *mbcset, Idx *char_class_alloc, const char *class_name, reg_syntax_t syntax) #else /* not RE_ENABLE_I18N */ build_charclass (RE_TRANSLATE_TYPE trans, bitset_t sbcset, const char *class_name, reg_syntax_t syntax) #endif /* not RE_ENABLE_I18N */ { int i; const char *name = class_name; /* In case of REG_ICASE "upper" and "lower" match the both of upper and lower cases. */ if ((syntax & RE_ICASE) && (strcmp (name, "upper") == 0 || strcmp (name, "lower") == 0)) name = "alpha"; #ifdef RE_ENABLE_I18N /* Check the space of the arrays. */ if (BE (*char_class_alloc == mbcset->nchar_classes, 0)) { /* Not enough, realloc it. */ /* +1 in case of mbcset->nchar_classes is 0. */ Idx new_char_class_alloc = 2 * mbcset->nchar_classes + 1; /* Use realloc since array is NULL if *alloc == 0. */ wctype_t *new_char_classes = re_realloc (mbcset->char_classes, wctype_t, new_char_class_alloc); if (BE (new_char_classes == NULL, 0)) return REG_ESPACE; mbcset->char_classes = new_char_classes; *char_class_alloc = new_char_class_alloc; } mbcset->char_classes[mbcset->nchar_classes++] = __wctype (name); #endif /* RE_ENABLE_I18N */ #define BUILD_CHARCLASS_LOOP(ctype_func) \ do { \ if (BE (trans != NULL, 0)) \ { \ for (i = 0; i < SBC_MAX; ++i) \ if (ctype_func (i)) \ bitset_set (sbcset, trans[i]); \ } \ else \ { \ for (i = 0; i < SBC_MAX; ++i) \ if (ctype_func (i)) \ bitset_set (sbcset, i); \ } \ } while (0) if (strcmp (name, "alnum") == 0) BUILD_CHARCLASS_LOOP (isalnum); else if (strcmp (name, "cntrl") == 0) BUILD_CHARCLASS_LOOP (iscntrl); else if (strcmp (name, "lower") == 0) BUILD_CHARCLASS_LOOP (islower); else if (strcmp (name, "space") == 0) BUILD_CHARCLASS_LOOP (isspace); else if (strcmp (name, "alpha") == 0) BUILD_CHARCLASS_LOOP (isalpha); else if (strcmp (name, "digit") == 0) BUILD_CHARCLASS_LOOP (isdigit); else if (strcmp (name, "print") == 0) BUILD_CHARCLASS_LOOP (isprint); else if (strcmp (name, "upper") == 0) BUILD_CHARCLASS_LOOP (isupper); else if (strcmp (name, "blank") == 0) BUILD_CHARCLASS_LOOP (isblank); else if (strcmp (name, "graph") == 0) BUILD_CHARCLASS_LOOP (isgraph); else if (strcmp (name, "punct") == 0) BUILD_CHARCLASS_LOOP (ispunct); else if (strcmp (name, "xdigit") == 0) BUILD_CHARCLASS_LOOP (isxdigit); else return REG_ECTYPE; return REG_NOERROR; } static bin_tree_t * build_charclass_op (re_dfa_t *dfa, RE_TRANSLATE_TYPE trans, const char *class_name, const char *extra, bool non_match, reg_errcode_t *err) { re_bitset_ptr_t sbcset; #ifdef RE_ENABLE_I18N re_charset_t *mbcset; Idx alloc = 0; #endif /* not RE_ENABLE_I18N */ reg_errcode_t ret; re_token_t br_token; bin_tree_t *tree; sbcset = (re_bitset_ptr_t) calloc (sizeof (bitset_t), 1); if (BE (sbcset == NULL, 0)) { *err = REG_ESPACE; return NULL; } #ifdef RE_ENABLE_I18N mbcset = (re_charset_t *) calloc (sizeof (re_charset_t), 1); if (BE (mbcset == NULL, 0)) { re_free (sbcset); *err = REG_ESPACE; return NULL; } mbcset->non_match = non_match; #endif /* RE_ENABLE_I18N */ /* We don't care the syntax in this case. */ ret = build_charclass (trans, sbcset, #ifdef RE_ENABLE_I18N mbcset, &alloc, #endif /* RE_ENABLE_I18N */ class_name, 0); if (BE (ret != REG_NOERROR, 0)) { re_free (sbcset); #ifdef RE_ENABLE_I18N free_charset (mbcset); #endif /* RE_ENABLE_I18N */ *err = ret; return NULL; } /* \w match '_' also. */ for (; *extra; extra++) bitset_set (sbcset, *extra); /* If it is non-matching list. */ if (non_match) bitset_not (sbcset); #ifdef RE_ENABLE_I18N /* Ensure only single byte characters are set. */ if (dfa->mb_cur_max > 1) bitset_mask (sbcset, dfa->sb_char); #endif /* Build a tree for simple bracket. */ #if defined GCC_LINT || defined lint memset (&br_token, 0, sizeof br_token); #endif br_token.type = SIMPLE_BRACKET; br_token.opr.sbcset = sbcset; tree = create_token_tree (dfa, NULL, NULL, &br_token); if (BE (tree == NULL, 0)) goto build_word_op_espace; #ifdef RE_ENABLE_I18N if (dfa->mb_cur_max > 1) { bin_tree_t *mbc_tree; /* Build a tree for complex bracket. */ br_token.type = COMPLEX_BRACKET; br_token.opr.mbcset = mbcset; dfa->has_mb_node = 1; mbc_tree = create_token_tree (dfa, NULL, NULL, &br_token); if (BE (mbc_tree == NULL, 0)) goto build_word_op_espace; /* Then join them by ALT node. */ tree = create_tree (dfa, tree, mbc_tree, OP_ALT); if (BE (mbc_tree != NULL, 1)) return tree; } else { free_charset (mbcset); return tree; } #else /* not RE_ENABLE_I18N */ return tree; #endif /* not RE_ENABLE_I18N */ build_word_op_espace: re_free (sbcset); #ifdef RE_ENABLE_I18N free_charset (mbcset); #endif /* RE_ENABLE_I18N */ *err = REG_ESPACE; return NULL; } /* This is intended for the expressions like "a{1,3}". Fetch a number from 'input', and return the number. Return -1 if the number field is empty like "{,1}". Return RE_DUP_MAX + 1 if the number field is too large. Return -2 if an error occurred. */ static Idx fetch_number (re_string_t *input, re_token_t *token, reg_syntax_t syntax) { Idx num = -1; unsigned char c; while (1) { fetch_token (token, input, syntax); c = token->opr.c; if (BE (token->type == END_OF_RE, 0)) return -2; if (token->type == OP_CLOSE_DUP_NUM || c == ',') break; num = ((token->type != CHARACTER || c < '0' || '9' < c || num == -2) ? -2 : num == -1 ? c - '0' : MIN (RE_DUP_MAX + 1, num * 10 + c - '0')); } return num; } #ifdef RE_ENABLE_I18N static void free_charset (re_charset_t *cset) { re_free (cset->mbchars); # ifdef _LIBC re_free (cset->coll_syms); re_free (cset->equiv_classes); re_free (cset->range_starts); re_free (cset->range_ends); # endif re_free (cset->char_classes); re_free (cset); } #endif /* RE_ENABLE_I18N */ /* Functions for binary tree operation. */ /* Create a tree node. */ static bin_tree_t * create_tree (re_dfa_t *dfa, bin_tree_t *left, bin_tree_t *right, re_token_type_t type) { re_token_t t; #if defined GCC_LINT || defined lint memset (&t, 0, sizeof t); #endif t.type = type; return create_token_tree (dfa, left, right, &t); } static bin_tree_t * create_token_tree (re_dfa_t *dfa, bin_tree_t *left, bin_tree_t *right, const re_token_t *token) { bin_tree_t *tree; if (BE (dfa->str_tree_storage_idx == BIN_TREE_STORAGE_SIZE, 0)) { bin_tree_storage_t *storage = re_malloc (bin_tree_storage_t, 1); if (storage == NULL) return NULL; storage->next = dfa->str_tree_storage; dfa->str_tree_storage = storage; dfa->str_tree_storage_idx = 0; } tree = &dfa->str_tree_storage->data[dfa->str_tree_storage_idx++]; tree->parent = NULL; tree->left = left; tree->right = right; tree->token = *token; tree->token.duplicated = 0; tree->token.opt_subexp = 0; tree->first = NULL; tree->next = NULL; tree->node_idx = -1; if (left != NULL) left->parent = tree; if (right != NULL) right->parent = tree; return tree; } /* Mark the tree SRC as an optional subexpression. To be called from preorder or postorder. */ static reg_errcode_t mark_opt_subexp (void *extra, bin_tree_t *node) { Idx idx = (uintptr_t) extra; if (node->token.type == SUBEXP && node->token.opr.idx == idx) node->token.opt_subexp = 1; return REG_NOERROR; } /* Free the allocated memory inside NODE. */ static void free_token (re_token_t *node) { #ifdef RE_ENABLE_I18N if (node->type == COMPLEX_BRACKET && node->duplicated == 0) free_charset (node->opr.mbcset); else #endif /* RE_ENABLE_I18N */ if (node->type == SIMPLE_BRACKET && node->duplicated == 0) re_free (node->opr.sbcset); } /* Worker function for tree walking. Free the allocated memory inside NODE and its children. */ static reg_errcode_t free_tree (void *extra, bin_tree_t *node) { free_token (&node->token); return REG_NOERROR; } /* Duplicate the node SRC, and return new node. This is a preorder visit similar to the one implemented by the generic visitor, but we need more infrastructure to maintain two parallel trees --- so, it's easier to duplicate. */ static bin_tree_t * duplicate_tree (const bin_tree_t *root, re_dfa_t *dfa) { const bin_tree_t *node; bin_tree_t *dup_root; bin_tree_t **p_new = &dup_root, *dup_node = root->parent; for (node = root; ; ) { /* Create a new tree and link it back to the current parent. */ *p_new = create_token_tree (dfa, NULL, NULL, &node->token); if (*p_new == NULL) return NULL; (*p_new)->parent = dup_node; (*p_new)->token.duplicated = 1; dup_node = *p_new; /* Go to the left node, or up and to the right. */ if (node->left) { node = node->left; p_new = &dup_node->left; } else { const bin_tree_t *prev = NULL; while (node->right == prev || node->right == NULL) { prev = node; node = node->parent; dup_node = dup_node->parent; if (!node) return dup_root; } node = node->right; p_new = &dup_node->right; } } } ne-3.3.4/src/regex.c000066400000000000000000000064461475116431000142040ustar00rootroot00000000000000/* Extended regular expression matching and search library. Copyright (C) 2002-2025 Free Software Foundation, Inc. This file is part of the GNU C Library. Contributed by Isamu Hasegawa . The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. The GNU C Library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with the GNU C Library; if not, see . */ #ifndef _LIBC /* PORTABILITY PROBLEM: We do not include config.h. */ /* # include */ # if (__GNUC__ == 4 && 6 <= __GNUC_MINOR__) || 4 < __GNUC__ # pragma GCC diagnostic ignored "-Wsuggest-attribute=pure" # endif # if (__GNUC__ == 4 && 3 <= __GNUC_MINOR__) || 4 < __GNUC__ # pragma GCC diagnostic ignored "-Wold-style-definition" # pragma GCC diagnostic ignored "-Wtype-limits" # endif #endif /* Make sure no one compiles this code with a C++ compiler. */ #if defined __cplusplus && defined _LIBC # error "This is C code, use a C compiler" #endif #ifdef _LIBC /* We have to keep the namespace clean. */ # define regfree(preg) __regfree (preg) # define regexec(pr, st, nm, pm, ef) __regexec (pr, st, nm, pm, ef) # define regcomp(preg, pattern, cflags) __regcomp (preg, pattern, cflags) # define regerror(errcode, preg, errbuf, errbuf_size) \ __regerror(errcode, preg, errbuf, errbuf_size) # define re_set_registers(bu, re, nu, st, en) \ __re_set_registers (bu, re, nu, st, en) # define re_match_2(bufp, string1, size1, string2, size2, pos, regs, stop) \ __re_match_2 (bufp, string1, size1, string2, size2, pos, regs, stop) # define re_match(bufp, string, size, pos, regs) \ __re_match (bufp, string, size, pos, regs) # define re_search(bufp, string, size, startpos, range, regs) \ __re_search (bufp, string, size, startpos, range, regs) # define re_compile_pattern(pattern, length, bufp) \ __re_compile_pattern (pattern, length, bufp) # define re_set_syntax(syntax) __re_set_syntax (syntax) # define re_search_2(bufp, st1, s1, st2, s2, startpos, range, regs, stop) \ __re_search_2 (bufp, st1, s1, st2, s2, startpos, range, regs, stop) # define re_compile_fastmap(bufp) __re_compile_fastmap (bufp) # include "../locale/localeinfo.h" #endif /* On some systems, limits.h sets RE_DUP_MAX to a lower value than GNU regex allows. Include it before , which correctly #undefs RE_DUP_MAX and sets it to the right value. */ #include /* PORTABILITY PROBLEM: "regex.h" instead of ; bool type injected. */ /* #include */ #include "regex.h" #include "regex_internal.h" #include "regex_internal.c" #include "regcomp.c" #include "regexec.c" /* Binary backward compatibility. */ #if _LIBC # include # if SHLIB_COMPAT (libc, GLIBC_2_0, GLIBC_2_3) link_warning (re_max_failures, "the 're_max_failures' variable is obsolete and will go away.") int re_max_failures = 2000; # endif #endif ne-3.3.4/src/regex.h000066400000000000000000000601631475116431000142050ustar00rootroot00000000000000/* Definitions for data structures and routines for the regular expression library. Copyright (C) 1985, 1989-1993, 1995-1998, 2000-2003, 2005-2016 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. The GNU C Library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with the GNU C Library; if not, see . */ #ifndef _REGEX_H #define _REGEX_H 1 #include /* Allow the use in C++ code. */ #ifdef __cplusplus extern "C" { #endif /* Define __USE_GNU to declare GNU extensions that violate the POSIX name space rules. */ #ifdef _GNU_SOURCE # define __USE_GNU 1 #endif #ifdef _REGEX_LARGE_OFFSETS /* Use types and values that are wide enough to represent signed and unsigned byte offsets in memory. This currently works only when the regex code is used outside of the GNU C library; it is not yet supported within glibc itself, and glibc users should not define _REGEX_LARGE_OFFSETS. */ /* The type of object sizes. */ typedef size_t __re_size_t; /* The type of object sizes, in places where the traditional code uses unsigned long int. */ typedef size_t __re_long_size_t; #else /* The traditional GNU regex implementation mishandles strings longer than INT_MAX. */ typedef unsigned int __re_size_t; typedef unsigned long int __re_long_size_t; #endif /* The following two types have to be signed and unsigned integer type wide enough to hold a value of a pointer. For most ANSI compilers ptrdiff_t and size_t should be likely OK. Still size of these two types is 2 for Microsoft C. Ugh... */ typedef long int s_reg_t; typedef unsigned long int active_reg_t; /* The following bits are used to determine the regexp syntax we recognize. The set/not-set meanings are chosen so that Emacs syntax remains the value 0. The bits are given in alphabetical order, and the definitions shifted by one from the previous bit; thus, when we add or remove a bit, only one other definition need change. */ typedef unsigned long int reg_syntax_t; #ifdef __USE_GNU /* If this bit is not set, then \ inside a bracket expression is literal. If set, then such a \ quotes the following character. */ # define RE_BACKSLASH_ESCAPE_IN_LISTS ((unsigned long int) 1) /* If this bit is not set, then + and ? are operators, and \+ and \? are literals. If set, then \+ and \? are operators and + and ? are literals. */ # define RE_BK_PLUS_QM (RE_BACKSLASH_ESCAPE_IN_LISTS << 1) /* If this bit is set, then character classes are supported. They are: [:alpha:], [:upper:], [:lower:], [:digit:], [:alnum:], [:xdigit:], [:space:], [:print:], [:punct:], [:graph:], and [:cntrl:]. If not set, then character classes are not supported. */ # define RE_CHAR_CLASSES (RE_BK_PLUS_QM << 1) /* If this bit is set, then ^ and $ are always anchors (outside bracket expressions, of course). If this bit is not set, then it depends: ^ is an anchor if it is at the beginning of a regular expression or after an open-group or an alternation operator; $ is an anchor if it is at the end of a regular expression, or before a close-group or an alternation operator. This bit could be (re)combined with RE_CONTEXT_INDEP_OPS, because POSIX draft 11.2 says that * etc. in leading positions is undefined. We already implemented a previous draft which made those constructs invalid, though, so we haven't changed the code back. */ # define RE_CONTEXT_INDEP_ANCHORS (RE_CHAR_CLASSES << 1) /* If this bit is set, then special characters are always special regardless of where they are in the pattern. If this bit is not set, then special characters are special only in some contexts; otherwise they are ordinary. Specifically, * + ? and intervals are only special when not after the beginning, open-group, or alternation operator. */ # define RE_CONTEXT_INDEP_OPS (RE_CONTEXT_INDEP_ANCHORS << 1) /* If this bit is set, then *, +, ?, and { cannot be first in an re or immediately after an alternation or begin-group operator. */ # define RE_CONTEXT_INVALID_OPS (RE_CONTEXT_INDEP_OPS << 1) /* If this bit is set, then . matches newline. If not set, then it doesn't. */ # define RE_DOT_NEWLINE (RE_CONTEXT_INVALID_OPS << 1) /* If this bit is set, then . doesn't match NUL. If not set, then it does. */ # define RE_DOT_NOT_NULL (RE_DOT_NEWLINE << 1) /* If this bit is set, nonmatching lists [^...] do not match newline. If not set, they do. */ # define RE_HAT_LISTS_NOT_NEWLINE (RE_DOT_NOT_NULL << 1) /* If this bit is set, either \{...\} or {...} defines an interval, depending on RE_NO_BK_BRACES. If not set, \{, \}, {, and } are literals. */ # define RE_INTERVALS (RE_HAT_LISTS_NOT_NEWLINE << 1) /* If this bit is set, +, ? and | aren't recognized as operators. If not set, they are. */ # define RE_LIMITED_OPS (RE_INTERVALS << 1) /* If this bit is set, newline is an alternation operator. If not set, newline is literal. */ # define RE_NEWLINE_ALT (RE_LIMITED_OPS << 1) /* If this bit is set, then '{...}' defines an interval, and \{ and \} are literals. If not set, then '\{...\}' defines an interval. */ # define RE_NO_BK_BRACES (RE_NEWLINE_ALT << 1) /* If this bit is set, (...) defines a group, and \( and \) are literals. If not set, \(...\) defines a group, and ( and ) are literals. */ # define RE_NO_BK_PARENS (RE_NO_BK_BRACES << 1) /* If this bit is set, then \ matches . If not set, then \ is a back-reference. */ # define RE_NO_BK_REFS (RE_NO_BK_PARENS << 1) /* If this bit is set, then | is an alternation operator, and \| is literal. If not set, then \| is an alternation operator, and | is literal. */ # define RE_NO_BK_VBAR (RE_NO_BK_REFS << 1) /* If this bit is set, then an ending range point collating higher than the starting range point, as in [z-a], is invalid. If not set, then when ending range point collates higher than the starting range point, the range is ignored. */ # define RE_NO_EMPTY_RANGES (RE_NO_BK_VBAR << 1) /* If this bit is set, then an unmatched ) is ordinary. If not set, then an unmatched ) is invalid. */ # define RE_UNMATCHED_RIGHT_PAREN_ORD (RE_NO_EMPTY_RANGES << 1) /* If this bit is set, succeed as soon as we match the whole pattern, without further backtracking. */ # define RE_NO_POSIX_BACKTRACKING (RE_UNMATCHED_RIGHT_PAREN_ORD << 1) /* If this bit is set, do not process the GNU regex operators. If not set, then the GNU regex operators are recognized. */ # define RE_NO_GNU_OPS (RE_NO_POSIX_BACKTRACKING << 1) /* If this bit is set, turn on internal regex debugging. If not set, and debugging was on, turn it off. This only works if regex.c is compiled -DDEBUG. We define this bit always, so that all that's needed to turn on debugging is to recompile regex.c; the calling code can always have this bit set, and it won't affect anything in the normal case. */ # define RE_DEBUG (RE_NO_GNU_OPS << 1) /* If this bit is set, a syntactically invalid interval is treated as a string of ordinary characters. For example, the ERE 'a{1' is treated as 'a\{1'. */ # define RE_INVALID_INTERVAL_ORD (RE_DEBUG << 1) /* If this bit is set, then ignore case when matching. If not set, then case is significant. */ # define RE_ICASE (RE_INVALID_INTERVAL_ORD << 1) /* This bit is used internally like RE_CONTEXT_INDEP_ANCHORS but only for ^, because it is difficult to scan the regex backwards to find whether ^ should be special. */ # define RE_CARET_ANCHORS_HERE (RE_ICASE << 1) /* If this bit is set, then \{ cannot be first in a regex or immediately after an alternation, open-group or \} operator. */ # define RE_CONTEXT_INVALID_DUP (RE_CARET_ANCHORS_HERE << 1) /* If this bit is set, then no_sub will be set to 1 during re_compile_pattern. */ # define RE_NO_SUB (RE_CONTEXT_INVALID_DUP << 1) #endif /* This global variable defines the particular regexp syntax to use (for some interfaces). When a regexp is compiled, the syntax used is stored in the pattern buffer, so changing this does not affect already-compiled regexps. */ extern reg_syntax_t re_syntax_options; #ifdef __USE_GNU /* Define combinations of the above bits for the standard possibilities. (The [[[ comments delimit what gets put into the Texinfo file, so don't delete them!) */ /* [[[begin syntaxes]]] */ # define RE_SYNTAX_EMACS 0 # define RE_SYNTAX_AWK \ (RE_BACKSLASH_ESCAPE_IN_LISTS | RE_DOT_NOT_NULL \ | RE_NO_BK_PARENS | RE_NO_BK_REFS \ | RE_NO_BK_VBAR | RE_NO_EMPTY_RANGES \ | RE_DOT_NEWLINE | RE_CONTEXT_INDEP_ANCHORS \ | RE_CHAR_CLASSES \ | RE_UNMATCHED_RIGHT_PAREN_ORD | RE_NO_GNU_OPS) # define RE_SYNTAX_GNU_AWK \ ((RE_SYNTAX_POSIX_EXTENDED | RE_BACKSLASH_ESCAPE_IN_LISTS \ | RE_INVALID_INTERVAL_ORD) \ & ~(RE_DOT_NOT_NULL | RE_CONTEXT_INDEP_OPS \ | RE_CONTEXT_INVALID_OPS )) # define RE_SYNTAX_POSIX_AWK \ (RE_SYNTAX_POSIX_EXTENDED | RE_BACKSLASH_ESCAPE_IN_LISTS \ | RE_INTERVALS | RE_NO_GNU_OPS \ | RE_INVALID_INTERVAL_ORD) # define RE_SYNTAX_GREP \ ((RE_SYNTAX_POSIX_BASIC | RE_NEWLINE_ALT) \ & ~(RE_CONTEXT_INVALID_DUP | RE_DOT_NOT_NULL)) # define RE_SYNTAX_EGREP \ ((RE_SYNTAX_POSIX_EXTENDED | RE_INVALID_INTERVAL_ORD | RE_NEWLINE_ALT) \ & ~(RE_CONTEXT_INVALID_OPS | RE_DOT_NOT_NULL)) /* POSIX grep -E behavior is no longer incompatible with GNU. */ # define RE_SYNTAX_POSIX_EGREP \ RE_SYNTAX_EGREP /* P1003.2/D11.2, section 4.20.7.1, lines 5078ff. */ # define RE_SYNTAX_ED RE_SYNTAX_POSIX_BASIC # define RE_SYNTAX_SED RE_SYNTAX_POSIX_BASIC /* Syntax bits common to both basic and extended POSIX regex syntax. */ # define _RE_SYNTAX_POSIX_COMMON \ (RE_CHAR_CLASSES | RE_DOT_NEWLINE | RE_DOT_NOT_NULL \ | RE_INTERVALS | RE_NO_EMPTY_RANGES) # define RE_SYNTAX_POSIX_BASIC \ (_RE_SYNTAX_POSIX_COMMON | RE_BK_PLUS_QM | RE_CONTEXT_INVALID_DUP) /* Differs from ..._POSIX_BASIC only in that RE_BK_PLUS_QM becomes RE_LIMITED_OPS, i.e., \? \+ \| are not recognized. Actually, this isn't minimal, since other operators, such as \`, aren't disabled. */ # define RE_SYNTAX_POSIX_MINIMAL_BASIC \ (_RE_SYNTAX_POSIX_COMMON | RE_LIMITED_OPS) # define RE_SYNTAX_POSIX_EXTENDED \ (_RE_SYNTAX_POSIX_COMMON | RE_CONTEXT_INDEP_ANCHORS \ | RE_CONTEXT_INDEP_OPS | RE_NO_BK_BRACES \ | RE_NO_BK_PARENS | RE_NO_BK_VBAR \ | RE_CONTEXT_INVALID_OPS | RE_UNMATCHED_RIGHT_PAREN_ORD) /* Differs from ..._POSIX_EXTENDED in that RE_CONTEXT_INDEP_OPS is removed and RE_NO_BK_REFS is added. */ # define RE_SYNTAX_POSIX_MINIMAL_EXTENDED \ (_RE_SYNTAX_POSIX_COMMON | RE_CONTEXT_INDEP_ANCHORS \ | RE_CONTEXT_INVALID_OPS | RE_NO_BK_BRACES \ | RE_NO_BK_PARENS | RE_NO_BK_REFS \ | RE_NO_BK_VBAR | RE_UNMATCHED_RIGHT_PAREN_ORD) /* [[[end syntaxes]]] */ /* Maximum number of duplicates an interval can allow. POSIX-conforming systems might define this in , but we want our value, so remove any previous define. */ # ifdef _REGEX_INCLUDE_LIMITS_H # include # endif # ifdef RE_DUP_MAX # undef RE_DUP_MAX # endif /* RE_DUP_MAX is 2**15 - 1 because an earlier implementation stored the counter as a 2-byte signed integer. This is no longer true, so RE_DUP_MAX could be increased to (INT_MAX / 10 - 1), or to ((SIZE_MAX - 9) / 10) if _REGEX_LARGE_OFFSETS is defined. However, there would be a huge performance problem if someone actually used a pattern like a\{214748363\}, so RE_DUP_MAX retains its historical value. */ # define RE_DUP_MAX (0x7fff) #endif /* POSIX 'cflags' bits (i.e., information for 'regcomp'). */ /* If this bit is set, then use extended regular expression syntax. If not set, then use basic regular expression syntax. */ #define REG_EXTENDED 1 /* If this bit is set, then ignore case when matching. If not set, then case is significant. */ #define REG_ICASE (1 << 1) /* If this bit is set, then anchors do not match at newline characters in the string. If not set, then anchors do match at newlines. */ #define REG_NEWLINE (1 << 2) /* If this bit is set, then report only success or fail in regexec. If not set, then returns differ between not matching and errors. */ #define REG_NOSUB (1 << 3) /* POSIX 'eflags' bits (i.e., information for regexec). */ /* If this bit is set, then the beginning-of-line operator doesn't match the beginning of the string (presumably because it's not the beginning of a line). If not set, then the beginning-of-line operator does match the beginning of the string. */ #define REG_NOTBOL 1 /* Like REG_NOTBOL, except for the end-of-line. */ #define REG_NOTEOL (1 << 1) /* Use PMATCH[0] to delimit the start and end of the search in the buffer. */ #define REG_STARTEND (1 << 2) /* If any error codes are removed, changed, or added, update the '__re_error_msgid' table in regcomp.c. */ typedef enum { _REG_ENOSYS = -1, /* This will never happen for this implementation. */ _REG_NOERROR = 0, /* Success. */ _REG_NOMATCH, /* Didn't find a match (for regexec). */ /* POSIX regcomp return error codes. (In the order listed in the standard.) */ _REG_BADPAT, /* Invalid pattern. */ _REG_ECOLLATE, /* Invalid collating element. */ _REG_ECTYPE, /* Invalid character class name. */ _REG_EESCAPE, /* Trailing backslash. */ _REG_ESUBREG, /* Invalid back reference. */ _REG_EBRACK, /* Unmatched left bracket. */ _REG_EPAREN, /* Parenthesis imbalance. */ _REG_EBRACE, /* Unmatched \{. */ _REG_BADBR, /* Invalid contents of \{\}. */ _REG_ERANGE, /* Invalid range end. */ _REG_ESPACE, /* Ran out of memory. */ _REG_BADRPT, /* No preceding re for repetition op. */ /* Error codes we've added. */ _REG_EEND, /* Premature end. */ _REG_ESIZE, /* Too large (e.g., repeat count too large). */ _REG_ERPAREN /* Unmatched ) or \); not returned from regcomp. */ } reg_errcode_t; #if defined _XOPEN_SOURCE || defined __USE_XOPEN2K # define REG_ENOSYS _REG_ENOSYS #endif #define REG_NOERROR _REG_NOERROR #define REG_NOMATCH _REG_NOMATCH #define REG_BADPAT _REG_BADPAT #define REG_ECOLLATE _REG_ECOLLATE #define REG_ECTYPE _REG_ECTYPE #define REG_EESCAPE _REG_EESCAPE #define REG_ESUBREG _REG_ESUBREG #define REG_EBRACK _REG_EBRACK #define REG_EPAREN _REG_EPAREN #define REG_EBRACE _REG_EBRACE #define REG_BADBR _REG_BADBR #define REG_ERANGE _REG_ERANGE #define REG_ESPACE _REG_ESPACE #define REG_BADRPT _REG_BADRPT #define REG_EEND _REG_EEND #define REG_ESIZE _REG_ESIZE #define REG_ERPAREN _REG_ERPAREN /* This data structure represents a compiled pattern. Before calling the pattern compiler, the fields 'buffer', 'allocated', 'fastmap', and 'translate' can be set. After the pattern has been compiled, the fields 're_nsub', 'not_bol' and 'not_eol' are available. All other fields are private to the regex routines. */ #ifndef RE_TRANSLATE_TYPE # define __RE_TRANSLATE_TYPE unsigned char * # ifdef __USE_GNU # define RE_TRANSLATE_TYPE __RE_TRANSLATE_TYPE # endif #endif #ifdef __USE_GNU # define __REPB_PREFIX(name) name #else # define __REPB_PREFIX(name) __##name #endif struct re_pattern_buffer { /* Space that holds the compiled pattern. The type 'struct re_dfa_t' is private and is not declared here. */ struct re_dfa_t *__REPB_PREFIX(buffer); /* Number of bytes to which 'buffer' points. */ __re_long_size_t __REPB_PREFIX(allocated); /* Number of bytes actually used in 'buffer'. */ __re_long_size_t __REPB_PREFIX(used); /* Syntax setting with which the pattern was compiled. */ reg_syntax_t __REPB_PREFIX(syntax); /* Pointer to a fastmap, if any, otherwise zero. re_search uses the fastmap, if there is one, to skip over impossible starting points for matches. */ char *__REPB_PREFIX(fastmap); /* Either a translate table to apply to all characters before comparing them, or zero for no translation. The translation is applied to a pattern when it is compiled and to a string when it is matched. */ __RE_TRANSLATE_TYPE __REPB_PREFIX(translate); /* Number of subexpressions found by the compiler. */ size_t re_nsub; /* Zero if this pattern cannot match the empty string, one else. Well, in truth it's used only in 're_search_2', to see whether or not we should use the fastmap, so we don't set this absolutely perfectly; see 're_compile_fastmap' (the "duplicate" case). */ unsigned __REPB_PREFIX(can_be_null) : 1; /* If REGS_UNALLOCATED, allocate space in the 'regs' structure for 'max (RE_NREGS, re_nsub + 1)' groups. If REGS_REALLOCATE, reallocate space if necessary. If REGS_FIXED, use what's there. */ #ifdef __USE_GNU # define REGS_UNALLOCATED 0 # define REGS_REALLOCATE 1 # define REGS_FIXED 2 #endif unsigned __REPB_PREFIX(regs_allocated) : 2; /* Set to zero when 're_compile_pattern' compiles a pattern; set to one by 're_compile_fastmap' if it updates the fastmap. */ unsigned __REPB_PREFIX(fastmap_accurate) : 1; /* If set, 're_match_2' does not return information about subexpressions. */ unsigned __REPB_PREFIX(no_sub) : 1; /* If set, a beginning-of-line anchor doesn't match at the beginning of the string. */ unsigned __REPB_PREFIX(not_bol) : 1; /* Similarly for an end-of-line anchor. */ unsigned __REPB_PREFIX(not_eol) : 1; /* If true, an anchor at a newline matches. */ unsigned __REPB_PREFIX(newline_anchor) : 1; }; typedef struct re_pattern_buffer regex_t; /* Type for byte offsets within the string. POSIX mandates this. */ #ifdef _REGEX_LARGE_OFFSETS /* POSIX 1003.1-2008 requires that regoff_t be at least as wide as ptrdiff_t and ssize_t. We don't know of any hosts where ptrdiff_t is wider than ssize_t, so ssize_t is safe. ptrdiff_t is not visible here, so use ssize_t. */ typedef ssize_t regoff_t; #else /* The traditional GNU regex implementation mishandles strings longer than INT_MAX. */ typedef int regoff_t; #endif #ifdef __USE_GNU /* This is the structure we store register match data in. See regex.texinfo for a full description of what registers match. */ struct re_registers { __re_size_t num_regs; regoff_t *start; regoff_t *end; }; /* If 'regs_allocated' is REGS_UNALLOCATED in the pattern buffer, 're_match_2' returns information about at least this many registers the first time a 'regs' structure is passed. */ # ifndef RE_NREGS # define RE_NREGS 30 # endif #endif /* POSIX specification for registers. Aside from the different names than 're_registers', POSIX uses an array of structures, instead of a structure of arrays. */ typedef struct { regoff_t rm_so; /* Byte offset from string's start to substring's start. */ regoff_t rm_eo; /* Byte offset from string's start to substring's end. */ } regmatch_t; /* Declarations for routines. */ #ifdef __USE_GNU /* Sets the current default syntax to SYNTAX, and return the old syntax. You can also simply assign to the 're_syntax_options' variable. */ extern reg_syntax_t re_set_syntax (reg_syntax_t __syntax); /* Compile the regular expression PATTERN, with length LENGTH and syntax given by the global 're_syntax_options', into the buffer BUFFER. Return NULL if successful, and an error string if not. To free the allocated storage, you must call 'regfree' on BUFFER. Note that the translate table must either have been initialized by 'regcomp', with a malloc'ed value, or set to NULL before calling 'regfree'. */ extern const char *re_compile_pattern (const char *__pattern, size_t __length, struct re_pattern_buffer *__buffer); /* Compile a fastmap for the compiled pattern in BUFFER; used to accelerate searches. Return 0 if successful and -2 if was an internal error. */ extern int re_compile_fastmap (struct re_pattern_buffer *__buffer); /* Search in the string STRING (with length LENGTH) for the pattern compiled into BUFFER. Start searching at position START, for RANGE characters. Return the starting position of the match, -1 for no match, or -2 for an internal error. Also return register information in REGS (if REGS and BUFFER->no_sub are nonzero). */ extern regoff_t re_search (struct re_pattern_buffer *__buffer, const char *__string, regoff_t __length, regoff_t __start, regoff_t __range, struct re_registers *__regs); /* Like 're_search', but search in the concatenation of STRING1 and STRING2. Also, stop searching at index START + STOP. */ extern regoff_t re_search_2 (struct re_pattern_buffer *__buffer, const char *__string1, regoff_t __length1, const char *__string2, regoff_t __length2, regoff_t __start, regoff_t __range, struct re_registers *__regs, regoff_t __stop); /* Like 're_search', but return how many characters in STRING the regexp in BUFFER matched, starting at position START. */ extern regoff_t re_match (struct re_pattern_buffer *__buffer, const char *__string, regoff_t __length, regoff_t __start, struct re_registers *__regs); /* Relates to 're_match' as 're_search_2' relates to 're_search'. */ extern regoff_t re_match_2 (struct re_pattern_buffer *__buffer, const char *__string1, regoff_t __length1, const char *__string2, regoff_t __length2, regoff_t __start, struct re_registers *__regs, regoff_t __stop); /* Set REGS to hold NUM_REGS registers, storing them in STARTS and ENDS. Subsequent matches using BUFFER and REGS will use this memory for recording register information. STARTS and ENDS must be allocated with malloc, and must each be at least 'NUM_REGS * sizeof (regoff_t)' bytes long. If NUM_REGS == 0, then subsequent matches should allocate their own register data. Unless this function is called, the first search or match using BUFFER will allocate its own register data, without freeing the old data. */ extern void re_set_registers (struct re_pattern_buffer *__buffer, struct re_registers *__regs, __re_size_t __num_regs, regoff_t *__starts, regoff_t *__ends); #endif /* Use GNU */ #if defined _REGEX_RE_COMP || (defined _LIBC && defined __USE_MISC) # ifndef _CRAY /* 4.2 bsd compatibility. */ extern char *re_comp (const char *); extern int re_exec (const char *); # endif #endif /* GCC 2.95 and later have "__restrict"; C99 compilers have "restrict", and "configure" may have defined "restrict". Other compilers use __restrict, __restrict__, and _Restrict, and 'configure' might #define 'restrict' to those words, so pick a different name. */ #ifndef _Restrict_ # if 199901L <= __STDC_VERSION__ # define _Restrict_ restrict # elif 2 < __GNUC__ || (2 == __GNUC__ && 95 <= __GNUC_MINOR__) # define _Restrict_ __restrict # else # define _Restrict_ # endif #endif /* gcc 3.1 and up support the [restrict] syntax. Don't trust sys/cdefs.h's definition of __restrict_arr, though, as it mishandles gcc -ansi -pedantic. */ #ifndef _Restrict_arr_ # if ((199901L <= __STDC_VERSION__ \ || ((3 < __GNUC__ || (3 == __GNUC__ && 1 <= __GNUC_MINOR__)) \ && !defined __STRICT_ANSI__)) \ && !defined __GNUG__) # define _Restrict_arr_ _Restrict_ # else # define _Restrict_arr_ # endif #endif /* POSIX compatibility. */ extern int regcomp (regex_t *_Restrict_ __preg, const char *_Restrict_ __pattern, int __cflags); extern int regexec (const regex_t *_Restrict_ __preg, const char *_Restrict_ __string, size_t __nmatch, regmatch_t __pmatch[_Restrict_arr_], int __eflags); extern size_t regerror (int __errcode, const regex_t *_Restrict_ __preg, char *_Restrict_ __errbuf, size_t __errbuf_size); extern void regfree (regex_t *__preg); #ifdef __cplusplus } #endif /* C++ */ #endif /* regex.h */ ne-3.3.4/src/regex_internal.c000066400000000000000000001365401475116431000160770ustar00rootroot00000000000000/* Extended regular expression matching and search library. Copyright (C) 2002-2025 Free Software Foundation, Inc. This file is part of the GNU C Library. Contributed by Isamu Hasegawa . The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. The GNU C Library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with the GNU C Library; if not, see . */ static void re_string_construct_common (const char *str, Idx len, re_string_t *pstr, RE_TRANSLATE_TYPE trans, bool icase, const re_dfa_t *dfa) internal_function; static re_dfastate_t *create_ci_newstate (const re_dfa_t *dfa, const re_node_set *nodes, re_hashval_t hash) internal_function; static re_dfastate_t *create_cd_newstate (const re_dfa_t *dfa, const re_node_set *nodes, unsigned int context, re_hashval_t hash) internal_function; /* Functions for string operation. */ /* This function allocate the buffers. It is necessary to call re_string_reconstruct before using the object. */ static reg_errcode_t internal_function __attribute_warn_unused_result__ re_string_allocate (re_string_t *pstr, const char *str, Idx len, Idx init_len, RE_TRANSLATE_TYPE trans, bool icase, const re_dfa_t *dfa) { reg_errcode_t ret; Idx init_buf_len; /* Ensure at least one character fits into the buffers. */ if (init_len < dfa->mb_cur_max) init_len = dfa->mb_cur_max; init_buf_len = (len + 1 < init_len) ? len + 1: init_len; re_string_construct_common (str, len, pstr, trans, icase, dfa); ret = re_string_realloc_buffers (pstr, init_buf_len); if (BE (ret != REG_NOERROR, 0)) return ret; pstr->word_char = dfa->word_char; pstr->word_ops_used = dfa->word_ops_used; pstr->mbs = pstr->mbs_allocated ? pstr->mbs : (unsigned char *) str; pstr->valid_len = (pstr->mbs_allocated || dfa->mb_cur_max > 1) ? 0 : len; pstr->valid_raw_len = pstr->valid_len; return REG_NOERROR; } /* This function allocate the buffers, and initialize them. */ static reg_errcode_t internal_function __attribute_warn_unused_result__ re_string_construct (re_string_t *pstr, const char *str, Idx len, RE_TRANSLATE_TYPE trans, bool icase, const re_dfa_t *dfa) { reg_errcode_t ret; memset (pstr, '\0', sizeof (re_string_t)); re_string_construct_common (str, len, pstr, trans, icase, dfa); if (len > 0) { ret = re_string_realloc_buffers (pstr, len + 1); if (BE (ret != REG_NOERROR, 0)) return ret; } pstr->mbs = pstr->mbs_allocated ? pstr->mbs : (unsigned char *) str; if (icase) { #ifdef RE_ENABLE_I18N if (dfa->mb_cur_max > 1) { while (1) { ret = build_wcs_upper_buffer (pstr); if (BE (ret != REG_NOERROR, 0)) return ret; if (pstr->valid_raw_len >= len) break; if (pstr->bufs_len > pstr->valid_len + dfa->mb_cur_max) break; ret = re_string_realloc_buffers (pstr, pstr->bufs_len * 2); if (BE (ret != REG_NOERROR, 0)) return ret; } } else #endif /* RE_ENABLE_I18N */ build_upper_buffer (pstr); } else { #ifdef RE_ENABLE_I18N if (dfa->mb_cur_max > 1) build_wcs_buffer (pstr); else #endif /* RE_ENABLE_I18N */ { if (trans != NULL) re_string_translate_buffer (pstr); else { pstr->valid_len = pstr->bufs_len; pstr->valid_raw_len = pstr->bufs_len; } } } return REG_NOERROR; } /* Helper functions for re_string_allocate, and re_string_construct. */ static reg_errcode_t internal_function __attribute_warn_unused_result__ re_string_realloc_buffers (re_string_t *pstr, Idx new_buf_len) { #ifdef RE_ENABLE_I18N if (pstr->mb_cur_max > 1) { wint_t *new_wcs; /* Avoid overflow in realloc. */ const size_t max_object_size = MAX (sizeof (wint_t), sizeof (Idx)); if (BE (MIN (IDX_MAX, SIZE_MAX / max_object_size) < new_buf_len, 0)) return REG_ESPACE; new_wcs = re_realloc (pstr->wcs, wint_t, new_buf_len); if (BE (new_wcs == NULL, 0)) return REG_ESPACE; pstr->wcs = new_wcs; if (pstr->offsets != NULL) { Idx *new_offsets = re_realloc (pstr->offsets, Idx, new_buf_len); if (BE (new_offsets == NULL, 0)) return REG_ESPACE; pstr->offsets = new_offsets; } } #endif /* RE_ENABLE_I18N */ if (pstr->mbs_allocated) { unsigned char *new_mbs = re_realloc (pstr->mbs, unsigned char, new_buf_len); if (BE (new_mbs == NULL, 0)) return REG_ESPACE; pstr->mbs = new_mbs; } pstr->bufs_len = new_buf_len; return REG_NOERROR; } static void internal_function re_string_construct_common (const char *str, Idx len, re_string_t *pstr, RE_TRANSLATE_TYPE trans, bool icase, const re_dfa_t *dfa) { pstr->raw_mbs = (const unsigned char *) str; pstr->len = len; pstr->raw_len = len; pstr->trans = trans; pstr->icase = icase; pstr->mbs_allocated = (trans != NULL || icase); pstr->mb_cur_max = dfa->mb_cur_max; pstr->is_utf8 = dfa->is_utf8; pstr->map_notascii = dfa->map_notascii; pstr->stop = pstr->len; pstr->raw_stop = pstr->stop; } #ifdef RE_ENABLE_I18N /* Build wide character buffer PSTR->WCS. If the byte sequence of the string are: (0), (1), (0), (1), Then wide character buffer will be: , WEOF , , WEOF , We use WEOF for padding, they indicate that the position isn't a first byte of a multibyte character. Note that this function assumes PSTR->VALID_LEN elements are already built and starts from PSTR->VALID_LEN. */ static void internal_function build_wcs_buffer (re_string_t *pstr) { #ifdef _LIBC unsigned char buf[MB_LEN_MAX]; assert (MB_LEN_MAX >= pstr->mb_cur_max); #else unsigned char buf[64]; #endif mbstate_t prev_st; Idx byte_idx, end_idx, remain_len; size_t mbclen; /* Build the buffers from pstr->valid_len to either pstr->len or pstr->bufs_len. */ end_idx = (pstr->bufs_len > pstr->len) ? pstr->len : pstr->bufs_len; for (byte_idx = pstr->valid_len; byte_idx < end_idx;) { wchar_t wc; const char *p; remain_len = end_idx - byte_idx; prev_st = pstr->cur_state; /* Apply the translation if we need. */ if (BE (pstr->trans != NULL, 0)) { int i, ch; for (i = 0; i < pstr->mb_cur_max && i < remain_len; ++i) { ch = pstr->raw_mbs [pstr->raw_mbs_idx + byte_idx + i]; buf[i] = pstr->mbs[byte_idx + i] = pstr->trans[ch]; } p = (const char *) buf; } else p = (const char *) pstr->raw_mbs + pstr->raw_mbs_idx + byte_idx; mbclen = __mbrtowc (&wc, p, remain_len, &pstr->cur_state); if (BE (mbclen == (size_t) -1 || mbclen == 0 || (mbclen == (size_t) -2 && pstr->bufs_len >= pstr->len), 0)) { /* We treat these cases as a singlebyte character. */ mbclen = 1; wc = (wchar_t) pstr->raw_mbs[pstr->raw_mbs_idx + byte_idx]; if (BE (pstr->trans != NULL, 0)) wc = pstr->trans[wc]; pstr->cur_state = prev_st; } else if (BE (mbclen == (size_t) -2, 0)) { /* The buffer doesn't have enough space, finish to build. */ pstr->cur_state = prev_st; break; } /* Write wide character and padding. */ pstr->wcs[byte_idx++] = wc; /* Write paddings. */ for (remain_len = byte_idx + mbclen - 1; byte_idx < remain_len ;) pstr->wcs[byte_idx++] = WEOF; } pstr->valid_len = byte_idx; pstr->valid_raw_len = byte_idx; } /* Build wide character buffer PSTR->WCS like build_wcs_buffer, but for REG_ICASE. */ static reg_errcode_t internal_function __attribute_warn_unused_result__ build_wcs_upper_buffer (re_string_t *pstr) { mbstate_t prev_st; Idx src_idx, byte_idx, end_idx, remain_len; size_t mbclen; #ifdef _LIBC char buf[MB_LEN_MAX]; assert (MB_LEN_MAX >= pstr->mb_cur_max); #else char buf[64]; #endif byte_idx = pstr->valid_len; end_idx = (pstr->bufs_len > pstr->len) ? pstr->len : pstr->bufs_len; /* The following optimization assumes that ASCII characters can be mapped to wide characters with a simple cast. */ if (! pstr->map_notascii && pstr->trans == NULL && !pstr->offsets_needed) { while (byte_idx < end_idx) { wchar_t wc; if (isascii (pstr->raw_mbs[pstr->raw_mbs_idx + byte_idx]) && mbsinit (&pstr->cur_state)) { /* In case of a singlebyte character. */ pstr->mbs[byte_idx] = toupper (pstr->raw_mbs[pstr->raw_mbs_idx + byte_idx]); /* The next step uses the assumption that wchar_t is encoded ASCII-safe: all ASCII values can be converted like this. */ pstr->wcs[byte_idx] = (wchar_t) pstr->mbs[byte_idx]; ++byte_idx; continue; } remain_len = end_idx - byte_idx; prev_st = pstr->cur_state; mbclen = __mbrtowc (&wc, ((const char *) pstr->raw_mbs + pstr->raw_mbs_idx + byte_idx), remain_len, &pstr->cur_state); if (BE (mbclen < (size_t) -2, 1)) { wchar_t wcu = __towupper (wc); if (wcu != wc) { size_t mbcdlen; mbcdlen = __wcrtomb (buf, wcu, &prev_st); if (BE (mbclen == mbcdlen, 1)) memcpy (pstr->mbs + byte_idx, buf, mbclen); else { src_idx = byte_idx; goto offsets_needed; } } else memcpy (pstr->mbs + byte_idx, pstr->raw_mbs + pstr->raw_mbs_idx + byte_idx, mbclen); pstr->wcs[byte_idx++] = wcu; /* Write paddings. */ for (remain_len = byte_idx + mbclen - 1; byte_idx < remain_len ;) pstr->wcs[byte_idx++] = WEOF; } else if (mbclen == (size_t) -1 || mbclen == 0 || (mbclen == (size_t) -2 && pstr->bufs_len >= pstr->len)) { /* It is an invalid character, an incomplete character at the end of the string, or '\0'. Just use the byte. */ int ch = pstr->raw_mbs[pstr->raw_mbs_idx + byte_idx]; pstr->mbs[byte_idx] = ch; /* And also cast it to wide char. */ pstr->wcs[byte_idx++] = (wchar_t) ch; if (BE (mbclen == (size_t) -1, 0)) pstr->cur_state = prev_st; } else { /* The buffer doesn't have enough space, finish to build. */ pstr->cur_state = prev_st; break; } } pstr->valid_len = byte_idx; pstr->valid_raw_len = byte_idx; return REG_NOERROR; } else for (src_idx = pstr->valid_raw_len; byte_idx < end_idx;) { wchar_t wc; const char *p; offsets_needed: remain_len = end_idx - byte_idx; prev_st = pstr->cur_state; if (BE (pstr->trans != NULL, 0)) { int i, ch; for (i = 0; i < pstr->mb_cur_max && i < remain_len; ++i) { ch = pstr->raw_mbs [pstr->raw_mbs_idx + src_idx + i]; buf[i] = pstr->trans[ch]; } p = (const char *) buf; } else p = (const char *) pstr->raw_mbs + pstr->raw_mbs_idx + src_idx; mbclen = __mbrtowc (&wc, p, remain_len, &pstr->cur_state); if (BE (mbclen < (size_t) -2, 1)) { wchar_t wcu = __towupper (wc); if (wcu != wc) { size_t mbcdlen; mbcdlen = wcrtomb ((char *) buf, wcu, &prev_st); if (BE (mbclen == mbcdlen, 1)) memcpy (pstr->mbs + byte_idx, buf, mbclen); else if (mbcdlen != (size_t) -1) { size_t i; if (byte_idx + mbcdlen > pstr->bufs_len) { pstr->cur_state = prev_st; break; } if (pstr->offsets == NULL) { pstr->offsets = re_malloc (Idx, pstr->bufs_len); if (pstr->offsets == NULL) return REG_ESPACE; } if (!pstr->offsets_needed) { for (i = 0; i < (size_t) byte_idx; ++i) pstr->offsets[i] = i; pstr->offsets_needed = 1; } memcpy (pstr->mbs + byte_idx, buf, mbcdlen); pstr->wcs[byte_idx] = wcu; pstr->offsets[byte_idx] = src_idx; for (i = 1; i < mbcdlen; ++i) { pstr->offsets[byte_idx + i] = src_idx + (i < mbclen ? i : mbclen - 1); pstr->wcs[byte_idx + i] = WEOF; } pstr->len += mbcdlen - mbclen; if (pstr->raw_stop > src_idx) pstr->stop += mbcdlen - mbclen; end_idx = (pstr->bufs_len > pstr->len) ? pstr->len : pstr->bufs_len; byte_idx += mbcdlen; src_idx += mbclen; continue; } else memcpy (pstr->mbs + byte_idx, p, mbclen); } else memcpy (pstr->mbs + byte_idx, p, mbclen); if (BE (pstr->offsets_needed != 0, 0)) { size_t i; for (i = 0; i < mbclen; ++i) pstr->offsets[byte_idx + i] = src_idx + i; } src_idx += mbclen; pstr->wcs[byte_idx++] = wcu; /* Write paddings. */ for (remain_len = byte_idx + mbclen - 1; byte_idx < remain_len ;) pstr->wcs[byte_idx++] = WEOF; } else if (mbclen == (size_t) -1 || mbclen == 0 || (mbclen == (size_t) -2 && pstr->bufs_len >= pstr->len)) { /* It is an invalid character or '\0'. Just use the byte. */ int ch = pstr->raw_mbs[pstr->raw_mbs_idx + src_idx]; if (BE (pstr->trans != NULL, 0)) ch = pstr->trans [ch]; pstr->mbs[byte_idx] = ch; if (BE (pstr->offsets_needed != 0, 0)) pstr->offsets[byte_idx] = src_idx; ++src_idx; /* And also cast it to wide char. */ pstr->wcs[byte_idx++] = (wchar_t) ch; if (BE (mbclen == (size_t) -1, 0)) pstr->cur_state = prev_st; } else { /* The buffer doesn't have enough space, finish to build. */ pstr->cur_state = prev_st; break; } } pstr->valid_len = byte_idx; pstr->valid_raw_len = src_idx; return REG_NOERROR; } /* Skip characters until the index becomes greater than NEW_RAW_IDX. Return the index. */ static Idx internal_function re_string_skip_chars (re_string_t *pstr, Idx new_raw_idx, wint_t *last_wc) { mbstate_t prev_st; Idx rawbuf_idx; size_t mbclen; wint_t wc = WEOF; /* Skip the characters which are not necessary to check. */ for (rawbuf_idx = pstr->raw_mbs_idx + pstr->valid_raw_len; rawbuf_idx < new_raw_idx;) { wchar_t wc2; Idx remain_len = pstr->raw_len - rawbuf_idx; prev_st = pstr->cur_state; mbclen = __mbrtowc (&wc2, (const char *) pstr->raw_mbs + rawbuf_idx, remain_len, &pstr->cur_state); if (BE (mbclen == (size_t) -2 || mbclen == (size_t) -1 || mbclen == 0, 0)) { /* We treat these cases as a single byte character. */ if (mbclen == 0 || remain_len == 0) wc = L'\0'; else wc = *(unsigned char *) (pstr->raw_mbs + rawbuf_idx); mbclen = 1; pstr->cur_state = prev_st; } else wc = wc2; /* Then proceed the next character. */ rawbuf_idx += mbclen; } *last_wc = wc; return rawbuf_idx; } #endif /* RE_ENABLE_I18N */ /* Build the buffer PSTR->MBS, and apply the translation if we need. This function is used in case of REG_ICASE. */ static void internal_function build_upper_buffer (re_string_t *pstr) { Idx char_idx, end_idx; end_idx = (pstr->bufs_len > pstr->len) ? pstr->len : pstr->bufs_len; for (char_idx = pstr->valid_len; char_idx < end_idx; ++char_idx) { int ch = pstr->raw_mbs[pstr->raw_mbs_idx + char_idx]; if (BE (pstr->trans != NULL, 0)) ch = pstr->trans[ch]; pstr->mbs[char_idx] = toupper (ch); } pstr->valid_len = char_idx; pstr->valid_raw_len = char_idx; } /* Apply TRANS to the buffer in PSTR. */ static void internal_function re_string_translate_buffer (re_string_t *pstr) { Idx buf_idx, end_idx; end_idx = (pstr->bufs_len > pstr->len) ? pstr->len : pstr->bufs_len; for (buf_idx = pstr->valid_len; buf_idx < end_idx; ++buf_idx) { int ch = pstr->raw_mbs[pstr->raw_mbs_idx + buf_idx]; pstr->mbs[buf_idx] = pstr->trans[ch]; } pstr->valid_len = buf_idx; pstr->valid_raw_len = buf_idx; } /* This function re-construct the buffers. Concretely, convert to wide character in case of pstr->mb_cur_max > 1, convert to upper case in case of REG_ICASE, apply translation. */ static reg_errcode_t internal_function __attribute_warn_unused_result__ re_string_reconstruct (re_string_t *pstr, Idx idx, int eflags) { Idx offset; if (BE (pstr->raw_mbs_idx <= idx, 0)) offset = idx - pstr->raw_mbs_idx; else { /* Reset buffer. */ #ifdef RE_ENABLE_I18N if (pstr->mb_cur_max > 1) memset (&pstr->cur_state, '\0', sizeof (mbstate_t)); #endif /* RE_ENABLE_I18N */ pstr->len = pstr->raw_len; pstr->stop = pstr->raw_stop; pstr->valid_len = 0; pstr->raw_mbs_idx = 0; pstr->valid_raw_len = 0; pstr->offsets_needed = 0; pstr->tip_context = ((eflags & REG_NOTBOL) ? CONTEXT_BEGBUF : CONTEXT_NEWLINE | CONTEXT_BEGBUF); if (!pstr->mbs_allocated) pstr->mbs = (unsigned char *) pstr->raw_mbs; offset = idx; } if (BE (offset != 0, 1)) { /* Should the already checked characters be kept? */ if (BE (offset < pstr->valid_raw_len, 1)) { /* Yes, move them to the front of the buffer. */ #ifdef RE_ENABLE_I18N if (BE (pstr->offsets_needed, 0)) { Idx low = 0, high = pstr->valid_len, mid; do { mid = (high + low) / 2; if (pstr->offsets[mid] > offset) high = mid; else if (pstr->offsets[mid] < offset) low = mid + 1; else break; } while (low < high); if (pstr->offsets[mid] < offset) ++mid; pstr->tip_context = re_string_context_at (pstr, mid - 1, eflags); /* This can be quite complicated, so handle specially only the common and easy case where the character with different length representation of lower and upper case is present at or after offset. */ if (pstr->valid_len > offset && mid == offset && pstr->offsets[mid] == offset) { memmove (pstr->wcs, pstr->wcs + offset, (pstr->valid_len - offset) * sizeof (wint_t)); memmove (pstr->mbs, pstr->mbs + offset, pstr->valid_len - offset); pstr->valid_len -= offset; pstr->valid_raw_len -= offset; for (low = 0; low < pstr->valid_len; low++) pstr->offsets[low] = pstr->offsets[low + offset] - offset; } else { /* Otherwise, just find out how long the partial multibyte character at offset is and fill it with WEOF/255. */ pstr->len = pstr->raw_len - idx + offset; pstr->stop = pstr->raw_stop - idx + offset; pstr->offsets_needed = 0; while (mid > 0 && pstr->offsets[mid - 1] == offset) --mid; while (mid < pstr->valid_len) if (pstr->wcs[mid] != WEOF) break; else ++mid; if (mid == pstr->valid_len) pstr->valid_len = 0; else { pstr->valid_len = pstr->offsets[mid] - offset; if (pstr->valid_len) { for (low = 0; low < pstr->valid_len; ++low) pstr->wcs[low] = WEOF; memset (pstr->mbs, 255, pstr->valid_len); } } pstr->valid_raw_len = pstr->valid_len; } } else #endif { pstr->tip_context = re_string_context_at (pstr, offset - 1, eflags); #ifdef RE_ENABLE_I18N if (pstr->mb_cur_max > 1) memmove (pstr->wcs, pstr->wcs + offset, (pstr->valid_len - offset) * sizeof (wint_t)); #endif /* RE_ENABLE_I18N */ if (BE (pstr->mbs_allocated, 0)) memmove (pstr->mbs, pstr->mbs + offset, pstr->valid_len - offset); pstr->valid_len -= offset; pstr->valid_raw_len -= offset; #if defined DEBUG && DEBUG assert (pstr->valid_len > 0); #endif } } else { #ifdef RE_ENABLE_I18N /* No, skip all characters until IDX. */ Idx prev_valid_len = pstr->valid_len; if (BE (pstr->offsets_needed, 0)) { pstr->len = pstr->raw_len - idx + offset; pstr->stop = pstr->raw_stop - idx + offset; pstr->offsets_needed = 0; } #endif pstr->valid_len = 0; #ifdef RE_ENABLE_I18N if (pstr->mb_cur_max > 1) { Idx wcs_idx; wint_t wc = WEOF; if (pstr->is_utf8) { const unsigned char *raw, *p, *end; /* Special case UTF-8. Multi-byte chars start with any byte other than 0x80 - 0xbf. */ raw = pstr->raw_mbs + pstr->raw_mbs_idx; end = raw + (offset - pstr->mb_cur_max); if (end < pstr->raw_mbs) end = pstr->raw_mbs; p = raw + offset - 1; #ifdef _LIBC /* We know the wchar_t encoding is UCS4, so for the simple case, ASCII characters, skip the conversion step. */ if (isascii (*p) && BE (pstr->trans == NULL, 1)) { memset (&pstr->cur_state, '\0', sizeof (mbstate_t)); /* pstr->valid_len = 0; */ wc = (wchar_t) *p; } else #endif for (; p >= end; --p) if ((*p & 0xc0) != 0x80) { mbstate_t cur_state; wchar_t wc2; Idx mlen = raw + pstr->len - p; unsigned char buf[6]; size_t mbclen; const unsigned char *pp = p; if (BE (pstr->trans != NULL, 0)) { int i = mlen < 6 ? mlen : 6; while (--i >= 0) buf[i] = pstr->trans[p[i]]; pp = buf; } /* XXX Don't use mbrtowc, we know which conversion to use (UTF-8 -> UCS4). */ memset (&cur_state, 0, sizeof (cur_state)); mbclen = __mbrtowc (&wc2, (const char *) pp, mlen, &cur_state); if (raw + offset - p <= mbclen && mbclen < (size_t) -2) { memset (&pstr->cur_state, '\0', sizeof (mbstate_t)); pstr->valid_len = mbclen - (raw + offset - p); wc = wc2; } break; } } if (wc == WEOF) pstr->valid_len = re_string_skip_chars (pstr, idx, &wc) - idx; if (wc == WEOF) pstr->tip_context = re_string_context_at (pstr, prev_valid_len - 1, eflags); else pstr->tip_context = ((BE (pstr->word_ops_used != 0, 0) && IS_WIDE_WORD_CHAR (wc)) ? CONTEXT_WORD : ((IS_WIDE_NEWLINE (wc) && pstr->newline_anchor) ? CONTEXT_NEWLINE : 0)); if (BE (pstr->valid_len, 0)) { for (wcs_idx = 0; wcs_idx < pstr->valid_len; ++wcs_idx) pstr->wcs[wcs_idx] = WEOF; if (pstr->mbs_allocated) memset (pstr->mbs, 255, pstr->valid_len); } pstr->valid_raw_len = pstr->valid_len; } else #endif /* RE_ENABLE_I18N */ { int c = pstr->raw_mbs[pstr->raw_mbs_idx + offset - 1]; pstr->valid_raw_len = 0; if (pstr->trans) c = pstr->trans[c]; pstr->tip_context = (bitset_contain (pstr->word_char, c) ? CONTEXT_WORD : ((IS_NEWLINE (c) && pstr->newline_anchor) ? CONTEXT_NEWLINE : 0)); } } if (!BE (pstr->mbs_allocated, 0)) pstr->mbs += offset; } pstr->raw_mbs_idx = idx; pstr->len -= offset; pstr->stop -= offset; /* Then build the buffers. */ #ifdef RE_ENABLE_I18N if (pstr->mb_cur_max > 1) { if (pstr->icase) { reg_errcode_t ret = build_wcs_upper_buffer (pstr); if (BE (ret != REG_NOERROR, 0)) return ret; } else build_wcs_buffer (pstr); } else #endif /* RE_ENABLE_I18N */ if (BE (pstr->mbs_allocated, 0)) { if (pstr->icase) build_upper_buffer (pstr); else if (pstr->trans != NULL) re_string_translate_buffer (pstr); } else pstr->valid_len = pstr->len; pstr->cur_idx = 0; return REG_NOERROR; } static unsigned char internal_function __attribute__ ((pure)) re_string_peek_byte_case (const re_string_t *pstr, Idx idx) { int ch; Idx off; /* Handle the common (easiest) cases first. */ if (BE (!pstr->mbs_allocated, 1)) return re_string_peek_byte (pstr, idx); #ifdef RE_ENABLE_I18N if (pstr->mb_cur_max > 1 && ! re_string_is_single_byte_char (pstr, pstr->cur_idx + idx)) return re_string_peek_byte (pstr, idx); #endif off = pstr->cur_idx + idx; #ifdef RE_ENABLE_I18N if (pstr->offsets_needed) off = pstr->offsets[off]; #endif ch = pstr->raw_mbs[pstr->raw_mbs_idx + off]; #ifdef RE_ENABLE_I18N /* Ensure that e.g. for tr_TR.UTF-8 BACKSLASH DOTLESS SMALL LETTER I this function returns CAPITAL LETTER I instead of first byte of DOTLESS SMALL LETTER I. The latter would confuse the parser, since peek_byte_case doesn't advance cur_idx in any way. */ if (pstr->offsets_needed && !isascii (ch)) return re_string_peek_byte (pstr, idx); #endif return ch; } static unsigned char internal_function re_string_fetch_byte_case (re_string_t *pstr) { if (BE (!pstr->mbs_allocated, 1)) return re_string_fetch_byte (pstr); #ifdef RE_ENABLE_I18N if (pstr->offsets_needed) { Idx off; int ch; /* For tr_TR.UTF-8 [[:islower:]] there is [[: CAPITAL LETTER I WITH DOT lower:]] in mbs. Skip in that case the whole multi-byte character and return the original letter. On the other side, with [[: DOTLESS SMALL LETTER I return [[:I, as doing anything else would complicate things too much. */ if (!re_string_first_byte (pstr, pstr->cur_idx)) return re_string_fetch_byte (pstr); off = pstr->offsets[pstr->cur_idx]; ch = pstr->raw_mbs[pstr->raw_mbs_idx + off]; if (! isascii (ch)) return re_string_fetch_byte (pstr); re_string_skip_bytes (pstr, re_string_char_size_at (pstr, pstr->cur_idx)); return ch; } #endif return pstr->raw_mbs[pstr->raw_mbs_idx + pstr->cur_idx++]; } static void internal_function re_string_destruct (re_string_t *pstr) { #ifdef RE_ENABLE_I18N re_free (pstr->wcs); re_free (pstr->offsets); #endif /* RE_ENABLE_I18N */ if (pstr->mbs_allocated) re_free (pstr->mbs); } /* Return the context at IDX in INPUT. */ static unsigned int internal_function re_string_context_at (const re_string_t *input, Idx idx, int eflags) { int c; if (BE (idx < 0, 0)) /* In this case, we use the value stored in input->tip_context, since we can't know the character in input->mbs[-1] here. */ return input->tip_context; if (BE (idx == input->len, 0)) return ((eflags & REG_NOTEOL) ? CONTEXT_ENDBUF : CONTEXT_NEWLINE | CONTEXT_ENDBUF); #ifdef RE_ENABLE_I18N if (input->mb_cur_max > 1) { wint_t wc; Idx wc_idx = idx; while(input->wcs[wc_idx] == WEOF) { #if defined DEBUG && DEBUG /* It must not happen. */ assert (wc_idx >= 0); #endif --wc_idx; if (wc_idx < 0) return input->tip_context; } wc = input->wcs[wc_idx]; if (BE (input->word_ops_used != 0, 0) && IS_WIDE_WORD_CHAR (wc)) return CONTEXT_WORD; return (IS_WIDE_NEWLINE (wc) && input->newline_anchor ? CONTEXT_NEWLINE : 0); } else #endif { c = re_string_byte_at (input, idx); if (bitset_contain (input->word_char, c)) return CONTEXT_WORD; return IS_NEWLINE (c) && input->newline_anchor ? CONTEXT_NEWLINE : 0; } } /* Functions for set operation. */ static reg_errcode_t internal_function __attribute_warn_unused_result__ re_node_set_alloc (re_node_set *set, Idx size) { set->alloc = size; set->nelem = 0; set->elems = re_malloc (Idx, size); if (BE (set->elems == NULL, 0) && (MALLOC_0_IS_NONNULL || size != 0)) return REG_ESPACE; return REG_NOERROR; } static reg_errcode_t internal_function __attribute_warn_unused_result__ re_node_set_init_1 (re_node_set *set, Idx elem) { set->alloc = 1; set->nelem = 1; set->elems = re_malloc (Idx, 1); if (BE (set->elems == NULL, 0)) { set->alloc = set->nelem = 0; return REG_ESPACE; } set->elems[0] = elem; return REG_NOERROR; } static reg_errcode_t internal_function __attribute_warn_unused_result__ re_node_set_init_2 (re_node_set *set, Idx elem1, Idx elem2) { set->alloc = 2; set->elems = re_malloc (Idx, 2); if (BE (set->elems == NULL, 0)) return REG_ESPACE; if (elem1 == elem2) { set->nelem = 1; set->elems[0] = elem1; } else { set->nelem = 2; if (elem1 < elem2) { set->elems[0] = elem1; set->elems[1] = elem2; } else { set->elems[0] = elem2; set->elems[1] = elem1; } } return REG_NOERROR; } static reg_errcode_t internal_function __attribute_warn_unused_result__ re_node_set_init_copy (re_node_set *dest, const re_node_set *src) { dest->nelem = src->nelem; if (src->nelem > 0) { dest->alloc = dest->nelem; dest->elems = re_malloc (Idx, dest->alloc); if (BE (dest->elems == NULL, 0)) { dest->alloc = dest->nelem = 0; return REG_ESPACE; } memcpy (dest->elems, src->elems, src->nelem * sizeof (Idx)); } else re_node_set_init_empty (dest); return REG_NOERROR; } /* Calculate the intersection of the sets SRC1 and SRC2. And merge it to DEST. Return value indicate the error code or REG_NOERROR if succeeded. Note: We assume dest->elems is NULL, when dest->alloc is 0. */ static reg_errcode_t internal_function __attribute_warn_unused_result__ re_node_set_add_intersect (re_node_set *dest, const re_node_set *src1, const re_node_set *src2) { Idx i1, i2, is, id, delta, sbase; if (src1->nelem == 0 || src2->nelem == 0) return REG_NOERROR; /* We need dest->nelem + 2 * elems_in_intersection; this is a conservative estimate. */ if (src1->nelem + src2->nelem + dest->nelem > dest->alloc) { Idx new_alloc = src1->nelem + src2->nelem + dest->alloc; Idx *new_elems = re_realloc (dest->elems, Idx, new_alloc); if (BE (new_elems == NULL, 0)) return REG_ESPACE; dest->elems = new_elems; dest->alloc = new_alloc; } /* Find the items in the intersection of SRC1 and SRC2, and copy into the top of DEST those that are not already in DEST itself. */ sbase = dest->nelem + src1->nelem + src2->nelem; i1 = src1->nelem - 1; i2 = src2->nelem - 1; id = dest->nelem - 1; for (;;) { if (src1->elems[i1] == src2->elems[i2]) { /* Try to find the item in DEST. Maybe we could binary search? */ while (id >= 0 && dest->elems[id] > src1->elems[i1]) --id; if (id < 0 || dest->elems[id] != src1->elems[i1]) dest->elems[--sbase] = src1->elems[i1]; if (--i1 < 0 || --i2 < 0) break; } /* Lower the highest of the two items. */ else if (src1->elems[i1] < src2->elems[i2]) { if (--i2 < 0) break; } else { if (--i1 < 0) break; } } id = dest->nelem - 1; is = dest->nelem + src1->nelem + src2->nelem - 1; delta = is - sbase + 1; /* Now copy. When DELTA becomes zero, the remaining DEST elements are already in place; this is more or less the same loop that is in re_node_set_merge. */ dest->nelem += delta; if (delta > 0 && id >= 0) for (;;) { if (dest->elems[is] > dest->elems[id]) { /* Copy from the top. */ dest->elems[id + delta--] = dest->elems[is--]; if (delta == 0) break; } else { /* Slide from the bottom. */ dest->elems[id + delta] = dest->elems[id]; if (--id < 0) break; } } /* Copy remaining SRC elements. */ memcpy (dest->elems, dest->elems + sbase, delta * sizeof (Idx)); return REG_NOERROR; } /* Calculate the union set of the sets SRC1 and SRC2. And store it to DEST. Return value indicate the error code or REG_NOERROR if succeeded. */ static reg_errcode_t internal_function __attribute_warn_unused_result__ re_node_set_init_union (re_node_set *dest, const re_node_set *src1, const re_node_set *src2) { Idx i1, i2, id; if (src1 != NULL && src1->nelem > 0 && src2 != NULL && src2->nelem > 0) { dest->alloc = src1->nelem + src2->nelem; dest->elems = re_malloc (Idx, dest->alloc); if (BE (dest->elems == NULL, 0)) return REG_ESPACE; } else { if (src1 != NULL && src1->nelem > 0) return re_node_set_init_copy (dest, src1); else if (src2 != NULL && src2->nelem > 0) return re_node_set_init_copy (dest, src2); else re_node_set_init_empty (dest); return REG_NOERROR; } for (i1 = i2 = id = 0 ; i1 < src1->nelem && i2 < src2->nelem ;) { if (src1->elems[i1] > src2->elems[i2]) { dest->elems[id++] = src2->elems[i2++]; continue; } if (src1->elems[i1] == src2->elems[i2]) ++i2; dest->elems[id++] = src1->elems[i1++]; } if (i1 < src1->nelem) { memcpy (dest->elems + id, src1->elems + i1, (src1->nelem - i1) * sizeof (Idx)); id += src1->nelem - i1; } else if (i2 < src2->nelem) { memcpy (dest->elems + id, src2->elems + i2, (src2->nelem - i2) * sizeof (Idx)); id += src2->nelem - i2; } dest->nelem = id; return REG_NOERROR; } /* Calculate the union set of the sets DEST and SRC. And store it to DEST. Return value indicate the error code or REG_NOERROR if succeeded. */ static reg_errcode_t internal_function __attribute_warn_unused_result__ re_node_set_merge (re_node_set *dest, const re_node_set *src) { Idx is, id, sbase, delta; if (src == NULL || src->nelem == 0) return REG_NOERROR; if (dest->alloc < 2 * src->nelem + dest->nelem) { Idx new_alloc = 2 * (src->nelem + dest->alloc); Idx *new_buffer = re_realloc (dest->elems, Idx, new_alloc); if (BE (new_buffer == NULL, 0)) return REG_ESPACE; dest->elems = new_buffer; dest->alloc = new_alloc; } if (BE (dest->nelem == 0, 0)) { dest->nelem = src->nelem; memcpy (dest->elems, src->elems, src->nelem * sizeof (Idx)); return REG_NOERROR; } /* Copy into the top of DEST the items of SRC that are not found in DEST. Maybe we could binary search in DEST? */ for (sbase = dest->nelem + 2 * src->nelem, is = src->nelem - 1, id = dest->nelem - 1; is >= 0 && id >= 0; ) { if (dest->elems[id] == src->elems[is]) is--, id--; else if (dest->elems[id] < src->elems[is]) dest->elems[--sbase] = src->elems[is--]; else /* if (dest->elems[id] > src->elems[is]) */ --id; } if (is >= 0) { /* If DEST is exhausted, the remaining items of SRC must be unique. */ sbase -= is + 1; memcpy (dest->elems + sbase, src->elems, (is + 1) * sizeof (Idx)); } id = dest->nelem - 1; is = dest->nelem + 2 * src->nelem - 1; delta = is - sbase + 1; if (delta == 0) return REG_NOERROR; /* Now copy. When DELTA becomes zero, the remaining DEST elements are already in place. */ dest->nelem += delta; for (;;) { if (dest->elems[is] > dest->elems[id]) { /* Copy from the top. */ dest->elems[id + delta--] = dest->elems[is--]; if (delta == 0) break; } else { /* Slide from the bottom. */ dest->elems[id + delta] = dest->elems[id]; if (--id < 0) { /* Copy remaining SRC elements. */ memcpy (dest->elems, dest->elems + sbase, delta * sizeof (Idx)); break; } } } return REG_NOERROR; } /* Insert the new element ELEM to the re_node_set* SET. SET should not already have ELEM. Return true if successful. */ static bool internal_function __attribute_warn_unused_result__ re_node_set_insert (re_node_set *set, Idx elem) { Idx idx; /* In case the set is empty. */ if (set->alloc == 0) return BE (re_node_set_init_1 (set, elem) == REG_NOERROR, 1); if (BE (set->nelem, 0) == 0) { /* We already guaranteed above that set->alloc != 0. */ set->elems[0] = elem; ++set->nelem; return true; } /* Realloc if we need. */ if (set->alloc == set->nelem) { Idx *new_elems; set->alloc = set->alloc * 2; new_elems = re_realloc (set->elems, Idx, set->alloc); if (BE (new_elems == NULL, 0)) return false; set->elems = new_elems; } /* Move the elements which follows the new element. Test the first element separately to skip a check in the inner loop. */ if (elem < set->elems[0]) { idx = 0; for (idx = set->nelem; idx > 0; idx--) set->elems[idx] = set->elems[idx - 1]; } else { for (idx = set->nelem; set->elems[idx - 1] > elem; idx--) set->elems[idx] = set->elems[idx - 1]; } /* Insert the new element. */ set->elems[idx] = elem; ++set->nelem; return true; } /* Insert the new element ELEM to the re_node_set* SET. SET should not already have any element greater than or equal to ELEM. Return true if successful. */ static bool internal_function __attribute_warn_unused_result__ re_node_set_insert_last (re_node_set *set, Idx elem) { /* Realloc if we need. */ if (set->alloc == set->nelem) { Idx *new_elems; set->alloc = (set->alloc + 1) * 2; new_elems = re_realloc (set->elems, Idx, set->alloc); if (BE (new_elems == NULL, 0)) return false; set->elems = new_elems; } /* Insert the new element. */ set->elems[set->nelem++] = elem; return true; } /* Compare two node sets SET1 and SET2. Return true if SET1 and SET2 are equivalent. */ static bool internal_function __attribute__ ((pure)) re_node_set_compare (const re_node_set *set1, const re_node_set *set2) { Idx i; if (set1 == NULL || set2 == NULL || set1->nelem != set2->nelem) return false; for (i = set1->nelem ; --i >= 0 ; ) if (set1->elems[i] != set2->elems[i]) return false; return true; } /* Return (idx + 1) if SET contains the element ELEM, return 0 otherwise. */ static Idx internal_function __attribute__ ((pure)) re_node_set_contains (const re_node_set *set, Idx elem) { __re_size_t idx, right, mid; if (set->nelem <= 0) return 0; /* Binary search the element. */ idx = 0; right = set->nelem - 1; while (idx < right) { mid = (idx + right) / 2; if (set->elems[mid] < elem) idx = mid + 1; else right = mid; } return set->elems[idx] == elem ? idx + 1 : 0; } static void internal_function re_node_set_remove_at (re_node_set *set, Idx idx) { if (idx < 0 || idx >= set->nelem) return; --set->nelem; for (; idx < set->nelem; idx++) set->elems[idx] = set->elems[idx + 1]; } /* Add the token TOKEN to dfa->nodes, and return the index of the token. Or return -1 if an error occurred. */ static Idx internal_function re_dfa_add_node (re_dfa_t *dfa, re_token_t token) { if (BE (dfa->nodes_len >= dfa->nodes_alloc, 0)) { size_t new_nodes_alloc = dfa->nodes_alloc * 2; Idx *new_nexts, *new_indices; re_node_set *new_edests, *new_eclosures; re_token_t *new_nodes; /* Avoid overflows in realloc. */ const size_t max_object_size = MAX (sizeof (re_token_t), MAX (sizeof (re_node_set), sizeof (Idx))); if (BE (MIN (IDX_MAX, SIZE_MAX / max_object_size) < new_nodes_alloc, 0)) return -1; new_nodes = re_realloc (dfa->nodes, re_token_t, new_nodes_alloc); if (BE (new_nodes == NULL, 0)) return -1; dfa->nodes = new_nodes; new_nexts = re_realloc (dfa->nexts, Idx, new_nodes_alloc); new_indices = re_realloc (dfa->org_indices, Idx, new_nodes_alloc); new_edests = re_realloc (dfa->edests, re_node_set, new_nodes_alloc); new_eclosures = re_realloc (dfa->eclosures, re_node_set, new_nodes_alloc); if (BE (new_nexts == NULL || new_indices == NULL || new_edests == NULL || new_eclosures == NULL, 0)) { re_free (new_nexts); re_free (new_indices); re_free (new_edests); re_free (new_eclosures); return -1; } dfa->nexts = new_nexts; dfa->org_indices = new_indices; dfa->edests = new_edests; dfa->eclosures = new_eclosures; dfa->nodes_alloc = new_nodes_alloc; } dfa->nodes[dfa->nodes_len] = token; dfa->nodes[dfa->nodes_len].constraint = 0; #ifdef RE_ENABLE_I18N dfa->nodes[dfa->nodes_len].accept_mb = ((token.type == OP_PERIOD && dfa->mb_cur_max > 1) || token.type == COMPLEX_BRACKET); #endif dfa->nexts[dfa->nodes_len] = -1; re_node_set_init_empty (dfa->edests + dfa->nodes_len); re_node_set_init_empty (dfa->eclosures + dfa->nodes_len); return dfa->nodes_len++; } static re_hashval_t internal_function calc_state_hash (const re_node_set *nodes, unsigned int context) { re_hashval_t hash = nodes->nelem + context; Idx i; for (i = 0 ; i < nodes->nelem ; i++) hash += nodes->elems[i]; return hash; } /* Search for the state whose node_set is equivalent to NODES. Return the pointer to the state, if we found it in the DFA. Otherwise create the new one and return it. In case of an error return NULL and set the error code in ERR. Note: - We assume NULL as the invalid state, then it is possible that return value is NULL and ERR is REG_NOERROR. - We never return non-NULL value in case of any errors, it is for optimization. */ static re_dfastate_t * internal_function __attribute_warn_unused_result__ re_acquire_state (reg_errcode_t *err, const re_dfa_t *dfa, const re_node_set *nodes) { re_hashval_t hash; re_dfastate_t *new_state; struct re_state_table_entry *spot; Idx i; #if defined GCC_LINT || defined lint /* Suppress bogus uninitialized-variable warnings. */ *err = REG_NOERROR; #endif if (BE (nodes->nelem == 0, 0)) { *err = REG_NOERROR; return NULL; } hash = calc_state_hash (nodes, 0); spot = dfa->state_table + (hash & dfa->state_hash_mask); for (i = 0 ; i < spot->num ; i++) { re_dfastate_t *state = spot->array[i]; if (hash != state->hash) continue; if (re_node_set_compare (&state->nodes, nodes)) return state; } /* There are no appropriate state in the dfa, create the new one. */ new_state = create_ci_newstate (dfa, nodes, hash); if (BE (new_state == NULL, 0)) *err = REG_ESPACE; return new_state; } /* Search for the state whose node_set is equivalent to NODES and whose context is equivalent to CONTEXT. Return the pointer to the state, if we found it in the DFA. Otherwise create the new one and return it. In case of an error return NULL and set the error code in ERR. Note: - We assume NULL as the invalid state, then it is possible that return value is NULL and ERR is REG_NOERROR. - We never return non-NULL value in case of any errors, it is for optimization. */ static re_dfastate_t * internal_function __attribute_warn_unused_result__ re_acquire_state_context (reg_errcode_t *err, const re_dfa_t *dfa, const re_node_set *nodes, unsigned int context) { re_hashval_t hash; re_dfastate_t *new_state; struct re_state_table_entry *spot; Idx i; #if defined GCC_LINT || defined lint /* Suppress bogus uninitialized-variable warnings. */ *err = REG_NOERROR; #endif if (nodes->nelem == 0) { *err = REG_NOERROR; return NULL; } hash = calc_state_hash (nodes, context); spot = dfa->state_table + (hash & dfa->state_hash_mask); for (i = 0 ; i < spot->num ; i++) { re_dfastate_t *state = spot->array[i]; if (state->hash == hash && state->context == context && re_node_set_compare (state->entrance_nodes, nodes)) return state; } /* There are no appropriate state in 'dfa', create the new one. */ new_state = create_cd_newstate (dfa, nodes, context, hash); if (BE (new_state == NULL, 0)) *err = REG_ESPACE; return new_state; } /* Finish initialization of the new state NEWSTATE, and using its hash value HASH put in the appropriate bucket of DFA's state table. Return value indicates the error code if failed. */ static reg_errcode_t __attribute_warn_unused_result__ register_state (const re_dfa_t *dfa, re_dfastate_t *newstate, re_hashval_t hash) { struct re_state_table_entry *spot; reg_errcode_t err; Idx i; newstate->hash = hash; err = re_node_set_alloc (&newstate->non_eps_nodes, newstate->nodes.nelem); if (BE (err != REG_NOERROR, 0)) return REG_ESPACE; for (i = 0; i < newstate->nodes.nelem; i++) { Idx elem = newstate->nodes.elems[i]; if (!IS_EPSILON_NODE (dfa->nodes[elem].type)) if (! re_node_set_insert_last (&newstate->non_eps_nodes, elem)) return REG_ESPACE; } spot = dfa->state_table + (hash & dfa->state_hash_mask); if (BE (spot->alloc <= spot->num, 0)) { Idx new_alloc = 2 * spot->num + 2; re_dfastate_t **new_array = re_realloc (spot->array, re_dfastate_t *, new_alloc); if (BE (new_array == NULL, 0)) return REG_ESPACE; spot->array = new_array; spot->alloc = new_alloc; } spot->array[spot->num++] = newstate; return REG_NOERROR; } static void free_state (re_dfastate_t *state) { re_node_set_free (&state->non_eps_nodes); re_node_set_free (&state->inveclosure); if (state->entrance_nodes != &state->nodes) { re_node_set_free (state->entrance_nodes); re_free (state->entrance_nodes); } re_node_set_free (&state->nodes); re_free (state->word_trtable); re_free (state->trtable); re_free (state); } /* Create the new state which is independent of contexts. Return the new state if succeeded, otherwise return NULL. */ static re_dfastate_t * internal_function __attribute_warn_unused_result__ create_ci_newstate (const re_dfa_t *dfa, const re_node_set *nodes, re_hashval_t hash) { Idx i; reg_errcode_t err; re_dfastate_t *newstate; newstate = (re_dfastate_t *) calloc (sizeof (re_dfastate_t), 1); if (BE (newstate == NULL, 0)) return NULL; err = re_node_set_init_copy (&newstate->nodes, nodes); if (BE (err != REG_NOERROR, 0)) { re_free (newstate); return NULL; } newstate->entrance_nodes = &newstate->nodes; for (i = 0 ; i < nodes->nelem ; i++) { re_token_t *node = dfa->nodes + nodes->elems[i]; re_token_type_t type = node->type; if (type == CHARACTER && !node->constraint) continue; #ifdef RE_ENABLE_I18N newstate->accept_mb |= node->accept_mb; #endif /* RE_ENABLE_I18N */ /* If the state has the halt node, the state is a halt state. */ if (type == END_OF_RE) newstate->halt = 1; else if (type == OP_BACK_REF) newstate->has_backref = 1; else if (type == ANCHOR || node->constraint) newstate->has_constraint = 1; } err = register_state (dfa, newstate, hash); if (BE (err != REG_NOERROR, 0)) { free_state (newstate); newstate = NULL; } return newstate; } /* Create the new state which is depend on the context CONTEXT. Return the new state if succeeded, otherwise return NULL. */ static re_dfastate_t * internal_function __attribute_warn_unused_result__ create_cd_newstate (const re_dfa_t *dfa, const re_node_set *nodes, unsigned int context, re_hashval_t hash) { Idx i, nctx_nodes = 0; reg_errcode_t err; re_dfastate_t *newstate; newstate = (re_dfastate_t *) calloc (sizeof (re_dfastate_t), 1); if (BE (newstate == NULL, 0)) return NULL; err = re_node_set_init_copy (&newstate->nodes, nodes); if (BE (err != REG_NOERROR, 0)) { re_free (newstate); return NULL; } newstate->context = context; newstate->entrance_nodes = &newstate->nodes; for (i = 0 ; i < nodes->nelem ; i++) { re_token_t *node = dfa->nodes + nodes->elems[i]; re_token_type_t type = node->type; unsigned int constraint = node->constraint; if (type == CHARACTER && !constraint) continue; #ifdef RE_ENABLE_I18N newstate->accept_mb |= node->accept_mb; #endif /* RE_ENABLE_I18N */ /* If the state has the halt node, the state is a halt state. */ if (type == END_OF_RE) newstate->halt = 1; else if (type == OP_BACK_REF) newstate->has_backref = 1; if (constraint) { if (newstate->entrance_nodes == &newstate->nodes) { newstate->entrance_nodes = re_malloc (re_node_set, 1); if (BE (newstate->entrance_nodes == NULL, 0)) { free_state (newstate); return NULL; } if (re_node_set_init_copy (newstate->entrance_nodes, nodes) != REG_NOERROR) return NULL; nctx_nodes = 0; newstate->has_constraint = 1; } if (NOT_SATISFY_PREV_CONSTRAINT (constraint,context)) { re_node_set_remove_at (&newstate->nodes, i - nctx_nodes); ++nctx_nodes; } } } err = register_state (dfa, newstate, hash); if (BE (err != REG_NOERROR, 0)) { free_state (newstate); newstate = NULL; } return newstate; } ne-3.3.4/src/regex_internal.h000066400000000000000000000620351475116431000161010ustar00rootroot00000000000000/* Extended regular expression matching and search library. Copyright (C) 2002-2025 Free Software Foundation, Inc. This file is part of the GNU C Library. Contributed by Isamu Hasegawa . The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. The GNU C Library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with the GNU C Library; if not, see . */ #ifndef _REGEX_INTERNAL_H #define _REGEX_INTERNAL_H 1 #include #include #include #include #include #include #include #include #include #include #include #ifdef _LIBC # include # define lock_define(name) __libc_lock_define (, name) # define lock_init(lock) (__libc_lock_init (lock), 0) # define lock_fini(lock) 0 # define lock_lock(lock) __libc_lock_lock (lock) # define lock_unlock(lock) __libc_lock_unlock (lock) #elif defined GNULIB_LOCK && !defined USE_UNLOCKED_IO # include "glthread/lock.h" /* Use gl_lock_define if empty macro arguments are known to work. Otherwise, fall back on less-portable substitutes. */ # if ((defined __GNUC__ && !defined __STRICT_ANSI__) \ || (defined __STDC_VERSION__ && 199901L <= __STDC_VERSION__)) # define lock_define(name) gl_lock_define (, name) # elif USE_POSIX_THREADS # define lock_define(name) pthread_mutex_t name; # elif USE_PTH_THREADS # define lock_define(name) pth_mutex_t name; # elif USE_SOLARIS_THREADS # define lock_define(name) mutex_t name; # elif USE_WINDOWS_THREADS # define lock_define(name) gl_lock_t name; # else # define lock_define(name) # endif # define lock_init(lock) glthread_lock_init (&(lock)) # define lock_fini(lock) glthread_lock_destroy (&(lock)) # define lock_lock(lock) glthread_lock_lock (&(lock)) # define lock_unlock(lock) glthread_lock_unlock (&(lock)) #elif defined GNULIB_PTHREAD && !defined USE_UNLOCKED_IO # include # define lock_define(name) pthread_mutex_t name; # define lock_init(lock) pthread_mutex_init (&(lock), 0) # define lock_fini(lock) pthread_mutex_destroy (&(lock)) # define lock_lock(lock) pthread_mutex_lock (&(lock)) # define lock_unlock(lock) pthread_mutex_unlock (&(lock)) #else # define lock_define(name) # define lock_init(lock) 0 # define lock_fini(lock) ((void) 0) /* The 'dfa' avoids an "unused variable 'dfa'" warning from GCC. */ # define lock_lock(lock) ((void) dfa) # define lock_unlock(lock) ((void) 0) #endif /* In case that the system doesn't have isblank(). */ #if !defined _LIBC && ! (defined isblank || (HAVE_ISBLANK && HAVE_DECL_ISBLANK)) # define isblank(ch) ((ch) == ' ' || (ch) == '\t') #endif #ifdef _LIBC # ifndef _RE_DEFINE_LOCALE_FUNCTIONS # define _RE_DEFINE_LOCALE_FUNCTIONS 1 # include # include # endif #endif /* This is for other GNU distributions with internationalized messages. */ #if (HAVE_LIBINTL_H && ENABLE_NLS) || defined _LIBC # include # ifdef _LIBC # undef gettext # define gettext(msgid) \ __dcgettext (_libc_intl_domainname, msgid, LC_MESSAGES) # endif #else # define gettext(msgid) (msgid) #endif #ifndef gettext_noop /* This define is so xgettext can find the internationalizable strings. */ # define gettext_noop(String) String #endif #if (defined MB_CUR_MAX && HAVE_WCTYPE_H && HAVE_ISWCTYPE) || _LIBC # define RE_ENABLE_I18N #endif #if __GNUC__ >= 3 # define BE(expr, val) __builtin_expect (expr, val) #else # define BE(expr, val) (expr) #endif /* Number of ASCII characters. */ #define ASCII_CHARS 0x80 /* Number of single byte characters. */ #define SBC_MAX (UCHAR_MAX + 1) #define COLL_ELEM_LEN_MAX 8 /* The character which represents newline. */ #define NEWLINE_CHAR '\n' #define WIDE_NEWLINE_CHAR L'\n' /* Rename to standard API for using out of glibc. */ #ifndef _LIBC # undef __wctype # undef __iswctype # define __wctype wctype # define __iswalnum iswalnum # define __iswctype iswctype # define __towlower towlower # define __towupper towupper # define __btowc btowc # define __mbrtowc mbrtowc # define __wcrtomb wcrtomb # define __regfree regfree # define attribute_hidden #endif /* not _LIBC */ #if __GNUC__ < 3 + (__GNUC_MINOR__ < 1) # define __attribute__(arg) #endif #ifndef SSIZE_MAX # define SSIZE_MAX ((ssize_t) (SIZE_MAX / 2)) #endif /* The type of indexes into strings. This is signed, not size_t, since the API requires indexes to fit in regoff_t anyway, and using signed integers makes the code a bit smaller and presumably faster. The traditional GNU regex implementation uses int for indexes. The POSIX-compatible implementation uses a possibly-wider type. The name 'Idx' is three letters to minimize the hassle of reindenting a lot of regex code that formerly used 'int'. */ typedef regoff_t Idx; #ifdef _REGEX_LARGE_OFFSETS # define IDX_MAX SSIZE_MAX #else # define IDX_MAX INT_MAX #endif /* A hash value, suitable for computing hash tables. */ typedef __re_size_t re_hashval_t; /* An integer used to represent a set of bits. It must be unsigned, and must be at least as wide as unsigned int. */ typedef unsigned long int bitset_word_t; /* All bits set in a bitset_word_t. */ #define BITSET_WORD_MAX ULONG_MAX /* Number of bits in a bitset_word_t. For portability to hosts with padding bits, do not use '(sizeof (bitset_word_t) * CHAR_BIT)'; instead, deduce it directly from BITSET_WORD_MAX. Avoid greater-than-32-bit integers and unconditional shifts by more than 31 bits, as they're not portable. */ #if BITSET_WORD_MAX == 0xffffffffUL # define BITSET_WORD_BITS 32 #elif BITSET_WORD_MAX >> 31 >> 4 == 1 # define BITSET_WORD_BITS 36 #elif BITSET_WORD_MAX >> 31 >> 16 == 1 # define BITSET_WORD_BITS 48 #elif BITSET_WORD_MAX >> 31 >> 28 == 1 # define BITSET_WORD_BITS 60 #elif BITSET_WORD_MAX >> 31 >> 31 >> 1 == 1 # define BITSET_WORD_BITS 64 #elif BITSET_WORD_MAX >> 31 >> 31 >> 9 == 1 # define BITSET_WORD_BITS 72 #elif BITSET_WORD_MAX >> 31 >> 31 >> 31 >> 31 >> 3 == 1 # define BITSET_WORD_BITS 128 #elif BITSET_WORD_MAX >> 31 >> 31 >> 31 >> 31 >> 31 >> 31 >> 31 >> 31 >> 7 == 1 # define BITSET_WORD_BITS 256 #elif BITSET_WORD_MAX >> 31 >> 31 >> 31 >> 31 >> 31 >> 31 >> 31 >> 31 >> 7 > 1 # define BITSET_WORD_BITS 257 /* any value > SBC_MAX will do here */ # if BITSET_WORD_BITS <= SBC_MAX # error "Invalid SBC_MAX" # endif #else # error "Add case for new bitset_word_t size" #endif /* Number of bitset_word_t values in a bitset_t. */ #define BITSET_WORDS ((SBC_MAX + BITSET_WORD_BITS - 1) / BITSET_WORD_BITS) typedef bitset_word_t bitset_t[BITSET_WORDS]; typedef bitset_word_t *re_bitset_ptr_t; typedef const bitset_word_t *re_const_bitset_ptr_t; #define PREV_WORD_CONSTRAINT 0x0001 #define PREV_NOTWORD_CONSTRAINT 0x0002 #define NEXT_WORD_CONSTRAINT 0x0004 #define NEXT_NOTWORD_CONSTRAINT 0x0008 #define PREV_NEWLINE_CONSTRAINT 0x0010 #define NEXT_NEWLINE_CONSTRAINT 0x0020 #define PREV_BEGBUF_CONSTRAINT 0x0040 #define NEXT_ENDBUF_CONSTRAINT 0x0080 #define WORD_DELIM_CONSTRAINT 0x0100 #define NOT_WORD_DELIM_CONSTRAINT 0x0200 typedef enum { INSIDE_WORD = PREV_WORD_CONSTRAINT | NEXT_WORD_CONSTRAINT, WORD_FIRST = PREV_NOTWORD_CONSTRAINT | NEXT_WORD_CONSTRAINT, WORD_LAST = PREV_WORD_CONSTRAINT | NEXT_NOTWORD_CONSTRAINT, INSIDE_NOTWORD = PREV_NOTWORD_CONSTRAINT | NEXT_NOTWORD_CONSTRAINT, LINE_FIRST = PREV_NEWLINE_CONSTRAINT, LINE_LAST = NEXT_NEWLINE_CONSTRAINT, BUF_FIRST = PREV_BEGBUF_CONSTRAINT, BUF_LAST = NEXT_ENDBUF_CONSTRAINT, WORD_DELIM = WORD_DELIM_CONSTRAINT, NOT_WORD_DELIM = NOT_WORD_DELIM_CONSTRAINT } re_context_type; typedef struct { Idx alloc; Idx nelem; Idx *elems; } re_node_set; typedef enum { NON_TYPE = 0, /* Node type, These are used by token, node, tree. */ CHARACTER = 1, END_OF_RE = 2, SIMPLE_BRACKET = 3, OP_BACK_REF = 4, OP_PERIOD = 5, #ifdef RE_ENABLE_I18N COMPLEX_BRACKET = 6, OP_UTF8_PERIOD = 7, #endif /* RE_ENABLE_I18N */ /* We define EPSILON_BIT as a macro so that OP_OPEN_SUBEXP is used when the debugger shows values of this enum type. */ #define EPSILON_BIT 8 OP_OPEN_SUBEXP = EPSILON_BIT | 0, OP_CLOSE_SUBEXP = EPSILON_BIT | 1, OP_ALT = EPSILON_BIT | 2, OP_DUP_ASTERISK = EPSILON_BIT | 3, ANCHOR = EPSILON_BIT | 4, /* Tree type, these are used only by tree. */ CONCAT = 16, SUBEXP = 17, /* Token type, these are used only by token. */ OP_DUP_PLUS = 18, OP_DUP_QUESTION, OP_OPEN_BRACKET, OP_CLOSE_BRACKET, OP_CHARSET_RANGE, OP_OPEN_DUP_NUM, OP_CLOSE_DUP_NUM, OP_NON_MATCH_LIST, OP_OPEN_COLL_ELEM, OP_CLOSE_COLL_ELEM, OP_OPEN_EQUIV_CLASS, OP_CLOSE_EQUIV_CLASS, OP_OPEN_CHAR_CLASS, OP_CLOSE_CHAR_CLASS, OP_WORD, OP_NOTWORD, OP_SPACE, OP_NOTSPACE, BACK_SLASH } re_token_type_t; #ifdef RE_ENABLE_I18N typedef struct { /* Multibyte characters. */ wchar_t *mbchars; /* Collating symbols. */ # ifdef _LIBC int32_t *coll_syms; # endif /* Equivalence classes. */ # ifdef _LIBC int32_t *equiv_classes; # endif /* Range expressions. */ # ifdef _LIBC uint32_t *range_starts; uint32_t *range_ends; # else /* not _LIBC */ wchar_t *range_starts; wchar_t *range_ends; # endif /* not _LIBC */ /* Character classes. */ wctype_t *char_classes; /* If this character set is the non-matching list. */ unsigned int non_match : 1; /* # of multibyte characters. */ Idx nmbchars; /* # of collating symbols. */ Idx ncoll_syms; /* # of equivalence classes. */ Idx nequiv_classes; /* # of range expressions. */ Idx nranges; /* # of character classes. */ Idx nchar_classes; } re_charset_t; #endif /* RE_ENABLE_I18N */ typedef struct { union { unsigned char c; /* for CHARACTER */ re_bitset_ptr_t sbcset; /* for SIMPLE_BRACKET */ #ifdef RE_ENABLE_I18N re_charset_t *mbcset; /* for COMPLEX_BRACKET */ #endif /* RE_ENABLE_I18N */ Idx idx; /* for BACK_REF */ re_context_type ctx_type; /* for ANCHOR */ } opr; #if __GNUC__ >= 2 && !defined __STRICT_ANSI__ re_token_type_t type : 8; #else re_token_type_t type; #endif unsigned int constraint : 10; /* context constraint */ unsigned int duplicated : 1; unsigned int opt_subexp : 1; #ifdef RE_ENABLE_I18N unsigned int accept_mb : 1; /* These 2 bits can be moved into the union if needed (e.g. if running out of bits; move opr.c to opr.c.c and move the flags to opr.c.flags). */ unsigned int mb_partial : 1; #endif unsigned int word_char : 1; } re_token_t; #define IS_EPSILON_NODE(type) ((type) & EPSILON_BIT) struct re_string_t { /* Indicate the raw buffer which is the original string passed as an argument of regexec(), re_search(), etc.. */ const unsigned char *raw_mbs; /* Store the multibyte string. In case of "case insensitive mode" like REG_ICASE, upper cases of the string are stored, otherwise MBS points the same address that RAW_MBS points. */ unsigned char *mbs; #ifdef RE_ENABLE_I18N /* Store the wide character string which is corresponding to MBS. */ wint_t *wcs; Idx *offsets; mbstate_t cur_state; #endif /* Index in RAW_MBS. Each character mbs[i] corresponds to raw_mbs[raw_mbs_idx + i]. */ Idx raw_mbs_idx; /* The length of the valid characters in the buffers. */ Idx valid_len; /* The corresponding number of bytes in raw_mbs array. */ Idx valid_raw_len; /* The length of the buffers MBS and WCS. */ Idx bufs_len; /* The index in MBS, which is updated by re_string_fetch_byte. */ Idx cur_idx; /* length of RAW_MBS array. */ Idx raw_len; /* This is RAW_LEN - RAW_MBS_IDX + VALID_LEN - VALID_RAW_LEN. */ Idx len; /* End of the buffer may be shorter than its length in the cases such as re_match_2, re_search_2. Then, we use STOP for end of the buffer instead of LEN. */ Idx raw_stop; /* This is RAW_STOP - RAW_MBS_IDX adjusted through OFFSETS. */ Idx stop; /* The context of mbs[0]. We store the context independently, since the context of mbs[0] may be different from raw_mbs[0], which is the beginning of the input string. */ unsigned int tip_context; /* The translation passed as a part of an argument of re_compile_pattern. */ RE_TRANSLATE_TYPE trans; /* Copy of re_dfa_t's word_char. */ re_const_bitset_ptr_t word_char; /* true if REG_ICASE. */ unsigned char icase; unsigned char is_utf8; unsigned char map_notascii; unsigned char mbs_allocated; unsigned char offsets_needed; unsigned char newline_anchor; unsigned char word_ops_used; int mb_cur_max; }; typedef struct re_string_t re_string_t; struct re_dfa_t; typedef struct re_dfa_t re_dfa_t; #ifndef _LIBC # define internal_function # define IS_IN(libc) false #endif static reg_errcode_t re_string_realloc_buffers (re_string_t *pstr, Idx new_buf_len) internal_function; #ifdef RE_ENABLE_I18N static void build_wcs_buffer (re_string_t *pstr) internal_function; static reg_errcode_t build_wcs_upper_buffer (re_string_t *pstr) internal_function; #endif /* RE_ENABLE_I18N */ static void build_upper_buffer (re_string_t *pstr) internal_function; static void re_string_translate_buffer (re_string_t *pstr) internal_function; static unsigned int re_string_context_at (const re_string_t *input, Idx idx, int eflags) internal_function __attribute__ ((pure)); #define re_string_peek_byte(pstr, offset) \ ((pstr)->mbs[(pstr)->cur_idx + offset]) #define re_string_fetch_byte(pstr) \ ((pstr)->mbs[(pstr)->cur_idx++]) #define re_string_first_byte(pstr, idx) \ ((idx) == (pstr)->valid_len || (pstr)->wcs[idx] != WEOF) #define re_string_is_single_byte_char(pstr, idx) \ ((pstr)->wcs[idx] != WEOF && ((pstr)->valid_len == (idx) + 1 \ || (pstr)->wcs[(idx) + 1] != WEOF)) #define re_string_eoi(pstr) ((pstr)->stop <= (pstr)->cur_idx) #define re_string_cur_idx(pstr) ((pstr)->cur_idx) #define re_string_get_buffer(pstr) ((pstr)->mbs) #define re_string_length(pstr) ((pstr)->len) #define re_string_byte_at(pstr,idx) ((pstr)->mbs[idx]) #define re_string_skip_bytes(pstr,idx) ((pstr)->cur_idx += (idx)) #define re_string_set_index(pstr,idx) ((pstr)->cur_idx = (idx)) #if defined _LIBC || HAVE_ALLOCA # include #endif #ifndef _LIBC # if HAVE_ALLOCA /* The OS usually guarantees only one guard page at the bottom of the stack, and a page size can be as small as 4096 bytes. So we cannot safely allocate anything larger than 4096 bytes. Also care for the possibility of a few compiler-allocated temporary stack slots. */ # define __libc_use_alloca(n) ((n) < 4032) # else /* alloca is implemented with malloc, so just use malloc. */ # define __libc_use_alloca(n) 0 # undef alloca # define alloca(n) malloc (n) # endif #endif #ifdef _LIBC # define MALLOC_0_IS_NONNULL 1 #elif !defined MALLOC_0_IS_NONNULL # define MALLOC_0_IS_NONNULL 0 #endif #ifndef MAX # define MAX(a,b) ((a) < (b) ? (b) : (a)) #endif #ifndef MIN # define MIN(a,b) ((a) < (b) ? (a) : (b)) #endif #define re_malloc(t,n) ((t *) malloc ((n) * sizeof (t))) #define re_realloc(p,t,n) ((t *) realloc (p, (n) * sizeof (t))) #define re_free(p) free (p) struct bin_tree_t { struct bin_tree_t *parent; struct bin_tree_t *left; struct bin_tree_t *right; struct bin_tree_t *first; struct bin_tree_t *next; re_token_t token; /* 'node_idx' is the index in dfa->nodes, if 'type' == 0. Otherwise 'type' indicate the type of this node. */ Idx node_idx; }; typedef struct bin_tree_t bin_tree_t; #define BIN_TREE_STORAGE_SIZE \ ((1024 - sizeof (void *)) / sizeof (bin_tree_t)) struct bin_tree_storage_t { struct bin_tree_storage_t *next; bin_tree_t data[BIN_TREE_STORAGE_SIZE]; }; typedef struct bin_tree_storage_t bin_tree_storage_t; #define CONTEXT_WORD 1 #define CONTEXT_NEWLINE (CONTEXT_WORD << 1) #define CONTEXT_BEGBUF (CONTEXT_NEWLINE << 1) #define CONTEXT_ENDBUF (CONTEXT_BEGBUF << 1) #define IS_WORD_CONTEXT(c) ((c) & CONTEXT_WORD) #define IS_NEWLINE_CONTEXT(c) ((c) & CONTEXT_NEWLINE) #define IS_BEGBUF_CONTEXT(c) ((c) & CONTEXT_BEGBUF) #define IS_ENDBUF_CONTEXT(c) ((c) & CONTEXT_ENDBUF) #define IS_ORDINARY_CONTEXT(c) ((c) == 0) #define IS_WORD_CHAR(ch) (isalnum (ch) || (ch) == '_') #define IS_NEWLINE(ch) ((ch) == NEWLINE_CHAR) #define IS_WIDE_WORD_CHAR(ch) (__iswalnum (ch) || (ch) == L'_') #define IS_WIDE_NEWLINE(ch) ((ch) == WIDE_NEWLINE_CHAR) #define NOT_SATISFY_PREV_CONSTRAINT(constraint,context) \ ((((constraint) & PREV_WORD_CONSTRAINT) && !IS_WORD_CONTEXT (context)) \ || ((constraint & PREV_NOTWORD_CONSTRAINT) && IS_WORD_CONTEXT (context)) \ || ((constraint & PREV_NEWLINE_CONSTRAINT) && !IS_NEWLINE_CONTEXT (context))\ || ((constraint & PREV_BEGBUF_CONSTRAINT) && !IS_BEGBUF_CONTEXT (context))) #define NOT_SATISFY_NEXT_CONSTRAINT(constraint,context) \ ((((constraint) & NEXT_WORD_CONSTRAINT) && !IS_WORD_CONTEXT (context)) \ || (((constraint) & NEXT_NOTWORD_CONSTRAINT) && IS_WORD_CONTEXT (context)) \ || (((constraint) & NEXT_NEWLINE_CONSTRAINT) && !IS_NEWLINE_CONTEXT (context)) \ || (((constraint) & NEXT_ENDBUF_CONSTRAINT) && !IS_ENDBUF_CONTEXT (context))) struct re_dfastate_t { re_hashval_t hash; re_node_set nodes; re_node_set non_eps_nodes; re_node_set inveclosure; re_node_set *entrance_nodes; struct re_dfastate_t **trtable, **word_trtable; unsigned int context : 4; unsigned int halt : 1; /* If this state can accept "multi byte". Note that we refer to multibyte characters, and multi character collating elements as "multi byte". */ unsigned int accept_mb : 1; /* If this state has backreference node(s). */ unsigned int has_backref : 1; unsigned int has_constraint : 1; }; typedef struct re_dfastate_t re_dfastate_t; struct re_state_table_entry { Idx num; Idx alloc; re_dfastate_t **array; }; /* Array type used in re_sub_match_last_t and re_sub_match_top_t. */ typedef struct { Idx next_idx; Idx alloc; re_dfastate_t **array; } state_array_t; /* Store information about the node NODE whose type is OP_CLOSE_SUBEXP. */ typedef struct { Idx node; Idx str_idx; /* The position NODE match at. */ state_array_t path; } re_sub_match_last_t; /* Store information about the node NODE whose type is OP_OPEN_SUBEXP. And information about the node, whose type is OP_CLOSE_SUBEXP, corresponding to NODE is stored in LASTS. */ typedef struct { Idx str_idx; Idx node; state_array_t *path; Idx alasts; /* Allocation size of LASTS. */ Idx nlasts; /* The number of LASTS. */ re_sub_match_last_t **lasts; } re_sub_match_top_t; struct re_backref_cache_entry { Idx node; Idx str_idx; Idx subexp_from; Idx subexp_to; char more; char unused; unsigned short int eps_reachable_subexps_map; }; typedef struct { /* The string object corresponding to the input string. */ re_string_t input; #if defined _LIBC || (defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901L) const re_dfa_t *const dfa; #else const re_dfa_t *dfa; #endif /* EFLAGS of the argument of regexec. */ int eflags; /* Where the matching ends. */ Idx match_last; Idx last_node; /* The state log used by the matcher. */ re_dfastate_t **state_log; Idx state_log_top; /* Back reference cache. */ Idx nbkref_ents; Idx abkref_ents; struct re_backref_cache_entry *bkref_ents; int max_mb_elem_len; Idx nsub_tops; Idx asub_tops; re_sub_match_top_t **sub_tops; } re_match_context_t; typedef struct { re_dfastate_t **sifted_states; re_dfastate_t **limited_states; Idx last_node; Idx last_str_idx; re_node_set limits; } re_sift_context_t; struct re_fail_stack_ent_t { Idx idx; Idx node; regmatch_t *regs; re_node_set eps_via_nodes; }; struct re_fail_stack_t { Idx num; Idx alloc; struct re_fail_stack_ent_t *stack; }; struct re_dfa_t { re_token_t *nodes; size_t nodes_alloc; size_t nodes_len; Idx *nexts; Idx *org_indices; re_node_set *edests; re_node_set *eclosures; re_node_set *inveclosures; struct re_state_table_entry *state_table; re_dfastate_t *init_state; re_dfastate_t *init_state_word; re_dfastate_t *init_state_nl; re_dfastate_t *init_state_begbuf; bin_tree_t *str_tree; bin_tree_storage_t *str_tree_storage; re_bitset_ptr_t sb_char; int str_tree_storage_idx; /* number of subexpressions 're_nsub' is in regex_t. */ re_hashval_t state_hash_mask; Idx init_node; Idx nbackref; /* The number of backreference in this dfa. */ /* Bitmap expressing which backreference is used. */ bitset_word_t used_bkref_map; bitset_word_t completed_bkref_map; unsigned int has_plural_match : 1; /* If this dfa has "multibyte node", which is a backreference or a node which can accept multibyte character or multi character collating element. */ unsigned int has_mb_node : 1; unsigned int is_utf8 : 1; unsigned int map_notascii : 1; unsigned int word_ops_used : 1; int mb_cur_max; bitset_t word_char; reg_syntax_t syntax; Idx *subexp_map; #ifdef DEBUG char* re_str; #endif lock_define (lock) }; #define re_node_set_init_empty(set) memset (set, '\0', sizeof (re_node_set)) #define re_node_set_remove(set,id) \ (re_node_set_remove_at (set, re_node_set_contains (set, id) - 1)) #define re_node_set_empty(p) ((p)->nelem = 0) #define re_node_set_free(set) re_free ((set)->elems) typedef enum { SB_CHAR, MB_CHAR, EQUIV_CLASS, COLL_SYM, CHAR_CLASS } bracket_elem_type; typedef struct { bracket_elem_type type; union { unsigned char ch; unsigned char *name; wchar_t wch; } opr; } bracket_elem_t; /* Functions for bitset_t operation. */ static void bitset_set (bitset_t set, Idx i) { set[i / BITSET_WORD_BITS] |= (bitset_word_t) 1 << i % BITSET_WORD_BITS; } static void bitset_clear (bitset_t set, Idx i) { set[i / BITSET_WORD_BITS] &= ~ ((bitset_word_t) 1 << i % BITSET_WORD_BITS); } static bool bitset_contain (const bitset_t set, Idx i) { return (set[i / BITSET_WORD_BITS] >> i % BITSET_WORD_BITS) & 1; } static void bitset_empty (bitset_t set) { memset (set, '\0', sizeof (bitset_t)); } static void bitset_set_all (bitset_t set) { memset (set, -1, sizeof (bitset_word_t) * (SBC_MAX / BITSET_WORD_BITS)); if (SBC_MAX % BITSET_WORD_BITS != 0) set[BITSET_WORDS - 1] = ((bitset_word_t) 1 << SBC_MAX % BITSET_WORD_BITS) - 1; } static void bitset_copy (bitset_t dest, const bitset_t src) { memcpy (dest, src, sizeof (bitset_t)); } static void __attribute__ ((unused)) bitset_not (bitset_t set) { int bitset_i; for (bitset_i = 0; bitset_i < SBC_MAX / BITSET_WORD_BITS; ++bitset_i) set[bitset_i] = ~set[bitset_i]; if (SBC_MAX % BITSET_WORD_BITS != 0) set[BITSET_WORDS - 1] = ((((bitset_word_t) 1 << SBC_MAX % BITSET_WORD_BITS) - 1) & ~set[BITSET_WORDS - 1]); } static void __attribute__ ((unused)) bitset_merge (bitset_t dest, const bitset_t src) { int bitset_i; for (bitset_i = 0; bitset_i < BITSET_WORDS; ++bitset_i) dest[bitset_i] |= src[bitset_i]; } static void __attribute__ ((unused)) bitset_mask (bitset_t dest, const bitset_t src) { int bitset_i; for (bitset_i = 0; bitset_i < BITSET_WORDS; ++bitset_i) dest[bitset_i] &= src[bitset_i]; } #ifdef RE_ENABLE_I18N /* Functions for re_string. */ static int internal_function __attribute__ ((pure, unused)) re_string_char_size_at (const re_string_t *pstr, Idx idx) { int byte_idx; if (pstr->mb_cur_max == 1) return 1; for (byte_idx = 1; idx + byte_idx < pstr->valid_len; ++byte_idx) if (pstr->wcs[idx + byte_idx] != WEOF) break; return byte_idx; } static wint_t internal_function __attribute__ ((pure, unused)) re_string_wchar_at (const re_string_t *pstr, Idx idx) { if (pstr->mb_cur_max == 1) return (wint_t) pstr->mbs[idx]; return (wint_t) pstr->wcs[idx]; } # ifdef _LIBC # include # endif static int internal_function __attribute__ ((pure, unused)) re_string_elem_size_at (const re_string_t *pstr, Idx idx) { # ifdef _LIBC const unsigned char *p, *extra; const int32_t *table, *indirect; uint_fast32_t nrules = _NL_CURRENT_WORD (LC_COLLATE, _NL_COLLATE_NRULES); if (nrules != 0) { table = (const int32_t *) _NL_CURRENT (LC_COLLATE, _NL_COLLATE_TABLEMB); extra = (const unsigned char *) _NL_CURRENT (LC_COLLATE, _NL_COLLATE_EXTRAMB); indirect = (const int32_t *) _NL_CURRENT (LC_COLLATE, _NL_COLLATE_INDIRECTMB); p = pstr->mbs + idx; findidx (table, indirect, extra, &p, pstr->len - idx); return p - pstr->mbs - idx; } else # endif /* _LIBC */ return 1; } #endif /* RE_ENABLE_I18N */ #ifndef __GNUC_PREREQ # if defined __GNUC__ && defined __GNUC_MINOR__ # define __GNUC_PREREQ(maj, min) \ ((__GNUC__ << 16) + __GNUC_MINOR__ >= ((maj) << 16) + (min)) # else # define __GNUC_PREREQ(maj, min) 0 # endif #endif #if __GNUC_PREREQ (3,4) # undef __attribute_warn_unused_result__ # define __attribute_warn_unused_result__ \ __attribute__ ((__warn_unused_result__)) #else # define __attribute_warn_unused_result__ /* empty */ #endif #endif /* _REGEX_INTERNAL_H */ ne-3.3.4/src/regexec.c000066400000000000000000003764511475116431000145220ustar00rootroot00000000000000/* Extended regular expression matching and search library. Copyright (C) 2002-2025 Free Software Foundation, Inc. This file is part of the GNU C Library. Contributed by Isamu Hasegawa . The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. The GNU C Library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with the GNU C Library; if not, see . */ static reg_errcode_t match_ctx_init (re_match_context_t *cache, int eflags, Idx n) internal_function; static void match_ctx_clean (re_match_context_t *mctx) internal_function; static void match_ctx_free (re_match_context_t *cache) internal_function; static reg_errcode_t match_ctx_add_entry (re_match_context_t *cache, Idx node, Idx str_idx, Idx from, Idx to) internal_function; static Idx search_cur_bkref_entry (const re_match_context_t *mctx, Idx str_idx) internal_function; static reg_errcode_t match_ctx_add_subtop (re_match_context_t *mctx, Idx node, Idx str_idx) internal_function; static re_sub_match_last_t * match_ctx_add_sublast (re_sub_match_top_t *subtop, Idx node, Idx str_idx) internal_function; static void sift_ctx_init (re_sift_context_t *sctx, re_dfastate_t **sifted_sts, re_dfastate_t **limited_sts, Idx last_node, Idx last_str_idx) internal_function; static reg_errcode_t re_search_internal (const regex_t *preg, const char *string, Idx length, Idx start, Idx last_start, Idx stop, size_t nmatch, regmatch_t pmatch[], int eflags) internal_function; static regoff_t re_search_2_stub (struct re_pattern_buffer *bufp, const char *string1, Idx length1, const char *string2, Idx length2, Idx start, regoff_t range, struct re_registers *regs, Idx stop, bool ret_len) internal_function; static regoff_t re_search_stub (struct re_pattern_buffer *bufp, const char *string, Idx length, Idx start, regoff_t range, Idx stop, struct re_registers *regs, bool ret_len) internal_function; static unsigned re_copy_regs (struct re_registers *regs, regmatch_t *pmatch, Idx nregs, int regs_allocated) internal_function; static reg_errcode_t prune_impossible_nodes (re_match_context_t *mctx) internal_function; static Idx check_matching (re_match_context_t *mctx, bool fl_longest_match, Idx *p_match_first) internal_function; static Idx check_halt_state_context (const re_match_context_t *mctx, const re_dfastate_t *state, Idx idx) internal_function; static void update_regs (const re_dfa_t *dfa, regmatch_t *pmatch, regmatch_t *prev_idx_match, Idx cur_node, Idx cur_idx, Idx nmatch) internal_function; static reg_errcode_t push_fail_stack (struct re_fail_stack_t *fs, Idx str_idx, Idx dest_node, Idx nregs, regmatch_t *regs, re_node_set *eps_via_nodes) internal_function; static reg_errcode_t set_regs (const regex_t *preg, const re_match_context_t *mctx, size_t nmatch, regmatch_t *pmatch, bool fl_backtrack) internal_function; static reg_errcode_t free_fail_stack_return (struct re_fail_stack_t *fs) internal_function; #ifdef RE_ENABLE_I18N static int sift_states_iter_mb (const re_match_context_t *mctx, re_sift_context_t *sctx, Idx node_idx, Idx str_idx, Idx max_str_idx) internal_function; #endif /* RE_ENABLE_I18N */ static reg_errcode_t sift_states_backward (const re_match_context_t *mctx, re_sift_context_t *sctx) internal_function; static reg_errcode_t build_sifted_states (const re_match_context_t *mctx, re_sift_context_t *sctx, Idx str_idx, re_node_set *cur_dest) internal_function; static reg_errcode_t update_cur_sifted_state (const re_match_context_t *mctx, re_sift_context_t *sctx, Idx str_idx, re_node_set *dest_nodes) internal_function; static reg_errcode_t add_epsilon_src_nodes (const re_dfa_t *dfa, re_node_set *dest_nodes, const re_node_set *candidates) internal_function; static bool check_dst_limits (const re_match_context_t *mctx, const re_node_set *limits, Idx dst_node, Idx dst_idx, Idx src_node, Idx src_idx) internal_function; static int check_dst_limits_calc_pos_1 (const re_match_context_t *mctx, int boundaries, Idx subexp_idx, Idx from_node, Idx bkref_idx) internal_function; static int check_dst_limits_calc_pos (const re_match_context_t *mctx, Idx limit, Idx subexp_idx, Idx node, Idx str_idx, Idx bkref_idx) internal_function; static reg_errcode_t check_subexp_limits (const re_dfa_t *dfa, re_node_set *dest_nodes, const re_node_set *candidates, re_node_set *limits, struct re_backref_cache_entry *bkref_ents, Idx str_idx) internal_function; static reg_errcode_t sift_states_bkref (const re_match_context_t *mctx, re_sift_context_t *sctx, Idx str_idx, const re_node_set *candidates) internal_function; static reg_errcode_t merge_state_array (const re_dfa_t *dfa, re_dfastate_t **dst, re_dfastate_t **src, Idx num) internal_function; static re_dfastate_t *find_recover_state (reg_errcode_t *err, re_match_context_t *mctx) internal_function; static re_dfastate_t *transit_state (reg_errcode_t *err, re_match_context_t *mctx, re_dfastate_t *state) internal_function; static re_dfastate_t *merge_state_with_log (reg_errcode_t *err, re_match_context_t *mctx, re_dfastate_t *next_state) internal_function; static reg_errcode_t check_subexp_matching_top (re_match_context_t *mctx, re_node_set *cur_nodes, Idx str_idx) internal_function; #if 0 static re_dfastate_t *transit_state_sb (reg_errcode_t *err, re_match_context_t *mctx, re_dfastate_t *pstate) internal_function; #endif #ifdef RE_ENABLE_I18N static reg_errcode_t transit_state_mb (re_match_context_t *mctx, re_dfastate_t *pstate) internal_function; #endif /* RE_ENABLE_I18N */ static reg_errcode_t transit_state_bkref (re_match_context_t *mctx, const re_node_set *nodes) internal_function; static reg_errcode_t get_subexp (re_match_context_t *mctx, Idx bkref_node, Idx bkref_str_idx) internal_function; static reg_errcode_t get_subexp_sub (re_match_context_t *mctx, const re_sub_match_top_t *sub_top, re_sub_match_last_t *sub_last, Idx bkref_node, Idx bkref_str) internal_function; static Idx find_subexp_node (const re_dfa_t *dfa, const re_node_set *nodes, Idx subexp_idx, int type) internal_function; static reg_errcode_t check_arrival (re_match_context_t *mctx, state_array_t *path, Idx top_node, Idx top_str, Idx last_node, Idx last_str, int type) internal_function; static reg_errcode_t check_arrival_add_next_nodes (re_match_context_t *mctx, Idx str_idx, re_node_set *cur_nodes, re_node_set *next_nodes) internal_function; static reg_errcode_t check_arrival_expand_ecl (const re_dfa_t *dfa, re_node_set *cur_nodes, Idx ex_subexp, int type) internal_function; static reg_errcode_t check_arrival_expand_ecl_sub (const re_dfa_t *dfa, re_node_set *dst_nodes, Idx target, Idx ex_subexp, int type) internal_function; static reg_errcode_t expand_bkref_cache (re_match_context_t *mctx, re_node_set *cur_nodes, Idx cur_str, Idx subexp_num, int type) internal_function; static bool build_trtable (const re_dfa_t *dfa, re_dfastate_t *state) internal_function; #ifdef RE_ENABLE_I18N static int check_node_accept_bytes (const re_dfa_t *dfa, Idx node_idx, const re_string_t *input, Idx idx) internal_function; # ifdef _LIBC static unsigned int find_collation_sequence_value (const unsigned char *mbs, size_t name_len) internal_function; # endif /* _LIBC */ #endif /* RE_ENABLE_I18N */ static Idx group_nodes_into_DFAstates (const re_dfa_t *dfa, const re_dfastate_t *state, re_node_set *states_node, bitset_t *states_ch) internal_function; static bool check_node_accept (const re_match_context_t *mctx, const re_token_t *node, Idx idx) internal_function; static reg_errcode_t extend_buffers (re_match_context_t *mctx, int min_len) internal_function; /* Entry point for POSIX code. */ /* regexec searches for a given pattern, specified by PREG, in the string STRING. If NMATCH is zero or REG_NOSUB was set in the cflags argument to 'regcomp', we ignore PMATCH. Otherwise, we assume PMATCH has at least NMATCH elements, and we set them to the offsets of the corresponding matched substrings. EFLAGS specifies "execution flags" which affect matching: if REG_NOTBOL is set, then ^ does not match at the beginning of the string; if REG_NOTEOL is set, then $ does not match at the end. We return 0 if we find a match and REG_NOMATCH if not. */ int regexec (const regex_t *_Restrict_ preg, const char *_Restrict_ string, size_t nmatch, regmatch_t pmatch[], int eflags) { reg_errcode_t err; Idx start, length; re_dfa_t *dfa = preg->buffer; if (eflags & ~(REG_NOTBOL | REG_NOTEOL | REG_STARTEND)) return REG_BADPAT; if (eflags & REG_STARTEND) { start = pmatch[0].rm_so; length = pmatch[0].rm_eo; } else { start = 0; length = strlen (string); } lock_lock (dfa->lock); if (preg->no_sub) err = re_search_internal (preg, string, length, start, length, length, 0, NULL, eflags); else err = re_search_internal (preg, string, length, start, length, length, nmatch, pmatch, eflags); lock_unlock (dfa->lock); return err != REG_NOERROR; } #ifdef _LIBC # include versioned_symbol (libc, __regexec, regexec, GLIBC_2_3_4); # if SHLIB_COMPAT (libc, GLIBC_2_0, GLIBC_2_3_4) __typeof__ (__regexec) __compat_regexec; int attribute_compat_text_section __compat_regexec (const regex_t *_Restrict_ preg, const char *_Restrict_ string, size_t nmatch, regmatch_t pmatch[], int eflags) { return regexec (preg, string, nmatch, pmatch, eflags & (REG_NOTBOL | REG_NOTEOL)); } compat_symbol (libc, __compat_regexec, regexec, GLIBC_2_0); # endif #endif /* Entry points for GNU code. */ /* re_match, re_search, re_match_2, re_search_2 The former two functions operate on STRING with length LENGTH, while the later two operate on concatenation of STRING1 and STRING2 with lengths LENGTH1 and LENGTH2, respectively. re_match() matches the compiled pattern in BUFP against the string, starting at index START. re_search() first tries matching at index START, then it tries to match starting from index START + 1, and so on. The last start position tried is START + RANGE. (Thus RANGE = 0 forces re_search to operate the same way as re_match().) The parameter STOP of re_{match,search}_2 specifies that no match exceeding the first STOP characters of the concatenation of the strings should be concerned. If REGS is not NULL, and BUFP->no_sub is not set, the offsets of the match and all groups is stored in REGS. (For the "_2" variants, the offsets are computed relative to the concatenation, not relative to the individual strings.) On success, re_match* functions return the length of the match, re_search* return the position of the start of the match. Return value -1 means no match was found and -2 indicates an internal error. */ regoff_t re_match (struct re_pattern_buffer *bufp, const char *string, Idx length, Idx start, struct re_registers *regs) { return re_search_stub (bufp, string, length, start, 0, length, regs, true); } #ifdef _LIBC weak_alias (__re_match, re_match) #endif regoff_t re_search (struct re_pattern_buffer *bufp, const char *string, Idx length, Idx start, regoff_t range, struct re_registers *regs) { return re_search_stub (bufp, string, length, start, range, length, regs, false); } #ifdef _LIBC weak_alias (__re_search, re_search) #endif regoff_t re_match_2 (struct re_pattern_buffer *bufp, const char *string1, Idx length1, const char *string2, Idx length2, Idx start, struct re_registers *regs, Idx stop) { return re_search_2_stub (bufp, string1, length1, string2, length2, start, 0, regs, stop, true); } #ifdef _LIBC weak_alias (__re_match_2, re_match_2) #endif regoff_t re_search_2 (struct re_pattern_buffer *bufp, const char *string1, Idx length1, const char *string2, Idx length2, Idx start, regoff_t range, struct re_registers *regs, Idx stop) { return re_search_2_stub (bufp, string1, length1, string2, length2, start, range, regs, stop, false); } #ifdef _LIBC weak_alias (__re_search_2, re_search_2) #endif static regoff_t internal_function re_search_2_stub (struct re_pattern_buffer *bufp, const char *string1, Idx length1, const char *string2, Idx length2, Idx start, regoff_t range, struct re_registers *regs, Idx stop, bool ret_len) { const char *str; regoff_t rval; Idx len = length1 + length2; char *s = NULL; if (BE (length1 < 0 || length2 < 0 || stop < 0 || len < length1, 0)) return -2; /* Concatenate the strings. */ if (length2 > 0) if (length1 > 0) { s = re_malloc (char, len); if (BE (s == NULL, 0)) return -2; #ifdef _LIBC memcpy (__mempcpy (s, string1, length1), string2, length2); #else memcpy (s, string1, length1); memcpy (s + length1, string2, length2); #endif str = s; } else str = string2; else str = string1; rval = re_search_stub (bufp, str, len, start, range, stop, regs, ret_len); re_free (s); return rval; } /* The parameters have the same meaning as those of re_search. Additional parameters: If RET_LEN is true the length of the match is returned (re_match style); otherwise the position of the match is returned. */ static regoff_t internal_function re_search_stub (struct re_pattern_buffer *bufp, const char *string, Idx length, Idx start, regoff_t range, Idx stop, struct re_registers *regs, bool ret_len) { reg_errcode_t result; regmatch_t *pmatch; Idx nregs; regoff_t rval; int eflags = 0; re_dfa_t *dfa = bufp->buffer; Idx last_start = start + range; /* Check for out-of-range. */ if (BE (start < 0 || start > length, 0)) return -1; if (BE (length < last_start || (0 <= range && last_start < start), 0)) last_start = length; else if (BE (last_start < 0 || (range < 0 && start <= last_start), 0)) last_start = 0; lock_lock (dfa->lock); eflags |= (bufp->not_bol) ? REG_NOTBOL : 0; eflags |= (bufp->not_eol) ? REG_NOTEOL : 0; /* Compile fastmap if we haven't yet. */ if (start < last_start && bufp->fastmap != NULL && !bufp->fastmap_accurate) re_compile_fastmap (bufp); if (BE (bufp->no_sub, 0)) regs = NULL; /* We need at least 1 register. */ if (regs == NULL) nregs = 1; else if (BE (bufp->regs_allocated == REGS_FIXED && regs->num_regs <= bufp->re_nsub, 0)) { nregs = regs->num_regs; if (BE (nregs < 1, 0)) { /* Nothing can be copied to regs. */ regs = NULL; nregs = 1; } } else nregs = bufp->re_nsub + 1; pmatch = re_malloc (regmatch_t, nregs); if (BE (pmatch == NULL, 0)) { rval = -2; goto out; } result = re_search_internal (bufp, string, length, start, last_start, stop, nregs, pmatch, eflags); rval = 0; /* I hope we needn't fill their regs with -1's when no match was found. */ if (result != REG_NOERROR) rval = result == REG_NOMATCH ? -1 : -2; else if (regs != NULL) { /* If caller wants register contents data back, copy them. */ bufp->regs_allocated = re_copy_regs (regs, pmatch, nregs, bufp->regs_allocated); if (BE (bufp->regs_allocated == REGS_UNALLOCATED, 0)) rval = -2; } if (BE (rval == 0, 1)) { if (ret_len) { assert (pmatch[0].rm_so == start); rval = pmatch[0].rm_eo - start; } else rval = pmatch[0].rm_so; } re_free (pmatch); out: lock_unlock (dfa->lock); return rval; } static unsigned internal_function re_copy_regs (struct re_registers *regs, regmatch_t *pmatch, Idx nregs, int regs_allocated) { int rval = REGS_REALLOCATE; Idx i; Idx need_regs = nregs + 1; /* We need one extra element beyond 'num_regs' for the '-1' marker GNU code uses. */ /* Have the register data arrays been allocated? */ if (regs_allocated == REGS_UNALLOCATED) { /* No. So allocate them with malloc. */ regs->start = re_malloc (regoff_t, need_regs); if (BE (regs->start == NULL, 0)) return REGS_UNALLOCATED; regs->end = re_malloc (regoff_t, need_regs); if (BE (regs->end == NULL, 0)) { re_free (regs->start); return REGS_UNALLOCATED; } regs->num_regs = need_regs; } else if (regs_allocated == REGS_REALLOCATE) { /* Yes. If we need more elements than were already allocated, reallocate them. If we need fewer, just leave it alone. */ if (BE (need_regs > regs->num_regs, 0)) { regoff_t *new_start = re_realloc (regs->start, regoff_t, need_regs); regoff_t *new_end; if (BE (new_start == NULL, 0)) return REGS_UNALLOCATED; new_end = re_realloc (regs->end, regoff_t, need_regs); if (BE (new_end == NULL, 0)) { re_free (new_start); return REGS_UNALLOCATED; } regs->start = new_start; regs->end = new_end; regs->num_regs = need_regs; } } else { assert (regs_allocated == REGS_FIXED); /* This function may not be called with REGS_FIXED and nregs too big. */ assert (regs->num_regs >= nregs); rval = REGS_FIXED; } /* Copy the regs. */ for (i = 0; i < nregs; ++i) { regs->start[i] = pmatch[i].rm_so; regs->end[i] = pmatch[i].rm_eo; } for ( ; i < regs->num_regs; ++i) regs->start[i] = regs->end[i] = -1; return rval; } /* Set REGS to hold NUM_REGS registers, storing them in STARTS and ENDS. Subsequent matches using PATTERN_BUFFER and REGS will use this memory for recording register information. STARTS and ENDS must be allocated using the malloc library routine, and must each be at least NUM_REGS * sizeof (regoff_t) bytes long. If NUM_REGS == 0, then subsequent matches should allocate their own register data. Unless this function is called, the first search or match using PATTERN_BUFFER will allocate its own register data, without freeing the old data. */ void re_set_registers (struct re_pattern_buffer *bufp, struct re_registers *regs, __re_size_t num_regs, regoff_t *starts, regoff_t *ends) { if (num_regs) { bufp->regs_allocated = REGS_REALLOCATE; regs->num_regs = num_regs; regs->start = starts; regs->end = ends; } else { bufp->regs_allocated = REGS_UNALLOCATED; regs->num_regs = 0; regs->start = regs->end = NULL; } } #ifdef _LIBC weak_alias (__re_set_registers, re_set_registers) #endif /* Entry points compatible with 4.2 BSD regex library. We don't define them unless specifically requested. */ #if defined _REGEX_RE_COMP || defined _LIBC int # ifdef _LIBC weak_function # endif re_exec (const char *s) { return 0 == regexec (&re_comp_buf, s, 0, NULL, 0); } #endif /* _REGEX_RE_COMP */ /* Internal entry point. */ /* Searches for a compiled pattern PREG in the string STRING, whose length is LENGTH. NMATCH, PMATCH, and EFLAGS have the same meaning as with regexec. LAST_START is START + RANGE, where START and RANGE have the same meaning as with re_search. Return REG_NOERROR if we find a match, and REG_NOMATCH if not, otherwise return the error code. Note: We assume front end functions already check ranges. (0 <= LAST_START && LAST_START <= LENGTH) */ static reg_errcode_t __attribute_warn_unused_result__ internal_function re_search_internal (const regex_t *preg, const char *string, Idx length, Idx start, Idx last_start, Idx stop, size_t nmatch, regmatch_t pmatch[], int eflags) { reg_errcode_t err; const re_dfa_t *dfa = preg->buffer; Idx left_lim, right_lim; int incr; bool fl_longest_match; int match_kind; Idx match_first; Idx match_last = -1; Idx extra_nmatch; bool sb; int ch; #if defined _LIBC || (defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901L) re_match_context_t mctx = { .dfa = dfa }; #else re_match_context_t mctx; #endif char *fastmap = ((preg->fastmap != NULL && preg->fastmap_accurate && start != last_start && !preg->can_be_null) ? preg->fastmap : NULL); RE_TRANSLATE_TYPE t = preg->translate; #if !(defined _LIBC || (defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901L)) memset (&mctx, '\0', sizeof (re_match_context_t)); mctx.dfa = dfa; #endif extra_nmatch = (nmatch > preg->re_nsub) ? nmatch - (preg->re_nsub + 1) : 0; nmatch -= extra_nmatch; /* Check if the DFA haven't been compiled. */ if (BE (preg->used == 0 || dfa->init_state == NULL || dfa->init_state_word == NULL || dfa->init_state_nl == NULL || dfa->init_state_begbuf == NULL, 0)) return REG_NOMATCH; #ifdef DEBUG /* We assume front-end functions already check them. */ assert (0 <= last_start && last_start <= length); #endif /* If initial states with non-begbuf contexts have no elements, the regex must be anchored. If preg->newline_anchor is set, we'll never use init_state_nl, so do not check it. */ if (dfa->init_state->nodes.nelem == 0 && dfa->init_state_word->nodes.nelem == 0 && (dfa->init_state_nl->nodes.nelem == 0 || !preg->newline_anchor)) { if (start != 0 && last_start != 0) return REG_NOMATCH; start = last_start = 0; } /* We must check the longest matching, if nmatch > 0. */ fl_longest_match = (nmatch != 0 || dfa->nbackref); err = re_string_allocate (&mctx.input, string, length, dfa->nodes_len + 1, preg->translate, (preg->syntax & RE_ICASE) != 0, dfa); if (BE (err != REG_NOERROR, 0)) goto free_return; mctx.input.stop = stop; mctx.input.raw_stop = stop; mctx.input.newline_anchor = preg->newline_anchor; err = match_ctx_init (&mctx, eflags, dfa->nbackref * 2); if (BE (err != REG_NOERROR, 0)) goto free_return; /* We will log all the DFA states through which the dfa pass, if nmatch > 1, or this dfa has "multibyte node", which is a back-reference or a node which can accept multibyte character or multi character collating element. */ if (nmatch > 1 || dfa->has_mb_node) { /* Avoid overflow. */ if (BE ((MIN (IDX_MAX, SIZE_MAX / sizeof (re_dfastate_t *)) <= mctx.input.bufs_len), 0)) { err = REG_ESPACE; goto free_return; } mctx.state_log = re_malloc (re_dfastate_t *, mctx.input.bufs_len + 1); if (BE (mctx.state_log == NULL, 0)) { err = REG_ESPACE; goto free_return; } } else mctx.state_log = NULL; match_first = start; mctx.input.tip_context = (eflags & REG_NOTBOL) ? CONTEXT_BEGBUF : CONTEXT_NEWLINE | CONTEXT_BEGBUF; /* Check incrementally whether the input string matches. */ incr = (last_start < start) ? -1 : 1; left_lim = (last_start < start) ? last_start : start; right_lim = (last_start < start) ? start : last_start; sb = dfa->mb_cur_max == 1; match_kind = (fastmap ? ((sb || !(preg->syntax & RE_ICASE || t) ? 4 : 0) | (start <= last_start ? 2 : 0) | (t != NULL ? 1 : 0)) : 8); for (;; match_first += incr) { err = REG_NOMATCH; if (match_first < left_lim || right_lim < match_first) goto free_return; /* Advance as rapidly as possible through the string, until we find a plausible place to start matching. This may be done with varying efficiency, so there are various possibilities: only the most common of them are specialized, in order to save on code size. We use a switch statement for speed. */ switch (match_kind) { case 8: /* No fastmap. */ break; case 7: /* Fastmap with single-byte translation, match forward. */ while (BE (match_first < right_lim, 1) && !fastmap[t[(unsigned char) string[match_first]]]) ++match_first; goto forward_match_found_start_or_reached_end; case 6: /* Fastmap without translation, match forward. */ while (BE (match_first < right_lim, 1) && !fastmap[(unsigned char) string[match_first]]) ++match_first; forward_match_found_start_or_reached_end: if (BE (match_first == right_lim, 0)) { ch = match_first >= length ? 0 : (unsigned char) string[match_first]; if (!fastmap[t ? t[ch] : ch]) goto free_return; } break; case 4: case 5: /* Fastmap without multi-byte translation, match backwards. */ while (match_first >= left_lim) { ch = match_first >= length ? 0 : (unsigned char) string[match_first]; if (fastmap[t ? t[ch] : ch]) break; --match_first; } if (match_first < left_lim) goto free_return; break; default: /* In this case, we can't determine easily the current byte, since it might be a component byte of a multibyte character. Then we use the constructed buffer instead. */ for (;;) { /* If MATCH_FIRST is out of the valid range, reconstruct the buffers. */ __re_size_t offset = match_first - mctx.input.raw_mbs_idx; if (BE (offset >= (__re_size_t) mctx.input.valid_raw_len, 0)) { err = re_string_reconstruct (&mctx.input, match_first, eflags); if (BE (err != REG_NOERROR, 0)) goto free_return; offset = match_first - mctx.input.raw_mbs_idx; } /* If MATCH_FIRST is out of the buffer, leave it as '\0'. Note that MATCH_FIRST must not be smaller than 0. */ ch = (match_first >= length ? 0 : re_string_byte_at (&mctx.input, offset)); if (fastmap[ch]) break; match_first += incr; if (match_first < left_lim || match_first > right_lim) { err = REG_NOMATCH; goto free_return; } } break; } /* Reconstruct the buffers so that the matcher can assume that the matching starts from the beginning of the buffer. */ err = re_string_reconstruct (&mctx.input, match_first, eflags); if (BE (err != REG_NOERROR, 0)) goto free_return; #ifdef RE_ENABLE_I18N /* Don't consider this char as a possible match start if it part, yet isn't the head, of a multibyte character. */ if (!sb && !re_string_first_byte (&mctx.input, 0)) continue; #endif /* It seems to be appropriate one, then use the matcher. */ /* We assume that the matching starts from 0. */ mctx.state_log_top = mctx.nbkref_ents = mctx.max_mb_elem_len = 0; match_last = check_matching (&mctx, fl_longest_match, start <= last_start ? &match_first : NULL); if (match_last != -1) { if (BE (match_last == -2, 0)) { err = REG_ESPACE; goto free_return; } else { mctx.match_last = match_last; if ((!preg->no_sub && nmatch > 1) || dfa->nbackref) { re_dfastate_t *pstate = mctx.state_log[match_last]; mctx.last_node = check_halt_state_context (&mctx, pstate, match_last); } if ((!preg->no_sub && nmatch > 1 && dfa->has_plural_match) || dfa->nbackref) { err = prune_impossible_nodes (&mctx); if (err == REG_NOERROR) break; if (BE (err != REG_NOMATCH, 0)) goto free_return; match_last = -1; } else break; /* We found a match. */ } } match_ctx_clean (&mctx); } #ifdef DEBUG assert (match_last != -1); assert (err == REG_NOERROR); #endif /* Set pmatch[] if we need. */ if (nmatch > 0) { Idx reg_idx; /* Initialize registers. */ for (reg_idx = 1; reg_idx < nmatch; ++reg_idx) pmatch[reg_idx].rm_so = pmatch[reg_idx].rm_eo = -1; /* Set the points where matching start/end. */ pmatch[0].rm_so = 0; pmatch[0].rm_eo = mctx.match_last; /* FIXME: This function should fail if mctx.match_last exceeds the maximum possible regoff_t value. We need a new error code REG_OVERFLOW. */ if (!preg->no_sub && nmatch > 1) { err = set_regs (preg, &mctx, nmatch, pmatch, dfa->has_plural_match && dfa->nbackref > 0); if (BE (err != REG_NOERROR, 0)) goto free_return; } /* At last, add the offset to each register, since we slid the buffers so that we could assume that the matching starts from 0. */ for (reg_idx = 0; reg_idx < nmatch; ++reg_idx) if (pmatch[reg_idx].rm_so != -1) { #ifdef RE_ENABLE_I18N if (BE (mctx.input.offsets_needed != 0, 0)) { pmatch[reg_idx].rm_so = (pmatch[reg_idx].rm_so == mctx.input.valid_len ? mctx.input.valid_raw_len : mctx.input.offsets[pmatch[reg_idx].rm_so]); pmatch[reg_idx].rm_eo = (pmatch[reg_idx].rm_eo == mctx.input.valid_len ? mctx.input.valid_raw_len : mctx.input.offsets[pmatch[reg_idx].rm_eo]); } #else assert (mctx.input.offsets_needed == 0); #endif pmatch[reg_idx].rm_so += match_first; pmatch[reg_idx].rm_eo += match_first; } for (reg_idx = 0; reg_idx < extra_nmatch; ++reg_idx) { pmatch[nmatch + reg_idx].rm_so = -1; pmatch[nmatch + reg_idx].rm_eo = -1; } if (dfa->subexp_map) for (reg_idx = 0; reg_idx + 1 < nmatch; reg_idx++) if (dfa->subexp_map[reg_idx] != reg_idx) { pmatch[reg_idx + 1].rm_so = pmatch[dfa->subexp_map[reg_idx] + 1].rm_so; pmatch[reg_idx + 1].rm_eo = pmatch[dfa->subexp_map[reg_idx] + 1].rm_eo; } } free_return: re_free (mctx.state_log); if (dfa->nbackref) match_ctx_free (&mctx); re_string_destruct (&mctx.input); return err; } static reg_errcode_t internal_function __attribute_warn_unused_result__ prune_impossible_nodes (re_match_context_t *mctx) { const re_dfa_t *const dfa = mctx->dfa; Idx halt_node, match_last; reg_errcode_t ret; re_dfastate_t **sifted_states; re_dfastate_t **lim_states = NULL; re_sift_context_t sctx; #ifdef DEBUG assert (mctx->state_log != NULL); #endif match_last = mctx->match_last; halt_node = mctx->last_node; /* Avoid overflow. */ if (BE (MIN (IDX_MAX, SIZE_MAX / sizeof (re_dfastate_t *)) <= match_last, 0)) return REG_ESPACE; sifted_states = re_malloc (re_dfastate_t *, match_last + 1); if (BE (sifted_states == NULL, 0)) { ret = REG_ESPACE; goto free_return; } if (dfa->nbackref) { lim_states = re_malloc (re_dfastate_t *, match_last + 1); if (BE (lim_states == NULL, 0)) { ret = REG_ESPACE; goto free_return; } while (1) { memset (lim_states, '\0', sizeof (re_dfastate_t *) * (match_last + 1)); sift_ctx_init (&sctx, sifted_states, lim_states, halt_node, match_last); ret = sift_states_backward (mctx, &sctx); re_node_set_free (&sctx.limits); if (BE (ret != REG_NOERROR, 0)) goto free_return; if (sifted_states[0] != NULL || lim_states[0] != NULL) break; do { --match_last; if (match_last < 0) { ret = REG_NOMATCH; goto free_return; } } while (mctx->state_log[match_last] == NULL || !mctx->state_log[match_last]->halt); halt_node = check_halt_state_context (mctx, mctx->state_log[match_last], match_last); } ret = merge_state_array (dfa, sifted_states, lim_states, match_last + 1); re_free (lim_states); lim_states = NULL; if (BE (ret != REG_NOERROR, 0)) goto free_return; } else { sift_ctx_init (&sctx, sifted_states, lim_states, halt_node, match_last); ret = sift_states_backward (mctx, &sctx); re_node_set_free (&sctx.limits); if (BE (ret != REG_NOERROR, 0)) goto free_return; if (sifted_states[0] == NULL) { ret = REG_NOMATCH; goto free_return; } } re_free (mctx->state_log); mctx->state_log = sifted_states; sifted_states = NULL; mctx->last_node = halt_node; mctx->match_last = match_last; ret = REG_NOERROR; free_return: re_free (sifted_states); re_free (lim_states); return ret; } /* Acquire an initial state and return it. We must select appropriate initial state depending on the context, since initial states may have constraints like "\<", "^", etc.. */ static inline re_dfastate_t * __attribute__ ((always_inline)) internal_function acquire_init_state_context (reg_errcode_t *err, const re_match_context_t *mctx, Idx idx) { const re_dfa_t *const dfa = mctx->dfa; if (dfa->init_state->has_constraint) { unsigned int context; context = re_string_context_at (&mctx->input, idx - 1, mctx->eflags); if (IS_WORD_CONTEXT (context)) return dfa->init_state_word; else if (IS_ORDINARY_CONTEXT (context)) return dfa->init_state; else if (IS_BEGBUF_CONTEXT (context) && IS_NEWLINE_CONTEXT (context)) return dfa->init_state_begbuf; else if (IS_NEWLINE_CONTEXT (context)) return dfa->init_state_nl; else if (IS_BEGBUF_CONTEXT (context)) { /* It is relatively rare case, then calculate on demand. */ return re_acquire_state_context (err, dfa, dfa->init_state->entrance_nodes, context); } else /* Must not happen? */ return dfa->init_state; } else return dfa->init_state; } /* Check whether the regular expression match input string INPUT or not, and return the index where the matching end. Return -1 if there is no match, and return -2 in case of an error. FL_LONGEST_MATCH means we want the POSIX longest matching. If P_MATCH_FIRST is not NULL, and the match fails, it is set to the next place where we may want to try matching. Note that the matcher assumes that the matching starts from the current index of the buffer. */ static Idx internal_function __attribute_warn_unused_result__ check_matching (re_match_context_t *mctx, bool fl_longest_match, Idx *p_match_first) { const re_dfa_t *const dfa = mctx->dfa; reg_errcode_t err; Idx match = 0; Idx match_last = -1; Idx cur_str_idx = re_string_cur_idx (&mctx->input); re_dfastate_t *cur_state; bool at_init_state = p_match_first != NULL; Idx next_start_idx = cur_str_idx; err = REG_NOERROR; cur_state = acquire_init_state_context (&err, mctx, cur_str_idx); /* An initial state must not be NULL (invalid). */ if (BE (cur_state == NULL, 0)) { assert (err == REG_ESPACE); return -2; } if (mctx->state_log != NULL) { mctx->state_log[cur_str_idx] = cur_state; /* Check OP_OPEN_SUBEXP in the initial state in case that we use them later. E.g. Processing back references. */ if (BE (dfa->nbackref, 0)) { at_init_state = false; err = check_subexp_matching_top (mctx, &cur_state->nodes, 0); if (BE (err != REG_NOERROR, 0)) return err; if (cur_state->has_backref) { err = transit_state_bkref (mctx, &cur_state->nodes); if (BE (err != REG_NOERROR, 0)) return err; } } } /* If the RE accepts NULL string. */ if (BE (cur_state->halt, 0)) { if (!cur_state->has_constraint || check_halt_state_context (mctx, cur_state, cur_str_idx)) { if (!fl_longest_match) return cur_str_idx; else { match_last = cur_str_idx; match = 1; } } } while (!re_string_eoi (&mctx->input)) { re_dfastate_t *old_state = cur_state; Idx next_char_idx = re_string_cur_idx (&mctx->input) + 1; if ((BE (next_char_idx >= mctx->input.bufs_len, 0) && mctx->input.bufs_len < mctx->input.len) || (BE (next_char_idx >= mctx->input.valid_len, 0) && mctx->input.valid_len < mctx->input.len)) { err = extend_buffers (mctx, next_char_idx + 1); if (BE (err != REG_NOERROR, 0)) { assert (err == REG_ESPACE); return -2; } } cur_state = transit_state (&err, mctx, cur_state); if (mctx->state_log != NULL) cur_state = merge_state_with_log (&err, mctx, cur_state); if (cur_state == NULL) { /* Reached the invalid state or an error. Try to recover a valid state using the state log, if available and if we have not already found a valid (even if not the longest) match. */ if (BE (err != REG_NOERROR, 0)) return -2; if (mctx->state_log == NULL || (match && !fl_longest_match) || (cur_state = find_recover_state (&err, mctx)) == NULL) break; } if (BE (at_init_state, 0)) { if (old_state == cur_state) next_start_idx = next_char_idx; else at_init_state = false; } if (cur_state->halt) { /* Reached a halt state. Check the halt state can satisfy the current context. */ if (!cur_state->has_constraint || check_halt_state_context (mctx, cur_state, re_string_cur_idx (&mctx->input))) { /* We found an appropriate halt state. */ match_last = re_string_cur_idx (&mctx->input); match = 1; /* We found a match, do not modify match_first below. */ p_match_first = NULL; if (!fl_longest_match) break; } } } if (p_match_first) *p_match_first += next_start_idx; return match_last; } /* Check NODE match the current context. */ static bool internal_function check_halt_node_context (const re_dfa_t *dfa, Idx node, unsigned int context) { re_token_type_t type = dfa->nodes[node].type; unsigned int constraint = dfa->nodes[node].constraint; if (type != END_OF_RE) return false; if (!constraint) return true; if (NOT_SATISFY_NEXT_CONSTRAINT (constraint, context)) return false; return true; } /* Check the halt state STATE match the current context. Return 0 if not match, if the node, STATE has, is a halt node and match the context, return the node. */ static Idx internal_function check_halt_state_context (const re_match_context_t *mctx, const re_dfastate_t *state, Idx idx) { Idx i; unsigned int context; #ifdef DEBUG assert (state->halt); #endif context = re_string_context_at (&mctx->input, idx, mctx->eflags); for (i = 0; i < state->nodes.nelem; ++i) if (check_halt_node_context (mctx->dfa, state->nodes.elems[i], context)) return state->nodes.elems[i]; return 0; } /* Compute the next node to which "NFA" transit from NODE("NFA" is a NFA corresponding to the DFA). Return the destination node, and update EPS_VIA_NODES; return -1 in case of errors. */ static Idx internal_function proceed_next_node (const re_match_context_t *mctx, Idx nregs, regmatch_t *regs, Idx *pidx, Idx node, re_node_set *eps_via_nodes, struct re_fail_stack_t *fs) { const re_dfa_t *const dfa = mctx->dfa; Idx i; bool ok; if (IS_EPSILON_NODE (dfa->nodes[node].type)) { re_node_set *cur_nodes = &mctx->state_log[*pidx]->nodes; re_node_set *edests = &dfa->edests[node]; Idx dest_node; ok = re_node_set_insert (eps_via_nodes, node); if (BE (! ok, 0)) return -2; /* Pick up a valid destination, or return -1 if none is found. */ for (dest_node = -1, i = 0; i < edests->nelem; ++i) { Idx candidate = edests->elems[i]; if (!re_node_set_contains (cur_nodes, candidate)) continue; if (dest_node == -1) dest_node = candidate; else { /* In order to avoid infinite loop like "(a*)*", return the second epsilon-transition if the first was already considered. */ if (re_node_set_contains (eps_via_nodes, dest_node)) return candidate; /* Otherwise, push the second epsilon-transition on the fail stack. */ else if (fs != NULL && push_fail_stack (fs, *pidx, candidate, nregs, regs, eps_via_nodes)) return -2; /* We know we are going to exit. */ break; } } return dest_node; } else { Idx naccepted = 0; re_token_type_t type = dfa->nodes[node].type; #ifdef RE_ENABLE_I18N if (dfa->nodes[node].accept_mb) naccepted = check_node_accept_bytes (dfa, node, &mctx->input, *pidx); else #endif /* RE_ENABLE_I18N */ if (type == OP_BACK_REF) { Idx subexp_idx = dfa->nodes[node].opr.idx + 1; naccepted = regs[subexp_idx].rm_eo - regs[subexp_idx].rm_so; if (fs != NULL) { if (regs[subexp_idx].rm_so == -1 || regs[subexp_idx].rm_eo == -1) return -1; else if (naccepted) { char *buf = (char *) re_string_get_buffer (&mctx->input); if (memcmp (buf + regs[subexp_idx].rm_so, buf + *pidx, naccepted) != 0) return -1; } } if (naccepted == 0) { Idx dest_node; ok = re_node_set_insert (eps_via_nodes, node); if (BE (! ok, 0)) return -2; dest_node = dfa->edests[node].elems[0]; if (re_node_set_contains (&mctx->state_log[*pidx]->nodes, dest_node)) return dest_node; } } if (naccepted != 0 || check_node_accept (mctx, dfa->nodes + node, *pidx)) { Idx dest_node = dfa->nexts[node]; *pidx = (naccepted == 0) ? *pidx + 1 : *pidx + naccepted; if (fs && (*pidx > mctx->match_last || mctx->state_log[*pidx] == NULL || !re_node_set_contains (&mctx->state_log[*pidx]->nodes, dest_node))) return -1; re_node_set_empty (eps_via_nodes); return dest_node; } } return -1; } static reg_errcode_t internal_function __attribute_warn_unused_result__ push_fail_stack (struct re_fail_stack_t *fs, Idx str_idx, Idx dest_node, Idx nregs, regmatch_t *regs, re_node_set *eps_via_nodes) { reg_errcode_t err; Idx num = fs->num++; if (fs->num == fs->alloc) { struct re_fail_stack_ent_t *new_array; new_array = realloc (fs->stack, (sizeof (struct re_fail_stack_ent_t) * fs->alloc * 2)); if (new_array == NULL) return REG_ESPACE; fs->alloc *= 2; fs->stack = new_array; } fs->stack[num].idx = str_idx; fs->stack[num].node = dest_node; fs->stack[num].regs = re_malloc (regmatch_t, nregs); if (fs->stack[num].regs == NULL) return REG_ESPACE; memcpy (fs->stack[num].regs, regs, sizeof (regmatch_t) * nregs); err = re_node_set_init_copy (&fs->stack[num].eps_via_nodes, eps_via_nodes); return err; } static Idx internal_function pop_fail_stack (struct re_fail_stack_t *fs, Idx *pidx, Idx nregs, regmatch_t *regs, re_node_set *eps_via_nodes) { Idx num = --fs->num; assert (num >= 0); *pidx = fs->stack[num].idx; memcpy (regs, fs->stack[num].regs, sizeof (regmatch_t) * nregs); re_node_set_free (eps_via_nodes); re_free (fs->stack[num].regs); *eps_via_nodes = fs->stack[num].eps_via_nodes; return fs->stack[num].node; } /* Set the positions where the subexpressions are starts/ends to registers PMATCH. Note: We assume that pmatch[0] is already set, and pmatch[i].rm_so == pmatch[i].rm_eo == -1 for 0 < i < nmatch. */ static reg_errcode_t internal_function __attribute_warn_unused_result__ set_regs (const regex_t *preg, const re_match_context_t *mctx, size_t nmatch, regmatch_t *pmatch, bool fl_backtrack) { const re_dfa_t *dfa = preg->buffer; Idx idx, cur_node; re_node_set eps_via_nodes; struct re_fail_stack_t *fs; struct re_fail_stack_t fs_body = { 0, 2, NULL }; regmatch_t *prev_idx_match; bool prev_idx_match_malloced = false; #ifdef DEBUG assert (nmatch > 1); assert (mctx->state_log != NULL); #endif if (fl_backtrack) { fs = &fs_body; fs->stack = re_malloc (struct re_fail_stack_ent_t, fs->alloc); if (fs->stack == NULL) return REG_ESPACE; } else fs = NULL; cur_node = dfa->init_node; re_node_set_init_empty (&eps_via_nodes); if (__libc_use_alloca (nmatch * sizeof (regmatch_t))) prev_idx_match = (regmatch_t *) alloca (nmatch * sizeof (regmatch_t)); else { prev_idx_match = re_malloc (regmatch_t, nmatch); if (prev_idx_match == NULL) { free_fail_stack_return (fs); return REG_ESPACE; } prev_idx_match_malloced = true; } memcpy (prev_idx_match, pmatch, sizeof (regmatch_t) * nmatch); for (idx = pmatch[0].rm_so; idx <= pmatch[0].rm_eo ;) { update_regs (dfa, pmatch, prev_idx_match, cur_node, idx, nmatch); if (idx == pmatch[0].rm_eo && cur_node == mctx->last_node) { Idx reg_idx; if (fs) { for (reg_idx = 0; reg_idx < nmatch; ++reg_idx) if (pmatch[reg_idx].rm_so > -1 && pmatch[reg_idx].rm_eo == -1) break; if (reg_idx == nmatch) { re_node_set_free (&eps_via_nodes); if (prev_idx_match_malloced) re_free (prev_idx_match); return free_fail_stack_return (fs); } cur_node = pop_fail_stack (fs, &idx, nmatch, pmatch, &eps_via_nodes); } else { re_node_set_free (&eps_via_nodes); if (prev_idx_match_malloced) re_free (prev_idx_match); return REG_NOERROR; } } /* Proceed to next node. */ cur_node = proceed_next_node (mctx, nmatch, pmatch, &idx, cur_node, &eps_via_nodes, fs); if (BE (cur_node < 0, 0)) { if (BE (cur_node == -2, 0)) { re_node_set_free (&eps_via_nodes); if (prev_idx_match_malloced) re_free (prev_idx_match); free_fail_stack_return (fs); return REG_ESPACE; } if (fs) cur_node = pop_fail_stack (fs, &idx, nmatch, pmatch, &eps_via_nodes); else { re_node_set_free (&eps_via_nodes); if (prev_idx_match_malloced) re_free (prev_idx_match); return REG_NOMATCH; } } } re_node_set_free (&eps_via_nodes); if (prev_idx_match_malloced) re_free (prev_idx_match); return free_fail_stack_return (fs); } static reg_errcode_t internal_function free_fail_stack_return (struct re_fail_stack_t *fs) { if (fs) { Idx fs_idx; for (fs_idx = 0; fs_idx < fs->num; ++fs_idx) { re_node_set_free (&fs->stack[fs_idx].eps_via_nodes); re_free (fs->stack[fs_idx].regs); } re_free (fs->stack); } return REG_NOERROR; } static void internal_function update_regs (const re_dfa_t *dfa, regmatch_t *pmatch, regmatch_t *prev_idx_match, Idx cur_node, Idx cur_idx, Idx nmatch) { int type = dfa->nodes[cur_node].type; if (type == OP_OPEN_SUBEXP) { Idx reg_num = dfa->nodes[cur_node].opr.idx + 1; /* We are at the first node of this sub expression. */ if (reg_num < nmatch) { pmatch[reg_num].rm_so = cur_idx; pmatch[reg_num].rm_eo = -1; } } else if (type == OP_CLOSE_SUBEXP) { Idx reg_num = dfa->nodes[cur_node].opr.idx + 1; if (reg_num < nmatch) { /* We are at the last node of this sub expression. */ if (pmatch[reg_num].rm_so < cur_idx) { pmatch[reg_num].rm_eo = cur_idx; /* This is a non-empty match or we are not inside an optional subexpression. Accept this right away. */ memcpy (prev_idx_match, pmatch, sizeof (regmatch_t) * nmatch); } else { if (dfa->nodes[cur_node].opt_subexp && prev_idx_match[reg_num].rm_so != -1) /* We transited through an empty match for an optional subexpression, like (a?)*, and this is not the subexp's first match. Copy back the old content of the registers so that matches of an inner subexpression are undone as well, like in ((a?))*. */ memcpy (pmatch, prev_idx_match, sizeof (regmatch_t) * nmatch); else /* We completed a subexpression, but it may be part of an optional one, so do not update PREV_IDX_MATCH. */ pmatch[reg_num].rm_eo = cur_idx; } } } } /* This function checks the STATE_LOG from the SCTX->last_str_idx to 0 and sift the nodes in each states according to the following rules. Updated state_log will be wrote to STATE_LOG. Rules: We throw away the Node 'a' in the STATE_LOG[STR_IDX] if... 1. When STR_IDX == MATCH_LAST(the last index in the state_log): If 'a' isn't the LAST_NODE and 'a' can't epsilon transit to the LAST_NODE, we throw away the node 'a'. 2. When 0 <= STR_IDX < MATCH_LAST and 'a' accepts string 's' and transit to 'b': i. If 'b' isn't in the STATE_LOG[STR_IDX+strlen('s')], we throw away the node 'a'. ii. If 'b' is in the STATE_LOG[STR_IDX+strlen('s')] but 'b' is thrown away, we throw away the node 'a'. 3. When 0 <= STR_IDX < MATCH_LAST and 'a' epsilon transit to 'b': i. If 'b' isn't in the STATE_LOG[STR_IDX], we throw away the node 'a'. ii. If 'b' is in the STATE_LOG[STR_IDX] but 'b' is thrown away, we throw away the node 'a'. */ #define STATE_NODE_CONTAINS(state,node) \ ((state) != NULL && re_node_set_contains (&(state)->nodes, node)) static reg_errcode_t internal_function sift_states_backward (const re_match_context_t *mctx, re_sift_context_t *sctx) { reg_errcode_t err; int null_cnt = 0; Idx str_idx = sctx->last_str_idx; re_node_set cur_dest; #ifdef DEBUG assert (mctx->state_log != NULL && mctx->state_log[str_idx] != NULL); #endif /* Build sifted state_log[str_idx]. It has the nodes which can epsilon transit to the last_node and the last_node itself. */ err = re_node_set_init_1 (&cur_dest, sctx->last_node); if (BE (err != REG_NOERROR, 0)) return err; err = update_cur_sifted_state (mctx, sctx, str_idx, &cur_dest); if (BE (err != REG_NOERROR, 0)) goto free_return; /* Then check each states in the state_log. */ while (str_idx > 0) { /* Update counters. */ null_cnt = (sctx->sifted_states[str_idx] == NULL) ? null_cnt + 1 : 0; if (null_cnt > mctx->max_mb_elem_len) { memset (sctx->sifted_states, '\0', sizeof (re_dfastate_t *) * str_idx); re_node_set_free (&cur_dest); return REG_NOERROR; } re_node_set_empty (&cur_dest); --str_idx; if (mctx->state_log[str_idx]) { err = build_sifted_states (mctx, sctx, str_idx, &cur_dest); if (BE (err != REG_NOERROR, 0)) goto free_return; } /* Add all the nodes which satisfy the following conditions: - It can epsilon transit to a node in CUR_DEST. - It is in CUR_SRC. And update state_log. */ err = update_cur_sifted_state (mctx, sctx, str_idx, &cur_dest); if (BE (err != REG_NOERROR, 0)) goto free_return; } err = REG_NOERROR; free_return: re_node_set_free (&cur_dest); return err; } static reg_errcode_t internal_function __attribute_warn_unused_result__ build_sifted_states (const re_match_context_t *mctx, re_sift_context_t *sctx, Idx str_idx, re_node_set *cur_dest) { const re_dfa_t *const dfa = mctx->dfa; const re_node_set *cur_src = &mctx->state_log[str_idx]->non_eps_nodes; Idx i; /* Then build the next sifted state. We build the next sifted state on 'cur_dest', and update 'sifted_states[str_idx]' with 'cur_dest'. Note: 'cur_dest' is the sifted state from 'state_log[str_idx + 1]'. 'cur_src' points the node_set of the old 'state_log[str_idx]' (with the epsilon nodes pre-filtered out). */ for (i = 0; i < cur_src->nelem; i++) { Idx prev_node = cur_src->elems[i]; int naccepted = 0; bool ok; #ifdef DEBUG re_token_type_t type = dfa->nodes[prev_node].type; assert (!IS_EPSILON_NODE (type)); #endif #ifdef RE_ENABLE_I18N /* If the node may accept "multi byte". */ if (dfa->nodes[prev_node].accept_mb) naccepted = sift_states_iter_mb (mctx, sctx, prev_node, str_idx, sctx->last_str_idx); #endif /* RE_ENABLE_I18N */ /* We don't check backreferences here. See update_cur_sifted_state(). */ if (!naccepted && check_node_accept (mctx, dfa->nodes + prev_node, str_idx) && STATE_NODE_CONTAINS (sctx->sifted_states[str_idx + 1], dfa->nexts[prev_node])) naccepted = 1; if (naccepted == 0) continue; if (sctx->limits.nelem) { Idx to_idx = str_idx + naccepted; if (check_dst_limits (mctx, &sctx->limits, dfa->nexts[prev_node], to_idx, prev_node, str_idx)) continue; } ok = re_node_set_insert (cur_dest, prev_node); if (BE (! ok, 0)) return REG_ESPACE; } return REG_NOERROR; } /* Helper functions. */ static reg_errcode_t internal_function clean_state_log_if_needed (re_match_context_t *mctx, Idx next_state_log_idx) { Idx top = mctx->state_log_top; if ((next_state_log_idx >= mctx->input.bufs_len && mctx->input.bufs_len < mctx->input.len) || (next_state_log_idx >= mctx->input.valid_len && mctx->input.valid_len < mctx->input.len)) { reg_errcode_t err; err = extend_buffers (mctx, next_state_log_idx + 1); if (BE (err != REG_NOERROR, 0)) return err; } if (top < next_state_log_idx) { memset (mctx->state_log + top + 1, '\0', sizeof (re_dfastate_t *) * (next_state_log_idx - top)); mctx->state_log_top = next_state_log_idx; } return REG_NOERROR; } static reg_errcode_t internal_function merge_state_array (const re_dfa_t *dfa, re_dfastate_t **dst, re_dfastate_t **src, Idx num) { Idx st_idx; reg_errcode_t err; for (st_idx = 0; st_idx < num; ++st_idx) { if (dst[st_idx] == NULL) dst[st_idx] = src[st_idx]; else if (src[st_idx] != NULL) { re_node_set merged_set; err = re_node_set_init_union (&merged_set, &dst[st_idx]->nodes, &src[st_idx]->nodes); if (BE (err != REG_NOERROR, 0)) return err; dst[st_idx] = re_acquire_state (&err, dfa, &merged_set); re_node_set_free (&merged_set); if (BE (err != REG_NOERROR, 0)) return err; } } return REG_NOERROR; } static reg_errcode_t internal_function update_cur_sifted_state (const re_match_context_t *mctx, re_sift_context_t *sctx, Idx str_idx, re_node_set *dest_nodes) { const re_dfa_t *const dfa = mctx->dfa; reg_errcode_t err = REG_NOERROR; const re_node_set *candidates; candidates = ((mctx->state_log[str_idx] == NULL) ? NULL : &mctx->state_log[str_idx]->nodes); if (dest_nodes->nelem == 0) sctx->sifted_states[str_idx] = NULL; else { if (candidates) { /* At first, add the nodes which can epsilon transit to a node in DEST_NODE. */ err = add_epsilon_src_nodes (dfa, dest_nodes, candidates); if (BE (err != REG_NOERROR, 0)) return err; /* Then, check the limitations in the current sift_context. */ if (sctx->limits.nelem) { err = check_subexp_limits (dfa, dest_nodes, candidates, &sctx->limits, mctx->bkref_ents, str_idx); if (BE (err != REG_NOERROR, 0)) return err; } } sctx->sifted_states[str_idx] = re_acquire_state (&err, dfa, dest_nodes); if (BE (err != REG_NOERROR, 0)) return err; } if (candidates && mctx->state_log[str_idx]->has_backref) { err = sift_states_bkref (mctx, sctx, str_idx, candidates); if (BE (err != REG_NOERROR, 0)) return err; } return REG_NOERROR; } static reg_errcode_t internal_function __attribute_warn_unused_result__ add_epsilon_src_nodes (const re_dfa_t *dfa, re_node_set *dest_nodes, const re_node_set *candidates) { reg_errcode_t err = REG_NOERROR; Idx i; re_dfastate_t *state = re_acquire_state (&err, dfa, dest_nodes); if (BE (err != REG_NOERROR, 0)) return err; if (!state->inveclosure.alloc) { err = re_node_set_alloc (&state->inveclosure, dest_nodes->nelem); if (BE (err != REG_NOERROR, 0)) return REG_ESPACE; for (i = 0; i < dest_nodes->nelem; i++) { err = re_node_set_merge (&state->inveclosure, dfa->inveclosures + dest_nodes->elems[i]); if (BE (err != REG_NOERROR, 0)) return REG_ESPACE; } } return re_node_set_add_intersect (dest_nodes, candidates, &state->inveclosure); } static reg_errcode_t internal_function sub_epsilon_src_nodes (const re_dfa_t *dfa, Idx node, re_node_set *dest_nodes, const re_node_set *candidates) { Idx ecl_idx; reg_errcode_t err; re_node_set *inv_eclosure = dfa->inveclosures + node; re_node_set except_nodes; re_node_set_init_empty (&except_nodes); for (ecl_idx = 0; ecl_idx < inv_eclosure->nelem; ++ecl_idx) { Idx cur_node = inv_eclosure->elems[ecl_idx]; if (cur_node == node) continue; if (IS_EPSILON_NODE (dfa->nodes[cur_node].type)) { Idx edst1 = dfa->edests[cur_node].elems[0]; Idx edst2 = ((dfa->edests[cur_node].nelem > 1) ? dfa->edests[cur_node].elems[1] : -1); if ((!re_node_set_contains (inv_eclosure, edst1) && re_node_set_contains (dest_nodes, edst1)) || (edst2 > 0 && !re_node_set_contains (inv_eclosure, edst2) && re_node_set_contains (dest_nodes, edst2))) { err = re_node_set_add_intersect (&except_nodes, candidates, dfa->inveclosures + cur_node); if (BE (err != REG_NOERROR, 0)) { re_node_set_free (&except_nodes); return err; } } } } for (ecl_idx = 0; ecl_idx < inv_eclosure->nelem; ++ecl_idx) { Idx cur_node = inv_eclosure->elems[ecl_idx]; if (!re_node_set_contains (&except_nodes, cur_node)) { Idx idx = re_node_set_contains (dest_nodes, cur_node) - 1; re_node_set_remove_at (dest_nodes, idx); } } re_node_set_free (&except_nodes); return REG_NOERROR; } static bool internal_function check_dst_limits (const re_match_context_t *mctx, const re_node_set *limits, Idx dst_node, Idx dst_idx, Idx src_node, Idx src_idx) { const re_dfa_t *const dfa = mctx->dfa; Idx lim_idx, src_pos, dst_pos; Idx dst_bkref_idx = search_cur_bkref_entry (mctx, dst_idx); Idx src_bkref_idx = search_cur_bkref_entry (mctx, src_idx); for (lim_idx = 0; lim_idx < limits->nelem; ++lim_idx) { Idx subexp_idx; struct re_backref_cache_entry *ent; ent = mctx->bkref_ents + limits->elems[lim_idx]; subexp_idx = dfa->nodes[ent->node].opr.idx; dst_pos = check_dst_limits_calc_pos (mctx, limits->elems[lim_idx], subexp_idx, dst_node, dst_idx, dst_bkref_idx); src_pos = check_dst_limits_calc_pos (mctx, limits->elems[lim_idx], subexp_idx, src_node, src_idx, src_bkref_idx); /* In case of: ( ) ( ) ( ) */ if (src_pos == dst_pos) continue; /* This is unrelated limitation. */ else return true; } return false; } static int internal_function check_dst_limits_calc_pos_1 (const re_match_context_t *mctx, int boundaries, Idx subexp_idx, Idx from_node, Idx bkref_idx) { const re_dfa_t *const dfa = mctx->dfa; const re_node_set *eclosures = dfa->eclosures + from_node; Idx node_idx; /* Else, we are on the boundary: examine the nodes on the epsilon closure. */ for (node_idx = 0; node_idx < eclosures->nelem; ++node_idx) { Idx node = eclosures->elems[node_idx]; switch (dfa->nodes[node].type) { case OP_BACK_REF: if (bkref_idx != -1) { struct re_backref_cache_entry *ent = mctx->bkref_ents + bkref_idx; do { Idx dst; int cpos; if (ent->node != node) continue; if (subexp_idx < BITSET_WORD_BITS && !(ent->eps_reachable_subexps_map & ((bitset_word_t) 1 << subexp_idx))) continue; /* Recurse trying to reach the OP_OPEN_SUBEXP and OP_CLOSE_SUBEXP cases below. But, if the destination node is the same node as the source node, don't recurse because it would cause an infinite loop: a regex that exhibits this behavior is ()\1*\1* */ dst = dfa->edests[node].elems[0]; if (dst == from_node) { if (boundaries & 1) return -1; else /* if (boundaries & 2) */ return 0; } cpos = check_dst_limits_calc_pos_1 (mctx, boundaries, subexp_idx, dst, bkref_idx); if (cpos == -1 /* && (boundaries & 1) */) return -1; if (cpos == 0 && (boundaries & 2)) return 0; if (subexp_idx < BITSET_WORD_BITS) ent->eps_reachable_subexps_map &= ~((bitset_word_t) 1 << subexp_idx); } while (ent++->more); } break; case OP_OPEN_SUBEXP: if ((boundaries & 1) && subexp_idx == dfa->nodes[node].opr.idx) return -1; break; case OP_CLOSE_SUBEXP: if ((boundaries & 2) && subexp_idx == dfa->nodes[node].opr.idx) return 0; break; default: break; } } return (boundaries & 2) ? 1 : 0; } static int internal_function check_dst_limits_calc_pos (const re_match_context_t *mctx, Idx limit, Idx subexp_idx, Idx from_node, Idx str_idx, Idx bkref_idx) { struct re_backref_cache_entry *lim = mctx->bkref_ents + limit; int boundaries; /* If we are outside the range of the subexpression, return -1 or 1. */ if (str_idx < lim->subexp_from) return -1; if (lim->subexp_to < str_idx) return 1; /* If we are within the subexpression, return 0. */ boundaries = (str_idx == lim->subexp_from); boundaries |= (str_idx == lim->subexp_to) << 1; if (boundaries == 0) return 0; /* Else, examine epsilon closure. */ return check_dst_limits_calc_pos_1 (mctx, boundaries, subexp_idx, from_node, bkref_idx); } /* Check the limitations of sub expressions LIMITS, and remove the nodes which are against limitations from DEST_NODES. */ static reg_errcode_t internal_function check_subexp_limits (const re_dfa_t *dfa, re_node_set *dest_nodes, const re_node_set *candidates, re_node_set *limits, struct re_backref_cache_entry *bkref_ents, Idx str_idx) { reg_errcode_t err; Idx node_idx, lim_idx; for (lim_idx = 0; lim_idx < limits->nelem; ++lim_idx) { Idx subexp_idx; struct re_backref_cache_entry *ent; ent = bkref_ents + limits->elems[lim_idx]; if (str_idx <= ent->subexp_from || ent->str_idx < str_idx) continue; /* This is unrelated limitation. */ subexp_idx = dfa->nodes[ent->node].opr.idx; if (ent->subexp_to == str_idx) { Idx ops_node = -1; Idx cls_node = -1; for (node_idx = 0; node_idx < dest_nodes->nelem; ++node_idx) { Idx node = dest_nodes->elems[node_idx]; re_token_type_t type = dfa->nodes[node].type; if (type == OP_OPEN_SUBEXP && subexp_idx == dfa->nodes[node].opr.idx) ops_node = node; else if (type == OP_CLOSE_SUBEXP && subexp_idx == dfa->nodes[node].opr.idx) cls_node = node; } /* Check the limitation of the open subexpression. */ /* Note that (ent->subexp_to = str_idx != ent->subexp_from). */ if (ops_node >= 0) { err = sub_epsilon_src_nodes (dfa, ops_node, dest_nodes, candidates); if (BE (err != REG_NOERROR, 0)) return err; } /* Check the limitation of the close subexpression. */ if (cls_node >= 0) for (node_idx = 0; node_idx < dest_nodes->nelem; ++node_idx) { Idx node = dest_nodes->elems[node_idx]; if (!re_node_set_contains (dfa->inveclosures + node, cls_node) && !re_node_set_contains (dfa->eclosures + node, cls_node)) { /* It is against this limitation. Remove it form the current sifted state. */ err = sub_epsilon_src_nodes (dfa, node, dest_nodes, candidates); if (BE (err != REG_NOERROR, 0)) return err; --node_idx; } } } else /* (ent->subexp_to != str_idx) */ { for (node_idx = 0; node_idx < dest_nodes->nelem; ++node_idx) { Idx node = dest_nodes->elems[node_idx]; re_token_type_t type = dfa->nodes[node].type; if (type == OP_CLOSE_SUBEXP || type == OP_OPEN_SUBEXP) { if (subexp_idx != dfa->nodes[node].opr.idx) continue; /* It is against this limitation. Remove it form the current sifted state. */ err = sub_epsilon_src_nodes (dfa, node, dest_nodes, candidates); if (BE (err != REG_NOERROR, 0)) return err; } } } } return REG_NOERROR; } static reg_errcode_t internal_function __attribute_warn_unused_result__ sift_states_bkref (const re_match_context_t *mctx, re_sift_context_t *sctx, Idx str_idx, const re_node_set *candidates) { const re_dfa_t *const dfa = mctx->dfa; reg_errcode_t err; Idx node_idx, node; re_sift_context_t local_sctx; Idx first_idx = search_cur_bkref_entry (mctx, str_idx); if (first_idx == -1) return REG_NOERROR; local_sctx.sifted_states = NULL; /* Mark that it hasn't been initialized. */ for (node_idx = 0; node_idx < candidates->nelem; ++node_idx) { Idx enabled_idx; re_token_type_t type; struct re_backref_cache_entry *entry; node = candidates->elems[node_idx]; type = dfa->nodes[node].type; /* Avoid infinite loop for the REs like "()\1+". */ if (node == sctx->last_node && str_idx == sctx->last_str_idx) continue; if (type != OP_BACK_REF) continue; entry = mctx->bkref_ents + first_idx; enabled_idx = first_idx; do { Idx subexp_len; Idx to_idx; Idx dst_node; bool ok; re_dfastate_t *cur_state; if (entry->node != node) continue; subexp_len = entry->subexp_to - entry->subexp_from; to_idx = str_idx + subexp_len; dst_node = (subexp_len ? dfa->nexts[node] : dfa->edests[node].elems[0]); if (to_idx > sctx->last_str_idx || sctx->sifted_states[to_idx] == NULL || !STATE_NODE_CONTAINS (sctx->sifted_states[to_idx], dst_node) || check_dst_limits (mctx, &sctx->limits, node, str_idx, dst_node, to_idx)) continue; if (local_sctx.sifted_states == NULL) { local_sctx = *sctx; err = re_node_set_init_copy (&local_sctx.limits, &sctx->limits); if (BE (err != REG_NOERROR, 0)) goto free_return; } local_sctx.last_node = node; local_sctx.last_str_idx = str_idx; ok = re_node_set_insert (&local_sctx.limits, enabled_idx); if (BE (! ok, 0)) { err = REG_ESPACE; goto free_return; } cur_state = local_sctx.sifted_states[str_idx]; err = sift_states_backward (mctx, &local_sctx); if (BE (err != REG_NOERROR, 0)) goto free_return; if (sctx->limited_states != NULL) { err = merge_state_array (dfa, sctx->limited_states, local_sctx.sifted_states, str_idx + 1); if (BE (err != REG_NOERROR, 0)) goto free_return; } local_sctx.sifted_states[str_idx] = cur_state; re_node_set_remove (&local_sctx.limits, enabled_idx); /* mctx->bkref_ents may have changed, reload the pointer. */ entry = mctx->bkref_ents + enabled_idx; } while (enabled_idx++, entry++->more); } err = REG_NOERROR; free_return: if (local_sctx.sifted_states != NULL) { re_node_set_free (&local_sctx.limits); } return err; } #ifdef RE_ENABLE_I18N static int internal_function sift_states_iter_mb (const re_match_context_t *mctx, re_sift_context_t *sctx, Idx node_idx, Idx str_idx, Idx max_str_idx) { const re_dfa_t *const dfa = mctx->dfa; int naccepted; /* Check the node can accept "multi byte". */ naccepted = check_node_accept_bytes (dfa, node_idx, &mctx->input, str_idx); if (naccepted > 0 && str_idx + naccepted <= max_str_idx && !STATE_NODE_CONTAINS (sctx->sifted_states[str_idx + naccepted], dfa->nexts[node_idx])) /* The node can't accept the "multi byte", or the destination was already thrown away, then the node couldn't accept the current input "multi byte". */ naccepted = 0; /* Otherwise, it is sure that the node could accept 'naccepted' bytes input. */ return naccepted; } #endif /* RE_ENABLE_I18N */ /* Functions for state transition. */ /* Return the next state to which the current state STATE will transit by accepting the current input byte, and update STATE_LOG if necessary. If STATE can accept a multibyte char/collating element/back reference update the destination of STATE_LOG. */ static re_dfastate_t * internal_function __attribute_warn_unused_result__ transit_state (reg_errcode_t *err, re_match_context_t *mctx, re_dfastate_t *state) { re_dfastate_t **trtable; unsigned char ch; #ifdef RE_ENABLE_I18N /* If the current state can accept multibyte. */ if (BE (state->accept_mb, 0)) { *err = transit_state_mb (mctx, state); if (BE (*err != REG_NOERROR, 0)) return NULL; } #endif /* RE_ENABLE_I18N */ /* Then decide the next state with the single byte. */ #if 0 if (0) /* don't use transition table */ return transit_state_sb (err, mctx, state); #endif /* Use transition table */ ch = re_string_fetch_byte (&mctx->input); for (;;) { trtable = state->trtable; if (BE (trtable != NULL, 1)) return trtable[ch]; trtable = state->word_trtable; if (BE (trtable != NULL, 1)) { unsigned int context; context = re_string_context_at (&mctx->input, re_string_cur_idx (&mctx->input) - 1, mctx->eflags); if (IS_WORD_CONTEXT (context)) return trtable[ch + SBC_MAX]; else return trtable[ch]; } if (!build_trtable (mctx->dfa, state)) { *err = REG_ESPACE; return NULL; } /* Retry, we now have a transition table. */ } } /* Update the state_log if we need */ static re_dfastate_t * internal_function merge_state_with_log (reg_errcode_t *err, re_match_context_t *mctx, re_dfastate_t *next_state) { const re_dfa_t *const dfa = mctx->dfa; Idx cur_idx = re_string_cur_idx (&mctx->input); if (cur_idx > mctx->state_log_top) { mctx->state_log[cur_idx] = next_state; mctx->state_log_top = cur_idx; } else if (mctx->state_log[cur_idx] == 0) { mctx->state_log[cur_idx] = next_state; } else { re_dfastate_t *pstate; unsigned int context; re_node_set next_nodes, *log_nodes, *table_nodes = NULL; /* If (state_log[cur_idx] != 0), it implies that cur_idx is the destination of a multibyte char/collating element/ back reference. Then the next state is the union set of these destinations and the results of the transition table. */ pstate = mctx->state_log[cur_idx]; log_nodes = pstate->entrance_nodes; if (next_state != NULL) { table_nodes = next_state->entrance_nodes; *err = re_node_set_init_union (&next_nodes, table_nodes, log_nodes); if (BE (*err != REG_NOERROR, 0)) return NULL; } else next_nodes = *log_nodes; /* Note: We already add the nodes of the initial state, then we don't need to add them here. */ context = re_string_context_at (&mctx->input, re_string_cur_idx (&mctx->input) - 1, mctx->eflags); next_state = mctx->state_log[cur_idx] = re_acquire_state_context (err, dfa, &next_nodes, context); /* We don't need to check errors here, since the return value of this function is next_state and ERR is already set. */ if (table_nodes != NULL) re_node_set_free (&next_nodes); } if (BE (dfa->nbackref, 0) && next_state != NULL) { /* Check OP_OPEN_SUBEXP in the current state in case that we use them later. We must check them here, since the back references in the next state might use them. */ *err = check_subexp_matching_top (mctx, &next_state->nodes, cur_idx); if (BE (*err != REG_NOERROR, 0)) return NULL; /* If the next state has back references. */ if (next_state->has_backref) { *err = transit_state_bkref (mctx, &next_state->nodes); if (BE (*err != REG_NOERROR, 0)) return NULL; next_state = mctx->state_log[cur_idx]; } } return next_state; } /* Skip bytes in the input that correspond to part of a multi-byte match, then look in the log for a state from which to restart matching. */ static re_dfastate_t * internal_function find_recover_state (reg_errcode_t *err, re_match_context_t *mctx) { re_dfastate_t *cur_state; do { Idx max = mctx->state_log_top; Idx cur_str_idx = re_string_cur_idx (&mctx->input); do { if (++cur_str_idx > max) return NULL; re_string_skip_bytes (&mctx->input, 1); } while (mctx->state_log[cur_str_idx] == NULL); cur_state = merge_state_with_log (err, mctx, NULL); } while (*err == REG_NOERROR && cur_state == NULL); return cur_state; } /* Helper functions for transit_state. */ /* From the node set CUR_NODES, pick up the nodes whose types are OP_OPEN_SUBEXP and which have corresponding back references in the regular expression. And register them to use them later for evaluating the corresponding back references. */ static reg_errcode_t internal_function check_subexp_matching_top (re_match_context_t *mctx, re_node_set *cur_nodes, Idx str_idx) { const re_dfa_t *const dfa = mctx->dfa; Idx node_idx; reg_errcode_t err; /* TODO: This isn't efficient. Because there might be more than one nodes whose types are OP_OPEN_SUBEXP and whose index is SUBEXP_IDX, we must check all nodes. E.g. RE: (a){2} */ for (node_idx = 0; node_idx < cur_nodes->nelem; ++node_idx) { Idx node = cur_nodes->elems[node_idx]; if (dfa->nodes[node].type == OP_OPEN_SUBEXP && dfa->nodes[node].opr.idx < BITSET_WORD_BITS && (dfa->used_bkref_map & ((bitset_word_t) 1 << dfa->nodes[node].opr.idx))) { err = match_ctx_add_subtop (mctx, node, str_idx); if (BE (err != REG_NOERROR, 0)) return err; } } return REG_NOERROR; } #if 0 /* Return the next state to which the current state STATE will transit by accepting the current input byte. */ static re_dfastate_t * transit_state_sb (reg_errcode_t *err, re_match_context_t *mctx, re_dfastate_t *state) { const re_dfa_t *const dfa = mctx->dfa; re_node_set next_nodes; re_dfastate_t *next_state; Idx node_cnt, cur_str_idx = re_string_cur_idx (&mctx->input); unsigned int context; *err = re_node_set_alloc (&next_nodes, state->nodes.nelem + 1); if (BE (*err != REG_NOERROR, 0)) return NULL; for (node_cnt = 0; node_cnt < state->nodes.nelem; ++node_cnt) { Idx cur_node = state->nodes.elems[node_cnt]; if (check_node_accept (mctx, dfa->nodes + cur_node, cur_str_idx)) { *err = re_node_set_merge (&next_nodes, dfa->eclosures + dfa->nexts[cur_node]); if (BE (*err != REG_NOERROR, 0)) { re_node_set_free (&next_nodes); return NULL; } } } context = re_string_context_at (&mctx->input, cur_str_idx, mctx->eflags); next_state = re_acquire_state_context (err, dfa, &next_nodes, context); /* We don't need to check errors here, since the return value of this function is next_state and ERR is already set. */ re_node_set_free (&next_nodes); re_string_skip_bytes (&mctx->input, 1); return next_state; } #endif #ifdef RE_ENABLE_I18N static reg_errcode_t internal_function transit_state_mb (re_match_context_t *mctx, re_dfastate_t *pstate) { const re_dfa_t *const dfa = mctx->dfa; reg_errcode_t err; Idx i; for (i = 0; i < pstate->nodes.nelem; ++i) { re_node_set dest_nodes, *new_nodes; Idx cur_node_idx = pstate->nodes.elems[i]; int naccepted; Idx dest_idx; unsigned int context; re_dfastate_t *dest_state; if (!dfa->nodes[cur_node_idx].accept_mb) continue; if (dfa->nodes[cur_node_idx].constraint) { context = re_string_context_at (&mctx->input, re_string_cur_idx (&mctx->input), mctx->eflags); if (NOT_SATISFY_NEXT_CONSTRAINT (dfa->nodes[cur_node_idx].constraint, context)) continue; } /* How many bytes the node can accept? */ naccepted = check_node_accept_bytes (dfa, cur_node_idx, &mctx->input, re_string_cur_idx (&mctx->input)); if (naccepted == 0) continue; /* The node can accepts 'naccepted' bytes. */ dest_idx = re_string_cur_idx (&mctx->input) + naccepted; mctx->max_mb_elem_len = ((mctx->max_mb_elem_len < naccepted) ? naccepted : mctx->max_mb_elem_len); err = clean_state_log_if_needed (mctx, dest_idx); if (BE (err != REG_NOERROR, 0)) return err; #ifdef DEBUG assert (dfa->nexts[cur_node_idx] != -1); #endif new_nodes = dfa->eclosures + dfa->nexts[cur_node_idx]; dest_state = mctx->state_log[dest_idx]; if (dest_state == NULL) dest_nodes = *new_nodes; else { err = re_node_set_init_union (&dest_nodes, dest_state->entrance_nodes, new_nodes); if (BE (err != REG_NOERROR, 0)) return err; } context = re_string_context_at (&mctx->input, dest_idx - 1, mctx->eflags); mctx->state_log[dest_idx] = re_acquire_state_context (&err, dfa, &dest_nodes, context); if (dest_state != NULL) re_node_set_free (&dest_nodes); if (BE (mctx->state_log[dest_idx] == NULL && err != REG_NOERROR, 0)) return err; } return REG_NOERROR; } #endif /* RE_ENABLE_I18N */ static reg_errcode_t internal_function transit_state_bkref (re_match_context_t *mctx, const re_node_set *nodes) { const re_dfa_t *const dfa = mctx->dfa; reg_errcode_t err; Idx i; Idx cur_str_idx = re_string_cur_idx (&mctx->input); for (i = 0; i < nodes->nelem; ++i) { Idx dest_str_idx, prev_nelem, bkc_idx; Idx node_idx = nodes->elems[i]; unsigned int context; const re_token_t *node = dfa->nodes + node_idx; re_node_set *new_dest_nodes; /* Check whether 'node' is a backreference or not. */ if (node->type != OP_BACK_REF) continue; if (node->constraint) { context = re_string_context_at (&mctx->input, cur_str_idx, mctx->eflags); if (NOT_SATISFY_NEXT_CONSTRAINT (node->constraint, context)) continue; } /* 'node' is a backreference. Check the substring which the substring matched. */ bkc_idx = mctx->nbkref_ents; err = get_subexp (mctx, node_idx, cur_str_idx); if (BE (err != REG_NOERROR, 0)) goto free_return; /* And add the epsilon closures (which is 'new_dest_nodes') of the backreference to appropriate state_log. */ #ifdef DEBUG assert (dfa->nexts[node_idx] != -1); #endif for (; bkc_idx < mctx->nbkref_ents; ++bkc_idx) { Idx subexp_len; re_dfastate_t *dest_state; struct re_backref_cache_entry *bkref_ent; bkref_ent = mctx->bkref_ents + bkc_idx; if (bkref_ent->node != node_idx || bkref_ent->str_idx != cur_str_idx) continue; subexp_len = bkref_ent->subexp_to - bkref_ent->subexp_from; new_dest_nodes = (subexp_len == 0 ? dfa->eclosures + dfa->edests[node_idx].elems[0] : dfa->eclosures + dfa->nexts[node_idx]); dest_str_idx = (cur_str_idx + bkref_ent->subexp_to - bkref_ent->subexp_from); context = re_string_context_at (&mctx->input, dest_str_idx - 1, mctx->eflags); dest_state = mctx->state_log[dest_str_idx]; prev_nelem = ((mctx->state_log[cur_str_idx] == NULL) ? 0 : mctx->state_log[cur_str_idx]->nodes.nelem); /* Add 'new_dest_node' to state_log. */ if (dest_state == NULL) { mctx->state_log[dest_str_idx] = re_acquire_state_context (&err, dfa, new_dest_nodes, context); if (BE (mctx->state_log[dest_str_idx] == NULL && err != REG_NOERROR, 0)) goto free_return; } else { re_node_set dest_nodes; err = re_node_set_init_union (&dest_nodes, dest_state->entrance_nodes, new_dest_nodes); if (BE (err != REG_NOERROR, 0)) { re_node_set_free (&dest_nodes); goto free_return; } mctx->state_log[dest_str_idx] = re_acquire_state_context (&err, dfa, &dest_nodes, context); re_node_set_free (&dest_nodes); if (BE (mctx->state_log[dest_str_idx] == NULL && err != REG_NOERROR, 0)) goto free_return; } /* We need to check recursively if the backreference can epsilon transit. */ if (subexp_len == 0 && mctx->state_log[cur_str_idx]->nodes.nelem > prev_nelem) { err = check_subexp_matching_top (mctx, new_dest_nodes, cur_str_idx); if (BE (err != REG_NOERROR, 0)) goto free_return; err = transit_state_bkref (mctx, new_dest_nodes); if (BE (err != REG_NOERROR, 0)) goto free_return; } } } err = REG_NOERROR; free_return: return err; } /* Enumerate all the candidates which the backreference BKREF_NODE can match at BKREF_STR_IDX, and register them by match_ctx_add_entry(). Note that we might collect inappropriate candidates here. However, the cost of checking them strictly here is too high, then we delay these checking for prune_impossible_nodes(). */ static reg_errcode_t internal_function __attribute_warn_unused_result__ get_subexp (re_match_context_t *mctx, Idx bkref_node, Idx bkref_str_idx) { const re_dfa_t *const dfa = mctx->dfa; Idx subexp_num, sub_top_idx; const char *buf = (const char *) re_string_get_buffer (&mctx->input); /* Return if we have already checked BKREF_NODE at BKREF_STR_IDX. */ Idx cache_idx = search_cur_bkref_entry (mctx, bkref_str_idx); if (cache_idx != -1) { const struct re_backref_cache_entry *entry = mctx->bkref_ents + cache_idx; do if (entry->node == bkref_node) return REG_NOERROR; /* We already checked it. */ while (entry++->more); } subexp_num = dfa->nodes[bkref_node].opr.idx; /* For each sub expression */ for (sub_top_idx = 0; sub_top_idx < mctx->nsub_tops; ++sub_top_idx) { reg_errcode_t err; re_sub_match_top_t *sub_top = mctx->sub_tops[sub_top_idx]; re_sub_match_last_t *sub_last; Idx sub_last_idx, sl_str, bkref_str_off; if (dfa->nodes[sub_top->node].opr.idx != subexp_num) continue; /* It isn't related. */ sl_str = sub_top->str_idx; bkref_str_off = bkref_str_idx; /* At first, check the last node of sub expressions we already evaluated. */ for (sub_last_idx = 0; sub_last_idx < sub_top->nlasts; ++sub_last_idx) { regoff_t sl_str_diff; sub_last = sub_top->lasts[sub_last_idx]; sl_str_diff = sub_last->str_idx - sl_str; /* The matched string by the sub expression match with the substring at the back reference? */ if (sl_str_diff > 0) { if (BE (bkref_str_off + sl_str_diff > mctx->input.valid_len, 0)) { /* Not enough chars for a successful match. */ if (bkref_str_off + sl_str_diff > mctx->input.len) break; err = clean_state_log_if_needed (mctx, bkref_str_off + sl_str_diff); if (BE (err != REG_NOERROR, 0)) return err; buf = (const char *) re_string_get_buffer (&mctx->input); } if (memcmp (buf + bkref_str_off, buf + sl_str, sl_str_diff) != 0) /* We don't need to search this sub expression any more. */ break; } bkref_str_off += sl_str_diff; sl_str += sl_str_diff; err = get_subexp_sub (mctx, sub_top, sub_last, bkref_node, bkref_str_idx); /* Reload buf, since the preceding call might have reallocated the buffer. */ buf = (const char *) re_string_get_buffer (&mctx->input); if (err == REG_NOMATCH) continue; if (BE (err != REG_NOERROR, 0)) return err; } if (sub_last_idx < sub_top->nlasts) continue; if (sub_last_idx > 0) ++sl_str; /* Then, search for the other last nodes of the sub expression. */ for (; sl_str <= bkref_str_idx; ++sl_str) { Idx cls_node; regoff_t sl_str_off; const re_node_set *nodes; sl_str_off = sl_str - sub_top->str_idx; /* The matched string by the sub expression match with the substring at the back reference? */ if (sl_str_off > 0) { if (BE (bkref_str_off >= mctx->input.valid_len, 0)) { /* If we are at the end of the input, we cannot match. */ if (bkref_str_off >= mctx->input.len) break; err = extend_buffers (mctx, bkref_str_off + 1); if (BE (err != REG_NOERROR, 0)) return err; buf = (const char *) re_string_get_buffer (&mctx->input); } if (buf [bkref_str_off++] != buf[sl_str - 1]) break; /* We don't need to search this sub expression any more. */ } if (mctx->state_log[sl_str] == NULL) continue; /* Does this state have a ')' of the sub expression? */ nodes = &mctx->state_log[sl_str]->nodes; cls_node = find_subexp_node (dfa, nodes, subexp_num, OP_CLOSE_SUBEXP); if (cls_node == -1) continue; /* No. */ if (sub_top->path == NULL) { sub_top->path = calloc (sizeof (state_array_t), sl_str - sub_top->str_idx + 1); if (sub_top->path == NULL) return REG_ESPACE; } /* Can the OP_OPEN_SUBEXP node arrive the OP_CLOSE_SUBEXP node in the current context? */ err = check_arrival (mctx, sub_top->path, sub_top->node, sub_top->str_idx, cls_node, sl_str, OP_CLOSE_SUBEXP); if (err == REG_NOMATCH) continue; if (BE (err != REG_NOERROR, 0)) return err; sub_last = match_ctx_add_sublast (sub_top, cls_node, sl_str); if (BE (sub_last == NULL, 0)) return REG_ESPACE; err = get_subexp_sub (mctx, sub_top, sub_last, bkref_node, bkref_str_idx); if (err == REG_NOMATCH) continue; } } return REG_NOERROR; } /* Helper functions for get_subexp(). */ /* Check SUB_LAST can arrive to the back reference BKREF_NODE at BKREF_STR. If it can arrive, register the sub expression expressed with SUB_TOP and SUB_LAST. */ static reg_errcode_t internal_function get_subexp_sub (re_match_context_t *mctx, const re_sub_match_top_t *sub_top, re_sub_match_last_t *sub_last, Idx bkref_node, Idx bkref_str) { reg_errcode_t err; Idx to_idx; /* Can the subexpression arrive the back reference? */ err = check_arrival (mctx, &sub_last->path, sub_last->node, sub_last->str_idx, bkref_node, bkref_str, OP_OPEN_SUBEXP); if (err != REG_NOERROR) return err; err = match_ctx_add_entry (mctx, bkref_node, bkref_str, sub_top->str_idx, sub_last->str_idx); if (BE (err != REG_NOERROR, 0)) return err; to_idx = bkref_str + sub_last->str_idx - sub_top->str_idx; return clean_state_log_if_needed (mctx, to_idx); } /* Find the first node which is '(' or ')' and whose index is SUBEXP_IDX. Search '(' if FL_OPEN, or search ')' otherwise. TODO: This function isn't efficient... Because there might be more than one nodes whose types are OP_OPEN_SUBEXP and whose index is SUBEXP_IDX, we must check all nodes. E.g. RE: (a){2} */ static Idx internal_function find_subexp_node (const re_dfa_t *dfa, const re_node_set *nodes, Idx subexp_idx, int type) { Idx cls_idx; for (cls_idx = 0; cls_idx < nodes->nelem; ++cls_idx) { Idx cls_node = nodes->elems[cls_idx]; const re_token_t *node = dfa->nodes + cls_node; if (node->type == type && node->opr.idx == subexp_idx) return cls_node; } return -1; } /* Check whether the node TOP_NODE at TOP_STR can arrive to the node LAST_NODE at LAST_STR. We record the path onto PATH since it will be heavily reused. Return REG_NOERROR if it can arrive, or REG_NOMATCH otherwise. */ static reg_errcode_t internal_function __attribute_warn_unused_result__ check_arrival (re_match_context_t *mctx, state_array_t *path, Idx top_node, Idx top_str, Idx last_node, Idx last_str, int type) { const re_dfa_t *const dfa = mctx->dfa; reg_errcode_t err = REG_NOERROR; Idx subexp_num, backup_cur_idx, str_idx, null_cnt; re_dfastate_t *cur_state = NULL; re_node_set *cur_nodes, next_nodes; re_dfastate_t **backup_state_log; unsigned int context; subexp_num = dfa->nodes[top_node].opr.idx; /* Extend the buffer if we need. */ if (BE (path->alloc < last_str + mctx->max_mb_elem_len + 1, 0)) { re_dfastate_t **new_array; Idx old_alloc = path->alloc; Idx incr_alloc = last_str + mctx->max_mb_elem_len + 1; Idx new_alloc; if (BE (IDX_MAX - old_alloc < incr_alloc, 0)) return REG_ESPACE; new_alloc = old_alloc + incr_alloc; if (BE (SIZE_MAX / sizeof (re_dfastate_t *) < new_alloc, 0)) return REG_ESPACE; new_array = re_realloc (path->array, re_dfastate_t *, new_alloc); if (BE (new_array == NULL, 0)) return REG_ESPACE; path->array = new_array; path->alloc = new_alloc; memset (new_array + old_alloc, '\0', sizeof (re_dfastate_t *) * (path->alloc - old_alloc)); } str_idx = path->next_idx ? path->next_idx : top_str; /* Temporary modify MCTX. */ backup_state_log = mctx->state_log; backup_cur_idx = mctx->input.cur_idx; mctx->state_log = path->array; mctx->input.cur_idx = str_idx; /* Setup initial node set. */ context = re_string_context_at (&mctx->input, str_idx - 1, mctx->eflags); if (str_idx == top_str) { err = re_node_set_init_1 (&next_nodes, top_node); if (BE (err != REG_NOERROR, 0)) return err; err = check_arrival_expand_ecl (dfa, &next_nodes, subexp_num, type); if (BE (err != REG_NOERROR, 0)) { re_node_set_free (&next_nodes); return err; } } else { cur_state = mctx->state_log[str_idx]; if (cur_state && cur_state->has_backref) { err = re_node_set_init_copy (&next_nodes, &cur_state->nodes); if (BE (err != REG_NOERROR, 0)) return err; } else re_node_set_init_empty (&next_nodes); } if (str_idx == top_str || (cur_state && cur_state->has_backref)) { if (next_nodes.nelem) { err = expand_bkref_cache (mctx, &next_nodes, str_idx, subexp_num, type); if (BE (err != REG_NOERROR, 0)) { re_node_set_free (&next_nodes); return err; } } cur_state = re_acquire_state_context (&err, dfa, &next_nodes, context); if (BE (cur_state == NULL && err != REG_NOERROR, 0)) { re_node_set_free (&next_nodes); return err; } mctx->state_log[str_idx] = cur_state; } for (null_cnt = 0; str_idx < last_str && null_cnt <= mctx->max_mb_elem_len;) { re_node_set_empty (&next_nodes); if (mctx->state_log[str_idx + 1]) { err = re_node_set_merge (&next_nodes, &mctx->state_log[str_idx + 1]->nodes); if (BE (err != REG_NOERROR, 0)) { re_node_set_free (&next_nodes); return err; } } if (cur_state) { err = check_arrival_add_next_nodes (mctx, str_idx, &cur_state->non_eps_nodes, &next_nodes); if (BE (err != REG_NOERROR, 0)) { re_node_set_free (&next_nodes); return err; } } ++str_idx; if (next_nodes.nelem) { err = check_arrival_expand_ecl (dfa, &next_nodes, subexp_num, type); if (BE (err != REG_NOERROR, 0)) { re_node_set_free (&next_nodes); return err; } err = expand_bkref_cache (mctx, &next_nodes, str_idx, subexp_num, type); if (BE (err != REG_NOERROR, 0)) { re_node_set_free (&next_nodes); return err; } } context = re_string_context_at (&mctx->input, str_idx - 1, mctx->eflags); cur_state = re_acquire_state_context (&err, dfa, &next_nodes, context); if (BE (cur_state == NULL && err != REG_NOERROR, 0)) { re_node_set_free (&next_nodes); return err; } mctx->state_log[str_idx] = cur_state; null_cnt = cur_state == NULL ? null_cnt + 1 : 0; } re_node_set_free (&next_nodes); cur_nodes = (mctx->state_log[last_str] == NULL ? NULL : &mctx->state_log[last_str]->nodes); path->next_idx = str_idx; /* Fix MCTX. */ mctx->state_log = backup_state_log; mctx->input.cur_idx = backup_cur_idx; /* Then check the current node set has the node LAST_NODE. */ if (cur_nodes != NULL && re_node_set_contains (cur_nodes, last_node)) return REG_NOERROR; return REG_NOMATCH; } /* Helper functions for check_arrival. */ /* Calculate the destination nodes of CUR_NODES at STR_IDX, and append them to NEXT_NODES. TODO: This function is similar to the functions transit_state*(), however this function has many additional works. Can't we unify them? */ static reg_errcode_t internal_function __attribute_warn_unused_result__ check_arrival_add_next_nodes (re_match_context_t *mctx, Idx str_idx, re_node_set *cur_nodes, re_node_set *next_nodes) { const re_dfa_t *const dfa = mctx->dfa; bool ok; Idx cur_idx; #ifdef RE_ENABLE_I18N reg_errcode_t err = REG_NOERROR; #endif re_node_set union_set; re_node_set_init_empty (&union_set); for (cur_idx = 0; cur_idx < cur_nodes->nelem; ++cur_idx) { int naccepted = 0; Idx cur_node = cur_nodes->elems[cur_idx]; #ifdef DEBUG re_token_type_t type = dfa->nodes[cur_node].type; assert (!IS_EPSILON_NODE (type)); #endif #ifdef RE_ENABLE_I18N /* If the node may accept "multi byte". */ if (dfa->nodes[cur_node].accept_mb) { naccepted = check_node_accept_bytes (dfa, cur_node, &mctx->input, str_idx); if (naccepted > 1) { re_dfastate_t *dest_state; Idx next_node = dfa->nexts[cur_node]; Idx next_idx = str_idx + naccepted; dest_state = mctx->state_log[next_idx]; re_node_set_empty (&union_set); if (dest_state) { err = re_node_set_merge (&union_set, &dest_state->nodes); if (BE (err != REG_NOERROR, 0)) { re_node_set_free (&union_set); return err; } } ok = re_node_set_insert (&union_set, next_node); if (BE (! ok, 0)) { re_node_set_free (&union_set); return REG_ESPACE; } mctx->state_log[next_idx] = re_acquire_state (&err, dfa, &union_set); if (BE (mctx->state_log[next_idx] == NULL && err != REG_NOERROR, 0)) { re_node_set_free (&union_set); return err; } } } #endif /* RE_ENABLE_I18N */ if (naccepted || check_node_accept (mctx, dfa->nodes + cur_node, str_idx)) { ok = re_node_set_insert (next_nodes, dfa->nexts[cur_node]); if (BE (! ok, 0)) { re_node_set_free (&union_set); return REG_ESPACE; } } } re_node_set_free (&union_set); return REG_NOERROR; } /* For all the nodes in CUR_NODES, add the epsilon closures of them to CUR_NODES, however exclude the nodes which are: - inside the sub expression whose number is EX_SUBEXP, if FL_OPEN. - out of the sub expression whose number is EX_SUBEXP, if !FL_OPEN. */ static reg_errcode_t internal_function check_arrival_expand_ecl (const re_dfa_t *dfa, re_node_set *cur_nodes, Idx ex_subexp, int type) { reg_errcode_t err; Idx idx, outside_node; re_node_set new_nodes; #ifdef DEBUG assert (cur_nodes->nelem); #endif err = re_node_set_alloc (&new_nodes, cur_nodes->nelem); if (BE (err != REG_NOERROR, 0)) return err; /* Create a new node set NEW_NODES with the nodes which are epsilon closures of the node in CUR_NODES. */ for (idx = 0; idx < cur_nodes->nelem; ++idx) { Idx cur_node = cur_nodes->elems[idx]; const re_node_set *eclosure = dfa->eclosures + cur_node; outside_node = find_subexp_node (dfa, eclosure, ex_subexp, type); if (outside_node == -1) { /* There are no problematic nodes, just merge them. */ err = re_node_set_merge (&new_nodes, eclosure); if (BE (err != REG_NOERROR, 0)) { re_node_set_free (&new_nodes); return err; } } else { /* There are problematic nodes, re-calculate incrementally. */ err = check_arrival_expand_ecl_sub (dfa, &new_nodes, cur_node, ex_subexp, type); if (BE (err != REG_NOERROR, 0)) { re_node_set_free (&new_nodes); return err; } } } re_node_set_free (cur_nodes); *cur_nodes = new_nodes; return REG_NOERROR; } /* Helper function for check_arrival_expand_ecl. Check incrementally the epsilon closure of TARGET, and if it isn't problematic append it to DST_NODES. */ static reg_errcode_t internal_function __attribute_warn_unused_result__ check_arrival_expand_ecl_sub (const re_dfa_t *dfa, re_node_set *dst_nodes, Idx target, Idx ex_subexp, int type) { Idx cur_node; for (cur_node = target; !re_node_set_contains (dst_nodes, cur_node);) { bool ok; if (dfa->nodes[cur_node].type == type && dfa->nodes[cur_node].opr.idx == ex_subexp) { if (type == OP_CLOSE_SUBEXP) { ok = re_node_set_insert (dst_nodes, cur_node); if (BE (! ok, 0)) return REG_ESPACE; } break; } ok = re_node_set_insert (dst_nodes, cur_node); if (BE (! ok, 0)) return REG_ESPACE; if (dfa->edests[cur_node].nelem == 0) break; if (dfa->edests[cur_node].nelem == 2) { reg_errcode_t err; err = check_arrival_expand_ecl_sub (dfa, dst_nodes, dfa->edests[cur_node].elems[1], ex_subexp, type); if (BE (err != REG_NOERROR, 0)) return err; } cur_node = dfa->edests[cur_node].elems[0]; } return REG_NOERROR; } /* For all the back references in the current state, calculate the destination of the back references by the appropriate entry in MCTX->BKREF_ENTS. */ static reg_errcode_t internal_function __attribute_warn_unused_result__ expand_bkref_cache (re_match_context_t *mctx, re_node_set *cur_nodes, Idx cur_str, Idx subexp_num, int type) { const re_dfa_t *const dfa = mctx->dfa; reg_errcode_t err; Idx cache_idx_start = search_cur_bkref_entry (mctx, cur_str); struct re_backref_cache_entry *ent; if (cache_idx_start == -1) return REG_NOERROR; restart: ent = mctx->bkref_ents + cache_idx_start; do { Idx to_idx, next_node; /* Is this entry ENT is appropriate? */ if (!re_node_set_contains (cur_nodes, ent->node)) continue; /* No. */ to_idx = cur_str + ent->subexp_to - ent->subexp_from; /* Calculate the destination of the back reference, and append it to MCTX->STATE_LOG. */ if (to_idx == cur_str) { /* The backreference did epsilon transit, we must re-check all the node in the current state. */ re_node_set new_dests; reg_errcode_t err2, err3; next_node = dfa->edests[ent->node].elems[0]; if (re_node_set_contains (cur_nodes, next_node)) continue; err = re_node_set_init_1 (&new_dests, next_node); err2 = check_arrival_expand_ecl (dfa, &new_dests, subexp_num, type); err3 = re_node_set_merge (cur_nodes, &new_dests); re_node_set_free (&new_dests); if (BE (err != REG_NOERROR || err2 != REG_NOERROR || err3 != REG_NOERROR, 0)) { err = (err != REG_NOERROR ? err : (err2 != REG_NOERROR ? err2 : err3)); return err; } /* TODO: It is still inefficient... */ goto restart; } else { re_node_set union_set; next_node = dfa->nexts[ent->node]; if (mctx->state_log[to_idx]) { bool ok; if (re_node_set_contains (&mctx->state_log[to_idx]->nodes, next_node)) continue; err = re_node_set_init_copy (&union_set, &mctx->state_log[to_idx]->nodes); ok = re_node_set_insert (&union_set, next_node); if (BE (err != REG_NOERROR || ! ok, 0)) { re_node_set_free (&union_set); err = err != REG_NOERROR ? err : REG_ESPACE; return err; } } else { err = re_node_set_init_1 (&union_set, next_node); if (BE (err != REG_NOERROR, 0)) return err; } mctx->state_log[to_idx] = re_acquire_state (&err, dfa, &union_set); re_node_set_free (&union_set); if (BE (mctx->state_log[to_idx] == NULL && err != REG_NOERROR, 0)) return err; } } while (ent++->more); return REG_NOERROR; } /* Build transition table for the state. Return true if successful. */ static bool internal_function build_trtable (const re_dfa_t *dfa, re_dfastate_t *state) { reg_errcode_t err; Idx i, j; int ch; bool need_word_trtable = false; bitset_word_t elem, mask; bool dests_node_malloced = false; bool dest_states_malloced = false; Idx ndests; /* Number of the destination states from 'state'. */ re_dfastate_t **trtable; re_dfastate_t **dest_states = NULL, **dest_states_word, **dest_states_nl; re_node_set follows, *dests_node; bitset_t *dests_ch; bitset_t acceptable; struct dests_alloc { re_node_set dests_node[SBC_MAX]; bitset_t dests_ch[SBC_MAX]; } *dests_alloc; /* We build DFA states which corresponds to the destination nodes from 'state'. 'dests_node[i]' represents the nodes which i-th destination state contains, and 'dests_ch[i]' represents the characters which i-th destination state accepts. */ if (__libc_use_alloca (sizeof (struct dests_alloc))) dests_alloc = (struct dests_alloc *) alloca (sizeof (struct dests_alloc)); else { dests_alloc = re_malloc (struct dests_alloc, 1); if (BE (dests_alloc == NULL, 0)) return false; dests_node_malloced = true; } dests_node = dests_alloc->dests_node; dests_ch = dests_alloc->dests_ch; /* Initialize transition table. */ state->word_trtable = state->trtable = NULL; /* At first, group all nodes belonging to 'state' into several destinations. */ ndests = group_nodes_into_DFAstates (dfa, state, dests_node, dests_ch); if (BE (ndests <= 0, 0)) { if (dests_node_malloced) free (dests_alloc); /* Return false in case of an error, true otherwise. */ if (ndests == 0) { state->trtable = (re_dfastate_t **) calloc (sizeof (re_dfastate_t *), SBC_MAX); if (BE (state->trtable == NULL, 0)) return false; return true; } return false; } err = re_node_set_alloc (&follows, ndests + 1); if (BE (err != REG_NOERROR, 0)) goto out_free; /* Avoid arithmetic overflow in size calculation. */ if (BE ((((SIZE_MAX - (sizeof (re_node_set) + sizeof (bitset_t)) * SBC_MAX) / (3 * sizeof (re_dfastate_t *))) < ndests), 0)) goto out_free; if (__libc_use_alloca ((sizeof (re_node_set) + sizeof (bitset_t)) * SBC_MAX + ndests * 3 * sizeof (re_dfastate_t *))) dest_states = (re_dfastate_t **) alloca (ndests * 3 * sizeof (re_dfastate_t *)); else { dest_states = (re_dfastate_t **) malloc (ndests * 3 * sizeof (re_dfastate_t *)); if (BE (dest_states == NULL, 0)) { out_free: if (dest_states_malloced) free (dest_states); re_node_set_free (&follows); for (i = 0; i < ndests; ++i) re_node_set_free (dests_node + i); if (dests_node_malloced) free (dests_alloc); return false; } dest_states_malloced = true; } dest_states_word = dest_states + ndests; dest_states_nl = dest_states_word + ndests; bitset_empty (acceptable); /* Then build the states for all destinations. */ for (i = 0; i < ndests; ++i) { Idx next_node; re_node_set_empty (&follows); /* Merge the follows of this destination states. */ for (j = 0; j < dests_node[i].nelem; ++j) { next_node = dfa->nexts[dests_node[i].elems[j]]; if (next_node != -1) { err = re_node_set_merge (&follows, dfa->eclosures + next_node); if (BE (err != REG_NOERROR, 0)) goto out_free; } } dest_states[i] = re_acquire_state_context (&err, dfa, &follows, 0); if (BE (dest_states[i] == NULL && err != REG_NOERROR, 0)) goto out_free; /* If the new state has context constraint, build appropriate states for these contexts. */ if (dest_states[i]->has_constraint) { dest_states_word[i] = re_acquire_state_context (&err, dfa, &follows, CONTEXT_WORD); if (BE (dest_states_word[i] == NULL && err != REG_NOERROR, 0)) goto out_free; if (dest_states[i] != dest_states_word[i] && dfa->mb_cur_max > 1) need_word_trtable = true; dest_states_nl[i] = re_acquire_state_context (&err, dfa, &follows, CONTEXT_NEWLINE); if (BE (dest_states_nl[i] == NULL && err != REG_NOERROR, 0)) goto out_free; } else { dest_states_word[i] = dest_states[i]; dest_states_nl[i] = dest_states[i]; } bitset_merge (acceptable, dests_ch[i]); } if (!BE (need_word_trtable, 0)) { /* We don't care about whether the following character is a word character, or we are in a single-byte character set so we can discern by looking at the character code: allocate a 256-entry transition table. */ trtable = state->trtable = (re_dfastate_t **) calloc (sizeof (re_dfastate_t *), SBC_MAX); if (BE (trtable == NULL, 0)) goto out_free; /* For all characters ch...: */ for (i = 0; i < BITSET_WORDS; ++i) for (ch = i * BITSET_WORD_BITS, elem = acceptable[i], mask = 1; elem; mask <<= 1, elem >>= 1, ++ch) if (BE (elem & 1, 0)) { /* There must be exactly one destination which accepts character ch. See group_nodes_into_DFAstates. */ for (j = 0; (dests_ch[j][i] & mask) == 0; ++j) ; /* j-th destination accepts the word character ch. */ if (dfa->word_char[i] & mask) trtable[ch] = dest_states_word[j]; else trtable[ch] = dest_states[j]; } } else { /* We care about whether the following character is a word character, and we are in a multi-byte character set: discern by looking at the character code: build two 256-entry transition tables, one starting at trtable[0] and one starting at trtable[SBC_MAX]. */ trtable = state->word_trtable = (re_dfastate_t **) calloc (sizeof (re_dfastate_t *), 2 * SBC_MAX); if (BE (trtable == NULL, 0)) goto out_free; /* For all characters ch...: */ for (i = 0; i < BITSET_WORDS; ++i) for (ch = i * BITSET_WORD_BITS, elem = acceptable[i], mask = 1; elem; mask <<= 1, elem >>= 1, ++ch) if (BE (elem & 1, 0)) { /* There must be exactly one destination which accepts character ch. See group_nodes_into_DFAstates. */ for (j = 0; (dests_ch[j][i] & mask) == 0; ++j) ; /* j-th destination accepts the word character ch. */ trtable[ch] = dest_states[j]; trtable[ch + SBC_MAX] = dest_states_word[j]; } } /* new line */ if (bitset_contain (acceptable, NEWLINE_CHAR)) { /* The current state accepts newline character. */ for (j = 0; j < ndests; ++j) if (bitset_contain (dests_ch[j], NEWLINE_CHAR)) { /* k-th destination accepts newline character. */ trtable[NEWLINE_CHAR] = dest_states_nl[j]; if (need_word_trtable) trtable[NEWLINE_CHAR + SBC_MAX] = dest_states_nl[j]; /* There must be only one destination which accepts newline. See group_nodes_into_DFAstates. */ break; } } if (dest_states_malloced) free (dest_states); re_node_set_free (&follows); for (i = 0; i < ndests; ++i) re_node_set_free (dests_node + i); if (dests_node_malloced) free (dests_alloc); return true; } /* Group all nodes belonging to STATE into several destinations. Then for all destinations, set the nodes belonging to the destination to DESTS_NODE[i] and set the characters accepted by the destination to DEST_CH[i]. This function return the number of destinations. */ static Idx internal_function group_nodes_into_DFAstates (const re_dfa_t *dfa, const re_dfastate_t *state, re_node_set *dests_node, bitset_t *dests_ch) { reg_errcode_t err; bool ok; Idx i, j, k; Idx ndests; /* Number of the destinations from 'state'. */ bitset_t accepts; /* Characters a node can accept. */ const re_node_set *cur_nodes = &state->nodes; bitset_empty (accepts); ndests = 0; /* For all the nodes belonging to 'state', */ for (i = 0; i < cur_nodes->nelem; ++i) { re_token_t *node = &dfa->nodes[cur_nodes->elems[i]]; re_token_type_t type = node->type; unsigned int constraint = node->constraint; /* Enumerate all single byte character this node can accept. */ if (type == CHARACTER) bitset_set (accepts, node->opr.c); else if (type == SIMPLE_BRACKET) { bitset_merge (accepts, node->opr.sbcset); } else if (type == OP_PERIOD) { #ifdef RE_ENABLE_I18N if (dfa->mb_cur_max > 1) bitset_merge (accepts, dfa->sb_char); else #endif bitset_set_all (accepts); if (!(dfa->syntax & RE_DOT_NEWLINE)) bitset_clear (accepts, '\n'); if (dfa->syntax & RE_DOT_NOT_NULL) bitset_clear (accepts, '\0'); } #ifdef RE_ENABLE_I18N else if (type == OP_UTF8_PERIOD) { if (ASCII_CHARS % BITSET_WORD_BITS == 0) memset (accepts, -1, ASCII_CHARS / CHAR_BIT); else bitset_merge (accepts, utf8_sb_map); if (!(dfa->syntax & RE_DOT_NEWLINE)) bitset_clear (accepts, '\n'); if (dfa->syntax & RE_DOT_NOT_NULL) bitset_clear (accepts, '\0'); } #endif else continue; /* Check the 'accepts' and sift the characters which are not match it the context. */ if (constraint) { if (constraint & NEXT_NEWLINE_CONSTRAINT) { bool accepts_newline = bitset_contain (accepts, NEWLINE_CHAR); bitset_empty (accepts); if (accepts_newline) bitset_set (accepts, NEWLINE_CHAR); else continue; } if (constraint & NEXT_ENDBUF_CONSTRAINT) { bitset_empty (accepts); continue; } if (constraint & NEXT_WORD_CONSTRAINT) { bitset_word_t any_set = 0; if (type == CHARACTER && !node->word_char) { bitset_empty (accepts); continue; } #ifdef RE_ENABLE_I18N if (dfa->mb_cur_max > 1) for (j = 0; j < BITSET_WORDS; ++j) any_set |= (accepts[j] &= (dfa->word_char[j] | ~dfa->sb_char[j])); else #endif for (j = 0; j < BITSET_WORDS; ++j) any_set |= (accepts[j] &= dfa->word_char[j]); if (!any_set) continue; } if (constraint & NEXT_NOTWORD_CONSTRAINT) { bitset_word_t any_set = 0; if (type == CHARACTER && node->word_char) { bitset_empty (accepts); continue; } #ifdef RE_ENABLE_I18N if (dfa->mb_cur_max > 1) for (j = 0; j < BITSET_WORDS; ++j) any_set |= (accepts[j] &= ~(dfa->word_char[j] & dfa->sb_char[j])); else #endif for (j = 0; j < BITSET_WORDS; ++j) any_set |= (accepts[j] &= ~dfa->word_char[j]); if (!any_set) continue; } } /* Then divide 'accepts' into DFA states, or create a new state. Above, we make sure that accepts is not empty. */ for (j = 0; j < ndests; ++j) { bitset_t intersec; /* Intersection sets, see below. */ bitset_t remains; /* Flags, see below. */ bitset_word_t has_intersec, not_subset, not_consumed; /* Optimization, skip if this state doesn't accept the character. */ if (type == CHARACTER && !bitset_contain (dests_ch[j], node->opr.c)) continue; /* Enumerate the intersection set of this state and 'accepts'. */ has_intersec = 0; for (k = 0; k < BITSET_WORDS; ++k) has_intersec |= intersec[k] = accepts[k] & dests_ch[j][k]; /* And skip if the intersection set is empty. */ if (!has_intersec) continue; /* Then check if this state is a subset of 'accepts'. */ not_subset = not_consumed = 0; for (k = 0; k < BITSET_WORDS; ++k) { not_subset |= remains[k] = ~accepts[k] & dests_ch[j][k]; not_consumed |= accepts[k] = accepts[k] & ~dests_ch[j][k]; } /* If this state isn't a subset of 'accepts', create a new group state, which has the 'remains'. */ if (not_subset) { bitset_copy (dests_ch[ndests], remains); bitset_copy (dests_ch[j], intersec); err = re_node_set_init_copy (dests_node + ndests, &dests_node[j]); if (BE (err != REG_NOERROR, 0)) goto error_return; ++ndests; } /* Put the position in the current group. */ ok = re_node_set_insert (&dests_node[j], cur_nodes->elems[i]); if (BE (! ok, 0)) goto error_return; /* If all characters are consumed, go to next node. */ if (!not_consumed) break; } /* Some characters remain, create a new group. */ if (j == ndests) { bitset_copy (dests_ch[ndests], accepts); err = re_node_set_init_1 (dests_node + ndests, cur_nodes->elems[i]); if (BE (err != REG_NOERROR, 0)) goto error_return; ++ndests; bitset_empty (accepts); } } return ndests; error_return: for (j = 0; j < ndests; ++j) re_node_set_free (dests_node + j); return -1; } #ifdef RE_ENABLE_I18N /* Check how many bytes the node 'dfa->nodes[node_idx]' accepts. Return the number of the bytes the node accepts. STR_IDX is the current index of the input string. This function handles the nodes which can accept one character, or one collating element like '.', '[a-z]', opposite to the other nodes can only accept one byte. */ # ifdef _LIBC # include # endif static int internal_function check_node_accept_bytes (const re_dfa_t *dfa, Idx node_idx, const re_string_t *input, Idx str_idx) { const re_token_t *node = dfa->nodes + node_idx; int char_len, elem_len; Idx i; if (BE (node->type == OP_UTF8_PERIOD, 0)) { unsigned char c = re_string_byte_at (input, str_idx), d; if (BE (c < 0xc2, 1)) return 0; if (str_idx + 2 > input->len) return 0; d = re_string_byte_at (input, str_idx + 1); if (c < 0xe0) return (d < 0x80 || d > 0xbf) ? 0 : 2; else if (c < 0xf0) { char_len = 3; if (c == 0xe0 && d < 0xa0) return 0; } else if (c < 0xf8) { char_len = 4; if (c == 0xf0 && d < 0x90) return 0; } else if (c < 0xfc) { char_len = 5; if (c == 0xf8 && d < 0x88) return 0; } else if (c < 0xfe) { char_len = 6; if (c == 0xfc && d < 0x84) return 0; } else return 0; if (str_idx + char_len > input->len) return 0; for (i = 1; i < char_len; ++i) { d = re_string_byte_at (input, str_idx + i); if (d < 0x80 || d > 0xbf) return 0; } return char_len; } char_len = re_string_char_size_at (input, str_idx); if (node->type == OP_PERIOD) { if (char_len <= 1) return 0; /* FIXME: I don't think this if is needed, as both '\n' and '\0' are char_len == 1. */ /* '.' accepts any one character except the following two cases. */ if ((!(dfa->syntax & RE_DOT_NEWLINE) && re_string_byte_at (input, str_idx) == '\n') || ((dfa->syntax & RE_DOT_NOT_NULL) && re_string_byte_at (input, str_idx) == '\0')) return 0; return char_len; } elem_len = re_string_elem_size_at (input, str_idx); if ((elem_len <= 1 && char_len <= 1) || char_len == 0) return 0; if (node->type == COMPLEX_BRACKET) { const re_charset_t *cset = node->opr.mbcset; # ifdef _LIBC const unsigned char *pin = ((const unsigned char *) re_string_get_buffer (input) + str_idx); Idx j; uint32_t nrules; # endif /* _LIBC */ int match_len = 0; wchar_t wc = ((cset->nranges || cset->nchar_classes || cset->nmbchars) ? re_string_wchar_at (input, str_idx) : 0); /* match with multibyte character? */ for (i = 0; i < cset->nmbchars; ++i) if (wc == cset->mbchars[i]) { match_len = char_len; goto check_node_accept_bytes_match; } /* match with character_class? */ for (i = 0; i < cset->nchar_classes; ++i) { wctype_t wt = cset->char_classes[i]; if (__iswctype (wc, wt)) { match_len = char_len; goto check_node_accept_bytes_match; } } # ifdef _LIBC nrules = _NL_CURRENT_WORD (LC_COLLATE, _NL_COLLATE_NRULES); if (nrules != 0) { unsigned int in_collseq = 0; const int32_t *table, *indirect; const unsigned char *weights, *extra; const char *collseqwc; /* match with collating_symbol? */ if (cset->ncoll_syms) extra = (const unsigned char *) _NL_CURRENT (LC_COLLATE, _NL_COLLATE_SYMB_EXTRAMB); for (i = 0; i < cset->ncoll_syms; ++i) { const unsigned char *coll_sym = extra + cset->coll_syms[i]; /* Compare the length of input collating element and the length of current collating element. */ if (*coll_sym != elem_len) continue; /* Compare each bytes. */ for (j = 0; j < *coll_sym; j++) if (pin[j] != coll_sym[1 + j]) break; if (j == *coll_sym) { /* Match if every bytes is equal. */ match_len = j; goto check_node_accept_bytes_match; } } if (cset->nranges) { if (elem_len <= char_len) { collseqwc = _NL_CURRENT (LC_COLLATE, _NL_COLLATE_COLLSEQWC); in_collseq = __collseq_table_lookup (collseqwc, wc); } else in_collseq = find_collation_sequence_value (pin, elem_len); } /* match with range expression? */ /* FIXME: Implement rational ranges here, too. */ for (i = 0; i < cset->nranges; ++i) if (cset->range_starts[i] <= in_collseq && in_collseq <= cset->range_ends[i]) { match_len = elem_len; goto check_node_accept_bytes_match; } /* match with equivalence_class? */ if (cset->nequiv_classes) { const unsigned char *cp = pin; table = (const int32_t *) _NL_CURRENT (LC_COLLATE, _NL_COLLATE_TABLEMB); weights = (const unsigned char *) _NL_CURRENT (LC_COLLATE, _NL_COLLATE_WEIGHTMB); extra = (const unsigned char *) _NL_CURRENT (LC_COLLATE, _NL_COLLATE_EXTRAMB); indirect = (const int32_t *) _NL_CURRENT (LC_COLLATE, _NL_COLLATE_INDIRECTMB); int32_t idx = findidx (table, indirect, extra, &cp, elem_len); if (idx > 0) for (i = 0; i < cset->nequiv_classes; ++i) { int32_t equiv_class_idx = cset->equiv_classes[i]; size_t weight_len = weights[idx & 0xffffff]; if (weight_len == weights[equiv_class_idx & 0xffffff] && (idx >> 24) == (equiv_class_idx >> 24)) { Idx cnt = 0; idx &= 0xffffff; equiv_class_idx &= 0xffffff; while (cnt <= weight_len && (weights[equiv_class_idx + 1 + cnt] == weights[idx + 1 + cnt])) ++cnt; if (cnt > weight_len) { match_len = elem_len; goto check_node_accept_bytes_match; } } } } } else # endif /* _LIBC */ { /* match with range expression? */ for (i = 0; i < cset->nranges; ++i) { if (cset->range_starts[i] <= wc && wc <= cset->range_ends[i]) { match_len = char_len; goto check_node_accept_bytes_match; } } } check_node_accept_bytes_match: if (!cset->non_match) return match_len; else { if (match_len > 0) return 0; else return (elem_len > char_len) ? elem_len : char_len; } } return 0; } # ifdef _LIBC static unsigned int internal_function find_collation_sequence_value (const unsigned char *mbs, size_t mbs_len) { uint32_t nrules = _NL_CURRENT_WORD (LC_COLLATE, _NL_COLLATE_NRULES); if (nrules == 0) { if (mbs_len == 1) { /* No valid character. Match it as a single byte character. */ const unsigned char *collseq = (const unsigned char *) _NL_CURRENT (LC_COLLATE, _NL_COLLATE_COLLSEQMB); return collseq[mbs[0]]; } return UINT_MAX; } else { int32_t idx; const unsigned char *extra = (const unsigned char *) _NL_CURRENT (LC_COLLATE, _NL_COLLATE_SYMB_EXTRAMB); int32_t extrasize = (const unsigned char *) _NL_CURRENT (LC_COLLATE, _NL_COLLATE_SYMB_EXTRAMB + 1) - extra; for (idx = 0; idx < extrasize;) { int mbs_cnt; bool found = false; int32_t elem_mbs_len; /* Skip the name of collating element name. */ idx = idx + extra[idx] + 1; elem_mbs_len = extra[idx++]; if (mbs_len == elem_mbs_len) { for (mbs_cnt = 0; mbs_cnt < elem_mbs_len; ++mbs_cnt) if (extra[idx + mbs_cnt] != mbs[mbs_cnt]) break; if (mbs_cnt == elem_mbs_len) /* Found the entry. */ found = true; } /* Skip the byte sequence of the collating element. */ idx += elem_mbs_len; /* Adjust for the alignment. */ idx = (idx + 3) & ~3; /* Skip the collation sequence value. */ idx += sizeof (uint32_t); /* Skip the wide char sequence of the collating element. */ idx = idx + sizeof (uint32_t) * (*(int32_t *) (extra + idx) + 1); /* If we found the entry, return the sequence value. */ if (found) return *(uint32_t *) (extra + idx); /* Skip the collation sequence value. */ idx += sizeof (uint32_t); } return UINT_MAX; } } # endif /* _LIBC */ #endif /* RE_ENABLE_I18N */ /* Check whether the node accepts the byte which is IDX-th byte of the INPUT. */ static bool internal_function check_node_accept (const re_match_context_t *mctx, const re_token_t *node, Idx idx) { unsigned char ch; ch = re_string_byte_at (&mctx->input, idx); switch (node->type) { case CHARACTER: if (node->opr.c != ch) return false; break; case SIMPLE_BRACKET: if (!bitset_contain (node->opr.sbcset, ch)) return false; break; #ifdef RE_ENABLE_I18N case OP_UTF8_PERIOD: if (ch >= ASCII_CHARS) return false; /* FALLTHROUGH */ #endif case OP_PERIOD: if ((ch == '\n' && !(mctx->dfa->syntax & RE_DOT_NEWLINE)) || (ch == '\0' && (mctx->dfa->syntax & RE_DOT_NOT_NULL))) return false; break; default: return false; } if (node->constraint) { /* The node has constraints. Check whether the current context satisfies the constraints. */ unsigned int context = re_string_context_at (&mctx->input, idx, mctx->eflags); if (NOT_SATISFY_NEXT_CONSTRAINT (node->constraint, context)) return false; } return true; } /* Extend the buffers, if the buffers have run out. */ static reg_errcode_t internal_function __attribute_warn_unused_result__ extend_buffers (re_match_context_t *mctx, int min_len) { reg_errcode_t ret; re_string_t *pstr = &mctx->input; /* Avoid overflow. */ if (BE (MIN (IDX_MAX, SIZE_MAX / sizeof (re_dfastate_t *)) / 2 <= pstr->bufs_len, 0)) return REG_ESPACE; /* Double the lengths of the buffers, but allocate at least MIN_LEN. */ ret = re_string_realloc_buffers (pstr, MAX (min_len, MIN (pstr->len, pstr->bufs_len * 2))); if (BE (ret != REG_NOERROR, 0)) return ret; if (mctx->state_log != NULL) { /* And double the length of state_log. */ /* XXX We have no indication of the size of this buffer. If this allocation fail we have no indication that the state_log array does not have the right size. */ re_dfastate_t **new_array = re_realloc (mctx->state_log, re_dfastate_t *, pstr->bufs_len + 1); if (BE (new_array == NULL, 0)) return REG_ESPACE; mctx->state_log = new_array; } /* Then reconstruct the buffers. */ if (pstr->icase) { #ifdef RE_ENABLE_I18N if (pstr->mb_cur_max > 1) { ret = build_wcs_upper_buffer (pstr); if (BE (ret != REG_NOERROR, 0)) return ret; } else #endif /* RE_ENABLE_I18N */ build_upper_buffer (pstr); } else { #ifdef RE_ENABLE_I18N if (pstr->mb_cur_max > 1) build_wcs_buffer (pstr); else #endif /* RE_ENABLE_I18N */ { if (pstr->trans != NULL) re_string_translate_buffer (pstr); } } return REG_NOERROR; } /* Functions for matching context. */ /* Initialize MCTX. */ static reg_errcode_t internal_function __attribute_warn_unused_result__ match_ctx_init (re_match_context_t *mctx, int eflags, Idx n) { mctx->eflags = eflags; mctx->match_last = -1; if (n > 0) { /* Avoid overflow. */ size_t max_object_size = MAX (sizeof (struct re_backref_cache_entry), sizeof (re_sub_match_top_t *)); if (BE (MIN (IDX_MAX, SIZE_MAX / max_object_size) < n, 0)) return REG_ESPACE; mctx->bkref_ents = re_malloc (struct re_backref_cache_entry, n); mctx->sub_tops = re_malloc (re_sub_match_top_t *, n); if (BE (mctx->bkref_ents == NULL || mctx->sub_tops == NULL, 0)) return REG_ESPACE; } /* Already zero-ed by the caller. else mctx->bkref_ents = NULL; mctx->nbkref_ents = 0; mctx->nsub_tops = 0; */ mctx->abkref_ents = n; mctx->max_mb_elem_len = 1; mctx->asub_tops = n; return REG_NOERROR; } /* Clean the entries which depend on the current input in MCTX. This function must be invoked when the matcher changes the start index of the input, or changes the input string. */ static void internal_function match_ctx_clean (re_match_context_t *mctx) { Idx st_idx; for (st_idx = 0; st_idx < mctx->nsub_tops; ++st_idx) { Idx sl_idx; re_sub_match_top_t *top = mctx->sub_tops[st_idx]; for (sl_idx = 0; sl_idx < top->nlasts; ++sl_idx) { re_sub_match_last_t *last = top->lasts[sl_idx]; re_free (last->path.array); re_free (last); } re_free (top->lasts); if (top->path) { re_free (top->path->array); re_free (top->path); } free (top); } mctx->nsub_tops = 0; mctx->nbkref_ents = 0; } /* Free all the memory associated with MCTX. */ static void internal_function match_ctx_free (re_match_context_t *mctx) { /* First, free all the memory associated with MCTX->SUB_TOPS. */ match_ctx_clean (mctx); re_free (mctx->sub_tops); re_free (mctx->bkref_ents); } /* Add a new backreference entry to MCTX. Note that we assume that caller never call this function with duplicate entry, and call with STR_IDX which isn't smaller than any existing entry. */ static reg_errcode_t internal_function __attribute_warn_unused_result__ match_ctx_add_entry (re_match_context_t *mctx, Idx node, Idx str_idx, Idx from, Idx to) { if (mctx->nbkref_ents >= mctx->abkref_ents) { struct re_backref_cache_entry* new_entry; new_entry = re_realloc (mctx->bkref_ents, struct re_backref_cache_entry, mctx->abkref_ents * 2); if (BE (new_entry == NULL, 0)) { re_free (mctx->bkref_ents); return REG_ESPACE; } mctx->bkref_ents = new_entry; memset (mctx->bkref_ents + mctx->nbkref_ents, '\0', sizeof (struct re_backref_cache_entry) * mctx->abkref_ents); mctx->abkref_ents *= 2; } if (mctx->nbkref_ents > 0 && mctx->bkref_ents[mctx->nbkref_ents - 1].str_idx == str_idx) mctx->bkref_ents[mctx->nbkref_ents - 1].more = 1; mctx->bkref_ents[mctx->nbkref_ents].node = node; mctx->bkref_ents[mctx->nbkref_ents].str_idx = str_idx; mctx->bkref_ents[mctx->nbkref_ents].subexp_from = from; mctx->bkref_ents[mctx->nbkref_ents].subexp_to = to; /* This is a cache that saves negative results of check_dst_limits_calc_pos. If bit N is clear, means that this entry won't epsilon-transition to an OP_OPEN_SUBEXP or OP_CLOSE_SUBEXP for the N+1-th subexpression. If it is set, check_dst_limits_calc_pos_1 will recurse and try to find one such node. A backreference does not epsilon-transition unless it is empty, so set to all zeros if FROM != TO. */ mctx->bkref_ents[mctx->nbkref_ents].eps_reachable_subexps_map = (from == to ? -1 : 0); mctx->bkref_ents[mctx->nbkref_ents++].more = 0; if (mctx->max_mb_elem_len < to - from) mctx->max_mb_elem_len = to - from; return REG_NOERROR; } /* Return the first entry with the same str_idx, or -1 if none is found. Note that MCTX->BKREF_ENTS is already sorted by MCTX->STR_IDX. */ static Idx internal_function search_cur_bkref_entry (const re_match_context_t *mctx, Idx str_idx) { Idx left, right, mid, last; last = right = mctx->nbkref_ents; for (left = 0; left < right;) { mid = (left + right) / 2; if (mctx->bkref_ents[mid].str_idx < str_idx) left = mid + 1; else right = mid; } if (left < last && mctx->bkref_ents[left].str_idx == str_idx) return left; else return -1; } /* Register the node NODE, whose type is OP_OPEN_SUBEXP, and which matches at STR_IDX. */ static reg_errcode_t internal_function __attribute_warn_unused_result__ match_ctx_add_subtop (re_match_context_t *mctx, Idx node, Idx str_idx) { #ifdef DEBUG assert (mctx->sub_tops != NULL); assert (mctx->asub_tops > 0); #endif if (BE (mctx->nsub_tops == mctx->asub_tops, 0)) { Idx new_asub_tops = mctx->asub_tops * 2; re_sub_match_top_t **new_array = re_realloc (mctx->sub_tops, re_sub_match_top_t *, new_asub_tops); if (BE (new_array == NULL, 0)) return REG_ESPACE; mctx->sub_tops = new_array; mctx->asub_tops = new_asub_tops; } mctx->sub_tops[mctx->nsub_tops] = calloc (1, sizeof (re_sub_match_top_t)); if (BE (mctx->sub_tops[mctx->nsub_tops] == NULL, 0)) return REG_ESPACE; mctx->sub_tops[mctx->nsub_tops]->node = node; mctx->sub_tops[mctx->nsub_tops++]->str_idx = str_idx; return REG_NOERROR; } /* Register the node NODE, whose type is OP_CLOSE_SUBEXP, and which matches at STR_IDX, whose corresponding OP_OPEN_SUBEXP is SUB_TOP. */ static re_sub_match_last_t * internal_function match_ctx_add_sublast (re_sub_match_top_t *subtop, Idx node, Idx str_idx) { re_sub_match_last_t *new_entry; if (BE (subtop->nlasts == subtop->alasts, 0)) { Idx new_alasts = 2 * subtop->alasts + 1; re_sub_match_last_t **new_array = re_realloc (subtop->lasts, re_sub_match_last_t *, new_alasts); if (BE (new_array == NULL, 0)) return NULL; subtop->lasts = new_array; subtop->alasts = new_alasts; } new_entry = calloc (1, sizeof (re_sub_match_last_t)); if (BE (new_entry != NULL, 1)) { subtop->lasts[subtop->nlasts] = new_entry; new_entry->node = node; new_entry->str_idx = str_idx; ++subtop->nlasts; } return new_entry; } static void internal_function sift_ctx_init (re_sift_context_t *sctx, re_dfastate_t **sifted_sts, re_dfastate_t **limited_sts, Idx last_node, Idx last_str_idx) { sctx->sifted_states = sifted_sts; sctx->limited_states = limited_sts; sctx->last_node = last_node; sctx->last_str_idx = last_str_idx; re_node_set_init_empty (&sctx->limits); } ne-3.3.4/src/request.c000066400000000000000000001435521475116431000145620ustar00rootroot00000000000000/* Requester handling. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2025 Todd M. Lewis and Sebastiano Vigna This file is part of ne, the nice editor. This library 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 library 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 . */ #include "ne.h" #include "termchar.h" #include /* How to build and use a req_list: req_list_init() loop: req_list_add() req_list_del() req_list_finalize() request_strings() req_list_free() req_list_init() initializes a req_list structure. req_list_add() is called once for every string you wish to include in the request. If during the process of adding entries to the req_list you decide to remove any previously added entry, you should use req_list_del() to remove it. req_list_finalize() must be called after all the entries have been added to the req_list but before request_strings(). It takes care of some final housekeeping duties to prepare the req_list for use by request_strings(); request_strings() prompts the user to choose one from among possibly many strings in the req_list. If string n was selected with RETURN, n is returned; if string n was selected with TAB, -n-2 is returned. On escaping, ERROR is returned. req_list_free() must be called on any req_list that has been initialized with req_list_init(). We rely on a set of auxiliary functions and a few static variables: req_order X, R, C, page, fuzz_len rl, rl0 */ /* Traditionally ne has displayed request entries BY_ROW (req_order==0): a b c d e f g h i j rather than BY_COLUMN (req_order==1): a e i b f j c g d h which, while easier to program, is somewhat harder to read. The request code is full of tricky expressions that deal with multiple pages of entries displayed either BY_ROW or BY_COLUMN, and attempts to consolidate as many of those tricky expressions into one small set of macros and functions. */ #define BY_COLUMN (req_order) #define BY_ROW (!BY_COLUMN) /* X and Y constitute a screen coordinate derived from (C,R) which varies from page to page. (Y, being a synonym for R, is implemented as a macro.) R and C are the row and column of the currently selected entry. page is the index into req_page_table.req_page. fuzz_len is the length of the matching prefix into our current entry. */ static int X, R, C, page, fuzz_len; #define Y (R) #define NAMES_PER_ROW(p) (req_page_table.req_page[(p)].cols) #define NAMES_PER_COL(p) (req_page_table.req_page[(p)].rows) #define N2PCRX(n,p,c,r,x) (n2pcrx((n),&(p),&(c),&(r),&(x))) #define N2P(n) (n2pcrx((n),NULL,NULL,NULL,NULL)) #define N2X(n,x) (n2pcrx((n),NULL,NULL,NULL,&(x))) #define PCR2N(p,c,r) (pcr2n((p),(c),(r))) #define DX(d) (dxd((d))) #define DY(d) (dyd((d))) /* The initial req_list (*rl0) that gets built prior to calling request_strings() has its own copy of each of the strings. request() builds a working copy of the initial req_list (rl). This working req_list doesn't duplicate rl0's strings, but it does have an array of pointers to some of those strings; it may exclude some entries if progressive search (rl.prune) is turned on (via the Insert key), and some entries may be out of order relative to those in the initial req_list because of F2/F3 reordering during SelectDoc, in which case *rl0 keeps a mapping between the original order and the displayed order. Conversely, rl does not keep a mapping going the other way; use reorder_referent(n) to discover which entry in *rl0 refers to the n-th entry in rl. The req_list_del() function removes an item from the req_list. It is the counterpart to req_list_add() which we use for building the initial req_list one item at a time. req_list_del() can be called either while building the req_list, or after the req_list has been finalized. It's used when we close a document during SelectDoc to remove that closed document's entry from *rl0. Coordinating changes between the working req_list (rl) and the initial req_list (*rl0) when deleting an entry requires some of the string data to be shifted. Because the working req_list doesn't have its own copy of the strings, there's no character data that needs shifting for the working req_list. However, the working req_list does have its own list of pointers into that data, and any of them pointing into the shifted character memory needs to be adjusted. */ static req_list rl, *rl0; /* working req_list and pointer to the original req_list */ #define MAX_REQ_COLS 16 typedef struct { int first; /* index into rl->entries[] of first entry on this page */ int entries; /* how many entries on this page */ int cols; /* columns fitting onto this page */ int rows; /* rows fitting onto this page */ int col_width[MAX_REQ_COLS]; /* array of ints with width of each column */ } req_page_t; /* fit_page() parameters are: n0 index of the first entry for this page. entries number of entries to fit onto this page. cols number of columns to create. rows number of rows to create. page req_page_t to fill in with these parameters. Returns true if the *page will fit on screen and has the requested number of entries; false otherwise. (Assumes 1 column always fits.) */ static bool fit_page(int n0, int entries, int cols, int rows, req_page_t * page) { int i, combined_col_widths; assert(n0 + entries <= rl.cur_entries); assert(cols <= MAX_REQ_COLS && cols > 0); assert(entries <= cols * rows); assert(rows <= ne_lines - 1); memset(page->col_width, 0, sizeof(int) * MAX_REQ_COLS); page->first = n0; page->entries = 0; page->cols = 0; page->rows = 0; for (i=0, combined_col_widths=0; icol_width[col] < rl.lengths[n0 + i]) { combined_col_widths += rl.lengths[n0 + i] - page->col_width[col]; page->col_width[col] = rl.lengths[n0 + i]; } page->entries++; if (page->cols < col+1) page->cols = col+1; if (page->rows < row+1) page->rows = row+1; } return page->entries == entries && (cols == 1 || combined_col_widths <= ne_columns); } static struct { int pages; /* allocated req_page_t structures */ req_page_t * req_page; /* array of allocated req_page_t */ } req_page_table = { 0, NULL} ; /* Return the index of the req_page containing entry n. If n is beyond configured req_pages, return 0 (i.e. it's probably not set up yet.) As a side effect, set any of *P, *C, *R, or *x if P, C, R, or x is not NULL. */ static int n2pcrx(int n, int *P, int *C, int *R, int *x) { req_page_t * pp = &req_page_table.req_page[0]; for (int pg = 0; pg < req_page_table.pages && pp->entries; pg++, pp++) { if (pp->first + pp->entries > n) { if (P) *P = pg; int dn = n - pp->first; if (R) *R = req_order ? dn % pp->rows : dn / pp->cols; int col = req_order ? dn / pp->rows : dn % pp->cols; if (C) *C = col; if (x) { int cum=0; for (int i=0; i < col; i++) cum += pp->col_width[i]; *x = cum; } return pg; } } if (P) *P = 0; if (C) *C = 0; if (R) *R = 0; if (x) *x = 0; return 0; } /* pcr2n() can return an n > rl.cur_entries. This is intentional. */ static int pcr2n(int p, int c, int r) { req_page_t *pp = &req_page_table.req_page[p]; if (BY_ROW) return pp->first + pp->cols * r + c; return pp->first + pp->rows * c + r; } static void req_page_table_free() { free(req_page_table.req_page); req_page_table.req_page = NULL; req_page_table.pages = 0; } /* sums n integers starting at *i */ static int sum_ints(int n, int *i) { int s = 0; while(n--) s += *i++; return s; } static int req_page_table_expand() { int new_pages = req_page_table.pages + MAX_REQ_COLS; /* Nothing to do with cols, but should be plenty. */ req_page_t *p = realloc(req_page_table.req_page, sizeof(req_page_t) * new_pages); if (!p) return 0; memset(p + req_page_table.pages, 0, MAX_REQ_COLS * sizeof(req_page_t)); req_page_table.pages = new_pages; req_page_table.req_page = p; return new_pages; } /* Rebuild the req_page array starting with the page containing n0. If n0==0, the array is rebuilt from scratch (as per requester start-up, or when the screen is resized). The case for n0>0 is when elements are re-ordered via F2/F3 (for requesters where reordering is allowed). You must never call with n0>0 unless existing pages contain n0. If progressive filtering is in play, then they all must be rebuilt (n0==0). Returns 0 on error, in which case the whole requester is abandoned. */ static int reset_req_pages(int n0) { if (req_page_table.pages == 0 && req_page_table_expand() == 0) return 0; int cur_page = N2P(n0); int first = cur_page == 0 ? 0 : req_page_table.req_page[cur_page-1].first + req_page_table.req_page[cur_page-1].entries; memset(&req_page_table.req_page[cur_page], 0, (req_page_table.pages - cur_page) * sizeof(req_page_t)); int max_cols = min(ne_columns / 3, MAX_REQ_COLS); int max_rows = ne_lines - 1; int cols; while (first < rl.cur_entries) { if (cur_page >= req_page_table.pages && req_page_table_expand() == 0) return 0; for (cols = max_cols; cols > 0; cols--) if (fit_page(first, min(cols*max_rows, rl.cur_entries - first), cols, max_rows, &req_page_table.req_page[cur_page])) break; if (cols==0) return 0; /* This should never happen. */ first += req_page_table.req_page[cur_page].entries; cur_page++; } /* Tweak the final page in a BY_COLUMN layout so that the rectangle containing strings is roughly proportional to the terminal window. This is purely an aesthetic thing. I'd like to do it for the BY_ROW layout, too, but "some people" have "opinions". Meh. */ if (BY_COLUMN) { req_page_t * pp = &req_page_table.req_page[cur_page-1]; int needed_entries = pp->entries; int first = pp->first; for (int rows = 1; rows <= max_rows; rows++) { cols = needed_entries / rows + (needed_entries % rows ? 1 : 0); if (cols >= MAX_REQ_COLS) continue; if (fit_page(first, needed_entries, cols, rows, pp)) { if (rows == max_rows || sum_ints(pp->cols, pp->col_width) * 1000 / pp->rows < ne_columns * 1000 / (ne_lines - 1)) { int good_rows = rows; while (good_rows > 1 && cols * (good_rows - 1) >= needed_entries && fit_page(first, needed_entries, cols, good_rows - 1, pp)) good_rows--; fit_page(first, needed_entries, cols, good_rows, pp); return cur_page; } } } } return cur_page; } /* Move 1 column left (negative) or right (positive). Assumes 'page', 'R', and 'C' are correct. Do not alter these values, as they are needed by normalize() to determine whether screen updates are needed. Returns the new selected entry's index. */ static int dxd(int dir) { req_page_t *pp = &req_page_table.req_page[page]; int last_n = pp->first + pp->entries - 1; int n0; if (dir == -1) { if (R == 0 && C == 0) { if (page > 0) return pp->first - 1; } else { if (C > 0) return pcr2n(page, C - 1, R); else if ((n0 = pcr2n(page, pp->cols - 1, R - 1)) <= last_n) return n0; else return pcr2n(page, pp->cols - 2, R - 1); } } else { /* dir == 1 */ if (pcr2n(page, C, R) == last_n) { if (last_n < rl.cur_entries) return last_n + 1; } else { if (C < pp->cols - 1 && (n0 = pcr2n(page, C + 1, R)) < pp->first + pp->entries) return n0; else if (R < pp->rows - 1) return pcr2n(page, 0, R + 1); } } return pcr2n(page, C, R); /* Can't move; hold position. */ } /* Like dxd(), but -1 is "up", +1 is "down". */ static int dyd(int dir) { req_page_t *pp = &req_page_table.req_page[page]; int last_n = pp->first + pp->entries - 1; if (dir == -1) { /* up */ if (R == 0 && C == 0) { if (page > 0) return pp->first - 1; } else { if (R > 0) return pcr2n(page, C, R - 1); else return pcr2n(page, C - 1, pp->rows - 1); } } else { /* dir == 1, i.e. "down" */ if (pcr2n(page, C, R) == last_n) return last_n + 1; else if (R == pp->rows - 1) return pcr2n(page, C + 1, 0); else return pcr2n(page, C, R + 1); } return pcr2n(page, C, R); /* Can't move; hold position */ } static int common_prefix_len(req_list *cpl_rl) { char * const p0 = cpl_rl->entries[0]; int len = strlen(p0); for (int i = 0; len && i < cpl_rl->cur_entries; i++) { char * const p1 = cpl_rl->entries[i]; for ( ; len && strncasecmp(p0, p1, len); len--) ; } return len; } /* Given that the req_list entries in rl may be reordered WRT *rl0, return the original index corresponding to the new index "n". */ static int reorder_referent(const int n) { if (!rl0->allow_reorder) return n; for (int i = 0; i < rl0->cur_entries; i++) if (rl0->reorder[i] == n) return i; return n; /* Should never happen! */ } #ifdef DEBUGPRINTF static void dump_reorder() { if (rl0->allow_reorder) { fprintf(stderr,"===================================\n"); for (int i = 0; i < rl0->cur_entries; i++) { fprintf(stderr,"%d:%d %s | %s\n", i, rl0->reorder[i], rl0->entries[i], i < rl.cur_entries ? rl.entries[i] : ""); } fprintf(stderr,"-----------------------------------\n"); fflush(NULL); } } static void dump_rl_chars(req_list * const drc_rl) { char *c = drc_rl->chars; int entry; fprintf(stderr,"===================================\n"); for (entry = 0; entry < drc_rl->cur_entries; entry++) { char *p = drc_rl->entries[entry]; if (c != p) { fprintf(stderr,"dump_rl_chars: c(%s) != p(%s); aborting\n", c, p); return; } fprintf(stderr, "dump_rl_chars: "); while (*c) fprintf(stderr," %c", *c++); fprintf(stderr, " \\0"); c++; while (*c) fprintf(stderr," %c", *c++); fprintf(stderr, " \\0\n"); c++; } fprintf(stderr,"-----------------------------------\n"); fflush(NULL); } #endif /* This is the printing function used by the requester. It prints the strings from the entries array existing in "page". */ static void print_strings() { set_attr(0); req_page_t *pp = &req_page_table.req_page[page]; for(int row = 0; row < ne_lines - 1; row++) { move_cursor(row, 0); clear_to_eol(); if (row < pp->rows) { for(int col = 0, x=0; col < pp->cols; col++) { if (PCR2N(page, col, row) < rl.cur_entries) { move_cursor(row, x); const char * const p = rl.entries[PCR2N(page, col, row)]; if (rl.suffix) set_attr(p[strlen(p) - 1] == rl.suffix ? BOLD : 0); output_string(p, io_utf8); x += pp->col_width[col]; } } } } } /* Reset X, (Y; not really; same as R), R, C, and page in terms of n. Returns true if we called print_strings(), false otherwise. */ static bool normalize(int n) { const int p = page; if (n < 0 ) n = 0; if (n >= rl.cur_entries ) n = rl.cur_entries - 1; N2PCRX(n, page, C, R, X); D(dump_reorder();) if ( p != page ) print_strings(); return false; } static void request_move_to_sol(void) { while (C > 0) normalize(DX(-1)); } static void request_move_to_eol(void) { while (C < NAMES_PER_ROW(page) - 1 && PCR2N(page, C + 1, R) < rl.cur_entries) normalize(DX(1)); } static void request_move_to_sof(void) { normalize(0); } static void request_move_to_eof(void) { normalize(rl.cur_entries - 1); } static void request_toggle_seof(void) { if (C + R + page == 0) request_move_to_eof(); else request_move_to_sof(); } static void request_prev_page(void) { if (page == 0 ) normalize(0); else { req_page_t *pp = &req_page_table.req_page[page]; int n_per_mille = ((PCR2N(page, C, R) - pp->first) * 65536) / pp->entries; pp--; int new_n = max((n_per_mille * (pp->entries+1)) / 65536 + pp->first, pp->first); normalize(new_n); } } static void request_next_page(void) { req_page_t *pp = &req_page_table.req_page[page]; if (pp->first + pp->entries < rl.cur_entries) { req_page_t *pp = &req_page_table.req_page[page]; int n_per_mille = ((PCR2N(page, C, R) - pp->first) * 65536) / pp->entries; pp++; int new_n = min((n_per_mille * (pp->entries+1)) / 65536 + pp->first, pp->first + pp->entries - 1); normalize(new_n); } else normalize(rl.cur_entries - 1); } static void request_move_up(void) { normalize(DY(-1)); } static void request_move_inc_up(void) { if (C == 0) { if (R == 0) request_move_to_sof(); else request_prev_page(); } else request_move_to_sol(); } static void request_move_down(void) { normalize(DY(1)); } static void request_move_inc_down(void) { if (C == NAMES_PER_ROW(page) - 1) { if (R == NAMES_PER_COL(page) - 1) request_move_to_eof(); else request_next_page(); } else request_move_to_eol(); } static void request_move_left(void) { normalize(DX(-1)); } static void request_move_next(void) { normalize(PCR2N(page, C, R) + 1); } static void request_move_previous(void) { normalize(PCR2N(page, C, R) - 1); } static void request_move_right(void) { normalize(DX(1)); } /* Reorder (i.e. swap) the current entry n with entry n+dir. dir must be either 1 or -1. Swaps rl.entries[n, n+dir], rl.lengths[n, n+dir], and rl0.reorder[a, b] where a and b refer to rl.entries[n, n+dir]. Note: under no circumstances do the original rl0->entries[] change order. */ static void request_reorder(int dir) { if (! rl0->allow_reorder || rl.cur_entries < 2 || abs(dir) != 1) return; const int n0 = PCR2N(page, C, R); const int n_fin = (n0 + dir + rl.cur_entries ) % rl.cur_entries; /* allows wrap-around */ dir = (n0 > n_fin) ? -1 : 1; /* adjust dir in case of wrap-around */ for (int n_cur = n0; n_cur != n_fin; n_cur += dir) { int n_nxt = n_cur + dir; int cur_ref = reorder_referent(n_cur); int nxt_ref = reorder_referent(n_nxt); int tmp = rl0->reorder[cur_ref]; rl0->reorder[cur_ref] = rl0->reorder[nxt_ref]; rl0->reorder[nxt_ref] = tmp; char * p = rl.entries[n_cur]; rl.entries[n_cur] = rl.entries[n_nxt]; rl.entries[n_nxt] = p; tmp = rl.lengths[n_cur]; rl.lengths[n_cur] = rl.lengths[n_nxt]; rl.lengths[n_nxt] = tmp; } rl0->reordered = rl.reordered = true; reset_req_pages(min(n0,n_fin)); page = -1; /* causes normalize() to call print_strings() */ normalize(n_fin); } /* rebuild rl.entries[] from rl0->entries[]. If rl.prune is true, include only those entries from rl0 which match the currently highlighted rl entry up through fuzz_len characters. If rl.prune is false, include all entries from rl0. In any case, return the (possibly new) index of the highlighted entry in rl. */ static int rebuild_rl_entries() { const char * const p0 = rl.entries[PCR2N(page, C, R)]; int i, j, n; for (i = j = n = 0; i < rl0->cur_entries; i++) { int orig = reorder_referent(i); char * const p1 = rl0->entries[orig]; D(fprintf(stderr, "rre: i:%d, j:%d, n:%d p0:%lx p1:%lx fuzz_len:%d rl.prune:%d\n", i, j, n, (unsigned long) p0, (unsigned long) p1, fuzz_len, rl.prune);) if ( ! rl.prune || ! strncasecmp(p0, p1, fuzz_len) ) { if (p1 == p0) { n = j; D(fprintf(stderr, "rre: set n <- j (%d)\n", j);) } rl.entries[j] = p1; rl.lengths[j++] = rl0->lengths[orig]; D(fprintf(stderr, "rre: set rl.entries[%d] <- %lx ('%s')\n", j-1, (unsigned long) p1, p1);) D(fprintf(stderr, "rre: set rl.lengths[%d] <- rl0->lengths[%d] (%d)\n", j-1, orig, rl0->lengths[orig]);) } } rl.cur_entries = j; reset_req_pages(0); return n; } /* Count how many entries match our highlighted entry up through len characters. */ static int count_fuzz_matches(const int len) { const int n0 = PCR2N(page, C, R); const char * const p0 = rl.entries[n0]; if (len <= 0) return rl0->cur_entries; if (len > strlen(p0)) return 1; int i, c; for (i = c = 0; i < rl0->cur_entries; i++) { if (p0 == rl0->entries[i] || ! strncasecmp(p0, rl0->entries[i], len)) c++; } return c; } /* Shift fuzz_len by +1 or -1 until the matching count changes or we run out of string. It's more subtle than that actually. Going left (-1), we want to stop on the first fuzz_len that has a different count_fuzz_matches() than the initial position. But going right (+1), we want to stop on the last fuzz_len that has the same count_fuzz_matches() as the initial position. In either case, the "gap" to the right of fuzz_len constitutes a transition. */ static void shift_fuzz(const int d) { const char * const p0 = rl.entries[PCR2N(page, C, R)]; assert(d == 1 || d == -1); int initial_fuzz_matches = count_fuzz_matches(fuzz_len); if (d == -1) { while(fuzz_len > 0 && count_fuzz_matches(fuzz_len) == initial_fuzz_matches) fuzz_len--; } else { while (fuzz_len < strlen(p0) + 1 && count_fuzz_matches(fuzz_len + 1) == initial_fuzz_matches) fuzz_len++; } } /* Back up one or more chars from fuzz_len, possibly pulling in matching entries from the original req_list *rl0 in such a way as to preserve original order. This can behave quite differently depending on the value of 'rl.prune'. If true, we disregard the current subset of entries and rely only on matching fuzz_len characters. If false, we keep the current subset while still pulling in matches from rl0. */ static void fuzz_back() { const int n0 = PCR2N(page, C, R); N2PCRX(n0, page, C, R, X); if (fuzz_len == 0) return; shift_fuzz(-1); int n1 = rebuild_rl_entries(); reset_req_pages(0); page = -1; /* causes normalize() to call print_strings() */ normalize(n1); } /* given a localised_up_case character c, find the next entry that matches our current fuzz_len chars plus this new character. The behavior is quite different depending on 'rl.prune'. If true, we keep only entries that match our current fuzz_len prefix plus this additional character while preserving the relative order of rl.entries[]. If false, we keep all the current entries, and only (possibly) change the selected one. */ static void fuzz_forward(const int c) { const int n0 = PCR2N(page, C, R); const char * const p0 = rl.entries[n0]; assert(fuzz_len >= 0); if (rl.prune) { int i = 0, n1 = 0; for (int j = 0; j < rl.cur_entries; j++) { char * const p1 = rl.entries[j]; int len = rl.lengths[j]; const int cmp = strncasecmp(p0, p1, fuzz_len); if (! cmp && strlen(p1) > fuzz_len && localised_up_case[(unsigned char)p1[fuzz_len]] == c) { if (p1 == p0) n1 = i; rl.entries[i] = p1; rl.lengths[i++] = len; } } if (i) { rl.cur_entries = i; fuzz_len = common_prefix_len(&rl); reset_req_pages(0); page = -1; /* causes normalize() to call print_strings() */ normalize(n1); } } else { /* find the next matching string, possibly wrapping around */ for (int n = n0, i = rl.cur_entries; i; i--, n = (n + 1) % rl.cur_entries) { char * const p1 = rl.entries[n]; const int cmp = strncasecmp(p0, p1, fuzz_len); if (!cmp && strlen(p1) > fuzz_len && localised_up_case[(unsigned char)p1[fuzz_len]] == c) { N2PCRX(n, page, C, R, X); fuzz_len++; shift_fuzz(1); page = -1; normalize(n); break; } } } } /* request_strings_init() is only ever called by request_strings(). request_strings_init() takes the original list of strings, described by *rlp0, and makes a working copy described by the static rl. This working copy has an allocated buffer large enough to hold a copy of all the original's char pointers, but at any time may have fewer entries due to fuzzy matching (prune) and entry deletions. The counterpart to request_strings_init() is request_strings_cleanup() which cleans up the allocations. It too is only ever called by request_strings(). */ static int request_strings_init(req_list *rlp0) { rl.cur_entries = rlp0->cur_entries; rl.suffix = rlp0->suffix; if (!(rl.entries = calloc(rlp0->cur_entries, sizeof(char *)))) return 0; if (!(rl.lengths = calloc(rlp0->cur_entries, sizeof(int)))) { free(rl.entries); return 0; } rl.alloc_entries = rlp0->cur_entries; memcpy(rl.entries, rlp0->entries, rl.cur_entries * sizeof(char *)); memcpy(rl.lengths, rlp0->lengths, rl.cur_entries * sizeof(int)); rl0 = rlp0; rl.allow_dupes = rl0->allow_dupes; rl.allow_reorder = rl0->allow_reorder; rl.ignore_tab = rl0->ignore_tab; rl.reordered = rl0->reordered; rl.find_quits = rl0->find_quits; rl.help_quits = rl0->help_quits; rl.selectdoc_quits = rl0->selectdoc_quits; rl.prune = rl0->prune; /* rl doesn't have its own allocated characters; critically, its entries point to allocations that belong to *rlp0, a.k.a the static *rl0. */ rl.cur_chars = rl.alloc_chars = 0; rl.chars = NULL; fuzz_len = common_prefix_len(&rl); return rl.cur_entries; } static int request_strings_cleanup() { int n = PCR2N(page, C, R); const char * const p0 = rl.entries[n]; for (int i = 0; icur_entries; i++) { if (rl0->entries[i] == p0) { n = i; break; } } if (rl.entries) free(rl.entries); rl.entries = NULL; if (rl.lengths) free(rl.lengths); rl.lengths = NULL; req_page_table_free(); return n; } /* indicates the function which request_strings() should call upon CLOSEDOC_A */ static int (*rs_closedoc)(int n) = NULL; static int handle_savedoc(int n); /* indicates the correct function to call to restore the status bar after suspend/resume, particularly during requesters. Note: not static, as this is used by interrupt handlers elsewhere in the source code. */ void (*resume_status_bar)(const char *message); static bool resume_bar = false; /* Present a list of strings for the user to select one from. If _rl->suffix is not '\0', bold names ending with it. The integer returned is one of the following: n >= 0 User selected string n with the enter key. -1 Error or abort; no selection made. n < -1 User selected string -n - 2 with the TAB key. (Yes, it's kind of evil, but it's nothing compared to what request() does!) If reordering occurred, the indicated number is the original index of the entry (modulo deletions). The index into the reordered entries would be rlp0->reorder[n]. */ int request_strings(req_list *rlp0, int n) { if (rlp0->cur_entries < 1) return ERROR; int ne_lines0 = 0, ne_columns0 = 0; X = R = C = page = fuzz_len = 0; if ( ! request_strings_init(rlp0) ) return ERROR; if ( ! reset_req_pages(0) ) { request_strings_cleanup(); return ERROR; } char * bar_msg_after_keystroke = NULL; while(true) { if (ne_lines0 != ne_lines || ne_columns0 != ne_columns || resume_bar) { if (ne_lines0 && ne_columns0 ) { n = PCR2N(page, C, R); reset_req_pages(0); } ne_lines0 = ne_lines; ne_columns0 = ne_columns; N2PCRX(n, page, C, R, X); print_strings(); if (resume_bar) { resume_status_bar(NULL); resume_bar = false; } } n = PCR2N(page, C, R); N2X(n,X); assert(fuzz_len >= 0); fuzz_len = min(fuzz_len, strlen(rl.entries[n])); move_cursor(R, X + fuzz_len); int c; input_class ic; do c = get_key_code(); while((ic = CHAR_CLASS(c)) == IGNORE); if (bar_msg_after_keystroke) { print_message(bar_msg_after_keystroke); bar_msg_after_keystroke = NULL; } if (window_changed_size) { window_changed_size = false; resume_bar = true; continue; /* Window resizing. */ } switch(ic) { case INVALID: /* ignore and move on */ break; case ALPHA: if (n >= rl.cur_entries) n = rl.cur_entries - 1; c = localised_up_case[(unsigned char)c]; fuzz_forward( c ); break; case TAB: if (! rlp0->ignore_tab) { n = request_strings_cleanup(); if (n >= rlp0->cur_entries) return ERROR; else return -n - 2; } break; case RETURN: n = request_strings_cleanup(); if (n >= rlp0->cur_entries) return ERROR; else return n; case COMMAND: if (c < 0) c = -c - 1; const int a = parse_command_line(key_binding[c], NULL, NULL, false); if (a >= 0) { switch(a) { case SUSPEND_A: stop_ne(); resume_bar = true; break; case BACKSPACE_A: fuzz_back(); break; case MOVERIGHT_A: request_move_right(); break; case MOVELEFT_A: request_move_left(); break; case MOVESOL_A: request_move_to_sol(); break; case MOVEEOL_A: request_move_to_eol(); break; case TOGGLESEOL_A: if (C != 0) C = 0; else request_move_to_eol(); break; case LINEUP_A: request_move_up(); break; case LINEDOWN_A: request_move_down(); break; case MOVEINCUP_A: request_move_inc_up(); break; case MOVEINCDOWN_A: request_move_inc_down(); break; case PAGEUP_A: case PREVPAGE_A: request_prev_page(); break; case PAGEDOWN_A: case NEXTPAGE_A: request_next_page(); break; case MOVESOF_A: request_move_to_sof(); break; case MOVEEOF_A: request_move_to_eof(); break; case TOGGLESEOF_A: request_toggle_seof(); break; case NEXTWORD_A: request_move_next(); break; case PREVWORD_A: request_move_previous(); break; case NEXTDOC_A: request_reorder(1); break; case PREVDOC_A: request_reorder(-1); break; case INSERT_A: case DELETECHAR_A: rl0->prune = rl.prune = !rl.prune; int n1 = rebuild_rl_entries(); page = -1; normalize(n1); break; case SAVE_A: if (rs_closedoc) { /* we allow save if we allow closedoc */ int n0 = PCR2N(page, C, R); handle_savedoc(n0); page = -1; normalize(n0); bar_msg_after_keystroke = info_msg[SELECT_DOC]; } break; case CLOSEDOC_A: if (rs_closedoc) { int n0 = rs_closedoc(PCR2N(page, C, R)); page = -1; normalize(n0); } break; /* Keystrokes that open requesters toggle them closed also. */ case FIND_A: if (a == FIND_A && !rl.find_quits) break; case HELP_A: if (a == HELP_A && !rl.help_quits) break; case SELECTDOC_A: if (a == SELECTDOC_A && !rl.selectdoc_quits) break; case ESCAPE_A: case QUIT_A: request_strings_cleanup(); return -1; } } break; default: break; } } } static void load_syntax_names(req_list *lsn_rl, DIR *d, int flag) { const int extlen = strlen(SYNTAX_EXT); stop = false; for( struct dirent *de; !stop && (de = readdir(d)); ) { if (is_directory(de->d_name)) continue; const int len = strlen(de->d_name); if (len > extlen && !strcmp(de->d_name+len - extlen, SYNTAX_EXT)) { char ch = de->d_name[len-extlen]; de->d_name[len-extlen] = '\0'; if (!req_list_add(lsn_rl, de->d_name, flag)) break; de->d_name[len-extlen] = ch; } } } /* This is the syntax requester. It reads the user's syntax directory and the global syntax directory, builds an array of strings and calls request_strings(). Returns NULL on error or escaping, or a pointer to the selected syntax name sans extension if RETURN or TAB key was pressed. As per request_files(), if the selection was made with the TAB key, the first character of the returned string is a NUL, so callers (currently only request()) must take care to handle this case. */ char *request_syntax() { char syn_dir_name[512]; char *p; req_list rs_rl; DIR *d; if (req_list_init(&rs_rl, filenamecmp, false, false, '*') != OK) return NULL; if ((p = exists_prefs_dir()) && strlen(p) + 2 + strlen(SYNTAX_DIR) < sizeof syn_dir_name) { strcat(strcpy(syn_dir_name, p), SYNTAX_DIR); if (d = opendir(syn_dir_name)) { load_syntax_names(&rs_rl, d, true); closedir(d); } } if ((p = exists_gprefs_dir()) && strlen(p) + 2 + strlen(SYNTAX_DIR) < sizeof syn_dir_name) { strcat(strcpy(syn_dir_name, p), SYNTAX_DIR); if (d = opendir(syn_dir_name)) { load_syntax_names(&rs_rl, d, false); closedir(d); } } req_list_finalize(&rs_rl); p = NULL; int result; if (rs_rl.cur_entries && (result = request_strings(&rs_rl, 0)) != ERROR) { char * const q = rs_rl.entries[result >= 0 ? result : -result - 2]; if (p = malloc(strlen(q)+3)) { strcpy(p, q); if (p[strlen(p)-1] == rs_rl.suffix) p[strlen(p)-1] = '\0'; if (result < 0) { memmove(p + 1, p, strlen(p) + 1); p[0] = '\0'; } } } req_list_free(&rs_rl); return p; } /* This is the file requester. It reads the directory in which the filename lives, builds an array of strings and calls request_strings(). If a directory name is returned, it enters the directory and the process just described is repeated for that directory. Returns NULL on error or escaping, a pointer to the selected filename if RETURN is pressed, or a pointer to a NUL char '\0' followed by the selected filename (or directory) if TAB is pressed (so by checking whether the first character of the returned string is NUL you can check which key the user pressed). */ char *request_files(const char * const filename, bool use_prefix) { char * const cur_dir_name = ne_getcwd(CUR_DIR_MAX_SIZE); if (!cur_dir_name) return NULL; bool absolute = false; char * const dir_name = str_dup(filename); if (dir_name) { int result = 0; if (dir_name[0] == '/') absolute = true; char * const p = (char *)file_part(dir_name); if (p != dir_name) { *p = 0; result = chdir(tilde_expand(dir_name)); } free(dir_name); if (result == -1) return NULL; } req_list rf_rl; bool next_dir; char *result = NULL; do { next_dir = false; if (req_list_init(&rf_rl, filenamecmp, true, false, '/') != OK) break; DIR * const d = opendir(CURDIR); if (d) { stop = false; for(struct dirent * de; !stop && (de = readdir(d)); ) { const bool is_dir = is_directory(de->d_name); if (use_prefix && !is_prefix(file_part(filename), de->d_name)) continue; if (!req_list_add(&rf_rl, de->d_name, is_dir)) break; } req_list_finalize(&rf_rl); if (rf_rl.cur_entries) { const int t = request_strings(&rf_rl, 0); if (t != ERROR) { char * const p = rf_rl.entries[t >= 0 ? t : -t - 2]; if (p[strlen(p) - 1] == '/' && t >= 0) { p[strlen(p) - 1] = 0; if (chdir(p)) alert(); else use_prefix = false; next_dir = true; } else { result = ne_getcwd(CUR_DIR_MAX_SIZE + strlen(p) + 2); if (strcmp(result, "/")) strcat(result, "/"); strcat(result, p); if (!absolute) { char *rp = relative_file_path(result, cur_dir_name); if (rp) { free(result); result = rp; } } if (t < 0) { memmove(result + 1, result, strlen(result) + 1); result[0] = 0; } } } } closedir(d); } else alert(); req_list_free(&rf_rl); } while(next_dir); if (chdir(cur_dir_name)) alert(); free(cur_dir_name); return result; } /* Requests a file name. If no_file_req is false, the file requester is firstly presented. If no_file_req is true, or the file requester is escaped, a long input is performed with the given prompt and default_name. */ char *request_file(const buffer *b, const char *prompt, const char *default_name) { char *p = NULL; if (!b->opt.no_file_req) { print_message(info_msg[PRESSF1]); p = request_files(default_name, false); reset_window(); draw_status_bar(); if (p && *p) return p; } if (p = request_string(b, prompt, p ? p + 1 : default_name, false, COMPLETE_FILE, io_utf8)) return p; return NULL; } /* Save the document referenced by index "n". If successful, clear the mark for this document. */ static int handle_savedoc(int n) { char *p = rl.entries[n]; int o; for (o = 0; o < rl0->cur_entries && rl0->entries[o] != p; o++) /* empty loop */ ; if (o == rl0->cur_entries) return n; /* This should never happen. */ buffer *bp = get_nth_buffer(o); int error = save_buffer_to_file(bp, NULL); if (error != OK) print_error(DOCUMENT_NOT_SAVED); else { print_info(SAVED); if (rl.suffix && p[strlen(p) - 1] == rl.suffix) p[strlen(p) - 1] = '\0'; } return n; } /* This is the callback function for the SelectDoc requester's CloseDoc action. "n" is the index into rl.entries of the "char *p" corresponding to an entry in rl0->entries, the index "o" of which corresponds to the index of the buffer we want to close IFF it is unmodified. */ static int handle_closedoc(int n) { const char *p = rl.entries[n]; int o; for (o = 0; o < rl0->cur_entries && rl0->entries[o] != p; o++) /* empty loop */ ; if (o == rl0->cur_entries) return n; /* This should never happen. */ buffer *bp = get_nth_buffer(o); /* We don't close modified buffers here, nor the last buffer. */ if (!bp || bp->is_modified || rl0->cur_entries == 1) return n; /* We've determined we are going to close document *bp. */ /* Ensure we'll still have an entry in view after closing *bp. */ if (rl.cur_entries == 1 && rl0->cur_entries > 1) { fuzz_back(); /* "*p" is still valid, but "n" isn't.*/ } for (int i = 0, j = 0; i < rl.cur_entries; i++) { char * const entry = rl.entries[i]; int len = rl.lengths[i]; if (p == entry) n = i; else { rl.entries[j] = entry; rl.lengths[j++] = len; } } /* n is valid again as the index in rl.entries[] that we just dropped. If it was the last entry, n == rl.cur_entries. */ rl.cur_entries--; fuzz_len = common_prefix_len(&rl); /* This is the only time we change rl0->entries[] or the character buffer rl0->chars. shift_len will be the number of bytes by which the end of rl0->chars was shifted to remove entry o. */ int shift_len = req_list_del(rl0, o); for (int i = 0; i < rl.cur_entries; i++) if (rl.entries[i] >= p) rl.entries[i] -= shift_len; /* rebuild_rl_entries() is not necessary, as dropping an entry will never necessitate pulling in new entries into rl.entries[] from rl0->entries[]. */ reset_req_pages(0); n = min(n, rl.cur_entries - 1); normalize(n); buffer *nextb = (buffer *)bp->b_node.next; rem(&bp->b_node); free_buffer(bp); if (! nextb->b_node.next) nextb = (buffer *)buffers.head; #if 0 /* As we don't close the last remaining document through this requester, this code is irrelevant. But leave it here in the quite likely event that decision is revisited. */ if (nextb == (buffer *)&buffers.tail) { close_history(); unset_interactive_mode(); exit(0); } #endif if (bp == cur_buffer) cur_buffer = nextb; return n; } /* Presents to the user a list of the documents currently available. It returns the number of the document selected, or -1 on escape or error. It may reorder the document list, and may close any unmodified document up to but not including the last remaining document. */ int request_document(void) { static int rd_prune = false; int i = -1; req_list rd_rl; buffer *b = (buffer *)buffers.head; if (b->b_node.next && req_list_init(&rd_rl, NULL, true, true, '*')==OK) { i = 0; int cur_entry = 0; while(b->b_node.next) { if (b == cur_buffer) cur_entry = i; req_list_add(&rd_rl, b->filename ? b->filename : UNNAMED_NAME, b->is_modified); b = (buffer *)b->b_node.next; i++; } rd_rl.ignore_tab = true; rd_rl.selectdoc_quits = true; rd_rl.prune = rd_prune; req_list_finalize(&rd_rl); print_message(info_msg[SELECT_DOC]); rs_closedoc = &handle_closedoc; i = request_strings(&rd_rl, cur_entry); /* i is the index into the local rd_rl.entries[] array, which may be shorter than it started due to closing documents, and the remaining document names may have been reordered. */ rs_closedoc = NULL; rd_prune = rd_rl.prune; if (i >= 0 && rd_rl.reordered) { /* The array of pointers at rd_rl.entries[] is no longer needed to reference strings, but as it's large enough to hold all the buffer pointers, and we need a place to temporary store these buffer pointers while we reorder them, we repurpose rd_rl.entries as a buffer pointer array. */ b = (buffer *)buffers.head; for (int j = 0; b->b_node.next; j++ ) { D(fprintf(stderr,"rqd: j:%d '%s'\n", j, b->filename ? b->filename : UNNAMED_NAME);) rd_rl.entries[rd_rl.reorder[j]] = (char *)b; b = (buffer *)b->b_node.next; rem(b->b_node.prev); } /* We're rem()'d all our buffers from the buffer list. rd_rl.entries[] now contains pointers to each buffer in their intended final order. */ for (int j = 0; j < rd_rl.cur_entries; j++) { b = (buffer *)rd_rl.entries[j]; D(fprintf(stderr,"rqd: add_tail %d ('%s')\n", j, b->filename ? b->filename : UNNAMED_NAME);) add_tail(&buffers, (node *)rd_rl.entries[j]); } D(fprintf(stderr,"i:%d -> %d\n", i, rd_rl.reorder[i]);) i = rd_rl.reorder[i]; } reset_window(); draw_status_bar(); req_list_free(&rd_rl); } return i; } /* The req_list_* functions below provide a mechanism suitable for building request lists for directory entries, syntax recognizers, autocompletions, etc. */ /* These are the default allocation sizes for the entry array and for the name array when reading a directory. The allocation sizes start with these values, and they are doubled each time more space is needed. This ensures a reasonable number of retries. */ #define DEF_ENTRIES_ALLOC_SIZE 256 #define DEF_CHARS_ALLOC_SIZE (4*1024) /* Delete the nth string from the given request list. This will work regardless of whether the req_list has been finalized. Returns the length of the shift. Note: should not be called on the working static req_list lr, as it doesn't contain it's own character buffer. */ int req_list_del(req_list * const rld, int nth) { if (nth < 0 || nth >= rld->cur_entries ) return ERROR; char * const str = rld->entries[nth]; const int len0 = strlen(str); int len = len0; len += str[len + 1] ? 3 : 2; /* 'a b c \0 * \0' or 'a b c \0 \0' or 'a b * \0 \0' depending on whether req_list_finalize() has been called. */ memmove(str, str + len, sizeof(char)*(rld->alloc_chars - (str + len - rld->chars))); rld->cur_chars -= len; for(int i = 0; i < rld->cur_entries; i++) if (rld->entries[i] >= str ) rld->entries[i] -= len; if (rld->reorder) { int val = rld->reorder[nth]; for (int i = 0, j = 0; i < rld->cur_entries; i++, j++) { if (i == nth) j--; else rld->reorder[j] = (rld->reorder[i] < val) ? rld->reorder[i] : rld->reorder[i] - 1; } } memmove(&rld->entries[nth], &rld->entries[nth+1], sizeof(char *) * (rld->cur_entries - nth)); memmove(&rld->lengths[nth], &rld->lengths[nth+1], sizeof(int) * (rld->cur_entries - nth)); rld->cur_entries--; return len; } void req_list_free(req_list * const rlf) { if (rlf->entries) free(rlf->entries); rlf->entries = NULL; if (rlf->chars) free(rlf->chars); rlf->chars = NULL; if (rlf->reorder) free(rlf->reorder); rlf->reorder = NULL; if (rlf->lengths) free(rlf->lengths); rlf->lengths = NULL; rlf->allow_reorder = false; rlf->cur_entries = rlf->alloc_entries = 0; rlf->cur_chars = rlf->alloc_chars = 0; } /* Initialize a request list. A comparison function cmpfnc may be provided; if it is provided, that function will be used to keep the entries sorted. If NULL is provided instead, entries are kept in the order they are added. The boolean allow_dupes determines whether duplicate entries are allowed. If not, and if cmpfnc is NULL, then each addition requires a linear search over the current entries. The boolean allow_reorder, when set, enables the user to move the highlighted entry forward or backward in the list of entries by use of the NextDoc and PrevDoc commands (invoked normally by the F2 and F3 keys). In this case, you will need to consult the rl0->reorder map upon return to determine the indicated new order for entries. If a suffix character is provided, it can optionally be added to individual entries as they are added to the req_list. Entries thus marked will be highlighted when displayed. Choose carefully, as entries which naturally end with said character are indistinguishable from marked entries. */ int req_list_init( req_list * const rli, int cmpfnc(const char *, const char *), const bool allow_dupes, const bool allow_reorder, const char suffix) { rli->cmpfnc = cmpfnc; rli->allow_dupes = allow_dupes; rli->allow_reorder = allow_reorder; rli->ignore_tab = false; rli->prune = false; rli->find_quits = false; rli->help_quits = false; rli->selectdoc_quits = false; rli->suffix = suffix; rli->cur_entries = rli->alloc_entries = 0; rli->cur_chars = rli->alloc_chars = 0; if (rli->entries = malloc(sizeof(char *) * DEF_ENTRIES_ALLOC_SIZE)) { if (rli->chars = malloc(sizeof(char) * DEF_CHARS_ALLOC_SIZE)) { if (rli->lengths = malloc(sizeof(int) * DEF_ENTRIES_ALLOC_SIZE)) { /* lengths will track alloc_entries, so we don't have to track it separately. */ rli->alloc_entries = DEF_ENTRIES_ALLOC_SIZE; rli->alloc_chars = DEF_CHARS_ALLOC_SIZE; return OK; } free(rli->chars); } free(rli->entries); } rli->chars = NULL; rli->entries = NULL; rli->lengths = NULL; return OUT_OF_MEMORY; } /* req_list strings are stored with a trailing '\0', followed by an optional suffix character, and an additional trailing '\0'. This allows comparing strings w/o having to consider the optional suffixes while adding entries to the req_list. Finalizing the req_list effectively shifts the suffixes left, exchanging them for the preceding '\0'. After this operation, all the strings will be just normal C strings, some of which happen to end with the suffix character, and all of which are followed by two null bytes. req_list_finalize() also initializes the reorder array IFF allow_reorder is true. If the array cannot be allocated, allow_reorder is simply reset to false rather than returning an error. But if your system is this tight on RAM, you've got bigger problems on the way. */ void req_list_finalize(req_list * const rlf) { /* until now, entries and suffixes have been stored as: 'a' 'b' 'c' '\0' suffix '\0' Morph that into 'a' 'b' 'c' suffix '\0' '\0' */ for (int i = 0; i < rlf->cur_entries; i++) { const int len = strlen(rlf->entries[i]); *(rlf->entries[i]+len) = *(rlf->entries[i]+len+1); *(rlf->entries[i]+len+1) = '\0'; } rlf->reorder = NULL; if (rlf->allow_reorder ) { if ( rlf->reorder = malloc(sizeof(int) * rlf->cur_entries)) { for (int i = 0; i < rlf->cur_entries; i++) rlf->reorder[i] = i; } else rlf->allow_reorder = false; } } /* Add a string plus an optional suffix to a request list. We really add two null-terminated strings: the actual entry, and a possibly empty suffix. These pairs should be merged later by req_list_finalize(). If duplicates are not allowed (see req_list_init()) and the str already exists in the table (according to the comparison function or by strcmp if there is no comparison function), then the conflicting entry is returned. Otherwise, the new entry is returned. On error, NULL is returned.*/ char *req_list_add(req_list * const rla, char * const str, const int suffix) { const int len = strlen(str); const int lentot = len + ((rla->suffix && suffix) ? 3 : 2); /* 'a b c \0 Suffix \0' or 'a b c \0 \0' */ int ins; if (rla->cmpfnc) { /* implies the entries are sorted */ int l = 0, m = 0; int r = rla->cur_entries - 1; while(l <= r) { m = (r + l)/2; const int cmp = (*rla->cmpfnc)(str, rla->entries[m]); if (cmp < 0 ) r = m - 1; else if (cmp > 0) l = m + 1; else { m = - m - 1; break; } } if (m < 0) { /* found a match at -m - 1 */ if (!rla->allow_dupes) return rla->entries[-m - 1]; ins = -m; } else if (r < m) { ins = m; /* insert at i */ } else if (l > m) { ins = m + 1; /* insert at i + 1 */ } else { /* impossible! */ ins = rla->cur_entries; } } else {/* not ordered */ ins = rla->cur_entries; /* append to end */ if (!rla->allow_dupes) { for(int i = 0; i < rla->cur_entries; i++) if(!strcmp(rla->entries[i], str)) return rla->entries[i]; } } /* make enough space to store the new string */ if (rla->cur_chars + lentot > rla->alloc_chars) { char * p0 = rla->chars; char * p1 = malloc(sizeof(char) * (rla->alloc_chars * 2 + lentot)); if (!p1) return NULL; memcpy(p1, p0, rla->alloc_chars); rla->alloc_chars = rla->alloc_chars * 2 + lentot; rla->chars = p1; /* all the strings just moved from *p0 to *p1, so adjust accordingly */ for (int i = 0; i < rla->cur_entries; i++) rla->entries[i] += ( p1 - p0 ); free(p0); } /* make enough slots to hold the string pointer and lengths */ if (rla->cur_entries >= rla->alloc_entries) { char **newentries; int *newlens, orig_alloc_entries = rla->alloc_entries; if (newentries = realloc(rla->entries, sizeof(char *) * (rla->alloc_entries * 2 + 1))) { rla->alloc_entries = rla->alloc_entries * 2 + 1; rla->entries = newentries; if (newlens = realloc(rla->lengths, sizeof(int) * (orig_alloc_entries * 2 + 1))) { rla->lengths = newlens; } else if (newentries = realloc(rla->entries, sizeof(char *) * orig_alloc_entries)) { rla->entries = newentries; rla->alloc_entries = orig_alloc_entries; return NULL; } else { rla->alloc_entries = orig_alloc_entries; /* not true, but it's the min(entries,lengths) */ return NULL; } } else return NULL; } char * const newstr = &rla->chars[rla->cur_chars]; char * p = strcpy(newstr, str)+len+1; if (rla->suffix && suffix) *p++ = rla->suffix; *p = '\0'; rla->cur_chars += lentot; if (ins < rla->cur_entries) { memmove(&rla->entries[ins+1], &rla->entries[ins], sizeof(char *) * (rla->cur_entries - ins)); memmove(&rla->lengths[ins+1], &rla->lengths[ins], sizeof(int) * (rla->cur_entries - ins)); } rla->entries[ins] = newstr; rla->lengths[ins] = lentot; rla->cur_entries++; return newstr; } ne-3.3.4/src/search.c000066400000000000000000000450371475116431000143360ustar00rootroot00000000000000/* Search/replace functions (with and without regular expressions). Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2025 Todd M. Lewis and Sebastiano Vigna This file is part of ne, the nice editor. This library 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 library 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 . */ #include "ne.h" #include "regex.h" #include "support.h" /* This is the initial allocation size for regex.library. */ #define START_BUFFER_SIZE 4096 /* A boolean recording whether the last replace was for an empty string (of course, this can happen only with regular expressions). */ bool last_replace_empty_match; /* This array is used both by the Boyer-Moore algorithm and by the regex library. It is updated if b->find_string_changed != search_serial_num (which should be the case the first time the string is searched for). */ static unsigned int d[256]; /* Track static search compilation data by incremented serial counter. Compared with b->find_string_changed, which gets set to 1 when the buffer wants to force a recompile. We never set search_serial_num to 0 or 1. If search_serial_num != b->find_string_changed we know either the last compilation was for a different buffer, a different string for this buffer, or this is the first compilation. */ static unsigned int search_serial_num = 2; /* This macro upper cases a character or not, depending on the boolean sense_case. It is used in find(). The result is always unsigned. */ #define CONV(c) (sense_case ? (unsigned char)c : up_case[(unsigned char)c]) /* This vector is a translation table for the regex library which maps lower case characters to upper case characters. It's normally adjusted on startup according to the current locale. */ unsigned char localised_up_case[256]; /* This vector is a translation table for the regex library which maps ASCII lower case characters to upper case characters. */ const unsigned char ascii_up_case[256] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, 0x60, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F, 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF, 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF, 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF, 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF, 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF }; /* Performs a search for the given pattern with a simplified Boyer-Moore algorithm starting at the given position, in the given direction, skipping a possible match at the current cursor position if skip_first is true. The search direction depends on b->opt.search_back. If pattern is NULL, it is fetched from b->find_string. In this case, b->find_string_changed is checked, and, if equal to search_serial_num, the string is not recompiled. Please check to set b->find_string_changed = 1 to force a recompile whenever a new string is set in b->find_string. The cursor is moved on the occurrence position if a match is found. */ int find(buffer * const b, const char *pattern, const bool skip_first, bool wrap_once) { bool recompile_string; if (!pattern) { pattern = b->find_string; recompile_string = b->find_string_changed != search_serial_num || b->last_was_regexp; } else recompile_string = true; if (recompile_string) { b->find_string_changed = 0; search_serial_num = ((search_serial_num & ~1) + 2)|2; } const int m = strlen(pattern); if (!pattern || !m) return ERROR; if (recompile_string) for(int i = 0; i < sizeof d / sizeof *d; i++) d[i] = m; const unsigned char * const up_case = b->encoding == ENC_UTF8 ? ascii_up_case : localised_up_case; const bool sense_case = (b->opt.case_search != 0); line_desc *ld = b->cur_line_desc; int64_t y = b->cur_line; stop = false; if (! b->opt.search_back) { if (recompile_string) { for(int i = 0; i < m - 1; i++) d[CONV(pattern[i])] = m - i-1; b->find_string_changed = search_serial_num; } char * p = ld->line + b->cur_pos + m - 1 + (skip_first ? 1 : 0); const unsigned char first_char = CONV(pattern[m - 1]); int64_t wrap_lines_left = b->num_lines + 1; while(y < b->num_lines && !stop && wrap_lines_left--) { assert(ld->ld_node.next != NULL); if (ld->line_len >= m) { while((p - ld->line) < ld->line_len) { const unsigned char c = CONV(*p); if (c != first_char) p += d[c]; else { int i; for (i = 1; i < m; i++) if (CONV(*(p - i)) != CONV(pattern[m - i-1])) { p += d[c]; break; } if (i == m) { goto_line_pos(b, y, (p - ld->line) - m + 1); return OK; } } } } ld = (line_desc *)ld->ld_node.next; if (ld->ld_node.next) p = ld->line + m-1; else if (wrap_once) { wrap_once = false; ld = (line_desc *)b->line_desc_list.head; p = ld->line + m-1; y = -1; } y++; } } else { if (recompile_string) { for(int i = m - 1; i > 0; i--) d[CONV(pattern[i])] = i; b->find_string_changed = search_serial_num; } char * p = ld->line + (b->cur_pos > ld->line_len - m ? ld->line_len - m : b->cur_pos + (skip_first ? -1 : 0)); const unsigned char first_char = CONV(pattern[0]); int64_t wrap_lines_left = b->num_lines + 1; while(y >= 0 && !stop && wrap_lines_left--) { assert(ld->ld_node.prev != NULL); if (ld->line_len >= m) { while((p - ld->line) >= 0) { const unsigned char c = CONV(*p); if (c != first_char) p -= d[c]; else { int i; for (i = 1; i < m; i++) if (CONV(*(p + i)) != CONV(pattern[i])) { p -= d[c]; break; } if (i == m) { goto_line_pos(b, y, p - ld->line); return OK; } } } } ld = (line_desc *)ld->ld_node.prev; if (ld->ld_node.prev) p = ld->line + ld->line_len - m; else if (wrap_once) { wrap_once = false; ld = (line_desc *)b->line_desc_list.tail_pred; p = ld->line + ld->line_len - m; y = b->num_lines; } y--; } } return stop ? STOPPED : NOT_FOUND; } /* Replaces n characters with the given string at the current cursor position, and then moves it to the end of the string. */ int replace(buffer * const b, const int n, const char * const string) { int64_t len; assert(string != NULL); last_replace_empty_match = false; len = strlen(string); start_undo_chain(b); delete_stream(b, b->cur_line_desc, b->cur_line, b->cur_pos, n); if (len) insert_stream(b, b->cur_line_desc, b->cur_line, b->cur_pos, string, len); end_undo_chain(b); if (! b->opt.search_back) goto_pos(b, b->cur_pos + len); return OK; } /* The following variables are used by regex. In particular, re_reg holds the start/end of the extended replacement registers. */ static struct re_pattern_buffer re_pb; static struct re_registers re_reg; /* This string is used to replace the dot in UTF-8 searches. It will match only whole UTF-8 sequences. */ #define UTF8DOT "([\x01-\x7F\xC0-\xFF][\x80-\xBF]*)" /* This string is prefixed to a complemented character class to force matches against UTF-8 non-US-ASCII characters. It will match any UTF-8 sequence of length at least two, besides all characters expressed by the character class. Note that a closing ] and a closing ) must be appended. */ #define UTF8COMP "([\xC0-\xFF][\x80-\xBF]+|[^" /* This string is used to replace non-word-constituents (\W) in UTF-8 searches. It will match only whole UTF-8 sequences of non-word-constituent characters. */ #define UTF8NONWORD "([\x01-\x1E\x20-\x2F\x3A-\x40\x5B-\x60\x7B-\x7F]|[\xC0-\xFF][\x80-\xBF]+)" /* In UTF-8 text, the numbering of a parenthesised group may differ from the "official" one, due to the usage of parenthesis in UTF8DOT, UT8COMP and UTF8NONWORD. This array records for each user-invoked group the corresponding (usually larger) regex group. The group may be larger than RE_NREGS, in which case there is no way to recover it. */ static int map_group[RE_NREGS]; /* Works exactly like find(), but uses the regex library instead. */ int find_regexp(buffer * const b, const char *regex, const bool skip_first, bool wrap_once) { const unsigned char * const up_case = b->encoding == ENC_UTF8 ? ascii_up_case : localised_up_case; bool recompile_string; if (!regex) { regex = b->find_string; recompile_string = b->find_string_changed != search_serial_num || !b->last_was_regexp; } else recompile_string = true; if (recompile_string) { b->find_string_changed = 0; search_serial_num = ((search_serial_num & ~1) + 2)|2; } if (!regex || !strlen(regex)) return ERROR; if (re_pb.buffer == NULL) { if (re_pb.buffer = malloc(START_BUFFER_SIZE)) re_pb.allocated = START_BUFFER_SIZE; else return OUT_OF_MEMORY; } re_pb.fastmap = (void *)d; /* We have to be careful: even if the search string has not changed, it is possible that case sensitivity has. In this case, we force recompilation. */ if (b->opt.case_search) { if (re_pb.translate != 0) recompile_string = true; re_pb.translate = 0; } else { if (re_pb.translate != up_case) recompile_string = true; re_pb.translate = (unsigned char *)up_case; } if (recompile_string) { const char *actual_regex = regex; /* If the buffer encoding is UTF-8, we need to replace dots with UTF8DOT, non-word-constituents (\W) with UTF8NONWORD, and embed complemented character classes in UTF8COMP, so that they do not match UTF-8 subsequences. Moreover, we must compute the remapping from the virtual to the actual groups caused by the new groups thus introduced. */ if (b->encoding == ENC_UTF8) { const char *s; char *q; bool escape = false; int virtual_group = 0, real_group = 0, dots = 0, comps = 0, nonwords = 0; s = regex; /* We first scan regex to compute the exact number of characters of the actual (i.e., after substitutions) regex. */ do { if (!escape) { if (*s == '.') dots++; else if (*s == '[') { if (*(s+1) == '^') { comps++; s++; } if (*(s+1) == ']') s++; /* A literal ]. */ /* We scan the list up to ] and check that no non-US-ASCII characters appear. */ do if (utf8len(*(++s)) != 1) return UTF8_REGEXP_CHARACTER_CLASS_NOT_SUPPORTED; while(*s && *s != ']'); } else if (*s == '\\') { escape = true; continue; } } else if (*s == 'W') nonwords++; escape = false; } while(*(++s)); actual_regex = q = malloc(strlen(regex) + 1 + (strlen(UTF8DOT) - 1) * dots + (strlen(UTF8NONWORD) - 2) * nonwords + (strlen(UTF8COMP) - 1) * comps); if (!actual_regex) return OUT_OF_MEMORY; s = regex; escape = false; do { if (escape || *s != '.' && *s != '(' && *s != '[' && *s != '\\') { if (escape && *s == 'W') { q--; strcpy(q, UTF8NONWORD); q += strlen(UTF8NONWORD); real_group++; } else *(q++) = *s; } else { if (*s == '\\') { escape = true; *(q++) = '\\'; continue; } if (*s == '.') { strcpy(q, UTF8DOT); q += strlen(UTF8DOT); real_group++; } else if (*s == '(') { *(q++) = '('; if (virtual_group < RE_NREGS - 1) map_group[++virtual_group] = ++real_group; } else if (*s == '[') { if (*(s+1) == '^') { strcpy(q, UTF8COMP); q += strlen(UTF8COMP); s++; if (*(s+1) == ']') *(q++) = *(++s); /* A literal ]. */ do *(q++) = *(++s); while (*s && *s != ']'); if (*s) *(q++) = ')'; real_group++; } else { *(q++) = '['; if (*(s+1) == ']') *(q++) = *(++s); /* A literal ]. */ do *(q++) = *(++s); while (*s && *s != ']'); } } } escape = false; } while(*(s++)); /* This assert may be false if a [ is not closed. */ assert(strlen(actual_regex) == strlen(regex) + (strlen(UTF8DOT) - 1) * dots + (strlen(UTF8NONWORD) - 2) * nonwords + (strlen(UTF8COMP) - 1) * comps); } const char * p = re_compile_pattern(actual_regex, strlen(actual_regex), &re_pb); if (b->encoding == ENC_UTF8) free((void*)actual_regex); if (p) { /* Here we have a very dirty hack: since we cannot return the error of regex, we print it here. Which means that we access term.c's functions. 8^( */ print_message(p); alert(); return ERROR; } } b->find_string_changed = search_serial_num; line_desc *ld = b->cur_line_desc; int64_t y = b->cur_line; stop = false; if (! b->opt.search_back) { int64_t start_pos = b->cur_pos + (skip_first ? 1 : 0); int64_t wrap_lines_left = b->num_lines + 1; while(y < b->num_lines && !stop && wrap_lines_left--) { assert(ld->ld_node.next != NULL); int64_t pos; if (start_pos <= ld->line_len && (pos = re_search(&re_pb, ld->line ? ld->line : "", ld->line_len, start_pos, ld->line_len - start_pos, &re_reg)) >= 0) { goto_line_pos(b, y, pos); return OK; } ld = (line_desc *)ld->ld_node.next; start_pos = 0; y++; if (wrap_once && y == b->num_lines) { wrap_once = false; ld = (line_desc *)b->line_desc_list.head; y = 0; } } } else { int64_t start_pos = b->cur_pos + (skip_first ? -1 : 0); int64_t wrap_lines_left = b->num_lines + 1; while(y >= 0 && !stop && wrap_lines_left--) { assert(ld->ld_node.prev != NULL); int64_t pos; if (start_pos >= 0 && (pos = re_search(&re_pb, ld->line ? ld->line : "", ld->line_len, start_pos, -start_pos - 1, &re_reg)) >= 0) { goto_line_pos(b, y, pos); return OK; } ld = (line_desc *)ld->ld_node.prev; if (ld->ld_node.prev) start_pos = ld->line_len; else if (wrap_once) { wrap_once = false; ld = (line_desc *)b->line_desc_list.tail_pred; start_pos = ld->line_len; y = b->num_lines; } y--; } } return stop ? STOPPED : NOT_FOUND; } /* This allows regexp users to retrieve matched substrings. They are responsible for freeing these strings. n should be <= number of paren groups in original regex. Note that n is the actual group index--no remapping is performed. */ char *nth_regex_substring(const line_desc *ld, const int n) { char *str; if (n >= RE_NREGS) return NULL; if (n > 0 && n < re_reg.num_regs ) { if (str = malloc(re_reg.end[n] - re_reg.start[n] + 1)) { memcpy(str, ld->line + re_reg.start[n], re_reg.end[n] - re_reg.start[n]); str[re_reg.end[n] - re_reg.start[n]] = 0; return str; } } return NULL; } /* This allows regexp users to check whether matched substrings are nonempty. Note that n is the actual group index--no remapping is performed. */ bool nth_regex_substring_nonempty(const line_desc *ld, const int n) { if (n >= RE_NREGS) return false; if (n > 0 && n < re_reg.num_regs) return re_reg.start[n] != re_reg.end[n]; return false; } /* Replaces a regular expression. The given string can contain \0, \1 etc. for the pattern matched by the i-th pair of brackets (\0 is the whole string). */ int replace_regexp(buffer * const b, const char * const string) { assert(string != NULL); bool reg_used = false; char *p, *q, *t = NULL; if (q = p = str_dup(string)) { int len = strlen(p); while(true) { while(*q && *q != '\\') q++; if (!*q) break; int i = *(q + 1) - '0'; if (*(q + 1) == '\\') { memmove(q, q + 1, strlen(q + 1) + 1); q++; len--; } else if (i >= 0 && i < re_reg.num_regs && re_reg.start[i] >= 0) { if (b->encoding == ENC_UTF8) { /* In the UTF-8 case, the replacement group index must be mapped through map_group to recover the real group. */ if ((i = map_group[i]) >= RE_NREGS) { free(p); return GROUP_NOT_AVAILABLE; } } *q++ = 0; *q++ = i; reg_used = true; } else { free(p); return WRONG_CHAR_AFTER_BACKSLASH; } } if (reg_used) { if (t = malloc(re_reg.end[0] - re_reg.start[0] + 1)) { memcpy(t, b->cur_line_desc->line + re_reg.start[0], re_reg.end[0] - re_reg.start[0]); t[re_reg.end[0] - re_reg.start[0]] = 0; } else { free(p); return OUT_OF_MEMORY; } } for(int i = re_reg.num_regs; i-- != 0;) { re_reg.end[i] -= re_reg.start[0]; re_reg.start[i] -= re_reg.start[0]; } start_undo_chain(b); delete_stream(b, b->cur_line_desc, b->cur_line, b->cur_pos, re_reg.end[0]); q = p; int64_t pos = 0; while(true) { if (strlen(q)) { insert_stream(b, b->cur_line_desc, b->cur_line, b->cur_pos + pos, q, strlen(q)); pos += strlen(q); } q += strlen(q) + 1; if (q - p > len) break; assert(*q < RE_NREGS); if (re_reg.end[*(unsigned char *)q] - re_reg.start[*(unsigned char *)q]) { char c = t[re_reg.end[*(unsigned char *)q]]; t[re_reg.end[*(unsigned char *)q]] = 0; insert_stream(b, b->cur_line_desc, b->cur_line, b->cur_pos + pos, t + re_reg.start[*(unsigned char *)q], re_reg.end[*(unsigned char *)q] - re_reg.start[*(unsigned char *)q]); t[re_reg.end[*(unsigned char *)q]] = c; pos += re_reg.end[*(unsigned char *)q] - re_reg.start[*(unsigned char *)q]; } q++; } end_undo_chain(b); if (! b->opt.search_back) goto_pos(b, b->cur_pos + pos); free(t); free(p); } else return OUT_OF_MEMORY; last_replace_empty_match = re_reg.start[0] == re_reg.end[0]; return OK; } ne-3.3.4/src/signals.c000066400000000000000000000102111475116431000145130ustar00rootroot00000000000000/* Signal handling setup and code. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2025 Todd M. Lewis and Sebastiano Vigna This file is part of ne, the nice editor. This library 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 library 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 . */ #include "ne.h" #include /* These variables remember if we are already in a signal handling code. In this case, the arrival of another signal must kill us. */ static bool fatal_code_in_progress, fatal_error_code; /* This code is called by all the fatal signals. It records that something bad is happening, and then tries to autosave all the files currently in memory using auto_save(). If another signal arrives during the execution, we exit without any other delay. */ static void fatal_code(const int sig) { fatal_error_code = sig; signal (sig, SIG_DFL); if (fatal_code_in_progress) kill(getpid (), fatal_error_code); fatal_code_in_progress = true; /* Let us clean up the terminal configuration. */ unset_interactive_mode(); apply_to_list(&buffers, auto_save); kill(getpid (), fatal_error_code); } /* The next function handles the suspend/restart system. When stopped, we reset the terminal status, set up the continuation handler and let the system stop us by sending again a TSTP signal, this time using the default handler. */ void stop_ne(void) { unset_interactive_mode(); kill(0, SIGTSTP); set_interactive_mode(); clear_entire_screen(); ttysize(); } /* This mask will hold all the existing signals. */ static sigset_t signal_full_mask; /* Diverts to fatal_code() the behaviour of all fatal signals. Moreover, signal_full_mask is filled with all the existing signals. PORTABILITY PROBLEM: certain systems could have extra, non-POSIX signals whose trapping could be necessary. Feel free to add other signals to this list, but please leave SIGINT for the interrupt character. */ void set_fatal_code(void) { sigfillset (&signal_full_mask); signal(SIGALRM, fatal_code); signal(SIGILL, fatal_code); signal(SIGABRT, fatal_code); signal(SIGFPE, fatal_code); signal(SIGSEGV, fatal_code); signal(SIGTERM, fatal_code); signal(SIGHUP, SIG_IGN);//fatal_code); signal(SIGQUIT, fatal_code); signal(SIGPIPE, fatal_code); signal(SIGUSR1, fatal_code); signal(SIGUSR2, fatal_code); signal(SIGTTIN, fatal_code); } /* This variable keeps track of the degree of nesting of blocking and releasing signals, so that block_signals() and release_signals() can be safely called at any time. */ static int signal_block_nest_count; /* The following functions block and release, respectively, all signals (except of course the ones which cannot be trapped). They are used in order to make atomic sections which modify vital parts of the internal state, such as lists. */ void block_signals(void) { if (!signal_block_nest_count++) sigprocmask(SIG_BLOCK, &signal_full_mask, NULL); } void release_signals(void) { if (!--signal_block_nest_count) sigprocmask(SIG_UNBLOCK, &signal_full_mask, NULL); } /* Handles SIGQUIT. It just sets the stop global variable to true, so that the interested functions can check it, and restores itself as signal handler. */ void set_stop (const int sig) { signal(sig, SIG_IGN); stop = true; signal(sig, set_stop); } /* Handles SIGINT. It just restores itself as signal handler. */ void handle_int (const int sig) { signal(sig, handle_int); } /* Handles SIGWINCH, if present. It calls ttysize(). */ #ifdef SIGWINCH void handle_winch (const int sig) { signal(sig, SIG_IGN); window_changed_size = ttysize(); signal(sig, handle_winch); } #endif bool window_changed_size; bool stop; ne-3.3.4/src/streams.c000066400000000000000000000176501475116431000145470ustar00rootroot00000000000000/* Stream handling functions. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2025 Todd M. Lewis and Sebastiano Vigna This file is part of ne, the nice editor. This library 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 library 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 . */ #include "ne.h" /* This is the least increment with which a char_stream is reallocated. */ #define CHAR_STREAM_SIZE_INC (2048) /* Allocates a stream of size bytes. Note that a size of 0 is legal, in which case a char_stream structure is allocated, but its stream pointer is left NULL. */ char_stream *alloc_char_stream(const int64_t size) { char_stream * const cs = calloc(1, sizeof *cs); if (cs) { if (!size || (cs->stream = calloc(size, sizeof *cs->stream))) { cs->size = size; assert_char_stream(cs); return cs; } free(cs); } return NULL; } /* Frees a stream. */ void free_char_stream(char_stream * const cs) { if (!cs) return; assert_char_stream(cs); free(cs->stream); free(cs); } /* Reallocates a stream. If cs is NULL, it is equivalent to alloc_char_stream(). Otherwise, the memory pointed by stream is realloc()ated to size bytes. If the reallocation is successful, cs is returned, otherwise NULL. */ char_stream *realloc_char_stream(char_stream * const cs, const int64_t size) { if (!cs) return alloc_char_stream(size); assert_char_stream(cs); if (!size) { free(cs->stream); cs->stream = NULL; cs->len = cs->size = 0; return cs; } if (cs->stream = realloc(cs->stream, size * sizeof *cs->stream)) { cs->size = size; if (cs->len > size) cs->len = size; return cs; } return NULL; } /* Duplicates a stream. If cs in NULL, it is equivalent to alloc_char_stream(). */ char_stream *dup_stream(char_stream * const cs) { if (!cs) return alloc_char_stream(0); assert_char_stream(cs); char_stream *cs_new = alloc_char_stream(cs->len); if (cs_new && cs->len) { memcpy(cs_new->stream, cs->stream, cs->len); cs_new->len = cs->len; } return cs_new; } /* Concatenates a block of len bytes pointed to by s to a stream. The stream is extended if necessary. Returns an error code. */ int add_to_stream(char_stream * const cs, const char * const s, const int64_t len) { if (!s) return OK; if (!cs) return ERROR; if (cs->size - cs->len < len && !realloc_char_stream(cs, cs->len + len + CHAR_STREAM_SIZE_INC)) return OUT_OF_MEMORY; memcpy(cs->stream + cs->len, s, len); cs->len += len; return OK; } /* Inserts a block of len bytes pointed to by s into a stream at offset pos. The stream is extended if necessary. Returns an error code. */ int insert_in_stream(char_stream *cs, const char *s, const int64_t pos, const int64_t len) { int64_t tail; if (!s || !len ) return OK; if (!cs) return ERROR; if (pos > cs->len) return ERROR; tail = cs->len - pos; if (cs->size - cs->len < len && !realloc_char_stream(cs, cs->len + len + CHAR_STREAM_SIZE_INC)) return OUT_OF_MEMORY; if (tail > 0) memmove(cs->stream + pos + len, cs->stream + pos, tail); memcpy(cs->stream + pos, s, len); cs->len += len; return OK; } /* Deletes a block of len bytes from stream cs at offset p. The stream size does not change. Returns an error code. */ int delete_from_stream(char_stream * const cs, const int64_t pos, int64_t len) { if (!len) return OK; if (!cs) return ERROR; if (len > cs->len) len = cs->len; memmove(cs->stream + pos, cs->stream + pos + len, cs->len - (pos + len)); cs->len -= len; return OK; } /* Resets a character stream. If cs is NULL, an empty character stream is returned. If it is non-NULL, everything inside it is freed. The stream memory is deallocated, unless its size is smaller or equal to 2*CHAR_STREAM_SIZE_INC (so that we won't continuously allocate and deallocate small streams). */ char_stream *reset_stream(char_stream * const cs) { if (!cs) return alloc_char_stream(0); assert_char_stream(cs); cs->len = 0; if (cs->size > 2 * CHAR_STREAM_SIZE_INC) { cs->size = 0; free(cs->stream); cs->stream = NULL; } return cs; } /* Sets the encoding of this stream by guessing it. The source type is used to avoid guessing UTF-8 when the source of this clip is ENC_8_BIT. */ void set_stream_encoding(char_stream * const cs, const encoding_type source) { cs->encoding = detect_encoding(cs->stream, cs->len); if (source == ENC_8_BIT && cs->encoding == ENC_UTF8) cs->encoding = ENC_8_BIT; } /* These two functions load a stream in memory. Carriage returns and line feeds are converted to NULLs. You can pass NULL for cs, and a char stream will be allocated for you. If preserve_cr is true, CRs are preserved. If binary is true, the stream is filled exactly with the file content. */ char_stream *load_stream(char_stream * cs, const char *name, const bool preserve_cr, const bool binary) { assert_char_stream(cs); assert(name != NULL); name = tilde_expand(name); if (is_directory(name) || is_migrated(name)) return NULL; const int fd = open(name, READ_FLAGS); cs = load_stream_from_fd(cs, fd, preserve_cr, binary); if (fd >= 0) close(fd); return cs; } char_stream *load_stream_from_fd(char_stream *cs, const int fd, const bool preserve_cr, const bool binary) { if (fd < 0) return NULL; char terminators[] = { 0x0d, 0x0a }; if (preserve_cr) terminators[0] = 0; assert_char_stream(cs); off_t len = lseek(fd, 0, SEEK_END); if (len < 0) return NULL; lseek(fd, 0, SEEK_SET); if (!(cs = realloc_char_stream(cs, len))) return NULL; if (read_safely(fd, cs->stream, len) < len) { free_char_stream(cs); return NULL; } if (binary) { cs->len = len; assert_char_stream(cs); return cs; } int64_t j; for(int64_t i = j = 0; i < len; i++, j++) { if (i < len - 1 && !preserve_cr && cs->stream[i] == '\r' && cs->stream[i + 1] == '\n') i++; cs->stream[j] = cs->stream[i]; if (cs->stream[j] == terminators[0] || cs->stream[j] == terminators[1]) cs->stream[j] = 0; } memset(cs->stream + j, 0, len - j); cs->len = j; assert_char_stream(cs); return cs; } /* These two functions save a stream to file. NULLs are converted to line feeds. If CRLF is true, we save CR/LF pairs as line terminators. If binary is true, the stream is dump literally. We return an error code. */ int save_stream(const char_stream *const cs, const char *name, const bool CRLF, const bool binary) { if (!cs) return ERROR; assert_char_stream(cs); assert(name != NULL); name = tilde_expand(name); if (is_directory(name)) return FILE_IS_DIRECTORY ; if (is_migrated(name)) return FILE_IS_MIGRATED ; const int fd = open(name, WRITE_FLAGS, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); if (fd >= 0) { const int error = save_stream_to_fd(cs, fd, CRLF, binary); close(fd); return error; } return CANT_OPEN_FILE; } int save_stream_to_fd(const char_stream *const cs, const int fd, const bool CRLF, const bool binary) { if (!cs) return ERROR; assert_char_stream(cs); if (binary) { if (write(fd, cs->stream, cs->len) < cs->len) return ERROR_WHILE_WRITING; return OK; } for(int64_t pos = 0; pos < cs->len; ) { const int64_t len = strnlen_ne(cs->stream + pos, cs->len - pos); /* ALERT: must fraction this and other write/read in gigabyte batches. */ if (write(fd, cs->stream + pos, len) < len) return ERROR_WHILE_WRITING; if (pos + len < cs->len) { if (CRLF && write(fd, "\r", 1) < 1) return ERROR_WHILE_WRITING; if (write(fd, "\n", 1) < 1) return ERROR_WHILE_WRITING; } pos += len + 1; } return OK; } ne-3.3.4/src/support.c000066400000000000000000000530421475116431000146000ustar00rootroot00000000000000/* Miscellaneous support functions. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2025 Todd M. Lewis and Sebastiano Vigna This file is part of ne, the nice editor. This library 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 library 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 . */ #include "ne.h" #include "support.h" #include "cm.h" #include #include #include /* Some systems do not define _POSIX_VDISABLE. We try to establish a reasonable value. */ #ifndef _POSIX_VDISABLE #define _POSIX_VDISABLE 0 #endif /* Returns a pointer to the global ne directory if the environment variable NE_GLOBAL_DIR is set. If it isn't set, then GLOBALDIR is returned. */ const char *get_global_dir(void) { char *ne_global_dir; ne_global_dir = getenv("NE_GLOBAL_DIR"); if (!ne_global_dir) ne_global_dir = GLOBALDIR; return tilde_expand(ne_global_dir); } /* Some UNIXes allow "getcwd(NULL, size)" and will allocate the buffer for you when your first parm is NULL. This is not really legal, so we've put a front end onto getcwd() called ne_getcwd() that allocates the buffer for you first. */ char *ne_getcwd(const int bufsize) { char *result = malloc(bufsize); if (result) result = getcwd(result, bufsize); return result; } /* 'normalize' path c. That is, remove multiple consecutive '/', "./", and deep "../". */ void normalize_path(char *c) { char *p = c; while (*p) { if ((p[0] == '.' || p[0] == '/') && p[1] == '/') { while ((p[0] == '.' || p[0] == '/') && p[1] == '/') { memmove(p, p+1, strlen(p)); } } else if (p[0] == '/') { p++; } else if (p[0] == '.' && p[1] == '.' && p[2] == '/') { char *t = p; if (t > c && *--t == '/') { while (t > c && *--t != '/') /* empty loop */ ; if (*t == '/' && (t == c || !( t[1] == '.' && t[2] == '.' && t[3] == '/'))) { memmove(t, p+2, strlen(p+2)+1); p = t + 1; } else p += 2; } else p += 2; } else { while (*p && *p != '/') p++; } while (p > c && *p == '/' && p[-1] == '/') p--; } } /* Given relative file path a and absolute directory path b, return a newly allocated file path c that is the absolute path to file a. Ex: "../../xx/yy/f.c","/aa/bb/dd" -> "/aa/xx/yy/f.c" The returned string has at least one extra char so it can be shifted if necessary as per relative_file_path(). */ char *absolute_file_path(const char *a0, const char *b) { char *a, *c; char *cc; const char *aa; if (!a0 || !b || b[0] != '/') return NULL; /* If *a is already an absolute path, get its relative equivalent first. */ if (a0[0] == '/') { a = relative_file_path(a0, b); if (!a) return NULL; } else a = (char *)a0; if ((c = malloc(strlen(a) + strlen(b) + 2))) { strcpy(c, b); normalize_path(c); cc = c + strlen(c); aa = a; while (*aa) { if (aa[0] == '.' && aa[1] == '.' && aa[2] == '/') { aa += 3; while (cc > c && *--cc != '/') ; } else if (aa[0] == '.' && aa[1] == '/') { aa += 2; } else { *cc++ = '/'; do { *cc++ = *aa++; } while (*aa && *aa != '/'); if (*aa == '/') aa++; } } *cc = '\0'; normalize_path(c); } if (a != a0) free (a); return c; } /* Given absolute file path aa and absolute directory path b, return a newly allocated file path c that is the relative path from b to aa. Ex: "/aa/bb/cc/x.c","/aa/bb/dd" -> "../cc/x.c" The returned string has one extra '\0' so request_files() can shift it. */ char *relative_file_path(const char *aa, const char *b) { int up_dirs=0, i, j=0; char *a, *c; if (!aa || !b) return NULL; a = str_dup(aa); if (!a) return NULL; normalize_path(a); int match = max_prefix(a, ENC_8_BIT, b, ENC_8_BIT); if (a[0] != '/' || b[0] != '/') { if (a) free(a); return NULL; } for (i=0; i<=match; i++) { if (a[i] == '/') { if (i == match && b[i]) break; j = i; } } for (i=j; i bool is_migrated(const char * const name) { struct cvxstat st; if (cvxstat(name, &st, sizeof(struct cvxstat)) == 0) if (st.st_dmonflags & IMIGRATED) return true; return false; } #elif defined ZERO_STAT_MIG_TEST /* Some systems which support hierarchical storage will report a non-zero file size but zero blocks used. (Since the file is on tape rather than disc, it's using no disc blocks.) If this describes the behaviour of your system, define ZERO_STAT_MIG_TEST when building ne. */ bool is_migrated(const char * const name) { struct stat statbuf; if ((stat(tilde_expand(name), &statbuf) == 0) && (statbuf.st_size > 0) && (statbuf.st_blocks == 0)) return true; else return false; } #else /* Most systems have no hierarchical storage facility and need never concern themselves with this problem. For these systems, is_migrated() will always be false. */ bool is_migrated(const char * const name) { return false; } #endif bool is_directory(const char * const name) { struct stat statbuf; return stat(tilde_expand(name), &statbuf) == 0 && S_ISDIR(statbuf.st_mode); } /* Returns the mtime of the named file, or 0 on error. If a file's mtime actually is 0, then this system probably doesn't keep mtimes, so we can't distinguish that case, but we probably don't care either, as that's also our sentinel value for buffers which have not been saved. */ unsigned long file_mod_time(const char *filename) { static struct stat statbuf; if ( stat(filename, &statbuf) ) return false; return statbuf.st_mtime; } /* Reads data from a file descriptors much as read() does, but never reads more than 1GiB in a single read(), ignores interruptions (EINTR) and tries again in case of EAGAIN errors. */ ssize_t read_safely(const int fd, void * const buf, const int64_t len) { for(size_t done = 0; done < len; ) { /* We read one GiB at a time, lest some OS complains. */ const int to_do = min(len - done, 1 << 30); const ssize_t t = read(fd, (char *)buf + done, to_do); if (t < 0) { if (errno == EINTR) continue; if (errno == EAGAIN) { /* We try again, but wait for a second. */ sleep(1); continue; } return t; } if (t == 0) return done; done += t; } return len; } /* Check a named file's mtime relative to a buffer's stored mtime. Note that stat errors are treated like 0 mtime, which also is the value for new buffers. Return values: true: if file's non-zero modification time differs from the buffer's mtime, false: everything else, including if the files mtime couldn't be checked (for example: possibly no file or couldn't stat). Uses filename from the buffer unless passed a name. */ bool buffer_file_modified(const buffer *b, const char *name) { assert_buffer(b); #ifdef NE_TEST return false; /* During tests, assume all buffers' files are safe to replace. */ #endif if (name == NULL) name = b->filename; if (!name) return false; name = tilde_expand(name); unsigned long fmtime = file_mod_time(name); if (fmtime && fmtime != b->mtime) return true; return false; } /* Returns a pointer to a tilde-expanded version of the string pointed to by filename. The string should not be free()ed, since it is tracked locally. Note that this function can return the same pointer which is passed, in case no tilde expansion has to be performed. */ const char *tilde_expand(const char * filename) { static char *expanded_filename; if (!filename) return NULL; if (filename[0] != '~') return filename; char *home_dir; if (filename[1] == '/') { home_dir = getenv("HOME"); if (!home_dir) return filename; filename++; } else { const char *s; char *t; s = filename + 1; while(*s && *s != '/') s++; struct passwd *passwd = NULL; if (t = malloc(s - filename)) { memcpy(t, filename + 1, s - filename - 1); t[s - filename - 1] = 0; passwd = getpwnam(t); free(t); } if (!passwd) return filename; filename = s; home_dir = passwd->pw_dir; } char * const p = realloc(expanded_filename, strlen(filename) + strlen(home_dir) + 1); if (p) { strcat(strcpy(expanded_filename = p, home_dir), filename); return expanded_filename; } return filename; } /* Given a pathname, returns a pointer to the real file name (i.e., the pointer points inside the string passed). */ const char *file_part(const char * const pathname) { if (!pathname) return NULL; const char * p = pathname + strlen(pathname); while(p > pathname && *(p - 1) != '/') p--; return p; } /* Duplicates a string. */ char *str_dup(const char * const s) { if (!s) return NULL; const int64_t len = strlen(s); char * const dup = malloc(len + 1); memcpy(dup, s, len + 1); return dup; } /* Copies len chars from s to a buffer, terminated with '\0'. The buffer is reused by strntmp(), so the caller must complete its use of the returned string before the next call to strntmp(). If len < 0, the buffer is freed. Returns the address of the buffer. */ char *strntmp(const char * const s, const int len) { static char *buf = NULL; static uint64_t buflen = 0; char *buf_new; int cplen = len; if (len < 1) { if (buf) free(buf); buf = NULL; buflen = 0; } else { if (len >= buflen) { if (buf_new = realloc(buf, len * 2 + 1)) { buflen = len * 2 + 1; buf = buf_new; } else if (buflen) cplen = buflen - 1; else cplen = 0; } if (cplen) strncpy(buf, s, cplen); buf[cplen] = '\0'; } return buf; } /* Tries to compute the length as a string of the given pointer, but stops after n characters (returning n). */ int64_t strnlen_ne(const char *s, int64_t n) { const char * const p = s; while(n-- != 0) if (!*(s++)) return s - p - 1; return s - p; } /* Compares strings for equality, but accepts NULLs. */ bool same_str(const char *p, const char *q) { if (p == q) return true; if (p == NULL || q == NULL) return false; return strcmp(p, q) == 0; } /* Computes the length of the maximal common prefix of s and t. */ int max_prefix(const char * const s, encoding_type s_enc, const char * const t, encoding_type t_enc) { int s_pos = 0, t_pos = 0; while (s[s_pos] && t[t_pos] && get_char(&s[s_pos], s_enc) == get_char(&t[t_pos], t_enc)) { s_pos = next_pos(s, s_pos, s_enc); t_pos = next_pos(t, t_pos, t_enc); } return s_pos; } /* Returns true if the first string is a prefix of the second one. */ bool is_prefix(const char * const p, const char * const s) { int i; for(i = 0; p[i] && s[i] && p[i] == s[i]; i++); return !p[i]; } /* The following *cmpp() functions are suitable for use with qsort(). They front comparison functions with "normal" (i.e., like strcmp()) calling conventions. */ /* A string pointer comparison function for qsort(). */ int strcmpp(const void *a, const void *b) { return strcmp(*(const char **)a, *(const char **)b); } /* Another comparison for qsort, this one does dictionary order. */ int strdictcmpp(const void *a, const void *b) { return strdictcmp(*(const char **)a, *(const char **)b); } int strdictcmp(const char *a, const char *b) { const int ci = strcasecmp(a, b); if (ci) return ci; return strcmp(a, b); } /* A filename comparison function for qsort(). It makes "../" the first string, "./" the second string and then orders lexicographically. */ int filenamecmpp(const void *a, const void *b) { return filenamecmp(*(const char **)a, *(const char **)b); } int filenamecmp(const char * s, const char * t) { if (strcmp(s, "../")==0) return strcmp(t, "../") == 0 ? 0 : -1; if (strcmp(s, "..")==0) return strcmp(t, "..") == 0 ? 0 : -1; if (strcmp(t, "../")==0) return 1; if (strcmp(t, "..")==0) return 1; if (strcmp(s, "./")==0) return strcmp(t, "./") == 0 ? 0 : -1; if (strcmp(s, ".")==0) return strcmp(t, ".") == 0 ? 0 : -1; if (strcmp(t, "./")==0) return 1; if (strcmp(t, ".")==0) return 1; return strdictcmp(s, t); } /* Sets the "interactive I/O mode" of the terminal. It suitably sets the mode bits of the termios structure, and then transmits various capability strings by calling set_terminal_modes(). This function assumes that the terminfo database has been properly initialized. The old_termios structure records the original state of the terminal interface. */ static struct termios termios, old_termios; void set_interactive_mode(void) { tcgetattr(0, &termios); old_termios = termios; termios.c_iflag &= ~(IXON | IXOFF | ICRNL | INLCR | ISTRIP); termios.c_iflag |= IGNBRK; termios.c_oflag &= ~OPOST; termios.c_lflag &= ~(ISIG | ICANON | ECHO | ECHONL | IEXTEN); /* Cygwin's signal must be disabled, or CTRL-C won't work. There is no way to really change the sequences associated to signals. */ #ifndef __CYGWIN__ termios.c_lflag |= ISIG; #endif termios.c_cflag &= ~(CSIZE | PARENB); termios.c_cflag |= CS8; termios.c_cc[VTIME] = 0; termios.c_cc[VMIN] = 1; /* Now we keep the kernel from intercepting any keyboard input in order to turn it into a signal. Note that some signals, such as dsusp on BSD, are not trackable here. They have to be disabled through suitable commands (for instance, `stty dsusp ^-'). */ termios.c_cc[VSUSP] = _POSIX_VDISABLE; termios.c_cc[VQUIT] = _POSIX_VDISABLE; termios.c_cc[VKILL] = _POSIX_VDISABLE; /* Control-\ is the stop control sequence for ne. */ termios.c_cc[VINTR] = '\\'-'@'; tcsetattr(0, TCSADRAIN, &termios); /* SIGINT is used for the interrupt character. */ signal(SIGINT, set_stop); /* We do not want to be stopped if we did not generate the signal */ signal(SIGTSTP, SIG_IGN); #ifdef SIGWINCH siginterrupt(SIGWINCH, 1); signal(SIGWINCH, handle_winch); #endif /* This ensures that a physical read will be performed at each getchar(). */ setbuf(stdin, NULL); /* We enable the keypad, cursor addressing, etc. */ set_terminal_modes(); interactive_mode = true; } /* Undoes the work of the previous function, in reverse order. It assumes the old_termios has been filled with the old termios structure. */ void unset_interactive_mode(void) { /* We move the cursor on the last line, clear it, and output a CR, so that the kernel can track the cursor position. Note that clear_to_eol() can move the cursor. */ losecursor(); move_cursor(ne_lines - 1, 0); clear_to_eol(); move_cursor(ne_lines - 1, 0); /* Now we disable the keypad, cursor addressing, etc. fflush() guarantees that tcsetattr() won't clip part of the capability strings output by reset_terminal_modes(). */ reset_terminal_modes(); putchar('\r'); fflush(stdout); /* Now we restore all the flags in the termios structure to the state they were before us. */ tcsetattr(0, TCSADRAIN, &old_termios); #ifdef SIGWINCH signal(SIGWINCH, SIG_IGN); #endif signal(SIGINT, SIG_DFL); signal(SIGTSTP, SIG_DFL); interactive_mode = false; } /* Given a readable file descriptor and a writable file descriptor, copies the content of the former into the latter for at most size bytes. Returns true on success, false on error. */ bool copy_file(const int in, const int out, size_t size) { char buffer[8192]; while(size) { const ssize_t to_do = min(size, sizeof buffer); ssize_t result = read(in, buffer, to_do); if (result < to_do) return false; if (write(out, buffer, to_do) < to_do) return false; size -= to_do; } return true; } /* Given a writable file descriptor, writes size zeroes to it. Returns true on success, false on error. */ bool zero_file(const int out, size_t size) { char buffer[8192] = {0}; while(size) { const ssize_t to_do = min(size, sizeof buffer); if (write(out, buffer, to_do) < to_do) return false; size -= to_do; } return true; } /* Allocates a region of memory either using malloc(), or using mmap() on a newly created file. If *force is -1, malloc() is tried first, then mmap(). If it is 0, just malloc() is tried. If it is 1, just mmap() is tried. If the result is not NULL, *force is set to 1 or 0 depending on whether mmap() or malloc() were used to allocate the region. In the case mmap() was used, the file is unlink()'d and its file descriptor closed: thus, after munmap()'ing the region it will automatically disappear. If fd_or_zero is zero, the region will be initialized to zero. Otherwise, fd_or_zero must be a readable file descriptor whose content will be used to initialize the region. If less than size bytes are available for reading this function will return NULL. */ void *alloc_or_mmap(const size_t size, const int fd_or_zero, int *force) { void *p = NULL; if (*force == 1 || *force == -1 && !(p = fd_or_zero? malloc(size) : calloc(1, size))) { *force = 1; char template[16] = ".ne-mmap-XXXXXX"; const int mapped_fd = mkstemp(template); if (mapped_fd) { if ((fd_or_zero ? copy_file(fd_or_zero, mapped_fd, size) : zero_file(mapped_fd, size)) && (p = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, mapped_fd, 0)) == MAP_FAILED) p = NULL; unlink(template); close(mapped_fd); } return p; } if (p == NULL) p = fd_or_zero? malloc(size) : calloc(1, size); if (p) { *force = 0; if (fd_or_zero) { if (read_safely(fd_or_zero, p, size) < size) { free(p); return NULL; } } } return p; } /* Detects (heuristically) the encoding of a piece of text. */ encoding_type detect_encoding(const char *ss, const int64_t len) { if (len == 0) return ENC_ASCII; const unsigned char *s = (const unsigned char *)ss; bool is_ascii = true; const unsigned char * const t = s + len; do { if (*s >= 0x80) { /* On US-ASCII text, we never enter here. */ is_ascii = false; /* Once we get here, we are either 8-bit or UTF-8. */ int l = utf8len(*s); if (l == -1) return ENC_8_BIT; else if (l > 1) { if (s + l > t) return ENC_8_BIT; else { /* We check for redundant representations. */ if (l == 2) { if (!(*s & 0x1E)) return ENC_8_BIT; } else if (!(*s & (1 << 7 - l) - 1) && !(*(s + 1) & ((1 << l - 2) - 1) << 8 - l)) return ENC_8_BIT; while(--l != 0) if ((*(++s) & 0xC0) != 0x80) return ENC_8_BIT; } } } } while(++s < t); return is_ascii ? ENC_ASCII : ENC_UTF8; } /* Returns a copy of the ne_isword() string to the left of the cursor, or an empty string if none is found. Consumer is responsible for seeing that the string is freed. *prefix_pos is the offset from the beginning of the current line where the prefix starts. */ int context_prefix(const buffer *b, char **p, int64_t *prefix_pos) { *prefix_pos = b->cur_pos; if (*prefix_pos && *prefix_pos <= b->cur_line_desc->line_len) { *prefix_pos = prev_pos(b->cur_line_desc->line, *prefix_pos, b->encoding); while (*prefix_pos && ne_isword(get_char(&b->cur_line_desc->line[*prefix_pos], b->encoding), b->encoding)) *prefix_pos = prev_pos(b->cur_line_desc->line, *prefix_pos, b->encoding); if (! ne_isword(get_char(&b->cur_line_desc->line[*prefix_pos], b->encoding), b->encoding)) *prefix_pos = next_pos(b->cur_line_desc->line, *prefix_pos, b->encoding); *p = malloc(b->cur_pos + 1 - *prefix_pos); if (!*p) return OUT_OF_MEMORY; strncpy(*p, &b->cur_line_desc->line[*prefix_pos], b->cur_pos - *prefix_pos); } else *p = malloc(1); /* no prefix left of the cursor; we'll give an empty one. */ (*p)[b->cur_pos - *prefix_pos] = 0; return OK; } /* Given a buffer, return a string like "134579-<>" indicating which bookmarks are set. */ const char *cur_bookmarks_string(const buffer *b) { static char str[NUM_BOOKMARKS]; char *s = str; int i; memset(str, 0, NUM_BOOKMARKS); for (i=0; ibookmark_mask & (1 << i )) *(s++) = "0123456789-<>w"[i]; } return str; } #define MAX_MESSAGE_SIZE 1024 static char str[MAX_MESSAGE_SIZE+1]; const char *cur_bracketed_paste_value(const buffer *b) { char *s = str; memset(str, 0, MAX_MESSAGE_SIZE); if (b->bpaste_support < 1) *s = '0'; else if (b->bpaste_support == 1) *s = '1'; else strncat(strncat(strncpy(str, b->bpaste_macro_before, MAX_MESSAGE_SIZE)," ",MAX_MESSAGE_SIZE), b->bpaste_macro_after, MAX_MESSAGE_SIZE); return str; } const char *cur_bracketed_paste_string(const buffer *b) { memset(str, 0, MAX_MESSAGE_SIZE); strncpy(str, "BracketedPaste ", MAX_MESSAGE_SIZE); if (b->bpaste_support < 1) strncat(str, "disabled", MAX_MESSAGE_SIZE); else if (b->bpaste_support == 1) strncat(str, "enabled with internal defaults", MAX_MESSAGE_SIZE); else if (b->bpaste_support == 2) strncat(strncat(strncat(strncat(str, "macros: ", MAX_MESSAGE_SIZE), b->bpaste_macro_before, MAX_MESSAGE_SIZE), ", ", MAX_MESSAGE_SIZE), b->bpaste_macro_after, MAX_MESSAGE_SIZE); return str; } ne-3.3.4/src/support.h000066400000000000000000000204111475116431000145770ustar00rootroot00000000000000/* Miscellaneous inline support functions. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2025 Todd M. Lewis and Sebastiano Vigna This file is part of ne, the nice editor. This library 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 library 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 . */ /* Returns the position of the character after the one pointed by pos in s. If s is NULL, just returns pos + 1. If encoding is UTF8 it uses utf8len() to move forward. */ static int64_t inline next_pos(const char * const ss, const int64_t pos, const encoding_type encoding) { const unsigned char *s = (const unsigned char *)ss; assert(encoding != ENC_UTF8 || s == NULL || utf8len(s[pos]) > 0); if (s == NULL) return pos + 1; if (encoding == ENC_UTF8) return pos + utf8len(s[pos]); else return pos + 1; } /* Returns the position of the character before the one pointed by pos in s. If s is NULL, just returns pos + 1. If encoding is UTF-8 moves back until the upper two bits are 10. If pos is 0, this function returns -1. */ static int64_t inline prev_pos(const char * const s, int64_t pos, const encoding_type encoding) { assert(pos >= 0); if (pos == 0) return -1; if (s == NULL) return pos - 1; if (encoding == ENC_UTF8) { while((s[--pos] & 0xC0) == 0x80 && pos > 0); return pos; } else return pos - 1; } /* Returns the width of the ISO 10646 character represented by the sequence of bytes starting at s, using the provided encoding. */ static int inline get_char_width(const char * const s, const encoding_type encoding) { assert(s != NULL); return *(unsigned char *)s < 128 ? 1 : encoding == ENC_UTF8 ? output_width(utf8char(s)) : output_width(*(unsigned char *)s); } /* Computes the TAB-expanded width of a line descriptor up to a certain position. The position can be greater than the line length, the usual convention of infinite expansion via spaces being in place. */ static int64_t inline calc_width(const line_desc * const ld, const int64_t n, const int tab_size, const encoding_type encoding) { int64_t width = 0; for(int64_t pos = 0; pos < n; pos = pos < ld->line_len ? next_pos(ld->line, pos, encoding) : pos + 1) { if (pos >= ld->line_len) width++; else if (ld->line[pos] != '\t') width += get_char_width(&ld->line[pos], encoding); else width += tab_size - width % tab_size; } return width; } /* Computes the TAB-expanded width of a line descriptor up to a certain position. The position can be greater than the line length, the usual convention of infinite expansion via spaces being in place. An initial hint can be provided in the form of a known position and a corresponding known width. */ static int64_t inline calc_width_hint(const line_desc * const ld, const int64_t n, const int tab_size, const encoding_type encoding, const int64_t cur_pos, const int64_t cur_width) { if (cur_pos < n) { int64_t width = cur_width; for(int64_t pos = cur_pos; pos < n; pos = pos < ld->line_len ? next_pos(ld->line, pos, encoding) : pos + 1) { if (pos >= ld->line_len) width++; else if (ld->line[pos] != '\t') width += get_char_width(&ld->line[pos], encoding); else width += tab_size - width % tab_size; } return width; } else return calc_width(ld, n, tab_size, encoding); } /* Computes character length of a line descriptor up to a given position. */ static int64_t inline calc_char_len(const line_desc * const ld, const int64_t n, const encoding_type encoding) { int64_t len = 0; for(int64_t pos = 0; pos < n; pos = next_pos(ld->line, pos, encoding), len++); return len; } /* Given a column, the index of the byte "containing" that position is given, that is, calc_width(index) > n, and index is minimum with this property. If the width of the line is smaller than the given column, the line length is returned. */ static int64_t inline calc_pos(const line_desc * const ld, const int64_t col, const int tab_size, const encoding_type encoding) { int c_width; int64_t pos = 0; for(int64_t width = 0; pos < ld->line_len && width + (c_width = get_char_width(&ld->line[pos], encoding)) <= col; pos = next_pos(ld->line, pos, encoding)) { if (ld->line[pos] != '\t') width += c_width; else width += tab_size - width % tab_size; } return pos; } /* Given a column, the index of the byte "containing" that position is given, that is, calc_width(index) > n, and index is minimum with this property. If the width of the line is smaller than the given column, the line is extended with spaces. */ static int64_t inline calc_virt_pos(const line_desc * const ld, const int64_t col, const int tab_size, const encoding_type encoding) { int c_width; int64_t pos, width; for(pos = width = 0; pos < ld->line_len && width + (c_width = get_char_width(&ld->line[pos], encoding)) <= col; pos = next_pos(ld->line, pos, encoding)) { if (ld->line[pos] != '\t') width += c_width; else width += tab_size - width % tab_size; } assert(pos <= ld->line_len); assert(pos == ld->line_len || width == col); if (pos == ld->line_len) pos += col - width; return pos; } /* Returns true if the specified character is an US-ASCII whitespace character. */ static bool inline isasciispace(const int c) { return c < 0x80 && isspace(c); } /* Returns true if the specified character is an US-ASCII alphabetic character. */ static bool inline isasciialpha(const int c) { return c < 0x80 && isalpha(c); } /* Returns true if the specified block of text is US-ASCII. */ static bool inline is_ascii(const char * const ss, int len) { const unsigned char * const s = (const unsigned char *)ss; while(len-- != 0) if (s[len] >= 0x80) return false; return true; } /* Returns toupper() of the given character, if it is US-ASCII, the character itself, otherwise. */ static int inline asciitoupper(const int c) { return c < 0x80 ? toupper(c) : c; } /* Returns tolower() of the given character, if it is US-ASCII, the character itself, otherwise. */ static int inline asciitolower(const int c) { return c < 0x80 ? tolower(c) : c; } /* Returns the ISO 10646 character represented by the sequence of bytes starting at s, using the provided encoding. */ static int inline get_char(const char * const s, const encoding_type encoding) { if (encoding == ENC_UTF8) return utf8char(s); else return *(unsigned char *)s; } /* Returns the width of the first len characters of s, using the provided encoding. If s is NULL, returns len. */ static int inline get_string_width(const char * const s, const int64_t len, const encoding_type encoding) { if (s == NULL) return len; int64_t width = 0; for(int64_t pos = 0; pos < len; pos = next_pos(s, pos, encoding)) width += get_char_width(s + pos, encoding); return width; } /* Returns whether the given character is a punctuation character. This function is compiled differently depending on whether wide-character function support is inhibited. */ static bool inline ne_ispunct(const int c, const int encoding) { #ifdef NOWCHAR return encoding != ENC_UTF8 ? ispunct(c) : c < 0x80 ? ispunct(c) : false; #else return encoding != ENC_UTF8 ? ispunct(c) : iswpunct(c); #endif } /* Returns whether the given character is whitespace. This function is compiled differently depending on whether wide-character function support is inhibited. */ static bool inline ne_isspace(const int c, const int encoding) { #ifdef NOWCHAR return encoding != ENC_UTF8 ? isspace(c) : c < 0x80 ? isspace(c) : false; #else return encoding != ENC_UTF8 ? isspace(c) : iswspace(c); #endif } /* Returns whether the given character is a "word" character. Word characters are '_' plus any non-punctuation or space. TODO: implement a way for users to specify their own word characters. For now, hardcode '_'. */ static bool inline ne_isword(const int c, const int encoding) { return c == '_' || !(c == '\0' || ne_isspace(c, encoding) || ne_ispunct(c, encoding)); } ne-3.3.4/src/syn_hash.c000066400000000000000000000064421475116431000147020ustar00rootroot00000000000000/* Syntax highlighting from Joe's Own Editor: Simple hash tables. Copyright (C) 1992 Joseph H. Allen Copyright (C) 2009-2025 Todd M. Lewis and Sebastiano Vigna This file is part of ne, the nice editor. This library 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 library 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 . */ #include #include #include #include #define PARAMS(protos) protos #include "syn_types.h" #include "syn_hash.h" #include "syn_regex.h" #include "syn_utf8.h" #include "syn_utils.h" static HENTRY *freentry = NULL; /* Compute hash value of string */ #define hnext(accu, c) (((accu) << 4) + ((accu) >> 28) + (c)) unsigned long hash(unsigned char *s) { unsigned long accu = 0; while (*s) { accu = hnext(accu, *s++); } return accu; } /* Create hash table */ HASH *htmk(int len) { HASH *t = (HASH *) joe_malloc(sizeof(HASH)); t->nentries = 0; t->len = len; t->tab = (HENTRY **) joe_calloc(sizeof(HENTRY *), len); return t; } /* Delete hash table. Only the hash table is deleted, not the names and values */ void htrm(HASH *ht) { int x; for (x = 0; x != ht->len; ++x) { HENTRY *p, *n; for (p = ht->tab[x]; p; p = n) { n = p->next; p->next = freentry; freentry = p; } } joe_free(ht->tab); joe_free(ht); } /* Expand hash table */ void htexpand(HASH *h) { unsigned x; /* Allocate new table */ unsigned new_size = h->len * 2; HENTRY **new_table = (HENTRY **)joe_calloc(new_size, sizeof(HENTRY *)); /* Copy entries from old table to new */ for (x = 0; x != h->len; ++x) { HENTRY *e; while ((e = h->tab[x])) { h->tab[x] = e->next; e->next = new_table[e->hash_val & (new_size - 1)]; new_table[e->hash_val & (new_size - 1)] = e; } } /* Replace old table with new */ free(h->tab); h->tab = new_table; h->len = new_size; } /* Bind a value to a name. This does not check for duplicate entries. The * name and value are not duplicated: it's up to you to keep them around for * the life of the hash table. */ void *htadd(HASH *ht, unsigned char *name, void *val) { unsigned hval = hash(name); unsigned idx = hval & (ht->len - 1); HENTRY *entry; int x; if (!freentry) { entry = (HENTRY *) joe_malloc(sizeof(HENTRY) * 64); for (x = 0; x != 64; ++x) { entry[x].next = freentry; freentry = entry + x; } } entry = freentry; freentry = entry->next; entry->next = ht->tab[idx]; ht->tab[idx] = entry; entry->name = name; entry->val = val; entry->hash_val = hval; if (++ht->nentries == (ht->len >> 1) + (ht->len >> 2)) htexpand(ht); return val; } /* Return value associated with name or NULL if there is none */ void *htfind(HASH *ht, unsigned char *name) { HENTRY *e; for (e = ht->tab[hash(name) & (ht->len - 1)]; e; e = e->next) { if (!zcmp(e->name, name)) { return e->val; } } return NULL; } ne-3.3.4/src/syn_hash.h000066400000000000000000000030621475116431000147020ustar00rootroot00000000000000/* Syntax highlighting from Joe's Own Editor: Simple hash tables. Copyright (C) 1992 Joseph H. Allen Copyright (C) 2009-2025 Todd M. Lewis and Sebastiano Vigna This file is part of ne, the nice editor. This library 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 library 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 . */ #ifndef _JOE_HASH_H #define _JOE_HASH_H 1 struct entry { HENTRY *next; unsigned char *name; unsigned hash_val; void *val; }; struct hash { unsigned len; HENTRY **tab; unsigned nentries; }; /* Compute hash code for a string */ unsigned long hash PARAMS((unsigned char *s)); /* Create a hash table of specified size, which must be a power of 2 */ HASH *htmk PARAMS((int len)); /* Delete a hash table. HENTRIES get freed, but name/vals don't. */ void htrm PARAMS((HASH *ht)); /* Add an entry to a hash table. Note: 'name' is _not_ strdup()ed */ void *htadd PARAMS((HASH *ht, unsigned char *name, void *val)); /* Look up an entry in a hash table, returns NULL if not found */ void *htfind PARAMS((HASH *ht, unsigned char *name)); #endif ne-3.3.4/src/syn_regex.c000066400000000000000000000215011475116431000150620ustar00rootroot00000000000000/* Syntax highlighting from Joe's Own Editor: Regular expression subroutines. Copyright (C) 1992 Joseph H. Allen Copyright (C) 2009-2025 Todd M. Lewis and Sebastiano Vigna This file is part of ne, the nice editor. This library 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 library 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 . */ #define PARAMS(protos) protos #include "syn_types.h" #include "syn_utf8.h" int escape(int utf8, unsigned char **a, int *b) { int c; unsigned char *s = *a; int l = *b; if (*s == '\\' && l >= 2) { ++s; --l; switch (*s) { case 'n': c = 10; ++s; --l; break; case 't': c = 9; ++s; --l; break; case 'a': c = 7; ++s; --l; break; case 'b': c = 8; ++s; --l; break; case 'f': c = 12; ++s; --l; break; case 'e': c = 27; ++s; --l; break; case 'r': c = 13; ++s; --l; break; case '8': c = 8; ++s; --l; break; case '9': c = 9; ++s; --l; break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': c = *s - '0'; ++s; --l; if (l > 0 && *s >= '0' && *s <= '7') { c = c * 8 + s[1] - '0'; ++s; --l; } if (l > 0 && *s >= '0' && *s <= '7') { c = c * 8 + s[1] - '0'; ++s; --l; } break; case 'x': case 'X': c = 0; ++s; --l; if (l > 0 && *s >= '0' && *s <= '9') { c = c * 16 + *s - '0'; ++s; --l; } else if (l > 0 && *s >= 'A' && *s <= 'F') { c = c * 16 + *s - 'A' + 10; ++s; --l; } else if (l > 0 && *s >= 'a' && *s <= 'f') { c = c * 16 + *s - 'a' + 10; ++s; --l; } if (l > 0 && *s >= '0' && *s <= '9') { c = c * 16 + *s - '0'; ++s; --l; } else if (l > 0 && *s >= 'A' && *s <= 'F') { c = c * 16 + *s - 'A' + 10; ++s; --l; } else if (l > 0 && *s >= 'a' && *s <= 'f') { c = c * 16 + *s - 'a' + 10; ++s; --l; } break; default: if (utf8) c = utf8_decode_fwrd(&s, &l); else { c = *s++; --l; } break; } } else if (utf8) { c = utf8_decode_fwrd(&s,&l); } else { c = *s++; --l; } *a = s; *b = l; return c; } /* static int brack(int utf8,unsigned char **a, int *la, int c) { int inverse = 0; int flag = 0; unsigned char *s = *a; int l = *la; if (!l) return 0; if (*s == '^' || *s == '*') { inverse = 1; ++s; --l; } if (l && *s == ']') { ++s; --l; if (c == ']') flag = 1; } while (l) if (*s == ']') { ++s; --l; break; } else { int cl, cr; cl = escape(utf8, &s, &l); if (l >= 2 && s[0] == '-' && s[1] != ']') { --l; ++s; cr = escape(utf8, &s, &l); if (c >= cl && c <= cr) flag = 1; } else if (c == cl) flag = 1; } *a = s; *la = l; if (inverse) return !flag; else return flag; } */ /* static void savec(int utf8,unsigned char **pieces, int n, int c) { unsigned char buf[16]; int len; unsigned char *s = NULL; if (utf8) len = utf8_encode(buf,c); else { buf[0] = c; len = 1; } if (pieces[n]) vsrm(pieces[n]); s = vsncpy(s, 0, buf, len); pieces[n] = s; } */ #define MAX_REGEX_SAVED 16384 /* Largest regex string we will save */ /* static void saves(unsigned char **pieces, int n, P *p, long int szz) { if (szz > MAX_REGEX_SAVED) pieces[n] = vstrunc(pieces[n], 0); else { pieces[n] = vstrunc(pieces[n], (int) szz); brmem(p, pieces[n], (int) szz); } } */ /* Returns -1 (NO_MORE_DATA) for end of file. * Returns -2 if we skipped a special sequence and didn't take the character * after it (this happens for "strings"). * Otherwise returns character after sequence (character will be >=0). */ /* static int skip_special(P *p) { int to, s; switch (s = pgetc(p)) { case '"': do { if ((s = pgetc(p)) == '\\') { pgetc(p); s = pgetc(p); } } while (s != NO_MORE_DATA && s != '"'); if (s == '"') return -2; break; case '\'': if ((s = pgetc(p)) == '\\') { s = pgetc(p); s = pgetc(p); } if (s == '\'') return -2; if ((s = pgetc(p)) == '\'') return -2; if ((s = pgetc(p)) == '\'') return -2; break; case '[': to = ']'; goto skip; case '(': to = ')'; goto skip; case '{': to = '}'; skip: do { s = skip_special(p); } while (s != to && s != NO_MORE_DATA); if (s == to) return -2; break; case '/': s = pgetc(p); if (s == '*') do { s = pgetc(p); while (s == '*') if ((s = pgetc(p)) == '/') return -2; } while (s != NO_MORE_DATA); else if (s != NO_MORE_DATA) s = prgetc(p); else s = '/'; break; } return s; } */ /* int pmatch(unsigned char **pieces, unsigned char *regex, int len, P *p, int n, int icase) { int c, d; P *q = pdup(p, USTR "pmatch"); P *o = NULL; int utf8 = p->b->o.charmap->type; struct charmap *map = p->b->o.charmap; struct utf8_sm sm; utf8_init(&sm); while (len) { if (utf8) { do { c = utf8_decode(&sm,*regex++); --len; } while (len && c<0); if (c<0) return 0; } else { c = *regex++; --len; } switch (c) { case '\\': if (!len--) goto fail; switch (c = *regex++) { case '?': d = pgetc(p); if (d == NO_MORE_DATA) goto fail; savec(utf8, pieces, n++, d); break; case 'n': case 'r': case 'a': case 'f': case 'b': case 't': case 'e': case 'x': case 'X': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': regex -= 2; len += 2; if (pgetc(p) != escape(utf8, ®ex, &len)) goto fail; break; case '*': o = pdup(p, USTR "pmatch"); do { long pb = p->byte; if (pmatch(pieces, regex, len, p, n + 1, icase)) { saves(pieces, n, o, pb - o->byte); goto succeed; } c = pgetc(p); } while (c != NO_MORE_DATA && c != '\n'); goto fail; case 'c': o = pdup(p, USTR "pmatch"); do { long pb = p->byte; if (pmatch(pieces, regex, len, p, n + 1, icase)) { saves(pieces, n, o, pb - o->byte); goto succeed; } } while (skip_special(p) != NO_MORE_DATA); goto fail; case '[': d = pgetc(p); if (d == NO_MORE_DATA) goto fail; if (!brack(utf8, ®ex, &len, d)) goto fail; savec(utf8, pieces, n++, d); break; case '+': { unsigned char *oregex = regex; int olen = len; unsigned char *tregex; int tlen; int match; P *r = NULL; int d = 0; o = pdup(p, USTR "pmatch"); / if (len >= 2 && regex[0] == '\\') { if (regex[1] == '[') { regex += 2; len -= 2; brack(utf8, ®ex, &len, 0); } else { d = escape(utf8, ®ex, &len); if (icase) d = joe_tolower(map,d); } } else if (utf8) { if ((d = utf8_decode_fwrd(®ex, &len)) < 0) goto done; else if (icase) d = joe_tolower(map,d); } else { if (len >= 1) { --len; d = *regex++; if (icase) d = joe_tolower(map,d); } else goto done; } / do { P *z = pdup(p, USTR "pmatch"); if (pmatch(pieces, regex, len, p, n + 1, icase)) { saves(pieces, n, o, z->byte - o->byte); if (r) prm(r); r = pdup(p, USTR "pmatch"); } pset(p, z); prm(z); c = pgetc(p); tregex = oregex; tlen = olen; if (*oregex == '\\') { if (oregex[1] == '[') { tregex += 2; tlen -= 2; match = brack(utf8, &tregex, &tlen, c); } else match = (d == c); } else { if(icase) match = (joe_tolower(map,c) == d); else match = (c == d); } } while (c != NO_MORE_DATA && match); done: if (r) { pset(p, r); prm(r); } if (r) goto succeed; else goto fail; } case '^': if (!pisbol(p)) goto fail; break; case '$': if (!piseol(p)) goto fail; break; case '<': if (!pisbow(p)) goto fail; break; case '>': if (!piseow(p)) goto fail; break; case '\\': d = pgetc(p); if (d != c) goto fail; break; default: goto fail; } break; default: d = pgetc(p); if (icase) { if (joe_tolower(map,d) != joe_tolower(map,c)) goto fail; } else { if (d != c) goto fail; } } } succeed: if (o) prm(o); prm(q); return 1; fail: if (o) prm(o); pset(p, q); prm(q); return 0; } */ ne-3.3.4/src/syn_regex.h000066400000000000000000000020451475116431000150710ustar00rootroot00000000000000/* Syntax highlighting from Joe's Own Editor: Regular expression subroutines. Copyright (C) 1992 Joseph H. Allen Copyright (C) 2009-2025 Todd M. Lewis and Sebastiano Vigna This file is part of ne, the nice editor. This library 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 library 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 . */ #ifndef _JOE_REGEX_H #define _JOE_REGEX_H 1 int escape PARAMS((int utf8,unsigned char **a, int *b)); /*int pmatch PARAMS((unsigned char **pieces, unsigned char *regex, int len, P *p, int n, int icase));*/ #endif ne-3.3.4/src/syn_types.h000066400000000000000000000125631475116431000151310ustar00rootroot00000000000000/* Syntax highlighting from Joe's Own Editor: Types Copyright (C) 2004 Joseph H. Allen Copyright (C) 2009-2025 Todd M. Lewis and Sebastiano Vigna This file is part of ne, the nice editor. This library 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 library 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 . */ #include #include #include #define joe_gettext(s) my_gettext((unsigned char *)(s)) /* Strings needing translation are marked with this macro */ #define _(s) (s) /* Global Defines */ /* Prefix to make string constants unsigned */ #define USTR (unsigned char *) /* Doubly-linked list node */ #define LINK(type) struct { type *next; type *prev; } #ifdef HAVE_SNPRINTF #define joe_snprintf_0(buf,len,fmt) snprintf((char *)(buf),(len),(char *)(fmt)) #define joe_snprintf_1(buf,len,fmt,a) snprintf((char *)(buf),(len),(char *)(fmt),(a)) #define joe_snprintf_2(buf,len,fmt,a,b) snprintf((char *)(buf),(len),(char *)(fmt),(a),(b)) #define joe_snprintf_3(buf,len,fmt,a,b,c) snprintf((char *)(buf),(len),(char *)(fmt),(a),(b),(c)) #define joe_snprintf_4(buf,len,fmt,a,b,c,d) snprintf((char *)(buf),(len),(char *)(fmt),(a),(b),(c),(d)) #define joe_snprintf_5(buf,len,fmt,a,b,c,d,e) snprintf((char *)(buf),(len),(char *)(fmt),(a),(b),(c),(d),(e)) #define joe_snprintf_6(buf,len,fmt,a,b,c,d,e,f) snprintf((char *)(buf),(len),(char *)(fmt),(a),(b),(c),(d),(e),(f)) #define joe_snprintf_7(buf,len,fmt,a,b,c,d,e,f,g) snprintf((char *)(buf),(len),(char *)(fmt),(a),(b),(c),(d),(e),(f),(g)) #define joe_snprintf_8(buf,len,fmt,a,b,c,d,e,f,g,h) snprintf((char *)(buf),(len),(char *)(fmt),(a),(b),(c),(d),(e),(f),(g),(h)) #define joe_snprintf_9(buf,len,fmt,a,b,c,d,e,f,g,h,i) snprintf((char *)(buf),(len),(char *)(fmt),(a),(b),(c),(d),(e),(f),(g),(h),(i)) #define joe_snprintf_10(buf,len,fmt,a,b,c,d,e,f,g,h,i,j) snprintf((char *)(buf),(len),(char *)(fmt),(a),(b),(c),(d),(e),(f),(g),(h),(i),(j)) #define i_printf_0(fmt) (snprintf((char *)(i_msg),sizeof(i_msg),(char *)(fmt)), internal_msg(i_msg)) #define i_printf_1(fmt,a) (snprintf((char *)(i_msg),sizeof(i_msg),(char *)(fmt),(a)), internal_msg(i_msg)) /*#define i_printf_2(fmt,a,b) (snprintf((char *)(i_msg),sizeof(i_msg),(char *)(fmt),(a),(b)), internal_msg(i_msg))*/ #define i_printf_2(fmt,a,b) (fprintf(stderr,(char *)(fmt),(a),(b))) #define i_printf_3(fmt,a,b,c) (snprintf((char *)(i_msg),sizeof(i_msg),(char *)(fmt),(a),(b),(c)), internal_msg(i_msg)) #define i_printf_4(fmt,a,b,c,d) (snprintf((char *)(i_msg),sizeof(i_msg),(char *)(fmt),(a),(b),(c),(d)), internal_msg(i_msg)) #else #define joe_snprintf_0(buf,len,fmt) sprintf((char *)(buf),(char *)(fmt)) #define joe_snprintf_1(buf,len,fmt,a) sprintf((char *)(buf),(char *)(fmt),(a)) #define joe_snprintf_2(buf,len,fmt,a,b) sprintf((char *)(buf),(char *)(fmt),(a),(b)) #define joe_snprintf_3(buf,len,fmt,a,b,c) sprintf((char *)(buf),(char *)(fmt),(a),(b),(c)) #define joe_snprintf_4(buf,len,fmt,a,b,c,d) sprintf((char *)(buf),(char *)(fmt),(a),(b),(c),(d)) #define joe_snprintf_5(buf,len,fmt,a,b,c,d,e) sprintf((char *)(buf),(char *)(fmt),(a),(b),(c),(d),(e)) #define joe_snprintf_6(buf,len,fmt,a,b,c,d,e,f) sprintf((char *)(buf),(char *)(fmt),(a),(b),(c),(d),(e),(f)) #define joe_snprintf_7(buf,len,fmt,a,b,c,d,e,f,g) sprintf((char *)(buf),(char *)(fmt),(a),(b),(c),(d),(e),(f),(g)) #define joe_snprintf_8(buf,len,fmt,a,b,c,d,e,f,g,h) sprintf((char *)(buf),(char *)(fmt),(a),(b),(c),(d),(e),(f),(g),(h)) #define joe_snprintf_9(buf,len,fmt,a,b,c,d,e,f,g,h,i) sprintf((char *)(buf),(char *)(fmt),(a),(b),(c),(d),(e),(f),(g),(h),(i)) #define joe_snprintf_10(buf,len,fmt,a,b,c,d,e,f,g,h,i,j) sprintf((char *)(buf),(char *)(fmt),(a),(b),(c),(d),(e),(f),(g),(h),(i),(j)) #define i_printf_0(fmt) (sprintf((char *)(i_msg),(char *)(fmt)), internal_msg(i_msg)) #define i_printf_1(fmt,a) (sprintf((char *)(i_msg),(char *)(fmt),(a)), internal_msg(i_msg)) /*#define i_printf_2(fmt,a,b) (sprintf((char *)(i_msg),(char *)(fmt),(a),(b)), internal_msg(i_msg))*/ #define i_printf_2(fmt,a,b) (fprintf(stderr,(char *)(fmt),(a),(b))) #define i_printf_3(fmt,a,b,c) (sprintf((char *)(i_msg),(char *)(fmt),(a),(b),(c)), internal_msg(i_msg)) #define i_printf_4(fmt,a,b,c,d) (sprintf((char *)(i_msg),(char *)(fmt),(a),(b),(c),(d)), internal_msg(i_msg)) #endif #define stdsiz 8192 typedef struct header H; typedef struct buffer B; typedef struct point P; typedef struct options OPTIONS; typedef struct macro MACRO; typedef struct cmd CMD; typedef struct entry HENTRY; typedef struct hash HASH; typedef struct bw BW; typedef struct scrn SCRN; typedef struct cap CAP; typedef struct vpage VPAGE; typedef struct vfile VFILE; typedef struct highlight_state HIGHLIGHT_STATE; /* Structure which are passed by value */ struct highlight_state { struct high_frame *stack; /* Pointer to the current frame in the call stack */ int state; /* Current state in the current subroutine */ unsigned char saved_s[24]; /* Buffer for saved delimiters */ }; ne-3.3.4/src/syn_utf8.c000066400000000000000000000102231475116431000146350ustar00rootroot00000000000000/* Syntax highlighting from Joe's Own Editor: UTF-8 utilities. Copyright (C) 2004 Joseph H. Allen Copyright (C) 2009-2025 Todd M. Lewis and Sebastiano Vigna This file is part of ne, the nice editor. This library 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 library 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 . */ #include "ne.h" /* Under AmigaOS we have setlocale() but don't have langinfo.h and associated stuff, * so we have to disable the whole piece of code */ #ifdef __amigaos #undef HAVE_SETLOCALE #endif /* Cygwin has CODESET, but it's crummy */ #ifdef __CYGWIN__ #undef HAVE_SETLOCALE #endif /* If it looks old, forget it */ #ifndef CODESET #undef HAVE_SETLOCALE #endif #if defined(HAVE_LOCALE_H) && defined(HAVE_SETLOCALE) # include # include #endif /* UTF-8 Decoder * * Returns 0 - 7FFFFFFF: decoded character * -1: character accepted, nothing decoded yet. * -2: incomplete sequence * -3: no sequence started, but character is between 128 - 191, 254 or 255 */ int utf8_decode(struct utf8_sm *utf8_sm,unsigned char c) { if (utf8_sm->state) { if ((c&0xC0)==0x80) { utf8_sm->buf[utf8_sm->ptr++] = c; --utf8_sm->state; utf8_sm->accu = ((utf8_sm->accu<<6)|(c&0x3F)); if(!utf8_sm->state) return utf8_sm->accu; } else { utf8_sm->state = 0; return -2; } } else if ((c&0xE0)==0xC0) { /* 192 - 223 */ utf8_sm->buf[0] = c; utf8_sm->ptr = 1; utf8_sm->state = 1; utf8_sm->accu = (c&0x1F); } else if ((c&0xF0)==0xE0) { /* 224 - 239 */ utf8_sm->buf[0] = c; utf8_sm->ptr = 1; utf8_sm->state = 2; utf8_sm->accu = (c&0x0F); } else if ((c&0xF8)==0xF0) { /* 240 - 247 */ utf8_sm->buf[0] = c; utf8_sm->ptr = 1; utf8_sm->state = 3; utf8_sm->accu = (c&0x07); } else if ((c&0xFC)==0xF8) { /* 248 - 251 */ utf8_sm->buf[0] = c; utf8_sm->ptr = 1; utf8_sm->state = 4; utf8_sm->accu = (c&0x03); } else if ((c&0xFE)==0xFC) { /* 252 - 253 */ utf8_sm->buf[0] = c; utf8_sm->ptr = 1; utf8_sm->state = 5; utf8_sm->accu = (c&0x01); } else if ((c&0x80)==0x00) { /* 0 - 127 */ utf8_sm->buf[0] = c; utf8_sm->ptr = 1; utf8_sm->state = 0; return c; } else { /* 128 - 191, 254, 255 */ utf8_sm->ptr = 0; utf8_sm->state = 0; return -3; } return -1; } /* Initialize state machine */ void utf8_init(struct utf8_sm *utf8_sm) { utf8_sm->ptr = 0; utf8_sm->state = 0; } /* Decode an entire string */ int utf8_decode_string(unsigned char *s) { struct utf8_sm sm; int x; int c = -1; utf8_init(&sm); for(x=0;s[x];++x) c = utf8_decode(&sm,s[x]); return c; } /* Decode and advance * * Returns: 0 - 7FFFFFFF: decoded character * -2: incomplete sequence * -3: bad start of sequence found. * * p/plen are always advanced in such a way that repeated called to utf8_decode_fwrd do not cause * infinite loops. */ int utf8_decode_fwrd(unsigned char **p,int *plen) { struct utf8_sm sm; unsigned char *s = *p; int len; int c = -2; /* Return this on no more input. */ if (plen) len = *plen; else len = -1; utf8_init(&sm); while (len) { c = utf8_decode(&sm, *s); if (c >= 0) { /* We've got a character */ --len; ++s; break; } else if (c == -2) { /* Bad sequence detected. Caller should feed rest of string in again. */ break; } else if (c == -3) { /* Bad start of UTF-8 sequence. We need to eat this char to avoid infinite loops. */ --len; ++s; /* But we should tell the caller that something bad was found. */ break; } else { /* If c is -1, utf8_decode accepted the character, so we should get the next one. */ --len; ++s; } } if (plen) *plen = len; *p = s; return c; } ne-3.3.4/src/syn_utf8.h000066400000000000000000000037761475116431000146610ustar00rootroot00000000000000/* Syntax highlighting from Joe's Own Editor: UTF-8 utilities. Copyright (C) 2004 Joseph H. Allen Copyright (C) 2009-2025 Todd M. Lewis and Sebastiano Vigna This file is part of ne, the nice editor. This library 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 library 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 . */ #ifndef _Iutf8 #define _Iutf8 1 /* UTF-8 Encoder * * c is unicode character. * buf is 7 byte buffer- utf-8 coded character is written to this followed by a 0 termination. * returns length (not including terminator). */ int utf8_encode PARAMS((unsigned char *buf,int c)); /* UTF-8 decoder state machine */ struct utf8_sm { unsigned char buf[8]; /* Record of sequence */ int ptr; /* Record pointer */ int state; /* Current state. 0 = idle, anything else is no. of chars left in sequence */ int accu; /* Character accumulator */ }; /* UTF-8 Decoder * * Returns 0 - 7FFFFFFF: decoded character * -1: character accepted, nothing decoded yet. * -2: incomplete sequence * -3: no sequence started, but character is between 128 - 191, 254 or 255 */ int utf8_decode PARAMS((struct utf8_sm *utf8_sm,unsigned char c)); int utf8_decode_string PARAMS((unsigned char *s)); int utf8_decode_fwrd PARAMS((unsigned char **p,int *plen)); /* Initialize state machine */ void utf8_init PARAMS((struct utf8_sm *utf8_sm)); int unictrl PARAMS((int ucs)); int mk_wcwidth PARAMS((int wide,int c)); extern int guess_non_utf8; extern int guess_utf8; #endif ne-3.3.4/src/syn_utils.c000066400000000000000000000200141475116431000151060ustar00rootroot00000000000000/* Syntax highlighting from Joe's Own Editor: Various utilities. Copyright (C) 1992 Joseph H. Allen Copyright (C) 2001 Marek 'Marx' Grac Copyright (C) 2009-2025 Todd M. Lewis and Sebastiano Vigna This file is part of ne, the nice editor. This library 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 library 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 . */ #include "ne.h" #include /* * return minimum/maximum of two numbers */ unsigned int uns_min(unsigned int a, unsigned int b) { return a < b ? a : b; } signed int int_min(signed int a, signed int b) { return a < b ? a : b; } signed long int long_max(signed long int a, signed long int b) { return a > b ? a : b; } signed long int long_min(signed long int a, signed long int b) { return a < b ? a : b; } /* Normal malloc() */ void *joe_malloc(size_t size) { void *p = malloc(size); if (!p) kill(getpid(), SIGTERM); return p; } void *joe_calloc(size_t nmemb,size_t size) { void *p = calloc(nmemb, size); if (!p) kill(getpid(), SIGTERM); return p; } void *joe_realloc(void *ptr,size_t size) { void *p = realloc(ptr, size); if (!p) /*ttsig(-1);*/ kill(getpid(), SIGTERM); return p; } void joe_free(void *ptr) { free(ptr); } size_t zlen(unsigned char *s) { return strlen((char *)s); } int zcmp(unsigned char *a, unsigned char *b) { return strcmp((char *)a, (char *)b); } int zncmp(unsigned char *a, unsigned char *b, size_t len) { return strncmp((char *)a, (char *)b, len); } unsigned char *zdup(unsigned char *bf) { int size = zlen(bf); unsigned char *p = (unsigned char *)joe_malloc(size+1); memcpy(p,bf,size+1); return p; } unsigned char *zcpy(unsigned char *a, unsigned char *b) { strcpy((char *)a,(char *)b); return a; } unsigned char *zstr(unsigned char *a, unsigned char *b) { return (unsigned char *)strstr((char *)a,(char *)b); } unsigned char *zncpy(unsigned char *a, unsigned char *b, size_t len) { strncpy((char *)a,(char *)b,len); return a; } unsigned char *zcat(unsigned char *a, unsigned char *b) { strcat((char *)a,(char *)b); return a; } unsigned char *zchr(unsigned char *s, int c) { return (unsigned char *)strchr((char *)s,c); } unsigned char *zrchr(unsigned char *s, int c) { return (unsigned char *)strrchr((char *)s,c); } #ifdef junk void *replenish(void **list,int size) { unsigned char *i = joe_malloc(size*16); int x; for (x=0; x!=15; ++x) { fr_single(list, i); i += size; } return i; } /* Destructors */ GC *gc_free_list = 0; void gc_add(GC **gc, void **var, void (*rm)(void *val)) { GC *g; for (g = *gc; g; g=g->next) if (g->var == var) return; g = al_single(&gc_free_list, GC); g = gc_free_list; gc_free_list = g->next; g->next = *gc; *gc = g; g->var = var; g->rm = rm; } void gc_collect(GC **gc) { GC *g = *gc; while (g) { GC *next = g->next; if (*g->var) { g->rm(*g->var); *g->var = 0; } fr_single(&gc_free_list,g); g = next; } *gc = 0; } #endif /* Zstrings */ void rm_zs(ZS z) { joe_free(z.s); } ZS raw_mk_zs(GC **gc,unsigned char *s,int len) { ZS zs; zs.s = (unsigned char *)joe_malloc(len+1); if (len) memcpy(zs.s,s,len); zs.s[len] = 0; return zs; } /* Helpful little parsing utilities */ /* Skip whitespace and return first non-whitespace character */ int parse_ws(unsigned char **pp,int cmt) { unsigned char *p = *pp; while (*p==' ' || *p=='\t') ++p; if (*p=='\r' || *p=='\n' || *p==cmt) *p = 0; *pp = p; return *p; } /* Parse an identifier into a buffer. Identifier is truncated to a maximum of len-1 chars. */ int parse_ident(unsigned char **pp, unsigned char *buf, int len) { unsigned char *p = *pp; if (isalpha(*p) || *p == '_') { while(len > 1 && (isalnum(*p) || *p == '_')) *buf++= *p++, --len; *buf=0; while(isalnum(*p) || *p == '_') ++p; *pp = p; return 0; } else return -1; } /* Parse to next whitespace */ int parse_tows(unsigned char **pp, unsigned char *buf) { unsigned char *p = *pp; while (*p && *p!=' ' && *p!='\t' && *p!='\n' && *p!='\r' && *p!='#') *buf++ = *p++; *pp = p; *buf = 0; return 0; } /* Parse over a specific keyword */ int parse_kw(unsigned char **pp, unsigned char *kw) { unsigned char *p = *pp; while(*kw && *kw==*p) ++kw, ++p; if(!*kw && !isalnum(*p)) { *pp = p; return 0; } else return -1; } /* Parse a field (same as parse_kw, but string must be terminated with whitespace) */ int parse_field(unsigned char **pp, unsigned char *kw) { unsigned char *p = *pp; while(*kw && *kw==*p) ++kw, ++p; if(!*kw && (!*p || *p==' ' || *p=='\t' || *p=='#' || *p=='\n' || *p=='\r')) { *pp = p; return 0; } else return -1; } /* Parse a specific character */ int parse_char(unsigned char **pp, unsigned char c) { unsigned char *p = *pp; if (*p == c) { *pp = p+1; return 0; } else return -1; } /* Parse an integer. Returns 0 for success. */ int parse_int(unsigned char **pp, int *buf) { unsigned char *p = *pp; if ((*p>='0' && *p<='9') || *p=='-') { *buf = atoi((char *)p); if(*p=='-') ++p; while(*p>='0' && *p<='9') ++p; *pp = p; return 0; } else return -1; } /* Parse a long */ int parse_long(unsigned char **pp, long *buf) { unsigned char *p = *pp; if ((*p>='0' && *p<='9') || *p=='-') { *buf = atol((char *)p); if(*p=='-') ++p; while(*p>='0' && *p<='9') ++p; *pp = p; return 0; } else return -1; } /* Parse a string of the form "xxxxx" into a fixed-length buffer. The * address of the buffer is 'buf'. The length of this buffer is 'len'. A * terminating NUL is added to the parsed string. If the string is larger * than the buffer, the string is truncated. * * C string escape sequences are handled. * * 'p' holds an address of the input string pointer. The pointer * is updated to point right after the parsed string if the function * succeeds. * * Returns the length of the string (not including the added NUL), or * -1 if there is no string or if the input ended before the terminating ". */ int parse_string(unsigned char **pp, unsigned char *buf, int len) { unsigned char *start = buf; unsigned char *p= *pp; if(*p=='\"') { ++p; while(len > 1 && *p && *p!='\"') { int x = 50; int c = escape(0, &p, &x); *buf++ = c; --len; } *buf = 0; while(*p && *p!='\"') if(*p=='\\' && p[1]) p += 2; else p++; if(*p == '\"') { *pp = p + 1; return buf - start; } } return -1; } /* Emit a string with escape sequences */ #ifdef junk /* Used originally for printing macros */ void emit_string(FILE *f,unsigned char *s,int len) { unsigned char buf[8]; unsigned char *p, *q; fputc('\"',f); while(len) { p = unescape(buf,*s++); for(q=buf;q!=p;++q) fputc(*q,f); --len; } fputc('\"',f); } #endif /* Emit a string */ void emit_string(FILE *f,unsigned char *s,int len) { fputc('"',f); while(len) { if (*s=='"' || *s=='\\') fputc('\\',f), fputc(*s,f); else if(*s=='\n') fputc('\\',f), fputc('n',f); else if(*s=='\r') fputc('\\',f), fputc('r',f); else if(*s==0) fputc('\\',f), fputc('0',f), fputc('0',f), fputc('0',f); else fputc(*s,f); ++s; --len; } fputc('"',f); } /* Parse a character range: a-z */ int parse_range(unsigned char **pp, int *first, int *second) { unsigned char *p= *pp; int a, b; if(!*p) return -1; if(*p=='\\' && p[1]) { ++p; if(*p=='n') a = '\n'; else if(*p=='t') a = '\t'; else a = *p; ++p; } else a = *p++; if(*p=='-' && p[1]) { ++p; if(*p=='\\' && p[1]) { ++p; if(*p=='n') b = '\n'; else if(*p=='t') b = '\t'; else b = *p; ++p; } else b = *p++; } else b = a; *first = a; *second = b; *pp = p; return 0; } ne-3.3.4/src/syn_utils.h000066400000000000000000000111211475116431000151120ustar00rootroot00000000000000/* Syntax highlighting from Joe's Own Editor: Various utilities. Copyright (C) 1992 Joseph H. Allen Copyright (C) 2001 Marek 'Marx' Grac Copyright (C) 2009-2025 Todd M. Lewis and Sebastiano Vigna This file is part of ne, the nice editor. This library 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 library 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 . */ #ifndef _JOE_UTILS_H #define _JOE_UTILS_H 1 /* Destructors */ #define AUTO_DESTRUCT GC *gc = 0; typedef struct gc GC; struct gc { struct gc *next; /* List */ void **var; /* Address of pointer variable */ void (*rm)(void *val); /* Destructor which takes pointer variable */ }; /* Add a variable to GC list */ void gc_add PARAMS((GC **gc, void **var, void (*rm)(void *val))); /* Call destructors */ void gc_collect PARAMS((GC **gc)); /* Version of return which calls destructors before returning */ #define RETURN(val) do { \ if (gc) gc_collect(&gc); \ return (val); \ } while(0) /* Pool allocation functions using singly-linked lists */ extern void *ITEM; /* Temporary global variable (from queue.c) */ /* Allocate item from free-list. If free-list empty, replenish it. */ void *replenish PARAMS((void **list,int size)); #define al_single(list,type) ( \ (ITEM = *(void **)(list)) ? \ ( (*(void **)(list) = *(void **)ITEM), ITEM ) \ : \ replenish((void **)(list),sizeof(type)) \ ) /* Put item on free list */ #define fr_single(list,item) do { \ *(void **)(item) = *(void **)(list); \ *(void **)(list) = (void *)(item); \ } while(0) /* JOE uses 'unsigned char *', never 'char *'. This is that when a character is loaded into an 'int', the codes 0-255 are used, not -128 - 127. */ /* Zero terminated strings */ typedef struct zs ZS; struct zs { unsigned char *s; }; /* Create zs in local gc */ #define mk_zs(var,s,len) do { \ (var) = raw_mk_zs((s),(len)); \ gc_add(&gc, &(var), rm_zs); \ } while(0) ZS raw_mk_zs PARAMS((GC **gc,unsigned char *s,int len)); /* Destructor for zs */ void rm_zs PARAMS((ZS z)); /* Unsigned versions of regular string functions */ /* JOE uses 'unsigned char *', never 'char *'. This is so that when a character is loaded from a string into an 'int', the codes 0-255 are used, not -128 - 127. */ size_t zlen PARAMS((unsigned char *s)); int zcmp PARAMS((unsigned char *a, unsigned char *b)); int zncmp PARAMS((unsigned char *a, unsigned char *b, size_t len)); unsigned char *zdup PARAMS((unsigned char *s)); unsigned char *zcpy PARAMS((unsigned char *a, unsigned char *b)); unsigned char *zncpy PARAMS((unsigned char *a, unsigned char *b,size_t len)); unsigned char *zstr PARAMS((unsigned char *a, unsigned char *b)); unsigned char *zchr PARAMS((unsigned char *s, int c)); unsigned char *zrchr PARAMS((unsigned char *s, int c)); unsigned char *zcat PARAMS((unsigned char *a, unsigned char *b)); /* * Functions which return minimum/maximum of two numbers */ unsigned int uns_min PARAMS((unsigned int a, unsigned int b)); signed int int_min PARAMS((signed int a, int signed b)); signed long long_max PARAMS((signed long a, signed long b)); signed long long_min PARAMS((signed long a, signed long b)); /* wrappers to *alloc routines */ void *joe_malloc PARAMS((size_t size)); unsigned char *joe_strdup PARAMS((unsigned char *ptr)); void *joe_calloc PARAMS((size_t nmemb, size_t size)); void *joe_realloc PARAMS((void *ptr, size_t size)); void joe_free PARAMS((void *ptr)); /* Simple parsers */ int parse_ws PARAMS((unsigned char **p,int cmt)); int parse_ident PARAMS((unsigned char **p,unsigned char *buf,int len)); int parse_kw PARAMS((unsigned char **p,unsigned char *kw)); long parse_num PARAMS((unsigned char **p)); int parse_tows PARAMS((unsigned char **p,unsigned char *buf)); int parse_field PARAMS((unsigned char **p,unsigned char *field)); int parse_char PARAMS((unsigned char **p,unsigned char c)); int parse_int PARAMS((unsigned char **p,int *buf)); int parse_long PARAMS((unsigned char **p,long *buf)); int parse_string PARAMS((unsigned char **p,unsigned char *buf,int len)); int parse_range PARAMS((unsigned char **p,int *first,int *second)); void emit_string PARAMS((FILE *f,unsigned char *s,int len)); #endif ne-3.3.4/src/syntax.c000066400000000000000000000653251475116431000144210ustar00rootroot00000000000000/* Syntax highlighting from Joe's Own Editor: Syntax highlighting DFA interpreter. Copyright (C) 2004 Joseph H. Allen Copyright (C) 2009-2025 Todd M. Lewis and Sebastiano Vigna This file is part of ne, the nice editor. This library 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 library 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 . */ #include "ne.h" #include "support.h" #include "termchar.h" #undef joe_gettext #define joe_gettext(a) (a) /* Convert color/attribute name into internal code */ uint32_t meta_color_single(unsigned char *s) { if(!zcmp(s,USTR "inverse")) return INVERSE; else if(!zcmp(s,USTR "underline")) return UNDERLINE; else if(!zcmp(s,USTR "bold")) return BOLD; else if(!zcmp(s,USTR "blink")) return BLINK; else if(!zcmp(s,USTR "dim")) return DIM; /* ISO colors */ else if(!zcmp(s,USTR "white")) return FG_WHITE; else if(!zcmp(s,USTR "cyan")) return FG_CYAN; else if(!zcmp(s,USTR "magenta")) return FG_MAGENTA; else if(!zcmp(s,USTR "blue")) return FG_BLUE; else if(!zcmp(s,USTR "yellow")) return FG_YELLOW; else if(!zcmp(s,USTR "green")) return FG_GREEN; else if(!zcmp(s,USTR "red")) return FG_RED; else if(!zcmp(s,USTR "black")) return FG_BLACK; else if(!zcmp(s,USTR "bg_white")) return BG_WHITE; else if(!zcmp(s,USTR "bg_cyan")) return BG_CYAN; else if(!zcmp(s,USTR "bg_magenta")) return BG_MAGENTA; else if(!zcmp(s,USTR "bg_blue")) return BG_BLUE; else if(!zcmp(s,USTR "bg_yellow")) return BG_YELLOW; else if(!zcmp(s,USTR "bg_green")) return BG_GREEN; else if(!zcmp(s,USTR "bg_red")) return BG_RED; else if(!zcmp(s,USTR "bg_black")) return BG_BLACK; /* 16 color xterm support: codes 8 - 15 are brighter versions of above */ else if(!zcmp(s,USTR "WHITE")) return FG_BWHITE; else if(!zcmp(s,USTR "CYAN")) return FG_BCYAN; else if(!zcmp(s,USTR "MAGENTA")) return FG_BMAGENTA; else if(!zcmp(s,USTR "BLUE")) return FG_BBLUE; else if(!zcmp(s,USTR "YELLOW")) return FG_BYELLOW; else if(!zcmp(s,USTR "GREEN")) return FG_BGREEN; else if(!zcmp(s,USTR "RED")) return FG_BRED; else if(!zcmp(s,USTR "BLACK")) return FG_BBLACK; else if(!zcmp(s,USTR "bg_WHITE")) return BG_BWHITE; else if(!zcmp(s,USTR "bg_CYAN")) return BG_BCYAN; else if(!zcmp(s,USTR "bg_MAGENTA")) return BG_BMAGENTA; else if(!zcmp(s,USTR "bg_BLUE")) return BG_BBLUE; else if(!zcmp(s,USTR "bg_YELLOW")) return BG_BYELLOW; else if(!zcmp(s,USTR "bg_GREEN")) return BG_BGREEN; else if(!zcmp(s,USTR "bg_RED")) return BG_BRED; else if(!zcmp(s,USTR "bg_BLACK")) return BG_BBLACK; /* Look at the "256colres.pl" PERL script in the xterm source distribution to see how these work. */ /* 256 color xterm support: bg_RGB and fg_RGB, where R, G, and B range from 0 - 5 */ /* Codes 16 - 231 are a 6x6x6 color cube */ else if(s[0]=='f' && s[1]=='g' && s[2]=='_' && s[3]>='0' && s[3]<='5' && s[4]>='0' && s[4]<='5' && s[5]>='0' && s[5]<='5' && !s[6]) return FG_NOT_DEFAULT | ((16 + (s[3]-'0')*6*6 + (s[4]-'0')*6 + (s[5]-'0')) << FG_SHIFT); else if(s[0]=='b' && s[1]=='g' && s[2]=='_' && s[3]>='0' && s[3]<='5' && s[4]>='0' && s[4]<='5' && s[5]>='0' && s[5]<='5' && !s[6]) return BG_NOT_DEFAULT | ((16 + (s[3]-'0')*6*6 + (s[4]-'0')*6 + (s[5]-'0')) << BG_SHIFT); /* 256 color xterm support: shades of grey */ /* Codes 232 - 255 are shades of grey */ else if(s[0]=='f' && s[1]=='g' && s[2]=='_' && atoi((char *)(s+3)) >= 0 && atoi((char *)(s+3)) <= 23) return FG_NOT_DEFAULT | (232 + (atoi((char *)(s+3)) << FG_SHIFT)); else if(s[0]=='b' && s[1]=='g' && s[2]=='_' && atoi((char *)(s+3)) >= 0 && atoi((char *)(s+3)) <= 23) return BG_NOT_DEFAULT | (232 + (atoi((char *)(s+3)) << BG_SHIFT)); else return 0; } uint32_t meta_color(unsigned char *s) { uint32_t code = 0; while (*s) { unsigned char buf[32]; int x = 0; while (*s) if (*s && *s != '+') { if (x != sizeof(buf) - 1) buf[x++] = *s; ++s; } else break; if (*s == '+') ++s; buf[x] = 0; code |= meta_color_single(buf); } return code; } unsigned char *lowerize(unsigned char *s) { unsigned char *t; for (t=s; *t; t++) *t = tolower(*t); return s; } /* Parse one line. Returns new state. 'syntax' is the loaded syntax definition for this buffer. 'line' is advanced to start of next line. Global array 'attr_buf' end up with coloring for each character of line (attr_len characters). 'state' is initial parser state for the line (0 is initial state). */ uint32_t *attr_buf = 0; int64_t attr_size = 0; int64_t attr_len = 0; int stack_count = 0; static int state_count = 0; /* Max transitions possible without cycling */ HIGHLIGHT_STATE parse(struct high_syntax * const syntax, line_desc * const ld, HIGHLIGHT_STATE h_state, const bool utf8) { struct high_frame *stack; struct high_state *h; /* Current state */ unsigned char buf[24]; /* Name buffer (trunc after 23 characters) */ unsigned char lbuf[24]; /* Lower case version of name buffer */ unsigned char lsaved_s[24]; /* Lower case version of delimiter match buffer */ int buf_idx=0; /* Index into buffer */ int c; /* Current character */ int c_len; /* Character length in bytes */ uint32_t *attr = attr_buf; uint32_t *attr_end = attr_buf+attr_size; int buf_en = 0; /* Set for name buffering */ int ofst = 0; /* record offset after we've stopped buffering */ int mark1 = 0; /* offset to mark start from current pos */ int mark2 = 0; /* offset to mark end from current pos */ int mark_en = 0; /* set if marking */ int recolor_delimiter_or_keyword; /* Nothing should reference 'h' above here. */ if (h_state.state < 0) { /* Indicates a previous error -- highlighting disabled */ return h_state; } const unsigned char *p = (const unsigned char *)ld->line; unsigned char *q = (unsigned char *)(ld->line + ld->line_len); stack = h_state.stack; h = (stack ? stack->syntax : syntax)->states[h_state.state]; buf[0]=0; /* Forgot this originally... took 5 months to fix! */ /* Get next character */ /* Una iterazione in più: aggiungo '\n' come ultimo carattere. */ while( p <= q ) { /* On the last iteration, process the virtual '\n' character. */ struct high_cmd *cmd, *kw_cmd; int iters = -8; /* +8 extra iterations before cycle detect. */ int x; if (p == q) c = '\n'; else c = utf8 ? get_char((const char*)p, ENC_UTF8) : *p; c_len = utf8 ? utf8seqlen(c) : 1; p += c_len; /* Hack so we can have UTF-8 characters without crashing */ if (c < 0 || c > 255) c = 0x1F; /* Create or expand attribute array if necessary */ if(attr==attr_end) { if(!attr_buf) { attr_size = 1024; attr_buf = joe_malloc(sizeof(int)*attr_size); attr = attr_buf; } else { attr_buf = joe_realloc(attr_buf,sizeof(int)*(attr_size*2)); attr = attr_buf + attr_size; attr_size *= 2; } attr_end = attr_buf + attr_size; } /* Advance to next attribute position (note attr[-1] below) */ attr++; /* Loop while noeat */ do { /* Guard against infinite loops from buggy syntaxes */ if (iters++ > state_count) { invalidate_state(&h_state); return h_state; } /* Color with current state */ attr[-1] = h->color; /* Get command for this character */ if (h->delim && c == h_state.saved_s[0] && h_state.saved_s[1] == 0) cmd = h->delim; else cmd = h->cmd[c]; /* Lowerize strings for case-insensitive matching */ if (cmd->ignore) { zcpy(lbuf,buf); lowerize(lbuf); if (cmd->delim) { zcpy(lsaved_s,h_state.saved_s); lowerize(lsaved_s); } } /* Check for delimiter or keyword matches */ recolor_delimiter_or_keyword = 0; if (cmd->delim && (cmd->ignore ? !zcmp(lsaved_s,lbuf) : !zcmp(h_state.saved_s,buf))) { cmd = cmd->delim; recolor_delimiter_or_keyword = 1; } else if (cmd->keywords && (cmd->ignore ? (kw_cmd=htfind(cmd->keywords,lbuf)) : (kw_cmd=htfind(cmd->keywords,buf)))) { cmd = kw_cmd; recolor_delimiter_or_keyword = 1; } /* Determine new state */ if (cmd->call) { /* Call */ struct high_frame **frame_ptr = stack ? &stack->child : &syntax->stack_base; /* Search for an existing stack frame for this call */ while (*frame_ptr && !((*frame_ptr)->syntax == cmd->call && (*frame_ptr)->return_state == cmd->new_state)) frame_ptr = &(*frame_ptr)->sibling; if (*frame_ptr) stack = *frame_ptr; else { struct high_frame *frame = joe_malloc(sizeof(struct high_frame)); frame->parent = stack; frame->child = 0; frame->sibling = 0; frame->syntax = cmd->call; frame->return_state = cmd->new_state; *frame_ptr = frame; stack = frame; ++stack_count; } h = stack->syntax->states[0]; } else if (cmd->rtn) { /* Return */ if (stack) { h = stack->return_state; stack = stack->parent; } else /* Not in a subroutine, so ignore the return */ h = cmd->new_state; } else if (cmd->reset) { /* Reset the state and call stack */ h = syntax->states[0]; stack = syntax->stack_base; } else { /* Normal edge */ h = cmd->new_state; } /* Recolor if necessary */ if (recolor_delimiter_or_keyword) for(x= -(buf_idx+1);x<-1;++x) attr[x-ofst] = h->color; for(x=cmd->recolor;x<0;++x) if (attr + x >= attr_buf) attr[x] = h->color; /* Mark recoloring */ if (cmd->recolor_mark) for(x= -mark1;x<-mark2;++x) attr[x] = h->color; /* Save string? */ if (cmd->save_s) zcpy(h_state.saved_s,buf); /* Save character? */ if (cmd->save_c) { h_state.saved_s[1] = 0; if (c=='<') h_state.saved_s[0] = '>'; else if (c=='(') h_state.saved_s[0] = ')'; else if (c=='[') h_state.saved_s[0] = ']'; else if (c=='{') h_state.saved_s[0] = '}'; else if (c=='`') h_state.saved_s[0] = '\''; else h_state.saved_s[0] = c; } /* Start buffering? */ if (cmd->start_buffering) { buf_idx = 0; buf_en = 1; ofst = 0; } /* Stop buffering? */ if (cmd->stop_buffering) buf_en = 0; /* Set mark begin? */ if (cmd->start_mark) { mark2 = 1; mark1 = 1; mark_en = 1; } /* Set mark end? */ if(cmd->stop_mark) { mark_en = 0; mark2 = 1; } } while(cmd->noeat); /* Save character in buffer */ if (buf_idx<23 && buf_en) buf[buf_idx++]=c; if (!buf_en) ++ofst; buf[buf_idx] = 0; /* Update mark pointers */ ++mark1; if(!mark_en) ++mark2; /*if(c=='\n') break;*/ } /* Return new state */ h_state.stack = stack; h_state.state = h->no; attr_len = attr - attr_buf - 1; /* -1 because of the fake newline. */ return h_state; } /* Subroutines for load_dfa() */ static struct high_state *find_state(struct high_syntax *syntax,unsigned char *name) { struct high_state *state; /* Find state */ state = htfind(syntax->ht_states, name); /* It doesn't exist, so create it */ if(!state) { int y; state=joe_malloc(sizeof(struct high_state)); state->name=zdup(name); state->no=syntax->nstates; state->color=FG_WHITE; /* Expand the state table if necessary */ if(syntax->nstates==syntax->szstates) syntax->states=joe_realloc(syntax->states,sizeof(struct high_state *)*(syntax->szstates*=2)); syntax->states[syntax->nstates++]=state; for(y=0; y!=256; ++y) state->cmd[y] = &syntax->default_cmd; state->delim = 0; htadd(syntax->ht_states, state->name, state); ++state_count; } return state; } /* Create empty command */ static void iz_cmd(struct high_cmd *cmd) { cmd->noeat = 0; cmd->recolor = 0; cmd->start_buffering = 0; cmd->stop_buffering = 0; cmd->save_c = 0; cmd->save_s = 0; cmd->new_state = 0; cmd->keywords = 0; cmd->delim = 0; cmd->ignore = 0; cmd->start_mark = 0; cmd->stop_mark = 0; cmd->recolor_mark = 0; cmd->rtn = 0; cmd->reset = 0; cmd->call = 0; } static struct high_cmd *mkcmd() { struct high_cmd *cmd = joe_malloc(sizeof(struct high_cmd)); iz_cmd(cmd); return cmd; } /* Globally defined colors */ struct high_color *global_colors; struct high_color *find_color(struct high_color *colors,unsigned char *name,unsigned char *syn) { unsigned char bf[256]; struct high_color *color; joe_snprintf_2(bf, sizeof(bf), "%s.%s", syn, name); for (color = colors; color; color = color->next) if (!zcmp(color->name,bf)) break; if (color) return color; for (color = colors; color; color = color->next) if (!zcmp(color->name,name)) break; return color; } void parse_color_def(struct high_color **color_list,unsigned char *p,unsigned char *name,int line) { unsigned char bf[256]; if(!parse_tows(&p, bf)) { struct high_color *color, *gcolor; /* Find color */ color=find_color(*color_list,bf,name); /* If it doesn't exist, create it */ if(!color) { color = joe_malloc(sizeof(struct high_color)); color->name = zdup(bf); color->color = 0; color->next = *color_list; *color_list = color; } else { i_printf_2((char *)joe_gettext(_("%s %d: Class already defined\n")),name,line); } /* Find it in global list */ if (color_list != &global_colors && (gcolor=find_color(global_colors,bf,name))) { color->color = gcolor->color; } else { /* Parse color definition */ while(parse_ws(&p,'#'), !parse_ident(&p,bf,sizeof(bf))) { color->color |= meta_color(bf); } } } else { i_printf_2((char *)joe_gettext(_("%s %d: Missing class name\n")),name,line); } } /* Load syntax file */ struct high_syntax *syntax_list; struct high_param *parse_params(struct high_param *current_params,unsigned char **ptr,unsigned char *name,int line) { unsigned char *p = *ptr; unsigned char bf[256]; struct high_param *params; struct high_param **param_ptr; /* Propagate currently defined parameters */ param_ptr = ¶ms; while (current_params) { *param_ptr = joe_malloc(sizeof(struct high_param)); (*param_ptr)->name = zdup(current_params->name); param_ptr = &(*param_ptr)->next; current_params = current_params->next; } *param_ptr = 0; parse_ws(&p, '#'); if (!parse_char(&p, '(')) { for (;;) { parse_ws(&p, '#'); if (!parse_char(&p, ')')) break; else if (!parse_char(&p, '-')) { if (!parse_ident(&p,bf,sizeof(bf))) { int cmp = 0; param_ptr = ¶ms; /* Parameters are sorted */ while (*param_ptr && (cmp = zcmp(bf,(*param_ptr)->name)) > 0) param_ptr = &(*param_ptr)->next; if (*param_ptr && !cmp) { /* Remove this parameter */ struct high_param *param = *param_ptr; *param_ptr = param->next; joe_free(param); } } else { i_printf_2((char *)joe_gettext(_("%s %d: Missing parameter name\n")),name,line); } } else if (!parse_ident(&p,bf,sizeof(bf))) { int cmp = 0; param_ptr = ¶ms; /* Keep parameters sorted */ while (*param_ptr && (cmp = zcmp(bf,(*param_ptr)->name)) > 0) param_ptr = &(*param_ptr)->next; /* Discard duplicates */ if (!*param_ptr || cmp) { struct high_param *param = joe_malloc(sizeof(struct high_param)); param->name = zdup(bf); param->next = *param_ptr; *param_ptr = param; } } else { i_printf_2((char *)joe_gettext(_("%s %d: Missing )\n")),name,line); break; } } } *ptr = p; return params; } struct high_syntax *load_syntax_subr(unsigned char *name,unsigned char *subr,struct high_param *params); /* Parse options */ void parse_options(struct high_syntax *syntax,struct high_cmd *cmd,FILE *f,unsigned char *p,int parsing_strings,unsigned char *name,int line) { unsigned char buf[1024]; unsigned char bf[256]; unsigned char bf1[256]; while (parse_ws(&p,'#'), !parse_ident(&p,bf,sizeof(bf))) if(!zcmp(bf,USTR "buffer")) { cmd->start_buffering = 1; } else if(!zcmp(bf,USTR "hold")) { cmd->stop_buffering = 1; } else if(!zcmp(bf,USTR "save_c")) { cmd->save_c = 1; } else if(!zcmp(bf,USTR "save_s")) { cmd->save_s = 1; } else if(!zcmp(bf,USTR "recolor")) { parse_ws(&p,'#'); if(!parse_char(&p,'=')) { parse_ws(&p,'#'); if(parse_int(&p,&cmd->recolor)) i_printf_2((char *)joe_gettext(_("%s %d: Missing value for option\n")),name,line); } else i_printf_2((char *)joe_gettext(_("%s %d: Missing value for option\n")),name,line); } else if(!zcmp(bf,USTR "call")) { parse_ws(&p,'#'); if(!parse_char(&p,'=')) { parse_ws(&p,'#'); if (!parse_char(&p,'.')) { zcpy(bf,syntax->name); goto subr; } else if (parse_ident(&p,bf,sizeof(bf))) i_printf_2((char *)joe_gettext(_("%s %d: Missing value for option\n")),name,line); else { if (!parse_char(&p,'.')) { subr: if (parse_ident(&p,bf1,sizeof(bf1))) i_printf_2((char *)joe_gettext(_("%s %d: Missing subroutine name\n")),name,line); cmd->call = load_syntax_subr(bf,bf1,parse_params(syntax->params,&p,name,line)); } else cmd->call = load_syntax_subr(bf,0,parse_params(syntax->params,&p,name,line)); } } else i_printf_2((char *)joe_gettext(_("%s %d: Missing value for option\n")),name,line); } else if(!zcmp(bf,USTR "return")) { cmd->rtn = 1; } else if(!zcmp(bf,USTR "reset")) { cmd->reset = 1; } else if(!parsing_strings && (!zcmp(bf,USTR "strings") || !zcmp(bf,USTR "istrings"))) { if (bf[0]=='i') cmd->ignore = 1; while(fgets((char *)buf,1023,f)) { ++line; p = buf; parse_ws(&p,'#'); if (*p) { if(!parse_field(&p,USTR "done")) break; if(parse_string(&p,bf,sizeof(bf)) >= 0) { parse_ws(&p,'#'); if (cmd->ignore) lowerize(bf); if(!parse_ident(&p,bf1,sizeof(bf1))) { struct high_cmd *kw_cmd=mkcmd(); kw_cmd->noeat=1; kw_cmd->new_state = find_state(syntax,bf1); if (!zcmp(bf, USTR "&")) { cmd->delim = kw_cmd; } else { if(!cmd->keywords) cmd->keywords = htmk(64); htadd(cmd->keywords,zdup(bf),kw_cmd); } parse_options(syntax,kw_cmd,f,p,1,name,line); } else i_printf_2((char *)joe_gettext(_("%s %d: Missing state name\n")),name,line); } else i_printf_2((char *)joe_gettext(_("%s %d: Missing string\n")),name,line); } } } else if(!zcmp(bf,USTR "noeat")) { cmd->noeat = 1; } else if(!zcmp(bf,USTR "mark")) { cmd->start_mark = 1; } else if(!zcmp(bf,USTR "markend")) { cmd->stop_mark = 1; } else if(!zcmp(bf,USTR "recolormark")) { cmd->recolor_mark = 1; } else i_printf_2((char *)joe_gettext(_("%s %d: Unknown option\n")),name,line); } struct ifstack { struct ifstack *next; int ignore; /* Ignore input lines if set */ int skip; /* Set to skip the else part */ int else_part; /* Set if we're in the else part */ int line; }; /* Load dfa */ struct high_state *load_dfa(struct high_syntax *syntax) { unsigned char name[1024]; unsigned char buf[1024]; unsigned char bf[256]; int clist[256]; unsigned char *p; int c; FILE *f = NULL; struct ifstack *stack=0; struct high_state *state=0; /* Current state */ struct high_state *first=0; /* First state */ int line = 0; int this_one = 0; int inside_subr = 0; /* Load it */ if ((p = (unsigned char *)exists_prefs_dir()) && strlen((const char *)p) + 2 + strlen(SYNTAX_DIR) + strlen(SYNTAX_EXT) + strlen((const char *)syntax->name) < sizeof name) { strcat(strcat(strcat(strcat(strcpy((char *)name, (const char *)p), SYNTAX_DIR), "/"), (const char *)syntax->name), SYNTAX_EXT); f = fopen((char *)name,"r"); } if (!f && (p = (unsigned char*)exists_gprefs_dir()) && strlen((const char *)p) + 2 + strlen(SYNTAX_DIR) + strlen(SYNTAX_EXT) + strlen((const char *)syntax->name) < sizeof name) { strcat(strcat(strcat(strcat(strcpy((char *)name, (const char *)p), SYNTAX_DIR), "/"), (const char *)syntax->name), SYNTAX_EXT); f = fopen((char *)name,"r"); } if (!f) return 0; /* Parse file */ while(fgets((char *)buf,1023,f)) { ++line; p = buf; c = parse_ws(&p,'#'); if (!parse_char(&p, '.')) { if (!parse_ident(&p, bf, sizeof(bf))) { if (!zcmp(bf, USTR "ifdef")) { struct ifstack *st = joe_malloc(sizeof(struct ifstack)); st->next = stack; st->else_part = 0; st->ignore = 1; st->skip = 1; st->line = line; if (!stack || !stack->ignore) { parse_ws(&p,'#'); if (!parse_ident(&p, bf, sizeof(bf))) { struct high_param *param; for (param = syntax->params; param; param = param->next) if (!zcmp(param->name, bf)) { st->ignore = 0; break; } st->skip = 0; } else { i_printf_2((char *)joe_gettext(_("%s %d: missing parameter for ifdef\n")),name,line); } } stack = st; } else if (!zcmp(bf, USTR "else")) { if (stack && !stack->else_part) { stack->else_part = 1; if (!stack->skip) stack->ignore = !stack->ignore; } else i_printf_2((char *)joe_gettext(_("%s %d: else with no matching if\n")),name,line); } else if (!zcmp(bf, USTR "endif")) { if (stack) { struct ifstack *st = stack; stack = st->next; joe_free(st); } else i_printf_2((char *)joe_gettext(_("%s %d: endif with no matching if\n")),name,line); } else if (!zcmp(bf, USTR "subr")) { parse_ws(&p, '#'); if (parse_ident(&p, bf, sizeof(bf))) { i_printf_2((char *)joe_gettext(_("%s %d: Missing subroutine name\n")),name,line); } else { if (!stack || !stack->ignore) { inside_subr = 1; this_one = 0; if (syntax->subr && !zcmp(bf, syntax->subr)) this_one = 1; } } } else if (!zcmp(bf, USTR "end")) { if (!stack || !stack->ignore) { this_one = 0; inside_subr = 0; } } else { i_printf_2((char *)joe_gettext(_("%s %d: Unknown control statement\n")),name,line); } } else { i_printf_2((char *)joe_gettext(_("%s %d: Missing control statement name\n")),name,line); } } else if (stack && stack->ignore) { /* Ignore this line because of ifdef */ } else if(!parse_char(&p, '=')) { /* Parse color */ parse_color_def(&syntax->color,p,name,line); } else if ((syntax->subr && !this_one) || (!syntax->subr && inside_subr)) { /* Ignore this line because it's not the code we want */ } else if(!parse_char(&p, ':')) { if(!parse_ident(&p, bf, sizeof(bf))) { state = find_state(syntax,bf); if (!first) first = state; parse_ws(&p,'#'); if(!parse_tows(&p,bf)) { struct high_color *color; for(color=syntax->color;color;color=color->next) if(!zcmp(color->name,bf)) break; if(color) state->color=color->color; else { state->color=0; i_printf_2((char *)joe_gettext(_("%s %d: Unknown class\n")),name,line); } while(parse_ws(&p, '#'), !parse_ident(&p, bf, sizeof(bf))) { if(!zcmp(bf, USTR "comment")) { state->color |= CONTEXT_COMMENT; } else if(!zcmp(bf, USTR "string")) { state->color |= CONTEXT_STRING; } else { i_printf_2((char *)joe_gettext(_("%s %d: Unknown context\n")),name,line); } } } else i_printf_2((char *)joe_gettext(_("%s %d: Missing color for state definition\n")),name,line); } else i_printf_2((char *)joe_gettext(_("%s %d: Missing state name\n")),name,line); } else if(!parse_char(&p, '-')) { /* No. sync lines ignored */ } else { c = parse_ws(&p,'#'); if (!c) { } else if (c=='"' || c=='*' || c=='&') { if (state) { struct high_cmd *cmd; int delim = 0; if(!parse_field(&p, USTR "*")) { int z; for(z=0;z!=256;++z) clist[z] = 1; } else if(!parse_field(&p, USTR "&")) { delim = 1; } else { c = parse_string(&p, bf, sizeof(bf)); if(c < 0) i_printf_2((char *)joe_gettext(_("%s %d: Bad string\n")),name,line); else { int z; int first, second; unsigned char *t = bf; for(z=0;z!=256;++z) clist[z] = 0; while(!parse_range(&t, &first, &second)) { if(first>second) second = first; while(first<=second) clist[first++] = 1; } } } /* Create command */ cmd = mkcmd(); parse_ws(&p,'#'); if(!parse_ident(&p,bf,sizeof(bf))) { int z; cmd->new_state = find_state(syntax,bf); parse_options(syntax,cmd,f,p,0,name,line); /* Install command */ if (delim) state->delim = cmd; else for(z=0;z!=256;++z) if(clist[z]) state->cmd[z]=cmd; } else i_printf_2((char *)joe_gettext(_("%s %d: Missing jump\n")),name,line); } else i_printf_2((char *)joe_gettext(_("%s %d: No state\n")),name,line); } else i_printf_2((char *)joe_gettext(_("%s %d: Unknown character\n")),name,line); } } while (stack) { struct ifstack *st = stack; stack = st->next; i_printf_2((char *)joe_gettext(_("%s %d: ifdef with no matching endif\n")),name,st->line); joe_free(st); } fclose(f); return first; } int syntax_match(struct high_syntax *syntax,unsigned char *name,unsigned char *subr,struct high_param *params) { struct high_param *syntax_params; if (zcmp(syntax->name,name)) return 0; if (!syntax->subr ^ !subr) return 0; if (subr && zcmp(syntax->subr,subr)) return 0; syntax_params = syntax->params; while (syntax_params && params) { if (zcmp(syntax_params->name,params->name)) return 0; syntax_params = syntax_params->next; params = params->next; } return syntax_params == params; } struct high_syntax *load_syntax_subr(unsigned char *name,unsigned char *subr,struct high_param *params) { struct high_syntax *syntax; /* New syntax table */ /* Find syntax table */ /* Already loaded? */ for(syntax=syntax_list;syntax;syntax=syntax->next) if(syntax_match(syntax,name,subr,params)) return syntax; /* Create new one */ syntax = joe_malloc(sizeof(struct high_syntax)); syntax->name = zdup(name); syntax->subr = subr ? zdup(subr) : 0; syntax->params = params; syntax->next = syntax_list; syntax->nstates = 0; syntax->color = 0; syntax->states = joe_malloc(sizeof(struct high_state *)*(syntax->szstates = 64)); syntax->ht_states = htmk(syntax->szstates); iz_cmd(&syntax->default_cmd); syntax->default_cmd.reset = 1; syntax->stack_base = 0; syntax_list = syntax; if (load_dfa(syntax)) { /* dump_syntax(syntax); */ return syntax; } else { if(syntax_list == syntax) syntax_list = syntax_list->next; else { struct high_syntax *syn; for(syn=syntax_list;syn->next!=syntax;syn=syn->next); syn->next = syntax->next; } htrm(syntax->ht_states); joe_free(syntax->name); joe_free(syntax->states); joe_free(syntax); return 0; } } struct high_syntax *load_syntax(unsigned char *name) { if (!name) return 0; return load_syntax_subr(name,0,0); } ne-3.3.4/src/syntax.h000066400000000000000000000102251475116431000144130ustar00rootroot00000000000000/* Syntax highlighting from Joe's Own Editor: Syntax highlighting DFA interpreter. Copyright (C) 2004 Joseph H. Allen Copyright (C) 2009-2025 Todd M. Lewis and Sebastiano Vigna This file is part of ne, the nice editor. This library 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 library 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 . */ #ifndef _Isyntax #define _Isyntax 1 /* Color definition */ struct high_color { struct high_color *next; unsigned char *name; /* Symbolic name of color */ uint32_t color; /* Color value */ }; /* State */ struct high_state { int32_t no; /* State number */ uint32_t color; /* Color for this state */ unsigned char *name; /* Highlight state name */ struct high_cmd *cmd[256]; /* Character table */ struct high_cmd *delim; /* Matching delimiter */ }; /* Parameter list */ struct high_param { struct high_param *next; unsigned char *name; }; /* Command (transition) */ struct high_cmd { unsigned noeat : 1; /* Set to give this character to next state */ unsigned start_buffering : 1; /* Set if we should start buffering */ unsigned stop_buffering : 1; /* Set if we should stop buffering */ unsigned save_c : 1; /* Save character */ unsigned save_s : 1; /* Save string */ unsigned ignore : 1; /* Set to ignore case */ unsigned start_mark : 1; /* Set to begin marked area including this char */ unsigned stop_mark : 1; /* Set to end marked area excluding this char */ unsigned recolor_mark : 1; /* Set to recolor marked area with new state */ unsigned rtn : 1; /* Set to return */ unsigned reset : 1; /* Set to reset the call stack */ int recolor; /* No. chars to recolor if <0. */ struct high_state *new_state; /* The new state */ HASH *keywords; /* Hash table of keywords */ struct high_cmd *delim; /* Matching delimiter */ struct high_syntax *call; /* Syntax subroutine to call */ }; /* Call stack frame */ struct high_frame { struct high_frame *parent; /* Caller's frame */ struct high_frame *child; /* First callee's frame */ struct high_frame *sibling; /* Caller's next callee's frame */ struct high_syntax *syntax; /* Current syntax subroutine */ struct high_state *return_state; /* Return state in the caller's subroutine */ }; /* Loaded form of syntax file or subroutine */ struct high_syntax { struct high_syntax *next; /* Linked list of loaded syntaxes */ unsigned char *name; /* Name of this syntax */ unsigned char *subr; /* Name of the subroutine (or NULL for whole file) */ struct high_param *params; /* Parameters defined */ struct high_state **states; /* The states of this syntax. states[0] is idle state */ HASH *ht_states; /* Hash table of states */ int nstates; /* No. states */ int szstates; /* Malloc size of states array */ struct high_color *color; /* Linked list of color definitions */ struct high_cmd default_cmd; /* Default transition for new states */ struct high_frame *stack_base; /* Root of run-time call tree */ }; /* Find a syntax. Load it if necessary. */ struct high_syntax *load_syntax PARAMS((unsigned char *name)); /* Parse a lines. Returns new state. */ extern uint32_t *attr_buf; extern int64_t attr_len; HIGHLIGHT_STATE parse PARAMS((struct high_syntax *syntax, line_desc *ld, HIGHLIGHT_STATE h_state, bool utf8)); #define clear_state(s) (((s)->saved_s[0] = 0), ((s)->state = 0), ((s)->stack = 0)) #define invalidate_state(s) ((s)->state = -1) #define move_state(to,from) (*(to)= *(from)) #define eq_state(x,y) ((x)->state == (y)->state && (x)->stack == (y)->stack && !zcmp((x)->saved_s, (y)->saved_s)) extern struct high_color *global_colors; void parse_color_def PARAMS((struct high_color **color_list,unsigned char *p,unsigned char *name,int line)); #endif ne-3.3.4/src/term.c000066400000000000000000001015601475116431000140320ustar00rootroot00000000000000/* Terminal control based on terminfo capabilities. Originally part of GNU Emacs. Vastly edited and modified for use within ne. Copyright (C) 1985, 1986, 1987 Free Software Foundation, Inc. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2025 Todd M. Lewis and Sebastiano Vigna This file is part of ne, the nice editor. This library 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 library 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 . */ #include #include #include #include #ifndef NE_TERMCAP #include #include #else #include "info2cap.h" #endif #include #include #include "term.h" #include "ansi.h" #include "termchar.h" #include "cm.h" #include "utf8.h" /* When displaying errors about the terminal database, we try to print the correct name. */ #ifdef NE_TERMCAP #define DATABASE_NAME "termcap" #else #define DATABASE_NAME "terminfo" #endif /* If true, we want the use the built-in ANSI terminal, not a real one. */ #ifdef NE_ANSI bool ansi = true; #else bool ansi = false; #endif /* Value is non-zero if attribute ATTR may be used with color. ATTR should be one of the enumerators from enum no_color_bit, or a bit set built from them. */ #define MAY_USE_WITH_COLORS(ATTR) (color_ok && ! (ne_no_color_video & (ATTR))) /* The mask values for no_color_video. */ enum no_color_bit { NC_STANDOUT = 1 << 0, NC_UNDERLINE = 1 << 1, NC_REVERSE = 1 << 2, NC_BLINK = 1 << 3, NC_DIM = 1 << 4, NC_BOLD = 1 << 5, NC_INVIS = 1 << 6, NC_PROTECT = 1 << 7, NC_ALT_CHARSET = 1 << 8 }; /* We use internal copies of the terminfo capabilities because we want to be able to use a hardwired set. */ bool ne_generic_type; int ne_lines; int ne_columns; int ne_no_color_video; char *ne_column_address; char *ne_row_address; char *ne_cursor_address; char *ne_carriage_return; char *ne_cursor_home; char *ne_cursor_to_ll; char *ne_cursor_right; char *ne_cursor_down; char *ne_cursor_left; char *ne_cursor_up; int ne_auto_right_margin; int ne_eat_newline_glitch; char *ne_clr_eos; char *ne_clear_screen; char *ne_bell; char *ne_flash_screen; char *ne_scroll_forward; char *ne_scroll_reverse; char *ne_enter_delete_mode; char *ne_exit_delete_mode; char *ne_enter_insert_mode; char *ne_exit_insert_mode; char *ne_enter_standout_mode; char *ne_enter_bold_mode; char *ne_exit_standout_mode; int ne_magic_cookie_glitch; bool ne_move_standout_mode; char *ne_change_scroll_region; char *ne_insert_line; char *ne_parm_insert_line; char *ne_delete_line; char *ne_parm_delete_line; char *ne_insert_character; char *ne_insert_padding; char *ne_parm_ich; char *ne_delete_character; char *ne_parm_dch; bool ne_move_insert_mode; char *ne_cursor_invisible; char *ne_cursor_normal; char *ne_init_1string; char *ne_init_2string; char *ne_init_3string; char *ne_enter_ca_mode; char *ne_exit_ca_mode; char *ne_exit_attribute_mode; char *ne_exit_alt_charset_mode; char *ne_repeat_char; bool ne_tilde_glitch; bool ne_memory_below; bool ne_has_meta_key; char *ne_meta_on; char *ne_meta_off; char *ne_set_window; char *ne_keypad_local; char *ne_keypad_xmit; char *ne_clr_eol; bool ne_transparent_underline; char *ne_set_background; char *ne_set_foreground; char *ne_enter_underline_mode; char *ne_exit_underline_mode; char *ne_enter_bold_mode; char *ne_enter_blink_mode; char *ne_enter_dim_mode; char *ne_enter_reverse_mode; char *ne_exit_attribute_mode; /* This is the real instantiation of the cm structure used by cm.c to hold the cursor motion strings. */ struct cm Wcm; #define OUTPUT(a) tputs (a, ne_lines - curY, cmputc) #define OUTPUT1(a) tputs (a, 1, cmputc) #define OUTPUTL(a, lines) tputs (a, lines, cmputc) #define OUTPUT_IF(a) { if (a) tputs (a, ne_lines - curY, cmputc); } #define OUTPUT1_IF(a) { if (a) tputs (a, 1, cmputc); } /* Terminal charateristics that higher levels want to look at. These are all extern'd in termchar.h */ bool line_ins_del_ok; /* Terminal can insert and delete lines */ bool char_ins_del_ok; /* Terminal can insert and delete chars */ bool scroll_region_ok; /* Terminal supports setting the scroll window */ bool standout_ok; /* Terminal supports standout without magic cookies */ bool underline_ok; /* Terminal supports standout without magic cookies */ bool cursor_on_off_ok; /* Terminal can make the cursor visible or invisible */ bool ansi_color_ok; /* Terminal supports ANSI color */ bool color_ok; /* Terminal supports color */ uint32_t curr_attr; /* The current video attributes. */ static int RPov; /* Least number of chars to start a TS_repeat. Less wouldn't be worth. */ static bool delete_in_insert_mode; /* True if delete mode == insert mode */ static bool se_is_so; /* True if same string both enters and leaves standout mode */ static bool esm_is_eam; /* True if exiting standout mode turns off all attributes */ static bool insert_mode; /* True when in insert mode. */ static bool standout_mode; /* True when in standout mode. */ static bool standout_wanted; /* True if we should be writing in standout mode. */ static bool underline_mode; /* True when in underline mode. */ static bool underline_wanted; /* True if we should be writing in underline mode. */ static bool bpaste_support_enabled; /* Size of window specified by higher levels. This is the number of lines, starting from top of screen, to participate in ins/del line operations. Effectively it excludes the bottom lines - specified_window_size lines from those operations. */ int specified_window; /* If true, then all I/O is to be performed in UTF-8. */ bool io_utf8; /* Returns the output width of the given character. It is maximised with 1 w.r.t. wcwidth(), so its result is equivalent to the width of the character that will be output by out(). */ int output_width(const int c) { const int width = wcwidth(c); return width > 0 ? width : 1; } /* Returns the output width of the given string. If s is NULL, returns len. If the width of the string exceeds maxWidth, modifies len so that it contains the longest prefix of s whose width is not greater than maxWidth, and returns the corresponding width. */ static int string_output_width(const char *s, int *len, int maxWidth, bool utf8) { if (s == NULL) { if (*len > maxWidth) *len = maxWidth; return *len; } else { int width = 0, char_width, l = *len; if (utf8) { while(l-- != 0) { char_width = output_width(utf8char(s)); if (width + char_width > maxWidth) { *len -= l + 1; break; } width += char_width; s += utf8len(*s); } } else while(l-- != 0) { char_width = output_width(*(s++)); if (width + char_width > maxWidth) { *len -= l + 1; break; } width += char_width; } return width; } } static int joe2color(const int joe_color) { if (ansi_color_ok) return joe_color & 7; switch(joe_color & 7) { case 0: return 0; /* BLACK */ case 1: return 4; /* RED */ case 2: return 2; /* GREEN */ case 3: return 6; /* YELLOW */ case 4: return 1; /* BLUE */ case 5: return 5; /* MAGENTA */ case 6: return 3; /* CYAN */ case 7: return 7; /* WHITE */ } return -1; } /* Sets up attributes */ #define TPARM2(a, b) tparm(a, b, 0, 0, 0, 0, 0, 0, 0, 0) #define TPARM3(a, b, c) tparm(a, b, c, 0, 0, 0, 0, 0, 0, 0) #define TPARM4(a, b, c, d) tparm(a, b, c, d, 0, 0, 0, 0, 0, 0) #define TPARM5(a, b, c, d, e) tparm(a, b, c, d, e, 0, 0, 0, 0, 0) #ifdef PLAIN_SET_ATTR void set_attr(const uint32_t attr) { OUTPUT1(ne_exit_attribute_mode); if (attr & INVERSE) OUTPUT1(ne_enter_reverse_mode); if (attr & BOLD) OUTPUT1(ne_enter_bold_mode); if (attr & UNDERLINE) OUTPUT1(ne_enter_underline_mode); if (attr & DIM) OUTPUT1(ne_enter_dim_mode); if (attr & BLINK) OUTPUT1(ne_enter_blink_mode); if (color_ok) { if (attr & FG_NOT_DEFAULT) { const char * const buf = TPARM2(ne_set_foreground , joe2color(attr >> FG_SHIFT)); OUTPUT1(buf); } if (attr & BG_NOT_DEFAULT) { const char * const buf = TPARM2(ne_set_background , joe2color(attr >> BG_SHIFT)); OUTPUT1(buf); } } } #else void set_attr(const uint32_t attr) { bool attr_reset = false; /* If we have to set a different subset of attributes, or if we have to set to the default at least one of the colors (background/foreground) we must necessarily reset all attributes. */ if ((curr_attr & AT_MASK) != (attr & AT_MASK) || (!(attr & FG_NOT_DEFAULT) && (curr_attr & FG_NOT_DEFAULT)) || (!(attr & BG_NOT_DEFAULT) && (curr_attr & BG_NOT_DEFAULT))) { OUTPUT1_IF(ne_exit_attribute_mode) attr_reset = true; if ((attr & INVERSE) && MAY_USE_WITH_COLORS(NC_REVERSE)) OUTPUT1_IF(ne_enter_reverse_mode) if ((attr & BOLD) && MAY_USE_WITH_COLORS(NC_BOLD)) OUTPUT1_IF(ne_enter_bold_mode) if ((attr & UNDERLINE) && MAY_USE_WITH_COLORS(NC_UNDERLINE)) OUTPUT1_IF(ne_enter_underline_mode) if ((attr & DIM) && MAY_USE_WITH_COLORS(NC_DIM)) OUTPUT1_IF(ne_enter_dim_mode) if ((attr & BLINK) && MAY_USE_WITH_COLORS(NC_BLINK)) OUTPUT1_IF(ne_enter_blink_mode) } if (color_ok) { /* Colors must be set if attributes have been reset and the required color is not default, or in any case if the color has changed. */ if (attr_reset && (attr & FG_NOT_DEFAULT) || (attr & FG_MASK) != (curr_attr & FG_MASK)) { if (attr & FG_NOT_DEFAULT) { const char * const buf = TPARM2(ne_set_foreground , joe2color(attr >> FG_SHIFT)); OUTPUT1(buf); } } if (attr_reset && (attr & BG_NOT_DEFAULT) || (attr & BG_MASK) != (curr_attr & BG_MASK)) { if (attr & BG_NOT_DEFAULT) { const char * const buf = TPARM2(ne_set_background , joe2color(attr >> BG_SHIFT)); OUTPUT1(buf); } } } curr_attr = attr; } #endif static void turn_off_standout(void) { OUTPUT1(ne_exit_standout_mode); /* If exiting standout mode deletes all attributes, we update curr_attr. */ if (esm_is_eam) curr_attr = 0; standout_mode = false; } static void standout_if_wanted(void) { if (standout_mode != standout_wanted) { if (standout_wanted) { OUTPUT1(ne_enter_standout_mode); standout_mode = true; } else turn_off_standout(); } } static void turn_off_underline(void) { OUTPUT1(ne_exit_underline_mode); underline_mode = false; } static void underline_if_wanted(void) { if (underline_mode != underline_wanted) { if (underline_wanted) { OUTPUT1(ne_enter_underline_mode); underline_mode = true; } else turn_off_underline(); } } /* These functions are called on all terminals in order to handle highlighting, but do nothing on terminals with a magic cookie (or without standout). */ void standout_on (void) { if (standout_ok) standout_wanted = true; } void standout_off (void) { standout_wanted = false; } void underline_on (void) { if (underline_ok) underline_wanted = true; } void underline_off (void) { underline_wanted = false; } /* Depending on the value of io_utf8, this function will do a simple putchar(), or a series of putchar() that expand the given character in UTF-8 encoding. If attr is -1, no attribute will be set. */ static void out(int c, const uint32_t attr) { uint32_t add_attr = 0; /* PORTABILITY PROBLEM: this code is responsible for filtering nonprintable characters. On systems with a wider system character set, it could be redefined, for instance, in order to allow characters between 128 and 160 to be printed. Currently, it returns '?' on all control characters (and non-ISO-8859-1 characters, if io_utf8 is false), space on 160, and the obvious capital letter for control characters below 32. */ if (c >= 127 && c < 160) { c = '?'; add_attr = INVERSE; } if (c == 160) { c = ' '; add_attr = INVERSE; } if (c < ' ') { c += '@'; add_attr = INVERSE; } if (c > 0xFF && !io_utf8) { c = '?'; add_attr = INVERSE; } /* If io_utf8 is off, we consider all characters in the range of ISO-8859-x encoding schemes as printable. */ if (io_utf8 && wcwidth(c) <= 0) { c = '?'; add_attr = INVERSE; } if (attr != -1) set_attr(attr | add_attr); if (io_utf8) { if (c < 0x80) putchar(c); /* ASCII */ else if (c < 0x800) { putchar(0xC0 | (c >> 6)); putchar(0x80 | (c >> 0) & 0x3F); } else if (c < 0x10000) { putchar(0xE0 | (c >> 12)); putchar(0x80 | (c >> 6) & 0x3F); putchar(0x80 | (c >> 0) & 0x3F); } else if (c < 0x200000) { putchar(0xF0 | (c >> 18)); putchar(0x80 | (c >> 12) & 0x3F); putchar(0x80 | (c >> 6) & 0x3F); putchar(0x80 | (c >> 0) & 0x3F); } else if (c < 0x4000000) { putchar(0xF8 | (c >> 24)); putchar(0x80 | (c >> 18) & 0x3F); putchar(0x80 | (c >> 12) & 0x3F); putchar(0x80 | (c >> 6) & 0x3F); putchar(0x80 | (c >> 0) & 0x3F); } else { putchar(0xFC | (c >> 30)); putchar(0x80 | (c >> 24) & 0x3F); putchar(0x80 | (c >> 18) & 0x3F); putchar(0x80 | (c >> 12) & 0x3F); putchar(0x80 | (c >> 6) & 0x3F); putchar(0x80 | (c >> 0) & 0x3F); } } else putchar(c); } /* Rings a bell or flashes the screen. If the service is not available, the other one is tried. */ void ring_bell(void) { OUTPUT1_IF (ne_bell ? ne_bell : ne_flash_screen); } void do_flash(void) { OUTPUT1_IF (ne_flash_screen ? ne_flash_screen : ne_bell); } /* Sets correctly the scroll region (first line is line 0). This function assumes scroll_region_ok == true. The cursor position is lost, as from the terminfo specs. */ static void set_scroll_region (const int start, const int stop) { assert(scroll_region_ok); /* Both control string have line range 0 to lines-1 */ char *buf; if (ne_change_scroll_region) buf = TPARM3(ne_change_scroll_region, start, stop); else buf = TPARM5(ne_set_window, start, stop, 0, ne_columns - 1); OUTPUT1(buf); losecursor(); } static void turn_on_insert (void) { if (!insert_mode) OUTPUT1(ne_enter_insert_mode); insert_mode = true; } static void turn_off_insert (void) { if (insert_mode) OUTPUT1(ne_exit_insert_mode); insert_mode = false; } void turn_on_bracketed_paste(void) { if (bpaste_support_enabled) OUTPUT1_IF(BPASTE_ENABLE_SEQ); bpaste_support_enabled = true; } void turn_off_bracketed_paste(void) { if (bpaste_support_enabled) OUTPUT1_IF(BPASTE_DISABLE_SEQ); bpaste_support_enabled = false; } /* Prepares the terminal for interactive I/O. It initializes the terminal, puts standout in a known state, prepares the cursor address mode, and activates the keypad and the meta key. */ void set_terminal_modes(void) { /* Note that presently we do not support if and iprog, the program and the file which should be used, if present, to initialize the terminal. */ OUTPUT1_IF(ne_exit_attribute_mode); OUTPUT1_IF(ne_exit_alt_charset_mode); OUTPUT1_IF(ne_exit_standout_mode); OUTPUT1_IF(ne_exit_underline_mode); OUTPUT1_IF(ne_enter_ca_mode); OUTPUT1_IF(ne_keypad_xmit); if (bracketed_paste) OUTPUT1_IF(BPASTE_ENABLE_SEQ); if (ne_has_meta_key) OUTPUT1_IF(ne_meta_on); turn_off_standout(); turn_off_underline(); losecursor(); } /* Puts again the terminal in its normal state. */ void reset_terminal_modes (void) { if (bracketed_paste) OUTPUT1_IF(BPASTE_DISABLE_SEQ); OUTPUT1_IF(ne_exit_attribute_mode); OUTPUT1_IF(ne_exit_alt_charset_mode); turn_off_standout(); turn_off_underline(); OUTPUT1_IF(ne_keypad_local); OUTPUT1_IF(ne_exit_ca_mode); } /* Sets the variable specified_window. Following line insert/delete operations will be limited to lines 0 to (size-1). */ void set_terminal_window(const int size) { specified_window = size ? size : ne_lines; } /* These functions are the external interface to cursor on/off strings. */ void cursor_on (void) { if (cursor_on_off_ok) OUTPUT1(ne_cursor_normal); } void cursor_off (void) { if (cursor_on_off_ok) OUTPUT1(ne_cursor_invisible); } /* Move to absolute position, specified origin 0 */ void move_cursor (const int row, const int col) { if (curY == row && curX == col) return; if (!ne_move_standout_mode) turn_off_standout(); if (!ne_move_insert_mode) turn_off_insert (); cmgoto (row, col); } /* Clears from the cursor position to the end of line. It assumes that the line is already clear starting at column first_unused_hpos. Note that the cursor may be moved, on terminals lacking a `ce' string. */ void clear_end_of_line(const int first_unused_hpos) { if (curX >= first_unused_hpos) return; if (curr_attr & BG_NOT_DEFAULT) set_attr(0); if (ne_clr_eol) OUTPUT1 (ne_clr_eol); else { /* We have to do it the hard way. */ turn_off_insert (); for (int i = curX; i < first_unused_hpos; i++) putchar (' '); cmplus (first_unused_hpos - curX); } } /* Shorthand; use this if you don't know anything about the state of the line. */ void clear_to_eol(void) { clear_end_of_line(ne_columns); } /* Clears from the cursor position to the end of screen */ void clear_to_end (void) { if (ne_clr_eos) OUTPUT(ne_clr_eos); else { for (int i = curY; i < ne_lines; i++) { move_cursor (i, 0); clear_to_eol(); } } } /* Clears the entire screen */ void clear_entire_screen (void) { if (ne_clear_screen) { OUTPUTL(ne_clear_screen, ne_lines); cmat (0, 0); } else { move_cursor (0, 0); clear_to_end(); } } /* Outputs raw_len characters pointed at by string, attributed as indicated by a corresponding vector of attributes, which can be NULL, in which case no attribute will be set. The characters will be truncated to the end of the current line. Passing a NULL for string results in outputting spaces. A len of 0 causes no action. If utf8 is true, the string is UTF-8 encoded. */ void output_chars(const char *string, const uint32_t *attr, const int raw_len, const bool utf8) { if (raw_len == 0) return; turn_off_insert(); standout_if_wanted(); underline_if_wanted(); /* If the string is UTF-8 encoded, compute its real length. */ int len = utf8 && string != NULL ? utf8strlen(string, raw_len) : raw_len; /* If the width of the string exceeds the remaining columns, we reduce len. Moreover, we don't dare write in last column of bottom line, if AutoWrap, since that would scroll the whole screen on some terminals. */ cmplus(string_output_width(string, &len, ne_columns - curX - (AutoWrap && !MagicWrap && curY == ne_lines - 1), utf8)); if (string == NULL) { for(int i = 0; i < len; i++) { /* When outputting spaces, it's only the first attribute that's used. */ if (attr) set_attr(*attr); putchar(' '); } return; } if (!ne_transparent_underline && !ne_tilde_glitch) { for(int i = 0; i < len; i++) { if (utf8) { const int c = utf8char(string); string += utf8len(*string); out(c, attr ? attr[i] : -1); } else { const int c = (unsigned char)*string++; out(c, attr ? attr[i] : -1); } } } else for(int i = 0; i < len; i++) { if (attr) set_attr(attr[i]); int c = utf8 ? utf8char(string) : (unsigned char)*string; if (c == '_' && ne_transparent_underline) { putchar (' '); OUTPUT1(Left); } if (ne_tilde_glitch && c == '~') c = '`'; out(c, attr ? attr[i] : -1); string += utf8 ? utf8len(*string) : 1; } } /* Outputs a NULL-terminated string without setting attributes. */ void output_string(const char * const s, const bool utf8) { assert(s != NULL); output_chars(s, NULL, strlen(s), utf8); } /* Outputs a single ISO 10646 character with a given set of attributes. If attr == -1, no attribute is set. */ void output_char(const int c, const uint32_t attr, const bool utf8) { static char t[8]; if (utf8) { memset(t, 0, sizeof t); utf8str(c, t); } else { t[0] = c; t[1] = 0; } assert(c != 0); output_chars(t, attr != -1 ? &attr : NULL, strlen(t), utf8); } /* Outputs spaces. */ void output_spaces(const int n, const uint32_t * const attr) { output_chars(NULL, attr, n, false); } /* Same as output_chars(), but inserts instead. */ void insert_chars(const char * start, const uint32_t * const attr, const int raw_len, const bool utf8) { if (raw_len == 0) return; standout_if_wanted(); underline_if_wanted(); /* If the string is non-NULL and UTF-8 encoded, compute its real length. */ int len = utf8 && start != NULL ? utf8strlen(start, raw_len) : raw_len; if (ne_parm_ich) { int width = 0; if (start != NULL) { if (utf8) for(int i = 0; i < raw_len; i += utf8len(start[i])) width += output_width(utf8char(start + i)); else for(int i = 0; i < raw_len; i++) width += output_width(start[i]); } else width = len; const char * const buf = TPARM2(ne_parm_ich, width); OUTPUT1 (buf); if (start) output_chars(start, attr, raw_len, utf8); return; } turn_on_insert (); /* If the width of the string exceeds the remaining columns, we reduce len. Moreovero, we don't dare to write in the last column of the bottom line, if AutoWrap, since that would scroll the whole screen on some terminals. */ cmplus(string_output_width(start, &len, ne_columns - curX - (AutoWrap && !MagicWrap && curY == ne_lines - 1), utf8)); if (!ne_transparent_underline && !ne_tilde_glitch && start && ne_insert_padding == NULL && ne_insert_character == NULL) { for(int i = 0; i < len; i++) { if (attr) set_attr(attr[i]); int c; if (utf8) { c = utf8char(start); start += utf8len(*start); } else c = (unsigned char)*start++; out(c, attr ? attr[i] : -1); } } else for(int i = 0; i < len; i++) { OUTPUT1_IF (ne_insert_character); if (!start) { /* When outputting spaces, it's only the first attribute that's used. */ out(' ', attr ? *attr : -1); } else { if (attr) set_attr(attr[i]); int c; if (utf8) { c = utf8char(start); start += utf8len(*start); } else c = (unsigned char)*start++; if (ne_tilde_glitch && c == '~') c = '`'; out(c, attr ? attr[i] : -1); } OUTPUT1_IF(ne_insert_padding); } } /* Inserts a single ISO 10646 character. If attr == -1, no attribute is set. */ void insert_char(const int c, const uint32_t attr, const bool utf8) { static char t[8]; if (utf8) { memset(t, 0, sizeof t); utf8str(c, t); } else { t[0] = c; t[1] = 0; } assert(c != 0); insert_chars(t, attr == -1 ? NULL : &attr, strlen(t), utf8); } /* Deletes n characters at the current cursor position. */ void delete_chars (int n) { if (n == 0) return; standout_if_wanted(); underline_if_wanted(); if (delete_in_insert_mode) turn_on_insert(); else { turn_off_insert(); OUTPUT1_IF(ne_enter_delete_mode); } if (ne_parm_dch) { const char * const buf = TPARM2(ne_parm_dch, n); OUTPUT1(buf); } else while(n-- != 0) OUTPUT1(ne_delete_character); if (!delete_in_insert_mode) OUTPUT_IF(ne_exit_delete_mode); } /* This internal function will do an insertion or deletion for n lines, given a parametrized and/or a one-line capability for that purpose. */ static void do_multi_ins_del(char * const multi, const char * const single, int n) { if (multi) { const char * const buf = TPARM2(multi, n); OUTPUT(buf); } else while(n-- != 0) OUTPUT(single); } /* Inserts n lines at vertical position vpos. If n is negative, it deletes -n lines. specified_window is taken into account. This function assumes line_ins_del_ok == true. Returns true if an insertion/deletion actually happened. */ int ins_del_lines (const int vpos, const int n) { int i = n > 0 ? n : -n; assert(line_ins_del_ok); assert(i != 0); assert(vpos < specified_window); if (scroll_region_ok && vpos + i >= specified_window) return false; if (!ne_memory_below && vpos + i >= ne_lines) return false; standout_if_wanted(); underline_if_wanted(); if (scroll_region_ok) { if (specified_window != ne_lines) set_scroll_region(vpos, specified_window - 1); if (n < 0) { move_cursor(specified_window - 1, 0); while (i-- != 0) OUTPUTL(ne_scroll_forward, specified_window - vpos + 1); } else { move_cursor(vpos, 0); while (i-- != 0) OUTPUTL(ne_scroll_reverse, specified_window - vpos + 1); } if (specified_window != ne_lines) set_scroll_region(0, ne_lines - 1); } else { if (n > 0) { if (specified_window != ne_lines) { move_cursor(specified_window - i, 0); do_multi_ins_del(ne_parm_delete_line, ne_delete_line, i); } move_cursor(vpos, 0); do_multi_ins_del(ne_parm_insert_line, ne_insert_line, i); } else { move_cursor(vpos, 0); do_multi_ins_del(ne_parm_delete_line, ne_delete_line, i); if (specified_window != ne_lines) { move_cursor(specified_window - i, 0); do_multi_ins_del(ne_parm_insert_line, ne_insert_line, i); } else if (ne_memory_below) { move_cursor(ne_lines + n, 0); clear_to_end (); } } } return true; } extern int cost; /* In cm.c */ extern int evalcost(int); /* Performs the cursor motion cost setup, and sets the variable RPov to the number of characters (with padding) which are really output when repeating one character. RPov is disable using UTF-8 I/O. */ static void calculate_costs (void) { if (ne_repeat_char) { char *const buf = TPARM3(ne_repeat_char, ' ', 1); cost = 0; tputs(buf, 1, evalcost); RPov = cost + 1; } else RPov = ne_columns * 2; cmcostinit(); } /* Gets the window size using TIOCGSIZE, TIOCGWINSZ, or LINES/COLUMNS as a last resort. It is called by the signal handler for SIGWINCH on systems that support it. Return 1 if the window size has changed. */ int ttysize(void) { #ifdef TIOCGSIZE /* try using the TIOCGSIZE call, if defined */ struct ttysize size; D(fprintf(stderr,"ttysize (TIOCGSIZE): CHECKING...\n");) if (ioctl(0, TIOCGSIZE, &size)) return 0; const int l = size.ts_lines; const int c = size.ts_cols; #elif defined(TIOCGWINSZ) /* try using the TIOCGWINSZ call, if defined */ struct winsize size; D(fprintf(stderr,"ttysize (TIOCGWINSZ): CHECKING...\n");) if (ioctl(0, TIOCGWINSZ, &size)) return 0; const int l = size.ws_row; const int c = size.ws_col; #else /* As a last resort, we try to read LINES and COLUMNS, falling back to the terminal-specified size. */ if (! getenv("LINES") || ! getenv("COLUMNS")) return 0; const int l = strtol(getenv("LINES"), NULL, 10); const int c = strtol(getenv("COLUMNS"), NULL, 10); #endif D(fprintf(stderr,"ttysize:...size is (%d,%d)\n", l, c);) if (((ne_lines != l) || (ne_columns != c)) && l > 0 && c > 0) { ScreenRows = ne_lines = l; ScreenCols = ne_columns = c; set_terminal_window(ne_lines - 1); if (scroll_region_ok) set_scroll_region(0, ne_lines - 1); D(fprintf(stderr,"ttysize: size changed.\n");) return 1; } return 0; } #ifndef NE_TERMCAP /* If we get capabilities from the database, then we copy them into our internal counterparts. */ void copy_caps(void) { ne_generic_type = generic_type; ne_lines = lines; ne_columns = columns; ne_no_color_video = no_color_video == -1 ? 0 : no_color_video; ne_column_address = column_address; ne_row_address = row_address; ne_cursor_address = cursor_address; ne_carriage_return = carriage_return; ne_cursor_home = cursor_home; ne_cursor_to_ll = cursor_to_ll; ne_cursor_right = cursor_right; ne_cursor_down = cursor_down; ne_cursor_left = cursor_left; ne_cursor_up = cursor_up; ne_auto_right_margin = auto_right_margin; ne_eat_newline_glitch = eat_newline_glitch; ne_clr_eos = clr_eos; ne_clear_screen = clear_screen; ne_bell = bell; ne_flash_screen = flash_screen; ne_scroll_forward = scroll_forward; ne_scroll_reverse = scroll_reverse; ne_enter_delete_mode = enter_delete_mode; ne_exit_delete_mode = exit_delete_mode; ne_enter_insert_mode = enter_insert_mode; ne_exit_insert_mode = exit_insert_mode; ne_enter_standout_mode = enter_standout_mode; ne_exit_standout_mode = exit_standout_mode; ne_magic_cookie_glitch = magic_cookie_glitch; ne_move_standout_mode = move_standout_mode; ne_change_scroll_region = change_scroll_region; ne_insert_line = insert_line; ne_parm_insert_line = parm_insert_line; ne_delete_line = delete_line; ne_parm_delete_line = parm_delete_line; ne_insert_character = insert_character; ne_insert_padding = insert_padding; ne_parm_ich = parm_ich; ne_delete_character = delete_character; ne_parm_dch = parm_dch; ne_move_insert_mode = move_insert_mode; ne_cursor_invisible = cursor_invisible; ne_cursor_normal = cursor_normal; ne_init_1string = init_1string; ne_init_2string = init_2string; ne_init_3string = init_3string; ne_enter_ca_mode = enter_ca_mode; ne_exit_ca_mode = exit_ca_mode; ne_exit_attribute_mode = exit_attribute_mode; ne_exit_alt_charset_mode = exit_alt_charset_mode; ne_repeat_char = repeat_char; ne_tilde_glitch = tilde_glitch; ne_memory_below = memory_below; ne_has_meta_key = has_meta_key; ne_meta_on = meta_on; ne_meta_off = meta_off; ne_set_window = set_window; ne_keypad_local = keypad_local; ne_keypad_xmit = keypad_xmit; ne_clr_eol = clr_eol; ne_transparent_underline = transparent_underline; if (ansi_color_ok = (set_a_foreground && set_a_background)) { ne_set_background = set_a_background; ne_set_foreground = set_a_foreground; } else { ne_set_background = set_background; ne_set_foreground = set_foreground; } ne_enter_underline_mode = enter_underline_mode; ne_exit_underline_mode = exit_underline_mode; ne_enter_bold_mode = enter_bold_mode; ne_enter_blink_mode = enter_blink_mode; ne_enter_dim_mode = enter_dim_mode; ne_enter_reverse_mode = enter_reverse_mode; ne_exit_attribute_mode = exit_attribute_mode; } #endif /* This is the main terminal initialization function. It sets up Wcm, patches here and there the terminfo database, calculates the costs, and initializes the terminal characteristics variables. Note that this function can exit(). */ void term_init (void) { int errret; /* First of all we initialize the terminfo database. */ if (ansi) setup_ansi_term(); else if (setupterm(NULL, 1, &errret) == ERR) { printf("There are problems in finding your terminal in the database.\n" "Please check that the variable TERM is set correctly, and that\n" "your " DATABASE_NAME " database is up to date.\n" "If your terminal is ANSI-compatible, you can also try to use\n" "the --ansi switch.\n"); exit(1); } #ifndef NE_TERMCAP else copy_caps(); #endif ColPosition = ne_column_address; RowPosition = ne_row_address; AbsPosition = ne_cursor_address; CR = ne_carriage_return; Home = ne_cursor_home; LastLine = ne_cursor_to_ll; Right = ne_cursor_right; Down = ne_cursor_down; Left = ne_cursor_left; Up = ne_cursor_up; AutoWrap = ne_auto_right_margin; MagicWrap = ne_eat_newline_glitch; ScreenRows = ne_lines; ScreenCols = ne_columns; if (!ne_bell) ne_bell = "\07"; if (!ne_scroll_forward) ne_scroll_forward = Down; if (!ne_scroll_reverse) ne_scroll_reverse = Up; if (!ansi && key_backspace && key_left && !strcmp(key_backspace, key_left)) { /* In case left and backspace produce the same sequence, we want to get key_left. */ key_backspace = NULL; } specified_window = ne_lines; if (Wcm_init()) { /* We can't do cursor motion */ if (ne_generic_type) { printf("Your terminal type is a generic terminal, not a real\n" "terminal, and it lacks the ability to position the cursor.\n" "Please check that the variable TERM is set correctly, and that\n" "your " DATABASE_NAME " database is up to date.\n"); } else { printf("Your terminal type is not powerful enough to run ne:\n" "it lacks the ability to position the cursor.\n" "Please check that the variable TERM is set correctly, and that\n" "your " DATABASE_NAME "database is up to date.\n"); } printf("If your terminal is ANSI-compatible, you can also try to use\n" "the --ansi switch.\n"); exit(1); } calculate_costs(); delete_in_insert_mode = ne_enter_delete_mode && ne_enter_insert_mode && !strcmp (ne_enter_delete_mode, ne_enter_insert_mode); se_is_so = ne_enter_standout_mode && ne_exit_standout_mode && !strcmp (ne_enter_standout_mode, ne_exit_standout_mode); esm_is_eam = ne_exit_standout_mode && ne_exit_attribute_mode && !strcmp (ne_exit_standout_mode, ne_exit_attribute_mode); scroll_region_ok = ne_set_window || ne_change_scroll_region; line_ins_del_ok = (((ne_insert_line || ne_parm_insert_line) && (ne_delete_line || ne_parm_delete_line)) || (scroll_region_ok && ne_scroll_forward && ne_scroll_reverse)); char_ins_del_ok = ((ne_insert_character || ne_enter_insert_mode || ne_insert_padding || ne_parm_ich) && (ne_delete_character || ne_parm_dch)); standout_ok = (ne_enter_standout_mode && ne_exit_standout_mode && ne_magic_cookie_glitch < 0); underline_ok = (ne_enter_underline_mode && ne_exit_underline_mode && ne_magic_cookie_glitch < 0); cursor_on_off_ok = (ne_cursor_invisible && ne_cursor_normal); color_ok = (ne_set_foreground && ne_set_background); } ne-3.3.4/src/term.h000066400000000000000000000036001475116431000140330ustar00rootroot00000000000000/* Function prototypes for term.c Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2025 Todd M. Lewis and Sebastiano Vigna This file is part of ne, the nice editor. This library 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 library 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 . */ #include #include int output_width(int c); void ring_bell(void); void do_flash(void); void turn_off_bracketed_paste(void); void turn_on_bracketed_paste(void); void set_terminal_modes(void); void reset_terminal_modes(void); void set_terminal_window(int size); void standout_on(void); void standout_off(void); void underline_on(void); void underline_off(void); void cursor_on(void); void cursor_off(void); void move_cursor(int row, int col); void clear_end_of_line(int first_unused_hpos); void clear_to_eol(void); void clear_to_end(void); void clear_entire_screen(void); void set_attr(const uint32_t); void output_chars(const char *string, const uint32_t *attr, int raw_len, bool utf8); void output_string(const char *s, bool utf8); void output_spaces(int n, const uint32_t *attr); void output_char(int c, const uint32_t attr, const bool utf8); void insert_chars(const char *start, const uint32_t *attr, int raw_len, bool utf8); void insert_char(int c, const uint32_t attr, bool utf8); void delete_chars(int n); int ins_del_lines(int vpos, int n); int ttysize(void); void term_init(void); ne-3.3.4/src/termcap.c000066400000000000000000000412551475116431000145220ustar00rootroot00000000000000/* Work-alike for termcap, plus extra features. Copyright (C) 1985, 86, 93, 94, 95, 2000, 2001 Free Software Foundation, Inc. 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 2, 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; see the file COPYING. If not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include #include /* Do this after the include, in case string.h prototypes bcopy. */ #if !defined(bcopy) #define bcopy(s, d, n) memcpy ((d), (s), (n)) #endif #ifndef NULL #define NULL (char *) 0 #endif #ifndef O_RDONLY #define O_RDONLY 0 #endif /* BUFSIZE is the initial size allocated for the buffer for reading the termcap file. It is not a limit. Make it large normally for speed. Make it variable when debugging, so can exercise increasing the space dynamically. */ #ifndef BUFSIZE #ifdef DEBUG #define BUFSIZE bufsize int bufsize = 128; #else #define BUFSIZE 2048 #endif #endif #ifndef TERMCAP_FILE #define TERMCAP_FILE "/etc/termcap" #endif #ifndef emacs static void memory_out () { write (2, "virtual memory exhausted\n", 25); exit (1); } static char * xmalloc (size) unsigned size; { register char *tem = malloc (size); if (!tem) memory_out (); return tem; } static char * xrealloc (ptr, size) char *ptr; unsigned size; { register char *tem = realloc (ptr, size); if (!tem) memory_out (); return tem; } #endif /* not emacs */ /* Looking up capabilities in the entry already found. */ /* The pointer to the data made by tgetent is left here for tgetnum, tgetflag and tgetstr to find. */ static char *term_entry; static char *tgetst1 (); /* Search entry BP for capability CAP. Return a pointer to the capability (in BP) if found, 0 if not found. */ static char * find_capability (bp, cap) register char *bp, *cap; { for (; *bp; bp++) if (bp[0] == ':' && bp[1] == cap[0] && bp[2] == cap[1]) return &bp[4]; return NULL; } int tgetnum (cap) char *cap; { register char *ptr = find_capability (term_entry, cap); if (!ptr || ptr[-1] != '#') return -1; return atoi (ptr); } int tgetflag (cap) char *cap; { register char *ptr = find_capability (term_entry, cap); return ptr && ptr[-1] == ':'; } /* Look up a string-valued capability CAP. If AREA is non-null, it points to a pointer to a block in which to store the string. That pointer is advanced over the space used. If AREA is null, space is allocated with `malloc'. */ char * tgetstr (cap, area) char *cap; char **area; { register char *ptr = find_capability (term_entry, cap); if (!ptr || (ptr[-1] != '=' && ptr[-1] != '~')) return NULL; return tgetst1 (ptr, area); } #ifdef IS_EBCDIC_HOST /* Table, indexed by a character in range 0200 to 0300 with 0200 subtracted, gives meaning of character following \, or a space if no special meaning. Sixteen characters per line within the string. */ static char esctab[] = " \057\026 \047\014 \ \025 \015 \ \005 \013 \ "; #else /* Table, indexed by a character in range 0100 to 0140 with 0100 subtracted, gives meaning of character following \, or a space if no special meaning. Eight characters per line within the string. */ static char esctab[] = " \007\010 \033\014 \ \012 \ \015 \011 \013 \ "; #endif /* PTR points to a string value inside a termcap entry. Copy that value, processing \ and ^ abbreviations, into the block that *AREA points to, or to newly allocated storage if AREA is NULL. Return the address to which we copied the value, or NULL if PTR is NULL. */ static char * tgetst1 (ptr, area) char *ptr; char **area; { register char *p, *r; register int c; register int size; char *ret; register int c1; if (!ptr) return NULL; /* `ret' gets address of where to store the string. */ if (!area) { /* Compute size of block needed (may overestimate). */ p = ptr; while ((c = *p++) && c != ':' && c != '\n') ; ret = (char *) xmalloc (p - ptr + 1); } else ret = *area; /* Copy the string value, stopping at null or colon. Also process ^ and \ abbreviations. */ p = ptr; r = ret; while ((c = *p++) && c != ':' && c != '\n') { if (c == '^') { c = *p++; if (c == '?') c = 0177; else c &= 037; } else if (c == '\\') { c = *p++; if (c >= '0' && c <= '7') { c -= '0'; size = 0; while (++size < 3 && (c1 = *p) >= '0' && c1 <= '7') { c *= 8; c += c1 - '0'; p++; } } #ifdef IS_EBCDIC_HOST else if (c >= 0200 && c < 0360) { c1 = esctab[(c & ~0100) - 0200]; if (c1 != ' ') c = c1; } #else else if (c >= 0100 && c < 0200) { c1 = esctab[(c & ~040) - 0100]; if (c1 != ' ') c = c1; } #endif } *r++ = c; } *r = '\0'; /* Update *AREA. */ if (area) *area = r + 1; return ret; } /* Outputting a string with padding. */ #ifndef emacs short ospeed; /* If OSPEED is 0, we use this as the actual baud rate. */ int tputs_baud_rate; #endif char PC; #ifndef emacs /* Actual baud rate if positive; - baud rate / 100 if negative. */ static int speeds[] = { #ifdef VMS 0, 50, 75, 110, 134, 150, -3, -6, -12, -18, -20, -24, -36, -48, -72, -96, -192 #else /* not VMS */ 0, 50, 75, 110, 135, 150, -2, -3, -6, -12, -18, -24, -48, -96, -192, -288, -384, -576, -1152 #endif /* not VMS */ }; #endif /* not emacs */ void tputs (str, nlines, outfun) register char *str; int nlines; register int (*outfun) (); { register int padcount = 0; register int speed; #ifdef emacs extern int baud_rate; speed = baud_rate; /* For quite high speeds, convert to the smaller units to avoid overflow. */ if (speed > 10000) speed = - speed / 100; #else if (ospeed == 0) speed = tputs_baud_rate; else speed = speeds[ospeed]; #endif if (!str) return; while (*str >= '0' && *str <= '9') { padcount += *str++ - '0'; padcount *= 10; } if (*str == '.') { str++; padcount += *str++ - '0'; } if (*str == '*') { str++; padcount *= nlines; } while (*str) (*outfun) (*str++); /* PADCOUNT is now in units of tenths of msec. SPEED is measured in characters per 10 seconds or in characters per .1 seconds (if negative). We use the smaller units for larger speeds to avoid overflow. */ padcount *= speed; padcount += 500; padcount /= 1000; if (speed < 0) padcount = -padcount; else { padcount += 50; padcount /= 100; } while (padcount-- > 0) (*outfun) (PC); } /* Finding the termcap entry in the termcap data base. */ struct termcap_buffer { char *beg; int size; char *ptr; int ateof; int full; }; /* Forward declarations of static functions. */ static int scan_file (); static char *gobble_line (); static int compare_contin (); static int name_match (); #ifdef VMS #include #include #include static int valid_filename_p (fn) char *fn; { struct FAB fab = cc$rms_fab; struct NAM nam = cc$rms_nam; char esa[NAM$C_MAXRSS]; fab.fab$l_fna = fn; fab.fab$b_fns = strlen(fn); fab.fab$l_nam = &nam; fab.fab$l_fop = FAB$M_NAM; nam.nam$l_esa = esa; nam.nam$b_ess = sizeof esa; return SYS$PARSE(&fab, 0, 0) == RMS$_NORMAL; } #else /* !VMS */ #ifdef MSDOS /* MW, May 1993 */ static int valid_filename_p (fn) char *fn; { return *fn == '/' || fn[1] == ':'; } #else #define valid_filename_p(fn) (*(fn) == '/') #endif #endif /* !VMS */ /* Find the termcap entry data for terminal type NAME and store it in the block that BP points to. Record its address for future use. If BP is null, space is dynamically allocated. Return -1 if there is some difficulty accessing the data base of terminal types, 0 if the data base is accessible but the type NAME is not defined in it, and some other value otherwise. */ int tgetent (bp, name) char *bp, *name; { register char *termcap_name; register int fd; struct termcap_buffer buf; register char *bp1; char *tc_search_point; char *term; int malloc_size = 0; register int c; char *tcenv = NULL; /* TERMCAP value, if it contains :tc=. */ char *indirect = NULL; /* Terminal type in :tc= in TERMCAP value. */ int filep; #ifdef INTERNAL_TERMINAL /* For the internal terminal we don't want to read any termcap file, so fake it. */ if (!strcmp (name, "internal")) { term = INTERNAL_TERMINAL; if (!bp) { malloc_size = 1 + strlen (term); bp = (char *) xmalloc (malloc_size); } strcpy (bp, term); goto ret; } #endif /* INTERNAL_TERMINAL */ /* For compatibility with programs like `less' that want to put data in the termcap buffer themselves as a fallback. */ if (bp) term_entry = bp; termcap_name = getenv ("TERMCAP"); if (termcap_name && *termcap_name == '\0') termcap_name = NULL; #if defined (MSDOS) && !defined (TEST) if (termcap_name && (*termcap_name == '\\' || *termcap_name == '/' || termcap_name[1] == ':')) dostounix_filename(termcap_name); #endif filep = termcap_name && valid_filename_p (termcap_name); /* If termcap_name is non-null and starts with / (in the un*x case, that is), it is a file name to use instead of /etc/termcap. If it is non-null and does not start with /, it is the entry itself, but only if the name the caller requested matches the TERM variable. */ if (termcap_name && !filep && !strcmp (name, getenv ("TERM"))) { indirect = tgetst1 (find_capability (termcap_name, "tc"), (char **) 0); if (!indirect) { if (!bp) bp = termcap_name; else strcpy (bp, termcap_name); goto ret; } else { /* It has tc=. Need to read /etc/termcap. */ tcenv = termcap_name; termcap_name = NULL; } } if (!termcap_name || !filep) termcap_name = TERMCAP_FILE; /* Here we know we must search a file and termcap_name has its name. */ #ifdef MSDOS fd = open (termcap_name, O_RDONLY|O_TEXT, 0); #else fd = open (termcap_name, O_RDONLY, 0); #endif if (fd < 0) return -1; buf.size = BUFSIZE; /* Add 1 to size to ensure room for terminating null. */ buf.beg = (char *) xmalloc (buf.size + 1); term = indirect ? indirect : name; if (!bp) { malloc_size = indirect ? strlen (tcenv) + 1 : buf.size; bp = (char *) xmalloc (malloc_size); } tc_search_point = bp1 = bp; if (indirect) /* Copy the data from the environment variable. */ { strcpy (bp, tcenv); bp1 += strlen (tcenv); } while (term) { /* Scan the file, reading it via buf, till find start of main entry. */ if (scan_file (term, fd, &buf) == 0) { close (fd); free (buf.beg); if (malloc_size) free (bp); return 0; } /* Free old `term' if appropriate. */ if (term != name) free (term); /* If BP is malloc'd by us, make sure it is big enough. */ if (malloc_size) { int offset1 = bp1 - bp, offset2 = tc_search_point - bp; malloc_size = offset1 + buf.size; bp = termcap_name = (char *) xrealloc (bp, malloc_size); bp1 = termcap_name + offset1; tc_search_point = termcap_name + offset2; } /* Copy the line of the entry from buf into bp. */ termcap_name = buf.ptr; while ((*bp1++ = c = *termcap_name++) && c != '\n') /* Drop out any \ newline sequence. */ if (c == '\\' && *termcap_name == '\n') { bp1--; termcap_name++; } *bp1 = '\0'; /* Does this entry refer to another terminal type's entry? If something is found, copy it into heap and null-terminate it. */ tc_search_point = find_capability (tc_search_point, "tc"); term = tgetst1 (tc_search_point, (char **) 0); } close (fd); free (buf.beg); if (malloc_size) bp = (char *) xrealloc (bp, bp1 - bp + 1); ret: term_entry = bp; return 1; } /* Given file open on FD and buffer BUFP, scan the file from the beginning until a line is found that starts the entry for terminal type STR. Return 1 if successful, with that line in BUFP, or 0 if no entry is found in the file. */ static int scan_file (str, fd, bufp) char *str; int fd; register struct termcap_buffer *bufp; { register char *end; bufp->ptr = bufp->beg; bufp->full = 0; bufp->ateof = 0; *bufp->ptr = '\0'; lseek (fd, 0L, 0); while (!bufp->ateof) { /* Read a line into the buffer. */ end = NULL; do { /* if it is continued, append another line to it, until a non-continued line ends. */ end = gobble_line (fd, bufp, end); } while (!bufp->ateof && end[-2] == '\\'); if (*bufp->ptr != '#' && name_match (bufp->ptr, str)) return 1; /* Discard the line just processed. */ bufp->ptr = end; } return 0; } /* Return nonzero if NAME is one of the names specified by termcap entry LINE. */ static int name_match (line, name) char *line, *name; { register char *tem; if (!compare_contin (line, name)) return 1; /* This line starts an entry. Is it the right one? */ for (tem = line; *tem && *tem != '\n' && *tem != ':'; tem++) if (*tem == '|' && !compare_contin (tem + 1, name)) return 1; return 0; } static int compare_contin (str1, str2) register char *str1, *str2; { register int c1, c2; while (1) { c1 = *str1++; c2 = *str2++; while (c1 == '\\' && *str1 == '\n') { str1++; while ((c1 = *str1++) == ' ' || c1 == '\t'); } if (c2 == '\0') { /* End of type being looked up. */ if (c1 == '|' || c1 == ':') /* If end of name in data base, we win. */ return 0; else return 1; } else if (c1 != c2) return 1; } } /* Make sure that the buffer <- BUFP contains a full line of the file open on FD, starting at the place BUFP->ptr points to. Can read more of the file, discard stuff before BUFP->ptr, or make the buffer bigger. Return the pointer to after the newline ending the line, or to the end of the file, if there is no newline to end it. Can also merge on continuation lines. If APPEND_END is non-null, it points past the newline of a line that is continued; we add another line onto it and regard the whole thing as one line. The caller decides when a line is continued. */ static char * gobble_line (fd, bufp, append_end) int fd; register struct termcap_buffer *bufp; char *append_end; { register char *end; register int nread; register char *buf = bufp->beg; register char *tem; if (!append_end) append_end = bufp->ptr; while (1) { end = append_end; while (*end && *end != '\n') end++; if (*end) break; if (bufp->ateof) return buf + bufp->full; if (bufp->ptr == buf) { if (bufp->full == bufp->size) { bufp->size *= 2; /* Add 1 to size to ensure room for terminating null. */ tem = (char *) xrealloc (buf, bufp->size + 1); bufp->ptr = (bufp->ptr - buf) + tem; append_end = (append_end - buf) + tem; bufp->beg = buf = tem; } } else { append_end -= bufp->ptr - buf; bcopy (bufp->ptr, buf, bufp->full -= bufp->ptr - buf); bufp->ptr = buf; } if (!(nread = read (fd, buf + bufp->full, bufp->size - bufp->full))) bufp->ateof = 1; bufp->full += nread; buf[bufp->full] = '\0'; } return end + 1; } #ifdef TEST #ifdef NULL #undef NULL #endif #include main (argc, argv) int argc; char **argv; { char *term; char *buf; term = argv[1]; printf ("TERM: %s\n", term); buf = (char *) tgetent (0, term); if ((int) buf <= 0) { printf ("No entry.\n"); return 0; } printf ("Entry: %s\n", buf); tprint ("cm"); tprint ("AL"); printf ("co: %d\n", tgetnum ("co")); printf ("am: %d\n", tgetflag ("am")); } tprint (cap) char *cap; { char *x = tgetstr (cap, 0); register char *y; printf ("%s: ", cap); if (x) { for (y = x; *y; y++) if (*y <= ' ' || *y == 0177) printf ("\\%0o", *y); else putchar (*y); free (x); } else printf ("none"); putchar ('\n'); } #endif /* TEST */ ne-3.3.4/src/termcap.h000066400000000000000000000031501475116431000145170ustar00rootroot00000000000000/* Declarations for termcap library. Copyright (C) 1991, 1992, 1995 Free Software Foundation, Inc. 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 2, 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, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #ifndef _TERMCAP_H #define _TERMCAP_H 1 #if __STDC__ extern int tgetent (char *buffer, const char *termtype); extern int tgetnum (const char *name); extern int tgetflag (const char *name); extern char *tgetstr (const char *name, char **area); extern char PC; extern short ospeed; extern void tputs (const char *string, int nlines, int (*outfun) (int)); extern char *tparam (const char *ctlstring, char *buffer, int size, ...); extern char *UP; extern char *BC; extern char *tgoto (const char *cstring, int hpos, int vpos); #else /* not __STDC__ */ extern int tgetent (); extern int tgetnum (); extern int tgetflag (); extern char *tgetstr (); extern char PC; extern short ospeed; extern void tputs (); extern char *tparam (); extern char *UP; extern char *BC; extern char *tgoto (); #endif /* not __STDC__ */ #endif /* not _TERMCAP_H */ ne-3.3.4/src/termchar.h000066400000000000000000000157031475116431000147000ustar00rootroot00000000000000/* extern's of flags describing terminal's characteristics. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2025 Todd M. Lewis and Sebastiano Vigna This file is part of ne, the nice editor. This library 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 library 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 . */ /** #define's from Joe's sources for colors and attributes. Please keep in sync. */ #include #define CONTEXT_COMMENT 1 #define CONTEXT_STRING 2 #define CONTEXT_MASK (CONTEXT_COMMENT+CONTEXT_STRING) #define ITALIC 128 #define INVERSE 256 #define UNDERLINE 512 #define BOLD 1024 #define BLINK 2048 #define DIM 4096 #define AT_MASK (INVERSE+UNDERLINE+BOLD+BLINK+DIM) #define BG_SHIFT 13 #define BG_VALUE (255< 0, then ne will emit the BPASTE_ENABLE_SEQ escape sequence whenever the terminal is set, and emit BPASTE_DISABLE_SEQ when relinquishing control of the terminal. */ #define BPASTE_DEFAULT 1 #define BPASTE_ENABLE_SEQ "\x1b[?2004h" #define BPASTE_DISABLE_SEQ "\x1b[?2004l" #define BPASTE_BEGIN_SEQ "\x1b[200~" #define BPASTE_END_SEQ "\x1b[201~" extern bool bracketed_paste; extern char *bpaste_macro_before; extern char *bpaste_macro_after; ne-3.3.4/src/test.rb000077500000000000000000000130401475116431000142210ustar00rootroot00000000000000#!/usr/bin/env ruby # test.rb creates a roughly N step random macro for ne to # run on a text file. Only an ne built with NE_TEST # defined will be able to run this macro; otherwise it will # stop on any command that can't succeed, like moving past # the end of the document. The macro makes arbitrary changes, # saves the document, undoes all the changes, saves again, # and finally redoes the changes and saves again, then exits. # You're supposed to compare the various saved files to see # if the undone file is byte-for-byte identical to the # original, and if the redone file is identical to the .test # version. If they are not, then ne is broken. # # By far the easiest way to use the program is to source # test.rc. # # Do not let a "NE_TEST" version of the ne binary out into # the wild; it does not do the Right Thing for production # use. if ARGV.length == 0 puts "Args: N FILE [BINARY]" exit end if ARGV.length > 2 then puts("BINARY"); end block = false clip = false a = IO.readlines(ARGV[1]) ops = 0 puts("OPEN \"" + ARGV[1] + "\"") puts("TURBO 10000") puts("AUTOMATCHBRACKET 1") puts("UTF8 " + rand(2).to_s); ARGV[0].to_i.times do |i| r = (rand * 100).to_i if r < 10 then # Move around case rand(29) when 0 puts("GOTOCOLUMN " + (rand(80)+1).to_s) when 1 puts("GOTOLINE " + (rand(a.length)+1).to_s) when 2 puts("LINEUP " + (rand(100)+1).to_s) when 3 puts("LINEDOWN " + (rand(100)+1).to_s) when 4 puts("MOVEBOS") when 5 puts("MOVEEOF") when 6 puts("MOVEEOL") when 7 puts("MOVEEOW " + "<>"[rand(2),1]) when 8 puts("MOVEINCDOWN") when 9 puts("MOVEINCUP") when 10 puts("MOVELEFT " + (rand(80)).to_s) when 11 puts("MOVERIGHT " + (rand(80)).to_s) when 12 puts("MOVESOF") when 13 puts("MOVESOL") when 14 puts("MOVETOS") when 15 puts("NEXTPAGE") when 16 puts("NEXTWORD " + (rand(20)).to_s + "<>"[rand(2),1]) when 17 puts("PAGEDOWN") when 18 puts("PAGEUP") when 19 puts("PREVPAGE") when 20 puts("PREVWORD " + (rand(20)).to_s + "<>"[rand(2),1]) when 21 puts("SETBOOKMARK") when 22 puts("GOTOBOOKMARK") when 23 puts("GOTOBOOKMARK -") when 24 puts("GOTOBOOKMARK <") when 25 puts("GOTOBOOKMARK >") when 26 puts("UNSETBOOKMARK") when 27 puts("ADJUSTVIEW " + "TMB"[rand(3),1] + "LCR"[rand(3),1]) when 28 puts("TOGGLESEOL") when 29 puts("TOGGLESEOF") when 30 # This is actually intended to start a bracket matching puts("FINDREGEXP \\(|\\)|\\[|\\]|{|}|<|>") end elsif r < 20 then # Changing flags case rand(9) when 0 puts("FREEFORM") when 1 puts("INSERT") when 2 puts("AUTOINDENT") when 3 puts("RIGHTMARGIN " + (rand(80) + 1).to_s) when 4 puts("WORDWRAP") when 5 puts("TABS") when 6 puts("SHIFTTABS") when 7 puts("TABSIZE " + (rand(20) + 1).to_s) when 8 puts("DELTABS") end elsif r < 30 # Deleting text ops += 1 case rand(6) when 0 puts("BACKSPACE " + (rand(20)).to_s) when 1 puts("DELETECHAR " + (rand(20)).to_s) when 2 puts("DELETEEOL") when 3 puts("DELETELINE " + (rand(10)).to_s) when 4 puts("DELETENEXTWORD " + (rand(10)).to_s) when 5 puts("DELETEPREVWORD " + (rand(10)).to_s) end elsif r < 40 if block case rand(4) when 0 puts("COPY") clip = true when 1 puts("CUT") clip = true when 2 puts("ERASE") when 3 puts("THROUGH sort") end block = false else case rand(4) when 0 if clip then puts("PASTE") clip = false else puts("MARK") block = true end when 1 if clip then puts("PASTEVERT") clip = false else puts("MARKVERT") block = true end end end elsif r < 50 # Editing case rand(14) when 0 puts("CAPITALIZE " + (rand(10)).to_s) when 1 puts("CENTER " + (rand(10)).to_s) when 2 begin s = a[rand(a.length)].chomp end while s.length == 0 start = rand(s.length/2); puts("FIND \"" + s[start..start+rand(s.length/2)] + "\"") begin s = a[rand(a.length)].chomp end while s.length == 0 start = rand(s.length/2); puts((rand(2)==0?"REPLACEALL":"REPLACEONCE") + " \"" + s[start..start+rand(s.length/2)] + "\"") when 3 case rand(3) when 0 puts("FINDREGEXP [A-Z][a-z]+") when 1 puts("FINDREGEXP [a-z]+"); when 2 puts("FINDREGEXP [0-9]+"); end begin s = a[rand(a.length)].chomp end while s.length == 0 start = rand(s.length/2); puts((rand(2)==0?"REPLACEALL":"REPLACEONCE") + " \"" + s[start..start+rand(s.length/2)] + "\"") when 4 puts("PARAGRAPH " + (rand(20)).to_s) when 5 t = rand(Math::sqrt(ops).to_i + 1) puts("UNDO " + t.to_s) puts("REDO " + t.to_s) when 6 puts("TOLOWER " + rand(10).to_s) when 7 puts("TOUPPER " + rand(10).to_s) when 8 puts("UNDELLINE " + (rand(10)).to_s) when 9 puts("AUTOCOMPLETE") when 10 puts("SHIFT " + (rand(2)==0 ? "<" : ">") + rand(20).to_s + (rand(2)==0?"t":"s")) when 11 puts("REPEATLAST") when 12 puts(rand(2)==0 ? "SYNTAX *" : "SYNTAX " + ARGV[1][/\.[a-z0-9]+$/][1..-1]) when 13 puts("NAMECONVERT") end elsif r < 60 # Atomicity puts("ATOMICUNDO") else # Generate text ops += 1 case rand(4) when 0 s = a[rand(a.length)].chomp puts("INSERTCHAR " + (rand(126) + 1).to_s) when 1 puts("INSERTLINE") when 2 begin s = a[rand(a.length)].chomp end while s.length == 0 puts("INSERTSTRING \"" + s + "\"") when 3 puts("INSERTTAB") end end end puts("SAVEAS \"" + ARGV[1] + ".test\"") puts("UNDO 10000000"); puts("SAVEAS \"" + ARGV[1] + ".undone\"") puts("REDO 10000000") puts("SAVEAS \"" + ARGV[1] + ".redone\"") puts("EXIT"); ne-3.3.4/src/test.rc000077500000000000000000000023031475116431000142220ustar00rootroot00000000000000#!/bin/bash # You can set the variables file and opts to change the default # filename, or introduce ne options (e.g., --no-syntax). # Run this file only if you have built ne with NE_TEST=1 if ! grep -q ne_test_dump_default_config ./ne ; then echo "Warning: ./ne apparently not built with NE_TEST=1 NE_DEBUG=1." echo "Enter to continue or ^C to stop." read fi function check_differences { cmp $1 $1.undone if [ $? -eq 0 ] then echo $1 and $1.undone match. GOOD. rm $1.undone else echo $1 and $1.undone differ. BAD. fi cmp $1.{test,redone} if [ $? -eq 0 ] then echo $1.{test,redone} match. GOOD. rm $1.{test,redone} else echo $1.{test,redone} differ. BAD. fi } file=${file:-buffer.c} export EF_ALLOW_MALLOC_0=1 ./test.rb 2000 $file > test.macro time ./ne $opts --macro test.macro 2>test.err check_differences $file | tee test.result # Create another macro from the same text file, but # this one will run in binary mode. Mung the extension # so that autoprefs don't override the Binary flag. cp $file $file.bin$$ file=$file.bin$$ ./test.rb 2000 $file BINARY > test.binary.macro time ./ne $opts --macro test.binary.macro 2>test.binary.err check_differences $file | tee -a test.result ne-3.3.4/src/tparam.c000066400000000000000000000164211475116431000143500ustar00rootroot00000000000000/* Merge parameters into a termcap entry string. Copyright (C) 1985, 87, 93, 95, 2000 Free Software Foundation, Inc. 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 2, 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; see the file COPYING. If not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Emacs config.h may rename various library functions such as malloc. */ #include #include #include /* Do this after the include, in case string.h prototypes bcopy. */ #if !defined(bcopy) #define bcopy(s, d, n) memcpy ((d), (s), (n)) #endif #ifndef NULL #define NULL (char *) 0 #endif #ifndef emacs static void memory_out () { write (2, "virtual memory exhausted\n", 25); exit (1); } static char * xmalloc (size) unsigned size; { register char *tem = malloc (size); if (!tem) memory_out (); return tem; } static char * xrealloc (ptr, size) char *ptr; unsigned size; { register char *tem = realloc (ptr, size); if (!tem) memory_out (); return tem; } #endif /* not emacs */ /* Assuming STRING is the value of a termcap string entry containing `%' constructs to expand parameters, merge in parameter values and store result in block OUTSTRING points to. LEN is the length of OUTSTRING. If more space is needed, a block is allocated with `malloc'. The value returned is the address of the resulting string. This may be OUTSTRING or may be the address of a block got with `malloc'. In the latter case, the caller must free the block. The fourth and following args to tparam serve as the parameter values. */ static char *tparam1 (); /* VARARGS 2 */ char * tparam (string, outstring, len, arg0, arg1, arg2, arg3) char *string; char *outstring; int len; int arg0, arg1, arg2, arg3; { int arg[4]; arg[0] = arg0; arg[1] = arg1; arg[2] = arg2; arg[3] = arg3; return tparam1 (string, outstring, len, NULL, NULL, arg); } char *BC; char *UP; static char tgoto_buf[50]; char * tgoto (cm, hpos, vpos) char *cm; int hpos, vpos; { int args[2]; if (!cm) return NULL; args[0] = vpos; args[1] = hpos; return tparam1 (cm, tgoto_buf, 50, UP, BC, args); } static char * tparam1 (string, outstring, len, up, left, argp) char *string; char *outstring; int len; char *up, *left; register int *argp; { register int c; register char *p = string; register char *op = outstring; char *outend; int outlen = 0; register int tem; int *old_argp = argp; int doleft = 0; int doup = 0; outend = outstring + len; while (1) { /* If the buffer might be too short, make it bigger. */ if (op + 5 >= outend) { register char *new; int offset = op - outstring; if (outlen == 0) { outlen = len + 40; new = (char *) xmalloc (outlen); bcopy (outstring, new, offset); } else { outlen *= 2; new = (char *) xrealloc (outstring, outlen); } op = new + offset; outend = new + outlen; outstring = new; } c = *p++; if (!c) break; if (c == '%') { c = *p++; tem = *argp; switch (c) { case 'd': /* %d means output in decimal. */ if (tem < 10) goto onedigit; if (tem < 100) goto twodigit; case '3': /* %3 means output in decimal, 3 digits. */ if (tem > 999) { *op++ = tem / 1000 + '0'; tem %= 1000; } *op++ = tem / 100 + '0'; case '2': /* %2 means output in decimal, 2 digits. */ twodigit: tem %= 100; *op++ = tem / 10 + '0'; onedigit: *op++ = tem % 10 + '0'; argp++; break; case 'C': /* For c-100: print quotient of value by 96, if nonzero, then do like %+. */ if (tem >= 96) { *op++ = tem / 96; tem %= 96; } case '+': /* %+x means add character code of char x. */ tem += *p++; case '.': /* %. means output as character. */ if (left) { /* If want to forbid output of 0 and \n and \t, and this is one of them, increment it. */ while (tem == 0 || tem == '\n' || tem == '\t') { tem++; if (argp == old_argp) doup++, outend -= strlen (up); else doleft++, outend -= strlen (left); } } *op++ = tem ? tem : 0200; case 'f': /* %f means discard next arg. */ argp++; break; case 'b': /* %b means back up one arg (and re-use it). */ argp--; break; case 'r': /* %r means interchange following two args. */ argp[0] = argp[1]; argp[1] = tem; old_argp++; break; case '>': /* %>xy means if arg is > char code of x, */ if (argp[0] > *p++) /* then add char code of y to the arg, */ argp[0] += *p; /* and in any case don't output. */ p++; /* Leave the arg to be output later. */ break; case 'a': /* %a means arithmetic. */ /* Next character says what operation. Add or subtract either a constant or some other arg. */ /* First following character is + to add or - to subtract or = to assign. */ /* Next following char is 'p' and an arg spec (0100 plus position of that arg relative to this one) or 'c' and a constant stored in a character. */ tem = p[2] & 0177; if (p[1] == 'p') tem = argp[tem - 0100]; if (p[0] == '-') argp[0] -= tem; else if (p[0] == '+') argp[0] += tem; else if (p[0] == '*') argp[0] *= tem; else if (p[0] == '/') argp[0] /= tem; else argp[0] = tem; p += 3; break; case 'i': /* %i means add one to arg, */ argp[0] ++; /* and leave it to be output later. */ argp[1] ++; /* Increment the following arg, too! */ break; case '%': /* %% means output %; no arg. */ goto ordinary; case 'n': /* %n means xor each of next two args with 140. */ argp[0] ^= 0140; argp[1] ^= 0140; break; case 'm': /* %m means xor each of next two args with 177. */ argp[0] ^= 0177; argp[1] ^= 0177; break; case 'B': /* %B means express arg as BCD char code. */ argp[0] += 6 * (tem / 10); break; case 'D': /* %D means weird Delta Data transformation. */ argp[0] -= 2 * (tem % 16); break; default: abort (); } } else /* Ordinary character in the argument string. */ ordinary: *op++ = c; } *op = 0; while (doup-- > 0) strcat (op, up); while (doleft-- > 0) strcat (op, left); return outstring; } #ifdef DEBUG main (argc, argv) int argc; char **argv; { char buf[50]; int args[3]; args[0] = atoi (argv[2]); args[1] = atoi (argv[3]); args[2] = atoi (argv[4]); tparam1 (argv[1], buf, "LEFT", "UP", args); printf ("%s\n", buf); return 0; } #endif /* DEBUG */ ne-3.3.4/src/trace.c000066400000000000000000000016241475116431000141610ustar00rootroot00000000000000#include "ne.h" static FILE *fp_trace; void __attribute__ ((constructor)) trace_begin (void) { fp_trace = fopen("trace.out", "w"); setvbuf(fp_trace, NULL, _IONBF, 0); } void __attribute__ ((destructor)) trace_end (void) { fclose(fp_trace); } void __cyg_profile_func_enter (void *func, void *caller) { if(func == add_tail || func == rem || func == detect_encoding || func == next_pos || func == utf8char || func == output_width || func == get_char_width || func == calc_pos || func == strnlen_ne) return; fprintf(fp_trace, "e %p %p %lu\n", func, caller, time(NULL)); } void __cyg_profile_func_exit (void *func, void *caller) { if(func == add_tail || func == rem || func == detect_encoding || func == next_pos || func == utf8char || func == output_width || func == get_char_width || func == calc_pos || func == strnlen_ne) return; fprintf(fp_trace, "x %p %p %lu\n", func, caller, time(NULL)); } ne-3.3.4/src/undo.c000066400000000000000000000205451475116431000140330ustar00rootroot00000000000000/* Undo/redo system management functions. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2025 Todd M. Lewis and Sebastiano Vigna This file is part of ne, the nice editor. This library 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 library 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 . */ #include "ne.h" #include "support.h" /* How many undo steps we (re)allocate whenever we need more. */ #define STD_UNDO_STEP_SIZE (1024) /* How many undo stream bytes we (re)allocate whenever we need more. */ #define STD_UNDO_STREAM_SIZE (16*1024) /* This is the main function for recording an undo step (though it should be called through add_undo_step). It adds to the given undo buffer an undo step with given line, position and length, possibly enlarging the undo step buffer. The redo stream is reset. Note that this function is transparent with respect to the various positive/negative conventions about len and pos. */ static int cat_undo_step(undo_buffer * const ub, const int64_t line, const int64_t pos, const int64_t len) { if (!ub) return false; assert_undo_buffer(ub); if (ub->cur_step >= ub->steps_size) { undo_step * const ud = realloc(ub->steps, (ub->steps_size + STD_UNDO_STEP_SIZE) * sizeof(undo_step)); if (ud) { ub->steps_size += STD_UNDO_STEP_SIZE; ub->steps = ud; } else return OUT_OF_MEMORY; } ub->steps[ub->cur_step].line = line; ub->steps[ub->cur_step].pos = pos; ub->steps[ub->cur_step].len = len; if (ub->last_save_step > ub->cur_step) ub->last_save_step = -1; ub->last_step = ++ub->cur_step; ub->last_stream = ub->cur_stream; reset_stream(&ub->redo); return 0; } /* Activates the chaining feature of the undo system. Any operations recorded between start_undo_chain() and end_undo_chain() will be undone or redone as a single entity. These calls can be nested, since a nesting index keeps track of multiple calls. */ void start_undo_chain(buffer * const b) { #ifdef NE_TEST D(fprintf(stderr, "# start_undo_chain: %d -> %d\n", b->link_undos, b->link_undos + 1);) D(fprintf(stderr, "# undo.cur_step: %d; undo.last_step: %d\n", b->undo.cur_step, b->undo.last_step);) #endif assert_buffer(b); assert(b->undo.cur_step == 0 || b->link_undos || b->undo.steps[b->undo.cur_step - 1].pos >= 0); b->link_undos++; } /* See the comments to the previous function. */ void end_undo_chain(buffer * const b) { #ifdef NE_TEST D(fprintf(stderr, "# end_undo_chain: %d -> %d\n", b->link_undos, b->link_undos - 1);) D(fprintf(stderr, "# undo.cur_step: %d; undo.last_step: %d\n", b->undo.cur_step, b->undo.last_step);) #endif assert_undo_buffer(&b->undo); if (--b->link_undos) return; if (b->undo.cur_step && b->undo.steps[b->undo.cur_step - 1].pos < 0) b->undo.steps[b->undo.cur_step - 1].pos = -(1 + b->undo.steps[b->undo.cur_step - 1].pos); } /* This function is the external interface to the undo recording system. It takes care of recording a position of -pos-1 if the undo linking feature is in use. A positive len records an insertion, a negative len records a deletion. When an insertion is recorded, len characters have to be added to the undo stream with add_to_undo_stream(). */ int add_undo_step(buffer * const b, const int64_t line, const int64_t pos, const int64_t len) { return cat_undo_step(&b->undo, line, b->link_undos ? -pos - 1 : pos, len); } /* Fixes the last undo step adding the given delta to its length. This function is needed by delete_stream(), as it is not possible to know the exact length of a deletion until it is performed. */ void fix_last_undo_step(buffer * const b, const int64_t delta) { b->undo.steps[b->undo.cur_step - 1].len += delta; } /* Adds to the undo stream a block of len characters pointed to by p. */ int add_to_undo_stream(undo_buffer * const ub, const char * const p, const int64_t len) { assert(len > 0); assert(ub != NULL); assert(ub->cur_step && ub->steps[ub->cur_step - 1].len > 0); if (!ub) return -1; assert_undo_buffer(ub); if (!ub->cur_step || ub->steps[ub->cur_step - 1].len < 0) return -1; if (ub->cur_stream + len >= ub->streams_size) { char *new_stream; if (new_stream = realloc(ub->streams, (ub->cur_stream + len + STD_UNDO_STREAM_SIZE))) { ub->streams_size = ub->cur_stream + len + STD_UNDO_STREAM_SIZE; ub->streams = new_stream; } else return OUT_OF_MEMORY; } memcpy(&ub->streams[ub->cur_stream], p, len); ub->last_stream = (ub->cur_stream += len); return 0; } /* Resets the undo buffer. All the previous undo steps are lost. */ void reset_undo_buffer(undo_buffer * const ub) { ub->cur_step = ub->last_step = ub->cur_stream = ub->last_stream = ub->steps_size = ub->streams_size = 0; ub->last_save_step = 0; free(ub->streams); free(ub->steps); ub->streams = NULL; ub->steps = NULL; reset_stream(&ub->redo); } /* Undoes the current undo step, which is the last one, if no undo has still be done, or an intermediate one, if some undo has already been done. */ int undo(buffer * const b) { if (!b) return -1; assert_buffer(b); if (b->undo.cur_step == 0) return NOTHING_TO_UNDO; /* WARNING: insert_stream() and delete_stream() do different things while undoing or redoing. */ b->undoing = 1; #ifdef NE_TEST D(fprintf(stderr, "# undo(): undo.cur_step: %d; undo.last_step: %d\n", b->undo.cur_step, b->undo.last_step);) #endif do { b->undo.cur_step--; if (b->undo.steps[b->undo.cur_step].len) { goto_line_pos(b, b->undo.steps[b->undo.cur_step].line, b->undo.steps[b->undo.cur_step].pos >= 0 ? b->undo.steps[b->undo.cur_step].pos : -(1 + b->undo.steps[b->undo.cur_step].pos)); if (b->undo.steps[b->undo.cur_step].len < 0) { delete_stream(b, b->cur_line_desc, b->cur_line, b->cur_pos, -b->undo.steps[b->undo.cur_step].len); update_syntax_states_delay(b, b->cur_line_desc, NULL); } else { line_desc *end_ld = (line_desc *)b->cur_line_desc->ld_node.next; insert_stream(b, b->cur_line_desc, b->cur_line, b->cur_pos, b->undo.streams + (b->undo.cur_stream -= b->undo.steps[b->undo.cur_step].len), b->undo.steps[b->undo.cur_step].len); update_syntax_states_delay(b, b->cur_line_desc, end_ld); } } #ifdef NE_TEST D(fprintf(stderr, "# undo(): undo.cur_step: %d; undo.last_step: %d\n", b->undo.cur_step, b->undo.last_step);) #endif } while(b->undo.cur_step && b->undo.steps[b->undo.cur_step - 1].pos < 0); b->undoing = 0; return 0; } /* Redoes the last step undone. */ int redo(buffer * const b) { if (!b) return -1; assert_buffer(b); if (b->undo.cur_step == b->undo.last_step) return NOTHING_TO_REDO; /* Important! insert_stream() and delete_stream() do different things while undoing or redoing. */ b->redoing = 1; #ifdef NE_TEST D(fprintf(stderr, "# redo(): undo.cur_step: %d; undo.last_step: %d\n", b->undo.cur_step, b->undo.last_step);) #endif do { if (b->undo.steps[b->undo.cur_step].len) { goto_line_pos(b, b->undo.steps[b->undo.cur_step].line, b->undo.steps[b->undo.cur_step].pos >= 0 ? b->undo.steps[b->undo.cur_step].pos : -(1 + b->undo.steps[b->undo.cur_step].pos)); if (b->undo.steps[b->undo.cur_step].len < 0) { line_desc *end_ld = (line_desc *)b->cur_line_desc->ld_node.next; insert_stream(b, b->cur_line_desc, b->cur_line, b->cur_pos, b->undo.redo.stream + (b->undo.redo.len += b->undo.steps[b->undo.cur_step].len), -b->undo.steps[b->undo.cur_step].len); update_syntax_states_delay(b, b->cur_line_desc, end_ld); } else { delete_stream(b, b->cur_line_desc, b->cur_line, b->cur_pos, b->undo.steps[b->undo.cur_step].len); b->undo.cur_stream += b->undo.steps[b->undo.cur_step].len; update_syntax_states_delay(b, b->cur_line_desc, NULL); } } b->undo.cur_step++; #ifdef NE_TEST D(fprintf(stderr, "# redo(): undo.cur_step: %d; undo.last_step: %d\n", b->undo.cur_step, b->undo.last_step);) #endif } while(b->undo.cur_step < b->undo.last_step && b->undo.steps[b->undo.cur_step - 1].pos < 0); b->redoing = 0; return 0; } ne-3.3.4/src/utf8.c000066400000000000000000000071651475116431000137570ustar00rootroot00000000000000/* UTF-8 support. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2025 Todd M. Lewis and Sebastiano Vigna This file is part of ne, the nice editor. This library 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 library 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 . */ #include #include #include "utf8.h" /* Computes the character length of an UTF-8 encoded sequence of bytes. */ int64_t utf8strlen(const char * const s, const int64_t len) { int64_t i = 0, l = 0; while(i < len) { assert(utf8len(s[i]) >= 0); i += utf8len(s[i]); l++; } return l; } /* Returns the length of a bytes sequence encoding the given character. */ int utf8seqlen(const int c) { assert(c >= 0); if (c < 0x80) return 1; if (c < 0x800) return 2; if (c < 0x10000) return 3; if (c < 0x200000) return 4; if (c < 0x4000000) return 5; return 6; } /* Return the Unicode characters represented by the given string, or -1 if an error occurs. */ int utf8char(const char * const ss) { const unsigned char * const s = (const unsigned char *)ss; if (s[0] < 0x80) return s[0]; if (s[0] < 0xC0) return -1; if (s[0] < 0xE0) return (s[0] & 0x1F) << 6 | s[1] & 0x3F; if (s[0] < 0xF0) return (s[0] & 0xF) << 12 | (s[1] & 0x3F) << 6 | (s[2] & 0x3F); if (s[0] < 0xF8) return (s[0] & 0x7) << 18 | (s[1] & 0x3F) << 12 | (s[2] & 0x3F) << 6 | (s[3] & 0x3F); if (s[0] < 0xFC) return (s[0] & 0x3) << 24 | (s[1] & 0x3F) << 18 | (s[2] & 0x3F) << 12 | (s[3] & 0x3F) << 6 | (s[4] & 0x3F); return (s[0] & 0x1) << 30 | (s[1] & 0x3F) << 24 | (s[2] & 0x3F) << 18 | (s[3] & 0x3F) << 12 | (s[4] & 0x3F) << 6 | (s[5] & 0x3F); } /* Writes the UTF-8 encoding (at most 6 bytes) of the given character to the given string. Returns the length of the string written. */ int utf8str(const int c, char * const ss) { assert(c >= 0); unsigned char * const s = (unsigned char *)ss; if (c < 0x80) { s[0] = c; return 1; } if (c < 0x800) { s[0] = c >> 6 | 0xC0; s[1] = c & 0x3F | 0x80; return 2; } if (c < 0x10000) { s[0] = c >> 12 | 0xE0; s[1] = c >> 6 & 0x3F | 0x80; s[2] = c & 0x3F | 0x80; return 3; } if (c < 0x200000) { s[0] = c >> 18 | 0xF0; s[1] = c >> 12 & 0x3F | 0x80; s[2] = c >> 6 & 0x3F | 0x80; s[3] = c & 0x3F | 0x80; return 4; } if (c < 0x4000000) { s[0] = c >> 24 | 0xF8; s[1] = c >> 18 & 0x3F | 0x80; s[2] = c >> 12 & 0x3F | 0x80; s[3] = c >> 6 & 0x3F | 0x80; s[4] = c & 0x3F | 0x80; return 5; } s[0] = c >> 30 | 0xFC; s[1] = c >> 24 & 0x3F | 0x80; s[2] = c >> 18 & 0x3F | 0x80; s[3] = c >> 12 & 0x3F | 0x80; s[4] = c >> 6 & 0x3F | 0x80; s[5] = c & 0x3F | 0x80; return 6; } /* Upper cases an UTF-8 character. The only point of this function is that is has the same prototype as toupper(). */ int utf8toupper(const int c) { assert(c >= 0); #ifdef NOWCHAR return c < 0x80 ? toupper(c) : c; #else return towupper(c); #endif } /* Lower cases an UTF-8 character. The only point of this function is that is has the same prototype as tolower(). */ int utf8tolower(const int c) { assert(c >= 0); #ifdef NOWCHAR return c < 0x80 ? tolower(c) : c; #else return towlower(c); #endif } ne-3.3.4/src/utf8.h000066400000000000000000000030201475116431000137460ustar00rootroot00000000000000/* UTF-8 support prototypes. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2025 Todd M. Lewis and Sebastiano Vigna This file is part of ne, the nice editor. This library 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 library 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 . */ #include int utf8char(const char *s); int64_t utf8strlen(const char *s, int64_t len); int utf8seqlen(int c); int utf8str(int c, char * s); int utf8tolower(int c); int utf8toupper(int c); /* Computes the length of an UTF-8 sequence, given the first byte. If the byte is not a legal sequence start, this function returns -1. */ #define utf8len(c) \ (((unsigned char)(c)) < 0x80 ? 1 : \ ((unsigned char)(c)) < 0xC0 ? -1 : \ ((unsigned char)(c)) < 0xE0 ? 2 : \ ((unsigned char)(c)) < 0xF0 ? 3 : \ ((unsigned char)(c)) < 0xF8 ? 4 : \ ((unsigned char)(c)) < 0xFC ? 5 : 6) #ifdef NOWCHAR #define wcwidth(x) (1) #else #include #include #endif #define MAX_UTF_8 (0x7FFFFFFF) ne-3.3.4/src/version.texinfo000077700000000000000000000000001475116431000220352../doc/version.texinfoustar00rootroot00000000000000ne-3.3.4/syntax/000077500000000000000000000000001475116431000134535ustar00rootroot00000000000000ne-3.3.4/syntax/4gl.jsf000066400000000000000000000066001475116431000146470ustar00rootroot00000000000000# JOE syntax highlight file for Progress 4GL # by Gediminas http://proc.w3.lt # Version 1.04 # bold inverse blink dim underline italic # white cyan magenta blue yellow green red black # bg_white bg_cyan bg_magenta bg_blue bg_yellow bg_green bg_red bg_black =Idle =Comment green =Constant cyan =Type magenta # conditional operators, blocks =Condition bold # buffer repositioning =KeyDB bold green # create, delete record =CreateDel bold yellow # preprocessor include, definition, reference =Include yellow =Preproc yellow =PreRef :idle Idle * idle "/" slash "{" brace "&" prep buffer "a-zA-Z" ident buffer "'" string recolor=-1 "\"" string2 recolor=-1 "0-9" number recolor=-1 "?" question recolor=-1 # Comments - 2 levels of nesting allowed :slash Idle * idle noeat "*" comment recolor=-2 :comment Comment * comment "/" slash2 "*" maybe_end_comment :maybe_end_comment Comment * comment "/" idle "*" maybe_end_comment :slash2 Idle * comment noeat "*" comment2 recolor=-2 :comment2 Comment * comment2 "*" maybe_end_comment2 :maybe_end_comment2 Comment * comment2 "/" comment "*" maybe_end_comment2 # Preprocessor # Allow preprocessor name reference inside include file reference: # {include/trace {&FILE-NAME} {&LINE-NUMBER}} :brace Include * include noeat "&" scoped recolor=-2 "}" idle :include Include * include recolor=-2 "{" brace2 "}" idle :brace2 Include * include2 noeat "}" idle :include2 Include * include2 recolor=-2 "}" include :scoped PreRef * scoped "}" idle :prep Preproc * idle noeat istrings "&IF" predir "&THEN" predir "&ELSEIF" predir "&ELSE" predir "&ENDIF" predir "&SCOPED-DEFINE" predir "&SCOP" predir "&GLOBAL-DEFINE" predir "&GLOB" predir "&MESSAGE" predir "&UNDEFINE" predir "&UNDEF" predir done "-a-zA-Z0-9_" prep :predir Preproc * idle noeat # String constants, copied from pascal.jsf with " added :string Constant * string "\n" idle "'" maybe_end_string :maybe_end_string Constant * idle recolor=-1 noeat "'" string :string2 Constant * string2 "\n" idle "\"" maybe_end_string2 :maybe_end_string2 Constant * idle recolor=-1 noeat "\"" string2 # Numeric constant, same as pascal.jsf :number Constant * idle noeat "0-9" number "eE" epart "." dot :dot Constant * idle noeat "0-9" float :float Constant * idle noeat "eE" epart "0-9" float :epart Constant * idle noeat "0-9+\-" enum :enum Constant * idle noeat "0-9" enum :question Constant * idle noeat # Keywords # Available jumps: operator type kw kwdb credel func :ident Idle * idle noeat istrings "assign" cond "case" cond "cha" type "char" type "character" type "create" credel "dat" type "date" type "dec" type "decimal" type "defined" predir "delete" credel "do" cond "else" cond "end" cond "find" kwdb "for" kwdb "function" cond "get" kwdb "handle" type "if" cond "int" type "integer" type "leave" cond "log" type "logical" type "memptr" type "next" cond "otherwise" cond "param" cond "parameter" cond "procedure" cond "raw" type "rec" type "repeat" cond "reposition" kwdb "return" cond "rowid" type "run" cond "then" cond "when" cond "widget-handle" type done "-a-zA-Z0-9_" ident :cond Condition * idle noeat :kwdb KeyDB * idle noeat :type Type * idle noeat :credel CreateDel * idle noeat ne-3.3.4/syntax/ada.jsf000066400000000000000000000047761475116431000147220ustar00rootroot00000000000000# JOE syntax highlight file for ADA # Define colors =Idle =Comment green =Constant cyan =Escape bold cyan =Keyword bold =Operator bold :idle Idle * idle "-" maybe_comment "'" char recolor=-1 "\"" string recolor=-1 "0-9" first_digit recolor=-1 "." maybe_float "\"" string recolor=-1 "Bb" maybe_binary buffer "Oo" maybe_octal buffer "Xx" maybe_hex buffer "ac-np-wyzAC-NP-WYZ" ident buffer :maybe_comment Idle * idle noeat "-" comment recolor=-2 :comment Comment * comment "\n" idle # Character constant :char Idle * char1 :char1 Idle * idle "'" char2 recolor=-3 :char2 Constant * idle noeat # Strings :maybe_binary Idle * ident noeat "\"" string recolor=-2 :maybe_octal Idle * ident noeat "\"" string recolor=-2 :maybe_hex Idle * ident noeat "\"" string recolor=-2 :string Constant * string "\n" idle "\"" idle "\\" string_escape recolor=-1 :string_escape Escape * string "\n" string recolor=-2 # Integer constants :first_digit Constant * idle noeat "." float "_" first_digit "0-9" first_digit # Floating point :maybe_float Constant * idle recolor=-2 noeat "0-9" float recolor=-2 :float Constant * idle noeat "eE" epart "0-9" float :epart Constant * idle noeat "0-9+\-" enum :enum Constant * idle noeat "0-9" enum # Identifiers :ident Idle * quote noeat istrings "abort" kw "abs" operator "abstract" kw "accept" kw "aliased" kw "all" kw "and" operator "array" kw "at" kw "begin" kw "body" kw "case" kw "constant" kw "declare" kw "delay" kw "delta" kw "digits" kw "do" kw "else" kw "elsif" kw "end" kw "entry" kw "exception" kw "exit" kw "for" kw "function" kw "generic" kw "goto" kw "if" kw "in" kw "interface" kw "is" kw "limited" kw "loop" kw "mod" kw "new" kw "not" operator "null" kw "of" kw "or" operator "others" kw "out" kw "overriding" kw "package" kw "pragma" kw "private" kw "procedure" kw "protected" kw "raise" kw "range" kw "record" kw "rem" operator "renames" kw "requeue" kw "return" kw "reverse" kw "select" kw "separate" kw "subtype" kw "synchronized" kw "tagged" kw "task" kw "terminate" kw "then" kw "type" kw "until" kw "use" kw "when" kw "while" kw "with" kw "xor" operator done "a-zA-Z0-9_" ident :operator Operator * idle noeat :kw Keyword * idle noeat # identifiers separated with quote ' :quote Idle * idle noeat "'" quote_word :quote_word Idle * idle noeat "a-zA-Z" ident buffer ne-3.3.4/syntax/ant.jsf000066400000000000000000000122251475116431000147430ustar00rootroot00000000000000# JOE syntax highlight file for Ant's build scripts # by Christian Nicolai (http://mycrobase.de) # http://ant.apache.org/ # heavily based on the XML highlighter but with better colors since # ant scripts heavily rely on the tags (blue is a bad color here) # instead of their contents # Improved XML highlighter by: Brian Candler =Idle =Bad red bold =Tag bold =Attr =Constant cyan =Escape bold cyan =EntityRef magenta =Decl cyan =CommentStart green =CommentBody green =CommentEnd green =PIStart yellow bold =PIBody yellow =PIEnd yellow bold =CdataStart blue bold =CdataBody bold =CdataEnd blue bold =Keytag bold magenta #fg_310 # brown # http://www.w3.org/TR/2004/REC-xml-20040204/ # # NOTE: For UNICODE compatibility, the ranges # "A-Za-z_:" -- first character of Name # "A-Za-z0-9._:-" -- subsequent characters of Name # ought to be replaced with some appropriate Unicode character classes :content Idle * content "<" tag recolor=-1 "&" entityref recolor=-1 # > is allowed # ">" error noeat recolor=-1 # In several contexts a space is an error, and since a coloured space is # not visible, we colour the next non-space character as well. :error Bad * error_visible noeat recolor=-1 " \t\r\n" error :error_visible Bad * content # Matched: & :entityref EntityRef * error noeat recolor=-1 "A-Za-z0-9._:-" entityref "#" entityref ";" content # Matched: < :tag Tag * error noeat recolor=-1 "/" end_tag "A-Za-z_:" start_or_empty_tag buffer "?" pi_start recolor=-2 "!" decl recolor=-2 buffer # Matched: " content :end_tag2_sp Idle * end_tag3 noeat :end_tag3 Tag * error noeat recolor=-1 " \t\r\n" end_tag3_sp recolor=-1 ">" content :end_tag3_sp Idle * end_tag_3 noeat # Matched: " content :start_or_empty_tag_sp Idle * tag_space noeat # Matched: " content # Matched: " close_tag recolor=-1 :tag_space_sp Idle * tag_space noeat # Matched: " error noeat recolor=-1 :string_sq Constant * string_sq "<" error noeat recolor=-1 "&" char_sq recolor=-1 "'" endstring "$" string_sq call=.maybe_antvar() # ">" error noeat recolor=-1 :char_dq EntityRef * error noeat recolor=-1 "A-Za-z0-9._:-" char_dq "#" char_dq ";" string_dq :char_sq EntityRef * error noeat recolor=-1 "A-Za-z0-9._:-" char_sq "#" char_sq ";" string_sq .subr maybe_antvar # initial state :maybe_antvar Constant * NULL return "{" antvar recolor=-2 :antvar Escape * antvar "}" NULL return .end # Matched: " close_tag recolor=-1 # This state is just to recolor the final ">" at the end of :close_tag Tag * content noeat # Matched: " pi_end recolor=-2 :pi_end PIEnd * content noeat recolor=-1 # Matched: " content # We allow one level of <...> nesting within declarations :decl_nest Decl * decl_nest ">" decl # Matched: . # # * "#" and "+" are lited as mistakes in unquoted tag values, # although there exist (broken) programs that generate them. # # * Recognizes html-entities and lites mistakes in them. # # * SGML comments are parsed in the SGML way. This means there # must be an even amount of "--" markers within the tag. # # * Recognizes 1018 different named entities. The list has been # copied from some version of Lynx. Most browsers don't recognize # that many. # # Todo: # * tag name recognition # * tag parameter name recognition # * inline stylesheet and javascript highlighting # (hard to do fool-proof, because the value may be entity-encoded). # # Colours =Background =Text =TagEdge green =TagName green #cyan =TagParam #green =TagDelim #bold green =TagValue cyan =TagEntity bold blue =SGMLtag magenta =XML_pi yellow =Entity bold blue =Mystery bold yellow bg_red inverse # Call HTML highlighter subroutine :first Background * call_failed noeat call=.html() :call_failed Mystery * call_failed # # HTML highlighter as a subroutine # .subr html :reset Background * idle noeat .ifdef mason "%" reset call=perl.perl(mason_line) .endif # Rules :idle Background * idle "\n" reset "<" tag_begin recolor=-1 "&" idle call=.entity() recolor=-1 :mistake_idle Mystery * idle noeat # Tags :tag_begin TagEdge * tag_name_first buffer noeat recolor=-1 "/" tag_name_first buffer .ifdef mason "&" rtn_php call=perl.perl(mason_block) .endif "!" sgml_tag recolor=-2 .ifdef php "?%" rtn_php call=php.php() .else "?" xml_pi recolor=-2 .endif .ifdef erb "%" maybe_erb_eq .endif # this state allows php and perl to recolor the ?> %> or &> properly. :rtn_php TagEdge * idle noeat :maybe_erb_eq TagEdge * rtn_php noeat call=ruby.ruby(erb) "=" rtn_php call=ruby.ruby(erb) :tag_name_first Mystery * tag_idle noeat "-A-Za-z0-9._:" tag_name recolor=-1 :tag_name TagName * tag_idle noeat strings "script" stag_enter_idle "style" ytag_enter_idle done "-A-Za-z0-9._:" tag_name :tag_idle Background * mistake_tag recolor=-1 "<" tag_maybe_php recolor=-1 " \t \n" tag_idle "/" tag_end recolor=-1 ">" tag_end noeat recolor=-1 "-A-Za-z0-9._:" tag_param noeat recolor=-1 :tag_maybe_php Mystery * tag_idle "?" tag_call_php recolor=-2 :tag_call_php TagEdge * rtn_php_tag noeat call=php.php() :rtn_php_tag TagEdge * tag_idle noeat :tag_param TagParam * tag_idle noeat recolor=-1 "-A-Za-z0-9._:" tag_param "=" tag_delim recolor=-1 :tag_delim TagDelim * mistake_tag noeat recolor=-1 "\"" tag_idle call=.tag_value_quoted(dquote) recolor=-1 "'" tag_idle call=.tag_value_quoted(squote) recolor=-1 "-A-Za-z0-9._:" tag_value noeat recolor=-1 :tag_value TagValue * tag_idle noeat recolor=-1 "-A-Za-z0-9._:" tag_value :tag_end TagEdge * idle :mistake_tag Mystery * tag_idle noeat # We're about to entry a script... :stag_enter_idle TagName * stag_idle noeat :stag_idle Background * smistake_tag recolor=-1 " \t \n" stag_idle "/" stag_end recolor=-1 ">" stag_end noeat recolor=-1 "-A-Za-z0-9._:" stag_param noeat recolor=-1 :stag_param TagParam * stag_idle noeat recolor=-1 "-A-Za-z0-9._:" stag_param "=" stag_delim recolor=-1 :stag_delim TagDelim * smistake_tag noeat recolor=-1 "\"" stag_idle call=.tag_value_quoted(dquote) recolor=-1 "'" stag_idle call=.tag_value_quoted(squote) recolor=-1 "-A-Za-z0-9._:" stag_value noeat recolor=-1 :stag_value TagValue * stag_idle noeat recolor=-1 "-A-Za-z0-9._:" stag_value :stag_end TagEdge .ifdef php * tag_begin call=js.js(php) .else * tag_begin call=js.js(html) .endif :stag_done TagEdge * tag_name_first buffer noeat :smistake_tag Mystery * stag_idle noeat # We're about to entry a script... :ytag_enter_idle TagName * ytag_idle noeat :ytag_idle Background * ymistake_tag recolor=-1 " \t \n" ytag_idle "/" ytag_end recolor=-1 ">" ytag_end noeat recolor=-1 "-A-Za-z0-9._:" ytag_param noeat recolor=-1 :ytag_param TagParam * ytag_idle noeat recolor=-1 "-A-Za-z0-9._:" ytag_param "=" ytag_delim recolor=-1 :ytag_delim TagDelim * ymistake_tag noeat recolor=-1 "\"" ytag_idle call=.tag_value_quoted(dquote) recolor=-1 "'" ytag_idle call=.tag_value_quoted(squote) recolor=-1 "-A-Za-z0-9._:" ytag_value noeat recolor=-1 :ytag_value TagValue * ytag_idle noeat recolor=-1 "-A-Za-z0-9._:" ytag_value :ytag_end TagEdge .ifdef php * tag_begin call=css.css(php) .else * tag_begin call=css.css() .endif :ytag_done TagEdge * tag_name_first buffer noeat :ymistake_tag Mystery * ytag_idle noeat # SGML and comments :sgml_tag SGMLtag * sgml_tag "-" sgml_tag_maybe_comment ">" sgml_end noeat recolor=-1 :sgml_tag_maybe_comment SGMLtag * sgml_tag "-" sgml_tag_comment :sgml_tag_comment SGMLtag * sgml_tag_comment "-" sgml_tag_maybe_comment_end :sgml_tag_maybe_comment_end SGMLtag * sgml_tag_comment "-" sgml_tag :sgml_end SGMLtag * idle # XML processing info :xml_pi XML_pi * xml_pi "?" xml_pi_maybe_end :xml_pi_maybe_end XML_pi * xml_pi "?" xml_pi_maybe_end ">" xml_pi_end noeat recolor=-1 :xml_pi_end XML_pi * idle .end # # Quoted string tag values # .subr tag_value_quoted :tag_value_quoted TagValue * tag_value_quoted .ifdef dquote "\"" tag_value_quoted return .endif .ifdef squote "'" tag_value_quoted return .endif "<" tag_value_maybe_php :tag_value_maybe_php TagValue * tag_value_quoted noeat "?" tag_value_call_php recolor=-2 :tag_value_call_php TagEdge * tag_value_rtn_php noeat call=php.php() :tag_value_rtn_php TagEdge * tag_value_quoted noeat # Too many non-html things can be in quotes to do this... # "&" tag_value_quoted call=.entity() recolor=-1 :mistake_tag_value Mystery * tag_value_quoted noeat .end # # Entity parser # .subr entity # Entities within plain content :entity Entity * entity_name noeat buffer recolor=-1 "#" entity_numeric_begin :entity_numeric_begin Entity * entity_numeric noeat "x" entity_hex :entity_numeric Entity * mistake_idle noeat recolor=-1 "0-9" entity_numeric ";" entity_end noeat recolor=-1 :entity_hex Entity * mistake_idle noeat recolor=-1 "0-9a-fA-F" entity_hex ";" entity_end noeat recolor=-1 :entity_end Entity * entity return :entity_name Mystery * mistake_idle noeat recolor=-1 strings "AElig" entity_ok "Aacgr" entity_ok "Aacute" entity_ok "Abreve" entity_ok "Acirc" entity_ok "Acy" entity_ok "Agr" entity_ok "Agrave" entity_ok "Alpha" entity_ok "Amacr" entity_ok "Aogon" entity_ok "Aring" entity_ok "Atilde" entity_ok "Auml" entity_ok "Barwed" entity_ok "Bcy" entity_ok "Beta" entity_ok "Bgr" entity_ok "CHcy" entity_ok "Cacute" entity_ok "Cap" entity_ok "Ccaron" entity_ok "Ccedil" entity_ok "Ccirc" entity_ok "Cdot" entity_ok "Chi" entity_ok "Cup" entity_ok "DJcy" entity_ok "DScy" entity_ok "DZcy" entity_ok "Dagger" entity_ok "Dcaron" entity_ok "Dcy" entity_ok "Delta" entity_ok "Dgr" entity_ok "Dot" entity_ok "DotDot" entity_ok "Dstrok" entity_ok "EEacgr" entity_ok "EEgr" entity_ok "ENG" entity_ok "ETH" entity_ok "Eacgr" entity_ok "Eacute" entity_ok "Ecaron" entity_ok "Ecirc" entity_ok "Ecy" entity_ok "Edot" entity_ok "Egr" entity_ok "Egrave" entity_ok "Emacr" entity_ok "Eogon" entity_ok "Epsilon" entity_ok "Eta" entity_ok "Euml" entity_ok "Fcy" entity_ok "GJcy" entity_ok "Gamma" entity_ok "Gbreve" entity_ok "Gcedil" entity_ok "Gcirc" entity_ok "Gcy" entity_ok "Gdot" entity_ok "Gg" entity_ok "Ggr" entity_ok "Gt" entity_ok "HARDcy" entity_ok "Hcirc" entity_ok "Hstrok" entity_ok "IEcy" entity_ok "IJlig" entity_ok "IOcy" entity_ok "Iacgr" entity_ok "Iacute" entity_ok "Icirc" entity_ok "Icy" entity_ok "Idigr" entity_ok "Idot" entity_ok "Igr" entity_ok "Igrave" entity_ok "Imacr" entity_ok "Iogon" entity_ok "Iota" entity_ok "Itilde" entity_ok "Iukcy" entity_ok "Iuml" entity_ok "Jcirc" entity_ok "Jcy" entity_ok "Jsercy" entity_ok "Jukcy" entity_ok "KHcy" entity_ok "KHgr" entity_ok "KJcy" entity_ok "Kappa" entity_ok "Kcedil" entity_ok "Kcy" entity_ok "Kgr" entity_ok "LJcy" entity_ok "Lacute" entity_ok "Lambda" entity_ok "Larr" entity_ok "Lcaron" entity_ok "Lcedil" entity_ok "Lcy" entity_ok "Lgr" entity_ok "Ll" entity_ok "Lmidot" entity_ok "Lstrok" entity_ok "Lt" entity_ok "Mcy" entity_ok "Mgr" entity_ok "Mu" entity_ok "NJcy" entity_ok "Nacute" entity_ok "Ncaron" entity_ok "Ncedil" entity_ok "Ncy" entity_ok "Ngr" entity_ok "Ntilde" entity_ok "Nu" entity_ok "OElig" entity_ok "OHacgr" entity_ok "OHgr" entity_ok "Oacgr" entity_ok "Oacute" entity_ok "Ocirc" entity_ok "Ocy" entity_ok "Odblac" entity_ok "Ogr" entity_ok "Ograve" entity_ok "Omacr" entity_ok "Omega" entity_ok "Omicron" entity_ok "Oslash" entity_ok "Otilde" entity_ok "Ouml" entity_ok "PHgr" entity_ok "PSgr" entity_ok "Pcy" entity_ok "Pgr" entity_ok "Phi" entity_ok "Pi" entity_ok "Prime" entity_ok "Psi" entity_ok "Racute" entity_ok "Rarr" entity_ok "Rcaron" entity_ok "Rcedil" entity_ok "Rcy" entity_ok "Rgr" entity_ok "Rho" entity_ok "SHCHcy" entity_ok "SHcy" entity_ok "SOFTcy" entity_ok "Sacute" entity_ok "Scaron" entity_ok "Scedil" entity_ok "Scirc" entity_ok "Scy" entity_ok "Sgr" entity_ok "Sigma" entity_ok "Sub" entity_ok "Sup" entity_ok "THORN" entity_ok "THgr" entity_ok "TSHcy" entity_ok "TScy" entity_ok "Tau" entity_ok "Tcaron" entity_ok "Tcedil" entity_ok "Tcy" entity_ok "Tgr" entity_ok "Theta" entity_ok "Tstrok" entity_ok "Uacgr" entity_ok "Uacute" entity_ok "Ubrcy" entity_ok "Ubreve" entity_ok "Ucirc" entity_ok "Ucy" entity_ok "Udblac" entity_ok "Udigr" entity_ok "Ugr" entity_ok "Ugrave" entity_ok "Umacr" entity_ok "Uogon" entity_ok "Upsi" entity_ok "Upsilon" entity_ok "Uring" entity_ok "Utilde" entity_ok "Uuml" entity_ok "Vcy" entity_ok "Vdash" entity_ok "Verbar" entity_ok "Vvdash" entity_ok "Wcirc" entity_ok "Xgr" entity_ok "Xi" entity_ok "YAcy" entity_ok "YIcy" entity_ok "YUcy" entity_ok "Yacute" entity_ok "Ycirc" entity_ok "Ycy" entity_ok "Yuml" entity_ok "ZHcy" entity_ok "Zacute" entity_ok "Zcaron" entity_ok "Zcy" entity_ok "Zdot" entity_ok "Zeta" entity_ok "Zgr" entity_ok "aacgr" entity_ok "aacute" entity_ok "abreve" entity_ok "acirc" entity_ok "acute" entity_ok "acy" entity_ok "aelig" entity_ok "agr" entity_ok "agrave" entity_ok "alefsym" entity_ok "aleph" entity_ok "alpha" entity_ok "amacr" entity_ok "amalg" entity_ok "amp" entity_ok "and" entity_ok "ang" entity_ok "ang90" entity_ok "angmsd" entity_ok "angsph" entity_ok "angst" entity_ok "aogon" entity_ok "ap" entity_ok "ape" entity_ok "apos" entity_ok "aring" entity_ok "ast" entity_ok "asymp" entity_ok "atilde" entity_ok "auml" entity_ok "b.Delta" entity_ok "b.Gamma" entity_ok "b.Lambda" entity_ok "b.Omega" entity_ok "b.Phi" entity_ok "b.Pi" entity_ok "b.Psi" entity_ok "b.Sigma" entity_ok "b.Theta" entity_ok "b.Upsi" entity_ok "b.Xi" entity_ok "b.alpha" entity_ok "b.beta" entity_ok "b.chi" entity_ok "b.delta" entity_ok "b.epsi" entity_ok "b.epsis" entity_ok "b.epsiv" entity_ok "b.eta" entity_ok "b.gamma" entity_ok "b.gammad" entity_ok "b.iota" entity_ok "b.kappa" entity_ok "b.kappav" entity_ok "b.lambda" entity_ok "b.mu" entity_ok "b.nu" entity_ok "b.omega" entity_ok "b.phis" entity_ok "b.phiv" entity_ok "b.pi" entity_ok "b.piv" entity_ok "b.psi" entity_ok "b.rho" entity_ok "b.rhov" entity_ok "b.sigma" entity_ok "b.sigmav" entity_ok "b.tau" entity_ok "b.thetas" entity_ok "b.thetav" entity_ok "b.upsi" entity_ok "b.xi" entity_ok "b.zeta" entity_ok "barwed" entity_ok "bcong" entity_ok "bcy" entity_ok "bdquo" entity_ok "becaus" entity_ok "bepsi" entity_ok "bernou" entity_ok "beta" entity_ok "beth" entity_ok "bgr" entity_ok "blank" entity_ok "blk12" entity_ok "blk14" entity_ok "blk34" entity_ok "block" entity_ok "bottom" entity_ok "bowtie" entity_ok "boxDL" entity_ok "boxDR" entity_ok "boxDl" entity_ok "boxDr" entity_ok "boxH" entity_ok "boxHD" entity_ok "boxHU" entity_ok "boxHd" entity_ok "boxHu" entity_ok "boxUL" entity_ok "boxUR" entity_ok "boxUl" entity_ok "boxUr" entity_ok "boxV" entity_ok "boxVH" entity_ok "boxVL" entity_ok "boxVR" entity_ok "boxVh" entity_ok "boxVl" entity_ok "boxVr" entity_ok "boxdL" entity_ok "boxdR" entity_ok "boxdl" entity_ok "boxdr" entity_ok "boxh" entity_ok "boxhD" entity_ok "boxhU" entity_ok "boxhd" entity_ok "boxhu" entity_ok "boxuL" entity_ok "boxuR" entity_ok "boxul" entity_ok "boxur" entity_ok "boxv" entity_ok "boxvH" entity_ok "boxvL" entity_ok "boxvR" entity_ok "boxvh" entity_ok "boxvl" entity_ok "boxvr" entity_ok "bprime" entity_ok "breve" entity_ok "brkbar" entity_ok "brvbar" entity_ok "bsim" entity_ok "bsime" entity_ok "bsol" entity_ok "bull" entity_ok "bump" entity_ok "bumpe" entity_ok "cacute" entity_ok "cap" entity_ok "caret" entity_ok "caron" entity_ok "ccaron" entity_ok "ccedil" entity_ok "ccirc" entity_ok "cdot" entity_ok "cedil" entity_ok "cent" entity_ok "chcy" entity_ok "check" entity_ok "chi" entity_ok "cir" entity_ok "circ" entity_ok "cire" entity_ok "clubs" entity_ok "colon" entity_ok "colone" entity_ok "comma" entity_ok "commat" entity_ok "comp" entity_ok "compfn" entity_ok "cong" entity_ok "conint" entity_ok "coprod" entity_ok "copy" entity_ok "copysr" entity_ok "crarr" entity_ok "cross" entity_ok "cuepr" entity_ok "cuesc" entity_ok "cularr" entity_ok "cup" entity_ok "cupre" entity_ok "curarr" entity_ok "curren" entity_ok "cuvee" entity_ok "cuwed" entity_ok "dArr" entity_ok "dagger" entity_ok "daleth" entity_ok "darr" entity_ok "darr2" entity_ok "dash" entity_ok "dashv" entity_ok "dblac" entity_ok "dcaron" entity_ok "dcy" entity_ok "deg" entity_ok "delta" entity_ok "dgr" entity_ok "dharl" entity_ok "dharr" entity_ok "diam" entity_ok "diams" entity_ok "die" entity_ok "divide" entity_ok "divonx" entity_ok "djcy" entity_ok "dlarr" entity_ok "dlcorn" entity_ok "dlcrop" entity_ok "dollar" entity_ok "dot" entity_ok "drarr" entity_ok "drcorn" entity_ok "drcrop" entity_ok "dscy" entity_ok "dstrok" entity_ok "dtri" entity_ok "dtrif" entity_ok "dzcy" entity_ok "eDot" entity_ok "eacgr" entity_ok "eacute" entity_ok "ecaron" entity_ok "ecir" entity_ok "ecirc" entity_ok "ecolon" entity_ok "ecy" entity_ok "edot" entity_ok "eeacgr" entity_ok "eegr" entity_ok "efDot" entity_ok "egr" entity_ok "egrave" entity_ok "egs" entity_ok "ell" entity_ok "els" entity_ok "emacr" entity_ok "emdash" entity_ok "empty" entity_ok "emsp" entity_ok "emsp13" entity_ok "emsp14" entity_ok "endash" entity_ok "eng" entity_ok "ensp" entity_ok "eogon" entity_ok "epsi" entity_ok "epsilon" entity_ok "epsis" entity_ok "epsiv" entity_ok "equals" entity_ok "equiv" entity_ok "erDot" entity_ok "esdot" entity_ok "eta" entity_ok "eth" entity_ok "euml" entity_ok "euro" entity_ok "excl" entity_ok "exist" entity_ok "fcy" entity_ok "female" entity_ok "ffilig" entity_ok "fflig" entity_ok "ffllig" entity_ok "filig" entity_ok "fjlig" entity_ok "flat" entity_ok "fllig" entity_ok "fnof" entity_ok "forall" entity_ok "fork" entity_ok "frac12" entity_ok "frac13" entity_ok "frac14" entity_ok "frac15" entity_ok "frac16" entity_ok "frac18" entity_ok "frac23" entity_ok "frac25" entity_ok "frac34" entity_ok "frac35" entity_ok "frac38" entity_ok "frac45" entity_ok "frac56" entity_ok "frac58" entity_ok "frac78" entity_ok "frasl" entity_ok "frown" entity_ok "gE" entity_ok "gEl" entity_ok "gacute" entity_ok "gamma" entity_ok "gammad" entity_ok "gap" entity_ok "gbreve" entity_ok "gcedil" entity_ok "gcirc" entity_ok "gcy" entity_ok "gdot" entity_ok "ge" entity_ok "gel" entity_ok "ges" entity_ok "ggr" entity_ok "gimel" entity_ok "gjcy" entity_ok "gl" entity_ok "gnE" entity_ok "gnap" entity_ok "gne" entity_ok "gnsim" entity_ok "grave" entity_ok "gsdot" entity_ok "gsim" entity_ok "gt" entity_ok "gvnE" entity_ok "hArr" entity_ok "hairsp" entity_ok "half" entity_ok "hamilt" entity_ok "hardcy" entity_ok "harr" entity_ok "harrw" entity_ok "hcirc" entity_ok "hearts" entity_ok "hellip" entity_ok "hibar" entity_ok "horbar" entity_ok "hstrok" entity_ok "hybull" entity_ok "hyphen" entity_ok "iacgr" entity_ok "iacute" entity_ok "icirc" entity_ok "icy" entity_ok "idiagr" entity_ok "idigr" entity_ok "iecy" entity_ok "iexcl" entity_ok "iff" entity_ok "igr" entity_ok "igrave" entity_ok "ijlig" entity_ok "imacr" entity_ok "image" entity_ok "incare" entity_ok "infin" entity_ok "inodot" entity_ok "int" entity_ok "intcal" entity_ok "iocy" entity_ok "iogon" entity_ok "iota" entity_ok "iquest" entity_ok "isin" entity_ok "itilde" entity_ok "iukcy" entity_ok "iuml" entity_ok "jcirc" entity_ok "jcy" entity_ok "jnodot" entity_ok "jsercy" entity_ok "jukcy" entity_ok "kappa" entity_ok "kappav" entity_ok "kcedil" entity_ok "kcy" entity_ok "kgr" entity_ok "kgreen" entity_ok "khcy" entity_ok "khgr" entity_ok "kjcy" entity_ok "lAarr" entity_ok "lArr" entity_ok "lE" entity_ok "lEg" entity_ok "lacute" entity_ok "lagran" entity_ok "lambda" entity_ok "lang" entity_ok "lap" entity_ok "laquo" entity_ok "larr" entity_ok "larr2" entity_ok "larrhk" entity_ok "larrlp" entity_ok "larrtl" entity_ok "lcaron" entity_ok "lcedil" entity_ok "lceil" entity_ok "lcub" entity_ok "lcy" entity_ok "ldot" entity_ok "ldquo" entity_ok "ldquor" entity_ok "le" entity_ok "leg" entity_ok "les" entity_ok "lfloor" entity_ok "lg" entity_ok "lgr" entity_ok "lhard" entity_ok "lharu" entity_ok "lhblk" entity_ok "ljcy" entity_ok "lmidot" entity_ok "lnE" entity_ok "lnap" entity_ok "lne" entity_ok "lnsim" entity_ok "lowast" entity_ok "lowbar" entity_ok "loz" entity_ok "loz" entity_ok "lozf" entity_ok "lpar" entity_ok "lpargt" entity_ok "lrarr2" entity_ok "lrhar2" entity_ok "lrm" entity_ok "lsaquo" entity_ok "lsh" entity_ok "lsim" entity_ok "lsqb" entity_ok "lsquo" entity_ok "lsquor" entity_ok "lstrok" entity_ok "lt" entity_ok "lthree" entity_ok "ltimes" entity_ok "ltri" entity_ok "ltrie" entity_ok "ltrif" entity_ok "lvnE" entity_ok "macr" entity_ok "male" entity_ok "malt" entity_ok "map" entity_ok "marker" entity_ok "mcy" entity_ok "mdash" entity_ok "mgr" entity_ok "micro" entity_ok "mid" entity_ok "middot" entity_ok "minus" entity_ok "minusb" entity_ok "mldr" entity_ok "mnplus" entity_ok "models" entity_ok "mu" entity_ok "mumap" entity_ok "nVDash" entity_ok "nVdash" entity_ok "nabla" entity_ok "nacute" entity_ok "nap" entity_ok "napos" entity_ok "natur" entity_ok "nbsp" entity_ok "ncaron" entity_ok "ncedil" entity_ok "ncong" entity_ok "ncy" entity_ok "ndash" entity_ok "ne" entity_ok "nearr" entity_ok "nequiv" entity_ok "nexist" entity_ok "ngE" entity_ok "nge" entity_ok "nges" entity_ok "ngr" entity_ok "ngt" entity_ok "nhArr" entity_ok "nharr" entity_ok "ni" entity_ok "njcy" entity_ok "nlArr" entity_ok "nlE" entity_ok "nlarr" entity_ok "nldr" entity_ok "nle" entity_ok "nles" entity_ok "nlt" entity_ok "nltri" entity_ok "nltrie" entity_ok "nmid" entity_ok "not" entity_ok "notin" entity_ok "npar" entity_ok "npr" entity_ok "npre" entity_ok "nrArr" entity_ok "nrarr" entity_ok "nrtri" entity_ok "nrtrie" entity_ok "nsc" entity_ok "nsce" entity_ok "nsim" entity_ok "nsime" entity_ok "nsmid" entity_ok "nspar" entity_ok "nsub" entity_ok "nsubE" entity_ok "nsube" entity_ok "nsup" entity_ok "nsupE" entity_ok "nsupe" entity_ok "ntilde" entity_ok "nu" entity_ok "num" entity_ok "numero" entity_ok "numsp" entity_ok "nvDash" entity_ok "nvdash" entity_ok "nwarr" entity_ok "oS" entity_ok "oacgr" entity_ok "oacute" entity_ok "oast" entity_ok "ocir" entity_ok "ocirc" entity_ok "ocy" entity_ok "odash" entity_ok "odblac" entity_ok "odot" entity_ok "oelig" entity_ok "ogon" entity_ok "ogr" entity_ok "ograve" entity_ok "ohacgr" entity_ok "ohgr" entity_ok "ohm" entity_ok "olarr" entity_ok "oline" entity_ok "omacr" entity_ok "omega" entity_ok "omicron" entity_ok "ominus" entity_ok "oplus" entity_ok "or" entity_ok "orarr" entity_ok "order" entity_ok "ordf" entity_ok "ordm" entity_ok "oslash" entity_ok "osol" entity_ok "otilde" entity_ok "otimes" entity_ok "ouml" entity_ok "par" entity_ok "para" entity_ok "part" entity_ok "pcy" entity_ok "percnt" entity_ok "period" entity_ok "permil" entity_ok "perp" entity_ok "pgr" entity_ok "phgr" entity_ok "phi" entity_ok "phis" entity_ok "phiv" entity_ok "phmmat" entity_ok "phone" entity_ok "pi" entity_ok "piv" entity_ok "planck" entity_ok "plus" entity_ok "plusb" entity_ok "plusdo" entity_ok "plusmn" entity_ok "pound" entity_ok "pr" entity_ok "prap" entity_ok "pre" entity_ok "prime" entity_ok "prnE" entity_ok "prnap" entity_ok "prnsim" entity_ok "prod" entity_ok "prop" entity_ok "prsim" entity_ok "psgr" entity_ok "psi" entity_ok "puncsp" entity_ok "quest" entity_ok "quot" entity_ok "rAarr" entity_ok "rArr" entity_ok "racute" entity_ok "radic" entity_ok "rang" entity_ok "raquo" entity_ok "rarr" entity_ok "rarr2" entity_ok "rarrhk" entity_ok "rarrlp" entity_ok "rarrtl" entity_ok "rarrw" entity_ok "rcaron" entity_ok "rcedil" entity_ok "rceil" entity_ok "rcub" entity_ok "rcy" entity_ok "rdquo" entity_ok "rdquor" entity_ok "real" entity_ok "rect" entity_ok "reg" entity_ok "rfloor" entity_ok "rgr" entity_ok "rhard" entity_ok "rharu" entity_ok "rho" entity_ok "rhov" entity_ok "ring" entity_ok "rlarr2" entity_ok "rlhar2" entity_ok "rlm" entity_ok "rpar" entity_ok "rpargt" entity_ok "rsaquo" entity_ok "rsh" entity_ok "rsqb" entity_ok "rsquo" entity_ok "rsquor" entity_ok "rthree" entity_ok "rtimes" entity_ok "rtri" entity_ok "rtrie" entity_ok "rtrif" entity_ok "rx" entity_ok "sacute" entity_ok "samalg" entity_ok "sbquo" entity_ok "sbsol" entity_ok "sc" entity_ok "scap" entity_ok "scaron" entity_ok "sccue" entity_ok "sce" entity_ok "scedil" entity_ok "scirc" entity_ok "scnE" entity_ok "scnap" entity_ok "scnsim" entity_ok "scsim" entity_ok "scy" entity_ok "sdot" entity_ok "sdotb" entity_ok "sect" entity_ok "semi" entity_ok "setmn" entity_ok "sext" entity_ok "sfgr" entity_ok "sfrown" entity_ok "sgr" entity_ok "sharp" entity_ok "shchcy" entity_ok "shcy" entity_ok "shy" entity_ok "sigma" entity_ok "sigmaf" entity_ok "sigmav" entity_ok "sim" entity_ok "sime" entity_ok "smid" entity_ok "smile" entity_ok "softcy" entity_ok "sol" entity_ok "spades" entity_ok "spar" entity_ok "sqcap" entity_ok "sqcup" entity_ok "sqsub" entity_ok "sqsube" entity_ok "sqsup" entity_ok "sqsupe" entity_ok "squ" entity_ok "square" entity_ok "squf" entity_ok "ssetmn" entity_ok "ssmile" entity_ok "sstarf" entity_ok "star" entity_ok "starf" entity_ok "sub" entity_ok "subE" entity_ok "sube" entity_ok "subnE" entity_ok "subne" entity_ok "sum" entity_ok "sung" entity_ok "sup" entity_ok "sup1" entity_ok "sup2" entity_ok "sup3" entity_ok "supE" entity_ok "supe" entity_ok "supnE" entity_ok "supne" entity_ok "szlig" entity_ok "target" entity_ok "tau" entity_ok "tcaron" entity_ok "tcedil" entity_ok "tcy" entity_ok "tdot" entity_ok "telrec" entity_ok "tgr" entity_ok "there4" entity_ok "theta" entity_ok "thetas" entity_ok "thetasym" entity_ok "thetav" entity_ok "thgr" entity_ok "thinsp" entity_ok "thkap" entity_ok "thksim" entity_ok "thorn" entity_ok "tilde" entity_ok "times" entity_ok "timesb" entity_ok "top" entity_ok "tprime" entity_ok "trade" entity_ok "trie" entity_ok "tscy" entity_ok "tshcy" entity_ok "tstrok" entity_ok "twixt" entity_ok "uArr" entity_ok "uacgr" entity_ok "uacute" entity_ok "uarr" entity_ok "uarr2" entity_ok "ubrcy" entity_ok "ubreve" entity_ok "ucirc" entity_ok "ucy" entity_ok "udblac" entity_ok "udiagr" entity_ok "udigr" entity_ok "ugr" entity_ok "ugrave" entity_ok "uharl" entity_ok "uharr" entity_ok "uhblk" entity_ok "ulcorn" entity_ok "ulcrop" entity_ok "umacr" entity_ok "uml" entity_ok "uogon" entity_ok "uplus" entity_ok "upsi" entity_ok "upsih" entity_ok "upsilon" entity_ok "urcorn" entity_ok "urcrop" entity_ok "uring" entity_ok "utilde" entity_ok "utri" entity_ok "utrif" entity_ok "uuml" entity_ok "vArr" entity_ok "vDash" entity_ok "varr" entity_ok "vcy" entity_ok "vdash" entity_ok "veebar" entity_ok "vellip" entity_ok "verbar" entity_ok "vltri" entity_ok "vprime" entity_ok "vprop" entity_ok "vrtri" entity_ok "vsubnE" entity_ok "vsubne" entity_ok "vsupnE" entity_ok "vsupne" entity_ok "wcirc" entity_ok "wedgeq" entity_ok "weierp" entity_ok "wreath" entity_ok "xcirc" entity_ok "xdtri" entity_ok "xgr" entity_ok "xhArr" entity_ok "xharr" entity_ok "xi" entity_ok "xlArr" entity_ok "xrArr" entity_ok "xutri" entity_ok "yacute" entity_ok "yacy" entity_ok "ycirc" entity_ok "ycy" entity_ok "yen" entity_ok "yicy" entity_ok "yucy" entity_ok "yuml" entity_ok "zacute" entity_ok "zcaron" entity_ok "zcy" entity_ok "zdot" entity_ok "zeta" entity_ok "zgr" entity_ok "zhcy" entity_ok "zwj" entity_ok "zwnj" entity_ok done ".0-9A-Za-z" entity_name :entity_ok Entity * mistake_entity noeat recolor=-1 ";" entity_end noeat recolor=-1 :mistake_entity Mystery * idle noeat .end ne-3.3.4/syntax/htmlerb.jsf000066400000000000000000000003211475116431000156100ustar00rootroot00000000000000# JOE syntax highlight file for HTML embedded ERB # by Christian Nicolai (http://mycrobase.de) =Idle =Keyword bold =Bad bold red =Brace magenta =ERB yellow :begin Idle * NULL noeat call=html.html(erb) ne-3.3.4/syntax/ini.jsf000066400000000000000000000022611475116431000147370ustar00rootroot00000000000000# JOE syntax highlight file for INI files # by Christian Nicolai (http://mycrobase.de) =Idle =Comment green =Constant cyan =Escape bold cyan =Bad bold red =Key =Separator bold =Section bold magenta :line_start Idle * key noeat "\n" line_start " \t\r" line_start # leading spaces ";#" line_comment recolor=-1 "[" section recolor=-1 "=" missing_key recolor=-1 :line_comment Comment * line_comment "\n" line_start :section Section * section "]" section_end "\n" section_unexp_end recolor=-2 :section_end Bad * section_end "\n" line_start :section_unexp_end Bad * line_start noeat :missing_key Bad * value_pre noeat :key Key * key " \t\r" key_post noeat "=" sep recolor=-1 "\n" key_error recolor=-2 :key_post Idle * value_pre noeat " \t\r" key_post "=" sep recolor=-1 :key_error Bad * key noeat :sep Separator * value_pre noeat :value_pre Idle * value noeat " \t\r" value_pre :value Constant * value "\\" value_esc "\n" line_start " " maybe_comment recolor=-1 :value_esc Escape * value "\n" value_error recolor=-2 :value_error Bad * value noeat :maybe_comment Idle * value noeat ";#" line_comment recolor=-1 ne-3.3.4/syntax/iptables.jsf000066400000000000000000000036621475116431000157710ustar00rootroot00000000000000# JOE syntax highlight file for ip(6)tables-save and -restore # by Christian Nicolai (http://mycrobase.de) =Idle =Comment green =Constant cyan =Escape bold cyan =Bad bold red =Table bold =Policy bold blue =Option yellow =Module blue :line_start Idle * rest noeat "#" line_comment recolor=-1 "*" maybe_table buffer ":" default_policy recolor=-1 "C" maybe_commit buffer "-" command :rest Idle * rest "\n" line_start :line_comment Comment * line_comment "\n" line_start :maybe_table Idle * bad noeat strings "*filter" table_name "*mangle" table_name "*nat" table_name "*raw" table_name done "a-z" maybe_table :table_name Table * bad noeat "\n" line_start :default_policy Policy * default_policy "\n" line_start :maybe_commit Idle * bad noeat strings "COMMIT" commit done "A-Z" maybe_commit :commit Table * bad noeat "\n" line_start :command Idle * bad noeat "ADINX" chain_name_pre :chain_name_pre Idle * chain_name noeat " " chain_name_pre :chain_name Option * chain_name " " rule_spec noeat "\n" line_start :rule_spec Idle * rule_option noeat " " rule_spec "\n" line_start :rule_option Idle * rule_option " " rule_spec noeat "\n" line_start "-" rule_flag :rule_flag Idle * rule_option "-" rule_flag_flag "iojp" rule_iojp_pre "sd" rule_sd_pre "m" rule_m_pre :rule_flag_flag Idle * rule_flag_flag1 buffer :rule_flag_flag1 Idle * rule_option noeat strings "sport" rule_sd_pre "dport" rule_sd_pre done "a-z-_" rule_flag_flag1 :rule_iojp_pre Idle * rule_iojp noeat " " rule_iojp_pre :rule_iojp Option * rule_iojp " " rule_option noeat "\n" line_start :rule_sd_pre Idle * rule_sd noeat " " rule_sd_pre :rule_sd Constant * rule_sd " " rule_option noeat "\n" line_start :rule_m_pre Idle * rule_m noeat " " rule_m_pre :rule_m Module * rule_m " " rule_option noeat "\n" line_start :bad Bad * bad "\n" line_start ne-3.3.4/syntax/java.jsf000066400000000000000000000077301475116431000151070ustar00rootroot00000000000000# JOE syntax highlight file for JAVA # Needs: improve escape parsing =Idle =Comment green =Constant cyan =Escape bold cyan =Type bold =Keyword bold =Operator bold =Bad bold red =Brace magenta =Control =Methods :begin Idle * begin noeat call=.java() # # Java as a subroute- for use as java script in html # .subr java :idle Idle * idle "\n" idle "/" slash "0" first_digit recolor=-1 "1-9" decimal recolor=-1 "." maybe_float "\"" string recolor=-1 "'" char recolor=-1 "a-zA-Z_" ident mark buffer "{}" brace recolor=-1 ",:;=()><[]*&|!~+\-%^" control recolor=-1 :maybe_done Control * idle noeat "/" idle noeat return recolor=-2 :brace Brace * idle noeat :control Control * idle noeat :slash Idle * idle noeat "*" comment recolor=-2 "/" line_comment recolor=-2 :comment Comment * comment # might be TODO label "BFHNTX" comment noeat call=comment_todo.comment_todo() "*" maybe_end_comment :maybe_end_comment Comment * comment noeat "/" idle "*" maybe_end_comment :line_comment Comment * line_comment # might be TODO label "BFHNTX" line_comment noeat call=comment_todo.comment_todo() "\n" idle :first_digit Constant * idle noeat "xX" hex "." float "eE" epart "0-7" octal "89" bad_number recolor=-1 :bad_number Bad * idle noeat "0-9" bad_number :octal Constant * idle noeat "0-7" octal "89" bad_number recolor=-1 :hex Constant * idle noeat "0-9A-Fa-f" hex :decimal Constant * idle noeat "0-9" decimal "eE" epart "." float :maybe_float Constant * idle recolor=-2 noeat "0-9" float recolor=-2 :float Constant * idle noeat "eE" epart "0-9" float :epart Constant * idle noeat "0-9+\-" enum :enum Constant * idle noeat "0-9" enum :string Constant * string "\"" idle "\\" string_escape recolor=-1 "%" string_control recolor=-1 :string_escape Escape * string "u" string_uni1 "0-7" string_octal2 "\n" string recolor=-2 :string_uni1 Escape * string noeat "0-9a-fA-F" string_uni2 :string_uni2 Escape * string noeat "0-9a-fA-F" string_uni3 :string_uni3 Escape * string noeat "0-9a-fA-F" string_uni4 :string_uni4 Escape * string noeat "0-9a-fA-F" string :string_octal2 Escape * string noeat "0-7" string_octal3 :string_octal3 Escape * string noeat "0-7" string :string_control Escape * string_control "\n" reset "\"" string noeat "diouxXeEfFgGaAcspn%SC" string :char Constant * char "\n" reset "'" idle "\\" char_escape recolor=-1 :char_escape Escape * char "u" char_uni1 "0-7" char_octal2 "\n" char recolor=-2 :char_uni1 Escape * char noeat "0-9a-fA-F" char_uni2 :char_uni2 Escape * char noeat "0-9a-fA-F" char_uni3 :char_uni3 Escape * char noeat "0-9a-fA-F" char_uni4 :char_uni4 Escape * char noeat "0-9a-fA-F" char :char_octal2 Escape * char noeat "0-7" char_octal3 :char_octal3 Escape * char noeat "0-7" char :ident Idle * ident_end noeat markend strings "abstract" kw "assert" kw "boolean" type "break" kw "byte" type "case" kw "catch" kw "char" type "class" kw "const" bad_kw "continue" kw "default" kw "do" kw "double" type "else" kw "enum" kw "extends" kw "false" lit "final" kw "finally" kw "float" type "for" kw "goto" bad_kw "if" kw "implements" kw "import" kw "instanceof" operator "int" type "interface" kw "long" type "native" kw "new" operator "null" lit "package" kw "private" kw "protected" kw "public" kw "return" kw "short" type "static" kw "strictfp" kw "super" kw "switch" kw "synchronized" kw "this" kw "throw" kw "throws" kw "transient" kw "true" lit "try" kw "void" kw "volatile" kw "while" kw done "a-zA-Z0-9_" ident :type Type * idle noeat :kw Keyword * idle noeat :bad_kw Bad * idle noeat :lit Constant * idle noeat :operator Operator * idle noeat :ident_end Idle * idle noeat " " ident_end "(" method_end noeat recolormark :method_end Methods * idle noeat .end ne-3.3.4/syntax/joerc.jsf000066400000000000000000000032361475116431000152650ustar00rootroot00000000000000# JOE syntax highlight file for typical UNIX configuration files =Idle =Comment green =String cyan =Escape bold cyan =Bad bold red :idle Idle * not_comment "\n" idle " " comment "-" option "\"" keybind_string recolor=-1 "a-zA-Z_" keybind :keybind Idle * keybind "\"" keybind_string recolor=-1 "\n" idle "," keybind_after_comma " " keybind1 :keybind_after_comma Idle * keybind noeat " " keybind_after_comma "\n" keybind_after_comma :keybind_string String * keybind_string "\\" keybind_escape recolor=-1 "\"" keybind :keybind_escape Escape * keybind_string :keybind1 Idle * keybind2 "\n" idle " " keybind1 :keybind2 Idle * keybind2 "\n" idle " " comment noeat " " maybe_com :maybe_com Idle * keybind2 noeat " " comment noeat :option Idle * bad recolor=-1 "-" option "a-zA-Z_" ident buffer :ident Idle * comment noeat strings "backpath" sopt "lines" nopt "baud" nopt "columns" nopt "skiptop" nopt "text_color" sopt "status_color" sopt "help_color" sopt "menu_color" sopt "prompt_color" sopt "msg_color" sopt "lmsg" sopt "rmsg" sopt "cpara" sopt "encoding" sopt "syntax" sopt "indentc" nopt "istep" nopt "lmargin" nopt "rmargin" nopt "keymap" sopt "mfirst" sopt "mnew" sopt "mold" sopt "msnew" sopt "msold" sopt "text_delimiters" sopt done "a-zA-Z0-9_" ident :sopt Idle * dosopt noeat :dosopt String * dosopt "\n" idle :nopt Idle * bad recolor=-1 " " nopt "0-9" nopt1 recolor=-1 :nopt1 String * comment noeat "0-9" nopt1 :bad Bad * bad "\n" idle :comment Comment * comment "\n" idle :not_comment Idle * not_comment "\n" idle ne-3.3.4/syntax/js.jsf000066400000000000000000000246321475116431000146020ustar00rootroot00000000000000# JOE syntax highlight file for JavaScript # by Christian Nicolai (http://mycrobase.de) and Rebecca Turner # Define colors =Idle =Comment green =Constant cyan =String cyan =StringEscape bold cyan =Regexp cyan =RegexpEscape bold cyan =RegexpOptions cyan =Number cyan =Type bold =CustomType =Keyword bold =Global bold =Exports =ExportItem =Operator bold =Bad inverse bold red =Brace magenta =Assign bold =Paren =Bracket =Semicolon =Comma =PropSep =Syntax =Ident =Method # from html.jsf to support embedded languages =TagEdge green :begin Idle * begin noeat call=.js() "#" shebang recolor=-1 :shebang Comment * shebang "\n" begin .subr js :idle Idle * idle "/" re_or_comment recolor=-1 "0" first_digit recolor=-1 "1-9" decimal recolor=-1 "." maybe_float "\"" string recolor=-1 save_c "'" string recolor=-1 save_c "A-Z" type_match mark buffer recolor=-1 "$a-z_" ident mark buffer recolor=-1 "{}" brace recolor=-1 "()" paren noeat recolor=-1 ";" semicolon recolor=-1 "," comma recolor=-1 "=" assign_maybe recolor=-1 "[]" bracket noeat recolor=-1 "\-" subtract recolor=-1 "+" add recolor=-1 "*|&^%" mutate_maybe recolor=-1 ":?~" syntax recolor=-1 .ifdef html "<" maybe_done recolor=-1 ">!" eqmixer_maybe recolor=-1 .else .ifdef php "<" maybe_done recolor=-1 ">!" eqmixer_maybe recolor=-1 .else .ifdef mason "<" maybe_done recolor=-1 ">!" eqmixer_maybe recolor=-1 .else "<>!" eqmixer_maybe recolor=-1 .endif .endif .endif :subtract Assign * mutate_maybe noeat "\-" assign recolor=-2 :add Assign * mutate_maybe noeat "+" assign recolor=-2 :mutate_maybe Assign * syntax recolor=-2 noeat "=" assign recolor=-2 :syntax Syntax * idle recolor=-1 noeat :assign Assign * idle recolor=-1 noeat :eqmixer_maybe Syntax * idle recolor=-1 noeat "=" eqmixer_maybe :assign_maybe Assign * idle recolor=-1 noeat "=" equality recolor=-2 :equality Syntax * idle recolor=-1 noeat "=" syntax :maybe_done TagEdge * eqmixer_maybe noeat "/" idle noeat return recolor=-2 .ifdef php "?" rtn_embed call=php.php() .endif .ifdef mason "&%" rtn_embed call=perl.perl() .endif :rtn_embed TagEdge * idle noeat :no_regex Idle * idle noeat "/" maybe_comment recolor=-1 " " no_regex :after_term Idle * after_term "\n" idle "/" maybe_comment recolor=-1 "." prop_sep recolor=-1 "a-z" infix_operator buffer recolor=-1 "\"'A-Z_0-9" bad_after_term recolor=-1 "{}" brace recolor=-1 "()" paren noeat recolor=-1 ";" semicolon recolor=-1 "=" assign_maybe recolor=-1 "," comma recolor=-1 "[]" bracket noeat recolor=-1 "\-" subtract recolor=-1 "+" add recolor=-1 "*|&^%" mutate_maybe recolor=-1 ":?~" syntax recolor=-1 .ifdef html "<" maybe_done recolor=-1 ">!" eqmixer_maybe recolor=-1 .else .ifdef php "<" maybe_done recolor=-1 ">!" eqmixer_maybe recolor=-1 .else .ifdef mason "<" maybe_done recolor=-1 ">!" eqmixer_maybe recolor=-1 .else "<>!" eqmixer_maybe recolor=-1 .endif .endif .endif :ident_only Idle * bad_after_term recolor=-1 "A-Z" type_match mark buffer recolor=-1 "$a-z_" ident mark buffer recolor=-1 " \t" ident_only "\n" idle :bad_after_term Bad * after_term noeat markend strings done "\"'" after_term "a-zA-Z0-9_." bad_after_term :re_or_comment Syntax * regex noeat recolor=-2 "*/" maybe_comment noeat :maybe_comment Syntax * syntax noeat "*" comment recolor=-2 "/" line_comment recolor=-2 "=" assign recolor=-2 :comment Comment * comment # might be TODO label "BFHNTX" comment noeat call=comment_todo.comment_todo() "*" maybe_end_comment :maybe_end_comment Comment * comment noeat "/" idle "*" maybe_end_comment :line_comment Comment * line_comment # might be TODO label "BFHNTX" line_comment noeat call=comment_todo.comment_todo() "\n" idle :regex Regexp * regex "\\" regex_quote recolor=-1 "[" regex_charclass "/" regex_mod "\n" regex_bad :regex_quote RegexpEscape * regex "\n" regex_bad :regex_charclass Regexp * regex_charclass "\\" regex_cc_quote recolor=-1 "\n" regex_bad_cc "]" regex :regex_cc_quote RegexpEscape * regex_charclass "\n" regex_bad_cc :regex_bad Bad * regex_bad "\\" regex_bad_quote "[" regex_bad_cc "/" after_term :regex_bad_quote Bad * regex_bad :regex_bad_cc Bad * regex_bad_cc "\\" regex_bad_quote_cc "]" regex_bad :regex_bad_quote_cc Bad * regex_bad_cc :regex_mod RegexpOptions * after_term noeat "igm" regex_mod :brace Brace * idle noeat :paren Paren "(" idle ")" no_regex :bracket Bracket "[" idle "]" after_term :syntax Syntax * idle noeat :comma Comma * idle noeat :semicolon Semicolon * idle noeat :first_digit Number * after_term noeat "xX" hex "." float "eE" epart "0-7" octal "89" bad_number recolor=-1 :bad_number Bad * after_term noeat "0-9" bad_number :octal Number * after_term noeat "0-7" octal "89" bad_number recolor=-1 :hex Number * after_term noeat "0-9A-Fa-f" hex :decimal Number * after_term noeat "0-9" decimal "eE" epart "." float :maybe_float Number * prop_sep noeat recolor=-2 "0-9" float recolor=-2 :prop_sep PropSep * ident_only noeat :float Number * after_term noeat "eE" epart "0-9" float :epart Number * after_term noeat "0-9+\-" enum :enum Number * after_term noeat "0-9" enum :string String * string_body noeat mark :string_body String * string "\n" string_bad & after_term "\\" string_escape recolor=-1 :string_bad Bad * string_bad "\\" string_bad_escape & after_term :string_bad_escape Bad * string_bad :string_escape StringEscape * string "x" string_hex1 "0-7" string_octal2 "\n" string_bad noeat :string_hex1 StringEscape * string noeat "0-9a-fA-F" string_hex2 :string_hex2 StringEscape * string noeat "0-9a-fA-F" string :string_octal2 StringEscape * string noeat "0-7" string_octal3 :string_octal3 StringEscape * string noeat "0-7" string :infix_operator Bad * bad_op noeat markend strings "in" operator "instanceof" operator done "a-zA-Z0-9_" infix_operator :bad_op Bad * idle noeat "a-zA-Z0-9_" bad_op :operator Operator * idle noeat :type_match CustomType * type_end noeat markend strings "Infinity" lit "NaN" lit "Array" type "ArrayBuffer" type "Boolean" type "DataView" type "Date" type "Error" type "EvalError" type "Function" type "Float32Array" type "Float64Array" type "Int16Array" type "Int32Array" type "Int8Array" type "JSON" type "Math" type "Number" type "Object" type "RangeError" type "ReferenceError" type "RegExp" type "String" type "SyntaxError" type "TypeError" type "Uint16Array" type "Uint32Array" type "Uint8Array" type "Uint8ClampedArray" type "URIError" type # node.js "Buffer" type done "a-zA-Z0-9_" type_match :type_end Idle * after_term noeat " " type_end "." prop_sep recolor=-1 .ifdef typescript :ident Ident * ident_end noeat markend strings "delete" operator "in" operator "instanceof" operator "typeof" operator "new" operator "arguments" kw "break" kw "case" kw "catch" kw "continue" kw "default" kw "do" kw "else" kw "finally" kw "for" kw "function" kw "if" kw "let" kw "return" kw "switch" kw "throw" kw "try" kw "var" kw "void" kw "while" kw "with" kw "false" lit "null" lit "true" lit "const" global "decodeURI" global "decodeURIComponent" global "encodeURI" global "encodeURIComponent" global "escape" global "eval" global "isFinite" global "isNaN" global "parseFloat" global "parseInt" global "undefined" global "unescape" global "setImmediate" global "this" quasikw "prototype" quasikw # node.js "exports" export "module" global "process" global "global" global "console" global "setTimeout" global "setInterval" global "clearInterval" global "clearTimeout" global "require" quasikw "__filename" quasikw "__dirname" quasikw # By convention... "self" quasikw # Typescript-specific "class" kw "constructor" kw "declare" kw "enum" kw "extends" kw "export" kw "get" kw "implements" kw "import" kw "interface" kw "module" kw "namespace" kw "private" kw "public" kw "require" kw "set" kw "static" kw "super" kw "any" type "boolean" type "number" type "string" type "void" type done "$a-zA-Z0-9_" ident .else :ident Ident * ident_end noeat markend strings "delete" operator "in" operator "instanceof" operator "typeof" operator "new" operator "arguments" kw "break" kw "case" kw "catch" kw "continue" kw "default" kw "do" kw "else" kw "finally" kw "for" kw "function" kw "if" kw "let" kw "return" kw "switch" kw "throw" kw "try" kw "var" kw "void" kw "while" kw "with" kw "false" lit "null" lit "true" lit "const" global "decodeURI" global "decodeURIComponent" global "encodeURI" global "encodeURIComponent" global "escape" global "eval" global "isFinite" global "isNaN" global "parseFloat" global "parseInt" global "undefined" global "unescape" global "setImmediate" global "this" quasikw "prototype" quasikw # node.js "exports" export "module" global "process" global "global" global "console" global "setTimeout" global "setInterval" global "clearInterval" global "clearTimeout" global "require" quasikw "__filename" quasikw "__dirname" quasikw # By convention... "self" quasikw done "$a-zA-Z0-9_" ident .endif :ident_end Idle * after_term noeat " " ident_end "." prop_sep recolor=-1 "(" method_start recolor=-1 :method_start Paren * method_end noeat recolormark :method_end Method * idle noeat :type Type * after_term noeat :kw Keyword * idle noeat :quasikw Keyword * after_term noeat :global Global * after_term noeat :export Exports * export_end noeat :export_end Exports * after_term noeat " " export_end "." export_item_start :export_item_start ExportItem * bad_after_term recolor=-1 noeat " " export_item_start "a-zA-Z_" export_item :export_item ExportItem * after_term noeat "a-zA-Z0-9_" export_item :lit Constant * lit_end noeat :lit_end Constant * after_term noeat " " lit_end "." prop_sep recolor=-1 .end ne-3.3.4/syntax/jsf.jsf000066400000000000000000000521651475116431000147520ustar00rootroot00000000000000# JOE Syntax-Highlighting Description # for # JOE Syntax-Highlighting Descriptions # # Author: Charles J. Tabony # Date: 2007-2-13 # # This is a highlighting description for files like this one. # # When CHECKING is defined, it is very aggressive about error checking. The # idea is that anywhere the highlighted file contains a syntax error, at least # one visible character should be highlighted as Bad. While that feature is # useful for finding syntax errors, it is annoying when editing a file, since # nearly everything is an error until you finish typing it. # # In order to not annoy people by default, but keep the option of strictly # checking syntax, I predicated the stricter checking on the CHECKING parameter. # By default, things that are incomplete are generally not marked as errors. # Only things that appear to be actual mistakes are highlighted as Bad. To # enable the stricter checking, one can highlight the file with the jsf_check # syntax. jsf_check.jsf simply calls the entire jsf.jsf file with CHECKING # defined. # # The idea is for authors of a jsf file to edit their file, highlight it with # jsf_check, and then look for any red characters. That way they can check for # syntax errors before testing the changes. ##################### # Color Definitions # ##################### =Idle =Comment green =Conditional blue =Parameter bold blue =Keyword bold =Color yellow =StandardColor bold =State =Subr magenta =Literal cyan =Escape bold cyan =Bad bold red ################## # Initial States # ################## # This is a dummy state that simply jumps to comment_or_bad. It is here so that # when this file calls itself with the STRINGS parameter defined, comment_or_bad # will effectively be the initial state. comment_or_bad should be the initial # state because strings and istrings options can only be used as the last option # of a transition. .ifdef STRINGS :strings_initial Idle * comment_or_bad noeat .endif # Each new line (that is not considered bad from the beginning) begins in the # idle state. The first non-whitespace character determines what the rest of # the line should contain. Following a strings or istrings option, only strings # and comments are allowed until the word "done" denotes the end of the list. :idle Idle * bad noeat " \t\n" idle .ifdef STRINGS .else "-" sync_lines_first "." conditional_first mark recolor=-1 "=" color_definition_first ":" state_first "*&" special_character recolor=-1 .endif "\"" string recolor=-1 .ifdef STRINGS "A-Za-z_" special_word mark recolor=-1 buffer .endif "#" comment recolor=-1 ############## # Sync Lines # ############## # Following a '-' should be either the number of sync lines or nothing (meaning # unlimited). Nothing else other than a comment should appear on the same line. .ifdef STRINGS # A sync lines directive should not appear between "[i]strings" and "done". .else # If we see a non-digit or a '0', then we have seen the entire sync lines # directive. The only thing that may appear on the rest of the line is a # comment. Otherwise there may be more digits in the number. :sync_lines_first Literal * comment_or_bad noeat "0" comment_or_bad "1-9" sync_lines # Highlight the remainder of the number. :sync_lines Literal * comment_or_bad noeat "0-9" sync_lines .endif ########################## # Conditional Directives # ########################## # Following a '.' should be a conditional directive. .ifdef STRINGS # A conditional directive should not appear between "[i]strings" and "done". .else # Start buffering the conditional directive. :conditional_first Conditional * conditional noeat buffer # Recognize the set of conditional directives. :conditional Idle * conditional_unknown noeat strings "ifdef" ifdef_color "else" conditional_color "endif" conditional_color "subr" subr_color "end" conditional_color done "A-Za-z0-9_" conditional # We encountered what looks like a conditional directive but is unrecognized as # such. :conditional_unknown Idle .ifdef CHECKING * bad_line recolormark noeat .else * comment_or_bad noeat .endif # We saw a conditional directive that does not take an argument. Nothing else # other than a comment should appear on the same line. :conditional_color Conditional * comment_or_bad noeat # We saw a ".ifdef" which must be followed by a parameter. :ifdef_color Conditional * need_parameter noeat # We loop over whitespace until we see the first character of the parameter. :need_parameter Idle * bad noeat " \t" need_parameter "A-Za-z_" parameter recolor=-1 # Now we highlight the remainder of the parameter. :parameter Parameter * comment_or_bad noeat "A-Za-z0-9_" parameter # The following three states are identical to the previous three except the # color. :subr_color Conditional * need_subr noeat :need_subr Idle * bad noeat " \t" need_subr "A-Za-z_" subr recolor=-1 :subr Subr * comment_or_bad noeat "A-Za-z0-9_" subr .endif #################### # Color Definition # #################### # Following an '=' should be a color definition. .ifdef STRINGS # Color definitions should not appear between "[i]strings" and "done". .else # A color name must have at least one character. :color_definition_first Color * color_definition " \t#\n" bad noeat # Highlight any remaining characters until we see whitespace, a comment, or a # newline. :color_definition Color * color_definition " \t#\n" colors_ws noeat # The color name may be followed by zero or more standard colors or attributes, # ending in a comment or newline. :colors_ws Idle * color_bad recolor=-1 " \t" colors_ws "A-Za-z_" color mark recolor=-1 buffer "#\n" comment noeat # Here we recognize the attributes and standard color names. None of the # attributes or standard color names contain a digit except fg_NNN and bg_NNN, # which are handled specially below. :color Idle * color_unknown noeat strings "inverse" color_color "underline" color_color "bold" color_color "italic" color_color "blink" color_color "dim" color_color "white" color_color "cyan" color_color "magenta" color_color "blue" color_color "yellow" color_color "green" color_color "red" color_color "black" color_color "bg_white" color_color "bg_cyan" color_color "bg_magenta" color_color "bg_blue" color_color "bg_yellow" color_color "bg_green" color_color "bg_red" color_color "bg_black" color_color "WHITE" color_color "CYAN" color_color "MAGENTA" color_color "BLUE" color_color "YELLOW" color_color "GREEN" color_color "RED" color_color "BLACK" color_color "bg_WHITE" color_color "bg_CYAN" color_color "bg_MAGENTA" color_color "bg_BLUE" color_color "bg_YELLOW" color_color "bg_GREEN" color_color "bg_RED" color_color "bg_BLACK" color_color "fg_" color_number_first "bg_" color_number_first done "A-Za-z_" color # We encountered what looks like a standard color but is unrecognized as such. :color_unknown Idle .ifdef CHECKING * color_bad recolormark noeat .else * colors_ws noeat .endif # Here we have seen either "fg_" or "bg_". We now expect to find a number. The # number should either be a one to two digit number, representing greyscale # intensity, in the range 0-23, or a three digit number, where each digit is in # the range 0-5 and represents the intensity of red, green, and blue # respectively. :color_number_first Idle .ifdef CHECKING * color_bad recolormark noeat .else * color_bad noeat .endif "0" color_zero "1" color_one "2" color_two "3-5" color_number_second "6-9" color_end # The first digit is a zero, thus we either have a greyscale intensity of 0, in # which case we should not see any more digits, or we have the first RGB digit, # in which case we should see two more ditits in the range 0-5. :color_zero Idle .ifdef CHECKING * color_bad recolormark noeat .else * color_bad noeat .endif "0-5" color_rgb_third " \t#\n" color_color recolormark noeat # The first digit is a one. If we see whitespace or a comment, then we have a # greyscale intensity of 1. If we see a digit 6-9, then we have a greyscale # intensity of 16-19. If we see a digit 0-5, then we either have a greyscale # intensity of 10-15 or an RGB value. :color_one Idle .ifdef CHECKING * color_bad recolormark noeat .else * color_bad noeat .endif "0-5" color_number_third "6-9" color_end " \t#\n" color_color recolormark noeat # The first digit is a two. If we see whitespace or a comment, then we have a # greyscale intensity of 2. If we see a digit 4-5, then we have the first two # digits of an RGB value. If we see a digit 0-3, then we either have a # greyscale intensity of 20-23 or an RGB value. :color_two Idle .ifdef CHECKING * color_bad recolormark noeat .else * color_bad noeat .endif "0-3" color_number_third "4-5" color_rgb_third " \t#\n" color_color recolormark noeat # We have seen one digit that could be either the greyscale intensity or the # first RGB digit. If we see any more digits, they we must have an RGB value, # because otherwise the number would be outside the range 0-23. :color_number_second Idle .ifdef CHECKING * color_bad recolormark noeat .else * color_bad noeat .endif "0-5" color_rgb_third " \t#\n" color_color recolormark noeat # We have seen two digits that could be either the greyscale intensity or the # first two RGB digits. If we see any more digits, they we must have an RGB # value, because otherwise the number would be outside the range 0-23. :color_number_third Idle .ifdef CHECKING * color_bad recolormark noeat .else * color_bad noeat .endif "0-5" color_end " \t#\n" color_color recolormark noeat # We have seen two digits, both 0-5, that either start with zero or are outside # the range 0-23. Thus we expect a third 0-5 digit. :color_rgb_third Idle .ifdef CHECKING * color_bad recolormark noeat .else * color_bad noeat .endif "0-5" color_end # We have seen "fg_" or "bg_" followed by one to three digits. Any more digits # would either be too many or make the number out of range. We now expect to # see whitespace, a comment, or a newline. :color_end Idle .ifdef CHECKING * color_bad recolormark noeat .else * color_bad noeat .endif " \t#\n" color_color recolormark noeat # This is a dummy state that simply provides the highlighting color for the # standard color or attribute and jumps to colors_ws without consuming any # characters. :color_color StandardColor * colors_ws noeat # We have encountered something that is not recognized as a standard color or # attribute. Continue to highlight characters as Bad until we see whitespace, a # comment, or a newline. :color_bad Bad * color_bad " \t#\n" colors_ws noeat .endif ######### # State # ######### # Following a ':' should be a state definition. .ifdef STRINGS # New states should not appear between "[i]strings" and "done". .else # A state name must begin with an alpha character or an underscore. :state_first State * bad noeat "A-Za-z_" state # Subsequent characters in a state name must be alpha-numeric or underscores. :state State * bad noeat "A-Za-z0-9_" state " \t" need_state_color recolor=-1 # A state must have a color. :need_state_color Idle * state_color recolor=-1 " \t" need_state_color "#\n" bad noeat # Highlight any remaining characters until we see whitespace, a comment, or a # newline. :state_color Color * state_color " \t" context_ws recolor=-1 "#\n" comment_or_bad noeat # Following the state color, there might be one or more contexts. Loop over # whitespace until we find something else. :context_ws Idle * comment_or_bad noeat " \t" context_ws "A-Za-z_" context mark recolor=-1 buffer # Here we recognize the possible contexts. :context Idle * context_unknown noeat strings "comment" context_color "string" context_color done "A-Za-z0-9_" context # We encountered what looks like a context but is unrecognized as such. :context_unknown Idle .ifdef CHECKING * context_bad recolormark noeat .else * context_ws noeat .endif # We encountered a valid context. :context_color Keyword * context_ws noeat # We saw something that is not a valid context name with checking enabled. # Continue to highlight it as Bad until we see whitespace or a comment. :context_bad Bad * context_bad " \t#\n" context_ws noeat .endif ############## # Transition # ############## # A state transition starts with a '*', an '&', or a string. .ifdef STRINGS # Transitions must start with a string between "[i]strings" and "done". .else # We saw either a '*' or an '&'. Now we need the next state. :special_character Keyword * need_next_state noeat .endif # We are in a string. Continue until we see the close quote or a newline. # Highlight escaped characters within the string differently. They start with a # '\'. :string Literal string * string "\\" escape recolor=-1 "\"" need_next_state .ifdef CHECKING "\n" bad .else "\n" bad noeat .endif # Highlight an escaped character within a string. :escape Escape string * string # Loop over whitespace until we see the first character of the next state. :need_next_state Idle * bad noeat " \t" need_next_state "A-Za-z_" next_state recolor=-1 # Now we highlight the remainder of the next state. :next_state State * bad noeat "A-Za-z0-9_" next_state " \t" options_ws "#\n" comment noeat # Following the next state should be zero or more options. Loop over whitespace # until we find an option, comment, or newline. :options_ws Idle * option_bad recolor=-1 " \t" options_ws "A-Za-z_" option mark recolor=-1 buffer "#\n" comment noeat # Here we recognize the possible options. The strings and istrings options # cannot be used between "[i]strings" and "done". Since conditional directives # cannot be used between "[i]strings" and "done" either, the list must be # duplicated, once without and once with the strings and istrings options. :option Idle .ifdef STRINGS * option_unknown recolormark noeat strings "noeat" option_color "recolor" recolor_color "mark" option_color "markend" option_color "recolormark" option_color "buffer" option_color "save_c" option_color "save_s" option_color "hold" option_color "call" call_color "return" option_color "reset" option_color done .else * option_unknown recolormark noeat strings "noeat" option_color "recolor" recolor_color "mark" option_color "markend" option_color "recolormark" option_color "buffer" option_color "save_c" option_color "save_s" option_color "strings" strings_color "istrings" strings_color "hold" option_color "call" call_color "return" option_color "reset" option_color done .endif "A-Za-z0-9_" option # We encountered what looks like an option but is unrecognized as such. :option_unknown Idle .ifdef CHECKING * option_bad recolormark noeat .else * options_ws noeat .endif # We have encountered an option that does not take an argument. Highlight it # and continue to look for more options. :option_color Keyword * options_ws noeat .ifdef STRINGS # The strings and istrings options cannot be used between "[i]strings" and # "done". .else # The strings and istrings options are followed by a list of transitions. # Rather than duplicate all of the states that highlight transitions, we call # this entire file as a subroutine and use the STRINGS parameter to disable # everything else and enable the done keyword. We return to the comment_or_bad # state since we will return after seeing the done keyword, and nothing but a # comment should follow the done keyword. :strings_color Keyword * comment_or_bad noeat call=jsf(STRINGS) .endif # Highlight the recolor option. :recolor_color Keyword * recolor_equal noeat # The recolor option must be followed by an '='. Loop over whitespace until we # find one. :recolor_equal Idle .ifdef CHECKING * option_bad recolormark noeat .else * options_ws noeat .endif " \t" recolor_equal "=" recolor_minus mark # The recolor option takes an integer argument, and that integer must be # negative. Thus the '=' must be followed by a minus sign. Loop over # whitespace until we find one. :recolor_minus Idle .ifdef CHECKING * option_bad recolormark noeat .else * options_ws noeat .endif " \t" recolor_minus "-" recolor_amount_first mark recolor=-1 # The first digit of the argument to recolor must be non-zero. :recolor_amount_first Literal .ifdef CHECKING * option_bad recolormark noeat .else * options_ws recolormark noeat "0" option_bad recolormark noeat .endif "1-9" recolor_amount # Keep highlighting digits until we see something else. :recolor_amount Literal * option_bad recolormark recolor=-1 "0-9" recolor_amount " \t#\n" options_ws noeat # Highlight the call option. :call_color Keyword * call_equal noeat # The call option must be followed by an '='. Loop over whitespace until we # find one. :call_equal Idle .ifdef CHECKING * option_bad recolormark noeat .else * options_ws noeat .endif " \t" call_equal "=" call_file_or_dot mark # The first part of the argument to the call option is the name of the file # containing the subroutine or a '.', implying the current file. Loop over # whitespace until we see one of those two things. :call_file_or_dot Idle .ifdef CHECKING * option_bad recolormark noeat .else * options_ws noeat .endif " \t" call_file_or_dot "A-Za-z_" call_file mark recolor=-1 "." call_dot mark # Highlight the remainder of the file name. The file name can be followed by a # '.', which must then be followed by the name of a subroutine, or by a list of # parameters in parentheses. The '.', if present, cannot have whitespace on # either side. :call_file Subr .ifdef CHECKING * option_bad recolormark noeat .else * options_ws noeat .endif "A-Za-z0-9_" call_file "." call_dot mark recolor=-1 " \t(" call_open_paren noeat # We saw a '.'. The next character must start the name of a subroutine. :call_dot Idle .ifdef CHECKING * option_bad recolormark noeat .else * options_ws noeat .endif "(" call_dot_bad recolormark noeat "A-Za-z_" call_subr mark recolor=-1 # We have seen a dot followed by an open parenthesis. A dot must be followed by # a subroutine name. Highlight the dot as Bad. :call_dot_bad Bad * call_open_paren noeat # Highlight the remainder of the subroutine name. Following the subroutine name # must be a list of parameters in parentheses, possibly preceded by whitespace. :call_subr Subr .ifdef CHECKING * option_bad recolormark noeat .else * options_ws noeat .endif "A-Za-z0-9_" call_subr " \t(" call_open_paren noeat # Loop over whitespace until we find the open parenthesis. :call_open_paren Idle .ifdef CHECKING * option_bad recolormark noeat .else * options_ws noeat .endif " \t" call_open_paren "(" call_parameters_ws # The list of parameters is delimited by whitespace. Loop over whitespace until # we find either the beginning of a parameter or a close parenthesis. We should # not see a comment or newline since the list should be terminated by a close # parenthesis. :call_parameters_ws Idle * call_parameter_bad recolor=-1 " \t" call_parameters_ws "-" call_parameter_undef "A-Za-z_" call_parameter recolor=-1 ")" options_ws "#\n" bad noeat # We saw a "-". The next character should start the parameter being undefined. :call_parameter_undef Parameter * call_parameters_ws noeat "A-Za-z_" call_parameter recolor=-2 # Highlight the remainder of the parameter. :call_parameter Parameter * call_parameters_ws noeat "A-Za-z0-9_" call_parameter # We saw something that is not a valid parameter name. Continue to highlight it # as Bad until we see whitespace. :call_parameter_bad Bad * call_parameter_bad ") \t#\n" call_parameters_ws noeat # We saw something that is not a valid option name. Continue to highlight it as # Bad until we see whitespace or a comment. :option_bad Bad * option_bad " \t#\n" options_ws noeat ######## # Done # ######## .ifdef STRINGS # The special word, "done", can only be used after a strings or istrings option. # Recognize the done keyword. :special_word Idle * bad_line recolormark noeat strings "done" done_color done "A-Za-z0-9_" special_word # Highlight the done keyword and return to highlighting things normally, since # the list of strings has been terminated. :done_color Keyword * comment_or_bad return noeat .endif ################## # Comment or Bad # ################## # We have seen everything that should appear on the current line except an # optional comment. Loop over whitespace until we find a comment or newline. :comment_or_bad Idle * bad noeat " \t" comment_or_bad "#\n" comment noeat ########### # Comment # ########### # Continue to highlight the comment until the end of the line. :comment Comment comment * comment "\n" idle ####### # Bad # ####### .ifdef CHECKING # We have encountered incorrect syntax. Loop over whitespace until we see the # first visible character. Highlight that character and the rest of the line as # Bad. :bad Bad * bad_line " \t\n" bad .else # When not performing strict checking, don't go searching for the next visible # character to highlight as Bad. Simply highlight the rest of the line as Bad, # even if it is invisible. :bad Bad * bad_line noeat .endif # Continue to highlight everything as Bad until the end of the line. :bad_line Bad * bad_line "\n" idle ne-3.3.4/syntax/jsf_check.jsf000066400000000000000000000001111475116431000160670ustar00rootroot00000000000000# See jsf.jsf =Idle :initial Idle * initial noeat call=jsf(CHECKING) ne-3.3.4/syntax/json.jsf000066400000000000000000000070051475116431000151320ustar00rootroot00000000000000# JSON highlighter per json.org # Written by Rebecca Turner (@ReBecaOrg) # Define colors =Idle =Comma =PairSep =Bracket magenta =Brace magenta =Number cyan =String cyan =StringEscape cyan bold =Boolean cyan =Null cyan =Bad red bold inverse # Syntax errors detected by the highlighter =ERROR bold yellow bg_red # Errors in the highlighter itself :json Idle * end noeat call=.value() " \t\n" json :end Idle * endBAD noeat " \t\n" end :endBAD Bad * end .subr value :value Idle * valueBAD noeat "\"" end noeat call=.string() "-0-9" end noeat call=.number() "tfn" end noeat call=.bareword() "{" end noeat call=.object() "[" end noeat call=.array() :valueBAD Bad * value :end Idle * NULL noeat return .end .subr object :object Brace * objectBAD noeat "{" maybeempty :objectBAD ERROR * end :maybeempty Brace * key noeat " \t\n" maybeempty "}" end recolor=-1 :key Idle * keyBAD noeat "\"" pairsep noeat call=.string() " \t\n" key :keyBAD Bad * key :pairsep PairSep * pairsepBAD noeat ":" value " \t\n" pairsep :pairsepBAD Bad * pairsep :value Idle * nextpair noeat call=.value() " \t\n" value :nextpair Comma * nextpairBAD noeat "}" end recolor=-1 "," key " \t\n" nextpair :nextpairBAD Bad * nextpair :end Brace * NULL noeat return .end .subr array :array Bracket * arrayBAD noeat "[" maybeempty :arrayBAD ERROR * end :maybeempty Bracket * value noeat " \t\n" maybeempty "]" end recolor=-1 :value Idle * nextvalue noeat call=.value() " \t\n" value :nextvalue Comma * nextvalueBAD noeat "]" end recolor=-1 "," value " \t\n" nextvalue :nextvalueBAD Bad * nextvalue :end Bracket * NULL noeat return .end .subr string :string String * stringBAD noeat "\"" body :stringBAD ERROR * end :body String * body "\"" end "\\" escape recolor=-1 :escape StringEscape * escapeBAD recolor=-2 noeat "\"/bfnrt\\" body "u" unicode1 :escapeBAD Bad * body :unicode1 StringEscape * unicodeBAD recolor=-3 noeat "0-9a-fA-F" unicode2 :unicode2 StringEscape * unicodeBAD recolor=-4 noeat "0-9a-fA-F" unicode3 :unicode3 StringEscape * unicodeBAD recolor=-5 noeat "0-9a-fA-F" unicode4 :unicode4 StringEscape * unicodeBAD recolor=-6 noeat "0-9a-fA-F" body :unicodeBAD Bad * body :end Idle * NULL noeat return .end .subr bareword :bareword Idle * body noeat mark buffer :body Bad * end noeat markend strings "true" boolean "false" boolean "null" null done "truefalsn" body :boolean Boolean * end noeat :null Null * end noeat :end Idle * NULL noeat return .end .subr number :number Number * numberBAD "0-9" numberA noeat "-" numberA :numberBAD ERROR * end :numberA Number "0" decimalpoint "1-9" integer :integer Number * end noeat "0-9" integer "." decimalpart "eE" exponentpart :decimalpoint Number * end noeat "0-9" decimalpointBAD "eE" exponentpart "." decimalpart :decimalpointBAD Bad * end :decimalpart Number * decimalpartBAD "0-9" decimalpartA :decimalpartBAD Bad * end :decimalpartA Number * end noeat "0-9" decimalpartA "eE" exponentpart :exponentpart Number * exponentpartBAD "-+" exponentpartA "0-9" exponentpartB :exponentpartBAD Bad * end :exponentpartA Number * exponentpartBAD "0-9" exponentpartB :exponentpartB Number * end noeat "0-9" exponentpartB :end Idle * NULL noeat return .end ne-3.3.4/syntax/lisp.jsf000066400000000000000000000005541475116431000151320ustar00rootroot00000000000000# JOE syntax highlight file for LISP # Needs lots of work... =Idle =Comment green =String cyan =Escape bold cyan :idle Idle * idle ";" comment recolor=-1 "\"" string recolor=-1 :comment Comment * comment "\n" idle :string String * string "\"" idle "\\" string_escape recolor=-1 :string_escape Escape * string "\n" string recolor=-2 ne-3.3.4/syntax/lua.jsf000066400000000000000000000122171475116431000147430ustar00rootroot00000000000000# JOE syntax highlight file for LUA =Idle =Bad bold red =Comment green =Constant cyan =Escape bold cyan =Keyword bold :idle Idle * idle "0" first_digit recolor=-1 "1-9" decimal recolor=-1 "." maybe_float "\"" string recolor=-1 "'" char recolor=-1 "a-zA-Z_" ident buffer "-" maybe_comment "[" maybe_lua_string :maybe_lua_string Idle * idle noeat "[" lua_string_0 recolor=-2 "=" maybe_lua_1 :maybe_lua_1 Idle * idle noeat "[" lua_string_1 recolor=-3 "=" maybe_lua_2 :maybe_lua_2 Idle * idle noeat "[" lua_string_2 recolor=-4 "=" maybe_lua_3 :maybe_lua_3 Idle * idle noeat "[" lua_string_3 recolor=-5 :lua_string_0 Constant * lua_string_0 "]" lua_string_0_maybe_done :lua_string_0_maybe_done Constant * lua_string_0 noeat "]" idle :lua_string_1 Constant * lua_string_1 "]" lua_string_1_maybe_donea :lua_string_1_maybe_donea Constant * lua_string_1 noeat "=" lua_string_1_maybe_done :lua_string_1_maybe_done Constant * lua_string_1 noeat "]" idle :lua_string_2 Constant * lua_string_2 "]" lua_string_2_maybe_donea :lua_string_2_maybe_donea Constant * lua_string_2 noeat "=" lua_string_2_maybe_doneb :lua_string_2_maybe_doneb Constant * lua_string_2 noeat "=" lua_string_2_maybe_done :lua_string_2_maybe_done Constant * lua_string_2 noeat "]" idle :lua_string_3 Constant * lua_string_3 "]" lua_string_3_maybe_donea :lua_string_3_maybe_donea Constant * lua_string_3 noeat "=" lua_string_3_maybe_doneb :lua_string_3_maybe_doneb Constant * lua_string_3 noeat "=" lua_string_3_maybe_donec :lua_string_3_maybe_donec Constant * lua_string_3 noeat "=" lua_string_3_maybe_done :lua_string_3_maybe_done Constant * lua_string_3 noeat "]" idle :maybe_comment Idle * idle noeat "-" maybe_long_comment recolor=-2 :maybe_long_comment Comment * line_comment noeat "[" maybe_long_comment_0 :maybe_long_comment_0 Comment * line_comment noeat "=" maybe_long_comment_1 "[" long_comment_0 :maybe_long_comment_1 Comment * line_comment noeat "=" maybe_long_comment_2 "[" long_comment_1 :maybe_long_comment_2 Comment * line_comment noeat "=" maybe_long_comment_3 "[" long_comment_2 :maybe_long_comment_3 Comment * line_comment noeat "[" long_comment_3 :long_comment_0 Comment * long_comment_0 "]" maybe_done_comment_0 :maybe_done_comment_0 Comment * long_comment_0 noeat "]" idle :long_comment_1 Comment * long_comment_1 "]" maybe_done_comment_1a :maybe_done_comment_1a Comment * long_comment_1 noeat "=" maybe_done_comment_1 :maybe_done_comment_1 Comment * long_comment_1 noeat "]" idle :long_comment_2 Comment * long_comment_2 "]" maybe_done_comment_2a :maybe_done_comment_2a Comment * long_comment_2 noeat "=" maybe_done_comment_2b :maybe_done_comment_2b Comment * long_comment_2 noeat "=" maybe_done_comment_2 :maybe_done_comment_2 Comment * long_comment_2 noeat "]" idle :long_comment_3 Comment * long_comment_3 "]" maybe_done_comment_3a :maybe_done_comment_3a Comment * long_comment_3 noeat "=" maybe_done_comment_3b :maybe_done_comment_3b Comment * long_comment_3 noeat "=" maybe_done_comment_3c :maybe_done_comment_3c Comment * long_comment_3 noeat "=" maybe_done_comment_3 :maybe_done_comment_3 Comment * long_comment_2 noeat "]" idle :line_comment Comment * line_comment "\n" idle :first_digit Constant * idle noeat "xX" hex "." float "eE" epart "0-7" octal "89" bad_number recolor=-1 :bad_number Bad * idle noeat "0-9" bad_number :octal Constant * idle noeat "0-7" octal "89" bad_number recolor=-1 :hex Constant * idle noeat "0-9A-Fa-f" hex :decimal Constant * idle noeat "0-9" decimal "eE" epart "." float :maybe_float Constant * idle recolor=-2 noeat "0-9" float recolor=-2 :float Constant * idle noeat "eE" epart "0-9" float :epart Constant * idle noeat "0-9+\-" enum :enum Constant * idle noeat "0-9" enum :string Constant * string "\"" idle "\\" string_escape recolor=-1 "%" string_control recolor=-1 :string_escape Escape * string "x" string_hex1 "0-7" string_octal2 "\n" string recolor=-2 :string_hex1 Escape * string noeat "0-9a-fA-F" string_hex2 :string_hex2 Escape * string noeat "0-9a-fA-F" string :string_octal2 Escape * string noeat "0-7" string_octal3 :string_octal3 Escape * string noeat "0-7" string :string_control Escape * string "\"" string noeat "\n" reset "0-9.\-+ #hjILtz$" string_control :char Constant * char "\n" reset "'" idle "\\" char_escape recolor=-1 :char_escape Escape * char "x" char_hex1 "0-7" char_octal2 "\n" char recolor=-2 :char_hex1 Escape * char noeat "0-9a-fA-F" char_hex2 :char_hex2 Escape * char noeat "0-9a-fA-F" char :char_octal2 Escape * char noeat "0-7" char_octal3 :char_octal3 Escape * char noeat "0-7" char :ident Idle * idle noeat strings "and" kw "end" kw "in" kw "repeat" kw "break" kw "false" kw "local" kw "return" kw "do" kw "for" kw "nil" kw "then" kw "else" kw "function" kw "not" kw "true" kw "elseif" kw "if" kw "or" kw "until" kw "while" kw done "a-zA-Z0-9_" ident :kw Keyword * idle noeat ne-3.3.4/syntax/m4.jsf000066400000000000000000000034371475116431000145060ustar00rootroot00000000000000# m4 =Idle =Comment green =Constant cyan =Var magenta =Brace bold magenta =Kw bold :start Idle * start noeat call=.m4() .subr m4 .ifdef quote :idle Constant .else :idle Idle .endif * idle "[" idle recolor=-1 call=.m4(quote -brace) # "[" idle recolor=-1 call=.quote() "a-zA-Z_" ident buffer mark .ifdef quote "]" idle return .endif .ifdef brace ")" endbrace recolor=-1 .endif :quote Constant * quote "]" idle :endbrace Brace * idle noeat return .ifdef quote :ident Constant .else :ident Idle .endif * maybe_macro noeat strings "builtin" maybe_kw "changecom" maybe_kw "changequote" maybe_kw "changeword" maybe_kw "debugfile" maybe_kw "debugmode" maybe_kw "decr" maybe_kw "define" maybe_kw "defn" maybe_kw "divert" maybe_kw "divnum" maybe_kw "dnl" comment "dumpdef" maybe_kw "errprint" maybe_kw "esyscmd" maybe_kw "eval" maybe_kw "file" maybe_kw "format" maybe_kw "ifdef" maybe_kw "ifelse" maybe_kw "include" maybe_kw "incr" maybe_kw "index" maybe_kw "indir" maybe_kw "len" maybe_kw "line" maybe_kw "m4exit" maybe_kw "m4wrap" maybe_kw "maketemp" maybe_kw "patsubst" maybe_kw "popdef" maybe_kw "pushdef" maybe_kw "regexp" maybe_kw "shift" maybe_kw "sinclude" maybe_kw "substr" maybe_kw "syscmd" maybe_kw "sysval" maybe_kw "traceoff" maybe_kw "traceon" maybe_kw "translit" maybe_kw "undefine" maybe_kw "undivert" maybe_kw done "a-zA-Z0-9_" ident :maybe_macro Idle * idle noeat "(" macro recolormark noeat :maybe_kw Idle * idle noeat "(" kw recolormark noeat :kw Kw * idle noeat "(" macro noeat :comment Comment * comment "\n" idle :macro Var * idle noeat "(" brace recolor=-1 :brace Brace * idle noeat call=.m4(brace -quote) .end .subr quote :idle Constant * idle "]" idle return "[" idle call=.quote() .end ne-3.3.4/syntax/mail.jsf000066400000000000000000000054521475116431000151070ustar00rootroot00000000000000# JOE syntax highlight file for typical UNIX mail files # 2004-04-25 Tomas Szepe # Improved to handle quote characters commonly seen on Usenet, and # highlighting of the more frequently edited mail and news headers, with # some sanity checking of the same. # 2004-06-26 Jeff Hurwit - =Idle =Head bold =HdTo red bg_white =HdFrom bold red =HdSbj bold magenta =HdDate bold =HdRRT black bg_white =HdGrp bold white bg_red =HdFuT bold red bg_yellow =HdBad bold red =Quot1 green bg_white =Quot2 bold blue bg_white =Sign bold magenta # Start in headers. We know we're out at the first blank line. :first Idle * hbol noeat "\n" newline ">|:~}#]" newline noeat # Require header # "\n" nohead :nohead HdBad * nohead :hbol Idle * hbad recolor=-1 "\n" newline "A-Z" bufhdrs buffer "-" newline noeat :hbad HdBad * hbad "\n" hbol :hlbad HdBad * hbol :bufhdrs Idle * hbad recolor=-1 "\n" hlbad noeat recolor=-2 "-_a-zA-Z0-9" bufhdrs ":" headers hold :headers Idle * hbad recolor=-1 " \n" hnocolor noeat strings "To" hsto recolor=-2 "Cc" hsto recolor=-2 "Bcc" hsto recolor=-2 "From" hsfrom recolor=-2 "Subject" hssbj recolor=-2 "Date" hsdate recolor=-2 "Return-Receipt-To" hsrrecp recolor=-2 "Newsgroups" hsgroups recolor=-2 "Followup-To" hsfolup recolor=-2 done :hnocolor Idle * hnocolor "\n" cont_nocolor :cont_nocolor Idle * hbol noeat " \t" hnocolor recolor=-1 :hsto Head * hto "\n" hbol :hto HdTo * hto "\n" cont_to :cont_to HdTo * hbol noeat " \t" hto recolor=-1 :hsfrom Head * hfrom "\n" hbol :hfrom HdFrom * hfrom "\n" hbol :hssbj Head * hsbj "\n" hbol :hsbj HdSbj * hsbj "\n" cont_sbj :cont_sbj HdSbj * hbol noeat " \t" hsbj recolor=-1 :hsdate Head * hdate "\n" hbol :hdate HdDate * hdate "\n" hbol :hsrrecp Head * hrrecp "\n" hbol :hrrecp HdRRT * hrrecp "\n" hbol :hsgroups Head * hgroups "\n" hbol :hgroups HdGrp * hgroups "\n" hbol :hsfolup Head * hfolup "\n" hbol :hfolup HdFuT * hfolup "\n" hbol # body of the message :newline Idle * knocolor "\n" newline ">|:~}#]" q1 recolor=-1 "-" maybesign1 recolor=-1 :maybesign1 Quot1 * q1 " \t" q1 "\n" newline recolor=-1 ">|:~}#]" q2 recolor=-1 "-" maybesign2 recolor=-1 :maybesign2 Quot2 * q2 "\t" q2 "\n" newline recolor=-1 " " maybesign3 recolor=-1 ">|:~}#]" q1 recolor=-1 "-" knocolor recolor=-3 :maybesign3 Quot2 * q2 " \t" q2 "\n" sign recolor=-4 "->|:~}#]" q1 recolor=-1 :sign Sign * sign :q1 Quot1 * kq1 "\n" newline " \t" q1 "->|:~}#]" q2 recolor=-1 :q2 Quot2 * kq2 "\n" newline " \t" q2 "->|:~}#]" q1 recolor=-1 :knocolor Idle * knocolor "\n" newline :kq1 Quot1 * kq1 "\n" newline :kq2 Quot2 * kq2 "\n" newline ne-3.3.4/syntax/mason.jsf000066400000000000000000000002051475116431000152710ustar00rootroot00000000000000# JOE syntax file for Mason =Idle # Call HTML with mason flag. It will call perl. :idle Idle * idle noeat call=html.html(mason) ne-3.3.4/syntax/matlab.jsf000066400000000000000000000022171475116431000154210ustar00rootroot00000000000000# joe Matlab syntax highlighting # Created by Gustav Stenberg 2007-10-30 # =Idle =Comment green =Keyword blue =String magenta =Secondary magenta =Error red =Command yellow :idle Idle * idle "%" comment recolor=-1 "\'" string mark recolor=-1 "a-zA-Z_." ident buffer "!" command recolor=-1 "([{" parent buffer :command Command * command "\n" idle :comment Comment * comment "\n" idle :keyword Keyword * idle noeat :keywordc Keyword * comment "\n" idle :parent Idle * parent ")]}" idle :string Error * string "\'" stringdone recolormark recolor=-1 "\n" idle :stringdone String * idle recolor=-1 :secondary Secondary * idle noeat "a-zA-Z_0-9. " secondary "\n;:=!\"\'+-,*(){}[]&\\\|/~" idle recolor=-1 :ident Idle * secondary noeat strings "..." keywordc "break" keyword "case" keyword "catch" keyword "continue" keyword "else" keyword "elseif" keyword "end" keyword "for" keyword "function" keyword "global" keyword "if" keyword "otherwise" keyword "persistent" keyword "return" keyword "switch" keyword "try" keyword "while" keyword done "a-zA-Z0-9_." ident ne-3.3.4/syntax/md.jsf000066400000000000000000000042571475116431000145670ustar00rootroot00000000000000# JOE syntax highlight file for Markdown # by Christian Nicolai (http://mycrobase.de) # And yes, this *is* a joke :p # bold parsing is not that perfect since this works: **bold__ =Idle =Tag green =Escape bold =Bad bold red =Bold bold =Headline bold yellow =Quote green =Code green =List yellow =LinkDesc green =Link blue =Rule inverse :line_start Idle * idle noeat "#" headline_prefix recolor=-1 ">" quote recolor=-1 " " maybe_code1 "\t" code "-*" maybe_list :idle Idle * idle "\n" line_start "<" tag recolor=-1 "\\" escape recolor=-1 "*_" maybe_bold1 "[" maybe_link_desc1 "`" backtick buffer noeat :headline_prefix Idle * headline "#" headline_prefix :headline Headline * headline "\n" line_start :quote Quote * quote "\n" line_start :maybe_code1 Idle * idle " " maybe_code2 :maybe_code2 Idle * idle " " maybe_code3 :maybe_code3 Idle * idle " " code recolor=-4 :code Code * code "\n" line_start :maybe_list Idle * idle " " list "-*" maybe_rule1 :list List * list "\n" line_start :maybe_rule1 Idle * idle "-*" rule recolor=-3 :rule Rule * rule "\n" line_start :tag Tag * idle "a-z0-9 /" tag ">" idle # do escaping of *_ and so on :escape Escape * idle :maybe_bold1 Idle * idle noeat "*_" bold recolor=-2 :bold Bold * bold "\n" line_start # end if we reach newline during bold "*_" maybe_end_bold :maybe_end_bold Bold * bold "*_" end_bold :end_bold Bold * idle noeat :maybe_link_desc1 Idle * maybe_link_desc mark :maybe_link_desc Idle * maybe_link_desc "]" maybe_end_link_desc markend :maybe_end_link_desc Idle * idle "(" link_inline1 recolormark "[" link_ref1 recolormark :link_inline1 LinkDesc * link_inline noeat :link_inline Link * link_inline ")" link_end noeat :link_ref1 LinkDesc * link_ref noeat :link_ref Link * link_ref "]" link_end noeat :link_end Idle * idle :backtick Quote * backtick_body save_s noeat "`" backtick :backtick_body Code * backtick_body "`" backtick_end_maybe buffer :backtick_end_maybe Code * backtick_body strings "&" backtick_end done "`" backtick_end_maybe :backtick_end Quote * idle noeat ne-3.3.4/syntax/ocaml.jsf000066400000000000000000000220521475116431000152530ustar00rootroot00000000000000# JOE syntax highlight file for OCaml # A (deterministic) state machine which performs lexical analysis of OCaml. # (This is the "assembly language" of syntax highlighting. A separate # program could be used to convert a regular expression NFA syntax into this # format). # Each state begins with ': ' # is the color used for characters eaten by the state # (really a symbol for a user definable color). # The first state defined is the initial state. # Within a state, define transitions (jumps) to other states. Each # jump has the form: [