ne-3.0.1/version.pl0000775000175200017510000000502412517166512013012 0ustar vignaprof#!/usr/bin/perl -w # # Usage: $0 [VERSION=XXXXX] # # version.pl creates/updates doc/version.texinfo and src/version.h # with the specified version or the version define from the top level # makefile. use strict; $| = 1; my $version; if ( ! defined $ARGV[0] || $ARGV[0] !~ m/^VERSION=(.+)/ ) { if ( open MAKEFILE, "makefile" ) { while () { if ( m/^\s*VERSION\s*=\s*([^\s]+)/ ) { $version = $1; print "$0: setting version to '$version' from top level makefile.\n"; last; } } close MAKEFILE; } } else { $version = $1; } unless ( $version ) { print "$0: could not determine version.\n"; exit 0; } my $year = 1900 + (localtime(time()))[5]; my $month = substr("00" . (1+(localtime(time()))[4]), -2); my $date = substr("00" . ( (localtime(time()))[3]), -2); open NE_VERSION_TEXINFO, ">doc/version.texinfo"; print NE_VERSION_TEXINFO qq[\@ignore This file was automatically generated by $0. \@end ignore \@set VERSION $version \@set RELEASE_YEAR $year \@set RELEASE_MONTH $month \@set RELEASE_DATE $date \@set DATE (\@value{RELEASE_YEAR}-\@value{RELEASE_MONTH}-\@value{RELEASE_DATE}) \@set PROGRAM_NAME ne, the nice editor \@set ABOUT_MSG \@value{PROGRAM_NAME} \@value{VERSION}. \@value{DATE} ]; close NE_VERSION_TEXINFO; open NE_VERSION_H, ">src/version.h"; print NE_VERSION_H qq[/* This file was automatically generated by $0. */ /* String definitions for version and 'About...' messages. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-$year Todd M. Lewis and Sebastiano Vigna This file is part of ne, the nice editor. 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 331, Boston, MA 02111-1307, USA. */ #define DATE "($year-$month-$date)" #define VERSION "$version" #define PROGRAM_NAME "ne, the nice editor" #define ABOUT_MSG PROGRAM_NAME " " VERSION ". " DATE #define VERSION_STRING "@(#)"ABOUT_MSG ]; close NE_VERSION_H; ne-3.0.1/makefile0000664000175200017510000000712212542017427012464 0ustar vignaprof# Makefile for ne's distribution archive. VERSION=3.0.1 # If you change this prefix, you can call "make 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 . .PHONY: install PREFIX=/usr/local PROGRAM = ne build: ( cd src; make clean; make NE_GLOBAL_DIR=$(PREFIX)/share/ne ) version: ./version.pl VERSION=$(VERSION) source: version ( cd doc; make ) ( 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 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)/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/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) cygwin: ( 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 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 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/ne.pdf doc/html doc/ne.txt doc/default.* README.md COPYING NEWS CHANGES $(DESTDIR)$(PREFIX)/share/doc/ne 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 package: # To create a Mac package, compile the editor, # give a "make install" as root (delete INSTALL to make it work) and run this target. # Finally, create using /Developer/Applications/Utilities/PackageMaker a package whose only content is # /tmp/package, build it, and use Disk Utility to create a (properly named) small disk image # containing the package. Finally, use the right button and "Convert" to store the image in compressed form. -rm -fr /tmp/package mkdir -p /tmp/package/usr/local/bin mkdir -p /tmp/package/usr/local/share/doc mkdir -p /tmp/package/usr/local/share/info mkdir -p /tmp/package/usr/local/share/man/man1 cp /usr/local/bin/ne /tmp/package/usr/local/bin cp -pr /usr/local/share/doc/ne /tmp/package/usr/local/share/doc/ cp -pr /usr/local/share/ne /tmp/package/usr/local/share/ cp /usr/local/share/info/ne.info.gz /tmp/package/usr/local/share/info/ cp /usr/local/share/man/man1/ne.1 /tmp/package/usr/local/share/man/man1/ clean: -rm -f ne-*.tar* really-clean: clean (cd src; make clean) (cd doc; make clean) ne-3.0.1/COPYING0000664000175200017510000010451312517166512012023 0ustar vignaprof 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.0.1/INSTALL0000664000175200017510000001000612517166512012012 0ustar vignaprof1. To build and install into /usr/local: # unpack distribution cd ne-x.y.z make install Note that you must have adequate privileges. 2. To build and install into some alternative location (for example, into your own `~/opt`), you must set the PREFIX variable: make PREFIX=/home//opt install 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) and "install" (build and installs ne). The PREFIX make variable (see above) decides where ne will be installed and which will be its global directory. 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 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=" (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 (vigna@di.unimi.it) Todd (Todd_Lewis@unc.edu) ne-3.0.1/README.md0000664000175200017510000000206512525431422012240 0ustar vignaprofWelcome 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 for some highlights of ne's features. ne is distributed under the GNU Public License (see COPYING). The INSTALL 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.0.1/NEWS0000664000175200017510000001304312540607121011454 0ustar vignaprof(This file, NEWS, lists new features and enhancements. See CHANGES for fixes.) 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.0.1/CHANGES0000664000175200017510000001011212541614145011747 0ustar vignaprof(This file, CHANGES, lists fixes. See NEWS for new features and enhancements.) 3.0.1 * 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.0.1/src/actions.c0000664000175200017510000014161512537653642013376 0ustar vignaprof/* Main command processing loop. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2015 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 "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) /* This is the dispatcher of all actions that have some effect on the text. The arguments are an action to be executed, a possible integer parameter and 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?). */ int do_action(buffer *b, action a, int64_t c, char *p) { static char msg[MAX_MESSAGE_SIZE]; line_desc *next_ld; HIGHLIGHT_STATE next_line_state; int error = OK, recording; int64_t col; char *q; 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); stop = false; if (b->recording) record_action(b->cur_macro, a, c, p, verbose_macros); 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: NORMALIZE(c); for(int64_t i = 0; i < c && !(error = search_word(b, 1)) && !stop; i++); return stop ? STOPPED : error; case PREVWORD_A: NORMALIZE(c); for(int64_t i = 0; i < c && !(error = search_word(b, -1)) && !stop; i++); return stop ? STOPPED : error; case DELETENEXTWORD_A: case DELETEPREVWORD_A: recording = b->recording; b->recording = 0; NORMALIZE(c); delay_update(); start_undo_chain(b); for(int64_t i = 0; i < c && !error && !stop; i++) { 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); b->marking = 1; b->mark_is_vertical = 0; b->block_start_line = b->cur_line; b->block_start_pos = b->cur_pos; if(!(error = do_action(b, a == DELETENEXTWORD_A ? NEXTWORD_A : PREVWORD_A, 1, NULL))) { if (!(error = erase_block(b))) update_window_lines(b, b->cur_y, ne_lines - 2, false); } 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; } end_undo_chain(b); b->recording = recording; return stop ? STOPPED : error; case MOVEEOW_A: move_to_eow(b); return OK; case MOVEINCUP_A: move_inc_up(b); return OK; case MOVEINCDOWN_A: move_inc_down(b); 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("All BookMarks cleared."); free(p); return OK; } /* Intentionally fall through to regular BOOKMARK parm parsing. */ case SETBOOKMARK_A: case GOTOBOOKMARK_A: { bool relative = false; /* *p can be "", "-", "0".."9", "+1","-1", for which, respectively, */ /* c becomes 0, AB, 0 .. 9, next,prev. Anything else is out of range. */ 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<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 c = *p - '0'; } else c = -1; } else c = 0; free(p); if (c < 0 || c > AUTO_BOOKMARK) 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", c<=MAX_USER_BOOKMARK?'0'+(int)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", c<=MAX_USER_BOOKMARK?'0'+(int)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(b, b->bookmark[c].line); goto_pos(b, 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<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("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= b->recording; b->recording = 0; error = ERROR; if (p || (p = request_string("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); } b->recording = 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("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 = b->recording; b->recording = 0; 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); b->recording = recording; return error; case INSERTCHAR_A: { static int last_inserted_char = ' '; int deleted_char, old_char; if (b->opt.read_only) return FILE_IS_READ_ONLY; if (c < 0 && (c = request_number("Char Code", last_inserted_char))<0) 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; /* Freeze the line attributes before any real update. */ if (b->syn && b->attr_len < 0) freeze_attributes(b, b->cur_line_desc); 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_y, true, 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_y, true, false); assert_buffer_content(b); } else { /* Fixes in case of word wrapping. */ const bool wont_scroll = b->win_x == 0; int64_t a = 0; if (b->syn) update_line(b, b->cur_y, false, true); else update_partial_line(b, 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, 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_y, false, false); else scroll_window(b, 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 FILE_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. */ do char_left(b); while((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] == ' ')); } 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) { freeze_attributes(b, b->cur_line_desc); memmove(b->attr_buf + b->cur_pos + 1, b->attr_buf + b->cur_pos + col, b->attr_len - (b->cur_pos + col)); b->attr_buf[b->cur_pos] = -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->syn) update_partial_line(b, b->cur_y, b->cur_x, true, true); } } 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) freeze_attributes(b, b->cur_line_desc); } if (b->syn && b->attr_len < 0) freeze_attributes(b, b->cur_line_desc); 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_pos] : 0; 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_y, true, 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; if (b->syn) { b->next_state = parse(b->syn, b->cur_line_desc, b->cur_line_desc->highlight_state, b->encoding == ENC_UTF8); update_line(b, b->cur_y, false, true); } else update_partial_line(b, b->cur_y, b->cur_x, true, false); if (b->cur_y < ne_lines - 2) scroll_window(b, 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 FILE_IS_READ_ONLY; NORMALIZE(c); for(int64_t i = 0; i < c && !stop; i++) { if (b->syn && b->attr_len < 0) freeze_attributes(b, b->cur_line_desc); 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) { 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) { freeze_attributes(b, b->cur_line_desc); ((line_desc *)b->cur_line_desc->ld_node.next)->highlight_state = b->next_state; } assert(b->cur_line_desc->ld_node.next->next != NULL); 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); if (a != -1) goto_pos(b, a); } else { int64_t a = -1; if (b->syn) update_line(b, b->cur_y, false, true); else update_partial_line(b, b->cur_y, b->cur_x, false, 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) ((line_desc *)b->cur_line_desc->ld_node.next)->highlight_state = b->next_state; assert(b->cur_line_desc->ld_node.next->next != NULL); 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); if (a != -1) goto_pos(b, a); if (b->cur_line == b->num_lines - 1) update_line(b, b->cur_y, false, false); else scroll_window(b, b->cur_y, 1); need_attr_update = true; } } } return stop ? STOPPED : 0; case DELETELINE_A: if (b->opt.read_only) return FILE_IS_READ_ONLY; NORMALIZE(c); col = b->win_x + b->cur_x; 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_y, -1); } end_undo_chain(b); if (b->syn) { update_line(b, b->cur_y, false, false); need_attr_update = true; } resync_pos(b); goto_column(b, col); return stop ? STOPPED : error; case UNDELLINE_A: if (b->opt.read_only) return FILE_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 && b->syn && b->attr_len < 0) freeze_attributes(b, b->cur_line_desc); 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_pos. We perform a differential update so that if we undelete in the middle of a line we avoid to rewrite the part up to b->cur_pos. */ b->attr_len = b->cur_pos; update_line(b, b->cur_y, false, true); next_line_state = b->next_state; } else update_partial_line(b, b->cur_y, b->cur_x, false, false); } if (b->syn) { assert(b->cur_line_desc->ld_node.next->next != NULL); /* For each undeletion, we must poke into the next line its correct initial state. */ ((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, 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 FILE_IS_READ_ONLY; if (b->syn && b->attr_len < 0) freeze_attributes(b, b->cur_line_desc); delete_to_eol(b, b->cur_line_desc, b->cur_line, b->cur_pos); if (b->syn) update_line(b, b->cur_y, false, true); else update_partial_line(b, b->cur_y, b->cur_x, false, false); need_attr_update = true; return OK; case SAVE_A: p = str_dup(b->filename); case SAVEAS_A: if (p || (q = p = request_file(b, "Filename", b->filename))) { print_info(SAVING); if (a==SAVE_A && buffer_file_modified(b,p) && !request_response(b, info_msg[FILE_HAS_BEEN_MODIFIED], false)) return DOCUMENT_NOT_SAVED; error = save_buffer_to_file(b, p); 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 && extension(p)) { load_syntax_by_name(b, extension(p)); load_auto_prefs(b, extension(p)); reset_window(); } print_info(SAVED); } else { free(p); return ERROR; } } b->undo.last_save_step = b->undo.cur_step; return OK; case KEYCODE_A: print_message(info_msg[PRESS_A_KEY]); c = get_key_code(); 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 OPENNEW_A: b = new_buffer(); reset_window(); case OPEN_A: if ((b->is_modified) && !request_response(b, info_msg[THIS_DOCUMENT_NOT_SAVED], false)) { if (a == OPENNEW_A) do_action(b, CLOSEDOC_A, 1, NULL); return ERROR; } if (p || (p = request_file(b, "Filename", b->filename))) { static bool dprompt = 0; /* Set to true if we ever respond 'yes' to the prompt. */ 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_IS_MIGRATED && error != FILE_IS_DIRECTORY && error != IO_ERROR && error != FILE_IS_TOO_LARGE && error != OUT_OF_MEMORY) { change_filename(b, p); b->syn = NULL; /* So that autoprefs will load the right syntax. */ if (b->opt.auto_prefs && extension(p)) { if (b->allocated_chars - b->free_chars <= MAX_SYNTAX_SIZE) { load_auto_prefs(b, extension(p)); reset_syntax_states(b); } else if (error == OK) error = FILE_TOO_LARGE_SYNTAX_HIGHLIGHTING_DISABLED; } } print_error(error); reset_window(); return OK; } free(p); } if (a == OPENNEW_A) do_action(b, CLOSEDOC_A, 1, NULL); return ERROR; case ABOUT_A: about(1); c = get_key_code(); about(0); return OK; case REFRESH_A: clear_entire_screen(); ttysize(); keep_cursor_on_screen(cur_buffer); reset_window(); return OK; case FIND_A: case FINDREGEXP_A: if (p || (p = request_string(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)); } 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 FILE_IS_READ_ONLY; } if ((q = b->find_string) || (q = request_string(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->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'))) { 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; if (b->last_was_regexp) error = replace_regexp(b, p); else error = replace(b, strlen(b->find_string), p); if (!error) { update_line(b, b->cur_y, false, false); if (b->syn) { need_attr_update = true; update_syntax_states(b, b->cur_y, b->cur_line_desc, NULL); } num_replace++; if (last_replace_empty_match) if (b->opt.search_back) error = char_left(cur_buffer); else error = char_right(cur_buffer); } 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.", num_replace, num_replace > 1 ? "s" : ""); print_message(msg); } if (stop) return STOPPED; 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 (b->opt.read_only && b->last_was_replace) return FILE_IS_READ_ONLY; if (!b->find_string) return NO_SEARCH_STRING; else if ((b->last_was_replace) && !b->replace_string) return NO_REPLACE_STRING; else { int return_code = 0; 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); for(int64_t i = 0; i < c; i++) { if (!print_error((b->last_was_regexp ? find_regexp : find)(b, NULL, !b->last_was_replace))) { if (b->last_was_replace) { 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) { update_line(b, b->cur_y, false, false); if (b->syn) { need_attr_update = true; update_syntax_states(b, b->cur_y, b->cur_line_desc, NULL); } if (last_replace_empty_match) if (b->opt.search_back) error = char_left(cur_buffer); else error = char_right(cur_buffer); } if (print_error(error)) { return_code = ERROR; break; } } } else { return_code = ERROR; break; } } return return_code; } return 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("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("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 (c < 0 && (c = request_number("Turbo Threshold", turbo))<0) return NUMERIC_ERROR(c); turbo = c; return OK; case CLIPNUMBER_A: if (c < 0 && (c = request_number("Clip Number", b->opt.cur_clip))<0) return NUMERIC_ERROR(c); b->opt.cur_clip = c; return OK; case RIGHTMARGIN_A: if (c < 0 && (c = request_number("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 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); return OK; } else return UNDO_NOT_ENABLED; case RECORD_A: recording = b->recording; SET_USER_FLAG(b, c, recording); if (b->recording && !recording) { b->cur_macro = reset_stream(b->cur_macro); print_message(info_msg[STARTING_MACRO_RECORDING]); } else if (!b->recording && recording) print_message(info_msg[MACRO_RECORDING_COMPLETED]); return OK; case PLAY_A: if (!b->recording && !b->executing_internal_macro) { if (c < 0 && (c = request_number("Times", 1))<=0) return NUMERIC_ERROR(c); b->executing_internal_macro = 1; for(int64_t i = 0; i < c && !(error = play_macro(b, b->cur_macro)); i++); b->executing_internal_macro = 0; return print_error(error) ? ERROR : 0; } else return ERROR; 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); } keep_cursor_on_screen(cur_buffer); reset_window(); /* We always return ERROR after a buffer has been deleted. Otherwise, the calling routines (and macros) could work on an unexisting buffer. */ return ERROR; 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; 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; 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; 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 FILE_IS_READ_ONLY; case COPY_A: if (!(error = print_error((b->mark_is_vertical ? copy_vert_to_clip : copy_to_clip)(b, c < 0 ? b->opt.cur_clip : c, a == CUT_A)))) { b->marking = 0; update_window_lines(b, b->cur_y, ne_lines - 2, false); } return error ? ERROR : 0; case ERASE_A: if (b->opt.read_only) return FILE_IS_READ_ONLY; if (!(error = print_error((b->mark_is_vertical ? erase_vert_block : erase_block)(b)))) { b->marking = 0; update_window_lines(b, b->cur_y, ne_lines - 2, false); } return OK; case PASTE_A: case PASTEVERT_A: if (b->opt.read_only) return FILE_IS_READ_ONLY; if (!(error = print_error((a == PASTE_A ? paste_to_buffer : paste_vert_to_buffer)(b, c < 0 ? b->opt.cur_clip : c)))) update_window_lines(b, 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(b, b->block_start_line); goto_column(b, calc_width(b->cur_line_desc, b->block_start_pos, b->opt.tab_size, b->encoding)); 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("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 SYSTEM_A: if (p || (p = request_string("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 FILE_IS_READ_ONLY; if (!b->marking) b->mark_is_vertical = 0; if (p || (p = request_string("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); 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(); return print_error(error) ? ERROR : OK; } return ERROR; case TOUPPER_A: if (b->opt.read_only) return FILE_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 FILE_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 FILE_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 FILE_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_y, false, 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 FILE_IS_READ_ONLY; NORMALIZE(c); for(int64_t i = 0; i < c && !(error = paragraph(b)) && !stop; i++); if (stop) error = STOPPED; return print_error(error) ? ERROR : 0; case SHIFT_A: if (b->opt.read_only) return FILE_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("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 FILE_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("AtomicUndo 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 FILE_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); reset_window(); return OK; case SUSPEND_A: stop_ne(); 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 = b->recording; 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)) { b->recording = 0; 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); b->recording = 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.0.1/src/ansi.c0000664000175200017510000000450212517166512012652 0ustar vignaprof/* Hardwired ANSI terminal control sequences. Copyright (C) 2001-2015 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 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 TERMCAP ne_cursor_address = "\x1b[%i%d;%dH"; ne_set_background = "\x1b[4%dm"; ne_set_foreground = "\x1b[3%dm"; #else 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.0.1/src/ansi.h0000664000175200017510000000146412517166512012663 0ustar vignaprof/* Hardwired ANSI terminal control sequences (prototypes). Copyright (C) 2001-2015 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.0.1/src/autocomp.c0000664000175200017510000001345112533416445013553 0ustar vignaprof/* AutoComplete Copyright (C) 2010-2015 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" #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) { 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; return; } if (len >= buflen) { if (buf_new = realloc(buf, len * 2 + 1)) { buflen = len * 2 + 1; buf = buf_new; } else cplen = buflen - 1; } strncpy(buf, s, cplen); buf[cplen] = '\0'; 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); 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 += get_char_width(&ld->line[l], b->encoding); if (l < ld->line_len - p_len ) { /* find right edge of word */ r = l + get_char_width(&ld->line[l], b->encoding); while (r < ld->line_len && ne_isword(get_char(&ld->line[r], b->encoding), b->encoding)) r += get_char_width(&ld->line[r], b->encoding); if (r - l > p_len && !(case_search ? strncmp : strncasecmp)(p, &ld->line[l], p_len)) { if (b->encoding == encoding || is_ascii(&ld->line[l], r - l)) 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) { int max_len = 0, min_len = INT_MAX, prefix_len = strlen(p); assert(p); req_list_init(&rl, (cur_buffer->opt.case_search ? strcmp : strdictcmp), false, false, EXTERNAL_FLAG_CHAR); 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 (rl.entries[0][m-1] == EXTERNAL_FLAG_CHAR) m--; for(int i = 1; i < rl.cur_entries; i++) { int j; for(j = 0; j < m; j++) if (rl.entries[i][j] != rl.entries[0][j]) break; m = j; } /* 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); 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.0.1/src/buffer.c0000664000175200017510000012162412536332011013164 0ustar vignaprof/* Buffer handling functions, including allocation, deallocation, and I/O. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2015 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" /* 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 amount by which we increment the first pool dimension, with respect to the size of the given file. */ #define STANDARD_INCREMENT (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 (64 * 1024 - 1) /* 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. */ char_pool *alloc_char_pool(int64_t size) { if (size < STD_POOL_SIZE) size = STD_POOL_SIZE; char_pool * const cp = calloc(1, sizeof(char_pool)); if (cp) { if (cp->pool = calloc(sizeof(char), size)) { 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; 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. */ line_desc_pool *alloc_line_desc_pool(int64_t pool_size) { 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 = calloc(pool_size, do_syntax ? sizeof(line_desc) : sizeof(no_syntax_line_desc))) { 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, &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; } void free_line_desc_pool(line_desc_pool * const ldp) { if (ldp == NULL) return; assert_line_desc_pool(ldp); 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->cur_macro = alloc_char_stream(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; } /* This leaves out onlyopt.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->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->recording = 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); 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 a buffer, given its name (i.e., the name of the file it contains). Note that file_part() is applied *both* to the string passed *and* to the buffer names, so that the path is immaterial. */ buffer *get_buffer_named(const char *p) { if (!p) return NULL; p = file_part(p); for(buffer *b = (buffer *)buffers.head; b->b_node.next; b = (buffer *)b->b_node.next) if (b->filename && !strcmp(file_part(b->filename), p)) return b; return NULL; } /* 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. The strategy is rather complex. 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)) { 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 (ld >= ldp->pool && (do_syntax && ld < ldp->pool + ldp->size || !do_syntax && ld < (line_desc*)((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)) { 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 record 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) { 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; } } /* 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; } } 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; 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; 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; } } 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; } } /* 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++) { if ( i == -1 ) fputc('.', stderr); // This is a nop that's here just to avoid a gcc bug 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 fh = open(name, READ_FLAGS); if (fh >= 0) { const int result = load_fh_in_buffer(b, fh); close(fh); b->mtime = file_mod_time(name); if (!result) b->opt.read_only = (access(name, W_OK) != 0); return result; } return CANT_OPEN_FILE; } /* 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_fh_in_buffer(buffer *b, int fh) { char terminators[] = { 0x0d, 0x0a }; if (b->opt.preserve_cr) terminators[0] = 0; off_t len = lseek(fh, 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; } char_pool *cp; if (len > 0) { // Seekable if (lseek(fh, 0, SEEK_SET) < 0) return IO_ERROR; block_signals(); free_buffer_contents(b); cp = alloc_char_pool(len + STANDARD_INCREMENT); if (!cp) { release_signals(); return OUT_OF_MEMORY; } if (read_safely(fh, cp->pool, len) < len) { free_char_pool(cp); release_signals(); return IO_ERROR; } } else { // Not seekable block_signals(); free_buffer_contents(b); int64_t curr_size = STANDARD_INCREMENT; len = 0; char *pool = calloc(curr_size, 1); for(;;) { const int64_t res = read_safely(fh, pool + len, curr_size - len); if (res < 0) { free(pool); release_signals(); return IO_ERROR; } len += res; if (len < curr_size) break; pool = realloc(pool, curr_size *= 2); memset(pool + len, 0, curr_size - len); } cp = alloc_char_pool_from_memory(pool, curr_size); if (!cp) { free(pool); release_signals(); return OUT_OF_MEMORY; } } 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. */ int64_t num_lines; for(int64_t i = num_lines = 0; i < len; i++, p++) if (!b->opt.binary && (*p == terminators[0] || *p == terminators[1]) || !*p) { if (i < len - 1 && !b->opt.preserve_cr && p[0] == '\r' && p[1] == '\n') { b->is_CRLF = true; p++, i++; b->free_chars++; } num_lines++; b->free_chars++; } num_lines++; /* 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; } line_desc_pool * const ldp = alloc_line_desc_pool(num_lines + STANDARD_LINE_INCREMENT); if (ldp) { char *p = cp->pool; /* 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 < num_lines; i++) { line_desc *ld = do_syntax ? &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); /* last line */ if (i == num_lines - 1) { if (p - cp->pool < len) { assert(*p && *p != terminators[0] && *p != terminators[1]); ld->line = p; ld->line_len = len - (p - cp->pool); } } else { char *q = p; while((b->opt.binary || *q != terminators[0] && *q != terminators[1]) && *q) q++; ld->line_len = q - p; ld->line = q - p ? p : NULL; if (q - cp->pool < len - 1 && !b->opt.preserve_cr && q[0] == '\r' && q[1] == '\n') *q++ = 0; *q++ = 0; p = q; } } ldp->allocated_items = num_lines; /* 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]) 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); b->num_lines = num_lines; reset_position_to_sof(b); if (b->opt.do_undo) b->undo.last_save_step = 0; release_signals(); return OK; } free_char_pool(cp); release_signals(); clear_buffer(b); return OUT_OF_MEMORY; } /* 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); } } } /* 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 (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 fh = open(name, WRITE_FLAGS, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); if (fh >= 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(fh, p, SAVE_BLOCK_LEN) < SAVE_BLOCK_LEN) { error = IO_ERROR; 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(fh, p, used) < used) { error = IO_ERROR; break; } else used = 0; } } if (!error && used && write(fh, 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(fh, 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(fh, "\r", 1) < 1) { error = IO_ERROR; break; } if (write(fh, b->opt.binary ? "\0" : "\n", 1) < 1) { error = IO_ERROR; break; } } } } if (close(fh)) 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.0.1/src/buffer2.c0000664000175200017510000012242412537775770013275 0ustar vignaprof/* Buffer handling functions, including allocation, deallocation, and I/O.    Copyright (C) 1993-1998 Sebastiano Vigna    Copyright (C) 1999-2015 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" /* 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 amount by which we increment the first pool dimension, with respect to the size of the given file. */ #define STANDARD_INCREMENT (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 (64 * 1024 - 1) /* 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. */ char_pool *alloc_char_pool(int64_t size) { if (size < STD_POOL_SIZE) size = STD_POOL_SIZE; char_pool * const cp = calloc(1, sizeof(char_pool)); if (cp) { if (cp->pool = calloc(sizeof(char), size)) { 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; 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. */ line_desc_pool *alloc_line_desc_pool(int64_t pool_size) { 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 = calloc(pool_size, do_syntax ? sizeof(line_desc) : sizeof(no_syntax_line_desc))) { 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, &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; } void free_line_desc_pool(line_desc_pool * const ldp) { if (ldp == NULL) return; assert_line_desc_pool(ldp); 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->cur_macro = alloc_char_stream(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; } /* This leaves out onlyopt.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->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->recording = 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); 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 a buffer, given its name (i.e., the name of the file it    contains). Note that file_part() is applied *both* to the string passed    *and* to the buffer names, so that the path is immaterial. */ buffer *get_buffer_named(const char *p) { if (!p) return NULL; p = file_part(p); for(buffer *b = (buffer *)buffers.head; b->b_node.next; b = (buffer *)b->b_node.next) if (b->filename && !strcmp(file_part(b->filename), p)) return b; return NULL; } /* 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. The strategy is rather    complex. 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)) { 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 (ld >= ldp->pool && (do_syntax && ld < ldp->pool + ldp->size || !do_syntax && ld < (line_desc*)((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)) { 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    record 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) { 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; } } /* 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; } } 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; 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; 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; } } 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; } } /* 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++) { if ( i == -1 ) fputc('.', stderr); // This is a nop that's here just to avoid a gcc bug 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 fh = open(name, READ_FLAGS); if (fh >= 0) { const int result = load_fh_in_buffer(b, fh); close(fh); b->mtime = file_mod_time(name); if (!result) b->opt.read_only = (access(name, W_OK) != 0); return result; } return CANT_OPEN_FILE; } /* 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_fh_in_buffer(buffer *b, int fh) { char terminators[] = { 0x0d, 0x0a }; if (b->opt.preserve_cr) terminators[0] = 0; off_t len = lseek(fh, 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; } char_pool *cp; if (len > 0) { // Seekable if (lseek(fh, 0, SEEK_SET) < 0) return IO_ERROR; block_signals(); free_buffer_contents(b); cp = alloc_char_pool(len + STANDARD_INCREMENT); if (!cp) { release_signals(); return OUT_OF_MEMORY; } if (read_safely(fh, cp->pool, len) < len) { free_char_pool(cp); release_signals(); return IO_ERROR; } } else { // Not seekable block_signals(); free_buffer_contents(b); int64_t curr_size = STANDARD_INCREMENT; len = 0; char *pool = calloc(curr_size, 1); for(;;) { const int64_t res = read_safely(fh, pool + len, curr_size - len); if (res < 0) { free(pool); release_signals(); return IO_ERROR; } len += res; if (len < curr_size) break; pool = realloc(pool, curr_size *= 2); memset(pool + len, 0, curr_size - len); } cp = alloc_char_pool_from_memory(pool, curr_size); if (!cp) { free(pool); release_signals(); return OUT_OF_MEMORY; } } 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. */ int64_t num_lines; for(int64_t i = num_lines = 0; i < len; i++, p++) if (!b->opt.binary && (*p == terminators[0] || *p == terminators[1]) || !*p) { if (i < len - 1 && !b->opt.preserve_cr && p[0] == '\r' && p[1] == '\n') { b->is_CRLF = true; p++, i++; b->free_chars++; } num_lines++; b->free_chars++; } num_lines++; /* 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; } line_desc_pool * const ldp = alloc_line_desc_pool(num_lines + STANDARD_LINE_INCREMENT); if (ldp) { char *p = cp->pool; /* 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 < num_lines; i++) { line_desc *ld = do_syntax ? &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); /* last line */ if (i == num_lines - 1) { if (p - cp->pool < len) { assert(*p && *p != terminators[0] && *p != terminators[1]); ld->line = p; ld->line_len = len - (p - cp->pool); } } else { char *q = p; while((b->opt.binary || *q != terminators[0] && *q != terminators[1]) && *q) q++; ld->line_len = q - p; ld->line = q - p ? p : NULL; if (q - cp->pool < len - 1 && !b->opt.preserve_cr && q[0] == '\r' && q[1] == '\n') *q++ = 0; *q++ = 0; p = q; } } ldp->allocated_items = num_lines; /* 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]) 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); b->num_lines = num_lines; reset_position_to_sof(b); if (b->opt.do_undo) b->undo.last_save_step = 0; release_signals(); return OK; } free_char_pool(cp); release_signals(); clear_buffer(b); return OUT_OF_MEMORY; } /* 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); } } } /* 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 (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 fh = open(name, WRITE_FLAGS, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); if (fh >= 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(fh, p, SAVE_BLOCK_LEN) < SAVE_BLOCK_LEN) { error = IO_ERROR; 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(fh, p, used) < used) { error = IO_ERROR; break; } else used = 0; } } if (!error && used && write(fh, 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(fh, 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(fh, "\r", 1) < 1) { error = IO_ERROR; break; } if (write(fh, b->opt.binary ? "\0" : "\n", 1) < 1) { error = IO_ERROR; break; } } } } if (close(fh)) 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.0.1/src/bug.c0000664000175200017510000006134412534644751012511 0ustar vignaprof/* Main typedefs and defines. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2015 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 #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" #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 */ CANT_OPEN_FILE, /* 30 */ CANT_OPEN_TEMPORARY_FILE, /* 31 */ ERROR_WHILE_WRITING, /* 32 */ HAS_NO_EXTENSION, /* 33 */ CANT_FIND_PREFS_DIR, /* 34 */ CLIP_DOESNT_EXIST, /* 35 */ MARK_OUT_OF_BUFFER, /* 36 */ CANT_OPEN_MACRO, /* 37 */ MAX_MACRO_DEPTH_EXCEEDED, /* 38 */ FILE_IS_READ_ONLY, /* 39 */ FILE_IS_MIGRATED, /* 40 */ FILE_IS_DIRECTORY, /* 41 */ FILE_IS_TOO_LARGE, /* 42 */ STOPPED, /* 43 */ IO_ERROR, /* 44 */ STRING_IS_EMPTY, /* 45 */ EXTERNAL_COMMAND_ERROR, /* 46 */ ESCAPE_TIME_OUT_OF_RANGE, /* 47 */ PREFS_STACK_FULL, /* 48 */ PREFS_STACK_EMPTY, /* 49 */ NOT_A_NUMBER, /* 50 */ INVALID_CHARACTER, /* 51 */ INVALID_STRING, /* 52 */ BUFFER_IS_NOT_UTF8, /* 53 */ INCOMPATIBLE_CLIP_ENCODING, /* 54 */ INCOMPATIBLE_COMMAND_ENCODING, /* 55 */ INCOMPATIBLE_SEARCH_STRING_ENCODING, /* 56 */ INCOMPATIBLE_REPLACE_STRING_ENCODING, /* 57 */ UTF8_REGEXP_CHARACTER_CLASS_NOT_SUPPORTED, /* 58 */ UTF8_REGEXP_COMP_CHARACTER_CLASS_NOT_SUPPORTED, /* 59 */ GROUP_NOT_AVAILABLE, /* 60 */ SYNTAX_NOT_ENABLED, /* 61 */ NO_SYNTAX_FOR_EXT, /* 62 */ INVALID_SHIFT_SPECIFIED, /* 63 */ INSUFFICIENT_WHITESPACE, /* 64 */ DOCUMENT_NOT_SAVED, /* 65 */ FILE_TOO_LARGE_SYNTAX_HIGHLIGHTING_DISABLED, ERROR_COUNT }; #include #include #include #include #include #ifndef 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[]; /* The name of the default preferences file name. */ #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. */ #define NUM_KEYS (256+256) /* 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. User get 0 through 9 plus the AUTO_BOOKMARK '-'. */ enum { MAX_USER_BOOKMARK = 9, AUTO_BOOKMARK, WORDWRAP_BOOKMARK, NUM_BOOKMARKS }; enum { COMPLETE_NONE = 0, COMPLETE_FILE, COMPLETE_CMD_FILE, /* 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. */ char suffix; int cur_entries; int alloc_entries; int max_entry_len; char **entries; int *orig_order; /* maps from current order to original order when allow_reorder is true. */ int cur_chars; int alloc_chars; char *chars; } 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; /* 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 pointer by ld_text 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. The first free descriptor is contained in first_free. The last free descriptor is contained in last_free. 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; line_desc *pool; } 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; } 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 free_form:1, /* Editing is free form (cursor can be anywhere) */ hex_code:1, /* Show hexadecimal code under the cursor */ word_wrap:1, /* Word wrap is on */ auto_indent:1, /* Replicate indentation when creating a new line */ preserve_cr:1, /* Preserve Carriage Returns, don't treat as line terminators. */ insert:1, /* Insert mode */ do_undo:1, /* Record each action and allow undoing it */ auto_prefs:1, /* Use autoprefs */ no_file_req:1, /* Do not display the file requester */ read_only:1, /* Read-only mode */ search_back:1, /* Last search was backwards */ case_search:1, /* Look at case matching in searches */ tabs:1, /* TAB inserts TABs(1) vs. spaces(0) */ del_tabs:1, /* DEL/BS deletes tab's worth of space. */ shift_tabs:1, /* Shift may insert tabs, but only if tabs is also true */ automatch:4, /* Automatically match visible brackets */ binary:1, /* Load and save in binary mode */ utf8auto:1, /* Try to detect automatically UTF-8 */ visual_bell:1; /* Prefer visible bell to audible */ } 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; unsigned long mtime; /* mod time of on-disk file when it was last loaded/saved, or 0 */ 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) */ int64_t cur_char; /* position of cursor within the attribute buffer (counts characters) */ int64_t num_lines; int64_t block_start_line, block_start_pos; struct { int shown; int x; /* Visual (on-screen) coordinates of the highlighted bracket, if shown is true. */ int y; } automatch; 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 */ recording:1, /* We are recording a macro */ 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 */ find_string_changed:1, /* The search string has to be recompiled before use. */ 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 */ mark_is_vertical:1, /* The current marking is vertical */ atomic_undo:1, /* subsequent commands undo as a block */ executing_macro:1, /* We are currently executing a macro. */ executing_internal_macro:1, /* We are currently executing the internal macro of the current buffer */ is_CRLF:1; /* Buffer should be saved with CR/LF terminators */ 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 >= (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->encoding));\ }} #else #define assert_buffer(b) ; #define assert_buffer_content(b); #endif #include "syntax.h" extern const char *key_binding[]; extern buffer *cur_buffer; /* These are the global lists. */ extern list buffers, clips, macros; /* 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; /* 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) #include "protos.h" /* Upcasing vectors for the regex library. */ extern char localised_up_case[]; extern const char ascii_up_case[]; /* 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; } } 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 { fprintf(stderr, "%" PRId64 "\n", b->num_lines - 1 - n); 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; } int main(int argc, char* argv[]) { buffer *b = calloc(1, sizeof *b); new_list(&b->line_desc_pool_list); new_list(&b->line_desc_list); new_list(&b->char_pool_list); line_desc *ld = (line_desc *)b->line_desc_list.tail_pred; ld->ld_node.prev = (node *)ld; b->num_lines = 2147698392; nth_line_desc( b, 1273204082 ); return 0; } ne-3.0.1/src/bug2.c0000664000175200017510000000056412534645026012564 0ustar vignaprof#include "ne.h" #include "protos.h" int main(int argc, char* argv[]) { buffer *b = calloc(1, sizeof *b); new_list(&b->line_desc_pool_list); new_list(&b->line_desc_list); new_list(&b->char_pool_list); line_desc *ld = (line_desc *)b->line_desc_list.tail_pred; ld->ld_node.prev = (node *)ld; b->num_lines = 2147698392; nth_line_desc( b, 1273204082 ); return 0; } ne-3.0.1/src/clips.c0000664000175200017510000004361112525431422013030 0ustar vignaprof/* Clip handling functions. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2015 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" /* 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(b, b->block_start_line); goto_column(b, calc_width(b->cur_line_desc, b->block_start_pos, b->opt.tab_size, b->encoding)); delete_stream(b, b->cur_line_desc, b->cur_line, b->cur_pos, clip_len); update_syntax_and_lines(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_and_lines(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_and_lines(). */ int erase_block(buffer *b) { 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(b, b->block_start_line); goto_column(b, calc_width(b->cur_line_desc, b->block_start_pos, b->opt.tab_size, b->encoding)); } 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); update_syntax_and_lines(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; 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); assert(ld == b->cur_line_desc); update_syntax_and_lines(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; } if (cut) 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_and_lines(b, (line_desc *)ld->ld_node.next, b->cur_line_desc); goto_line(b, min(b->block_start_line, b->cur_line)); goto_column(b, min(calc_width(b->cur_line_desc, b->block_start_pos, b->opt.tab_size, b->encoding), b->win_x + b->cur_x)); 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_and_lines(b, b->cur_line_desc, (line_desc *)ld->ld_node.prev); goto_line(b, min(b->block_start_line, b->cur_line)); goto_column(b, min(calc_width(b->cur_line_desc, b->block_start_pos, b->opt.tab_size, b->encoding), b->win_x + b->cur_x)); 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_and_lines(). */ int erase_vert_block(buffer *b) { 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; 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; } update_syntax_and_lines(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; } update_syntax_and_lines(b, b->cur_line_desc, (line_desc *)ld->ld_node.prev); } end_undo_chain(b); goto_line(b, min(b->block_start_line, b->cur_line)); goto_column(b, min(calc_width(b->cur_line_desc, b->block_start_pos, b->opt.tab_size, b->encoding), b->win_x + b->cur_x)); 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); 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; for(uint64_t 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); } 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); insert_stream(b, ld, line, ld->line_len, p, len); } else insert_stream(b, ld, line, pos, p, len); } p += len + 1; ld = (line_desc *)ld->ld_node.next; line++; } end_undo_chain(b); update_syntax_and_lines(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.0.1/src/cm.c0000664000175200017510000002222012517166512012314 0ustar vignaprof/* 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-2015 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 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.0.1/src/cm.h0000664000175200017510000001032212517166512012321 0ustar vignaprof/* 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-2015 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.0.1/src/command.c0000664000175200017510000007756212542017427013354 0ustar vignaprof/* Command table manipulation functions and vectors. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2015 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 "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(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 ),0 }, { 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(NEWDOC ), NO_ARGS }, { NAHL(NEXTDOC ),0 }, { NAHL(NEXTPAGE ),0 }, { NAHL(NEXTWORD ),0 }, { 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 ),0 }, { NAHL(POPPREFS ),0 }, { NAHL(PRESERVECR ), IS_OPTION }, { NAHL(PREVDOC ),0 }, { NAHL(PREVPAGE ),0 }, { NAHL(PREVWORD ),0 }, { NAHL(PUSHPREFS ), IS_OPTION }, { NAHL(QUIT ), NO_ARGS }, { NAHL(READONLY ), IS_OPTION }, { NAHL(RECORD ), IS_OPTION | DO_NOT_RECORD }, { NAHL(REDO ),0 }, { NAHL(REFRESH ), NO_ARGS }, { NAHL(REPEATLAST ),0 }, { 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; 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; 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 error: Can execute only options.\n");) return -CAN_EXECUTE_ONLY_OPTIONS; } D(fprintf(stderr,"parse_command error: Has numeric argument.\n");) return -HAS_NUMERIC_ARGUMENT; } D(fprintf(stderr,"parse_command error: Has no argument.\n");) return -HAS_NO_ARGUMENT; } D(fprintf(stderr,"parse_command error: No such command.\n");) 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 descriptors. */ 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 calls to 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 openning 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; } } /* This function is the ultimate goal of this file. It plays a character stream, 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 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(buffer *b, char_stream *cs) { 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) return OK; char * const stream = malloc(len); if (!stream) return OUT_OF_MEMORY; char * p = stream; memcpy(stream, cs->stream, len); stop = false; b->executing_macro = 1; int error = OK; 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(b, 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); 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); 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); 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); 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) { static int call_depth = 0; if (++call_depth > 32) { --call_depth; return MAX_MACRO_DEPTH_EXCEEDED; } const char * const p = file_part(name); int h = hash_macro(p, strlen(p)); macro_desc *md; for(md = macro_hash_table[h]; md && cmdcmp(md->name, p); md = md->next ); if (!md) md = load_macro(name); assert_macro_desc(md); if (md) { if (b->recording) { add_to_stream(b->cur_macro, "# include macro ", 16); add_to_stream(b->cur_macro, md->name, strlen(md->name)+1); } h = play_macro(b, md->cs); if (b->recording) { add_to_stream(b->cur_macro, "# conclude macro ", 17); add_to_stream(b->cur_macro, md->name, strlen(md->name)+1); } --call_depth; return h; } --call_depth; return CANT_OPEN_MACRO; } /* 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 any 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) { char *str=NULL, *p; for(int i = 0; i < NUM_KEYS; 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)] == ' ') ) ) ) { if (!str) { if (!(str = malloc(14))) return NULL; strcpy(str,"Bound key(s):"); } if (p = realloc(str,strlen(str) + strlen(key_stroke[i]) + 2)) { str = strcat(strcat(p," "),key_stroke[i]); } else { free(str); return NULL; } } } } return str; } /* This function helps. The help text relative to the command name pointed to by p is displayed (p can also contain arguments). If p is NULL, the alphabetically ordered list of commands is displayed with the string requester. The help finishes when the user escapes. */ void help(char *p) { req_list rl = { .ignore_tab=true }; D(fprintf(stderr,"Help Called with parm %p.\n",p);) int r = 0; do { print_message("Help: select Command and press Enter, or F1 or Escape or Escape-Escape"); rl.cur_entries = ACTION_COUNT; rl.alloc_entries = 0; rl.max_entry_len = MAX_COMMAND_WIDTH; rl.entries = (char **)command_names; if (p || (r = request_strings(&rl, r)) >= 0) { D(fprintf(stderr,"Help check #2: p=%p, r=%d\n",p,r);) if (p) { for(int i = 0; i < strlen(p); i++) if (isasciispace((unsigned char)p[i])) break; r = hash_cmd(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; 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("Help: press Enter, or F1 or Escape or Escape-Escape"); char *key_strokes, **tmphelp; if ((key_strokes = find_key_strokes(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.max_entry_len = ne_columns; rl.entries = tmphelp; const int s = request_strings(&rl, 0); if (s < 0) r = s; free(tmphelp); } else { rl.cur_entries = commands[r].help_len; rl.alloc_entries = 0; rl.max_entry_len = ne_columns; rl.entries = (char **)commands[r].help; const int s = request_strings(&rl, 0); if (s < 0) r = s; } if (key_strokes) free(key_strokes); } } while(r >= 0); draw_status_bar(); } ne-3.0.1/src/debug.h0000664000175200017510000000164212517166512013015 0ustar vignaprof/* Definition for debug statements and assertions. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2015 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.0.1/src/display.c0000664000175200017510000007610212534404024013362 0ustar vignaprof/* Display handling functions with optional update delay Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2015 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 "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 visibile 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, b->encoding)); /* We update lines until the currenct starting state is equal to next_line_state, but we go until end_ld if it is not NULL. In any case, we bail out at the end of the file. */ for(;;) { /* We move one row down. */ ld = (line_desc *) ld->ld_node.next; if ((highlight_cmp(&ld->highlight_state, &next_line_state) && got_end_ld) || !ld->ld_node.next) break; if (ld == end_ld) got_end_ld = true; 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 { freeze_attributes(b, ld); invalidate_attr_buf = true; } } } ld->highlight_state = next_line_state; next_line_state = parse(b->syn, ld, ld->highlight_state, b->encoding == ENC_UTF8); 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 (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() and update_partial_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, 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; int i; for(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 || output_col + c_width > col && output_col >= 0) { 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); } } 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 and a starting column. It can handle lines with no associated line descriptor (such as lines after the end of the buffer). It checks for updated_lines bypassing TURBO. 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 differential is nonzero, the line is update 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 characters. Note that this function guarantees that it will call parse() on the specified line. Returns the line descriptor corresponding to row, or NULL if the row is beyond the end of text. */ line_desc *update_partial_line(buffer * const b, const int row, const int64_t from_col, const bool cleared_at_end, const bool differential) { 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; } line_desc *ld = b->top_line_desc; assert_line_desc(ld, b->encoding); for(int i = 0; i < row && ld->ld_node.next; i++) ld = (line_desc *)ld->ld_node.next; if (! ld->ld_node.next) { move_cursor(row, from_col); clear_to_eol(); return NULL; } else if (b->syn) parse(b->syn, ld, ld->highlight_state, b->encoding == ENC_UTF8); if (! window_needs_refresh) { assert(b->syn || ! differential); assert(b->attr_len >= 0 || ! differential); 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, b->syn ? attr_buf : NULL, differential ? b->attr_buf : NULL, differential ? b->attr_len : 0); } return ld; } /* Similar to the previous call, but updates the whole line. If the updated line is the current line, we update the local attribute buffer. */ void update_line(buffer * const b, const int n, const bool cleared_at_end, const bool differential) { line_desc * const ld = update_partial_line(b, n, 0, cleared_at_end, differential); if (b->syn && ld == b->cur_line_desc) { /* If we updated the entire current line, we update the local attribute buffer. */ b->next_state = parse(b->syn, ld, ld->highlight_state, b->encoding == ENC_UTF8); ensure_attr_buf(b, attr_len); memcpy(b->attr_buf, attr_buf, (b->attr_len = attr_len) * sizeof *b->attr_buf); } } /* 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, 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; line_desc *ld = b->top_line_desc; assert_line_desc(ld, b->encoding); int i; for(i = 0; i <= last_line && i + b->win_y < b->num_lines; i++) { assert(ld->ld_node.next != NULL); if (i >= first_line) { 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, 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_and_lines(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, const line_desc * const ld, int64_t pos, int64_t attr_pos, const int line, const int x) { if (b->syn) { assert(b->attr_len >= 0); assert(b->attr_len - 1 == calc_char_len(ld, b->encoding)); memmove(b->attr_buf + attr_pos, b->attr_buf + attr_pos + 1, (--b->attr_len - attr_pos) * sizeof *b->attr_buf); } 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; return; } 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) { /* Argh! We can't insert or delete! Just update the rest of the line. */ if (b->syn) update_line(b, line, false, false); else update_partial_line(b, line, x, false, 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_partial_line(b, line, ne_columns - b->opt.tab_size, true, false); } 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_partial_line(b, line, ne_columns - c_width, true, false); } } /* See comments for update_deleted_char(). */ void update_inserted_char(buffer * const b, const int c, const line_desc * const ld, const int64_t pos, const int64_t attr_pos, const int line, const int x) { assert(pos < ld->line_len); 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, b->encoding), pos, ld->line_len, attr_pos);*/ assert(b->attr_len + 1 == calc_char_len(ld, 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; } 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; return; } 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_partial_line(b, line, x, false, 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, const 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); 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, b->encoding), pos, ld->line_len, attr_pos);*/ assert(b->attr_len + 1 == calc_char_len(ld, b->encoding) || b->attr_len == calc_char_len(ld, 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; } 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; return; } 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_partial_line(b, line, x, false, 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 curren 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_partial_line(b, line, ne_columns - b->opt.tab_size, true, false); } 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_partial_line(b, line, ne_columns - width_delta, true, false); } 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) update_window_lines(b, first_line, last_line, 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, 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, line, ne_lines - 2, false); return; } if (n > 0) update_line(b, line, ins_del_lines(line, 1), false); else update_line(b, ne_lines - 2, ins_del_lines(line, -1), false); } /* Computes the attributes of the given line and stores them into the attribute buffer. 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). */ HIGHLIGHT_STATE freeze_attributes(buffer *b, line_desc *ld) { b->next_state = parse(b->syn, ld, ld->highlight_state, b->encoding == ENC_UTF8); ensure_attr_buf(b, attr_len); memcpy(b->attr_buf, attr_buf, (b->attr_len = attr_len) * sizeof *b->attr_buf); return b->next_state; } /* (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 ) { move_cursor(b->automatch.y, b->automatch.x); if (b->syn) { parse(b->syn, matching_ld, matching_ld->highlight_state, b->encoding == ENC_UTF8); orig_attr = attr_buf[match_pos]; } else orig_attr = 0; /* That's a stretch. FIX_ME */ tmp_attr = orig_attr; if (b->opt.automatch & 1 ) { /* invert boldness of FG, BG */ 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; } } 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; } output_char(c, tmp_attr, b->encoding == ENC_UTF8); b->automatch.shown = 1; } } } else { if (b->automatch.shown) { move_cursor(b->automatch.y, b->automatch.x); output_char(c, orig_attr, b->encoding == ENC_UTF8); b->automatch.shown = 0; } } } ne-3.0.1/src/edit.c0000664000175200017510000007067012542017427012654 0ustar vignaprof/* Various editing functions such as word wrap, to upper, etc. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2015 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" /* 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) != OK) return ERROR; bool changed = false; int64_t new_len = 0; pos = b->cur_pos; /* 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); } b->attr_len = -1; update_line(b, b->cur_y, false, false); if (b->syn) { need_attr_update = true; update_syntax_states(b, b->cur_y, b->cur_line_desc, NULL); } return search_word(b, 1); } /* 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(b, match_line); goto_pos(b, 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 characters existing on the new line is returned, or ERROR if no word wrap was possible. */ int 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 cur_pos, pos, first_pos; if (!(cur_pos = 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]. */ /* Start from the other end now and 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); 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 b->cur_pos - pos - 1; } /* 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); goto_line(b, b->bookmark[WORDWRAP_BOOKMARK].line); goto_pos( b, 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. The strategy is as follows: 1. 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[]. 1.1 If the leading non-blank (the part after space[]) is not an alphanumeric, then we take it as a comment initiator and preserve it for later as spots[]. 2. Start an undo chain. 3. Trim trailing space off the current line. 4. while the current line is too long (i.e., needs to be split: 4.1 Find the split point 4.2 Remove any space at the split point 4.3 Split the line at the split point. (We are done with this line) 4.4 Make the new line the current line 4.5 Insert the space[] stream we saved in step 1. 4.5.1 Insert the spots[] stream we saved from step 1.1. 4.6 Trim trailing space off the end of the line. 5. If the _following_ line is part of this paragraph (i.e., its first non-blank character is in the correct position): 5.1 Add a space to the end of the current line. 5.2 Delete this line's leading white space. 5.3 If the leading non-blank character matches the first character of spots[], remove it any any subsequent non-alphanumeric. 5.2 Copy following line's data starting with the first non-blank to the end of the current line. 5.3 Remove the following line. 5.4 Goto step 3. 6. end the undo chain. 7. Free space[]. 8. and refresh the screen 9. move to the next non-blank after the current line. (We have to do this so that commands like "Paragraph 5" will do 5 paragraphs instead of only three.) */ static char *pa_spots = NULL; /* Where we keep leading non-alphanumerics */ static int pa_spots_pos; /* How long pa_spots is in chars */ /* save_spots() is like save_space(), but it preserves the string of non-alphanumerics immediately follow where save_space() left off. */ static int save_spots(line_desc * const ld, const int pos, const encoding_type encoding) { if (pa_spots) free(pa_spots); pa_spots = NULL; pa_spots_pos = 0; if (!ld->line || ld->line_len <= pos) return 0; /* No data on this line. */ while(pos + pa_spots_pos < ld->line_len && isparaspot(ld->line[pos+pa_spots_pos])) pa_spots_pos = next_pos(ld->line, pos+pa_spots_pos, encoding) - pos; if (pa_spots_pos) { if ((pa_spots = malloc(pa_spots_pos))) memcpy(pa_spots, ld->line+pos, pa_spots_pos); else pa_spots_pos = 0; } return pa_spots_pos > 0; } 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) { pa_space = NULL; save_spots(ld, pos, encoding); return 1; } if ((pa_space = malloc(pos))) { memcpy(pa_space, ld->line, pos); save_spots(ld, pos, encoding); 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 if you want to be able to undo later. */ 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. */ int paragraph(buffer * const b) { line_desc *ld = b->cur_line_desc, *start_line_desc = ld; if (!ld->line) return line_down(b); /** Step 1 **/ 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); /** Step 2 **/ start_undo_chain(b); /* This useless insertion and deletion of a single character ensures that the text isn't shifted way over to the left after an undo. */ int64_t line = b->cur_line; 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 = false, skip; int64_t pos; do { /** Step 3 **/ trim_trailing_space(b, ld, line, b->encoding); /** Step 4 **/ while (!stop && !done && calc_width(ld, ld->line_len, b->opt.tab_size, b->encoding) > right_margin) { int64_t spaces; int64_t split_pos; bool did_split; /** 4.1 Find the split point **/ pos = 0; /* Skip past leading spaces... */ while(pos < ld->line_len && isasciispace(ld->line[pos])) pos = next_pos(ld->line, pos, b->encoding); /* ...and the invariants if any. */ while(pos < ld->line_len && isparaspot(ld->line[pos])) pos = next_pos(ld->line, pos, b->encoding); did_split = false; 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) { /** 4.2 Remove any space at the split point. **/ if (spaces) delete_stream(b, ld, line, split_pos, spaces); /** 4.3 Split the line at the split point. (We are done with this line) **/ insert_one_line(b, ld, line, split_pos); did_split = true; } /** 4.4 Make the (new?) next line the current line **/ if (ld->ld_node.next->next) { ld = (line_desc *)ld->ld_node.next; line++; /** 4.5 Insert the pa_space[] stream we saved in step 1. Note that **/ /** we only want to do this if this line is the result of a split, **/ /** which is true if did_split is true. **/ if (did_split) { if (pa_space && pa_space_len && pa_space_pos) insert_stream(b, ld, line, 0, pa_space, pa_space_pos); /** 4.5.1 Insert the pa_spots[] stream if there is one. **/ if (pa_spots && pa_spots_pos) insert_stream(b, ld, line, pa_space_pos, pa_spots, pa_spots_pos); } /** 4.6 Trim trailing space off the end of the line. **/ trim_trailing_space(b, ld, line, b->encoding); } else done = true; } /** If the current line is just a spot (no text), we skip over it. **/ pos = 0; /* Skip past leading spaces... */ skip = false; while(pos < ld->line_len && isasciispace(ld->line[pos])) pos = next_pos(ld->line, pos, b->encoding); /* ...and the invariants if any. */ while(pos < ld->line_len && isparaspot(ld->line[pos])) pos = next_pos(ld->line, pos, b->encoding); if (pos == ld->line_len) skip = true; /** 5. If the _following_ line is part of this paragraph (i.e., its first **/ /** non-blank character is in the correct position): **/ if (ld->ld_node.next->next && is_part_of_paragraph((line_desc *)ld->ld_node.next, b->opt.tab_size, &pos, b->encoding)) { /** If the next line is just a spot (no text), we want to skip over it **/ /** rather than splicing it to the current line. **/ if (skip || save_spots((line_desc *)ld->ld_node.next, pos, b->encoding) && ((line_desc *)ld->ld_node.next)->line_len <= pos+pa_spots_pos) { /* skip to next line */ ld = (line_desc *)ld->ld_node.next; line++; } else { /** 5.1 Add a space to the end of the current line. **/ insert_one_char(b, ld, line, ld->line_len, ' '); /** 5.4 Move following line's data starting with the first **/ /** non-blank to the end of the current line. **/ /** We do this by first deleting the leading spaces **/ if (pos > 0) delete_stream(b, (line_desc *)ld->ld_node.next, line + 1, 0, pos); /** 5.2 Cache the leading non-alphanumeric in pa_spots, then delete it, **/ if (save_spots((line_desc *)ld->ld_node.next, 0, b->encoding)) delete_stream(b, (line_desc *)ld->ld_node.next, line + 1, 0, pa_spots_pos); /** Finally splice the lines by deleting the newline at the end of the current line. **/ delete_stream(b, ld, line, ld->line_len, 1); } } else done = true; } while (!done && !stop); /** Step 6 **/ end_undo_chain(b); /** Step 7 **/ if (pa_space) { free(pa_space); pa_space = NULL; } /** Step 8 **/ 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_y, ne_lines - 2, false); /** Step 9 **/ goto_line(b, line); 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 { if (ld->line) { for (pos = 0; pos < ld->line_len; pos = next_pos(ld->line, pos, b->encoding)) { if (!isasciispace(ld->line[pos])) { goto_line(b, line); goto_pos(b, pos); return ld->ld_node.next ? OK : ERROR; } } } ld = (line_desc *)ld->ld_node.next; line++; } while (ld->ld_node.next); return b->cur_line_desc->ld_node.next ? OK : 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; 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; assert_line_desc(prev_ld, b->encoding); if (!prev_ld->ld_node.prev || prev_ld->line_len == 0) return 0; 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); 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, 0, ne_lines - 2, false); } /* put the screen back where way we found it. */ goto_line(b, init_line); goto_pos(b, 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.0.1/src/enums.h0000664000175200017510000000542212542017454013054 0ustar vignaprof/* 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 (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2015 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 . */ typedef enum { ABOUT_A, ADJUSTVIEW_A, ALERT_A, ATOMICUNDO_A, AUTOCOMPLETE_A, AUTOINDENT_A, AUTOMATCHBRACKET_A, AUTOPREFS_A, BACKSPACE_A, BEEP_A, BINARY_A, CAPITALIZE_A, CASESEARCH_A, CENTER_A, CLEAR_A, CLIPNUMBER_A, CLOSEDOC_A, COPY_A, CRLF_A, CUT_A, DELETECHAR_A, DELETEEOL_A, DELETELINE_A, DELETENEXTWORD_A, DELETEPREVWORD_A, DELTABS_A, DOUNDO_A, ERASE_A, ESCAPE_A, ESCAPETIME_A, EXEC_A, EXIT_A, FASTGUI_A, FIND_A, FINDREGEXP_A, FLAGS_A, FLASH_A, FREEFORM_A, GOTOBOOKMARK_A, GOTOCOLUMN_A, GOTOLINE_A, GOTOMARK_A, HELP_A, HEXCODE_A, INSERT_A, INSERTCHAR_A, INSERTLINE_A, INSERTSTRING_A, INSERTTAB_A, KEYCODE_A, LINEDOWN_A, LINEUP_A, LOADAUTOPREFS_A, LOADPREFS_A, MACRO_A, MARK_A, MARKVERT_A, MATCHBRACKET_A, MODIFIED_A, MOVEBOS_A, MOVEEOF_A, MOVEEOL_A, MOVEEOW_A, MOVEINCDOWN_A, MOVEINCUP_A, MOVELEFT_A, MOVERIGHT_A, MOVESOF_A, MOVESOL_A, MOVETOS_A, NEWDOC_A, NEXTDOC_A, NEXTPAGE_A, NEXTWORD_A, NOFILEREQ_A, NOP_A, OPEN_A, OPENCLIP_A, OPENMACRO_A, OPENNEW_A, PAGEDOWN_A, PAGEUP_A, PARAGRAPH_A, PASTE_A, PASTEVERT_A, PLAY_A, POPPREFS_A, PRESERVECR_A, PREVDOC_A, PREVPAGE_A, PREVWORD_A, PUSHPREFS_A, QUIT_A, READONLY_A, RECORD_A, REDO_A, REFRESH_A, REPEATLAST_A, REPLACE_A, REPLACEALL_A, REPLACEONCE_A, REQUESTORDER_A, RIGHTMARGIN_A, SAVE_A, SAVEALL_A, SAVEAS_A, SAVEAUTOPREFS_A, SAVECLIP_A, SAVEDEFPREFS_A, SAVEMACRO_A, SAVEPREFS_A, SEARCHBACK_A, SELECTDOC_A, SETBOOKMARK_A, SHIFT_A, SHIFTTABS_A, STATUSBAR_A, SUSPEND_A, SYNTAX_A, SYSTEM_A, TABS_A, TABSIZE_A, THROUGH_A, TOGGLESEOF_A, TOGGLESEOL_A, TOLOWER_A, TOUPPER_A, TURBO_A, UNDELLINE_A, UNDO_A, UNLOADMACROS_A, UNSETBOOKMARK_A, UTF8_A, UTF8AUTO_A, UTF8IO_A, VERBOSEMACROS_A, VISUALBELL_A, WORDWRAP_A, ACTION_COUNT } action; /* This file was automatically generated by info2src.pl. */ ne-3.0.1/src/errors.c0000664000175200017510000001146512517741324013242 0ustar vignaprof/* Error message vector. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2015 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.", /* 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 */ "Can't open file.", /* 30 */ "Can't open temporary files.", /* 31 */ "Error while writing.", /* 32 */ "Document name has no extension.", /* 33 */ "Can't find or create $HOME/.ne directory.", /* 34 */ "Clip does not exist.", /* 35 */ "Mark is out of document.", /* 36 */ "Can't open macro.", /* 37 */ "Maximum macro depth exceeded.", /* 38 */ "This file is read-only.", /* 39 */ "Can't open file (file is migrated).", /* 40 */ "Can't open file (file is a directory).", /* 41 */ "Can't open file (file is too large).", /* 42 */ "Stopped.", /* 43 */ "I/O error.", /* 44 */ "The argument string is empty.", /* 45 */ "External command error.", /* 46 */ "Escape time out of range.", /* 47 */ "Prefs stack is full.", /* 48 */ "Prefs stack is empty.", /* 49 */ "The argument is not a number.", /* 50 */ "This character is not supported in this configuration.", /* 51 */ "This string is not supported in this configuration.", /* 52 */ "This buffer is not UTF-8 encoded.", /* 53 */ "This clip cannot be pasted in this buffer (incompatible encoding).", /* 54 */ "This command line cannot be executed in this buffer (incompatible encoding).", /* 55 */ "This string cannot be searched for in this buffer (incompatible encoding).", /* 56 */ "This replacement string cannot be used in this buffer (incompatible encoding).", /* 57 */ "UTF-8 character classes in regular expressions are not supported.", /* 58 */ "Character classes cannot be complemented when matching against UTF-8 text.", /* 59 */ "The specified regex replacement group is not available in UTF-8 mode", /* 60 */ "Syntax highlighting is not enabled", /* 61 */ "There is no syntax for that extension", /* 62 */ "Invalid Shift specified (use [<|>][#][s|t]; default is \">1t\").", /* 63 */ "Insufficient white space for requested left shift.", /* 64 */ "Document not saved.", /* 65*/ "File is too large--syntax highlighting disabled (use SYNTAX to reactivate)." }; char *info_msg[INFO_COUNT] = { "Saving...", "Saved.", "All modified documents saved.", "Select file or press F1, Escape or Escape-Escape to enter a file name.", "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.", "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, backspace. REORDER: F2/F3. ABORT: Esc", "File has been modified since buffer was loaded or saved; are you sure?" }; ne-3.0.1/src/errors.h0000664000175200017510000000771112517741342013246 0ustar vignaprof/* Error index enum, and extern declaration of the error vector. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2015 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 */ CANT_OPEN_FILE, /* 30 */ CANT_OPEN_TEMPORARY_FILE, /* 31 */ ERROR_WHILE_WRITING, /* 32 */ HAS_NO_EXTENSION, /* 33 */ CANT_FIND_PREFS_DIR, /* 34 */ CLIP_DOESNT_EXIST, /* 35 */ MARK_OUT_OF_BUFFER, /* 36 */ CANT_OPEN_MACRO, /* 37 */ MAX_MACRO_DEPTH_EXCEEDED, /* 38 */ FILE_IS_READ_ONLY, /* 39 */ FILE_IS_MIGRATED, /* 40 */ FILE_IS_DIRECTORY, /* 41 */ FILE_IS_TOO_LARGE, /* 42 */ STOPPED, /* 43 */ IO_ERROR, /* 44 */ STRING_IS_EMPTY, /* 45 */ EXTERNAL_COMMAND_ERROR, /* 46 */ ESCAPE_TIME_OUT_OF_RANGE, /* 47 */ PREFS_STACK_FULL, /* 48 */ PREFS_STACK_EMPTY, /* 49 */ NOT_A_NUMBER, /* 50 */ INVALID_CHARACTER, /* 51 */ INVALID_STRING, /* 52 */ BUFFER_IS_NOT_UTF8, /* 53 */ INCOMPATIBLE_CLIP_ENCODING, /* 54 */ INCOMPATIBLE_COMMAND_ENCODING, /* 55 */ INCOMPATIBLE_SEARCH_STRING_ENCODING, /* 56 */ INCOMPATIBLE_REPLACE_STRING_ENCODING, /* 57 */ UTF8_REGEXP_CHARACTER_CLASS_NOT_SUPPORTED, /* 58 */ UTF8_REGEXP_COMP_CHARACTER_CLASS_NOT_SUPPORTED, /* 59 */ GROUP_NOT_AVAILABLE, /* 60 */ SYNTAX_NOT_ENABLED, /* 61 */ NO_SYNTAX_FOR_EXT, /* 62 */ INVALID_SHIFT_SPECIFIED, /* 63 */ INSUFFICIENT_WHITESPACE, /* 64 */ DOCUMENT_NOT_SAVED, /* 65 */ FILE_TOO_LARGE_SYNTAX_HIGHLIGHTING_DISABLED, ERROR_COUNT }; enum info { SAVING, SAVED, MODIFIED_SAVED, PRESSF1, BLOCK_START_MARKED, VERTICAL_BLOCK_START_MARKED, STARTING_MACRO_RECORDING, MACRO_RECORDING_COMPLETED, 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, INFO_COUNT }; extern char *error_msg[ERROR_COUNT]; extern char *info_msg[INFO_COUNT]; ne-3.0.1/src/exec.c0000664000175200017510000000514512517166512012650 0ustar vignaprof/* List handling functions. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2015 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.0.1/src/ext.c0000664000175200017510000000507212542017454012521 0ustar vignaprof/* Extern's for names and abbreviations of all the commands. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2015 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[] = { { "1", "troff" }, { "adb", "ada" }, { "ads", "ada" }, { "bash", "sh" }, { "bash_login", "sh" }, { "bash_logout", "sh" }, { "bash_profile", "sh" }, { "bashrc", "sh" }, { "c++", "c" }, { "cbl", "cobol" }, { "cc", "c" }, { "cob", "cobol" }, { "cpp", "c" }, { "dtx", "tex" }, { "el", "lisp" }, { "eps", "ps" }, { "f", "fortran" }, { "for", "fortran" }, { "h", "c" }, { "h++", "c" }, { "hpp", "c" }, { "htm", "html" }, { "il", "skill" }, { "js", "java" }, { "ksh", "sh" }, { "l", "c" }, { "latex", "tex" }, { "lex", "c" }, { "lsp", "lisp" }, { "mas", "mason" }, { "ml", "ocaml" }, { "mli", "ocaml" }, { "p", "pascal" }, { "pas", "pascal" }, { "patch", "diff" }, { "pl", "perl" }, { "pm", "perl" }, { "pp", "puppet" }, { "profile", "sh" }, { "py", "python" }, { "rb", "ruby" }, { "rc", "sh" }, { "rex", "rexx" }, { "s", "asm" }, { "sage", "python" }, { "sty", "tex" }, { "tcsh", "csh" }, { "texi", "texinfo" }, { "txi", "texinfo" }, { "v", "verilog" }, { "vh", "verilog" }, { "vhd", "verilog" }, { "xsd", "xml" }, { "y", "c" }, { "yacc", "c" } }; 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.0.1/src/genlong.c0000644000175200017510000000043612517461760013354 0ustar vignaprof#include #include #include int main() { for(int64_t i = 0; i < ( 1ULL << 31 ) + 2; i++ ) { printf("%x %x %x %x\n", (uint16_t)i, (uint16_t)(i >> 16), (uint16_t)(i >> 32), (uint16_t)(i >> 48) ); if ( i % 10000 == 0 ) printf("\n"); } return 0; } ne-3.0.1/src/hash.c0000664000175200017510000007072312542017454012651 0ustar vignaprof/* Precompiled hash tables for internal commands. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2015 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 "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. */ const unsigned char hash_table[HASH_TABLE_SIZE] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 92, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 117, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 104, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 132, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 38, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 70, 56, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 63, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 77, 0, 0, 0, 0, 87, 45, 0, 0, 0, 0, 0, 111, 0, 0, 0, 0, 0, 0, 0, 0, 61, 0, 0, 0, 39, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 107, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 46, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 115, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 113, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 71, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19, 105, 0, 0, 0, 0, 0, 0, 129, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 109, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 91, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 103, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13, 0, 0, 25, 0, 0, 0, 0, 99, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 137, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 126, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 127, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 95, 0, 0, 0, 0, 0, 0, 0, 0, 0, 58, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 54, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 27, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 53, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 94, 0, 0, 0, 0, 0, 0, 90, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 0, 0, 0, 0, 84, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 123, 0, 0, 0, 0, 0, 66, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 86, 0, 0, 0, 0, 0, 133, 0, 0, 0, 0, 121, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 69, 0, 0, 0, 0, 0, 0, 0, 124, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 22, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 0, 93, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 138, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 125, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 102, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 23, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 72, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 110, 0, 0, 0, 0, 0, 0, 0, 0, 28, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 98, 80, 122, 0, 0, 0, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 79, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 106, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 114, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 131, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 130, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 97, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 83, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 82, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40, 0, 0, 0, 0, 0, 0, 0, 75, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 43, 0, 0, 0, 0, 0, 0, 0, 0, 0, 119, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 41, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 47, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 81, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 26, 0, 0, 120, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 52, 0, 0, 0, 0, 101, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 76, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 74, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 48, 0, 0, 0, 67, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 55, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 108, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 136, 0, 0, 0, 0, 0, 0, 0, 134, 0, 0, 0, 0, 0, 0, 0, 0, 49, 0, 0, 0, 0, 0, 0, 116, 0, 0, 112, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 0, 0, 0, 0, 59, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 21, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 78, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 118, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 89, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 88, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 85, 0, 0, 0, 0, 0, 0, 0, 68, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 20, 0, 0, 0, 73, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 36, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 135, 0, 0, 0, 0, 0, 57, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; const unsigned char short_hash_table[HASH_TABLE_SIZE] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 124, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 18, 0, 28, 34, 0, 43, 45, 0, 0, 0, 56, 71, 77, 84, 93, 99, 104, 123, 130, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 81, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 101, 0, 0, 0, 0, 0, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 88, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 75, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 133, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 134, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 59, 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 55, 0, 0, 83, 0, 100, 106, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 58, 0, 0, 0, 0, 0, 112, 0, 0, 137, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 21, 0, 0, 40, 44, 46, 0, 50, 0, 0, 0, 78, 0, 0, 0, 108, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 0, 0, 0, 0, 0, 0, 0, 0, 51, 0, 72, 0, 89, 0, 0, 113, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 14, 22, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 38, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 115, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 15, 23, 0, 37, 41, 0, 47, 0, 0, 0, 66, 0, 0, 86, 0, 98, 105, 126, 129, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42, 0, 0, 0, 0, 0, 0, 0, 79, 0, 0, 103, 110, 0, 131, 136, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 94, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 54, 0, 73, 0, 90, 0, 0, 111, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 67, 0, 0, 0, 0, 0, 0, 0, 0, 102, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 13, 0, 0, 0, 0, 0, 48, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 122, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 26, 30, 0, 0, 0, 49, 0, 0, 0, 0, 0, 0, 0, 0, 0, 117, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 20, 27, 0, 0, 0, 0, 0, 0, 0, 52, 0, 0, 0, 0, 0, 0, 118, 127, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 57, 0, 0, 85, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 74, 0, 91, 0, 0, 0, 0, 0, 0, 138, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 31, 35, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 119, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 69, 0, 0, 0, 0, 0, 70, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 121, 0, 53, 0, 0, 0, 0, 0, 0, 107, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 125, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 109, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 120, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 92, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 63, 0, 0, 0, 76, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 39, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 114, 0, 132, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 97, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 82, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 116, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 36, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 135, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 61, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 68, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 87, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 95, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; /* This file was automatically generated by info2src.pl. */ ne-3.0.1/src/hash.h0000664000175200017510000000317412542017454012652 0ustar vignaprof/* Header for precompiled hash tables for internal commands. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2015 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 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 (3361) 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 16 /* This file was automatically generated by info2src.pl. */ ne-3.0.1/src/help.c0000664000175200017510000024001412542017454012646 0ustar vignaprof/* Help strings. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2015 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 . */ const char * const ABOUT_HELP[7] = { "Syntax: About", "Abbreviation: About", "", "displays the copyright splash screen and places a simple information", "line containing the version and build date of 'ne' on the status bar.", "Press any key to dismiss this screen.", }; const char * const ADJUSTVIEW_HELP[26] = { "Syntax: AdjustView [T|M|B|L|C|R] [N]", "Abbreviation: AV", "", "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 TAB size and the length and", "width of the current document. If called with no arguments 'T' is", "assumed.", "", " 'T', 'M', and 'B' cause vertical shifts so that the current line", "becomes the top, middle, or bottom-most visible line respectively.", "", " 'L', 'C', and 'R' cause horizontal shifts, making the current column", "the left-most, center, or right-most visible positions.", "", " A optional number N immediately after 'T', 'B', 'L', or '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 'AdjustView TL' shifts the view so that the current", "position becomes the top left-most character on screen (within the", "limits of the current TAB size). Likewise, 'AdjustView B3R5' shifts the", "view three lines toward the bottom and five columns (excepting TAB size)", "toward the right.", }; const char * const ALERT_HELP[5] = { "Syntax: Alert", "Abbreviation: AL", "", "beeps or flashes, depending on the value of the visual bell flag.", }; const char * const ATOMICUNDO_HELP[22] = { "Syntax: AtomicUndo [0|+|-]", "Abbreviation: AU", "", "increases, decreases, sets or clears the 'AtomicUndo' level. The normal", "level is zero. All current document changes made while the 'AtomicUndo'", "level is above zero are treated as a single change by the 'Undo' and", "'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 '+' and '-' respectively", "increment and decrement the level, which in no case can be negative. If", "the level is above zero, the 'DoUndo' flag in the status bar, which is", "normally a lower-case 'u', becomes upper case 'U'.", "", " Two other actions will reset the 'AtomicUndo' level to zero: invoking", "the 'Undo' command, and disabling the undo system with the 'DoUndo'", "command. You cannot set a non-zero '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 'AtomicUndo +' and end with", "'AtomicUndo -' so that they can call and/or be called by other macros.", }; const char * const AUTOCOMPLETE_HELP[20] = { "Syntax: AutoComplete [PREFIX]", "Abbreviation: AC", "", "attempts to extend the PREFIX using matching words from your open", "documents, and inserts the extended text into your document. If the", "PREFIX can be extended unambiguously, the matching text is immediately", "inserted into your document. Otherwise, 'ne' displays a selection of", "all words in open documents that match 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 PREFIX is given", "on the command line, or if 'AutoComplete' is selected from the '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 PREFIX. Note", "that if no word characters are to the left of the cursor, or the PREFIX", "given on the command line is an empty string ('\"\"'), then all words in", "all your open documents are displayed. Prefix matches may be case", "sensitive or not depending on the current document's 'CaseSearch' flag", "state. See `CaseSearch'.", }; const char * const AUTOINDENT_HELP[22] = { "Syntax: AutoIndent [0|1]", "Abbreviation: AI", "", "sets the auto indent flag. When this flag is true, 'ne' will", "automatically insert TABs and spaces on a new line (created by an", "'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 '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 'a' will appear on the status bar if the", "flag is true.", "", " 'AutoIndent' features a nice interaction with '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 '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 ", "`Undo'.", }; const char * const AUTOMATCHBRACKET_HELP[11] = { "Syntax: AutoMatchBracket [0..15]", "Abbreviation: AMB", "", "sets the auto match bracket mode. When the cursor is on a recognized", "bracket ('{}', '()', '[]', '<>', or '`'') 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, 'ne'", "prompts you for one. The default mode is 1. See `MatchBracket'.", }; const char * const AUTOPREFS_HELP[14] = { "Syntax: AutoPrefs [0|1]", "Abbreviation: AP", "", "sets the automatic preferences flag. If this flag is true, each time an", "'Open' command is executed and a file is loaded, 'ne' will look for an", "automatic preferences file in your '~/.ne' directory. The preferences", "file name is given by the extension of the file loaded, postfixed with", "'#ap'. Thus, for instance, C sources have an associated 'c#ap' file.", "", " If you invoke '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 'p' will appear on the status bar if the", "flag is true.", }; const char * const BACKSPACE_HELP[6] = { "Syntax: Backspace [N]", "Abbreviation: BS", "", "acts like 'DeleteChar', but moves the cursor to the left before deleting", "each character.", }; const char * const BEEP_HELP[6] = { "Syntax: Beep", "Abbreviation: BE", "", "beeps. If your terminal cannot beep, it flashes. If it cannot flash,", "nothing happens (but you have a very bad terminal).", }; const char * const BINARY_HELP[22] = { "Syntax: Binary [0|1]", "Abbreviation: B", "", "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 'ne' should be", "considered not a normal activity, but rather an exceptional one.", "", " If you invoke '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 'B' will appear on the status bar if the", "flag is true.", }; const char * const CAPITALIZE_HELP[6] = { "Syntax: Capitalize [N]", "Abbreviation: CA", "", "acts exactly like 'ToUpper', but capitalizes, that is, makes the first", "letter upper case and the other ones lower case. See `ToUpper'.", }; const char * const CASESEARCH_HELP[12] = { "Syntax: CaseSearch [0|1]", "Abbreviation: CS", "", "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 '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 'c' will appear on the status bar if the", "flag is true.", }; const char * const CENTER_HELP[8] = { "Syntax: Center [N]", "Abbreviation: CE", "", "centers N lines from the cursor position onwards. If 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 'RightMargin'", "command. See `RightMargin'.", }; const char * const CLEAR_HELP[7] = { "Syntax: Clear", "Abbreviation: CL", "", "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.", }; const char * const CLIPNUMBER_HELP[11] = { "Syntax: ClipNumber [N]", "Abbreviation: CN", "", "sets the current clip number. This number is used by 'OpenClip' and", "'SaveClip', and by 'Copy', 'Cut' and 'Paste' if they are called without", "any argument. Its default value is zero. N is limited only by the", "integer size of the machine 'ne' is running on.", "", " If the optional argument N is not specified, you can enter it on the", "input line, the default being the current clip number.", }; const char * const CLOSEDOC_HELP[7] = { "Syntax: CloseDoc", "Abbreviation: CD", "", "closes the current document. The document is removed from 'ne''s list", "and, if it is the only existing document, 'ne' exits. If the document", "was modified since it was last saved, you have to confirm the action.", }; const char * const COPY_HELP[10] = { "Syntax: Copy [N]", "Abbreviation: C", "", "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", "'ClipNumber' command; see `ClipNumber'. If the current mark was", "vertical, the rectangle of characters defined by the cursor and the mark", "is copied instead.", }; const char * const CRLF_HELP[15] = { "Syntax: CRLF [0|1]", "Abbreviation: CRLF", "", "sets the CR/LF flag. When a file is saved from a buffer 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 into it.", "", " If you invoke '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 'C' will appear on the status bar if the flag is true.", }; const char * const CUT_HELP[5] = { "Syntax: Cut [N]", "Abbreviation: CU", "", "acts just like 'Copy', but also deletes the block being copied.", }; const char * const DELETECHAR_HELP[10] = { "Syntax: DeleteChar [N]", "Abbreviation: DC", "", "deletes N characters from the text. If the optional 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.", }; const char * const DELETEEOL_HELP[10] = { "Syntax: DeleteEOL", "Abbreviation: DE", "", "deletes all characters from the current cursor position to the end of", "the line.", "", " 'DeleteEOL' could be easily implemented with a macro, but it is such", "a common, basic editing feature that it seemed worth a separate", "implementation.", }; const char * const DELETELINE_HELP[9] = { "Syntax: DeleteLine [N]", "Abbreviation: DL", "", "deletes N lines starting from the current cursor position, putting the", "last one in the temporary buffer, from which it can be undeleted. See", "`UndelLine'. If the optional N argument is not specified, it is", "assumed to be one. Note that this action is in no way inverse with", "respect to 'InsertLine'.", }; const char * const DELETENEXTWORD_HELP[6] = { "Syntax: DeleteNextWord [N]", "Abbreviation: DNW", "", "deletes text from the current position to the next word N times. If the", "optional N argument is not specified, it is assumed to be one.", }; const char * const DELETEPREVWORD_HELP[8] = { "Syntax: DeletePrevWord [N]", "Abbreviation: DPW", "", "deletes text from the current position to the first character of the", "previous word N times. If the optional 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).", }; const char * const DELTABS_HELP[9] = { "Syntax: DelTabs [0|1]", "Abbreviation: DT", "", "sets the 'DelTabs' flag. When this flag is set, a 'd' will appear on", "the status bar, and the and keys will remove a tab's", "worth of SPACE characters if a TAB character could have occupied the", "same whitespace in the current line as the removed spaces. This is the", "deletion counterpart to the 'Tabs' flag. `Tabs'.", }; const char * const DOUNDO_HELP[21] = { "Syntax: DoUndo [0|1]", "Abbreviation: DU", "", "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 '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 'u' will appear on the status bar if the", "flag is true. (The 'U' will be upper case if the flag is true and the", "'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.", }; const char * const ERASE_HELP[6] = { "Syntax: Erase", "Abbreviation: E", "", "acts like 'Cut', but the block is just deleted and not copied into any", "clip.", }; const char * const ESCAPE_HELP[7] = { "Syntax: Escape", "Abbreviation: ESC", "", "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 `Record'.", }; const char * const ESCAPETIME_HELP[16] = { "Syntax: EscapeTime [N]", "Abbreviation: ET", "", "sets the escape time. The key is recognized as such after N", "tenths of second. 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. Note that you can", "accelerate the recognition of the key by hitting it twice in a", "row.", "", " Note that the escape time is global to 'ne', and it is not saved.", "However, you can add an 'EscapeTime' command manually to a preferences", "file.", }; const char * const EXEC_HELP[14] = { "Syntax: Exec", "Abbreviation: EX", "", "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).", "", " '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 'ne''s", "internal tables, it is considered to be a macro name. See ", "`Macro'.", }; const char * const EXIT_HELP[11] = { "Syntax: Exit", "Abbreviation: X", "", "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 buffers can be saved,", "so 'Exit' will report an error if you have any modified unnamed buffers.", "Like 'SaveAll', 'Exit' will not save a document if its corresponding", "file has been modified since the document was loaded or last saved, in", "which case an error is reported and no documents are closed.", }; const char * const FASTGUI_HELP[19] = { "Syntax: FastGUI [0|1]", "Abbreviation: FG", "", "sets the fast graphical user interface flag. When this flag is true,", "'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) and the hexadecimal code is not displayed. This", "option is only (but very) useful if you are using 'ne' through a slow", "connection.", "", " If you invoke '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 'FastGUI' setting is saved in your '~/.ne/.default#ap' file when", "you use the 'SaveDefPrefs' command or the 'Save Def Prefs' menu. It is", "not saved by the 'SaveAutoPrefs' command.", }; const char * const FIND_HELP[12] = { "Syntax: Find [PATTERN]", "Abbreviation: F", "", "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 ", "`SearchBack', and `CaseSearch'.", "", " If the optional argument PATTERN is not specified, you can enter it", "on the input line, the default being the last pattern used.", }; const char * const FINDREGEXP_HELP[12] = { "Syntax: FindRegExp [PATTERN]", "Abbreviation: FX", "", "searches the current document for the given extended regular expression", ". 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 `SearchBack',", "and `CaseSearch'.", "", " If the optional argument PATTERN is not specified, you can enter it", "on the input line, the default being the last pattern used.", }; const char * const FLAGS_HELP[34] = { "Syntax: Flags", "Abbreviation: FLAG", "", "displays a list of all the status flags for ne and their associated", "commands. It is not recorded when recording a macro.", "", " 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", " t/T Tabs TAB TAB key inserts TABs instead of spaces", " T ShiftTabs ST 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", " * Modified MOD document has been modified since last saved", " @ 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)", "", " The 'RequestOrder' and 'AutoMatchBracket' flags' states are not", "indicated on the status bar. See `RequestOrder' and ", "`AutoMatchBracket' respectively.", }; const char * const FLASH_HELP[6] = { "Syntax: Flash", "Abbreviation: FL", "", "acts as 'Beep', but interchanging the words \"beep\" and \"flash\". Same", "comments apply. See `Beep'.", }; const char * const FREEFORM_HELP[18] = { "Syntax: FreeForm [0|1]", "Abbreviation: FF", "", "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 TAB", "character).", "", " If you invoke '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 '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.", }; const char * const GOTOBOOKMARK_HELP[15] = { "Syntax: GotoBookmark [N|+1|-1|-]", "Abbreviation: GBM", "", "moves the cursor to the designated bookmark if that bookmark is set; see", "`SetBookmark'. Each document has 10 available bookmarks", "designated '0' to '9', plus the automatic bookmark designated by '-'.", "If no option is given, '0' is assumed. The optons '+1' and '-1'", "indicate respectively the next and previous set bookmarks, so that", "repeated 'GotoBookmark +1' commands will cycle through all currently set", "bookmarks. When successful, the '-' automatic bookmark is set to the", "position in the document from which the command was issued, so that", "'GotoBookmark -' returns you to the location from which you last issued", "a successful 'GotoBookmark' command. Subsequent repeated 'GotoBookmark", "-' commands will toggle you between the two locations.", }; const char * const GOTOCOLUMN_HELP[8] = { "Syntax: GotoColumn [COLUMN]", "Abbreviation: GC", "", "moves the cursor to the COLUMNth column of the file.", "", " If the optional argument LINE is not specified, you can enter it on", "the input line; the default input response is the current column number.", }; const char * const GOTOLINE_HELP[10] = { "Syntax: GotoLine [LINE]", "Abbreviation: GL", "", "moves the cursor to the LINEth line of the file. If 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 LINE is not specified, you can enter it on", "the input line; the default input response is the current line number.", }; const char * const GOTOMARK_HELP[9] = { "Syntax: GotoMark", "Abbreviation: GM", "", "moves the cursor to the current mark, if it exists. See `Mark'.", "", " '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 a bookmarks. See `SetBookmark'.", }; const char * const HELP_HELP[14] = { "Syntax: Help [NAME]", "Abbreviation: H", "", "displays some help about the command 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 , the command list will be displayed again.", "If you press or , you will return to normal editing.", "", " Invocations of the 'Help' command are never registered while", "recording macros so that you can safely access the help system while", "recording. See `Record'.", }; const char * const HEXCODE_HELP[6] = { "Syntax: HexCode [0|1]", "Abbreviation: HC", "", "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.", }; const char * const INSERT_HELP[12] = { "Syntax: Insert [0|1]", "Abbreviation: I", "", "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 'InsertChar' and 'InsertString' commands.", "", " If you invoke '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 'i' will appear on the status bar if the", "flag is true.", }; const char * const INSERTCHAR_HELP[21] = { "Syntax: InsertChar [CODE]", "Abbreviation: IC", "", "inserts a character whose ASCII code is CODE at the current cursor", "position. CODE can be either decimal, hexadecimal if preceded by '0x',", "or octal if preceded by '0'. In any case, CODE must be different from", "0. All the currently active preferences options (insert, word wrapping,", "auto indent, et cetera) are applied.", "", " If the optional argument 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 'InsertLine'. 'InsertChar 10' puts the control", "char '-J' in the text at the current cursor position. See", "`InsertLine'.", "", " Note also that 'SaveMacro' converts 'InsertChar' commands into a", "possibly smaller number of 'InsertString' commands. This makes macros", "easier to read and edit. See `SaveMacro'.", }; const char * const INSERTLINE_HELP[7] = { "Syntax: InsertLine [N]", "Abbreviation: IL", "", "inserts N lines at the current cursor position, breaking the current", "line. If the optional N argument is not specified, it is assumed to be", "one.", }; const char * const INSERTSTRING_HELP[12] = { "Syntax: InsertString [TEXT]", "Abbreviation: IS", "", "inserts TEXT at the current cursor position. If the optional argument", "TEXT is omitted, you will be prompted for it on the command line. All", "the currently active preferences options (insert, word wrapping, auto", "indent, et cetera) are applied.", "", " Note that 'SaveMacro' converts 'InsertChar' commands into a possibly", "smaller number of 'InsertString' commands. This makes macros easier to", "read and edit. See `SaveMacro'.", }; const char * const INSERTTAB_HELP[7] = { "Syntax: InsertTab [N]", "Abbreviation: IT", "", "inserts either N literal TAB characters or one or more spaces sufficient", "to advance the current cursor position N tab stops depending on the", "'Tabs' flag. See `Tabs', `TabSize'.", }; const char * const KEYCODE_HELP[8] = { "Syntax: KeyCode", "Abbreviation: KC", "", "prompts you to press a key, and reports on the status bar the key code", "'ne' associates with that key. This can be useful while configuring", "your '~/.ne/.keys' file. It also reports the input class for that key.", "Input class codes are: ALPHA, COMMAND, RETURN, TAB, IGNORE, and INVALID.", }; const char * const LINEDOWN_HELP[6] = { "Syntax: LineDown [N]", "Abbreviation: LD", "", "moves the cursor down by one line N times. If the optional N argument", "is not specified, it is assumed to be one.", }; const char * const LINEUP_HELP[6] = { "Syntax: LineUp [N]", "Abbreviation: LU", "", "moves the cursor up by one line N times. If the optional N argument is", "not specified, it is assumed to be one.", }; const char * const LOADAUTOPREFS_HELP[7] = { "Syntax: LoadAutoPrefs", "Abbreviation: LAP", "", "loads the preferences file in '~/.ne' associated with the current", "document's file name extension. If the current file name has no", "extension, the default preferences are loaded. See `AutoPrefs'.", }; const char * const LOADPREFS_HELP[16] = { "Syntax: LoadPrefs [FILENAME]", "Abbreviation: LP", "", "loads the given preference file, and sets the current preferences", "accordingly.", "", " If the optional 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 'NoFileReq' command; see", "`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.", }; const char * const MACRO_HELP[33] = { "Syntax: Macro [FILENAME]", "Abbreviation: MA", "", "executes the given file name as a macro.", "", " If the optional 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 'NoFileReq' command; see", "`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 'Macro'. Whenever '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, 'ne'", "looks in '~/.ne' and finally in the 'macros' subdirectory of 'ne''s", "global directory (defined when 'ne' was built, or in a place specified", "by your 'NE_GLOBAL_DIR' environment variable) for a macro file by that", "name.", "", "*Warning:* the first time a macro is executed it is cached into a hash", "table and is kept _forever_ in memory unless the 'UnloadMacros' command", "is issued; see `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 '~/foobar/macro', a subsequent call for '/usr/MACRO' or even", "just 'MaCrO' will use the cached version of '~/foobar/macro'. Note that", "the cache table is global to 'ne' and not specific to any single", "document. This greatly improves efficiency when macros are used", "repeatedly.", }; const char * const MARK_HELP[12] = { "Syntax: Mark [0|1]", "Abbreviation: M", "", "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", "('Cut', 'Copy', 'Erase') and left and right shifts operate .", "", " If you invoke 'Mark' with no arguments, it will set the mark. If you", "specify 0 or 1, the mark will be canceled or set to the current", "position, respectively. A capital 'M' appears on the status bar, if the", "mark is active.", }; const char * const MARKVERT_HELP[21] = { "Syntax: MarkVert [0|1]", "Abbreviation: MV", "", "is the same as 'Mark', but the region manipulated by the cut/paste", "commands is the rectangle having as vertices the cursor and the mark.", "If you invoke 'MarkVert' with no arguments, it will set the mark. If", "you specify 0 or 1, the mark will be canceled or set to the current", "position, respectively. Moreover, a capital 'V', rather than a capital", "'M', will appear on the status bar.", "", " For example, if you have the following text:", " aaaBbbccc", " aaabbbccc", " aaabbbCcc", "and you set a vertical mark at 'B' then move the cursor to 'C', you can", "cut or copy all of the 'B's.", "", " If you have made a vertical cut or copy, it's very likely you will", "want to use 'PasteVert' rather than the usual 'Paste' to reinsert the", "text in a rectangle. See `PasteVert'.", }; const char * const MATCHBRACKET_HELP[9] = { "Syntax: MatchBracket", "Abbreviation: MB", "", "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 '{}', '()', '[]' '<>', and '`''. See ", "`AutoMatchBracket'.", }; const char * const MODIFIED_HELP[14] = { "Syntax: Modified [0|1]", "Abbreviation: MOD", "", "sets the modified flag. This flag is set automatically whenever a", "buffer is modified, and is used to determine which buffers need to be", "saved when 'ne' exits. Normally you would not alter this flag, but when", "a buffer is inadvertently modified and you don't want the changes saved,", "'Modified' provides a way to make 'ne' consider the buffer unchanged.", "", " If you invoke '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 ('*') will appear on the status bar if the", "flag is true.", }; const char * const MOVEBOS_HELP[6] = { "Syntax: MoveBOS", "Abbreviation: BOS", "", "moves the cursor to the lowest line currently visible ('BOS' = bottom of", "screen).", }; const char * const MOVEEOF_HELP[5] = { "Syntax: MoveEOF", "Abbreviation: EOF", "", "moves the cursor to the end of the document ('EOF' = end of file).", }; const char * const MOVEEOL_HELP[5] = { "Syntax: MoveEOL", "Abbreviation: EOL", "", "moves the cursor to the end of the current line ('EOL' = end of line).", }; const char * const MOVEEOW_HELP[8] = { "Syntax: MoveEOW", "Abbreviation: EOW", "", "moves the cursor one character past the end of the current word.", "", " 'MoveEOW' is extremely useful in macros, because it allows you to", "copy precisely the word the cursor is on.", }; const char * const MOVEINCDOWN_HELP[9] = { "Syntax: MoveIncDown", "Abbreviation: MID", "", "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.", }; const char * const MOVEINCUP_HELP[9] = { "Syntax: MoveIncUp", "Abbreviation: MIU", "", "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.", }; const char * const MOVELEFT_HELP[6] = { "Syntax: MoveLeft [N]", "Abbreviation: ML", "", "moves the cursor to the left by one character N times. If the optional", "N argument is not specified, it is assumed to be one.", }; const char * const MOVERIGHT_HELP[6] = { "Syntax: MoveRight [N]", "Abbreviation: MR", "", "moves the cursor to the right by one character N times. If the optional", "N argument is not specified, it is assumed to be one.", }; const char * const MOVESOF_HELP[5] = { "Syntax: MoveSOF", "Abbreviation: SOF", "", "moves the cursor to the start of the document ('SOF' = start of file).", }; const char * const MOVESOL_HELP[6] = { "Syntax: MoveSOL", "Abbreviation: SOL", "", "moves the cursor to the start of the current line ('SOL' = start of", "line).", }; const char * const MOVETOS_HELP[5] = { "Syntax: MoveTOS", "Abbreviation: TOS", "", "moves the cursor to the top line of the screen ('TOS' = top of screen).", }; const char * const NEWDOC_HELP[8] = { "Syntax: NewDoc", "Abbreviation: N", "", "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.", }; const char * const NEXTDOC_HELP[5] = { "Syntax: NextDoc", "Abbreviation: ND", "", "sets as current document the next document in the document list.", }; const char * const NEXTPAGE_HELP[8] = { "Syntax: NextPage [N]", "Abbreviation: NP", "", "moves the cursor 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 N-1 pages. If the optional N argument is not specified, it", "is assumed to be one.", }; const char * const NEXTWORD_HELP[6] = { "Syntax: NextWord [N]", "Abbreviation: NW", "", "moves the cursor to the next word N times. If the optional N argument", "is not specified, it is assumed to be one.", }; const char * const NOFILEREQ_HELP[10] = { "Syntax: NoFileReq [0|1]", "Abbreviation: NFR", "", "sets the file requester flag. When this flag is true, the file", "requester is never opened, under any circumstances.", "", " If you invoke '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.", }; const char * const NOP_HELP[5] = { "Syntax: NOP", "Abbreviation: NOP", "", "does nothing. Mainly useful for inhibiting standard key bindings.", }; const char * const OPEN_HELP[18] = { "Syntax: Open [FILENAME]", "Abbreviation: O", "", "loads the file specified by the FILENAME string into the current", "document.", "", " If the optional 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 'NoFileReq' command; see", "`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.", }; const char * const OPENCLIP_HELP[14] = { "Syntax: OpenClip [FILENAME]", "Abbreviation: OC", "", "loads the given file name as the current clip, just as if you cut or", "copied it from the current document; see `Copy'.", "", " If the optional 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 'NoFileReq' command; see", "`NoFileReq'.)", "", " If you escape from the file requester, you can enter the file name on", "the input line.", }; const char * const OPENMACRO_HELP[14] = { "Syntax: OpenMacro [FILENAME]", "Abbreviation: OM", "", "loads the given file name as the current macro just as if you 'Record'ed", "it; see `Record'.", "", " If the optional 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 'NoFileReq' command; see", "`NoFileReq'.)", "", " If you escape from the file requester, you can input the file name on", "the command line.", }; const char * const OPENNEW_HELP[6] = { "Syntax: OpenNew [FILENAME]", "Abbreviation: ON", "", "is the same as 'Open', but loads the file specified by the FILENAME", "string into a new document. See `Open'.", }; const char * const PAGEDOWN_HELP[6] = { "Syntax: PageDown [N]", "Abbreviation: PDN", "", "pages the screen forward by N screens. If N is not specified, it is", "assumed to be one.", }; const char * const PAGEUP_HELP[6] = { "Syntax: PageUp [N]", "Abbreviation: PUP", "", "pages the screen backward by N screens. If N is not specified, it is", "assumed to be one.", }; const char * const PARAGRAPH_HELP[27] = { "Syntax: Paragraph [N]", "Abbreviation: PA", "", "reformats N paragraphs from the cursor position onwards. If N is not", "specified, it is assumed to be one. The paragraph are formatted", "relatively to the value of the right margin as set by the 'RightMargin'", "command. See `RightMargin'.", "", " '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. 'Paragraph' also takes into account", "characters commonly used at the left edge of block comments ('/', '*',", "'#', spaces, and tabs) or quoted email ('>'), and attempts to preserve", "those on the left edge when possible.", "", " After the '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).", "", " '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.", }; const char * const PASTE_HELP[7] = { "Syntax: Paste [N]", "Abbreviation: P", "", "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 `ClipNumber'.", }; const char * const PASTEVERT_HELP[7] = { "Syntax: PasteVert [N]", "Abbreviation: PV", "", "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.", }; const char * const PLAY_HELP[15] = { "Syntax: Play [TIMES]", "Abbreviation: PL", "", "plays the current macro for the given number of times. If the optional", "argument 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 '-\\'.", }; const char * const POPPREFS_HELP[24] = { "Syntax: PopPrefs [N]", "Abbreviation: POPP", "", "pops N sets of preferences from the preferences stack (where they were", "placed previously by 'PushPrefs') and applies those preferences to the", "current buffer. See `PushPrefs'. If not specified, N defaults to", "one. Note that the preferences stack is global, not buffer specific.", "Therefore you could 'PushPrefs' one buffer's preferences, switch", "buffers, then 'PopPrefs' those settings altering the preferences for the", "second buffer. The maximum preferences stack depth is 32.", "", " 'PushPrefs' and 'PopPrefs' are useful in macros that require certain", "preferences to work properly. A macro can 'PushPrefs', change any", "preferences necessary, do its work, then 'PopPrefs' to restore the users", "previous preferences settings.", "", "PopPrefs restores the following values from the preferences stack:", "", " AutoIndent DelTabs NoFileReq StatusBar VisualBell", " AutoPrefs DoUndo PreserveCR ShiftTabs WordWrap", " Binary FreeForm ReadOnly Tabs", " CaseSearch HexCode RightMargin TabSize", " ClipNumber Insert SearchBack UTF8Auto", }; const char * const PRESERVECR_HELP[15] = { "Syntax: PreserveCR [0|1]", "Abbreviation: PCR", "", "sets the preserve carriage returns flag. When a file is loaded into a", "buffer 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 buffer. This flag has no effect except when loading a", "file into a buffer.", "", " If you invoke '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 'P' will appear on the status bar if the", "flag is true.", }; const char * const PREVDOC_HELP[5] = { "Syntax: PrevDoc", "Abbreviation: PD", "", "sets as current document the previous document in the document list.", }; const char * const PREVPAGE_HELP[8] = { "Syntax: PrevPage [N]", "Abbreviation: PP", "", "moves the cursor 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 N-1 pages. If the optional N argument is not specified, it", "is assumed to be one.", }; const char * const PREVWORD_HELP[8] = { "Syntax: PrevWord [N]", "Abbreviation: PW", "", "moves the cursor to the first character of the previous word N times.", "If the optional 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 move it to the start of that word).", }; const char * const PUSHPREFS_HELP[24] = { "Syntax: PushPrefs [N]", "Abbreviation: PUSHP", "", "pushes N copies of the user preferences onto a stack. If not specified,", "N defaults to one. Use the 'PopPrefs' command to pop preferences off", "the stack and restore the values. See `PopPrefs'. Note that the", "preferences stack is global, not buffer-specific, so you could", "'PushPrefs' one buffer's preferences, switch buffers, then 'PopPrefs'", "those preferences, thereby altering the preferences for the second", "buffer. The maximum preferences stack depth is 32.", "", " 'PushPrefs' and 'PopPrefs' are useful in macros that require certain", "preferences to work properly. A macro can 'PushPrefs', change any", "preferences necessary, do its work, then 'PopPrefs' to restore the users", "previous preferences settings.", "", "'PushPrefs' saves the following values on the preferences stack:", "", " AutoIndent DelTabs NoFileReq StatusBar VisualBell", " AutoPrefs DoUndo PreserveCR ShiftTabs WordWrap", " Binary FreeForm ReadOnly Tabs", " CaseSearch HexCode RightMargin TabSize", " ClipNumber Insert SearchBack UTF8Auto", }; const char * const QUIT_HELP[6] = { "Syntax: Quit", "Abbreviation: Q", "", "closes all documents and exits. If any documents are modified, you have", "to confirm the action.", }; const char * const READONLY_HELP[13] = { "Syntax: ReadOnly [0|1]", "Abbreviation: RO", "", "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).", "This flag is automatically set whenever you open a file that you cannot", "write to. See `Open'.", "", " If you invoke '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 'r' will appear on the status bar if the", "flag is true.", }; const char * const RECORD_HELP[21] = { "Syntax: Record [0|1]", "Abbreviation: REC", "", "sets the recording state flag. When this flag becomes true, 'ne' starts", "recording your actions in a new macro. When it becomes false, the macro", "recording is stopped, and the macro can be played or saved via ", "`Play', or `SaveMacro'.", "", " If you call invoke 'Record' 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 'R' will appear on the status bar if the", "flag is true.", "", " The reason for providing a flag instead of an explicit start/stop", "recording command pair is that this way it is possible to bind both", "starting and stopping macro recording to a single key while still being", "able to specify \"absolute\" menu items (by using 'Record 0' and 'Record", "1'). For instance, the default key binding for '-T' is simply", "'Record', which means that this shortcut can be used both for initiating", "and for terminating a macro recording.", }; const char * const REDO_HELP[8] = { "Syntax: Redo [N]", "Abbreviation: RE", "", "redoes the last N actions undone by 'Undo' (as long as you don't take", "any actions that change the text between the 'Undo' and 'Redo'", "commands). If N is not specified, it is assumed to be one. You can", "only 'Redo' actions that have been 'Undo'ne. See `Undo'.", }; const char * const REFRESH_HELP[11] = { "Syntax: Refresh", "Abbreviation: REF", "", "refreshes the display. 'Refresh' is very important, and should", "preferably be bound to the '-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.", "", " '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.", }; const char * const REPEATLAST_HELP[25] = { "Syntax: RepeatLast [TIMES]", "Abbreviation: RL", "", "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 'Replace'", "operation ended with a global substitution).", "", " '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:", "", " 1. 'Find' (or 'FindRegExp') the string you are interested in;", "", " 2. if you want to repeat a replace operation, 'ReplaceOnce' with the", " replacement string you are interested in;", "", " 3. now issue a 'RepeatLast N-1' command, where N is the number of", " occurrences you wanted to skip over, or replace.", "", " The important thing about this sequence of actions is that it will", "work this way even in a macro. The 'Replace' command cannot be used in", "a macro unless you really want to interact with 'ne' during the macro", "execution. Avoiding interaction during macros is the primary reason the", "commands 'ReplaceAll' and 'ReplaceOnce' are provided.", }; const char * const REPLACE_HELP[45] = { "Syntax: Replace [STRING]", "Abbreviation: R", "", "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:", "", " * replacing the string found with the given string and moving to the", " next match ('Yes');", "", " * moving to the next match ('No');", "", " * replacing the string found with the given string, and stopping the", " search ('Last');", "", " * stopping the search immediately ('Quit');", "", " * replacing _all_ occurrences of the find string with the given", " string ('All');", "", " * reversing the search direction ('Backward' or 'Forward'); this", " choice will also modify the value of the back search flag. See", " `SearchBack'.", "", " 'Replace' is mainly useful for interactive editing. 'ReplaceOnce',", "'ReplaceAll' and '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 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 . See `FindRegExp'.", "", " Note that normally a search starts just one character after the", "cursor. However, when 'Replace' is invoked, the search starts at the", "character just _under_ the cursor, so that you can safely 'Find' a", "pattern and 'Replace' it without having to move back.", "", "*Warning:* when recording a macro with `Record', there is no trace", "in the macro of your interaction with '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 ", "`ReplaceOnce', `ReplaceAll', and `RepeatLast'.", }; const char * const REPLACEALL_HELP[12] = { "Syntax: ReplaceAll [STRING]", "Abbreviation: RA", "", "is similar to 'ReplaceOnce', but replaces _all_ occurrences of the last", "search pattern with the given replacement string.", "", " If the optional argument STRING is not specified, you can enter it on", "the input line, the default being the last string used.", "", " Note that 'Undo' will restore _all_ the occurrences of the search", "pattern replaced by 'ReplaceAll'. See `Undo'.", }; const char * const REPLACEONCE_HELP[10] = { "Syntax: ReplaceOnce [STRING]", "Abbreviation: R1", "", "acts just like '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 STRING is not specified, you can enter it on", "the input line, the default being the last string used.", }; const char * const REQUESTORDER_HELP[15] = { "Syntax: RequestOrder [0|1]", "Abbreviation: RQO", "", "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 '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 'RequestOrder' setting is saved in your '~/.ne/.default#ap' file", "when you use the 'SaveDefPrefs' command or the 'Save Def Prefs' menu.", "It is not saved by the 'SaveAutoPrefs' command.", }; const char * const RIGHTMARGIN_HELP[12] = { "Syntax: RightMargin [N]", "Abbreviation: RM", "", "sets the right margin for all formatting operations, and for 'WordWrap'.", "See `WordWrap'.", "", " If the optional argument 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 N will force 'ne' to use (what it thinks it is)", "the current screen width as right margin.", }; const char * const SAVE_HELP[17] = { "Syntax: Save", "Abbreviation: S", "", "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 'NoFileReq' command; see ", "`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), 'ne' will warn you before", "overwriting the updated file.", }; const char * const SAVEALL_HELP[10] = { "Syntax: SaveAll", "Abbreviation: SL", "", "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 buffers can be saved, so 'SaveAll' will report an error", "if you have any modified unnamed buffers, or if any of the modified", "documents' corresponding files have been updated since the buffers were", "loaded or last saved.", }; const char * const SAVEAS_HELP[18] = { "Syntax: SaveAs [FILENAME]", "Abbreviation: SA", "", "saves the current document using the specified string as the file name.", "", " If the optional 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 'NoFileReq' command; see", "`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), 'ne' will warn you before", "overwriting the updated file.", }; const char * const SAVEAUTOPREFS_HELP[7] = { "Syntax: SaveAutoPrefs", "Abbreviation: SAP", "", "saves the current preferences to the file in '~/.ne' associated with the", "current document's file name extension. If the current file name has no", "extension, an error message is issued. See `AutoPrefs'.", }; const char * const SAVECLIP_HELP[13] = { "Syntax: SaveClip [FILENAME]", "Abbreviation: SC", "", "saves the current clip to the given file name.", "", " If the optional 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 'NoFileReq' command; see", "`NoFileReq'.)", "", " If you escape from the file requester, you can enter the file name on", "the input line.", }; const char * const SAVEDEFPREFS_HELP[6] = { "Syntax: SaveDefPrefs", "Abbreviation: SDP", "", "saves the current preferences to the '~/.ne/.default#ap' file. This", "file is always loaded by 'ne' at startup.", }; const char * const SAVEMACRO_HELP[20] = { "Syntax: SaveMacro [FILENAME]", "Abbreviation: SM", "", "saves the current macro in a file with the given name.", "", " If the optional 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 'NoFileReq' command; see", "`NoFileReq'.)", "", " If you escape from the file requester, you can input the file name on", "the command line.", "", " '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 'SaveMacro' converts", "'InsertChar' commands into a possibly smaller number of 'InsertString'", "commands. This makes macros easier to read and edit. See ", "`InsertChar', and `InsertString'.", }; const char * const SAVEPREFS_HELP[11] = { "Syntax: SavePrefs [FILENAME]", "Abbreviation: SP", "", "saves the current preferences to the given file.", "", " If the optional 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 'NoFileReq' command; see", "`NoFileReq'.) If you escape from the file requester, you can", "input the file name on the command line.", }; const char * const SEARCHBACK_HELP[14] = { "Syntax: SearchBack [0|1]", "Abbreviation: SB", "", "sets the back search flag. When this flag is true, every search or", "replacement command is performed backwards.", "", " If you invoke '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 'b' will appear on the status bar if the", "flag is true.", "", " Note that this flag also can be set through interactions with the", "'Replace' command. See `Replace'.", }; const char * const SELECTDOC_HELP[22] = { "Syntax: SelectDoc", "Abbreviation: SD", "", "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.", "", " While the list of documents is displayed, you can alter their", "relative order using your 'NextDoc' and 'PrevDoc' keys ( and ),", "which have the effect of swapping your currently selected document with", "the next or previous document in the list, respectively.", "", " If you escape from the requester, the requester goes away, you are", "returned to your original current document, and no reordering of", "documents takes place.", "", " '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 `NextDoc', and `PrevDoc'.", }; const char * const SETBOOKMARK_HELP[12] = { "Syntax: SetBookmark [N|+1|-1|-]", "Abbreviation: SBM", "", "sets a document bookmark to the current cursor position. Each document", "has 10 available bookmarks designated '0' to '9', plus the automatic", "bookmark designated by '-'. If no option is given, '0' is assumed.", "Values of N from '0' to '9' set the Nth bookmark, while '+1' and '-1'", "indicate respectively the next and previous available unset bookmarks.", "You can also set the '-' automatic bookmark, but it will be reset", "automatically to the current position whenever a 'GotoBookmark' command", "is issued.", }; const char * const SHIFT_HELP[21] = { "Syntax: Shift [<|>][n][t|s]]", "Abbreviation: SH", "", "shifts the text on lines between the mark and the cursor either right", "('>', the default) or left ('<') by adding or removing white space on", "each line. The adjustment size, specified as an unsigned integer 'n',", "is in units of the current tab size ('t') or spaces ('s'). The default", "is 1. 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.", "", " 'Shift' will insert tab characters only if the document's 'Tabs' flag", "and the 'ShiftTabs' flag are both set--in which case an upper case 'T'", "will appear in the status bar. If either of the 'Tabs' or 'ShiftTabs'", "flags is unset (i.e there is no upper case 'T' in the status bar)", "'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", "'Shift' reports an error and makes no changes.", }; const char * const SHIFTTABS_HELP[9] = { "Syntax: ShiftTabs [0|1]", "Abbreviation: SHT", "", "sets the 'ShiftTabs' flag. 'ShiftTabs' has an effect only when the", "'Tabs' flag is set, in which case an upper case 'T' appears in the", "status bar. When this flag and the 'Tabs' flag are both set, left and", "right 'Shift' commands may use tab characters to adjust leading white", "space. Otherwise only spaces are used. `Shift'.", }; const char * const STATUSBAR_HELP[22] = { "Syntax: StatusBar [0|1]", "Abbreviation: ST", "", "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:", "", " * if you are using 'ne' through a slow connection, updating the", " line/column indicator can really slow down editing;", "", " * 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.", "", " If you invoke '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 'StatusBar' setting is saved in your '~/.ne/.default#ap' file", "when you use the 'SaveDefPrefs' command or the 'Save Def Prefs' menu.", "It is not saved by the 'SaveAutoPrefs' command.", }; const char * const SUSPEND_HELP[6] = { "Syntax: Suspend", "Abbreviation: SU", "", "suspends 'ne' and returns you to a shell prompt; usually, the shell", "command 'fg' is used to resume 'ne'.", }; const char * const SYNTAX_HELP[45] = { "Syntax: Syntax [NAME|*]", "Abbreviation: SY", "", "loads the syntax with the given name, and colors the current buffer", "accordingly.", "", " If the optional NAME argument is not specified, you are prompted for", "one. The current one, if set, is suggested as the default. Use the", " key for a requester of the extant syntax recognizers. The special", "NAME * turns off syntax highlighting for the current document.", "Otherwise, NAME must match a syntax definition either in your", "'~/.ne/syntax' directory or in a directory named 'syntax' inside 'ne''s", "global directory. Additionally, 'ne' has a table mapping common", "suffixes to syntax names. If there is no syntax with a given name, 'ne'", "will try to remap the name using the following table (the string before", "the colon is the name of the syntax file):", "", " ada: adb, ads", " asm: s", " c: c++, cc, cpp, h, h++ hpp, l, lex, y, yacc", " cobol: cbl, cob", " csh: tcsh", " diff: patch", " fortran: f, for", " 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", }; const char * const SYSTEM_HELP[10] = { "Syntax: System [COMMAND]", "Abbreviation: SYS", "", "asks the shell to execute COMMAND. The terminal is temporarily reset to", "the state it was in before 'ne''s activation, and COMMAND is started.", "When the execution is finished, control returns to 'ne'.", "", " If the optional argument COMMAND is not specified, you can enter it", "on the input line.", }; const char * const TABS_HELP[17] = { "Syntax: Tabs [0|1]", "Abbreviation: TAB", "", "sets the 'Tabs' flag. When this flag is set, key and the", "'InsertTab' command will insert literal TAB characters. Otherwise it", "will insert enough spaces to have the same visual effect.", "", " In normal editing, the key invokes the command \"'InsertTab' 1\".", "Unlike most others, the key cannot be mapped to other commands.", "Thus the 'Tabs' flag provides the only customization 'ne' offers for the", " key.", "", " If set, either a lower case 't' or upper case 'T' will appear in the", "status bar depending on the state of the 'ShiftTabs' flag. (The", "'ShiftTabs' flag is irrelevant if the 'Tabs' flag is off.) ", "`ShiftTabs'.", }; const char * const TABSIZE_HELP[9] = { "Syntax: TabSize [SIZE]", "Abbreviation: TS", "", "sets the number of spaces 'ne' will use when expanding a TAB character.", "", " If the optional argument SIZE is not specified, you can enter it on", "the input line, the default being the current TAB size. Allowed values", "are strictly between 0 and half the width of the screen.", }; const char * const THROUGH_HELP[15] = { "Syntax: Through [COMMAND]", "Abbreviation: T", "", "asks the shell to execute 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 '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 'Through' to insert the output of any UN*X", "command in your file.", "", " If the optional argument COMMAND is not specified, you can enter it", "on the input line.", }; const char * const TOGGLESEOF_HELP[10] = { "Syntax: ToggleSEOF", "Abbreviation: TSEOF", "", "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 `ToggleSEOL',", "`MoveSOF', and `MoveEOF'.", }; const char * const TOGGLESEOL_HELP[10] = { "Syntax: ToggleSEOL", "Abbreviation: TSEOL", "", "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 `ToggleSEOF',", "`MoveSOL', and `MoveEOL'.", }; const char * const TOLOWER_HELP[5] = { "Syntax: ToLower [N]", "Abbreviation: TL", "", "acts exactly like 'ToUpper', but lowers the case. See `ToUpper'.", }; const char * const TOUPPER_HELP[15] = { "Syntax: ToUpper [N]", "Abbreviation: TU", "", "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 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 'ToUpper' with no argument.", "", " If you apply 'ToUpper' on the first character of a word, it will just", "upper case N words.", }; const char * const TURBO_HELP[29] = { "Syntax: Turbo [STEPS]", "Abbreviation: TUR", "", "sets the turbo parameter. Iterated actions and global replaces will", "update at most STEPS lines of the screen (or at most twice the number of", "visible rows if 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 STEPS lines, '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.", "'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.", }; const char * const UNDELLINE_HELP[15] = { "Syntax: UndelLine [N]", "Abbreviation: UL", "", "inserts at the cursor position for N times the last non-empty line that", "was deleted with the 'DeleteLine' command. If N is not specified, it is", "assumed to be one.", "", " '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 'UndelLine' works independently of the status of the undo", "flag. See `DoUndo'.", }; const char * const UNDO_HELP[9] = { "Syntax: Undo [N]", "Abbreviation: U", "", "undoes the last N actions. If N is not specified, it is assumed to be", "one. After you undo a number of actions, you can 'Redo' all or some of", "them; see `Redo'. However, if you take any new actions after", "having 'Undo'ne some, you can no longer 'Redo' those 'Undo'ne actions.", "See `Redo'.", }; const char * const UNLOADMACROS_HELP[12] = { "Syntax: UnloadMacros", "Abbreviation: UM", "", "frees the macro cache list. After this command, the 'Macro' command", "will be forced to search for the file containing the macros it has to", "play.", "", " 'UnloadMacros' is especially useful if you are experimenting with a", "macro bound to some keystroke, and you are interactively modifying it", "and playing it. 'UnloadMacros' forces 'ne' to look for the newer", "version available.", }; const char * const UNSETBOOKMARK_HELP[11] = { "Syntax: UnsetBookmark [N|+1|-1|-|*]", "Abbreviation: UBM", "", "unsets either the Nth bookmark, the next (+1) or previous (-1) set", "bookmarks, the automatic (-) bookmark, or all (*) bookmarks, making it", "as if they had never been set; see `SetBookmark'. If no option is", "specified, N is assumed to be zero. While you can unset the automatic", "bookmark '-', it will be reset automatically to the current position", "whenever a 'GotoBookmark' command is issued. Each document's valid", "bookmark designations are 0 to 9, and the '-' automatic bookmark.", }; const char * const UTF8_HELP[20] = { "Syntax: UTF8 [0|1]", "Abbreviation: U8", "", "sets the UTF-8 flag. When this flag is true, 'ne' considers the current", "buffer as UTF-8 coded. Note that this flag is set automatically upon", "file loading (if possible) if you required automatic detection. See", "`UTF8Auto'.", "", " If you invoke '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 buffer 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 buffer is set to ASCII or 8-bit, depending on", "its content. A 'U' will appear on the status bar if the flag is true.", "Alternatively, an 'A' or an '8' will be displayed to denote whether the", "buffer 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 buffer encoding,", "it also resets the undo buffer.", }; const char * const UTF8AUTO_HELP[14] = { "Syntax: UTF8Auto [0|1]", "Abbreviation: U8A", "", "sets the UTF-8 automatic-detection flag. When this flag is true, '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", "buffer, ne will automatically switch to UTF-8. See `UTF8'. The", "flag is true by default if 'ne' detects UTF-8 I/O at startup. See ", "`UTF8IO'.", "", " If you invoke '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.", }; const char * const UTF8IO_HELP[13] = { "Syntax: UTF8IO [0|1]", "Abbreviation: U8IO", "", "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 'ne' may", "make the wrong guess (e.g., when you are remotely connected).", "", " If you invoke '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 '@' will appear on the status bar if the flag is true.", }; const char * const VERBOSEMACROS_HELP[21] = { "Syntax: VerboseMacros [0|1]", "Abbreviation: VM", "", "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 '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 '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 'VerboseMacros' setting is saved in your '~/.ne/.default#ap' file", "when you use the 'SaveDefPrefs' command or the 'Save Def Prefs' menu.", "It is not saved by the 'SaveAutoPrefs' command.", }; const char * const VISUALBELL_HELP[10] = { "Syntax: VisualBell [0|1]", "Abbreviation: VB", "", "sets the visual bell flag. When this flag is true, the terminal will", "flash (if possible) instead of beeping.", "", " If you invoke '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.", }; const char * const WORDWRAP_HELP[15] = { "Syntax: WordWrap [0|1]", "Abbreviation: WW", "", "sets the word wrap flag. When this flag is true, 'ne' will", "automatically break lines of text when you attempt to insert characters", "beyond the right margin. See `RightMargin'. '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 `Paragraph'.", "", " If you invoke '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 'w' will appear on the status bar if the", "flag is true.", }; /* This file was automatically generated by info2src.pl. */ ne-3.0.1/src/help.h0000664000175200017510000001615212542017454012657 0ustar vignaprof/* Help string externs. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2015 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 . */ extern const char * const ABOUT_HELP[ 7 ]; extern const char * const ADJUSTVIEW_HELP[ 26 ]; extern const char * const ALERT_HELP[ 5 ]; extern const char * const ATOMICUNDO_HELP[ 22 ]; extern const char * const AUTOCOMPLETE_HELP[ 20 ]; extern const char * const AUTOINDENT_HELP[ 22 ]; extern const char * const AUTOMATCHBRACKET_HELP[ 11 ]; extern const char * const AUTOPREFS_HELP[ 14 ]; extern const char * const BACKSPACE_HELP[ 6 ]; extern const char * const BEEP_HELP[ 6 ]; extern const char * const BINARY_HELP[ 22 ]; extern const char * const CAPITALIZE_HELP[ 6 ]; extern const char * const CASESEARCH_HELP[ 12 ]; extern const char * const CENTER_HELP[ 8 ]; extern const char * const CLEAR_HELP[ 7 ]; extern const char * const CLIPNUMBER_HELP[ 11 ]; extern const char * const CLOSEDOC_HELP[ 7 ]; extern const char * const COPY_HELP[ 10 ]; extern const char * const CRLF_HELP[ 15 ]; extern const char * const CUT_HELP[ 5 ]; extern const char * const DELETECHAR_HELP[ 10 ]; extern const char * const DELETEEOL_HELP[ 10 ]; extern const char * const DELETELINE_HELP[ 9 ]; extern const char * const DELETENEXTWORD_HELP[ 6 ]; extern const char * const DELETEPREVWORD_HELP[ 8 ]; extern const char * const DELTABS_HELP[ 9 ]; extern const char * const DOUNDO_HELP[ 21 ]; extern const char * const ERASE_HELP[ 6 ]; extern const char * const ESCAPE_HELP[ 7 ]; extern const char * const ESCAPETIME_HELP[ 16 ]; extern const char * const EXEC_HELP[ 14 ]; extern const char * const EXIT_HELP[ 11 ]; extern const char * const FASTGUI_HELP[ 19 ]; extern const char * const FIND_HELP[ 12 ]; extern const char * const FINDREGEXP_HELP[ 12 ]; extern const char * const FLAGS_HELP[ 34 ]; extern const char * const FLASH_HELP[ 6 ]; extern const char * const FREEFORM_HELP[ 18 ]; extern const char * const GOTOBOOKMARK_HELP[ 15 ]; extern const char * const GOTOCOLUMN_HELP[ 8 ]; extern const char * const GOTOLINE_HELP[ 10 ]; extern const char * const GOTOMARK_HELP[ 9 ]; extern const char * const HELP_HELP[ 14 ]; extern const char * const HEXCODE_HELP[ 6 ]; extern const char * const INSERT_HELP[ 12 ]; extern const char * const INSERTCHAR_HELP[ 21 ]; extern const char * const INSERTLINE_HELP[ 7 ]; extern const char * const INSERTSTRING_HELP[ 12 ]; extern const char * const INSERTTAB_HELP[ 7 ]; extern const char * const KEYCODE_HELP[ 8 ]; extern const char * const LINEDOWN_HELP[ 6 ]; extern const char * const LINEUP_HELP[ 6 ]; extern const char * const LOADAUTOPREFS_HELP[ 7 ]; extern const char * const LOADPREFS_HELP[ 16 ]; extern const char * const MACRO_HELP[ 33 ]; extern const char * const MARK_HELP[ 12 ]; extern const char * const MARKVERT_HELP[ 21 ]; extern const char * const MATCHBRACKET_HELP[ 9 ]; extern const char * const MODIFIED_HELP[ 14 ]; extern const char * const MOVEBOS_HELP[ 6 ]; extern const char * const MOVEEOF_HELP[ 5 ]; extern const char * const MOVEEOL_HELP[ 5 ]; extern const char * const MOVEEOW_HELP[ 8 ]; extern const char * const MOVEINCDOWN_HELP[ 9 ]; extern const char * const MOVEINCUP_HELP[ 9 ]; extern const char * const MOVELEFT_HELP[ 6 ]; extern const char * const MOVERIGHT_HELP[ 6 ]; extern const char * const MOVESOF_HELP[ 5 ]; extern const char * const MOVESOL_HELP[ 6 ]; extern const char * const MOVETOS_HELP[ 5 ]; extern const char * const NEWDOC_HELP[ 8 ]; extern const char * const NEXTDOC_HELP[ 5 ]; extern const char * const NEXTPAGE_HELP[ 8 ]; extern const char * const NEXTWORD_HELP[ 6 ]; extern const char * const NOFILEREQ_HELP[ 10 ]; extern const char * const NOP_HELP[ 5 ]; extern const char * const OPEN_HELP[ 18 ]; extern const char * const OPENCLIP_HELP[ 14 ]; extern const char * const OPENMACRO_HELP[ 14 ]; extern const char * const OPENNEW_HELP[ 6 ]; extern const char * const PAGEDOWN_HELP[ 6 ]; extern const char * const PAGEUP_HELP[ 6 ]; extern const char * const PARAGRAPH_HELP[ 27 ]; extern const char * const PASTE_HELP[ 7 ]; extern const char * const PASTEVERT_HELP[ 7 ]; extern const char * const PLAY_HELP[ 15 ]; extern const char * const POPPREFS_HELP[ 24 ]; extern const char * const PRESERVECR_HELP[ 15 ]; extern const char * const PREVDOC_HELP[ 5 ]; extern const char * const PREVPAGE_HELP[ 8 ]; extern const char * const PREVWORD_HELP[ 8 ]; extern const char * const PUSHPREFS_HELP[ 24 ]; extern const char * const QUIT_HELP[ 6 ]; extern const char * const READONLY_HELP[ 13 ]; extern const char * const RECORD_HELP[ 21 ]; extern const char * const REDO_HELP[ 8 ]; extern const char * const REFRESH_HELP[ 11 ]; extern const char * const REPEATLAST_HELP[ 25 ]; extern const char * const REPLACE_HELP[ 45 ]; extern const char * const REPLACEALL_HELP[ 12 ]; extern const char * const REPLACEONCE_HELP[ 10 ]; extern const char * const REQUESTORDER_HELP[ 15 ]; extern const char * const RIGHTMARGIN_HELP[ 12 ]; extern const char * const SAVE_HELP[ 17 ]; extern const char * const SAVEALL_HELP[ 10 ]; extern const char * const SAVEAS_HELP[ 18 ]; extern const char * const SAVEAUTOPREFS_HELP[ 7 ]; extern const char * const SAVECLIP_HELP[ 13 ]; extern const char * const SAVEDEFPREFS_HELP[ 6 ]; extern const char * const SAVEMACRO_HELP[ 20 ]; extern const char * const SAVEPREFS_HELP[ 11 ]; extern const char * const SEARCHBACK_HELP[ 14 ]; extern const char * const SELECTDOC_HELP[ 22 ]; extern const char * const SETBOOKMARK_HELP[ 12 ]; extern const char * const SHIFT_HELP[ 21 ]; extern const char * const SHIFTTABS_HELP[ 9 ]; extern const char * const STATUSBAR_HELP[ 22 ]; extern const char * const SUSPEND_HELP[ 6 ]; extern const char * const SYNTAX_HELP[ 45 ]; extern const char * const SYSTEM_HELP[ 10 ]; extern const char * const TABS_HELP[ 17 ]; extern const char * const TABSIZE_HELP[ 9 ]; extern const char * const THROUGH_HELP[ 15 ]; extern const char * const TOGGLESEOF_HELP[ 10 ]; extern const char * const TOGGLESEOL_HELP[ 10 ]; extern const char * const TOLOWER_HELP[ 5 ]; extern const char * const TOUPPER_HELP[ 15 ]; extern const char * const TURBO_HELP[ 29 ]; extern const char * const UNDELLINE_HELP[ 15 ]; extern const char * const UNDO_HELP[ 9 ]; extern const char * const UNLOADMACROS_HELP[ 12 ]; extern const char * const UNSETBOOKMARK_HELP[ 11 ]; extern const char * const UTF8_HELP[ 20 ]; extern const char * const UTF8AUTO_HELP[ 14 ]; extern const char * const UTF8IO_HELP[ 13 ]; extern const char * const VERBOSEMACROS_HELP[ 21 ]; extern const char * const VISUALBELL_HELP[ 10 ]; extern const char * const WORDWRAP_HELP[ 15 ]; /* This file was automatically generated by info2src.pl. */ ne-3.0.1/src/info2cap.c0000664000175200017510000002030112517166512013414 0ustar vignaprof/* terminfo emulation through GNU termcap code. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2015 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 dunmmy2, 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.0.1/src/info2cap.h0000664000175200017510000000621012517166512013424 0ustar vignaprof/* terminfo emulation through GNU termcap definitions. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2015 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.0.1/src/input.c0000664000175200017510000005610512525431422013057 0ustar vignaprof/* Input line handling. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2015 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" /* 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 /* 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) { static const char *prior_prompt; assert(prompt != NULL || prior_prompt); if (prompt) prior_prompt = prompt; move_cursor(ne_lines - 1, 0); clear_to_eol(); standout_on(); output_string(prior_prompt, false); output_string(":", false); standout_off(); output_string(" ", false); reset_status_bar(); return 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. */ char request_char(const buffer * const b, const char * const prompt, const char default_value) { set_attr(0); print_prompt(prompt); if (default_value) output_char(default_value, 0, false); 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 || ic == INVALID); switch(ic) { case ALPHA: return (char)localised_up_case[(unsigned char)c]; case RETURN: return (char)localised_up_case[(unsigned char)default_value]; default: break; } } } /* 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 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(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 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(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 life feed, 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); } /* 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 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. */ static char input_buffer[MAX_INPUT_LINE_LEN + 1]; /* The current encoding of the command line. Contrarily to buffers, the command line may (and will) move back to ASCII if all non-US-ASCII characters are deleted .*/ static encoding_type encoding; /* start_x is the first usable screen x position for editing; len is the current raw length of the input buffer (input_buffer[len] is always a NULL); x is the screen x position of the cursor; pos is the position of the cursor inside the input buffer; offset is the first displayed buffer character. */ static int start_x, len, pos, x, offset; static int input_buffer_is_ascii() { return is_ascii(input_buffer, len); } /* The following functions perform basic editing actions on the input line. */ static void input_refresh(void) { move_cursor(ne_lines - 1, start_x); for(int i = start_x, j = offset; j < len; i += get_char_width(&input_buffer[j], encoding), j = next_pos(input_buffer, j, encoding)) { if (i + get_char_width(&input_buffer[j], encoding) >= ne_columns) break; output_char(get_char(&input_buffer[j], encoding), 0, encoding); } clear_to_eol(); fflush(stdout); } static void input_autocomplete(void) { int dx = 0, prefix_pos = pos; char *p; /* find a usable prefix */ if (prefix_pos && prefix_pos <= len) { prefix_pos = prev_pos(input_buffer, prefix_pos, encoding); dx--; while (prefix_pos && ne_isword(get_char(&input_buffer[prefix_pos], encoding), encoding)) { dx--; prefix_pos = prev_pos(input_buffer, prefix_pos, encoding); } if (! ne_isword(get_char(&input_buffer[prefix_pos], encoding), encoding)) { dx++; prefix_pos = next_pos(input_buffer, prefix_pos, encoding); } p = malloc(pos - prefix_pos + 1); if (!p) { alert(); /* OUT_OF_MEMORY */ return; } strncpy(p, &input_buffer[prefix_pos], pos - prefix_pos); } else p = malloc(1); /* no prefix left of the cursor; we'll give an empty one. */ p[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 && encoding != ENC_ASCII && ac_encoding != encoding) { free(p); alert(); } else { encoding = ac_encoding; if (prefix_pos < pos) { memmove(&input_buffer[prefix_pos], &input_buffer[pos], len - pos + 1); len -= pos - prefix_pos; pos = prefix_pos; } int ac_len = strlen(p); if (ac_len + len >= MAX_INPUT_LINE_LEN) ac_len = MAX_INPUT_LINE_LEN - len; memmove(&input_buffer[pos + ac_len], &input_buffer[pos], len - pos + 1); memmove(&input_buffer[pos], p, ac_len); len += ac_len; while (ac_len > 0) { const int cw = get_char_width(&input_buffer[pos],encoding); pos = next_pos(input_buffer, pos, encoding); ac_len -= cw; dx++; } free(p); x += dx; if (x >= ne_columns) { dx = x - ne_columns + 1; while (dx--) { offset = next_pos(input_buffer, offset, encoding); } 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); set_attr(0); print_prompt(NULL); } input_refresh(); } static void input_move_left(const bool do_refresh) { if (pos > 0) { const int x_delta = get_char_width(&input_buffer[pos = prev_pos(input_buffer, pos, encoding)], encoding); assert(pos >= 0); if (x == start_x) { offset = pos; if (char_ins_del_ok) { int i, j; for(i = start_x, j = offset; j < len && i + get_char_width(&input_buffer[j], encoding) < ne_columns; i += get_char_width(&input_buffer[j], encoding), j = next_pos(input_buffer, j, encoding)); if (j < len) { move_cursor(ne_lines - 1, i); delete_chars(get_char_width(&input_buffer[j], encoding)); } move_cursor(ne_lines - 1, start_x); insert_char(get_char(&input_buffer[pos], encoding), 0, encoding); move_cursor(ne_lines - 1, start_x); } else if (do_refresh) input_refresh(); } else x -= x_delta; } } static void input_move_right(const bool do_refresh) { const int prev_pos = pos; if (pos < len) { const int x_delta = get_char_width(&input_buffer[pos], encoding); pos = next_pos(input_buffer, pos, encoding); assert(pos <= len); if ((x += x_delta) >= ne_columns) { const int shift = x - (ne_columns - 1); int width = 0; do { width += get_char_width(&input_buffer[offset], encoding); offset = next_pos(input_buffer, offset, encoding); } while(width < shift && offset < len); assert(offset < len); x -= width; if (char_ins_del_ok) { int i, j; move_cursor(ne_lines - 1, start_x); delete_chars(width); move_cursor(ne_lines - 1, x - x_delta); output_char(get_char(&input_buffer[prev_pos], encoding), 0, encoding); i = x; j = pos; while(j < len && i + (width = get_char_width(&input_buffer[j], encoding)) < ne_columns) { output_char(get_char(&input_buffer[j], encoding), 0, encoding); i += width; j = next_pos(input_buffer, j, encoding); } } else if (do_refresh) input_refresh(); } } } static void input_move_to_sol(void) { if (offset == 0) { x = start_x; pos = 0; return; } x = start_x; offset = pos = 0; input_refresh(); } static void input_move_to_eol(void) { const int width_to_end = get_string_width(input_buffer + pos, len - pos, encoding); if (x + width_to_end < ne_columns) { x += width_to_end; pos = len; return; } x = start_x; pos = offset = len; int i = ne_columns - 1 - start_x; for(;;) { const int prev = prev_pos(input_buffer, offset, encoding); const int width = get_char_width(&input_buffer[prev], encoding); if (i - width < 0) break; offset = prev; i -= width; x += width; } input_refresh(); } static void input_next_word(void) { bool space_skipped = false; while(pos < len) { const int c = get_char(&input_buffer[pos], encoding); if (!ne_isword(c, encoding)) space_skipped = true; else if (space_skipped) break; input_move_right(false); } if (x == ne_columns - 1) { offset = pos; x = start_x; } input_refresh(); } static void input_prev_word(void) { bool word_skipped = false; while(pos > 0) { input_move_left(false); const int c = get_char(&input_buffer[pos], encoding); if (ne_isword(c, encoding)) word_skipped = true; else if (word_skipped) { input_move_right(false); break; } } input_refresh(); } static void input_paste(void) { const clip_desc * const cd = get_nth_clip(cur_buffer->opt.cur_clip); if (cd) { if (cd->cs->encoding != ENC_ASCII && encoding != ENC_ASCII && cd->cs->encoding != encoding) { alert(); return; } int paste_len = strnlen_ne(cd->cs->stream, cd->cs->len); if (len + paste_len > MAX_INPUT_LINE_LEN) paste_len = MAX_INPUT_LINE_LEN - len; memmove(&input_buffer[pos + paste_len], &input_buffer[pos], len - pos + 1); strncpy(&input_buffer[pos], cd->cs->stream, paste_len); len += paste_len; if (!input_buffer_is_ascii() && cd->cs->encoding != ENC_ASCII) encoding = cd->cs->encoding; input_refresh(); } } char *request(const char *prompt, const char * const default_string, const bool alpha_allowed, const int completion_type, const bool prefer_utf8) { set_attr(0); input_buffer[pos = len = offset = 0] = 0; encoding = ENC_ASCII; x = start_x = print_prompt(prompt); init_history(); if (default_string) { strncpy(input_buffer, default_string, MAX_INPUT_LINE_LEN); len = strlen(input_buffer); encoding = detect_encoding(input_buffer, len); input_refresh(); } bool first_char_typed = true, last_char_completion = false, selection = false; while(true) { assert(input_buffer[len] == 0); move_cursor(ne_lines - 1, x); int c; input_class ic; do c = get_key_code(); while((ic = CHAR_CLASS(c)) == IGNORE); /* 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 && encoding != ENC_ASCII && 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) { input_buffer[len = 0] = 0; clear_to_eol(); } if (encoding == ENC_ASCII && c > 0x7F) encoding = prefer_utf8 || c > 0xFF ? ENC_UTF8 : ENC_8_BIT; int c_len = encoding == ENC_UTF8 ? utf8seqlen(c) : 1; int c_width = output_width(c); assert(c_len > 0); if (len <= MAX_INPUT_LINE_LEN - c_len && (alpha_allowed || (c < 0x100 && isxdigit(c)) || c=='X' || c=='x')) { memmove(&input_buffer[pos + c_len], &input_buffer[pos], len - pos + 1); if (c_len == 1) input_buffer[pos] = c; else utf8str(c, &input_buffer[pos]); len += c_len; move_cursor(ne_lines - 1, x); if (x < ne_columns - c_width) { if (pos == len - c_len) output_char(c, 0, encoding); else if (char_ins_del_ok) insert_char(c, 0, 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 (len && input_buffer[len - 1] == '"') { input_buffer[len - 1] = 0; if (prefix = strrchr(input_buffer, '"')) { quoted = true; prefix++; } else input_buffer[len - 1] = '"'; } if (!quoted) { prefix = strrchr(input_buffer, ' '); if (prefix) prefix++; else prefix = input_buffer; } 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 - input_buffer) + strlen(completion) + 1 < MAX_INPUT_LINE_LEN) { const encoding_type completion_encoding = detect_encoding(completion, strlen(completion)); if (encoding == ENC_ASCII || completion_encoding == ENC_ASCII || encoding == completion_encoding) { strcpy(prefix, completion); if (quoted) strcat(prefix, "\""); len = strlen(input_buffer); pos = offset = 0; x = start_x; if (encoding == ENC_ASCII) 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 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, input_buffer, history_buff->cur_line_desc->line_len)) line_up(history_buff); if (history_buff->cur_line_desc->line) { strncpy(input_buffer, history_buff->cur_line_desc->line, min(history_buff->cur_line_desc->line_len,MAX_INPUT_LINE_LEN)); input_buffer[min(history_buff->cur_line_desc->line_len,MAX_INPUT_LINE_LEN)] = 0; len = strlen(input_buffer); encoding = detect_encoding(input_buffer, len); } else { input_buffer[len = 0] = 0; encoding = ENC_ASCII; } x = start_x; pos = 0; 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 (pos == 0) break; input_move_left(true); case DELETECHAR_A: if (len > 0 && pos < len) { int c_len = encoding == ENC_UTF8 ? utf8len(input_buffer[pos]) : 1; int c_width = get_char_width(&input_buffer[pos], encoding); memmove(&input_buffer[pos], &input_buffer[pos + c_len], len - pos - c_len + 1); len -= c_len; if (input_buffer_is_ascii()) encoding = ENC_ASCII; if (char_ins_del_ok) { int i, j; move_cursor(ne_lines - 1, x); delete_chars(c_width); for(i = x, j = pos; j < len && i + get_char_width(&input_buffer[j], encoding) < ne_columns - c_width; i += get_char_width(&input_buffer[j], encoding), j = next_pos(input_buffer, j, encoding)); if (j < len) { move_cursor(ne_lines - 1, i); while(j < len && i + get_char_width(&input_buffer[j], encoding) < ne_columns) { output_char(get_char(&input_buffer[j], encoding), 0, encoding); i += get_char_width(&input_buffer[j], encoding); j = next_pos(input_buffer, j, encoding); } } } else input_refresh(); } break; case DELETELINE_A: move_cursor(ne_lines - 1, start_x); clear_to_eol(); input_buffer[len = pos = offset = 0] = 0; encoding = ENC_ASCII; x = start_x; break; case DELETEEOL_A: input_buffer[len = pos] = 0; clear_to_eol(); if (input_buffer_is_ascii()) encoding = ENC_ASCII; break; case MOVEINCUP_A: if (x != start_x) { pos = offset; x = start_x; break; } case MOVESOL_A: input_move_to_sol(); break; case MOVEINCDOWN_A: { int i, j; for(i = x, j = pos; j < len && i + get_char_width(&input_buffer[j], encoding) < ne_columns; i += get_char_width(&input_buffer[j], encoding), j = next_pos(input_buffer, j, encoding)); if (j != pos && j < len) { pos = j; x = i; break; } } case MOVEEOL_A: input_move_to_eol(); break; case TOGGLESEOL_A: case TOGGLESEOF_A: if (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(); break; case AUTOCOMPLETE_A: input_autocomplete(); break; case ESCAPE_A: 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(input_buffer[len] == 0); if (history_buff->num_lines == 0 || len != last->line_len || strncmp(input_buffer, last->line, last->line_len)) add_to_history(input_buffer); return input_buffer; } first_char_typed = false; } } ne-3.0.1/src/inputclass.c0000664000175200017510000004730112517166512014111 0ustar vignaprof/* Input class, key bindings and upper casing vector definitions. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2015 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" }; /* 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 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, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, DELETEPREVWORD_ABBREV, /* 336..351 150..15F */ DELETENEXTWORD_ABBREV, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, /* 352..367 160..16F */ 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 _ */ NULL }; /* 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..31 0..1f */ "^@", "^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. 31..126 20..7e */ " ", "!", "\"","#", "$", "%", "&", "'", "(", ")", "*", "+", ",", "-", ".", "/", /* 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..159 80..9f */ "^[@", "^[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..175 A0..AF */ "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", /* 176..191 B0..BF */ "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", /* 192..207 C0..CF */ "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", /* 207..223 D0..DF */ "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", /* 224..239 E0..EF */ "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", /* 240..255 F0..FF */ /* The following bindings are for the terminfo extended codes (see keycodes.h). */ /* Cursor movement keys 256..271 100..10F */ "", "Up", "Down", "Left", "Right", "IncUp", "IncDn", "PgDn", "PgUp", "LnDn", "LnUp", "", "", "", "", "", /* Editing keys 272..287 110..11F */ "Del", "", "BackSp", "DelLn", "UndelLn", "DelCh", "Ins", "", "Clear", "", "", "", "", "", "", "", /* Keypad keys and fake (simulated) menu key 288..303 120..12F */ "KPSoF", "KPPrPg", "S/EoL", "EOF", "NxtPg", "Exec", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", /* 304..319 130..13F */ /* Function keys 320..335 140..14F */ "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..351 150..15F */ "F32", "F33", "F34", "F35", "F36", "F37", "F38", "F39", "F40", "F41", "F42", "F43", "F44", "F45", "F46", "F47", /* 352..367 160..16F */ "F48", "F49", "F50", "F51", "F52", "F53", "F54", "F55", "F56", "F57", "F58", "F59", "F60", "F61", "F62", "F63", /* 368..383 170..17F */ /* 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-^_", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", /* 416..431 1A0..1AF */ "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", /* 432..447 1B0..1BF */ /* Prefix-simulated META (ESC X) 448..479 1C0..1DF */ "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..511 1E0..1FF */ "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-_", }; ne-3.0.1/src/keycodes.h0000664000175200017510000000455112517166512013537 0ustar vignaprof/* Extended codes for special keys. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2015 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)) ne-3.0.1/src/keys.c0000664000175200017510000004165512525431422012677 0ustar vignaprof/* Terminfo database scanning and keyboard escape sequence matching functions. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2015 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 #include #include #include /* Maximum number of key definitions from terminfo plus others we may get from keys file -- i.e. key_may_set(). */ #define MAX_TERM_KEY 512 /* 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; } 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 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\n", key[i].code ); } } #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) { if (!cap_string) return; key[num_keys].string = cap_string; key[num_keys].code = code; 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) { 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; 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; 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); key_set(key_down, NE_KEY_DOWN); key_set(key_left, NE_KEY_LEFT); key_set(key_right, NE_KEY_RIGHT); key_set(key_home, NE_KEY_HOME); key_set(key_end, NE_KEY_END); key_set(key_npage, NE_KEY_NPAGE); key_set(key_ppage, NE_KEY_PPAGE); key_set(key_sf, NE_KEY_SCROLL_FORWARD); key_set(key_sr, NE_KEY_SCROLL_REVERSE); /* Editing keys */ key_set(key_eol, NE_KEY_CLEAR_TO_EOL); key_set(key_eos, NE_KEY_CLEAR_TO_EOS); key_set(key_backspace, NE_KEY_BACKSPACE); key_set(key_dl, NE_KEY_DELETE_LINE); key_set(key_il, NE_KEY_INSERT_LINE); key_set(key_dc, NE_KEY_DELETE_CHAR); key_set(key_ic, NE_KEY_INSERT_CHAR); key_set(key_eic, NE_KEY_EXIT_INSERT_CHAR); key_set(key_clear, NE_KEY_CLEAR); /* Keypad keys */ key_set(key_a1, NE_KEY_A1); key_set(key_a3, NE_KEY_A3); key_set(key_b2, NE_KEY_B2); key_set(key_c1, NE_KEY_C1); key_set(key_c3, NE_KEY_C3); /* Tab keys (never used in the standard configuration) */ key_set(key_catab, NE_KEY_CLEAR_ALL_TABS); key_set(key_ctab, NE_KEY_CLEAR_TAB); key_set(key_stab, NE_KEY_SET_TAB); /* Function keys */ key_set(key_f0, NE_KEY_F(0)); key_set(key_f1, NE_KEY_F(1)); key_set(key_f2, NE_KEY_F(2)); key_set(key_f3, NE_KEY_F(3)); key_set(key_f4, NE_KEY_F(4)); key_set(key_f5, NE_KEY_F(5)); key_set(key_f6, NE_KEY_F(6)); key_set(key_f7, NE_KEY_F(7)); key_set(key_f8, NE_KEY_F(8)); key_set(key_f9, NE_KEY_F(9)); key_set(key_f10, NE_KEY_F(10)); key_set(key_f11, NE_KEY_F(11)); key_set(key_f12, NE_KEY_F(12)); key_set(key_f13, NE_KEY_F(13)); key_set(key_f14, NE_KEY_F(14)); key_set(key_f15, NE_KEY_F(15)); key_set(key_f16, NE_KEY_F(16)); key_set(key_f17, NE_KEY_F(17)); key_set(key_f18, NE_KEY_F(18)); key_set(key_f19, NE_KEY_F(19)); key_set(key_f20, NE_KEY_F(20)); key_set(key_f21, NE_KEY_F(21)); key_set(key_f22, NE_KEY_F(22)); key_set(key_f23, NE_KEY_F(23)); key_set(key_f24, NE_KEY_F(24)); key_set(key_f25, NE_KEY_F(25)); key_set(key_f26, NE_KEY_F(26)); key_set(key_f27, NE_KEY_F(27)); key_set(key_f28, NE_KEY_F(28)); key_set(key_f29, NE_KEY_F(29)); key_set(key_f30, NE_KEY_F(30)); key_set(key_f31, NE_KEY_F(31)); key_set(key_f32, NE_KEY_F(32)); key_set(key_f33, NE_KEY_F(33)); key_set(key_f34, NE_KEY_F(34)); key_set(key_f35, NE_KEY_F(35)); key_set(key_f36, NE_KEY_F(36)); key_set(key_f37, NE_KEY_F(37)); key_set(key_f38, NE_KEY_F(38)); key_set(key_f39, NE_KEY_F(39)); key_set(key_f40, NE_KEY_F(40)); key_set(key_f41, NE_KEY_F(41)); key_set(key_f42, NE_KEY_F(42)); key_set(key_f43, NE_KEY_F(43)); key_set(key_f44, NE_KEY_F(44)); key_set(key_f45, NE_KEY_F(45)); key_set(key_f46, NE_KEY_F(46)); key_set(key_f47, NE_KEY_F(47)); key_set(key_f48, NE_KEY_F(48)); key_set(key_f49, NE_KEY_F(49)); key_set(key_f50, NE_KEY_F(50)); key_set(key_f51, NE_KEY_F(51)); key_set(key_f52, NE_KEY_F(52)); key_set(key_f53, NE_KEY_F(53)); key_set(key_f54, NE_KEY_F(54)); key_set(key_f55, NE_KEY_F(55)); key_set(key_f56, NE_KEY_F(56)); key_set(key_f57, NE_KEY_F(57)); key_set(key_f58, NE_KEY_F(58)); key_set(key_f59, NE_KEY_F(59)); key_set(key_f60, NE_KEY_F(60)); key_set(key_f61, NE_KEY_F(61)); key_set(key_f62, NE_KEY_F(62)); key_set(key_f63, NE_KEY_F(63)); } /* Fake (simulated) command key. */ key_set("\x1B:", NE_KEY_COMMAND); assert(num_keys < MAX_TERM_KEY - 1); D(fprintf(stderr,"Got %d keys from terminfo\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); key_may_set("\x1b?x", NE_KEY_UP); /* key_may_set("\x1b" "A", NE_KEY_UP);*/ key_may_set("\x1bOA", NE_KEY_UP); key_may_set("\x1b[B", NE_KEY_DOWN); key_may_set("\x1b?r", NE_KEY_DOWN); /* key_may_set("\x1b" "B", NE_KEY_DOWN);*/ key_may_set("\x1bOB", NE_KEY_DOWN); key_may_set("\x1b[D", NE_KEY_LEFT); key_may_set("\x1b?t", NE_KEY_LEFT); /*key_may_set("\x1b" "D", NE_KEY_LEFT);*/ key_may_set("\x1bOD", NE_KEY_LEFT); key_may_set("\x1b[C", NE_KEY_RIGHT); key_may_set("\x1b?v", NE_KEY_RIGHT); /*key_may_set("\x1b" "C", NE_KEY_RIGHT);*/ key_may_set("\x1bOC", NE_KEY_RIGHT); key_may_set("\x1b[1~", NE_KEY_HOME); key_may_set("\x1b[4~", NE_KEY_END); key_may_set("\x1b[6~", NE_KEY_NPAGE); key_may_set("\x1b[5~", NE_KEY_PPAGE); key_may_set("\x1b[2~", NE_KEY_INSERT_CHAR); key_may_set("\x1b[3~", NE_KEY_DELETE_CHAR); key_may_set("\x1b[H", NE_KEY_HOME); key_may_set("\x1b[L", NE_KEY_INSERT_CHAR); /* gnome-terminal bizarre home/end keys */ key_may_set("\x1bOH", NE_KEY_HOME); key_may_set("\x1bOF", NE_KEY_END); /* 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)); /* 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)); key_may_set("\x1b[12~", NE_KEY_F(2)); key_may_set("\x1b[13~", NE_KEY_F(3)); key_may_set("\x1b[14~", NE_KEY_F(4)); key_may_set("\x1b[15~", NE_KEY_F(5)); key_may_set("\x1b[17~", NE_KEY_F(6)); key_may_set("\x1b[18~", NE_KEY_F(7)); key_may_set("\x1b[19~", NE_KEY_F(8)); key_may_set("\x1b[20~", NE_KEY_F(9)); key_may_set("\x1b[21~", NE_KEY_F(10)); key_may_set("\x1b[23~", NE_KEY_F(11)); key_may_set("\x1b[24~", NE_KEY_F(12)); /* 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)); key_may_set("\x1bOQ", NE_KEY_F(2)); key_may_set("\x1bOR", NE_KEY_F(3)); key_may_set("\x1bOS", NE_KEY_F(4)); key_may_set("\x1bOt", NE_KEY_F(5)); key_may_set("\x1bOu", NE_KEY_F(6)); key_may_set("\x1bOv", NE_KEY_F(7)); key_may_set("\x1bOl", NE_KEY_F(8)); key_may_set("\x1bOw", NE_KEY_F(9)); key_may_set("\x1bOy", NE_KEY_F(10)); /* 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)); key_may_set("\x1b[[B", NE_KEY_F(2)); key_may_set("\x1b[[C", NE_KEY_F(3)); key_may_set("\x1b[[D", NE_KEY_F(4)); key_may_set("\x1b[[E", NE_KEY_F(5)); /* 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)); #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 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 if 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 (kbd_buffer[last_match] > key[cur_key].string[last_match]) { c = kbd_buffer[0]; if (--cur_len) memmove(kbd_buffer, kbd_buffer + 1, cur_len); return 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 c; } } else return INVALID_CHAR; } } } ne-3.0.1/src/menu.c0000664000175200017510000007503612540112470012664 0ustar vignaprof/* Menu handling function. Includes also key and menu configuration parsing. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2015 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" /* 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 name of the key bindings file. */ #define KEY_BINDINGS_NAME ".keys" /* The keywords used in the configuration files. */ #define MENU_KEYWORD "MENU" #define ITEM_KEYWORD "ITEM" #define KEY_KEYWORD "KEY" #define SEQ_KEYWORD "SEQ" /* 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 }, { "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 }, { "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 }, { "Adjust View ^^", ADJUSTVIEW_ABBREV }, { "Middle View [C", MIDDLEVIEW_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 }, 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 }, { "Auto Indent ", 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, 17, 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 int dump_config(void) { int menu, item, key; FILE *f; if (!(f = fopen("ne_test_dump_config","w")) ) return ERROR; 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"); } for (key = 0; key < NUM_KEYS; key++) { if (key_binding[key] && key_binding[key][0]) fprintf(f,"%s\t%4x\t%s\n",KEY_KEYWORD, key, key_binding[key] ); } fclose(f); return OK; } #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); update_line(cur_buffer, 0, false, 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). */ char *gen_flag_string(const buffer * const b) { static char string[MAX_FLAG_STRING_SIZE]; const int ch = 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' : '-'; string[i++] = b->marking ? (b->mark_is_vertical ? 'V' :'M') : '-'; string[i++] = b->recording ? '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'; 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; } 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 (!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 remaning 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(); } 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]; if (message) { strncpy(msg_cache, message, MAX_MESSAGE_LENGTH); msg_cache[MAX_MESSAGE_LENGTH - 1] = '\0'; } 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); switch(ic) { case INVALID: alert(); 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: draw_item(0); break; case NEXTPAGE_A: draw_item(menus[current_menu].item_num - 1); break; case ESCAPE_A: undraw_last_menu(); return; default: undraw_last_menu(); 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)()) { 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); if (menus == def_menus) get_menu_conf(menu_conf_name, exists_gprefs_dir); } 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)()) { 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; 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) == 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) { get_key_bind(key_bindings_name, exists_gprefs_dir); get_key_bind(key_bindings_name, exists_prefs_dir); get_key_bind(key_bindings_name, cur_dir); } ne-3.0.1/src/names.c0000664000175200017510000003516312542017454013030 0ustar vignaprof/* Names and abbreviations of all the commands. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2015 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 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. */ const char ABOUT_NAME[] = "About"; const char ABOUT_ABBREV[] = "About"; const char ADJUSTVIEW_NAME[] = "AdjustView"; const char ADJUSTVIEW_ABBREV[] = "AV"; const char ALERT_NAME[] = "Alert"; const char ALERT_ABBREV[] = "AL"; const char ATOMICUNDO_NAME[] = "AtomicUndo"; const char ATOMICUNDO_ABBREV[] = "AU"; const char AUTOCOMPLETE_NAME[] = "AutoComplete"; const char AUTOCOMPLETE_ABBREV[] = "AC"; const char AUTOINDENT_NAME[] = "AutoIndent"; const char AUTOINDENT_ABBREV[] = "AI"; const char AUTOMATCHBRACKET_NAME[] = "AutoMatchBracket"; const char AUTOMATCHBRACKET_ABBREV[] = "AMB"; const char AUTOPREFS_NAME[] = "AutoPrefs"; const char AUTOPREFS_ABBREV[] = "AP"; const char BACKSPACE_NAME[] = "Backspace"; const char BACKSPACE_ABBREV[] = "BS"; const char BEEP_NAME[] = "Beep"; const char BEEP_ABBREV[] = "BE"; const char BINARY_NAME[] = "Binary"; const char BINARY_ABBREV[] = "B"; const char CAPITALIZE_NAME[] = "Capitalize"; const char CAPITALIZE_ABBREV[] = "CA"; const char CASESEARCH_NAME[] = "CaseSearch"; const char CASESEARCH_ABBREV[] = "CS"; const char CENTER_NAME[] = "Center"; const char CENTER_ABBREV[] = "CE"; const char CLEAR_NAME[] = "Clear"; const char CLEAR_ABBREV[] = "CL"; const char CLIPNUMBER_NAME[] = "ClipNumber"; const char CLIPNUMBER_ABBREV[] = "CN"; const char CLOSEDOC_NAME[] = "CloseDoc"; const char CLOSEDOC_ABBREV[] = "CD"; const char COPY_NAME[] = "Copy"; const char COPY_ABBREV[] = "C"; const char CRLF_NAME[] = "CRLF"; const char CRLF_ABBREV[] = "CRLF"; const char CUT_NAME[] = "Cut"; const char CUT_ABBREV[] = "CU"; const char DELETECHAR_NAME[] = "DeleteChar"; const char DELETECHAR_ABBREV[] = "DC"; const char DELETEEOL_NAME[] = "DeleteEOL"; const char DELETEEOL_ABBREV[] = "DE"; const char DELETELINE_NAME[] = "DeleteLine"; const char DELETELINE_ABBREV[] = "DL"; const char DELETENEXTWORD_NAME[] = "DeleteNextWord"; const char DELETENEXTWORD_ABBREV[] = "DNW"; const char DELETEPREVWORD_NAME[] = "DeletePrevWord"; const char DELETEPREVWORD_ABBREV[] = "DPW"; const char DELTABS_NAME[] = "DelTabs"; const char DELTABS_ABBREV[] = "DT"; const char DOUNDO_NAME[] = "DoUndo"; const char DOUNDO_ABBREV[] = "DU"; const char ERASE_NAME[] = "Erase"; const char ERASE_ABBREV[] = "E"; const char ESCAPE_NAME[] = "Escape"; const char ESCAPE_ABBREV[] = "ESC"; const char ESCAPETIME_NAME[] = "EscapeTime"; const char ESCAPETIME_ABBREV[] = "ET"; const char EXEC_NAME[] = "Exec"; const char EXEC_ABBREV[] = "EX"; const char EXIT_NAME[] = "Exit"; const char EXIT_ABBREV[] = "X"; const char FASTGUI_NAME[] = "FastGUI"; const char FASTGUI_ABBREV[] = "FG"; const char FIND_NAME[] = "Find"; const char FIND_ABBREV[] = "F"; const char FINDREGEXP_NAME[] = "FindRegExp"; const char FINDREGEXP_ABBREV[] = "FX"; const char FLAGS_NAME[] = "Flags"; const char FLAGS_ABBREV[] = "FLAG"; const char FLASH_NAME[] = "Flash"; const char FLASH_ABBREV[] = "FL"; const char FREEFORM_NAME[] = "FreeForm"; const char FREEFORM_ABBREV[] = "FF"; const char GOTOBOOKMARK_NAME[] = "GotoBookmark"; const char GOTOBOOKMARK_ABBREV[] = "GBM"; const char GOTOCOLUMN_NAME[] = "GotoColumn"; const char GOTOCOLUMN_ABBREV[] = "GC"; const char GOTOLINE_NAME[] = "GotoLine"; const char GOTOLINE_ABBREV[] = "GL"; const char GOTOMARK_NAME[] = "GotoMark"; const char GOTOMARK_ABBREV[] = "GM"; const char HELP_NAME[] = "Help"; const char HELP_ABBREV[] = "H"; const char HEXCODE_NAME[] = "HexCode"; const char HEXCODE_ABBREV[] = "HC"; const char INSERT_NAME[] = "Insert"; const char INSERT_ABBREV[] = "I"; const char INSERTCHAR_NAME[] = "InsertChar"; const char INSERTCHAR_ABBREV[] = "IC"; const char INSERTLINE_NAME[] = "InsertLine"; const char INSERTLINE_ABBREV[] = "IL"; const char INSERTSTRING_NAME[] = "InsertString"; const char INSERTSTRING_ABBREV[] = "IS"; const char INSERTTAB_NAME[] = "InsertTab"; const char INSERTTAB_ABBREV[] = "IT"; const char KEYCODE_NAME[] = "KeyCode"; const char KEYCODE_ABBREV[] = "KC"; const char LINEDOWN_NAME[] = "LineDown"; const char LINEDOWN_ABBREV[] = "LD"; const char LINEUP_NAME[] = "LineUp"; const char LINEUP_ABBREV[] = "LU"; const char LOADAUTOPREFS_NAME[] = "LoadAutoPrefs"; const char LOADAUTOPREFS_ABBREV[] = "LAP"; const char LOADPREFS_NAME[] = "LoadPrefs"; const char LOADPREFS_ABBREV[] = "LP"; const char MACRO_NAME[] = "Macro"; const char MACRO_ABBREV[] = "MA"; const char MARK_NAME[] = "Mark"; const char MARK_ABBREV[] = "M"; const char MARKVERT_NAME[] = "MarkVert"; const char MARKVERT_ABBREV[] = "MV"; const char MATCHBRACKET_NAME[] = "MatchBracket"; const char MATCHBRACKET_ABBREV[] = "MB"; const char MODIFIED_NAME[] = "Modified"; const char MODIFIED_ABBREV[] = "MOD"; const char MOVEBOS_NAME[] = "MoveBOS"; const char MOVEBOS_ABBREV[] = "BOS"; const char MOVEEOF_NAME[] = "MoveEOF"; const char MOVEEOF_ABBREV[] = "EOF"; const char MOVEEOL_NAME[] = "MoveEOL"; const char MOVEEOL_ABBREV[] = "EOL"; const char MOVEEOW_NAME[] = "MoveEOW"; const char MOVEEOW_ABBREV[] = "EOW"; const char MOVEINCDOWN_NAME[] = "MoveIncDown"; const char MOVEINCDOWN_ABBREV[] = "MID"; const char MOVEINCUP_NAME[] = "MoveIncUp"; const char MOVEINCUP_ABBREV[] = "MIU"; const char MOVELEFT_NAME[] = "MoveLeft"; const char MOVELEFT_ABBREV[] = "ML"; const char MOVERIGHT_NAME[] = "MoveRight"; const char MOVERIGHT_ABBREV[] = "MR"; const char MOVESOF_NAME[] = "MoveSOF"; const char MOVESOF_ABBREV[] = "SOF"; const char MOVESOL_NAME[] = "MoveSOL"; const char MOVESOL_ABBREV[] = "SOL"; const char MOVETOS_NAME[] = "MoveTOS"; const char MOVETOS_ABBREV[] = "TOS"; const char NEWDOC_NAME[] = "NewDoc"; const char NEWDOC_ABBREV[] = "N"; const char NEXTDOC_NAME[] = "NextDoc"; const char NEXTDOC_ABBREV[] = "ND"; const char NEXTPAGE_NAME[] = "NextPage"; const char NEXTPAGE_ABBREV[] = "NP"; const char NEXTWORD_NAME[] = "NextWord"; const char NEXTWORD_ABBREV[] = "NW"; const char NOFILEREQ_NAME[] = "NoFileReq"; const char NOFILEREQ_ABBREV[] = "NFR"; const char NOP_NAME[] = "NOP"; const char NOP_ABBREV[] = "NOP"; const char OPEN_NAME[] = "Open"; const char OPEN_ABBREV[] = "O"; const char OPENCLIP_NAME[] = "OpenClip"; const char OPENCLIP_ABBREV[] = "OC"; const char OPENMACRO_NAME[] = "OpenMacro"; const char OPENMACRO_ABBREV[] = "OM"; const char OPENNEW_NAME[] = "OpenNew"; const char OPENNEW_ABBREV[] = "ON"; const char PAGEDOWN_NAME[] = "PageDown"; const char PAGEDOWN_ABBREV[] = "PDN"; const char PAGEUP_NAME[] = "PageUp"; const char PAGEUP_ABBREV[] = "PUP"; const char PARAGRAPH_NAME[] = "Paragraph"; const char PARAGRAPH_ABBREV[] = "PA"; const char PASTE_NAME[] = "Paste"; const char PASTE_ABBREV[] = "P"; const char PASTEVERT_NAME[] = "PasteVert"; const char PASTEVERT_ABBREV[] = "PV"; const char PLAY_NAME[] = "Play"; const char PLAY_ABBREV[] = "PL"; const char POPPREFS_NAME[] = "PopPrefs"; const char POPPREFS_ABBREV[] = "POPP"; const char PRESERVECR_NAME[] = "PreserveCR"; const char PRESERVECR_ABBREV[] = "PCR"; const char PREVDOC_NAME[] = "PrevDoc"; const char PREVDOC_ABBREV[] = "PD"; const char PREVPAGE_NAME[] = "PrevPage"; const char PREVPAGE_ABBREV[] = "PP"; const char PREVWORD_NAME[] = "PrevWord"; const char PREVWORD_ABBREV[] = "PW"; const char PUSHPREFS_NAME[] = "PushPrefs"; const char PUSHPREFS_ABBREV[] = "PUSHP"; const char QUIT_NAME[] = "Quit"; const char QUIT_ABBREV[] = "Q"; const char READONLY_NAME[] = "ReadOnly"; const char READONLY_ABBREV[] = "RO"; const char RECORD_NAME[] = "Record"; const char RECORD_ABBREV[] = "REC"; const char REDO_NAME[] = "Redo"; const char REDO_ABBREV[] = "RE"; const char REFRESH_NAME[] = "Refresh"; const char REFRESH_ABBREV[] = "REF"; const char REPEATLAST_NAME[] = "RepeatLast"; const char REPEATLAST_ABBREV[] = "RL"; const char REPLACE_NAME[] = "Replace"; const char REPLACE_ABBREV[] = "R"; const char REPLACEALL_NAME[] = "ReplaceAll"; const char REPLACEALL_ABBREV[] = "RA"; const char REPLACEONCE_NAME[] = "ReplaceOnce"; const char REPLACEONCE_ABBREV[] = "R1"; const char REQUESTORDER_NAME[] = "RequestOrder"; const char REQUESTORDER_ABBREV[] = "RQO"; const char RIGHTMARGIN_NAME[] = "RightMargin"; const char RIGHTMARGIN_ABBREV[] = "RM"; const char SAVE_NAME[] = "Save"; const char SAVE_ABBREV[] = "S"; const char SAVEALL_NAME[] = "SaveAll"; const char SAVEALL_ABBREV[] = "SL"; const char SAVEAS_NAME[] = "SaveAs"; const char SAVEAS_ABBREV[] = "SA"; const char SAVEAUTOPREFS_NAME[] = "SaveAutoPrefs"; const char SAVEAUTOPREFS_ABBREV[] = "SAP"; const char SAVECLIP_NAME[] = "SaveClip"; const char SAVECLIP_ABBREV[] = "SC"; const char SAVEDEFPREFS_NAME[] = "SaveDefPrefs"; const char SAVEDEFPREFS_ABBREV[] = "SDP"; const char SAVEMACRO_NAME[] = "SaveMacro"; const char SAVEMACRO_ABBREV[] = "SM"; const char SAVEPREFS_NAME[] = "SavePrefs"; const char SAVEPREFS_ABBREV[] = "SP"; const char SEARCHBACK_NAME[] = "SearchBack"; const char SEARCHBACK_ABBREV[] = "SB"; const char SELECTDOC_NAME[] = "SelectDoc"; const char SELECTDOC_ABBREV[] = "SD"; const char SETBOOKMARK_NAME[] = "SetBookmark"; const char SETBOOKMARK_ABBREV[] = "SBM"; const char SHIFT_NAME[] = "Shift"; const char SHIFT_ABBREV[] = "SH"; const char SHIFTTABS_NAME[] = "ShiftTabs"; const char SHIFTTABS_ABBREV[] = "SHT"; const char STATUSBAR_NAME[] = "StatusBar"; const char STATUSBAR_ABBREV[] = "ST"; const char SUSPEND_NAME[] = "Suspend"; const char SUSPEND_ABBREV[] = "SU"; const char SYNTAX_NAME[] = "Syntax"; const char SYNTAX_ABBREV[] = "SY"; const char SYSTEM_NAME[] = "System"; const char SYSTEM_ABBREV[] = "SYS"; const char TABS_NAME[] = "Tabs"; const char TABS_ABBREV[] = "TAB"; const char TABSIZE_NAME[] = "TabSize"; const char TABSIZE_ABBREV[] = "TS"; const char THROUGH_NAME[] = "Through"; const char THROUGH_ABBREV[] = "T"; const char TOGGLESEOF_NAME[] = "ToggleSEOF"; const char TOGGLESEOF_ABBREV[] = "TSEOF"; const char TOGGLESEOL_NAME[] = "ToggleSEOL"; const char TOGGLESEOL_ABBREV[] = "TSEOL"; const char TOLOWER_NAME[] = "ToLower"; const char TOLOWER_ABBREV[] = "TL"; const char TOUPPER_NAME[] = "ToUpper"; const char TOUPPER_ABBREV[] = "TU"; const char TURBO_NAME[] = "Turbo"; const char TURBO_ABBREV[] = "TUR"; const char UNDELLINE_NAME[] = "UndelLine"; const char UNDELLINE_ABBREV[] = "UL"; const char UNDO_NAME[] = "Undo"; const char UNDO_ABBREV[] = "U"; const char UNLOADMACROS_NAME[] = "UnloadMacros"; const char UNLOADMACROS_ABBREV[] = "UM"; const char UNSETBOOKMARK_NAME[] = "UnsetBookmark"; const char UNSETBOOKMARK_ABBREV[] = "UBM"; const char UTF8_NAME[] = "UTF8"; const char UTF8_ABBREV[] = "U8"; const char UTF8AUTO_NAME[] = "UTF8Auto"; const char UTF8AUTO_ABBREV[] = "U8A"; const char UTF8IO_NAME[] = "UTF8IO"; const char UTF8IO_ABBREV[] = "U8IO"; const char VERBOSEMACROS_NAME[] = "VerboseMacros"; const char VERBOSEMACROS_ABBREV[] = "VM"; const char VISUALBELL_NAME[] = "VisualBell"; const char VISUALBELL_ABBREV[] = "VB"; const char WORDWRAP_NAME[] = "WordWrap"; const char WORDWRAP_ABBREV[] = "WW"; /* These are extras that are very useful in the default menus and key bindings. */ const char PLAYONCE_ABBREV[] = "PL 1"; const char MIDDLEVIEW_ABBREV[] = "AV M"; const char SHIFTLEFT_ABBREV[] = "SH <"; /* This is the NULL-terminated, ordered list of names, useful for help etc. */ const char * const command_names[ACTION_COUNT+1] = { ABOUT_NAME, ADJUSTVIEW_NAME, ALERT_NAME, ATOMICUNDO_NAME, AUTOCOMPLETE_NAME, AUTOINDENT_NAME, AUTOMATCHBRACKET_NAME, AUTOPREFS_NAME, BACKSPACE_NAME, BEEP_NAME, BINARY_NAME, CAPITALIZE_NAME, CASESEARCH_NAME, CENTER_NAME, CLEAR_NAME, CLIPNUMBER_NAME, CLOSEDOC_NAME, COPY_NAME, CRLF_NAME, CUT_NAME, DELETECHAR_NAME, DELETEEOL_NAME, DELETELINE_NAME, DELETENEXTWORD_NAME, DELETEPREVWORD_NAME, DELTABS_NAME, DOUNDO_NAME, ERASE_NAME, ESCAPE_NAME, ESCAPETIME_NAME, EXEC_NAME, EXIT_NAME, FASTGUI_NAME, FIND_NAME, FINDREGEXP_NAME, FLAGS_NAME, FLASH_NAME, FREEFORM_NAME, GOTOBOOKMARK_NAME, GOTOCOLUMN_NAME, GOTOLINE_NAME, GOTOMARK_NAME, HELP_NAME, HEXCODE_NAME, INSERT_NAME, INSERTCHAR_NAME, INSERTLINE_NAME, INSERTSTRING_NAME, INSERTTAB_NAME, KEYCODE_NAME, LINEDOWN_NAME, LINEUP_NAME, LOADAUTOPREFS_NAME, LOADPREFS_NAME, MACRO_NAME, MARK_NAME, MARKVERT_NAME, MATCHBRACKET_NAME, MODIFIED_NAME, MOVEBOS_NAME, MOVEEOF_NAME, MOVEEOL_NAME, MOVEEOW_NAME, MOVEINCDOWN_NAME, MOVEINCUP_NAME, MOVELEFT_NAME, MOVERIGHT_NAME, MOVESOF_NAME, MOVESOL_NAME, MOVETOS_NAME, NEWDOC_NAME, NEXTDOC_NAME, NEXTPAGE_NAME, NEXTWORD_NAME, NOFILEREQ_NAME, NOP_NAME, OPEN_NAME, OPENCLIP_NAME, OPENMACRO_NAME, OPENNEW_NAME, PAGEDOWN_NAME, PAGEUP_NAME, PARAGRAPH_NAME, PASTE_NAME, PASTEVERT_NAME, PLAY_NAME, POPPREFS_NAME, PRESERVECR_NAME, PREVDOC_NAME, PREVPAGE_NAME, PREVWORD_NAME, PUSHPREFS_NAME, QUIT_NAME, READONLY_NAME, RECORD_NAME, REDO_NAME, REFRESH_NAME, REPEATLAST_NAME, REPLACE_NAME, REPLACEALL_NAME, REPLACEONCE_NAME, REQUESTORDER_NAME, RIGHTMARGIN_NAME, SAVE_NAME, SAVEALL_NAME, SAVEAS_NAME, SAVEAUTOPREFS_NAME, SAVECLIP_NAME, SAVEDEFPREFS_NAME, SAVEMACRO_NAME, SAVEPREFS_NAME, SEARCHBACK_NAME, SELECTDOC_NAME, SETBOOKMARK_NAME, SHIFT_NAME, SHIFTTABS_NAME, STATUSBAR_NAME, SUSPEND_NAME, SYNTAX_NAME, SYSTEM_NAME, TABS_NAME, TABSIZE_NAME, THROUGH_NAME, TOGGLESEOF_NAME, TOGGLESEOL_NAME, TOLOWER_NAME, TOUPPER_NAME, TURBO_NAME, UNDELLINE_NAME, UNDO_NAME, UNLOADMACROS_NAME, UNSETBOOKMARK_NAME, UTF8_NAME, UTF8AUTO_NAME, UTF8IO_NAME, VERBOSEMACROS_NAME, VISUALBELL_NAME, WORDWRAP_NAME, NULL }; ne-3.0.1/src/names.h0000664000175200017510000002602012542017454013025 0ustar vignaprof/* Extern's for names and abbreviations of all the commands. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2015 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 . */ extern const char ABOUT_NAME[]; extern const char ABOUT_ABBREV[]; extern const char ADJUSTVIEW_NAME[]; extern const char ADJUSTVIEW_ABBREV[]; extern const char ALERT_NAME[]; extern const char ALERT_ABBREV[]; extern const char ATOMICUNDO_NAME[]; extern const char ATOMICUNDO_ABBREV[]; extern const char AUTOCOMPLETE_NAME[]; extern const char AUTOCOMPLETE_ABBREV[]; extern const char AUTOINDENT_NAME[]; extern const char AUTOINDENT_ABBREV[]; extern const char AUTOMATCHBRACKET_NAME[]; extern const char AUTOMATCHBRACKET_ABBREV[]; extern const char AUTOPREFS_NAME[]; extern const char AUTOPREFS_ABBREV[]; extern const char BACKSPACE_NAME[]; extern const char BACKSPACE_ABBREV[]; extern const char BEEP_NAME[]; extern const char BEEP_ABBREV[]; extern const char BINARY_NAME[]; extern const char BINARY_ABBREV[]; extern const char CAPITALIZE_NAME[]; extern const char CAPITALIZE_ABBREV[]; extern const char CASESEARCH_NAME[]; extern const char CASESEARCH_ABBREV[]; extern const char CENTER_NAME[]; extern const char CENTER_ABBREV[]; extern const char CLEAR_NAME[]; extern const char CLEAR_ABBREV[]; extern const char CLIPNUMBER_NAME[]; extern const char CLIPNUMBER_ABBREV[]; extern const char CLOSEDOC_NAME[]; extern const char CLOSEDOC_ABBREV[]; extern const char COPY_NAME[]; extern const char COPY_ABBREV[]; extern const char CRLF_NAME[]; extern const char CRLF_ABBREV[]; extern const char CUT_NAME[]; extern const char CUT_ABBREV[]; extern const char DELETECHAR_NAME[]; extern const char DELETECHAR_ABBREV[]; extern const char DELETEEOL_NAME[]; extern const char DELETEEOL_ABBREV[]; extern const char DELETELINE_NAME[]; extern const char DELETELINE_ABBREV[]; extern const char DELETENEXTWORD_NAME[]; extern const char DELETENEXTWORD_ABBREV[]; extern const char DELETEPREVWORD_NAME[]; extern const char DELETEPREVWORD_ABBREV[]; extern const char DELTABS_NAME[]; extern const char DELTABS_ABBREV[]; extern const char DOUNDO_NAME[]; extern const char DOUNDO_ABBREV[]; extern const char ERASE_NAME[]; extern const char ERASE_ABBREV[]; extern const char ESCAPE_NAME[]; extern const char ESCAPE_ABBREV[]; extern const char ESCAPETIME_NAME[]; extern const char ESCAPETIME_ABBREV[]; extern const char EXEC_NAME[]; extern const char EXEC_ABBREV[]; extern const char EXIT_NAME[]; extern const char EXIT_ABBREV[]; extern const char FASTGUI_NAME[]; extern const char FASTGUI_ABBREV[]; extern const char FIND_NAME[]; extern const char FIND_ABBREV[]; extern const char FINDREGEXP_NAME[]; extern const char FINDREGEXP_ABBREV[]; extern const char FLAGS_NAME[]; extern const char FLAGS_ABBREV[]; extern const char FLASH_NAME[]; extern const char FLASH_ABBREV[]; extern const char FREEFORM_NAME[]; extern const char FREEFORM_ABBREV[]; extern const char GOTOBOOKMARK_NAME[]; extern const char GOTOBOOKMARK_ABBREV[]; extern const char GOTOCOLUMN_NAME[]; extern const char GOTOCOLUMN_ABBREV[]; extern const char GOTOLINE_NAME[]; extern const char GOTOLINE_ABBREV[]; extern const char GOTOMARK_NAME[]; extern const char GOTOMARK_ABBREV[]; extern const char HELP_NAME[]; extern const char HELP_ABBREV[]; extern const char HEXCODE_NAME[]; extern const char HEXCODE_ABBREV[]; extern const char INSERT_NAME[]; extern const char INSERT_ABBREV[]; extern const char INSERTCHAR_NAME[]; extern const char INSERTCHAR_ABBREV[]; extern const char INSERTLINE_NAME[]; extern const char INSERTLINE_ABBREV[]; extern const char INSERTSTRING_NAME[]; extern const char INSERTSTRING_ABBREV[]; extern const char INSERTTAB_NAME[]; extern const char INSERTTAB_ABBREV[]; extern const char KEYCODE_NAME[]; extern const char KEYCODE_ABBREV[]; extern const char LINEDOWN_NAME[]; extern const char LINEDOWN_ABBREV[]; extern const char LINEUP_NAME[]; extern const char LINEUP_ABBREV[]; extern const char LOADAUTOPREFS_NAME[]; extern const char LOADAUTOPREFS_ABBREV[]; extern const char LOADPREFS_NAME[]; extern const char LOADPREFS_ABBREV[]; extern const char MACRO_NAME[]; extern const char MACRO_ABBREV[]; extern const char MARK_NAME[]; extern const char MARK_ABBREV[]; extern const char MARKVERT_NAME[]; extern const char MARKVERT_ABBREV[]; extern const char MATCHBRACKET_NAME[]; extern const char MATCHBRACKET_ABBREV[]; extern const char MODIFIED_NAME[]; extern const char MODIFIED_ABBREV[]; extern const char MOVEBOS_NAME[]; extern const char MOVEBOS_ABBREV[]; extern const char MOVEEOF_NAME[]; extern const char MOVEEOF_ABBREV[]; extern const char MOVEEOL_NAME[]; extern const char MOVEEOL_ABBREV[]; extern const char MOVEEOW_NAME[]; extern const char MOVEEOW_ABBREV[]; extern const char MOVEINCDOWN_NAME[]; extern const char MOVEINCDOWN_ABBREV[]; extern const char MOVEINCUP_NAME[]; extern const char MOVEINCUP_ABBREV[]; extern const char MOVELEFT_NAME[]; extern const char MOVELEFT_ABBREV[]; extern const char MOVERIGHT_NAME[]; extern const char MOVERIGHT_ABBREV[]; extern const char MOVESOF_NAME[]; extern const char MOVESOF_ABBREV[]; extern const char MOVESOL_NAME[]; extern const char MOVESOL_ABBREV[]; extern const char MOVETOS_NAME[]; extern const char MOVETOS_ABBREV[]; extern const char NEWDOC_NAME[]; extern const char NEWDOC_ABBREV[]; extern const char NEXTDOC_NAME[]; extern const char NEXTDOC_ABBREV[]; extern const char NEXTPAGE_NAME[]; extern const char NEXTPAGE_ABBREV[]; extern const char NEXTWORD_NAME[]; extern const char NEXTWORD_ABBREV[]; extern const char NOFILEREQ_NAME[]; extern const char NOFILEREQ_ABBREV[]; extern const char NOP_NAME[]; extern const char NOP_ABBREV[]; extern const char OPEN_NAME[]; extern const char OPEN_ABBREV[]; extern const char OPENCLIP_NAME[]; extern const char OPENCLIP_ABBREV[]; extern const char OPENMACRO_NAME[]; extern const char OPENMACRO_ABBREV[]; extern const char OPENNEW_NAME[]; extern const char OPENNEW_ABBREV[]; extern const char PAGEDOWN_NAME[]; extern const char PAGEDOWN_ABBREV[]; extern const char PAGEUP_NAME[]; extern const char PAGEUP_ABBREV[]; extern const char PARAGRAPH_NAME[]; extern const char PARAGRAPH_ABBREV[]; extern const char PASTE_NAME[]; extern const char PASTE_ABBREV[]; extern const char PASTEVERT_NAME[]; extern const char PASTEVERT_ABBREV[]; extern const char PLAY_NAME[]; extern const char PLAY_ABBREV[]; extern const char POPPREFS_NAME[]; extern const char POPPREFS_ABBREV[]; extern const char PRESERVECR_NAME[]; extern const char PRESERVECR_ABBREV[]; extern const char PREVDOC_NAME[]; extern const char PREVDOC_ABBREV[]; extern const char PREVPAGE_NAME[]; extern const char PREVPAGE_ABBREV[]; extern const char PREVWORD_NAME[]; extern const char PREVWORD_ABBREV[]; extern const char PUSHPREFS_NAME[]; extern const char PUSHPREFS_ABBREV[]; extern const char QUIT_NAME[]; extern const char QUIT_ABBREV[]; extern const char READONLY_NAME[]; extern const char READONLY_ABBREV[]; extern const char RECORD_NAME[]; extern const char RECORD_ABBREV[]; extern const char REDO_NAME[]; extern const char REDO_ABBREV[]; extern const char REFRESH_NAME[]; extern const char REFRESH_ABBREV[]; extern const char REPEATLAST_NAME[]; extern const char REPEATLAST_ABBREV[]; extern const char REPLACE_NAME[]; extern const char REPLACE_ABBREV[]; extern const char REPLACEALL_NAME[]; extern const char REPLACEALL_ABBREV[]; extern const char REPLACEONCE_NAME[]; extern const char REPLACEONCE_ABBREV[]; extern const char REQUESTORDER_NAME[]; extern const char REQUESTORDER_ABBREV[]; extern const char RIGHTMARGIN_NAME[]; extern const char RIGHTMARGIN_ABBREV[]; extern const char SAVE_NAME[]; extern const char SAVE_ABBREV[]; extern const char SAVEALL_NAME[]; extern const char SAVEALL_ABBREV[]; extern const char SAVEAS_NAME[]; extern const char SAVEAS_ABBREV[]; extern const char SAVEAUTOPREFS_NAME[]; extern const char SAVEAUTOPREFS_ABBREV[]; extern const char SAVECLIP_NAME[]; extern const char SAVECLIP_ABBREV[]; extern const char SAVEDEFPREFS_NAME[]; extern const char SAVEDEFPREFS_ABBREV[]; extern const char SAVEMACRO_NAME[]; extern const char SAVEMACRO_ABBREV[]; extern const char SAVEPREFS_NAME[]; extern const char SAVEPREFS_ABBREV[]; extern const char SEARCHBACK_NAME[]; extern const char SEARCHBACK_ABBREV[]; extern const char SELECTDOC_NAME[]; extern const char SELECTDOC_ABBREV[]; extern const char SETBOOKMARK_NAME[]; extern const char SETBOOKMARK_ABBREV[]; extern const char SHIFT_NAME[]; extern const char SHIFT_ABBREV[]; extern const char SHIFTTABS_NAME[]; extern const char SHIFTTABS_ABBREV[]; extern const char STATUSBAR_NAME[]; extern const char STATUSBAR_ABBREV[]; extern const char SUSPEND_NAME[]; extern const char SUSPEND_ABBREV[]; extern const char SYNTAX_NAME[]; extern const char SYNTAX_ABBREV[]; extern const char SYSTEM_NAME[]; extern const char SYSTEM_ABBREV[]; extern const char TABS_NAME[]; extern const char TABS_ABBREV[]; extern const char TABSIZE_NAME[]; extern const char TABSIZE_ABBREV[]; extern const char THROUGH_NAME[]; extern const char THROUGH_ABBREV[]; extern const char TOGGLESEOF_NAME[]; extern const char TOGGLESEOF_ABBREV[]; extern const char TOGGLESEOL_NAME[]; extern const char TOGGLESEOL_ABBREV[]; extern const char TOLOWER_NAME[]; extern const char TOLOWER_ABBREV[]; extern const char TOUPPER_NAME[]; extern const char TOUPPER_ABBREV[]; extern const char TURBO_NAME[]; extern const char TURBO_ABBREV[]; extern const char UNDELLINE_NAME[]; extern const char UNDELLINE_ABBREV[]; extern const char UNDO_NAME[]; extern const char UNDO_ABBREV[]; extern const char UNLOADMACROS_NAME[]; extern const char UNLOADMACROS_ABBREV[]; extern const char UNSETBOOKMARK_NAME[]; extern const char UNSETBOOKMARK_ABBREV[]; extern const char UTF8_NAME[]; extern const char UTF8_ABBREV[]; extern const char UTF8AUTO_NAME[]; extern const char UTF8AUTO_ABBREV[]; extern const char UTF8IO_NAME[]; extern const char UTF8IO_ABBREV[]; extern const char VERBOSEMACROS_NAME[]; extern const char VERBOSEMACROS_ABBREV[]; extern const char VISUALBELL_NAME[]; extern const char VISUALBELL_ABBREV[]; extern const char WORDWRAP_NAME[]; extern const char WORDWRAP_ABBREV[]; /* These are extras that are very useful in the default menus and key bindings. */ extern const char PLAYONCE_ABBREV[]; extern const char MIDDLEVIEW_ABBREV[]; extern const char SHIFTLEFT_ABBREV[]; /* 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 info2src.pl. */ ne-3.0.1/src/navigation.c0000664000175200017510000006455012522713204014060 0ustar vignaprof/* Navigation functions. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2015 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" /* 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; 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); 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, 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, 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; } void goto_line(buffer * const b, const int64_t n) { b->y_wanted = 0; if (n >= b->num_lines || n == b->cur_line) return; line_desc *ld; if (n >= b->win_y && n < b->win_y + ne_lines - 1) { 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; 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); 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) 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 character offset) instead. */ void goto_pos(buffer * const b, const int64_t pos) { goto_column(b, calc_width(b->cur_line_desc, pos, b->opt.tab_size, b->encoding)); } 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, 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, b->encoding); if (t != b->win_x) 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); } /* Searches for the start of the next or previous word, depending on the value of dir. */ int search_word(buffer * const b, const int dir) { assert(dir == -1 || dir == 1); line_desc *ld = b->cur_line_desc; int64_t pos = b->cur_pos; bool word_started = false, space_skipped = false; if (pos >= ld->line_len) pos = ld->line_len; else if (!ne_isword(get_char(&ld->line[pos], b->encoding), b->encoding)) space_skipped = true; if (dir < 0 || pos < ld->line_len) pos = (dir > 0 ? next_pos : prev_pos)(ld->line, pos, b->encoding); int64_t y = b->cur_line; while(y < b->num_lines && y >= 0) { while(pos < ld->line_len && pos >= 0) { const int c = get_char(&ld->line[pos], b->encoding); if (!ne_isword(c, b->encoding)) space_skipped = true; else word_started = true; if (dir > 0) { if (space_skipped && ne_isword(c, b->encoding)) { goto_line(b, y); goto_pos(b, pos); return OK; } } else { if (word_started) { if (!ne_isword(c, b->encoding)) { goto_line(b, y); goto_pos(b, pos + 1); return OK; } else if (pos == 0) { goto_line(b, y); goto_pos(b, 0); return OK; } } } pos = (dir > 0 ? next_pos : prev_pos)(ld->line, pos, b->encoding); } space_skipped = true; if (dir > 0) { ld = (line_desc *)ld->ld_node.next; y++; pos = 0; } else { ld = (line_desc *)ld->ld_node.prev; y--; if (ld->ld_node.prev) pos = prev_pos(ld->line, ld->line_len, b->encoding); } } return ERROR; } /* 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.0.1/src/ne.c0000664000175200017510000003377112542017427012332 0ustar vignaprof/* main(), global initialization and global buffer functions. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2015 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-2015 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 or Escape-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/", "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.\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" "--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.\n" "--no-config do not read configuration files.\n" "--prefs EXT set autoprefs for the provided extension before loading the first file.\n" "--no-syntax disable syntax-highlighting support.\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 contains 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 }; list macros = { (node *)¯os.tail, NULL, (node *)¯os.head }; /* 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 verbose_macros = true; /* end of global prefs */ buffer *cur_buffer; int turbo; bool do_syntax = true; /* These function 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; } int delete_buffer(void) { buffer *nb = (buffer *)cur_buffer->b_node.next, *pb = (buffer *)cur_buffer->b_node.prev; rem(&cur_buffer->b_node); free_buffer(cur_buffer); if (pb->b_node.prev) { cur_buffer = pb; return true; } if (nb->b_node.next) { cur_buffer = nb; return true; } return false; } void about(const bool show) { if (show) { clear_entire_screen(); 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); } if (++i < ne_lines - 1) { move_cursor(i, 0); if (exists_gprefs_dir()) { output_string("Global Directory: ", false); output_string(exists_gprefs_dir(), false); } else { output_string("Global directory \"", false); output_string(get_global_dir(), false); output_string("\" not found!", false); } } print_message(ABOUT_MSG); } else { ttysize(); keep_cursor_on_screen(cur_buffer); } } /* 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, displaying_info = 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++) { /* 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], "help" "\0" VERSION_STRING)) { puts(ARG_HELP); exit(0); } else if (!strcmp(&argv[i][2], "noconfig") || !strcmp(&argv[i][2], "no-config")) { no_config = true; skiplist[i] = 1; /* argv[i] = NULL; */ } else if (!strcmp(&argv[i][2], "noansi") || !strcmp(&argv[i][2], "no-ansi")) { ansi = false; skiplist[i] = 1; /* argv[i] = NULL; */ } else if (!strcmp(&argv[i][2], "no-syntax")) { do_syntax = 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_config(void); dump_config(); #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); } /* If we cannot even create a buffer, better go... */ if (!new_buffer()) exit(1); 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 initalization. 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_auto_prefs(cur_buffer, startup_prefs_name); if (!isatty(fileno(stdin))) { first_file = false; const int error = load_fh_in_buffer(cur_buffer, fileno(stdin)); print_error(error); 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; 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],"--")) 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); first_line = first_col = 0; skip_plus = binary = false; } } } free(skiplist); /* This call makes current the first specified file. It is called only if more than one buffer exist. */ 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. */ 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(true); displaying_info = true; } 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->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 = false; cur_buffer->automatch.shown = 0; } if (c == INVALID_CHAR) continue; /* Window resizing. */ const input_class ic = CHAR_CLASS(c); if (displaying_info) { about(false); displaying_info = false; } else 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])); break; default: break; } } } ne-3.0.1/src/ne.h0000664000175200017510000005077212537653642012350 0ustar vignaprof/* Main typedefs and defines. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2015 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 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[]; /* The name of the default preferences file name. */ #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. */ #define NUM_KEYS (256+256) /* 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. User get 0 through 9 plus the AUTO_BOOKMARK '-'. */ enum { MAX_USER_BOOKMARK = 9, AUTO_BOOKMARK, WORDWRAP_BOOKMARK, NUM_BOOKMARKS }; enum { COMPLETE_NONE = 0, COMPLETE_FILE, COMPLETE_CMD_FILE, /* 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. */ char suffix; int cur_entries; int alloc_entries; int max_entry_len; char **entries; int *orig_order; /* maps from current order to original order when allow_reorder is true. */ int cur_chars; int alloc_chars; char *chars; } 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; /* 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 pointer by ld_text 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. The first free descriptor is contained in first_free. The last free descriptor is contained in last_free. 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; line_desc *pool; } 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; } 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 free_form:1, /* Editing is free form (cursor can be anywhere) */ hex_code:1, /* Show hexadecimal code under the cursor */ word_wrap:1, /* Word wrap is on */ auto_indent:1, /* Replicate indentation when creating a new line */ preserve_cr:1, /* Preserve Carriage Returns, don't treat as line terminators. */ insert:1, /* Insert mode */ do_undo:1, /* Record each action and allow undoing it */ auto_prefs:1, /* Use autoprefs */ no_file_req:1, /* Do not display the file requester */ read_only:1, /* Read-only mode */ search_back:1, /* Last search was backwards */ case_search:1, /* Look at case matching in searches */ tabs:1, /* TAB inserts TABs(1) vs. spaces(0) */ del_tabs:1, /* DEL/BS deletes tab's worth of space. */ shift_tabs:1, /* Shift may insert tabs, but only if tabs is also true */ automatch:4, /* Automatically match visible brackets */ binary:1, /* Load and save in binary mode */ utf8auto:1, /* Try to detect automatically UTF-8 */ visual_bell:1; /* Prefer visible bell to audible */ } 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; unsigned long mtime; /* mod time of on-disk file when it was last loaded/saved, or 0 */ 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) */ int64_t cur_char; /* position of cursor within the attribute buffer (counts characters) */ int64_t num_lines; int64_t block_start_line, block_start_pos; struct { int shown; int x; /* Visual (on-screen) coordinates of the highlighted bracket, if shown is true. */ int y; } automatch; 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 */ recording:1, /* We are recording a macro */ 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 */ find_string_changed:1, /* The search string has to be recompiled before use. */ 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 */ mark_is_vertical:1, /* The current marking is vertical */ atomic_undo:1, /* subsequent commands undo as a block */ executing_macro:1, /* We are currently executing a macro. */ executing_internal_macro:1, /* We are currently executing the internal macro of the current buffer */ is_CRLF:1; /* Buffer should be saved with CR/LF terminators */ 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 >= (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->encoding));\ }} #else #define assert_buffer(b) ; #define assert_buffer_content(b); #endif #include "syntax.h" extern const char *key_binding[]; extern buffer *cur_buffer; /* These are the global lists. */ extern list buffers, clips, macros; /* 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; /* 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) /* 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.0.1/src/prefs.c0000664000175200017510000002536712517741617013060 0ustar vignaprof/* Preferences functions. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2015 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 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" /* This string is appended to the filename extension. */ #define SYNTAX_FILE_SUFFIX ".jsf" /* 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_global; /* Returns a pointer to the extension of a filename, or NULL if there is no extension. Note that filename has to be non NULL. */ const char *extension(const char * const filename) { assert(filename != NULL); for(int i = strlen(filename); i-- != 0;) if (filename[i] == '.') return &filename[i + 1]; return NULL; } /* Returns a pointer to the absolute name of ne's prefs directory. 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. 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_global && 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); record_action(cs, CLIPNUMBER_A, b->opt.cur_clip, NULL, verbose_macros); 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 (saving_global) { /* 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_global = 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(b, cs); free_char_stream(cs); } else error = ERROR; 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; } /* 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 && (!b->filename || !(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 return 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_global = 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; } } ne-3.0.1/src/protos.h0000664000175200017510000003242212534424611013251 0ustar vignaprof/* Function prototypes Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2015 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); 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); 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); 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_fh_in_buffer(buffer *b, int fh); 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); 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); 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(buffer *b, char_stream *cs); 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); void optimize_macro(char_stream *cs, bool verbose); /* 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, 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); line_desc *update_partial_line(buffer *b, int n, int64_t start_x, bool cleared_at_end, const bool differential); void update_line(buffer *b, int n, const bool cleared_at_end, const bool differential); void update_window_lines(buffer *b, int start_line, int end_line, bool doit); void update_syntax_and_lines(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, const line_desc *ld, int64_t pos, int64_t attr_pos, int line, int x); void update_inserted_char(buffer *b, int c, const 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, const 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, int line, int n); HIGHLIGHT_STATE freeze_attributes(buffer *b, line_desc *ld); void automatch_bracket(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); int word_wrap(buffer *b); int paragraph(buffer *b); 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); /* 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 *); void get_key_bindings(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(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); void move_to_eow(buffer *b); void move_inc_down(buffer *b); void move_inc_up(buffer *b); /* ne.c */ buffer *new_buffer(void); int delete_buffer(void); void about(bool show); 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); int pop_prefs(buffer *b); int push_prefs(buffer *b); /* input.c */ 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 char *prompt, int64_t default_value); char *request_string(const char *prompt, const char *default_string, bool accept_null_string, int completion_type, bool prefer_utf8); char *request(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); char *complete_filename(const char *start_prefix); /* 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); int replace(buffer *b, int n, const char *string); int find_regexp(buffer *b, const char *regex, const bool skip_first); int replace_regexp(buffer *b, const char *string); /* 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); 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_fh(char_stream *cs, int fh, bool preserve_cr, bool binary); int save_stream(const char_stream *cs, const char *name, bool CRLF, bool binary); int save_stream_to_fh(const char_stream *cs, int fh, 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); 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); int64_t read_safely(const int fh, void * const buf, const int64_t len); bool buffer_file_modified(const buffer *b, const char *name); char *str_dup(const char *s); 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); int64_t calc_width(const line_desc *ld, int64_t n, int tab_size, encoding_type encoding); int64_t 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); int64_t calc_char_len(const line_desc *ld, encoding_type encoding); int64_t calc_pos(const line_desc *ld, int64_t n, int tab_size, encoding_type encoding); int get_string_width(const char * const s, const int64_t len, const encoding_type encoding); int max_prefix(const char *s, const char *t); bool is_prefix(const char *p, const char *s); bool is_migrated(const char *name); bool is_directory(const char *name); bool is_ascii(const char *s, int len); bool isparaspot(int c); bool isasciispace(int c); bool isasciipunct(int c); bool isasciialpha(int c); int asciitoupper(int c); int asciitolower(int c); encoding_type detect_encoding(const char *s, int64_t len); int64_t next_pos(const char *s, int64_t pos, encoding_type encoding); int64_t prev_pos(const char *s, int64_t pos, encoding_type encoding); int get_char(const char *s, encoding_type encoding); int get_char_width(const char * const s, const encoding_type encoding); bool ne_ispunct(const int c, const int encoding); bool ne_isspace(const int c, const int encoding); bool ne_isword(const int c, const int encoding); int context_prefix(const buffer *b, char **p, int64_t *prefix_pos); line_desc *nth_line_desc(const buffer *b, const int64_t n); /* 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.0.1/src/regcomp.c0000664000175200017510000033532012541614145013356 0ustar vignaprof/* Extended regular expression matching and search library. Copyright (C) 2002-2015 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 General Public License as published by the Free Software Foundation; either version 3 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 General Public License for more details. You should have received a copy of the GNU General Public License along with the GNU C Library; if not, see . */ 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. */ #ifdef _LIBC const char * re_compile_pattern (pattern, length, bufp) const char *pattern; size_t length; struct re_pattern_buffer *bufp; #else /* size_t might promote */ const char * re_compile_pattern (const char *pattern, size_t length, struct re_pattern_buffer *bufp) #endif { 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 (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 (bufp) 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 (preg, pattern, cflags) 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. */ #ifdef _LIBC size_t regerror (errcode, preg, errbuf, errbuf_size) int errcode; const regex_t *_Restrict_ preg; char *_Restrict_ errbuf; size_t errbuf_size; #else /* size_t might promote */ size_t regerror (int errcode, const regex_t *_Restrict_ preg, char *_Restrict_ errbuf, size_t errbuf_size) #endif { 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 (preg) 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 (s) 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 == REG_MISSING, 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 (REG_VALID_INDEX (left)); assert (REG_VALID_INDEX (right)); 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 == REG_MISSING, 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 == REG_MISSING, 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 == REG_MISSING) { /* 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 == REG_MISSING, 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 == REG_MISSING, 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 REG_MISSING; /* Not found. */ } /* Duplicate the node whose index is ORG_IDX and set the constraint CONSTRAINT. Return the index of the new node, or REG_MISSING 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 != REG_MISSING, 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 != REG_MISSING); #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 = REG_MISSING; /* 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 == REG_MISSING) { 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; 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)) { 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; } } 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 == REG_MISSING) { 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 != REG_ERROR, 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) : REG_ERROR)); } if (BE (start == REG_ERROR || end == REG_ERROR, 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 != REG_MISSING && 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 == REG_MISSING ? start : end), 0)) { *err = REG_ESIZE; return NULL; } } else { start = (token->type == OP_DUP_PLUS) ? 1 : 0; end = (token->type == OP_DUP_QUESTION) ? 1 : REG_MISSING; } 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 == REG_MISSING ? 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 != REG_MISSING, to rewrite {0,n} as ((...?)?)?... We have already created the start+1-th copy. */ if (TYPE_SIGNED (Idx) || end != REG_MISSING) 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 /* 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) ? __btowc (start_ch) : start_elem->opr.wch); end_wc = ((end_elem->type == SB_CHAR || end_elem->type == COLL_SYM) ? __btowc (end_ch) : 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)) 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; 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; 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; /* This #include defines a local function! */ # include /* 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 (&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 (&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); #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 /* not RE_ENABLE_I18N */ if (BE (sbcset == NULL, 0)) #endif /* not RE_ENABLE_I18N */ { *err = REG_ESPACE; return NULL; } if (non_match) { #ifdef RE_ENABLE_I18N mbcset->non_match = 1; #endif /* not 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. */ 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 REG_MISSING if the number field is empty like "{,1}". Return RE_DUP_MAX + 1 if the number field is too large. Return REG_ERROR if an error occurred. */ static Idx fetch_number (re_string_t *input, re_token_t *token, reg_syntax_t syntax) { Idx num = REG_MISSING; unsigned char c; while (1) { fetch_token (token, input, syntax); c = token->opr.c; if (BE (token->type == END_OF_RE, 0)) return REG_ERROR; if (token->type == OP_CLOSE_DUP_NUM || c == ',') break; num = ((token->type != CHARACTER || c < '0' || '9' < c || num == REG_ERROR) ? REG_ERROR : num == REG_MISSING ? 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; 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 = REG_MISSING; 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.0.1/src/regex.c0000664000175200017510000000642212541614145013032 0ustar vignaprof/* Extended regular expression matching and search library. Copyright (C) 2002-2015 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 General Public License as published by the Free Software Foundation; either version 3 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 General Public License for more details. You should have received a copy of the GNU General Public License along with the GNU C Library; if not, see . */ #ifndef _LIBC /* PORTABILITY PROBLEM: We do not include config.h. */ # 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. */ typedef int bool; #define false (0) #define true (1) #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.0.1/src/regex.h0000664000175200017510000006060012541614145013035 0ustar vignaprof/* Definitions for data structures and routines for the regular expression library. Copyright (C) 1985, 1989-1993, 1995-1998, 2000-2003, 2005-2015 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 General Public License as published by the Free Software Foundation; either version 3 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 General Public License for more details. You should have received a copy of the GNU 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 nonnegative object indexes. Traditionally, GNU regex uses 'int' for these. Code that uses __re_idx_t should work regardless of whether the type is signed. */ typedef size_t __re_idx_t; /* 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 int __re_idx_t; 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_BK_PLUS_QM | RE_CHAR_CLASSES \ | RE_HAT_LISTS_NOT_NEWLINE | RE_INTERVALS \ | RE_NEWLINE_ALT) # define RE_SYNTAX_EGREP \ (RE_CHAR_CLASSES | RE_CONTEXT_INDEP_ANCHORS \ | RE_CONTEXT_INDEP_OPS | RE_HAT_LISTS_NOT_NEWLINE \ | RE_NEWLINE_ALT | RE_NO_BK_PARENS \ | RE_NO_BK_VBAR) # define RE_SYNTAX_POSIX_EGREP \ (RE_SYNTAX_EGREP | RE_INTERVALS | RE_NO_BK_BRACES \ | RE_INVALID_INTERVAL_ORD) /* 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. */ 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 initialised 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, __re_idx_t __length, __re_idx_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, __re_idx_t __length1, const char *__string2, __re_idx_t __length2, __re_idx_t __start, regoff_t __range, struct re_registers *__regs, __re_idx_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, __re_idx_t __length, __re_idx_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, __re_idx_t __length1, const char *__string2, __re_idx_t __length2, __re_idx_t __start, struct re_registers *__regs, __re_idx_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.0.1/src/regex_internal.c0000664000175200017510000013673212541614145014736 0ustar vignaprof/* Extended regular expression matching and search library. Copyright (C) 2002-2015 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 General Public License as published by the Free Software Foundation; either version 3 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 General Public License for more details. You should have received a copy of the GNU 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 (! REG_VALID_INDEX (idx), 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 (REG_VALID_INDEX (wc_idx)); #endif --wc_idx; if (! REG_VALID_INDEX (wc_idx)) 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 (REG_VALID_INDEX (id) && dest->elems[id] > src1->elems[i1]) --id; if (! REG_VALID_INDEX (id) || dest->elems[id] != src1->elems[i1]) dest->elems[--sbase] = src1->elems[i1]; if (! REG_VALID_INDEX (--i1) || ! REG_VALID_INDEX (--i2)) break; } /* Lower the highest of the two items. */ else if (src1->elems[i1] < src2->elems[i2]) { if (! REG_VALID_INDEX (--i2)) break; } else { if (! REG_VALID_INDEX (--i1)) 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 && REG_VALID_INDEX (id)) 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 (! REG_VALID_INDEX (--id)) 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; REG_VALID_INDEX (is) && REG_VALID_INDEX (id); ) { 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 (REG_VALID_INDEX (is)) { /* 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 (! REG_VALID_INDEX (--id)) { /* 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 ; REG_VALID_INDEX (--i) ; ) 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 (! REG_VALID_NONZERO_INDEX (set->nelem)) 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 REG_MISSING 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 REG_MISSING; new_nodes = re_realloc (dfa->nodes, re_token_t, new_nodes_alloc); if (BE (new_nodes == NULL, 0)) return REG_MISSING; 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)) return REG_MISSING; 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] = REG_MISSING; 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; #ifdef 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; #ifdef 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.0.1/src/regex_internal.h0000664000175200017510000006176112541614145014742 0ustar vignaprof/* Extended regular expression matching and search library. Copyright (C) 2002-2015 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 General Public License as published by the Free Software Foundation; either version 3 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 General Public License for more details. You should have received a copy of the GNU 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 # 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 __iswctype iswctype # 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 typedef __re_idx_t Idx; #ifdef _REGEX_LARGE_OFFSETS # define IDX_MAX (SIZE_MAX - 2) #else # define IDX_MAX INT_MAX #endif /* Special return value for failure to match. */ #define REG_MISSING ((Idx) -1) /* Special return value for internal error. */ #define REG_ERROR ((Idx) -2) /* Test whether N is a valid index, and is not one of the above. */ #ifdef _REGEX_LARGE_OFFSETS # define REG_VALID_INDEX(n) ((Idx) (n) < REG_ERROR) #else # define REG_VALID_INDEX(n) (0 <= (n)) #endif /* Test whether N is a valid nonzero index. */ #ifdef _REGEX_LARGE_OFFSETS # define REG_VALID_NONZERO_INDEX(n) ((Idx) ((n) - 1) < (Idx) (REG_ERROR - 1)) #else # define REG_VALID_NONZERO_INDEX(n) (0 < (n)) #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 #endif #ifndef NOT_IN_libc 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)); #endif #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]; } # ifndef NOT_IN_libc 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; # include 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 (&p, pstr->len - idx); return p - pstr->mbs - idx; } else # endif /* _LIBC */ return 1; } # endif #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.0.1/src/regexec.c0000664000175200017510000037771612541614145013363 0ustar vignaprof/* Extended regular expression matching and search library. Copyright (C) 2002-2015 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 General Public License as published by the Free Software Foundation; either version 3 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 General Public License for more details. You should have received a copy of the GNU 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 (preg, string, nmatch, pmatch, eflags) const regex_t *_Restrict_ preg; const char *_Restrict_ string; size_t nmatch; regmatch_t pmatch[_Restrict_arr_]; 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 (bufp, string, length, start, regs) struct re_pattern_buffer *bufp; const char *string; Idx length, 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 (bufp, string, length, start, range, regs) struct re_pattern_buffer *bufp; const char *string; Idx length, 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 (bufp, string1, length1, string2, length2, start, regs, stop) struct re_pattern_buffer *bufp; const char *string1, *string2; Idx length1, length2, start, stop; struct re_registers *regs; { 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 (bufp, string1, length1, string2, length2, start, range, regs, stop) struct re_pattern_buffer *bufp; const char *string1, *string2; Idx length1, length2, start, stop; regoff_t range; struct re_registers *regs; { 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 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 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 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 (bufp, regs, num_regs, starts, ends) struct re_pattern_buffer *bufp; struct re_registers *regs; __re_size_t num_regs; regoff_t *starts, *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 (s) 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__ 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 = REG_MISSING; 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 != REG_MISSING) { if (BE (match_last == REG_ERROR, 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 = REG_MISSING; } else break; /* We found a match. */ } } match_ctx_clean (&mctx); } #ifdef DEBUG assert (match_last != REG_MISSING); 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 __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 (! REG_VALID_INDEX (match_last)) { 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 REG_MISSING if there is no match, and return REG_ERROR 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 = REG_MISSING; 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 REG_ERROR; } 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 REG_ERROR; } } 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 REG_ERROR; 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 REG_MISSING 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 REG_ERROR; /* Pick up a valid destination, or return REG_MISSING if none is found. */ for (dest_node = REG_MISSING, i = 0; i < edests->nelem; ++i) { Idx candidate = edests->elems[i]; if (!re_node_set_contains (cur_nodes, candidate)) continue; if (dest_node == REG_MISSING) 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 REG_ERROR; /* 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 REG_MISSING; 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 REG_MISSING; } } if (naccepted == 0) { Idx dest_node; ok = re_node_set_insert (eps_via_nodes, node); if (BE (! ok, 0)) return REG_ERROR; 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 REG_MISSING; re_node_set_empty (eps_via_nodes); return dest_node; } } return REG_MISSING; } 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 (REG_VALID_INDEX (num)); *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 (! REG_VALID_INDEX (cur_node), 0)) { if (BE (cur_node == REG_ERROR, 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] : REG_MISSING); if ((!re_node_set_contains (inv_eclosure, edst1) && re_node_set_contains (dest_nodes, edst1)) || (REG_VALID_NONZERO_INDEX (edst2) && !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 != REG_MISSING) { 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 = REG_MISSING; Idx cls_node = REG_MISSING; 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 (REG_VALID_INDEX (ops_node)) { 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 (REG_VALID_INDEX (cls_node)) 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 == REG_MISSING) 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 could'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] != REG_MISSING); #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] != REG_MISSING); #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 != REG_MISSING) { 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 == REG_MISSING) 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 REG_MISSING; } /* 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 == REG_MISSING) { /* 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 == REG_MISSING) 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 (! REG_VALID_NONZERO_INDEX (ndests), 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 != REG_MISSING) { 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 REG_MISSING; } #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. */ 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; /* This #include defines a local function! */ # include /* 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 (&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 = REG_MISSING; 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 REG_MISSING 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 REG_MISSING; } /* 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.0.1/src/request.c0000664000175200017510000007320512525431422013410 0ustar vignaprof/* Requester handling. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2015 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 /* This is the expected max length of the current directory name. */ #define CUR_DIR_MAX_SIZE (16*1024) /* request_strings() prompts the user to choose one between several (cur_entries) strings, contained in the entries array. The maximum string width is given as max_name_len. The strings are displayed as an array. More than one page will be available if there are many strings. 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. We rely on a series of auxiliary functions and a few static variables. */ static req_list rl, *rl0; /* working and original * req_list */ static int x, y, page, max_names_per_line, max_names_per_col, names_per_page, fuzz_len; /* ne traditionally has displayed request entries by row, i.e. a b c d e f g h rather than by column, i.e. a d g b e h c f which, while easier to program, is somewhat harder to read. The request code was also full of tricky expressions like those in the macros below. This version attempts to switch to a by-row request display, and also to consolidate as many of the tricky expressions into one small set of macros. If request_order is true you should get by-column request list. Otherwise it will exibit the by-row behavior of the older versions of ne. */ #define BR_NAMES_PER_LINE(p) max_names_per_line #define BR_NAMES_PER_COL(p) max_names_per_col #define BR_PXY2N(p,x,y) (((y) + (p) * max_names_per_col) * max_names_per_line + (x)) #define BR_N2P(n) ((n) / names_per_page) #define BR_N2X(n) (((n) % names_per_page) % max_names_per_line) #define BR_N2Y(n) (((n) % names_per_page) / max_names_per_line) #define BR_DX(p) 1 #define BR_DY max_names_per_line /* This Perl snippet is useful for tweaking the NAMES_PER_LINE and NAMES_PER_COL macros. The point of the complexity on the last page is to use a rectangle in the upper-left part of the window that's roughly proportional to the window itself. (Prior pages use the entire window of course.) Calculating $x first gives slight priority to taller columns rather than wider lines. Translating the Perl "$x =" and "$y =" to the C macros NAMES_PER_LINE and NAMES_PER_COL, respectively, is a matter of substituting the following: $N ($M - $n) $n (rl.cur_entries % names_per_page) The number of actual entries on the last page. $X max_names_per_line $Y max_names_per_col $M names_per_page $x NAMES_PER_LINE(p) #!/usr/bin/perl -w use strict; my ($X,$Y,$M,$N,$x,$y,$n); use integer; # use integer math, like C macros do ($X,$Y) = (5,9); # change to test different column/row configurations. $M = $X * $Y; for $n ( 1 .. $M ) { $N = $M - $n; $x = $X - ($X*($N-1)*($N-1)-$M)/($M*$M); $y = ($n+$x-1) / $x; print " n (rows,cols) (capacity => n)\n" if $n == 1; printf "%3d: (%2d,%2d) (%3d >= %3d)? %s\n", $n, $x, $y, $x*$y, $n, ($x*$y >= $n ? "good" : '**BAD**'); } */ #define LASTPAGE(p) ((rl.cur_entries / names_per_page) > p ? 0 : 1 ) #define BC_NAMES_PER_LINE(p) (LASTPAGE(p) ? (max_names_per_line - (max_names_per_line*((names_per_page - (rl.cur_entries % names_per_page))-1)*((names_per_page - (rl.cur_entries % names_per_page))-1)-names_per_page)/(names_per_page*names_per_page)) : max_names_per_line ) #define BC_NAMES_PER_COL(p) (LASTPAGE(p) ? (((rl.cur_entries % names_per_page)+NAMES_PER_LINE(p)-1) / NAMES_PER_LINE(p)) : max_names_per_col) #define BC_PXY2N(p,x,y) ((p) * names_per_page + (x) * NAMES_PER_COL(p) + (y)) #define BC_N2P(n) ((n) / names_per_page) #define BC_N2X(n) (((n) % names_per_page) / NAMES_PER_COL(N2P(n))) #define BC_N2Y(n) (((n) % names_per_page) % NAMES_PER_COL(N2P(n))) #define BC_DX(p) NAMES_PER_COL(p) #define BC_DY 1 #define NAMES_PER_LINE(p) (req_order ? BC_NAMES_PER_LINE(p) : BR_NAMES_PER_LINE(p)) #define NAMES_PER_COL(p) (req_order ? BC_NAMES_PER_COL(p) : BR_NAMES_PER_COL(p) ) #define PXY2N(p,x,y) (req_order ? BC_PXY2N(p,x,y) : BR_PXY2N(p,x,y) ) #define N2P(n) (req_order ? BC_N2P(n) : BR_N2P(n) ) #define N2X(n) (req_order ? BC_N2X(n) : BR_N2X(n) ) #define N2Y(n) (req_order ? BC_N2Y(n) : BR_N2Y(n) ) #define DX(p) (req_order ? BC_DX(p) : BR_DX(p) ) #define DY (req_order ? BC_DY : BR_DY ) int common_prefix_len(req_list *rlp) { char * const p0 = rlp->entries[0]; int len = strlen(p0); for (int i = 0; len && i < rlp->cur_entries; i++) { char * const p1 = rlp->entries[i]; for ( ; len && strncasecmp(p0, p1, len); len--) ; } return len; } /* This is the printing function used by the requester. It prints the strings from the entries array existing in a certain page (a page contains (lines-1)*max_names_per_line items) with rl.max_entry_len maximum width. */ static void print_strings() { const int dx = rl.max_entry_len + 1 + (rl.suffix ? 1 : 0); set_attr(0); for(int row = 0; row < max_names_per_col; row++) { move_cursor(row, 0); clear_to_eol(); if (row < NAMES_PER_COL(page)) { for(int col = 0; col < NAMES_PER_LINE(page); col++) { if (PXY2N(page,col,row) < rl.cur_entries) { move_cursor(row, col * dx); const char * const p = rl.entries[PXY2N(page,col,row)]; if (rl.suffix) set_attr(p[strlen(p) - 1] == rl.suffix ? BOLD : 0); output_string(p, io_utf8); } } } } } static void normalize(int n) { const int p = page; if (n < 0 ) n = 0; if (n >= rl.cur_entries ) n = rl.cur_entries - 1; x = N2X(n); y = N2Y(n); page = N2P(n); if ( p != page ) print_strings(); } static void request_move_to_sol(void) { x = 0; } static void request_move_to_eol(void) { while (x < NAMES_PER_LINE(page) - 1 && PXY2N(page,x+1,y) < rl.cur_entries) { x++; } } static void request_move_to_sof(void) { normalize(0); } static void request_move_to_eof() { normalize(rl.cur_entries - 1); } static void request_toggle_seof(void) { if (x + y+page == 0) request_move_to_eof(); else request_move_to_sof(); } static void request_prev_page(void) { if (page == 0 ) normalize(PXY2N(page,0,0)); else normalize(PXY2N(page-1,x,y)); } static void request_next_page(void) { normalize(PXY2N(page+1,x,y)); } static void request_move_up(void) { normalize(PXY2N(page,x,y) - DY); } static void request_move_inc_up(void) { if (x == 0) { if (y == 0) request_move_to_sof(); else request_prev_page(); } else request_move_to_sol(); } static void request_move_down(void) { normalize(PXY2N(page,x,y) + DY); } void request_move_inc_down(void) { if (x == NAMES_PER_LINE(page) - 1) { if (y == NAMES_PER_COL(page) - 1) request_move_to_eof(); else request_next_page(); } else request_move_to_eol(); } static void request_move_left(void) { if ( x == 0 && y + page > 0 ) { request_move_up(); request_move_to_eol(); } else { normalize(PXY2N(page,x,y) - DX(page)); } } static void request_move_next(void) { normalize(PXY2N(page,x,y)+1); } static void request_move_previous(void) { normalize(PXY2N(page,x,y)-1); } static void request_move_right(void) { if (y < NAMES_PER_COL(page) - 1 && PXY2N(page,0,y+1) < rl.cur_entries && (x == NAMES_PER_LINE(page) - 1 || PXY2N(page,x+1,y) > rl.cur_entries -1) ) { request_move_to_sol(); request_move_down(); } else if (y == NAMES_PER_COL(page) - 1 && x == NAMES_PER_LINE(page) - 1 && PXY2N(page+1,0,0) < rl.cur_entries) { normalize(PXY2N(page+1,0,0)); } else if (PXY2N(page,x,y) + DX(page) < rl.cur_entries ) { normalize(PXY2N(page,x,y) + DX(page)); } } /* Reorder (i.e. swap) the current entry n with entry n+dir. dir should be either 1 or -1. */ static int request_reorder(const int dir) { if (! rl0->allow_reorder || rl.cur_entries < 2) return 0; const int n0 = PXY2N(page,x,y); const int n1 = (n0 + dir + rl.cur_entries ) % rl.cur_entries; /* Allows wrap around. */ char * const p0 = rl.entries[n0]; char * const p1 = rl.entries[n1]; int i0, i1, i; for (i=0, i0=-1, i1=-1; icur_entries && (i0<0 || i1<0); i++) { if (i0 < 0 && p0 == rl0->entries[i] ) { i0 = i; } if (i1 < 0 && p1 == rl0->entries[i] ) { i1 = i; } } i = rl0->orig_order[i0]; rl0->orig_order[i0] = rl0->orig_order[i1]; rl0->orig_order[i1] = i; rl.entries[n0] = p1; rl.entries[n1] = p0; rl0->entries[i0] = p1; rl0->entries[i1] = p0; page = -1; /* causes normalize() to call print_strings() */ normalize(n1); return 1; } /* Back up to the first entry with a common prefix, pulling in matching entries from the original req_list *rl0 in such a way as to preserve original order. */ static void fuzz_back() { const int orig_entries = rl.cur_entries; const char * const p0 = rl.entries[PXY2N(page,x,y)]; if (fuzz_len == 0 || orig_entries == rl0->cur_entries) return; int n1; while (rl.cur_entries == orig_entries) { fuzz_len = max(0,fuzz_len-1); int i; for (int j = n1 = i = 0; j < rl0->cur_entries; j++) { char * const p1 = rl0->entries[j]; if ( ! strncasecmp(p0, p1, fuzz_len) ) { if (p1 == p0) n1 = i; rl.entries[i++] = p1; } } rl.cur_entries = i; } page = -1; /* causes normalize() to call print_strings() */ normalize(n1); } /* given a localised_up_case character c, keep only entries that matches our current fuzz_len prefix plus this additional character. Note that relative order of rl.entries[] is preserved. */ static void fuzz_forward(const int c) { const char * const p0 = rl.entries[PXY2N(page,x,y)]; assert(fuzz_len >= 0); int i = 0, n1 = 0; for (int j = 0; j < rl.cur_entries; j++) { char * const p1 = rl.entries[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; } } if (i) { rl.cur_entries = i; fuzz_len = common_prefix_len(&rl); page = -1; /* causes normalize() to call print_strings() */ normalize(n1); } } /* The original master list of strings is described by *rlp0. We make a working copy described by rl, which has an allocated buffer large enough to hold all the original char pointers, but may at any time have fewer entries due to fuzzy matching. request_strings_init() sets up this copy, and request_strings_cleanup() cleans up the allocations. A small handful of static variables keep up with common prefix size, default entry index, etc. */ static int request_strings_init(req_list *rlp0) { rl.cur_entries = rlp0->cur_entries; rl.max_entry_len = rlp0->max_entry_len; rl.suffix = rlp0->suffix; if (!(rl.entries = calloc(rlp0->cur_entries, sizeof(char *)))) { return 0; } memcpy(rl.entries, rlp0->entries, rl.cur_entries * sizeof(char *)); rl0 = rlp0; fuzz_len = common_prefix_len(&rl); return rl.cur_entries; } static int request_strings_cleanup(int reordered) { int n = PXY2N(page,x,y); 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 (reordered) rl0->reordered = true; else rl0->reordered = false; return n; } /* Given a list of strings, let the user pick one. If _rl->suffix is not '\0', we 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 - 2 User selected string n with the TAB key. (Yes, it's kind of evil, but it's nothing compared to what request() does!) */ int request_strings(req_list *rlp0, int n) { assert(rlp0->cur_entries > 0); int ne_lines0 = 0, ne_columns0 = 0, reordered = 0; max_names_per_line = max_names_per_col = x = y = page = fuzz_len = 0; if ( ! request_strings_init(rlp0) ) return ERROR; const int dx = rl.max_entry_len + 1 + (rl.suffix ? 1 : 0); while(true) { if (ne_lines0 != ne_lines || ne_columns0 != ne_columns) { if (ne_lines0 && ne_columns0 ) n = PXY2N(page,x,y); if (!(max_names_per_line = ne_columns / dx)) max_names_per_line = 1; max_names_per_col = ne_lines - 1; names_per_page = max_names_per_line * max_names_per_col; ne_lines0 = ne_lines; ne_columns0 = ne_columns; page = N2P(n); x = N2X(n); y = N2Y(n); print_strings(); print_message(NULL); } n = PXY2N(page,x,y); assert(fuzz_len >= 0); fuzz_len = min(fuzz_len, strlen(rl.entries[n])); move_cursor(y, x * dx + fuzz_len); int c; input_class ic; do c = get_key_code(); while((ic = CHAR_CLASS(c)) == IGNORE || ic == INVALID); switch(ic) { 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(reordered); if (n >= rlp0->cur_entries) return ERROR; else return -n - 2; } break; case RETURN: n = request_strings_cleanup(reordered); 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 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 (x != 0) x = 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: reordered += request_reorder(1); break; case PREVDOC_A: reordered += request_reorder(-1); break; case CLOSEDOC_A: case ESCAPE_A: case QUIT_A: case SELECTDOC_A: request_strings_cleanup(reordered); return -1; } } break; default: break; } } } /* The completion function. Returns NULL if no file matches start_prefix, or the longest prefix common to all files extending start_prefix. */ 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, de->d_name)] = 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; } static void load_syntax_names(req_list *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(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 rl; DIR *d; if (req_list_init(&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(&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(&rl,d,false); closedir(d); } } req_list_finalize(&rl); p = NULL; int result; if (rl.cur_entries && (result = request_strings(&rl, 0)) != ERROR) { char * const q = rl.entries[result >= 0 ? result : -result - 2]; if (p = malloc(strlen(q)+3)) { strcpy(p,q); if (p[strlen(p)-1] == rl.suffix) p[strlen(p)-1] = '\0'; if (result < 0) { memmove(p + 1, p, strlen(p) + 1); p[0] = '\0'; } } } req_list_free(&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. Returns NULL on error or escaping, a pointer to the selected filename if RETURN is pressed, or a pointer to the selected filename (or directory) preceeded by a NUL 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; char * const dir_name = str_dup(filename); if (dir_name) { int result = 0; 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 rl; bool next_dir; char *result = NULL; do { next_dir = false; if (req_list_init(&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(&rl, de->d_name, is_dir)) break; } req_list_finalize(&rl); if (rl.cur_entries) { /* qsort(rl.entries, rl.cur_entries, sizeof(char *), filenamecmpp); */ const int t = request_strings(&rl, 0); if (t != ERROR) { char * const p = 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 (t < 0) { memmove(result + 1, result, strlen(result) + 1); result[0] = 0; } } } } closedir(d); } else alert(); req_list_free(&rl); } while(next_dir); chdir(cur_dir_name); 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(prompt, p ? p + 1 : default_name, false, COMPLETE_FILE, io_utf8)) return p; return NULL; } /* 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. */ int request_document(void) { int i = -1; req_list rl; buffer *b = (buffer *)buffers.head; if (b->b_node.next && req_list_init(&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(&rl, b->filename ? b->filename : UNNAMED_NAME, b->is_modified); b = (buffer *)b->b_node.next; i++; } rl.ignore_tab = true; req_list_finalize(&rl); print_message(info_msg[SELECT_DOC]); i = request_strings(&rl, cur_entry); reset_window(); draw_status_bar(); if (i >= 0 && rl.reordered) { /* We're going to cheat big time here. We have an array of pointers at rl.entries that's big enough to hold all the buffer pointers, and that's exactly what we're going to use it for now. */ b = (buffer *)buffers.head; for (int j = 0; b->b_node.next; j++ ) { rl.entries[j] = (char *)b; b = (buffer *)b->b_node.next; rem(b->b_node.prev); } /* Ack! We're removed all our buffers! */ for (int j = 0; j < rl.cur_entries; j++) { add_tail(&buffers, (node *)rl.entries[rl.orig_order[j]]); } } req_list_free(&rl); } return i; } /* The req_list functions below provide a simple mechanism suitable for building request lists for directory entries, syntax recognizers, 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) #if 0 /* The req_list_del() function works just fine; we just don't need it yet/any more. */ /* Delete the nth string from the given request list. This will work regardelss of whether the req_list has been finalized. */ int req_list_del(req_list * const rl, int nth) { if (nth < 0 || nth >= rl->cur_entries ) return ERROR; const char * const str = rl->entries[nth]; const int len0 = strlen(str); int len = len0; len += str[len + 1] ? 3 : 2; /* 'a b c \0 Suffix \0' or 'a b c \0 \0' or 'a b c Suffix \0 \0' */ memmove(str, str + len, sizeof(char)*(rl->cur_chars - ((str + len) - rl->chars))); for(int i = 0; i < rl->cur_entries; i++) if (rl->entries[i] > str ) rl->entries[i] -= len; rl->cur_chars -= len; memmove(&rl->entries[nth], &rl->entries[nth+1], sizeof(char *)*(rl->cur_entries - nth)); rl->cur_entries--; /* did we just delete longest string? */ if (len0 == rl->max_entry_len) { for (int i = rl->max_entry_len = 0; i < rl->cur_entries; i++) { if ((len = strlen(rl->entries[i])) > rl->max_entry_len) rl->max_entry_len = len; } } return rl->cur_entries; } #endif void req_list_free(req_list * const rl) { if (rl->entries) free(rl->entries); rl->entries = NULL; if (rl->chars) free(rl->chars); rl->chars = NULL; rl->cur_entries = rl->alloc_entries = rl->max_entry_len = 0; rl->cur_chars = rl->alloc_chars = 0; } /* Initialize a request list. A comparison function 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. If a suffix character is provided, it can optionally be added to individual entries as they are added, in which case req_list_finalize() should be called before the entries are used in a request_strings() call. */ 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) { rl->cmpfnc = cmpfnc; rl->allow_dupes = allow_dupes; rl->allow_reorder = allow_reorder; rl->ignore_tab = false; rl->suffix = suffix; rl->cur_entries = rl->alloc_entries = rl->max_entry_len = 0; rl->cur_chars = rl->alloc_chars = 0; if (rl->entries = malloc(sizeof(char *) * DEF_ENTRIES_ALLOC_SIZE)) { if (rl->chars = malloc(sizeof(char) * DEF_CHARS_ALLOC_SIZE)) { rl->alloc_entries = DEF_ENTRIES_ALLOC_SIZE; rl->alloc_chars = DEF_CHARS_ALLOC_SIZE; return OK; } free(rl->entries); rl->entries = 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 suffexes. Finalizing the req_list effectively shifts the suffixes left, exchanging them for the preceeding '\0'. After this operation, all the strings will be just 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 orig_order array if allow_reorder is true. If the array cannot be allocated, allow_reorder is simply reset to false rather than returning an error. */ void req_list_finalize(req_list * const rl) { for (int i = 0; i < rl->cur_entries; i++) { const int len = strlen(rl->entries[i]); *(rl->entries[i]+len) = *(rl->entries[i]+len+1); *(rl->entries[i]+len+1) = '\0'; } if (rl->allow_reorder ) { if ( rl->orig_order = malloc(sizeof(int) * rl->cur_entries)) { for (int i = 0; i < rl->cur_entries; i++) rl->orig_order[i] = i; } else rl->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 not comparison function), then the conflicting entry is returned. */ char *req_list_add(req_list * const rl, char * const str, const int suffix) { const int len = strlen(str); const int lentot = len + ((rl->suffix && suffix) ? 3 : 2); /* 'a b c \0 Suffix \0' or 'a b c \0 \0' */ int ins; if (rl->cmpfnc) { /* implies the entries are sorted */ int l = 0, m = 0; int r = rl->cur_entries - 1; while(l <= r) { m = (r + l)/2; const int cmp = (*rl->cmpfnc)(str, rl->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 (!rl->allow_dupes) return rl->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 = rl->cur_entries; } } else {/* not ordered */ ins = rl->cur_entries; /* append to end */ if (!rl->allow_dupes) { for(int i = 0; i < rl->cur_entries; i++) if(!strcmp(rl->entries[i], str)) return rl->entries[i]; } } if (len > rl->max_entry_len) rl->max_entry_len = len; /* make enough space to store the new string */ if (rl->cur_chars + lentot > rl->alloc_chars) { char * p0 = rl->chars; char * p1; p1 = realloc(rl->chars, sizeof(char) * (rl->alloc_chars * 2 + lentot)); if (!p1) return NULL; rl->alloc_chars = rl->alloc_chars * 2 + lentot; rl->chars = p1; /* all the strings just moved from *p0 to *p1, so adjust accordingly */ for (int i = 0; i < rl->cur_entries; i++) rl->entries[i] += ( p1 - p0 ); } /* make enough slots to hold the string pointer */ if (rl->cur_entries >= rl->alloc_entries) { char **t; t = realloc(rl->entries, sizeof(char *) * (rl->alloc_entries * 2 + 1)); if (!t) return NULL; rl->alloc_entries = rl->alloc_entries * 2 + 1; rl->entries = t; } char * const newstr = &rl->chars[rl->cur_chars]; char * p = strcpy(newstr,str)+len+1; if (rl->suffix && suffix) *p++ = rl->suffix; *p = '\0'; rl->cur_chars += lentot; if (ins < rl->cur_entries) memmove(&rl->entries[ins+1], &rl->entries[ins], sizeof(char *)*(rl->cur_entries - ins)); rl->entries[ins] = newstr; rl->cur_entries++; return newstr; } ne-3.0.1/src/search.c0000664000175200017510000004011712537653642013176 0ustar vignaprof/* Search/replace functions (with and without regular expressions). Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2015 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" /* 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 is true (it should always be the first time the string is searched for). */ static unsigned int d[256]; /* This macro upper cases a character or not, depending on the boolean sense_case. It is used in find(). Note that the argument *MUST* be unsigned. */ #define CONV(c) (sense_case ? c : up_case[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 it is false, the string is not recompiled. Please check to set b->find_string_changed 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 recompile_string; if (!pattern) { pattern = b->find_string; recompile_string = b->find_string_changed || b->last_was_regexp; } else recompile_string = true; 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((unsigned char)pattern[i])] = m - i-1; b->find_string_changed = 0; } char * p = ld->line + b->cur_pos + m - 1 + (skip_first ? 1 : 0); const unsigned char first_char = CONV((unsigned char)pattern[m - 1]); while(y < b->num_lines && !stop) { assert(ld->ld_node.next != NULL); if (ld->line_len >= m) { while((p - ld->line) < ld->line_len) { const unsigned char c = CONV((unsigned char)*p); if (c != first_char) p += d[c]; else { int i; for (i = 1; i < m; i++) if (CONV((unsigned char)*(p - i)) != CONV((unsigned char)pattern[m - i-1])) { p += d[c]; break; } if (i == m) { goto_line(b, y); goto_pos(b, (p - ld->line) - m + 1); return OK; } } } } ld = (line_desc *)ld->ld_node.next; if (ld->ld_node.next) p = ld->line + m-1; y++; } } else { if (recompile_string) { for(int i = m - 1; i > 0; i--) d[CONV((unsigned char)pattern[i])] = i; b->find_string_changed = 0; } 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((unsigned char)pattern[0]); while(y >= 0 && !stop) { assert(ld->ld_node.prev != NULL); if (ld->line_len >= m) { while((p - ld->line) >= 0) { const unsigned char c = CONV((unsigned char)*p); if (c != first_char) p -= d[c]; else { int i; for (i = 1; i < m; i++) if (CONV((unsigned char)*(p + i)) != CONV((unsigned char)pattern[i])) { p -= d[c]; break; } if (i == m) { goto_line(b, y); goto_pos(b, p - ld->line); return OK; } } } } ld = (line_desc *)ld->ld_node.prev; if (ld->ld_node.prev) p = ld->line + ld->line_len - m; 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 stard/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) { 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 || !b->last_was_regexp; } else recompile_string = true; 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 = 0; 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); while(y < b->num_lines && !stop) { 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(b, y); goto_pos(b, pos); return OK; } ld = (line_desc *)ld->ld_node.next; start_pos = 0; y++; } } else { int64_t start_pos = b->cur_pos + (skip_first ? -1 : 0); while(y >= 0 && !stop) { 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(b, y); goto_pos(b, pos); return OK; } ld = (line_desc *)ld->ld_node.prev; if (ld->ld_node.prev) start_pos = ld->line_len; y--; } } return stop ? STOPPED : NOT_FOUND; } /* 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_NREGS && 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.0.1/src/signals.c0000664000175200017510000001023212522713153013350 0ustar vignaprof/* Signal handling setup and code. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2015 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(); reset_window(); } /* 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.0.1/src/streams.c0000664000175200017510000001706112525431516013400 0ustar vignaprof/* Stream handling functions. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2015 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 successfull, 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; } /* 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 continously 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 fh = open(name, READ_FLAGS); cs = load_stream_from_fh(cs, fh, preserve_cr, binary); if (fh >= 0) close(fh); return cs; } char_stream *load_stream_from_fh(char_stream *cs, const int fh, const bool preserve_cr, const bool binary) { if (fh < 0) return NULL; char terminators[] = { 0x0d, 0x0a }; if (preserve_cr) terminators[0] = 0; assert_char_stream(cs); off_t len = lseek(fh, 0, SEEK_END); if (len < 0) return NULL; lseek(fh, 0, SEEK_SET); if (!(cs = realloc_char_stream(cs, len))) return NULL; if (read_safely(fh, 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 fh = open(name, WRITE_FLAGS, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); if (fh >= 0) { const int error = save_stream_to_fh(cs, fh, CRLF, binary); close(fh); return error; } return CANT_OPEN_FILE; } int save_stream_to_fh(const char_stream *const cs, const int fh, const bool CRLF, const bool binary) { if (!cs) return ERROR; assert_char_stream(cs); if (binary) { if (write(fh, 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(fh, cs->stream + pos, len) < len) return ERROR_WHILE_WRITING; if (pos + len < cs->len) { if (CRLF && write(fh, "\r", 1) < 1) return ERROR_WHILE_WRITING; if (write(fh, "\n", 1) < 1) return ERROR_WHILE_WRITING; } pos += len + 1; } return OK; } ne-3.0.1/src/support.c0000664000175200017510000005122512540112470013426 0ustar vignaprof/* Miscellaneous support functions. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2015 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 "cm.h" #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; } /* is_migrated() tells whether the specified file is currently migrated. A migrated file is one which is not actually on-line but is out on tape or other media. In general we don't want to try to load a migrated file in an interactive program because the delay (waiting for a tape mount, etc.) is most annoying and frustrating. On systems which don't support hierarchical storage, non-zero length files always take up at least one disk block, so this should never be an issue for them. */ #if defined _CONVEX_SOURCE /* The Convex system uses CDVM, and can call cvxstat to see if a file is migrated. */ #include 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. */ int64_t read_safely(const int fh, void * const buf, const int64_t len) { for(int64_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 int64_t t = read(fh, (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: 1 if file's non-zero modification time differs from the buffer's non-zero mtime, 0 everything else, including if the files mtime couldn't be checked (possibly no file or couldn't stat), buffer's stored mtime is 0, no usable name. 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 && b->mtime && 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; } /* 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, const char * const t) { int i; for(i = 0; s[i] && t[i] && s[i] == t[i]; i++); return i; } /* Returns true is 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(); } /* 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); } /* 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. */ int64_t 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. */ int64_t 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. */ int64_t calc_char_len(const line_desc * const ld, const encoding_type encoding) { int64_t len = 0; for(int64_t pos = 0; pos < ld->line_len; pos = next_pos(ld->line, pos, encoding), len++); return len; } /* Given a column, the index of the character "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. */ int64_t 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; } /* Returns true if the specified character is invariant on the left edge of re-wrapped paragraphs */ bool isparaspot(const int c) { char *spots = "%/*#>\t "; char *p = spots; while (*p) { if (*p++ == c) return true; } return false; } /* Returns true if the specified character is an US-ASCII whitespace character. */ bool isasciispace(const int c) { return c < 0x80 && isspace(c); } /* Returns true if the specified character is an US-ASCII alphabetic character. */ bool isasciialpha(const int c) { return c < 0x80 && isalpha(c); } /* Returns true if the specified block of text is US-ASCII. */ bool 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. */ int 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. */ int asciitolower(const int c) { return c < 0x80 ? tolower(c) : c; } /* 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 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. */ int64_t 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. */ int64_t 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 ISO 10646 character represented by the sequence of bytes starting at s, using the provided encoding. */ int 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 ISO 10646 character represented by the sequence of bytes starting at s, using the provided encoding. */ int get_char_width(const char * const s, const encoding_type encoding) { assert(s != NULL); return encoding == ENC_UTF8 ? output_width(utf8char(s)) : output_width(*(unsigned char *)s); } /* Returns the width of the first len characters of s, using the provided encoding. If s is NULL, returns len. */ int 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. */ bool 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. */ bool 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 '_'. */ bool ne_isword(const int c, const int encoding) { return c == '_' || !(ne_isspace(c, encoding) || ne_ispunct(c, encoding)); } /* 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; } ne-3.0.1/src/syn_hash.c0000664000175200017510000000644212517166512013541 0ustar vignaprof/* Syntax highlighting from Joe's Own Editor: Simple hash tables. Copyright (C) 1992 Joseph H. Allen Copyright (C) 2009-2015 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.0.1/src/syn_hash.h0000664000175200017510000000306212517166512013541 0ustar vignaprof/* Syntax highlighting from Joe's Own Editor: Simple hash tables. Copyright (C) 1992 Joseph H. Allen Copyright (C) 2009-2015 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.0.1/src/syn_regex.c0000664000175200017510000002150112517166512013721 0ustar vignaprof/* Syntax highlighting from Joe's Own Editor: Regular expression subroutines. Copyright (C) 1992 Joseph H. Allen Copyright (C) 2009-2015 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.0.1/src/syn_regex.h0000664000175200017510000000204512517166512013730 0ustar vignaprof/* Syntax highlighting from Joe's Own Editor: Regular expression subroutines. Copyright (C) 1992 Joseph H. Allen Copyright (C) 2009-2015 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.0.1/src/syn_types.h0000664000175200017510000001256312517166512013770 0ustar vignaprof/* Syntax highlighting from Joe's Own Editor: Types Copyright (C) 2004 Joseph H. Allen Copyright (C) 2009-2015 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.0.1/src/syn_utf8.c0000664000175200017510000001022312517166512013474 0ustar vignaprof/* Syntax highlighting from Joe's Own Editor: UTF-8 utilities. Copyright (C) 2004 Joseph H. Allen Copyright (C) 2009-2015 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.0.1/src/syn_utf8.h0000664000175200017510000000377612517166512013520 0ustar vignaprof/* Syntax highlighting from Joe's Own Editor: UTF-8 utilities. Copyright (C) 2004 Joseph H. Allen Copyright (C) 2009-2015 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.0.1/src/syn_utils.c0000664000175200017510000002001412517166512013745 0ustar vignaprof/* Syntax highlighting from Joe's Own Editor: Various utilities. Copyright (C) 1992 Joseph H. Allen Copyright (C) 2001 Marek 'Marx' Grac Copyright (C) 2009-2015 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.0.1/src/syn_utils.h0000664000175200017510000001112112517166512013751 0ustar vignaprof/* Syntax highlighting from Joe's Own Editor: Various utilities. Copyright (C) 1992 Joseph H. Allen Copyright (C) 2001 Marek 'Marx' Grac Copyright (C) 2009-2015 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.0.1/src/syntax.c0000664000175200017510000006362312517225453013257 0ustar vignaprof/* Syntax highlighting from Joe's Own Editor: Syntax highlighting DFA interpreter. Copyright (C) 2004 Joseph H. Allen Copyright (C) 2009-2015 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" #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; HIGHLIGHT_STATE parse(struct high_syntax * const syntax, line_desc * const ld, HIGHLIGHT_STATE h_state, const bool utf8) { struct high_frame *stack = h_state.stack; struct high_state *h = (stack ? stack->syntax : syntax)->states[h_state.state]; /* 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; const unsigned char *p = (const unsigned char *)ld->line; unsigned char *q = (unsigned char *)(ld->line + ld->line_len); 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 itteration, process the virtual '\n' character. */ struct high_cmd *cmd, *kw_cmd; 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 { /* 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); } 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); } } 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.0.1/src/syntax.h0000664000175200017510000001022512517501413013243 0ustar vignaprof/* Syntax highlighting from Joe's Own Editor: Syntax highlighting DFA interpreter. Copyright (C) 2004 Joseph H. Allen Copyright (C) 2009-2015 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.0.1/src/term.c0000664000175200017510000007600312517225453012674 0ustar vignaprof/* 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-2015 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 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 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 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 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 */ 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 uint32_t curr_attr; /* The current video attributes. */ /* 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 */ #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 = tparm(ne_set_foreground , joe2color(attr >> FG_SHIFT)); OUTPUT1(buf); } if (attr & BG_NOT_DEFAULT) { const char * const buf = tparm(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 = tparm(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 = tparm(ne_set_background , joe2color(attr >> BG_SHIFT)); OUTPUT1(buf); } } } curr_attr = attr; } #endif static void turn_off_standout(void) { OUTPUT1(ne_exit_standout_mode); /* We 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(); } } /* 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; } /* 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 = tparm (ne_change_scroll_region, start, stop); else buf = tparm (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; } /* Prepares the terminal for interactive I/O. It initializes the terminal, 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_enter_ca_mode); OUTPUT1_IF(ne_keypad_xmit); if (ne_has_meta_key) OUTPUT1_IF(ne_meta_on); losecursor(); } /* Puts again the terminal in its normal state. */ void reset_terminal_modes (void) { OUTPUT1_IF(ne_exit_attribute_mode); OUTPUT1_IF(ne_exit_alt_charset_mode); OUTPUT1_IF(ne_exit_standout_mode); 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(); /* 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 && 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(); /* 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 = tparm (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 && 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(); 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 = tparm(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 = tparm(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(); 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 = tparm(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 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(0, 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 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); cursor_on_off_ok = (ne_cursor_invisible && ne_cursor_normal); color_ok = (ne_set_foreground && ne_set_background); } ne-3.0.1/src/term.h0000664000175200017510000000340412541241677012677 0ustar vignaprof/* Function prototypes for term.c Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2015 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 set_terminal_modes(void); void reset_terminal_modes(void); void set_terminal_window(int size); void standout_on(void); void standout_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.0.1/src/termcap.c0000664000175200017510000004125512517166512013361 0ustar vignaprof/* 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.0.1/src/termcap.h0000664000175200017510000000315012517166512013356 0ustar vignaprof/* 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.0.1/src/termchar.h0000664000175200017510000001312312517166512013531 0ustar vignaprof/* extern's of flags describing terminal's characteristics. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2015 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 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< #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.0.1/src/trace.c0000664000175200017510000000162412534147341013016 0ustar vignaprof#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.0.1/src/undo.c0000664000175200017510000002052412517166512012667 0ustar vignaprof/* Undo/redo system management functions. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2015 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" /* 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(b, b->undo.steps[b->undo.cur_step].line); goto_pos(b, 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_and_lines(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_and_lines(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(b, b->undo.steps[b->undo.cur_step].line); goto_pos(b, 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_and_lines(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_and_lines(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.0.1/src/utf8.c0000664000175200017510000000706112517225453012611 0ustar vignaprof/* UTF-8 support. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2015 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) { 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) { 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) { #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) { #ifdef NOWCHAR return c < 0x80 ? tolower(c) : c; #else return towlower(c); #endif } ne-3.0.1/src/utf8.h0000664000175200017510000000276012517166512012617 0ustar vignaprof/* UTF-8 support prototypes. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2015 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 ne-3.0.1/src/version.h0000664000175200017510000000221312542017435013404 0ustar vignaprof/* This file was automatically generated by ./version.pl. */ /* String definitions for version and 'About...' messages. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2015 Todd M. Lewis and Sebastiano Vigna This file is part of ne, the nice editor. 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 331, Boston, MA 02111-1307, USA. */ #define DATE "(2015-06-22)" #define VERSION "3.0.1" #define PROGRAM_NAME "ne, the nice editor" #define ABOUT_MSG PROGRAM_NAME " " VERSION ". " DATE #define VERSION_STRING "@(#)"ABOUT_MSG ne-3.0.1/src/ext.c.in0000664000175200017510000000300112517166512013116 0ustar vignaprof/* Extern's for names and abbreviations of all the commands. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2015 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.0.1/src/copyright++.pl0000775000175200017510000000427212517166512014256 0ustar vignaprof#!/usr/bin/perl -w use strict; $| = 1; # This program looks for Copyright notices in .c, .in, .h, .pl, and .jsf files # in the current directory and updates them for the new year. # Give it two parameters: from_year and to_year, each of which # should be four digits. # It changes files with lines containing text like these: # Copyright (C) ####-$from_year # Copyright (C) $from_year # to these: # Copyright (C) ####-$to_year # Copyright (C) $from_year-$to_year # # It should work on itself too! Behold: # Copyright (C) 2011-2015 Todd M. Lewis and Sebastiano Vigna # Ciao! use Getopt::Long; my ($from_year, $to_year) = (shift,shift); if ( @ARGV || !defined $from_year || !defined $to_year || $from_year !~ m/^\d\d\d\d$/ || $to_year !~ m/^\d\d\d\d$/ ) { print qq[Usage: $0 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.0.1/src/info2src.pl0000775000175200017510000004232212517166512013643 0ustar vignaprof#!/usr/bin/perl -w use strict; $| = 1; # Todd_Lewis@unc.edu # # 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-2015 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; $line =~ s<\*note *(\s*)(.+?)::> <$1`$2'>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 occuring 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. */ const char PLAYONCE_ABBREV[] = "PL 1"; const char MIDDLEVIEW_ABBREV[] = "AV M"; const char SHIFTLEFT_ABBREV[] = "SH <"; /* This is the NULL-terminated, ordered list of names, useful for help etc. */ const char * const command_names[ACTION_COUNT+1] = { $body_names NULL }; _EOT_ } ################################################################## ## N A M E S _ H ################################################################## sub head_names_h { print NAMES_H <<"_EOT_"; /* Extern's for names and abbreviations of all the commands. $copyright */ _EOT_ } sub body_names_h { my ($uccmd) = @_; my $cmd = $commands{$uccmd}->{"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. */ extern const char PLAYONCE_ABBREV[]; extern const char MIDDLEVIEW_ABBREV[]; extern const char SHIFTLEFT_ABBREV[]; /* 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.0.1/macros/DeleteSOL0000664000175200017510000000162412517166512013756 0ustar vignaprof# 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.0.1/macros/aspell0000664000175200017510000000107512517166512013456 0ustar vignaprof# 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 AtomicUndo - ne-3.0.1/syntax/4gl.jsf0000664000175200017510000000660012517166512013506 0ustar vignaprof# 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.0.1/syntax/ada.jsf0000664000175200017510000000477612517166512013561 0ustar vignaprof# 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.0.1/syntax/ant.jsf0000664000175200017510000001222512517166512013602 0ustar vignaprof# 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.0.1/syntax/htmlerb.jsf0000664000175200017510000000032112517166512014447 0ustar vignaprof# 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.0.1/syntax/ini.jsf0000664000175200017510000000226112517166512013576 0ustar vignaprof# 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.0.1/syntax/iptables.jsf0000664000175200017510000000366212517166512014630 0ustar vignaprof# 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.0.1/syntax/java.jsf0000664000175200017510000000770312517166512013746 0ustar vignaprof# 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 "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.0.1/syntax/joerc.jsf0000664000175200017510000000323612517166512014124 0ustar vignaprof# 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.0.1/syntax/js.jsf0000664000175200017510000002463212517166512013441 0ustar vignaprof# 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.0.1/syntax/jsf.jsf0000664000175200017510000005032212517166512013602 0ustar vignaprof# 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#\n" comment_or_bad 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 "\\" 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 # 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. :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 "\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.0.1/syntax/jsf_check.jsf0000664000175200017510000000011112517166512014726 0ustar vignaprof# See jsf.jsf =Idle :initial Idle * initial noeat call=jsf(CHECKING) ne-3.0.1/syntax/json.jsf0000664000175200017510000000672312517166512013777 0ustar vignaprof# 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 "}" 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 "]" 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 "." decimalpoint "eE" exponentpart :decimalpoint Number * end "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.0.1/syntax/lisp.jsf0000664000175200017510000000055412517166512013771 0ustar vignaprof# 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.0.1/syntax/lua.jsf0000664000175200017510000001221712517166512013602 0ustar vignaprof# 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.0.1/syntax/m4.jsf0000664000175200017510000000343712517166512013345 0ustar vignaprof# 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.0.1/syntax/mail.jsf0000664000175200017510000000545212517166512013746 0ustar vignaprof# 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.0.1/syntax/mason.jsf0000664000175200017510000000020512517166512014130 0ustar vignaprof# 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.0.1/syntax/matlab.jsf0000664000175200017510000000221712517166512014260 0ustar vignaprof# 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.0.1/syntax/md.jsf0000664000175200017510000000357012517166512013423 0ustar vignaprof# 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 :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 ne-3.0.1/syntax/ocaml.jsf0000664000175200017510000002205212517166512014112 0ustar vignaprof# 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: [